Skip to content

Commit 7be8ac3

Browse files
committed
Do not transfer mountable file when tar name is "." or ".." (docker is unable to use this files) or transferring into directory
1 parent 43c6a97 commit 7be8ac3

File tree

4 files changed

+172
-20
lines changed

4 files changed

+172
-20
lines changed

core/src/main/java/org/testcontainers/images/builder/traits/FilesTrait.java

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import org.testcontainers.utility.MountableFile;
44

55
import java.io.File;
6+
import java.nio.file.Files;
67
import java.nio.file.Path;
78

89
/**
@@ -49,6 +50,20 @@ default SELF withFileFromFile(String path, File file, Integer mode) {
4950
* @return self
5051
*/
5152
default SELF withFileFromPath(String path, Path filePath, Integer mode) {
53+
final boolean fileStoredToDir =
54+
Files.isRegularFile(filePath) &&
55+
(
56+
path.endsWith("/") ||
57+
path.endsWith("/.") ||
58+
path.endsWith("/..") ||
59+
path.endsWith("./") ||
60+
path.endsWith("../") ||
61+
".".equals(path) ||
62+
"..".equals(path)
63+
);
64+
if (fileStoredToDir) {
65+
throw new IllegalArgumentException("Unable to store file '" + filePath + "' to docker path '" + path + "'");
66+
}
5267
final MountableFile mountableFile = MountableFile.forHostPath(filePath, mode);
5368
return ((SELF) this).withFileFromTransferable(path, mountableFile);
5469
}

core/src/main/java/org/testcontainers/utility/MountableFile.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -358,6 +358,7 @@ private void recursiveTar(
358358
tarEntry.setMode(getUnixFileMode(itemPath));
359359
tarArchive.putArchiveEntry(tarEntry);
360360

361+
log.trace("Transferring {} '{}'", sourceFile.isFile() ? "file" : "directory", sourceFile);
361362
if (sourceFile.isFile()) {
362363
Files.copy(sourceFile.toPath(), tarArchive);
363364
}

core/src/test/java/org/testcontainers/images/builder/ImageFromDockerfileTest.java

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,16 @@
33
import com.github.dockerjava.api.DockerClient;
44
import com.github.dockerjava.api.command.InspectImageResponse;
55
import org.junit.jupiter.api.Test;
6+
import org.junit.jupiter.params.ParameterizedTest;
7+
import org.junit.jupiter.params.provider.ValueSource;
68
import org.testcontainers.DockerClientFactory;
79
import org.testcontainers.utility.Base58;
810

11+
import java.io.File;
12+
import java.nio.file.Paths;
13+
914
import static org.assertj.core.api.Assertions.assertThat;
15+
import static org.assertj.core.api.Assertions.assertThatThrownBy;
1016

1117
class ImageFromDockerfileTest {
1218

@@ -44,4 +50,44 @@ void shouldNotAddSessionLabelIfDeleteOnExitIsFalse() {
4450
dockerClient.removeImageCmd(imageId).exec();
4551
}
4652
}
53+
54+
@ParameterizedTest
55+
@ValueSource(
56+
strings = {
57+
"..",
58+
".",
59+
"../",
60+
"./",
61+
"xxx/",
62+
"yyy/xxx/",
63+
"/xxx/",
64+
"/yyy/xxx/",
65+
"/..",
66+
"/.",
67+
"/../",
68+
"/./",
69+
".",
70+
"..",
71+
"aa/.",
72+
"aa/..",
73+
"bb/./",
74+
"bb/../",
75+
"cc./",
76+
"cc../",
77+
}
78+
)
79+
void unableToTransferFileWithDotsToDockerDaemon(String tarPath) {
80+
assertThatThrownBy(() -> {
81+
new ImageFromDockerfile()
82+
.withFileFromFile(tarPath, new File("src/test/resources/mappable-resource/test-resource.txt"));
83+
})
84+
.isInstanceOf(IllegalArgumentException.class)
85+
.hasMessage(
86+
"Unable to store file '" +
87+
Paths.get("src", "test", "resources", "mappable-resource", "test-resource.txt") +
88+
"' to docker path '" +
89+
tarPath +
90+
"'"
91+
);
92+
}
4793
}

core/src/test/java/org/testcontainers/utility/DirectoryTarResourceTest.java

Lines changed: 110 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,17 @@
11
package org.testcontainers.utility;
22

3+
import org.apache.commons.lang3.StringUtils;
34
import org.assertj.core.api.Condition;
45
import org.junit.jupiter.api.Test;
56
import org.testcontainers.containers.GenericContainer;
67
import org.testcontainers.containers.startupcheck.OneShotStartupCheckStrategy;
78
import org.testcontainers.images.builder.ImageFromDockerfile;
89

910
import java.io.File;
11+
import java.util.ArrayList;
12+
import java.util.Arrays;
13+
import java.util.List;
14+
import java.util.function.Consumer;
1015
import java.util.function.Predicate;
1116

