Skip to content

Commit

Permalink
#444 'MountableFile path on Windows' - added separate filesystem path…
Browse files Browse the repository at this point in the history
… for file source (#445)

* #444 'MountableFile path on Windows' - added separate filesystem path for file source

* #444 'MountableFile path on Windows' - made FS path only remove first slash on Windows; made recursiveTar work with file real FS paths on any platform; changed file attribute reading to work with FS path; minor renames and better exc message;

* #444 'MountableFile path on Windows' - small changelog update
  • Loading branch information
glebsts authored and rnorth committed Sep 17, 2017
1 parent 76ef418 commit 540f2ee
Show file tree
Hide file tree
Showing 4 changed files with 47 additions and 24 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ All notable changes to this project will be documented in this file.
## UNRELEASED
### Fixed
- Fixed local Docker Compose executable name resolution on Windows (#416)
- Made tar composing work on Windows as well (#444)

### Changed
- Load `DockerClientProviderStrategy` via Service Loader (#434, #435)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,6 @@ public interface ClasspathTrait<SELF extends ClasspathTrait<SELF> & BuildContext
default SELF withFileFromClasspath(String path, String resourcePath) {
final MountableFile mountableFile = MountableFile.forClasspathResource(resourcePath);

return ((SELF) this).withFileFromPath(path, Paths.get(mountableFile.getResolvedPath()));
return ((SELF) this).withFileFromPath(path, Paths.get(mountableFile.getFilesystemPath()));
}
}
58 changes: 42 additions & 16 deletions core/src/main/java/org/testcontainers/utility/MountableFile.java
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,11 @@ public class MountableFile implements Transferable {
@Getter(lazy = true)
private final String resolvedPath = resolvePath();

@Getter(lazy = true)
private final String filesystemPath = resolveFilesystemPath();

private String resourcePath;

/**
* Obtains a {@link MountableFile} corresponding to a resource on the classpath (including resources in JAR files)
*
Expand Down Expand Up @@ -115,12 +120,7 @@ private static String unencodeResourceURIToFilePath(@NotNull final String resour
* @return a volume-mountable path.
*/
private String resolvePath() {
String result;
if (path.contains(".jar!")) {
result = extractClassPathResourceToTempLocation(this.path);
} else {
result = unencodeResourceURIToFilePath(path);
}
String result = getResourcePath();

if (SystemUtils.IS_OS_WINDOWS) {
result = PathUtils.createMinGWPath(result);
Expand All @@ -129,6 +129,32 @@ private String resolvePath() {
return result;
}

/**
* Obtain a path in local filesystem that the Docker daemon should be able to use to volume mount a file/resource
* into a container. If this is a classpath resource residing in a JAR, it will be extracted to
* a temporary location so that the Docker daemon is able to access it.
*
* @return
*/
private String resolveFilesystemPath() {
String result = getResourcePath();

if (SystemUtils.IS_OS_WINDOWS && result.startsWith("/")) {
result = result.substring(1);
}

return result;
}

private String getResourcePath() {
if (path.contains(".jar!")) {
resourcePath = extractClassPathResourceToTempLocation(this.path);
} else {
resourcePath = unencodeResourceURIToFilePath(path);
}
return resourcePath;
}

/**
* Extract a file or directory tree from a JAR file to a temporary location.
* This allows Docker to mount classpath resources as files.
Expand Down Expand Up @@ -205,22 +231,22 @@ private void deleteOnExit(final Path path) {
*/
@Override
public void transferTo(final TarArchiveOutputStream outputStream, String destinationPathInTar) {
recursiveTar(destinationPathInTar, this.getResolvedPath(), this.getResolvedPath(), outputStream);
recursiveTar(destinationPathInTar, this.getFilesystemPath(), this.getFilesystemPath(), outputStream);
}

/*
* Recursively copies a file/directory into a TarArchiveOutputStream
*/
private void recursiveTar(String destination, String sourceRootDir, String sourceCurrentItem, TarArchiveOutputStream tarArchive) {
private void recursiveTar(String entryFilename, String rootPath, String itemPath, TarArchiveOutputStream tarArchive) {
try {
final File sourceFile = new File(sourceCurrentItem).getCanonicalFile(); // e.g. /foo/bar/baz
final File sourceRootFile = new File(sourceRootDir).getCanonicalFile(); // e.g. /foo
final File sourceFile = new File(itemPath).getCanonicalFile(); // e.g. /foo/bar/baz
final File sourceRootFile = new File(rootPath).getCanonicalFile(); // e.g. /foo
final String relativePathToSourceFile = sourceRootFile.toPath().relativize(sourceFile.toPath()).toFile().toString(); // e.g. /bar/baz

final TarArchiveEntry tarEntry = new TarArchiveEntry(sourceFile, destination + "/" + relativePathToSourceFile); // entry filename e.g. /xyz/bar/baz
final TarArchiveEntry tarEntry = new TarArchiveEntry(sourceFile, entryFilename + "/" + relativePathToSourceFile); // entry filename e.g. /xyz/bar/baz

// TarArchiveEntry automatically sets the mode for file/directory, but we can update to ensure that the mode is set exactly (inc executable bits)
tarEntry.setMode(getUnixFileMode(sourceCurrentItem));
tarEntry.setMode(getUnixFileMode(itemPath));
tarArchive.putArchiveEntry(tarEntry);

if (sourceFile.isFile()) {
Expand All @@ -233,19 +259,19 @@ private void recursiveTar(String destination, String sourceRootDir, String sourc
if (children != null) {
// recurse into child files/directories
for (final File child : children) {
recursiveTar(destination, sourceRootDir + File.separator, child.getCanonicalPath(), tarArchive);
recursiveTar(entryFilename, sourceRootFile.getCanonicalPath(), child.getCanonicalPath(), tarArchive);
}
}
} catch (IOException e) {
log.error("Error when copying TAR file entry: {}", sourceCurrentItem, e);
log.error("Error when copying TAR file entry: {}", itemPath, e);
throw new UncheckedIOException(e); // fail fast
}
}

@Override
public long getSize() {

final File file = new File(this.getResolvedPath());
final File file = new File(this.getFilesystemPath());
if (file.isFile()) {
return file.length();
} else {
Expand All @@ -260,7 +286,7 @@ public String getDescription() {

@Override
public int getFileMode() {
return getUnixFileMode(this.getResolvedPath());
return getUnixFileMode(this.getFilesystemPath());
}

private int getUnixFileMode(final String pathAsString) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,10 +60,6 @@ public void forHostPathWithSpaces() throws Exception {
assertFalse("The resolved path does not contain an escaped space", mountableFile.getResolvedPath().contains("\\ "));
}

/*
*
*/

@SuppressWarnings("ResultOfMethodCallIgnored")
@NotNull
private Path createTempFile(final String name) throws IOException {
Expand All @@ -77,9 +73,9 @@ private Path createTempFile(final String name) throws IOException {
}

private void performChecks(final MountableFile mountableFile) {
final String mountablePath = mountableFile.getResolvedPath();
assertTrue("The resolved path can be found", new File(mountablePath).exists());
assertFalse("The resolved path does not contain any URL escaping", mountablePath.contains("%20"));
final String mountablePath = mountableFile.getFilesystemPath();
assertTrue("The filesystem path '" + mountablePath + "' can be found", new File(mountablePath).exists());
assertFalse("The filesystem path '" + mountablePath + "' does not contain any URL escaping", mountablePath.contains("%20"));
}

}

0 comments on commit 540f2ee

Please sign in to comment.