Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add feature to choose which services to start with docker-compose up #236

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import com.github.zafarkhaja.semver.Version;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
import com.palantir.docker.compose.configuration.DockerComposeFiles;
import com.palantir.docker.compose.configuration.ProjectName;
import com.palantir.docker.compose.connection.Container;
Expand All @@ -32,6 +33,7 @@
import java.io.IOException;
import java.io.OutputStream;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import org.apache.commons.io.IOUtils;
Expand All @@ -48,20 +50,33 @@ public class DefaultDockerCompose implements DockerCompose {
private final Command command;
private final DockerMachine dockerMachine;
private final DockerComposeExecutable rawExecutable;

private final ImmutableList<String> servicesToStart;

public DefaultDockerCompose(DockerComposeFiles dockerComposeFiles, DockerMachine dockerMachine, ProjectName projectName) {
this(DockerComposeExecutable.builder()
.dockerComposeFiles(dockerComposeFiles)
.dockerConfiguration(dockerMachine)
.projectName(projectName)
.build(), dockerMachine);
.build(), dockerMachine, Collections.emptyList());
}

public DefaultDockerCompose(DockerComposeFiles dockerComposeFiles, DockerMachine dockerMachine, ProjectName projectName, List<String> servicesToStart) {
this(DockerComposeExecutable.builder()
.dockerComposeFiles(dockerComposeFiles)
.dockerConfiguration(dockerMachine)
.projectName(projectName)
.build(), dockerMachine, servicesToStart);
}

public DefaultDockerCompose(DockerComposeExecutable rawExecutable, DockerMachine dockerMachine) {
this(rawExecutable, dockerMachine, Collections.emptyList());
}

public DefaultDockerCompose(DockerComposeExecutable rawExecutable, DockerMachine dockerMachine, List<String> servicesToStart) {
this.rawExecutable = rawExecutable;
this.command = new Command(rawExecutable, log::trace);
this.dockerMachine = dockerMachine;
this.servicesToStart = ImmutableList.copyOf(servicesToStart);
}

