Skip to content

Commit ff15988

Browse files
authored
fix: allow pushing images with different arch/os to docker daemon (#4268)
* fix: allow pushing images with different arch/os to docker daemon; fix warning logging
1 parent b06b001 commit ff15988

File tree

4 files changed

+178
-88
lines changed

4 files changed

+178
-88
lines changed

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

+18-57
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,6 @@
1616

1717
package com.google.cloud.tools.jib.api;
1818

19-
import static com.google.common.truth.Truth.assertThat;
20-
import static org.junit.Assert.assertThrows;
21-
2219
import com.google.cloud.tools.jib.Command;
2320
import com.google.cloud.tools.jib.api.buildplan.Platform;
2421
import com.google.cloud.tools.jib.blob.Blobs;
@@ -60,6 +57,8 @@ public class JibIntegrationTest {
6057

6158
@Rule public final TemporaryFolder temporaryFolder = new TemporaryFolder();
6259

60+
private String imageToDelete;
61+
6362
private final String dockerHost =
6463
System.getenv("DOCKER_IP") != null ? System.getenv("DOCKER_IP") : "localhost";
6564

@@ -104,8 +103,11 @@ public void setUp() {
104103
}
105104

106105
@After
107-
public void tearDown() {
106+
public void tearDown() throws IOException, InterruptedException {
108107
System.clearProperty("sendCredentialsOverHttp");
108+
if (imageToDelete != null) {
109+
new Command("docker", "rmi", imageToDelete).run();
110+
}
109111
}
110112

111113
@Test
@@ -122,6 +124,7 @@ public void testBasic_helloWorld()
122124
Assert.assertEquals("Hello World\n", pullAndRunBuiltImage(toImage));
123125
Assert.assertEquals(
124126
"Hello World\n", pullAndRunBuiltImage(toImage + "@" + jibContainer.getDigest()));
127+
imageToDelete = toImage;
125128
}
126129

127130
@Test
@@ -138,20 +141,21 @@ public void testBasic_dockerDaemonBaseImage()
138141
Assert.assertEquals("Hello World\n", pullAndRunBuiltImage(toImage));
139142
Assert.assertEquals(
140143
"Hello World\n", pullAndRunBuiltImage(toImage + "@" + jibContainer.getDigest()));
144+
imageToDelete = toImage;
141145
}
142146

143147
@Test
144148
public void testBasic_dockerDaemonBaseImageToDockerDaemon()
145149
throws IOException, InterruptedException, InvalidImageReferenceException, ExecutionException,
146150
RegistryException, CacheDirectoryCreationException {
151+
String toImage = dockerHost + ":5000/docker-to-docker";
147152
Jib.from(DockerDaemonImage.named(dockerHost + ":5000/busybox"))
148153
.setEntrypoint("echo", "Hello World")
149-
.containerize(
150-
Containerizer.to(DockerDaemonImage.named(dockerHost + ":5000/docker-to-docker")));
154+
.containerize(Containerizer.to(DockerDaemonImage.named(toImage)));
151155

152-
String output =
153-
new Command("docker", "run", "--rm", dockerHost + ":5000/docker-to-docker").run();
156+
String output = new Command("docker", "run", "--rm", toImage).run();
154157
Assert.assertEquals("Hello World\n", output);
158+
imageToDelete = toImage;
155159
}
156160

157161
@Test
@@ -171,6 +175,7 @@ public void testBasic_tarBaseImage_dockerSavedCommand()
171175
Assert.assertEquals("Hello World\n", pullAndRunBuiltImage(toImage));
172176
Assert.assertEquals(
173177
"Hello World\n", pullAndRunBuiltImage(toImage + "@" + jibContainer.getDigest()));
178+
imageToDelete = toImage;
174179
}
175180

176181
@Test
@@ -233,6 +238,7 @@ public void testBasic_tarBaseImage_jibImageToDockerDaemon()
233238
Assert.assertEquals("Hello World\n", pullAndRunBuiltImage(toImage));
234239
Assert.assertEquals(
235240
"Hello World\n", pullAndRunBuiltImage(toImage + "@" + jibContainer.getDigest()));
241+
imageToDelete = toImage;
236242
}
237243

