import java.util.LinkedHashMap; import java.util.Map; import java.util.Objects; /** Successful deployment state is separate from immutable allocated version tags. */ record ReleaseState(ReleaseState.Deployment current, ReleaseState.Deployment previous, ReleaseVersion highest) { record Deployment( ReleaseVersion version, String sha, String backend, String frontend, String directory, String build) { Deployment { ReleaseApi.sha(sha); Pipeline.check(directory.matches("[a-zA-Z0-9-]+"), "Invalid retained release directory"); Pipeline.check(!backend.equals("-") || !frontend.equals("-"), "A release must contain an image"); for (String image : new String[] {backend, frontend}) Pipeline.check( (image.equals("-") || image.matches("[a-zA-Z0-9.:/_-]+@sha256:[0-9a-f]{64}")), "Invalid retained image digest"); } Map json() { return Map.of( "version", version.toString(), "sha", sha, "backend", backend, "frontend", frontend, "directory", directory, "build", build); } static Deployment parse(Object value) { if (value == null) return null; var data = Json.object(value); return new Deployment( ReleaseVersion.parse(text(data, "version")), text(data, "sha"), text(data, "backend"), text(data, "frontend"), text(data, "directory"), text(data, "build")); } } static String text(Map data, String key) { return Objects.toString(data.get(key), ""); } static ReleaseState parse(String value) { var data = Json.object(Json.parse(value)); if (data.isEmpty()) return new ReleaseState(null, null, null); Pipeline.check("1".equals(text(data, "schema")), "Unsupported deployment state schema"); Deployment current = Deployment.parse(data.get("current")); Deployment previous = Deployment.parse(data.get("previous")); ReleaseVersion highest = ReleaseVersion.parse(text(data, "highest")); Pipeline.check( current != null && highest.compareTo(current.version()) >= 0 && (previous == null || highest.compareTo(previous.version()) >= 0), "Inconsistent successful release state"); return new ReleaseState(current, previous, highest); } String json() { var data = new LinkedHashMap(); data.put("schema", "1"); data.put("current", current == null ? null : current.json()); data.put("previous", previous == null ? null : previous.json()); data.put("highest", highest == null ? null : highest.toString()); return Json.write(data) + "\n"; } ReleaseState success(Deployment selected) { ReleaseVersion watermark = highest == null || selected.version().compareTo(highest) > 0 ? selected.version() : highest; // A redeploy of the same release must not discard the distinct rollback target. Deployment fallback = current != null && current.version().equals(selected.version()) ? previous : current; return new ReleaseState(selected, fallback, watermark); } }