Skip to content

Commit

Permalink
Enable multi-platform image building and add integration test (#2734)
Browse files Browse the repository at this point in the history
* Integration Test
* Fix multi-platform image building test
* Test additional tags
* Lift feature lock
* Update CHANGELOG entries
* Preserve image order

Co-authored-by: Chanseok Oh <[email protected]>
  • Loading branch information
louismurerwa and chanseokoh authored Oct 5, 2020
1 parent c208133 commit 76e93ea
Show file tree
Hide file tree
Showing 11 changed files with 236 additions and 63 deletions.
2 changes: 1 addition & 1 deletion jib-core/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ All notable changes to this project will be documented in this file.

- Allow setting platform when building image from scratch. ([#2765](https://github.com/GoogleContainerTools/jib/issues/2765))
- New system property `jib.skipExistingImages` (false by default) to skip pushing images (manifests) if the image already exists in the registry. ([#2360](https://github.com/GoogleContainerTools/jib/issues/2360))
- _Incubating feature_: can now configure desired platform (architecture and OS) to select the matching manifest from a Docker manifest list for a base image. Currently supports building only one image. OCI image indices are not supported. ([#1567](https://github.com/GoogleContainerTools/jib/issues/1567))
- _Incubating feature_: can now configure multiple platforms (such as architectures) to build multiple images as a bundle and push as a manifest list (also known as a fat manifest). As an incubating feature, there are certain limitations. For example, OCI image indices are not supported, and building a manifest list is supported only for registry pushing (using `RegistryImage`). ([#2523](https://github.com/GoogleContainerTools/jib/issues/2523), [#1567](https://github.com/GoogleContainerTools/jib/issues/1567))

### Changed

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@
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.V22ManifestListTemplate;
import com.google.cloud.tools.jib.image.json.V22ManifestListTemplate.ManifestDescriptorTemplate;
import com.google.cloud.tools.jib.image.json.V22ManifestTemplate;
import com.google.cloud.tools.jib.registry.LocalRegistry;
import com.google.cloud.tools.jib.registry.ManifestPullerIntegrationTest;
Expand Down Expand Up @@ -73,6 +75,20 @@ private static Containerizer getLocalRegistryContainerizer(ImageReference target
.setAllowInsecureRegistries(true);
}

private static RegistryClient getRegistryClient(
ImageReference imageReference, Credential credential) {
RegistryClient registryClient =
RegistryClient.factory(
EventHandlers.NONE,
imageReference.getRegistry(),
imageReference.getRepository(),
new FailoverHttpClient(true, true, ignored -> {}))
.setCredential(credential)
.newRegistryClient();
registryClient.configureBasicAuth();
return registryClient;
}

@Before
public void setUp() {
System.setProperty("sendCredentialsOverHttp", "true");
Expand Down Expand Up @@ -280,19 +296,27 @@ public void testScratch_singlePlatform()
public void testScratch_multiPlatform()
throws IOException, InterruptedException, ExecutionException, RegistryException,
CacheDirectoryCreationException {
// TODO: Modify this test to check for multiple platforms instead of throwing exception once
// multi-platform feature is enabled.
ImageReference targetImageReference =
ImageReference.of("localhost:5000", "multi-platform-scratch", null);
try {
Jib.fromScratch()
.setPlatforms(
ImmutableSet.of(new Platform("arm64", "windows"), new Platform("amd32", "windows")))
.containerize(getLocalRegistryContainerizer(targetImageReference));
Assert.fail();
} catch (UnsupportedOperationException ex) {
Assert.assertEquals("multi-platform image building is not yet supported", ex.getMessage());
}
Jib.fromScratch()
.setPlatforms(
ImmutableSet.of(new Platform("arm64", "windows"), new Platform("amd32", "windows")))
.containerize(getLocalRegistryContainerizer(targetImageReference));
RegistryClient registryClient =
getRegistryClient(targetImageReference, Credential.from("username", "password"));

V22ManifestListTemplate manifestList =
(V22ManifestListTemplate) registryClient.pullManifest("latest").getManifest();
Assert.assertEquals(2, manifestList.getManifests().size());
ManifestDescriptorTemplate.Platform platform1 =
manifestList.getManifests().get(0).getPlatform();
ManifestDescriptorTemplate.Platform platform2 =
manifestList.getManifests().get(1).getPlatform();

Assert.assertEquals("arm64", platform1.getArchitecture());
Assert.assertEquals("windows", platform1.getOs());
Assert.assertEquals("amd32", platform2.getArchitecture());
Assert.assertEquals("windows", platform2.getOs());
}

@Test
Expand Down Expand Up @@ -386,19 +410,4 @@ public void testManifestListReferenceByShaDoesNotFail()
Jib.from(sourceImageReferenceAsManifestList).containerize(containerizer);
// pass, no exceptions thrown
}

private static RegistryClient getRegistryClient(
ImageReference imageReference, Credential credential) {
FailoverHttpClient httpClient = new FailoverHttpClient(true, true, ignored -> {});
RegistryClient registryClient =
RegistryClient.factory(
EventHandlers.NONE,
imageReference.getRegistry(),
imageReference.getRepository(),
httpClient)
.setCredential(credential)
.newRegistryClient();
registryClient.configureBasicAuth();
return registryClient;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -593,11 +593,6 @@ public JibContainer containerize(Containerizer containerizer)
TimerEventDispatcher ignored =
new TimerEventDispatcher(
buildContext.getEventHandlers(), containerizer.getDescription())) {

if (buildContext.getContainerConfiguration().getPlatforms().size() != 1) {
throw new UnsupportedOperationException(
"multi-platform image building is not yet supported");
}
logSources(buildContext.getEventHandlers());

BuildResult buildResult = containerizer.run(buildContext);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,12 +54,12 @@
import com.google.common.base.Preconditions;
import com.google.common.base.Verify;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.Callable;
import javax.annotation.Nullable;

Expand Down Expand Up @@ -98,7 +98,7 @@ public ImagesAndRegistryClient call()
// Skip this step if this is a scratch image
ImageReference imageReference = buildContext.getBaseImageConfiguration().getImage();
if (imageReference.isScratch()) {
ImmutableSet<Platform> platforms = buildContext.getContainerConfiguration().getPlatforms();
Set<Platform> platforms = buildContext.getContainerConfiguration().getPlatforms();
Verify.verify(!platforms.isEmpty());

eventHandlers.dispatch(LogEvent.progress("Getting scratch base image..."));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
Expand Down Expand Up @@ -312,7 +313,7 @@ private void obtainBaseImagesLayers(
results.baseImagesAndRegistryClient.get().images.size())) {

Map<DescriptorDigest, Future<PreparedLayer>> preparedLayersCache = new HashMap<>();
Map<Image, List<Future<PreparedLayer>>> baseImagesAndLayers = new HashMap<>();
Map<Image, List<Future<PreparedLayer>>> baseImagesAndLayers = new LinkedHashMap<>();
for (Image baseImage : results.baseImagesAndRegistryClient.get().images) {
List<Future<PreparedLayer>> layers =
obtainBaseImageLayers(
Expand Down Expand Up @@ -378,7 +379,7 @@ private void pushBaseImagesLayers(ProgressEventDispatcher.Factory progressDispat
"scheduling pushing base images layers",
results.baseImagesAndLayers.get().size())) {

Map<Image, List<Future<BlobDescriptor>>> layerPushResults = new HashMap<>();
Map<Image, List<Future<BlobDescriptor>>> layerPushResults = new LinkedHashMap<>();
for (Map.Entry<Image, List<Future<PreparedLayer>>> entry :
results.baseImagesAndLayers.get().entrySet()) {
Image baseImage = entry.getKey();
Expand Down Expand Up @@ -420,7 +421,7 @@ private void buildImages(ProgressEventDispatcher.Factory progressDispatcherFacto
progressDispatcherFactory.create(
"scheduling building manifests", results.baseImagesAndLayers.get().size())) {

Map<Image, Future<Image>> baseImagesAndBuiltImages = new HashMap<>();
Map<Image, Future<Image>> baseImagesAndBuiltImages = new LinkedHashMap<>();
for (Map.Entry<Image, List<Future<PreparedLayer>>> entry :
results.baseImagesAndLayers.get().entrySet()) {
Image baseImage = entry.getKey();
Expand Down Expand Up @@ -472,7 +473,7 @@ private void pushContainerConfigurations(
"scheduling pushing container configurations",
results.baseImagesAndBuiltImages.get().size())) {

Map<Image, Future<BlobDescriptor>> configPushResults = new HashMap<>();
Map<Image, Future<BlobDescriptor>> configPushResults = new LinkedHashMap<>();
for (Map.Entry<Image, Future<Image>> entry :
results.baseImagesAndBuiltImages.get().entrySet()) {
Image baseImage = entry.getKey();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,16 @@ public static class ManifestDescriptorTemplate implements JsonTemplate {
public static class Platform implements JsonTemplate {
@Nullable private String architecture;
@Nullable private String os;

@Nullable
public String getArchitecture() {
return architecture;
}

@Nullable
public String getOs() {
return os;
}
}

@Nullable private String mediaType;
Expand Down Expand Up @@ -167,14 +177,13 @@ public String getMediaType() {
* @param os the manifest os
*/
public void setPlatform(String architecture, String os) {
this.platform = new Platform();
this.platform.architecture = architecture;
this.platform.os = os;
platform = new Platform();
platform.architecture = architecture;
platform.os = os;
}

@VisibleForTesting
@Nullable
Platform getPlatform() {
public Platform getPlatform() {
return platform;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,6 @@
import java.time.Instant;
import java.util.Arrays;
import java.util.Collections;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.function.Consumer;
import org.hamcrest.CoreMatchers;
Expand Down Expand Up @@ -363,23 +362,4 @@ public void setApplyContainerBuildPlan()
Assert.assertEquals(
ImmutableSet.of(new Platform("testArchitecture", "testOS")), convertedPlan.getPlatforms());
}

@Test
public void testContainerize_multiPlatformsList()
throws InvalidImageReferenceException, CacheDirectoryCreationException, InterruptedException,
RegistryException, IOException, ExecutionException {
ImageConfiguration imageConfiguration =
ImageConfiguration.builder(ImageReference.parse("base/image")).build();
JibContainerBuilder containerBuilder =
new JibContainerBuilder(imageConfiguration, spyBuildContextBuilder)
.setPlatforms(
ImmutableSet.of(new Platform("arch1", "os1"), new Platform("arch2", "os2")));
Containerizer containerizer = Containerizer.to(RegistryImage.named("target/image"));
try {
containerBuilder.containerize(containerizer);
Assert.fail();
} catch (UnsupportedOperationException ex) {
Assert.assertEquals("multi-platform image building is not yet supported", ex.getMessage());
}
}
}
12 changes: 12 additions & 0 deletions jib-gradle-plugin/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,18 @@ All notable changes to this project will be documented in this file.
### Added

- Added lazy evaluation for `jib.to.image` and `jib.to.tags` using Gradle Property and Provider. ([#2727](https://github.com/GoogleContainerTools/jib/issues/2727))
- _Incubating feature_: can now configure multiple platforms (such as architectures) to build multiple images as a bundle and push as a manifest list (also known as a fat manifest). As an incubating feature, there are certain limitations. For example, OCI image indices are not supported, and building a manifest list is supported only for registry pushing (the `jib` task). ([#2523](https://github.com/GoogleContainerTools/jib/issues/2523))
```gradle
jib.from {
image = '... image reference to a manifest list ...'
platforms {
platform {
architecture = 'arm64'
os = 'linux'
}
}
}
```

### Changed

Expand Down
12 changes: 12 additions & 0 deletions jib-maven-plugin/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,18 @@ All notable changes to this project will be documented in this file.

- Previous locally cached base image manifests will be ignored, as the caching mechanism changed to enable multi-platform image building. ([#2730](https://github.com/GoogleContainerTools/jib/pull/2730), [#2711](https://github.com/GoogleContainerTools/jib/pull/2711))
- Upgraded the ASM library to 9.0 to resolve an issue when auto-inferring main class in Java 15+. ([#2776](https://github.com/GoogleContainerTools/jib/pull/2776))
- _Incubating feature_: can now configure multiple platforms (such as architectures) to build multiple images as a bundle and push as a manifest list (also known as a fat manifest). As an incubating feature, there are certain limitations. For example, OCI image indices are not supported, and building a manifest list is supported only for registry pushing (the `jib:build` goal). ([#2523](https://github.com/GoogleContainerTools/jib/issues/2523))
```xml
<from>
<image>... image reference to a manifest list ...</image>
<platforms>
<platform>
<architecture>arm64</architecture>
<os>linux</os>
</platform>
</platforms>
</from>
```

### Fixed

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,19 @@

import com.google.cloud.tools.jib.Command;
import com.google.cloud.tools.jib.IntegrationTestingConfiguration;
import com.google.cloud.tools.jib.api.Credential;
import com.google.cloud.tools.jib.api.DescriptorDigest;
import com.google.cloud.tools.jib.api.ImageReference;
import com.google.cloud.tools.jib.api.InvalidImageReferenceException;
import com.google.cloud.tools.jib.api.RegistryException;
import com.google.cloud.tools.jib.event.EventHandlers;
import com.google.cloud.tools.jib.http.FailoverHttpClient;
import com.google.cloud.tools.jib.image.json.ManifestTemplate;
import com.google.cloud.tools.jib.image.json.V22ManifestListTemplate;
import com.google.cloud.tools.jib.image.json.V22ManifestTemplate;
import com.google.cloud.tools.jib.registry.LocalRegistry;
import com.google.cloud.tools.jib.registry.ManifestAndDigest;
import com.google.cloud.tools.jib.registry.RegistryClient;
import com.google.common.base.Splitter;
import java.io.IOException;
import java.net.URL;
Expand Down Expand Up @@ -686,6 +695,85 @@ public void testExecute_springBootPackaged()
HttpGetVerifier.verifyBody("Hello world", new URL("http://localhost:8080"));
}

@Test
public void testExecute_multiPlatformBuild()
throws IOException, VerificationException, RegistryException {
String targetImage = "localhost:5000/multiplatform:maven" + System.nanoTime();

Verifier verifier = new Verifier(simpleTestProject.getProjectRoot().toString());
verifier.setSystemProperty("_TARGET_IMAGE", targetImage);

verifier.setSystemProperty("jib.to.auth.username", "testuser");
verifier.setSystemProperty("jib.to.auth.password", "testpassword");
verifier.setSystemProperty("sendCredentialsOverHttp", "true");
verifier.setSystemProperty("jib.allowInsecureRegistries", "true");

verifier.setAutoclean(false);
verifier.addCliOption("-X");
verifier.addCliOption("--file=pom-multiplatform-build.xml");
verifier.executeGoals(Arrays.asList("clean", "compile", "jib:build"));
verifier.verifyErrorFreeLog();

FailoverHttpClient httpClient = new FailoverHttpClient(true, true, ignored -> {});
RegistryClient registryClient =
RegistryClient.factory(EventHandlers.NONE, "localhost:5000", "multiplatform", httpClient)
.setCredential(Credential.from("testuser", "testpassword"))
.newRegistryClient();
registryClient.configureBasicAuth();

// manifest list by tag ":latest"
ManifestTemplate manifestListTemplate = registryClient.pullManifest("latest").getManifest();
MatcherAssert.assertThat(
manifestListTemplate, CoreMatchers.instanceOf(V22ManifestListTemplate.class));
V22ManifestListTemplate manifestList = (V22ManifestListTemplate) manifestListTemplate;
Assert.assertEquals(
Arrays.asList("sha256:fee2655e19e5138150606c99cfc16fcbf502d72b0f3b9ccf3a8f4509c47e46d9"),
manifestList.getDigestsForPlatform("arm64", "linux"));
Assert.assertEquals(
Arrays.asList("sha256:f3f4a91c68bcafea351280085d17e25fa598f5644c8b5e31e6133eddfc35e7ff"),
manifestList.getDigestsForPlatform("amd64", "linux"));

// manifest list by tag ":another"
ManifestTemplate manifestListTemplate2 = registryClient.pullManifest("another").getManifest();
MatcherAssert.assertThat(
manifestListTemplate2, CoreMatchers.instanceOf(V22ManifestListTemplate.class));
V22ManifestListTemplate manifestList2 = (V22ManifestListTemplate) manifestListTemplate2;
Assert.assertEquals(
Arrays.asList("sha256:fee2655e19e5138150606c99cfc16fcbf502d72b0f3b9ccf3a8f4509c47e46d9"),
manifestList2.getDigestsForPlatform("arm64", "linux"));
Assert.assertEquals(
Arrays.asList("sha256:f3f4a91c68bcafea351280085d17e25fa598f5644c8b5e31e6133eddfc35e7ff"),
manifestList2.getDigestsForPlatform("amd64", "linux"));

// arm64/linux manifest
ManifestAndDigest<ManifestTemplate> manifestAndDigest1 =
registryClient.pullManifest(
"sha256:fee2655e19e5138150606c99cfc16fcbf502d72b0f3b9ccf3a8f4509c47e46d9");
ManifestTemplate manifestTemplate1 = manifestAndDigest1.getManifest();
Assert.assertEquals(
"sha256:fee2655e19e5138150606c99cfc16fcbf502d72b0f3b9ccf3a8f4509c47e46d9",
manifestAndDigest1.getDigest().toString());
MatcherAssert.assertThat(manifestTemplate1, CoreMatchers.instanceOf(V22ManifestTemplate.class));
V22ManifestTemplate manifest1 = (V22ManifestTemplate) manifestTemplate1;
Assert.assertEquals(
"sha256:cecb4d0f179207a1c7f2ee33819d4fb70bbb9d98eebe78dfe1b439896925dc27",
manifest1.getContainerConfiguration().getDigest().toString());

// amd64/linux manifest
ManifestAndDigest<ManifestTemplate> manifestAndDigest2 =
registryClient.pullManifest(
"sha256:f3f4a91c68bcafea351280085d17e25fa598f5644c8b5e31e6133eddfc35e7ff");
ManifestTemplate manifestTemplate2 = manifestAndDigest2.getManifest();
Assert.assertEquals(
"sha256:f3f4a91c68bcafea351280085d17e25fa598f5644c8b5e31e6133eddfc35e7ff",
manifestAndDigest2.getDigest().toString());
MatcherAssert.assertThat(manifestTemplate2, CoreMatchers.instanceOf(V22ManifestTemplate.class));
V22ManifestTemplate manifest2 = (V22ManifestTemplate) manifestTemplate2;
Assert.assertEquals(
"sha256:a287f6aab9f8771c35ee8c60388abf845ee3ed6ef98d785c295200523fe9e4b7",
manifest2.getContainerConfiguration().getDigest().toString());
}

private void buildAndRunWebApp(TestProject project, String label, String pomXml)
throws VerificationException, IOException, InterruptedException {
String targetImage = getTestImageReference(label);
Expand Down
Loading

0 comments on commit 76e93ea

Please sign in to comment.