Skip to content

Commit

Permalink
Merge pull request #82 from jkozlowski/feature/start-stop-container
Browse files Browse the repository at this point in the history
Implement start/stop/state for containers.
  • Loading branch information
iamdanfox authored Jul 4, 2016
2 parents 203d87d + fd7cb3e commit 10cfb23
Show file tree
Hide file tree
Showing 8 changed files with 227 additions and 12 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,9 @@
import java.util.Objects;
import java.util.function.Function;
import java.util.stream.Collectors;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class Container {

private static final Logger log = LoggerFactory.getLogger(Container.class);

private final String containerName;
private final DockerCompose dockerComposeProcess;

Expand Down Expand Up @@ -77,6 +73,18 @@ public DockerPort portMappedInternallyTo(int internalPort) {
.orElseThrow(() -> new IllegalArgumentException("No internal port '" + internalPort + "' for container '" + containerName + "'"));
}

public void start() throws IOException, InterruptedException {
dockerComposeProcess.start(this);
}

public void stop() throws IOException, InterruptedException {
dockerComposeProcess.stop(this);
}

public State state() throws IOException, InterruptedException {
return dockerComposeProcess.state(containerName);
}

private Ports getDockerPorts() {
try {
return dockerComposeProcess.ports(containerName);
Expand Down
36 changes: 36 additions & 0 deletions src/main/java/com/palantir/docker/compose/connection/State.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/*
* 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 com.google.common.base.Preconditions;
import com.google.common.base.Strings;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public enum State {
Up, Exit;

private static final Pattern STATE_PATTERN = Pattern.compile("(Up|Exit)");
private static final int STATE_INDEX = 1;

public static State parseFromDockerComposePs(String psOutput) {
Preconditions.checkArgument(!Strings.isNullOrEmpty(psOutput), "No container found");
Matcher matcher = STATE_PATTERN.matcher(psOutput);
Preconditions.checkState(matcher.find(), "Could not parse status: %s", psOutput);
String matchedStatus = matcher.group(STATE_INDEX);
return valueOf(matchedStatus);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
import com.palantir.docker.compose.connection.ContainerNames;
import com.palantir.docker.compose.connection.DockerMachine;
import com.palantir.docker.compose.connection.Ports;
import com.palantir.docker.compose.connection.State;
import java.io.IOException;
import java.io.OutputStream;
import org.apache.commons.io.IOUtils;
Expand Down Expand Up @@ -84,6 +85,16 @@ public void rm() throws IOException, InterruptedException {
command.execute(Command.throwingOnError(), "rm", "--force", "-v");
}

@Override
public void start(Container container) throws IOException, InterruptedException {
command.execute(Command.throwingOnError(), "start", container.getContainerName());
}

@Override
public void stop(Container container) throws IOException, InterruptedException {
command.execute(Command.throwingOnError(), "stop", container.getContainerName());
}

@Override
public String exec(DockerComposeExecOption dockerComposeExecOption, String containerName,
DockerComposeExecArgument dockerComposeExecArgument) throws IOException, InterruptedException {
Expand Down Expand Up @@ -148,9 +159,12 @@ private Process followLogs(String container) throws IOException, InterruptedExce

@Override
public Ports ports(String service) throws IOException, InterruptedException {
String psOutput = command.execute(Command.throwingOnError(), "ps", service);
validState(!Strings.isNullOrEmpty(psOutput), "No container with name '" + service + "' found");
return Ports.parseFromDockerComposePs(psOutput, dockerMachine.getIp());
return Ports.parseFromDockerComposePs(psOutput(service), dockerMachine.getIp());
}

@Override
public State state(String service) throws IOException, InterruptedException {
return State.parseFromDockerComposePs(psOutput(service));
}

private ErrorHandler swallowingDownCommandDoesNotExist() {
Expand All @@ -169,4 +183,9 @@ private boolean downCommandWasPresent(String output) {
return !output.contains("No such command");
}

private String psOutput(String service) throws IOException, InterruptedException {
String psOutput = command.execute(Command.throwingOnError(), "ps", service);
validState(!Strings.isNullOrEmpty(psOutput), "No container with name '" + service + "' found");
return psOutput;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import com.palantir.docker.compose.connection.Container;
import com.palantir.docker.compose.connection.ContainerNames;
import com.palantir.docker.compose.connection.Ports;
import com.palantir.docker.compose.connection.State;
import java.io.IOException;
import java.io.OutputStream;

Expand Down Expand Up @@ -53,6 +54,16 @@ public void rm() throws IOException, InterruptedException {
dockerCompose.rm();
}

@Override
public void start(Container container) throws IOException, InterruptedException {
dockerCompose.start(container);
}

@Override
public void stop(Container container) throws IOException, InterruptedException {
dockerCompose.stop(container);
}

@Override
public String exec(DockerComposeExecOption dockerComposeExecOption, String containerName,
DockerComposeExecArgument dockerComposeExecArgument) throws IOException, InterruptedException {
Expand All @@ -79,6 +90,11 @@ public Ports ports(String service) throws IOException, InterruptedException {
return dockerCompose.ports(service);
}

@Override
public State state(String service) throws IOException, InterruptedException {
return dockerCompose.state(service);
}

protected final DockerCompose getDockerCompose() {
return dockerCompose;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import com.palantir.docker.compose.connection.Container;
import com.palantir.docker.compose.connection.ContainerNames;
import com.palantir.docker.compose.connection.Ports;
import com.palantir.docker.compose.connection.State;
import java.io.IOException;
import java.io.OutputStream;

Expand All @@ -27,9 +28,12 @@ public interface DockerCompose {
void down() throws IOException, InterruptedException;
void kill() throws IOException, InterruptedException;
void rm() throws IOException, InterruptedException;
void start(Container container) throws IOException, InterruptedException;
void stop(Container container) throws IOException, InterruptedException;
String exec(DockerComposeExecOption dockerComposeExecOption, String containerName, DockerComposeExecArgument dockerComposeExecArgument) throws IOException, InterruptedException;
ContainerNames ps() throws IOException, InterruptedException;
Container container(String containerName);
boolean writeLogs(String container, OutputStream output) throws IOException;
Ports ports(String service) throws IOException, InterruptedException;
State state(String service) throws IOException, InterruptedException;
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,9 @@
import static org.hamcrest.core.Is.is;

import com.google.common.collect.ImmutableList;
import com.palantir.docker.compose.configuration.DockerComposeFiles;
import com.palantir.docker.compose.connection.Container;
import com.palantir.docker.compose.connection.State;
import com.palantir.docker.compose.connection.waiting.HealthCheck;
import com.palantir.docker.compose.connection.waiting.SuccessOrFailure;
import java.io.IOException;
Expand All @@ -41,12 +43,15 @@ public class DockerCompositionIntegrationTest {

private static final List<String> CONTAINERS = ImmutableList.of("db", "db2", "db3", "db4");

private final DockerComposeRule rule = DockerComposeRule.builder()
.files(DockerComposeFiles.from("src/test/resources/docker-compose.yaml"))
.waitingForService("db", toHaveAllPortsOpen())
.waitingForService("db2", toHaveAllPortsOpen())
.waitingForServices(ImmutableList.of("db3", "db4"), toAllHaveAllPortsOpen())
.build();

@Rule
public DockerComposition composition = DockerComposition.of("src/test/resources/docker-compose.yaml")
.waitingForService("db", toHaveAllPortsOpen())
.waitingForService("db2", toHaveAllPortsOpen())
.waitingForServices(ImmutableList.of("db3", "db4"), toAllHaveAllPortsOpen())
.build();
public DockerComposition composition = new DockerComposition(rule);

private HealthCheck<List<Container>> toAllHaveAllPortsOpen() {
return containers -> {
Expand All @@ -60,6 +65,7 @@ private HealthCheck<List<Container>> toAllHaveAllPortsOpen() {

@Rule
public ExpectedException exception = ExpectedException.none();

@Rule
public TemporaryFolder logFolder = new TemporaryFolder();

Expand Down Expand Up @@ -102,6 +108,53 @@ public void can_access_external_port_for_internal_port_of_machine() throws IOExc
});
}

@Test
public void can_stop_and_start_containers() {
forEachContainer(containerName -> {
try {
Container container = rule.containers().container(containerName);

container.stop();
assertThat(container.state(), is(State.Exit));

container.start();
assertThat(container.state(), is(State.Up));
} catch (IOException | InterruptedException e) {
propagate(e);
}
});
}

@Test
public void stop_can_be_run_on_stopped_container() {
forEachContainer(containerName -> {
try {
Container container = rule.containers().container(containerName);

container.stop();
assertThat(container.state(), is(State.Exit));

container.stop();
} catch (IOException | InterruptedException e) {
propagate(e);
}
});
}

@Test
public void start_can_be_run_on_running_container() {
forEachContainer(containerName -> {
try {
Container container = rule.containers().container(containerName);

container.start();
assertThat(container.state(), is(State.Up));
} catch (IOException | InterruptedException e) {
propagate(e);
}
});
}

@Ignore // This test will not run on Circle CI because it does not currently support docker-compose exec.
@Test
public void exec_returns_output() throws Exception {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
/*
* 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 org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.core.Is.is;

import java.io.IOException;

import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;

public class StateShould {

@Rule
public final ExpectedException exception = ExpectedException.none();

@Test
public void parse_actual_docker_compose_output_when_state_is_Up() throws IOException, InterruptedException {
String psOutput =
" Name Command State Ports \n"
+ "-------------------------------------------------------------------------------------------------------------------------------------------------\n"
+ "magritte_magritte_1 /bin/sh -c /usr/local/bin/ ... Up 0.0.0.0:7000->7000/tcp, 7001/tcp, 7002/tcp, 7003/tcp, 7004/tcp, 7005/tcp, 7006/tcp \n"
+ "";
State state = State.parseFromDockerComposePs(psOutput);
assertThat(state, is(State.Up));
}

@Test
public void parse_actual_docker_compose_output_when_state_is_Exit() throws IOException, InterruptedException {
String psOutput =
" Name Command State Ports \n"
+ "-------------------------------------------------------------------------------------------------------------------------------------------------\n"
+ "magritte_magritte_1 /bin/sh -c /usr/local/bin/ ... Exit 0.0.0.0:7000->7000/tcp, 7001/tcp, 7002/tcp, 7003/tcp, 7004/tcp, 7005/tcp, 7006/tcp \n"
+ "";
State state = State.parseFromDockerComposePs(psOutput);
assertThat(state, is(State.Exit));
}

@Test
public void throw_on_unknown_state() throws IOException, InterruptedException {
String psOutput =
" Name Command State Ports \n"
+ "-------------------------------------------------------------------------------------------------------------------------------------------------\n"
+ "magritte_magritte_1 /bin/sh -c /usr/local/bin/ ... WhatIsThis 0.0.0.0:7000->7000/tcp, 7001/tcp, 7002/tcp, 7003/tcp, 7004/tcp, 7005/tcp, 7006/tcp \n"
+ "";
exception.expect(IllegalStateException.class);
State.parseFromDockerComposePs(psOutput);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import com.palantir.docker.compose.connection.Container;
import com.palantir.docker.compose.connection.ContainerNames;
import com.palantir.docker.compose.connection.DockerMachine;
import com.palantir.docker.compose.connection.DockerPort;
Expand All @@ -48,13 +49,15 @@ public class DockerComposeShould {
private final DockerCompose compose = new DefaultDockerCompose(executor, dockerMachine);

private final Process executedProcess = mock(Process.class);
private final Container container = mock(Container.class);

@Before
public void setup() throws IOException, InterruptedException {
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(container.getContainerName()).thenReturn("my-container");
}

@Test
Expand All @@ -69,6 +72,18 @@ public void call_docker_compose_rm_with_force_and_volume_flags_on_rm() throws IO
verify(executor).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");
}

@Test
public void call_docker_compose_start_on_start() throws IOException, InterruptedException {
compose.start(container);
verify(executor).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"));
Expand Down

0 comments on commit 10cfb23

Please sign in to comment.