Skip to content

Commit 98dbc4a

Browse files
authored
Refactor Docker Compose version detection to predictably pick v2 only if v1 is not available (opensearch-project#16793)
Signed-off-by: Andriy Redko <andriy.redko@aiven.io>
1 parent 75a2fc3 commit 98dbc4a

File tree

2 files changed

+64
-42
lines changed

2 files changed

+64
-42
lines changed

buildSrc/src/main/java/org/opensearch/gradle/docker/DockerSupportService.java

+60-35
Original file line numberDiff line numberDiff line change
@@ -105,16 +105,15 @@ public DockerAvailability getDockerAvailability() {
105105
Result lastResult = null;
106106
Version version = null;
107107
boolean isVersionHighEnough = false;
108-
boolean isComposeAvailable = false;
109-
boolean isComposeV2Available = false;
108+
DockerComposeAvailability dockerComposeAvailability = null;
110109

111110
// Check if the Docker binary exists
112111
final Optional<String> dockerBinary = getDockerPath();
113112
if (isExcludedOs() == false && dockerBinary.isPresent()) {
114113
dockerPath = dockerBinary.get();
115114

116115
// Since we use a multi-stage Docker build, check the Docker version meets minimum requirement
117-
lastResult = runCommand(dockerPath, "version", "--format", "{{.Server.Version}}");
116+
lastResult = runCommand(execOperations, dockerPath, "version", "--format", "{{.Server.Version}}");
118117

119118
if (lastResult.isSuccess()) {
120119
version = Version.fromString(lastResult.stdout.trim(), Version.Mode.RELAXED);
@@ -123,15 +122,11 @@ public DockerAvailability getDockerAvailability() {
123122

124123
if (isVersionHighEnough) {
125124
// Check that we can execute a privileged command
126-
lastResult = runCommand(dockerPath, "images");
127-
125+
lastResult = runCommand(execOperations, dockerPath, "images");
128126
// If docker all checks out, see if docker-compose is available and working
129-
Optional<String> composePath = getDockerComposePath();
130-
if (lastResult.isSuccess() && composePath.isPresent()) {
131-
isComposeAvailable = runCommand(composePath.get(), "version").isSuccess();
127+
if (lastResult.isSuccess()) {
128+
dockerComposeAvailability = DockerComposeAvailability.detect(execOperations, dockerPath).orElse(null);
132129
}
133-
134-
isComposeV2Available = runCommand(dockerPath, "compose", "version").isSuccess();
135130
}
136131
}
137132
}
@@ -140,8 +135,7 @@ public DockerAvailability getDockerAvailability() {
140135

141136
this.dockerAvailability = new DockerAvailability(
142137
isAvailable,
143-
isComposeAvailable,
144-
isComposeV2Available,
138+
dockerComposeAvailability,
145139
isVersionHighEnough,
146140
dockerPath,
147141
version,
@@ -291,17 +285,6 @@ private Optional<String> getDockerPath() {
291285
return Arrays.asList(DOCKER_BINARIES).stream().filter(path -> new File(path).exists()).findFirst();
292286
}
293287

294-
/**
295-
* Searches the entries in {@link #DOCKER_COMPOSE_BINARIES} for the Docker Compose CLI. This method does
296-
* not check whether the installation appears usable, see {@link #getDockerAvailability()} instead.
297-
*
298-
* @return the path to a CLI, if available.
299-
*/
300-
private Optional<String> getDockerComposePath() {
301-
// Check if the Docker binary exists
302-
return Arrays.asList(DOCKER_COMPOSE_BINARIES).stream().filter(path -> new File(path).exists()).findFirst();
303-
}
304-
305288
private void throwDockerRequiredException(final String message) {
306289
throwDockerRequiredException(message, null);
307290
}
@@ -321,7 +304,7 @@ private void throwDockerRequiredException(final String message, Exception e) {
321304
* while running the command, or the process was killed after reaching the 10s timeout,
322305
* then the exit code will be -1.
323306
*/
324-
private Result runCommand(String... args) {
307+
private static Result runCommand(ExecOperations execOperations, String... args) {
325308
if (args.length == 0) {
326309
throw new IllegalArgumentException("Cannot execute with no command");
327310
}
@@ -356,14 +339,9 @@ public static class DockerAvailability {
356339
public final boolean isAvailable;
357340

358341
/**
359-
* True if docker-compose is available.
342+
* Non-null if docker-compose v1 or v2 is available.
360343
*/
361-
public final boolean isComposeAvailable;
362-
363-
/**
364-
* True if docker compose is available.
365-
*/
366-
public final boolean isComposeV2Available;
344+
public final DockerComposeAvailability dockerComposeAvailability;
367345

368346
/**
369347
* True if the installed Docker version is &gt;= 17.05
@@ -387,23 +365,70 @@ public static class DockerAvailability {
387365

388366
DockerAvailability(
389367
boolean isAvailable,
390-
boolean isComposeAvailable,
391-
boolean isComposeV2Available,
368+
DockerComposeAvailability dockerComposeAvailability,
392369
boolean isVersionHighEnough,
393370
String path,
394371
Version version,
395372
Result lastCommand
396373
) {
397374
this.isAvailable = isAvailable;
398-
this.isComposeAvailable = isComposeAvailable;
399-
this.isComposeV2Available = isComposeV2Available;
375+
this.dockerComposeAvailability = dockerComposeAvailability;
400376
this.isVersionHighEnough = isVersionHighEnough;
401377
this.path = path;
402378
this.version = version;
403379
this.lastCommand = lastCommand;
404380
}
381+
382+
public boolean isDockerComposeAvailable() {
383+
return dockerComposeAvailability != null;
384+
}
385+
}
386+
387+
/**
388+
* Marker interface for Docker Compose availability
389+
*/
390+
private interface DockerComposeAvailability {
391+
/**
392+
* Detects Docker Compose V1/V2 availability
393+
*/
394+
private static Optional<DockerComposeAvailability> detect(ExecOperations execOperations, String dockerPath) {
395+
Optional<String> composePath = getDockerComposePath();
396+
if (composePath.isPresent()) {
397+
if (runCommand(execOperations, composePath.get(), "version").isSuccess()) {
398+
return Optional.of(new DockerComposeV1Availability());
399+
}
400+
}
401+
402+
if (runCommand(execOperations, dockerPath, "compose", "version").isSuccess()) {
403+
return Optional.of(new DockerComposeV2Availability());
404+
}
405+
406+
return Optional.empty();
407+
}
408+
409+
/**
410+
* Searches the entries in {@link #DOCKER_COMPOSE_BINARIES} for the Docker Compose CLI. This method does
411+
* not check whether the installation appears usable, see {@link #getDockerAvailability()} instead.
412+
*
413+
* @return the path to a CLI, if available.
414+
*/
415+
private static Optional<String> getDockerComposePath() {
416+
// Check if the Docker binary exists
417+
return Arrays.asList(DOCKER_COMPOSE_BINARIES).stream().filter(path -> new File(path).exists()).findFirst();
418+
}
419+
405420
}
406421

422+
/**
423+
* Docker Compose V1 availability
424+
*/
425+
public static class DockerComposeV1Availability implements DockerComposeAvailability {}
426+
427+
/**
428+
* Docker Compose V2 availability
429+
*/
430+
public static class DockerComposeV2Availability implements DockerComposeAvailability {}
431+
407432
/**
408433
* This class models the result of running a command. It captures the exit code, standard output and standard error.
409434
*/

buildSrc/src/main/java/org/opensearch/gradle/testfixtures/TestFixturesPlugin.java

+4-7
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
import org.opensearch.gradle.SystemPropertyCommandLineArgumentProvider;
4444
import org.opensearch.gradle.docker.DockerSupportPlugin;
4545
import org.opensearch.gradle.docker.DockerSupportService;
46+
import org.opensearch.gradle.docker.DockerSupportService.DockerComposeV2Availability;
4647
import org.opensearch.gradle.info.BuildParams;
4748
import org.opensearch.gradle.precommit.TestingConventionsTasks;
4849
import org.opensearch.gradle.util.GradleUtils;
@@ -171,11 +172,8 @@ public void execute(Task task) {
171172
.findFirst();
172173

173174
composeExtension.getExecutable().set(dockerCompose.isPresent() ? dockerCompose.get() : "/usr/bin/docker");
174-
if (dockerSupport.get().getDockerAvailability().isComposeV2Available) {
175-
composeExtension.getUseDockerComposeV2().set(true);
176-
} else if (dockerSupport.get().getDockerAvailability().isComposeAvailable) {
177-
composeExtension.getUseDockerComposeV2().set(false);
178-
}
175+
composeExtension.getUseDockerComposeV2()
176+
.set(dockerSupport.get().getDockerAvailability().dockerComposeAvailability instanceof DockerComposeV2Availability);
179177

180178
tasks.named("composeUp").configure(t -> {
181179
// Avoid running docker-compose tasks in parallel in CI due to some issues on certain Linux distributions
@@ -232,8 +230,7 @@ private void maybeSkipTask(Provider<DockerSupportService> dockerSupport, TaskPro
232230

233231
private void maybeSkipTask(Provider<DockerSupportService> dockerSupport, Task task) {
234232
task.onlyIf(spec -> {
235-
boolean isComposeAvailable = dockerSupport.get().getDockerAvailability().isComposeV2Available
236-
|| dockerSupport.get().getDockerAvailability().isComposeAvailable;
233+
boolean isComposeAvailable = dockerSupport.get().getDockerAvailability().isDockerComposeAvailable();
237234
if (isComposeAvailable == false) {
238235
LOGGER.info("Task {} requires docker-compose but it is unavailable. Task will be skipped.", task.getPath());
239236
}

0 commit comments

Comments
 (0)