feat: build Docker entrypoint per runtime type with custom args support
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -104,6 +104,28 @@ public class DeploymentExecutor {
|
|||||||
updateStage(deployment.id(), DeployStage.PRE_FLIGHT);
|
updateStage(deployment.id(), DeployStage.PRE_FLIGHT);
|
||||||
preFlightChecks(jarPath, config);
|
preFlightChecks(jarPath, config);
|
||||||
|
|
||||||
|
// Resolve runtime type
|
||||||
|
String resolvedRuntimeType = config.runtimeType();
|
||||||
|
String mainClass = null;
|
||||||
|
if ("auto".equalsIgnoreCase(resolvedRuntimeType)) {
|
||||||
|
AppVersion appVersion = appService.getVersion(deployment.appVersionId());
|
||||||
|
if (appVersion.detectedRuntimeType() == null) {
|
||||||
|
throw new IllegalStateException(
|
||||||
|
"Could not detect runtime type for JAR '" + appVersion.jarFilename() +
|
||||||
|
"'. Set runtimeType explicitly in app configuration.");
|
||||||
|
}
|
||||||
|
resolvedRuntimeType = appVersion.detectedRuntimeType();
|
||||||
|
mainClass = appVersion.detectedMainClass();
|
||||||
|
} else if ("plain-java".equals(resolvedRuntimeType)) {
|
||||||
|
AppVersion appVersion = appService.getVersion(deployment.appVersionId());
|
||||||
|
mainClass = appVersion.detectedMainClass();
|
||||||
|
if (mainClass == null) {
|
||||||
|
throw new IllegalStateException(
|
||||||
|
"Runtime type 'plain-java' requires a Main-Class in the JAR manifest, " +
|
||||||
|
"but none was detected for '" + appVersion.jarFilename() + "'.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// === PULL IMAGE ===
|
// === PULL IMAGE ===
|
||||||
updateStage(deployment.id(), DeployStage.PULL_IMAGE);
|
updateStage(deployment.id(), DeployStage.PULL_IMAGE);
|
||||||
// Docker pulls on create if not present locally
|
// Docker pulls on create if not present locally
|
||||||
@@ -147,7 +169,8 @@ public class DeploymentExecutor {
|
|||||||
config.memoryLimitBytes(), config.memoryReserveBytes(),
|
config.memoryLimitBytes(), config.memoryReserveBytes(),
|
||||||
config.dockerCpuShares(), config.dockerCpuQuota(),
|
config.dockerCpuShares(), config.dockerCpuQuota(),
|
||||||
config.exposedPorts(), agentHealthPort,
|
config.exposedPorts(), agentHealthPort,
|
||||||
"on-failure", 3
|
"on-failure", 3,
|
||||||
|
resolvedRuntimeType, config.customArgs(), mainClass
|
||||||
);
|
);
|
||||||
|
|
||||||
String containerId = orchestrator.startContainer(request);
|
String containerId = orchestrator.startContainer(request);
|
||||||
@@ -277,6 +300,8 @@ public class DeploymentExecutor {
|
|||||||
envVars.put("CAMELEER_AGENT_EXPORT_ENDPOINT", config.serverUrl());
|
envVars.put("CAMELEER_AGENT_EXPORT_ENDPOINT", config.serverUrl());
|
||||||
envVars.put("CAMELEER_AGENT_ROUTECONTROL_ENABLED", String.valueOf(config.routeControlEnabled()));
|
envVars.put("CAMELEER_AGENT_ROUTECONTROL_ENABLED", String.valueOf(config.routeControlEnabled()));
|
||||||
envVars.put("CAMELEER_AGENT_REPLAY_ENABLED", String.valueOf(config.replayEnabled()));
|
envVars.put("CAMELEER_AGENT_REPLAY_ENABLED", String.valueOf(config.replayEnabled()));
|
||||||
|
envVars.put("CAMELEER_AGENT_HEALTH_ENABLED", "true");
|
||||||
|
envVars.put("CAMELEER_AGENT_HEALTH_PORT", String.valueOf(agentHealthPort));
|
||||||
if (bootstrapToken != null && !bootstrapToken.isBlank()) {
|
if (bootstrapToken != null && !bootstrapToken.isBlank()) {
|
||||||
envVars.put("CAMELEER_AGENT_AUTH_TOKEN", bootstrapToken);
|
envVars.put("CAMELEER_AGENT_AUTH_TOKEN", bootstrapToken);
|
||||||
}
|
}
|
||||||
@@ -343,6 +368,8 @@ public class DeploymentExecutor {
|
|||||||
map.put("serverUrl", config.serverUrl());
|
map.put("serverUrl", config.serverUrl());
|
||||||
map.put("replicas", config.replicas());
|
map.put("replicas", config.replicas());
|
||||||
map.put("deploymentStrategy", config.deploymentStrategy());
|
map.put("deploymentStrategy", config.deploymentStrategy());
|
||||||
|
map.put("runtimeType", config.runtimeType());
|
||||||
|
map.put("customArgs", config.customArgs());
|
||||||
return map;
|
return map;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -84,10 +84,12 @@ public class DockerRuntimeOrchestrator implements RuntimeOrchestrator {
|
|||||||
hostConfig.withCpuQuota(request.cpuQuota());
|
hostConfig.withCpuQuota(request.cpuQuota());
|
||||||
}
|
}
|
||||||
|
|
||||||
// When using volume mount, the JAR is at the original path, not /app/app.jar
|
// Resolve the JAR path for the entrypoint
|
||||||
|
String appJarPath;
|
||||||
if (request.jarVolumeName() != null && !request.jarVolumeName().isBlank()) {
|
if (request.jarVolumeName() != null && !request.jarVolumeName().isBlank()) {
|
||||||
envList = new ArrayList<>(envList);
|
appJarPath = request.jarPath();
|
||||||
envList.add("CAMELEER_APP_JAR=" + request.jarPath());
|
} else {
|
||||||
|
appJarPath = "/app/app.jar";
|
||||||
}
|
}
|
||||||
|
|
||||||
var createCmd = dockerClient.createContainerCmd(request.baseImage())
|
var createCmd = dockerClient.createContainerCmd(request.baseImage())
|
||||||
@@ -103,21 +105,19 @@ public class DockerRuntimeOrchestrator implements RuntimeOrchestrator {
|
|||||||
.withRetries(3)
|
.withRetries(3)
|
||||||
.withStartPeriod(30_000_000_000L));
|
.withStartPeriod(30_000_000_000L));
|
||||||
|
|
||||||
// Override entrypoint to use the volume-mounted JAR path
|
// Build entrypoint based on runtime type
|
||||||
if (request.jarVolumeName() != null && !request.jarVolumeName().isBlank()) {
|
String customArgs = request.customArgs() != null && !request.customArgs().isBlank()
|
||||||
createCmd.withEntrypoint("sh", "-c",
|
? " " + request.customArgs() : "";
|
||||||
"exec java -javaagent:/app/agent.jar " +
|
String entrypoint = switch (request.runtimeType()) {
|
||||||
"-Dcameleer.export.type=${CAMELEER_EXPORT_TYPE:-HTTP} " +
|
case "quarkus" -> "exec java -javaagent:/app/agent.jar" + customArgs + " -jar " + appJarPath;
|
||||||
"-Dcameleer.export.endpoint=${CAMELEER_SERVER_URL} " +
|
case "plain-java" -> "exec java -javaagent:/app/agent.jar -cp " + appJarPath +
|
||||||
"-Dcameleer.agent.name=${HOSTNAME} " +
|
":/app/cameleer3-log-appender.jar" + customArgs + " " + request.mainClass();
|
||||||
"-Dcameleer.agent.application=${CAMELEER_APPLICATION_ID:-default} " +
|
case "native" -> "exec " + appJarPath + customArgs;
|
||||||
"-Dcameleer.agent.environment=${CAMELEER_ENVIRONMENT_ID:-default} " +
|
default -> // spring-boot (default)
|
||||||
"-Dcameleer.routeControl.enabled=${CAMELEER_ROUTE_CONTROL_ENABLED:-false} " +
|
"exec java -javaagent:/app/agent.jar -Dloader.path=/app/cameleer3-log-appender.jar" +
|
||||||
"-Dcameleer.replay.enabled=${CAMELEER_REPLAY_ENABLED:-false} " +
|
customArgs + " -cp " + appJarPath + " org.springframework.boot.loader.launch.PropertiesLauncher";
|
||||||
"-Dcameleer.health.enabled=true " +
|
};
|
||||||
"-Dcameleer.health.port=9464 " +
|
createCmd.withEntrypoint("sh", "-c", entrypoint);
|
||||||
"-jar ${CAMELEER_APP_JAR}");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (request.exposedPorts() != null && !request.exposedPorts().isEmpty()) {
|
if (request.exposedPorts() != null && !request.exposedPorts().isEmpty()) {
|
||||||
var ports = request.exposedPorts().stream()
|
var ports = request.exposedPorts().stream()
|
||||||
|
|||||||
@@ -32,6 +32,11 @@ public class AppService {
|
|||||||
public App getBySlug(String slug) { return appRepo.findBySlug(slug).orElseThrow(() -> new IllegalArgumentException("App not found: " + slug)); }
|
public App getBySlug(String slug) { return appRepo.findBySlug(slug).orElseThrow(() -> new IllegalArgumentException("App not found: " + slug)); }
|
||||||
public List<AppVersion> listVersions(UUID appId) { return versionRepo.findByAppId(appId); }
|
public List<AppVersion> listVersions(UUID appId) { return versionRepo.findByAppId(appId); }
|
||||||
|
|
||||||
|
public AppVersion getVersion(UUID versionId) {
|
||||||
|
return versionRepo.findById(versionId)
|
||||||
|
.orElseThrow(() -> new IllegalArgumentException("AppVersion not found: " + versionId));
|
||||||
|
}
|
||||||
|
|
||||||
public void updateContainerConfig(UUID id, Map<String, Object> containerConfig) {
|
public void updateContainerConfig(UUID id, Map<String, Object> containerConfig) {
|
||||||
getById(id); // verify exists
|
getById(id); // verify exists
|
||||||
appRepo.updateContainerConfig(id, containerConfig);
|
appRepo.updateContainerConfig(id, containerConfig);
|
||||||
|
|||||||
@@ -20,5 +20,8 @@ public record ContainerRequest(
|
|||||||
List<Integer> exposedPorts,
|
List<Integer> exposedPorts,
|
||||||
int healthCheckPort,
|
int healthCheckPort,
|
||||||
String restartPolicyName,
|
String restartPolicyName,
|
||||||
int restartPolicyMaxRetries
|
int restartPolicyMaxRetries,
|
||||||
|
String runtimeType,
|
||||||
|
String customArgs,
|
||||||
|
String mainClass
|
||||||
) {}
|
) {}
|
||||||
|
|||||||
Reference in New Issue
Block a user