refactor(core): AppService writes via ArtifactStore; remove resolveJarPath

Task 4 of the init-container JAR fetch plan: migrate AppService.uploadJar
off direct filesystem writes onto the ArtifactStore abstraction so future
backends (OCI/Zot, S3) can swap in without touching service or controller
code.

- AppService constructor now takes (AppRepository, AppVersionRepository,
  ArtifactStore, tenantId[, CreateGuard]). The store owns layout and the
  locator string written into app_versions.jar_path.
- uploadJar buffers the request body once for hashing + storage, then
  writes a scratch temp file solely for RuntimeDetector (which still
  takes a Path); scratch is unconditionally deleted in finally.
- Add coordinatesFor(AppVersion) helper so downstream callers (Task 5+)
  can derive ArtifactCoordinates without knowing the tenant binding.
- Remove resolveJarPath. DeploymentExecutor now reads jarPath directly
  off the AppVersion record; the clean cut to download-URL delivery
  lands in Task 11.
- RuntimeBeanConfig wires a FilesystemArtifactStore bean rooted at
  cameleer.server.runtime.jarstoragepath and threads tenantId into the
  AppService bean.
This commit is contained in:
hsiegeln
2026-04-27 15:05:40 +02:00
parent 5238c58dd5
commit 07a2fd6090
4 changed files with 109 additions and 40 deletions

View File

@@ -1,5 +1,6 @@
package com.cameleer.server.app.config;
import com.cameleer.server.app.storage.FilesystemArtifactStore;
import com.cameleer.server.app.storage.PostgresAppRepository;
import com.cameleer.server.app.storage.PostgresAppVersionRepository;
import com.cameleer.server.app.storage.PostgresDeploymentRepository;
@@ -12,6 +13,7 @@ import com.cameleer.server.core.runtime.DeploymentService;
import com.cameleer.server.core.runtime.DirtyStateCalculator;
import com.cameleer.server.core.runtime.EnvironmentRepository;
import com.cameleer.server.core.runtime.EnvironmentService;
import com.cameleer.server.core.storage.ArtifactStore;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
@@ -56,11 +58,17 @@ public class RuntimeBeanConfig {
enforcer.assertWithinCap("max_environments", current, 1));
}
@Bean
public ArtifactStore artifactStore(@Value("${cameleer.server.runtime.jarstoragepath:/data/jars}") String jarStoragePath) {
return new FilesystemArtifactStore(jarStoragePath);
}
@Bean
public AppService appService(AppRepository appRepo, AppVersionRepository versionRepo,
@Value("${cameleer.server.runtime.jarstoragepath:/data/jars}") String jarStoragePath,
ArtifactStore artifactStore,
@Value("${cameleer.server.tenant.id:default}") String tenantId,
com.cameleer.server.app.license.LicenseEnforcer enforcer) {
return new AppService(appRepo, versionRepo, jarStoragePath,
return new AppService(appRepo, versionRepo, artifactStore, tenantId,
current -> enforcer.assertWithinCap("max_apps", current, 1));
}

View File

@@ -134,7 +134,7 @@ public class DeploymentExecutor {
try {
App app = appService.getById(deployment.appId());
Environment env = envService.getById(deployment.environmentId());
String jarPath = appService.resolveJarPath(deployment.appVersionId());
String jarPath = appService.getVersion(deployment.appVersionId()).jarPath();
String generation = generationOf(deployment);
var globalDefaults = new ConfigMerger.GlobalRuntimeDefaults(