Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,6 @@
<spotless.check.skip>false</spotless.check.skip>
<enforcer.skip>false</enforcer.skip>

<maven.compiler.release>17</maven.compiler.release>

<!-- Version -->
<slf4j.version>2.0.16</slf4j.version>
<docker-java.version>3.4.1</docker-java.version>
Expand All @@ -48,6 +46,11 @@
<maven-enforcer-plugin.version>3.5.0</maven-enforcer-plugin.version>
<maven-javadoc-plugin.version>3.11.2</maven-javadoc-plugin.version>

<!-- Target Java 17 -->
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<maven.compiler.release>17</maven.compiler.release>

</properties>

<dependencyManagement>
Expand Down
1 change: 1 addition & 0 deletions src/main/java/land/oras/Registry.java
Original file line number Diff line number Diff line change
Expand Up @@ -562,6 +562,7 @@ private boolean switchTokenAuth(OrasHttpClient.ResponseWrapper<String> response)
private void handleError(OrasHttpClient.ResponseWrapper<?> responseWrapper) {
if (responseWrapper.statusCode() >= 400) {
if (responseWrapper.response() instanceof String) {
LOG.debug("Response: {}", responseWrapper.response());
throw new OrasException((OrasHttpClient.ResponseWrapper<String>) responseWrapper);
}
throw new OrasException(new OrasHttpClient.ResponseWrapper<>("", responseWrapper.statusCode(), Map.of()));
Expand Down
3 changes: 3 additions & 0 deletions src/test/java/land/oras/AnnotationsTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,10 @@

import java.util.Map;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.parallel.Execution;
import org.junit.jupiter.api.parallel.ExecutionMode;

@Execution(ExecutionMode.CONCURRENT)
public class AnnotationsTest {

@Test
Expand Down
3 changes: 3 additions & 0 deletions src/test/java/land/oras/ContainerRefTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,10 @@
import static org.junit.jupiter.api.Assertions.assertNull;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.parallel.Execution;
import org.junit.jupiter.api.parallel.ExecutionMode;

@Execution(ExecutionMode.CONCURRENT)
public class ContainerRefTest {

@Test
Expand Down
3 changes: 3 additions & 0 deletions src/test/java/land/oras/LayerTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,10 @@
import static org.junit.jupiter.api.Assertions.assertEquals;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.parallel.Execution;
import org.junit.jupiter.api.parallel.ExecutionMode;

@Execution(ExecutionMode.CONCURRENT)
public class LayerTest {

@Test
Expand Down
5 changes: 3 additions & 2 deletions src/test/java/land/oras/ManifestTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,12 @@
import static org.junit.jupiter.api.Assertions.assertEquals;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.parallel.Execution;
import org.junit.jupiter.api.parallel.ExecutionMode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.testcontainers.junit.jupiter.Testcontainers;

@Testcontainers
@Execution(ExecutionMode.CONCURRENT)
public class ManifestTest {

/**
Expand Down
243 changes: 243 additions & 0 deletions src/test/java/land/oras/RegistryContainerTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,243 @@
package land.oras;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;

import com.github.tomakehurst.wiremock.client.WireMock;
import com.github.tomakehurst.wiremock.junit5.WireMockRuntimeInfo;
import com.github.tomakehurst.wiremock.junit5.WireMockTest;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
import java.util.Map;
import land.oras.utils.Const;
import land.oras.utils.JsonUtils;
import land.oras.utils.RegistryContainer;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import org.junit.jupiter.api.parallel.Execution;
import org.junit.jupiter.api.parallel.ExecutionMode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;

@Testcontainers
@WireMockTest
@Execution(ExecutionMode.CONCURRENT)
public class RegistryContainerTest {

private static final Logger LOG = LoggerFactory.getLogger(RegistryContainerTest.class);

@Container
private final RegistryContainer registry = new RegistryContainer().withStartupAttempts(3);

/**
* Blob temporary dir
*/
@TempDir
private Path blobDir;

@TempDir
private Path artifactDir;

@BeforeEach
void before() {
registry.withFollowOutput();
}

@Test
void shouldListTags(WireMockRuntimeInfo wmRuntimeInfo) {

// Return data from wiremock
WireMock wireMock = wmRuntimeInfo.getWireMock();
wireMock.register(WireMock.get(WireMock.urlEqualTo("/v2/library/artifact-text/tags/list"))
.willReturn(WireMock.okJson(JsonUtils.toJson(new Tags("artifact-text", List.of("latest", "0.1.1"))))));

// Insecure registry
Registry registry = Registry.Builder.builder().withInsecure(true).build();

// Test
List<String> tags = registry.getTags(ContainerRef.parse("%s/library/artifact-text"
.formatted(wmRuntimeInfo.getHttpBaseUrl().replace("http://", ""))));

// Assert
assertEquals(2, tags.size());
assertEquals("latest", tags.get(0));
assertEquals("0.1.1", tags.get(1));
}

@Test
void shouldPushAndGetBlobThenDelete() {
Registry registry = Registry.Builder.builder()
.withInsecure(true)
.withSkipTlsVerify(true)
.build();
ContainerRef containerRef =
ContainerRef.parse("%s/library/artifact-text".formatted(this.registry.getRegistry()));
Layer layer = registry.pushBlob(containerRef, "hello".getBytes());
assertEquals("sha256:2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824", layer.getDigest());
byte[] blob = registry.getBlob(
containerRef.withDigest("sha256:2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824"));
assertEquals("hello", new String(blob));
registry.pushBlob(containerRef, "hello".getBytes());
registry.deleteBlob(
containerRef.withDigest("sha256:2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824"));

// Ensure the blob is deleted
assertThrows(OrasException.class, () -> {
registry.getBlob(
containerRef.withDigest("sha256:2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824"));
});
}

@Test
void shouldUploadAndFetchBlobThenDelete() throws IOException {
Registry registry = Registry.Builder.builder()
.withInsecure(true)
.withSkipTlsVerify(true)
.build();
ContainerRef containerRef =
ContainerRef.parse("%s/library/artifact-text".formatted(this.registry.getRegistry()));
Files.createFile(blobDir.resolve("temp.txt"));
Files.writeString(blobDir.resolve("temp.txt"), "hello");
Layer layer = registry.uploadBlob(containerRef, blobDir.resolve("temp.txt"));
assertEquals("sha256:2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824", layer.getDigest());

registry.fetchBlob(
containerRef.withDigest("sha256:2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824"),
blobDir.resolve("temp.txt"));

try (InputStream is = registry.fetchBlob(
containerRef.withDigest("sha256:2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824"))) {
assertEquals("hello", new String(is.readAllBytes()));
}

assertEquals("hello", Files.readString(blobDir.resolve("temp.txt")));
registry.deleteBlob(
containerRef.withDigest("sha256:2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824"));

// Ensure the blob is deleted
assertThrows(OrasException.class, () -> {
registry.getBlob(
containerRef.withDigest("sha256:2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824"));
});
}

@Test
void shouldPushAndGetManifestThenDelete() {
Registry registry = Registry.Builder.builder()
.withInsecure(true)
.withSkipTlsVerify(true)
.build();

// Empty manifest
ContainerRef containerRef =
ContainerRef.parse("%s/library/empty-manifest".formatted(this.registry.getRegistry()));
Layer emptyLayer = registry.pushBlob(containerRef, Layer.empty().getDataBytes());
Manifest emptyManifest = Manifest.empty().withLayers(List.of(Layer.fromDigest(emptyLayer.getDigest(), 2)));
String location = registry.pushManifest(containerRef, emptyManifest);
assertEquals(
"http://%s/v2/library/empty-manifest/manifests/sha256:f570eb29564f04e73d15cc2a2bb4153d488b9e8428c7f5108b895baa379750bd"
.formatted(this.registry.getRegistry()),
location);
Manifest manifest = registry.getManifest(containerRef);

// Assert
assertEquals(2, manifest.getSchemaVersion());
assertEquals(Const.DEFAULT_MANIFEST_MEDIA_TYPE, manifest.getMediaType());
assertEquals(Config.empty().getDigest(), manifest.getConfig().getDigest());
assertEquals(1, manifest.getLayers().size()); // One empty layer
Layer layer = manifest.getLayers().get(0);

// An empty layer
assertEquals(2, layer.getSize());
assertEquals(Const.DEFAULT_EMPTY_MEDIA_TYPE, layer.getMediaType());

assertNull(manifest.getArtifactType());
assertTrue(manifest.getAnnotations().isEmpty());

// Push again
registry.pushManifest(containerRef, manifest);

// Delete manifest
registry.deleteManifest(containerRef);
// Ensure the blob is deleted
assertThrows(OrasException.class, () -> {
registry.getManifest(containerRef);
});
}

@Test
void testShouldPushAndPullMinimalArtifact() throws IOException {

Registry registry = Registry.Builder.builder()
.withInsecure(true)
.withSkipTlsVerify(true)
.build();
ContainerRef containerRef =
ContainerRef.parse("%s/library/artifact-full".formatted(this.registry.getRegistry()));

Path file1 = blobDir.resolve("file1.txt");
Files.writeString(file1, "foobar");

// Upload
Manifest manifest = registry.pushArtifact(containerRef, file1);
assertEquals(1, manifest.getLayers().size());

Layer layer = manifest.getLayers().get(0);

// A test file layer
assertEquals(6, layer.getSize());
assertEquals("text/plain", layer.getMediaType());
assertEquals("sha256:c3ab8ff13720e8ad9047dd39466b3c8974e592c2fa383d4a3960714caef0c4f2", layer.getDigest());

Map<String, String> annotations = layer.getAnnotations();

// Assert annotations of the layer
assertEquals(1, annotations.size());
assertEquals("file1.txt", annotations.get(Const.ANNOTATION_TITLE));

// Pull
registry.pullArtifact(containerRef, artifactDir, true);
assertEquals("foobar", Files.readString(artifactDir.resolve("file1.txt")));
}

@Test
void testShouldPushCompressedDirectory() throws IOException {

Registry registry = Registry.Builder.builder()
.withInsecure(true)
.withSkipTlsVerify(true)
.build();
ContainerRef containerRef =
ContainerRef.parse("%s/library/artifact-full".formatted(this.registry.getRegistry()));

Path file1 = blobDir.resolve("file1.txt");
Path file2 = blobDir.resolve("file2.txt");
Path file3 = blobDir.resolve("file3.txt");
Files.writeString(file1, "foobar");
Files.writeString(file2, "test1234");
Files.writeString(file3, "barfoo");

// Upload blob dir
Manifest manifest = registry.pushArtifact(containerRef, blobDir);
assertEquals(1, manifest.getLayers().size());

Layer layer = manifest.getLayers().get(0);

// A compressed directory file
assertEquals(Const.DEFAULT_BLOB_DIR_MEDIA_TYPE, layer.getMediaType());
Map<String, String> annotations = layer.getAnnotations();

// Assert annotations of the layer
assertEquals(2, annotations.size());
assertEquals(blobDir.getFileName().toString(), annotations.get(Const.ANNOTATION_TITLE));
assertEquals("true", annotations.get(Const.ANNOTATION_ORAS_UNPACK));
}
}
29 changes: 3 additions & 26 deletions src/test/java/land/oras/RegistryTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,28 +5,26 @@
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;

import com.github.tomakehurst.wiremock.client.WireMock;
import com.github.tomakehurst.wiremock.junit5.WireMockRuntimeInfo;
import com.github.tomakehurst.wiremock.junit5.WireMockTest;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
import java.util.Map;
import land.oras.utils.Const;
import land.oras.utils.JsonUtils;
import land.oras.utils.RegistryContainer;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import org.junit.jupiter.api.parallel.Execution;
import org.junit.jupiter.api.parallel.ExecutionMode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;

@Testcontainers
@WireMockTest
@Execution(ExecutionMode.CONCURRENT)
public class RegistryTest {

private static final Logger LOG = LoggerFactory.getLogger(RegistryTest.class);
Expand All @@ -48,27 +46,6 @@ void before() {
registry.withFollowOutput();
}

@Test
void shouldListTags(WireMockRuntimeInfo wmRuntimeInfo) {

// Return data from wiremock
WireMock wireMock = wmRuntimeInfo.getWireMock();
wireMock.register(WireMock.get(WireMock.urlEqualTo("/v2/library/artifact-text/tags/list"))
.willReturn(WireMock.okJson(JsonUtils.toJson(new Tags("artifact-text", List.of("latest", "0.1.1"))))));

// Insecure registry
Registry registry = Registry.Builder.builder().withInsecure(true).build();

// Test
List<String> tags = registry.getTags(ContainerRef.parse("%s/library/artifact-text"
.formatted(wmRuntimeInfo.getHttpBaseUrl().replace("http://", ""))));

// Assert
assertEquals(2, tags.size());
assertEquals("latest", tags.get(0));
assertEquals("0.1.1", tags.get(1));
}

@Test
void shouldPushAndGetBlobThenDelete() {
Registry registry = Registry.Builder.builder()
Expand Down
Loading