Skip to content

Commit

Permalink
Merge pull request #87 from iamdanfox/deprecate-docker-composition
Browse files Browse the repository at this point in the history
Deprecate DockerComposition class
  • Loading branch information
iamdanfox authored Jul 4, 2016
2 parents 10cfb23 + 58242d1 commit 9a17c3a
Show file tree
Hide file tree
Showing 12 changed files with 111 additions and 79 deletions.
58 changes: 38 additions & 20 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,13 +40,15 @@ dependencies {
For the most basic use simply add a `DockerComposition` object as a `@ClassRule` or `@Rule` in a JUnit test class.

```java
public class DockerCompositionTest {
public class MyIntegrationTest {

@ClassRule
public static DockerComposition composition = DockerComposition.of("src/test/resources/docker-compose.yml").build();
public static DockerComposeRule docker = DockerComposeRule.builder()
.file("src/test/resources/docker-compose.yml")
.build();

@Test
public void testThatDependsOnDockerComposition() throws InterruptedException, IOException {
public void testThatUsesSomeDockerServices() throws InterruptedException, IOException {
...
}

Expand All @@ -71,10 +73,11 @@ Waiting for a service to be available
To wait for services to be available before executing tests use the following methods on the DockerComposition object:

```java
public class DockerCompositionTest {
public class MyEndToEndTest {

@ClassRule
public static DockerComposition composition = DockerComposition.of("src/test/resources/docker-compose.yml")
public static DockerComposeRule docker = DockerComposeRule.builder()
.file("src/test/resources/docker-compose.yml")
.waitingForService("db", HealthChecks.toHaveAllPortsOpen())
.waitingForService("web", HealthChecks.toRespondOverHttp(8080, (port) -> port.inFormat("https://$HOST:$EXTERNAL_PORT")))
.waitingForService("other", (container) -> customServiceCheck(container), Duration.standardMinutes(2))
Expand All @@ -89,8 +92,8 @@ public class DockerCompositionTest {
}
```

The entrypoint method `waitingForService(String container, HealthCheck<Container> check[, Duration timeout])` will make sure the healthcheck passes for that container before the tests start.
The entrypoint method `waitingForServices(List<String> containers, HealthCheck<List<Container>> check[, Duration timeout])` will make sure the healthcheck passes for the cluster of containers before the tests start.
The entrypoint method `waitingForService(String container, HealthCheck<Container> check[, Duration timeout])` will make sure the healthcheck passes for that container before the tests start.
The entrypoint method `waitingForServices(List<String> containers, HealthCheck<List<Container>> check[, Duration timeout])` will make sure the healthcheck passes for the cluster of containers before the tests start.
The entrypoint method `waitingForHostNetworkedPort(int portNumber, HealthCheck<DockerPort> check[, Duration timeout])` will make sure the healthcheck passes for a particular host networked port.

We provide 2 default healthChecks in the HealthChecks class:
Expand All @@ -103,17 +106,30 @@ Accessing services in containers from outside a container

In tests it is likely services inside containers will need to be accessed in order to assert that they are behaving correctly. In addition, when tests run on Mac the Docker contains will be inside a Virtual Box machine and so must be accessed on an external IP address rather than the loopback interface.

It is recommended to only specify internal ports in the `docker-compose.yml` as described in the [https://docs.docker.com/compose/compose-file/#ports](reference). This makes tests independent of the environment on the host machine and of each other.
It is recommended to only specify internal ports in the `docker-compose.yml` as described in the [https://docs.docker.com/compose/compose-file/#ports](reference). This makes tests independent of the environment on the host machine and of each other. Docker will then randomly allocate an external port. For example:

There are then two methods for accessing port information:
```yaml
postgres:
image: postgres:9.5
ports:
- 5432
```
```java
DockerPort portOnContainerWithExternalMapping(String container, int portNumber)
Given a `DockerComposeRule` instance called `docker`, you could then access a service called
`postgres` as follows

DockerPort portOnContainerWithInternalMapping(String container, int portNumber)
```java
DockerPort postgres = docker.containers()
.container("postgres")
.port(5432);
```

In both cases the port in the Docker compose file must be referenced. Using the latter method no external port needs to be declared, this will be allocated by Docker at runtime and the DockerPort object contains the dynamic port and IP assignment.
You could then interpolate the host IP address and random external port as follows:

```java
String url = postgres.inFormat("jdbc:postgresql://$HOST:$EXTERNAL_PORT/mydb");
// e.g. "jdbc:postgresql://192.168.99.100:33045/mydb"
```

Run docker-compose exec
---------------------------------------------------------
Expand All @@ -137,9 +153,10 @@ To record the logs from your containers specify a location:
public class DockerCompositionTest {
@ClassRule
public static DockerComposition composition = DockerComposition.of("src/test/resources/docker-compose.yml")
.saveLogsTo("build/dockerLogs/dockerCompositionTest")
.build();
public static DockerComposeRule docker = DockerComposeRule.builder()
.file("src/test/resources/docker-compose.yml")
.saveLogsTo("build/dockerLogs/dockerCompositionTest")
.build();
@Test
public void testRecordsLogs() throws InterruptedException, IOException {
Expand All @@ -159,9 +176,10 @@ To skip shutdown of containers after tests are finished executing:
```java
public class DockerCompositionTest {
@ClassRule
public static DockerComposition composition = DockerComposition.of("src/test/resources/docker-compose.yml")
.skipShutdown(true)
.build();
public static DockerComposeRule docker = DockerComposeRule.builder()
.file("src/test/resources/docker-compose.yml")
.skipShutdown(true)
.build();
}
```

Expand All @@ -170,7 +188,7 @@ This can shorten iteration time when services take a long time to start. Remembe
Docker Machine
--------------

Docker is able to connect to daemon's that either live on the machine where the client is running, or somewhere remote.
Docker is able to connect to daemons that either live on the machine where the client is running, or somewhere remote.
Using the `docker` client, you are able to control which daemon to connect to using the `DOCKER_HOST` environment
variable.

Expand Down
16 changes: 14 additions & 2 deletions src/main/java/com/palantir/docker/compose/DockerComposition.java
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,14 @@
import java.io.IOException;
import org.junit.rules.ExternalResource;

/**
* @deprecated
* Please use the `DockerComposeRule` class directly instead.
* This class is deprecated because we had to manually implement a builder, the DockerComposeRule has one auto generated by Immutables.
* Note, if you want to make this transition incrementally, you can use `DockerComposition#rule()` to access the underlying
* DockerComposeRule instance.
*/
@Deprecated
public class DockerComposition extends ExternalResource {

private DockerComposeRule rule;
Expand All @@ -43,12 +51,16 @@ public void after() {
rule.after();
}

public DockerComposeRule rule() {
return rule;
}

public DockerPort portOnContainerWithExternalMapping(String container, int portNumber) throws IOException, InterruptedException {
return rule.containers().container(container).portMappedExternallyTo(portNumber);
return rule().containers().container(container).portMappedExternallyTo(portNumber);
}

public DockerPort portOnContainerWithInternalMapping(String container, int portNumber) throws IOException, InterruptedException {
return rule.containers().container(container).portMappedInternallyTo(portNumber);
return rule().containers().container(container).port(portNumber);
}

public static DockerCompositionBuilder of(String dockerComposeFile) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,11 @@
import java.util.List;
import org.joda.time.Duration;

/**
* @deprecated
* Please use `DockerComposeRule.builder()` instead, it has all the same methods.
*/
@Deprecated
public class DockerCompositionBuilder {
private final Builder builder;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ public String getContainerName() {

public SuccessOrFailure portIsListeningOnHttp(int internalPort, Function<DockerPort, String> urlFunction) {
try {
DockerPort port = portMappedInternallyTo(internalPort);
DockerPort port = port(internalPort);
if (!port.isListeningNow()) {
return SuccessOrFailure.failure(internalPort + " is not listening");
}
Expand All @@ -65,7 +65,15 @@ public DockerPort portMappedExternallyTo(int externalPort) {
.orElseThrow(() -> new IllegalArgumentException("No port mapped externally to '" + externalPort + "' for container '" + containerName + "'"));
}

/**
* @deprecated Please use `port(internalPort)` instead.
*/
@Deprecated
public DockerPort portMappedInternallyTo(int internalPort) {
return port(internalPort);
}

public DockerPort port(int internalPort) {
return portMappings.get()
.stream()
.filter(port -> port.getInternalPort() == internalPort)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ public boolean isHttpResponding(Function<DockerPort, String> urlFunction) {
* <ul>
* <li>$HOST - the hostname/ip address of the docker port</li>
* <li>$EXTERNAL_PORT - the external version of the docker port</li>
* <li>$INTERNAL_PORT - the internal versino of the docker port</li>
* <li>$INTERNAL_PORT - the internal version of the docker port</li>
* </ul>
*
* @param format a format string using the substitutions listed above
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,10 @@ public class DockerComposeRuleRestartContainersIntegrationTest {

@Test
public void test_docker_compose_rule_fails_with_existing_containers() throws IOException, InterruptedException {
DockerComposition composition = DockerComposition.of(DOCKER_COMPOSE_YAML_PATH).build();
DockerComposeRule composition = DockerComposeRule.builder().file(DOCKER_COMPOSE_YAML_PATH).build();
composition.before();
composition = DockerComposition.of(DOCKER_COMPOSE_YAML_PATH)
composition = DockerComposeRule.builder()
.file(DOCKER_COMPOSE_YAML_PATH)
.removeConflictingContainersOnStartup(false)
.build();

Expand All @@ -43,10 +44,10 @@ public void test_docker_compose_rule_fails_with_existing_containers() throws IOE

@Test
public void test_docker_compose_rule_removes_existing_containers() throws IOException, InterruptedException {
DockerComposition composition = DockerComposition.of(DOCKER_COMPOSE_YAML_PATH).build();
DockerComposeRule composition = DockerComposeRule.builder().file(DOCKER_COMPOSE_YAML_PATH).build();
composition.before();

composition = DockerComposition.of(DOCKER_COMPOSE_YAML_PATH).build();
composition = DockerComposeRule.builder().file(DOCKER_COMPOSE_YAML_PATH).build();
composition.before();
composition.after();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,7 @@ public void retrieve_port_for_container_by_internal_mapping() throws IOException
DockerPort expectedPort = env.port("db", IP, 5433, 5432);
withComposeExecutableReturningContainerFor("db");

@SuppressWarnings("deprecation") // intentionally using the deprecated method temporarily
DockerPort actualPort = rule.containers().container("db").portMappedInternallyTo(5432);

assertThat(actualPort, is(expectedPort));
Expand All @@ -172,8 +173,8 @@ public void execute_ps_once_when_two_external_ports_on_a_container_are_requested
env.ports("db", IP, 5432, 8080);
withComposeExecutableReturningContainerFor("db");

rule.containers().container("db").portMappedInternallyTo(5432);
rule.containers().container("db").portMappedInternallyTo(8080);
rule.containers().container("db").port(5432);
rule.containers().container("db").port(8080);

verify(dockerCompose, times(1)).ports("db");
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@
import static com.palantir.docker.compose.connection.waiting.HealthChecks.toHaveAllPortsOpen;
import static com.palantir.docker.compose.execution.DockerComposeExecArgument.arguments;
import static com.palantir.docker.compose.execution.DockerComposeExecOption.options;

import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.core.Is.is;

Expand All @@ -32,7 +31,6 @@
import java.io.IOException;
import java.util.List;
import java.util.function.Consumer;

import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
Expand All @@ -43,16 +41,14 @@ public class DockerCompositionIntegrationTest {

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

private final DockerComposeRule rule = DockerComposeRule.builder()
@Rule
public final DockerComposeRule docker = 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 = new DockerComposition(rule);

private HealthCheck<List<Container>> toAllHaveAllPortsOpen() {
return containers -> {
boolean healthy = containers.stream()
Expand All @@ -64,55 +60,43 @@ private HealthCheck<List<Container>> toAllHaveAllPortsOpen() {
}

@Rule
public ExpectedException exception = ExpectedException.none();
public TemporaryFolder logFolder = new TemporaryFolder();

@Rule
public TemporaryFolder logFolder = new TemporaryFolder();
public ExpectedException exception = ExpectedException.none();

private void forEachContainer(Consumer<String> consumer) {
CONTAINERS.forEach(consumer);
}

@Test
public void should_run_docker_compose_up_using_the_specified_docker_compose_file_to_bring_postgres_up() throws InterruptedException, IOException {
forEachContainer((container) -> {
try {
assertThat(composition.portOnContainerWithExternalMapping("db", 5442).isListeningNow(), is(true));
} catch (IOException | InterruptedException e) {
propagate(e);
}
forEachContainer(container -> {
assertThat(docker.containers().container(container).port(5432).isListeningNow(), is(true));
});
}

@Test
public void after_test_is_executed_the_launched_postgres_container_is_no_longer_listening() throws IOException, InterruptedException {
composition.after();
docker.after();

forEachContainer(container -> {
try {
assertThat(composition.portOnContainerWithInternalMapping("db", 5432).isListeningNow(), is(false));
} catch (IOException | InterruptedException e) {
propagate(e);
}
assertThat(docker.containers().container(container).port(5432).isListeningNow(), is(false));
});
}

@Test
public void can_access_external_port_for_internal_port_of_machine() throws IOException, InterruptedException {
forEachContainer(container -> {
try {
assertThat(composition.portOnContainerWithInternalMapping("db", 5432).isListeningNow(), is(true));
} catch (IOException | InterruptedException e) {
propagate(e);
}
assertThat(docker.containers().container(container).port(5432).isListeningNow(), is(true));
});
}

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

container.stop();
assertThat(container.state(), is(State.Exit));
Expand All @@ -129,7 +113,7 @@ public void can_stop_and_start_containers() {
public void stop_can_be_run_on_stopped_container() {
forEachContainer(containerName -> {
try {
Container container = rule.containers().container(containerName);
Container container = docker.containers().container(containerName);

container.stop();
assertThat(container.state(), is(State.Exit));
Expand All @@ -145,7 +129,7 @@ public void stop_can_be_run_on_stopped_container() {
public void start_can_be_run_on_running_container() {
forEachContainer(containerName -> {
try {
Container container = rule.containers().container(containerName);
Container container = docker.containers().container(containerName);

container.start();
assertThat(container.state(), is(State.Up));
Expand All @@ -158,7 +142,7 @@ public void start_can_be_run_on_running_container() {
@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 {
assertThat(composition.exec(options(), CONTAINERS.get(0), arguments("echo", "hello")), is("hello"));
assertThat(docker.exec(options(), CONTAINERS.get(0), arguments("echo", "hello")), is("hello"));
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@

public class HostNetworkedPortsIntegrationTest {
@Rule
public DockerComposition composition = DockerComposition.of("src/test/resources/host-networked-docker-compose.yaml")
public DockerComposeRule docker = DockerComposeRule.builder()
.file("src/test/resources/host-networked-docker-compose.yaml")
.waitingForHostNetworkedPort(5432, toBeOpen())
.build();

Expand All @@ -36,7 +37,7 @@ private HealthCheck<DockerPort> toBeOpen() {

@Test public void
can_access_host_networked_ports() {
assertThat(composition.hostNetworkedPort(5432).getInternalPort(), is(5432));
assertThat(composition.hostNetworkedPort(5432).getExternalPort(), is(5432));
assertThat(docker.hostNetworkedPort(5432).getInternalPort(), is(5432));
assertThat(docker.hostNetworkedPort(5432).getExternalPort(), is(5432));
}
}
Loading

0 comments on commit 9a17c3a

Please sign in to comment.