env(admin): per-environment color field + V2 migration
- V2__add_environment_color.sql adds a CHECK-constrained VARCHAR color column (default 'slate'); existing rows backfill to slate. - Environment record + EnvironmentColor constants (8 preset values) flow through repository, service, and admin API. - UpdateEnvironmentRequest.color nullable: null preserves existing; unknown values → 400. - ITs cover valid / invalid / null-preserves behaviour; existing Environment constructor call-sites updated with the new color arg. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -72,7 +72,7 @@ Env-scoped read-path controllers (`AlertController`, `AlertRuleController`, `Ale
|
|||||||
|
|
||||||
### Env admin (env-slug-parameterized, not env-scoped data)
|
### Env admin (env-slug-parameterized, not env-scoped data)
|
||||||
|
|
||||||
- `EnvironmentAdminController` — `/api/v1/admin/environments`. GET list / POST create / GET `{envSlug}` / PUT `{envSlug}` / DELETE `{envSlug}` / PUT `{envSlug}/default-container-config` / PUT `{envSlug}/jar-retention`. Slug immutable — PUT body has no slug field; any slug supplied is dropped by Jackson. Slug validated on POST.
|
- `EnvironmentAdminController` — `/api/v1/admin/environments`. GET list / POST create / GET `{envSlug}` / PUT `{envSlug}` / DELETE `{envSlug}` / PUT `{envSlug}/default-container-config` / PUT `{envSlug}/jar-retention`. Slug immutable — PUT body has no slug field; any slug supplied is dropped by Jackson. Slug validated on POST. `UpdateEnvironmentRequest` carries `color` (nullable); unknown values rejected with 400 via `EnvironmentColor.isValid`. Null/absent color preserves the existing value.
|
||||||
|
|
||||||
### Agent-only (JWT-authoritative, intentionally flat)
|
### Agent-only (JWT-authoritative, intentionally flat)
|
||||||
|
|
||||||
|
|||||||
@@ -26,7 +26,8 @@ paths:
|
|||||||
|
|
||||||
- `App` — record: id, environmentId, slug, displayName, containerConfig (JSONB)
|
- `App` — record: id, environmentId, slug, displayName, containerConfig (JSONB)
|
||||||
- `AppVersion` — record: id, appId, version, jarPath, detectedRuntimeType, detectedMainClass
|
- `AppVersion` — record: id, appId, version, jarPath, detectedRuntimeType, detectedMainClass
|
||||||
- `Environment` — record: id, slug, jarRetentionCount
|
- `Environment` — record: id, slug, displayName, production, enabled, defaultContainerConfig, jarRetentionCount, color, createdAt. `color` is one of the 8 preset palette values validated by `EnvironmentColor.VALUES` and CHECK-constrained in PostgreSQL (V2 migration).
|
||||||
|
- `EnvironmentColor` — constants: `DEFAULT = "slate"`, `VALUES = {slate,red,amber,green,teal,blue,purple,pink}`, `isValid(String)`.
|
||||||
- `Deployment` — record: id, appId, appVersionId, environmentId, status, targetState, deploymentStrategy, replicaStates (JSONB), deployStage, containerId, containerName
|
- `Deployment` — record: id, appId, appVersionId, environmentId, status, targetState, deploymentStrategy, replicaStates (JSONB), deployStage, containerId, containerName
|
||||||
- `DeploymentStatus` — enum: STOPPED, STARTING, RUNNING, DEGRADED, STOPPING, FAILED
|
- `DeploymentStatus` — enum: STOPPED, STARTING, RUNNING, DEGRADED, STOPPING, FAILED
|
||||||
- `DeployStage` — enum: PRE_FLIGHT, PULL_IMAGE, CREATE_NETWORK, START_REPLICAS, HEALTH_CHECK, SWAP_TRAFFIC, COMPLETE
|
- `DeployStage` — enum: PRE_FLIGHT, PULL_IMAGE, CREATE_NETWORK, START_REPLICAS, HEALTH_CHECK, SWAP_TRAFFIC, COMPLETE
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package com.cameleer.server.app.controller;
|
package com.cameleer.server.app.controller;
|
||||||
|
|
||||||
import com.cameleer.server.core.runtime.Environment;
|
import com.cameleer.server.core.runtime.Environment;
|
||||||
|
import com.cameleer.server.core.runtime.EnvironmentColor;
|
||||||
import com.cameleer.server.core.runtime.EnvironmentService;
|
import com.cameleer.server.core.runtime.EnvironmentService;
|
||||||
import com.cameleer.server.core.runtime.RuntimeType;
|
import com.cameleer.server.core.runtime.RuntimeType;
|
||||||
import io.swagger.v3.oas.annotations.Operation;
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
@@ -58,16 +59,22 @@ public class EnvironmentAdminController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@PutMapping("/{envSlug}")
|
@PutMapping("/{envSlug}")
|
||||||
@Operation(summary = "Update an environment's mutable fields (displayName, production, enabled)",
|
@Operation(summary = "Update an environment's mutable fields (displayName, production, enabled, color)",
|
||||||
description = "Slug is immutable after creation and cannot be changed. "
|
description = "Slug is immutable after creation and cannot be changed. "
|
||||||
+ "Any slug field in the request body is ignored.")
|
+ "Any slug field in the request body is ignored. "
|
||||||
|
+ "If color is null or absent, the existing color is preserved.")
|
||||||
@ApiResponse(responseCode = "200", description = "Environment updated")
|
@ApiResponse(responseCode = "200", description = "Environment updated")
|
||||||
|
@ApiResponse(responseCode = "400", description = "Unknown color value")
|
||||||
@ApiResponse(responseCode = "404", description = "Environment not found")
|
@ApiResponse(responseCode = "404", description = "Environment not found")
|
||||||
public ResponseEntity<?> updateEnvironment(@PathVariable String envSlug,
|
public ResponseEntity<?> updateEnvironment(@PathVariable String envSlug,
|
||||||
@RequestBody UpdateEnvironmentRequest request) {
|
@RequestBody UpdateEnvironmentRequest request) {
|
||||||
try {
|
try {
|
||||||
Environment current = environmentService.getBySlug(envSlug);
|
Environment current = environmentService.getBySlug(envSlug);
|
||||||
environmentService.update(current.id(), request.displayName(), request.production(), request.enabled());
|
String nextColor = request.color() == null ? current.color() : request.color();
|
||||||
|
if (!EnvironmentColor.isValid(nextColor)) {
|
||||||
|
return ResponseEntity.badRequest().body(Map.of("error", "unknown environment color: " + request.color()));
|
||||||
|
}
|
||||||
|
environmentService.update(current.id(), request.displayName(), request.production(), request.enabled(), nextColor);
|
||||||
return ResponseEntity.ok(environmentService.getBySlug(envSlug));
|
return ResponseEntity.ok(environmentService.getBySlug(envSlug));
|
||||||
} catch (IllegalArgumentException e) {
|
} catch (IllegalArgumentException e) {
|
||||||
if (e.getMessage().contains("not found")) {
|
if (e.getMessage().contains("not found")) {
|
||||||
@@ -149,6 +156,6 @@ public class EnvironmentAdminController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public record CreateEnvironmentRequest(String slug, String displayName, boolean production) {}
|
public record CreateEnvironmentRequest(String slug, String displayName, boolean production) {}
|
||||||
public record UpdateEnvironmentRequest(String displayName, boolean production, boolean enabled) {}
|
public record UpdateEnvironmentRequest(String displayName, boolean production, boolean enabled, String color) {}
|
||||||
public record JarRetentionRequest(Integer jarRetentionCount) {}
|
public record JarRetentionRequest(Integer jarRetentionCount) {}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package com.cameleer.server.app.storage;
|
package com.cameleer.server.app.storage;
|
||||||
|
|
||||||
import com.cameleer.server.core.runtime.Environment;
|
import com.cameleer.server.core.runtime.Environment;
|
||||||
|
import com.cameleer.server.core.runtime.EnvironmentColor;
|
||||||
import com.cameleer.server.core.runtime.EnvironmentRepository;
|
import com.cameleer.server.core.runtime.EnvironmentRepository;
|
||||||
import com.fasterxml.jackson.core.type.TypeReference;
|
import com.fasterxml.jackson.core.type.TypeReference;
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
@@ -24,7 +25,8 @@ public class PostgresEnvironmentRepository implements EnvironmentRepository {
|
|||||||
this.objectMapper = objectMapper;
|
this.objectMapper = objectMapper;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final String SELECT_COLS = "id, slug, display_name, production, enabled, default_container_config, jar_retention_count, created_at";
|
private static final String SELECT_COLS =
|
||||||
|
"id, slug, display_name, production, enabled, default_container_config, jar_retention_count, color, created_at";
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<Environment> findAll() {
|
public List<Environment> findAll() {
|
||||||
@@ -58,9 +60,9 @@ public class PostgresEnvironmentRepository implements EnvironmentRepository {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void update(UUID id, String displayName, boolean production, boolean enabled) {
|
public void update(UUID id, String displayName, boolean production, boolean enabled, String color) {
|
||||||
jdbc.update("UPDATE environments SET display_name = ?, production = ?, enabled = ?, updated_at = now() WHERE id = ?",
|
jdbc.update("UPDATE environments SET display_name = ?, production = ?, enabled = ?, color = ?, updated_at = now() WHERE id = ?",
|
||||||
displayName, production, enabled, id);
|
displayName, production, enabled, color, id);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -93,6 +95,10 @@ public class PostgresEnvironmentRepository implements EnvironmentRepository {
|
|||||||
} catch (Exception e) { /* use empty default */ }
|
} catch (Exception e) { /* use empty default */ }
|
||||||
int retentionRaw = rs.getInt("jar_retention_count");
|
int retentionRaw = rs.getInt("jar_retention_count");
|
||||||
Integer jarRetentionCount = rs.wasNull() ? null : retentionRaw;
|
Integer jarRetentionCount = rs.wasNull() ? null : retentionRaw;
|
||||||
|
String color = rs.getString("color");
|
||||||
|
if (color == null || color.isBlank()) {
|
||||||
|
color = EnvironmentColor.DEFAULT;
|
||||||
|
}
|
||||||
return new Environment(
|
return new Environment(
|
||||||
UUID.fromString(rs.getString("id")),
|
UUID.fromString(rs.getString("id")),
|
||||||
rs.getString("slug"),
|
rs.getString("slug"),
|
||||||
@@ -101,6 +107,7 @@ public class PostgresEnvironmentRepository implements EnvironmentRepository {
|
|||||||
rs.getBoolean("enabled"),
|
rs.getBoolean("enabled"),
|
||||||
config,
|
config,
|
||||||
jarRetentionCount,
|
jarRetentionCount,
|
||||||
|
color,
|
||||||
rs.getTimestamp("created_at").toInstant()
|
rs.getTimestamp("created_at").toInstant()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
-- V2: per-environment color for UI indicator
|
||||||
|
-- Added after V1 baseline (2026-04-22). 8-swatch preset palette; default 'slate'.
|
||||||
|
|
||||||
|
ALTER TABLE environments
|
||||||
|
ADD COLUMN color VARCHAR(16) NOT NULL DEFAULT 'slate'
|
||||||
|
CHECK (color IN ('slate','red','amber','green','teal','blue','purple','pink'));
|
||||||
@@ -37,7 +37,7 @@ class AgentLifecycleEvaluatorTest {
|
|||||||
events = mock(AgentEventRepository.class);
|
events = mock(AgentEventRepository.class);
|
||||||
envRepo = mock(EnvironmentRepository.class);
|
envRepo = mock(EnvironmentRepository.class);
|
||||||
when(envRepo.findById(ENV_ID)).thenReturn(Optional.of(
|
when(envRepo.findById(ENV_ID)).thenReturn(Optional.of(
|
||||||
new Environment(ENV_ID, ENV_SLUG, "Prod", true, true, Map.of(), 5, Instant.EPOCH)));
|
new Environment(ENV_ID, ENV_SLUG, "Prod", true, true, Map.of(), 5, "slate", Instant.EPOCH)));
|
||||||
eval = new AgentLifecycleEvaluator(events, envRepo);
|
eval = new AgentLifecycleEvaluator(events, envRepo);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ class ExchangeMatchEvaluatorTest {
|
|||||||
null, null, null, null, null, null, null, null, null, null, null, null, null, null);
|
null, null, null, null, null, null, null, null, null, null, null, null, null, null);
|
||||||
eval = new ExchangeMatchEvaluator(searchIndex, envRepo, props);
|
eval = new ExchangeMatchEvaluator(searchIndex, envRepo, props);
|
||||||
|
|
||||||
var env = new Environment(ENV_ID, "prod", "Production", false, true, null, null, null);
|
var env = new Environment(ENV_ID, "prod", "Production", false, true, null, null, "slate", null);
|
||||||
when(envRepo.findById(ENV_ID)).thenReturn(Optional.of(env));
|
when(envRepo.findById(ENV_ID)).thenReturn(Optional.of(env));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ class LogPatternEvaluatorTest {
|
|||||||
envRepo = mock(EnvironmentRepository.class);
|
envRepo = mock(EnvironmentRepository.class);
|
||||||
eval = new LogPatternEvaluator(logStore, envRepo);
|
eval = new LogPatternEvaluator(logStore, envRepo);
|
||||||
|
|
||||||
var env = new Environment(ENV_ID, "prod", "Production", false, true, null, null, null);
|
var env = new Environment(ENV_ID, "prod", "Production", false, true, null, null, "slate", null);
|
||||||
when(envRepo.findById(ENV_ID)).thenReturn(Optional.of(env));
|
when(envRepo.findById(ENV_ID)).thenReturn(Optional.of(env));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ class RouteMetricEvaluatorTest {
|
|||||||
envRepo = mock(EnvironmentRepository.class);
|
envRepo = mock(EnvironmentRepository.class);
|
||||||
eval = new RouteMetricEvaluator(statsStore, envRepo);
|
eval = new RouteMetricEvaluator(statsStore, envRepo);
|
||||||
|
|
||||||
var env = new Environment(ENV_ID, "prod", "Production", false, true, null, null, null);
|
var env = new Environment(ENV_ID, "prod", "Production", false, true, null, null, "slate", null);
|
||||||
when(envRepo.findById(ENV_ID)).thenReturn(Optional.of(env));
|
when(envRepo.findById(ENV_ID)).thenReturn(Optional.of(env));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ class NotificationContextBuilderTest {
|
|||||||
// ---- helpers ----
|
// ---- helpers ----
|
||||||
|
|
||||||
private Environment env() {
|
private Environment env() {
|
||||||
return new Environment(ENV_ID, "prod", "Production", true, true, Map.of(), 5, Instant.EPOCH);
|
return new Environment(ENV_ID, "prod", "Production", true, true, Map.of(), 5, "slate", Instant.EPOCH);
|
||||||
}
|
}
|
||||||
|
|
||||||
private AlertRule rule(ConditionKind kind) {
|
private AlertRule rule(ConditionKind kind) {
|
||||||
|
|||||||
@@ -88,9 +88,80 @@ class EnvironmentAdminControllerIT extends AbstractPostgresIT {
|
|||||||
assertThat(body.path("displayName").asText()).isEqualTo("Staging");
|
assertThat(body.path("displayName").asText()).isEqualTo("Staging");
|
||||||
assertThat(body.path("production").asBoolean()).isFalse();
|
assertThat(body.path("production").asBoolean()).isFalse();
|
||||||
assertThat(body.path("enabled").asBoolean()).isTrue();
|
assertThat(body.path("enabled").asBoolean()).isTrue();
|
||||||
|
assertThat(body.path("color").asText()).isEqualTo("slate");
|
||||||
assertThat(body.has("id")).isTrue();
|
assertThat(body.has("id")).isTrue();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void updateEnvironment_withValidColor_persists() throws Exception {
|
||||||
|
restTemplate.exchange(
|
||||||
|
"/api/v1/admin/environments", HttpMethod.POST,
|
||||||
|
new HttpEntity<>("""
|
||||||
|
{"slug": "color-ok", "displayName": "Color OK", "production": false}
|
||||||
|
""", securityHelper.authHeaders(adminJwt)),
|
||||||
|
String.class);
|
||||||
|
|
||||||
|
ResponseEntity<String> response = restTemplate.exchange(
|
||||||
|
"/api/v1/admin/environments/color-ok", HttpMethod.PUT,
|
||||||
|
new HttpEntity<>("""
|
||||||
|
{"displayName": "Color OK", "production": false, "enabled": true, "color": "amber"}
|
||||||
|
""", securityHelper.authHeaders(adminJwt)),
|
||||||
|
String.class);
|
||||||
|
|
||||||
|
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
|
||||||
|
JsonNode body = objectMapper.readTree(response.getBody());
|
||||||
|
assertThat(body.path("color").asText()).isEqualTo("amber");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void updateEnvironment_withNullColor_preservesExisting() throws Exception {
|
||||||
|
restTemplate.exchange(
|
||||||
|
"/api/v1/admin/environments", HttpMethod.POST,
|
||||||
|
new HttpEntity<>("""
|
||||||
|
{"slug": "color-preserve", "displayName": "Keep", "production": false}
|
||||||
|
""", securityHelper.authHeaders(adminJwt)),
|
||||||
|
String.class);
|
||||||
|
// Set color to teal
|
||||||
|
restTemplate.exchange(
|
||||||
|
"/api/v1/admin/environments/color-preserve", HttpMethod.PUT,
|
||||||
|
new HttpEntity<>("""
|
||||||
|
{"displayName": "Keep", "production": false, "enabled": true, "color": "teal"}
|
||||||
|
""", securityHelper.authHeaders(adminJwt)),
|
||||||
|
String.class);
|
||||||
|
|
||||||
|
// Update without color field → teal preserved
|
||||||
|
ResponseEntity<String> response = restTemplate.exchange(
|
||||||
|
"/api/v1/admin/environments/color-preserve", HttpMethod.PUT,
|
||||||
|
new HttpEntity<>("""
|
||||||
|
{"displayName": "Still Keep", "production": false, "enabled": true}
|
||||||
|
""", securityHelper.authHeaders(adminJwt)),
|
||||||
|
String.class);
|
||||||
|
|
||||||
|
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
|
||||||
|
JsonNode body = objectMapper.readTree(response.getBody());
|
||||||
|
assertThat(body.path("displayName").asText()).isEqualTo("Still Keep");
|
||||||
|
assertThat(body.path("color").asText()).isEqualTo("teal");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void updateEnvironment_withUnknownColor_returns400() throws Exception {
|
||||||
|
restTemplate.exchange(
|
||||||
|
"/api/v1/admin/environments", HttpMethod.POST,
|
||||||
|
new HttpEntity<>("""
|
||||||
|
{"slug": "color-bad", "displayName": "Bad", "production": false}
|
||||||
|
""", securityHelper.authHeaders(adminJwt)),
|
||||||
|
String.class);
|
||||||
|
|
||||||
|
ResponseEntity<String> response = restTemplate.exchange(
|
||||||
|
"/api/v1/admin/environments/color-bad", HttpMethod.PUT,
|
||||||
|
new HttpEntity<>("""
|
||||||
|
{"displayName": "Bad", "production": false, "enabled": true, "color": "neon"}
|
||||||
|
""", securityHelper.authHeaders(adminJwt)),
|
||||||
|
String.class);
|
||||||
|
|
||||||
|
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.BAD_REQUEST);
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void updateEnvironment_asAdmin_returns200() throws Exception {
|
void updateEnvironment_asAdmin_returns200() throws Exception {
|
||||||
// Create an environment first
|
// Create an environment first
|
||||||
|
|||||||
@@ -12,5 +12,6 @@ public record Environment(
|
|||||||
boolean enabled,
|
boolean enabled,
|
||||||
Map<String, Object> defaultContainerConfig,
|
Map<String, Object> defaultContainerConfig,
|
||||||
Integer jarRetentionCount,
|
Integer jarRetentionCount,
|
||||||
|
String color,
|
||||||
Instant createdAt
|
Instant createdAt
|
||||||
) {}
|
) {}
|
||||||
|
|||||||
@@ -0,0 +1,28 @@
|
|||||||
|
package com.cameleer.server.core.runtime;
|
||||||
|
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Preset palette for the per-environment UI color indicator. Stored as a plain
|
||||||
|
* lowercase string on {@link Environment#color()}. The eight values are
|
||||||
|
* CHECK-constrained in PostgreSQL (V2 migration) and validated again here on
|
||||||
|
* the write path so the controller can return a 400 with a readable message.
|
||||||
|
*
|
||||||
|
* <p>Unknown values are silently tolerated on read (the UI falls back to
|
||||||
|
* {@link #DEFAULT}), so a manual DB tweak won't break rendering — but the API
|
||||||
|
* refuses to persist anything outside this set.
|
||||||
|
*/
|
||||||
|
public final class EnvironmentColor {
|
||||||
|
|
||||||
|
public static final String DEFAULT = "slate";
|
||||||
|
|
||||||
|
public static final Set<String> VALUES = Set.of(
|
||||||
|
"slate", "red", "amber", "green", "teal", "blue", "purple", "pink"
|
||||||
|
);
|
||||||
|
|
||||||
|
private EnvironmentColor() {}
|
||||||
|
|
||||||
|
public static boolean isValid(String color) {
|
||||||
|
return color != null && VALUES.contains(color);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -10,7 +10,7 @@ public interface EnvironmentRepository {
|
|||||||
Optional<Environment> findById(UUID id);
|
Optional<Environment> findById(UUID id);
|
||||||
Optional<Environment> findBySlug(String slug);
|
Optional<Environment> findBySlug(String slug);
|
||||||
UUID create(String slug, String displayName, boolean production);
|
UUID create(String slug, String displayName, boolean production);
|
||||||
void update(UUID id, String displayName, boolean production, boolean enabled);
|
void update(UUID id, String displayName, boolean production, boolean enabled, String color);
|
||||||
void updateDefaultContainerConfig(UUID id, Map<String, Object> defaultContainerConfig);
|
void updateDefaultContainerConfig(UUID id, Map<String, Object> defaultContainerConfig);
|
||||||
void updateJarRetentionCount(UUID id, Integer jarRetentionCount);
|
void updateJarRetentionCount(UUID id, Integer jarRetentionCount);
|
||||||
void delete(UUID id);
|
void delete(UUID id);
|
||||||
|
|||||||
@@ -43,9 +43,17 @@ public class EnvironmentService {
|
|||||||
return repo.create(slug, displayName, production);
|
return repo.create(slug, displayName, production);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void update(UUID id, String displayName, boolean production, boolean enabled) {
|
/**
|
||||||
|
* Update mutable environment fields. Color is validated against
|
||||||
|
* {@link EnvironmentColor#VALUES}. Unknown colors raise
|
||||||
|
* {@link IllegalArgumentException}; the controller maps that to 400.
|
||||||
|
*/
|
||||||
|
public void update(UUID id, String displayName, boolean production, boolean enabled, String color) {
|
||||||
getById(id); // verify exists
|
getById(id); // verify exists
|
||||||
repo.update(id, displayName, production, enabled);
|
if (!EnvironmentColor.isValid(color)) {
|
||||||
|
throw new IllegalArgumentException("unknown environment color: " + color);
|
||||||
|
}
|
||||||
|
repo.update(id, displayName, production, enabled, color);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void updateDefaultContainerConfig(UUID id, Map<String, Object> defaultContainerConfig) {
|
public void updateDefaultContainerConfig(UUID id, Map<String, Object> defaultContainerConfig) {
|
||||||
|
|||||||
Reference in New Issue
Block a user