diff --git a/docker-compose-rule-core/src/main/java/com/palantir/docker/compose/execution/DockerCommandLocations.java b/docker-compose-rule-core/src/main/java/com/palantir/docker/compose/execution/DockerCommandLocations.java index a1e0c0a06..a7614cc5e 100644 --- a/docker-compose-rule-core/src/main/java/com/palantir/docker/compose/execution/DockerCommandLocations.java +++ b/docker-compose-rule-core/src/main/java/com/palantir/docker/compose/execution/DockerCommandLocations.java @@ -15,33 +15,53 @@ */ package com.palantir.docker.compose.execution; -import static java.util.Arrays.asList; - import java.io.File; +import java.nio.file.Paths; +import java.util.Arrays; +import java.util.Collections; import java.util.List; import java.util.Optional; import java.util.function.Predicate; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import org.immutables.value.Value; -public class DockerCommandLocations { +@Value.Immutable +public abstract class DockerCommandLocations { private static final Predicate IS_NOT_NULL = path -> path != null; private static final Predicate FILE_EXISTS = path -> new File(path).exists(); - private final List possiblePaths; - - public DockerCommandLocations(String... possiblePaths) { - this.possiblePaths = asList(possiblePaths); + protected abstract String executableName(); + protected abstract List> additionalSearchLocations(); + @Value.Default protected List pathLocations() { + String envpath = System.getenv("PATH"); + return envpath == null ? Collections.emptyList() : Arrays.asList(envpath.split(File.pathSeparator)); } + @Value.Derived public Optional preferredLocation() { - - return possiblePaths.stream() - .filter(IS_NOT_NULL) - .filter(FILE_EXISTS) - .findFirst(); + return Stream.concat( + pathLocations().stream().map(path -> Paths.get(path, executableName()).toAbsolutePath().toString()), + additionalSearchLocations().stream().filter(Optional::isPresent).map(Optional::get)) + .filter(IS_NOT_NULL) + .filter(FILE_EXISTS) + .findFirst(); } @Override public String toString() { - return "DockerCommandLocations{possiblePaths=" + possiblePaths + "}"; + return "DockerCommandLocations{additionalSearchLocations=" + additionalSearchLocations() + "}"; + } + + public static ImmutableDockerCommandLocations.Builder builder() { + return ImmutableDockerCommandLocations.builder(); + } + + /** + * convert an array of strings to a list of optionals. used to make calling `additionalSearchLocations' a bit friendlier. + */ + public static List> optionals(String ... strings) { + return Arrays.stream(strings).map(Optional::ofNullable).collect(Collectors.toList()); } } diff --git a/docker-compose-rule-core/src/main/java/com/palantir/docker/compose/execution/DockerComposeExecutable.java b/docker-compose-rule-core/src/main/java/com/palantir/docker/compose/execution/DockerComposeExecutable.java index 7a580b130..9aa3f58f2 100644 --- a/docker-compose-rule-core/src/main/java/com/palantir/docker/compose/execution/DockerComposeExecutable.java +++ b/docker-compose-rule-core/src/main/java/com/palantir/docker/compose/execution/DockerComposeExecutable.java @@ -28,11 +28,13 @@ public abstract class DockerComposeExecutable implements Executable { private static final Logger log = LoggerFactory.getLogger(DockerComposeExecutable.class); - private static final DockerCommandLocations DOCKER_COMPOSE_LOCATIONS = new DockerCommandLocations( - System.getenv("DOCKER_COMPOSE_LOCATION"), - "/usr/local/bin/docker-compose", - "/usr/bin/docker-compose" - ); + private static final DockerCommandLocations DOCKER_COMPOSE_LOCATIONS = DockerCommandLocations.builder() + .executableName("docker-compose") + .additionalSearchLocations(DockerCommandLocations.optionals( + System.getenv("DOCKER_COMPOSE_LOCATION"), + "/usr/local/bin/docker-compose", + "/usr/bin/docker-compose" + )).build(); @Value.Parameter protected abstract DockerComposeFiles dockerComposeFiles(); @Value.Parameter protected abstract DockerConfiguration dockerConfiguration(); diff --git a/docker-compose-rule-core/src/main/java/com/palantir/docker/compose/execution/DockerExecutable.java b/docker-compose-rule-core/src/main/java/com/palantir/docker/compose/execution/DockerExecutable.java index 1ff5cf01e..014c76347 100644 --- a/docker-compose-rule-core/src/main/java/com/palantir/docker/compose/execution/DockerExecutable.java +++ b/docker-compose-rule-core/src/main/java/com/palantir/docker/compose/execution/DockerExecutable.java @@ -26,11 +26,13 @@ public abstract class DockerExecutable implements Executable { private static final Logger log = LoggerFactory.getLogger(DockerExecutable.class); - private static final DockerCommandLocations DOCKER_LOCATIONS = new DockerCommandLocations( - System.getenv("DOCKER_LOCATION"), - "/usr/local/bin/docker", - "/usr/bin/docker" - ); + private static final DockerCommandLocations DOCKER_LOCATIONS = DockerCommandLocations.builder() + .executableName("docker") + .additionalSearchLocations(DockerCommandLocations.optionals( + System.getenv("DOCKER_LOCATION"), + "/usr/local/bin/docker", + "/usr/bin/docker" + )).build(); @Value.Parameter protected abstract DockerConfiguration dockerConfiguration(); diff --git a/docker-compose-rule-core/src/test/java/com/palantir/docker/compose/execution/DockerCommandLocationsShould.java b/docker-compose-rule-core/src/test/java/com/palantir/docker/compose/execution/DockerCommandLocationsShould.java index f5b2e44eb..4492505b3 100644 --- a/docker-compose-rule-core/src/test/java/com/palantir/docker/compose/execution/DockerCommandLocationsShould.java +++ b/docker-compose-rule-core/src/test/java/com/palantir/docker/compose/execution/DockerCommandLocationsShould.java @@ -19,7 +19,11 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.core.Is.is; +import java.io.File; import java.io.IOException; +import java.util.Arrays; +import java.util.Collections; + import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -28,22 +32,33 @@ public class DockerCommandLocationsShould { private static final String badLocation = "file/that/does/not/exist"; private static final String otherBadLocation = "another/file/that/does/not/exist"; + private static final String someLocationInPath = "/folder/not/containing/docker-cmd"; + private static final String someOtherLocationInPath = "/folder/not/containing/docker-cmd"; + private static final String FILENAME = "docker-compose.yml"; + @Rule public TemporaryFolder folder = new TemporaryFolder(); private String goodLocation; + private String goodLocationParent; @Before public void setup() throws IOException { - goodLocation = folder.newFile("docker-compose.yml").getAbsolutePath(); + File file = folder.newFile(FILENAME); + goodLocation = file.getAbsolutePath(); + goodLocationParent = file.getParent(); } @Test public void provide_the_first_docker_command_location_if_it_exists() { - DockerCommandLocations dockerCommandLocations = new DockerCommandLocations( - badLocation, - goodLocation, - otherBadLocation); + DockerCommandLocations dockerCommandLocations = DockerCommandLocations.builder() + .executableName(FILENAME) + .pathLocations(Collections.emptyList()) + .additionalSearchLocations(DockerCommandLocations.optionals( + badLocation, + goodLocation, + otherBadLocation + )).build(); assertThat(dockerCommandLocations.preferredLocation().get(), is(goodLocation)); @@ -51,9 +66,13 @@ public void setup() throws IOException { @Test public void skip_paths_from_environment_variables_that_are_unset() { - DockerCommandLocations dockerCommandLocations = new DockerCommandLocations( - System.getenv("AN_UNSET_DOCKER_COMPOSE_PATH"), - goodLocation); + DockerCommandLocations dockerCommandLocations = DockerCommandLocations.builder() + .executableName(FILENAME) + .pathLocations(Collections.emptyList()) + .additionalSearchLocations(DockerCommandLocations.optionals( + System.getenv("AN_UNSET_DOCKER_COMPOSE_PATH"), + goodLocation + )).build(); assertThat(dockerCommandLocations.preferredLocation().get(), is(goodLocation)); @@ -61,8 +80,53 @@ public void setup() throws IOException { @Test public void have_no_preferred_path_when_all_possible_paths_are_all_invalid() { - DockerCommandLocations dockerCommandLocations = new DockerCommandLocations( - badLocation); + DockerCommandLocations dockerCommandLocations = DockerCommandLocations.builder() + .executableName(FILENAME) + .pathLocations(Collections.emptyList()) + .additionalSearchLocations(DockerCommandLocations.optionals( + badLocation + )).build(); + + assertThat(dockerCommandLocations.preferredLocation(), + is(empty())); + } + + @Test public void + discover_docker_command_in_envpath() { + DockerCommandLocations dockerCommandLocations = DockerCommandLocations.builder() + .executableName(FILENAME) + //path will contain directories, not full path to docker cmd + .pathLocations(Arrays.asList(someLocationInPath, someOtherLocationInPath, goodLocationParent)) + .additionalSearchLocations(DockerCommandLocations.optionals( + badLocation, + otherBadLocation + )).build(); + + assertThat(dockerCommandLocations.preferredLocation().get(), + is(goodLocation)); //but result will be the full path to docker cmd + } + + @Test public void + have_no_preferred_path_when_all_possible_paths_are_all_invalid_with_envpath() { + DockerCommandLocations dockerCommandLocations = DockerCommandLocations.builder() + .executableName(FILENAME) + .pathLocations(Arrays.asList(someLocationInPath, someOtherLocationInPath)) + .additionalSearchLocations(DockerCommandLocations.optionals( + badLocation, + otherBadLocation + )).build(); + + assertThat(dockerCommandLocations.preferredLocation(), + is(empty())); + } + + @Test public void + dont_fail_for_empty_lists() { + DockerCommandLocations dockerCommandLocations = DockerCommandLocations.builder() + .executableName(FILENAME) + .pathLocations(Collections.emptyList()) + .additionalSearchLocations(Collections.emptyList()) + .build(); assertThat(dockerCommandLocations.preferredLocation(), is(empty()));