Skip to content

Commit 4c3d155

Browse files
committed
Support building multi-platform OCI index
1 parent 349c809 commit 4c3d155

File tree

5 files changed

+183
-24
lines changed

5 files changed

+183
-24
lines changed

docs/faq.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -534,7 +534,6 @@ The default when not specified is a single "amd64/linux" platform, whose behavio
534534
When multiple platforms are specified, Jib creates and pushes a manifest list (also known as a fat manifest) after building and pushing all the images for the specified platforms.
535535

536536
As an incubating feature, there are certain limitations:
537-
- OCI image indices are not supported (as opposed to Docker manifest lists).
538537
- Only `architecture` and `os` are supported. If the base image manifest list contains multiple images with the given architecture and os, the first image will be selected.
539538
- Does not support using a local Docker daemon or tarball image for a base image.
540539
- Does not support pushing to a Docker daemon (`jib:dockerBuild` / `jibDockerBuild`) or building a local tarball (`jib:buildTar` / `jibBuildTar`).

jib-core/src/integration-test/java/com/google/cloud/tools/jib/api/JibIntegrationTest.java

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,12 @@
1717
package com.google.cloud.tools.jib.api;
1818

1919
import com.google.cloud.tools.jib.Command;
20+
import com.google.cloud.tools.jib.api.buildplan.ImageFormat;
2021
import com.google.cloud.tools.jib.api.buildplan.Platform;
2122
import com.google.cloud.tools.jib.blob.Blobs;
2223
import com.google.cloud.tools.jib.event.EventHandlers;
2324
import com.google.cloud.tools.jib.http.FailoverHttpClient;
25+
import com.google.cloud.tools.jib.image.json.OciIndexTemplate;
2426
import com.google.cloud.tools.jib.image.json.V22ManifestListTemplate;
2527
import com.google.cloud.tools.jib.image.json.V22ManifestListTemplate.ManifestDescriptorTemplate;
2628
import com.google.cloud.tools.jib.image.json.V22ManifestTemplate;
@@ -354,6 +356,33 @@ public void testDistroless_ociManifest()
354356
Assert.assertEquals("linux", platform2.getOs());
355357
}
356358

359+
@Test
360+
public void testScratch_multiPlatformOci()
361+
throws IOException, InterruptedException, ExecutionException, RegistryException,
362+
CacheDirectoryCreationException, InvalidImageReferenceException {
363+
Jib.fromScratch()
364+
.setFormat(ImageFormat.OCI)
365+
.setPlatforms(
366+
ImmutableSet.of(new Platform("arm64", "windows"), new Platform("amd32", "windows")))
367+
.containerize(
368+
Containerizer.to(
369+
RegistryImage.named(dockerHost + ":5000/jib-scratch:multi-platform-oci"))
370+
.setAllowInsecureRegistries(true));
371+
372+
OciIndexTemplate manifestList =
373+
(OciIndexTemplate) registryClient.pullManifest("multi-platform-oci").getManifest();
374+
Assert.assertEquals(2, manifestList.getManifests().size());
375+
OciIndexTemplate.ManifestDescriptorTemplate.Platform platform1 =
376+
manifestList.getManifests().get(0).getPlatform();
377+
OciIndexTemplate.ManifestDescriptorTemplate.Platform platform2 =
378+
manifestList.getManifests().get(1).getPlatform();
379+
380+
Assert.assertEquals("arm64", platform1.getArchitecture());
381+
Assert.assertEquals("windows", platform1.getOs());
382+
Assert.assertEquals("amd32", platform2.getArchitecture());
383+
Assert.assertEquals("windows", platform2.getOs());
384+
}
385+
357386
@Test
358387
public void testOffline()
359388
throws IOException, InterruptedException, InvalidImageReferenceException, ExecutionException,

jib-core/src/main/java/com/google/cloud/tools/jib/image/json/ManifestListGenerator.java

Lines changed: 43 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@
2020
import com.google.cloud.tools.jib.blob.BlobDescriptor;
2121
import com.google.cloud.tools.jib.hash.Digests;
2222
import com.google.cloud.tools.jib.image.Image;
23-
import com.google.cloud.tools.jib.image.json.V22ManifestListTemplate.ManifestDescriptorTemplate;
2423
import java.io.IOException;
2524
import java.util.List;
2625

