diff --git a/src/main/java/land/oras/ContainerRef.java b/src/main/java/land/oras/ContainerRef.java index 7b886e1..9085ffe 100644 --- a/src/main/java/land/oras/ContainerRef.java +++ b/src/main/java/land/oras/ContainerRef.java @@ -213,6 +213,15 @@ public ContainerRef withDigest(String digest) { return new ContainerRef(registry, unqualified, namespace, repository, tag, digest); } + /** + * Return a copy of reference with the given tag + * @param tag The tag + * @return The container reference with the given tag + */ + public ContainerRef withTag(String tag) { + return new ContainerRef(registry, unqualified, namespace, repository, tag, digest); + } + @Override public SupportedAlgorithm getAlgorithm() { // Default if not set diff --git a/src/main/java/land/oras/CopyUtils.java b/src/main/java/land/oras/CopyUtils.java index 302a633..8173f25 100644 --- a/src/main/java/land/oras/CopyUtils.java +++ b/src/main/java/land/oras/CopyUtils.java @@ -43,16 +43,60 @@ private CopyUtils() { // Utils class } + /** + * Options for copy. + * @param includeReferrers Whether to include referrers in the copy + * @param recursive Recursive copy that include index of index. If false will copy manifests media type when encountered inside index + */ + public record CopyOptions(boolean includeReferrers, boolean recursive) { + + /** + * The default copy options with includeReferrers and recursive set to false. + * @return The default copy options + */ + public static CopyOptions shallow() { + return new CopyOptions(false, false); + } + + /** + * The copy options with includeReferrers and recursive set to true. + * @return The copy options with includeReferrers and recursive set to true + */ + public static CopyOptions deep() { + return new CopyOptions(true, true); + } + + /** + * Create a new copy options with default values. + * @param includeReferrers Whether to include referrers in the copy + * @return The copy options + */ + public CopyOptions withIncludeReferrers(boolean includeReferrers) { + return new CopyOptions(includeReferrers, this.recursive); + } + + /** + * Create a new copy options with default values. + * @param recursive Recursive copy that include index of index. If false will copy manifests media type when encountered inside index + * @return The copy options + */ + public CopyOptions withRecursive(boolean recursive) { + return new CopyOptions(this.includeReferrers, recursive); + } + } + /** * Copy a container from source to target. + * @deprecated Use {@link #copy(OCI, Ref, OCI, Ref, CopyOptions)} instead. This method will be removed in a future release. * @param source The source OCI * @param sourceRef The source reference * @param target The target OCI * @param targetRef The target reference - * @param recursive Whether to copy referrers recursively + * @param recursive Copy refferers * @param The source reference type * @param The target reference type */ + @Deprecated(forRemoval = true) public static , TargetRefType extends Ref<@NonNull TargetRefType>> void copy( OCI source, @@ -60,6 +104,29 @@ void copy( OCI target, TargetRefType targetRef, boolean recursive) { + copy(source, sourceRef, target, targetRef, CopyOptions.shallow().withRecursive(recursive)); + } + + /** + * Copy a container from source to target. + * @param source The source OCI + * @param sourceRef The source reference + * @param target The target OCI + * @param targetRef The target reference + * @param options The copy option + * @param The source reference type + * @param The target reference type + */ + public static , TargetRefType extends Ref<@NonNull TargetRefType>> + void copy( + OCI source, + SourceRefType sourceRef, + OCI target, + TargetRefType targetRef, + CopyOptions options) { + + boolean recursive = options.recursive(); + boolean includeReferrers = options.includeReferrers(); Descriptor descriptor = source.probeDescriptor(sourceRef); @@ -80,7 +147,7 @@ void copy( TargetRefType effectiveTargetRef = targetRef.forTarget(target).forTarget(effectiveTargetRegistry); // Write all layer - for (Layer layer : source.collectLayers(effectiveSourceRef, contentType, true)) { + for (Layer layer : source.collectLayers(effectiveSourceRef, contentType, true, false)) { Objects.requireNonNull(layer.getDigest(), "Layer digest is required for streaming copy"); Objects.requireNonNull(layer.getSize(), "Layer size is required for streaming copy"); LOG.debug("Copying layer {}", layer.getDigest()); @@ -109,18 +176,20 @@ void copy( target.pushManifest(effectiveTargetRef.withDigest(tag), manifest); LOG.debug("Copied manifest {}", manifestDigest); - if (recursive) { - LOG.debug("Recursively copy referrers"); + if (includeReferrers) { + LOG.debug("Including referrers on copy of manifest {}", manifestDigest); Referrers referrers = source.getReferrers(effectiveSourceRef.withDigest(manifestDigest), null); for (ManifestDescriptor referer : referrers.getManifests()) { - LOG.info("Copy reference {}", referer.getDigest()); + LOG.debug("Copy reference from referrers {}", referer.getDigest()); copy( source, effectiveSourceRef.withDigest(referer.getDigest()), target, effectiveTargetRef, - recursive); + options); } + } else { + LOG.debug("Not including referrers on copy of manifest {}", manifestDigest); } } @@ -132,21 +201,46 @@ else if (source.isIndexMediaType(contentType)) { // Write all manifests and their config for (ManifestDescriptor manifestDescriptor : index.getManifests()) { - Manifest manifest = source.getManifest(effectiveSourceRef.withDigest(manifestDescriptor.getDigest())); - // Push config - copyConfig(manifest, source, effectiveSourceRef, target, effectiveTargetRef); + if (!recursive && source.isIndexMediaType(manifestDescriptor.getMediaType())) { + LOG.debug( + "Encountered index media type {} in index {}, skipping manifest {} due to non-recursive copy", + manifestDescriptor.getMediaType(), + manifestDigest, + manifestDescriptor.getDigest()); + index = index.removeDescriptor(manifestDescriptor); + continue; + } + + // Copy manifest + if (source.isManifestMediaType(manifestDescriptor.getMediaType())) { + Manifest manifest = + source.getManifest(effectiveSourceRef.withDigest(manifestDescriptor.getDigest())); + + // Push config + copyConfig(manifest, source, effectiveSourceRef, target, effectiveTargetRef); - // Push the manifest - LOG.debug("Copying manifest {}", manifestDigest); - target.pushManifest( - effectiveTargetRef.withDigest(manifest.getDigest()), - manifest.withDescriptor(manifestDescriptor)); - LOG.debug("Copied manifest {}", manifestDigest); + // Push the manifest + LOG.debug("Copying nested manifest {}", manifestDigest); + target.pushManifest( + effectiveTargetRef.withDigest(manifest.getDigest()), + manifest.withDescriptor(manifestDescriptor)); + LOG.debug("Copied nested manifest {}", manifestDigest); + } else if (source.isIndexMediaType(manifestDescriptor.getMediaType())) { + // Copy index of index + LOG.debug("Copying nested index {}", manifestDigest); + copy( + source, + effectiveSourceRef.withDigest(manifestDescriptor.getDigest()), + target, + effectiveTargetRef.withDigest(manifestDescriptor.getDigest()), + options); + LOG.debug("Copied nested index {}", manifestDigest); + } } LOG.debug("Copying index {}", manifestDigest); - target.pushIndex(effectiveTargetRef.withDigest(tag), index); + index = target.pushIndex(effectiveTargetRef.withDigest(tag), index); LOG.debug("Copied index {}", manifestDigest); } else { diff --git a/src/main/java/land/oras/Index.java b/src/main/java/land/oras/Index.java index 03d48fb..d5ca560 100644 --- a/src/main/java/land/oras/Index.java +++ b/src/main/java/land/oras/Index.java @@ -203,6 +203,30 @@ public String getArtifactTypeAsString() { return artifactType; } + /** + * Return a new index with the given manifest descriptor removed from the index + * @param descriptor The manifest descriptor to remove + * @return The index with the manifest descriptor removed + */ + public Index removeDescriptor(ManifestDescriptor descriptor) { + List newManifests = new LinkedList<>(); + for (ManifestDescriptor d : manifests) { + if (!d.getDigest().equals(descriptor.getDigest())) { + newManifests.add(ManifestDescriptor.fromJson(d.toJson())); + } + } + return new Index( + schemaVersion, + mediaType, + ArtifactType.from(artifactType), + newManifests, + annotations, + subject, + this.descriptor, + registry, + json); + } + /** * Return a new index with new manifest added to index * @param manifest The manifest diff --git a/src/main/java/land/oras/OCI.java b/src/main/java/land/oras/OCI.java index f8be7dd..8865eaf 100644 --- a/src/main/java/land/oras/OCI.java +++ b/src/main/java/land/oras/OCI.java @@ -124,28 +124,54 @@ public Layer pushBlob(T ref, InputStream input) { * @param ref The ref * @param contentType The content type * @param includeAll Include all layers or only the ones with title annotation + * @param deep Whether to collect layers of index of index * @return The layers */ - protected List collectLayers(T ref, String contentType, boolean includeAll) { + protected List collectLayers(T ref, String contentType, boolean includeAll, boolean deep) { List layers = new LinkedList<>(); if (isManifestMediaType(contentType)) { return getManifest(ref).getLayers(); } Index index = getIndex(ref); for (ManifestDescriptor manifestDescriptor : index.getManifests()) { - List manifestLayers = - getManifest(ref.withDigest(manifestDescriptor.getDigest())).getLayers(); - for (Layer manifestLayer : manifestLayers) { - if (manifestLayer.getAnnotations().isEmpty() - || !manifestLayer.getAnnotations().containsKey(Const.ANNOTATION_TITLE)) { - if (includeAll) { - LOG.debug("Including layer without title annotation: {}", manifestLayer.getDigest()); - layers.add(manifestLayer); + String manifestContentType = manifestDescriptor.getMediaType(); + // We just skip unknown media type descriptor + if (!isManifestMediaType(manifestContentType) && !isIndexMediaType(manifestContentType)) { + LOG.info( + "Unrecognized content type {}, skipping descriptor {}", + manifestContentType, + manifestDescriptor.getDigest()); + continue; + } + // Shallow + if (!deep && isIndexMediaType(manifestContentType)) { + LOG.debug( + "Skipping index of index due to shallow copy of manifest with digest {}", + manifestDescriptor.getDigest()); + continue; + } + // Deep copy + if (isIndexMediaType(manifestContentType)) { + LOG.debug("Collecting layers from index of index with digest {}", manifestDescriptor.getDigest()); + layers.addAll(collectLayers( + ref.withDigest(manifestDescriptor.getDigest()), manifestContentType, includeAll, deep)); + } + // Collect layer for each manifest + else { + List manifestLayers = getManifest(ref.withDigest(manifestDescriptor.getDigest())) + .getLayers(); + for (Layer manifestLayer : manifestLayers) { + if (manifestLayer.getAnnotations().isEmpty() + || !manifestLayer.getAnnotations().containsKey(Const.ANNOTATION_TITLE)) { + if (includeAll) { + LOG.debug("Including layer without title annotation: {}", manifestLayer.getDigest()); + layers.add(manifestLayer); + } + LOG.debug("Skipping layer without title annotation: {}", manifestLayer.getDigest()); + continue; } - LOG.debug("Skipping layer without title annotation: {}", manifestLayer.getDigest()); - continue; + layers.add(manifestLayer); } - layers.add(manifestLayer); } } return layers; diff --git a/src/main/java/land/oras/Registry.java b/src/main/java/land/oras/Registry.java index 07562c8..6923707 100644 --- a/src/main/java/land/oras/Registry.java +++ b/src/main/java/land/oras/Registry.java @@ -332,7 +332,7 @@ public void pullArtifact(ContainerRef containerRef, Path path, boolean overwrite } // Only collect layer that are files String contentType = getContentType(ref); - List layers = collectLayers(ref, contentType, false); + List layers = collectLayers(ref, contentType, false, false); if (layers.isEmpty() || layers.stream().noneMatch(layer -> layer.getAnnotations().containsKey(Const.ANNOTATION_TITLE))) { LOG.info("Skipped pulling layers without file name in '{}'", Const.ANNOTATION_TITLE); diff --git a/src/test/java/land/oras/DockerIoITCase.java b/src/test/java/land/oras/DockerIoITCase.java index 4d374a9..d013829 100644 --- a/src/test/java/land/oras/DockerIoITCase.java +++ b/src/test/java/land/oras/DockerIoITCase.java @@ -99,13 +99,8 @@ void shouldCopyTagToInternalRegistry() { ContainerRef containerTarget = ContainerRef.parse("%s/docker/library/alpine:latest".formatted(unsecureRegistry.getRegistry())); - // CopyUtils.copy(sourceRegistry, containerSource, targetRegistry, containerTarget, true); - // assertTrue(targetRegistry.exists(containerTarget)); - - Index index = targetRegistry.getIndex(containerSource); - - // Ensure standard platform matching - assertTrue(index.getManifests().stream().anyMatch(m -> m.getPlatform().equals(Platform.linux386()))); + CopyUtils.copy(sourceRegistry, containerSource, targetRegistry, containerTarget, CopyUtils.CopyOptions.deep()); + assertTrue(targetRegistry.exists(containerTarget)); } @Test @@ -156,7 +151,8 @@ void shouldCopyTagToInternalRegistryViaAlias(@TempDir Path homeDir) throws Excep ContainerRef containerTarget = ContainerRef.parse("%s/docker/library/alpine:latest".formatted(unsecureRegistry.getRegistry())); - CopyUtils.copy(sourceRegistry, containerSource, targetRegistry, containerTarget, true); + CopyUtils.copy( + sourceRegistry, containerSource, targetRegistry, containerTarget, CopyUtils.CopyOptions.deep()); assertTrue(targetRegistry.exists(containerTarget)); }); } diff --git a/src/test/java/land/oras/GitHubContainerRegistryITCase.java b/src/test/java/land/oras/GitHubContainerRegistryITCase.java index 6740d08..16f588f 100644 --- a/src/test/java/land/oras/GitHubContainerRegistryITCase.java +++ b/src/test/java/land/oras/GitHubContainerRegistryITCase.java @@ -120,7 +120,7 @@ void shouldCopyTagToInternalRegistry() { ContainerRef containerTarget = ContainerRef.parse("%s/docker/library/oras:main".formatted(unsecureRegistry.getRegistry())); - CopyUtils.copy(sourceRegistry, containerSource, targetRegistry, containerTarget, true); + CopyUtils.copy(sourceRegistry, containerSource, targetRegistry, containerTarget, CopyUtils.CopyOptions.deep()); assertTrue(targetRegistry.exists(containerTarget)); } } diff --git a/src/test/java/land/oras/OCILayoutTest.java b/src/test/java/land/oras/OCILayoutTest.java index 0767c71..ebd6d0c 100644 --- a/src/test/java/land/oras/OCILayoutTest.java +++ b/src/test/java/land/oras/OCILayoutTest.java @@ -705,7 +705,7 @@ void testShouldCopyArtifactFromRegistryIntoOciLayout() throws IOException { registry.attachArtifact(containerRef, ArtifactType.from("application/foo"), LocalPath.of(file2)); // Copy to oci layout - CopyUtils.copy(registry, containerRef, ociLayout, layoutRef, false); + CopyUtils.copy(registry, containerRef, ociLayout, layoutRef, CopyUtils.CopyOptions.shallow()); assertOciLayout(layoutPath); @@ -736,7 +736,7 @@ void testShouldCopyFromOciLayoutIntoOciLayoutRecursive() throws IOException { OCILayout target = OCILayout.builder().defaults(targetRef.getFolder()).build(); // Copy to oci layout - CopyUtils.copy(source, sourceRef, target, targetRef, true); + CopyUtils.copy(source, sourceRef, target, targetRef, CopyUtils.CopyOptions.deep()); // Assertion assertOciLayout(ociLayoutPath); @@ -766,7 +766,7 @@ void testShouldCopyFromOciLayoutIntoOciLayoutNonRecursive() throws IOException { OCILayout target = OCILayout.builder().defaults(targetRef.getFolder()).build(); // Copy to oci layout - CopyUtils.copy(source, sourceRef, target, targetRef, false); + CopyUtils.copy(source, sourceRef, target, targetRef, CopyUtils.CopyOptions.shallow()); // Assertion assertOciLayout(ociLayoutPath); @@ -814,7 +814,7 @@ void testShouldCopyRecursivelyArtifactFromRegistryIntoOciLayout() throws IOExcep LocalPath.of(file3)); // Copy to oci layout - CopyUtils.copy(registry, containerRef, ociLayout, layoutRef, true); + CopyUtils.copy(registry, containerRef, ociLayout, layoutRef, CopyUtils.CopyOptions.deep()); assertOciLayout(layoutPath); @@ -865,7 +865,7 @@ void testShouldCopyImageIntoOciLayoutWithoutIndexAndTag() { Manifest pushedManifest = registry.pushManifest(containerRef, emptyManifest); // Copy to oci layout - CopyUtils.copy(registry, containerRef, ociLayout, layoutRef, true); + CopyUtils.copy(registry, containerRef, ociLayout, layoutRef, CopyUtils.CopyOptions.deep()); assertOciLayout(layoutPath); @@ -895,7 +895,7 @@ void testShouldCopyImageIntoOciLayoutWithoutIndexAndTag() { assertLayerExists(layoutPath, layer2); // Copy to oci layout again - CopyUtils.copy(registry, containerRef, ociLayout, layoutRef, true); + CopyUtils.copy(registry, containerRef, ociLayout, layoutRef, CopyUtils.CopyOptions.deep()); // Check manifest exists assertBlobExists(layoutPath, pushedManifest.getDescriptor().getDigest()); @@ -941,7 +941,7 @@ void testShouldCopyImageIntoOciLayoutWithIndex() { Index index = registry.pushIndex(containerRef, Index.fromManifests(List.of(pushedManifest.getDescriptor()))); // Copy to oci layout - CopyUtils.copy(registry, containerRef, ociLayout, layoutRef, true); + CopyUtils.copy(registry, containerRef, ociLayout, layoutRef, CopyUtils.CopyOptions.deep()); assertOciLayout(layoutPathIndex); @@ -971,7 +971,7 @@ void testShouldCopyImageIntoOciLayoutWithIndex() { assertBlobExists(layoutPathIndex, pushedManifest.getDescriptor().getDigest()); // Copy to oci layout again - CopyUtils.copy(registry, containerRef, ociLayout, layoutRef, true); + CopyUtils.copy(registry, containerRef, ociLayout, layoutRef, CopyUtils.CopyOptions.deep()); // Check manifest exists assertLayerExists(layoutPathIndex, layer1); @@ -1007,7 +1007,7 @@ void testShouldCopyIntoOciLayoutWithBlobConfig() throws IOException { containerRef, ArtifactType.from("my/artifact"), Annotations.empty(), config, LocalPath.of(file1)); // Copy to oci layout - CopyUtils.copy(registry, containerRef, ociLayout, layoutRef, true); + CopyUtils.copy(registry, containerRef, ociLayout, layoutRef, CopyUtils.CopyOptions.deep()); assertOciLayout(layoutPath); diff --git a/src/test/java/land/oras/RegistryTest.java b/src/test/java/land/oras/RegistryTest.java index 3aea737..411c2ce 100644 --- a/src/test/java/land/oras/RegistryTest.java +++ b/src/test/java/land/oras/RegistryTest.java @@ -936,6 +936,104 @@ void testShouldFailReferrerWithoutDigest() { "Digest is required to get referrers"); } + @Test + void testShouldCopyIndexOfIndexArtifactFromRegistryIntoRegistryWithShallowCopy() throws IOException { + // Copy to same registry + Registry registry = Registry.Builder.builder() + .defaults("myuser", "mypass") + .withInsecure(true) + .build(); + + ContainerRef containerSource = + ContainerRef.parse("%s/library/artifact-source".formatted(this.registry.getRegistry())); + + // Create files + Path file1 = blobDir.resolve("source1.txt"); + Files.writeString(file1, "foobar"); + Path file2 = blobDir.resolve("source2.txt"); + Files.writeString(file2, "barfoo"); + + // Push individual manifests + Manifest manifest1 = registry.pushArtifact(containerSource.withTag("manifest1"), LocalPath.of(file1)); + Manifest manifest2 = registry.pushArtifact(containerSource.withTag("manifest2"), LocalPath.of(file2)); + + assertNotNull(manifest1.getDescriptor(), "Manifest 1 descriptor should not be null"); + assertNotNull(manifest2.getDescriptor(), "Manifest 2 descriptor should not be null"); + + Index index1 = Index.fromManifests(List.of(manifest1.getDescriptor())); + + // Push first index + index1 = registry.pushIndex(containerSource.withTag("index1"), index1); + assertNotNull(index1.getDescriptor(), "Index 1 descriptor should not be null"); + + Index index2 = Index.fromManifests(List.of(manifest2.getDescriptor(), index1.getDescriptor())); + registry.pushIndex(containerSource.withTag("index2"), index2); + + // Copy to other registry + try (RegistryContainer otherRegistryContainer = new RegistryContainer()) { + otherRegistryContainer.start(); + ContainerRef containerTarget = + ContainerRef.parse("%s/library/artifact-target".formatted(otherRegistryContainer.getRegistry())); + CopyUtils.copy( + registry, + containerSource.withTag("index2"), + registry, + containerTarget.withTag("index2"), + CopyUtils.CopyOptions.shallow()); + Index index = registry.getIndex(containerTarget.withTag("index2")); + assertEquals(1, index.getManifests().size(), "Index should have 1 manifests due to shallow copy"); + } + } + + @Test + void testShouldCopyIndexOfIndexArtifactFromRegistryIntoRegistryWithDeepCopy() throws IOException { + // Copy to same registry + Registry registry = Registry.Builder.builder() + .defaults("myuser", "mypass") + .withInsecure(true) + .build(); + + ContainerRef containerSource = + ContainerRef.parse("%s/library/artifact-source".formatted(this.registry.getRegistry())); + + // Create files + Path file1 = blobDir.resolve("source1.txt"); + Files.writeString(file1, "foobar"); + Path file2 = blobDir.resolve("source2.txt"); + Files.writeString(file2, "barfoo"); + + // Push individual manifests + Manifest manifest1 = registry.pushArtifact(containerSource.withTag("manifest1"), LocalPath.of(file1)); + Manifest manifest2 = registry.pushArtifact(containerSource.withTag("manifest2"), LocalPath.of(file2)); + + assertNotNull(manifest1.getDescriptor(), "Manifest 1 descriptor should not be null"); + assertNotNull(manifest2.getDescriptor(), "Manifest 2 descriptor should not be null"); + + Index index1 = Index.fromManifests(List.of(manifest1.getDescriptor())); + + // Push first index + index1 = registry.pushIndex(containerSource.withTag("index1"), index1); + assertNotNull(index1.getDescriptor(), "Index 1 descriptor should not be null"); + + Index index2 = Index.fromManifests(List.of(manifest2.getDescriptor(), index1.getDescriptor())); + index2 = registry.pushIndex(containerSource.withTag("index2"), index2); + + // Copy to other registry + try (RegistryContainer otherRegistryContainer = new RegistryContainer()) { + otherRegistryContainer.start(); + ContainerRef containerTarget = + ContainerRef.parse("%s/library/artifact-target".formatted(otherRegistryContainer.getRegistry())); + CopyUtils.copy( + registry, + containerSource.withTag("index2"), + registry, + containerTarget.withTag("index2"), + CopyUtils.CopyOptions.deep()); + Index index = registry.getIndex(containerTarget.withTag("index2")); + assertEquals(2, index.getManifests().size(), "Index should have 1 manifests due to shallow copy"); + } + } + @Test void testShouldCopySingleArtifactFromRegistryIntoRegistry() throws IOException { // Copy to same registry @@ -958,7 +1056,7 @@ void testShouldCopySingleArtifactFromRegistryIntoRegistry() throws IOException { otherRegistryContainer.start(); ContainerRef containerTarget = ContainerRef.parse("%s/library/artifact-target".formatted(otherRegistryContainer.getRegistry())); - CopyUtils.copy(registry, containerSource, registry, containerTarget, false); + CopyUtils.copy(registry, containerSource, registry, containerTarget, CopyUtils.CopyOptions.shallow()); registry.pullArtifact(containerTarget, artifactDir, true); assertEquals("foobar", Files.readString(artifactDir.resolve("source.txt"))); } @@ -1051,7 +1149,8 @@ void testShouldCopyFromAliasToAlias(@TempDir Path homeDir) throws Exception { // Copy to other registry ContainerRef containerTarget = ContainerRef.parse("the-target"); - CopyUtils.copy(registry, containerSource, registry, containerTarget, false); + CopyUtils.copy( + registry, containerSource, registry, containerTarget, CopyUtils.CopyOptions.shallow()); registry.pullArtifact(containerTarget, artifactDir, true); assertEquals("foobar", Files.readString(artifactDir.resolve("source.txt"))); } catch (Exception e) { @@ -1076,7 +1175,7 @@ void testShouldCopyFromOciLayoutToRegistryNonRecursive() throws IOException { OCILayout ociLayout = OCILayout.Builder.builder().defaults(layoutRef.getFolder()).build(); - CopyUtils.copy(ociLayout, layoutRef, registry, targetRef, false); + CopyUtils.copy(ociLayout, layoutRef, registry, targetRef, CopyUtils.CopyOptions.shallow()); // Pull Path extractPath = artifactDir.resolve("testShouldCopyFromOciLayoutToRegistryNonRecursive"); @@ -1113,7 +1212,7 @@ void testShouldCopyFromOciLayoutToRegistryRecursive() throws IOException { OCILayout ociLayout = OCILayout.Builder.builder().defaults(layoutRef.getFolder()).build(); - CopyUtils.copy(ociLayout, layoutRef, registry, targetRef, true); + CopyUtils.copy(ociLayout, layoutRef, registry, targetRef, CopyUtils.CopyOptions.deep()); // Pull Path extractPath = artifactDir.resolve("testShouldCopyFromOciLayoutToRegistryRecursive");