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
28 changes: 28 additions & 0 deletions src/main/java/land/oras/LayoutRef.java
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,34 @@
return withTag(digest);
}

/**
* Convert the manifest to a layout ref.
* @param layout The OCI layout.
* @param manifest The manifest.
* @return The layout ref.
*/
public static LayoutRef fromManifest(OCILayout layout, Manifest manifest) {
ManifestDescriptor descriptor = manifest.getDescriptor();
if (descriptor == null) {
throw new OrasException("Manifest descriptor is null");

Check warning on line 81 in src/main/java/land/oras/LayoutRef.java

View check run for this annotation

Codecov / codecov/patch

src/main/java/land/oras/LayoutRef.java#L81

Added line #L81 was not covered by tests
}
return new LayoutRef(layout.getPath(), manifest.getDescriptor().getDigest());
}

/**
* Convert the manifest to a layout ref.
* @param layout The OCI layout.
* @param index The manifest.
* @return The layout ref.
*/
public static LayoutRef fromIndex(OCILayout layout, Index index) {
ManifestDescriptor descriptor = index.getDescriptor();
if (descriptor == null) {
throw new OrasException("Index descriptor is null");

Check warning on line 95 in src/main/java/land/oras/LayoutRef.java

View check run for this annotation

Codecov / codecov/patch

src/main/java/land/oras/LayoutRef.java#L95

Added line #L95 was not covered by tests
}
return new LayoutRef(layout.getPath(), index.getDescriptor().getDigest());
}

/**
* Parse the layout ref with folder and tag.
* @param name The layout ref.
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 @@ -211,6 +211,14 @@ public abstract Manifest pushArtifact(
*/
public abstract Manifest pushManifest(T ref, Manifest manifest);

/**
* Push an index
* @param ref The ref
* @param index The index
* @return The index
*/
public abstract Index pushIndex(T ref, Index index);

/**
* Retrieve an index
* @param ref The ref
Expand Down
93 changes: 82 additions & 11 deletions src/main/java/land/oras/OCILayout.java
Original file line number Diff line number Diff line change
Expand Up @@ -152,13 +152,54 @@
// Write blobs
try {
writeManifest(manifest);
writeIndex(index);
writeOCIIndex(index);
} catch (IOException e) {
throw new OrasException("Failed to write manifest", e);
}
return manifest;
}

@Override
public Index pushIndex(LayoutRef layoutRef, Index index) {
byte[] indexData = index.getJson() != null
? index.getJson().getBytes()
: index.toJson().getBytes();

String indexDigest = layoutRef
.getAlgorithm()
.digest(
index.getJson() != null
? index.getJson().getBytes()
: index.toJson().getBytes());

ManifestDescriptor indexDescriptor = ManifestDescriptor.of(
Const.DEFAULT_INDEX_MEDIA_TYPE, indexDigest, indexData.length)
.withAnnotations(
index.getAnnotations() == null || index.getAnnotations().isEmpty()
? null
: index.getAnnotations())

Check warning on line 180 in src/main/java/land/oras/OCILayout.java

View check run for this annotation

Codecov / codecov/patch

src/main/java/land/oras/OCILayout.java#L180

Added line #L180 was not covered by tests
.withArtifactType(index.getMediaType());
if (layoutRef.getTag() != null && !layoutRef.isValidDigest()) {
Map<String, String> newAnnotations = new HashMap<>();
if (index.getAnnotations() != null) {
newAnnotations.putAll(index.getAnnotations());

Check warning on line 185 in src/main/java/land/oras/OCILayout.java

View check run for this annotation

Codecov / codecov/patch

src/main/java/land/oras/OCILayout.java#L185

Added line #L185 was not covered by tests
}
newAnnotations.put(Const.ANNOTATION_REF, layoutRef.getTag());
indexDescriptor = indexDescriptor.withAnnotations(newAnnotations);
}

Index ociIndex = Index.fromPath(getIndexPath()).withNewManifests(indexDescriptor);

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

Check warning on line 198 in src/main/java/land/oras/OCILayout.java

View check run for this annotation

Codecov / codecov/patch

src/main/java/land/oras/OCILayout.java#L197-L198

Added lines #L197 - L198 were not covered by tests
}
return index;
}

@Override
public Index getIndex(LayoutRef ref) {
Path path = getIndexPath();
Expand Down Expand Up @@ -348,7 +389,9 @@

// Write manifest as any blob
Manifest manifest = registry.getManifest(containerRef);
writeManifest(manifest);
String tag = containerRef.getTag();
LayoutRef layoutRef = LayoutRef.fromManifest(this, manifest).withTag(tag);
pushManifest(layoutRef, manifest);

if (recursive) {
LOG.debug("Recursively copy referrers");
Expand All @@ -359,28 +402,25 @@
}
}

// Write the index.json containing this manifest
Index index = Index.fromManifests(List.of(manifest.getDescriptor()));
writeIndex(index);

// Write config as any blob
writeConfig(registry, containerRef, manifest.getConfig());
}
// Index
else if (registry.isIndexMediaType(contentType)) {

Index index = registry.getIndex(containerRef);
String tag = containerRef.getTag();
LayoutRef layoutRef = LayoutRef.fromIndex(this, index);
pushIndex(layoutRef.withTag(tag), index);

// Write all manifests and their config
for (ManifestDescriptor descriptor : index.getManifests()) {
Manifest manifest = registry.getManifest(containerRef.withDigest(descriptor.getDigest()));
writeManifest(manifest.withDescriptor(descriptor));
LayoutRef manifestLayoutRef = LayoutRef.fromManifest(this, manifest);
pushManifest(manifestLayoutRef, manifest.withDescriptor(descriptor));
writeConfig(registry, containerRef, manifest.getConfig());
}

// Write the index
writeIndex(index);

} else {
throw new OrasException("Unsupported content type: %s".formatted(contentType));
}
Expand Down Expand Up @@ -497,7 +537,7 @@
return getBlobPath().resolve(algorithm.getPrefix());
}

private void writeIndex(Index index) throws IOException {
private void writeOCIIndex(Index index) throws IOException {
Path indexFile = getIndexPath();
Files.writeString(indexFile, index.getJson() != null ? index.getJson() : index.toJson());
if (index.getJson() != null) {
Expand Down Expand Up @@ -528,6 +568,29 @@
}
}

private void writeIndex(Index index) throws IOException {
ManifestDescriptor descriptor = index.getDescriptor();
Path manifestFile = getBlobPath(descriptor);
Path manifestPrefixDirectory =
getBlobAlgorithmPath(index.getDescriptor().getDigest());

if (!Files.exists(manifestPrefixDirectory)) {
Files.createDirectory(manifestPrefixDirectory);
}
// Skip if already exists
if (Files.exists(manifestFile)) {
LOG.debug("Manifest already exists: {}", manifestFile);
return;
}
if (index.getJson() == null) {
LOG.debug("Writing new manifest: {}", manifestFile);
Files.writeString(manifestFile, index.toJson());
} else {
LOG.debug("Writing existing manifest: {}", manifestFile);
Files.writeString(manifestFile, index.getJson());
}
}

private void writeConfig(Registry registry, ContainerRef containerRef, Config config) throws IOException {
String configDigest = config.getDigest();
Path configFile = getBlobPath(config);
Expand Down Expand Up @@ -576,6 +639,14 @@
return layout;
}

/**
* Return the path to the OCI layout
* @return The path to the OCI layout
*/
public Path getPath() {
return path;
}