238244
@Test
@@ -311,59 +317,14 @@ public void testScratch_multiPlatform()
311317
public void testBasic_jibImageToDockerDaemon()
312318
throws IOException, InterruptedException, InvalidImageReferenceException, ExecutionException,
313319
RegistryException, CacheDirectoryCreationException {
320+
String toImage = dockerHost + ":5000/docker-to-docker";
314321
Jib.from(DockerDaemonImage.named(dockerHost + ":5000/busybox"))
315322
.setEntrypoint("echo", "Hello World")
316-
.containerize(
317-
Containerizer.to(DockerDaemonImage.named(dockerHost + ":5000/docker-to-docker")));
323+
.containerize(Containerizer.to(DockerDaemonImage.named(toImage)));
318324

319-
String output =
320-
new Command("docker", "run", "--rm", dockerHost + ":5000/docker-to-docker").run();
325+
String output = new Command("docker", "run", "--rm", toImage).run();
321326
Assert.assertEquals("Hello World\n", output);
322-
}
323-
324-
@Test
325-
public void testBasicMultiPlatform_toDockerDaemon()
326-
throws IOException, InterruptedException, ExecutionException, RegistryException,
327-
CacheDirectoryCreationException, InvalidImageReferenceException {
328-
Jib.from(
329-
RegistryImage.named(
330-
"busybox@sha256:4f47c01fa91355af2865ac10fef5bf6ec9c7f42ad2321377c21e844427972977"))
331-
.setPlatforms(
332-
ImmutableSet.of(new Platform("arm64", "linux"), new Platform("amd64", "linux")))
333-
.setEntrypoint("echo", "Hello World")
334-
.containerize(
335-
Containerizer.to(
336-
DockerDaemonImage.named(dockerHost + ":5000/docker-daemon-multi-platform"))
337-
.setAllowInsecureRegistries(true));
338-
339-
String output =
340-
new Command("docker", "run", "--rm", dockerHost + ":5000/docker-daemon-multi-platform")
341-
.run();
342-
Assert.assertEquals("Hello World\n", output);
343-
}
344-
345-
@Test
346-
public void testBasicMultiPlatform_toDockerDaemon_noMatchingImage() {
347-
ExecutionException exception =
348-
assertThrows(
349-
ExecutionException.class,
350-
() ->
351-
Jib.from(
352-
RegistryImage.named(
353-
"busybox@sha256:4f47c01fa91355af2865ac10fef5bf6ec9c7f42ad2321377c21e844427972977"))
354-
.setPlatforms(
355-
ImmutableSet.of(
356-
new Platform("s390x", "linux"), new Platform("arm", "linux")))
357-
.setEntrypoint("echo", "Hello World")
358-
.containerize(
359-
Containerizer.to(
360-
DockerDaemonImage.named(
361-
dockerHost + ":5000/docker-daemon-multi-platform"))
362-
.setAllowInsecureRegistries(true)));
363-
assertThat(exception)
364-
.hasCauseThat()
365-
.hasMessageThat()
366-
.startsWith("The configured platforms don't match the Docker Engine's OS and architecture");
327+
imageToDelete = toImage;
367328
}
368329

