diff --git a/src/main/java/land/oras/Describable.java b/src/main/java/land/oras/Describable.java index 1b70d4b5..53b0d744 100644 --- a/src/main/java/land/oras/Describable.java +++ b/src/main/java/land/oras/Describable.java @@ -30,4 +30,10 @@ public interface Describable { * @return The manifest descriptor */ ManifestDescriptor getDescriptor(); + + /** + * Get the subject + * @return The subject + */ + Subject getSubject(); } diff --git a/src/main/java/land/oras/Index.java b/src/main/java/land/oras/Index.java index 7818ed82..5a6e9140 100644 --- a/src/main/java/land/oras/Index.java +++ b/src/main/java/land/oras/Index.java @@ -149,10 +149,7 @@ protected Index withJson(String json) { return this; } - /** - * Get the subject - * @return The subject - */ + @Override public Subject getSubject() { return subject; } diff --git a/src/main/java/land/oras/Manifest.java b/src/main/java/land/oras/Manifest.java index 45410fef..5eb59774 100644 --- a/src/main/java/land/oras/Manifest.java +++ b/src/main/java/land/oras/Manifest.java @@ -111,10 +111,7 @@ public Config getConfig() { return config; } - /** - * Get the subject - * @return The subject - */ + @Override public Subject getSubject() { return subject; } diff --git a/src/main/java/land/oras/OCI.java b/src/main/java/land/oras/OCI.java index d7085369..e5b892b2 100644 --- a/src/main/java/land/oras/OCI.java +++ b/src/main/java/land/oras/OCI.java @@ -358,6 +358,14 @@ public abstract Manifest pushArtifact( */ public abstract Layer pushBlob(T ref, byte[] data); + /** + * Get the referrers of a container + * @param ref The ref + * @param artifactType The optional artifact type + * @return The referrers + */ + public abstract Referrers getReferrers(T ref, @Nullable ArtifactType artifactType); + /** * Attach file to an existing manifest * @param ref The ref diff --git a/src/main/java/land/oras/OCILayout.java b/src/main/java/land/oras/OCILayout.java index 42c13a49..fdc29cb8 100644 --- a/src/main/java/land/oras/OCILayout.java +++ b/src/main/java/land/oras/OCILayout.java @@ -25,6 +25,7 @@ import java.nio.file.Files; import java.nio.file.Path; import java.util.HashMap; +import java.util.LinkedList; import java.util.List; import java.util.Map; import land.oras.exception.OrasException; @@ -283,6 +284,31 @@ public Layer pushBlob(LayoutRef ref, byte[] data) { } } + @Override + public Referrers getReferrers(LayoutRef ref, @Nullable ArtifactType artifactType) { + Index index = Index.fromPath(getIndexPath()); + ManifestDescriptor currentDescriptor = findManifestDescriptor(ref); + String currentDescriptorDigest = currentDescriptor.getDigest(); + LOG.info("Looking for referrers of manifest: {}", currentDescriptorDigest); + List manifestDescriptors = new LinkedList<>(); + for (ManifestDescriptor manifestDescriptor : index.getManifests()) { + String digest = manifestDescriptor.getDigest(); + Descriptor descriptor = probeDescriptor(ref.withDigest(digest)); + Describable describable = isIndexMediaType(descriptor.getMediaType()) + ? getIndex(ref.withDigest(digest)) + : getManifest(ref.withDigest(digest)); + if (describable.getSubject() != null) { + Subject subject = describable.getSubject(); + String subjectDigest = subject.getDigest(); + if (subjectDigest.equals(currentDescriptorDigest)) { + LOG.info("Subject with digest {} found for manifest: {}", subjectDigest, digest); + manifestDescriptors.add(manifestDescriptor); + } + } + } + return Referrers.from(manifestDescriptors); + } + private void setPath(Path path) { this.path = path; } @@ -505,12 +531,6 @@ private Path getBlobPath(ManifestDescriptor manifestDescriptor) { return getBlobPath().resolve(algorithm.getPrefix()).resolve(SupportedAlgorithm.getDigest(digest)); } - private Path getBlobPath(Config config) { - String digest = config.getDigest(); - SupportedAlgorithm algorithm = SupportedAlgorithm.fromDigest(digest); - return getBlobPath().resolve(algorithm.getPrefix()).resolve(SupportedAlgorithm.getDigest(digest)); - } - private Path getBlobPath(Layer layer) { String digest = layer.getDigest(); SupportedAlgorithm algorithm = SupportedAlgorithm.fromDigest(digest); diff --git a/src/main/java/land/oras/Referrers.java b/src/main/java/land/oras/Referrers.java index 2ab9747a..c8a78f13 100644 --- a/src/main/java/land/oras/Referrers.java +++ b/src/main/java/land/oras/Referrers.java @@ -21,6 +21,7 @@ package land.oras; import java.util.List; +import land.oras.utils.Const; import land.oras.utils.JsonUtils; /** @@ -34,7 +35,10 @@ public class Referrers { /** * Private constructor */ - private Referrers() {} + private Referrers(List manifests) { + this.mediaType = Const.DEFAULT_INDEX_MEDIA_TYPE; + this.manifests = manifests; + } /** * Get the media type @@ -68,4 +72,13 @@ public String toJson() { public static Referrers fromJson(String json) { return JsonUtils.fromJson(json, Referrers.class); } + + /** + * Create a referrers object from a list of descriptors + * @param descriptors The list of descriptors + * @return The referrers object + */ + public static Referrers from(List descriptors) { + return new Referrers(descriptors); + } } diff --git a/src/main/java/land/oras/Registry.java b/src/main/java/land/oras/Registry.java index 97f92bba..18938ad2 100644 --- a/src/main/java/land/oras/Registry.java +++ b/src/main/java/land/oras/Registry.java @@ -138,12 +138,7 @@ public List getTags(ContainerRef containerRef) { return JsonUtils.fromJson(response.response(), Tags.class).tags(); } - /** - * Get the referrers of a container - * @param containerRef The container - * @param artifactType The optional artifact type - * @return The referrers - */ + @Override public Referrers getReferrers(ContainerRef containerRef, @Nullable ArtifactType artifactType) { if (containerRef.getDigest() == null) { throw new OrasException("Digest is required to get referrers"); diff --git a/src/test/java/land/oras/OCILayoutTest.java b/src/test/java/land/oras/OCILayoutTest.java index fc4fd71a..d1adebfb 100644 --- a/src/test/java/land/oras/OCILayoutTest.java +++ b/src/test/java/land/oras/OCILayoutTest.java @@ -491,6 +491,30 @@ void shouldPullViaTagFromOciLayout() throws IOException { Files.readString(extractDir1.resolve("manifest.json"))); } + @Test + void shouldGetReferrers() throws IOException { + + Path extractDir1 = extractDir.resolve("shouldGetReferrers"); + Files.createDirectory(extractDir1); + + LayoutRef layoutRef = LayoutRef.parse("src/test/resources/oci/subject:latest"); + OCILayout ociLayout = + OCILayout.Builder.builder().defaults(layoutRef.getFolder()).build(); + + Referrers referrers = ociLayout.getReferrers(layoutRef, null); + assertEquals(1, referrers.getManifests().size()); + + ManifestDescriptor manifestDescriptor = referrers.getManifests().get(0); + assertEquals( + "sha256:ccec2a2be7ce7c6aadc8ed0dc03df8f91cbd3534272dd1f8284226a8d3516dd6", + manifestDescriptor.getDigest()); + assertEquals(746, manifestDescriptor.getSize()); + assertEquals("application/vnd.oci.image.manifest.v1+json", manifestDescriptor.getMediaType()); + assertNotNull(manifestDescriptor.getAnnotations()); + assertEquals(1, manifestDescriptor.getAnnotations().size()); + assertEquals("2025-04-07T14:54:25Z", manifestDescriptor.getAnnotations().get(Const.ANNOTATION_CREATED)); + } + @Test void shouldPullIndex() throws IOException { diff --git a/src/test/resources/oci/subject/blobs/sha256/44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a b/src/test/resources/oci/subject/blobs/sha256/44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a new file mode 100644 index 00000000..0967ef42 --- /dev/null +++ b/src/test/resources/oci/subject/blobs/sha256/44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a @@ -0,0 +1 @@ +{} diff --git a/src/test/resources/oci/subject/blobs/sha256/98ea6e4f216f2fb4b69fff9b3a44842c38686ca685f3f55dc48c5d3fb1107be4 b/src/test/resources/oci/subject/blobs/sha256/98ea6e4f216f2fb4b69fff9b3a44842c38686ca685f3f55dc48c5d3fb1107be4 new file mode 100644 index 00000000..45b983be --- /dev/null +++ b/src/test/resources/oci/subject/blobs/sha256/98ea6e4f216f2fb4b69fff9b3a44842c38686ca685f3f55dc48c5d3fb1107be4 @@ -0,0 +1 @@ +hi diff --git a/src/test/resources/oci/subject/blobs/sha256/bb329f103a5fd68e96771f7dcfaa7722e9ec727bb9ab83c2beee96d6f25b08d6 b/src/test/resources/oci/subject/blobs/sha256/bb329f103a5fd68e96771f7dcfaa7722e9ec727bb9ab83c2beee96d6f25b08d6 new file mode 100644 index 00000000..ff6814e1 --- /dev/null +++ b/src/test/resources/oci/subject/blobs/sha256/bb329f103a5fd68e96771f7dcfaa7722e9ec727bb9ab83c2beee96d6f25b08d6 @@ -0,0 +1 @@ +{"schemaVersion":2,"mediaType":"application/vnd.oci.image.manifest.v1+json","artifactType":"application/vnd.text.file.v1+json","config":{"mediaType":"application/vnd.oci.empty.v1+json","digest":"sha256:44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a","size":2,"data":"e30="},"layers":[{"mediaType":"plain/text","digest":"sha256:98ea6e4f216f2fb4b69fff9b3a44842c38686ca685f3f55dc48c5d3fb1107be4","size":3,"annotations":{"org.opencontainers.image.title":"hi.txt"}}],"annotations":{"org.opencontainers.image.created":"2025-04-07T14:54:19Z"}} diff --git a/src/test/resources/oci/subject/blobs/sha256/ccec2a2be7ce7c6aadc8ed0dc03df8f91cbd3534272dd1f8284226a8d3516dd6 b/src/test/resources/oci/subject/blobs/sha256/ccec2a2be7ce7c6aadc8ed0dc03df8f91cbd3534272dd1f8284226a8d3516dd6 new file mode 100644 index 00000000..ce80bdb5 --- /dev/null +++ b/src/test/resources/oci/subject/blobs/sha256/ccec2a2be7ce7c6aadc8ed0dc03df8f91cbd3534272dd1f8284226a8d3516dd6 @@ -0,0 +1 @@ +{"schemaVersion":2,"mediaType":"application/vnd.oci.image.manifest.v1+json","artifactType":"application/vnd.text.file.v1+json","config":{"mediaType":"application/vnd.oci.empty.v1+json","digest":"sha256:44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a","size":2,"data":"e30="},"layers":[{"mediaType":"application/vnd.oci.image.layer.v1.tar","digest":"sha256:e094bc809626f0a401a40d75c56df478e546902ff812772c4594265203b23980","size":4,"annotations":{"org.opencontainers.image.title":"hi2.txt"}}],"subject":{"mediaType":"application/vnd.oci.image.manifest.v1+json","digest":"sha256:bb329f103a5fd68e96771f7dcfaa7722e9ec727bb9ab83c2beee96d6f25b08d6","size":554},"annotations":{"org.opencontainers.image.created":"2025-04-07T14:54:25Z"}} diff --git a/src/test/resources/oci/subject/blobs/sha256/e094bc809626f0a401a40d75c56df478e546902ff812772c4594265203b23980 b/src/test/resources/oci/subject/blobs/sha256/e094bc809626f0a401a40d75c56df478e546902ff812772c4594265203b23980 new file mode 100644 index 00000000..7cc39036 --- /dev/null +++ b/src/test/resources/oci/subject/blobs/sha256/e094bc809626f0a401a40d75c56df478e546902ff812772c4594265203b23980 @@ -0,0 +1 @@ +hi2 diff --git a/src/test/resources/oci/subject/index.json b/src/test/resources/oci/subject/index.json new file mode 100644 index 00000000..55120b65 --- /dev/null +++ b/src/test/resources/oci/subject/index.json @@ -0,0 +1 @@ +{"schemaVersion":2,"manifests":[{"mediaType":"application/vnd.oci.image.manifest.v1+json","digest":"sha256:bb329f103a5fd68e96771f7dcfaa7722e9ec727bb9ab83c2beee96d6f25b08d6","size":554,"annotations":{"org.opencontainers.image.ref.name":"latest"}},{"mediaType":"application/vnd.oci.image.manifest.v1+json","digest":"sha256:ccec2a2be7ce7c6aadc8ed0dc03df8f91cbd3534272dd1f8284226a8d3516dd6","size":746,"annotations":{"org.opencontainers.image.created":"2025-04-07T14:54:25Z"},"artifactType":"application/vnd.text.file.v1+json"}]} diff --git a/src/test/resources/oci/subject/oci-layout b/src/test/resources/oci/subject/oci-layout new file mode 100644 index 00000000..d5e10fd6 --- /dev/null +++ b/src/test/resources/oci/subject/oci-layout @@ -0,0 +1 @@ +{"imageLayoutVersion":"1.0.0"}