|
@@ -14,12 +14,10 @@ import org.elasticsearch.gradle.internal.info.BuildParams;
|
|
|
import org.gradle.api.GradleException;
|
|
|
import org.gradle.api.logging.Logger;
|
|
|
import org.gradle.api.logging.Logging;
|
|
|
+import org.gradle.api.provider.ProviderFactory;
|
|
|
import org.gradle.api.services.BuildService;
|
|
|
import org.gradle.api.services.BuildServiceParameters;
|
|
|
-import org.gradle.process.ExecOperations;
|
|
|
-import org.gradle.process.ExecResult;
|
|
|
|
|
|
-import java.io.ByteArrayOutputStream;
|
|
|
import java.io.File;
|
|
|
import java.io.IOException;
|
|
|
import java.nio.file.Files;
|
|
@@ -56,12 +54,12 @@ public abstract class DockerSupportService implements BuildService<DockerSupport
|
|
|
"/usr/libexec/docker/cli-plugins/docker-compose" };
|
|
|
private static final Version MINIMUM_DOCKER_VERSION = Version.fromString("17.05.0");
|
|
|
|
|
|
- private final ExecOperations execOperations;
|
|
|
+ private final ProviderFactory providerFactory;
|
|
|
private DockerAvailability dockerAvailability;
|
|
|
|
|
|
@Inject
|
|
|
- public DockerSupportService(ExecOperations execOperations) {
|
|
|
- this.execOperations = execOperations;
|
|
|
+ public DockerSupportService(ProviderFactory providerFactory) {
|
|
|
+ this.providerFactory = providerFactory;
|
|
|
}
|
|
|
|
|
|
/**
|
|
@@ -71,9 +69,9 @@ public abstract class DockerSupportService implements BuildService<DockerSupport
|
|
|
*/
|
|
|
public DockerAvailability getDockerAvailability() {
|
|
|
if (this.dockerAvailability == null) {
|
|
|
- String dockerPath = null;
|
|
|
+ String dockerPath;
|
|
|
String dockerComposePath = null;
|
|
|
- Result lastResult = null;
|
|
|
+ DockerResult lastResult = null;
|
|
|
Version version = null;
|
|
|
boolean isVersionHighEnough = false;
|
|
|
boolean isComposeAvailable = false;
|
|
@@ -86,21 +84,17 @@ public abstract class DockerSupportService implements BuildService<DockerSupport
|
|
|
|
|
|
// Since we use a multi-stage Docker build, check the Docker version meets minimum requirement
|
|
|
lastResult = runCommand(dockerPath, "version", "--format", "{{.Server.Version}}");
|
|
|
-
|
|
|
- var lastResultOutput = lastResult.stdout.trim();
|
|
|
+ var lastResultOutput = lastResult.getStdout().trim();
|
|
|
// docker returns 0/success if the daemon is not running, so we need to check the
|
|
|
// output before continuing
|
|
|
if (lastResult.isSuccess() && dockerDaemonIsRunning(lastResultOutput)) {
|
|
|
-
|
|
|
version = Version.fromString(lastResultOutput, Version.Mode.RELAXED);
|
|
|
-
|
|
|
isVersionHighEnough = version.onOrAfter(MINIMUM_DOCKER_VERSION);
|
|
|
|
|
|
if (isVersionHighEnough) {
|
|
|
// Check that we can execute a privileged command
|
|
|
- lastResult = runCommand(dockerPath, "images");
|
|
|
+ lastResult = runCommand(Arrays.asList(dockerPath, "images"), input -> "");
|
|
|
|
|
|
- // If docker all checks out, see if docker-compose is available and working
|
|
|
Optional<String> composePath = getDockerComposePath();
|
|
|
if (lastResult.isSuccess() && composePath.isPresent()) {
|
|
|
isComposeAvailable = runCommand(composePath.get(), "version").isSuccess();
|
|
@@ -109,9 +103,12 @@ public abstract class DockerSupportService implements BuildService<DockerSupport
|
|
|
|
|
|
// Now let's check if buildx is available and what supported platforms exist
|
|
|
if (lastResult.isSuccess()) {
|
|
|
- Result buildxResult = runCommand(dockerPath, "buildx", "inspect", "--bootstrap");
|
|
|
+ DockerResult buildxResult = runCommand(
|
|
|
+ Arrays.asList(dockerPath, "buildx", "inspect", "--bootstrap"),
|
|
|
+ input -> input.lines().filter(l -> l.startsWith("Platforms:")).collect(Collectors.joining("\n"))
|
|
|
+ );
|
|
|
if (buildxResult.isSuccess()) {
|
|
|
- supportedArchitectures = buildxResult.stdout()
|
|
|
+ supportedArchitectures = buildxResult.getStdout()
|
|
|
.lines()
|
|
|
.filter(l -> l.startsWith("Platforms:"))
|
|
|
.map(l -> l.substring(10))
|
|
@@ -127,6 +124,8 @@ public abstract class DockerSupportService implements BuildService<DockerSupport
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
+ } else {
|
|
|
+ dockerPath = null;
|
|
|
}
|
|
|
|
|
|
boolean isAvailable = isVersionHighEnough && lastResult != null && lastResult.isSuccess();
|
|
@@ -146,6 +145,17 @@ public abstract class DockerSupportService implements BuildService<DockerSupport
|
|
|
return this.dockerAvailability;
|
|
|
}
|
|
|
|
|
|
+ private DockerResult runCommand(List args, DockerValueSource.OutputFilter outputFilter) {
|
|
|
+ return providerFactory.of(DockerValueSource.class, params -> {
|
|
|
+ params.getParameters().getArgs().addAll(args);
|
|
|
+ params.getParameters().getOutputFilter().set(outputFilter);
|
|
|
+ }).get();
|
|
|
+ }
|
|
|
+
|
|
|
+ private DockerResult runCommand(String... args) {
|
|
|
+ return runCommand(Arrays.asList(args), input -> input);
|
|
|
+ }
|
|
|
+
|
|
|
private boolean dockerDaemonIsRunning(String lastResultOutput) {
|
|
|
return lastResultOutput.contains("Cannot connect to the Docker daemon") == false;
|
|
|
}
|
|
@@ -198,8 +208,8 @@ public abstract class DockerSupportService implements BuildService<DockerSupport
|
|
|
availability.version == null ? "" : " v" + availability.version,
|
|
|
tasks.size() > 1 ? "s" : "",
|
|
|
String.join("\n", tasks),
|
|
|
- availability.lastCommand.exitCode,
|
|
|
- availability.lastCommand.stderr.trim()
|
|
|
+ availability.lastCommand.getExitCode(),
|
|
|
+ availability.lastCommand.getStderr().trim()
|
|
|
);
|
|
|
throwDockerRequiredException(message);
|
|
|
}
|
|
@@ -319,32 +329,6 @@ public abstract class DockerSupportService implements BuildService<DockerSupport
|
|
|
);
|
|
|
}
|
|
|
|
|
|
- /**
|
|
|
- * Runs a command and captures the exit code, standard output and standard error.
|
|
|
- *
|
|
|
- * @param args the command and any arguments to execute
|
|
|
- * @return a object that captures the result of running the command. If an exception occurring
|
|
|
- * while running the command, or the process was killed after reaching the 10s timeout,
|
|
|
- * then the exit code will be -1.
|
|
|
- */
|
|
|
- private Result runCommand(String... args) {
|
|
|
- if (args.length == 0) {
|
|
|
- throw new IllegalArgumentException("Cannot execute with no command");
|
|
|
- }
|
|
|
-
|
|
|
- ByteArrayOutputStream stdout = new ByteArrayOutputStream();
|
|
|
- ByteArrayOutputStream stderr = new ByteArrayOutputStream();
|
|
|
-
|
|
|
- final ExecResult execResult = execOperations.exec(spec -> {
|
|
|
- // The redundant cast is to silence a compiler warning.
|
|
|
- spec.setCommandLine((Object[]) args);
|
|
|
- spec.setStandardOutput(stdout);
|
|
|
- spec.setErrorOutput(stderr);
|
|
|
- spec.setIgnoreExitValue(true);
|
|
|
- });
|
|
|
- return new Result(execResult.getExitValue(), stdout.toString(), stderr.toString());
|
|
|
- }
|
|
|
-
|
|
|
/**
|
|
|
* An immutable class that represents the results of a Docker search from {@link #getDockerAvailability()}}.
|
|
|
*/
|
|
@@ -377,22 +361,12 @@ public abstract class DockerSupportService implements BuildService<DockerSupport
|
|
|
Version version,
|
|
|
|
|
|
// Information about the last command executes while probing Docker, or null.
|
|
|
- Result lastCommand,
|
|
|
+ DockerResult lastCommand,
|
|
|
|
|
|
// Supported build architectures
|
|
|
Set<Architecture> supportedArchitectures
|
|
|
) {}
|
|
|
|
|
|
- /**
|
|
|
- * This class models the result of running a command. It captures the exit code, standard output and standard error.
|
|
|
- */
|
|
|
- private record Result(int exitCode, String stdout, String stderr) {
|
|
|
-
|
|
|
- boolean isSuccess() {
|
|
|
- return exitCode == 0;
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
interface Parameters extends BuildServiceParameters {
|
|
|
File getExclusionsFile();
|
|
|
|