diff --git a/src/main/java/land/oras/OCI.java b/src/main/java/land/oras/OCI.java index 4275591c..5fafa6de 100644 --- a/src/main/java/land/oras/OCI.java +++ b/src/main/java/land/oras/OCI.java @@ -167,11 +167,15 @@ protected final List pushLayers(T ref, boolean withDigest, LocalPath... p ref = ref.withDigest(ref.getAlgorithm().digest(tempArchive.getPath())); } try (InputStream is = Files.newInputStream(tempArchive.getPath())) { + String title = path.getPath().isAbsolute() + ? path.getPath().getFileName().toString() + : path.getPath().toString(); + LOG.debug("Uploading directory as archive with title: {}", title); Layer layer = pushBlob(ref, is) .withMediaType(path.getMediaType()) .withAnnotations(Map.of( Const.ANNOTATION_TITLE, - path.getPath().getFileName().toString(), + title, Const.ANNOTATION_ORAS_CONTENT_DIGEST, ref.getAlgorithm().digest(tempTar.getPath()), Const.ANNOTATION_ORAS_UNPACK, diff --git a/src/main/java/land/oras/utils/ArchiveUtils.java b/src/main/java/land/oras/utils/ArchiveUtils.java index 860b1c6b..477c0e07 100644 --- a/src/main/java/land/oras/utils/ArchiveUtils.java +++ b/src/main/java/land/oras/utils/ArchiveUtils.java @@ -93,6 +93,7 @@ public static Path createTempDir() { */ public static LocalPath tar(LocalPath sourceDir) { Path tarFile = createTempTar(); + boolean isAbsolute = sourceDir.getPath().isAbsolute(); try (OutputStream fos = Files.newOutputStream(tarFile); // Output stream chain @@ -104,8 +105,8 @@ public static LocalPath tar(LocalPath sourceDir) { paths.forEach(path -> { LOG.trace("Visiting path: {}", path); try { - - Path relativePath = sourceDir.getPath().relativize(path); + Path baseName = isAbsolute ? sourceDir.getPath().getFileName() : sourceDir.getPath(); + Path relativePath = baseName.resolve(sourceDir.getPath().relativize(path)); if (relativePath.toString().isEmpty()) { LOG.trace("Skipping root directory: {}", path); return; diff --git a/src/test/java/land/oras/RegistryTest.java b/src/test/java/land/oras/RegistryTest.java index bd0e9319..9662a9c0 100644 --- a/src/test/java/land/oras/RegistryTest.java +++ b/src/test/java/land/oras/RegistryTest.java @@ -905,13 +905,14 @@ void testShouldPushAndPullCompressedTarGzDirectory() throws IOException { registry.pullArtifact(containerRef, extractDir, true); // Assert extracted files - assertEquals("foobar", Files.readString(extractDir.resolve("file1.txt"))); - assertEquals("test1234", Files.readString(extractDir.resolve("file2.txt"))); - assertEquals("barfoo", Files.readString(extractDir.resolve("file3.txt"))); + Path extractedDir = extractDir.resolve(blobDir.getFileName()); + assertEquals("foobar", Files.readString(extractedDir.resolve("file1.txt"))); + assertEquals("test1234", Files.readString(extractedDir.resolve("file2.txt"))); + assertEquals("barfoo", Files.readString(extractedDir.resolve("file3.txt"))); } @Test - void testShouldPushAndPullUncompressedTarDirectory() throws IOException { + void testShouldPushAndPullUncompressedTarDirectoryWithAbsolutePath() throws IOException { Registry registry = Registry.Builder.builder() .defaults("myuser", "mypass") @@ -949,9 +950,47 @@ void testShouldPushAndPullUncompressedTarDirectory() throws IOException { registry.pullArtifact(containerRef, extractDir, true); // Assert extracted files - assertEquals("foobar", Files.readString(extractDir.resolve("file1.txt"))); - assertEquals("test1234", Files.readString(extractDir.resolve("file2.txt"))); - assertEquals("barfoo", Files.readString(extractDir.resolve("file3.txt"))); + Path extractedDir = this.extractDir.resolve(blobDir.getFileName()); + assertEquals("foobar", Files.readString(extractedDir.resolve("file1.txt"))); + assertEquals("test1234", Files.readString(extractedDir.resolve("file2.txt"))); + assertEquals("barfoo", Files.readString(extractedDir.resolve("file3.txt"))); + } + + @Test + void testShouldPushAndPullUncompressedTarDirectoryWithRelativePath() throws IOException { + + Registry registry = Registry.Builder.builder() + .defaults("myuser", "mypass") + .withInsecure(true) + .build(); + ContainerRef containerRef = + ContainerRef.parse("%s/library/artifact-relative-path".formatted(this.registry.getRegistry())); + + // Source + Manifest manifest = registry.pushArtifact( + containerRef, LocalPath.of(Path.of("src/main/java"), Const.DEFAULT_BLOB_MEDIA_TYPE)); + assertEquals(1, manifest.getLayers().size()); + + Layer layer = manifest.getLayers().get(0); + + // A compressed directory file + assertEquals(Const.DEFAULT_BLOB_MEDIA_TYPE, layer.getMediaType()); + Map annotations = layer.getAnnotations(); + + // Assert annotations of the layer + assertEquals(3, annotations.size()); + assertEquals("src/main/java", annotations.get(Const.ANNOTATION_TITLE)); // Keep relative path + assertEquals("true", annotations.get(Const.ANNOTATION_ORAS_UNPACK)); + assertEquals( + SupportedAlgorithm.SHA256, + SupportedAlgorithm.fromDigest(annotations.get(Const.ANNOTATION_ORAS_CONTENT_DIGEST))); + + // Pull + registry.pullArtifact(containerRef, extractDir, false); + + // Assert files under src/main/java + Path extractedDir = this.extractDir.resolve("src/main/java"); + assertTrue(Files.exists(extractedDir.resolve("land/oras/Config.java")), "Config.java should exist"); } @Test @@ -993,9 +1032,10 @@ void testShouldPushAndPullCompressedZstdDirectory() throws IOException { registry.pullArtifact(containerRef, extractDir, true); // Assert extracted files - assertEquals("foobar", Files.readString(extractDir.resolve("file1.txt"))); - assertEquals("test1234", Files.readString(extractDir.resolve("file2.txt"))); - assertEquals("barfoo", Files.readString(extractDir.resolve("file3.txt"))); + Path extractedDir = extractDir.resolve(blobDir.getFileName()); + assertEquals("foobar", Files.readString(extractedDir.resolve("file1.txt"))); + assertEquals("test1234", Files.readString(extractedDir.resolve("file2.txt"))); + assertEquals("barfoo", Files.readString(extractedDir.resolve("file3.txt"))); } @Test diff --git a/src/test/java/land/oras/ZotLocalhostITCase.java b/src/test/java/land/oras/ZotLocalhostITCase.java new file mode 100644 index 00000000..814a7bb8 --- /dev/null +++ b/src/test/java/land/oras/ZotLocalhostITCase.java @@ -0,0 +1,38 @@ +/*- + * =LICENSE= + * ORAS Java SDK + * === + * Copyright (C) 2024 - 2025 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 org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.parallel.Execution; +import org.junit.jupiter.api.parallel.ExecutionMode; + +@Execution(ExecutionMode.CONCURRENT) +class ZotLocalhostITCase { + + @Test + @Disabled("Only to test using localhost:5000 container") + void shouldPushArtifactAndPullWithOrasCli() { + Registry registry = Registry.builder().insecure().build(); + ContainerRef containerRef1 = ContainerRef.parse("localhost:5000/foo:java"); + registry.pushArtifact(containerRef1, LocalPath.of("src/main/java")); + } +} diff --git a/src/test/java/land/oras/utils/ArchiveUtilsTest.java b/src/test/java/land/oras/utils/ArchiveUtilsTest.java index 1d64fccb..908c8b95 100644 --- a/src/test/java/land/oras/utils/ArchiveUtilsTest.java +++ b/src/test/java/land/oras/utils/ArchiveUtilsTest.java @@ -43,7 +43,7 @@ import org.slf4j.LoggerFactory; @Execution(ExecutionMode.CONCURRENT) -public class ArchiveUtilsTest { +class ArchiveUtilsTest { /** * Logger @@ -155,34 +155,35 @@ void shouldCreateTarGzAndExtractIt() throws Exception { // Untar to temporary Path tmp = ArchiveUtils.untar(archive.getPath()); assertTrue(Files.exists(tmp), "Temp should exist"); - assertTrue(Files.exists(tmp.resolve("dir1")), "dir1 should exist"); + assertTrue(Files.exists(tmp.resolve(directory.getPath().getFileName()).resolve("dir1")), "dir1 should exist"); // Ensure all files are extracted - assertTrue(Files.exists(targetGzDir.resolve("dir1")), "dir1 should exist"); - assertTrue(Files.exists(targetGzDir.resolve("dir2")), "dir2 should exist"); - assertTrue(Files.exists(targetGzDir.resolve("dir1").resolve("file1")), "file1 should exist"); - assertTrue(Files.exists(targetGzDir.resolve("dir2").resolve("file2")), "file2 should exist"); - assertTrue(Files.exists(targetGzDir.resolve("dir1").resolve("file3")), "file3 should exist"); - assertTrue(Files.exists(targetGzDir.resolve("dir2").resolve("dir3")), "dir3 should exist"); - assertTrue(Files.exists(targetGzDir.resolve("dir2").resolve("dir3").resolve("file4")), "file4 should exist"); + Path extractedDir = targetGzDir.resolve(directory.getPath().getFileName()); + assertTrue(Files.exists(extractedDir.resolve("dir1")), "dir1 should exist"); + assertTrue(Files.exists(extractedDir.resolve("dir2")), "dir2 should exist"); + assertTrue(Files.exists(extractedDir.resolve("dir1").resolve("file1")), "file1 should exist"); + assertTrue(Files.exists(extractedDir.resolve("dir2").resolve("file2")), "file2 should exist"); + assertTrue(Files.exists(extractedDir.resolve("dir1").resolve("file3")), "file3 should exist"); + assertTrue(Files.exists(extractedDir.resolve("dir2").resolve("dir3")), "dir3 should exist"); + assertTrue(Files.exists(extractedDir.resolve("dir2").resolve("dir3").resolve("file4")), "file4 should exist"); // Empty directory - assertTrue(Files.exists(targetGzDir.resolve("empty")), "empty should exist"); + assertTrue(Files.exists(extractedDir.resolve("empty")), "empty should exist"); // Assert file content assertTrue( - Files.readString(targetGzDir.resolve("dir1").resolve("file1")).equals("file1"), + Files.readString(extractedDir.resolve("dir1").resolve("file1")).equals("file1"), "file1 content should match"); assertTrue( - Files.readString(targetGzDir.resolve("dir2").resolve("file2")).equals("file2"), + Files.readString(extractedDir.resolve("dir2").resolve("file2")).equals("file2"), "file2 content should match"); assertTrue( - Files.readString(targetGzDir.resolve("dir2").resolve("dir3").resolve("file4")) + Files.readString(extractedDir.resolve("dir2").resolve("dir3").resolve("file4")) .equals("file4"), "file4 content should match"); // Ensure symlink is extracted - assertTrue(Files.isSymbolicLink(targetGzDir.resolve("dir1").resolve("file3")), "file3 should be symlink"); + assertTrue(Files.isSymbolicLink(extractedDir.resolve("dir1").resolve("file3")), "file3 should be symlink"); // To temporary Path temp = ArchiveUtils.uncompressuntar(compressedArchive, directory.getMediaType()); @@ -207,34 +208,35 @@ void shouldCreateTarZstdAndExtractIt() throws Exception { // Untar to temporary Path tmp = ArchiveUtils.untar(archive.getPath()); assertTrue(Files.exists(tmp), "Temp should exist"); - assertTrue(Files.exists(tmp.resolve("dir1")), "dir1 should exist"); + assertTrue(Files.exists(tmp.resolve(directory.getPath().getFileName()).resolve("dir1")), "dir1 should exist"); // Ensure all files are extracted - assertTrue(Files.exists(targetZstdDir.resolve("dir1")), "dir1 should exist"); - assertTrue(Files.exists(targetZstdDir.resolve("dir2")), "dir2 should exist"); - assertTrue(Files.exists(targetZstdDir.resolve("dir1").resolve("file1")), "file1 should exist"); - assertTrue(Files.exists(targetZstdDir.resolve("dir2").resolve("file2")), "file2 should exist"); - assertTrue(Files.exists(targetZstdDir.resolve("dir1").resolve("file3")), "file3 should exist"); - assertTrue(Files.exists(targetZstdDir.resolve("dir2").resolve("dir3")), "dir3 should exist"); - assertTrue(Files.exists(targetZstdDir.resolve("dir2").resolve("dir3").resolve("file4")), "file4 should exist"); + Path extractedDir = targetZstdDir.resolve(directory.getPath().getFileName()); + assertTrue(Files.exists(extractedDir.resolve("dir1")), "dir1 should exist"); + assertTrue(Files.exists(extractedDir.resolve("dir2")), "dir2 should exist"); + assertTrue(Files.exists(extractedDir.resolve("dir1").resolve("file1")), "file1 should exist"); + assertTrue(Files.exists(extractedDir.resolve("dir2").resolve("file2")), "file2 should exist"); + assertTrue(Files.exists(extractedDir.resolve("dir1").resolve("file3")), "file3 should exist"); + assertTrue(Files.exists(extractedDir.resolve("dir2").resolve("dir3")), "dir3 should exist"); + assertTrue(Files.exists(extractedDir.resolve("dir2").resolve("dir3").resolve("file4")), "file4 should exist"); // Empty directory - assertTrue(Files.exists(targetZstdDir.resolve("empty")), "empty should exist"); + assertTrue(Files.exists(extractedDir.resolve("empty")), "empty should exist"); // Assert file content assertTrue( - Files.readString(targetZstdDir.resolve("dir1").resolve("file1")).equals("file1"), + Files.readString(extractedDir.resolve("dir1").resolve("file1")).equals("file1"), "file1 content should match"); assertTrue( - Files.readString(targetZstdDir.resolve("dir2").resolve("file2")).equals("file2"), + Files.readString(extractedDir.resolve("dir2").resolve("file2")).equals("file2"), "file2 content should match"); assertTrue( - Files.readString(targetZstdDir.resolve("dir2").resolve("dir3").resolve("file4")) + Files.readString(extractedDir.resolve("dir2").resolve("dir3").resolve("file4")) .equals("file4"), "file4 content should match"); // Ensure symlink is extracted - assertTrue(Files.isSymbolicLink(targetZstdDir.resolve("dir1").resolve("file3")), "file3 should be symlink"); + assertTrue(Files.isSymbolicLink(extractedDir.resolve("dir1").resolve("file3")), "file3 should be symlink"); // To temporary Path temp = ArchiveUtils.uncompressuntar(compressedArchive, directory.getMediaType());