diff --git a/docker-compose-rule-core/src/integrationTest/java/com/palantir/docker/compose/AggressiveShutdownStrategyIntegrationTest.java b/docker-compose-rule-core/src/integrationTest/java/com/palantir/docker/compose/AggressiveShutdownStrategyIntegrationTest.java index f6db46cea..01aa9b941 100644 --- a/docker-compose-rule-core/src/integrationTest/java/com/palantir/docker/compose/AggressiveShutdownStrategyIntegrationTest.java +++ b/docker-compose-rule-core/src/integrationTest/java/com/palantir/docker/compose/AggressiveShutdownStrategyIntegrationTest.java @@ -19,6 +19,7 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.is; +import com.google.common.collect.ImmutableList; import com.palantir.docker.compose.configuration.ShutdownStrategy; import com.palantir.docker.compose.logging.DoNothingLogCollector; import org.hamcrest.MatcherAssert; @@ -36,13 +37,13 @@ public void shut_down_multiple_containers_immediately() throws Exception { .shutdownStrategy(ShutdownStrategy.AGGRESSIVE) .build(); - MatcherAssert.assertThat(docker.dockerCompose().ps(), Matchers.is(TestContainerNames.of())); + MatcherAssert.assertThat(docker.dockerCompose().ps(), Matchers.is(ImmutableList.of())); docker.before(); assertThat(docker.dockerCompose().ps().size(), is(2)); docker.after(); - assertThat(docker.dockerCompose().ps(), is(TestContainerNames.of())); + assertThat(docker.dockerCompose().ps(), is(ImmutableList.of())); } } diff --git a/docker-compose-rule-core/src/integrationTest/java/com/palantir/docker/compose/AggressiveShutdownWithNetworkCleanupStrategyIntegrationTest.java b/docker-compose-rule-core/src/integrationTest/java/com/palantir/docker/compose/AggressiveShutdownWithNetworkCleanupStrategyIntegrationTest.java index d3fa3342d..4ae1b6669 100644 --- a/docker-compose-rule-core/src/integrationTest/java/com/palantir/docker/compose/AggressiveShutdownWithNetworkCleanupStrategyIntegrationTest.java +++ b/docker-compose-rule-core/src/integrationTest/java/com/palantir/docker/compose/AggressiveShutdownWithNetworkCleanupStrategyIntegrationTest.java @@ -20,6 +20,7 @@ import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.not; +import com.google.common.collect.ImmutableList; import com.google.common.collect.Sets; import com.palantir.docker.compose.configuration.ShutdownStrategy; import com.palantir.docker.compose.logging.DoNothingLogCollector; @@ -39,13 +40,13 @@ public class AggressiveShutdownWithNetworkCleanupStrategyIntegrationTest { @Test public void shut_down_multiple_containers_immediately() throws Exception { - assertThat(docker.dockerCompose().ps(), is(TestContainerNames.of())); + assertThat(docker.dockerCompose().ps(), is(ImmutableList.of())); docker.before(); assertThat(docker.dockerCompose().ps().size(), is(2)); docker.after(); - assertThat(docker.dockerCompose().ps(), is(TestContainerNames.of())); + assertThat(docker.dockerCompose().ps(), is(ImmutableList.of())); } @Test diff --git a/docker-compose-rule-core/src/main/java/com/palantir/docker/compose/DockerComposeManager.java b/docker-compose-rule-core/src/main/java/com/palantir/docker/compose/DockerComposeManager.java index d6d746078..00b2b304c 100644 --- a/docker-compose-rule-core/src/main/java/com/palantir/docker/compose/DockerComposeManager.java +++ b/docker-compose-rule-core/src/main/java/com/palantir/docker/compose/DockerComposeManager.java @@ -126,7 +126,7 @@ public ShutdownStrategy shutdownStrategy() { @Value.Default public com.palantir.docker.compose.execution.DockerCompose dockerCompose() { com.palantir.docker.compose.execution.DockerCompose - dockerCompose = new DefaultDockerCompose(dockerComposeExecutable(), machine()); + dockerCompose = new DefaultDockerCompose(dockerComposeExecutable(), dockerExecutable(), machine()); return new RetryingDockerCompose(retryAttempts(), dockerCompose); } diff --git a/docker-compose-rule-core/src/main/java/com/palantir/docker/compose/connection/ContainerName.java b/docker-compose-rule-core/src/main/java/com/palantir/docker/compose/connection/ContainerName.java index e029a79d1..38f98e55c 100644 --- a/docker-compose-rule-core/src/main/java/com/palantir/docker/compose/connection/ContainerName.java +++ b/docker-compose-rule-core/src/main/java/com/palantir/docker/compose/connection/ContainerName.java @@ -4,16 +4,20 @@ package com.palantir.docker.compose.connection; -import static java.util.stream.Collectors.joining; -import com.google.common.base.Splitter; -import java.util.Arrays; -import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import org.immutables.value.Value; @Value.Immutable public abstract class ContainerName { + // Docker default container names have the format of: + // __ or ___ + // Regex without escape characters: ^(?[^_]+)_(?[^_]+)_(?[^_]+)(_(?[^_]+))?$ + private static final Pattern DEFAULT_CONTAINER_NAME_PATTERN = + Pattern.compile("^(?[^_]+)_(?[^_]+)_(?[^_]+)(_(?[^_]+))?$"); + public abstract String rawName(); public abstract String semanticName(); @@ -23,39 +27,20 @@ public String toString() { return semanticName(); } - public static ContainerName fromPsLine(String psLine) { - List lineComponents = Splitter.on(" ").splitToList(psLine); - String rawName = lineComponents.get(0); - - if (probablyCustomName(rawName)) { - return ImmutableContainerName.builder() - .rawName(rawName) - .semanticName(rawName) - .build(); - } - - String semanticName = withoutDirectory(withoutScaleNumber(rawName)); + public static ContainerName fromName(String name) { return ImmutableContainerName.builder() - .rawName(rawName) - .semanticName(semanticName) + .rawName(name) + .semanticName(parseSemanticName(name)) .build(); } - private static boolean probablyCustomName(String rawName) { - return !(rawName.split("_").length >= 3); - } + private static String parseSemanticName(String name) { + Matcher matcher = DEFAULT_CONTAINER_NAME_PATTERN.matcher(name); - private static String withoutDirectory(String rawName) { - return Arrays.stream(rawName.split("_")) - .skip(1) - .collect(joining("_")); - } + if (matcher.matches()) { + return matcher.group("service"); + } - public static String withoutScaleNumber(String rawName) { - String[] components = rawName.split("_"); - return Arrays.stream(components) - .limit(components.length - 1) - .collect(joining("_")); + return name; } - } diff --git a/docker-compose-rule-core/src/main/java/com/palantir/docker/compose/connection/ContainerNames.java b/docker-compose-rule-core/src/main/java/com/palantir/docker/compose/connection/ContainerNames.java deleted file mode 100644 index 5129f02d3..000000000 --- a/docker-compose-rule-core/src/main/java/com/palantir/docker/compose/connection/ContainerNames.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * (c) Copyright 2016 Palantir Technologies Inc. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.palantir.docker.compose.connection; - -import static java.util.Collections.emptyList; -import static java.util.stream.Collectors.toList; - -import com.google.common.base.Splitter; -import java.util.List; -import java.util.regex.Pattern; -import java.util.stream.Stream; - -public class ContainerNames { - private static final Pattern HEAD_PATTERN = Pattern.compile("-+(\r|\n)+"); - private static final Pattern BODY_PATTERN = Pattern.compile("(\r|\n)+"); - - private ContainerNames() {} - - public static List parseFromDockerComposePs(String psOutput) { - List psHeadAndBody = Splitter.on(HEAD_PATTERN).splitToList(psOutput); - if (psHeadAndBody.size() < 2) { - return emptyList(); - } - - String psBody = psHeadAndBody.get(1); - return psBodyLines(psBody) - .map(ContainerName::fromPsLine) - .collect(toList()); - } - - private static Stream psBodyLines(String psBody) { - List lines = Splitter.on(BODY_PATTERN).splitToList(psBody); - return lines.stream() - .map(String::trim) - .filter(line -> !line.isEmpty()); - } - -} diff --git a/docker-compose-rule-core/src/main/java/com/palantir/docker/compose/execution/DefaultDockerCompose.java b/docker-compose-rule-core/src/main/java/com/palantir/docker/compose/execution/DefaultDockerCompose.java index c450f351a..8f3d056b2 100644 --- a/docker-compose-rule-core/src/main/java/com/palantir/docker/compose/execution/DefaultDockerCompose.java +++ b/docker-compose-rule-core/src/main/java/com/palantir/docker/compose/execution/DefaultDockerCompose.java @@ -19,13 +19,15 @@ import static org.joda.time.Duration.standardMinutes; import com.github.zafarkhaja.semver.Version; +import com.google.common.base.CharMatcher; +import com.google.common.base.Splitter; import com.google.common.base.Strings; import com.google.common.collect.ImmutableList; +import com.google.common.collect.Streams; import com.palantir.docker.compose.configuration.DockerComposeFiles; import com.palantir.docker.compose.configuration.ProjectName; import com.palantir.docker.compose.connection.Container; import com.palantir.docker.compose.connection.ContainerName; -import com.palantir.docker.compose.connection.ContainerNames; import com.palantir.docker.compose.connection.DockerMachine; import com.palantir.docker.compose.connection.Ports; import java.io.IOException; @@ -34,6 +36,8 @@ import java.util.List; import java.util.Optional; import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; +import java.util.stream.Stream; import org.apache.commons.io.IOUtils; import org.joda.time.Duration; import org.slf4j.Logger; @@ -45,9 +49,10 @@ public class DefaultDockerCompose implements DockerCompose { private static final Duration LOG_TIMEOUT = standardMinutes(1); private static final Logger log = LoggerFactory.getLogger(DefaultDockerCompose.class); - private final Command command; + private final Command dockerComposeCommand; + private final DockerComposeExecutable dockerComposeRawExecutable; + private final Command dockerCommand; private final DockerMachine dockerMachine; - private final DockerComposeExecutable rawExecutable; public DefaultDockerCompose(DockerComposeFiles dockerComposeFiles, DockerMachine dockerMachine, ProjectName projectName) { @@ -55,69 +60,77 @@ public DefaultDockerCompose(DockerComposeFiles dockerComposeFiles, DockerMachine .dockerComposeFiles(dockerComposeFiles) .dockerConfiguration(dockerMachine) .projectName(projectName) - .build(), dockerMachine); + .build(), + DockerExecutable.builder() + .dockerConfiguration(dockerMachine) + .build(), + dockerMachine); } - public DefaultDockerCompose(DockerComposeExecutable rawExecutable, DockerMachine dockerMachine) { - this.rawExecutable = rawExecutable; - this.command = new Command(rawExecutable, log::trace); + public DefaultDockerCompose( + DockerComposeExecutable dockerComposeRawExecutable, + DockerExecutable dockerRawExecutable, + DockerMachine dockerMachine) { + this.dockerComposeRawExecutable = dockerComposeRawExecutable; + this.dockerComposeCommand = new Command(dockerComposeRawExecutable, log::trace); + this.dockerCommand = new Command(dockerRawExecutable, log::trace); this.dockerMachine = dockerMachine; } @Override public void pull() throws IOException, InterruptedException { - command.execute(Command.throwingOnError(), "pull"); + dockerComposeCommand.execute(Command.throwingOnError(), "pull"); } @Override public void build() throws IOException, InterruptedException { - command.execute(Command.throwingOnError(), "build"); + dockerComposeCommand.execute(Command.throwingOnError(), "build"); } @Override public void up() throws IOException, InterruptedException { - command.execute(Command.throwingOnError(), "up", "-d"); + dockerComposeCommand.execute(Command.throwingOnError(), "up", "-d"); } @Override public void down() throws IOException, InterruptedException { - command.execute(swallowingDownCommandDoesNotExist(), "down", "--volumes"); + dockerComposeCommand.execute(swallowingDownCommandDoesNotExist(), "down", "--volumes"); } @Override public void stop() throws IOException, InterruptedException { - command.execute(Command.throwingOnError(), "stop"); + dockerComposeCommand.execute(Command.throwingOnError(), "stop"); } @Override public void kill() throws IOException, InterruptedException { - command.execute(Command.throwingOnError(), "kill"); + dockerComposeCommand.execute(Command.throwingOnError(), "kill"); } @Override public void rm() throws IOException, InterruptedException { - command.execute(Command.throwingOnError(), "rm", "--force", "-v"); + dockerComposeCommand.execute(Command.throwingOnError(), "rm", "--force", "-v"); } @Override public void up(Container container) throws IOException, InterruptedException { - command.execute(Command.throwingOnError(), "up", "-d", container.getContainerName()); + dockerComposeCommand.execute(Command.throwingOnError(), "up", "-d", container.getContainerName()); } @Override public void start(Container container) throws IOException, InterruptedException { - command.execute(Command.throwingOnError(), "start", container.getContainerName()); + dockerComposeCommand.execute(Command.throwingOnError(), "start", container.getContainerName()); } @Override public void stop(Container container) throws IOException, InterruptedException { - command.execute(Command.throwingOnError(), "stop", container.getContainerName()); + dockerComposeCommand.execute(Command.throwingOnError(), "stop", container.getContainerName()); } @Override public void kill(Container container) throws IOException, InterruptedException { - command.execute(Command.throwingOnError(), "kill", container.getContainerName()); + dockerComposeCommand.execute(Command.throwingOnError(), "kill", container.getContainerName()); } @Override @@ -125,14 +138,14 @@ public String exec(DockerComposeExecOption dockerComposeExecOption, String conta DockerComposeExecArgument dockerComposeExecArgument) throws IOException, InterruptedException { verifyDockerComposeVersionAtLeast(VERSION_1_7_0, "You need at least docker-compose 1.7 to run docker-compose exec"); String[] fullArgs = constructFullDockerComposeExecArguments(dockerComposeExecOption, containerName, dockerComposeExecArgument); - return command.execute(Command.throwingOnError(), fullArgs); + return dockerComposeCommand.execute(Command.throwingOnError(), fullArgs); } @Override public String run(DockerComposeRunOption dockerComposeRunOption, String containerName, DockerComposeRunArgument dockerComposeRunArgument) throws IOException, InterruptedException { String[] fullArgs = constructFullDockerComposeRunArguments(dockerComposeRunOption, containerName, dockerComposeRunArgument); - return command.execute(Command.throwingOnError(), fullArgs); + return dockerComposeCommand.execute(Command.throwingOnError(), fullArgs); } private void verifyDockerComposeVersionAtLeast(Version targetVersion, String message) throws IOException, InterruptedException { @@ -140,7 +153,7 @@ private void verifyDockerComposeVersionAtLeast(Version targetVersion, String mes } private Version version() throws IOException, InterruptedException { - String versionOutput = command.execute(Command.throwingOnError(), "-v"); + String versionOutput = dockerComposeCommand.execute(Command.throwingOnError(), "-v"); return DockerComposeVersion.parseFromDockerComposeVersion(versionOutput); } @@ -170,8 +183,26 @@ private static String[] constructFullDockerComposeRunArguments(DockerComposeRunO @Override public List ps() throws IOException, InterruptedException { - String psOutput = command.execute(Command.throwingOnError(), "ps"); - return ContainerNames.parseFromDockerComposePs(psOutput); + List containerIds = split(dockerComposeCommand.execute(Command.throwingOnError(), "ps", "-q")); + + if (containerIds.isEmpty()) { + return ImmutableList.of(); + } + + List dockerPsBaseCommand = ImmutableList.of("ps", "--no-trunc", "--format", "{{.Names}}"); + + List additionalContainerFilters = containerIds.stream() + .flatMap(containerId -> Stream.of("--filter", String.format("id=%s", containerId))) + .collect(Collectors.toList()); + + String[] dockerPsCommand = Streams.concat( + dockerPsBaseCommand.stream(), additionalContainerFilters.stream()).toArray(String[]::new); + + List containerNames = split(dockerCommand.execute(Command.throwingOnError(), dockerPsCommand)); + + return containerNames.stream() + .map(ContainerName::fromName) + .collect(Collectors.toList()); } @Override @@ -181,12 +212,12 @@ public Optional id(Container container) throws IOException, InterruptedE @Override public String config() throws IOException, InterruptedException { - return command.execute(Command.throwingOnError(), "config"); + return dockerComposeCommand.execute(Command.throwingOnError(), "config"); } @Override public List services() throws IOException, InterruptedException { - String servicesOutput = command.execute(Command.throwingOnError(), "config", "--services"); + String servicesOutput = dockerComposeCommand.execute(Command.throwingOnError(), "config", "--services"); return Arrays.asList(servicesOutput.split("(\r|\n)+")); } @@ -210,7 +241,7 @@ public boolean writeLogs(String container, OutputStream output) throws IOExcepti } private Optional id(String containerName) throws IOException, InterruptedException { - String id = command.execute(Command.throwingOnError(), "ps", "-q", containerName); + String id = dockerComposeCommand.execute(Command.throwingOnError(), "ps", "-q", containerName); if (id.isEmpty()) { return Optional.empty(); } @@ -220,7 +251,7 @@ private Optional id(String containerName) throws IOException, Interrupte private Process logs(String container) throws IOException, InterruptedException { verifyDockerComposeVersionAtLeast(VERSION_1_7_0, "You need at least docker-compose 1.7 to run docker-compose logs"); - return rawExecutable.execute("logs", "--no-color", container); + return dockerComposeRawExecutable.execute("logs", "--no-color", container); } @Override @@ -245,8 +276,14 @@ private static boolean downCommandWasPresent(String output) { } private String psOutput(String service) throws IOException, InterruptedException { - String psOutput = command.execute(Command.throwingOnError(), "ps", service); + String psOutput = dockerComposeCommand.execute(Command.throwingOnError(), "ps", service); validState(!Strings.isNullOrEmpty(psOutput), "No container with name '" + service + "' found"); return psOutput; } + + public static List split(String input) { + return Splitter.on(CharMatcher.whitespace()) + .omitEmptyStrings() + .splitToList(input); + } } diff --git a/docker-compose-rule-core/src/test/java/com/palantir/docker/compose/TestContainerNames.java b/docker-compose-rule-core/src/test/java/com/palantir/docker/compose/TestContainerNames.java deleted file mode 100644 index 1bbd6ed63..000000000 --- a/docker-compose-rule-core/src/test/java/com/palantir/docker/compose/TestContainerNames.java +++ /dev/null @@ -1,31 +0,0 @@ -/* - * (c) Copyright 2016 Palantir Technologies Inc. All rights reserved. - */ - -package com.palantir.docker.compose; - -import static java.util.stream.Collectors.toList; - -import com.palantir.docker.compose.connection.ContainerName; -import com.palantir.docker.compose.connection.ImmutableContainerName; -import java.util.Arrays; -import java.util.List; - -public class TestContainerNames { - - private TestContainerNames() {} - - public static List of(String... semanticNames) { - return Arrays.stream(semanticNames) - .map(TestContainerNames::testContainerName) - .collect(toList()); - } - - private static ContainerName testContainerName(String testName) { - return ImmutableContainerName.builder() - .semanticName(testName) - .rawName("123456_" + testName + "_1") - .build(); - } - -} diff --git a/docker-compose-rule-core/src/test/java/com/palantir/docker/compose/connection/ContainerNameShould.java b/docker-compose-rule-core/src/test/java/com/palantir/docker/compose/connection/ContainerNameShould.java index 6147c74fe..2743628c7 100644 --- a/docker-compose-rule-core/src/test/java/com/palantir/docker/compose/connection/ContainerNameShould.java +++ b/docker-compose-rule-core/src/test/java/com/palantir/docker/compose/connection/ContainerNameShould.java @@ -4,86 +4,40 @@ package com.palantir.docker.compose.connection; -import static java.util.Collections.emptyList; import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.contains; import static org.hamcrest.core.Is.is; -import java.util.List; import org.junit.Test; public class ContainerNameShould { @Test - public void parse_a_semantic_and_raw_name_correctly_from_a_single_line() { - ContainerName actual = ContainerName.fromPsLine("dir_db_1 other line contents"); - - ContainerName expected = ImmutableContainerName.builder() - .rawName("dir_db_1") - .semanticName("db") - .build(); - - assertThat(actual, is(expected)); - } - - @Test - public void can_handle_custom_container_names() { - ContainerName name = ContainerName.fromPsLine("test-1.container.name /docker-entrypoint.sh postgres Up 5432/tcp"); - - ContainerName expected = ImmutableContainerName.builder() - .rawName("test-1.container.name") - .semanticName("test-1.container.name") - .build(); - - assertThat(name, is(expected)); - } - - @Test - public void result_in_no_container_names_when_ps_output_is_empty() { - List names = ContainerNames.parseFromDockerComposePs("\n----\n"); - assertThat(names, is(emptyList())); - } - - @Test - public void result_in_a_single_container_name_when_ps_output_has_a_single_container() { - List names = ContainerNames.parseFromDockerComposePs("\n----\ndir_db_1 other line contents"); - assertThat(names, contains(containerName("dir", "db", "1"))); - } - - @Test - public void allow_windows_newline_characters() { - List names = ContainerNames.parseFromDockerComposePs("\r\n----\r\ndir_db_1 other line contents"); - assertThat(names, contains(containerName("dir", "db", "1"))); - } - - @Test - public void allow_containers_with_underscores_in_their_name() { - List names = ContainerNames.parseFromDockerComposePs("\n----\ndir_left_right_1 other line contents"); - assertThat(names, contains(containerName("dir", "left_right", "1"))); - } - - @Test - public void result_in_two_container_names_when_ps_output_has_two_containers() { - List names = ContainerNames.parseFromDockerComposePs("\n----\ndir_db_1 other line contents\ndir_db2_1 other stuff"); - assertThat(names, contains(containerName("dir", "db", "1"), containerName("dir", "db2", "1"))); + public void parse_container_name_correctly() { + String customContainerName = "custom.container.name"; + assertThat(ContainerName.fromName(customContainerName), is( + ImmutableContainerName.builder() + .rawName(customContainerName) + .semanticName(customContainerName) + .build())); } @Test - public void ignore_an_empty_line_in_ps_output() { - List names = ContainerNames.parseFromDockerComposePs("\n----\ndir_db_1 other line contents\n\n"); - assertThat(names, contains(containerName("dir", "db", "1"))); + public void parse_default_container_name_correctly() { + String defaultContainerName = "directory_service_index"; + assertThat(ContainerName.fromName(defaultContainerName), is( + ImmutableContainerName.builder() + .rawName(defaultContainerName) + .semanticName("service") + .build())); } @Test - public void ignore_a_line_with_ony_spaces_in_ps_output() { - List names = ContainerNames.parseFromDockerComposePs("\n----\ndir_db_1 other line contents\n \n"); - assertThat(names, contains(containerName("dir", "db", "1"))); - } - - private static ContainerName containerName(String project, String semantic, String number) { - return ImmutableContainerName.builder() - .rawName(project + "_" + semantic + "_" + number) - .semanticName(semantic) - .build(); + public void parse_default_container_name_with_slug_correctly() { + String defaultContainerNameWithSlug = "directory_service_index_slug"; + assertThat(ContainerName.fromName(defaultContainerNameWithSlug), is( + ImmutableContainerName.builder() + .rawName(defaultContainerNameWithSlug) + .semanticName("service") + .build())); } } diff --git a/docker-compose-rule-core/src/test/java/com/palantir/docker/compose/execution/DockerComposeShould.java b/docker-compose-rule-core/src/test/java/com/palantir/docker/compose/execution/DockerComposeShould.java index 054576209..f15ccc9a7 100644 --- a/docker-compose-rule-core/src/test/java/com/palantir/docker/compose/execution/DockerComposeShould.java +++ b/docker-compose-rule-core/src/test/java/com/palantir/docker/compose/execution/DockerComposeShould.java @@ -20,14 +20,18 @@ import static org.apache.commons.io.IOUtils.toInputStream; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.contains; +import static org.hamcrest.Matchers.empty; import static org.hamcrest.core.Is.is; import static org.mockito.Matchers.anyVararg; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.reset; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyZeroInteractions; import static org.mockito.Mockito.when; +import com.google.common.base.Joiner; +import com.google.common.collect.ImmutableList; import com.palantir.docker.compose.connection.Container; import com.palantir.docker.compose.connection.ContainerName; import com.palantir.docker.compose.connection.DockerMachine; @@ -38,6 +42,7 @@ import java.io.IOException; import java.nio.charset.StandardCharsets; import java.util.List; +import org.apache.commons.lang3.RandomStringUtils; import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -48,89 +53,175 @@ public class DockerComposeShould { @Rule public ExpectedException exception = ExpectedException.none(); - private final DockerComposeExecutable executor = mock(DockerComposeExecutable.class); + private static final String CONTAINER_ID = RandomStringUtils.randomAlphanumeric(20); + + private final DockerComposeExecutable dockerComposeExecutor = mock(DockerComposeExecutable.class); + private final DockerExecutable dockerExecutor = mock(DockerExecutable.class); private final DockerMachine dockerMachine = mock(DockerMachine.class); - private final DockerCompose compose = new DefaultDockerCompose(executor, dockerMachine); + private final DockerCompose compose = + new DefaultDockerCompose(dockerComposeExecutor, dockerExecutor, dockerMachine); - private final Process executedProcess = mock(Process.class); + private final Process dockerComposeExecutedProcess = mock(Process.class); + private final Process dockerExecutedProcess = mock(Process.class); private final Container container = mock(Container.class); @Before public void before() throws IOException { when(dockerMachine.getIp()).thenReturn("0.0.0.0"); - when(executor.execute(anyVararg())).thenReturn(executedProcess); - when(executedProcess.getInputStream()).thenReturn(toInputStream("0.0.0.0:7000->7000/tcp")); - when(executedProcess.exitValue()).thenReturn(0); + when(dockerComposeExecutor.execute(anyVararg())).thenReturn(dockerComposeExecutedProcess); + when(dockerComposeExecutedProcess.getInputStream()).thenReturn(toInputStream("0.0.0.0:7000->7000/tcp")); + when(dockerComposeExecutedProcess.exitValue()).thenReturn(0); when(container.getContainerName()).thenReturn("my-container"); } @Test public void call_docker_compose_up_with_daemon_flag_on_up() throws IOException, InterruptedException { compose.up(); - verify(executor).execute("up", "-d"); + verify(dockerComposeExecutor).execute("up", "-d"); } @Test public void call_docker_compose_rm_with_force_and_volume_flags_on_rm() throws IOException, InterruptedException { compose.rm(); - verify(executor).execute("rm", "--force", "-v"); + verify(dockerComposeExecutor).execute("rm", "--force", "-v"); } @Test public void call_docker_compose_stop_on_stop() throws IOException, InterruptedException { compose.stop(container); - verify(executor).execute("stop", "my-container"); + verify(dockerComposeExecutor).execute("stop", "my-container"); } @Test public void call_docker_compose_start_on_start() throws IOException, InterruptedException { compose.start(container); - verify(executor).execute("start", "my-container"); + verify(dockerComposeExecutor).execute("start", "my-container"); } @Test - public void parse_and_returns_container_names_on_ps() throws IOException, InterruptedException { - when(executedProcess.getInputStream()).thenReturn(toInputStream("ps\n----\ndir_db_1")); + public void ps_returns_container_names() throws IOException, InterruptedException { + when(dockerComposeExecutor.execute(anyVararg())).thenReturn(dockerComposeExecutedProcess); + when(dockerComposeExecutedProcess.getInputStream()).thenReturn(toInputStream(CONTAINER_ID)); + when(dockerComposeExecutedProcess.exitValue()).thenReturn(0); + + when(dockerExecutor.execute(anyVararg())).thenReturn(dockerExecutedProcess); + when(dockerExecutedProcess.getInputStream()).thenReturn(toInputStream("dir_db_1")); + when(dockerExecutedProcess.exitValue()).thenReturn(0); + List containerNames = compose.ps(); - verify(executor).execute("ps"); + verify(dockerComposeExecutor).execute("ps", "-q"); + verify(dockerExecutor).execute( + "ps", "--no-trunc", "--format", "{{.Names}}", "--filter", String.format("id=%s", CONTAINER_ID)); assertThat(containerNames, contains(ImmutableContainerName.builder().semanticName("db").rawName("dir_db_1").build())); } + @Test + public void ps_returns_multiple_container_names() throws IOException, InterruptedException { + String containerIdA = RandomStringUtils.randomAlphanumeric(20); + String containerIdB = RandomStringUtils.randomAlphanumeric(20); + String containerIdC = RandomStringUtils.randomAlphanumeric(20); + String containerIdString = Joiner.on("\n").join(containerIdA, containerIdB, containerIdC); + + when(dockerComposeExecutor.execute(anyVararg())).thenReturn(dockerComposeExecutedProcess); + when(dockerComposeExecutedProcess.getInputStream()).thenReturn(toInputStream(containerIdString)); + when(dockerComposeExecutedProcess.exitValue()).thenReturn(0); + + String containerNameA = "custom.container.name"; + String containerNameB = "directory_service_index"; + String containerNameC = "directory_service_index_slug"; + String containerNameString = Joiner.on("\n").join(containerNameA, containerNameB, containerNameC); + when(dockerExecutor.execute(anyVararg())).thenReturn(dockerExecutedProcess); + when(dockerExecutedProcess.getInputStream()).thenReturn(toInputStream(containerNameString)); + when(dockerExecutedProcess.exitValue()).thenReturn(0); + + + List containerNames = compose.ps(); + assertThat(containerNames, is(ImmutableList.of( + ImmutableContainerName.builder() + .rawName("custom.container.name") + .semanticName("custom.container.name") + .build(), + ImmutableContainerName.builder() + .rawName("directory_service_index") + .semanticName("service") + .build(), + ImmutableContainerName.builder() + .rawName("directory_service_index_slug") + .semanticName("service") + .build() + ))); + verify(dockerComposeExecutor).execute("ps", "-q"); + verify(dockerExecutor).execute( + "ps", "--no-trunc", "--format", "{{.Names}}", + "--filter", String.format("id=%s", containerIdA), + "--filter", String.format("id=%s", containerIdB), + "--filter", String.format("id=%s", containerIdC)); + } + + @Test + public void ps_returns_no_container_names_when_no_container_ids_are_found() throws IOException, InterruptedException { + when(dockerComposeExecutor.execute(anyVararg())).thenReturn(dockerComposeExecutedProcess); + when(dockerComposeExecutedProcess.getInputStream()).thenReturn(toInputStream("")); + when(dockerComposeExecutedProcess.exitValue()).thenReturn(0); + + List containerNames = compose.ps(); + assertThat(containerNames, empty()); + verify(dockerComposeExecutor).execute("ps", "-q"); + verifyZeroInteractions(dockerExecutor); + } + + @Test + public void ps_returns_no_container_names_when_no_names_can_be_found_for_container_ids() throws IOException, InterruptedException { + when(dockerComposeExecutor.execute(anyVararg())).thenReturn(dockerComposeExecutedProcess); + when(dockerComposeExecutedProcess.getInputStream()).thenReturn(toInputStream(CONTAINER_ID)); + when(dockerComposeExecutedProcess.exitValue()).thenReturn(0); + + when(dockerExecutor.execute(anyVararg())).thenReturn(dockerExecutedProcess); + when(dockerExecutedProcess.getInputStream()).thenReturn(toInputStream("")); + when(dockerExecutedProcess.exitValue()).thenReturn(0); + + List containerNames = compose.ps(); + assertThat(containerNames, empty()); + verify(dockerComposeExecutor).execute("ps", "-q"); + verify(dockerExecutor).execute( + "ps", "--no-trunc", "--format", "{{.Names}}", "--filter", String.format("id=%s", CONTAINER_ID)); + } + @Test public void call_docker_compose_with_no_colour_flag_on_logs() throws IOException { - when(executedProcess.getInputStream()).thenReturn( + when(dockerComposeExecutedProcess.getInputStream()).thenReturn( toInputStream("docker-compose version 1.7.0, build 1ad8866"), toInputStream("logs")); ByteArrayOutputStream output = new ByteArrayOutputStream(); compose.writeLogs("db", output); - verify(executor).execute("logs", "--no-color", "db"); + verify(dockerComposeExecutor).execute("logs", "--no-color", "db"); assertThat(new String(output.toByteArray(), StandardCharsets.UTF_8), is("logs")); } @Test public void call_docker_compose_with_no_container_on_logs() throws IOException { - reset(executor); + reset(dockerComposeExecutor); final Process mockIdProcess = mock(Process.class); when(mockIdProcess.exitValue()).thenReturn(0); final Process mockVersionProcess = mock(Process.class); when(mockVersionProcess.exitValue()).thenReturn(0); when(mockVersionProcess.getInputStream()).thenReturn(toInputStream("docker-compose version 1.7.0, build 1ad8866")); - when(executor.execute("-v")).thenReturn(mockVersionProcess); - when(executor.execute("logs", "--no-color", "db")).thenReturn(executedProcess); - when(executedProcess.getInputStream()).thenReturn(toInputStream("logs")); + when(dockerComposeExecutor.execute("-v")).thenReturn(mockVersionProcess); + when(dockerComposeExecutor.execute("logs", "--no-color", "db")).thenReturn(dockerComposeExecutedProcess); + when(dockerComposeExecutedProcess.getInputStream()).thenReturn(toInputStream("logs")); ByteArrayOutputStream output = new ByteArrayOutputStream(); compose.writeLogs("db", output); - verify(executor).execute("logs", "--no-color", "db"); + verify(dockerComposeExecutor).execute("logs", "--no-color", "db"); assertThat(new String(output.toByteArray(), StandardCharsets.UTF_8), is("logs")); } @Test public void fail_if_docker_compose_version_is_prior_1_7_on_logs() throws IOException, InterruptedException { - when(executedProcess.getInputStream()).thenReturn(toInputStream("docker-compose version 1.5.6, build 1ad8866")); + when(dockerComposeExecutedProcess.getInputStream()).thenReturn(toInputStream("docker-compose version 1.5.6, build 1ad8866")); exception.expect(IllegalStateException.class); exception.expectMessage("You need at least docker-compose 1.7 to run docker-compose exec"); compose.exec(options("-d"), "container_1", arguments("ls")); @@ -138,7 +229,7 @@ public void fail_if_docker_compose_version_is_prior_1_7_on_logs() @Test public void throw_exception_when_kill_exits_with_a_non_zero_exit_code() throws IOException, InterruptedException { - when(executedProcess.exitValue()).thenReturn(1); + when(dockerComposeExecutedProcess.exitValue()).thenReturn(1); exception.expect(DockerExecutionException.class); exception.expectMessage("'docker-compose kill' returned exit code 1"); compose.kill(); @@ -147,16 +238,16 @@ public void throw_exception_when_kill_exits_with_a_non_zero_exit_code() throws I @Test public void not_throw_exception_when_down_fails_because_the_command_does_not_exist() throws IOException, InterruptedException { - when(executedProcess.exitValue()).thenReturn(1); - when(executedProcess.getInputStream()).thenReturn(toInputStream("No such command: down")); + when(dockerComposeExecutedProcess.exitValue()).thenReturn(1); + when(dockerComposeExecutedProcess.getInputStream()).thenReturn(toInputStream("No such command: down")); compose.down(); } @Test public void throw_exception_when_down_fails_for_a_reason_other_than_the_command_not_being_present() throws IOException, InterruptedException { - when(executedProcess.exitValue()).thenReturn(1); - when(executedProcess.getInputStream()).thenReturn(toInputStream("")); + when(dockerComposeExecutedProcess.exitValue()).thenReturn(1); + when(dockerComposeExecutedProcess.getInputStream()).thenReturn(toInputStream("")); exception.expect(DockerExecutionException.class); @@ -166,20 +257,20 @@ public void throw_exception_when_down_fails_for_a_reason_other_than_the_command_ @Test public void use_the_remove_volumes_flag_when_down_exists() throws IOException, InterruptedException { compose.down(); - verify(executor).execute("down", "--volumes"); + verify(dockerComposeExecutor).execute("down", "--volumes"); } @Test public void parse_the_ps_output_on_ports() throws IOException, InterruptedException { Ports ports = compose.ports("db"); - verify(executor).execute("ps", "db"); + verify(dockerComposeExecutor).execute("ps", "db"); assertThat(ports, is(new Ports(new DockerPort("0.0.0.0", 7000, 7000)))); } @Test public void throw_illegal_state_exception_when_there_is_no_container_found_for_ports() throws IOException, InterruptedException { - when(executedProcess.getInputStream()).thenReturn(toInputStream("")); + when(dockerComposeExecutedProcess.getInputStream()).thenReturn(toInputStream("")); exception.expect(IllegalStateException.class); exception.expectMessage("No container with name 'db' found"); compose.ports("db"); @@ -188,15 +279,15 @@ public void throw_illegal_state_exception_when_there_is_no_container_found_for_p @Test public void pass_concatenated_arguments_to_executor_on_docker_compose_exec() throws IOException, InterruptedException { - when(executedProcess.getInputStream()).thenReturn(toInputStream("docker-compose version 1.7.0rc1, build 1ad8866")); + when(dockerComposeExecutedProcess.getInputStream()).thenReturn(toInputStream("docker-compose version 1.7.0rc1, build 1ad8866")); compose.exec(options("-d"), "container_1", arguments("ls")); - verify(executor, times(1)).execute("exec", "-T", "-d", "container_1", "ls"); + verify(dockerComposeExecutor, times(1)).execute("exec", "-T", "-d", "container_1", "ls"); } @Test public void fail_if_docker_compose_version_is_prior_1_7_on_docker_compose_exec() throws IOException, InterruptedException { - when(executedProcess.getInputStream()).thenReturn(toInputStream("docker-compose version 1.5.6, build 1ad8866")); + when(dockerComposeExecutedProcess.getInputStream()).thenReturn(toInputStream("docker-compose version 1.5.6, build 1ad8866")); exception.expect(IllegalStateException.class); exception.expectMessage("You need at least docker-compose 1.7 to run docker-compose exec"); compose.exec(options("-d"), "container_1", arguments("ls")); @@ -206,13 +297,13 @@ public void fail_if_docker_compose_version_is_prior_1_7_on_docker_compose_exec() public void pass_concatenated_arguments_to_executor_on_docker_compose_run() throws IOException, InterruptedException { compose.run(DockerComposeRunOption.options("-d"), "container_1", DockerComposeRunArgument.arguments("ls")); - verify(executor, times(1)).execute("run", "-d", "container_1", "ls"); + verify(dockerComposeExecutor, times(1)).execute("run", "-d", "container_1", "ls"); } @Test public void return_the_output_from_the_executed_process_on_docker_compose_exec() throws Exception { String lsString = String.format("-rw-r--r-- 1 user 1318458867 11326 Mar 9 17:47 LICENSE%n" - + "-rw-r--r-- 1 user 1318458867 12570 May 12 14:51 README.md"); + + "-rw-r--r-- 1 user 1318458867 12570 May 12 14:51 README.md"); String versionString = "docker-compose version 1.7.0rc1, build 1ad8866"; @@ -221,7 +312,7 @@ public void return_the_output_from_the_executed_process_on_docker_compose_exec() addProcessToExecutor(processExecutor, processWithOutput(versionString), "-v"); addProcessToExecutor(processExecutor, processWithOutput(lsString), "exec", "-T", "container_1", "ls", "-l"); - DockerCompose processCompose = new DefaultDockerCompose(processExecutor, dockerMachine); + DockerCompose processCompose = new DefaultDockerCompose(processExecutor, dockerExecutor, dockerMachine); assertThat(processCompose.exec(options(), "container_1", arguments("ls", "-l")), is(lsString)); } @@ -229,13 +320,13 @@ public void return_the_output_from_the_executed_process_on_docker_compose_exec() @Test public void return_the_output_from_the_executed_process_on_docker_compose_run() throws Exception { String lsString = String.format("-rw-r--r-- 1 user 1318458867 11326 Mar 9 17:47 LICENSE%n" - + "-rw-r--r-- 1 user 1318458867 12570 May 12 14:51 README.md"); + + "-rw-r--r-- 1 user 1318458867 12570 May 12 14:51 README.md"); DockerComposeExecutable processExecutor = mock(DockerComposeExecutable.class); addProcessToExecutor(processExecutor, processWithOutput(lsString), "run", "-it", "container_1", "ls", "-l"); - DockerCompose processCompose = new DefaultDockerCompose(processExecutor, dockerMachine); + DockerCompose processCompose = new DefaultDockerCompose(processExecutor, dockerExecutor, dockerMachine); assertThat(processCompose.run(DockerComposeRunOption.options("-it"), "container_1", DockerComposeRunArgument.arguments("ls", "-l")), is(lsString)); } diff --git a/docker-compose-rule-core/src/test/java/com/palantir/docker/compose/execution/RetryingDockerComposeShould.java b/docker-compose-rule-core/src/test/java/com/palantir/docker/compose/execution/RetryingDockerComposeShould.java index 8f524d70e..d65e079ff 100644 --- a/docker-compose-rule-core/src/test/java/com/palantir/docker/compose/execution/RetryingDockerComposeShould.java +++ b/docker-compose-rule-core/src/test/java/com/palantir/docker/compose/execution/RetryingDockerComposeShould.java @@ -26,7 +26,7 @@ import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; -import com.palantir.docker.compose.TestContainerNames; +import com.google.common.collect.ImmutableList; import com.palantir.docker.compose.connection.ContainerName; import com.palantir.docker.compose.execution.Retryer.RetryableDockerOperation; import java.io.IOException; @@ -38,7 +38,7 @@ public class RetryingDockerComposeShould { private final DockerCompose dockerCompose = mock(DockerCompose.class); private final Retryer retryer = mock(Retryer.class); private final RetryingDockerCompose retryingDockerCompose = new RetryingDockerCompose(retryer, dockerCompose); - private final List someContainerNames = TestContainerNames.of("hey"); + private final List containerNames = ImmutableList.of(ContainerName.fromName("name")); private static final String CONTAINER_NAME = "container"; @Before @@ -68,9 +68,9 @@ public void calls_up_on_the_underlying_docker_compose() throws IOException, Inte @Test public void call_ps_on_the_underlying_docker_compose_and_returns_the_same_value() throws IOException, InterruptedException { - when(dockerCompose.ps()).thenReturn(someContainerNames); + when(dockerCompose.ps()).thenReturn(containerNames); - assertThat(retryingDockerCompose.ps(), is(someContainerNames)); + assertThat(retryingDockerCompose.ps(), is(containerNames)); verifyRetryerWasUsed(); verify(dockerCompose).ps();