Skip to content

Commit

Permalink
Update tests to work on Windows platform as well as on *nix platforms.
Browse files Browse the repository at this point in the history
* Consolidates constants and test-related utility methods in PathTestUtil
* Updates Javadoc and comments
* Updates DockerFileUtilTest to be tolerant of line-ending differences (fails on Windows without these changes)

Signed-off-by: Elliot Metsger <emetsger@jhu.edu>
  • Loading branch information
emetsger committed Sep 7, 2017
1 parent 8552deb commit 406a690
Show file tree
Hide file tree
Showing 9 changed files with 589 additions and 153 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ public class DockerPathUtil {
* @throws IllegalArgumentException if the supplied {@code baseDir} does not represent an absolute path
*/
public static File resolveAbsolutely(String pathToResolve, String baseDir) {
// TODO: handle the case where pathToResolve specifies a non-existent path, for example, a base directory equal to "/" and a relative path of "../../foo".
File fileToResolve = new File(pathToResolve);

if (fileToResolve.isAbsolute()) {
Expand Down
123 changes: 96 additions & 27 deletions src/main/java/io/fabric8/maven/docker/util/VolumeBindingUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,81 @@

import java.io.File;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import java.util.regex.Pattern;

import static io.fabric8.maven.docker.util.DockerPathUtil.resolveAbsolutely;

/**
* Utility methods for working with Docker volume bindings.
* <p>
* This class provides explicit support for relative binding paths. This means that the plugin configuration or
* docker compose file can specify a relative path when configuring a volume binding. Methods in this class will
* examine volume binding strings in a {@link RunVolumeConfiguration} and resolve any relative paths in the host portion
* of volume bindings. Examples of relative bindings include:
* <dl>
* <dd>A host path relative to the current working directory</dd>
* <dt>./relative/path:/absolute/container/path</dt>
*
* <dd>A host path relative to the current working directory</dd>
* <dt>relative/path/:/absolute/container/path</dt>
*
* <dd>A host path relative to the parent of the current working directory</dd>
* <dt>../relative/path:/absolute/container/path</dt>
*
* <dd>A host path equal to the current user's home directory</dd>
* <dt>~:/absolute/container/path</dt>
*
* <dd>A host path relative to the current user's home directory</dd>
* <dt>~/relative/path:/absolute/container/path</dt>
* </dl>
* </p>
* <p>
* Understand that the following is <em>not</em> considered a relative binding path, and is instead interpreted as a
* <em>named volume</em>:
* <dl>
* <dd>{@code rel} is interpreted as a <em>named volume</em>. Use {@code ./rel} or {@code rel/} to have it
* interpreted as a relative path.</dd>
* <dt>rel:/absolute/container/path</dt>
* </dl>
* </p>
* <p>
* Volume bindings that specify an absolute path for the host portion are preserved and returned unmodified.
* </p>
*/
public class VolumeBindingUtil {

/**
* A dot representing the current working directory
*/
private static final String DOT = ".";

/**
* A tilde representing the current user's home directory
*/
private static final String TILDE = "~";

/**
* The current runtime platform file separator, '/' for *nix, '\' for Windows
*/
private static final String RUNTIME_SEP = System.getProperty("file.separator");

/**
* Windows file separator: '\'
*/
private static final String WINDOWS_SEP = "\\";

/**
* Unix file separator '/'
*/
private static final String UNIX_SEP = "/";

/**
* Matches a windows drive letter followed by a colon and backwards slash. For example, will match:
* 'C:\' or 'x:\'.
*/
private static final Pattern WINDOWS_DRIVE_PATTERN = Pattern.compile("^[A-Za-z]:\\\\.*");

/**
* Resolves relative paths in the supplied {@code bindingString}, and returns a binding string that has relative
* paths replaced with absolute paths. If the supplied {@code bindingString} does not contain a relative path, it
Expand Down Expand Up @@ -43,8 +108,8 @@ public class VolumeBindingUtil {
* <p>
* This method only operates on volume strings that are relative: beginning with {@code ./}, {@code ../}, or
* {@code ~}. Relative paths beginning with {@code ./} or {@code ../} are absolutized relative to the supplied
* {@code baseDir}, which <em>must</em> be absolute. Paths beginning with {@code ~/} are interpreted relative to
* {@code new File(System.getProperty( "user.home"))}, and {@code baseDir} is ignored.
* {@code baseDir}, which <em>must</em> be absolute. Paths beginning with {@code ~} are interpreted relative to
* {@code new File(System.getProperty("user.home"))}, and {@code baseDir} is ignored.
* </p>
* <p>
* Volume strings that do not begin with a {@code ./}, {@code ../}, or {@code ~} are returned as-is.
Expand All @@ -60,7 +125,7 @@ public class VolumeBindingUtil {
* </p>
* <p>
* Given {@code baseDir} equal to "/path/to/basedir" and a {@code bindingString} string equal to
* "~/reldir:/some/other/dir", this method returns {@code /path/to/user/home/reldir:/some/other/dir}
* "~/reldir:/some/other/dir", this method returns {@code /home/user/reldir:/some/other/dir}
* </p>
* <p>
* Given {@code baseDir} equal to "/path/to/basedir" and a {@code bindingString} equal to
Expand All @@ -79,10 +144,6 @@ public class VolumeBindingUtil {
*/
public static String resolveRelativeVolumeBinding(File baseDir, String bindingString) {

if (!baseDir.isAbsolute()) {
throw new IllegalArgumentException("Base directory '" + baseDir + "' must be absolute.");
}

// a 'services:' -> service -> 'volumes:' may be formatted as:
// (https://docs.docker.com/compose/compose-file/compose-file-v2/#volumes-volume_driver)
//
Expand Down Expand Up @@ -110,6 +171,9 @@ public static String resolveRelativeVolumeBinding(File baseDir, String bindingSt
if (isUserHomeRelativePath(localPath)) {
resolvedFile = resolveAbsolutely(prepareUserHomeRelativePath(localPath), System.getProperty("user.home"));
} else {
if (!baseDir.isAbsolute()) {
throw new IllegalArgumentException("Base directory '" + baseDir + "' must be absolute.");
}
resolvedFile = resolveAbsolutely(localPath, baseDir.getAbsolutePath());
}
try {
Expand All @@ -130,18 +194,17 @@ public static String resolveRelativeVolumeBinding(File baseDir, String bindingSt
/**
* Iterates over each {@link RunVolumeConfiguration#getBind() binding} in the {@code volumeConfiguration}, and
* resolves any relative paths in the binding strings using {@link #resolveRelativeVolumeBinding(File, String)}.
* The {@code volumeConfiguration} is modified in place, with any relative paths replaced with absolute paths.
* <p>
* Relative paths are resolved relative to the supplied {@code baseDir}, which <em>must</em> be absolute.
* </p>
*
* @param baseDir the base directory used to resolve relative paths (e.g. beginning with {@code ./}, {@code ../},
* {@code ~}) present in the binding string; <em>must</em> be absolute
* @param volumeConfiguration the volume configuration that may contain volume binding specifications
* @throws IllegalArgumentException if the supplied {@code baseDir} is not absolute
*/
public static void resolveRelativeVolumeBindings(File baseDir, RunVolumeConfiguration volumeConfiguration) {

if (!baseDir.isAbsolute()) {
throw new IllegalArgumentException("Base directory '" + baseDir + "' must be absolute.");
}

List<String> bindings = volumeConfiguration.getBind();

if (bindings.isEmpty()) {
Expand Down Expand Up @@ -206,15 +269,23 @@ public static void resolveRelativeVolumeBindings(File baseDir, RunVolumeConfigur
* @return true if the candidate path is considered to be a relative path
*/
static boolean isRelativePath(String candidatePath) {
if (candidatePath.startsWith("/")) {

// java.io.File considers Windows paths to be absolute _only_ if they start with a drive letter. That is,
// a Windows path '\foo\bar\baz' is _not_ considered absolute by File#isAbsolute. This block differs from
// java.io.File in that it considers Windows paths to be absolute if they begin with the file separator _or_ a
// drive letter
if (candidatePath.startsWith(UNIX_SEP) ||
candidatePath.startsWith(WINDOWS_SEP) ||
WINDOWS_DRIVE_PATTERN.matcher(candidatePath).matches()) {
return false;
}

if (candidatePath.startsWith("./") || candidatePath.startsWith("../")) {
// './' or '../'
if (candidatePath.startsWith(DOT + RUNTIME_SEP) || candidatePath.startsWith(DOT + DOT + RUNTIME_SEP)) {
return true;
}

if (candidatePath.contains("/")) {
if (candidatePath.contains(UNIX_SEP) || candidatePath.contains(WINDOWS_SEP)) {
return true;
}

Expand All @@ -233,7 +304,7 @@ static boolean isRelativePath(String candidatePath) {
* @return true if the path begins with {@code ~}
*/
static boolean isUserHomeRelativePath(String candidatePath) {
return candidatePath.startsWith("~");
return candidatePath.startsWith(TILDE);
}

private static String prepareUserHomeRelativePath(String userHomePath) {
Expand All @@ -243,22 +314,20 @@ private static String prepareUserHomeRelativePath(String userHomePath) {

// Handle ~user and ~/path and ~

if (userHomePath.equals("~")) {
// '~'
if (userHomePath.equals(TILDE)) {
return "";
}

if (userHomePath.startsWith("~/")) {
// '~/'
if (userHomePath.startsWith(TILDE + RUNTIME_SEP)) {
return userHomePath.substring(2);
}

// e.g. userHomePath = '~user/foo' we just want 'foo'
if (userHomePath.contains("/")) {
return userHomePath.substring(userHomePath.indexOf("/") + 1);
}

// otherwise userHomePath = '~user' and we can just return the empty string.

return "";
// '~user' is not supported; no logic to support "find the home directory for an arbitrary user".
// e.g. '~user' or '~user/foo'
throw new IllegalArgumentException("'" + userHomePath + "' cannot be relativized, cannot resolve arbitrary" +
" user home paths.");
}

private static String join(String with, String... components) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@

import java.io.File;

import static io.fabric8.maven.docker.util.PathTestUtil.DOT;
import static io.fabric8.maven.docker.util.PathTestUtil.SEP;
import static io.fabric8.maven.docker.util.PathTestUtil.createTmpFile;
import static io.fabric8.maven.docker.util.PathTestUtil.join;
import static org.junit.Assert.assertEquals;

/**
Expand All @@ -18,30 +22,33 @@
@RunWith(JMockit.class)
public class ComposeUtilsTest {

private final String className = ComposeUtilsTest.class.getSimpleName();

private final String ABS_BASEDIR = createTmpFile(className).getAbsolutePath();

@Mocked
private MavenProject project;

@Test
public void resolveComposeFileWithAbsoluteComposeFile() throws Exception {
String absComposeFile = "/absolute/path/to/docker-compose.yaml";
String absComposeFile = createTmpFile(className).getAbsolutePath() + SEP + "docker-compose.yaml";

assertEquals(new File(absComposeFile),
ComposeUtils.resolveComposeFileAbsolutely(null, absComposeFile, null));
}

@Test
public void resolveComposeFileWithRelativeComposeFileAndAbsoluteBaseDir() throws Exception {
String relComposeFile = "relative/path/to/docker-compose.yaml";
String absBaseDir = "/basedir/";
final String absMavenProjectDir = "/absolute/path/to/maven/project";
String relComposeFile = join(SEP, "relative", "path", "to", "docker-compose.yaml"); // relative/path/to/docker-compose.yaml
final String absMavenProjectDir = createTmpFile(className).getAbsolutePath();

new Expectations() {{
project.getBasedir();
result = new File(absMavenProjectDir);
}};

assertEquals(new File(absBaseDir, relComposeFile),
ComposeUtils.resolveComposeFileAbsolutely(absBaseDir, relComposeFile, project));
assertEquals(new File(ABS_BASEDIR, relComposeFile),
ComposeUtils.resolveComposeFileAbsolutely(ABS_BASEDIR, relComposeFile, project));

new VerificationsInOrder() {{
project.getBasedir();
Expand All @@ -50,9 +57,9 @@ public void resolveComposeFileWithRelativeComposeFileAndAbsoluteBaseDir() throws

@Test
public void resolveComposeFileWithRelativeComposeFileAndRelativeBaseDir() throws Exception {
String relComposeFile = "relative/path/to/docker-compose.yaml";
String relBaseDir = "basedir/";
final String absMavenProjectDir = "/absolute/path/to/maven/project";
String relComposeFile = join(SEP, "relative", "path", "to", "docker-compose.yaml"); // relative/path/to/docker-compose.yaml
String relBaseDir = "basedir" + SEP;
final String absMavenProjectDir = createTmpFile(className).getAbsolutePath();

new Expectations() {{
project.getBasedir();
Expand All @@ -69,10 +76,11 @@ public void resolveComposeFileWithRelativeComposeFileAndRelativeBaseDir() throws

@Test
public void resolveComposesFileWithRelativeComposeFileParentDirectory() throws Exception {
String relComposeFile = "../relative/path/to/docker-compose.yaml";
String absBaseDir = "/basedir/";
String relComposeFile = join(SEP, DOT + DOT, "relative", "path", "to", "docker-compose.yaml"); // ../relative/path/to/docker-compose.yaml
File tmpDir = createTmpFile(ComposeUtilsTest.class.getName());
String absBaseDir = tmpDir.getAbsolutePath();

assertEquals(new File(relComposeFile.substring(2)),
assertEquals(new File(tmpDir.getParentFile(), relComposeFile.substring(3)),
ComposeUtils.resolveComposeFileAbsolutely(absBaseDir, relComposeFile, null));
}
}
Loading

0 comments on commit 406a690

Please sign in to comment.