Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: allow pushing images with different arch/os to docker daemon #4268

Merged
merged 45 commits into from
Jun 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
982897e
fix: allow pushing images with different arch/os to docker daemon
mpeddada1 Jun 6, 2024
ffc4df7
fix formatting
mpeddada1 Jun 6, 2024
f2d886b
stubbing
mpeddada1 Jun 6, 2024
9ff90bd
add test for arm64 image
mpeddada1 Jun 7, 2024
62ef484
choose more detailed image name
mpeddada1 Jun 10, 2024
49e84f1
temporarily comment out arm64 test
mpeddada1 Jun 10, 2024
4b62074
fix formatting
mpeddada1 Jun 10, 2024
6a8d809
use different base image
mpeddada1 Jun 10, 2024
ba4cfdf
try simple base image to verify start up
mpeddada1 Jun 10, 2024
8b53402
debug
mpeddada1 Jun 11, 2024
bf39817
undo debugging
mpeddada1 Jun 11, 2024
d3ee6b3
fix arm64 test
mpeddada1 Jun 11, 2024
d98b504
debug macos
mpeddada1 Jun 11, 2024
02cc351
undo workaround
mpeddada1 Jun 11, 2024
74b6c04
revert name change
mpeddada1 Jun 11, 2024
25d6e94
rename test and revert image change
mpeddada1 Jun 11, 2024
2144d08
empty commit
mpeddada1 Jun 11, 2024
2c4afc6
rename to testBasic_toDockerDaemon_arm64
mpeddada1 Jun 11, 2024
d4829f6
debug CI behavior with test names
mpeddada1 Jun 11, 2024
25e12b6
verify with diff arch name
mpeddada1 Jun 11, 2024
3c8a6f0
rename to arm64BaseImage
mpeddada1 Jun 11, 2024
fca0b68
add logic to clean up containers after tests
mpeddada1 Jun 13, 2024
e7a51d8
verify with original name after test resource clean up
mpeddada1 Jun 14, 2024
d92123d
understand order of execution on CI
mpeddada1 Jun 14, 2024
0a6862d
change back to original name
mpeddada1 Jun 14, 2024
8ca43ca
verify image creation
mpeddada1 Jun 14, 2024
9552133
skip distroless_ociManifest test
mpeddada1 Jun 14, 2024
61d528d
add Ignore
mpeddada1 Jun 14, 2024
b762590
verify with lightweight base image
mpeddada1 Jun 14, 2024
1668da6
use separate registry for ociManifest test
mpeddada1 Jun 14, 2024
87d9113
revert to previous image ref
mpeddada1 Jun 14, 2024
908a72b
try different base image
mpeddada1 Jun 14, 2024
693f09c
debug
mpeddada1 Jun 15, 2024
ec31463
docker daemon
mpeddada1 Jun 15, 2024
c32a132
add logging for tear down and move jib build into one test
mpeddada1 Jun 18, 2024
a9f4415
debugging
mpeddada1 Jun 18, 2024
72ab27c
switch order
mpeddada1 Jun 19, 2024
b1c242f
sleep before next build
mpeddada1 Jun 19, 2024
772c883
check with timeout of 120
mpeddada1 Jun 19, 2024
9d51b1f
fromScratch
mpeddada1 Jun 20, 2024
74c25a2
enable all tests
mpeddada1 Jun 20, 2024
3adfb49
add header
mpeddada1 Jun 20, 2024
ebc6c14
clean up tests; fix logging
mpeddada1 Jun 21, 2024
841f207
undo debugging logs
mpeddada1 Jun 21, 2024
71772a6
remove debugging comments
mpeddada1 Jun 24, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,6 @@

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;
Expand Down Expand Up @@ -60,6 +57,8 @@ public class JibIntegrationTest {

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

private String imageToDelete;

private final String dockerHost =
System.getenv("DOCKER_IP") != null ? System.getenv("DOCKER_IP") : "localhost";

Expand Down Expand Up @@ -104,8 +103,11 @@ public void setUp() {
}

@After
public void tearDown() {
public void tearDown() throws IOException, InterruptedException {
System.clearProperty("sendCredentialsOverHttp");
if (imageToDelete != null) {
new Command("docker", "rmi", imageToDelete).run();
}
}

@Test
Expand All @@ -122,6 +124,7 @@ public void testBasic_helloWorld()
Assert.assertEquals("Hello World\n", pullAndRunBuiltImage(toImage));
Assert.assertEquals(
"Hello World\n", pullAndRunBuiltImage(toImage + "@" + jibContainer.getDigest()));
imageToDelete = toImage;
}

@Test
Expand All @@ -138,20 +141,21 @@ public void testBasic_dockerDaemonBaseImage()
Assert.assertEquals("Hello World\n", pullAndRunBuiltImage(toImage));
Assert.assertEquals(
"Hello World\n", pullAndRunBuiltImage(toImage + "@" + jibContainer.getDigest()));
imageToDelete = toImage;
}

