-
Notifications
You must be signed in to change notification settings - Fork 2.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Container runtime detection cached in sys prop, container-docker extension #31857
Changes from all commits
4c449e0
f8c61cf
781eb53
ea1728b
38d3c0d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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<AppCDSRequestedBuildItem> producer) | ||
|
@@ -204,12 +203,10 @@ private Path createClassesList(JarBuildItem jarResult, | |
// generate the classes file on the host | ||
private List<String> 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); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The exception will be thrown automatically if required is true. |
||
|
||
List<String> 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 | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,5 @@ | ||
package io.quarkus.deployment.pkg.steps; | ||
|
||
import static io.quarkus.deployment.pkg.steps.AppCDSBuildStep.CONTAINER_RUNTIME; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Let's not involved something that has nothing to do with this test. |
||
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) { | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,9 +5,7 @@ | |
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.concurrent.TimeUnit; | ||
import java.util.function.Predicate; | ||
import java.util.stream.Collectors; | ||
|
||
|
@@ -19,18 +17,14 @@ | |
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); | ||
/* | ||
* 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 <argLine>-Djava.io.tmpdir="${project.build.directory}"</argLine>, | ||
* 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() { | ||
} | ||
|
@@ -47,86 +41,85 @@ public static ContainerRuntime detectContainerRuntime() { | |
} | ||
|
||
public static ContainerRuntime detectContainerRuntime(boolean required) { | ||
final ContainerRuntime containerRuntime = loadConfig(); | ||
final ContainerRuntime containerRuntime = loadContainerRuntimeFromSystemProperty(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. just a simple name change for clarity. |
||
if (containerRuntime != null) { | ||
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"); | ||
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."); | ||
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; | ||
} | ||
} | ||
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)) { | ||
storeConfig(ContainerRuntime.PODMAN); | ||
if (dockerVersionOutput.startsWith("podman version")) { | ||
storeContainerRuntimeInSystemProperty(ContainerRuntime.PODMAN); | ||
return ContainerRuntime.PODMAN; | ||
} | ||
storeConfig(ContainerRuntime.DOCKER); | ||
storeContainerRuntimeInSystemProperty(ContainerRuntime.DOCKER); | ||
return ContainerRuntime.DOCKER; | ||
} else if (podmanAvailable) { | ||
storeConfig(ContainerRuntime.PODMAN); | ||
} | ||
podmanVersionOutput = getVersionOutputFor(ContainerRuntime.PODMAN); | ||
podmanAvailable = podmanVersionOutput.startsWith("podman version"); | ||
if (podmanAvailable) { | ||
storeContainerRuntimeInSystemProperty(ContainerRuntime.PODMAN); | ||
return ContainerRuntime.PODMAN; | ||
} 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; | ||
} | ||
} | ||
|
||
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; | ||
} | ||
} | ||
|
||
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); | ||
private static ContainerRuntime loadContainerRuntimeFromSystemProperty() { | ||
final String runtime = System.getProperty(CONTAINER_RUNTIME_SYS_PROP); | ||
|
||
if (runtime == null) { | ||
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); | ||
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 containerRuntime; | ||
} | ||
|
||
private static void storeContainerRuntimeInSystemProperty(ContainerRuntime containerRuntime) { | ||
System.setProperty(CONTAINER_RUNTIME_SYS_PROP, containerRuntime.name()); | ||
} | ||
|
||
private static String getVersionOutputFor(ContainerRuntime containerRuntime) { | ||
|
@@ -135,8 +128,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()); | ||
|
@@ -200,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(); | ||
} | ||
|
||
|
@@ -216,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; | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's avoid doing the autodetection in static fields.