├── .github └── workflows │ └── publish.yml ├── .gitignore ├── CITATION.cff ├── LICENSE ├── README.md ├── assets ├── banner.png ├── logo.png └── teaser.png ├── docker-run-cli ├── LICENSE ├── MANIFEST.in ├── README.md ├── pyproject.toml ├── scripts │ ├── activate-python-docker-run-shell-completion │ └── docker-run └── src │ └── docker_run │ ├── __init__.py │ ├── __main__.py │ ├── bash_completion.d │ └── docker-run │ ├── core.py │ ├── plugins │ ├── __init__.py │ ├── core.py │ └── plugin.py │ └── utils.py └── docker-run-docker-ros ├── LICENSE ├── README.md ├── pyproject.toml └── src └── docker_run └── plugins └── docker_ros.py /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: Publish to PyPI 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | tags: 8 | - '*' 9 | pull_request: 10 | branches: 11 | - main 12 | 13 | jobs: 14 | publish: 15 | name: Publish ${{ matrix.package }} 16 | runs-on: ubuntu-latest 17 | strategy: 18 | matrix: 19 | package: [docker-run-cli, docker-run-docker-ros] 20 | steps: 21 | - name: Checkout repository 22 | uses: actions/checkout@v3 23 | - name: Set up Python 24 | uses: actions/setup-python@v4 25 | with: 26 | python-version: "3.x" 27 | - name: Install pypa/build 28 | run: python3 -m pip install --user build 29 | - name: Build wheel and tarball 30 | run: python3 -m build --sdist --wheel --outdir dist/ ${{ matrix.package }} 31 | - name: Publish to TestPyPI 32 | uses: pypa/gh-action-pypi-publish@v1.12.4 33 | with: 34 | password: ${{ secrets.TEST_PYPI_API_TOKEN }} 35 | repository-url: https://test.pypi.org/legacy/ 36 | skip-existing: true 37 | verbose: true 38 | - name: Publish to PyPI 39 | if: startsWith(github.ref, 'refs/tags') 40 | uses: pypa/gh-action-pypi-publish@v1.8.6 41 | with: 42 | password: ${{ secrets.PYPI_API_TOKEN }} 43 | skip-existing: true 44 | verbose: true 45 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by https://www.toptal.com/developers/gitignore/api/python 2 | # Edit at https://www.toptal.com/developers/gitignore?templates=python 3 | 4 | ### Python ### 5 | # Byte-compiled / optimized / DLL files 6 | __pycache__/ 7 | *.py[cod] 8 | *$py.class 9 | 10 | # C extensions 11 | *.so 12 | 13 | # Distribution / packaging 14 | .Python 15 | build/ 16 | develop-eggs/ 17 | dist/ 18 | downloads/ 19 | eggs/ 20 | .eggs/ 21 | lib/ 22 | lib64/ 23 | parts/ 24 | sdist/ 25 | var/ 26 | wheels/ 27 | share/python-wheels/ 28 | *.egg-info/ 29 | .installed.cfg 30 | *.egg 31 | MANIFEST 32 | 33 | # PyInstaller 34 | # Usually these files are written by a python script from a template 35 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 36 | *.manifest 37 | *.spec 38 | 39 | # Installer logs 40 | pip-log.txt 41 | pip-delete-this-directory.txt 42 | 43 | # Unit test / coverage reports 44 | htmlcov/ 45 | .tox/ 46 | .nox/ 47 | .coverage 48 | .coverage.* 49 | .cache 50 | nosetests.xml 51 | coverage.xml 52 | *.cover 53 | *.py,cover 54 | .hypothesis/ 55 | .pytest_cache/ 56 | cover/ 57 | 58 | # Translations 59 | *.mo 60 | *.pot 61 | 62 | # Django stuff: 63 | *.log 64 | local_settings.py 65 | db.sqlite3 66 | db.sqlite3-journal 67 | 68 | # Flask stuff: 69 | instance/ 70 | .webassets-cache 71 | 72 | # Scrapy stuff: 73 | .scrapy 74 | 75 | # Sphinx documentation 76 | docs/_build/ 77 | 78 | # PyBuilder 79 | .pybuilder/ 80 | target/ 81 | 82 | # Jupyter Notebook 83 | .ipynb_checkpoints 84 | 85 | # IPython 86 | profile_default/ 87 | ipython_config.py 88 | 89 | # pyenv 90 | # For a library or package, you might want to ignore these files since the code is 91 | # intended to run in multiple environments; otherwise, check them in: 92 | # .python-version 93 | 94 | # pipenv 95 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 96 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 97 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 98 | # install all needed dependencies. 99 | #Pipfile.lock 100 | 101 | # poetry 102 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. 103 | # This is especially recommended for binary packages to ensure reproducibility, and is more 104 | # commonly ignored for libraries. 105 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control 106 | #poetry.lock 107 | 108 | # pdm 109 | # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. 110 | #pdm.lock 111 | # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it 112 | # in version control. 113 | # https://pdm.fming.dev/#use-with-ide 114 | .pdm.toml 115 | 116 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm 117 | __pypackages__/ 118 | 119 | # Celery stuff 120 | celerybeat-schedule 121 | celerybeat.pid 122 | 123 | # SageMath parsed files 124 | *.sage.py 125 | 126 | # Environments 127 | .env 128 | .venv 129 | env/ 130 | venv/ 131 | ENV/ 132 | env.bak/ 133 | venv.bak/ 134 | 135 | # Spyder project settings 136 | .spyderproject 137 | .spyproject 138 | 139 | # Rope project settings 140 | .ropeproject 141 | 142 | # mkdocs documentation 143 | /site 144 | 145 | # mypy 146 | .mypy_cache/ 147 | .dmypy.json 148 | dmypy.json 149 | 150 | # Pyre type checker 151 | .pyre/ 152 | 153 | # pytype static type analyzer 154 | .pytype/ 155 | 156 | # Cython debug symbols 157 | cython_debug/ 158 | 159 | # PyCharm 160 | # JetBrains specific template is maintained in a separate JetBrains.gitignore that can 161 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore 162 | # and can be added to the global gitignore or merged into this file. For a more nuclear 163 | # option (not recommended) you can uncomment the following to ignore the entire idea folder. 164 | #.idea/ 165 | 166 | ### Python Patch ### 167 | # Poetry local configuration file - https://python-poetry.org/docs/configuration/#local-configuration 168 | poetry.toml 169 | 170 | # ruff 171 | .ruff_cache/ 172 | 173 | # LSP config files 174 | pyrightconfig.json 175 | 176 | # End of https://www.toptal.com/developers/gitignore/api/python -------------------------------------------------------------------------------- /CITATION.cff: -------------------------------------------------------------------------------- 1 | cff-version: 1.2.0 2 | message: "We hope that our tools can help your research. If this is the case, please cite it using the following metadata." 3 | 4 | title: dorotos 5 | type: software 6 | repository-code: "https://github.com/ika-rwth-aachen/docker-run" 7 | date-released: 2023-05-28 8 | authors: 9 | - given-names: Jean-Pierre 10 | family-names: Busch 11 | - given-names: Lennart 12 | family-names: Reiher 13 | 14 | preferred-citation: 15 | title: "Enabling the Deployment of Any-Scale Robotic Applications in Microservice-Based Service-Oriented Architectures through Automated Containerization" 16 | type: conference-paper 17 | conference: 18 | name: "2024 IEEE International Conference on Robotics and Automation (ICRA)" 19 | year: 2024 20 | pages: "17650-17656" 21 | doi: "10.1109/ICRA57147.2024.10611586" 22 | url: "https://ieeexplore.ieee.org/document/10611586" 23 | authors: 24 | - given-names: Jean-Pierre 25 | family-names: Busch 26 | orcid: "https://orcid.org/0009-0000-1417-0463" 27 | - given-names: Lennart 28 | family-names: Reiher 29 | orcid: "https://orcid.org/0000-0002-7309-164X" 30 | - given-names: Lutz 31 | family-names: Eckstein 32 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023-2024 Institute for Automotive Engineering (ika), RWTH Aachen University 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # *docker-run* – ``docker run`` and ``docker exec`` with useful defaults 4 | 5 |