@Override
Expand All @@ -76,7 +91,9 @@ public void build() throws IOException, InterruptedException {

@Override
public void up() throws IOException, InterruptedException {
command.execute(Command.throwingOnError(), "up", "-d");
List<String> commands = Lists.newArrayList("up", "-d");
commands.addAll(servicesToStart);
command.execute(Command.throwingOnError(), commands.toArray(new String[0]));
}

@Override
Expand Down Expand Up @@ -188,6 +205,18 @@ public List<String> services() throws IOException, InterruptedException {
return Arrays.asList(servicesOutput.split("(\r|\n)+"));
}

/**
* The names of services to start, via {@code docker-compose up -d SERVICE...}. If empty (the
* default), all services are started. This can be a subset of the services defined in
* the Docker Compose files.
*
* @return the names of services to start
*/
@Override
public List<String> servicesToStart() {
return servicesToStart;
}

/**
* Blocks until all logs collected from the container.
* @return Whether the docker container terminated prior to log collection ending
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,11 @@ public List<String> services() throws IOException, InterruptedException {
return dockerCompose.services();
}

@Override
public List<String> servicesToStart() {
return dockerCompose.servicesToStart();
}

@Override
public boolean writeLogs(String container, OutputStream output) throws IOException {
return dockerCompose.writeLogs(container, output);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ static Version version() throws IOException, InterruptedException {
Optional<String> id(Container container) throws IOException, InterruptedException;
String config() throws IOException, InterruptedException;
List<String> services() throws IOException, InterruptedException;
List<String> servicesToStart();
boolean writeLogs(String container, OutputStream output) throws IOException;
Ports ports(String service) throws IOException, InterruptedException;
}
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,13 @@ public synchronized void startCollecting(DockerCompose dockerCompose) throws IOE
if (serviceNames.size() == 0) {
return;
}

List<String> servicesToStart = dockerCompose.servicesToStart();
if (!servicesToStart.isEmpty()) {
// Services to start up are a subset of all services.
serviceNames = servicesToStart;
}

executor = Executors.newFixedThreadPool(serviceNames.size());
serviceNames.stream().forEachOrdered(service -> this.collectLogs(service, dockerCompose));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,19 +20,23 @@
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.arrayContaining;
import static org.hamcrest.Matchers.arrayContainingInAnyOrder;
import static org.hamcrest.Matchers.empty;
import static org.hamcrest.Matchers.emptyArray;
import static org.hamcrest.core.Is.is;
import static org.junit.Assert.fail;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyString;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
import com.palantir.docker.compose.execution.DockerCompose;
import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import org.apache.commons.io.IOUtils;
Expand Down Expand Up @@ -198,6 +202,29 @@ public void throw_exception_when_trying_to_start_a_started_collector_a_second_ti
logCollector.startCollecting(compose);
}

@Test
public void collect_logs_for_partial_services() throws IOException, InterruptedException {
when(compose.services()).thenReturn(ImmutableList.of("db", "db2"));
when(compose.servicesToStart()).thenReturn(ImmutableList.of("db"));

List<Exception> exceptions = Lists.newArrayList();
CountDownLatch latch = new CountDownLatch(1);
when(compose.writeLogs(anyString(), any(OutputStream.class))).thenAnswer((args) -> {
String container = (String) args.getArguments()[0];
if (!"db".equals(container)) {
exceptions.add(new IOException("Logs shouldn't be written for container: " + container));
} else {
latch.countDown();
}
return true;
});

logCollector.startCollecting(compose);
assertThat(latch.await(1, TimeUnit.SECONDS), is(true));
logCollector.stopCollecting();
assertThat(exceptions, is(empty()));
}

private static File cannotBeCreatedDirectory() {
File cannotBeCreatedDirectory = mock(File.class);
when(cannotBeCreatedDirectory.isFile()).thenReturn(false);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,15 @@ public ProjectName projectName() {
return ProjectName.random();
}

/**
* The names of services to start, via {@code docker-compose up -d SERVICE...}. If empty (the
* default), all services are started. This can be a subset of the services defined in
* the Docker Compose files.
*
* @return the names of services to start
*/
public abstract List<String> servicesToStart();

@Value.Default
public DockerComposeExecutable dockerComposeExecutable() {
return DockerComposeExecutable.builder()
Expand Down Expand Up @@ -96,7 +105,7 @@ public ShutdownStrategy shutdownStrategy() {

@Value.Default
public DockerCompose dockerCompose() {
DockerCompose dockerCompose = new DefaultDockerCompose(dockerComposeExecutable(), machine());
DockerCompose dockerCompose = new DefaultDockerCompose(dockerComposeExecutable(), machine(), servicesToStart());
return new RetryingDockerCompose(retryAttempts(), dockerCompose);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
/*
* 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;

import static com.google.common.base.Throwables.propagate;
import static com.palantir.docker.compose.connection.waiting.HealthChecks.toHaveAllPortsOpen;
import static org.hamcrest.MatcherAssert.assertThat;
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.execution.DockerExecutionException;
import java.io.IOException;
import java.util.List;
import java.util.function.Consumer;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;

public class DockerComposeRulePartialServicesIntegrationTest {

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

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

@Rule
public final DockerComposeRule docker = DockerComposeRule.builder()
.files(DockerComposeFiles.from("src/test/resources/docker-compose.yaml"))
.addServicesToStart("db") // Only start the db container.
.waitingForService("db", toHaveAllPortsOpen())
.build();

// Not a @Rule so that we can call before() and handle the exception.
// db5 is a nonexistent service in docker-compose.yaml.
private final DockerComposeRule invalidDocker = DockerComposeRule.builder()
.files(DockerComposeFiles.from("src/test/resources/docker-compose.yaml"))
.addServicesToStart("db5") // Only start the db container.
.waitingForService("db5", toHaveAllPortsOpen())
.build();

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

@Test
public void should_run_docker_compose_up_for_db_container_only() {
forEachContainer(containerName -> {
Container container = docker.containers().container(containerName);
try {
if ("db".equals(containerName)) {
assertThat(container.state().isUp(), is(true));
assertThat(container.port(5432).isListeningNow(), is(true));
} else {
assertThat(container.state().isUp(), is(false));
}
} catch (IOException | InterruptedException e) {
propagate(e);
}
});
}

@Test
public void after_test_is_executed_the_launched_db_container_is_no_longer_listening() {
docker.after();

forEachContainer(containerName -> {
Container container = docker.containers().container(containerName);
try {
assertThat(container.state().isUp(), is(false));
if ("db".equals(containerName)) {
assertThat(container.port(5432).isListeningNow(), is(false));
}
} catch (IOException | InterruptedException e) {
propagate(e);
}
});
}

@Test
public void should_run_docker_compose_up_for_nonexistent_container() throws IOException, InterruptedException {
exception.expect(DockerExecutionException.class);
exception.expectMessage("No such service: db5");

invalidDocker.before();
}
}