diff --git a/core/src/main/java/org/testcontainers/containers/ComposeContainer.java b/core/src/main/java/org/testcontainers/containers/ComposeContainer.java index 79d706d3b6a..1ba2552ef7a 100644 --- a/core/src/main/java/org/testcontainers/containers/ComposeContainer.java +++ b/core/src/main/java/org/testcontainers/containers/ComposeContainer.java @@ -69,28 +69,85 @@ public class ComposeContainer extends FailureDetectingExternalResource implement private List filesInDirectory = new ArrayList<>(); + /** + * Creates a new ComposeContainer with a random identifier using the specified Docker image and compose files. + * + * @param image The Docker image to use for the container + * @param composeFiles One or more Docker Compose configuration files + */ + public ComposeContainer(DockerImageName image, File... composeFiles) { + this(image, Arrays.asList(composeFiles)); + } + + /** + * Creates a new ComposeContainer with a random identifier using the specified Docker image and compose files. + * + * @param image The Docker image to use for the container + * @param composeFiles A list of Docker Compose configuration files + */ + public ComposeContainer(DockerImageName image, List composeFiles) { + this(image, Base58.randomString(6).toLowerCase(), composeFiles); + } + + /** + * Creates a new ComposeContainer with the specified Docker image, identifier, and compose files. + * + * @param image The Docker image to use for the container + * @param identifier A unique identifier for this compose environment + * @param composeFiles One or more Docker Compose configuration files + */ + public ComposeContainer(DockerImageName image, String identifier, File... composeFiles) { + this(image, identifier, Arrays.asList(composeFiles)); + } + + /** + * Creates a new ComposeContainer with the specified Docker image, identifier, and compose files. + * This is the primary constructor that all other constructors delegate to. + * + * @param image The Docker image to use for the container + * @param identifier A unique identifier for this compose environment + * @param composeFiles A list of Docker Compose configuration files + */ + public ComposeContainer(DockerImageName image, String identifier, List composeFiles) { + this.composeDelegate = + new ComposeDelegate(ComposeDelegate.ComposeVersion.V2, composeFiles, identifier, COMPOSE_EXECUTABLE, image); + this.project = this.composeDelegate.getProject(); + } + + /** + * @deprecated + * Use the new constructor ComposeContainer(DockerImageName image, File... composeFiles) + */ + @Deprecated public ComposeContainer(File... composeFiles) { - this(Arrays.asList(composeFiles)); + this(DEFAULT_IMAGE_NAME, Arrays.asList(composeFiles)); } + /** + * @deprecated + * Use the new constructor ComposeContainer(DockerImageName image,List composeFiles) + */ + @Deprecated public ComposeContainer(List composeFiles) { - this(Base58.randomString(6).toLowerCase(), composeFiles); + this(DEFAULT_IMAGE_NAME, composeFiles); } + /** + * @deprecated + * Use the new constructor ComposeContainer(DockerImageName image, String identifier, File... composeFile) + */ + @Deprecated public ComposeContainer(String identifier, File... composeFiles) { - this(identifier, Arrays.asList(composeFiles)); + this(DEFAULT_IMAGE_NAME, identifier, Arrays.asList(composeFiles)); } + /** + * @deprecated + * Use the new constructor ComposeContainer(DockerImageName image,String identifier, List composeFiles) + */ + @Deprecated public ComposeContainer(String identifier, List composeFiles) { - this.composeDelegate = - new ComposeDelegate( - ComposeDelegate.ComposeVersion.V2, - composeFiles, - identifier, - COMPOSE_EXECUTABLE, - DEFAULT_IMAGE_NAME - ); - this.project = this.composeDelegate.getProject(); + this(DEFAULT_IMAGE_NAME, identifier, composeFiles); } @Override diff --git a/core/src/main/java/org/testcontainers/containers/DockerComposeContainer.java b/core/src/main/java/org/testcontainers/containers/DockerComposeContainer.java index 92dc75b6cde..571d5a96398 100644 --- a/core/src/main/java/org/testcontainers/containers/DockerComposeContainer.java +++ b/core/src/main/java/org/testcontainers/containers/DockerComposeContainer.java @@ -18,6 +18,7 @@ import java.time.Duration; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; @@ -62,31 +63,99 @@ public class DockerComposeContainer> public static final String COMPOSE_EXECUTABLE = SystemUtils.IS_OS_WINDOWS ? "docker-compose.exe" : "docker-compose"; - private static final DockerImageName DEFAULT_IMAGE_NAME = DockerImageName.parse("docker/compose:1.29.2"); - private final ComposeDelegate composeDelegate; private String project; private List filesInDirectory = new ArrayList<>(); + /** + * Creates a new DockerComposeContainer with the specified Docker image, identifier, and a single compose file. + * + * @param image The Docker image to use for the container + * @param identifier A unique identifier for this compose environment + * @param composeFile A Docker Compose configuration file + */ + public DockerComposeContainer(DockerImageName image, String identifier, File composeFile) { + this(image, identifier, Collections.singletonList(composeFile)); + } + + /** + * Creates a new DockerComposeContainer with a random identifier using the specified Docker image and compose files. + * + * @param image The Docker image to use for the container + * @param composeFiles A list of Docker Compose configuration files + */ + public DockerComposeContainer(DockerImageName image, List composeFiles) { + this(image, Base58.randomString(6).toLowerCase(), composeFiles); + } + + /** + * Creates a new DockerComposeContainer with the specified Docker image, identifier, and compose files. + * + * @param image The Docker image to use for the container + * @param identifier A unique identifier for this compose environment + * @param composeFiles One or more Docker Compose configuration files + */ + public DockerComposeContainer(DockerImageName image, String identifier, File... composeFiles) { + this(image, identifier, Arrays.asList(composeFiles)); + } + + /** + * Creates a new DockerComposeContainer with the specified Docker image, identifier, and compose files. + * This is the primary constructor that all other constructors delegate to. + * + * @param image The Docker image to use for the container + * @param identifier A unique identifier for this compose environment + * @param composeFiles A list of Docker Compose configuration files + */ + public DockerComposeContainer(DockerImageName image, String identifier, List composeFiles) { + this.composeDelegate = + new ComposeDelegate(ComposeDelegate.ComposeVersion.V1, composeFiles, identifier, COMPOSE_EXECUTABLE, image); + this.project = this.composeDelegate.getProject(); + } + + /** + * @deprecated + * Use the new constructor DockerComposeContainer(File composeFile, String identifier) + */ @Deprecated public DockerComposeContainer(File composeFile, String identifier) { this(identifier, composeFile); } + /** + * @deprecated + * Use the new constructor DockerComposeContainer(File... composeFiles) + */ + @Deprecated public DockerComposeContainer(File... composeFiles) { this(Arrays.asList(composeFiles)); } + /** + * @deprecated + * Use the new constructor DockerComposeContainer(List composeFiles) + */ + @Deprecated public DockerComposeContainer(List composeFiles) { this(Base58.randomString(6).toLowerCase(), composeFiles); } + /** + * @deprecated + * Use the new constructor DockerComposeContainer(String identifier,File... composeFiles) + */ + @Deprecated public DockerComposeContainer(String identifier, File... composeFiles) { this(identifier, Arrays.asList(composeFiles)); } + /** + * @deprecated + * Use the new constructor DockerComposeContainer(String identifier,List composeFiles) + */ + @Deprecated public DockerComposeContainer(String identifier, List composeFiles) { this.composeDelegate = new ComposeDelegate( @@ -94,7 +163,7 @@ public DockerComposeContainer(String identifier, List composeFiles) { composeFiles, identifier, COMPOSE_EXECUTABLE, - DEFAULT_IMAGE_NAME + DockerImageName.parse("docker/compose:1.29.2") ); this.project = this.composeDelegate.getProject(); } diff --git a/core/src/main/java/org/testcontainers/utility/TestcontainersConfiguration.java b/core/src/main/java/org/testcontainers/utility/TestcontainersConfiguration.java index 5c80b82ba9c..47954ea7624 100644 --- a/core/src/main/java/org/testcontainers/utility/TestcontainersConfiguration.java +++ b/core/src/main/java/org/testcontainers/utility/TestcontainersConfiguration.java @@ -244,7 +244,7 @@ private String getConfigurable( } for (final Properties properties : propertiesSources) { - if (properties.get(propertyName) != null) { + if (properties.get(propertyName) != null && !properties.get(propertyName).toString().trim().isEmpty()) { return (String) properties.get(propertyName); } } diff --git a/core/src/test/java/org/testcontainers/containers/ComposeContainerTest.java b/core/src/test/java/org/testcontainers/containers/ComposeContainerTest.java new file mode 100644 index 00000000000..81c056af823 --- /dev/null +++ b/core/src/test/java/org/testcontainers/containers/ComposeContainerTest.java @@ -0,0 +1,82 @@ +package org.testcontainers.containers; + +import ch.qos.logback.classic.Logger; +import ch.qos.logback.classic.spi.ILoggingEvent; +import ch.qos.logback.core.AppenderBase; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.slf4j.LoggerFactory; +import org.testcontainers.utility.DockerImageName; +import org.testcontainers.utility.TestcontainersConfiguration; + +import java.io.File; +import java.util.ArrayList; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; + +public class ComposeContainerTest { + + public static final String DOCKER_IMAGE = "docker:25.0.2"; + + private static final String COMPOSE_FILE_PATH = "src/test/resources/docker-compose-imagename-parsing-v2.yml"; + + private ComposeContainer composeContainer; + + private TestLogAppender testLogAppender; + + private Logger rootLogger; + + @Before + public void setup() { + testLogAppender = new TestLogAppender(); + testLogAppender.start(); + rootLogger = (Logger) LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME); + rootLogger.addAppender(testLogAppender); + TestcontainersConfiguration.getInstance().updateUserConfig("compose.container.image", DOCKER_IMAGE); + } + + @After + public void tearDown() { + composeContainer.stop(); + rootLogger.detachAppender(testLogAppender); + TestcontainersConfiguration.getInstance().updateUserConfig("compose.container.image", ""); + System.clearProperty("compose.container.image"); + } + + @Test + public void testWithCustomDockerImage() { + composeContainer = new ComposeContainer(DockerImageName.parse(DOCKER_IMAGE), new File(COMPOSE_FILE_PATH)); + composeContainer.start(); + verifyContainerCreation(); + } + + @Test + public void testWithCustomDockerImageAndIdentifier() { + composeContainer = + new ComposeContainer(DockerImageName.parse(DOCKER_IMAGE), "myidentifier", new File(COMPOSE_FILE_PATH)); + composeContainer.start(); + verifyContainerCreation(); + } + + private void verifyContainerCreation() { + List logs = testLogAppender.getLogs(); + + assertThat(logs).isNotNull().anyMatch(line -> line.contains("Creating container for image: " + DOCKER_IMAGE)); + } + + private static class TestLogAppender extends AppenderBase { + + private final List logs = new ArrayList<>(); + + @Override + protected void append(ILoggingEvent eventObject) { + logs.add(eventObject.getFormattedMessage()); + } + + public List getLogs() { + return logs; + } + } +} diff --git a/core/src/test/java/org/testcontainers/containers/DockerComposeContainerCustomImageTest.java b/core/src/test/java/org/testcontainers/containers/DockerComposeContainerCustomImageTest.java new file mode 100644 index 00000000000..cfdff3a18d0 --- /dev/null +++ b/core/src/test/java/org/testcontainers/containers/DockerComposeContainerCustomImageTest.java @@ -0,0 +1,87 @@ +package org.testcontainers.containers; + +import ch.qos.logback.classic.Logger; +import ch.qos.logback.classic.spi.ILoggingEvent; +import ch.qos.logback.core.AppenderBase; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.slf4j.LoggerFactory; +import org.testcontainers.utility.DockerImageName; +import org.testcontainers.utility.TestcontainersConfiguration; + +import java.io.File; +import java.util.ArrayList; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; + +public class DockerComposeContainerCustomImageTest { + + public static final String DOCKER_IMAGE = "docker/compose:debian-1.29.2"; + + private static final String COMPOSE_FILE_PATH = "src/test/resources/docker-compose-imagename-parsing-v1.yml"; + + private DockerComposeContainer composeContainer; + + private TestLogAppender testLogAppender; + + private Logger rootLogger; + + @Before + public void setup() { + testLogAppender = new TestLogAppender(); + testLogAppender.start(); + rootLogger = (Logger) LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME); + rootLogger.addAppender(testLogAppender); + TestcontainersConfiguration.getInstance().updateUserConfig("compose.container.image", DOCKER_IMAGE); + } + + @After + public void tearDown() { + rootLogger.detachAppender(testLogAppender); + TestcontainersConfiguration.getInstance().updateUserConfig("compose.container.image", ""); + System.clearProperty("compose.container.image"); + composeContainer.stop(); + } + + @Test + public void testWithCustomDockerImage() { + composeContainer = + new DockerComposeContainer(DockerImageName.parse(DOCKER_IMAGE), "testing", new File(COMPOSE_FILE_PATH)); + composeContainer.start(); + verifyContainerCreation(); + } + + @Test + public void testWithCustomDockerImageAndIdentifier() { + composeContainer = + new DockerComposeContainer( + DockerImageName.parse(DOCKER_IMAGE), + "myidentifier", + new File(COMPOSE_FILE_PATH) + ); + composeContainer.start(); + verifyContainerCreation(); + } + + private void verifyContainerCreation() { + List logs = testLogAppender.getLogs(); + + assertThat(logs).isNotNull().anyMatch(line -> line.contains("Creating container for image: " + DOCKER_IMAGE)); + } + + private static class TestLogAppender extends AppenderBase { + + private final List logs = new ArrayList<>(); + + @Override + protected void append(ILoggingEvent eventObject) { + logs.add(eventObject.getFormattedMessage()); + } + + public List getLogs() { + return logs; + } + } +}