@Test
public void testBasic_dockerDaemonBaseImageToDockerDaemon()
throws IOException, InterruptedException, InvalidImageReferenceException, ExecutionException,
RegistryException, CacheDirectoryCreationException {
String toImage = dockerHost + ":5000/docker-to-docker";
Jib.from(DockerDaemonImage.named(dockerHost + ":5000/busybox"))
.setEntrypoint("echo", "Hello World")
.containerize(
Containerizer.to(DockerDaemonImage.named(dockerHost + ":5000/docker-to-docker")));
.containerize(Containerizer.to(DockerDaemonImage.named(toImage)));

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

@Test
Expand All @@ -171,6 +175,7 @@ public void testBasic_tarBaseImage_dockerSavedCommand()
Assert.assertEquals("Hello World\n", pullAndRunBuiltImage(toImage));
Assert.assertEquals(
"Hello World\n", pullAndRunBuiltImage(toImage + "@" + jibContainer.getDigest()));
imageToDelete = toImage;
}

@Test
Expand Down Expand Up @@ -233,6 +238,7 @@ public void testBasic_tarBaseImage_jibImageToDockerDaemon()
Assert.assertEquals("Hello World\n", pullAndRunBuiltImage(toImage));
Assert.assertEquals(
"Hello World\n", pullAndRunBuiltImage(toImage + "@" + jibContainer.getDigest()));
imageToDelete = toImage;
}

@Test
Expand Down Expand Up @@ -311,59 +317,14 @@ public void testScratch_multiPlatform()
public void testBasic_jibImageToDockerDaemon()
throws IOException, InterruptedException, InvalidImageReferenceException, ExecutionException,
RegistryException, CacheDirectoryCreationException {
String toImage = dockerHost + ":5000/docker-to-docker";
Jib.from(DockerDaemonImage.named(dockerHost + ":5000/busybox"))
.setEntrypoint("echo", "Hello World")
.containerize(
Containerizer.to(DockerDaemonImage.named(dockerHost + ":5000/docker-to-docker")));
.containerize(Containerizer.to(DockerDaemonImage.named(toImage)));

String output =
new Command("docker", "run", "--rm", dockerHost + ":5000/docker-to-docker").run();
String output = new Command("docker", "run", "--rm", toImage).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");
imageToDelete = toImage;
}

