Skip to content

Commit 62a341a

Browse files
committed
When copy ensure to push all manifests before index
Signed-off-by: Valentin Delaye <jonesbusy@users.noreply.github.com>
1 parent 5f9ed21 commit 62a341a

4 files changed

Lines changed: 71 additions & 4 deletions

File tree

src/main/java/land/oras/CopyUtils.java

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ void copy(
7676
for (Layer layer : source.collectLayers(sourceRef, contentType, true)) {
7777
Objects.requireNonNull(layer.getDigest(), "Layer digest is required for streaming copy");
7878
Objects.requireNonNull(layer.getSize(), "Layer size is required for streaming copy");
79+
LOG.debug("Copying layer {}", layer.getDigest());
7980
target.pushBlob(
8081
targetRef.withDigest(layer.getDigest()),
8182
layer.getSize(),
@@ -92,12 +93,17 @@ void copy(
9293
String tag = sourceRef.getTag();
9394

9495
// Write config as any blob
96+
Objects.requireNonNull(manifest.getDigest(), "Manifest digest is required for streaming copy");
9597
try (InputStream is = source.pullConfig(sourceRef, manifest.getConfig())) {
98+
LOG.debug("Copying config blob {}", manifest.getConfig().getDigest());
9699
target.pushBlob(targetRef.withDigest(manifest.getConfig().getDigest()), is);
100+
LOG.debug("Copied config blob {}", manifest.getConfig().getDigest());
97101
}
98102

99103
// Push the manifest
104+
LOG.debug("Copying manifest {}", manifestDigest);
100105
target.pushManifest(targetRef.withDigest(tag), manifest);
106+
LOG.debug("Copied manifest {}", manifestDigest);
101107

102108
if (recursive) {
103109
LOG.debug("Recursively copy referrers");
@@ -114,23 +120,28 @@ else if (source.isIndexMediaType(contentType)) {
114120

115121
Index index = source.getIndex(sourceRef);
116122
String tag = sourceRef.getTag();
117-
target.pushIndex(targetRef.withDigest(tag), index);
118123

119124
// Write all manifests and their config
120125
for (ManifestDescriptor manifestDescriptor : index.getManifests()) {
121126
Manifest manifest = source.getManifest(sourceRef.withDigest(manifestDescriptor.getDigest()));
122127

123128
// Write config as any blob
124129
try (InputStream is = source.pullConfig(sourceRef, manifest.getConfig())) {
130+
LOG.debug("Copying config blob {}", manifest.getConfig().getDigest());
125131
target.pushBlob(
126132
targetRef.withDigest(manifest.getConfig().getDigest()), is);
133+
LOG.debug("Copied config blob {}", manifest.getConfig().getDigest());
127134
}
128135

129136
// Push the manifest
130137
target.pushManifest(
131138
targetRef.withDigest(manifest.getDigest()), manifest.withDescriptor(manifestDescriptor));
132139
}
133140

141+
LOG.debug("Copying index {}", manifestDigest);
142+
target.pushIndex(targetRef.withDigest(tag), index);
143+
LOG.debug("Copied index {}", manifestDigest);
144+
134145
} else {
135146
throw new OrasException("Unsupported content type: %s".formatted(contentType));
136147
}

src/test/java/land/oras/DockerIoITCase.java

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,17 +23,24 @@
2323
import static org.junit.jupiter.api.Assertions.*;
2424

2525
import java.nio.file.Path;
26+
import land.oras.utils.ZotUnsecureContainer;
2627
import org.junit.jupiter.api.Test;
2728
import org.junit.jupiter.api.io.TempDir;
2829
import org.junit.jupiter.api.parallel.Execution;
2930
import org.junit.jupiter.api.parallel.ExecutionMode;
31+
import org.testcontainers.junit.jupiter.Container;
32+
import org.testcontainers.junit.jupiter.Testcontainers;
3033

34+
@Testcontainers
3135
@Execution(ExecutionMode.CONCURRENT)
3236
class DockerIoITCase {
3337

3438
@TempDir
3539
Path tempDir;
3640

41+
@Container
42+
private final ZotUnsecureContainer unsecureRegistry = new ZotUnsecureContainer().withStartupAttempts(3);
43+
3744
@Test
3845
void shouldPullAnonymousIndexFQDN() {
3946

@@ -75,4 +82,24 @@ void shouldPullOneBlob() {
7582
tempDir.resolve("my-blob"));
7683
assertNotNull(tempDir.resolve("my-blob"));
7784
}
85+
86+
@Test
87+
void shouldCopyTagToInternalRegistry() {
88+
89+
// Source registry
90+
Registry sourceRegistry = Registry.Builder.builder().defaults().build();
91+
92+
// Copy to this internal registry
93+
Registry targetRegistry = Registry.Builder.builder()
94+
.defaults("myuser", "mypass")
95+
.withInsecure(true)
96+
.build();
97+
98+
ContainerRef containerSource = ContainerRef.parse("docker.io/library/alpine:latest");
99+
ContainerRef containerTarget =
100+
ContainerRef.parse("%s/docker/library/alpine:latest".formatted(unsecureRegistry.getRegistry()));
101+
102+
CopyUtils.copy(sourceRegistry, containerSource, targetRegistry, containerTarget, true);
103+
assertTrue(targetRegistry.exists(containerTarget));
104+
}
78105
}

src/test/java/land/oras/GitHubContainerRegistryITCase.java

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,18 +21,26 @@
2121
package land.oras;
2222

2323
import static org.junit.jupiter.api.Assertions.assertNotNull;
24+
import static org.junit.jupiter.api.Assertions.assertTrue;
2425

2526
import java.nio.file.Path;
2627
import land.oras.utils.ArchiveUtils;
2728
import land.oras.utils.Const;
29+
import land.oras.utils.ZotUnsecureContainer;
2830
import org.junit.jupiter.api.Test;
2931
import org.junit.jupiter.api.io.TempDir;
3032
import org.junit.jupiter.api.parallel.Execution;
3133
import org.junit.jupiter.api.parallel.ExecutionMode;
34+
import org.testcontainers.junit.jupiter.Container;
35+
import org.testcontainers.junit.jupiter.Testcontainers;
3236

37+
@Testcontainers
3338
@Execution(ExecutionMode.CONCURRENT)
3439
class GitHubContainerRegistryITCase {
3540

41+
@Container
42+
private final ZotUnsecureContainer unsecureRegistry = new ZotUnsecureContainer().withStartupAttempts(3);
43+
3644
@TempDir
3745
Path tempDir;
3846

@@ -95,4 +103,24 @@ void shouldPullArtifact() {
95103
ArchiveUtils.uncompressuntar(
96104
tempDir.resolve("db.tar.gz"), tempDir.resolve("db"), Const.DEFAULT_BLOB_DIR_MEDIA_TYPE);
97105
}
106+
107+
@Test
108+
void shouldCopyTagToInternalRegistry() {
109+
110+
// Source registry
111+
Registry sourceRegistry = Registry.Builder.builder().defaults().build();
112+
113+
// Copy to this internal registry
114+
Registry targetRegistry = Registry.Builder.builder()
115+
.defaults("myuser", "mypass")
116+
.withInsecure(true)
117+
.build();
118+
119+
ContainerRef containerSource = ContainerRef.parse("ghcr.io/oras-project/oras:main");
120+
ContainerRef containerTarget =
121+
ContainerRef.parse("%s/docker/library/oras:main".formatted(unsecureRegistry.getRegistry()));
122+
123+
CopyUtils.copy(sourceRegistry, containerSource, targetRegistry, containerTarget, true);
124+
assertTrue(targetRegistry.exists(containerTarget));
125+
}
98126
}

src/test/java/land/oras/OCILayoutTest.java

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -948,7 +948,7 @@ void testShouldCopyImageIntoOciLayoutWithIndex() {
948948

949949
// Check index and manifest are stored in index
950950
assertIndex(layoutPathIndex, index, 2);
951-
assertIndex(layoutPathIndex, pushedManifest, 2, 1);
951+
assertIndex(layoutPathIndex, pushedManifest, 2, 0);
952952

953953
// Check manifest exists
954954
assertBlobExists(layoutPathIndex, pushedManifest.getDescriptor().getDigest());
@@ -983,7 +983,7 @@ void testShouldCopyImageIntoOciLayoutWithIndex() {
983983
// Check latest tag
984984
Index ociIndex = Index.fromPath(layoutPathIndex.resolve(Const.OCI_LAYOUT_INDEX));
985985
assertEquals(2, ociIndex.getManifests().size());
986-
assertEquals("latest", ociIndex.getManifests().get(0).getAnnotations().get(Const.ANNOTATION_REF));
986+
assertEquals("latest", ociIndex.getManifests().get(1).getAnnotations().get(Const.ANNOTATION_REF));
987987
}
988988

989989
@Test
@@ -1048,7 +1048,8 @@ private void assertIndex(Path ociLayoutPath, Index index, int size) {
10481048
assertEquals(size, ociIndex.getManifests().size());
10491049
assertEquals(Const.DEFAULT_INDEX_MEDIA_TYPE, ociIndex.getMediaType());
10501050
assertEquals(
1051-
index.getDescriptor().getSize(), ociIndex.getManifests().get(0).getSize());
1051+
index.getDescriptor().getSize(),
1052+
ociIndex.getManifests().get(ociIndex.getManifests().size() - 1).getSize());
10521053
}
10531054

10541055
private void assertLayerExists(Path ociLayoutPath, Layer layer) {

0 commit comments

Comments
 (0)