Skip to content
Closed
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 @@ -77,6 +77,7 @@ public class DockerComposeContainer<SELF extends DockerComposeContainer<SELF>> e
private final List<File> composeFiles;
private Set<ParsedDockerComposeFile> parsedComposeFiles;
private final Map<String, Integer> scalingPreferences = new HashMap<>();
private final Map<String, String> customContainerNames = new HashMap<>();
private DockerClient dockerClient;
private boolean localCompose;
private boolean pull = true;
Expand Down Expand Up @@ -124,6 +125,8 @@ public DockerComposeContainer(String identifier, List<File> composeFiles) {

this.composeFiles = composeFiles;
this.parsedComposeFiles = composeFiles.stream().map(ParsedDockerComposeFile::new).collect(Collectors.toSet());
this.parsedComposeFiles
.forEach(composeFile -> this.customContainerNames.putAll(composeFile.getContainerNames()));

// Use a unique identifier so that containers created for this compose environment can be identified
this.identifier = identifier;
Expand Down Expand Up @@ -302,12 +305,19 @@ private void registerContainersForShutdown() {

@VisibleForTesting
List<Container> listChildContainers() {
Set<String> names = customContainerNames.values()
.stream()
.map(name -> String.format("/%s", name))
.collect(Collectors.toSet());
return dockerClient.listContainersCmd()
.withShowAll(true)
.exec().stream()
.filter(container -> Arrays.stream(container.getNames()).anyMatch(name ->
name.startsWith("/" + project)))
.collect(toList());
.withShowAll(true)
.exec().stream()
.filter(
container -> Arrays.stream(container.getNames()).anyMatch(name ->
names.contains(name) || name.startsWith("/" + project)
)
)
.collect(toList());
}

private void startAmbassadorContainers() {
Expand Down Expand Up @@ -370,7 +380,8 @@ public SELF withExposedService(String serviceName, int servicePort, @NonNull Wai
int ambassadorPort = nextAmbassadorPort.getAndIncrement();
ambassadorPortMappings.computeIfAbsent(serviceInstanceName, __ -> new ConcurrentHashMap<>()).put(servicePort, ambassadorPort);
ambassadorContainer.withTarget(ambassadorPort, serviceInstanceName, servicePort);
ambassadorContainer.addLink(new FutureContainer(this.project + "_" + serviceInstanceName), serviceInstanceName);
String containerName = getContainerName(serviceInstanceName);
ambassadorContainer.addLink(new FutureContainer(containerName), serviceInstanceName);
addWaitStrategy(serviceInstanceName, waitStrategy);
return self();
}
Expand All @@ -383,6 +394,17 @@ private String getServiceInstanceName(String serviceName) {
return serviceInstanceName;
}

private String getContainerName(String serviceName) {
String serviceInstanceName = serviceName;
if (serviceInstanceName.matches(".*_[0-9]+")) {
serviceName = serviceInstanceName.substring(0, serviceInstanceName.indexOf('_'));
if (customContainerNames.containsKey(serviceName)) {
return customContainerNames.get(serviceName);
}
}
return String.format("%s_%s", project, serviceInstanceName);
}

/*
* can have multiple wait strategies for a single container, e.g. if waiting on several ports
* if no wait strategy is defined, the WaitAllStrategy will return immediately.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import java.io.FileInputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
Expand All @@ -30,6 +31,8 @@ class ParsedDockerComposeFile {

@Getter
private Set<String> dependencyImageNames = new HashSet<>();
@Getter
private Map<String, String> containerNames = new HashMap<>();

ParsedDockerComposeFile(File composeFile) {
Yaml yaml = new Yaml();
Expand Down Expand Up @@ -86,19 +89,15 @@ private void parseAndValidate() {

final Map serviceDefinitionMap = (Map) serviceDefinition;

validateNoContainerNameSpecified(serviceName, serviceDefinitionMap);
findContainerName(serviceName, serviceDefinitionMap);
findServiceImageName(serviceDefinitionMap);
findImageNamesInDockerfile(serviceDefinitionMap);
}
}

private void validateNoContainerNameSpecified(String serviceName, Map serviceDefinitionMap) {
private void findContainerName(String serviceName, Map serviceDefinitionMap) {
if (serviceDefinitionMap.containsKey("container_name")) {
throw new IllegalStateException(String.format(
"Compose file %s has 'container_name' property set for service '%s' but this property is not supported by Testcontainers, consider removing it",
composeFileName,
serviceName
));
this.containerNames.put(serviceName, (String) serviceDefinitionMap.get("container_name"));
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,39 +14,9 @@
public class ParsedDockerComposeFileValidationTest {

@Test
public void shouldValidate() {
public void shouldAcceptContainerName() {
File file = new File("src/test/resources/docker-compose-container-name-v1.yml");
Assertions
.assertThatThrownBy(() -> {
new ParsedDockerComposeFile(file);
})
.hasMessageContaining(file.getAbsolutePath())
.hasMessageContaining("'container_name' property set for service 'redis'");
}

@Test
public void shouldRejectContainerNameV1() {
Assertions
.assertThatThrownBy(() -> {
new ParsedDockerComposeFile(ImmutableMap.of(
"redis", ImmutableMap.of("container_name", "redis")
));
})
.hasMessageContaining("'container_name' property set for service 'redis'");
}

@Test
public void shouldRejectContainerNameV2() {
Assertions
.assertThatThrownBy(() -> {
new ParsedDockerComposeFile(ImmutableMap.of(
"version", "2",
"services", ImmutableMap.of(
"redis", ImmutableMap.of("container_name", "redis")
)
));
})
.hasMessageContaining("'container_name' property set for service 'redis'");
ParsedDockerComposeFile parsedDockerComposeFile = new ParsedDockerComposeFile(file);
}

@Test
Expand Down Expand Up @@ -99,4 +69,5 @@ public void shouldObtainImageFromDockerfileBuildWithContext() {
ParsedDockerComposeFile parsedFile = new ParsedDockerComposeFile(file);
assertEquals("all defined images are found", Sets.newHashSet("redis", "mysql", "alpine:3.2"), parsedFile.getDependencyImageNames()); // redis, mysql from compose file, alpine:3.2 from Dockerfile build
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package org.testcontainers.junit;

import org.junit.Rule;
import org.junit.Test;
import org.testcontainers.containers.ContainerState;
import org.testcontainers.containers.DockerComposeContainer;

import java.io.File;
import java.util.Optional;

import static org.junit.Assert.assertTrue;
import static org.rnorth.visibleassertions.VisibleAssertions.assertEquals;
import static org.rnorth.visibleassertions.VisibleAssertions.assertNotNull;


public class DockerComposeContainerWithNameTest extends BaseDockerComposeTest {
private final String REDIS_SERVICE_NAME = "redis";
private final String REDIS_INSTANCE_NAME = "redis_1";
private static final String DB_INSTANCE_NAME = "db_1";
private static final int DB_PORT = 3306;

@Rule
public DockerComposeContainer environment =
new DockerComposeContainer(new File("src/test/resources/compose-test-with-name.yml"))
.withExposedService(REDIS_INSTANCE_NAME, REDIS_PORT)
.withExposedService(DB_INSTANCE_NAME, DB_PORT);

@Override
public DockerComposeContainer getEnvironment() {
return environment;
}

@Test
public void testGetServicePort() {
int serviceWithInstancePort = environment.getServicePort(REDIS_INSTANCE_NAME, REDIS_PORT);
assertNotNull("Port is set for service with instance number", serviceWithInstancePort);
int serviceWithoutInstancePort = environment.getServicePort(REDIS_SERVICE_NAME, REDIS_PORT);
assertNotNull("Port is set for service with instance number", serviceWithoutInstancePort);
assertEquals("Service ports are the same", serviceWithInstancePort, serviceWithoutInstancePort);
}

@Test
public void containerNamesShouldBeCorrect() {
String containerName = "/redis_container";
Optional<ContainerState> result = environment.getContainerByServiceName(REDIS_INSTANCE_NAME);
assertTrue(String.format("container should be found for service %s", REDIS_INSTANCE_NAME), result.isPresent());
ContainerState state = result.get();
assertEquals("container name should be same as compose file", containerName, state.getContainerInfo().getName());

result = environment.getContainerByServiceName(DB_INSTANCE_NAME);
assertTrue(String.format("container should be found for service %s", DB_INSTANCE_NAME), result.isPresent());
state = result.get();
assertTrue("container name should contain service name", state.getContainerInfo().getName().contains(DB_INSTANCE_NAME));
}
}
7 changes: 7 additions & 0 deletions core/src/test/resources/compose-test-with-name.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
redis:
image: redis
container_name: redis_container
db:
image: mysql:5.7.22
environment:
MYSQL_RANDOM_ROOT_PASSWORD: "true"