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
21 changes: 21 additions & 0 deletions src/main/java/land/oras/Annotations.java
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,27 @@ public Map<String, String> getFileAnnotations(String key) {
return this.filesAnnotations().getOrDefault(key, new HashMap<>());
}

/**
* Check if there are annotations for a file
* @param key The key
* @return True if there are annotations, false otherwise
*/
public boolean hasFileAnnotations(String key) {
return this.filesAnnotations().containsKey(key);
}

/**
* Create a new annotations record with the given file annotations
* @param key The key of the file annotations
* @param annotations The file annotations
* @return The new annotations record
*/
public Annotations withFileAnnotations(String key, Map<String, String> annotations) {
Map<String, Map<String, String>> newFilesAnnotations = new HashMap<>(this.filesAnnotations());
newFilesAnnotations.put(key, annotations);
return new Annotations(this.configAnnotations(), this.manifestAnnotations(), newFilesAnnotations);
}

/**
* Annotations file format
*/
Expand Down
33 changes: 21 additions & 12 deletions src/main/java/land/oras/OCI.java
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
Expand Down Expand Up @@ -156,7 +157,7 @@ protected List<Layer> collectLayers(T ref, String contentType, boolean includeAl
* @param paths The paths to the files
* @return The layers
*/
protected final List<Layer> pushLayers(T ref, boolean withDigest, LocalPath... paths) {
protected final List<Layer> pushLayers(T ref, Annotations annotations, boolean withDigest, LocalPath... paths) {
List<Layer> layers = new ArrayList<>();
for (LocalPath path : paths) {
try {
Expand All @@ -172,15 +173,20 @@ protected final List<Layer> pushLayers(T ref, boolean withDigest, LocalPath... p
? path.getPath().getFileName().toString()
: path.getPath().toString();
LOG.debug("Uploading directory as archive with title: {}", title);

Map<String, String> layerAnnotations = annotations.hasFileAnnotations(title)
? annotations.getFileAnnotations(title)
: new LinkedHashMap<>(Map.of(Const.ANNOTATION_TITLE, title));

// Add oras digest/unpack
layerAnnotations.put(
Const.ANNOTATION_ORAS_CONTENT_DIGEST,
ref.getAlgorithm().digest(tempTar.getPath()));
layerAnnotations.put(Const.ANNOTATION_ORAS_UNPACK, "true");

Layer layer = pushBlob(ref, is)
.withMediaType(path.getMediaType())
.withAnnotations(Map.of(
Const.ANNOTATION_TITLE,
title,
Const.ANNOTATION_ORAS_CONTENT_DIGEST,
ref.getAlgorithm().digest(tempTar.getPath()),
Const.ANNOTATION_ORAS_UNPACK,
"true"));
.withAnnotations(layerAnnotations);
layers.add(layer);
LOG.info("Uploaded directory: {}", layer.getDigest());
}
Expand All @@ -190,11 +196,14 @@ protected final List<Layer> pushLayers(T ref, boolean withDigest, LocalPath... p
if (withDigest) {
ref = ref.withDigest(ref.getAlgorithm().digest(path.getPath()));
}
String title = path.getPath().getFileName().toString();
Map<String, String> layerAnnotations = annotations.hasFileAnnotations(title)
? annotations.getFileAnnotations(title)
: Map.of(Const.ANNOTATION_TITLE, title);

Layer layer = pushBlob(ref, is)
.withMediaType(path.getMediaType())
.withAnnotations(Map.of(
Const.ANNOTATION_TITLE,
path.getPath().getFileName().toString()));
.withAnnotations(layerAnnotations);
layers.add(layer);
LOG.info("Uploaded: {}", layer.getDigest());
}
Expand Down Expand Up @@ -413,7 +422,7 @@ public abstract Manifest pushArtifact(
public Manifest attachArtifact(T ref, ArtifactType artifactType, Annotations annotations, LocalPath... paths) {

// Push layers
List<Layer> layers = pushLayers(ref, true, paths);
List<Layer> layers = pushLayers(ref, annotations, true, paths);

// Get the subject from the descriptor
Descriptor descriptor = getDescriptor(ref);
Expand Down
2 changes: 1 addition & 1 deletion src/main/java/land/oras/OCILayout.java
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ public Manifest pushArtifact(
}

// Push layers
List<Layer> layers = pushLayers(ref, true, paths);
List<Layer> layers = pushLayers(ref, annotations, true, paths);

// Push the config like any other blob
Config configToPush = config != null ? config : Config.empty();
Expand Down
2 changes: 1 addition & 1 deletion src/main/java/land/oras/Registry.java
Original file line number Diff line number Diff line change
Expand Up @@ -406,7 +406,7 @@ public Manifest pushArtifact(
ContainerRef resolvedRef = containerRef.forRegistry(this).forRegistry(resolvedRegistry);

// Push layers
List<Layer> layers = pushLayers(resolvedRef, false, paths);
List<Layer> layers = pushLayers(resolvedRef, annotations, false, paths);

// Add layer and config
manifest = manifest.withLayers(layers).withConfig(pushedConfig);
Expand Down
10 changes: 10 additions & 0 deletions src/test/java/land/oras/AnnotationsTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@
package land.oras;

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

import java.util.Map;
import org.junit.jupiter.api.Test;
Expand Down Expand Up @@ -51,6 +53,14 @@ public void nullAnnotations() {
assertEquals(0, annotations.filesAnnotations().size());
}

@Test
public void shouldAddFileAnnotation() {
Annotations annotations = Annotations.empty().withFileAnnotations("cake.txt", Map.of("fun", "more cream"));
assertEquals("more cream", annotations.getFileAnnotations("cake.txt").get("fun"));
assertTrue(annotations.hasFileAnnotations("cake.txt"));
assertFalse(annotations.hasFileAnnotations("nonexistent.txt"));
}

@Test
public void toJson() {
Annotations annotations = new Annotations(
Expand Down
25 changes: 22 additions & 3 deletions src/test/java/land/oras/RegistryTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -1336,14 +1336,33 @@ void testShouldArtifactWithAnnotations() throws IOException {
Files.writeString(pomFile, "my pom file");

// Push the main OCI artifact
Annotations annotations = Annotations.ofManifest(Map.of("foo", "bar"));
Annotations annotations =
Annotations.ofManifest(Map.of("foo", "bar")).withFileAnnotations("jenkins.png", Map.of("foo", "bar"));

// Add image (without title (so it's not unpack) and specific annotation)
Manifest manifest = registry.pushArtifact(
containerRef, ArtifactType.from(artifactType), annotations, LocalPath.of(pomFile, "application/xml"));
containerRef,
ArtifactType.from(artifactType),
annotations,
LocalPath.of(pomFile, "application/xml"),
LocalPath.of(Path.of("src/test/resources/img/jenkins.png"), "image/png"));

// Check annotations
// Check annotations (manifest)
assertEquals(2, manifest.getAnnotations().size());
assertEquals("bar", manifest.getAnnotations().get("foo"));
assertNotNull(manifest.getAnnotations().get(Const.ANNOTATION_CREATED));

// Check annotations (layer 0)
Layer layer = manifest.getLayers().get(0);
assertEquals(1, layer.getAnnotations().size());
assertEquals(
"pom.xml", layer.getAnnotations().get(Const.ANNOTATION_TITLE), "Title annotation should be pom.xml");

// Check annotation (layer 1)
Layer layer2 = manifest.getLayers().get(1);
assertEquals(1, layer2.getAnnotations().size());
assertNull(layer2.getAnnotations().get(Const.ANNOTATION_TITLE), "Title should not be added");
assertEquals("bar", layer2.getAnnotations().get("foo"), "Custom annotation should be preserved");
}

@Test
Expand Down