Skip to content

Commit f2e18a6

Browse files
authored
Add check for minimum required Docker version (#36497)
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.
1 parent dafea3c commit f2e18a6

File tree

1 file changed

+50
-30
lines changed

1 file changed

+50
-30
lines changed

buildSrc/src/main/groovy/org/elasticsearch/gradle/BuildPlugin.groovy

+50-30
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ import org.gradle.util.GradleVersion
5757
import java.nio.charset.StandardCharsets
5858
import java.time.ZoneOffset
5959
import java.time.ZonedDateTime
60+
import java.util.regex.Matcher
6061

6162
/**
6263
* Encapsulates build configuration for elasticsearch projects.
@@ -265,56 +266,68 @@ class BuildPlugin implements Plugin<Project> {
265266
rootProject.rootProject.ext.buildDocker = buildDocker
266267
rootProject.rootProject.ext.requiresDocker = []
267268
rootProject.gradle.taskGraph.whenReady { TaskExecutionGraph taskGraph ->
268-
int exitCode
269-
String dockerErrorOutput
270-
if (dockerBinary == null) {
271-
exitCode = -1
272-
dockerErrorOutput = null
273-
} else {
274-
// the Docker binary executes, check that we can execute a privileged command
275-
final ByteArrayOutputStream output = new ByteArrayOutputStream()
276-
final ExecResult result = LoggedExec.exec(rootProject, { ExecSpec it ->
277-
it.commandLine dockerBinary, "images"
278-
it.errorOutput = output
279-
it.ignoreExitValue = true
280-
})
281-
if (result.exitValue == 0) {
282-
return
283-
}
284-
exitCode = result.exitValue
285-
dockerErrorOutput = output.toString()
286-
}
287269
final List<String> tasks =
288270
((List<Task>)rootProject.requiresDocker).findAll { taskGraph.hasTask(it) }.collect { " ${it.path}".toString()}
289271
if (tasks.isEmpty() == false) {
290272
/*
291273
* There are tasks in the task graph that require Docker. Now we are failing because either the Docker binary does not
292274
* exist or because execution of a privileged Docker command failed.
293275
*/
294-
String message
295276
if (dockerBinary == null) {
296-
message = String.format(
277+
final String message = String.format(
297278
Locale.ROOT,
298279
"Docker (checked [%s]) is required to run the following task%s: \n%s",
299280
maybeDockerBinaries.join(","),
300281
tasks.size() > 1 ? "s" : "",
301282
tasks.join('\n'))
302-
} else {
303-
assert exitCode > 0 && dockerErrorOutput != null
304-
message = String.format(
283+
throwDockerRequiredException(message)
284+
}
285+
286+
// we use a multi-stage Docker build, check the Docker version since 17.05
287+
final ByteArrayOutputStream dockerVersionOutput = new ByteArrayOutputStream()
288+
LoggedExec.exec(
289+
rootProject,
290+
{ ExecSpec it ->
291+
it.commandLine = [dockerBinary, '--version']
292+
it.standardOutput = dockerVersionOutput
293+
})
294+
final String dockerVersion = dockerVersionOutput.toString().trim()
295+
final Matcher matcher = dockerVersion =~ /Docker version (\d+\.\d+)\.\d+(?:-ce)?, build [0-9a-f]{7}/
296+
assert matcher.matches() : dockerVersion
297+
final dockerMajorMinorVersion = matcher.group(1)
298+
final String[] majorMinor = dockerMajorMinorVersion.split("\\.")
299+
if (Integer.parseInt(majorMinor[0]) < 17
300+
|| (Integer.parseInt(majorMinor[0]) == 17 && Integer.parseInt(majorMinor[1]) < 5)) {
301+
final String message = String.format(
302+
Locale.ROOT,
303+
"building Docker images requires Docker version 17.05+ due to use of multi-stage builds yet was [%s]",
304+
dockerVersion)
305+
throwDockerRequiredException(message)
306+
}
307+
308+
final ByteArrayOutputStream dockerImagesErrorOutput = new ByteArrayOutputStream()
309+
// the Docker binary executes, check that we can execute a privileged command
310+
final ExecResult dockerImagesResult = LoggedExec.exec(
311+
rootProject,
312+
{ ExecSpec it ->
313+
it.commandLine = [dockerBinary, "images"]
314+
it.errorOutput = dockerImagesErrorOutput
315+
it.ignoreExitValue = true
316+
})
317+
318+
if (dockerImagesResult.exitValue != 0) {
319+
final String message = String.format(
305320
Locale.ROOT,
306321
"a problem occurred running Docker from [%s] yet it is required to run the following task%s: \n%s\n" +
307322
"the problem is that Docker exited with exit code [%d] with standard error output [%s]",
308323
dockerBinary,
309324
tasks.size() > 1 ? "s" : "",
310325
tasks.join('\n'),
311-
exitCode,
312-
dockerErrorOutput.trim())
326+
dockerImagesResult.exitValue,
327+
dockerImagesErrorOutput.toString().trim())
328+
throwDockerRequiredException(message)
313329
}
314-
throw new GradleException(
315-
message + "\nyou can address this by attending to the reported issue, "
316-
+ "removing the offending tasks from being executed, "
317-
+ "or by passing -Dbuild.docker=false")
330+
318331
}
319332
}
320333
}
@@ -325,6 +338,13 @@ class BuildPlugin implements Plugin<Project> {
325338
}
326339
}
327340

341+
private static void throwDockerRequiredException(final String message) {
342+
throw new GradleException(
343+
message + "\nyou can address this by attending to the reported issue, "
344+
+ "removing the offending tasks from being executed, "
345+
+ "or by passing -Dbuild.docker=false")
346+
}
347+
328348
private static String findCompilerJavaHome() {
329349
String compilerJavaHome = System.getenv('JAVA_HOME')
330350
final String compilerJavaProperty = System.getProperty('compiler.java')

0 commit comments

Comments
 (0)