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
41 changes: 41 additions & 0 deletions src/main/java/land/oras/Index.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,10 @@
package land.oras;

import java.nio.file.Path;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import land.oras.utils.Const;
import land.oras.utils.JsonUtils;

Expand Down Expand Up @@ -92,6 +95,44 @@ public List<ManifestDescriptor> getManifests() {
return manifests;
}

/**
* Return a new index with new manifest added to index
* @param manifest The manifest
* @return The index
*/
public Index withNewManifests(ManifestDescriptor manifest) {
List<ManifestDescriptor> newManifests = new LinkedList<>();
for (ManifestDescriptor descriptor : manifests) {

// Ignore same digest
if (descriptor.getDigest().equals(manifest.getDigest())) {
continue;
}

// Move previous ref
if (descriptor.getAnnotations() != null
&& descriptor.getAnnotations().containsKey(Const.ANNOTATION_REF)
&& manifest.getAnnotations() != null
&& manifest.getAnnotations().containsKey(Const.ANNOTATION_REF)
&& descriptor
.getAnnotations()
.get(Const.ANNOTATION_REF)
.equals(manifest.getAnnotations().get(Const.ANNOTATION_REF))) {
Map<String, String> newAnnotations = new LinkedHashMap<>(descriptor.getAnnotations());
newAnnotations.remove(Const.ANNOTATION_REF);
if (newAnnotations.isEmpty()) {
newAnnotations = null;
}
newManifests.add(ManifestDescriptor.fromJson(
descriptor.withAnnotations(newAnnotations).toJson()));
continue;
}
newManifests.add(ManifestDescriptor.fromJson(descriptor.toJson()));
}
newManifests.add(manifest);
return new Index(schemaVersion, mediaType, artifactType, newManifests, descriptor, json);
}

