Skip to content

Commit

Permalink
#446 'set permission when copying'
Browse files Browse the repository at this point in the history
  • Loading branch information
Gleb Stsenov authored and rnorth committed Nov 11, 2017
1 parent 1c66a14 commit 2ad57da
Show file tree
Hide file tree
Showing 5 changed files with 190 additions and 6 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ All notable changes to this project will be documented in this file.

### Changed
- Make Network instances reusable (i.e. work with `@ClassRule`) ([\#469](https://github.com/testcontainers/testcontainers-java/issues/469))
- Added support for explicitly setting file mode when copying file into container (#446)

## [1.4.3] - 2017-10-14
### Fixed
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,46 @@
*/
public interface FilesTrait<SELF extends FilesTrait<SELF> & BuildContextBuilderTrait<SELF>> {

/**
* Adds file to tarball copied into container.
* @param path in tarball
* @param file in host filesystem
* @return self
*/
default SELF withFileFromFile(String path, File file) {
return withFileFromPath(path, file.toPath());
return withFileFromPath(path, file.toPath(), 0);
}

/**
* Adds file to tarball copied into container.
* @param path in tarball
* @param filePath in host filesystem
* @return self
*/
default SELF withFileFromPath(String path, Path filePath) {
final MountableFile mountableFile = MountableFile.forHostPath(filePath);
return withFileFromPath(path, filePath, 0);
}

/**
* Adds file with given mode to tarball copied into container.
* @param path in tarball
* @param file in host filesystem
* @param mode octal value of posix file mode (000..777)
* @return self
*/
default SELF withFileFromFile(String path, File file, int mode) {
return withFileFromPath(path, file.toPath(), mode);
}

/**
* Adds file with given mode to tarball copied into container.
* @param path in tarball
* @param filePath in host filesystem
* @param mode octal value of posix file mode (000..777)
* @return self
*/
default SELF withFileFromPath(String path, Path filePath, int mode) {
final MountableFile mountableFile = MountableFile.forHostPath(filePath, mode);
return ((SELF) this).withFileFromTransferable(path, mountableFile);
}
}
59 changes: 55 additions & 4 deletions core/src/main/java/org/testcontainers/utility/MountableFile.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,11 @@
import org.jetbrains.annotations.NotNull;
import org.testcontainers.images.builder.Transferable;

import java.io.*;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.UncheckedIOException;
import java.io.UnsupportedEncodingException;
import java.net.URL;
import java.net.URLDecoder;
import java.nio.file.Files;
Expand All @@ -33,7 +37,11 @@
@Slf4j
public class MountableFile implements Transferable {

private static final int BASE_FILE_MODE = 0100000;
private static final int BASE_DIR_MODE = 0040000;

private final String path;
private final int forcedFileMode;

@Getter(lazy = true)
private final String resolvedPath = resolvePath();
Expand All @@ -50,7 +58,7 @@ public class MountableFile implements Transferable {
* @return a {@link MountableFile} that may be used to obtain a mountable path
*/
public static MountableFile forClasspathResource(@NotNull final String resourceName) {
return new MountableFile(getClasspathResource(resourceName, new HashSet<>()).toString());
return forClasspathResource(resourceName, -1);
}

/**
Expand All @@ -60,7 +68,7 @@ public static MountableFile forClasspathResource(@NotNull final String resourceN
* @return a {@link MountableFile} that may be used to obtain a mountable path
*/
public static MountableFile forHostPath(@NotNull final String path) {
return new MountableFile(new File(path).toURI().toString());
return forHostPath(path, -1);
}

/**
Expand All @@ -70,9 +78,43 @@ public static MountableFile forHostPath(@NotNull final String path) {
* @return a {@link MountableFile} that may be used to obtain a mountable path
*/
public static MountableFile forHostPath(final Path path) {
return new MountableFile(path.toAbsolutePath().toString());
return forHostPath(path, -1);
}

/**
* Obtains a {@link MountableFile} corresponding to a resource on the classpath (including resources in JAR files)
*
* @param resourceName the classpath path to the resource
* @param mode octal value of posix file mode (000..777)
* @return a {@link MountableFile} that may be used to obtain a mountable path
*/
public static MountableFile forClasspathResource(@NotNull final String resourceName, int mode) {
return new MountableFile(getClasspathResource(resourceName, new HashSet<>()).toString(), mode);
}

/**
* Obtains a {@link MountableFile} corresponding to a file on the docker host filesystem.
*
* @param path the path to the resource
* @param mode octal value of posix file mode (000..777)
* @return a {@link MountableFile} that may be used to obtain a mountable path
*/
public static MountableFile forHostPath(@NotNull final String path, int mode) {
return new MountableFile(new File(path).toURI().toString(), mode);
}

/**
* Obtains a {@link MountableFile} corresponding to a file on the docker host filesystem.
*
* @param path the path to the resource
* @param mode octal value of posix file mode (000..777)
* @return a {@link MountableFile} that may be used to obtain a mountable path
*/
public static MountableFile forHostPath(final Path path, int mode) {
return new MountableFile(path.toAbsolutePath().toString(), mode);
}


@NotNull
private static URL getClasspathResource(@NotNull final String resourcePath, @NotNull final Set<ClassLoader> classLoaders) {

Expand Down Expand Up @@ -291,6 +333,10 @@ public int getFileMode() {

private int getUnixFileMode(final String pathAsString) {
final Path path = Paths.get(pathAsString);
if (this.forcedFileMode > -1) {
return this.getModeValue(path);
}

try {
return (int) Files.getAttribute(path, "unix:mode");
} catch (IOException | UnsupportedOperationException e) {
Expand All @@ -305,4 +351,9 @@ private int getUnixFileMode(final String pathAsString) {
return mode;
}
}

private int getModeValue(final Path path) {
int result = Files.isDirectory(path) ? BASE_DIR_MODE : BASE_FILE_MODE;
return result | this.forcedFileMode;
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
package org.testcontainers.utility;

import org.hamcrest.Description;
import org.hamcrest.Matcher;
import org.hamcrest.core.IsCollectionContaining;
import org.junit.Test;
import org.testcontainers.containers.GenericContainer;
import org.testcontainers.containers.output.ToStringConsumer;
Expand All @@ -8,9 +11,13 @@
import org.testcontainers.images.builder.ImageFromDockerfile;

import java.io.File;
import java.util.Arrays;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

import static org.hamcrest.CoreMatchers.allOf;
import static org.hamcrest.CoreMatchers.containsString;
import static org.rnorth.visibleassertions.VisibleAssertions.assertThat;
import static org.rnorth.visibleassertions.VisibleAssertions.assertTrue;

public class DirectoryTarResourceTest {
Expand Down Expand Up @@ -41,6 +48,35 @@ public void simpleRecursiveFileTest() throws TimeoutException {
assertTrue("The container has a file that was copied in via a recursive copy", results.contains("Used for DirectoryTarResourceTest"));
}

@Test
public void simpleRecursiveFileWithPermissionTest() throws TimeoutException {

WaitingConsumer wait = new WaitingConsumer();

final ToStringConsumer toString = new ToStringConsumer();

GenericContainer container = new GenericContainer(
new ImageFromDockerfile()
.withDockerfileFromBuilder(builder ->
builder.from("alpine:3.3")
.copy("/tmp/foo", "/foo")
.cmd("ls", "-al", "/")
.build()
).withFileFromFile("/tmp/foo", new File("/mappable-resource/test-resource.txt"),
0754))
.withStartupCheckStrategy(new OneShotStartupCheckStrategy())
.withLogConsumer(wait.andThen(toString));

container.start();
wait.waitUntilEnd(60, TimeUnit.SECONDS);

String listing = toString.toUtf8String();

assertThat("Listing shows that file is copied with mode requested.",
Arrays.asList(listing.split("\\n")),
exactlyNItems(1, allOf(containsString("-rwxr-xr--"), containsString("foo"))));
}

@Test
public void simpleRecursiveClasspathResourceTest() throws TimeoutException {
// This test combines the copying of classpath resources from JAR files with the recursive TAR approach, to allow JARed classpath resources to be copied in to an image
Expand Down Expand Up @@ -68,4 +104,31 @@ public void simpleRecursiveClasspathResourceTest() throws TimeoutException {
// ExternalResource.class is known to exist in a subdirectory of /org/junit so should be successfully copied in
assertTrue("The container has a file that was copied in via a recursive copy from a JAR resource", results.contains("content.txt"));
}

public static <T> Matcher<Iterable<? super T>> exactlyNItems(final int n, Matcher<? super T> elementMatcher) {
return new IsCollectionContaining<T>(elementMatcher) {
@Override
protected boolean matchesSafely(Iterable<? super T> collection, Description mismatchDescription) {
int count = 0;
boolean isPastFirst = false;

for (Object item : collection) {

if (elementMatcher.matches(item)) {
count++;
}
if (isPastFirst) {
mismatchDescription.appendText(", ");
}
elementMatcher.describeMismatch(item, mismatchDescription);
isPastFirst = true;
}

if (count != n) {
mismatchDescription.appendText(". Expected exactly " + n + " but got " + count);
}
return count == n;
}
};
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,16 @@
import java.nio.file.Files;
import java.nio.file.Path;

import static org.rnorth.visibleassertions.VisibleAssertions.assertEquals;
import static org.rnorth.visibleassertions.VisibleAssertions.assertFalse;
import static org.rnorth.visibleassertions.VisibleAssertions.assertTrue;

public class MountableFileTest {

private static final int TEST_FILE_MODE = 0532;
private static final int BASE_FILE_MODE = 0100000;
private static final int BASE_DIR_MODE = 0040000;

@Test
public void forClasspathResource() throws Exception {
final MountableFile mountableFile = MountableFile.forClasspathResource("mappable-resource/test-resource.txt");
Expand Down Expand Up @@ -60,6 +65,31 @@ public void forHostPathWithSpaces() throws Exception {
assertFalse("The resolved path does not contain an escaped space", mountableFile.getResolvedPath().contains("\\ "));
}

@Test
public void forClasspathResourceWithPermission() throws Exception {
final MountableFile mountableFile = MountableFile.forClasspathResource("mappable-resource/test-resource.txt",
TEST_FILE_MODE);

performChecks(mountableFile);
assertEquals("Valid file mode.", BASE_FILE_MODE | TEST_FILE_MODE, mountableFile.getFileMode());
}

@Test
public void forHostFilePathWithPermission() throws Exception {
final Path file = createTempFile("somepath");
final MountableFile mountableFile = MountableFile.forHostPath(file.toString(), TEST_FILE_MODE);
performChecks(mountableFile);
assertEquals("Valid file mode.", BASE_FILE_MODE | TEST_FILE_MODE, mountableFile.getFileMode());
}

@Test
public void forHostDirPathWithPermission() throws Exception {
final Path dir = createTempDir();
final MountableFile mountableFile = MountableFile.forHostPath(dir.toString(), TEST_FILE_MODE);
performChecks(mountableFile);
assertEquals("Valid dir mode.", BASE_DIR_MODE | TEST_FILE_MODE, mountableFile.getFileMode());
}

@SuppressWarnings("ResultOfMethodCallIgnored")
@NotNull
private Path createTempFile(final String name) throws IOException {
Expand All @@ -72,6 +102,11 @@ private Path createTempFile(final String name) throws IOException {
return file;
}

@NotNull
private Path createTempDir() throws IOException {
return Files.createTempDirectory("testcontainers");
}

private void performChecks(final MountableFile mountableFile) {
final String mountablePath = mountableFile.getFilesystemPath();
assertTrue("The filesystem path '" + mountablePath + "' can be found", new File(mountablePath).exists());
Expand Down

0 comments on commit 2ad57da

Please sign in to comment.