diff --git a/testcontainers-floci/src/main/java/io/floci/testcontainers/FlociContainer.java b/testcontainers-floci/src/main/java/io/floci/testcontainers/FlociContainer.java
index eab2b3f..8dbd268 100644
--- a/testcontainers-floci/src/main/java/io/floci/testcontainers/FlociContainer.java
+++ b/testcontainers-floci/src/main/java/io/floci/testcontainers/FlociContainer.java
@@ -15,15 +15,15 @@
import org.testcontainers.containers.wait.strategy.Wait;
import org.testcontainers.utility.DockerImageName;
-import java.io.File;
import java.io.IOException;
+import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
+import java.nio.file.SimpleFileVisitor;
+import java.nio.file.attribute.BasicFileAttributes;
import java.time.Duration;
-import java.util.Comparator;
import java.util.UUID;
import java.util.function.Consumer;
-import java.util.stream.Stream;
/**
* Testcontainers module for Floci — a
@@ -174,18 +174,69 @@ public FlociContainer(DockerImageName dockerImageName) {
@Override
public void stop() {
+ preparePersistentStorageForCleanup();
super.stop();
+ deletePersistentStorage();
+ }
+
+ private void preparePersistentStorageForCleanup() {
+ if (storageConfig.getHostPersistentPath().isEmpty() || !isRunning()) {
+ return;
+ }
+
+ // Child containers can create root-owned files in Floci's bind-mounted data directory.
+ try {
+ ExecResult result = execInContainer("chmod", "-R", "a+rwX", "/app/data");
+ if (result.getExitCode() != 0) {
+ logger.warn("Failed to make Floci persistent storage deletable: {}", result.getStderr());
+ }
+ } catch (IOException e) {
+ logger.warn("Failed to make Floci persistent storage deletable", e);
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ logger.warn("Interrupted while making Floci persistent storage deletable", e);
+ }
+ }
- // Cleanup persistent storage if a host path was configured
+ private void deletePersistentStorage() {
storageConfig.getHostPersistentPath().ifPresent(path -> {
- try (Stream paths = Files.walk(path)) {
- paths.sorted(Comparator.reverseOrder()).map(Path::toFile).forEach(File::delete);
+ try {
+ Files.walkFileTree(path, new SimpleFileVisitor() {
+ @Override
+ public FileVisitResult visitFile(Path file, BasicFileAttributes attributes) {
+ deletePath(file);
+ return FileVisitResult.CONTINUE;
+ }
+
+ @Override
+ public FileVisitResult visitFileFailed(Path file, IOException failure) {
+ logger.warn("Failed to visit Floci persistent storage path {}", file, failure);
+ return FileVisitResult.CONTINUE;
+ }
+
+ @Override
+ public FileVisitResult postVisitDirectory(Path directory, IOException failure) {
+ if (failure != null) {
+ logger.warn("Failed to visit Floci persistent storage directory {}", directory, failure);
+ }
+ deletePath(directory);
+ return FileVisitResult.CONTINUE;
+ }
+ });
} catch (IOException e) {
- // Ignore exception silently
+ logger.warn("Failed to delete Floci persistent storage at {}", path, e);
}
});
}
+ private static void deletePath(Path path) {
+ try {
+ Files.deleteIfExists(path);
+ } catch (IOException e) {
+ logger.warn("Failed to delete Floci persistent storage path {}", path, e);
+ }
+ }
+
/**
* Returns the endpoint URL for connecting to Floci (e.g. {@code http://localhost:32781}).
*
@@ -2214,4 +2265,4 @@ private void configureEnvVars() {
private static String uniqueShortId() {
return UUID.randomUUID().toString().replace("-", "").substring(0, 8);
}
-}
\ No newline at end of file
+}
diff --git a/testcontainers-floci/src/test/java/io/floci/testcontainers/FlociContainerTest.java b/testcontainers-floci/src/test/java/io/floci/testcontainers/FlociContainerTest.java
index a4c113c..58b26ab 100644
--- a/testcontainers-floci/src/test/java/io/floci/testcontainers/FlociContainerTest.java
+++ b/testcontainers-floci/src/test/java/io/floci/testcontainers/FlociContainerTest.java
@@ -5,6 +5,8 @@
import org.testcontainers.utility.DockerImageName;
import org.testcontainers.images.builder.Transferable;
+import java.nio.file.Path;
+
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
@@ -147,4 +149,21 @@ void shouldWaitForStartScriptsToComplete() throws Exception {
.getExitCode()).isZero();
}
}
+
+ @Test
+ void shouldDeleteContainerOwnedPersistentStorage() throws Exception {
+ FlociContainer container = new FlociContainer()
+ .withStorageConfig(config -> config.randomHostPersistentPath());
+ Path persistentStorage = container.getStorageConfig().getHostPersistentPath().orElseThrow();
+ try {
+ container.start();
+ var result = container.execInContainer(
+ "sh", "-c", "mkdir -p /app/data/restricted && touch /app/data/restricted/file && chmod 000 /app/data/restricted");
+ assertThat(result.getExitCode()).isZero();
+ } finally {
+ container.stop();
+ }
+
+ assertThat(persistentStorage).doesNotExist();
+ }
}