/**
* Builder for the registry
*/
Expand Down
9 changes: 2 additions & 7 deletions src/main/java/land/oras/Registry.java
Original file line number Diff line number Diff line change
Expand Up @@ -205,12 +205,7 @@ public Manifest pushManifest(ContainerRef containerRef, Manifest manifest) {
return getManifest(containerRef);
}

/**
* Push a manifest
* @param containerRef The container
* @param index The index
* @return The location
*/
@Override
public Index pushIndex(ContainerRef containerRef, Index index) {
URI uri = URI.create("%s://%s".formatted(getScheme(), containerRef.getManifestsPath()));
OrasHttpClient.ResponseWrapper<String> response = client.put(
Expand Down Expand Up @@ -571,7 +566,7 @@ private byte[] ensureDigest(ContainerRef ref, byte[] data) {
SupportedAlgorithm algorithm = SupportedAlgorithm.fromDigest(ref.getDigest());
String dataDigest = algorithm.digest(data);
if (!ref.getDigest().equals(dataDigest)) {
throw new OrasException("Digest mismatch: %s != %s".formatted(ref.getTag(), dataDigest));
throw new OrasException("Digest mismatch: %s != %s".formatted(ref.getDigest(), dataDigest));
}
return data;
}
Expand Down
10 changes: 10 additions & 0 deletions src/test/java/land/oras/LayoutRefTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,16 @@ void shouldParseLayoutWithAllParts() {
assertFalse(layoutRef.isValidDigest(), "v1 is not a valid digest");
}

@Test
void shouldCreateLayoutRefFromManifest() {
Manifest manifest = Manifest.empty();
OCILayout layout = OCILayout.Builder.builder().defaults(tempDir).build();
LayoutRef layoutRef = LayoutRef.fromManifest(layout, manifest);
assertNotNull(layoutRef);
assertEquals(layout.getPath().toString(), layoutRef.getFolder().toString());
assertEquals("sha256:44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a", layoutRef.getTag());
}

@Test
void shouldParseLayoutWithDigest() {
String ociLayout = tempDir.resolve("foo").toString();
Expand Down
Loading