diff --git a/src/main/java/land/oras/ManifestDescriptor.java b/src/main/java/land/oras/ManifestDescriptor.java index 162c0b40..5bea00cb 100644 --- a/src/main/java/land/oras/ManifestDescriptor.java +++ b/src/main/java/land/oras/ManifestDescriptor.java @@ -28,6 +28,7 @@ import java.util.Objects; import land.oras.utils.Const; import land.oras.utils.JsonUtils; +import land.oras.utils.SupportedAlgorithm; import org.jspecify.annotations.NullMarked; import org.jspecify.annotations.Nullable; @@ -210,6 +211,34 @@ public static ManifestDescriptor of(Descriptor descriptor) { return of(descriptor, descriptor.getDigest()); } + /** + * Utility method. Useful when assembly manifest to be added to an Index using no platform, empty annotations and default supported algorithm + * @param manifest The manifest + * @return The manifest descriptor + */ + public static ManifestDescriptor of(Manifest manifest) { + return of(manifest, Platform.empty(), Annotations.empty(), SupportedAlgorithm.getDefault()); + } + + /** + * Utility method. Useful when assembly manifest to be added to an Index + * @param manifest The manifest + * @param platform The platform + * @param annotations The annotations + * @param supportedAlgorithm The supported algorithm to calculate the digest of the manifest + * @return The manifest descriptor + */ + public static ManifestDescriptor of( + Manifest manifest, Platform platform, Annotations annotations, SupportedAlgorithm supportedAlgorithm) { + String json = manifest.toJson(); + String digest = supportedAlgorithm.digest(json.getBytes()); + long size = json.length(); + return ManifestDescriptor.of(manifest.getMediaType(), digest, size) + .withAnnotations(annotations.manifestAnnotations()) + .withPlatform(platform) + .withArtifactType(manifest.getArtifactTypeAsString()); + } + /** * Create a manifest descriptor with the given digest * @param descriptor The descriptor diff --git a/src/main/java/land/oras/Registry.java b/src/main/java/land/oras/Registry.java index 83a3c9f0..32581eeb 100644 --- a/src/main/java/land/oras/Registry.java +++ b/src/main/java/land/oras/Registry.java @@ -262,7 +262,7 @@ public Manifest pushManifest(ContainerRef containerRef, Manifest manifest) { } ContainerRef ref = containerRef.forRegistry(this).checkBlocked(this); if (ref.isInsecure(this) && !this.isInsecure()) { - return asInsecure().pushManifest(containerRef, manifest); + return asInsecure().pushManifest(ref, manifest); } URI uri = URI.create("%s://%s".formatted(getScheme(), ref.getManifestsPath(this))); byte[] manifestData = manifest.getJson() != null @@ -284,7 +284,7 @@ public Manifest pushManifest(ContainerRef containerRef, Manifest manifest) { "Subject was set on manifest but not OCI subject header was returned. Legacy flow not implemented"); } } - return getManifest(containerRef); + return getManifest(ref); } @Override diff --git a/src/test/java/land/oras/HarborS3ITCase.java b/src/test/java/land/oras/HarborS3ITCase.java index e3288ce1..4ed8c6c7 100644 --- a/src/test/java/land/oras/HarborS3ITCase.java +++ b/src/test/java/land/oras/HarborS3ITCase.java @@ -91,7 +91,7 @@ void shouldPushFluxArtifact() { // The compressed manifests Path archive = Paths.get("src/test/resources/archives").resolve("flux-manifests.tgz"); - Path image = Paths.get("src/test/resources/img").resolve("flux-cd.png"); + Path image = Paths.get("src/test/resources/img").resolve("opentofu.png"); String configMediaType = "application/vnd.cncf.flux.config.v1+json"; String contentMediaType = "application/vnd.cncf.flux.content.v1.tar+gzip"; diff --git a/src/test/java/land/oras/ManifestDescriptorTest.java b/src/test/java/land/oras/ManifestDescriptorTest.java index d1d9a8e8..621d876a 100644 --- a/src/test/java/land/oras/ManifestDescriptorTest.java +++ b/src/test/java/land/oras/ManifestDescriptorTest.java @@ -28,9 +28,22 @@ import org.junit.jupiter.api.parallel.Execution; import org.junit.jupiter.api.parallel.ExecutionMode; +/** + * Test for {@link ManifestDescriptor} + */ @Execution(ExecutionMode.CONCURRENT) class ManifestDescriptorTest { + @Test + void shouldBuildDescriptorFromManifest() { + Manifest manifest = Manifest.empty(); + ManifestDescriptor descriptor = ManifestDescriptor.of(manifest); + assertEquals("sha256:961dcd96e41989cc3cbf17141e0a9b3d39447cdcf2540b844e22b4f207a2e1f1", descriptor.getDigest()); + assertEquals(253, descriptor.getSize()); + assertEquals(Platform.empty(), descriptor.getPlatform()); + assertEquals(Map.of(), descriptor.getAnnotations()); + } + @Test void shouldSetAnnotations() { Manifest manifest = Manifest.empty(); diff --git a/src/test/java/land/oras/OpenTofuITCase.java b/src/test/java/land/oras/OpenTofuITCase.java new file mode 100644 index 00000000..b31ae39d --- /dev/null +++ b/src/test/java/land/oras/OpenTofuITCase.java @@ -0,0 +1,97 @@ +/*- + * =LICENSE= + * ORAS Java SDK + * === + * Copyright (C) 2024 - 2026 ORAS + * === + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * =LICENSEEND= + */ + +package land.oras; + +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.List; +import java.util.Map; +import land.oras.utils.SupportedAlgorithm; +import land.oras.utils.ZotUnsecureContainer; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.parallel.Execution; +import org.junit.jupiter.api.parallel.ExecutionMode; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; + +@Testcontainers +@Execution(ExecutionMode.CONCURRENT) +class OpenTofuITCase { + + @Container + private final ZotUnsecureContainer unsecureRegistry = new ZotUnsecureContainer().withStartupAttempts(3); + + /** + * This test demonstrate how to assemble a Flux CD OCI Artifact + */ + @Test + void shouldAssembleProviderArtifact() { + + // The compressed manifests + Path archive = + Paths.get("src/test/resources/archives").resolve("terraform-provider-random_3.8.1_linux_amd64.zip"); + + Annotations annotations = Annotations.empty(); + ArtifactType indexArtifactType = ArtifactType.from("application/vnd.opentofu.provider"); + ArtifactType manifestArtifactType = ArtifactType.from("application/vnd.opentofu.provider-target"); + String contentMediaType = "archive/zip"; + + Path image = Paths.get("src/test/resources/img").resolve("flux-cd.png"); + + Platform linuxAmd64 = Platform.linuxAmd64(); + + // Create objects + Config config = Config.empty(); + Layer layer = Layer.fromFile(archive).withMediaType(contentMediaType); + Layer imageLayer = Layer.fromFile(image) + .withMediaType("image/png") + .withAnnotations(Map.of("io.goharbor.artifact.v1alpha1.icon", "")); + Manifest manifest = Manifest.empty() + .withArtifactType(manifestArtifactType) + .withConfig(config) + .withLayers(List.of(layer, imageLayer)); + + // Index with given platform + ManifestDescriptor manifestDescriptor = + ManifestDescriptor.of(manifest, linuxAmd64, annotations, SupportedAlgorithm.SHA256); + Index index = Index.fromManifests(List.of(manifestDescriptor)).withArtifactType(indexArtifactType); + + // Push config, layers and manifest to registry + Registry registry = Registry.builder() + .defaults() + .insecure() + .withRegistry(unsecureRegistry.getRegistry()) + .build(); + ContainerRef containerRef = ContainerRef.parse("oras/opentofu-providers/terraform-provider-random:3.8.1"); + + registry.pushConfig(containerRef, config); + registry.pushBlob(containerRef, archive); + registry.pushBlob(containerRef, image); + registry.pushManifest(containerRef.withDigest(manifestDescriptor.getDigest()), manifest); + registry.pushIndex(containerRef, index); + + // Ensure we can pull + Index createdIndex = registry.getIndex(containerRef); + assertNotNull(createdIndex); + } +} diff --git a/src/test/resources/img/opentofu.png b/src/test/resources/img/opentofu.png new file mode 100644 index 00000000..c812e1a2 Binary files /dev/null and b/src/test/resources/img/opentofu.png differ