Skip to content

Commit 3cb3379

Browse files
authored
Hash copied files (#2055)
* Fix session label when reuse is not supported but requested * Hash copied files * restore import * add file mode to the checksum * Mock TestcontainersConfiguration * checksum folders too
1 parent 3880dd4 commit 3cb3379

File tree

3 files changed

+272
-9
lines changed

3 files changed

+272
-9
lines changed

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

Lines changed: 44 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
import org.apache.commons.compress.archivers.tar.TarArchiveInputStream;
3030
import org.apache.commons.compress.archivers.tar.TarArchiveOutputStream;
3131
import org.apache.commons.compress.utils.IOUtils;
32+
import org.apache.commons.io.FileUtils;
3233
import org.apache.commons.lang.StringUtils;
3334
import org.jetbrains.annotations.NotNull;
3435
import org.jetbrains.annotations.Nullable;
@@ -75,6 +76,7 @@
7576
import java.lang.reflect.Method;
7677
import java.lang.reflect.UndeclaredThrowableException;
7778
import java.nio.charset.Charset;
79+
import java.nio.file.Files;
7880
import java.nio.file.Path;
7981
import java.time.Duration;
8082
import java.time.Instant;
@@ -87,6 +89,7 @@
8789
import java.util.LinkedHashSet;
8890
import java.util.List;
8991
import java.util.Map;
92+
import java.util.Map.Entry;
9093
import java.util.Optional;
9194
import java.util.Set;
9295
import java.util.concurrent.ExecutionException;
@@ -96,6 +99,8 @@
9699
import java.util.function.Consumer;
97100
import java.util.stream.Collectors;
98101
import java.util.stream.Stream;
102+
import java.util.zip.Adler32;
103+
import java.util.zip.Checksum;
99104

100105
import static com.google.common.collect.Lists.newArrayList;
101106
import static org.testcontainers.utility.CommandLine.runShellCommand;
@@ -116,6 +121,8 @@ public class GenericContainer<SELF extends GenericContainer<SELF>>
116121

117122
static final String HASH_LABEL = "org.testcontainers.hash";
118123

124+
static final String COPIED_FILES_HASH_LABEL = "org.testcontainers.copied_files.hash";
125+
119126
/*
120127
* Default settings
121128
*/
@@ -353,6 +360,11 @@ private void tryStart(Instant startedAt) {
353360
}
354361

355362
if (TestcontainersConfiguration.getInstance().environmentSupportsReuse()) {
363+
createCommand.getLabels().put(
364+
COPIED_FILES_HASH_LABEL,
365+
Long.toHexString(hashCopiedFiles().getValue())
366+
);
367+
356368
String hash = hash(createCommand);
357369

358370
containerId = findContainerForReuse(hash).orElse(null);
@@ -381,7 +393,7 @@ private void tryStart(Instant startedAt) {
381393
if (!reused) {
382394
containerId = createCommand.exec().getId();
383395

384-
// TODO add to the hash
396+
// TODO use single "copy" invocation (and calculate an hash of the resulting tar archive)
385397
copyToFileContainerPathMap.forEach(this::copyFileToContainer);
386398
}
387399

@@ -474,6 +486,36 @@ private void tryStart(Instant startedAt) {
474486
}
475487
}
476488

489+
@VisibleForTesting
490+
Checksum hashCopiedFiles() {
491+
Checksum checksum = new Adler32();
492+
copyToFileContainerPathMap.entrySet().stream().sorted(Entry.comparingByValue()).forEach(entry -> {
493+
byte[] pathBytes = entry.getValue().getBytes();
494+
// Add path to the hash
495+
checksum.update(pathBytes, 0, pathBytes.length);
496+
497+
File file = new File(entry.getKey().getResolvedPath());
498+
checksumFile(file, checksum);
499+
});
500+
return checksum;
501+
}
502+
503+
@VisibleForTesting
504+
@SneakyThrows(IOException.class)
505+
void checksumFile(File file, Checksum checksum) {
506+
Path path = file.toPath();
507+
checksum.update(MountableFile.getUnixFileMode(path));
508+
if (file.isDirectory()) {
509+
try (Stream<Path> stream = Files.walk(path)) {
510+
stream.filter(it -> it != path).forEach(it -> {
511+
checksumFile(it.toFile(), checksum);
512+
});
513+
}
514+
} else {
515+
FileUtils.checksum(file, checksum);
516+
}
517+
}
518+
477519
@UnstableAPI
478520
@SneakyThrows(JsonProcessingException.class)
479521
final String hash(CreateContainerCmd createCommand) {
@@ -708,7 +750,7 @@ private void applyConfiguration(CreateContainerCmd createCommand) {
708750

709751
Set<Link> allLinks = new HashSet<>();
710752
Set<String> allLinkedContainerNetworks = new HashSet<>();
711-
for (Map.Entry<String, LinkableContainer> linkEntries : linkedContainers.entrySet()) {
753+
for (Entry<String, LinkableContainer> linkEntries : linkedContainers.entrySet()) {
712754

713755
String alias = linkEntries.getKey();
714756
LinkableContainer linkableContainer = linkEntries.getValue();

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

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import org.apache.commons.lang.SystemUtils;
1010
import org.jetbrains.annotations.NotNull;
1111
import org.testcontainers.DockerClientFactory;
12+
import org.testcontainers.UnstableAPI;
1213
import org.testcontainers.images.builder.Transferable;
1314

1415
import java.io.File;
@@ -361,9 +362,13 @@ private int getUnixFileMode(final String pathAsString) {
361362
if (this.forcedFileMode != null) {
362363
return this.getModeValue(path);
363364
}
365+
return getUnixFileMode(path);
366+
}
364367

368+
@UnstableAPI
369+
public static int getUnixFileMode(final Path path) {
365370
try {
366-
return (int) Files.getAttribute(path, "unix:mode");
371+
return (int) Files.readAttributes(path, "unix:mode").get("mode");
367372
} catch (IOException | UnsupportedOperationException e) {
368373
// fallback for non-posix environments
369374
int mode = DEFAULT_FILE_MODE;

0 commit comments

Comments
 (0)