/**
* Get the descriptor
* @return The descriptor
Expand Down
11 changes: 10 additions & 1 deletion src/main/java/land/oras/ManifestDescriptor.java
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ public Map<String, String> getPlatform() {
* Get the annotations
* @return The annotations
*/
public Map<String, String> getAnnotations() {
public @Nullable Map<String, String> getAnnotations() {
return annotations;
}

Expand Down Expand Up @@ -137,6 +137,15 @@ public Subject toSubject() {
return Subject.of(mediaType, digest, size);
}

/**
* Create a manifest descriptor with the given annotations
* @param annotations The annotations
* @return The subject
*/
public ManifestDescriptor withAnnotations(@Nullable Map<String, String> annotations) {
return new ManifestDescriptor(artifactType, mediaType, digest, size, platform, annotations);
}

/**
* Create a manifest descriptor
* @param mediaType The media type
Expand Down
8 changes: 8 additions & 0 deletions src/main/java/land/oras/OCI.java
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,14 @@ public abstract Manifest pushArtifact(
*/
public abstract void pullArtifact(T ref, Path path, boolean overwrite);

/**
* Push a manifest
* @param ref The ref
* @param manifest The manifest
* @return The location
*/
public abstract Manifest pushManifest(T ref, Manifest manifest);

/**
* Get the blob for the given digest. Not be suitable for large blobs
* @param ref The ref
Expand Down
37 changes: 35 additions & 2 deletions src/main/java/land/oras/OCILayout.java
Original file line number Diff line number Diff line change
Expand Up @@ -85,8 +85,41 @@ public void pullArtifact(LayoutRef ref, Path path, boolean overwrite) {
}

@Override
public byte[] getBlob(LayoutRef containerRef) {
try (InputStream is = fetchBlob(containerRef)) {
public Manifest pushManifest(LayoutRef layoutRef, Manifest manifest) {

// For portability each layer should have at least one entry
if (manifest.getLayers().isEmpty()) {
Config config = manifest.getConfig();
Layer configLayer = Layer.fromJson(config.toJson());
manifest = manifest.withLayers(List.of(configLayer));
}

// Create the manifest descriptor with ref if tag is present
byte[] manifestData = manifest.toJson().getBytes();
String manifestDigest =
SupportedAlgorithm.getDefault().digest(manifest.toJson().getBytes());
ManifestDescriptor manifestDescriptor =
ManifestDescriptor.of(Const.DEFAULT_MANIFEST_MEDIA_TYPE, manifestDigest, manifestData.length);
if (layoutRef.getTag() != null) {
manifestDescriptor = manifestDescriptor.withAnnotations(Map.of(Const.ANNOTATION_REF, layoutRef.getTag()));
}
manifest = manifest.withDescriptor(manifestDescriptor);

Index index = Index.fromPath(getIndexPath()).withNewManifests(manifestDescriptor);

// Write blobs
try {
writeManifest(manifest);
writeIndex(index);
} catch (IOException e) {
throw new OrasException("Failed to write manifest", e);
}
return manifest;
}

@Override
public byte[] getBlob(LayoutRef layoutRef) {
try (InputStream is = fetchBlob(layoutRef)) {
return is.readAllBytes();
} catch (IOException e) {
throw new OrasException("Failed to get blob", e);
Expand Down
7 changes: 1 addition & 6 deletions src/main/java/land/oras/Registry.java
Original file line number Diff line number Diff line change
Expand Up @@ -176,12 +176,7 @@ public void deleteManifest(ContainerRef containerRef) {
handleError(response);
}

/**
* Push a manifest
* @param containerRef The container
* @param manifest The manifest
* @return The location
*/
@Override
public Manifest pushManifest(ContainerRef containerRef, Manifest manifest) {

Map<String, String> annotations = manifest.getAnnotations();
Expand Down
106 changes: 106 additions & 0 deletions src/test/java/land/oras/IndexTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNull;

import java.util.List;
import java.util.Map;
import land.oras.utils.Const;
import land.oras.utils.SupportedAlgorithm;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.parallel.Execution;
import org.junit.jupiter.api.parallel.ExecutionMode;
Expand All @@ -47,4 +51,106 @@ void shouldReadAndWriteIndex() {
assertEquals(json, index.toJson());
index.toJson();
}

@Test
void shouldAddManifest() {
Index index = Index.fromManifests(List.of());
index = index.withNewManifests(Manifest.empty().getDescriptor());
assertEquals(1, index.getManifests().size());

Manifest newManifest = Manifest.empty().withAnnotations(Map.of("foo", "bar"));
String digest =
SupportedAlgorithm.getDefault().digest(newManifest.toJson().getBytes());
int size = newManifest.toJson().getBytes().length;
ManifestDescriptor descriptor = ManifestDescriptor.of(Const.DEFAULT_MANIFEST_MEDIA_TYPE, digest, size);
newManifest.withDescriptor(descriptor);
index = index.withNewManifests(descriptor);
assertEquals(2, index.getManifests().size());
}

@Test
void shouldNotAddIfSameDigest() {
Index index = Index.fromManifests(List.of());
index = index.withNewManifests(Manifest.empty().getDescriptor());
assertEquals(1, index.getManifests().size());
index = index.withNewManifests(Manifest.empty().getDescriptor());
assertEquals(1, index.getManifests().size());
index = index.withNewManifests(Manifest.empty().getDescriptor());
assertEquals(1, index.getManifests().size());
}

@Test
void shouldMoveRefAndSetNullAnnotations() {
Index index = Index.fromManifests(List.of());
index = index.withNewManifests(
Manifest.empty().getDescriptor().withAnnotations(Map.of(Const.ANNOTATION_REF, "latest")));
assertEquals(1, index.getManifests().size());
index = index.withNewManifests(ManifestDescriptor.of(Const.DEFAULT_MANIFEST_MEDIA_TYPE, "sha256:123", 123)
.withAnnotations(Map.of(Const.ANNOTATION_REF, "latest")));
assertEquals(2, index.getManifests().size());

// Ensure 1st descriptor has null annotations
assertNull(index.getManifests().get(0).getAnnotations());

// Ensure 2nd descriptor has ref
assertEquals("latest", index.getManifests().get(1).getAnnotations().get(Const.ANNOTATION_REF));
}

@Test
void shouldNotMoveRefIfDifferent() {
Index index = Index.fromManifests(List.of());
index = index.withNewManifests(
Manifest.empty().getDescriptor().withAnnotations(Map.of(Const.ANNOTATION_REF, "latest")));
assertEquals(1, index.getManifests().size());
index = index.withNewManifests(ManifestDescriptor.of(Const.DEFAULT_MANIFEST_MEDIA_TYPE, "sha256:123", 123)
.withAnnotations(Map.of(Const.ANNOTATION_REF, "stable")));
assertEquals(2, index.getManifests().size());

// No change
assertEquals("latest", index.getManifests().get(0).getAnnotations().get(Const.ANNOTATION_REF));

// Added ref
assertEquals("stable", index.getManifests().get(1).getAnnotations().get(Const.ANNOTATION_REF));
}

@Test
void shouldKeepExistingAnnotation() {
Index index = Index.fromManifests(List.of());
index = index.withNewManifests(
Manifest.empty().getDescriptor().withAnnotations(Map.of(Const.ANNOTATION_REF, "latest", "foo", "bar")));
assertEquals(1, index.getManifests().size());
index = index.withNewManifests(ManifestDescriptor.of(Const.DEFAULT_MANIFEST_MEDIA_TYPE, "sha256:123", 123)
.withAnnotations(Map.of(Const.ANNOTATION_REF, "latest")));
assertEquals(2, index.getManifests().size());

// One annotation
assertEquals(1, index.getManifests().get(0).getAnnotations().size());
assertEquals("bar", index.getManifests().get(0).getAnnotations().get("foo"));

// Added ref
assertEquals("latest", index.getManifests().get(1).getAnnotations().get(Const.ANNOTATION_REF));

// Add one more
index = index.withNewManifests(ManifestDescriptor.of(Const.DEFAULT_MANIFEST_MEDIA_TYPE, "sha256:532", 123)
.withAnnotations(Map.of("test", "hello")));
assertEquals(3, index.getManifests().size());
assertEquals(1, index.getManifests().get(0).getAnnotations().size());
assertEquals("bar", index.getManifests().get(0).getAnnotations().get("foo"));
assertEquals(1, index.getManifests().get(1).getAnnotations().size());
assertEquals("latest", index.getManifests().get(1).getAnnotations().get(Const.ANNOTATION_REF));
assertEquals(1, index.getManifests().get(2).getAnnotations().size());
assertEquals("hello", index.getManifests().get(2).getAnnotations().get("test"));

// With null annotations
index = index.withNewManifests(ManifestDescriptor.of(Const.DEFAULT_MANIFEST_MEDIA_TYPE, "sha256:789", 123)
.withAnnotations(null));
assertEquals(4, index.getManifests().size());
assertEquals(1, index.getManifests().get(0).getAnnotations().size());
assertEquals("bar", index.getManifests().get(0).getAnnotations().get("foo"));
assertEquals(1, index.getManifests().get(1).getAnnotations().size());
assertEquals("latest", index.getManifests().get(1).getAnnotations().get(Const.ANNOTATION_REF));
assertEquals(1, index.getManifests().get(2).getAnnotations().size());
assertEquals("hello", index.getManifests().get(2).getAnnotations().get("test"));
assertNull(index.getManifests().get(3).getAnnotations());
}
}
10 changes: 10 additions & 0 deletions src/test/java/land/oras/ManifestDescriptorTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,20 @@

import static org.junit.jupiter.api.Assertions.assertEquals;

import java.util.Map;
import land.oras.utils.Const;
import org.junit.jupiter.api.Test;

public class ManifestDescriptorTest {

@Test
void shouldSetAnnotations() {
Manifest manifest = Manifest.empty();
ManifestDescriptor descriptor = manifest.getDescriptor();
descriptor = descriptor.withAnnotations(Map.of(Const.ANNOTATION_REF, "latest"));
assertEquals("latest", descriptor.getAnnotations().get(Const.ANNOTATION_REF));
}

@Test
void shouldReadFromJson() {
ManifestDescriptor descriptor = ManifestDescriptor.fromJson(descriptor());
Expand Down
Loading