feat: add EnvironmentService, AppService, DeploymentService
- EnvironmentService: CRUD with slug uniqueness, default env protection - AppService: CRUD, JAR upload with SHA-256 checksumming - DeploymentService: create, promote, status transitions Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,79 @@
|
||||
package com.cameleer3.server.core.runtime;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.security.MessageDigest;
|
||||
import java.util.HexFormat;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
public class AppService {
|
||||
private static final Logger log = LoggerFactory.getLogger(AppService.class);
|
||||
|
||||
private final AppRepository appRepo;
|
||||
private final AppVersionRepository versionRepo;
|
||||
private final String jarStoragePath;
|
||||
|
||||
public AppService(AppRepository appRepo, AppVersionRepository versionRepo, String jarStoragePath) {
|
||||
this.appRepo = appRepo;
|
||||
this.versionRepo = versionRepo;
|
||||
this.jarStoragePath = jarStoragePath;
|
||||
}
|
||||
|
||||
public List<App> listByEnvironment(UUID environmentId) { return appRepo.findByEnvironmentId(environmentId); }
|
||||
public App getById(UUID id) { return appRepo.findById(id).orElseThrow(() -> new IllegalArgumentException("App not found: " + id)); }
|
||||
public List<AppVersion> listVersions(UUID appId) { return versionRepo.findByAppId(appId); }
|
||||
|
||||
public UUID createApp(UUID environmentId, String slug, String displayName) {
|
||||
if (appRepo.findByEnvironmentIdAndSlug(environmentId, slug).isPresent()) {
|
||||
throw new IllegalArgumentException("App with slug '" + slug + "' already exists in this environment");
|
||||
}
|
||||
return appRepo.create(environmentId, slug, displayName);
|
||||
}
|
||||
|
||||
public AppVersion uploadJar(UUID appId, String filename, InputStream jarData, long size) throws IOException {
|
||||
getById(appId); // verify app exists
|
||||
int nextVersion = versionRepo.findMaxVersion(appId) + 1;
|
||||
|
||||
// Store JAR: {jarStoragePath}/{appId}/v{version}/app.jar
|
||||
Path versionDir = Path.of(jarStoragePath, appId.toString(), "v" + nextVersion);
|
||||
Files.createDirectories(versionDir);
|
||||
Path jarFile = versionDir.resolve("app.jar");
|
||||
|
||||
MessageDigest digest;
|
||||
try { digest = MessageDigest.getInstance("SHA-256"); }
|
||||
catch (Exception e) { throw new RuntimeException(e); }
|
||||
|
||||
try (InputStream in = jarData) {
|
||||
byte[] buffer = new byte[8192];
|
||||
int bytesRead;
|
||||
try (var out = Files.newOutputStream(jarFile)) {
|
||||
while ((bytesRead = in.read(buffer)) != -1) {
|
||||
out.write(buffer, 0, bytesRead);
|
||||
digest.update(buffer, 0, bytesRead);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
String checksum = HexFormat.of().formatHex(digest.digest());
|
||||
UUID versionId = versionRepo.create(appId, nextVersion, jarFile.toString(), checksum, filename, size);
|
||||
|
||||
log.info("Uploaded JAR for app {}: version={}, size={}, sha256={}", appId, nextVersion, size, checksum);
|
||||
return versionRepo.findById(versionId).orElseThrow();
|
||||
}
|
||||
|
||||
public String resolveJarPath(UUID appVersionId) {
|
||||
AppVersion version = versionRepo.findById(appVersionId)
|
||||
.orElseThrow(() -> new IllegalArgumentException("AppVersion not found: " + appVersionId));
|
||||
return version.jarPath();
|
||||
}
|
||||
|
||||
public void deleteApp(UUID id) {
|
||||
appRepo.delete(id);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
package com.cameleer3.server.core.runtime;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
public class DeploymentService {
|
||||
private static final Logger log = LoggerFactory.getLogger(DeploymentService.class);
|
||||
|
||||
private final DeploymentRepository deployRepo;
|
||||
private final AppService appService;
|
||||
private final EnvironmentService envService;
|
||||
|
||||
public DeploymentService(DeploymentRepository deployRepo, AppService appService, EnvironmentService envService) {
|
||||
this.deployRepo = deployRepo;
|
||||
this.appService = appService;
|
||||
this.envService = envService;
|
||||
}
|
||||
|
||||
public List<Deployment> listByApp(UUID appId) { return deployRepo.findByAppId(appId); }
|
||||
public Deployment getById(UUID id) { return deployRepo.findById(id).orElseThrow(() -> new IllegalArgumentException("Deployment not found: " + id)); }
|
||||
|
||||
/** Create a deployment record. Actual container start is handled by DeploymentExecutor (async). */
|
||||
public Deployment createDeployment(UUID appId, UUID appVersionId, UUID environmentId) {
|
||||
App app = appService.getById(appId);
|
||||
Environment env = envService.getById(environmentId);
|
||||
String containerName = env.slug() + "-" + app.slug();
|
||||
|
||||
UUID deploymentId = deployRepo.create(appId, appVersionId, environmentId, containerName);
|
||||
return deployRepo.findById(deploymentId).orElseThrow();
|
||||
}
|
||||
|
||||
/** Promote: deploy the same app version to a different environment. */
|
||||
public Deployment promote(UUID appId, UUID appVersionId, UUID targetEnvironmentId) {
|
||||
return createDeployment(appId, appVersionId, targetEnvironmentId);
|
||||
}
|
||||
|
||||
public void markRunning(UUID deploymentId, String containerId) {
|
||||
deployRepo.updateStatus(deploymentId, DeploymentStatus.RUNNING, containerId, null);
|
||||
deployRepo.markDeployed(deploymentId);
|
||||
}
|
||||
|
||||
public void markFailed(UUID deploymentId, String errorMessage) {
|
||||
deployRepo.updateStatus(deploymentId, DeploymentStatus.FAILED, null, errorMessage);
|
||||
}
|
||||
|
||||
public void markStopped(UUID deploymentId) {
|
||||
deployRepo.updateStatus(deploymentId, DeploymentStatus.STOPPED, null, null);
|
||||
deployRepo.markStopped(deploymentId);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package com.cameleer3.server.core.runtime;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
public class EnvironmentService {
|
||||
private final EnvironmentRepository repo;
|
||||
|
||||
public EnvironmentService(EnvironmentRepository repo) {
|
||||
this.repo = repo;
|
||||
}
|
||||
|
||||
public List<Environment> listAll() { return repo.findAll(); }
|
||||
|
||||
public Environment getById(UUID id) {
|
||||
return repo.findById(id).orElseThrow(() -> new IllegalArgumentException("Environment not found: " + id));
|
||||
}
|
||||
|
||||
public Environment getBySlug(String slug) {
|
||||
return repo.findBySlug(slug).orElseThrow(() -> new IllegalArgumentException("Environment not found: " + slug));
|
||||
}
|
||||
|
||||
public UUID create(String slug, String displayName) {
|
||||
if (repo.findBySlug(slug).isPresent()) {
|
||||
throw new IllegalArgumentException("Environment with slug '" + slug + "' already exists");
|
||||
}
|
||||
return repo.create(slug, displayName);
|
||||
}
|
||||
|
||||
public void delete(UUID id) {
|
||||
Environment env = getById(id);
|
||||
if ("default".equals(env.slug())) {
|
||||
throw new IllegalArgumentException("Cannot delete the default environment");
|
||||
}
|
||||
repo.delete(id);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user