From 36dfaa78129652567e572c2f659482d8832415ea Mon Sep 17 00:00:00 2001 From: Jason Tedor Date: Tue, 11 Dec 2018 12:05:49 -0500 Subject: [PATCH] Add check for minimum required Docker version Our Docker build uses a multi-stage Docker build. This requires Docker version 17.05 or greater. Without an explicit check here, the build fails in a mysterious way such as "invalid reference format" that is hard to track down (Google searches for "Docker invalid reference format" do not turn up anything useful). This commit refactors our existing Docker checks, and adds a new one for the minimum Docker version. --- .../elasticsearch/gradle/BuildPlugin.groovy | 80 ++++++++++++------- 1 file changed, 50 insertions(+), 30 deletions(-) diff --git a/buildSrc/src/main/groovy/org/elasticsearch/gradle/BuildPlugin.groovy b/buildSrc/src/main/groovy/org/elasticsearch/gradle/BuildPlugin.groovy index 2311a6dbbc3dc..5a32586b979ff 100644 --- a/buildSrc/src/main/groovy/org/elasticsearch/gradle/BuildPlugin.groovy +++ b/buildSrc/src/main/groovy/org/elasticsearch/gradle/BuildPlugin.groovy @@ -57,6 +57,7 @@ import org.gradle.util.GradleVersion import java.nio.charset.StandardCharsets import java.time.ZoneOffset import java.time.ZonedDateTime +import java.util.regex.Matcher /** * Encapsulates build configuration for elasticsearch projects. @@ -265,25 +266,6 @@ class BuildPlugin implements Plugin { rootProject.rootProject.ext.buildDocker = buildDocker rootProject.rootProject.ext.requiresDocker = [] rootProject.gradle.taskGraph.whenReady { TaskExecutionGraph taskGraph -> - int exitCode - String dockerErrorOutput - if (dockerBinary == null) { - exitCode = -1 - dockerErrorOutput = null - } else { - // the Docker binary executes, check that we can execute a privileged command - final ByteArrayOutputStream output = new ByteArrayOutputStream() - final ExecResult result = LoggedExec.exec(rootProject, { ExecSpec it -> - it.commandLine dockerBinary, "images" - it.errorOutput = output - it.ignoreExitValue = true - }) - if (result.exitValue == 0) { - return - } - exitCode = result.exitValue - dockerErrorOutput = output.toString() - } final List tasks = ((List)rootProject.requiresDocker).findAll { taskGraph.hasTask(it) }.collect { " ${it.path}".toString()} if (tasks.isEmpty() == false) { @@ -291,30 +273,61 @@ class BuildPlugin implements Plugin { * There are tasks in the task graph that require Docker. Now we are failing because either the Docker binary does not * exist or because execution of a privileged Docker command failed. */ - String message if (dockerBinary == null) { - message = String.format( + final String message = String.format( Locale.ROOT, "Docker (checked [%s]) is required to run the following task%s: \n%s", maybeDockerBinaries.join(","), tasks.size() > 1 ? "s" : "", tasks.join('\n')) - } else { - assert exitCode > 0 && dockerErrorOutput != null - message = String.format( + throwDockerRequiredException(message) + } + + // we use a multi-stage Docker build, check the Docker version since 17.05 + final ByteArrayOutputStream dockerVersionOutput = new ByteArrayOutputStream() + LoggedExec.exec( + rootProject, + { ExecSpec it -> + it.commandLine = [dockerBinary, '--version'] + it.standardOutput = dockerVersionOutput + }) + final String dockerVersion = dockerVersionOutput.toString().trim() + final Matcher matcher = dockerVersion =~ /Docker version (\d+\.\d+)\.\d+(?:-ce)?, build [0-9a-f]{7}/ + assert matcher.matches() : dockerVersion + final dockerMajorMinorVersion = matcher.group(1) + final String[] majorMinor = dockerMajorMinorVersion.split("\\.") + if (Integer.parseInt(majorMinor[0]) < 17 + || (Integer.parseInt(majorMinor[0]) == 17 && Integer.parseInt(majorMinor[1]) < 5)) { + final String message = String.format( + Locale.ROOT, + "building Docker images requires Docker version 17.05+ due to use of multi-stage builds yet was [%s]", + dockerVersion) + throwDockerRequiredException(message) + } + + final ByteArrayOutputStream dockerImagesErrorOutput = new ByteArrayOutputStream() + // the Docker binary executes, check that we can execute a privileged command + final ExecResult dockerImagesResult = LoggedExec.exec( + rootProject, + { ExecSpec it -> + it.commandLine = [dockerBinary, "images"] + it.errorOutput = dockerImagesErrorOutput + it.ignoreExitValue = true + }) + + if (dockerImagesResult.exitValue != 0) { + final String message = String.format( Locale.ROOT, "a problem occurred running Docker from [%s] yet it is required to run the following task%s: \n%s\n" + "the problem is that Docker exited with exit code [%d] with standard error output [%s]", dockerBinary, tasks.size() > 1 ? "s" : "", tasks.join('\n'), - exitCode, - dockerErrorOutput.trim()) + dockerImagesResult.exitValue, + dockerImagesErrorOutput.toString().trim()) + throwDockerRequiredException(message) } - throw new GradleException( - message + "\nyou can address this by attending to the reported issue, " - + "removing the offending tasks from being executed, " - + "or by passing -Dbuild.docker=false") + } } } @@ -325,6 +338,13 @@ class BuildPlugin implements Plugin { } } + private static void throwDockerRequiredException(final String message) { + throw new GradleException( + message + "\nyou can address this by attending to the reported issue, " + + "removing the offending tasks from being executed, " + + "or by passing -Dbuild.docker=false") + } + private static String findCompilerJavaHome() { String compilerJavaHome = System.getenv('JAVA_HOME') final String compilerJavaProperty = System.getProperty('compiler.java')