From 4c449e00d247defaa223c3d02109e12cc0228127 Mon Sep 17 00:00:00 2001 From: Michal Karm Babacek Date: Wed, 15 Mar 2023 11:06:49 +0100 Subject: [PATCH 1/5] Swaps container runtime caching from file to sys prop --- .../runtime/util/ContainerRuntimeUtil.java | 58 ++++++------------- 1 file changed, 18 insertions(+), 40 deletions(-) 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 9ac79ff44719a..834bf99476005 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 @@ -5,9 +5,6 @@ import java.io.InputStream; import java.io.InputStreamReader; import java.nio.charset.StandardCharsets; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.StandardOpenOption; import java.util.function.Predicate; import java.util.stream.Collectors; @@ -21,16 +18,12 @@ public final class ContainerRuntimeUtil { private static final Logger log = Logger.getLogger(ContainerRuntimeUtil.class); private static final String DOCKER_EXECUTABLE = ConfigProvider.getConfig().unwrap(SmallRyeConfig.class) .getOptionalValue("quarkus.native.container-runtime", String.class).orElse(null); - /* - * Caching the value in a file helps us only as much as one JVM execution is concerned. - * Test suite's pom.xml sets things like -Djava.io.tmpdir="${project.build.directory}", - * so the file could appear in /tmp/ or C:\Users\karm\AppData\Local\Temp\ or in fact in - * quarkus/integration-tests/something/target. - * There is no point in reaching it in `Path.of(Paths.get("").toAbsolutePath().toString(), "target", - * "quarkus_container_runtime.txt")` - * as the file is deleted between JVM executions anyway. + + /** + * Static variable is not used because the class gets loaded by different classloaders at + * runtime and the container runtime would be detected again and again unnecessarily. */ - static final Path CONTAINER_RUNTIME = Path.of(System.getProperty("java.io.tmpdir"), "quarkus_container_runtime.txt"); + private static final String CONTAINER_RUNTIME_SYS_PROP = "quarkus-local-container-runtime"; private ContainerRuntimeUtil() { } @@ -94,39 +87,24 @@ public static ContainerRuntime detectContainerRuntime(boolean required) { } private static ContainerRuntime loadConfig() { - try { - if (Files.isReadable(CONTAINER_RUNTIME)) { - final String runtime = Files.readString(CONTAINER_RUNTIME, StandardCharsets.UTF_8); - if (ContainerRuntime.DOCKER.name().equalsIgnoreCase(runtime)) { - return ContainerRuntime.DOCKER; - } else if (ContainerRuntime.PODMAN.name().equalsIgnoreCase(runtime)) { - return ContainerRuntime.PODMAN; - } else if (ContainerRuntime.UNAVAILABLE.name().equalsIgnoreCase(runtime)) { - return ContainerRuntime.UNAVAILABLE; - } else { - log.warnf("The file %s contains an unknown value %s. Ignoring it.", - CONTAINER_RUNTIME.toAbsolutePath(), runtime); - return null; - } - } else { - return null; - } - } catch (IOException e) { - log.warnf("Error reading file %s. Ignoring it. See: %s", - CONTAINER_RUNTIME.toAbsolutePath(), e); + final String runtime = System.getProperty(CONTAINER_RUNTIME_SYS_PROP); + if (runtime == null) { + return null; + } else if (ContainerRuntime.DOCKER.name().equalsIgnoreCase(runtime)) { + return ContainerRuntime.DOCKER; + } else if (ContainerRuntime.PODMAN.name().equalsIgnoreCase(runtime)) { + return ContainerRuntime.PODMAN; + } else if (ContainerRuntime.UNAVAILABLE.name().equalsIgnoreCase(runtime)) { + return ContainerRuntime.UNAVAILABLE; + } else { + log.warnf("System property %s contains an unknown value %s. Ignoring it.", + CONTAINER_RUNTIME_SYS_PROP, runtime); return null; } } private static void storeConfig(ContainerRuntime containerRuntime) { - try { - Files.writeString(CONTAINER_RUNTIME, containerRuntime.name(), StandardCharsets.UTF_8, - StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING); - CONTAINER_RUNTIME.toFile().deleteOnExit(); - } catch (IOException e) { - log.warnf("Error writing to file %s. Ignoring it. See: %s", - CONTAINER_RUNTIME.toAbsolutePath(), e); - } + System.setProperty(CONTAINER_RUNTIME_SYS_PROP, containerRuntime.name()); } private static String getVersionOutputFor(ContainerRuntime containerRuntime) { From f8c61cf409cea59977793b373e63f17b1864cde1 Mon Sep 17 00:00:00 2001 From: Michal Karm Babacek Date: Wed, 15 Mar 2023 15:45:35 +0100 Subject: [PATCH 2/5] Remove superflous calls to podman/docker on podman only/docker only box --- .../runtime/util/ContainerRuntimeUtil.java | 56 +++++++++++-------- 1 file changed, 34 insertions(+), 22 deletions(-) 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 834bf99476005..1a44a0c6ff4d6 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 @@ -45,43 +45,55 @@ public static ContainerRuntime detectContainerRuntime(boolean required) { return containerRuntime; } else { // Docker version 19.03.14, build 5eb3275d40 - final String dockerVersionOutput = getVersionOutputFor(ContainerRuntime.DOCKER); - boolean dockerAvailable = dockerVersionOutput.contains("Docker version"); + String dockerVersionOutput; + boolean dockerAvailable; // Check if Podman is installed // podman version 2.1.1 - final String podmanVersionOutput = getVersionOutputFor(ContainerRuntime.PODMAN); - boolean podmanAvailable = podmanVersionOutput.startsWith("podman version"); + String podmanVersionOutput; + boolean podmanAvailable; if (DOCKER_EXECUTABLE != null) { - if (DOCKER_EXECUTABLE.trim().equalsIgnoreCase("docker") && dockerAvailable) { - storeConfig(ContainerRuntime.DOCKER); - return ContainerRuntime.DOCKER; - } else if (DOCKER_EXECUTABLE.trim().equalsIgnoreCase("podman") && podmanAvailable) { - storeConfig(ContainerRuntime.PODMAN); - return ContainerRuntime.PODMAN; - } else { - log.warn("quarkus.native.container-runtime config property must be set to either podman or docker " + - "and the executable must be available. Ignoring it."); + if (DOCKER_EXECUTABLE.trim().equalsIgnoreCase("docker")) { + dockerVersionOutput = getVersionOutputFor(ContainerRuntime.DOCKER); + dockerAvailable = dockerVersionOutput.contains("Docker version"); + if (dockerAvailable) { + storeConfig(ContainerRuntime.DOCKER); + return ContainerRuntime.DOCKER; + } + } + if (DOCKER_EXECUTABLE.trim().equalsIgnoreCase("podman")) { + podmanVersionOutput = getVersionOutputFor(ContainerRuntime.PODMAN); + podmanAvailable = podmanVersionOutput.startsWith("podman version"); + if (podmanAvailable) { + storeConfig(ContainerRuntime.PODMAN); + return 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.equals(podmanVersionOutput)) { + if (dockerVersionOutput.startsWith("podman version")) { storeConfig(ContainerRuntime.PODMAN); return ContainerRuntime.PODMAN; } storeConfig(ContainerRuntime.DOCKER); return ContainerRuntime.DOCKER; - } else if (podmanAvailable) { + } + podmanVersionOutput = getVersionOutputFor(ContainerRuntime.PODMAN); + podmanAvailable = podmanVersionOutput.startsWith("podman version"); + if (podmanAvailable) { storeConfig(ContainerRuntime.PODMAN); return ContainerRuntime.PODMAN; + } + if (required) { + throw new IllegalStateException("No container runtime was found. " + + "Make sure you have either Docker or Podman installed in your environment."); } else { - if (required) { - throw new IllegalStateException("No container runtime was found. " - + "Make sure you have either Docker or Podman installed in your environment."); - } else { - storeConfig(ContainerRuntime.UNAVAILABLE); - return ContainerRuntime.UNAVAILABLE; - } + storeConfig(ContainerRuntime.UNAVAILABLE); + return ContainerRuntime.UNAVAILABLE; } } } From 781eb53aef05b5fc2138f5c8b660d8c447dde755 Mon Sep 17 00:00:00 2001 From: Michal Karm Babacek Date: Wed, 15 Mar 2023 15:55:58 +0100 Subject: [PATCH 3/5] Similar to AppCDSBuildStep, IntegrationTestUtil needs to handle a situation\nwhere there is no container runtime working and none is needed. --- .../quarkus/test/junit/IntegrationTestUtil.java | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/test-framework/junit5/src/main/java/io/quarkus/test/junit/IntegrationTestUtil.java b/test-framework/junit5/src/main/java/io/quarkus/test/junit/IntegrationTestUtil.java index e9987475c21f0..82674141ec870 100644 --- a/test-framework/junit5/src/main/java/io/quarkus/test/junit/IntegrationTestUtil.java +++ b/test-framework/junit5/src/main/java/io/quarkus/test/junit/IntegrationTestUtil.java @@ -52,6 +52,7 @@ import io.quarkus.paths.PathList; import io.quarkus.runtime.configuration.ProfileManager; import io.quarkus.runtime.logging.LoggingSetupRecorder; +import io.quarkus.runtime.util.ContainerRuntimeUtil; import io.quarkus.test.common.ArtifactLauncher; import io.quarkus.test.common.LauncherUtil; import io.quarkus.test.common.PathTestHelper; @@ -64,7 +65,7 @@ public final class IntegrationTestUtil { public static final int DEFAULT_PORT = 8081; public static final int DEFAULT_HTTPS_PORT = 8444; public static final long DEFAULT_WAIT_TIME_SECONDS = 60; - private static final String DOCKER_BINARY = detectContainerRuntime().getExecutableName(); + public static final ContainerRuntimeUtil.ContainerRuntime CONTAINER_RUNTIME = detectContainerRuntime(false); private IntegrationTestUtil() { } @@ -336,9 +337,15 @@ public void accept(String s, String s2) { private static void createNetworkIfNecessary( final ArtifactLauncher.InitContext.DevServicesLaunchResult devServicesLaunchResult) { if (devServicesLaunchResult.manageNetwork() && (devServicesLaunchResult.networkId() != null)) { + if (CONTAINER_RUNTIME == ContainerRuntimeUtil.ContainerRuntime.UNAVAILABLE) { + throw new IllegalStateException("No container runtime was found. " + + "Make sure you have either Docker or Podman installed in your environment."); + } try { int networkCreateResult = new ProcessBuilder().redirectError(DISCARD).redirectOutput(DISCARD) - .command(DOCKER_BINARY, "network", "create", devServicesLaunchResult.networkId()).start().waitFor(); + .command(CONTAINER_RUNTIME.getExecutableName(), "network", "create", + devServicesLaunchResult.networkId()) + .start().waitFor(); if (networkCreateResult > 0) { throw new RuntimeException("Creating container network '" + devServicesLaunchResult.networkId() + "' completed unsuccessfully"); @@ -349,7 +356,9 @@ private static void createNetworkIfNecessary( public void run() { try { new ProcessBuilder().redirectError(DISCARD).redirectOutput(DISCARD) - .command(DOCKER_BINARY, "network", "rm", devServicesLaunchResult.networkId()).start() + .command(CONTAINER_RUNTIME.getExecutableName(), "network", "rm", + devServicesLaunchResult.networkId()) + .start() .waitFor(); } catch (InterruptedException | IOException ignored) { System.out.println( From ea1728b15edc47f0ba9fabb2f4b7a9713e3aa5f7 Mon Sep 17 00:00:00 2001 From: Michal Karm Babacek Date: Wed, 15 Mar 2023 23:33:07 +0100 Subject: [PATCH 4/5] Container Image Docker: uses Core autodetection too --- .../runtime/util/ContainerRuntimeUtil.java | 19 ++++++--- .../image/docker/deployment/DockerConfig.java | 5 ++- .../docker/deployment/DockerProcessor.java | 42 +++++++++++-------- 3 files changed, 41 insertions(+), 25 deletions(-) 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 1a44a0c6ff4d6..1e6cff3a46c07 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 @@ -5,6 +5,7 @@ import java.io.InputStream; import java.io.InputStreamReader; import java.nio.charset.StandardCharsets; +import java.util.concurrent.TimeUnit; import java.util.function.Predicate; import java.util.stream.Collectors; @@ -16,7 +17,7 @@ public final class ContainerRuntimeUtil { private static final Logger log = Logger.getLogger(ContainerRuntimeUtil.class); - private static final String DOCKER_EXECUTABLE = ConfigProvider.getConfig().unwrap(SmallRyeConfig.class) + private static final String CONTAINER_EXECUTABLE = ConfigProvider.getConfig().unwrap(SmallRyeConfig.class) .getOptionalValue("quarkus.native.container-runtime", String.class).orElse(null); /** @@ -51,8 +52,8 @@ public static ContainerRuntime detectContainerRuntime(boolean required) { // podman version 2.1.1 String podmanVersionOutput; boolean podmanAvailable; - if (DOCKER_EXECUTABLE != null) { - if (DOCKER_EXECUTABLE.trim().equalsIgnoreCase("docker")) { + if (CONTAINER_EXECUTABLE != null) { + if (CONTAINER_EXECUTABLE.trim().equalsIgnoreCase("docker")) { dockerVersionOutput = getVersionOutputFor(ContainerRuntime.DOCKER); dockerAvailable = dockerVersionOutput.contains("Docker version"); if (dockerAvailable) { @@ -60,7 +61,7 @@ public static ContainerRuntime detectContainerRuntime(boolean required) { return ContainerRuntime.DOCKER; } } - if (DOCKER_EXECUTABLE.trim().equalsIgnoreCase("podman")) { + if (CONTAINER_EXECUTABLE.trim().equalsIgnoreCase("podman")) { podmanVersionOutput = getVersionOutputFor(ContainerRuntime.PODMAN); podmanAvailable = podmanVersionOutput.startsWith("podman version"); if (podmanAvailable) { @@ -125,8 +126,14 @@ private static String getVersionOutputFor(ContainerRuntime containerRuntime) { final ProcessBuilder pb = new ProcessBuilder(containerRuntime.getExecutableName(), "--version") .redirectErrorStream(true); versionProcess = pb.start(); - versionProcess.waitFor(); - return new String(versionProcess.getInputStream().readAllBytes(), StandardCharsets.UTF_8); + final int timeoutS = 10; + if (versionProcess.waitFor(timeoutS, TimeUnit.SECONDS)) { + return new String(versionProcess.getInputStream().readAllBytes(), StandardCharsets.UTF_8); + } else { + log.debugf("Failure. It took command %s more than %d seconds to execute.", containerRuntime.getExecutableName(), + timeoutS); + return ""; + } } 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()); diff --git a/extensions/container-image/container-image-docker/deployment/src/main/java/io/quarkus/container/image/docker/deployment/DockerConfig.java b/extensions/container-image/container-image-docker/deployment/src/main/java/io/quarkus/container/image/docker/deployment/DockerConfig.java index 99d5420a51d92..6e10784ef2a04 100644 --- a/extensions/container-image/container-image-docker/deployment/src/main/java/io/quarkus/container/image/docker/deployment/DockerConfig.java +++ b/extensions/container-image/container-image-docker/deployment/src/main/java/io/quarkus/container/image/docker/deployment/DockerConfig.java @@ -50,9 +50,10 @@ public class DockerConfig { /** * Name of binary used to execute the docker commands. + * This setting can override the global container runtime detection. */ - @ConfigItem(defaultValue = "docker") - public String executableName; + @ConfigItem + public Optional executableName; /** * Configuration for Docker Buildx options diff --git a/extensions/container-image/container-image-docker/deployment/src/main/java/io/quarkus/container/image/docker/deployment/DockerProcessor.java b/extensions/container-image/container-image-docker/deployment/src/main/java/io/quarkus/container/image/docker/deployment/DockerProcessor.java index f6a5d79ab181d..2ede3959d21f8 100644 --- a/extensions/container-image/container-image-docker/deployment/src/main/java/io/quarkus/container/image/docker/deployment/DockerProcessor.java +++ b/extensions/container-image/container-image-docker/deployment/src/main/java/io/quarkus/container/image/docker/deployment/DockerProcessor.java @@ -4,6 +4,7 @@ import static io.quarkus.container.image.deployment.util.EnablementUtil.buildContainerImageNeeded; import static io.quarkus.container.image.deployment.util.EnablementUtil.pushContainerImageNeeded; import static io.quarkus.container.util.PathsUtil.findMainSourcesRoot; +import static io.quarkus.runtime.util.ContainerRuntimeUtil.detectContainerRuntime; import java.io.BufferedReader; import java.io.IOException; @@ -87,9 +88,10 @@ public void dockerBuildFromJar(DockerConfig dockerConfig, throw new RuntimeException("Unable to build docker image. Please check your docker installation"); } - var dockerfilePaths = getDockerfilePaths(dockerConfig, false, packageConfig, out); - var dockerFileBaseInformationProvider = DockerFileBaseInformationProvider.impl(); - var dockerFileBaseInformation = dockerFileBaseInformationProvider.determine(dockerfilePaths.getDockerfilePath()); + DockerfilePaths dockerfilePaths = getDockerfilePaths(dockerConfig, false, packageConfig, out); + DockerFileBaseInformationProvider dockerFileBaseInformationProvider = DockerFileBaseInformationProvider.impl(); + Optional dockerFileBaseInformation = dockerFileBaseInformationProvider + .determine(dockerfilePaths.getDockerfilePath()); if ((compiledJavaVersion.getJavaVersion().isJava17OrHigher() == CompiledJavaVersionBuildItem.JavaVersion.Status.TRUE) && dockerFileBaseInformation.isPresent() && (dockerFileBaseInformation.get().getJavaVersion() < 17)) { @@ -168,11 +170,11 @@ private String createContainerImage(ContainerImageConfig containerImageConfig, D boolean pushContainerImage, PackageConfig packageConfig) { - var useBuildx = dockerConfig.buildx.useBuildx(); + boolean useBuildx = dockerConfig.buildx.useBuildx(); - // useBuildx: Whether or not any of the buildx parameters are set + // useBuildx: Whether any of the buildx parameters are set // - // pushImages: Whether or not the user requested the built images to be pushed to a registry + // pushImages: Whether the user requested the built images to be pushed to a registry // Pushing images is different based on if you're using buildx or not. // If not using any of the buildx params (useBuildx == false), then the flow is as it was before: // @@ -196,9 +198,10 @@ private String createContainerImage(ContainerImageConfig containerImageConfig, D } if (buildContainerImage) { - log.infof("Executing the following command to build docker image: '%s %s'", dockerConfig.executableName, + final String executableName = dockerConfig.executableName.orElse(detectContainerRuntime(true).getExecutableName()); + log.infof("Executing the following command to build docker image: '%s %s'", executableName, String.join(" ", dockerArgs)); - boolean buildSuccessful = ExecUtil.exec(out.getOutputDirectory().toFile(), reader, dockerConfig.executableName, + boolean buildSuccessful = ExecUtil.exec(out.getOutputDirectory().toFile(), reader, executableName, dockerArgs); if (!buildSuccessful) { throw dockerException(dockerArgs); @@ -234,7 +237,7 @@ private String createContainerImage(ContainerImageConfig containerImageConfig, D private void loginToRegistryIfNeeded(ContainerImageConfig containerImageConfig, ContainerImageInfoBuildItem containerImageInfo, DockerConfig dockerConfig) { - var registry = containerImageInfo.getRegistry() + String registry = containerImageInfo.getRegistry() .orElseGet(() -> { log.info("No container image registry was set, so 'docker.io' will be used"); return "docker.io"; @@ -242,7 +245,8 @@ private void loginToRegistryIfNeeded(ContainerImageConfig containerImageConfig, // Check if we need to login first if (containerImageConfig.username.isPresent() && containerImageConfig.password.isPresent()) { - boolean loginSuccessful = ExecUtil.exec(dockerConfig.executableName, "login", registry, "-u", + final String executableName = dockerConfig.executableName.orElse(detectContainerRuntime(true).getExecutableName()); + boolean loginSuccessful = ExecUtil.exec(executableName, "login", registry, "-u", containerImageConfig.username.get(), "-p" + containerImageConfig.password.get()); if (!loginSuccessful) { @@ -254,15 +258,17 @@ private void loginToRegistryIfNeeded(ContainerImageConfig containerImageConfig, private String[] getDockerArgs(String image, DockerfilePaths dockerfilePaths, ContainerImageConfig containerImageConfig, DockerConfig dockerConfig, ContainerImageInfoBuildItem containerImageInfo, boolean pushImages) { List dockerArgs = new ArrayList<>(6 + dockerConfig.buildArgs.size()); - var useBuildx = dockerConfig.buildx.useBuildx(); + boolean useBuildx = dockerConfig.buildx.useBuildx(); if (useBuildx) { + final String executableName = dockerConfig.executableName.orElse(detectContainerRuntime(true).getExecutableName()); // Check the executable. If not 'docker', then fail the build - if (!DOCKER.equals(dockerConfig.executableName)) { + if (!DOCKER.equals(executableName)) { throw new IllegalArgumentException( String.format( - "The 'buildx' properties are specific to 'executable-name=docker' and can not be used with the '%s' executable name. Either remove the `buildx` properties or the `executable-name` property.", - dockerConfig.executableName)); + "The 'buildx' properties are specific to 'executable-name=docker' and can not be used with " + + "the '%s' executable name. Either remove the `buildx` properties or the `executable-name` property.", + executableName)); } dockerArgs.add("buildx"); @@ -315,9 +321,10 @@ private String[] getDockerArgs(String image, DockerfilePaths dockerfilePaths, Co } private void createAdditionalTags(String image, List additionalImageTags, DockerConfig dockerConfig) { + final String executableName = dockerConfig.executableName.orElse(detectContainerRuntime(true).getExecutableName()); for (String additionalTag : additionalImageTags) { String[] tagArgs = { "tag", image, additionalTag }; - boolean tagSuccessful = ExecUtil.exec(dockerConfig.executableName, tagArgs); + boolean tagSuccessful = ExecUtil.exec(executableName, tagArgs); if (!tagSuccessful) { throw dockerException(tagArgs); } @@ -325,8 +332,9 @@ private void createAdditionalTags(String image, List additionalImageTags } private void pushImage(String image, DockerConfig dockerConfig) { + final String executableName = dockerConfig.executableName.orElse(detectContainerRuntime(true).getExecutableName()); String[] pushArgs = { "push", image }; - boolean pushSuccessful = ExecUtil.exec(dockerConfig.executableName, pushArgs); + boolean pushSuccessful = ExecUtil.exec(executableName, pushArgs); if (!pushSuccessful) { throw dockerException(pushArgs); } @@ -464,7 +472,7 @@ public static ProvidedDockerfile get(Path dockerfilePath, Path outputDirectory) : mainSourcesRoot.getValue().resolve(dockerfilePath); if (!effectiveDockerfilePath.toFile().exists()) { throw new IllegalArgumentException( - "Specified Dockerfile path " + effectiveDockerfilePath.toAbsolutePath().toString() + " does not exist"); + "Specified Dockerfile path " + effectiveDockerfilePath.toAbsolutePath() + " does not exist"); } return new ProvidedDockerfile( effectiveDockerfilePath, From 38d3c0d228b7ef3f28a620b171c16b3e961df91a Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Thu, 16 Mar 2023 14:26:13 +0100 Subject: [PATCH 5/5] Various adjustments for the container detection --- .../deployment/pkg/steps/AppCDSBuildStep.java | 11 ++-- .../NativeImageBuildContainerRunnerTest.java | 13 ++--- .../runtime/util/ContainerRuntimeUtil.java | 54 ++++++++++++------- .../docker/deployment/DockerProcessor.java | 14 ++--- .../test/junit/IntegrationTestUtil.java | 11 ++-- 5 files changed, 56 insertions(+), 47 deletions(-) diff --git a/core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/AppCDSBuildStep.java b/core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/AppCDSBuildStep.java index 8e7352ba8184f..49d64e6e45251 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/AppCDSBuildStep.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/AppCDSBuildStep.java @@ -29,7 +29,7 @@ import io.quarkus.deployment.pkg.builditem.OutputTargetBuildItem; import io.quarkus.deployment.steps.MainClassBuildStep; import io.quarkus.runtime.LaunchMode; -import io.quarkus.runtime.util.ContainerRuntimeUtil; +import io.quarkus.runtime.util.ContainerRuntimeUtil.ContainerRuntime; import io.quarkus.utilities.JavaBinFinder; public class AppCDSBuildStep { @@ -39,7 +39,6 @@ public class AppCDSBuildStep { public static final String CLASSES_LIST_FILE_NAME = "classes.lst"; private static final String CONTAINER_IMAGE_BASE_BUILD_DIR = "/tmp/quarkus"; private static final String CONTAINER_IMAGE_APPCDS_DIR = CONTAINER_IMAGE_BASE_BUILD_DIR + "/appcds"; - public static final ContainerRuntimeUtil.ContainerRuntime CONTAINER_RUNTIME = detectContainerRuntime(false); @BuildStep(onlyIf = AppCDSRequired.class) public void requested(OutputTargetBuildItem outputTarget, BuildProducer producer) @@ -204,12 +203,10 @@ private Path createClassesList(JarBuildItem jarResult, // generate the classes file on the host private List dockerRunCommands(OutputTargetBuildItem outputTarget, String containerImage, String containerWorkingDir) { - if (CONTAINER_RUNTIME == ContainerRuntimeUtil.ContainerRuntime.UNAVAILABLE) { - throw new IllegalStateException("No container runtime was found. " - + "Make sure you have either Docker or Podman installed in your environment."); - } + ContainerRuntime containerRuntime = detectContainerRuntime(true); + List command = new ArrayList<>(10); - command.add(CONTAINER_RUNTIME.getExecutableName()); + command.add(containerRuntime.getExecutableName()); command.add("run"); command.add("-v"); command.add(outputTarget.getOutputDirectory().toAbsolutePath().toString() + ":" + CONTAINER_IMAGE_BASE_BUILD_DIR diff --git a/core/deployment/src/test/java/io/quarkus/deployment/pkg/steps/NativeImageBuildContainerRunnerTest.java b/core/deployment/src/test/java/io/quarkus/deployment/pkg/steps/NativeImageBuildContainerRunnerTest.java index 4fc2f9698b783..498130d4b71ac 100644 --- a/core/deployment/src/test/java/io/quarkus/deployment/pkg/steps/NativeImageBuildContainerRunnerTest.java +++ b/core/deployment/src/test/java/io/quarkus/deployment/pkg/steps/NativeImageBuildContainerRunnerTest.java @@ -1,6 +1,5 @@ package io.quarkus.deployment.pkg.steps; -import static io.quarkus.deployment.pkg.steps.AppCDSBuildStep.CONTAINER_RUNTIME; import static org.assertj.core.api.Assertions.assertThat; import java.nio.file.Path; @@ -19,10 +18,8 @@ class NativeImageBuildContainerRunnerTest { @DisabledIfSystemProperty(named = "avoid-containers", matches = "true") @Test void testBuilderImageBeingPickedUp() { - if (CONTAINER_RUNTIME == ContainerRuntimeUtil.ContainerRuntime.UNAVAILABLE) { - throw new IllegalStateException("No container runtime was found. " - + "Make sure you have either Docker or Podman installed in your environment."); - } + ContainerRuntimeUtil.ContainerRuntime containerRuntime = ContainerRuntimeUtil.detectContainerRuntime(true); + NativeConfig nativeConfig = new NativeConfig(); nativeConfig.containerRuntime = Optional.empty(); boolean found; @@ -31,7 +28,7 @@ void testBuilderImageBeingPickedUp() { nativeConfig.builderImage = "graalvm"; localRunner = new NativeImageBuildLocalContainerRunner(nativeConfig, Path.of("/tmp")); - command = localRunner.buildCommand(CONTAINER_RUNTIME.getExecutableName(), Collections.emptyList(), + command = localRunner.buildCommand(containerRuntime.getExecutableName(), Collections.emptyList(), Collections.emptyList()); found = false; for (String part : command) { @@ -44,7 +41,7 @@ void testBuilderImageBeingPickedUp() { nativeConfig.builderImage = "mandrel"; localRunner = new NativeImageBuildLocalContainerRunner(nativeConfig, Path.of("/tmp")); - command = localRunner.buildCommand(CONTAINER_RUNTIME.getExecutableName(), Collections.emptyList(), + command = localRunner.buildCommand(containerRuntime.getExecutableName(), Collections.emptyList(), Collections.emptyList()); found = false; for (String part : command) { @@ -57,7 +54,7 @@ void testBuilderImageBeingPickedUp() { nativeConfig.builderImage = "RandomString"; localRunner = new NativeImageBuildLocalContainerRunner(nativeConfig, Path.of("/tmp")); - command = localRunner.buildCommand(CONTAINER_RUNTIME.getExecutableName(), Collections.emptyList(), + command = localRunner.buildCommand(containerRuntime.getExecutableName(), Collections.emptyList(), Collections.emptyList()); found = false; for (String part : command) { 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 1e6cff3a46c07..2be3cbf76bdaa 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 @@ -41,7 +41,7 @@ public static ContainerRuntime detectContainerRuntime() { } public static ContainerRuntime detectContainerRuntime(boolean required) { - final ContainerRuntime containerRuntime = loadConfig(); + final ContainerRuntime containerRuntime = loadContainerRuntimeFromSystemProperty(); if (containerRuntime != null) { return containerRuntime; } else { @@ -57,7 +57,7 @@ public static ContainerRuntime detectContainerRuntime(boolean required) { dockerVersionOutput = getVersionOutputFor(ContainerRuntime.DOCKER); dockerAvailable = dockerVersionOutput.contains("Docker version"); if (dockerAvailable) { - storeConfig(ContainerRuntime.DOCKER); + storeContainerRuntimeInSystemProperty(ContainerRuntime.DOCKER); return ContainerRuntime.DOCKER; } } @@ -65,7 +65,7 @@ public static ContainerRuntime detectContainerRuntime(boolean required) { podmanVersionOutput = getVersionOutputFor(ContainerRuntime.PODMAN); podmanAvailable = podmanVersionOutput.startsWith("podman version"); if (podmanAvailable) { - storeConfig(ContainerRuntime.PODMAN); + storeContainerRuntimeInSystemProperty(ContainerRuntime.PODMAN); return ContainerRuntime.PODMAN; } } @@ -77,46 +77,48 @@ public static ContainerRuntime detectContainerRuntime(boolean required) { if (dockerAvailable) { // Check if "docker" is an alias to "podman" if (dockerVersionOutput.startsWith("podman version")) { - storeConfig(ContainerRuntime.PODMAN); + storeContainerRuntimeInSystemProperty(ContainerRuntime.PODMAN); return ContainerRuntime.PODMAN; } - storeConfig(ContainerRuntime.DOCKER); + storeContainerRuntimeInSystemProperty(ContainerRuntime.DOCKER); return ContainerRuntime.DOCKER; } podmanVersionOutput = getVersionOutputFor(ContainerRuntime.PODMAN); podmanAvailable = podmanVersionOutput.startsWith("podman version"); if (podmanAvailable) { - storeConfig(ContainerRuntime.PODMAN); + storeContainerRuntimeInSystemProperty(ContainerRuntime.PODMAN); return ContainerRuntime.PODMAN; } + + 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."); - } else { - storeConfig(ContainerRuntime.UNAVAILABLE); - return ContainerRuntime.UNAVAILABLE; } + + return ContainerRuntime.UNAVAILABLE; } } - private static ContainerRuntime loadConfig() { + private static ContainerRuntime loadContainerRuntimeFromSystemProperty() { final String runtime = System.getProperty(CONTAINER_RUNTIME_SYS_PROP); + if (runtime == null) { return null; - } else if (ContainerRuntime.DOCKER.name().equalsIgnoreCase(runtime)) { - return ContainerRuntime.DOCKER; - } else if (ContainerRuntime.PODMAN.name().equalsIgnoreCase(runtime)) { - return ContainerRuntime.PODMAN; - } else if (ContainerRuntime.UNAVAILABLE.name().equalsIgnoreCase(runtime)) { - return ContainerRuntime.UNAVAILABLE; - } else { + } + + ContainerRuntime containerRuntime = ContainerRuntime.valueOf(runtime); + + if (containerRuntime == null) { log.warnf("System property %s contains an unknown value %s. Ignoring it.", CONTAINER_RUNTIME_SYS_PROP, runtime); - return null; } + + return containerRuntime; } - private static void storeConfig(ContainerRuntime containerRuntime) { + private static void storeContainerRuntimeInSystemProperty(ContainerRuntime containerRuntime) { System.setProperty(CONTAINER_RUNTIME_SYS_PROP, containerRuntime.name()); } @@ -197,6 +199,10 @@ public enum ContainerRuntime { private Boolean rootless; public String getExecutableName() { + if (this == UNAVAILABLE) { + throw new IllegalStateException("Cannot get an executable name when no container runtime is available"); + } + return this.name().toLowerCase(); } @@ -213,5 +219,15 @@ public boolean isRootless() { } return rootless; } + + public static ContainerRuntime of(String value) { + for (ContainerRuntime containerRuntime : values()) { + if (containerRuntime.name().equalsIgnoreCase(value)) { + return containerRuntime; + } + } + + return null; + } } } diff --git a/extensions/container-image/container-image-docker/deployment/src/main/java/io/quarkus/container/image/docker/deployment/DockerProcessor.java b/extensions/container-image/container-image-docker/deployment/src/main/java/io/quarkus/container/image/docker/deployment/DockerProcessor.java index 2ede3959d21f8..a3933d6d3faa1 100644 --- a/extensions/container-image/container-image-docker/deployment/src/main/java/io/quarkus/container/image/docker/deployment/DockerProcessor.java +++ b/extensions/container-image/container-image-docker/deployment/src/main/java/io/quarkus/container/image/docker/deployment/DockerProcessor.java @@ -204,7 +204,7 @@ private String createContainerImage(ContainerImageConfig containerImageConfig, D boolean buildSuccessful = ExecUtil.exec(out.getOutputDirectory().toFile(), reader, executableName, dockerArgs); if (!buildSuccessful) { - throw dockerException(dockerArgs); + throw dockerException(executableName, dockerArgs); } dockerConfig.buildx.platform @@ -250,7 +250,8 @@ private void loginToRegistryIfNeeded(ContainerImageConfig containerImageConfig, containerImageConfig.username.get(), "-p" + containerImageConfig.password.get()); if (!loginSuccessful) { - throw dockerException(new String[] { "-u", containerImageConfig.username.get(), "-p", "********" }); + throw dockerException(executableName, + new String[] { "-u", containerImageConfig.username.get(), "-p", "********" }); } } } @@ -326,7 +327,7 @@ private void createAdditionalTags(String image, List additionalImageTags String[] tagArgs = { "tag", image, additionalTag }; boolean tagSuccessful = ExecUtil.exec(executableName, tagArgs); if (!tagSuccessful) { - throw dockerException(tagArgs); + throw dockerException(executableName, tagArgs); } } } @@ -336,14 +337,15 @@ private void pushImage(String image, DockerConfig dockerConfig) { String[] pushArgs = { "push", image }; boolean pushSuccessful = ExecUtil.exec(executableName, pushArgs); if (!pushSuccessful) { - throw dockerException(pushArgs); + throw dockerException(executableName, pushArgs); } log.info("Successfully pushed docker image " + image); } - private RuntimeException dockerException(String[] dockerArgs) { + private RuntimeException dockerException(String executableName, String[] dockerArgs) { return new RuntimeException( - "Execution of 'docker " + String.join(" ", dockerArgs) + "' failed. See docker output for more details"); + "Execution of '" + executableName + " " + String.join(" ", dockerArgs) + + "' failed. See docker output for more details"); } private DockerfilePaths getDockerfilePaths(DockerConfig dockerConfig, boolean forNative, diff --git a/test-framework/junit5/src/main/java/io/quarkus/test/junit/IntegrationTestUtil.java b/test-framework/junit5/src/main/java/io/quarkus/test/junit/IntegrationTestUtil.java index 82674141ec870..a721afa2ba601 100644 --- a/test-framework/junit5/src/main/java/io/quarkus/test/junit/IntegrationTestUtil.java +++ b/test-framework/junit5/src/main/java/io/quarkus/test/junit/IntegrationTestUtil.java @@ -65,7 +65,6 @@ public final class IntegrationTestUtil { public static final int DEFAULT_PORT = 8081; public static final int DEFAULT_HTTPS_PORT = 8444; public static final long DEFAULT_WAIT_TIME_SECONDS = 60; - public static final ContainerRuntimeUtil.ContainerRuntime CONTAINER_RUNTIME = detectContainerRuntime(false); private IntegrationTestUtil() { } @@ -337,13 +336,11 @@ public void accept(String s, String s2) { private static void createNetworkIfNecessary( final ArtifactLauncher.InitContext.DevServicesLaunchResult devServicesLaunchResult) { if (devServicesLaunchResult.manageNetwork() && (devServicesLaunchResult.networkId() != null)) { - if (CONTAINER_RUNTIME == ContainerRuntimeUtil.ContainerRuntime.UNAVAILABLE) { - throw new IllegalStateException("No container runtime was found. " - + "Make sure you have either Docker or Podman installed in your environment."); - } + ContainerRuntimeUtil.ContainerRuntime containerRuntime = detectContainerRuntime(true); + try { int networkCreateResult = new ProcessBuilder().redirectError(DISCARD).redirectOutput(DISCARD) - .command(CONTAINER_RUNTIME.getExecutableName(), "network", "create", + .command(containerRuntime.getExecutableName(), "network", "create", devServicesLaunchResult.networkId()) .start().waitFor(); if (networkCreateResult > 0) { @@ -356,7 +353,7 @@ private static void createNetworkIfNecessary( public void run() { try { new ProcessBuilder().redirectError(DISCARD).redirectOutput(DISCARD) - .command(CONTAINER_RUNTIME.getExecutableName(), "network", "rm", + .command(containerRuntime.getExecutableName(), "network", "rm", devServicesLaunchResult.networkId()) .start() .waitFor();