storage(deploy): persist deployed_config_snapshot as JSONB
Wire SELECT_COLS, mapRow deserialization, and saveDeployedConfigSnapshot update method. Adds PostgresDeploymentRepositoryIT with roundtrip, null-default, and clear-to-null tests. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
package com.cameleer.server.app.storage;
|
||||
|
||||
import com.cameleer.server.core.runtime.Deployment;
|
||||
import com.cameleer.server.core.runtime.DeploymentConfigSnapshot;
|
||||
import com.cameleer.server.core.runtime.DeploymentRepository;
|
||||
import com.cameleer.server.core.runtime.DeploymentStatus;
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
@@ -21,7 +22,7 @@ public class PostgresDeploymentRepository implements DeploymentRepository {
|
||||
private static final String SELECT_COLS =
|
||||
"id, app_id, app_version_id, environment_id, status, target_state, deployment_strategy, " +
|
||||
"replica_states, deploy_stage, container_id, container_name, error_message, " +
|
||||
"resolved_config, deployed_at, stopped_at, created_at";
|
||||
"resolved_config, deployed_config_snapshot, deployed_at, stopped_at, created_at";
|
||||
|
||||
private final JdbcTemplate jdbc;
|
||||
private final ObjectMapper objectMapper;
|
||||
@@ -129,6 +130,15 @@ public class PostgresDeploymentRepository implements DeploymentRepository {
|
||||
}
|
||||
}
|
||||
|
||||
public void saveDeployedConfigSnapshot(UUID id, DeploymentConfigSnapshot snapshot) {
|
||||
try {
|
||||
String json = snapshot != null ? objectMapper.writeValueAsString(snapshot) : null;
|
||||
jdbc.update("UPDATE deployments SET deployed_config_snapshot = ?::jsonb WHERE id = ?", json, id);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Failed to serialize deployed_config_snapshot", e);
|
||||
}
|
||||
}
|
||||
|
||||
public Optional<Deployment> findByContainerId(String containerId) {
|
||||
var results = jdbc.query(
|
||||
"SELECT " + SELECT_COLS + " FROM deployments WHERE replica_states::text LIKE ? " +
|
||||
@@ -158,6 +168,15 @@ public class PostgresDeploymentRepository implements DeploymentRepository {
|
||||
throw new SQLException("Failed to deserialize resolved_config", e);
|
||||
}
|
||||
}
|
||||
DeploymentConfigSnapshot deployedConfigSnapshot = null;
|
||||
String snapshotJson = rs.getString("deployed_config_snapshot");
|
||||
if (snapshotJson != null) {
|
||||
try {
|
||||
deployedConfigSnapshot = objectMapper.readValue(snapshotJson, DeploymentConfigSnapshot.class);
|
||||
} catch (Exception e) {
|
||||
throw new SQLException("Failed to deserialize deployed_config_snapshot", e);
|
||||
}
|
||||
}
|
||||
return new Deployment(
|
||||
UUID.fromString(rs.getString("id")),
|
||||
UUID.fromString(rs.getString("app_id")),
|
||||
@@ -172,7 +191,7 @@ public class PostgresDeploymentRepository implements DeploymentRepository {
|
||||
rs.getString("container_name"),
|
||||
rs.getString("error_message"),
|
||||
resolvedConfig,
|
||||
null, // deployedConfigSnapshot — wired in Task 1.4
|
||||
deployedConfigSnapshot,
|
||||
deployedAt != null ? deployedAt.toInstant() : null,
|
||||
stoppedAt != null ? stoppedAt.toInstant() : null,
|
||||
rs.getTimestamp("created_at").toInstant()
|
||||
|
||||
@@ -0,0 +1,108 @@
|
||||
package com.cameleer.server.app.storage;
|
||||
|
||||
import com.cameleer.common.model.ApplicationConfig;
|
||||
import com.cameleer.server.app.AbstractPostgresIT;
|
||||
import com.cameleer.server.core.runtime.Deployment;
|
||||
import com.cameleer.server.core.runtime.DeploymentConfigSnapshot;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
class PostgresDeploymentRepositoryIT extends AbstractPostgresIT {
|
||||
|
||||
private PostgresDeploymentRepository repository;
|
||||
|
||||
private UUID envId;
|
||||
private UUID appId;
|
||||
private UUID appVersionId;
|
||||
|
||||
@BeforeEach
|
||||
void setup() {
|
||||
repository = new PostgresDeploymentRepository(jdbcTemplate, new ObjectMapper());
|
||||
|
||||
envId = UUID.randomUUID();
|
||||
jdbcTemplate.update(
|
||||
"INSERT INTO environments (id, slug, display_name) VALUES (?, ?, ?)",
|
||||
envId, "test-env-" + envId, "Test Env");
|
||||
|
||||
appId = UUID.randomUUID();
|
||||
jdbcTemplate.update(
|
||||
"INSERT INTO apps (id, environment_id, slug, display_name) VALUES (?, ?, ?, ?)",
|
||||
appId, envId, "app-it-" + appId, "App IT");
|
||||
|
||||
appVersionId = UUID.randomUUID();
|
||||
jdbcTemplate.update(
|
||||
"INSERT INTO app_versions (id, app_id, version, jar_path, jar_checksum) VALUES (?, ?, ?, ?, ?)",
|
||||
appVersionId, appId, 1, "/tmp/app.jar", "deadbeef");
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
void cleanup() {
|
||||
jdbcTemplate.update("DELETE FROM deployments WHERE app_id = ?", appId);
|
||||
jdbcTemplate.update("DELETE FROM app_versions WHERE app_id = ?", appId);
|
||||
jdbcTemplate.update("DELETE FROM apps WHERE id = ?", appId);
|
||||
jdbcTemplate.update("DELETE FROM environments WHERE id = ?", envId);
|
||||
}
|
||||
|
||||
@Test
|
||||
void deployedConfigSnapshot_roundtrips() {
|
||||
// given — create a deployment then store a snapshot
|
||||
ApplicationConfig agentConfig = new ApplicationConfig();
|
||||
agentConfig.setApplication("app-it");
|
||||
agentConfig.setEnvironment("staging");
|
||||
agentConfig.setVersion(3);
|
||||
agentConfig.setSamplingRate(0.5);
|
||||
|
||||
UUID jarVersionId = UUID.randomUUID();
|
||||
DeploymentConfigSnapshot snapshot = new DeploymentConfigSnapshot(
|
||||
jarVersionId,
|
||||
agentConfig,
|
||||
Map.of("memoryLimitMb", 1024, "replicas", 2)
|
||||
);
|
||||
|
||||
UUID deploymentId = repository.create(appId, appVersionId, envId, "test-container");
|
||||
repository.saveDeployedConfigSnapshot(deploymentId, snapshot);
|
||||
|
||||
// when — load it back
|
||||
Deployment loaded = repository.findById(deploymentId).orElseThrow();
|
||||
|
||||
// then
|
||||
assertThat(loaded.deployedConfigSnapshot()).isNotNull();
|
||||
assertThat(loaded.deployedConfigSnapshot().jarVersionId()).isEqualTo(jarVersionId);
|
||||
assertThat(loaded.deployedConfigSnapshot().agentConfig().getSamplingRate()).isEqualTo(0.5);
|
||||
assertThat(loaded.deployedConfigSnapshot().containerConfig()).containsEntry("memoryLimitMb", 1024);
|
||||
}
|
||||
|
||||
@Test
|
||||
void deployedConfigSnapshot_nullByDefault() {
|
||||
// deployments created without a snapshot must return null (not throw)
|
||||
UUID deploymentId = repository.create(appId, appVersionId, envId, "test-container-null");
|
||||
|
||||
Deployment loaded = repository.findById(deploymentId).orElseThrow();
|
||||
|
||||
assertThat(loaded.deployedConfigSnapshot()).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void deployedConfigSnapshot_canBeClearedToNull() {
|
||||
UUID jarVersionId = UUID.randomUUID();
|
||||
DeploymentConfigSnapshot snapshot = new DeploymentConfigSnapshot(
|
||||
jarVersionId,
|
||||
new ApplicationConfig(),
|
||||
Map.of()
|
||||
);
|
||||
|
||||
UUID deploymentId = repository.create(appId, appVersionId, envId, "test-container-clear");
|
||||
repository.saveDeployedConfigSnapshot(deploymentId, snapshot);
|
||||
repository.saveDeployedConfigSnapshot(deploymentId, null);
|
||||
|
||||
Deployment loaded = repository.findById(deploymentId).orElseThrow();
|
||||
assertThat(loaded.deployedConfigSnapshot()).isNull();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user