From a6745e07b8f4399f5a0c8c10989e993074871a02 Mon Sep 17 00:00:00 2001 From: Georgios Andrianakis Date: Thu, 10 Mar 2022 15:41:51 +0200 Subject: [PATCH] Detect container runtime in when using Jib The detection uses the same method Quarkus already used when building the native binary using a container build Fixes: #24231 --- .../quarkus/deployment/pkg/NativeConfig.java | 16 +--- .../NativeImageBuildContainerRunner.java | 58 ++------------- .../NativeImageBuildLocalContainerRunner.java | 3 +- .../pkg/steps/UpxCompressionBuildStep.java | 7 +- .../deployment/util/ContainerRuntimeUtil.java | 73 +++++++++++++++++++ .../image/jib/deployment/JibProcessor.java | 6 ++ 6 files changed, 92 insertions(+), 71 deletions(-) create mode 100644 core/deployment/src/main/java/io/quarkus/deployment/util/ContainerRuntimeUtil.java diff --git a/core/deployment/src/main/java/io/quarkus/deployment/pkg/NativeConfig.java b/core/deployment/src/main/java/io/quarkus/deployment/pkg/NativeConfig.java index 90f78d299581d..fe97f741a510b 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/pkg/NativeConfig.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/pkg/NativeConfig.java @@ -2,10 +2,10 @@ import java.io.File; import java.util.List; -import java.util.Locale; import java.util.Optional; import java.util.OptionalInt; +import io.quarkus.deployment.util.ContainerRuntimeUtil; import io.quarkus.runtime.annotations.ConfigGroup; import io.quarkus.runtime.annotations.ConfigItem; import io.quarkus.runtime.annotations.ConfigPhase; @@ -229,7 +229,7 @@ public String getEffectiveBuilderImage() { * a container build is always done. */ @ConfigItem - public Optional containerRuntime; + public Optional containerRuntime; /** * Options to pass to the container runtime @@ -442,18 +442,6 @@ public static class Compression { public Optional> additionalArgs; } - /** - * Supported Container runtimes - */ - public static enum ContainerRuntime { - DOCKER, - PODMAN; - - public String getExecutableName() { - return this.name().toLowerCase(); - } - } - /** * Supported Builder Image providers/distributions */ diff --git a/core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/NativeImageBuildContainerRunner.java b/core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/NativeImageBuildContainerRunner.java index 21cf66e779abb..a612c8f307507 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/NativeImageBuildContainerRunner.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/NativeImageBuildContainerRunner.java @@ -1,7 +1,6 @@ package io.quarkus.deployment.pkg.steps; import java.io.IOException; -import java.nio.charset.StandardCharsets; import java.nio.file.Path; import java.util.ArrayList; import java.util.Arrays; @@ -15,7 +14,7 @@ import org.jboss.logging.Logger; import io.quarkus.deployment.pkg.NativeConfig; -import io.quarkus.deployment.util.FileUtil; +import io.quarkus.deployment.util.ContainerRuntimeUtil; import io.quarkus.deployment.util.ProcessUtil; public abstract class NativeImageBuildContainerRunner extends NativeImageBuildRunner { @@ -23,14 +22,14 @@ public abstract class NativeImageBuildContainerRunner extends NativeImageBuildRu private static final Logger log = Logger.getLogger(NativeImageBuildContainerRunner.class); final NativeConfig nativeConfig; - protected final NativeConfig.ContainerRuntime containerRuntime; + protected final ContainerRuntimeUtil.ContainerRuntime containerRuntime; String[] baseContainerRuntimeArgs; protected final String outputPath; private final String containerName; public NativeImageBuildContainerRunner(NativeConfig nativeConfig, Path outputDir) { this.nativeConfig = nativeConfig; - containerRuntime = nativeConfig.containerRuntime.orElseGet(NativeImageBuildContainerRunner::detectContainerRuntime); + containerRuntime = nativeConfig.containerRuntime.orElseGet(ContainerRuntimeUtil::detectContainerRuntime); log.infof("Using %s to run the native image builder", containerRuntime.getExecutableName()); this.baseContainerRuntimeArgs = new String[] { "--env", "LANG=C", "--rm" }; @@ -41,8 +40,8 @@ public NativeImageBuildContainerRunner(NativeConfig nativeConfig, Path outputDir @Override public void setup(boolean processInheritIODisabled) { - if (containerRuntime == NativeConfig.ContainerRuntime.DOCKER - || containerRuntime == NativeConfig.ContainerRuntime.PODMAN) { + if (containerRuntime == ContainerRuntimeUtil.ContainerRuntime.DOCKER + || containerRuntime == ContainerRuntimeUtil.ContainerRuntime.PODMAN) { // we pull the docker image in order to give users an indication of which step the process is at // it's not strictly necessary we do this, however if we don't the subsequent version command // will appear to block and no output will be shown @@ -125,51 +124,4 @@ protected String[] buildCommand(String dockerCmd, List containerRuntimeA .flatMap(Function.identity()).toArray(String[]::new); } - /** - * @return {@link NativeConfig.ContainerRuntime#DOCKER} if it's available, or {@link NativeConfig.ContainerRuntime#PODMAN} - * if the podman - * executable exists in the environment or if the docker executable is an alias to podman - * @throws IllegalStateException if no container runtime was found to build the image - */ - public static NativeConfig.ContainerRuntime detectContainerRuntime() { - // Docker version 19.03.14, build 5eb3275d40 - String dockerVersionOutput = getVersionOutputFor(NativeConfig.ContainerRuntime.DOCKER); - boolean dockerAvailable = dockerVersionOutput.contains("Docker version"); - // Check if Podman is installed - // podman version 2.1.1 - String podmanVersionOutput = getVersionOutputFor(NativeConfig.ContainerRuntime.PODMAN); - boolean podmanAvailable = podmanVersionOutput.startsWith("podman version"); - if (dockerAvailable) { - // Check if "docker" is an alias to "podman" - if (dockerVersionOutput.equals(podmanVersionOutput)) { - return NativeConfig.ContainerRuntime.PODMAN; - } - return NativeConfig.ContainerRuntime.DOCKER; - } else if (podmanAvailable) { - return NativeConfig.ContainerRuntime.PODMAN; - } else { - throw new IllegalStateException("No container runtime was found to run the native image builder. " - + "Make sure you have Docker or Podman installed in your environment."); - } - } - - private static String getVersionOutputFor(NativeConfig.ContainerRuntime containerRuntime) { - Process versionProcess = null; - try { - ProcessBuilder pb = new ProcessBuilder(containerRuntime.getExecutableName(), "--version") - .redirectErrorStream(true); - versionProcess = pb.start(); - versionProcess.waitFor(); - return new String(FileUtil.readFileContents(versionProcess.getInputStream()), StandardCharsets.UTF_8); - } catch (IOException | InterruptedException e) { - // If an exception is thrown in the process, just return an empty String - log.debugf(e, "Failure to read version output from %s", containerRuntime.getExecutableName()); - return ""; - } finally { - if (versionProcess != null) { - versionProcess.destroy(); - } - } - } - } diff --git a/core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/NativeImageBuildLocalContainerRunner.java b/core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/NativeImageBuildLocalContainerRunner.java index a3001044c1015..2f635a7a4b7c6 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/NativeImageBuildLocalContainerRunner.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/NativeImageBuildLocalContainerRunner.java @@ -11,6 +11,7 @@ import org.apache.commons.lang3.SystemUtils; import io.quarkus.deployment.pkg.NativeConfig; +import io.quarkus.deployment.util.ContainerRuntimeUtil; import io.quarkus.deployment.util.FileUtil; public class NativeImageBuildLocalContainerRunner extends NativeImageBuildContainerRunner { @@ -23,7 +24,7 @@ public NativeImageBuildLocalContainerRunner(NativeConfig nativeConfig, Path outp String gid = getLinuxID("-gr"); if (uid != null && gid != null && !uid.isEmpty() && !gid.isEmpty()) { Collections.addAll(containerRuntimeArgs, "--user", uid + ":" + gid); - if (containerRuntime == NativeConfig.ContainerRuntime.PODMAN) { + if (containerRuntime == ContainerRuntimeUtil.ContainerRuntime.PODMAN) { // Needed to avoid AccessDeniedExceptions containerRuntimeArgs.add("--userns=keep-id"); } diff --git a/core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/UpxCompressionBuildStep.java b/core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/UpxCompressionBuildStep.java index 27b702c80613c..e0df4c86b0d8d 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/UpxCompressionBuildStep.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/UpxCompressionBuildStep.java @@ -20,6 +20,7 @@ import io.quarkus.deployment.pkg.builditem.ArtifactResultBuildItem; import io.quarkus.deployment.pkg.builditem.NativeImageBuildItem; import io.quarkus.deployment.pkg.builditem.UpxCompressedBuildItem; +import io.quarkus.deployment.util.ContainerRuntimeUtil; import io.quarkus.deployment.util.FileUtil; import io.quarkus.deployment.util.ProcessUtil; @@ -99,8 +100,8 @@ private boolean runUpxInContainer(NativeImageBuildItem nativeImage, NativeConfig List extraArgs = nativeConfig.compression.additionalArgs.orElse(Collections.emptyList()); List commandLine = new ArrayList<>(); - NativeConfig.ContainerRuntime containerRuntime = nativeConfig.containerRuntime - .orElseGet(NativeImageBuildContainerRunner::detectContainerRuntime); + ContainerRuntimeUtil.ContainerRuntime containerRuntime = nativeConfig.containerRuntime + .orElseGet(ContainerRuntimeUtil::detectContainerRuntime); commandLine.add(containerRuntime.getExecutableName()); commandLine.add("run"); @@ -121,7 +122,7 @@ private boolean runUpxInContainer(NativeImageBuildItem nativeImage, NativeConfig String gid = getLinuxID("-gr"); if (uid != null && gid != null && !uid.isEmpty() && !gid.isEmpty()) { Collections.addAll(commandLine, "--user", uid + ":" + gid); - if (containerRuntime == NativeConfig.ContainerRuntime.PODMAN) { + if (containerRuntime == ContainerRuntimeUtil.ContainerRuntime.PODMAN) { // Needed to avoid AccessDeniedExceptions commandLine.add("--userns=keep-id"); } diff --git a/core/deployment/src/main/java/io/quarkus/deployment/util/ContainerRuntimeUtil.java b/core/deployment/src/main/java/io/quarkus/deployment/util/ContainerRuntimeUtil.java new file mode 100644 index 0000000000000..d1e7fe6988462 --- /dev/null +++ b/core/deployment/src/main/java/io/quarkus/deployment/util/ContainerRuntimeUtil.java @@ -0,0 +1,73 @@ +package io.quarkus.deployment.util; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; + +import org.jboss.logging.Logger; + +public final class ContainerRuntimeUtil { + + private static final Logger log = Logger.getLogger(ContainerRuntimeUtil.class); + + private ContainerRuntimeUtil() { + } + + /** + * @return {@link ContainerRuntime#DOCKER} if it's available, or {@link ContainerRuntime#PODMAN} + * if the podman + * executable exists in the environment or if the docker executable is an alias to podman + * @throws IllegalStateException if no container runtime was found to build the image + */ + public static ContainerRuntime detectContainerRuntime() { + // Docker version 19.03.14, build 5eb3275d40 + String dockerVersionOutput = getVersionOutputFor(ContainerRuntime.DOCKER); + boolean dockerAvailable = dockerVersionOutput.contains("Docker version"); + // Check if Podman is installed + // podman version 2.1.1 + String podmanVersionOutput = getVersionOutputFor(ContainerRuntime.PODMAN); + boolean podmanAvailable = podmanVersionOutput.startsWith("podman version"); + if (dockerAvailable) { + // Check if "docker" is an alias to "podman" + if (dockerVersionOutput.equals(podmanVersionOutput)) { + return ContainerRuntime.PODMAN; + } + return ContainerRuntime.DOCKER; + } else if (podmanAvailable) { + return ContainerRuntime.PODMAN; + } else { + throw new IllegalStateException("No container runtime was found to. " + + "Make sure you have Docker or Podman installed in your environment."); + } + } + + private static String getVersionOutputFor(ContainerRuntime containerRuntime) { + Process versionProcess = null; + try { + ProcessBuilder pb = new ProcessBuilder(containerRuntime.getExecutableName(), "--version") + .redirectErrorStream(true); + versionProcess = pb.start(); + versionProcess.waitFor(); + return new String(FileUtil.readFileContents(versionProcess.getInputStream()), StandardCharsets.UTF_8); + } catch (IOException | InterruptedException e) { + // If an exception is thrown in the process, just return an empty String + log.debugf(e, "Failure to read version output from %s", containerRuntime.getExecutableName()); + return ""; + } finally { + if (versionProcess != null) { + versionProcess.destroy(); + } + } + } + + /** + * Supported Container runtimes + */ + public enum ContainerRuntime { + DOCKER, + PODMAN; + + public String getExecutableName() { + return this.name().toLowerCase(); + } + } +} diff --git a/extensions/container-image/container-image-jib/deployment/src/main/java/io/quarkus/container/image/jib/deployment/JibProcessor.java b/extensions/container-image/container-image-jib/deployment/src/main/java/io/quarkus/container/image/jib/deployment/JibProcessor.java index 3c7399252148e..234b8821b81a6 100644 --- a/extensions/container-image/container-image-jib/deployment/src/main/java/io/quarkus/container/image/jib/deployment/JibProcessor.java +++ b/extensions/container-image/container-image-jib/deployment/src/main/java/io/quarkus/container/image/jib/deployment/JibProcessor.java @@ -69,6 +69,7 @@ import io.quarkus.deployment.pkg.builditem.UpxCompressedBuildItem; import io.quarkus.deployment.pkg.steps.JarResultBuildStep; import io.quarkus.deployment.pkg.steps.NativeBuild; +import io.quarkus.deployment.util.ContainerRuntimeUtil; import io.quarkus.fs.util.ZipUtils; import io.quarkus.maven.dependency.ResolvedDependency; @@ -266,6 +267,11 @@ private Containerizer createContainerizer(ContainerImageConfig containerImageCon dockerDaemonImage.setDockerExecutable(Paths.get(jibConfigExecutableName.get())); } else if (dockerConfigExecutableName.isPresent()) { dockerDaemonImage.setDockerExecutable(Paths.get(dockerConfigExecutableName.get())); + } else { + // detect the container runtime instead of falling back to 'docker' as the default + ContainerRuntimeUtil.ContainerRuntime detectedContainerRuntime = ContainerRuntimeUtil.detectContainerRuntime(); + log.infof("Using %s to run the native image builder", detectedContainerRuntime.getExecutableName()); + dockerDaemonImage.setDockerExecutable(Paths.get(detectedContainerRuntime.getExecutableName())); } containerizer = Containerizer.to(dockerDaemonImage); }