From b1d39fd20c8e22425c25b92a03397d004a095d46 Mon Sep 17 00:00:00 2001 From: Wim Deblauwe Date: Thu, 4 Jul 2024 15:54:51 +0200 Subject: [PATCH] Allow configuration of files that are copied for docker compose (#8847) This commit adds support for a `withFileCopyInclusions` method on `ComposeContainer` and `DockerComposeContainer`. It allows to specify what files or directories should be copied, instead of just copying all files. If not used, the current behaviour is preserved. --- .../containers/ComposeContainer.java | 12 ++++++++++-- .../containers/ComposeDelegate.java | 11 ++++++----- .../containers/ContainerisedDockerCompose.java | 16 ++++++++++++++-- .../containers/DockerComposeContainer.java | 15 ++++++++++++--- 4 files changed, 42 insertions(+), 12 deletions(-) diff --git a/core/src/main/java/org/testcontainers/containers/ComposeContainer.java b/core/src/main/java/org/testcontainers/containers/ComposeContainer.java index a865e744dd6..e7ee4d1e762 100644 --- a/core/src/main/java/org/testcontainers/containers/ComposeContainer.java +++ b/core/src/main/java/org/testcontainers/containers/ComposeContainer.java @@ -67,6 +67,8 @@ public class ComposeContainer extends FailureDetectingExternalResource implement private String project; + private List fileCopyInclusions = new ArrayList<>(); + public ComposeContainer(File... composeFiles) { this(Arrays.asList(composeFiles)); } @@ -134,7 +136,8 @@ public void start() { this.options, this.services, this.scalingPreferences, - this.env + this.env, + this.fileCopyInclusions ); this.composeDelegate.startAmbassadorContainer(); this.composeDelegate.waitUntilServiceStarted(this.tailChildContainers); @@ -165,7 +168,7 @@ public void stop() { if (removeImages != null) { cmd += " --rmi " + removeImages.dockerRemoveImagesType(); } - this.composeDelegate.runWithCompose(this.localCompose, cmd, this.env); + this.composeDelegate.runWithCompose(this.localCompose, cmd, this.env, this.fileCopyInclusions); } finally { this.project = this.composeDelegate.randomProjectId(); } @@ -352,6 +355,11 @@ public ComposeContainer withStartupTimeout(Duration startupTimeout) { return this; } + public ComposeContainer withFileCopyInclusions(String... fileCopyInclusions) { + this.fileCopyInclusions = Arrays.asList(fileCopyInclusions); + return this; + } + public Optional getContainerByServiceName(String serviceName) { return this.composeDelegate.getContainerByServiceName(serviceName); } diff --git a/core/src/main/java/org/testcontainers/containers/ComposeDelegate.java b/core/src/main/java/org/testcontainers/containers/ComposeDelegate.java index a5189c8efa3..77d89bfa015 100644 --- a/core/src/main/java/org/testcontainers/containers/ComposeDelegate.java +++ b/core/src/main/java/org/testcontainers/containers/ComposeDelegate.java @@ -126,7 +126,8 @@ void createServices( final Set options, final List services, final Map scalingPreferences, - Map env + Map env, + List fileCopyInclusions ) { // services that have been explicitly requested to be started. If empty, all services should be started. final String serviceNameArgs = Stream @@ -160,7 +161,7 @@ void createServices( } // Run the docker compose container, which starts up the services - runWithCompose(localCompose, command, env); + runWithCompose(localCompose, command, env, fileCopyInclusions); } private String getUpCommand(String options) { @@ -237,10 +238,10 @@ private String getServiceNameFromContainer(com.github.dockerjava.api.model.Conta } public void runWithCompose(boolean localCompose, String cmd) { - runWithCompose(localCompose, cmd, Collections.emptyMap()); + runWithCompose(localCompose, cmd, Collections.emptyMap(), Collections.emptyList()); } - public void runWithCompose(boolean localCompose, String cmd, Map env) { + public void runWithCompose(boolean localCompose, String cmd, Map env, List fileCopyInclusions) { Preconditions.checkNotNull(composeFiles); Preconditions.checkArgument(!composeFiles.isEmpty(), "No docker compose file have been provided"); @@ -248,7 +249,7 @@ public void runWithCompose(boolean localCompose, String cmd, Map if (localCompose) { dockerCompose = new LocalDockerCompose(this.executable, composeFiles, project); } else { - dockerCompose = new ContainerisedDockerCompose(this.defaultImageName, composeFiles, project); + dockerCompose = new ContainerisedDockerCompose(this.defaultImageName, composeFiles, project, fileCopyInclusions); } dockerCompose.withCommand(cmd).withEnv(env).invoke(); diff --git a/core/src/main/java/org/testcontainers/containers/ContainerisedDockerCompose.java b/core/src/main/java/org/testcontainers/containers/ContainerisedDockerCompose.java index e1b7aaa604b..025cfc693ca 100644 --- a/core/src/main/java/org/testcontainers/containers/ContainerisedDockerCompose.java +++ b/core/src/main/java/org/testcontainers/containers/ContainerisedDockerCompose.java @@ -24,7 +24,7 @@ class ContainerisedDockerCompose extends GenericContainer composeFiles, String identifier) { + public ContainerisedDockerCompose(DockerImageName dockerImageName, List composeFiles, String identifier, List fileCopyInclusions) { super(dockerImageName); addEnv(ENV_PROJECT_NAME, identifier); @@ -43,7 +43,19 @@ public ContainerisedDockerCompose(DockerImageName dockerImageName, List co final String composeFileEnvVariableValue = Joiner.on(UNIX_PATH_SEPARATOR).join(absoluteDockerComposeFiles); // we always need the UNIX path separator logger().debug("Set env COMPOSE_FILE={}", composeFileEnvVariableValue); addEnv(ENV_COMPOSE_FILE, composeFileEnvVariableValue); - withCopyFileToContainer(MountableFile.forHostPath(pwd), containerPwd); + if (fileCopyInclusions.isEmpty()) { + logger().info("Copying all files in {} into the container", pwd); + withCopyFileToContainer(MountableFile.forHostPath(pwd), containerPwd); + } else { + // Always copy the compose file itself + logger().info("Copying docker compose file: {}", dockerComposeBaseFile.getAbsolutePath()); + withCopyFileToContainer(MountableFile.forHostPath(dockerComposeBaseFile.getAbsolutePath()), convertToUnixFilesystemPath(dockerComposeBaseFile.getAbsolutePath())); + for (String pathToCopy : fileCopyInclusions) { + String hostPath = pwd + "/" + pathToCopy; + logger().info("Copying inclusion file: {}", hostPath); + withCopyFileToContainer(MountableFile.forHostPath(hostPath), convertToUnixFilesystemPath(hostPath)); + } + } // Ensure that compose can access docker. Since the container is assumed to be running on the same machine // as the docker daemon, just mapping the docker control socket is OK. diff --git a/core/src/main/java/org/testcontainers/containers/DockerComposeContainer.java b/core/src/main/java/org/testcontainers/containers/DockerComposeContainer.java index 68f9ec26ba6..64bb8bf8a97 100644 --- a/core/src/main/java/org/testcontainers/containers/DockerComposeContainer.java +++ b/core/src/main/java/org/testcontainers/containers/DockerComposeContainer.java @@ -68,6 +68,8 @@ public class DockerComposeContainer> private String project; + private List fileCopyInclusions = new ArrayList<>(); + @Deprecated public DockerComposeContainer(File composeFile, String identifier) { this(identifier, composeFile); @@ -140,8 +142,9 @@ public void start() { this.options, this.services, this.scalingPreferences, - this.env - ); + this.env, + this.fileCopyInclusions + ); this.composeDelegate.startAmbassadorContainer(); this.composeDelegate.waitUntilServiceStarted(this.tailChildContainers); } @@ -172,7 +175,7 @@ public void stop() { if (removeImages != null) { cmd += " --rmi " + removeImages.dockerRemoveImagesType(); } - this.composeDelegate.runWithCompose(this.localCompose, cmd, this.env); + this.composeDelegate.runWithCompose(this.localCompose, cmd, this.env, this.fileCopyInclusions); } finally { this.project = this.composeDelegate.randomProjectId(); } @@ -355,6 +358,12 @@ public SELF withStartupTimeout(Duration startupTimeout) { return self(); } + public SELF withFileCopyInclusions(String... fileCopyInclusions) { + this.fileCopyInclusions = Arrays.asList(fileCopyInclusions); + return self(); + } + + public Optional getContainerByServiceName(String serviceName) { return this.composeDelegate.getContainerByServiceName(serviceName); }