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 60d9243131ad5..48e38e68d061b 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 @@ -2,27 +2,16 @@ import static io.quarkus.deployment.pkg.steps.LinuxIDUtil.getLinuxID; -import java.io.File; -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.LinkOption; import java.nio.file.Path; -import java.time.Duration; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; -import java.util.Objects; -import java.util.Set; -import java.util.function.Predicate; -import java.util.stream.Collectors; import org.apache.commons.lang3.SystemUtils; import org.jboss.logging.Logger; -import io.quarkus.deployment.OutputFilter; import io.quarkus.deployment.pkg.NativeConfig; -import io.quarkus.deployment.util.ExecUtil; import io.quarkus.deployment.util.FileUtil; import io.quarkus.runtime.util.ContainerRuntimeUtil; @@ -34,7 +23,8 @@ public NativeImageBuildLocalContainerRunner(NativeConfig nativeConfig, Path outp super(nativeConfig, outputDir); if (SystemUtils.IS_OS_LINUX) { ArrayList containerRuntimeArgs = new ArrayList<>(Arrays.asList(baseContainerRuntimeArgs)); - if (isDockerRootless(containerRuntime)) { + if (containerRuntime == ContainerRuntimeUtil.ContainerRuntime.DOCKER + && containerRuntime.isRootless()) { Collections.addAll(containerRuntimeArgs, "--user", String.valueOf(0)); } else { String uid = getLinuxID("-ur"); @@ -51,63 +41,6 @@ public NativeImageBuildLocalContainerRunner(NativeConfig nativeConfig, Path outp } } - private static boolean isDockerRootless(ContainerRuntimeUtil.ContainerRuntime containerRuntime) { - if (containerRuntime != ContainerRuntimeUtil.ContainerRuntime.DOCKER) { - return false; - } - String dockerEndpoint = fetchDockerEndpoint(); - // docker socket? - String socketUriPrefix = "unix://"; - if (dockerEndpoint == null || !dockerEndpoint.startsWith(socketUriPrefix)) { - return false; - } - String dockerSocket = dockerEndpoint.substring(socketUriPrefix.length()); - String currentUid = getLinuxID("-ur"); - if (currentUid == null || currentUid.isEmpty() || currentUid.equals(String.valueOf(0))) { - return false; - } - - int socketOwnerUid; - try { - socketOwnerUid = (int) Files.getAttribute(Path.of(dockerSocket), "unix:uid", LinkOption.NOFOLLOW_LINKS); - } catch (IOException e) { - LOGGER.infof("Owner UID lookup on '%s' failed with '%s'", dockerSocket, e.getMessage()); - return false; - } - return currentUid.equals(String.valueOf(socketOwnerUid)); - } - - private static String fetchDockerEndpoint() { - // DOCKER_HOST environment variable overrides the active context - String dockerHost = System.getenv("DOCKER_HOST"); - if (dockerHost != null) { - return dockerHost; - } - - OutputFilter outputFilter = new OutputFilter(); - if (!ExecUtil.execWithTimeout(new File("."), outputFilter, Duration.ofMillis(3000), - "docker", "context", "ls", "--format", - "{{- if .Current -}} {{- .DockerEndpoint -}} {{- end -}}")) { - LOGGER.debug("Docker context lookup didn't succeed in time"); - return null; - } - - Set endpoints = outputFilter.getOutput().lines() - .filter(Objects::nonNull) - .filter(Predicate.not(String::isBlank)) - .collect(Collectors.toSet()); - if (endpoints.size() == 1) { - return endpoints.stream().findFirst().orElse(null); - } - if (LOGGER.isDebugEnabled()) { - LOGGER.debugf("Found too many active Docker endpoints: [%s]", - endpoints.stream() - .map(endpoint -> String.format("'%s'", endpoint)) - .collect(Collectors.joining(","))); - } - return null; - } - @Override protected List getContainerRuntimeBuildArgs() { List containerRuntimeArgs = super.getContainerRuntimeBuildArgs(); 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 b886e12ff980e..512f7fe7c90dc 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 @@ -1,7 +1,10 @@ package io.quarkus.runtime.util; +import java.io.BufferedReader; import java.io.IOException; +import java.io.InputStreamReader; import java.nio.charset.StandardCharsets; +import java.util.function.Predicate; import org.jboss.logging.Logger; @@ -59,6 +62,33 @@ private static String getVersionOutputFor(ContainerRuntime containerRuntime) { } } + private static boolean getRootlessStateFor(ContainerRuntime containerRuntime) { + Process rootlessProcess = null; + try { + ProcessBuilder pb = new ProcessBuilder(containerRuntime.getExecutableName(), "info") + .redirectErrorStream(true); + rootlessProcess = pb.start(); + rootlessProcess.waitFor(); + BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(rootlessProcess.getInputStream())); + 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); + } 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()); + return false; + } finally { + if (rootlessProcess != null) { + rootlessProcess.destroy(); + } + } + } + /** * Supported Container runtimes */ @@ -66,8 +96,18 @@ public enum ContainerRuntime { DOCKER, PODMAN; + private final boolean rootless; + + ContainerRuntime() { + this.rootless = getRootlessStateFor(this); + } + public String getExecutableName() { return this.name().toLowerCase(); } + + public boolean isRootless() { + return rootless; + } } }