From 8bad2ac599e78e6b222961a6469234b1ce298f85 Mon Sep 17 00:00:00 2001 From: Michal Karm Babacek Date: Sat, 25 Feb 2023 18:53:12 +0100 Subject: [PATCH 1/4] Enable Podman for Windows in-container tests Enables enforcing podman on systems where both podman and docker are available --- .../quarkus/deployment/IsDockerWorking.java | 4 +- .../deployment/pkg/steps/AppCDSBuildStep.java | 3 +- .../NativeImageBuildLocalContainerRunner.java | 5 +- .../NativeImageBuildContainerRunnerTest.java | 10 ++- .../runtime/util/ContainerRuntimeUtil.java | 15 ++++ .../DefaultDockerContainerLauncher.java | 73 ++++++++----------- .../common/DefaultNativeImageLauncher.java | 2 +- .../io/quarkus/test/common/LauncherUtil.java | 2 +- .../test/junit/IntegrationTestUtil.java | 4 +- 9 files changed, 65 insertions(+), 53 deletions(-) diff --git a/core/deployment/src/main/java/io/quarkus/deployment/IsDockerWorking.java b/core/deployment/src/main/java/io/quarkus/deployment/IsDockerWorking.java index 2b26c34844365..2fe3f196040a6 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/IsDockerWorking.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/IsDockerWorking.java @@ -175,7 +175,7 @@ private static class DockerBinaryStrategy implements Strategy { private DockerBinaryStrategy(boolean silent) { this.silent = silent; - this.binary = ConfigProvider.getConfig().getOptionalValue("quarkus.docker.executable-name", String.class) + this.binary = ConfigProvider.getConfig().getOptionalValue("quarkus.native.container-runtime", String.class) .orElse("docker"); } @@ -194,7 +194,7 @@ public Result get() { try { OutputFilter filter = new OutputFilter(); if (ExecUtil.execWithTimeout(new File("."), filter, Duration.ofMillis(DOCKER_CMD_CHECK_TIMEOUT), - "docker", "version", "--format", "'{{.Server.Version}}'")) { + binary, "version", "--format", "'{{.Server.Version}}'")) { LOGGER.debugf("Docker daemon found. Version: %s", filter.getOutput()); return Result.AVAILABLE; } else { 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 22c2714cb9a7f..f75c9026a9b66 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 @@ -1,6 +1,7 @@ package io.quarkus.deployment.pkg.steps; import static io.quarkus.deployment.pkg.steps.LinuxIDUtil.getLinuxID; +import static io.quarkus.deployment.pkg.steps.NativeImageBuildLocalContainerRunner.DOCKER_EXECUTABLE; import java.io.File; import java.io.IOException; @@ -202,7 +203,7 @@ private Path createClassesList(JarBuildItem jarResult, private List dockerRunCommands(OutputTargetBuildItem outputTarget, String containerImage, String containerWorkingDir) { List command = new ArrayList<>(10); - command.add("docker"); + command.add(DOCKER_EXECUTABLE); command.add("run"); command.add("-v"); command.add(outputTarget.getOutputDirectory().toAbsolutePath().toString() + ":" + CONTAINER_IMAGE_BASE_BUILD_DIR 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 a7a8aa27725e3..f916ea37d08e2 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 @@ -9,7 +9,7 @@ import java.util.List; import org.apache.commons.lang3.SystemUtils; -import org.jboss.logging.Logger; +import org.eclipse.microprofile.config.ConfigProvider; import io.quarkus.deployment.pkg.NativeConfig; import io.quarkus.deployment.util.FileUtil; @@ -17,7 +17,8 @@ public class NativeImageBuildLocalContainerRunner extends NativeImageBuildContainerRunner { - private static final Logger LOGGER = Logger.getLogger(NativeImageBuildLocalContainerRunner.class.getName()); + public static final String DOCKER_EXECUTABLE = ConfigProvider.getConfig() + .getOptionalValue("quarkus.native.container-runtime", String.class).orElse("docker"); public NativeImageBuildLocalContainerRunner(NativeConfig nativeConfig, Path outputDir) { super(nativeConfig, outputDir); 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 17d9c77287d21..3d3bd861d108b 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,5 +1,6 @@ package io.quarkus.deployment.pkg.steps; +import static io.quarkus.deployment.pkg.steps.NativeImageBuildLocalContainerRunner.DOCKER_EXECUTABLE; import static org.assertj.core.api.Assertions.assertThat; import java.nio.file.Path; @@ -25,33 +26,36 @@ void testBuilderImageBeingPickedUp() { nativeConfig.builderImage = "graalvm"; localRunner = new NativeImageBuildLocalContainerRunner(nativeConfig, Path.of("/tmp")); - command = localRunner.buildCommand("docker", Collections.emptyList(), Collections.emptyList()); + command = localRunner.buildCommand(DOCKER_EXECUTABLE, Collections.emptyList(), Collections.emptyList()); found = false; for (String part : command) { if (part.contains("ubi-quarkus-graalvmce-builder-image")) { found = true; + break; } } assertThat(found).isTrue(); nativeConfig.builderImage = "mandrel"; localRunner = new NativeImageBuildLocalContainerRunner(nativeConfig, Path.of("/tmp")); - command = localRunner.buildCommand("docker", Collections.emptyList(), Collections.emptyList()); + command = localRunner.buildCommand(DOCKER_EXECUTABLE, Collections.emptyList(), Collections.emptyList()); found = false; for (String part : command) { if (part.contains("ubi-quarkus-mandrel-builder-image")) { found = true; + break; } } assertThat(found).isTrue(); nativeConfig.builderImage = "RandomString"; localRunner = new NativeImageBuildLocalContainerRunner(nativeConfig, Path.of("/tmp")); - command = localRunner.buildCommand("docker", Collections.emptyList(), Collections.emptyList()); + command = localRunner.buildCommand(DOCKER_EXECUTABLE, Collections.emptyList(), Collections.emptyList()); found = false; for (String part : command) { if (part.equals("RandomString")) { found = true; + break; } } assertThat(found).isTrue(); 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 94cee990fc25b..c24572b5d3c3d 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 @@ -7,6 +7,7 @@ import java.nio.charset.StandardCharsets; import java.util.function.Predicate; +import org.eclipse.microprofile.config.ConfigProvider; import org.jboss.logging.Logger; public final class ContainerRuntimeUtil { @@ -30,6 +31,20 @@ public static ContainerRuntime detectContainerRuntime() { // podman version 2.1.1 String podmanVersionOutput = getVersionOutputFor(ContainerRuntime.PODMAN); boolean podmanAvailable = podmanVersionOutput.startsWith("podman version"); + + final String executable = ConfigProvider.getConfig() + .getOptionalValue("quarkus.native.container-runtime", String.class).orElse(null); + if (executable != null) { + if (executable.trim().equalsIgnoreCase("docker") && dockerAvailable) { + return ContainerRuntime.DOCKER; + } else if (executable.trim().equalsIgnoreCase("podman") && podmanAvailable) { + 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 (dockerAvailable) { // Check if "docker" is an alias to "podman" if (dockerVersionOutput.equals(podmanVersionOutput)) { diff --git a/test-framework/common/src/main/java/io/quarkus/test/common/DefaultDockerContainerLauncher.java b/test-framework/common/src/main/java/io/quarkus/test/common/DefaultDockerContainerLauncher.java index 4918c94d6dacf..8203acbf65699 100644 --- a/test-framework/common/src/main/java/io/quarkus/test/common/DefaultDockerContainerLauncher.java +++ b/test-framework/common/src/main/java/io/quarkus/test/common/DefaultDockerContainerLauncher.java @@ -5,15 +5,12 @@ import static io.quarkus.test.common.LauncherUtil.waitForCapturedListeningData; import static io.quarkus.test.common.LauncherUtil.waitForStartedFunction; import static java.lang.ProcessBuilder.Redirect.DISCARD; -import static java.lang.ProcessBuilder.Redirect.PIPE; -import java.io.FileOutputStream; import java.io.IOException; -import java.io.InputStream; import java.net.ServerSocket; +import java.nio.file.FileSystemException; import java.nio.file.Files; import java.nio.file.Path; -import java.nio.file.Paths; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; @@ -24,9 +21,8 @@ import java.util.concurrent.TimeUnit; import java.util.function.Function; -import org.apache.commons.io.IOUtils; -import org.apache.commons.io.input.TeeInputStream; import org.apache.commons.lang3.RandomStringUtils; +import org.jboss.logging.Logger; import io.quarkus.runtime.util.ContainerRuntimeUtil; import io.quarkus.test.common.http.TestHTTPResourceManager; @@ -34,6 +30,8 @@ public class DefaultDockerContainerLauncher implements DockerContainerArtifactLauncher { + private static final Logger log = Logger.getLogger(DefaultDockerContainerLauncher.class); + private int httpPort; private int httpsPort; private long waitTimeSeconds; @@ -43,16 +41,11 @@ public class DefaultDockerContainerLauncher implements DockerContainerArtifactLa private String containerImage; private boolean pullRequired; private Map additionalExposedPorts; - private final Map systemProps = new HashMap<>(); - private boolean isSsl; - - private String containerName; - + private final String containerName = "quarkus-integration-test-" + RandomStringUtils.random(5, true, false); private String containerRuntimeBinaryName; - - private ExecutorService executorService = Executors.newSingleThreadExecutor(); + private final ExecutorService executorService = Executors.newSingleThreadExecutor(); @Override public void init(DockerContainerArtifactLauncher.DockerInitContext initContext) { @@ -78,7 +71,7 @@ public void start() throws IOException { containerRuntimeBinaryName = determineBinary(); if (pullRequired) { - System.out.println("Pulling container image '" + containerImage + "'"); + log.infof("Pulling container image '%s'", containerImage); try { int pullResult = new ProcessBuilder().redirectError(DISCARD).redirectOutput(DISCARD) .command(containerRuntimeBinaryName, "pull", containerImage).start().waitFor(); @@ -99,21 +92,21 @@ public void start() throws IOException { httpsPort = getRandomPort(); } - List args = new ArrayList<>(); + final List args = new ArrayList<>(); args.add(containerRuntimeBinaryName); args.add("run"); if (!argLine.isEmpty()) { args.addAll(argLine); } args.add("--name"); - containerName = "quarkus-integration-test-" + RandomStringUtils.random(5, true, false); args.add(containerName); + args.add("-i"); // Interactive, write logs to stdout args.add("--rm"); args.add("-p"); args.add(httpPort + ":" + httpPort); args.add("-p"); args.add(httpsPort + ":" + httpsPort); - for (var entry : additionalExposedPorts.entrySet()) { + for (Map.Entry entry : additionalExposedPorts.entrySet()) { args.add("-p"); args.add(entry.getKey() + ":" + entry.getValue()); } @@ -125,7 +118,7 @@ public void start() throws IOException { if (DefaultJarLauncher.HTTP_PRESENT) { args.addAll(toEnvVar("quarkus.http.port", "" + httpPort)); args.addAll(toEnvVar("quarkus.http.ssl-port", "" + httpsPort)); - // this won't be correct when using the random port but it's really only used by us for the rest client tests + // This won't be correct when using the random port, but it's really only used by us for the rest client tests // in the main module, since those tests hit the application itself args.addAll(toEnvVar("test.url", TestHTTPResourceManager.getUri())); } @@ -138,31 +131,31 @@ public void start() throws IOException { } args.add(containerImage); - Path logFile = PropertyTestUtil.getLogFilePath(); - Files.deleteIfExists(logFile); - Files.createDirectories(logFile.getParent()); - - Path containerLogFile = Paths.get("target", "container.log"); - Files.createDirectories(containerLogFile.getParent()); - FileOutputStream containerLogOutputStream = new FileOutputStream(containerLogFile.toFile(), true); + final Path logFile = PropertyTestUtil.getLogFilePath(); + try { + Files.deleteIfExists(logFile); + Files.createDirectories(logFile.getParent()); + } catch (FileSystemException e) { + log.warnf("Log file %s deletion failed, could happen on Windows, we can carry on.", logFile); + } - System.out.println("Executing \"" + String.join(" ", args) + "\""); + log.infof("Executing \"%s\"", String.join(" ", args)); - Function startedFunction = createStartedFunction(); + final Function startedFunction = createStartedFunction(); - // the idea here is to obtain the logs of the application simply by redirecting all its output the a file - // this is done in contrast with the JarLauncher and NativeImageLauncher because in the case of the container - // the log itself is written inside the container - Process quarkusProcess = new ProcessBuilder(args).redirectError(PIPE).redirectOutput(PIPE).start(); - InputStream tee = new TeeInputStream(quarkusProcess.getInputStream(), new FileOutputStream(logFile.toFile())); - executorService.submit(() -> IOUtils.copy(tee, containerLogOutputStream)); + // We rely on the container writing log to stdout. If it just writes to a logfile inside itself, we would have + // to mount /work/ directory to get quarkus.log. + final Process containerProcess = new ProcessBuilder(args) + .redirectErrorStream(true) + .redirectOutput(ProcessBuilder.Redirect.appendTo(logFile.toFile())) + .start(); if (startedFunction != null) { - IntegrationTestStartedNotifier.Result result = waitForStartedFunction(startedFunction, quarkusProcess, + final IntegrationTestStartedNotifier.Result result = waitForStartedFunction(startedFunction, containerProcess, waitTimeSeconds, logFile); isSsl = result.isSsl(); } else { - ListeningAddress result = waitForCapturedListeningData(quarkusProcess, logFile, waitTimeSeconds); + final ListeningAddress result = waitForCapturedListeningData(containerProcess, logFile, waitTimeSeconds); updateConfigForPort(result.getPort()); isSsl = result.isSsl(); } @@ -188,10 +181,7 @@ public void includeAsSysProps(Map systemProps) { private List toEnvVar(String property, String value) { if ((property != null) && (!property.isEmpty())) { - List result = new ArrayList<>(2); - result.add("--env"); - result.add(String.format("%s=%s", convertPropertyToEnvVar(property), value)); - return result; + return List.of("--env", String.format("%s=%s", convertPropertyToEnvVar(property), value)); } return Collections.emptyList(); } @@ -203,14 +193,13 @@ private String convertPropertyToEnvVar(String property) { @Override public void close() { try { - Process dockerStopProcess = new ProcessBuilder(containerRuntimeBinaryName, "stop", containerName) + final Process dockerStopProcess = new ProcessBuilder(containerRuntimeBinaryName, "stop", containerName) .redirectError(DISCARD) .redirectOutput(DISCARD).start(); dockerStopProcess.waitFor(10, TimeUnit.SECONDS); } catch (IOException | InterruptedException e) { - System.out.println("Unable to stop container '" + containerName + "'"); + log.errorf("Unable to stop container '%s'", containerName); } executorService.shutdown(); } - } diff --git a/test-framework/common/src/main/java/io/quarkus/test/common/DefaultNativeImageLauncher.java b/test-framework/common/src/main/java/io/quarkus/test/common/DefaultNativeImageLauncher.java index 2680369cdbe12..9fa084ce4001e 100644 --- a/test-framework/common/src/main/java/io/quarkus/test/common/DefaultNativeImageLauncher.java +++ b/test-framework/common/src/main/java/io/quarkus/test/common/DefaultNativeImageLauncher.java @@ -130,7 +130,7 @@ public void start(String[] programArgs, boolean handleIo) throws IOException { args.add("-Dtest.url=" + TestHTTPResourceManager.getUri()); } Path logFile = PropertyTestUtil.getLogFilePath(); - args.add("-Dquarkus.log.file.path=" + logFile.toAbsolutePath().toString()); + args.add("-Dquarkus.log.file.path=" + logFile.toAbsolutePath()); args.add("-Dquarkus.log.file.enable=true"); if (testProfile != null) { args.add("-Dquarkus.profile=" + testProfile); diff --git a/test-framework/common/src/main/java/io/quarkus/test/common/LauncherUtil.java b/test-framework/common/src/main/java/io/quarkus/test/common/LauncherUtil.java index 76c2701fadded..e6fe3df54729d 100644 --- a/test-framework/common/src/main/java/io/quarkus/test/common/LauncherUtil.java +++ b/test-framework/common/src/main/java/io/quarkus/test/common/LauncherUtil.java @@ -253,7 +253,7 @@ public void run() { long now = System.currentTimeMillis(); // if we have seen info that the app is started in the log a while ago - // or waiting the the next check interval will exceed the bailout time, it's time to finish waiting: + // or waiting the next check interval will exceed the bailout time, it's time to finish waiting: if (now + LOG_CHECK_INTERVAL > bailoutTime || now - 2 * LOG_CHECK_INTERVAL > timeStarted) { if (started) { dataDetermined(null, null); // no http, all is null 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 f3aa07dc40270..372d19651d899 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 @@ -34,6 +34,7 @@ import org.apache.commons.lang3.RandomStringUtils; import org.eclipse.microprofile.config.Config; +import org.eclipse.microprofile.config.ConfigProvider; import org.eclipse.microprofile.config.inject.ConfigProperty; import org.jboss.jandex.Index; import org.junit.jupiter.api.extension.ExtensionContext; @@ -64,7 +65,8 @@ public final class IntegrationTestUtil { public static final int DEFAULT_HTTPS_PORT = 8444; public static final long DEFAULT_WAIT_TIME_SECONDS = 60; - private static final String DOCKER_BINARY = "docker"; + private static final String DOCKER_BINARY = ConfigProvider.getConfig() + .getOptionalValue("quarkus.native.container-runtime", String.class).orElse("docker"); private IntegrationTestUtil() { } From 127c4b0f491b1bcd3c29b0d3b3fc672e4b7b6b13 Mon Sep 17 00:00:00 2001 From: Michal Karm Babacek Date: Mon, 27 Feb 2023 17:21:19 +0100 Subject: [PATCH 2/4] AWT in-container test --- integration-tests/awt/pom.xml | 41 ++++++++++++++++++- .../awt/src/main/docker/Dockerfile.native | 15 +++++++ 2 files changed, 54 insertions(+), 2 deletions(-) create mode 100644 integration-tests/awt/src/main/docker/Dockerfile.native diff --git a/integration-tests/awt/pom.xml b/integration-tests/awt/pom.xml index 7f5c5ff6af75e..6f7ab12430a13 100644 --- a/integration-tests/awt/pom.xml +++ b/integration-tests/awt/pom.xml @@ -27,6 +27,10 @@ io.quarkus quarkus-resteasy-multipart + + io.quarkus + quarkus-container-image-docker + @@ -45,6 +49,8 @@ test + + io.quarkus @@ -59,7 +65,6 @@ - io.quarkus quarkus-resteasy-multipart-deployment @@ -99,7 +104,19 @@ - + + io.quarkus + quarkus-container-image-docker-deployment + ${project.version} + pom + test + + + * + * + + + @@ -128,6 +145,26 @@ false + + maven-failsafe-plugin + + + + integration-test + verify + + + + ${project.build.directory}/${project.build.finalName}-runner + org.jboss.logmanager.LogManager + ${maven.home} + localhost + 8081 + + + + + diff --git a/integration-tests/awt/src/main/docker/Dockerfile.native b/integration-tests/awt/src/main/docker/Dockerfile.native new file mode 100644 index 0000000000000..8b7e7fd728806 --- /dev/null +++ b/integration-tests/awt/src/main/docker/Dockerfile.native @@ -0,0 +1,15 @@ +FROM registry.access.redhat.com/ubi8/ubi-minimal:8.7 +# Dependencies for AWT +RUN microdnf install freetype fontconfig \ + && microdnf clean all +WORKDIR /work/ +RUN chown 1001 /work \ + && chmod "g+rwX" /work \ + && chown 1001:root /work +COPY --chown=1001:root target/*-runner /work/application +# Permissions fix for Windows +RUN chmod "ugo+x" /work/application +EXPOSE 8081 +USER 1001 + +CMD ["./application", "-Dquarkus.http.host=0.0.0.0"] From 36b2d77b262389cca7bd8aa62c3f2913b5ed0f3a Mon Sep 17 00:00:00 2001 From: Michal Karm Babacek Date: Mon, 6 Mar 2023 10:41:20 +0100 Subject: [PATCH 3/4] Logs proper command when detecting rootless container runtime e.g. Before: ``` WARN [io.qua.run.uti.ContainerRuntimeUtil] (main) Command "docker" exited with error code 1. Rootless container runtime detection might not be reliable. ``` after: ``` WARN [io.qua.run.uti.ContainerRuntimeUtil] (main) Command "docker info" exited with error code 1. Rootless container runtime detection might not be reliable or the container service is not running at all. ``` plus with `-Dquarkus.log.level=DEBUG` it logs: ``` DEBUG [io.qua.run.uti.ContainerRuntimeUtil] (main) Command "docker info" output: Client: Context: default Debug Mode: false Plugins: buildx: Docker Buildx (Docker Inc.) Version: v0.10.2 Path: /usr/libexec/docker/cli-plugins/docker-buildx compose: Docker Compose (Docker Inc.) Version: v2.16.0 Path: /usr/libexec/docker/cli-plugins/docker-compose scan: Docker Scan (Docker Inc.) Version: v0.23.0 Path: /usr/libexec/docker/cli-plugins/docker-scan Server: ERROR: Cannot connect to the Docker daemon at unix:///var/run/docker.sock. Is the docker daemon running? ``` --- .../runtime/util/ContainerRuntimeUtil.java | 29 ++++++++++++------- 1 file changed, 18 insertions(+), 11 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 c24572b5d3c3d..4fc6bc1a8eb4f 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 @@ -6,6 +6,7 @@ import java.io.InputStreamReader; import java.nio.charset.StandardCharsets; import java.util.function.Predicate; +import java.util.stream.Collectors; import org.eclipse.microprofile.config.ConfigProvider; import org.jboss.logging.Logger; @@ -80,31 +81,37 @@ private static String getVersionOutputFor(ContainerRuntime containerRuntime) { private static boolean getRootlessStateFor(ContainerRuntime containerRuntime) { Process rootlessProcess = null; + ProcessBuilder pb = null; try { - ProcessBuilder pb = new ProcessBuilder(containerRuntime.getExecutableName(), "info") - .redirectErrorStream(true); + pb = new ProcessBuilder(containerRuntime.getExecutableName(), "info").redirectErrorStream(true); rootlessProcess = pb.start(); int exitCode = rootlessProcess.waitFor(); if (exitCode != 0) { log.warnf("Command \"%s\" exited with error code %d. " + - "Rootless container runtime detection might not be reliable.", - containerRuntime.getExecutableName(), exitCode); + "Rootless container runtime detection might not be reliable or the container service is not running at all.", + String.join(" ", pb.command()), exitCode); } try (InputStream inputStream = rootlessProcess.getInputStream(); InputStreamReader inputStreamReader = new InputStreamReader(inputStream); BufferedReader bufferedReader = new BufferedReader(inputStreamReader)) { - Predicate stringPredicate; - // Docker includes just "rootless" under SecurityOptions, while podman includes "rootless: " - if (containerRuntime == ContainerRuntime.DOCKER) { - stringPredicate = line -> line.trim().equals("rootless"); + if (exitCode != 0) { + log.debugf("Command \"%s\" output: %s", String.join(" ", pb.command()), + bufferedReader.lines().collect(Collectors.joining(System.lineSeparator()))); + return false; } else { - stringPredicate = line -> line.trim().equals("rootless: true"); + Predicate stringPredicate; + // Docker includes just "rootless" under SecurityOptions, while podman includes "rootless: " + if (containerRuntime == ContainerRuntime.DOCKER) { + stringPredicate = line -> line.trim().equals("rootless"); + } else { + stringPredicate = line -> line.trim().equals("rootless: true"); + } + return bufferedReader.lines().anyMatch(stringPredicate); } - return bufferedReader.lines().anyMatch(stringPredicate); } } catch (IOException | InterruptedException e) { // If an exception is thrown in the process, assume we are not running rootless (default docker installation) - log.debugf(e, "Failure to read info output from %s", containerRuntime.getExecutableName()); + log.debugf(e, "Failure to read info output from %s", String.join(" ", pb.command())); return false; } finally { if (rootlessProcess != null) { From c6b44126324c61ef37299967ceff16e79e4c8d1b Mon Sep 17 00:00:00 2001 From: Michal Karm Babacek Date: Tue, 7 Mar 2023 15:02:19 +0100 Subject: [PATCH 4/4] ConfigProvider.getConfig() different classloader issue --- .../deployment/pkg/steps/AppCDSBuildStep.java | 3 ++- .../quarkus/runtime/util/ContainerRuntimeUtil.java | 13 +++++++------ .../io/quarkus/test/junit/IntegrationTestUtil.java | 6 ++---- 3 files changed, 11 insertions(+), 11 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 f75c9026a9b66..7d9efb860968c 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 @@ -1,7 +1,7 @@ package io.quarkus.deployment.pkg.steps; import static io.quarkus.deployment.pkg.steps.LinuxIDUtil.getLinuxID; -import static io.quarkus.deployment.pkg.steps.NativeImageBuildLocalContainerRunner.DOCKER_EXECUTABLE; +import static io.quarkus.runtime.util.ContainerRuntimeUtil.detectContainerRuntime; import java.io.File; import java.io.IOException; @@ -38,6 +38,7 @@ 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 String DOCKER_EXECUTABLE = detectContainerRuntime().getExecutableName(); @BuildStep(onlyIf = AppCDSRequired.class) public void requested(OutputTargetBuildItem outputTarget, BuildProducer producer) 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 4fc6bc1a8eb4f..707d54fa963e8 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 @@ -11,9 +11,13 @@ import org.eclipse.microprofile.config.ConfigProvider; import org.jboss.logging.Logger; +import io.smallrye.config.SmallRyeConfig; + 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); private ContainerRuntimeUtil() { } @@ -32,13 +36,10 @@ public static ContainerRuntime detectContainerRuntime() { // podman version 2.1.1 String podmanVersionOutput = getVersionOutputFor(ContainerRuntime.PODMAN); boolean podmanAvailable = podmanVersionOutput.startsWith("podman version"); - - final String executable = ConfigProvider.getConfig() - .getOptionalValue("quarkus.native.container-runtime", String.class).orElse(null); - if (executable != null) { - if (executable.trim().equalsIgnoreCase("docker") && dockerAvailable) { + if (DOCKER_EXECUTABLE != null) { + if (DOCKER_EXECUTABLE.trim().equalsIgnoreCase("docker") && dockerAvailable) { return ContainerRuntime.DOCKER; - } else if (executable.trim().equalsIgnoreCase("podman") && podmanAvailable) { + } else if (DOCKER_EXECUTABLE.trim().equalsIgnoreCase("podman") && podmanAvailable) { return ContainerRuntime.PODMAN; } else { log.warn("quarkus.native.container-runtime config property must be set to either podman or docker " + 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 372d19651d899..6a1bdcac2201f 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 @@ -1,5 +1,6 @@ package io.quarkus.test.junit; +import static io.quarkus.runtime.util.ContainerRuntimeUtil.detectContainerRuntime; import static io.quarkus.test.common.PathTestHelper.getAppClassLocationForTestLocation; import static io.quarkus.test.common.PathTestHelper.getTestClassesLocation; import static java.lang.ProcessBuilder.Redirect.DISCARD; @@ -34,7 +35,6 @@ import org.apache.commons.lang3.RandomStringUtils; import org.eclipse.microprofile.config.Config; -import org.eclipse.microprofile.config.ConfigProvider; import org.eclipse.microprofile.config.inject.ConfigProperty; import org.jboss.jandex.Index; import org.junit.jupiter.api.extension.ExtensionContext; @@ -64,9 +64,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 = ConfigProvider.getConfig() - .getOptionalValue("quarkus.native.container-runtime", String.class).orElse("docker"); + private static final String DOCKER_BINARY = detectContainerRuntime().getExecutableName(); private IntegrationTestUtil() { }