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