├── .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 |
--------------------------------------------------------------------------------