Skip to content

Commit d93dcea

Browse files
thomasdarimontAlex Stockingerbsideupkiview
authored
Allow to create a file in container from a Transferable (#3814) (#3815)
Co-authored-by: Alex Stockinger <[email protected]> Co-authored-by: Sergei Egorov <[email protected]> Co-authored-by: Kevin Wittek <[email protected]>
1 parent c7449ed commit d93dcea

File tree

7 files changed

+193
-41
lines changed

7 files changed

+193
-41
lines changed

core/build.gradle

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@ tasks.japicmp {
4545
"org.testcontainers.utility.ResourceReaper#start(com.github.dockerjava.api.DockerClient)",
4646
"org.testcontainers.utility.ResourceReaper#registerNetworkForCleanup(java.lang.String)",
4747
"org.testcontainers.utility.ResourceReaper#removeNetworks(java.lang.String)",
48+
"org.testcontainers.images.builder.Transferable#of(java.lang.String)",
49+
"org.testcontainers.images.builder.Transferable#updateChecksum(java.util.zip.Checksum)"
4850
]
4951

5052
fieldExcludes = []

core/src/main/java/org/testcontainers/containers/Container.java

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
import org.testcontainers.containers.startupcheck.StartupCheckStrategy;
1313
import org.testcontainers.containers.traits.LinkableContainer;
1414
import org.testcontainers.containers.wait.strategy.WaitStrategy;
15+
import org.testcontainers.images.builder.Transferable;
1516
import org.testcontainers.utility.LogUtils;
1617
import org.testcontainers.utility.MountableFile;
1718

@@ -169,11 +170,23 @@ default SELF withFileSystemBind(String hostPath, String containerPath) {
169170
* Set the file to be copied before starting a created container
170171
*
171172
* @param mountableFile a Mountable file with path of source file / folder on host machine
172-
* @param containerPath a destination path on conatiner to which the files / folders to be copied
173+
* @param containerPath a destination path on container to which the files / folders to be copied
173174
* @return this
175+
*
176+
* @deprecated Use {@link #withCopyToContainer(Transferable, String)} instead
174177
*/
178+
@Deprecated
175179
SELF withCopyFileToContainer(MountableFile mountableFile, String containerPath);
176180

181+
/**
182+
* Set the content to be copied before starting a created container
183+
*
184+
* @param transferable a Transferable
185+
* @param containerPath a destination path on container to which the files / folders to be copied
186+
* @return this
187+
*/
188+
SELF withCopyToContainer(Transferable transferable, String containerPath);
189+
177190
/**
178191
* Add an environment variable to be passed to the container.
179192
*

core/src/main/java/org/testcontainers/containers/GenericContainer.java

Lines changed: 29 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import com.google.common.hash.Hashing;
2424
import lombok.AccessLevel;
2525
import lombok.Data;
26+
import lombok.Getter;
2627
import lombok.NonNull;
2728
import lombok.Setter;
2829
import lombok.SneakyThrows;
@@ -49,6 +50,7 @@
4950
import org.testcontainers.containers.wait.strategy.WaitStrategyTarget;
5051
import org.testcontainers.images.ImagePullPolicy;
5152
import org.testcontainers.images.RemoteDockerImage;
53+
import org.testcontainers.images.builder.Transferable;
5254
import org.testcontainers.lifecycle.Startable;
5355
import org.testcontainers.lifecycle.Startables;
5456
import org.testcontainers.lifecycle.TestDescription;
@@ -186,8 +188,15 @@ public class GenericContainer<SELF extends GenericContainer<SELF>>
186188
private Long shmSize;
187189

188190
// Maintain order in which entries are added, as earlier target location may be a prefix of a later location.
191+
@Deprecated
189192
private Map<MountableFile, String> copyToFileContainerPathMap = new LinkedHashMap<>();
190193

194+
// Maintain order in which entries are added, as earlier target location may be a prefix of a later location.
195+
@Setter(AccessLevel.NONE)
196+
@Getter(AccessLevel.MODULE)
197+
@VisibleForTesting
198+
private Map<Transferable, String> copyToTransferableContainerPathMap = new LinkedHashMap<>();
199+
191200
protected final Set<Startable> dependencies = new HashSet<>();
192201

193202
/**
@@ -415,6 +424,8 @@ private void tryStart(Instant startedAt) {
415424

416425
// TODO use single "copy" invocation (and calculate an hash of the resulting tar archive)
417426
copyToFileContainerPathMap.forEach(this::copyFileToContainer);
427+
428+
copyToTransferableContainerPathMap.forEach(this::copyFileToContainer);
418429
}
419430

420431
connectToPortForwardingNetwork(createCommand.getNetworkMode());
@@ -530,33 +541,18 @@ private void tryStart(Instant startedAt) {
530541
@VisibleForTesting
531542
Checksum hashCopiedFiles() {
532543
Checksum checksum = new Adler32();
533-
copyToFileContainerPathMap.entrySet().stream().sorted(Entry.comparingByValue()).forEach(entry -> {
534-
byte[] pathBytes = entry.getValue().getBytes();
535-
// Add path to the hash
536-
checksum.update(pathBytes, 0, pathBytes.length);
537-
538-
File file = new File(entry.getKey().getResolvedPath());
539-
checksumFile(file, checksum);
540-
});
544+
Stream.of(copyToFileContainerPathMap, copyToTransferableContainerPathMap)
545+
.flatMap(it -> it.entrySet().stream())
546+
.sorted(Entry.comparingByValue()).forEach(entry -> {
547+
byte[] pathBytes = entry.getValue().getBytes();
548+
// Add path to the hash
549+
checksum.update(pathBytes, 0, pathBytes.length);
550+
551+
entry.getKey().updateChecksum(checksum);
552+
});
541553
return checksum;
542554
}
543555

544-
@VisibleForTesting
545-
@SneakyThrows(IOException.class)
546-
void checksumFile(File file, Checksum checksum) {
547-
Path path = file.toPath();
548-
checksum.update(MountableFile.getUnixFileMode(path));
549-
if (file.isDirectory()) {
550-
try (Stream<Path> stream = Files.walk(path)) {
551-
stream.filter(it -> it != path).forEach(it -> {
552-
checksumFile(it.toFile(), checksum);
553-
});
554-
}
555-
} else {
556-
FileUtils.checksum(file, checksum);
557-
}
558-
}
559-
560556
@UnstableAPI
561557
@SneakyThrows(JsonProcessingException.class)
562558
final String hash(CreateContainerCmd createCommand) {
@@ -1296,6 +1292,15 @@ public SELF withCopyFileToContainer(MountableFile mountableFile, String containe
12961292
return self();
12971293
}
12981294

1295+
/**
1296+
* {@inheritDoc}
1297+
*/
1298+
@Override
1299+
public SELF withCopyToContainer(Transferable transferable, String containerPath) {
1300+
copyToTransferableContainerPathMap.put(transferable, containerPath);
1301+
return self();
1302+
}
1303+
12991304
/**
13001305
* Get the IP address that this container may be reached on (may not be the local machine).
13011306
*

core/src/main/java/org/testcontainers/images/builder/Transferable.java

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,18 @@
55
import org.apache.commons.io.IOUtils;
66

77
import java.io.IOException;
8+
import java.nio.charset.StandardCharsets;
9+
import java.util.zip.Checksum;
810

911
public interface Transferable {
1012

1113
int DEFAULT_FILE_MODE = 0100644;
1214
int DEFAULT_DIR_MODE = 040755;
1315

16+
static Transferable of(String string) {
17+
return of(string.getBytes(StandardCharsets.UTF_8));
18+
}
19+
1420
static Transferable of(byte[] bytes) {
1521
return of(bytes, DEFAULT_FILE_MODE);
1622
}
@@ -27,6 +33,11 @@ public byte[] getBytes() {
2733
return bytes;
2834
}
2935

36+
@Override
37+
public void updateChecksum(Checksum checksum) {
38+
checksum.update(bytes, 0, bytes.length);
39+
}
40+
3041
@Override
3142
public int getFileMode() {
3243
return fileMode;
@@ -78,4 +89,8 @@ default byte[] getBytes() {
7889
default String getDescription() {
7990
return "";
8091
}
92+
93+
default void updateChecksum(Checksum checksum) {
94+
throw new UnsupportedOperationException("Provide implementation in subclass");
95+
}
8196
}

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

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

3+
import com.google.common.annotations.VisibleForTesting;
34
import com.google.common.base.Charsets;
45
import lombok.Getter;
56
import lombok.RequiredArgsConstructor;
7+
import lombok.SneakyThrows;
68
import lombok.extern.slf4j.Slf4j;
79
import org.apache.commons.compress.archivers.tar.TarArchiveEntry;
810
import org.apache.commons.compress.archivers.tar.TarArchiveOutputStream;
911
import org.apache.commons.compress.archivers.tar.TarConstants;
12+
import org.apache.commons.io.FileUtils;
1013
import org.apache.commons.lang3.SystemUtils;
1114
import org.jetbrains.annotations.NotNull;
1215
import org.testcontainers.DockerClientFactory;
@@ -28,6 +31,8 @@
2831
import java.util.Set;
2932
import java.util.jar.JarEntry;
3033
import java.util.jar.JarFile;
34+
import java.util.stream.Stream;
35+
import java.util.zip.Checksum;
3136

3237
import static lombok.AccessLevel.PACKAGE;
3338
import static org.testcontainers.utility.PathUtils.recursiveDeleteDir;
@@ -361,6 +366,27 @@ public String getDescription() {
361366
return this.getResolvedPath();
362367
}
363368

369+
@Override
370+
public void updateChecksum(Checksum checksum) {
371+
File file = new File(getResolvedPath());
372+
checksumFile(file, checksum);
373+
}
374+
375+
@SneakyThrows(IOException.class)
376+
private void checksumFile(File file, Checksum checksum) {
377+
Path path = file.toPath();
378+
checksum.update(MountableFile.getUnixFileMode(path));
379+
if (file.isDirectory()) {
380+
try (Stream<Path> stream = Files.walk(path)) {
381+
stream.filter(it -> it != path).forEach(it -> {
382+
checksumFile(it.toFile(), checksum);
383+
});
384+
}
385+
} else {
386+
FileUtils.checksum(file, checksum);
387+
}
388+
}
389+
364390
@Override
365391
public int getFileMode() {
366392
return getUnixFileMode(this.getResolvedPath());

core/src/test/java/org/testcontainers/containers/GenericContainerTest.java

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,11 @@
2020
import org.testcontainers.containers.startupcheck.OneShotStartupCheckStrategy;
2121
import org.testcontainers.containers.startupcheck.StartupCheckStrategy;
2222
import org.testcontainers.containers.wait.strategy.AbstractWaitStrategy;
23+
import org.testcontainers.images.builder.Transferable;
24+
25+
import java.nio.charset.StandardCharsets;
2326
import org.testcontainers.images.builder.ImageFromDockerfile;
27+
import org.testcontainers.utility.MountableFile;
2428

2529
import java.util.Arrays;
2630
import java.util.List;
@@ -75,6 +79,38 @@ public void shouldReportErrorAfterWait() {
7579
}
7680
}
7781

82+
@Test
83+
public void shouldCopyTransferableAsFile() {
84+
try (
85+
GenericContainer<?> container = new GenericContainer<>(TestImages.TINY_IMAGE)
86+
.withStartupCheckStrategy(new NoopStartupCheckStrategy())
87+
.withCopyToContainer(Transferable.of("test"), "/tmp/test")
88+
.waitingFor(new WaitForExitedState(state -> state.getExitCodeLong() > 0))
89+
.withCommand("sh", "-c", "grep -q test /tmp/test && exit 100")
90+
) {
91+
assertThatThrownBy(container::start)
92+
.hasStackTraceContaining("Container exited with code 100");
93+
}
94+
}
95+
96+
@Test
97+
public void shouldCopyTransferableAfterMountableFile() {
98+
try (
99+
GenericContainer<?> container = new GenericContainer<>(TestImages.TINY_IMAGE)
100+
.withStartupCheckStrategy(new NoopStartupCheckStrategy())
101+
.withCopyFileToContainer(
102+
MountableFile.forClasspathResource("test_copy_to_container.txt"),
103+
"/tmp/test"
104+
)
105+
.withCopyToContainer(Transferable.of("test"), "/tmp/test")
106+
.waitingFor(new WaitForExitedState(state -> state.getExitCodeLong() > 0))
107+
.withCommand("sh", "-c", "grep -q test /tmp/test && exit 100")
108+
) {
109+
assertThatThrownBy(container::start)
110+
.hasStackTraceContaining("Container exited with code 100");
111+
}
112+
}
113+
78114
@Test
79115
public void shouldOnlyPublishExposedPorts() {
80116
ImageFromDockerfile image = new ImageFromDockerfile("publish-multiple")

0 commit comments

Comments
 (0)