Skip to content

Commit

Permalink
Simplify checks for rootless container runtime
Browse files Browse the repository at this point in the history
(cherry picked from commit 140a6ff)
  • Loading branch information
zakkak authored and gsmet committed May 9, 2023
1 parent 9e188ba commit 6a2c6cf
Show file tree
Hide file tree
Showing 2 changed files with 42 additions and 69 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -34,7 +23,8 @@ public NativeImageBuildLocalContainerRunner(NativeConfig nativeConfig, Path outp
super(nativeConfig, outputDir);
if (SystemUtils.IS_OS_LINUX) {
ArrayList<String> 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");
Expand All @@ -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<String> 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<String> getContainerRuntimeBuildArgs() {
List<String> containerRuntimeArgs = super.getContainerRuntimeBuildArgs();
Expand Down
Original file line number Diff line number Diff line change
@@ -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;

Expand Down Expand Up @@ -59,15 +62,52 @@ 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<String> stringPredicate;
// Docker includes just "rootless" under SecurityOptions, while podman includes "rootless: <boolean>"
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
*/
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;
}
}
}

0 comments on commit 6a2c6cf

Please sign in to comment.