From f2f24bed7f45e832e34458fc783b270be7007839 Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Fri, 31 Mar 2023 18:24:46 +0200 Subject: [PATCH] Improve container runtime detection Fully resolve the container runtime once and for all. It costs us one more call when the rootless info is not needed but I prefer things to be resolved entirely once, rather than using patterns that are a bit brittle. Fixes #32246 --- .../NativeImageBuildContainerRunner.java | 3 +- .../NativeImageBuildLocalContainerRunner.java | 8 +- .../pkg/steps/UpxCompressionBuildStep.java | 3 +- .../runtime/util/ContainerRuntimeUtil.java | 156 ++++++++++-------- 4 files changed, 93 insertions(+), 77 deletions(-) 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 ef53e1b4f7b8b..ccca09727a2ed 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 @@ -42,8 +42,7 @@ public boolean isContainer() { @Override public void setup(boolean processInheritIODisabled) { - if (containerRuntime == ContainerRuntimeUtil.ContainerRuntime.DOCKER - || containerRuntime == ContainerRuntimeUtil.ContainerRuntime.PODMAN) { + if (containerRuntime != ContainerRuntimeUtil.ContainerRuntime.UNAVAILABLE) { log.infof("Using %s to run the native image builder", containerRuntime.getExecutableName()); // 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 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 e9f1c1125ef0c..c5e50652d4dd7 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 @@ -1,8 +1,6 @@ package io.quarkus.deployment.pkg.steps; import static io.quarkus.deployment.pkg.steps.LinuxIDUtil.getLinuxID; -import static io.quarkus.runtime.util.ContainerRuntimeUtil.ContainerRuntime.DOCKER; -import static io.quarkus.runtime.util.ContainerRuntimeUtil.ContainerRuntime.PODMAN; import java.nio.file.Path; import java.util.ArrayList; @@ -21,14 +19,14 @@ public NativeImageBuildLocalContainerRunner(NativeConfig nativeConfig) { super(nativeConfig); if (SystemUtils.IS_OS_LINUX) { final ArrayList containerRuntimeArgs = new ArrayList<>(Arrays.asList(baseContainerRuntimeArgs)); - if (containerRuntime == DOCKER && containerRuntime.isRootless()) { + if (containerRuntime.isDocker() && containerRuntime.isRootless()) { Collections.addAll(containerRuntimeArgs, "--user", String.valueOf(0)); } else { String uid = getLinuxID("-ur"); String gid = getLinuxID("-gr"); if (uid != null && gid != null && !uid.isEmpty() && !gid.isEmpty()) { Collections.addAll(containerRuntimeArgs, "--user", uid + ":" + gid); - if (containerRuntime == PODMAN && containerRuntime.isRootless()) { + if (containerRuntime.isPodman() && containerRuntime.isRootless()) { // Needed to avoid AccessDeniedExceptions containerRuntimeArgs.add("--userns=keep-id"); } @@ -47,7 +45,7 @@ protected List getContainerRuntimeBuildArgs(Path outputDir) { } final String selinuxBindOption; - if (SystemUtils.IS_OS_MAC && containerRuntime == PODMAN) { + if (SystemUtils.IS_OS_MAC && containerRuntime.isPodman()) { selinuxBindOption = ""; } else { selinuxBindOption = ":z"; 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 9174db0e4c76e..a4880647973e4 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 @@ -126,8 +126,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 == ContainerRuntimeUtil.ContainerRuntime.PODMAN - && containerRuntime.isRootless()) { + if (containerRuntime.isPodman() && containerRuntime.isRootless()) { // Needed to avoid AccessDeniedExceptions commandLine.add("--userns=keep-id"); } diff --git a/core/runtime/src/main/java/io/quarkus/runtime/util/ContainerRuntimeUtil.java b/core/runtime/src/main/java/io/quarkus/runtime/util/ContainerRuntimeUtil.java index 1ae65cc8a5b80..c2d254d25edbc 100644 --- a/core/runtime/src/main/java/io/quarkus/runtime/util/ContainerRuntimeUtil.java +++ b/core/runtime/src/main/java/io/quarkus/runtime/util/ContainerRuntimeUtil.java @@ -31,10 +31,7 @@ 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, - * or {@link ContainerRuntime#UNAVAILABLE} if no container runtime is available and the required arg is false. + * @return a fully resolved {@link ContainerRuntime} indicating if Docker or Podman is available and in rootless mode or not * @throws IllegalStateException if no container runtime was found to build the image */ public static ContainerRuntime detectContainerRuntime() { @@ -42,66 +39,88 @@ public static ContainerRuntime detectContainerRuntime() { } public static ContainerRuntime detectContainerRuntime(boolean required) { - final ContainerRuntime containerRuntime = loadContainerRuntimeFromSystemProperty(); + ContainerRuntime containerRuntime = loadContainerRuntimeFromSystemProperty(); if (containerRuntime != null) { return containerRuntime; - } else { - // Docker version 19.03.14, build 5eb3275d40 - String dockerVersionOutput; - boolean dockerAvailable; - // Check if Podman is installed - // podman version 2.1.1 - String podmanVersionOutput; - boolean podmanAvailable; - if (CONTAINER_EXECUTABLE != null) { - if (CONTAINER_EXECUTABLE.trim().equalsIgnoreCase("docker")) { - dockerVersionOutput = getVersionOutputFor(ContainerRuntime.DOCKER); - dockerAvailable = dockerVersionOutput.contains("Docker version"); - if (dockerAvailable) { - storeContainerRuntimeInSystemProperty(ContainerRuntime.DOCKER); - return ContainerRuntime.DOCKER; - } - } - if (CONTAINER_EXECUTABLE.trim().equalsIgnoreCase("podman")) { - podmanVersionOutput = getVersionOutputFor(ContainerRuntime.PODMAN); - podmanAvailable = podmanVersionOutput.startsWith("podman version"); - if (podmanAvailable) { - storeContainerRuntimeInSystemProperty(ContainerRuntime.PODMAN); - return ContainerRuntime.PODMAN; - } + } + + ContainerRuntime containerRuntimeEnvironment = getContainerRuntimeEnvironment(); + if (containerRuntimeEnvironment == ContainerRuntime.UNAVAILABLE) { + storeContainerRuntimeInSystemProperty(ContainerRuntime.UNAVAILABLE); + + if (required) { + throw new IllegalStateException("No container runtime was found. " + + "Make sure you have either Docker or Podman installed in your environment."); + } + + return ContainerRuntime.UNAVAILABLE; + } + + // we have a working container environment, let's resolve it fully + containerRuntime = fullyResolveContainerRuntime(containerRuntimeEnvironment); + + storeContainerRuntimeInSystemProperty(containerRuntime); + + return containerRuntime; + } + + private static ContainerRuntime getContainerRuntimeEnvironment() { + // Docker version 19.03.14, build 5eb3275d40 + String dockerVersionOutput; + boolean dockerAvailable; + // Check if Podman is installed + // podman version 2.1.1 + String podmanVersionOutput; + boolean podmanAvailable; + + if (CONTAINER_EXECUTABLE != null) { + if (CONTAINER_EXECUTABLE.trim().equalsIgnoreCase("docker")) { + dockerVersionOutput = getVersionOutputFor(ContainerRuntime.DOCKER); + dockerAvailable = dockerVersionOutput.contains("Docker version"); + if (dockerAvailable) { + return ContainerRuntime.DOCKER; } - log.warn("quarkus.native.container-runtime config property must be set to either podman or docker " + - "and the executable must be available. Ignoring it."); } - dockerVersionOutput = getVersionOutputFor(ContainerRuntime.DOCKER); - dockerAvailable = dockerVersionOutput.contains("Docker version"); - if (dockerAvailable) { - // Check if "docker" is an alias to "podman" - if (dockerVersionOutput.startsWith("podman version") || - dockerVersionOutput.startsWith("podman.exe version")) { - storeContainerRuntimeInSystemProperty(ContainerRuntime.PODMAN); + if (CONTAINER_EXECUTABLE.trim().equalsIgnoreCase("podman")) { + podmanVersionOutput = getVersionOutputFor(ContainerRuntime.PODMAN); + podmanAvailable = podmanVersionOutput.startsWith("podman version"); + if (podmanAvailable) { return ContainerRuntime.PODMAN; } - storeContainerRuntimeInSystemProperty(ContainerRuntime.DOCKER); - return ContainerRuntime.DOCKER; } - podmanVersionOutput = getVersionOutputFor(ContainerRuntime.PODMAN); - podmanAvailable = podmanVersionOutput.startsWith("podman version") || - podmanVersionOutput.startsWith("podman.exe version"); - if (podmanAvailable) { - storeContainerRuntimeInSystemProperty(ContainerRuntime.PODMAN); + log.warn("quarkus.native.container-runtime config property must be set to either podman or docker " + + "and the executable must be available. Ignoring it."); + } + + dockerVersionOutput = getVersionOutputFor(ContainerRuntime.DOCKER); + dockerAvailable = dockerVersionOutput.contains("Docker version"); + if (dockerAvailable) { + // Check if "docker" is an alias to "podman" + if (dockerVersionOutput.startsWith("podman version") || + dockerVersionOutput.startsWith("podman.exe version")) { return ContainerRuntime.PODMAN; } + return ContainerRuntime.DOCKER; + } + podmanVersionOutput = getVersionOutputFor(ContainerRuntime.PODMAN); + podmanAvailable = podmanVersionOutput.startsWith("podman version") || + podmanVersionOutput.startsWith("podman.exe version"); + if (podmanAvailable) { + return ContainerRuntime.PODMAN; + } - storeContainerRuntimeInSystemProperty(ContainerRuntime.UNAVAILABLE); + return ContainerRuntime.UNAVAILABLE; + } - if (required) { - throw new IllegalStateException("No container runtime was found. " - + "Make sure you have either Docker or Podman installed in your environment."); - } + private static ContainerRuntime fullyResolveContainerRuntime(ContainerRuntime containerRuntimeEnvironment) { + boolean rootless = getRootlessStateFor(containerRuntimeEnvironment); - return ContainerRuntime.UNAVAILABLE; + if (!rootless) { + return containerRuntimeEnvironment; } + + return containerRuntimeEnvironment == ContainerRuntime.DOCKER ? ContainerRuntime.DOCKER_ROOTLESS + : ContainerRuntime.PODMAN_ROOTLESS; } private static ContainerRuntime loadContainerRuntimeFromSystemProperty() { @@ -195,16 +214,19 @@ private static boolean getRootlessStateFor(ContainerRuntime containerRuntime) { * Supported Container runtimes */ public enum ContainerRuntime { - DOCKER("docker" + (OS.current() == OS.WINDOWS ? ".exe" : "")), - PODMAN("podman" + (OS.current() == OS.WINDOWS ? ".exe" : "")), - UNAVAILABLE(null); + DOCKER("docker", false), + DOCKER_ROOTLESS("docker", true), + PODMAN("podman", false), + PODMAN_ROOTLESS("podman", true), + UNAVAILABLE(null, false); - private Boolean rootless; + private final String executableName; - private String executableName; + private final boolean rootless; - ContainerRuntime(String executableName) { - this.executableName = executableName; + ContainerRuntime(String executableName, boolean rootless) { + this.executableName = executableName + (OS.current() == OS.WINDOWS ? ".exe" : ""); + this.rootless = rootless; } public String getExecutableName() { @@ -215,17 +237,15 @@ public String getExecutableName() { return executableName; } + public boolean isDocker() { + return this == DOCKER || this == DOCKER_ROOTLESS; + } + + public boolean isPodman() { + return this == PODMAN || this == PODMAN_ROOTLESS; + } + public boolean isRootless() { - if (rootless != null) { - return rootless; - } else { - if (this != ContainerRuntime.UNAVAILABLE) { - rootless = getRootlessStateFor(this); - } else { - throw new IllegalStateException("No container runtime was found. " - + "Make sure you have either Docker or Podman installed in your environment."); - } - } return rootless; }