Skip to content

Commit aed35c3

Browse files
committed
Enable Podman for Windows in-container tests
Enables enforcing podman on systems where both podman and docker are available
1 parent d1c9c14 commit aed35c3

File tree

9 files changed

+64
-53
lines changed

9 files changed

+64
-53
lines changed

core/deployment/src/main/java/io/quarkus/deployment/IsDockerWorking.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -175,7 +175,7 @@ private static class DockerBinaryStrategy implements Strategy {
175175

176176
private DockerBinaryStrategy(boolean silent) {
177177
this.silent = silent;
178-
this.binary = ConfigProvider.getConfig().getOptionalValue("quarkus.docker.executable-name", String.class)
178+
this.binary = ConfigProvider.getConfig().getOptionalValue("quarkus.native.container-runtime", String.class)
179179
.orElse("docker");
180180
}
181181

@@ -194,7 +194,7 @@ public Result get() {
194194
try {
195195
OutputFilter filter = new OutputFilter();
196196
if (ExecUtil.execWithTimeout(new File("."), filter, Duration.ofMillis(DOCKER_CMD_CHECK_TIMEOUT),
197-
"docker", "version", "--format", "'{{.Server.Version}}'")) {
197+
binary, "version", "--format", "'{{.Server.Version}}'")) {
198198
LOGGER.debugf("Docker daemon found. Version: %s", filter.getOutput());
199199
return Result.AVAILABLE;
200200
} else {

core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/AppCDSBuildStep.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
import java.util.function.BooleanSupplier;
1414

1515
import org.apache.commons.lang3.SystemUtils;
16+
import org.eclipse.microprofile.config.ConfigProvider;
1617
import org.jboss.logging.Logger;
1718

1819
import io.quarkus.bootstrap.util.IoUtils;
@@ -37,6 +38,8 @@ public class AppCDSBuildStep {
3738
public static final String CLASSES_LIST_FILE_NAME = "classes.lst";
3839
private static final String CONTAINER_IMAGE_BASE_BUILD_DIR = "/tmp/quarkus";
3940
private static final String CONTAINER_IMAGE_APPCDS_DIR = CONTAINER_IMAGE_BASE_BUILD_DIR + "/appcds";
41+
public static final String DOCKER_EXECUTABLE = ConfigProvider.getConfig()
42+
.getOptionalValue("quarkus.native.container-runtime", String.class).orElse("docker");
4043

4144
@BuildStep(onlyIf = AppCDSRequired.class)
4245
public void requested(OutputTargetBuildItem outputTarget, BuildProducer<AppCDSRequestedBuildItem> producer)
@@ -202,7 +205,7 @@ private Path createClassesList(JarBuildItem jarResult,
202205
private List<String> dockerRunCommands(OutputTargetBuildItem outputTarget, String containerImage,
203206
String containerWorkingDir) {
204207
List<String> command = new ArrayList<>(10);
205-
command.add("docker");
208+
command.add(DOCKER_EXECUTABLE);
206209
command.add("run");
207210
command.add("-v");
208211
command.add(outputTarget.getOutputDirectory().toAbsolutePath().toString() + ":" + CONTAINER_IMAGE_BASE_BUILD_DIR

core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/NativeImageBuildLocalContainerRunner.java

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,16 +9,13 @@
99
import java.util.List;
1010

1111
import org.apache.commons.lang3.SystemUtils;
12-
import org.jboss.logging.Logger;
1312

1413
import io.quarkus.deployment.pkg.NativeConfig;
1514
import io.quarkus.deployment.util.FileUtil;
1615
import io.quarkus.runtime.util.ContainerRuntimeUtil;
1716

1817
public class NativeImageBuildLocalContainerRunner extends NativeImageBuildContainerRunner {
1918

20-
private static final Logger LOGGER = Logger.getLogger(NativeImageBuildLocalContainerRunner.class.getName());
21-
2219
public NativeImageBuildLocalContainerRunner(NativeConfig nativeConfig, Path outputDir) {
2320
super(nativeConfig, outputDir);
2421
if (SystemUtils.IS_OS_LINUX) {

core/deployment/src/test/java/io/quarkus/deployment/pkg/steps/NativeImageBuildContainerRunnerTest.java

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package io.quarkus.deployment.pkg.steps;
22

3+
import static io.quarkus.deployment.pkg.steps.AppCDSBuildStep.DOCKER_EXECUTABLE;
34
import static org.assertj.core.api.Assertions.assertThat;
45

56
import java.nio.file.Path;
@@ -25,33 +26,36 @@ void testBuilderImageBeingPickedUp() {
2526

2627
nativeConfig.builderImage = "graalvm";
2728
localRunner = new NativeImageBuildLocalContainerRunner(nativeConfig, Path.of("/tmp"));
28-
command = localRunner.buildCommand("docker", Collections.emptyList(), Collections.emptyList());
29+
command = localRunner.buildCommand(DOCKER_EXECUTABLE, Collections.emptyList(), Collections.emptyList());
2930
found = false;
3031
for (String part : command) {
3132
if (part.contains("ubi-quarkus-graalvmce-builder-image")) {
3233
found = true;
34+
break;
3335
}
3436
}
3537
assertThat(found).isTrue();
3638

3739
nativeConfig.builderImage = "mandrel";
3840
localRunner = new NativeImageBuildLocalContainerRunner(nativeConfig, Path.of("/tmp"));
39-
command = localRunner.buildCommand("docker", Collections.emptyList(), Collections.emptyList());
41+
command = localRunner.buildCommand(DOCKER_EXECUTABLE, Collections.emptyList(), Collections.emptyList());
4042
found = false;
4143
for (String part : command) {
4244
if (part.contains("ubi-quarkus-mandrel-builder-image")) {
4345
found = true;
46+
break;
4447
}
4548
}
4649
assertThat(found).isTrue();
4750

4851
nativeConfig.builderImage = "RandomString";
4952
localRunner = new NativeImageBuildLocalContainerRunner(nativeConfig, Path.of("/tmp"));
50-
command = localRunner.buildCommand("docker", Collections.emptyList(), Collections.emptyList());
53+
command = localRunner.buildCommand(DOCKER_EXECUTABLE, Collections.emptyList(), Collections.emptyList());
5154
found = false;
5255
for (String part : command) {
5356
if (part.equals("RandomString")) {
5457
found = true;
58+
break;
5559
}
5660
}
5761
assertThat(found).isTrue();

core/runtime/src/main/java/io/quarkus/runtime/util/ContainerRuntimeUtil.java

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import java.nio.charset.StandardCharsets;
88
import java.util.function.Predicate;
99

10+
import org.eclipse.microprofile.config.ConfigProvider;
1011
import org.jboss.logging.Logger;
1112

1213
public final class ContainerRuntimeUtil {
@@ -30,6 +31,20 @@ public static ContainerRuntime detectContainerRuntime() {
3031
// podman version 2.1.1
3132
String podmanVersionOutput = getVersionOutputFor(ContainerRuntime.PODMAN);
3233
boolean podmanAvailable = podmanVersionOutput.startsWith("podman version");
34+
35+
final String executable = ConfigProvider.getConfig()
36+
.getOptionalValue("quarkus.native.container-runtime", String.class).orElse(null);
37+
if (executable != null) {
38+
if (executable.trim().equalsIgnoreCase("docker") && dockerAvailable) {
39+
return ContainerRuntime.DOCKER;
40+
} else if (executable.trim().equalsIgnoreCase("podman") && podmanAvailable) {
41+
return ContainerRuntime.PODMAN;
42+
} else {
43+
log.warn("quarkus.native.container-runtime config property must be set to either podman or docker " +
44+
"and the executable must be available. Ignoring it.");
45+
}
46+
}
47+
3348
if (dockerAvailable) {
3449
// Check if "docker" is an alias to "podman"
3550
if (dockerVersionOutput.equals(podmanVersionOutput)) {

test-framework/common/src/main/java/io/quarkus/test/common/DefaultDockerContainerLauncher.java

Lines changed: 31 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,12 @@
55
import static io.quarkus.test.common.LauncherUtil.waitForCapturedListeningData;
66
import static io.quarkus.test.common.LauncherUtil.waitForStartedFunction;
77
import static java.lang.ProcessBuilder.Redirect.DISCARD;
8-
import static java.lang.ProcessBuilder.Redirect.PIPE;
98

10-
import java.io.FileOutputStream;
119
import java.io.IOException;
12-
import java.io.InputStream;
1310
import java.net.ServerSocket;
11+
import java.nio.file.FileSystemException;
1412
import java.nio.file.Files;
1513
import java.nio.file.Path;
16-
import java.nio.file.Paths;
1714
import java.util.ArrayList;
1815
import java.util.Collections;
1916
import java.util.HashMap;
@@ -24,15 +21,17 @@
2421
import java.util.concurrent.TimeUnit;
2522
import java.util.function.Function;
2623

27-
import org.apache.commons.io.input.TeeInputStream;
2824
import org.apache.commons.lang3.RandomStringUtils;
25+
import org.jboss.logging.Logger;
2926

3027
import io.quarkus.runtime.util.ContainerRuntimeUtil;
3128
import io.quarkus.test.common.http.TestHTTPResourceManager;
3229
import io.smallrye.config.common.utils.StringUtil;
3330

3431
public class DefaultDockerContainerLauncher implements DockerContainerArtifactLauncher {
3532

33+
private static final Logger log = Logger.getLogger(DefaultDockerContainerLauncher.class);
34+
3635
private int httpPort;
3736
private int httpsPort;
3837
private long waitTimeSeconds;
@@ -42,16 +41,11 @@ public class DefaultDockerContainerLauncher implements DockerContainerArtifactLa
4241
private String containerImage;
4342
private boolean pullRequired;
4443
private Map<Integer, Integer> additionalExposedPorts;
45-
4644
private final Map<String, String> systemProps = new HashMap<>();
47-
4845
private boolean isSsl;
49-
50-
private String containerName;
51-
46+
private final String containerName = "quarkus-integration-test-" + RandomStringUtils.random(5, true, false);
5247
private String containerRuntimeBinaryName;
53-
54-
private ExecutorService executorService = Executors.newSingleThreadExecutor();
48+
private final ExecutorService executorService = Executors.newSingleThreadExecutor();
5549

5650
@Override
5751
public void init(DockerContainerArtifactLauncher.DockerInitContext initContext) {
@@ -77,7 +71,7 @@ public void start() throws IOException {
7771
containerRuntimeBinaryName = determineBinary();
7872

7973
if (pullRequired) {
80-
System.out.println("Pulling container image '" + containerImage + "'");
74+
log.infof("Pulling container image '%s'", containerImage);
8175
try {
8276
int pullResult = new ProcessBuilder().redirectError(DISCARD).redirectOutput(DISCARD)
8377
.command(containerRuntimeBinaryName, "pull", containerImage).start().waitFor();
@@ -98,21 +92,21 @@ public void start() throws IOException {
9892
httpsPort = getRandomPort();
9993
}
10094

101-
List<String> args = new ArrayList<>();
95+
final List<String> args = new ArrayList<>();
10296
args.add(containerRuntimeBinaryName);
10397
args.add("run");
10498
if (!argLine.isEmpty()) {
10599
args.addAll(argLine);
106100
}
107101
args.add("--name");
108-
containerName = "quarkus-integration-test-" + RandomStringUtils.random(5, true, false);
109102
args.add(containerName);
103+
args.add("-i"); // Interactive, write logs to stdout
110104
args.add("--rm");
111105
args.add("-p");
112106
args.add(httpPort + ":" + httpPort);
113107
args.add("-p");
114108
args.add(httpsPort + ":" + httpsPort);
115-
for (var entry : additionalExposedPorts.entrySet()) {
109+
for (Map.Entry<Integer, Integer> entry : additionalExposedPorts.entrySet()) {
116110
args.add("-p");
117111
args.add(entry.getKey() + ":" + entry.getValue());
118112
}
@@ -125,7 +119,7 @@ public void start() throws IOException {
125119
if (DefaultJarLauncher.HTTP_PRESENT) {
126120
args.addAll(toEnvVar("quarkus.http.port", "" + httpPort));
127121
args.addAll(toEnvVar("quarkus.http.ssl-port", "" + httpsPort));
128-
// this won't be correct when using the random port but it's really only used by us for the rest client tests
122+
// This won't be correct when using the random port, but it's really only used by us for the rest client tests
129123
// in the main module, since those tests hit the application itself
130124
args.addAll(toEnvVar("test.url", TestHTTPResourceManager.getUri()));
131125
}
@@ -138,31 +132,31 @@ public void start() throws IOException {
138132
}
139133
args.add(containerImage);
140134

141-
Path logFile = PropertyTestUtil.getLogFilePath();
142-
Files.deleteIfExists(logFile);
143-
Files.createDirectories(logFile.getParent());
144-
145-
Path containerLogFile = Paths.get("target", "container.log");
146-
Files.createDirectories(containerLogFile.getParent());
147-
FileOutputStream containerLogOutputStream = new FileOutputStream(containerLogFile.toFile(), true);
135+
final Path logFile = PropertyTestUtil.getLogFilePath();
136+
try {
137+
Files.deleteIfExists(logFile);
138+
Files.createDirectories(logFile.getParent());
139+
} catch (FileSystemException e) {
140+
log.warnf("Log file %s deletion failed, could happen on Windows, we can carry on.", logFile);
141+
}
148142

149-
System.out.println("Executing \"" + String.join(" ", args) + "\"");
143+
log.infof("Executing \"%s\"", String.join(" ", args));
150144

151-
Function<IntegrationTestStartedNotifier.Context, IntegrationTestStartedNotifier.Result> startedFunction = createStartedFunction();
145+
final Function<IntegrationTestStartedNotifier.Context, IntegrationTestStartedNotifier.Result> startedFunction = createStartedFunction();
152146

153-
// the idea here is to obtain the logs of the application simply by redirecting all its output the a file
154-
// this is done in contrast with the JarLauncher and NativeImageLauncher because in the case of the container
155-
// the log itself is written inside the container
156-
Process quarkusProcess = new ProcessBuilder(args).redirectError(PIPE).redirectOutput(PIPE).start();
157-
InputStream tee = new TeeInputStream(quarkusProcess.getInputStream(), new FileOutputStream(logFile.toFile()));
158-
executorService.submit(() -> tee.transferTo(containerLogOutputStream));
147+
// We rely on the container writing log to stdout. If it just writes to a logfile inside itself, we would have
148+
// to mount /work/ directory to get quarkus.log.
149+
final Process containerProcess = new ProcessBuilder(args)
150+
.redirectErrorStream(true)
151+
.redirectOutput(ProcessBuilder.Redirect.appendTo(logFile.toFile()))
152+
.start();
159153

160154
if (startedFunction != null) {
161-
IntegrationTestStartedNotifier.Result result = waitForStartedFunction(startedFunction, quarkusProcess,
155+
final IntegrationTestStartedNotifier.Result result = waitForStartedFunction(startedFunction, containerProcess,
162156
waitTimeSeconds, logFile);
163157
isSsl = result.isSsl();
164158
} else {
165-
ListeningAddress result = waitForCapturedListeningData(quarkusProcess, logFile, waitTimeSeconds);
159+
final ListeningAddress result = waitForCapturedListeningData(containerProcess, logFile, waitTimeSeconds);
166160
updateConfigForPort(result.getPort());
167161
isSsl = result.isSsl();
168162
}
@@ -188,10 +182,7 @@ public void includeAsSysProps(Map<String, String> systemProps) {
188182

189183
private List<String> toEnvVar(String property, String value) {
190184
if ((property != null) && (!property.isEmpty())) {
191-
List<String> result = new ArrayList<>(2);
192-
result.add("--env");
193-
result.add(String.format("%s=%s", convertPropertyToEnvVar(property), value));
194-
return result;
185+
return List.of("--env", String.format("%s=%s", convertPropertyToEnvVar(property), value));
195186
}
196187
return Collections.emptyList();
197188
}
@@ -203,14 +194,13 @@ private String convertPropertyToEnvVar(String property) {
203194
@Override
204195
public void close() {
205196
try {
206-
Process dockerStopProcess = new ProcessBuilder(containerRuntimeBinaryName, "stop", containerName)
197+
final Process dockerStopProcess = new ProcessBuilder(containerRuntimeBinaryName, "stop", containerName)
207198
.redirectError(DISCARD)
208199
.redirectOutput(DISCARD).start();
209200
dockerStopProcess.waitFor(10, TimeUnit.SECONDS);
210201
} catch (IOException | InterruptedException e) {
211-
System.out.println("Unable to stop container '" + containerName + "'");
202+
log.errorf("Unable to stop container '%s'", containerName);
212203
}
213204
executorService.shutdown();
214205
}
215-
216206
}

test-framework/common/src/main/java/io/quarkus/test/common/DefaultNativeImageLauncher.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,7 @@ public void start(String[] programArgs, boolean handleIo) throws IOException {
130130
args.add("-Dtest.url=" + TestHTTPResourceManager.getUri());
131131
}
132132
Path logFile = PropertyTestUtil.getLogFilePath();
133-
args.add("-Dquarkus.log.file.path=" + logFile.toAbsolutePath().toString());
133+
args.add("-Dquarkus.log.file.path=" + logFile.toAbsolutePath());
134134
args.add("-Dquarkus.log.file.enable=true");
135135
args.add("-Dquarkus.log.category.\"io.quarkus\".level=INFO");
136136
if (testProfile != null) {

test-framework/common/src/main/java/io/quarkus/test/common/LauncherUtil.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -253,7 +253,7 @@ public void run() {
253253

254254
long now = System.currentTimeMillis();
255255
// if we have seen info that the app is started in the log a while ago
256-
// or waiting the the next check interval will exceed the bailout time, it's time to finish waiting:
256+
// or waiting the next check interval will exceed the bailout time, it's time to finish waiting:
257257
if (now + LOG_CHECK_INTERVAL > bailoutTime || now - 2 * LOG_CHECK_INTERVAL > timeStarted) {
258258
if (started) {
259259
dataDetermined(null, null); // no http, all is null

test-framework/junit5/src/main/java/io/quarkus/test/junit/IntegrationTestUtil.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434

3535
import org.apache.commons.lang3.RandomStringUtils;
3636
import org.eclipse.microprofile.config.Config;
37+
import org.eclipse.microprofile.config.ConfigProvider;
3738
import org.eclipse.microprofile.config.inject.ConfigProperty;
3839
import org.jboss.jandex.Index;
3940
import org.junit.jupiter.api.extension.ExtensionContext;
@@ -64,7 +65,8 @@ public final class IntegrationTestUtil {
6465
public static final int DEFAULT_HTTPS_PORT = 8444;
6566
public static final long DEFAULT_WAIT_TIME_SECONDS = 60;
6667

67-
private static final String DOCKER_BINARY = "docker";
68+
private static final String DOCKER_BINARY = ConfigProvider.getConfig()
69+
.getOptionalValue("quarkus.native.container-runtime", String.class).orElse("docker");
6870

6971
private IntegrationTestUtil() {
7072
}

0 commit comments

Comments
 (0)