diff --git a/cameleer-server-core/src/main/java/com/cameleer/server/core/storage/ArtifactCoordinates.java b/cameleer-server-core/src/main/java/com/cameleer/server/core/storage/ArtifactCoordinates.java new file mode 100644 index 00000000..be72a626 --- /dev/null +++ b/cameleer-server-core/src/main/java/com/cameleer/server/core/storage/ArtifactCoordinates.java @@ -0,0 +1,27 @@ +package com.cameleer.server.core.storage; + +import java.util.UUID; + +/** + * Opaque coordinate that addresses one stored JAR. Designed so a future + * OCI-backed store can reuse the same coordinate without schema changes — + * see issue (Zot follow-up). + */ +public record ArtifactCoordinates(String tenantId, UUID appId, int version) { + public ArtifactCoordinates { + if (tenantId == null) throw new IllegalArgumentException("tenantId must not be null"); + if (appId == null) throw new IllegalArgumentException("appId must not be null"); + if (version <= 0) throw new IllegalArgumentException("version must be > 0, got " + version); + } + + /** Path-fragment used by FilesystemArtifactStore. Mirrors today's layout. */ + public String filesystemKey() { + return appId + "/v" + version + "/app.jar"; + } + + /** OCI reference used by a future registry-backed store. Stable so coordinates + * survive a backend swap. */ + public String ociRef() { + return tenantId + "/" + appId + ":v" + version; + } +} diff --git a/cameleer-server-core/src/test/java/com/cameleer/server/core/storage/ArtifactCoordinatesTest.java b/cameleer-server-core/src/test/java/com/cameleer/server/core/storage/ArtifactCoordinatesTest.java new file mode 100644 index 00000000..587f7d53 --- /dev/null +++ b/cameleer-server-core/src/test/java/com/cameleer/server/core/storage/ArtifactCoordinatesTest.java @@ -0,0 +1,33 @@ +package com.cameleer.server.core.storage; + +import org.junit.jupiter.api.Test; +import java.util.UUID; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +class ArtifactCoordinatesTest { + @Test + void rejectsNullsAndNonPositiveVersion() { + UUID appId = UUID.randomUUID(); + assertThatThrownBy(() -> new ArtifactCoordinates(null, appId, 1)) + .isInstanceOf(IllegalArgumentException.class); + assertThatThrownBy(() -> new ArtifactCoordinates("default", null, 1)) + .isInstanceOf(IllegalArgumentException.class); + assertThatThrownBy(() -> new ArtifactCoordinates("default", appId, 0)) + .isInstanceOf(IllegalArgumentException.class); + } + + @Test + void filesystemKeyMatchesLegacyLayout() { + UUID appId = UUID.fromString("11111111-1111-1111-1111-111111111111"); + ArtifactCoordinates c = new ArtifactCoordinates("default", appId, 7); + assertThat(c.filesystemKey()).isEqualTo("11111111-1111-1111-1111-111111111111/v7/app.jar"); + } + + @Test + void ociRefIsDeterministic() { + UUID appId = UUID.fromString("11111111-1111-1111-1111-111111111111"); + ArtifactCoordinates c = new ArtifactCoordinates("acme", appId, 7); + assertThat(c.ociRef()).isEqualTo("acme/11111111-1111-1111-1111-111111111111:v7"); + } +}