-
Notifications
You must be signed in to change notification settings - Fork 2.7k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #14635 from jonathan-meier/native_image_build_remo…
…te_docker Containerized native image build on remote docker daemons (issue #1610)
- Loading branch information
Showing
8 changed files
with
424 additions
and
211 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
153 changes: 153 additions & 0 deletions
153
...oyment/src/main/java/io/quarkus/deployment/pkg/steps/NativeImageBuildContainerRunner.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,153 @@ | ||
package io.quarkus.deployment.pkg.steps; | ||
|
||
import static io.quarkus.deployment.pkg.steps.LinuxIDUtil.getLinuxID; | ||
|
||
import java.io.IOException; | ||
import java.nio.charset.StandardCharsets; | ||
import java.nio.file.Path; | ||
import java.util.ArrayList; | ||
import java.util.Arrays; | ||
import java.util.Collections; | ||
import java.util.List; | ||
import java.util.function.Function; | ||
import java.util.stream.Stream; | ||
|
||
import org.apache.commons.lang3.SystemUtils; | ||
import org.jboss.logging.Logger; | ||
|
||
import io.quarkus.deployment.pkg.NativeConfig; | ||
import io.quarkus.deployment.util.FileUtil; | ||
import io.quarkus.deployment.util.ProcessUtil; | ||
|
||
public abstract class NativeImageBuildContainerRunner extends NativeImageBuildRunner { | ||
|
||
private static final Logger log = Logger.getLogger(NativeImageBuildContainerRunner.class); | ||
|
||
private final NativeConfig nativeConfig; | ||
protected final NativeConfig.ContainerRuntime containerRuntime; | ||
private final String[] baseContainerRuntimeArgs; | ||
protected final String outputPath; | ||
|
||
public NativeImageBuildContainerRunner(NativeConfig nativeConfig, Path outputDir) { | ||
this.nativeConfig = nativeConfig; | ||
containerRuntime = nativeConfig.containerRuntime.orElseGet(NativeImageBuildContainerRunner::detectContainerRuntime); | ||
log.infof("Using %s to run the native image builder", containerRuntime.getExecutableName()); | ||
|
||
List<String> containerRuntimeArgs = new ArrayList<>(); | ||
Collections.addAll(containerRuntimeArgs, "--env", "LANG=C"); | ||
|
||
outputPath = outputDir == null ? null : outputDir.toAbsolutePath().toString(); | ||
|
||
if (SystemUtils.IS_OS_LINUX) { | ||
String uid = getLinuxID("-ur"); | ||
String gid = getLinuxID("-gr"); | ||
if (uid != null && gid != null && !uid.isEmpty() && !gid.isEmpty()) { | ||
Collections.addAll(containerRuntimeArgs, "--user", uid + ":" + gid); | ||
if (containerRuntime == NativeConfig.ContainerRuntime.PODMAN) { | ||
// Needed to avoid AccessDeniedExceptions | ||
containerRuntimeArgs.add("--userns=keep-id"); | ||
} | ||
} | ||
} | ||
this.baseContainerRuntimeArgs = containerRuntimeArgs.toArray(new String[0]); | ||
} | ||
|
||
@Override | ||
public void setup(boolean processInheritIODisabled) { | ||
if (containerRuntime == NativeConfig.ContainerRuntime.DOCKER | ||
|| containerRuntime == NativeConfig.ContainerRuntime.PODMAN) { | ||
// we pull the docker image in order to give users an indication of which step the process is at | ||
// it's not strictly necessary we do this, however if we don't the subsequent version command | ||
// will appear to block and no output will be shown | ||
log.info("Checking image status " + nativeConfig.builderImage); | ||
Process pullProcess = null; | ||
try { | ||
final ProcessBuilder pb = new ProcessBuilder( | ||
Arrays.asList(containerRuntime.getExecutableName(), "pull", nativeConfig.builderImage)); | ||
pullProcess = ProcessUtil.launchProcess(pb, processInheritIODisabled); | ||
pullProcess.waitFor(); | ||
} catch (IOException | InterruptedException e) { | ||
throw new RuntimeException("Failed to pull builder image " + nativeConfig.builderImage, e); | ||
} finally { | ||
if (pullProcess != null) { | ||
pullProcess.destroy(); | ||
} | ||
} | ||
} | ||
} | ||
|
||
@Override | ||
protected String[] getGraalVMVersionCommand(List<String> args) { | ||
return buildCommand("run", Collections.singletonList("--rm"), args); | ||
} | ||
|
||
@Override | ||
protected String[] getBuildCommand(List<String> args) { | ||
return buildCommand("run", getContainerRuntimeBuildArgs(), args); | ||
} | ||
|
||
protected List<String> getContainerRuntimeBuildArgs() { | ||
List<String> containerRuntimeArgs = new ArrayList<>(); | ||
nativeConfig.containerRuntimeOptions.ifPresent(containerRuntimeArgs::addAll); | ||
if (nativeConfig.debugBuildProcess && nativeConfig.publishDebugBuildProcessPort) { | ||
// publish the debug port onto the host if asked for | ||
containerRuntimeArgs.add("--publish=" + NativeImageBuildStep.DEBUG_BUILD_PROCESS_PORT + ":" | ||
+ NativeImageBuildStep.DEBUG_BUILD_PROCESS_PORT); | ||
} | ||
return containerRuntimeArgs; | ||
} | ||
|
||
protected String[] buildCommand(String dockerCmd, List<String> containerRuntimeArgs, List<String> command) { | ||
return Stream | ||
.of(Stream.of(containerRuntime.getExecutableName()), Stream.of(dockerCmd), Stream.of(baseContainerRuntimeArgs), | ||
containerRuntimeArgs.stream(), Stream.of(nativeConfig.builderImage), command.stream()) | ||
.flatMap(Function.identity()).toArray(String[]::new); | ||
} | ||
|
||
/** | ||
* @return {@link NativeConfig.ContainerRuntime#DOCKER} if it's available, or {@link NativeConfig.ContainerRuntime#PODMAN} | ||
* if the podman | ||
* executable exists in the environment or if the docker executable is an alias to podman | ||
* @throws IllegalStateException if no container runtime was found to build the image | ||
*/ | ||
private static NativeConfig.ContainerRuntime detectContainerRuntime() { | ||
// Docker version 19.03.14, build 5eb3275d40 | ||
String dockerVersionOutput = getVersionOutputFor(NativeConfig.ContainerRuntime.DOCKER); | ||
boolean dockerAvailable = dockerVersionOutput.contains("Docker version"); | ||
// Check if Podman is installed | ||
// podman version 2.1.1 | ||
String podmanVersionOutput = getVersionOutputFor(NativeConfig.ContainerRuntime.PODMAN); | ||
boolean podmanAvailable = podmanVersionOutput.startsWith("podman version"); | ||
if (dockerAvailable) { | ||
// Check if "docker" is an alias to "podman" | ||
if (dockerVersionOutput.equals(podmanVersionOutput)) { | ||
return NativeConfig.ContainerRuntime.PODMAN; | ||
} | ||
return NativeConfig.ContainerRuntime.DOCKER; | ||
} else if (podmanAvailable) { | ||
return NativeConfig.ContainerRuntime.PODMAN; | ||
} else { | ||
throw new IllegalStateException("No container runtime was found to run the native image builder"); | ||
} | ||
} | ||
|
||
private static String getVersionOutputFor(NativeConfig.ContainerRuntime containerRuntime) { | ||
Process versionProcess = null; | ||
try { | ||
ProcessBuilder pb = new ProcessBuilder(containerRuntime.getExecutableName(), "--version") | ||
.redirectErrorStream(true); | ||
versionProcess = pb.start(); | ||
versionProcess.waitFor(); | ||
return new String(FileUtil.readFileContents(versionProcess.getInputStream()), StandardCharsets.UTF_8); | ||
} 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()); | ||
return ""; | ||
} finally { | ||
if (versionProcess != null) { | ||
versionProcess.destroy(); | ||
} | ||
} | ||
} | ||
|
||
} |
29 changes: 29 additions & 0 deletions
29
...t/src/main/java/io/quarkus/deployment/pkg/steps/NativeImageBuildLocalContainerRunner.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
package io.quarkus.deployment.pkg.steps; | ||
|
||
import java.nio.file.Path; | ||
import java.util.Collections; | ||
import java.util.List; | ||
|
||
import org.apache.commons.lang3.SystemUtils; | ||
|
||
import io.quarkus.deployment.pkg.NativeConfig; | ||
import io.quarkus.deployment.util.FileUtil; | ||
|
||
public class NativeImageBuildLocalContainerRunner extends NativeImageBuildContainerRunner { | ||
|
||
public NativeImageBuildLocalContainerRunner(NativeConfig nativeConfig, Path outputDir) { | ||
super(nativeConfig, outputDir); | ||
} | ||
|
||
@Override | ||
protected List<String> getContainerRuntimeBuildArgs() { | ||
List<String> containerRuntimeArgs = super.getContainerRuntimeBuildArgs(); | ||
String volumeOutputPath = outputPath; | ||
if (SystemUtils.IS_OS_WINDOWS) { | ||
volumeOutputPath = FileUtil.translateToVolumePath(volumeOutputPath); | ||
} | ||
Collections.addAll(containerRuntimeArgs, "--rm", "-v", | ||
volumeOutputPath + ":" + NativeImageBuildStep.CONTAINER_BUILD_VOLUME_PATH + ":z"); | ||
return containerRuntimeArgs; | ||
} | ||
} |
40 changes: 40 additions & 0 deletions
40
...deployment/src/main/java/io/quarkus/deployment/pkg/steps/NativeImageBuildLocalRunner.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
package io.quarkus.deployment.pkg.steps; | ||
|
||
import java.io.File; | ||
import java.io.IOException; | ||
import java.util.List; | ||
import java.util.stream.Stream; | ||
|
||
import io.quarkus.deployment.util.ProcessUtil; | ||
|
||
public class NativeImageBuildLocalRunner extends NativeImageBuildRunner { | ||
|
||
private final String nativeImageExecutable; | ||
|
||
public NativeImageBuildLocalRunner(String nativeImageExecutable) { | ||
this.nativeImageExecutable = nativeImageExecutable; | ||
} | ||
|
||
@Override | ||
public void cleanupServer(File outputDir, boolean processInheritIODisabled) throws InterruptedException, IOException { | ||
final ProcessBuilder pb = new ProcessBuilder(nativeImageExecutable, "--server-shutdown"); | ||
pb.directory(outputDir); | ||
final Process process = ProcessUtil.launchProcess(pb, processInheritIODisabled); | ||
process.waitFor(); | ||
} | ||
|
||
@Override | ||
protected String[] getGraalVMVersionCommand(List<String> args) { | ||
return buildCommand(args); | ||
} | ||
|
||
@Override | ||
protected String[] getBuildCommand(List<String> args) { | ||
return buildCommand(args); | ||
} | ||
|
||
private String[] buildCommand(List<String> args) { | ||
return Stream.concat(Stream.of(nativeImageExecutable), args.stream()).toArray(String[]::new); | ||
} | ||
|
||
} |
53 changes: 53 additions & 0 deletions
53
.../src/main/java/io/quarkus/deployment/pkg/steps/NativeImageBuildRemoteContainerRunner.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
package io.quarkus.deployment.pkg.steps; | ||
|
||
import java.io.BufferedReader; | ||
import java.io.IOException; | ||
import java.io.InputStreamReader; | ||
import java.nio.file.Path; | ||
import java.util.List; | ||
|
||
import io.quarkus.deployment.pkg.NativeConfig; | ||
|
||
public class NativeImageBuildRemoteContainerRunner extends NativeImageBuildContainerRunner { | ||
|
||
private final String nativeImageName; | ||
private String containerId; | ||
|
||
public NativeImageBuildRemoteContainerRunner(NativeConfig nativeConfig, Path outputDir, String nativeImageName) { | ||
super(nativeConfig, outputDir); | ||
this.nativeImageName = nativeImageName; | ||
} | ||
|
||
@Override | ||
protected void preBuild(List<String> buildArgs) throws InterruptedException, IOException { | ||
List<String> containerRuntimeArgs = getContainerRuntimeBuildArgs(); | ||
String[] createContainerCommand = buildCommand("create", containerRuntimeArgs, buildArgs); | ||
Process createContainerProcess = new ProcessBuilder(createContainerCommand).start(); | ||
createContainerProcess.waitFor(); | ||
try (BufferedReader reader = new BufferedReader(new InputStreamReader(createContainerProcess.getInputStream()))) { | ||
containerId = reader.readLine(); | ||
} | ||
String[] copyCommand = new String[] { containerRuntime.getExecutableName(), "cp", outputPath + "/.", | ||
containerId + ":" + NativeImageBuildStep.CONTAINER_BUILD_VOLUME_PATH }; | ||
Process copyProcess = new ProcessBuilder(copyCommand).start(); | ||
copyProcess.waitFor(); | ||
super.preBuild(buildArgs); | ||
} | ||
|
||
@Override | ||
protected String[] getBuildCommand(List<String> args) { | ||
return new String[] { containerRuntime.getExecutableName(), "start", "--attach", containerId }; | ||
} | ||
|
||
@Override | ||
protected void postBuild() throws InterruptedException, IOException { | ||
String[] copyCommand = new String[] { containerRuntime.getExecutableName(), "cp", | ||
containerId + ":" + NativeImageBuildStep.CONTAINER_BUILD_VOLUME_PATH + "/" + nativeImageName, outputPath }; | ||
Process copyProcess = new ProcessBuilder(copyCommand).start(); | ||
copyProcess.waitFor(); | ||
String[] removeCommand = new String[] { containerRuntime.getExecutableName(), "container", "rm", "--volumes", | ||
containerId }; | ||
Process removeProcess = new ProcessBuilder(removeCommand).start(); | ||
removeProcess.waitFor(); | ||
} | ||
} |
73 changes: 73 additions & 0 deletions
73
core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/NativeImageBuildRunner.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
package io.quarkus.deployment.pkg.steps; | ||
|
||
import java.io.BufferedReader; | ||
import java.io.File; | ||
import java.io.IOException; | ||
import java.io.InputStreamReader; | ||
import java.nio.charset.StandardCharsets; | ||
import java.nio.file.Path; | ||
import java.util.Collections; | ||
import java.util.List; | ||
import java.util.concurrent.CountDownLatch; | ||
import java.util.concurrent.ExecutorService; | ||
import java.util.concurrent.Executors; | ||
|
||
import io.quarkus.deployment.pkg.steps.NativeImageBuildStep.GraalVM; | ||
import io.quarkus.deployment.util.ProcessUtil; | ||
|
||
public abstract class NativeImageBuildRunner { | ||
|
||
public GraalVM.Version getGraalVMVersion() { | ||
final GraalVM.Version graalVMVersion; | ||
try { | ||
String[] versionCommand = getGraalVMVersionCommand(Collections.singletonList("--version")); | ||
Process versionProcess = new ProcessBuilder(versionCommand) | ||
.redirectErrorStream(true) | ||
.start(); | ||
versionProcess.waitFor(); | ||
try (BufferedReader reader = new BufferedReader( | ||
new InputStreamReader(versionProcess.getInputStream(), StandardCharsets.UTF_8))) { | ||
graalVMVersion = GraalVM.Version.of(reader.lines()); | ||
} | ||
} catch (Exception e) { | ||
throw new RuntimeException("Failed to get GraalVM version", e); | ||
} | ||
return graalVMVersion; | ||
} | ||
|
||
public void setup(boolean processInheritIODisabled) { | ||
} | ||
|
||
public void cleanupServer(File outputDir, boolean processInheritIODisabled) throws InterruptedException, IOException { | ||
} | ||
|
||
public int build(List<String> args, Path outputDir, boolean processInheritIODisabled) | ||
throws InterruptedException, IOException { | ||
preBuild(args); | ||
try { | ||
CountDownLatch errorReportLatch = new CountDownLatch(1); | ||
final ProcessBuilder processBuilder = new ProcessBuilder(getBuildCommand(args)) | ||
.directory(outputDir.toFile()); | ||
final Process process = ProcessUtil.launchProcessStreamStdOut(processBuilder, processInheritIODisabled); | ||
ExecutorService executor = Executors.newSingleThreadExecutor(); | ||
executor.submit(new ErrorReplacingProcessReader(process.getErrorStream(), outputDir.resolve("reports").toFile(), | ||
errorReportLatch)); | ||
executor.shutdown(); | ||
errorReportLatch.await(); | ||
return process.waitFor(); | ||
} finally { | ||
postBuild(); | ||
} | ||
} | ||
|
||
protected abstract String[] getGraalVMVersionCommand(List<String> args); | ||
|
||
protected abstract String[] getBuildCommand(List<String> args); | ||
|
||
protected void preBuild(List<String> buildArgs) throws IOException, InterruptedException { | ||
} | ||
|
||
protected void postBuild() throws InterruptedException, IOException { | ||
} | ||
|
||
} |
Oops, something went wrong.