Skip to content

Commit 4551bda

Browse files
committed
Support building multi-platform OCI index
1 parent 349c809 commit 4551bda

File tree

5 files changed

+177
-22
lines changed

5 files changed

+177
-22
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: 28 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,32 @@ 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(RegistryImage.named(dockerHost + ":5000/jib-scratch:multi-platform"))
369+
.setAllowInsecureRegistries(true));
370+
371+
OciIndexTemplate manifestList =
372+
(OciIndexTemplate) registryClient.pullManifest("multi-platform").getManifest();
373+
Assert.assertEquals(2, manifestList.getManifests().size());
374+
OciIndexTemplate.ManifestDescriptorTemplate.Platform platform1 =
375+
manifestList.getManifests().get(0).getPlatform();
376+
OciIndexTemplate.ManifestDescriptorTemplate.Platform platform2 =
377+
manifestList.getManifests().get(1).getPlatform();
378+
379+
Assert.assertEquals("arm64", platform1.getArchitecture());
380+
Assert.assertEquals("windows", platform1.getOs());
381+
Assert.assertEquals("amd32", platform2.getArchitecture());
382+
Assert.assertEquals("windows", platform2.getOs());
383+
}
384+
357385
@Test
358386
public void testOffline()
359387
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(manifestTemplateClass);
5550

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

58+
private <T extends BuildableManifestTemplate> V22ManifestListTemplate getV22ManifestListTemplate(
59+
Class<T> manifestTemplateClass) throws IOException {
60+
V22ManifestListTemplate manifestList = new V22ManifestListTemplate();
61+
for (Image image : images) {
5962
BuildableManifestTemplate manifestTemplate =
60-
imageTranslator.getManifestTemplate(manifestTemplateClass, configDescriptor);
63+
getBuildableManifestTemplate(manifestTemplateClass, 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+
Class<T> manifestTemplateClass) throws IOException {
79+
OciIndexTemplate manifestList = new OciIndexTemplate();
80+
for (Image image : images) {
81+
BuildableManifestTemplate manifestTemplate =
82+
getBuildableManifestTemplate(manifestTemplateClass, 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: 57 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -32,13 +32,7 @@ public class ManifestListGeneratorTest {
3232
private ManifestListGenerator manifestListGenerator;
3333

3434
@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-
}
35+
public void setUp() {}
4236

4337
@Test
4438
public void testGetManifestListTemplate() throws IOException {
@@ -68,6 +62,11 @@ public void testGetManifestListTemplate() throws IOException {
6862
// }
6963
// ]
7064
// }
65+
image1 =
66+
Image.builder(V22ManifestTemplate.class).setArchitecture("amd64").setOs("linux").build();
67+
image2 =
68+
Image.builder(V22ManifestTemplate.class).setArchitecture("arm64").setOs("windows").build();
69+
manifestListGenerator = new ManifestListGenerator(Arrays.asList(image1, image2));
7170

7271
ManifestTemplate manifestTemplate =
7372
manifestListGenerator.getManifestListTemplate(V22ManifestTemplate.class);
@@ -82,6 +81,53 @@ public void testGetManifestListTemplate() throws IOException {
8281
manifestList.getDigestsForPlatform("arm64", "windows"));
8382
}
8483

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

96142
@Test
97143
public void testGetManifestListTemplate_unsupportedImageFormat() throws IOException {
144+
Class<? extends BuildableManifestTemplate> unknownFormat = BuildableManifestTemplate.class;
98145
try {
99146
new ManifestListGenerator(Arrays.asList(image1, image2))
100-
.getManifestListTemplate(OciManifestTemplate.class);
147+
.getManifestListTemplate(unknownFormat);
101148
Assert.fail();
102149
} catch (IllegalArgumentException ex) {
103-
Assert.assertEquals("Build an OCI image index is not yet supported", ex.getMessage());
150+
Assert.assertEquals(
151+
"Unsupported manifestTemplateClass format " + unknownFormat, ex.getMessage());
104152
}
105153
}
106154
}

0 commit comments

Comments
 (0)