Skip to content

Commit

Permalink
feat: determine platform details from local docker installation for j…
Browse files Browse the repository at this point in the history
…ibDockerBuild (#4249)

* feat: determine platform from local docker env when building to docker daemon
  • Loading branch information
mpeddada1 authored May 30, 2024
1 parent 8b13f0d commit ec44330
Show file tree
Hide file tree
Showing 12 changed files with 378 additions and 16 deletions.
2 changes: 1 addition & 1 deletion config/checkstyle/copyright-java.header
Original file line number Diff line number Diff line change
@@ -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$
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,4 +69,15 @@ void save(ImageReference imageReference, Path outputPath, Consumer<Long> 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();
}
}
Original file line number Diff line number Diff line change
@@ -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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;

Expand All @@ -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 {

Expand Down Expand Up @@ -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(
() -> {
Expand Down Expand Up @@ -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<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));
return new LoadDockerStep(
buildContext, progressDispatcherFactory, dockerClient, builtImage)
buildContext, progressDispatcherFactory, dockerClient, builtImage.get())
.call();
});
}
Expand All @@ -647,4 +656,34 @@ private void writeTarFile(
private <E> List<Future<E>> scheduleCallables(ImmutableList<? extends Callable<E>> 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<Image> 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<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);
}
}
return Optional.empty();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -184,6 +185,17 @@ public boolean supported(Map<String, String> 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<Long> writtenByteCountListener)
throws InterruptedException, IOException {
Expand Down
Loading

0 comments on commit ec44330

Please sign in to comment.