@@ -44,23 +43,28 @@ public ManifestListGenerator(List<Image> images) {
4443
*/
4544
public <T extends BuildableManifestTemplate> ManifestTemplate getManifestListTemplate(
4645
Class<T> manifestTemplateClass) throws IOException {
47-
Preconditions.checkArgument(
48-
manifestTemplateClass == V22ManifestTemplate.class,
49-
"Build an OCI image index is not yet supported");
5046
Preconditions.checkState(!images.isEmpty(), "no images given");
5147

52-
V22ManifestListTemplate manifestList = new V22ManifestListTemplate();
53-
for (Image image : images) {
54-
ImageToJsonTranslator imageTranslator = new ImageToJsonTranslator(image);
48+
if (manifestTemplateClass == V22ManifestTemplate.class) {
49+
return getV22ManifestListTemplate();
5550

56-
BlobDescriptor configDescriptor =
57-
Digests.computeDigest(imageTranslator.getContainerConfiguration());
51+
} else if (manifestTemplateClass == OciManifestTemplate.class) {
52+
return getOciIndexTemplate();
53+
}
54+
throw new IllegalArgumentException(
55+
"Unsupported manifestTemplateClass format " + manifestTemplateClass);
56+
}
5857

58+
private <T extends BuildableManifestTemplate> V22ManifestListTemplate getV22ManifestListTemplate()
59+
throws IOException {
60+
V22ManifestListTemplate manifestList = new V22ManifestListTemplate();
61+
for (Image image : images) {
5962
BuildableManifestTemplate manifestTemplate =
60-
imageTranslator.getManifestTemplate(manifestTemplateClass, configDescriptor);
63+
getBuildableManifestTemplate(V22ManifestTemplate.class, image);
6164
BlobDescriptor manifestDescriptor = Digests.computeDigest(manifestTemplate);
6265

63-
ManifestDescriptorTemplate manifest = new ManifestDescriptorTemplate();
66+
V22ManifestListTemplate.ManifestDescriptorTemplate manifest =
67+
new V22ManifestListTemplate.ManifestDescriptorTemplate();
6468
manifest.setMediaType(manifestTemplate.getManifestMediaType());
6569
manifest.setSize(manifestDescriptor.getSize());
6670
manifest.setDigest(manifestDescriptor.getDigest().toString());
@@ -69,4 +73,32 @@ public <T extends BuildableManifestTemplate> ManifestTemplate getManifestListTem
6973
}
7074
return manifestList;
7175
}
76+
77+
private <T extends BuildableManifestTemplate> OciIndexTemplate getOciIndexTemplate()
78+
throws IOException {
79+
OciIndexTemplate manifestList = new OciIndexTemplate();
80+
for (Image image : images) {
81+
BuildableManifestTemplate manifestTemplate =
82+
getBuildableManifestTemplate(OciManifestTemplate.class, image);
83+
BlobDescriptor manifestDescriptor = Digests.computeDigest(manifestTemplate);
84+
85+
OciIndexTemplate.ManifestDescriptorTemplate manifest =
86+
new OciIndexTemplate.ManifestDescriptorTemplate(
87+
manifestTemplate.getManifestMediaType(),
88+
manifestDescriptor.getSize(),
89+
manifestDescriptor.getDigest());
90+
manifest.setPlatform(image.getArchitecture(), image.getOs());
91+
manifestList.addManifest(manifest);
92+
}
93+
return manifestList;
94+
}
95+
96+
private <T extends BuildableManifestTemplate>
97+
BuildableManifestTemplate getBuildableManifestTemplate(
98+
Class<T> manifestTemplateClass, Image image) throws IOException {
99+
ImageToJsonTranslator imageTranslator = new ImageToJsonTranslator(image);
100+
BlobDescriptor configDescriptor =
101+
Digests.computeDigest(imageTranslator.getContainerConfiguration());
102+
return imageTranslator.getManifestTemplate(manifestTemplateClass, configDescriptor);
103+
}
72104
}

jib-core/src/test/java/com/google/cloud/tools/jib/builder/steps/BuildManifestListOrSingleManifestStepTest.java

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@
2323
import com.google.cloud.tools.jib.image.Image;
2424
import com.google.cloud.tools.jib.image.Layer;
2525
import com.google.cloud.tools.jib.image.json.ManifestTemplate;
26+
import com.google.cloud.tools.jib.image.json.OciIndexTemplate;
27+
import com.google.cloud.tools.jib.image.json.OciManifestTemplate;
2628
import com.google.cloud.tools.jib.image.json.V22ManifestListTemplate;
2729
import com.google.cloud.tools.jib.image.json.V22ManifestTemplate;
2830
import java.io.IOException;
@@ -36,7 +38,7 @@
3638
import org.mockito.Mockito;
3739
import org.mockito.junit.MockitoJUnitRunner;
3840

39-
/** Tests for {@link BuildManifestListOrSingleManifest}. */
41+
/** Tests for {@link BuildManifestListOrSingleManifestStep}. */
4042
@RunWith(MockitoJUnitRunner.class)
4143
public class BuildManifestListOrSingleManifestStepTest {
4244

@@ -149,6 +151,52 @@ public void testCall_manifestList() throws IOException {
149151
manifestList.getDigestsForPlatform("arm64", "windows"));
150152
}
151153

154+
@Test
155+
public void testCall_manifestOciIndex() throws IOException {
156+
157+
// Expected Manifest Index JSON
158+
// {
159+
// "schemaVersion":2,
160+
// "mediaType":"application/vnd.oci.image.index.v1+json",
161+
// "manifests":[
162+
// {
163+
// "mediaType":"application/vnd.oci.image.manifest.v1+json",
164+
// "digest":"sha256:9591d0e20a39c41abdf52d2f8f30c97d7aeccbc3835999152e73a85de434d781",
165+
// "size":338,
166+
// "platform":{
167+
// "architecture":"amd64",
168+
// "os":"linux"
169+
// }
170+
// },
171+
// {
172+
// "mediaType":"application/vnd.oci.image.manifest.v1+json",
173+
// "digest":"sha256:8e0e6885ba5969d8fedf3f1b38ec68bb8fbf9f528c6e4c516328a81525ec479f",
174+
// "size":338,
175+
// "platform":{
176+
// "architecture":"arm64",
177+
// "os":"windows"
178+
// }
179+
// }
180+
// ]
181+
// }
182+
183+
Mockito.doReturn(OciManifestTemplate.class).when(buildContext).getTargetFormat();
184+
ManifestTemplate manifestTemplate =
185+
new BuildManifestListOrSingleManifestStep(
186+
buildContext, progressDispatcherFactory, Arrays.asList(image1, image2))
187+
.call();
188+
189+
Assert.assertTrue(manifestTemplate instanceof OciIndexTemplate);
190+
OciIndexTemplate manifestList = (OciIndexTemplate) manifestTemplate;
191+
Assert.assertEquals(2, manifestList.getSchemaVersion());
192+
Assert.assertEquals(
193+
Arrays.asList("sha256:9591d0e20a39c41abdf52d2f8f30c97d7aeccbc3835999152e73a85de434d781"),
194+
manifestList.getDigestsForPlatform("amd64", "linux"));
195+
Assert.assertEquals(
196+
Arrays.asList("sha256:8e0e6885ba5969d8fedf3f1b38ec68bb8fbf9f528c6e4c516328a81525ec479f"),
197+
manifestList.getDigestsForPlatform("arm64", "windows"));
198+
}
199+
152200
@Test
153201
public void testCall_emptyImagesList() throws IOException {
154202
try {

jib-core/src/test/java/com/google/cloud/tools/jib/image/json/ManifestListGeneratorTest.java

Lines changed: 62 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -27,18 +27,10 @@
2727
/** Tests for {@link ManifestListGenerator}. */
2828
public class ManifestListGeneratorTest {
2929

30-
private Image image1;
31-
private Image image2;
3230
private ManifestListGenerator manifestListGenerator;
3331

3432
@Before
35-
public void setUp() {
36-
image1 =
37-
Image.builder(V22ManifestTemplate.class).setArchitecture("amd64").setOs("linux").build();
38-
image2 =
39-
Image.builder(V22ManifestTemplate.class).setArchitecture("arm64").setOs("windows").build();
40-
manifestListGenerator = new ManifestListGenerator(Arrays.asList(image1, image2));
41-
}
33+
public void setUp() {}
4234

4335
@Test
4436
public void testGetManifestListTemplate() throws IOException {
@@ -68,6 +60,11 @@ public void testGetManifestListTemplate() throws IOException {
6860
// }
6961
// ]
7062
// }
63+
Image image1 =
64+
Image.builder(V22ManifestTemplate.class).setArchitecture("amd64").setOs("linux").build();
65+
Image image2 =
66+
Image.builder(V22ManifestTemplate.class).setArchitecture("arm64").setOs("windows").build();
67+
manifestListGenerator = new ManifestListGenerator(Arrays.asList(image1, image2));
7168

7269
ManifestTemplate manifestTemplate =
7370
manifestListGenerator.getManifestListTemplate(V22ManifestTemplate.class);
@@ -82,8 +79,56 @@ public void testGetManifestListTemplate() throws IOException {
8279
manifestList.getDigestsForPlatform("arm64", "windows"));
8380
}
8481

82+
@Test
83+
public void testGetManifestListTemplate_ociIndex() throws IOException {
84+
85+
// Expected Manifest List JSON
86+
// {
87+
// "schemaVersion":2,
88+
// "mediaType":"application/vnd.oci.image.index.v1+json",
89+
// "manifests":[
90+
// {
91+
// "mediaType":"application/vnd.oci.image.manifest.v1+json",
92+
// "digest":"sha256:835e93ca9c952a5f811fecadbc6337c50415cce1ce4d7a4f9b6347ce4605c1fa",
93+
// "size":248,
94+
// "platform":{
95+
// "architecture":"amd64",
96+
// "os":"linux"
97+
// }
98+
// },
99+
// {
100+
// "mediaType":"application/vnd.oci.image.manifest.v1+json",
101+
// "digest":"sha256:7ad84c70b22af31a7b0cc2218121d7e0a93f822374ccf0a634447921295c795d",
102+
// "size":248,
103+
// "platform":{
104+
// "architecture":"arm64",
105+
// "os":"windows"
106+
// }
107+
// }
108+
// ]
109+
// }
110+
Image image1 =
111+
Image.builder(OciManifestTemplate.class).setArchitecture("amd64").setOs("linux").build();
112+
Image image2 =
113+
Image.builder(OciManifestTemplate.class).setArchitecture("arm64").setOs("windows").build();
114+
manifestListGenerator = new ManifestListGenerator(Arrays.asList(image1, image2));
115+
116+
ManifestTemplate manifestTemplate =
117+
manifestListGenerator.getManifestListTemplate(OciManifestTemplate.class);
118+
Assert.assertTrue(manifestTemplate instanceof OciIndexTemplate);
119+
OciIndexTemplate manifestList = (OciIndexTemplate) manifestTemplate;
120+
Assert.assertEquals(2, manifestList.getSchemaVersion());
121+
Assert.assertEquals(
122+
Arrays.asList("sha256:835e93ca9c952a5f811fecadbc6337c50415cce1ce4d7a4f9b6347ce4605c1fa"),
123+
manifestList.getDigestsForPlatform("amd64", "linux"));
124+
Assert.assertEquals(
125+
Arrays.asList("sha256:7ad84c70b22af31a7b0cc2218121d7e0a93f822374ccf0a634447921295c795d"),
126+
manifestList.getDigestsForPlatform("arm64", "windows"));
127+
}
128+
85129
@Test
86130
public void testGetManifestListTemplate_emptyImagesList() throws IOException {
131+
87132
try {
88133
new ManifestListGenerator(Collections.emptyList())
89134
.getManifestListTemplate(V22ManifestTemplate.class);
@@ -95,12 +140,18 @@ public void testGetManifestListTemplate_emptyImagesList() throws IOException {
95140

96141
@Test
97142
public void testGetManifestListTemplate_unsupportedImageFormat() throws IOException {
143+
Image image1 =
144+
Image.builder(V22ManifestTemplate.class).setArchitecture("amd64").setOs("linux").build();
145+
Image image2 =
146+
Image.builder(V22ManifestTemplate.class).setArchitecture("arm64").setOs("windows").build();
147+
Class<? extends BuildableManifestTemplate> unknownFormat = BuildableManifestTemplate.class;
98148
try {
99149
new ManifestListGenerator(Arrays.asList(image1, image2))
100-
.getManifestListTemplate(OciManifestTemplate.class);
150+
.getManifestListTemplate(unknownFormat);
101151
Assert.fail();
102152
} catch (IllegalArgumentException ex) {
103-
Assert.assertEquals("Build an OCI image index is not yet supported", ex.getMessage());
153+
Assert.assertEquals(
154+
"Unsupported manifestTemplateClass format " + unknownFormat, ex.getMessage());
104155
}
105156
}
106157
}

0 commit comments

Comments
 (0)