369330
@Test
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
/*
2+
* Copyright 2024 Google LLC.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
5+
* use this file except in compliance with the License. You may obtain a copy of
6+
* the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12+
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13+
* License for the specific language governing permissions and limitations under
14+
* the License.
15+
*/
16+
17+
package com.google.cloud.tools.jib.api;
18+
19+
import static com.google.common.truth.Truth.assertThat;
20+
21+
import com.google.cloud.tools.jib.Command;
22+
import com.google.cloud.tools.jib.api.buildplan.Platform;
23+
import com.google.cloud.tools.jib.registry.LocalRegistry;
24+
import com.google.common.collect.ImmutableSet;
25+
import java.io.IOException;
26+
import java.util.concurrent.ExecutionException;
27+
import org.junit.After;
28+
import org.junit.Assert;
29+
import org.junit.ClassRule;
30+
import org.junit.Test;
31+
32+
public class JibMultiPlatformIntegrationTest {
33+
34+
@ClassRule public static final LocalRegistry localRegistry = new LocalRegistry(5000);
35+
36+
private final String dockerHost =
37+
System.getenv("DOCKER_IP") != null ? System.getenv("DOCKER_IP") : "localhost";
38+
private String imageToDelete;
39+
40+
@After
41+
public void tearDown() throws IOException, InterruptedException {
42+
System.clearProperty("sendCredentialsOverHttp");
43+
if (imageToDelete != null) {
44+
new Command("docker", "rmi", imageToDelete).run();
45+
}
46+
}
47+
48+
@Test
49+
public void testBasic_jibImageToDockerDaemon_arm64()
50+
throws IOException, InterruptedException, InvalidImageReferenceException, ExecutionException,
51+
RegistryException, CacheDirectoryCreationException {
52+
// Use arm64v8/busybox as base image.
53+
String toImage = dockerHost + ":5000/docker-daemon-mismatched-arch";
54+
Jib.from(
55+
RegistryImage.named(
56+
"busybox@sha256:eb427d855f82782c110b48b9a398556c629ce4951ae252c6f6751a136e194668"))
57+
.containerize(Containerizer.to(DockerDaemonImage.named(toImage)));
58+
String os =
59+
new Command("docker", "inspect", toImage, "--format", "{{.Os}}").run().replace("\n", "");
60+
String architecture =
61+
new Command("docker", "inspect", toImage, "--format", "{{.Architecture}}")
62+
.run()
63+
.replace("\n", "");
64+
assertThat(os).isEqualTo("linux");
65+
assertThat(architecture).isEqualTo("arm64");
66+
imageToDelete = toImage;
67+
}
68+
69+
@Test
70+
public void testBasicMultiPlatform_toDockerDaemon_pickFirstPlatformWhenNoMatchingImage()
71+
throws IOException, InterruptedException, InvalidImageReferenceException,
72+
CacheDirectoryCreationException, ExecutionException, RegistryException {
73+
String toImage = dockerHost + ":5000/docker-daemon-multi-plat-mismatched-configs";
74+
Jib.from(
75+
RegistryImage.named(
76+
"busybox@sha256:4f47c01fa91355af2865ac10fef5bf6ec9c7f42ad2321377c21e844427972977"))
77+
.setPlatforms(ImmutableSet.of(new Platform("s390x", "linux"), new Platform("arm", "linux")))
78+
.containerize(
79+
Containerizer.to(DockerDaemonImage.named(toImage)).setAllowInsecureRegistries(true));
80+
String os =
81+
new Command("docker", "inspect", toImage, "--format", "{{.Os}}").run().replace("\n", "");
82+
String architecture =
83+
new Command("docker", "inspect", toImage, "--format", "{{.Architecture}}")
84+
.run()
85+
.replace("\n", "");
86+
assertThat(os).isEqualTo("linux");
87+
assertThat(architecture).isEqualTo("s390x");
88+
imageToDelete = toImage;
89+
}
90+
91+
@Test
92+
public void testBasicMultiPlatform_toDockerDaemon()
93+
throws IOException, InterruptedException, ExecutionException, RegistryException,
94+
CacheDirectoryCreationException, InvalidImageReferenceException {
95+
String toImage = dockerHost + ":5000/docker-daemon-multi-platform";
96+
Jib.from(
97+
RegistryImage.named(
98+
"busybox@sha256:4f47c01fa91355af2865ac10fef5bf6ec9c7f42ad2321377c21e844427972977"))
99+
.setPlatforms(
100+
ImmutableSet.of(new Platform("arm64", "linux"), new Platform("amd64", "linux")))
101+
.setEntrypoint("echo", "Hello World")
102+
.containerize(
103+
Containerizer.to(DockerDaemonImage.named(toImage)).setAllowInsecureRegistries(true));
104+
105+
String output = new Command("docker", "run", "--rm", toImage).run();
106+
Assert.assertEquals("Hello World\n", output);
107+
imageToDelete = toImage;
108+
}
109+
}

jib-core/src/main/java/com/google/cloud/tools/jib/builder/steps/StepsRunner.java