6 | 7 | 8 | 9 | 10 |

11 | 12 | *docker-run* is a CLI tool for simplified interaction with Docker images. Use it to easily start and attach to Docker containers with useful predefined arguments. 13 | 14 | > [!IMPORTANT] 15 | > This repository is open-sourced and maintained by the [**Institute for Automotive Engineering (ika) at RWTH Aachen University**](https://www.ika.rwth-aachen.de/). 16 | > **DevOps, Containerization and Orchestration of Software-Defined Vehicles** are some of many research topics within our [*Vehicle Intelligence & Automated Driving*](https://www.ika.rwth-aachen.de/en/competences/fields-of-research/vehicle-intelligence-automated-driving.html) domain. 17 | > If you would like to learn more about how we can support your advanced driver assistance and automated driving efforts, feel free to reach out to us! 18 | > :email: ***opensource@ika.rwth-aachen.de*** 19 | 20 |

21 | 22 |

23 | 24 | While *docker-run* can be used with any Docker image, we recommend to also check out our other tools for Docker and ROS. 25 | - [*docker-ros*](https://github.com/ika-rwth-aachen/docker-ros) automatically builds minimal container images of ROS applications 26 | - [*docker-ros-ml-images*](https://github.com/ika-rwth-aachen/docker-ros-ml-images) provides machine learning-enabled ROS Docker images 27 | 28 | 29 | ## Quick Demo 30 | 31 | The following quickly launches the GUI application `xeyes` to demonstrate how `docker-run` takes care of X11 forwarding from container to host. The `--verbose` flag prints the underlying `docker run` command that is run under the hood. 32 | 33 | ```bash 34 | docker-run --verbose 607qwq/xeyes 35 | ``` 36 | 37 | 38 | ## Functionality 39 | 40 | `docker-run` is designed to be used the same way as the official [`docker run`](https://docs.docker.com/engine/reference/commandline/run/) and [`docker exec`](https://docs.docker.com/engine/reference/commandline/exec/) commands. 41 | 42 | In general, you can pass the same arguments to `docker-run` as you would pass to `docker run`, e.g. 43 | 44 | ```bash 45 | docker-run --volume $(pwd):/volume ubuntu ls /volume 46 | ``` 47 | 48 | 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). 49 | - container removal after exit (`--rm`) 50 | - interactive tty (`--interactive --tty`) 51 | - current directory name as container name (`--name`) 52 | - relative bind mounts (`--volume [./RELATIVE_PATH>]:[TARGET_PATH]`) 53 | - GPU support (`--gpus all` / `--runtime nvidia`) 54 | - X11 GUI forwarding 55 | 56 | If a container with matching name is already running, `docker-run` will execute a command in that container via `docker exec` instead. This lets you quickly attach to a running container without passing any command, e.g. 57 | 58 | ```bash 59 | docker-run --name my-running-container 60 | ``` 61 | 62 | Unlike with `docker run`, you can also set the Docker image via the `--image` arguments, see [Usage](#usage). This may be required for more complex use cases. 63 | 64 | 65 | ## Installation 66 | 67 | ```bash 68 | pip install docker-run-cli 69 | 70 | # (optional) shell auto-completion 71 | source $(activate-python-docker-run-shell-completion 2> /dev/null) 72 | ``` 73 | 74 | > [!WARNING] 75 | > Outside of a virtual environment, *pip* may default to a user-site installation of executables to `~/.local/bin`, which may not be present in your shell's `PATH`. If running `docker-run` errors with `docker-run: command not found`, add the directory to your path. [*(More information)*](https://packaging.python.org/en/latest/tutorials/installing-packages/#installing-to-the-user-site) 76 | > ```bash 77 | > echo "export PATH=\$HOME/.local/bin:\$PATH" >> ~/.bashrc 78 | > source ~/.bashrc 79 | > ``` 80 | 81 | 82 | ## Usage 83 | 84 | ``` 85 | usage: docker-run [--help] [--image IMAGE] [--loc] [--mwd] [--name NAME] 86 | [--no-gpu] [--no-it] [--no-name] [--no-rm] [--no-tz] 87 | [--no-x11] [--verbose] [--version] 88 | 89 | Executes `docker run` with the following features enabled by default, each of 90 | which can be disabled individually: container removal after exit, interactive 91 | tty, current directory name as container name, GPU support, X11 GUI 92 | forwarding. Passes any additional arguments to `docker run`. Executes `docker 93 | exec` instead if a container with the specified name (`--name`) is already 94 | running. 95 | 96 | options: 97 | --help show this help message and exit 98 | --image IMAGE image name (may also be specified without --image as last 99 | argument before command) 100 | --loc enable automatic locale 101 | --mwd mount current directory at same path 102 | --name NAME container name; generates `docker exec` command if already 103 | running 104 | --no-gpu disable automatic GPU support 105 | --no-it disable automatic interactive tty 106 | --no-name disable automatic container name (current directory) 107 | --no-rm disable automatic container removal 108 | --no-tz disable automatic timezone 109 | --no-x11 disable automatic X11 GUI forwarding 110 | --verbose print generated command 111 | --version show program's version number and exit 112 | ``` 113 | 114 | ## Plugins 115 | 116 | `docker-run` can be extended through plugins. Plugins are installed as optional dependencies. 117 | 118 | ```bash 119 | # install specific plugin 120 | pip install docker-run-cli[] 121 | 122 | # install all plugins 123 | pip install docker-run-cli[plugins] 124 | ``` 125 | 126 | | Plugin | Description | 127 | | --- | --- | 128 | | [`docker-ros`](https://pypi.org/project/docker-run-docker-ros) | extra functionality for Docker images built by [*docker-ros*](https://github.com/ika-rwth-aachen/docker-ros) | 129 | -------------------------------------------------------------------------------- /assets/banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ika-rwth-aachen/docker-run/9181077a729fbe020dcd32bc0bcf2d8effc527d7/assets/banner.png -------------------------------------------------------------------------------- /assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ika-rwth-aachen/docker-run/9181077a729fbe020dcd32bc0bcf2d8effc527d7/assets/logo.png -------------------------------------------------------------------------------- /assets/teaser.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ika-rwth-aachen/docker-run/9181077a729fbe020dcd32bc0bcf2d8effc527d7/assets/teaser.png -------------------------------------------------------------------------------- /docker-run-cli/LICENSE: -------------------------------------------------------------------------------- 1 | ../LICENSE -------------------------------------------------------------------------------- /docker-run-cli/MANIFEST.in: -------------------------------------------------------------------------------- 1 | include src/docker_run/bash_completion.d/docker-run -------------------------------------------------------------------------------- /docker-run-cli/README.md: -------------------------------------------------------------------------------- 1 | ../README.md -------------------------------------------------------------------------------- /docker-run-cli/pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["setuptools>=61.0.0", "wheel"] 3 | build-backend = "setuptools.build_meta" 4 | 5 | [project] 6 | name = "docker-run-cli" 7 | version = "0.10.0" 8 | description = "'docker run' and 'docker exec' with useful defaults" 9 | license = {file = "LICENSE"} 10 | readme = "README.md" 11 | authors = [ 12 | {name = "Lennart Reiher", email = "lennart.reiher@rwth-aachen.de"}, 13 | {name = "Jean-Pierre Busch", email = "jean-pierre.busch@rwth-aachen.de"}, 14 | ] 15 | maintainers = [ 16 | {name = "Lennart Reiher", email = "lennart.reiher@rwth-aachen.de"}, 17 | {name = "Jean-Pierre Busch", email = "jean-pierre.busch@rwth-aachen.de"}, 18 | ] 19 | classifiers = [ 20 | "License :: OSI Approved :: MIT License", 21 | "Programming Language :: Python", 22 | "Programming Language :: Python :: 3", 23 | "Operating System :: POSIX :: Linux", 24 | ] 25 | keywords = ["docker", "container"] 26 | dependencies = ["nvidia-ml-py~=12.570.86"] 27 | requires-python = ">=3.7" 28 | 29 | [project.optional-dependencies] 30 | dev = ["build", "twine"] 31 | docker-ros = ["docker-run-docker-ros>=1.0.7"] 32 | plugins = ["docker-run-docker-ros>=1.0.7"] 33 | all = ["docker-run-docker-ros>=1.0.7", "build", "twine"] 34 | 35 | [project.urls] 36 | "Repository" = "https://github.com/ika-rwth-aachen/docker-run" 37 | "Bug Tracker" = "https://github.com/ika-rwth-aachen/docker-run/issues" 38 | 39 | [tool.setuptools] 40 | script-files = [ 41 | "scripts/activate-python-docker-run-shell-completion", 42 | "scripts/docker-run", 43 | ] 44 | 45 | [tool.setuptools.package-data] 46 | docker_run = ["bash_completion.d/docker-run"] 47 | -------------------------------------------------------------------------------- /docker-run-cli/scripts/activate-python-docker-run-shell-completion: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import os 4 | import shutil 5 | import sys 6 | 7 | import docker_run 8 | 9 | 10 | MODULE_BASH_COMPLETION_FILE = os.path.join(os.path.dirname(docker_run.__file__), "bash_completion.d", "docker-run") 11 | USER_BASH_COMPLETION_MAIN_FILE = os.path.join(os.path.expanduser("~"), ".bash_completion") 12 | USER_BASH_COMPLETION_DIRNAME = ".bash_completion.d" 13 | USER_BASH_COMPLETION_DIR = os.path.join(os.path.expanduser("~"), USER_BASH_COMPLETION_DIRNAME) 14 | 15 | 16 | def installBashCompletion(): 17 | 18 | # copy completion file to user folder 19 | source_file = MODULE_BASH_COMPLETION_FILE 20 | target_file = os.path.join(USER_BASH_COMPLETION_DIR, os.path.basename(MODULE_BASH_COMPLETION_FILE)) 21 | if not os.path.exists(USER_BASH_COMPLETION_DIR): 22 | os.makedirs(USER_BASH_COMPLETION_DIR) 23 | shutil.copy(source_file, target_file) 24 | 25 | # setup sourcing of completion 26 | setupBashCompletion() 27 | 28 | print(f"Successfully installed bash completion to '{target_file}'.", file=sys.stderr) 29 | print(f"Restart your shell or source the following to activate it:", file=sys.stderr) 30 | print(f"{USER_BASH_COMPLETION_MAIN_FILE}") 31 | 32 | 33 | def setupBashCompletion(): 34 | 35 | # configure sourcing of user completion files 36 | user_bash_completion = f"for f in ~/{USER_BASH_COMPLETION_DIRNAME}/*; do . $f; done\n" 37 | if os.path.isfile(USER_BASH_COMPLETION_MAIN_FILE): 38 | with open(USER_BASH_COMPLETION_MAIN_FILE, "r") as f: 39 | if user_bash_completion in f.read(): 40 | return 41 | with open(USER_BASH_COMPLETION_MAIN_FILE, "w") as f: 42 | f.write(user_bash_completion) 43 | 44 | 45 | if __name__ == "__main__": 46 | 47 | installBashCompletion() 48 | -------------------------------------------------------------------------------- /docker-run-cli/scripts/docker-run: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | OS="$(uname -s)" # supported: "Linux", "Darwin" (Mac) 6 | ARCH="$(uname -m)" # supported: "x86_64", "aarch64" (ARM, Jetson Orin), "arm64" (ARM, Mac) 7 | 8 | # check if user is in docker group 9 | if [[ $OS != "Darwin" ]]; then 10 | if ! groups | grep -q "\bdocker\b"; then 11 | echo "User '${USER}' must be in 'docker' group to run containers." 12 | echo "User can be added via: sudo usermod -aG docker ${USER}" 13 | echo "Afterwards, the user may need to logout and login again." 14 | fi 15 | fi 16 | 17 | # check operating system and architecture 18 | if ! { [[ $OS = "Linux" && $ARCH = "x86_64" ]] || [[ $OS = "Darwin" && $ARCH = "arm64" ]] || [[ $OS = "Linux" && $ARCH = "aarch64" ]]; }; then 19 | >&2 echo "docker-run does not support $OS with $ARCH architecture." 20 | exit 1 21 | fi 22 | 23 | # generate docker run/exec command 24 | CMD_FILE=$(mktemp) 25 | python3 -m docker_run "${@}" 2>&1 >$CMD_FILE 26 | CMD=$(cat $CMD_FILE) 27 | rm $CMD_FILE 28 | 29 | # convert command string to array to allow for escaped characters, e.g. "docker run -v /path\ with\ spaces:/path\ with\ spaces ..." 30 | CMD_ARRAY=() 31 | while IFS= read -r -d ' ' part; do 32 | while [[ $part == *"\\" ]]; do 33 | part+=" " 34 | part="${part//\\/}" 35 | IFS= read -r -d ' ' next_part 36 | part+=$next_part 37 | done 38 | CMD_ARRAY+=("$part") 39 | done <<< "$CMD" 40 | CMD_ARRAY+=("${part%$'\n'}") 41 | 42 | # execute command 43 | if [[ ! -z "$CMD" ]]; then 44 | echo -e "================================================================================\n" 45 | exec "${CMD_ARRAY[@]}" 46 | fi 47 | -------------------------------------------------------------------------------- /docker-run-cli/src/docker_run/__init__.py: -------------------------------------------------------------------------------- 1 | __name__ = "docker-run" 2 | __version__ = "0.10.0" -------------------------------------------------------------------------------- /docker-run-cli/src/docker_run/__main__.py: -------------------------------------------------------------------------------- 1 | from docker_run.core import generateDockerCommand 2 | 3 | 4 | if __name__ == "__main__": 5 | generateDockerCommand() 6 | -------------------------------------------------------------------------------- /docker-run-cli/src/docker_run/bash_completion.d/docker-run: -------------------------------------------------------------------------------- 1 | # source official docker auto-competion 2 | source /usr/share/bash-completion/completions/docker 3 | 4 | _docker-run() { 5 | # use settings from the official docker auto-competion main function (_docker) 6 | local previous_extglob_setting=$(shopt -p extglob) 7 | shopt -s extglob 8 | 9 | COMPREPLY=() 10 | local cur prev words cword 11 | _get_comp_words_by_ref -n : cur prev words cword 12 | 13 | # set default command to run and loop over cli words 14 | local command='run' command_pos=0 subcommand_pos 15 | local counter=1 16 | while [ "$counter" -lt "$cword" ]; do 17 | case "${words[$counter]}" in 18 | run) 19 | return 0 20 | ;; 21 | -*) 22 | ;; 23 | =) 24 | (( counter++ )) 25 | ;; 26 | *) 27 | command="${words[$counter]}" 28 | command_pos=$counter 29 | break 30 | ;; 31 | esac 32 | (( counter++ )) 33 | done 34 | 35 | # save list of local docker images (with and without tag) 36 | local docker_images=($(docker images --format "{{.Repository}}:{{.Tag}}")) 37 | local docker_images_no_tag=($(docker images --format "{{.Repository}}")) 38 | 39 | # check if previous word is docker image or not 40 | if [[ " ${docker_images[*]} " =~ " ${prev} " ]]; then 41 | COMPREPLY=$(docker inspect --format='{{range .Config.Env}}{{if eq (index (split . "=") 0) "DEFAULT_CMD"}}{{index (split . "=") 1}}{{end}}{{end}}' $prev) 42 | if [[ -z "$COMPREPLY" ]]; then 43 | COMPREPLY=$(docker inspect --format='{{join .Config.Cmd " "}}' $prev) 44 | fi 45 | elif [[ " ${docker_images_no_tag[*]} " =~ " ${prev} " ]] && [[ " ${docker_images[*]} " =~ " ${prev}:latest " ]]; then 46 | COMPREPLY=$(docker inspect --format='{{range .Config.Env}}{{if eq (index (split . "=") 0) "DEFAULT_CMD"}}{{index (split . "=") 1}}{{end}}{{end}}' $prev:latest) 47 | if [[ -z "$COMPREPLY" ]]; then 48 | COMPREPLY=$(docker inspect --format='{{join .Config.Cmd " "}}' $prev:latest) 49 | fi 50 | else 51 | # use official docker run auto-completion 52 | local completions_func=_docker_run 53 | declare -F $completions_func >/dev/null && $completions_func 54 | fi 55 | 56 | # add custom args to auto-complete suggestions 57 | if [[ ${cur} == -* ]]; then 58 | COMPREPLY+=( 59 | "--help" 60 | "--image" 61 | "--mwd" 62 | "--mws" 63 | "--no-gpu" 64 | "--no-gpu" 65 | "--no-it" 66 | "--no-name" 67 | "--no-rm" 68 | "--no-user" 69 | "--no-x11" 70 | "--verbose" 71 | "--version" 72 | ) 73 | COMPREPLY=($(compgen -W "${COMPREPLY[*]}" -- "${cur}")) 74 | elif [[ ${prev} == "--name" ]]; then 75 | # auto-complete with name of running containers 76 | COMPREPLY=$(docker ps --format '{{.Names}}') 77 | COMPREPLY=($(compgen -W "${COMPREPLY[*]}" -- "${cur}")) 78 | fi 79 | 80 | eval "$previous_extglob_setting" 81 | return 0 82 | } 83 | 84 | complete -F _docker-run docker-run -------------------------------------------------------------------------------- /docker-run-cli/src/docker_run/core.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import argparse 4 | import importlib 5 | import os 6 | import sys 7 | from typing import Any, Dict, List, Tuple 8 | 9 | import docker_run 10 | from docker_run.utils import log, runCommand, validDockerContainerName 11 | from docker_run.plugins.plugin import Plugin 12 | 13 | # automatically load all available plugins inheriting from `Plugin` 14 | PLUGINS = [] 15 | PLUGINS_DIR = os.path.join(os.path.dirname(__file__), "plugins") 16 | for file_name in os.listdir(PLUGINS_DIR): 17 | if file_name.endswith(".py") and file_name != "plugin.py": 18 | module_name = os.path.splitext(file_name)[0] 19 | module = importlib.import_module(f"docker_run.plugins.{module_name}") 20 | for name, cls in module.__dict__.items(): 21 | if isinstance(cls, type) and issubclass(cls, Plugin) and cls is not Plugin: 22 | PLUGINS.append(cls) 23 | 24 | DEFAULT_CONTAINER_NAME = validDockerContainerName(os.path.basename(os.getcwd())) 25 | 26 | def parseArguments() -> Tuple[argparse.Namespace, List[str], List[str]]: 27 | 28 | class DockerRunArgumentParser(argparse.ArgumentParser): 29 | 30 | def print_help(self, file=None): 31 | super().print_help(file=sys.stderr if file is None else file) 32 | 33 | def format_help(self): 34 | parser._actions.sort(key=lambda x: x.dest) 35 | parser._action_groups[1]._group_actions.sort(key=lambda x: x.dest) 36 | docker_run_help = runCommand("docker run --help")[0] 37 | separator = f"\n{'-' * 80}\n\n" 38 | return docker_run_help + separator + super().format_help() 39 | 40 | parser = DockerRunArgumentParser(prog="docker-run", 41 | description="Executes `docker run` with the following features enabled by default, each of which can be disabled individually: " 42 | "container removal after exit, interactive tty, current directory name as container name, GPU support, X11 GUI forwarding. " 43 | "Passes any additional arguments to `docker run`. " 44 | "Executes `docker exec` instead if a container with the specified name (`--name`) is already running.", 45 | add_help=False) 46 | 47 | parser.add_argument("--help", action="help", default=argparse.SUPPRESS, help="show this help message and exit") 48 | parser.add_argument("--image", help="image name (may also be specified without --image as last argument before command)") 49 | parser.add_argument("--name", default=DEFAULT_CONTAINER_NAME, help="container name; generates `docker exec` command if already running") 50 | parser.add_argument("--no-name", action="store_true", help="disable automatic container name (current directory)") 51 | parser.add_argument("--verbose", action="store_true", help="print generated command") 52 | parser.add_argument("--version", action="store_true", help="show program's version number and exit") 53 | 54 | # plugin args 55 | for plugin in PLUGINS: 56 | plugin.addArguments(parser) 57 | 58 | args, unknown = parser.parse_known_args() 59 | 60 | # separate unknown args before and after -- 61 | try: 62 | double_dash_index = unknown.index("--") 63 | unknown_args = unknown[:double_dash_index] 64 | cmd_args = unknown[double_dash_index+1:] 65 | except ValueError: 66 | unknown_args = unknown 67 | cmd_args = [] 68 | 69 | # version 70 | if args.version: 71 | log(f"{docker_run.__name__} v{docker_run.__version__}") 72 | parser.exit() 73 | 74 | return args, unknown_args, cmd_args 75 | 76 | 77 | def buildDockerCommand(args: Dict[str, Any], unknown_args: List[str] = [], cmd_args: List[str] = []) -> str: 78 | """Builds an executable `docker run` or `docker exec` command based on the given arguments. 79 | 80 | Args: 81 | args (Dict[str, Any]): known arguments that are handled explicitly 82 | unknown_args (List[str], optional): extra arguments to include in `docker` command ([]) 83 | cmd_args (List[str], optional): extra arguments to append at the end of `docker` command ([]) 84 | 85 | Returns: 86 | str: executable `docker run` or `docker exec` command 87 | """ 88 | 89 | # check for running container 90 | if args["no_name"]: 91 | args["name"] = None 92 | new_container = True 93 | else: 94 | new_container = False 95 | running_containers = runCommand('docker ps --format "{{.Names}}"')[0].split('\n') 96 | new_container = not (args["name"] in running_containers) 97 | if not new_container and args["image"] is not None and len(args["image"]) > 0: 98 | args["name"] = None if args["name"] == DEFAULT_CONTAINER_NAME else args["name"] 99 | new_container = True 100 | 101 | if new_container: # docker run 102 | 103 | log_msg = f"Starting new container " 104 | if args["name"] is not None: 105 | log_msg += f"'{args['name']}'" 106 | log(log_msg + " ...") 107 | docker_cmd = ["docker", "run"] 108 | 109 | # name 110 | if args["name"] is not None and len(args["name"]) > 0: 111 | docker_cmd += [f"--name {args['name']}"] 112 | 113 | # plugin flags 114 | for plugin in PLUGINS: 115 | docker_cmd += plugin.getRunFlags(args, unknown_args) 116 | 117 | else: # docker exec 118 | 119 | log(f"Attaching to running container '{args['name']}' ...") 120 | docker_cmd = ["docker", "exec"] 121 | 122 | # plugin flags 123 | for plugin in PLUGINS: 124 | docker_cmd += plugin.getExecFlags(args, unknown_args) 125 | 126 | # append all extra args 127 | docker_cmd += unknown_args 128 | 129 | if new_container: # docker run 130 | 131 | # image 132 | if args["image"] is not None and len(args["image"]) > 0: 133 | docker_cmd += [args["image"]] 134 | 135 | # command 136 | docker_cmd += cmd_args 137 | 138 | else: # docker exec 139 | 140 | # name 141 | docker_cmd += [args["name"]] 142 | 143 | # command 144 | if len(cmd_args) > 0: 145 | docker_cmd += cmd_args 146 | else: 147 | docker_cmd += ["bash"] # default exec command 148 | 149 | # plugin modifications 150 | for plugin in PLUGINS: 151 | docker_cmd = plugin.modifyFinalCommand(docker_cmd, args, unknown_args) 152 | 153 | return " ".join(docker_cmd) 154 | 155 | 156 | def printDockerCommand(cmd: str): 157 | """Prints a docker command in human-readable way by line-breaking on each new argument. 158 | 159 | Args: 160 | cmd (str): docker command 161 | """ 162 | 163 | components = cmd.split() 164 | log(f"{components[0]} {components[1]}", end="") 165 | 166 | for c in components[2:]: 167 | if c.startswith("-"): 168 | log(f" \\\n {c}", end="") 169 | else: 170 | log(f" {c}", end="") 171 | log("") 172 | 173 | 174 | def generateDockerCommand(): 175 | 176 | args, unknown_args, cmd_args = parseArguments() 177 | 178 | cmd = buildDockerCommand(vars(args), unknown_args, cmd_args) 179 | print(cmd) 180 | if args.verbose: 181 | printDockerCommand(cmd) 182 | -------------------------------------------------------------------------------- /docker-run-cli/src/docker_run/plugins/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ika-rwth-aachen/docker-run/9181077a729fbe020dcd32bc0bcf2d8effc527d7/docker-run-cli/src/docker_run/plugins/__init__.py -------------------------------------------------------------------------------- /docker-run-cli/src/docker_run/plugins/core.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import os 3 | import platform 4 | import tempfile 5 | from typing import Any, Dict, List 6 | 7 | import pynvml 8 | 9 | from docker_run.utils import log, runCommand 10 | from docker_run.plugins.plugin import Plugin 11 | 12 | 13 | class CorePlugin(Plugin): 14 | 15 | OS = platform.uname().system 16 | ARCH = platform.uname().machine 17 | 18 | @classmethod 19 | def addArguments(cls, parser: argparse.ArgumentParser): 20 | 21 | parser.add_argument("--no-rm", action="store_true", help="disable automatic container removal") 22 | parser.add_argument("--no-it", action="store_true", help="disable automatic interactive tty") 23 | parser.add_argument("--no-tz", action="store_true", help="disable automatic timezone") 24 | parser.add_argument("--no-gpu", action="store_true", help="disable automatic GPU support") 25 | parser.add_argument("--no-x11", action="store_true", help="disable automatic X11 GUI forwarding") 26 | parser.add_argument("--loc", action="store_true", help="enable automatic locale") 27 | parser.add_argument("--mwd", action="store_true", help="mount current directory at same path") 28 | 29 | @classmethod 30 | def getRunFlags(cls, args: Dict[str, Any], unknown_args: List[str]) -> List[str]: 31 | flags = [] 32 | if not args["no_rm"]: 33 | flags += cls.removeFlags() 34 | if not args["no_it"]: 35 | flags += cls.interactiveFlags() 36 | if not args["no_tz"]: 37 | flags += cls.timezoneFlags() 38 | if not args["no_gpu"]: 39 | flags += cls.gpuSupportFlags() 40 | if not args["no_x11"]: 41 | gui_forwarding_kwargs = {} 42 | if "--network" in unknown_args: 43 | network_arg_index = unknown_args.index("--network") + 1 44 | if network_arg_index < len(unknown_args): 45 | gui_forwarding_kwargs["docker_network"] = unknown_args[network_arg_index] 46 | flags += cls.x11GuiForwardingFlags(**gui_forwarding_kwargs) 47 | if args["loc"]: 48 | flags += cls.localeFlags() 49 | if args["mwd"]: 50 | flags += cls.currentDirMountFlags() 51 | return flags 52 | 53 | @classmethod 54 | def getExecFlags(cls, args: Dict[str, Any], unknown_args: List[str]) -> List[str]: 55 | flags = [] 56 | if not args["no_it"]: 57 | flags += cls.interactiveFlags() 58 | return flags 59 | 60 | @classmethod 61 | def modifyFinalCommand(cls, cmd: List[str], args: Dict[str, Any], unknown_args: List[str]) -> List[str]: 62 | if "-v" in cmd or "--volume" in cmd: 63 | cmd = cls.resolveRelativeVolumeFlags(cmd) 64 | cmd = cls.fixSpacesInVolumeFlags(cmd) 65 | return cmd 66 | 67 | @classmethod 68 | def removeFlags(cls) -> List[str]: 69 | return ["--rm"] 70 | 71 | @classmethod 72 | def interactiveFlags(cls) -> List[str]: 73 | return ["--interactive", "--tty"] 74 | 75 | @classmethod 76 | def timezoneFlags(cls) -> List[str]: 77 | flags = [] 78 | if os.path.isfile("/etc/timezone"): 79 | flags.append("--volume /etc/timezone:/etc/timezone:ro") 80 | if os.path.isfile("/etc/localtime"): 81 | flags.append("--volume /etc/localtime:/etc/localtime:ro") 82 | return flags 83 | 84 | @classmethod 85 | def localeFlags(cls) -> List[str]: 86 | return ["--env LANG", "--env LANGUAGE", "--env LC_ALL"] 87 | 88 | @classmethod 89 | def gpuSupportFlags(cls) -> List[str]: 90 | n_gpus = 0 91 | try: 92 | pynvml.nvmlInit() 93 | n_gpus = pynvml.nvmlDeviceGetCount() 94 | except pynvml.NVMLError: 95 | pass 96 | if n_gpus > 0: 97 | if cls.ARCH == "x86_64": 98 | return ["--gpus all"] 99 | elif cls.ARCH == "aarch64" and cls.OS == "Linux": 100 | return ["--runtime nvidia"] 101 | else: 102 | log(f"GPU not supported by `docker-run` on {cls.OS} with {cls.ARCH} architecture") 103 | return [] 104 | else: 105 | log(f"No GPU detected") 106 | return [] 107 | 108 | @classmethod 109 | def x11GuiForwardingFlags(cls, docker_network: str = "bridge") -> List[str]: 110 | 111 | display = os.environ.get("DISPLAY") 112 | if display is None: 113 | return [] 114 | 115 | if cls.OS == "Darwin": 116 | runCommand(f"xhost +local:") 117 | 118 | xsock = "/tmp/.X11-unix" 119 | xauth = tempfile.NamedTemporaryFile(prefix='.docker.xauth.', delete=False).name 120 | xauth_display = display if cls.OS != "Darwin" else runCommand("ifconfig en0 | grep 'inet '")[0].split()[1] + ":0" 121 | xauth_output = "ffff" + runCommand(f"xauth nlist {xauth_display}")[0][4:] 122 | runCommand(f"xauth -f {xauth} nmerge - 2>/dev/null", input=xauth_output.encode()) 123 | os.chmod(xauth, 0o777) 124 | 125 | if docker_network != "host" and not display.startswith(":"): 126 | display = "172.17.0.1:" + display.split(":")[1] 127 | if cls.OS == "Darwin": 128 | display = "host.docker.internal:" + display.split(":")[1] 129 | 130 | flags = [] 131 | flags.append(f"--env DISPLAY={display}") 132 | flags.append(f"--env XAUTHORITY={xauth}") 133 | flags.append(f"--env QT_X11_NO_MITSHM=1") 134 | flags.append(f"--volume {xauth}:{xauth}") 135 | flags.append(f"--volume {xsock}:{xsock}") 136 | 137 | return flags 138 | 139 | @classmethod 140 | def currentDirMountFlags(cls) -> List[str]: 141 | cwd = os.getcwd().replace(" ", "\\ ") 142 | return [f"--volume {cwd}:{cwd}", f"--workdir {cwd}"] 143 | 144 | @classmethod 145 | def resolveRelativeVolumeFlags(cls, cmd: List[str]) -> List[str]: 146 | for i, arg in enumerate(cmd): 147 | if arg in ["-v", "--volume"]: 148 | mount_path = cmd[i + 1].split(":")[0] 149 | if mount_path.startswith("."): 150 | absolute_mount_path = os.path.abspath(mount_path) 151 | cmd[i + 1] = absolute_mount_path + cmd[i + 1][len(mount_path):] 152 | return cmd 153 | 154 | @classmethod 155 | def fixSpacesInVolumeFlags(cls, cmd: List[str]) -> List[str]: 156 | for i, arg in enumerate(cmd): 157 | if arg in ["-v", "--volume"]: 158 | cmd[i + 1] = cmd[i + 1].replace(" ", "\\ ") 159 | return cmd -------------------------------------------------------------------------------- /docker-run-cli/src/docker_run/plugins/plugin.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | from abc import ABC, abstractmethod 3 | from typing import Any, Dict, List 4 | 5 | 6 | class Plugin(ABC): 7 | 8 | @classmethod 9 | def addArguments(cls, parser: argparse.ArgumentParser): 10 | pass 11 | 12 | @classmethod 13 | @abstractmethod 14 | def getRunFlags(cls, args: Dict[str, Any], unknown_args: List[str]) -> List[str]: 15 | raise NotImplementedError() 16 | 17 | @classmethod 18 | @abstractmethod 19 | def getExecFlags(cls, args: Dict[str, Any], unknown_args: List[str]) -> List[str]: 20 | raise NotImplementedError() 21 | 22 | @classmethod 23 | def modifyFinalCommand(cls, cmd: List[str], args: Dict[str, Any], unknown_args: List[str]) -> List[str]: 24 | return cmd 25 | -------------------------------------------------------------------------------- /docker-run-cli/src/docker_run/utils.py: -------------------------------------------------------------------------------- 1 | import re 2 | import subprocess 3 | import sys 4 | from typing import Tuple 5 | 6 | 7 | def log(msg: str, *args, **kwargs): 8 | """Log message to stderr. 9 | Args: 10 | msg (str): log message 11 | """ 12 | 13 | print(msg, file=sys.stderr, *args, **kwargs) 14 | 15 | 16 | def runCommand(cmd: str, *args, **kwargs) -> Tuple[str, str]: 17 | """Execute system command. 18 | 19 | Args: 20 | cmd (str): system command 21 | 22 | Returns: 23 | Tuple[str, str]: stdout, stderr output 24 | """ 25 | 26 | try: 27 | output = subprocess.run(cmd, check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True, *args, **kwargs) 28 | except subprocess.CalledProcessError as exc: 29 | raise RuntimeError(f"System command '{cmd}' failed: {exc.stderr.decode()}") 30 | 31 | return output.stdout.decode(), output.stderr.decode() 32 | 33 | 34 | def validDockerContainerName(name: str) -> str: 35 | """Cleans a string such that it is a valid Docker container name. 36 | 37 | [a-zA-Z0-9][a-zA-Z0-9_.-] 38 | 39 | Args: 40 | name (str): raw name 41 | 42 | Returns: 43 | str: valid container name 44 | """ 45 | 46 | name = re.sub('[^a-zA-Z0-9_.-]', '-', name) 47 | name = re.sub('^[^a-zA-Z0-9]+', '', name) 48 | 49 | return name 50 | -------------------------------------------------------------------------------- /docker-run-docker-ros/LICENSE: -------------------------------------------------------------------------------- 1 | ../LICENSE -------------------------------------------------------------------------------- /docker-run-docker-ros/README.md: -------------------------------------------------------------------------------- 1 | # docker-run-docker-ros 2 | 3 | [`docker-run`](https://pypi.org/project/docker-run/) plugin for Docker images built by [*docker-ros*](https://github.com/ika-rwth-aachen/docker-ros). 4 | 5 | ## Installation 6 | 7 | ```bash 8 | pip install docker-run[docker-ros] 9 | ``` 10 | 11 | ## Usage 12 | 13 | ``` 14 | usage: docker-run [--help] [--image IMAGE] [--loc] [--mwd] [--mws] 15 | [--name NAME] [--no-gpu] [--no-it] [--no-name] [--no-rm] 16 | [--no-tz] [--no-user] [--no-x11] [--verbose] [--version] 17 | 18 | Executes `docker run` with the following features enabled by default, each of 19 | which can be disabled individually: container removal after exit, interactive 20 | tty, current directory name as container name, GPU support, X11 GUI 21 | forwarding. Passes any additional arguments to `docker run`. Executes `docker 22 | exec` instead if a container with the specified name (`--name`) is already 23 | running. 24 | 25 | options: 26 | --help show this help message and exit 27 | --image IMAGE image name (may also be specified without --image as last 28 | argument before command) 29 | --loc enable automatic locale 30 | --mwd mount current directory at same path 31 | --mws [docker-ros] mount current directory into ROS workspace at 32 | `/docker-ros/ws/src/target` 33 | --name NAME container name; generates `docker exec` command if already 34 | running 35 | --no-gpu disable automatic GPU support 36 | --no-it disable automatic interactive tty 37 | --no-name disable automatic container name (current directory) 38 | --no-rm disable automatic container removal 39 | --no-tz disable automatic timezone 40 | --no-user [docker-ros] disable passing local UID/GID into container 41 | --no-x11 disable automatic X11 GUI forwarding 42 | --verbose print generated command 43 | --version show program's version number and exit 44 | ``` 45 | -------------------------------------------------------------------------------- /docker-run-docker-ros/pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["setuptools>=61.0.0", "wheel"] 3 | build-backend = "setuptools.build_meta" 4 | 5 | [project] 6 | name = "docker-run-docker-ros" 7 | version = "1.0.7" 8 | description = "docker-run plugin for Docker images built by docker-ros" 9 | license = {file = "LICENSE"} 10 | readme = "README.md" 11 | authors = [ 12 | {name = "Lennart Reiher", email = "lennart.reiher@rwth-aachen.de"}, 13 | {name = "Jean-Pierre Busch", email = "jean-pierre.busch@rwth-aachen.de"}, 14 | ] 15 | maintainers = [ 16 | {name = "Lennart Reiher", email = "lennart.reiher@rwth-aachen.de"}, 17 | {name = "Jean-Pierre Busch", email = "jean-pierre.busch@rwth-aachen.de"}, 18 | ] 19 | classifiers = [ 20 | "License :: OSI Approved :: MIT License", 21 | "Programming Language :: Python", 22 | "Programming Language :: Python :: 3", 23 | "Operating System :: POSIX :: Linux", 24 | ] 25 | keywords = ["docker", "container", "ros"] 26 | dependencies = ["docker-run-cli>=0.9.7"] 27 | requires-python = ">=3.7" 28 | 29 | [project.urls] 30 | "Repository" = "https://github.com/ika-rwth-aachen/docker-run" 31 | "Bug Tracker" = "https://github.com/ika-rwth-aachen/docker-run/issues" 32 | -------------------------------------------------------------------------------- /docker-run-docker-ros/src/docker_run/plugins/docker_ros.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import os 3 | from typing import Any, Dict, List 4 | 5 | from docker_run.utils import runCommand 6 | from docker_run.plugins.plugin import Plugin 7 | 8 | 9 | __version__ = "1.0.4" 10 | 11 | 12 | class DockerRosPlugin(Plugin): 13 | 14 | WORKSPACE = "/docker-ros/ws" 15 | TARGET_MOUNT = f"{WORKSPACE}/src/target" 16 | 17 | @classmethod 18 | def addArguments(cls, parser: argparse.ArgumentParser): 19 | 20 | prefix = "[docker-ros]" 21 | 22 | parser.add_argument("--no-user", action="store_true", help=f"{prefix} disable passing local UID/GID into container") 23 | parser.add_argument("--mws", action="store_true", help=f"{prefix} mount current directory into ROS workspace at `{cls.TARGET_MOUNT}`") 24 | 25 | @classmethod 26 | def getRunFlags(cls, args: Dict[str, Any], unknown_args: List[str]) -> List[str]: 27 | flags = [] 28 | if not args["no_user"]: 29 | flags += cls.userFlags() 30 | if args["mws"]: 31 | flags += cls.currentDirMountWorkspaceFlags() 32 | return flags 33 | 34 | @classmethod 35 | def getExecFlags(cls, args: Dict[str, Any], unknown_args: List[str]) -> List[str]: 36 | flags = [] 37 | is_docker_user = False 38 | docker_uid = runCommand(f"docker exec {args['name']} printenv DOCKER_UID || true")[0][:-1] 39 | if len(docker_uid) > 0: 40 | is_docker_user = (len(runCommand(f"docker exec {args['name']} id -u {docker_uid} || true")[0][:-1]) > 0) 41 | if not args["no_user"] and is_docker_user: 42 | flags += cls.userExecFlags(docker_uid) 43 | return flags 44 | 45 | @classmethod 46 | def userFlags(cls) -> List[str]: 47 | return [f"--env DOCKER_UID={os.getuid()}", f"--env DOCKER_GID={os.getgid()}"] 48 | 49 | @classmethod 50 | def userExecFlags(cls, user: str) -> List[str]: 51 | return [f"--user {user}"] 52 | 53 | @classmethod 54 | def currentDirMountWorkspaceFlags(cls) -> List[str]: 55 | cwd = os.getcwd().replace(" ", "\\ ") 56 | return [f"--volume {cwd}:{cls.TARGET_MOUNT}", f"--workdir {cls.WORKSPACE}"] 57 | --------------------------------------------------------------------------------