All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.fabric8.maven.docker.util.VolumeBindingUtil Maven / Gradle / Ivy

There is a newer version: 0.45.0
Show newest version
package io.fabric8.maven.docker.util;

import io.fabric8.maven.docker.config.RunVolumeConfiguration;

import java.io.File;
import java.io.IOException;
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.
 * 

* 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: *

*
A host path relative to the current working directory
*
./relative/path:/absolute/container/path
* *
A host path relative to the current working directory
*
relative/path/:/absolute/container/path
* *
A host path relative to the parent of the current working directory
*
../relative/path:/absolute/container/path
* *
A host path equal to the current user's home directory
*
~:/absolute/container/path
* *
A host path relative to the current user's home directory
*
~/relative/path:/absolute/container/path
*
*

*

* Understand that the following is not considered a relative binding path, and is instead interpreted as a * named volume: *

*
{@code rel} is interpreted as a named volume. Use {@code ./rel} or {@code rel/} to have it * interpreted as a relative path.
*
rel:/absolute/container/path
*
*

*

* Volume bindings that specify an absolute path for the host portion are preserved and returned unmodified. *

*/ 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 * is returned unmodified. *

Discussion:

*

* Volumes may be defined inside of {@code service} blocks * as documented here: *

*
     * volumes:
     * # Just specify a path and let the Engine create a volume
     * - /var/lib/mysql
     *
     * # Specify an absolute path mapping
     * - /opt/data:/var/lib/mysql
     *
     * # Path on the host, relative to the Compose file
     * - ./cache:/tmp/cache
     *
     * # User-relative path
     * - ~/configs:/etc/configs/:ro
     *
     * # Named volume
     * - datavolume:/var/lib/mysql"
     * 
*

* 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 must be absolute. Paths beginning with {@code ~} are interpreted relative to * {@code new File(System.getProperty("user.home"))}, and {@code baseDir} is ignored. *

*

* Volume strings that do not begin with a {@code ./}, {@code ../}, or {@code ~} are returned as-is. *

*

Examples:

*

* 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/basedir/reldir:/some/other/dir} *

*

* 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/reldir:/some/other/dir} *

*

* Given {@code baseDir} equal to "/path/to/basedir" and a {@code bindingString} string equal to * "~/reldir:/some/other/dir", this method returns {@code /home/user/reldir:/some/other/dir} *

*

* Given {@code baseDir} equal to "/path/to/basedir" and a {@code bindingString} equal to * "src/test/docker:/some/other/dir", this method returns {@code /path/to/basedir/src/test/docker:/some/other/dir} *

*

* Given a {@code bindingString} equal to "foo:/some/other/dir", this method returns {@code foo:/some/other/dir}, * because {@code foo} is considered to be a named volume, not a relative path. *