+21-22
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,14 @@
1919
import com.google.cloud.tools.jib.api.DescriptorDigest;
2020
import com.google.cloud.tools.jib.api.DockerClient;
2121
import com.google.cloud.tools.jib.api.DockerInfoDetails;
22+
import com.google.cloud.tools.jib.api.LogEvent;
2223
import com.google.cloud.tools.jib.blob.BlobDescriptor;
2324
import com.google.cloud.tools.jib.builder.ProgressEventDispatcher;
2425
import com.google.cloud.tools.jib.builder.steps.LocalBaseImageSteps.LocalImage;
2526
import com.google.cloud.tools.jib.builder.steps.PullBaseImageStep.ImagesAndRegistryClient;
2627
import com.google.cloud.tools.jib.configuration.BuildContext;
2728
import com.google.cloud.tools.jib.configuration.ImageConfiguration;
29+
import com.google.cloud.tools.jib.event.EventHandlers;
2830
import com.google.cloud.tools.jib.filesystem.TempDirectoryProvider;
2931
import com.google.cloud.tools.jib.global.JibSystemProperties;
3032
import com.google.cloud.tools.jib.image.Image;
@@ -53,7 +55,6 @@
5355
import java.util.concurrent.ExecutorService;
5456
import java.util.concurrent.Future;
5557
import java.util.function.Consumer;
56-
import java.util.logging.Logger;
5758
import java.util.stream.Collectors;
5859
import javax.annotation.Nullable;
5960

@@ -66,8 +67,6 @@
6667
*/
6768
public class StepsRunner {
6869

69-
private static final Logger LOGGER = Logger.getLogger(StepsRunner.class.getName());
70-
7170
/** Holds the individual step results. */
7271
private static class StepResults {
7372

@@ -624,14 +623,11 @@ private void loadDocker(
624623
DockerInfoDetails dockerInfoDetails = dockerClient.info();
625624
String osType = dockerInfoDetails.getOsType();
626625
String architecture = normalizeArchitecture(dockerInfoDetails.getArchitecture());
627-
Optional<Image> builtImage = fetchBuiltImageForLocalBuild(osType, architecture);
628-
Preconditions.checkState(
629-
builtImage.isPresent(),
630-
String.format(
631-
"The configured platforms don't match the Docker Engine's OS and architecture (%s/%s)",
632-
osType, architecture));
626+
Image builtImage =
627+
fetchBuiltImageForLocalBuild(
628+
osType, architecture, buildContext.getEventHandlers());
633629
return new LoadDockerStep(
634-
buildContext, progressDispatcherFactory, dockerClient, builtImage.get())
630+
buildContext, progressDispatcherFactory, dockerClient, builtImage)
635631
.call();
636632
});
637633
}
@@ -669,21 +665,24 @@ String normalizeArchitecture(String architecture) {
669665
}
670666

671667
@VisibleForTesting
672-
Optional<Image> fetchBuiltImageForLocalBuild(String osType, String architecture)
668+
Image fetchBuiltImageForLocalBuild(
669+
String osType, String architecture, EventHandlers eventHandlers)
673670
throws InterruptedException, ExecutionException {
674671
if (results.baseImagesAndBuiltImages.get().size() > 1) {
675-
LOGGER.warning(
676-
String.format(
677-
"Detected multi-platform configuration, only building the one that matches the local Docker Engine's os and architecture (%s/%s)",
678-
osType, architecture));
679-
}
680-
for (Map.Entry<Image, Future<Image>> imageEntry :
681-
results.baseImagesAndBuiltImages.get().entrySet()) {
682-
Image image = imageEntry.getValue().get();
683-
if (image.getArchitecture().equals(architecture) && image.getOs().equals(osType)) {
684-
return Optional.of(image);
672+
eventHandlers.dispatch(
673+
LogEvent.warn(
674+
String.format(
675+
"Detected multi-platform configuration, only building image that matches the local Docker Engine's os and architecture (%s/%s) or "
676+
+ "the first platform specified",
677+
osType, architecture)));
678+
for (Map.Entry<Image, Future<Image>> imageEntry :
679+
results.baseImagesAndBuiltImages.get().entrySet()) {
680+
Image image = imageEntry.getValue().get();
681+
if (image.getArchitecture().equals(architecture) && image.getOs().equals(osType)) {
682+
return image;
683+
}
685684
}
686685
}
687-
return Optional.empty();
686+
return results.baseImagesAndBuiltImages.get().values().iterator().next().get();
688687
}
689688
}

0 commit comments

Comments
 (0)