diff --git a/docs/faq.md b/docs/faq.md index 273854ead9..55dd69e4ae 100644 --- a/docs/faq.md +++ b/docs/faq.md @@ -534,7 +534,6 @@ The default when not specified is a single "amd64/linux" platform, whose behavio 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. As an incubating feature, there are certain limitations: -- OCI image indices are not supported (as opposed to Docker manifest lists). - 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. - Does not support using a local Docker daemon or tarball image for a base image. - Does not support pushing to a Docker daemon (`jib:dockerBuild` / `jibDockerBuild`) or building a local tarball (`jib:buildTar` / `jibBuildTar`). diff --git a/jib-core/src/integration-test/java/com/google/cloud/tools/jib/api/JibIntegrationTest.java b/jib-core/src/integration-test/java/com/google/cloud/tools/jib/api/JibIntegrationTest.java index bf60f5d025..814379c3de 100644 --- a/jib-core/src/integration-test/java/com/google/cloud/tools/jib/api/JibIntegrationTest.java +++ b/jib-core/src/integration-test/java/com/google/cloud/tools/jib/api/JibIntegrationTest.java @@ -17,10 +17,12 @@ package com.google.cloud.tools.jib.api; import com.google.cloud.tools.jib.Command; +import com.google.cloud.tools.jib.api.buildplan.ImageFormat; import com.google.cloud.tools.jib.api.buildplan.Platform; import com.google.cloud.tools.jib.blob.Blobs; import com.google.cloud.tools.jib.event.EventHandlers; import com.google.cloud.tools.jib.http.FailoverHttpClient; +import com.google.cloud.tools.jib.image.json.OciIndexTemplate; import com.google.cloud.tools.jib.image.json.V22ManifestListTemplate; import com.google.cloud.tools.jib.image.json.V22ManifestListTemplate.ManifestDescriptorTemplate; import com.google.cloud.tools.jib.image.json.V22ManifestTemplate; @@ -354,6 +356,33 @@ public void testDistroless_ociManifest() Assert.assertEquals("linux", platform2.getOs()); } + @Test + public void testScratch_multiPlatformOci() + throws IOException, InterruptedException, ExecutionException, RegistryException, + CacheDirectoryCreationException, InvalidImageReferenceException { + Jib.fromScratch() + .setFormat(ImageFormat.OCI) + .setPlatforms( + ImmutableSet.of(new Platform("arm64", "windows"), new Platform("amd64", "windows"))) + .containerize( + Containerizer.to( + RegistryImage.named(dockerHost + ":5000/jib-scratch:multi-platform-oci")) + .setAllowInsecureRegistries(true)); + + OciIndexTemplate manifestList = + (OciIndexTemplate) registryClient.pullManifest("multi-platform-oci").getManifest(); + Assert.assertEquals(2, manifestList.getManifests().size()); + OciIndexTemplate.ManifestDescriptorTemplate.Platform platform1 = + manifestList.getManifests().get(0).getPlatform(); + OciIndexTemplate.ManifestDescriptorTemplate.Platform platform2 = + manifestList.getManifests().get(1).getPlatform(); + + Assert.assertEquals("arm64", platform1.getArchitecture()); + Assert.assertEquals("windows", platform1.getOs()); + Assert.assertEquals("amd64", platform2.getArchitecture()); + Assert.assertEquals("windows", platform2.getOs()); + } + @Test public void testOffline() throws IOException, InterruptedException, InvalidImageReferenceException, ExecutionException, diff --git a/jib-core/src/main/java/com/google/cloud/tools/jib/image/json/ManifestListGenerator.java b/jib-core/src/main/java/com/google/cloud/tools/jib/image/json/ManifestListGenerator.java index 1e3c80ab03..072be65e45 100644 --- a/jib-core/src/main/java/com/google/cloud/tools/jib/image/json/ManifestListGenerator.java +++ b/jib-core/src/main/java/com/google/cloud/tools/jib/image/json/ManifestListGenerator.java @@ -20,7 +20,6 @@ import com.google.cloud.tools.jib.blob.BlobDescriptor; import com.google.cloud.tools.jib.hash.Digests; import com.google.cloud.tools.jib.image.Image; -import com.google.cloud.tools.jib.image.json.V22ManifestListTemplate.ManifestDescriptorTemplate; import java.io.IOException; import java.util.List; @@ -44,23 +43,27 @@ public ManifestListGenerator(List images) { */ public ManifestTemplate getManifestListTemplate( Class manifestTemplateClass) throws IOException { - Preconditions.checkArgument( - manifestTemplateClass == V22ManifestTemplate.class, - "Build an OCI image index is not yet supported"); Preconditions.checkState(!images.isEmpty(), "no images given"); - V22ManifestListTemplate manifestList = new V22ManifestListTemplate(); - for (Image image : images) { - ImageToJsonTranslator imageTranslator = new ImageToJsonTranslator(image); + if (manifestTemplateClass == V22ManifestTemplate.class) { + return getV22ManifestListTemplate(); - BlobDescriptor configDescriptor = - Digests.computeDigest(imageTranslator.getContainerConfiguration()); + } else if (manifestTemplateClass == OciManifestTemplate.class) { + return getOciIndexTemplate(); + } + throw new IllegalArgumentException( + "Unsupported manifestTemplateClass format " + manifestTemplateClass); + } + private V22ManifestListTemplate getV22ManifestListTemplate() throws IOException { + V22ManifestListTemplate manifestList = new V22ManifestListTemplate(); + for (Image image : images) { BuildableManifestTemplate manifestTemplate = - imageTranslator.getManifestTemplate(manifestTemplateClass, configDescriptor); + getBuildableManifestTemplate(V22ManifestTemplate.class, image); BlobDescriptor manifestDescriptor = Digests.computeDigest(manifestTemplate); - ManifestDescriptorTemplate manifest = new ManifestDescriptorTemplate(); + V22ManifestListTemplate.ManifestDescriptorTemplate manifest = + new V22ManifestListTemplate.ManifestDescriptorTemplate(); manifest.setMediaType(manifestTemplate.getManifestMediaType()); manifest.setSize(manifestDescriptor.getSize()); manifest.setDigest(manifestDescriptor.getDigest().toString()); @@ -69,4 +72,31 @@ public ManifestTemplate getManifestListTem } return manifestList; } + + private OciIndexTemplate getOciIndexTemplate() throws IOException { + OciIndexTemplate manifestList = new OciIndexTemplate(); + for (Image image : images) { + BuildableManifestTemplate manifestTemplate = + getBuildableManifestTemplate(OciManifestTemplate.class, image); + BlobDescriptor manifestDescriptor = Digests.computeDigest(manifestTemplate); + + OciIndexTemplate.ManifestDescriptorTemplate manifest = + new OciIndexTemplate.ManifestDescriptorTemplate( + manifestTemplate.getManifestMediaType(), + manifestDescriptor.getSize(), + manifestDescriptor.getDigest()); + manifest.setPlatform(image.getArchitecture(), image.getOs()); + manifestList.addManifest(manifest); + } + return manifestList; + } + + private + BuildableManifestTemplate getBuildableManifestTemplate( + Class manifestTemplateClass, Image image) throws IOException { + ImageToJsonTranslator imageTranslator = new ImageToJsonTranslator(image); + BlobDescriptor configDescriptor = + Digests.computeDigest(imageTranslator.getContainerConfiguration()); + return imageTranslator.getManifestTemplate(manifestTemplateClass, configDescriptor); + } } diff --git a/jib-core/src/test/java/com/google/cloud/tools/jib/builder/steps/BuildManifestListOrSingleManifestStepTest.java b/jib-core/src/test/java/com/google/cloud/tools/jib/builder/steps/BuildManifestListOrSingleManifestStepTest.java index be8951005e..7b3afac98b 100644 --- a/jib-core/src/test/java/com/google/cloud/tools/jib/builder/steps/BuildManifestListOrSingleManifestStepTest.java +++ b/jib-core/src/test/java/com/google/cloud/tools/jib/builder/steps/BuildManifestListOrSingleManifestStepTest.java @@ -23,6 +23,8 @@ import com.google.cloud.tools.jib.image.Image; import com.google.cloud.tools.jib.image.Layer; import com.google.cloud.tools.jib.image.json.ManifestTemplate; +import com.google.cloud.tools.jib.image.json.OciIndexTemplate; +import com.google.cloud.tools.jib.image.json.OciManifestTemplate; import com.google.cloud.tools.jib.image.json.V22ManifestListTemplate; import com.google.cloud.tools.jib.image.json.V22ManifestTemplate; import java.io.IOException; @@ -36,7 +38,7 @@ import org.mockito.Mockito; import org.mockito.junit.MockitoJUnitRunner; -/** Tests for {@link BuildManifestListOrSingleManifest}. */ +/** Tests for {@link BuildManifestListOrSingleManifestStep}. */ @RunWith(MockitoJUnitRunner.class) public class BuildManifestListOrSingleManifestStepTest { @@ -149,6 +151,52 @@ public void testCall_manifestList() throws IOException { manifestList.getDigestsForPlatform("arm64", "windows")); } + @Test + public void testCall_manifestOciIndex() throws IOException { + + // Expected Manifest Index JSON + // { + // "schemaVersion":2, + // "mediaType":"application/vnd.oci.image.index.v1+json", + // "manifests":[ + // { + // "mediaType":"application/vnd.oci.image.manifest.v1+json", + // "digest":"sha256:9591d0e20a39c41abdf52d2f8f30c97d7aeccbc3835999152e73a85de434d781", + // "size":338, + // "platform":{ + // "architecture":"amd64", + // "os":"linux" + // } + // }, + // { + // "mediaType":"application/vnd.oci.image.manifest.v1+json", + // "digest":"sha256:8e0e6885ba5969d8fedf3f1b38ec68bb8fbf9f528c6e4c516328a81525ec479f", + // "size":338, + // "platform":{ + // "architecture":"arm64", + // "os":"windows" + // } + // } + // ] + // } + + Mockito.doReturn(OciManifestTemplate.class).when(buildContext).getTargetFormat(); + ManifestTemplate manifestTemplate = + new BuildManifestListOrSingleManifestStep( + buildContext, progressDispatcherFactory, Arrays.asList(image1, image2)) + .call(); + + Assert.assertTrue(manifestTemplate instanceof OciIndexTemplate); + OciIndexTemplate manifestList = (OciIndexTemplate) manifestTemplate; + Assert.assertEquals(2, manifestList.getSchemaVersion()); + Assert.assertEquals( + Arrays.asList("sha256:9591d0e20a39c41abdf52d2f8f30c97d7aeccbc3835999152e73a85de434d781"), + manifestList.getDigestsForPlatform("amd64", "linux")); + Assert.assertEquals( + Arrays.asList("sha256:8e0e6885ba5969d8fedf3f1b38ec68bb8fbf9f528c6e4c516328a81525ec479f"), + manifestList.getDigestsForPlatform("arm64", "windows")); + } + @Test public void testCall_emptyImagesList() throws IOException { try { diff --git a/jib-core/src/test/java/com/google/cloud/tools/jib/image/json/ManifestListGeneratorTest.java b/jib-core/src/test/java/com/google/cloud/tools/jib/image/json/ManifestListGeneratorTest.java index 3c7a15e2ac..6d774d3605 100644 --- a/jib-core/src/test/java/com/google/cloud/tools/jib/image/json/ManifestListGeneratorTest.java +++ b/jib-core/src/test/java/com/google/cloud/tools/jib/image/json/ManifestListGeneratorTest.java @@ -27,18 +27,10 @@ /** Tests for {@link ManifestListGenerator}. */ public class ManifestListGeneratorTest { - private Image image1; - private Image image2; private ManifestListGenerator manifestListGenerator; @Before - public void setUp() { - image1 = - Image.builder(V22ManifestTemplate.class).setArchitecture("amd64").setOs("linux").build(); - image2 = - Image.builder(V22ManifestTemplate.class).setArchitecture("arm64").setOs("windows").build(); - manifestListGenerator = new ManifestListGenerator(Arrays.asList(image1, image2)); - } + public void setUp() {} @Test public void testGetManifestListTemplate() throws IOException { @@ -68,6 +60,11 @@ public void testGetManifestListTemplate() throws IOException { // } // ] // } + Image image1 = + Image.builder(V22ManifestTemplate.class).setArchitecture("amd64").setOs("linux").build(); + Image image2 = + Image.builder(V22ManifestTemplate.class).setArchitecture("arm64").setOs("windows").build(); + manifestListGenerator = new ManifestListGenerator(Arrays.asList(image1, image2)); ManifestTemplate manifestTemplate = manifestListGenerator.getManifestListTemplate(V22ManifestTemplate.class); @@ -82,8 +79,56 @@ public void testGetManifestListTemplate() throws IOException { manifestList.getDigestsForPlatform("arm64", "windows")); } + @Test + public void testGetManifestListTemplate_ociIndex() throws IOException { + + // Expected Manifest List JSON + // { + // "schemaVersion":2, + // "mediaType":"application/vnd.oci.image.index.v1+json", + // "manifests":[ + // { + // "mediaType":"application/vnd.oci.image.manifest.v1+json", + // "digest":"sha256:835e93ca9c952a5f811fecadbc6337c50415cce1ce4d7a4f9b6347ce4605c1fa", + // "size":248, + // "platform":{ + // "architecture":"amd64", + // "os":"linux" + // } + // }, + // { + // "mediaType":"application/vnd.oci.image.manifest.v1+json", + // "digest":"sha256:7ad84c70b22af31a7b0cc2218121d7e0a93f822374ccf0a634447921295c795d", + // "size":248, + // "platform":{ + // "architecture":"arm64", + // "os":"windows" + // } + // } + // ] + // } + Image image1 = + Image.builder(OciManifestTemplate.class).setArchitecture("amd64").setOs("linux").build(); + Image image2 = + Image.builder(OciManifestTemplate.class).setArchitecture("arm64").setOs("windows").build(); + manifestListGenerator = new ManifestListGenerator(Arrays.asList(image1, image2)); + + ManifestTemplate manifestTemplate = + manifestListGenerator.getManifestListTemplate(OciManifestTemplate.class); + Assert.assertTrue(manifestTemplate instanceof OciIndexTemplate); + OciIndexTemplate manifestList = (OciIndexTemplate) manifestTemplate; + Assert.assertEquals(2, manifestList.getSchemaVersion()); + Assert.assertEquals( + Arrays.asList("sha256:835e93ca9c952a5f811fecadbc6337c50415cce1ce4d7a4f9b6347ce4605c1fa"), + manifestList.getDigestsForPlatform("amd64", "linux")); + Assert.assertEquals( + Arrays.asList("sha256:7ad84c70b22af31a7b0cc2218121d7e0a93f822374ccf0a634447921295c795d"), + manifestList.getDigestsForPlatform("arm64", "windows")); + } + @Test public void testGetManifestListTemplate_emptyImagesList() throws IOException { + try { new ManifestListGenerator(Collections.emptyList()) .getManifestListTemplate(V22ManifestTemplate.class); @@ -95,12 +140,18 @@ public void testGetManifestListTemplate_emptyImagesList() throws IOException { @Test public void testGetManifestListTemplate_unsupportedImageFormat() throws IOException { + Image image1 = + Image.builder(V22ManifestTemplate.class).setArchitecture("amd64").setOs("linux").build(); + Image image2 = + Image.builder(V22ManifestTemplate.class).setArchitecture("arm64").setOs("windows").build(); + Class unknownFormat = BuildableManifestTemplate.class; try { new ManifestListGenerator(Arrays.asList(image1, image2)) - .getManifestListTemplate(OciManifestTemplate.class); + .getManifestListTemplate(unknownFormat); Assert.fail(); } catch (IllegalArgumentException ex) { - Assert.assertEquals("Build an OCI image index is not yet supported", ex.getMessage()); + Assert.assertEquals( + "Unsupported manifestTemplateClass format " + unknownFormat, ex.getMessage()); } } }