diff --git a/config/checkstyle/copyright-java.header b/config/checkstyle/copyright-java.header
index d15ad5912ea..e60211cae38 100644
--- a/config/checkstyle/copyright-java.header
+++ b/config/checkstyle/copyright-java.header
@@ -1,5 +1,5 @@
^/\*$
-^ \* Copyright 20(17|18|19|20|21|22|23) Google LLC\.$
+^ \* Copyright 20(17|18|19|20|21|22|23|24) Google LLC\.$
^ \*$
^ \* Licensed under the Apache License, Version 2\.0 \(the "License"\); you may not$
^ \* use this file except in compliance with the License\. You may obtain a copy of$
diff --git a/examples/dropwizard/pom.xml b/examples/dropwizard/pom.xml
index ab09f99b0a8..1a0780ecde6 100644
--- a/examples/dropwizard/pom.xml
+++ b/examples/dropwizard/pom.xml
@@ -26,7 +26,7 @@
1.5.0
/app
- 3.4.2
+ 3.4.3
diff --git a/examples/helloworld/build.gradle b/examples/helloworld/build.gradle
index c6c2225278f..5ffb87042c0 100644
--- a/examples/helloworld/build.gradle
+++ b/examples/helloworld/build.gradle
@@ -1,6 +1,6 @@
plugins {
id 'java'
- id 'com.google.cloud.tools.jib' version '3.4.2'
+ id 'com.google.cloud.tools.jib' version '3.4.3'
}
sourceCompatibility = 1.8
diff --git a/examples/helloworld/pom.xml b/examples/helloworld/pom.xml
index 924c67a1364..b5a5bf3479c 100644
--- a/examples/helloworld/pom.xml
+++ b/examples/helloworld/pom.xml
@@ -9,7 +9,7 @@
UTF-8
- 3.4.2
+ 3.4.3
3.8.0
diff --git a/examples/java-agent/build.gradle b/examples/java-agent/build.gradle
index ae272cdffde..1f24cc34581 100644
--- a/examples/java-agent/build.gradle
+++ b/examples/java-agent/build.gradle
@@ -1,6 +1,6 @@
plugins {
id 'java'
- id 'com.google.cloud.tools.jib' version '3.4.2'
+ id 'com.google.cloud.tools.jib' version '3.4.3'
id 'de.undercouch.download' version '4.0.0'
id 'com.gorylenko.gradle-git-properties' version '2.2.0'
}
diff --git a/examples/java-agent/pom.xml b/examples/java-agent/pom.xml
index 7fc43504e53..a4d476488b2 100644
--- a/examples/java-agent/pom.xml
+++ b/examples/java-agent/pom.xml
@@ -9,7 +9,7 @@
UTF-8
- 3.4.2
+ 3.4.3
3.8.0
1.4.2
3.0.1
diff --git a/examples/ktor/build.gradle.kts b/examples/ktor/build.gradle.kts
index 963c3fd4188..128daeab3cc 100644
--- a/examples/ktor/build.gradle.kts
+++ b/examples/ktor/build.gradle.kts
@@ -1,7 +1,7 @@
plugins {
application
kotlin("jvm") version "1.3.10"
- id("com.google.cloud.tools.jib") version "3.4.2"
+ id("com.google.cloud.tools.jib") version "3.4.3"
}
group = "example"
diff --git a/examples/micronaut/build.gradle b/examples/micronaut/build.gradle
index c0f0842bbdd..fe153ba0a98 100644
--- a/examples/micronaut/build.gradle
+++ b/examples/micronaut/build.gradle
@@ -2,7 +2,7 @@ plugins {
id "groovy"
id "com.github.johnrengelman.shadow" version "5.2.0"
id "application"
- id 'com.google.cloud.tools.jib' version '3.4.2'
+ id 'com.google.cloud.tools.jib' version '3.4.3'
}
version "0.1"
diff --git a/examples/multi-module/build.gradle b/examples/multi-module/build.gradle
index ea07fc421f0..f79edd79874 100644
--- a/examples/multi-module/build.gradle
+++ b/examples/multi-module/build.gradle
@@ -2,5 +2,5 @@
plugins {
id 'org.springframework.boot' version '2.0.3.RELEASE' apply false
id 'io.spring.dependency-management' version '1.0.6.RELEASE' apply false
- id 'com.google.cloud.tools.jib' version '3.4.2' apply false
+ id 'com.google.cloud.tools.jib' version '3.4.3' apply false
}
diff --git a/examples/multi-module/pom.xml b/examples/multi-module/pom.xml
index 135e9a8c5e3..38351f0b50d 100644
--- a/examples/multi-module/pom.xml
+++ b/examples/multi-module/pom.xml
@@ -41,7 +41,7 @@
com.google.cloud.tools
jib-maven-plugin
- 3.4.2
+ 3.4.3
diff --git a/examples/spring-boot/build.gradle b/examples/spring-boot/build.gradle
index 5ce5d12ef15..fbd2a775c9a 100644
--- a/examples/spring-boot/build.gradle
+++ b/examples/spring-boot/build.gradle
@@ -4,7 +4,7 @@ plugins {
id 'idea'
id 'org.springframework.boot' version '2.1.6.RELEASE'
id 'io.spring.dependency-management' version '1.0.6.RELEASE'
- id 'com.google.cloud.tools.jib' version '3.4.2'
+ id 'com.google.cloud.tools.jib' version '3.4.3'
}
repositories {
diff --git a/examples/spring-boot/pom.xml b/examples/spring-boot/pom.xml
index 6cf906c0da1..e59baa83776 100644
--- a/examples/spring-boot/pom.xml
+++ b/examples/spring-boot/pom.xml
@@ -29,7 +29,7 @@
com.google.cloud.tools
jib-maven-plugin
- 3.4.2
+ 3.4.3
diff --git a/examples/vertx/build.gradle b/examples/vertx/build.gradle
index 956df52ea2f..02717a203b4 100644
--- a/examples/vertx/build.gradle
+++ b/examples/vertx/build.gradle
@@ -1,6 +1,6 @@
plugins {
id 'io.vertx.vertx-plugin' version '0.1.0'
- id 'com.google.cloud.tools.jib' version '3.4.2'
+ id 'com.google.cloud.tools.jib' version '3.4.3'
}
repositories {
diff --git a/jib-core/CHANGELOG.md b/jib-core/CHANGELOG.md
index b0f87625669..ab9f84acfd2 100644
--- a/jib-core/CHANGELOG.md
+++ b/jib-core/CHANGELOG.md
@@ -9,6 +9,12 @@ All notable changes to this project will be documented in this file.
### Fixed
+## 0.27.1
+
+### Fixed
+- fix: When building to the local docker daemon with multiple platforms configured, Jib will now automatically select the image that matches the OS type and architecture of the local Docker environment. ([#4249](https://github.com/GoogleContainerTools/jib/pull/4249))
+
+
## 0.27.0
### Changed
diff --git a/jib-core/README.md b/jib-core/README.md
index cc6baf2d245..82637ecd91a 100644
--- a/jib-core/README.md
+++ b/jib-core/README.md
@@ -22,7 +22,7 @@ Add Jib Core as a dependency using Maven:
com.google.cloud.tools
jib-core
- 0.27.0
+ 0.27.1
```
@@ -30,7 +30,7 @@ Add Jib Core as a dependency using Gradle:
```groovy
dependencies {
- compile 'com.google.cloud.tools:jib-core:0.27.0'
+ compile 'com.google.cloud.tools:jib-core:0.27.1'
}
```
diff --git a/jib-core/gradle.properties b/jib-core/gradle.properties
index 86ae6f81f55..67f1f13bde4 100644
--- a/jib-core/gradle.properties
+++ b/jib-core/gradle.properties
@@ -1 +1 @@
-version = 0.27.1-SNAPSHOT
+version = 0.27.2-SNAPSHOT
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 a9916708c35..4e49d1e12d3 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
@@ -16,6 +16,9 @@
package com.google.cloud.tools.jib.api;
+import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.assertThrows;
+
import com.google.cloud.tools.jib.Command;
import com.google.cloud.tools.jib.api.buildplan.Platform;
import com.google.cloud.tools.jib.blob.Blobs;
@@ -304,6 +307,65 @@ public void testScratch_multiPlatform()
Assert.assertEquals("windows", platform2.getOs());
}
+ @Test
+ public void testBasic_jibImageToDockerDaemon()
+ throws IOException, InterruptedException, InvalidImageReferenceException, ExecutionException,
+ RegistryException, CacheDirectoryCreationException {
+ Jib.from(DockerDaemonImage.named(dockerHost + ":5000/busybox"))
+ .setEntrypoint("echo", "Hello World")
+ .containerize(
+ Containerizer.to(DockerDaemonImage.named(dockerHost + ":5000/docker-to-docker")));
+
+ String output =
+ new Command("docker", "run", "--rm", dockerHost + ":5000/docker-to-docker").run();
+ Assert.assertEquals("Hello World\n", output);
+ }
+
+ @Test
+ public void testBasicMultiPlatform_toDockerDaemon()
+ throws IOException, InterruptedException, ExecutionException, RegistryException,
+ CacheDirectoryCreationException, InvalidImageReferenceException {
+ Jib.from(
+ RegistryImage.named(
+ "busybox@sha256:4f47c01fa91355af2865ac10fef5bf6ec9c7f42ad2321377c21e844427972977"))
+ .setPlatforms(
+ ImmutableSet.of(new Platform("arm64", "linux"), new Platform("amd64", "linux")))
+ .setEntrypoint("echo", "Hello World")
+ .containerize(
+ Containerizer.to(
+ DockerDaemonImage.named(dockerHost + ":5000/docker-daemon-multi-platform"))
+ .setAllowInsecureRegistries(true));
+
+ String output =
+ new Command("docker", "run", "--rm", dockerHost + ":5000/docker-daemon-multi-platform")
+ .run();
+ Assert.assertEquals("Hello World\n", output);
+ }
+
+ @Test
+ public void testBasicMultiPlatform_toDockerDaemon_noMatchingImage() {
+ ExecutionException exception =
+ assertThrows(
+ ExecutionException.class,
+ () ->
+ Jib.from(
+ RegistryImage.named(
+ "busybox@sha256:4f47c01fa91355af2865ac10fef5bf6ec9c7f42ad2321377c21e844427972977"))
+ .setPlatforms(
+ ImmutableSet.of(
+ new Platform("s390x", "linux"), new Platform("arm", "linux")))
+ .setEntrypoint("echo", "Hello World")
+ .containerize(
+ Containerizer.to(
+ DockerDaemonImage.named(
+ dockerHost + ":5000/docker-daemon-multi-platform"))
+ .setAllowInsecureRegistries(true)));
+ assertThat(exception)
+ .hasCauseThat()
+ .hasMessageThat()
+ .startsWith("The configured platforms don't match the Docker Engine's OS and architecture");
+ }
+
@Test
public void testDistroless_ociManifest()
throws IOException, InterruptedException, ExecutionException, RegistryException,
diff --git a/jib-core/src/main/java/com/google/cloud/tools/jib/api/DockerClient.java b/jib-core/src/main/java/com/google/cloud/tools/jib/api/DockerClient.java
index 50ce2f463fc..7e08a794950 100644
--- a/jib-core/src/main/java/com/google/cloud/tools/jib/api/DockerClient.java
+++ b/jib-core/src/main/java/com/google/cloud/tools/jib/api/DockerClient.java
@@ -69,4 +69,15 @@ void save(ImageReference imageReference, Path outputPath, Consumer written
* @throws InterruptedException if the {@code docker inspect} process was interrupted
*/
ImageDetails inspect(ImageReference imageReference) throws IOException, InterruptedException;
+
+ /**
+ * Gets docker info details of local docker installation.
+ *
+ * @return docker info details.
+ * @throws IOException if an I/O exception occurs or {@code docker info} failed
+ * @throws InterruptedException if the {@code docker info} process was interrupted
+ */
+ default DockerInfoDetails info() throws IOException, InterruptedException {
+ return new DockerInfoDetails();
+ }
}
diff --git a/jib-core/src/main/java/com/google/cloud/tools/jib/api/DockerInfoDetails.java b/jib-core/src/main/java/com/google/cloud/tools/jib/api/DockerInfoDetails.java
new file mode 100644
index 00000000000..fa486c40f95
--- /dev/null
+++ b/jib-core/src/main/java/com/google/cloud/tools/jib/api/DockerInfoDetails.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2024 Google LLC.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.google.cloud.tools.jib.api;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.google.cloud.tools.jib.json.JsonTemplate;
+
+/** Contains docker info details outputted by {@code docker info}. */
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class DockerInfoDetails implements JsonTemplate {
+
+ @JsonProperty("OSType")
+ private String osType = "";
+
+ @JsonProperty("Architecture")
+ private String architecture = "";
+
+ public String getOsType() {
+ return osType;
+ }
+
+ public String getArchitecture() {
+ return architecture;
+ }
+}
diff --git a/jib-core/src/main/java/com/google/cloud/tools/jib/builder/steps/StepsRunner.java b/jib-core/src/main/java/com/google/cloud/tools/jib/builder/steps/StepsRunner.java
index 46c63bfc9de..be760a13eb8 100644
--- a/jib-core/src/main/java/com/google/cloud/tools/jib/builder/steps/StepsRunner.java
+++ b/jib-core/src/main/java/com/google/cloud/tools/jib/builder/steps/StepsRunner.java
@@ -18,6 +18,7 @@
import com.google.cloud.tools.jib.api.DescriptorDigest;
import com.google.cloud.tools.jib.api.DockerClient;
+import com.google.cloud.tools.jib.api.DockerInfoDetails;
import com.google.cloud.tools.jib.blob.BlobDescriptor;
import com.google.cloud.tools.jib.builder.ProgressEventDispatcher;
import com.google.cloud.tools.jib.builder.steps.LocalBaseImageSteps.LocalImage;
@@ -52,6 +53,7 @@
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
import java.util.function.Consumer;
+import java.util.logging.Logger;
import java.util.stream.Collectors;
import javax.annotation.Nullable;
@@ -64,6 +66,8 @@
*/
public class StepsRunner {
+ private static final Logger LOGGER = Logger.getLogger(StepsRunner.class.getName());
+
/** Holds the individual step results. */
private static class StepResults {
@@ -413,7 +417,8 @@ private void buildAndCacheApplicationLayers(
BuildAndCacheApplicationLayerStep.makeList(buildContext, progressDispatcherFactory));
}
- private void buildImages(ProgressEventDispatcher.Factory progressDispatcherFactory) {
+ @VisibleForTesting
+ void buildImages(ProgressEventDispatcher.Factory progressDispatcherFactory) {
results.baseImagesAndBuiltImages =
executorService.submit(
() -> {
@@ -616,13 +621,17 @@ private void loadDocker(
results.buildResult =
executorService.submit(
() -> {
- Verify.verify(
- results.baseImagesAndBuiltImages.get().size() == 1,
- "multi-platform image building not supported when pushing to Docker engine");
- Image builtImage =
- results.baseImagesAndBuiltImages.get().values().iterator().next().get();
+ DockerInfoDetails dockerInfoDetails = dockerClient.info();
+ String osType = dockerInfoDetails.getOsType();
+ String architecture = normalizeArchitecture(dockerInfoDetails.getArchitecture());
+ Optional builtImage = fetchBuiltImageForLocalBuild(osType, architecture);
+ Preconditions.checkState(
+ builtImage.isPresent(),
+ String.format(
+ "The configured platforms don't match the Docker Engine's OS and architecture (%s/%s)",
+ osType, architecture));
return new LoadDockerStep(
- buildContext, progressDispatcherFactory, dockerClient, builtImage)
+ buildContext, progressDispatcherFactory, dockerClient, builtImage.get())
.call();
});
}
@@ -647,4 +656,34 @@ private void writeTarFile(
private List> scheduleCallables(ImmutableList extends Callable> callables) {
return callables.stream().map(executorService::submit).collect(Collectors.toList());
}
+
+ @VisibleForTesting
+ String normalizeArchitecture(String architecture) {
+ // Create mapping based on https://docs.docker.com/engine/install/#supported-platforms
+ if (architecture.equals("x86_64")) {
+ return "amd64";
+ } else if (architecture.equals("aarch64")) {
+ return "arm64";
+ }
+ return architecture;
+ }
+
+ @VisibleForTesting
+ Optional fetchBuiltImageForLocalBuild(String osType, String architecture)
+ throws InterruptedException, ExecutionException {
+ if (results.baseImagesAndBuiltImages.get().size() > 1) {
+ LOGGER.warning(
+ String.format(
+ "Detected multi-platform configuration, only building the one that matches the local Docker Engine's os and architecture (%s/%s)",
+ osType, architecture));
+ }
+ for (Map.Entry> imageEntry :
+ results.baseImagesAndBuiltImages.get().entrySet()) {
+ Image image = imageEntry.getValue().get();
+ if (image.getArchitecture().equals(architecture) && image.getOs().equals(osType)) {
+ return Optional.of(image);
+ }
+ }
+ return Optional.empty();
+ }
}
diff --git a/jib-core/src/main/java/com/google/cloud/tools/jib/docker/CliDockerClient.java b/jib-core/src/main/java/com/google/cloud/tools/jib/docker/CliDockerClient.java
index 7c9c23cd9fa..1842a79de3e 100644
--- a/jib-core/src/main/java/com/google/cloud/tools/jib/docker/CliDockerClient.java
+++ b/jib-core/src/main/java/com/google/cloud/tools/jib/docker/CliDockerClient.java
@@ -20,6 +20,7 @@
import com.fasterxml.jackson.annotation.JsonProperty;
import com.google.cloud.tools.jib.api.DescriptorDigest;
import com.google.cloud.tools.jib.api.DockerClient;
+import com.google.cloud.tools.jib.api.DockerInfoDetails;
import com.google.cloud.tools.jib.api.ImageDetails;
import com.google.cloud.tools.jib.api.ImageReference;
import com.google.cloud.tools.jib.http.NotifyingOutputStream;
@@ -184,6 +185,17 @@ public boolean supported(Map parameters) {
return true;
}
+ @Override
+ public DockerInfoDetails info() throws IOException, InterruptedException {
+ // Runs 'docker info'.
+ Process infoProcess = docker("info", "-f", "{{json .}}");
+ if (infoProcess.waitFor() != 0) {
+ throw new IOException(
+ "'docker info' command failed with error: " + getStderrOutput(infoProcess));
+ }
+ return JsonTemplateMapper.readJson(infoProcess.getInputStream(), DockerInfoDetails.class);
+ }
+
@Override
public String load(ImageTarball imageTarball, Consumer writtenByteCountListener)
throws InterruptedException, IOException {
diff --git a/jib-core/src/test/java/com/google/cloud/tools/jib/builder/steps/StepsRunnerTest.java b/jib-core/src/test/java/com/google/cloud/tools/jib/builder/steps/StepsRunnerTest.java
index e8a60de2d57..9436c7df4a0 100644
--- a/jib-core/src/test/java/com/google/cloud/tools/jib/builder/steps/StepsRunnerTest.java
+++ b/jib-core/src/test/java/com/google/cloud/tools/jib/builder/steps/StepsRunnerTest.java
@@ -16,6 +16,9 @@
package com.google.cloud.tools.jib.builder.steps;
+import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Mockito.when;
+
import com.google.cloud.tools.jib.api.DescriptorDigest;
import com.google.cloud.tools.jib.builder.ProgressEventDispatcher;
import com.google.cloud.tools.jib.builder.steps.PullBaseImageStep.ImagesAndRegistryClient;
@@ -26,6 +29,7 @@
import com.google.cloud.tools.jib.image.json.ManifestTemplate;
import com.google.cloud.tools.jib.registry.ManifestAndDigest;
import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
import com.google.common.util.concurrent.ForwardingExecutorService;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
@@ -83,6 +87,10 @@ protected ExecutorService delegate() {
@Mock private ProgressEventDispatcher.Factory progressDispatcherFactory;
@Mock private ProgressEventDispatcher progressDispatcher;
@Mock private ExecutorService executorService;
+ @Mock private Image builtArm64AndLinuxImage;
+ @Mock private Image builtAmd64AndWindowsImage;
+ @Mock private Image baseImage1;
+ @Mock private Image baseImage2;
private StepsRunner stepsRunner;
@@ -90,14 +98,14 @@ protected ExecutorService delegate() {
public void setup() {
stepsRunner = new StepsRunner(new MockListeningExecutorService(), buildContext);
- Mockito.when(progressDispatcherFactory.create(Mockito.anyString(), Mockito.anyLong()))
+ when(progressDispatcherFactory.create(Mockito.anyString(), Mockito.anyLong()))
.thenReturn(progressDispatcher);
}
@Test
public void testObtainBaseImageLayers_skipObtainingDuplicateLayers()
throws DigestException, InterruptedException, ExecutionException {
- Mockito.when(executorService.submit(Mockito.any(PullBaseImageStep.class)))
+ when(executorService.submit(Mockito.any(PullBaseImageStep.class)))
.thenReturn(Futures.immediateFuture(new ImagesAndRegistryClient(null, null)));
// Pretend that a thread pulling base images returned some (meaningless) result.
stepsRunner.pullBaseImages(progressDispatcherFactory);
@@ -118,7 +126,7 @@ public void testObtainBaseImageLayers_skipObtainingDuplicateLayers()
PreparedLayer preparedLayer1 = Mockito.mock(PreparedLayer.class);
PreparedLayer preparedLayer2 = Mockito.mock(PreparedLayer.class);
PreparedLayer preparedLayer3 = Mockito.mock(PreparedLayer.class);
- Mockito.when(executorService.submit(Mockito.any(ObtainBaseImageLayerStep.class)))
+ when(executorService.submit(Mockito.any(ObtainBaseImageLayerStep.class)))
.thenReturn(Futures.immediateFuture(preparedLayer1))
.thenReturn(Futures.immediateFuture(preparedLayer2))
.thenReturn(Futures.immediateFuture(preparedLayer3));
@@ -127,7 +135,7 @@ public void testObtainBaseImageLayers_skipObtainingDuplicateLayers()
// 1. Should schedule two threads to obtain new layers.
Image image = Mockito.mock(Image.class);
- Mockito.when(image.getLayers()).thenReturn(ImmutableList.of(layer1, layer2));
+ when(image.getLayers()).thenReturn(ImmutableList.of(layer1, layer2));
stepsRunner.obtainBaseImageLayers(image, true, preparedLayersCache, progressDispatcherFactory);
Assert.assertEquals(2, preparedLayersCache.size()); // two new layers cached
@@ -141,7 +149,7 @@ public void testObtainBaseImageLayers_skipObtainingDuplicateLayers()
Assert.assertEquals(preparedLayer2, preparedLayersCache.get(digest2).get());
// 3. Another image with one duplicate layer.
- Mockito.when(image.getLayers()).thenReturn(ImmutableList.of(layer3, layer2));
+ when(image.getLayers()).thenReturn(ImmutableList.of(layer3, layer2));
stepsRunner.obtainBaseImageLayers(image, true, preparedLayersCache, progressDispatcherFactory);
Assert.assertEquals(3, preparedLayersCache.size()); // one new layer cached
Assert.assertEquals(preparedLayer1, preparedLayersCache.get(digest1).get());
@@ -156,7 +164,7 @@ public void testObtainBaseImageLayers_skipObtainingDuplicateLayers()
@Test
public void testIsImagePushed_skipExistingEnabledAndManifestPresent() {
Optional> manifestResult = Mockito.mock(Optional.class);
- Mockito.when(manifestResult.isPresent()).thenReturn(true);
+ when(manifestResult.isPresent()).thenReturn(true);
System.setProperty(JibSystemProperties.SKIP_EXISTING_IMAGES, "true");
Assert.assertFalse(stepsRunner.isImagePushed(manifestResult));
@@ -174,8 +182,86 @@ public void testIsImagePushed_skipExistingImageDisabledAndManifestPresent() {
public void testIsImagePushed_skipExistingImageEnabledAndManifestNotPresent() {
Optional> manifestResult = Mockito.mock(Optional.class);
System.setProperty(JibSystemProperties.SKIP_EXISTING_IMAGES, "true");
- Mockito.when(manifestResult.isPresent()).thenReturn(false);
+ when(manifestResult.isPresent()).thenReturn(false);
Assert.assertTrue(stepsRunner.isImagePushed(manifestResult));
}
+
+ @Test
+ public void testFetchBuildImageForLocalBuild_matchingOsAndArch()
+ throws ExecutionException, InterruptedException {
+ when(builtArm64AndLinuxImage.getArchitecture()).thenReturn("arm64");
+ when(builtAmd64AndWindowsImage.getArchitecture()).thenReturn("amd64");
+ when(builtAmd64AndWindowsImage.getOs()).thenReturn("windows");
+ when(executorService.submit(Mockito.any(Callable.class)))
+ .thenReturn(
+ Futures.immediateFuture(
+ ImmutableMap.of(
+ baseImage1,
+ Futures.immediateFuture(builtArm64AndLinuxImage),
+ baseImage2,
+ Futures.immediateFuture(builtAmd64AndWindowsImage))));
+ stepsRunner.buildImages(progressDispatcherFactory);
+
+ Optional expectedImage = stepsRunner.fetchBuiltImageForLocalBuild("windows", "amd64");
+
+ assertThat(expectedImage.get().getOs()).isEqualTo("windows");
+ assertThat(expectedImage.get().getArchitecture()).isEqualTo("amd64");
+ }
+
+ @Test
+ public void testFetchBuildImageForLocalBuild_differentOs()
+ throws ExecutionException, InterruptedException {
+ when(builtArm64AndLinuxImage.getArchitecture()).thenReturn("arm64");
+ when(builtArm64AndLinuxImage.getOs()).thenReturn("linux");
+ when(builtAmd64AndWindowsImage.getArchitecture()).thenReturn("amd64");
+ when(executorService.submit(Mockito.any(Callable.class)))
+ .thenReturn(
+ Futures.immediateFuture(
+ ImmutableMap.of(
+ baseImage1,
+ Futures.immediateFuture(builtArm64AndLinuxImage),
+ baseImage2,
+ Futures.immediateFuture(builtAmd64AndWindowsImage))));
+ stepsRunner.buildImages(progressDispatcherFactory);
+
+ Optional expectedImage = stepsRunner.fetchBuiltImageForLocalBuild("os", "arm64");
+
+ assertThat(expectedImage.isPresent()).isFalse();
+ }
+
+ @Test
+ public void testFetchBuildImageForLocalBuild_differentArch()
+ throws ExecutionException, InterruptedException {
+ when(builtArm64AndLinuxImage.getArchitecture()).thenReturn("arm64");
+ when(builtAmd64AndWindowsImage.getArchitecture()).thenReturn("amd64");
+ when(executorService.submit(Mockito.any(Callable.class)))
+ .thenReturn(
+ Futures.immediateFuture(
+ ImmutableMap.of(
+ baseImage1,
+ Futures.immediateFuture(builtArm64AndLinuxImage),
+ baseImage2,
+ Futures.immediateFuture(builtAmd64AndWindowsImage))));
+ stepsRunner.buildImages(progressDispatcherFactory);
+
+ Optional expectedImage = stepsRunner.fetchBuiltImageForLocalBuild("linux", "arch");
+
+ assertThat(expectedImage.isPresent()).isFalse();
+ }
+
+ @Test
+ public void testNormalizeArchitecture_aarch64() {
+ assertThat(stepsRunner.normalizeArchitecture("aarch64")).isEqualTo("arm64");
+ }
+
+ @Test
+ public void testNormalizeArchitecture_x86_64() {
+ assertThat(stepsRunner.normalizeArchitecture("x86_64")).isEqualTo("amd64");
+ }
+
+ @Test
+ public void testNormalizeArchitecture_arm() {
+ assertThat(stepsRunner.normalizeArchitecture("arm")).isEqualTo("arm");
+ }
}
diff --git a/jib-core/src/test/java/com/google/cloud/tools/jib/docker/CliDockerClientTest.java b/jib-core/src/test/java/com/google/cloud/tools/jib/docker/CliDockerClientTest.java
index 3e7cca5b4ac..a88539f8c05 100644
--- a/jib-core/src/test/java/com/google/cloud/tools/jib/docker/CliDockerClientTest.java
+++ b/jib-core/src/test/java/com/google/cloud/tools/jib/docker/CliDockerClientTest.java
@@ -16,8 +16,12 @@
package com.google.cloud.tools.jib.docker;
+import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.assertThrows;
+
import com.google.cloud.tools.jib.api.DescriptorDigest;
import com.google.cloud.tools.jib.api.DockerClient;
+import com.google.cloud.tools.jib.api.DockerInfoDetails;
import com.google.cloud.tools.jib.api.ImageReference;
import com.google.cloud.tools.jib.docker.CliDockerClient.DockerImageDetails;
import com.google.cloud.tools.jib.image.ImageTarball;
@@ -83,6 +87,60 @@ public void testIsDockerInstalled_pass() throws URISyntaxException {
Paths.get(Resources.getResource("core/docker/emptyFile").toURI())));
}
+ @Test
+ public void testInfo() throws InterruptedException, IOException {
+ String dockerInfoJson = "{ \"OSType\": \"windows\"," + "\"Architecture\": \"arm64\"}";
+ DockerClient testDockerClient =
+ new CliDockerClient(
+ subcommand -> {
+ assertThat(subcommand).containsExactly("info", "-f", "{{json .}}");
+ return mockProcessBuilder;
+ });
+ // Simulates stdout.
+ Mockito.when(mockProcess.getInputStream())
+ .thenReturn(new ByteArrayInputStream(dockerInfoJson.getBytes()));
+
+ DockerInfoDetails infoDetails = testDockerClient.info();
+ assertThat(infoDetails.getArchitecture()).isEqualTo("arm64");
+ assertThat(infoDetails.getOsType()).isEqualTo("windows");
+ }
+
+ @Test
+ public void testInfo_fail() throws InterruptedException {
+ DockerClient testDockerClient =
+ new CliDockerClient(
+ subcommand -> {
+ assertThat(subcommand).containsExactly("info", "-f", "{{json .}}");
+ return mockProcessBuilder;
+ });
+ Mockito.when(mockProcess.waitFor()).thenReturn(1);
+ Mockito.when(mockProcess.getErrorStream())
+ .thenReturn(new ByteArrayInputStream("error".getBytes(StandardCharsets.UTF_8)));
+
+ IOException exception = assertThrows(IOException.class, testDockerClient::info);
+ assertThat(exception)
+ .hasMessageThat()
+ .contains("'docker info' command failed with error: error");
+ }
+
+ @Test
+ public void testInfo_returnsUnknownKeys() throws InterruptedException, IOException {
+ String dockerInfoJson = "{ \"unknownOS\": \"windows\"," + "\"unknownArchitecture\": \"arm64\"}";
+ DockerClient testDockerClient =
+ new CliDockerClient(
+ subcommand -> {
+ assertThat(subcommand).containsExactly("info", "-f", "{{json .}}");
+ return mockProcessBuilder;
+ });
+ Mockito.when(mockProcess.waitFor()).thenReturn(0);
+ Mockito.when(mockProcess.getInputStream())
+ .thenReturn(new ByteArrayInputStream(dockerInfoJson.getBytes()));
+
+ DockerInfoDetails infoDetails = testDockerClient.info();
+ assertThat(infoDetails.getArchitecture()).isEmpty();
+ assertThat(infoDetails.getOsType()).isEmpty();
+ }
+
@Test
public void testLoad() throws IOException, InterruptedException {
DockerClient testDockerClient =
diff --git a/jib-gradle-plugin/CHANGELOG.md b/jib-gradle-plugin/CHANGELOG.md
index c91b85fee8b..1d519865818 100644
--- a/jib-gradle-plugin/CHANGELOG.md
+++ b/jib-gradle-plugin/CHANGELOG.md
@@ -9,6 +9,11 @@ All notable changes to this project will be documented in this file.
### Fixed
+## 3.4.3
+
+### Fixed
+- fix: When building to the local docker daemon with multiple platforms configured, Jib will now automatically select the image that matches the OS type and architecture of the local Docker environment. ([#4249](https://github.com/GoogleContainerTools/jib/pull/4249))
+
## 3.4.2
### Changed
diff --git a/jib-gradle-plugin/README.md b/jib-gradle-plugin/README.md
index 6bdf8aca965..5c4001018d3 100644
--- a/jib-gradle-plugin/README.md
+++ b/jib-gradle-plugin/README.md
@@ -53,7 +53,7 @@ In your Gradle Java project, add the plugin to your `build.gradle`:
```groovy
plugins {
- id 'com.google.cloud.tools.jib' version '3.4.2'
+ id 'com.google.cloud.tools.jib' version '3.4.3'
}
```
diff --git a/jib-gradle-plugin/gradle.properties b/jib-gradle-plugin/gradle.properties
index 22c5f533eed..882261a1db3 100644
--- a/jib-gradle-plugin/gradle.properties
+++ b/jib-gradle-plugin/gradle.properties
@@ -1 +1 @@
-version = 3.4.3-SNAPSHOT
+version = 3.4.4-SNAPSHOT
diff --git a/jib-gradle-plugin/src/integration-test/java/com/google/cloud/tools/jib/gradle/SingleProjectIntegrationTest.java b/jib-gradle-plugin/src/integration-test/java/com/google/cloud/tools/jib/gradle/SingleProjectIntegrationTest.java
index 7c53878dc50..faf32e883ed 100644
--- a/jib-gradle-plugin/src/integration-test/java/com/google/cloud/tools/jib/gradle/SingleProjectIntegrationTest.java
+++ b/jib-gradle-plugin/src/integration-test/java/com/google/cloud/tools/jib/gradle/SingleProjectIntegrationTest.java
@@ -592,4 +592,14 @@ public void testCredHelperConfiguration()
simpleTestProject, targetImage, "build-cred-helper.gradle"))
.isEqualTo("Hello, world. \n1970-01-01T00:00:01Z\n");
}
+
+ @Test
+ public void testToDockerDaemon_multiPlatform()
+ throws DigestException, IOException, InterruptedException {
+ String targetImage = "multiplatform:gradle" + System.nanoTime();
+ assertThat(
+ JibRunHelper.buildToDockerDaemonAndRun(
+ simpleTestProject, targetImage, "build-multi-platform.gradle"))
+ .isEqualTo("Hello, world. \n1970-01-01T00:00:01Z\n");
+ }
}
diff --git a/jib-gradle-plugin/src/integration-test/resources/gradle/projects/simple/build-multi-platform.gradle b/jib-gradle-plugin/src/integration-test/resources/gradle/projects/simple/build-multi-platform.gradle
new file mode 100644
index 00000000000..52351648848
--- /dev/null
+++ b/jib-gradle-plugin/src/integration-test/resources/gradle/projects/simple/build-multi-platform.gradle
@@ -0,0 +1,34 @@
+plugins {
+ id 'java'
+ id 'com.google.cloud.tools.jib'
+}
+
+sourceCompatibility = 1.8
+targetCompatibility = 1.8
+
+repositories {
+ mavenCentral()
+}
+
+dependencies {
+ implementation files('libs/dependency-1.0.0.jar')
+}
+
+jib {
+ from {
+ image = 'eclipse-temurin:11'
+ platforms {
+ platform {
+ architecture = 'amd64'
+ os = 'linux'
+ }
+ platform {
+ architecture = 'arm64'
+ os = 'linux'
+ }
+ }
+ }
+ to {
+ image = System.getProperty('_TARGET_IMAGE')
+ }
+}
diff --git a/jib-maven-plugin/CHANGELOG.md b/jib-maven-plugin/CHANGELOG.md
index 77a8f260457..2f4ca92c14e 100644
--- a/jib-maven-plugin/CHANGELOG.md
+++ b/jib-maven-plugin/CHANGELOG.md
@@ -9,6 +9,11 @@ All notable changes to this project will be documented in this file.
### Fixed
+## 3.4.3
+
+### Fixed
+- fix: When building to the local docker daemon with multiple platforms configured, Jib will now automatically select the image that matches the OS type and architecture of the local Docker environment. ([#4249](https://github.com/GoogleContainerTools/jib/pull/4249))
+
## 3.4.2
### Changed
diff --git a/jib-maven-plugin/README.md b/jib-maven-plugin/README.md
index 37d347355aa..11046482ff9 100644
--- a/jib-maven-plugin/README.md
+++ b/jib-maven-plugin/README.md
@@ -48,7 +48,7 @@ For information about the project, see the [Jib project README](../README.md).
You can containerize your application easily with one command:
```shell
-mvn compile com.google.cloud.tools:jib-maven-plugin:3.4.1:build -Dimage=
+mvn compile com.google.cloud.tools:jib-maven-plugin:3.4.3:build -Dimage=
```
This builds and pushes a container image for your application to a container registry. *If you encounter authentication issues, see [Authentication Methods](#authentication-methods).*
@@ -56,7 +56,7 @@ This builds and pushes a container image for your application to a container reg
To build to a Docker daemon, use:
```shell
-mvn compile com.google.cloud.tools:jib-maven-plugin:3.4.1:dockerBuild
+mvn compile com.google.cloud.tools:jib-maven-plugin:3.4.3:dockerBuild
```
If you would like to set up Jib as part of your Maven build, follow the guide below.
@@ -74,7 +74,7 @@ In your Maven Java project, add the plugin to your `pom.xml`:
com.google.cloud.tools
jib-maven-plugin
- 3.4.1
+ 3.4.3
myimage
diff --git a/jib-maven-plugin/gradle.properties b/jib-maven-plugin/gradle.properties
index 22c5f533eed..882261a1db3 100644
--- a/jib-maven-plugin/gradle.properties
+++ b/jib-maven-plugin/gradle.properties
@@ -1 +1 @@
-version = 3.4.3-SNAPSHOT
+version = 3.4.4-SNAPSHOT
diff --git a/jib-maven-plugin/src/integration-test/java/com/google/cloud/tools/jib/maven/BuildDockerMojoIntegrationTest.java b/jib-maven-plugin/src/integration-test/java/com/google/cloud/tools/jib/maven/BuildDockerMojoIntegrationTest.java
index 7d3be1d8c51..d41781d223e 100644
--- a/jib-maven-plugin/src/integration-test/java/com/google/cloud/tools/jib/maven/BuildDockerMojoIntegrationTest.java
+++ b/jib-maven-plugin/src/integration-test/java/com/google/cloud/tools/jib/maven/BuildDockerMojoIntegrationTest.java
@@ -275,4 +275,14 @@ public void testCredHelperConfigurationComplex()
"Hello, world. \n1970-01-01T00:00:01Z\n",
new Command("docker", "run", "--rm", targetImage).run());
}
+
+ @Test
+ public void testMultiPlatform()
+ throws DigestException, VerificationException, IOException, InterruptedException {
+ String targetImage = "multiplatformproject:maven" + System.nanoTime();
+ buildToDockerDaemon(simpleTestProject, targetImage, "pom-multiplatform-build.xml");
+ Assert.assertEquals(
+ "Hello, world. \n1970-01-01T00:00:01Z\n",
+ new Command("docker", "run", "--rm", targetImage).run());
+ }
}
diff --git a/jib-maven-plugin/src/test/resources/maven/projects/simple/pom-multiplatform-build.xml b/jib-maven-plugin/src/test/resources/maven/projects/simple/pom-multiplatform-build.xml
index 76a5966111d..3269081f05d 100644
--- a/jib-maven-plugin/src/test/resources/maven/projects/simple/pom-multiplatform-build.xml
+++ b/jib-maven-plugin/src/test/resources/maven/projects/simple/pom-multiplatform-build.xml
@@ -41,7 +41,7 @@
${jib-maven-plugin.version}
- busybox@sha256:4f47c01fa91355af2865ac10fef5bf6ec9c7f42ad2321377c21e844427972977
+ eclipse-temurin:11
arm64