* * @param baseDir the base directory used to resolve relative paths (e.g. beginning with {@code ./}, {@code ../}, * {@code ~}) present in the {@code bindingString}; must be absolute * @param bindingString the volume string from the docker-compose file * @return the volume string, with any relative paths resolved as absolute paths * @throws IllegalArgumentException if the supplied {@code baseDir} is not absolute */ public static String resolveRelativeVolumeBinding(File baseDir, String bindingString) { // a 'services:' -> service -> 'volumes:' may be formatted as: // (https://docs.docker.com/compose/compose-file/compose-file-v2/#volumes-volume_driver) // // volumes: // # Just specify a path and let the Engine create a volume // - /var/lib/mysql // // # Specify an absolute path mapping // - /opt/data:/var/lib/mysql // // # Path on the host, relative to the Compose file // - ./cache:/tmp/cache // // # User-relative path // - ~/configs:/etc/configs/:ro // // # Named volume // - datavolume:/var/lib/mysql String[] pathParts = bindingString.split(":"); String localPath = pathParts[0]; if (isRelativePath(localPath)) { File resolvedFile; 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 { localPath = resolvedFile.getCanonicalFile().getAbsolutePath(); } catch (IOException e) { throw new RuntimeException("Unable to canonicalize '" + resolvedFile + "'"); } } if (pathParts.length > 1) { pathParts[0] = localPath; return join(":", pathParts); } return localPath; } /** * 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. *

* Relative paths are resolved relative to the supplied {@code baseDir}, which must be absolute. *

* * @param baseDir the base directory used to resolve relative paths (e.g. beginning with {@code ./}, {@code ../}, * {@code ~}) present in the binding string; must 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) { List bindings = volumeConfiguration.getBind(); if (bindings == null || bindings.isEmpty()) { return; } for (int i = 0; i < bindings.size(); i++) { bindings.set(i, resolveRelativeVolumeBinding(baseDir, bindings.get(i))); } } /** * Determines if the supplied volume binding path contains a relative path. This is subtle, because volume * bindings may specify a named volume per the discussion below. *

Discussion:

*

* Volumes may be defined inside of {@code service} blocks * as documented here: *

*
     * volumes:
     * # Just specify a path and let the Engine create a volume
     * - /var/lib/mysql
     *
     * # Specify an absolute path mapping
     * - /opt/data:/var/lib/mysql
     *
     * # Path on the host, relative to the Compose file
     * - ./cache:/tmp/cache
     *
     * # User-relative path
     * - ~/configs:/etc/configs/:ro
     *
     * # Named volume
     * - datavolume:/var/lib/mysql"
     * 
*

* Volume binding paths that begin with {@code ./}, {@code ../}, or {@code ~} clearly represent a relative path. * However, binding paths that do not begin with those characters may represent a named volume. For * example, the binding string {@code rel:/path/to/container/mountpoint} refers to the named volume {@code * rel}. Because it is desirable to fully support relative paths for volumes provided in a run configuration, this * method attempts to resolve the ambiguity between a named volume and a relative path. *

*

* Therefore, volume binding strings will be considered to contain a relative path when any of the following * conditions are true: *

    *
  • the volume binding path begins with {@code ./}, {@code ../}, or {@code ~}
  • *
  • the volume binding path contains the character {@code /} and {@code /} is not at index 0 of * the volume binding path
  • *
*

*

* If the binding string {@code rel:/path/to/container/mountpoint} is intended to represent {@code rel} as a * relative path and not as a named volume, then the binding string should be modified to contain * a forward slash like so: {@code rel/:/path/to/container/mountpoint}. Another option would be to prefix {@code * rel} with a {@code ./} like so: {@code ./rel:/path/to/container/mountpoint} *

* * * @param candidatePath the candidate volume binding path * @return true if the candidate path is considered to be a relative path */ static boolean isRelativePath(String candidatePath) { // 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; } // './' or '../' if (candidatePath.startsWith(DOT + RUNTIME_SEP) || candidatePath.startsWith(DOT + DOT + RUNTIME_SEP)) { return true; } if (candidatePath.contains(UNIX_SEP) || candidatePath.contains(WINDOWS_SEP)) { return true; } if (isUserHomeRelativePath(candidatePath)) { return true; } return false; } /** * Returns true if the supplied path begins with {@code ~}. This means that the path should be resolved relative * to the user's home directory. * * @param candidatePath the candidate path that may represent a path under the user's home directory * @return true if the path begins with {@code ~} */ static boolean isUserHomeRelativePath(String candidatePath) { return candidatePath.startsWith(TILDE); } private static String prepareUserHomeRelativePath(String userHomePath) { if (!(isUserHomeRelativePath(userHomePath))) { return userHomePath; } // Handle ~user and ~/path and ~ // '~' if (userHomePath.equals(TILDE)) { return ""; } // '~/' if (userHomePath.startsWith(TILDE + RUNTIME_SEP)) { return userHomePath.substring(2); } // '~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) { StringBuilder result = new StringBuilder(); int i = 0; while (i < components.length) { result.append(components[i++]); if (i < components.length) { result.append(with); } } return result.toString(); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy