Skip to content

Commit

Permalink
Include bin location used by Homebrew for MacOS users
Browse files Browse the repository at this point in the history
- This commit adds location of docker binaries when installed via Homebrew on MacOS
- Making initialization of the DockerAvailability thread-safe

Notice that Homebrew can also be used on Linux, but we are not listing this default bin location for now. This means Linux users using brew to install docker will need to create a suitable alias as a workaround.

Closes #3476

Signed-off-by: Lukáš Vlček <[email protected]>
  • Loading branch information
lukas-vlcek committed Jul 1, 2022
1 parent 3681ac7 commit 2385dd3
Showing 1 changed file with 64 additions and 42 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
* Build service for detecting available Docker installation and checking for compatibility with OpenSearch Docker image build
Expand All @@ -66,13 +67,17 @@ public abstract class DockerSupportService implements BuildService<DockerSupport

private static Logger LOGGER = Logging.getLogger(DockerSupportService.class);
// Defines the possible locations of the Docker CLI. These will be searched in order.
private static String[] DOCKER_BINARIES_UNIX = { "/usr/bin/docker", "/usr/local/bin/docker" };
private static String[] LINUX_BIN_LOCATIONS = { "/usr/local/bin", "/usr/bin", "/opt/homebrew/bin" };

private static String[] DOCKER_BINARIES_UNIX = Stream.of(LINUX_BIN_LOCATIONS).map(e -> e.concat("/docker")).toArray(String[]::new);

private static String[] DOCKER_BINARIES_WINDOWS = { System.getenv("PROGRAMFILES") + "\\Docker\\Docker\\resources\\bin\\docker.exe" };

private static String[] DOCKER_BINARIES = Os.isFamily(Os.FAMILY_WINDOWS) ? DOCKER_BINARIES_WINDOWS : DOCKER_BINARIES_UNIX;

private static String[] DOCKER_COMPOSE_BINARIES_UNIX = { "/usr/local/bin/docker-compose", "/usr/bin/docker-compose" };
private static String[] DOCKER_COMPOSE_BINARIES_UNIX = Stream.of(LINUX_BIN_LOCATIONS)
.map(e -> e.concat("/docker-compose"))
.toArray(String[]::new);

private static String[] DOCKER_COMPOSE_BINARIES_WINDOWS = {
System.getenv("PROGRAMFILES") + "\\Docker\\Docker\\resources\\bin\\docker-compose.exe" };
Expand All @@ -84,7 +89,7 @@ public abstract class DockerSupportService implements BuildService<DockerSupport
private static final Version MINIMUM_DOCKER_VERSION = Version.fromString("17.05.0");

private final ExecOperations execOperations;
private DockerAvailability dockerAvailability;
private volatile DockerAvailability dockerAvailability;

@Inject
public DockerSupportService(ExecOperations execOperations) {
Expand All @@ -98,48 +103,61 @@ public DockerSupportService(ExecOperations execOperations) {
*/
public DockerAvailability getDockerAvailability() {
if (this.dockerAvailability == null) {
String dockerPath = null;
Result lastResult = null;
Version version = null;
boolean isVersionHighEnough = false;
boolean isComposeAvailable = false;

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

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

if (lastResult.isSuccess()) {
version = Version.fromString(lastResult.stdout.trim(), Version.Mode.RELAXED);

isVersionHighEnough = version.onOrAfter(MINIMUM_DOCKER_VERSION);

if (isVersionHighEnough) {
// Check that we can execute a privileged command
lastResult = runCommand(dockerPath, "images");
// Gradle BuildService implementations must be thread-safe
synchronized (this) {
String dockerPath = null;
Result lastResult = null;
Version version = null;
boolean isVersionHighEnough = false;
boolean isComposeAvailable = false;

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

// Since we use a multi-stage Docker build, check the Docker version meets minimum requirement
lastResult = runCommand(dockerPath, "version", "--format", "{{.Server.Version}}");
if (LOGGER.isInfoEnabled()) {
LOGGER.info("Docker binary found at: {}", dockerPath);
LOGGER.info("Docker version check: {}", lastResult.toString());
}

// 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();
if (lastResult.isSuccess()) {
version = Version.fromString(lastResult.stdout.trim(), Version.Mode.RELAXED);

isVersionHighEnough = version.onOrAfter(MINIMUM_DOCKER_VERSION);

if (isVersionHighEnough) {
// Check that we can execute a privileged command
lastResult = runCommand(dockerPath, "images");

// If docker all checks out, see if docker-compose is available and working
Optional<String> composePath = getDockerComposePath();
if (lastResult.isSuccess() && composePath.isPresent()) {
Result composeCmdCheck = runCommand(composePath.get(), "version");
isComposeAvailable = composeCmdCheck.isSuccess();
if (LOGGER.isInfoEnabled()) {
LOGGER.info("Docker-compose binary found at: {}", composePath);
LOGGER.info("Docker-compose version check: {}", composeCmdCheck.toString());
}
}
}
}
}
}

boolean isAvailable = isVersionHighEnough && lastResult != null && lastResult.isSuccess();

this.dockerAvailability = new DockerAvailability(
isAvailable,
isComposeAvailable,
isVersionHighEnough,
dockerPath,
version,
lastResult
);
boolean isAvailable = isVersionHighEnough && lastResult != null && lastResult.isSuccess();

// "Double checked locking"
if (this.dockerAvailability == null) this.dockerAvailability = new DockerAvailability(
isAvailable,
isComposeAvailable,
isVersionHighEnough,
dockerPath,
version,
lastResult
);
}
}

return this.dockerAvailability;
Expand Down Expand Up @@ -272,6 +290,10 @@ static Map<String, String> parseOsRelease(final List<String> osReleaseLines) {
return values;
}

private Optional<String> findFirstPath(String[] paths) {
return Arrays.asList(paths).stream().filter(path -> new File(path).exists()).findFirst();
}

/**
* Searches the entries in {@link #DOCKER_BINARIES} for the Docker CLI. This method does
* not check whether the Docker installation appears usable, see {@link #getDockerAvailability()}
Expand All @@ -281,7 +303,7 @@ static Map<String, String> parseOsRelease(final List<String> osReleaseLines) {
*/
private Optional<String> getDockerPath() {
// Check if the Docker binary exists
return Arrays.asList(DOCKER_BINARIES).stream().filter(path -> new File(path).exists()).findFirst();
return findFirstPath(DOCKER_BINARIES);
}

/**
Expand All @@ -292,7 +314,7 @@ private Optional<String> getDockerPath() {
*/
private Optional<String> getDockerComposePath() {
// Check if the Docker binary exists
return Arrays.asList(DOCKER_COMPOSE_BINARIES).stream().filter(path -> new File(path).exists()).findFirst();
return findFirstPath(DOCKER_COMPOSE_BINARIES);
}

private void throwDockerRequiredException(final String message) {
Expand Down

0 comments on commit 2385dd3

Please sign in to comment.