@Test
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
/*
* 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 static com.google.common.truth.Truth.assertThat;

import com.google.cloud.tools.jib.Command;
import com.google.cloud.tools.jib.api.buildplan.Platform;
import com.google.cloud.tools.jib.registry.LocalRegistry;
import com.google.common.collect.ImmutableSet;
import java.io.IOException;
import java.util.concurrent.ExecutionException;
import org.junit.After;
import org.junit.Assert;
import org.junit.ClassRule;
import org.junit.Test;

public class JibMultiPlatformIntegrationTest {

@ClassRule public static final LocalRegistry localRegistry = new LocalRegistry(5000);

private final String dockerHost =
System.getenv("DOCKER_IP") != null ? System.getenv("DOCKER_IP") : "localhost";
private String imageToDelete;

@After
public void tearDown() throws IOException, InterruptedException {
System.clearProperty("sendCredentialsOverHttp");
if (imageToDelete != null) {
new Command("docker", "rmi", imageToDelete).run();
}
}

@Test
public void testBasic_jibImageToDockerDaemon_arm64()
throws IOException, InterruptedException, InvalidImageReferenceException, ExecutionException,
RegistryException, CacheDirectoryCreationException {
// Use arm64v8/busybox as base image.
String toImage = dockerHost + ":5000/docker-daemon-mismatched-arch";
Jib.from(
RegistryImage.named(
"busybox@sha256:eb427d855f82782c110b48b9a398556c629ce4951ae252c6f6751a136e194668"))
.containerize(Containerizer.to(DockerDaemonImage.named(toImage)));
String os =
new Command("docker", "inspect", toImage, "--format", "{{.Os}}").run().replace("\n", "");
String architecture =
new Command("docker", "inspect", toImage, "--format", "{{.Architecture}}")
.run()
.replace("\n", "");
assertThat(os).isEqualTo("linux");
assertThat(architecture).isEqualTo("arm64");
imageToDelete = toImage;
}

@Test
public void testBasicMultiPlatform_toDockerDaemon_pickFirstPlatformWhenNoMatchingImage()
throws IOException, InterruptedException, InvalidImageReferenceException,
CacheDirectoryCreationException, ExecutionException, RegistryException {
String toImage = dockerHost + ":5000/docker-daemon-multi-plat-mismatched-configs";
Jib.from(
RegistryImage.named(
"busybox@sha256:4f47c01fa91355af2865ac10fef5bf6ec9c7f42ad2321377c21e844427972977"))
.setPlatforms(ImmutableSet.of(new Platform("s390x", "linux"), new Platform("arm", "linux")))
.containerize(
Containerizer.to(DockerDaemonImage.named(toImage)).setAllowInsecureRegistries(true));
String os =
new Command("docker", "inspect", toImage, "--format", "{{.Os}}").run().replace("\n", "");
String architecture =
new Command("docker", "inspect", toImage, "--format", "{{.Architecture}}")
.run()
.replace("\n", "");
assertThat(os).isEqualTo("linux");
assertThat(architecture).isEqualTo("s390x");
imageToDelete = toImage;
}

@Test
public void testBasicMultiPlatform_toDockerDaemon()
throws IOException, InterruptedException, ExecutionException, RegistryException,
CacheDirectoryCreationException, InvalidImageReferenceException {
String toImage = dockerHost + ":5000/docker-daemon-multi-platform";
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(toImage)).setAllowInsecureRegistries(true));

String output = new Command("docker", "run", "--rm", toImage).run();
Assert.assertEquals("Hello World\n", output);
imageToDelete = toImage;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,14 @@
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.LogEvent;
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;
import com.google.cloud.tools.jib.builder.steps.PullBaseImageStep.ImagesAndRegistryClient;
import com.google.cloud.tools.jib.configuration.BuildContext;
import com.google.cloud.tools.jib.configuration.ImageConfiguration;
import com.google.cloud.tools.jib.event.EventHandlers;
import com.google.cloud.tools.jib.filesystem.TempDirectoryProvider;
import com.google.cloud.tools.jib.global.JibSystemProperties;
import com.google.cloud.tools.jib.image.Image;
Expand Down Expand Up @@ -53,7 +55,6 @@
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;

Expand All @@ -66,8 +67,6 @@
*/
public class StepsRunner {

private static final Logger LOGGER = Logger.getLogger(StepsRunner.class.getName());

/** Holds the individual step results. */
private static class StepResults {

Expand Down Expand Up @@ -624,14 +623,11 @@ private void loadDocker(
DockerInfoDetails dockerInfoDetails = dockerClient.info();
String osType = dockerInfoDetails.getOsType();
String architecture = normalizeArchitecture(dockerInfoDetails.getArchitecture());
Optional<Image> 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));
Image builtImage =
fetchBuiltImageForLocalBuild(
osType, architecture, buildContext.getEventHandlers());
return new LoadDockerStep(
buildContext, progressDispatcherFactory, dockerClient, builtImage.get())
buildContext, progressDispatcherFactory, dockerClient, builtImage)
.call();
});
}
Expand Down Expand Up @@ -669,21 +665,24 @@ String normalizeArchitecture(String architecture) {
}

@VisibleForTesting
Optional<Image> fetchBuiltImageForLocalBuild(String osType, String architecture)
Image fetchBuiltImageForLocalBuild(
String osType, String architecture, EventHandlers eventHandlers)
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<Image, Future<Image>> imageEntry :
results.baseImagesAndBuiltImages.get().entrySet()) {
Image image = imageEntry.getValue().get();
if (image.getArchitecture().equals(architecture) && image.getOs().equals(osType)) {
return Optional.of(image);
eventHandlers.dispatch(
LogEvent.warn(
String.format(
"Detected multi-platform configuration, only building image that matches the local Docker Engine's os and architecture (%s/%s) or "
+ "the first platform specified",
osType, architecture)));
for (Map.Entry<Image, Future<Image>> imageEntry :
results.baseImagesAndBuiltImages.get().entrySet()) {
Image image = imageEntry.getValue().get();
if (image.getArchitecture().equals(architecture) && image.getOs().equals(osType)) {
return image;
}
}
}
return Optional.empty();
return results.baseImagesAndBuiltImages.get().values().iterator().next().get();
}
}
Loading
Loading