diff --git a/README.md b/README.md index 820861d..ed9d5dc 100644 --- a/README.md +++ b/README.md @@ -46,10 +46,11 @@ In general, you can pass the same arguments to `docker-run` as you would pass to docker-run --volume $(pwd):/volume ubuntu ls /volume ``` -In addition to the arguments you are passing, `docker-run` however also enables the following features by default. Each of these default features can be disabled, see [Usage](#usage). +In addition to the arguments you are passing, `docker-run` however also enables the following features by default. Most of these default features can be disabled, see [Usage](#usage). - container removal after exit (`--rm`) - interactive tty (`--interactive --tty`) - current directory name as container name (`--name`) +- relative bind mounts (`--volume [./RELATIVE_PATH>]:[TARGET_PATH]`) - GPU support (`--gpus all` / `--runtime nvidia`) - X11 GUI forwarding diff --git a/docker-run-cli/pyproject.toml b/docker-run-cli/pyproject.toml index d1db6b1..73c79d4 100644 --- a/docker-run-cli/pyproject.toml +++ b/docker-run-cli/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "docker-run-cli" -version = "0.9.6" +version = "0.9.7" description = "'docker run' and 'docker exec' with useful defaults" license = {file = "LICENSE"} readme = "README.md" @@ -23,14 +23,14 @@ classifiers = [ "Operating System :: POSIX :: Linux", ] keywords = ["docker", "container"] -dependencies = [] +dependencies = ["GPUtil~=1.4.0"] requires-python = ">=3.7" [project.optional-dependencies] dev = ["build", "twine"] -docker-ros = ["docker-run-docker-ros>=1.0.4"] -plugins = ["docker-run-docker-ros>=1.0.4"] -all = ["docker-run-docker-ros>=1.0.4", "build", "twine"] +docker-ros = ["docker-run-docker-ros>=1.0.5"] +plugins = ["docker-run-docker-ros>=1.0.5"] +all = ["docker-run-docker-ros>=1.0.5", "build", "twine"] [project.urls] "Repository" = "https://github.com/ika-rwth-aachen/docker-run" diff --git a/docker-run-cli/scripts/docker-run b/docker-run-cli/scripts/docker-run index ca238de..ccb0b9a 100755 --- a/docker-run-cli/scripts/docker-run +++ b/docker-run-cli/scripts/docker-run @@ -26,8 +26,21 @@ python3 -m docker_run "${@}" 2>&1 >$CMD_FILE CMD=$(cat $CMD_FILE) rm $CMD_FILE +# convert command string to array to allow for escaped characters, e.g. "docker run -v /path\ with\ spaces:/path\ with\ spaces ..." +CMD_ARRAY=() + while IFS= read -r -d ' ' part; do + while [[ $part == *"\\" ]]; do + part+=" " + part="${part//\\/}" + IFS= read -r -d ' ' next_part + part+=$next_part + done + CMD_ARRAY+=("$part") + done <<< "$CMD" + CMD_ARRAY+=("${part%$'\n'}") + # execute command if [[ ! -z "$CMD" ]]; then echo -e "================================================================================\n" - exec $CMD + exec "${CMD_ARRAY[@]}" fi diff --git a/docker-run-cli/src/docker_run/__init__.py b/docker-run-cli/src/docker_run/__init__.py index 8c336c3..ed32f3f 100644 --- a/docker-run-cli/src/docker_run/__init__.py +++ b/docker-run-cli/src/docker_run/__init__.py @@ -1,2 +1,2 @@ __name__ = "docker-run" -__version__ = "0.9.6" \ No newline at end of file +__version__ = "0.9.7" \ No newline at end of file diff --git a/docker-run-cli/src/docker_run/core.py b/docker-run-cli/src/docker_run/core.py index 6b51630..ced9acf 100644 --- a/docker-run-cli/src/docker_run/core.py +++ b/docker-run-cli/src/docker_run/core.py @@ -146,6 +146,10 @@ def buildDockerCommand(args: Dict[str, Any], unknown_args: List[str] = [], cmd_a else: docker_cmd += ["bash"] # default exec command + # plugin modifications + for plugin in PLUGINS: + docker_cmd = plugin.modifyFinalCommand(docker_cmd, args, unknown_args) + return " ".join(docker_cmd) diff --git a/docker-run-cli/src/docker_run/plugins/core.py b/docker-run-cli/src/docker_run/plugins/core.py index 38957b2..dc8dd99 100644 --- a/docker-run-cli/src/docker_run/plugins/core.py +++ b/docker-run-cli/src/docker_run/plugins/core.py @@ -4,6 +4,8 @@ import tempfile from typing import Any, Dict, List +import GPUtil + from docker_run.utils import log, runCommand from docker_run.plugins.plugin import Plugin @@ -55,6 +57,13 @@ def getExecFlags(cls, args: Dict[str, Any], unknown_args: List[str]) -> List[str flags += cls.interactiveFlags() return flags + @classmethod + def modifyFinalCommand(cls, cmd: List[str], args: Dict[str, Any], unknown_args: List[str]) -> List[str]: + if "-v" in cmd or "--volume" in cmd: + cmd = cls.resolveRelativeVolumeFlags(cmd) + cmd = cls.fixSpacesInVolumeFlags(cmd) + return cmd + @classmethod def removeFlags(cls) -> List[str]: return ["--rm"] @@ -78,12 +87,16 @@ def localeFlags(cls) -> List[str]: @classmethod def gpuSupportFlags(cls) -> List[str]: - if cls.ARCH == "x86_64": - return ["--gpus all"] - elif cls.ARCH == "aarch64" and cls.OS == "Linux": - return ["--runtime nvidia"] + if len(GPUtil.getGPUs()) > 0: + if cls.ARCH == "x86_64": + return ["--gpus all"] + elif cls.ARCH == "aarch64" and cls.OS == "Linux": + return ["--runtime nvidia"] + else: + log(f"GPU not supported by `docker-run` on {cls.OS} with {cls.ARCH} architecture") + return [] else: - log(f"GPU not supported by `docker-run` on {cls.OS} with {cls.ARCH} architecture") + log(f"No GPU detected") return [] @classmethod @@ -92,7 +105,7 @@ def x11GuiForwardingFlags(cls, docker_network: str = "bridge") -> List[str]: display = os.environ.get("DISPLAY") if display is None: return [] - + if cls.OS == "Darwin": runCommand(f"xhost +local:") @@ -119,4 +132,22 @@ def x11GuiForwardingFlags(cls, docker_network: str = "bridge") -> List[str]: @classmethod def currentDirMountFlags(cls) -> List[str]: - return [f"--volume {os.getcwd()}:{os.getcwd()}", f"--workdir {os.getcwd()}"] + cwd = os.getcwd().replace(" ", "\\ ") + return [f"--volume {cwd}:{cwd}", f"--workdir {cwd}"] + + @classmethod + def resolveRelativeVolumeFlags(cls, cmd: List[str]) -> List[str]: + for i, arg in enumerate(cmd): + if arg in ["-v", "--volume"]: + mount_path = cmd[i + 1].split(":")[0] + if mount_path.startswith("."): + absolute_mount_path = os.path.abspath(mount_path) + cmd[i + 1] = absolute_mount_path + cmd[i + 1][len(mount_path):] + return cmd + + @classmethod + def fixSpacesInVolumeFlags(cls, cmd: List[str]) -> List[str]: + for i, arg in enumerate(cmd): + if arg in ["-v", "--volume"]: + cmd[i + 1] = cmd[i + 1].replace(" ", "\\ ") + return cmd \ No newline at end of file diff --git a/docker-run-cli/src/docker_run/plugins/plugin.py b/docker-run-cli/src/docker_run/plugins/plugin.py index 38c6c37..44430ea 100644 --- a/docker-run-cli/src/docker_run/plugins/plugin.py +++ b/docker-run-cli/src/docker_run/plugins/plugin.py @@ -18,3 +18,7 @@ def getRunFlags(cls, args: Dict[str, Any], unknown_args: List[str]) -> List[str] @abstractmethod def getExecFlags(cls, args: Dict[str, Any], unknown_args: List[str]) -> List[str]: raise NotImplementedError() + + @classmethod + def modifyFinalCommand(cls, cmd: List[str], args: Dict[str, Any], unknown_args: List[str]) -> List[str]: + return cmd diff --git a/docker-run-docker-ros/pyproject.toml b/docker-run-docker-ros/pyproject.toml index 48d58a4..a9e8ac4 100644 --- a/docker-run-docker-ros/pyproject.toml +++ b/docker-run-docker-ros/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "docker-run-docker-ros" -version = "1.0.4" +version = "1.0.5" description = "docker-run plugin for Docker images built by docker-ros" license = {file = "LICENSE"} readme = "README.md" @@ -23,7 +23,7 @@ classifiers = [ "Operating System :: POSIX :: Linux", ] keywords = ["docker", "container", "ros"] -dependencies = ["docker-run-cli>=0.9.4"] +dependencies = ["docker-run-cli>=0.9.7"] requires-python = ">=3.7" [project.urls] diff --git a/docker-run-docker-ros/src/docker_run/plugins/docker_ros.py b/docker-run-docker-ros/src/docker_run/plugins/docker_ros.py index 0b4e28d..484d2bf 100644 --- a/docker-run-docker-ros/src/docker_run/plugins/docker_ros.py +++ b/docker-run-docker-ros/src/docker_run/plugins/docker_ros.py @@ -52,4 +52,5 @@ def userExecFlags(cls, user: str) -> List[str]: @classmethod def currentDirMountWorkspaceFlags(cls) -> List[str]: - return [f"--volume {os.getcwd()}:{cls.TARGET_MOUNT}", f"--workdir {cls.WORKSPACE}"] + cwd = os.getcwd().replace(" ", "\\ ") + return [f"--volume {cwd}:{cls.TARGET_MOUNT}", f"--workdir {cls.WORKSPACE}"]