1217
import static org.assertj.core.api.Assertions.assertThat;
@@ -18,26 +23,28 @@ void simpleRecursiveFileTest() {
1823
// 'src' is expected to be the project base directory, so all source code/resources should be copied in
1924
File directory = new File("src");
2025

21-
GenericContainer container = new GenericContainer(
22-
new ImageFromDockerfile()
23-
.withDockerfileFromBuilder(builder -> {
24-
builder
25-
.from("alpine:3.17")
26-
.copy("/tmp/foo", "/foo")
27-
.cmd("cat /foo/test/resources/test-recursive-file.txt")
28-
.build();
29-
})
30-
.withFileFromFile("/tmp/foo", directory)
31-
)
32-
.withStartupCheckStrategy(new OneShotStartupCheckStrategy());
33-
34-
container.start();
35-
36-
final String results = container.getLogs();
37-
38-
assertThat(results)
39-
.as("The container has a file that was copied in via a recursive copy")
40-
.contains("Used for DirectoryTarResourceTest");
26+
try (
27+
GenericContainer container = new GenericContainer(
28+
new ImageFromDockerfile()
29+
.withDockerfileFromBuilder(builder -> {
30+
builder
31+
.from("alpine:3.17")
32+
.copy("/tmp/foo", "/foo")
33+
.cmd("cat /foo/test/resources/test-recursive-file.txt")
34+
.build();
35+
})
36+
.withFileFromFile("/tmp/foo", directory)
37+
)
38+
.withStartupCheckStrategy(new OneShotStartupCheckStrategy())
39+
) {
40+
container.start();
41+
42+
final String results = container.getLogs();
43+
44+
assertThat(results)
45+
.as("The container has a file that was copied in via a recursive copy")
46+
.contains("Used for DirectoryTarResourceTest");
47+
}
4148
}
4249

4350
@Test
@@ -94,4 +101,87 @@ void simpleRecursiveClasspathResourceTest() {
94101
.contains("content.txt");
95102
}
96103
}
104+
105+
@Test
106+
public void transferFileDockerDaemon() {
107+
final File theFile = new File("src/test/resources/mappable-resource/test-resource.txt");
108+
try (
109+
GenericContainer container = new GenericContainer(
110+
new ImageFromDockerfile()
111+
.withDockerfileFromBuilder(builder -> {
112+
builder.from("alpine:3.3").copy(".", "/foo/").cmd("ls", "-lapR", "/foo").build();
113+
})
114+
.withFileFromFile("bar1", theFile)
115+
.withFileFromFile("./bar2", theFile)
116+
.withFileFromFile("../bar3", theFile)
117+
.withFileFromFile(".bar4", theFile)
118+
.withFileFromFile("..bar5", theFile)
119+
.withFileFromFile("xxx/../bar6", theFile)
120+
.withFileFromFile("x7/./bar7", theFile)
121+
.withFileFromFile("x8/././bar8", theFile)
122+
.withFileFromFile("x9/../../bar9", theFile)
123+
)
124+
.withStartupCheckStrategy(new OneShotStartupCheckStrategy())
125+
) {
126+
container.start();
127+
128+
final List<String> logLines = Arrays.asList(container.getLogs().split("\\n"));
129+
assertThat(logLines.stream().filter(StringUtils::isEmpty).count())
130+
.describedAs("Three groups of dirs")
131+
.isEqualTo(2);
132+
133+
final LsOutput lsOutput = LsOutput.parse(logLines);
134+
135+
assertThat(lsOutput.parentDir).satisfiesOnlyOnce(endsWith(" bar1"));
136+
assertThat(lsOutput.parentDir).satisfiesOnlyOnce(endsWith(" bar2"));
137+
assertThat(lsOutput.parentDir).satisfiesOnlyOnce(endsWith(" bar3"));
138+
assertThat(lsOutput.parentDir).satisfiesOnlyOnce(endsWith(" .bar4"));
139+
assertThat(lsOutput.parentDir).satisfiesOnlyOnce(endsWith(" ..bar5"));
140+
assertThat(lsOutput.parentDir).satisfiesOnlyOnce(endsWith(" bar6"));
141+
assertThat(lsOutput.parentDir).satisfiesOnlyOnce(endsWith(" x7/"));
142+
assertThat(lsOutput.parentDir).satisfiesOnlyOnce(endsWith(" x8/"));
143+
assertThat(lsOutput.parentDir).satisfiesOnlyOnce(endsWith(" bar9"));
144+
assertThat(lsOutput.subDir1).satisfiesOnlyOnce(endsWith(" bar7"));
145+
assertThat(lsOutput.subDir2).satisfiesOnlyOnce(endsWith(" bar8"));
146+
}
147+
}
148+
149+
private static class LsOutput {
150+
151+
private final List<String> parentDir;
152+
153+
private final List<String> subDir1;
154+
155+
private final List<String> subDir2;
156+
157+
private static LsOutput parse(List<String> logLines) {
158+
List<String> parentDir = null;
159+
List<String> subDir1 = null;
160+
int start = 0;
161+
for (int i = 0; i < logLines.size(); i++) {
162+
if (logLines.get(i).isEmpty()) {
163+
if (parentDir == null) {
164+
parentDir = new ArrayList<>(logLines.subList(start, i));
165+
start = i;
166+
} else if (subDir1 == null) {
167+
subDir1 = new ArrayList<>(logLines.subList(start, i));
168+
start = i;
169+
}
170+
}
171+
}
172+
List<String> subDir2 = new ArrayList<>(logLines.subList(start, logLines.size()));
173+
174+
return new LsOutput(parentDir, subDir1, subDir2);
175+
}
176+
177+
private LsOutput(List<String> parentDir, List<String> subDir1, List<String> subDir2) {
178+
this.parentDir = parentDir;
179+
this.subDir1 = subDir1;
180+
this.subDir2 = subDir2;
181+
}
182+
}
183+
184+
public static Consumer<String> endsWith(String suffix) {
185+
return value -> assertThat(value).endsWith(suffix);
186+
}
97187
}

0 commit comments

Comments
 (0)