Skip to content

Commit

Permalink
fix: fix Docker path mounting
Browse files Browse the repository at this point in the history
Currently pre-commit mounts the current directory to /src and uses
current directory name as mount base.
However this does not work when pre-commit is run inside the container
on some mounted path already, because mount points are relative to the
host, not to the container.

See https://gist.github.com/dpfoose/f96d4e4b76c2e01265619d545b77987a
Fixes pre-commit#1387
  • Loading branch information
okainov committed Apr 21, 2021
1 parent 24d9dc7 commit c845189
Show file tree
Hide file tree
Showing 2 changed files with 66 additions and 1 deletion.
3 changes: 2 additions & 1 deletion pre_commit/languages/docker.py
Expand Up @@ -8,6 +8,7 @@
from pre_commit.languages import helpers
from pre_commit.prefix import Prefix
from pre_commit.util import clean_path_on_failure
from pre_commit.util import translate_path

ENVIRONMENT_DIR = 'docker'
PRE_COMMIT_LABEL = 'PRE_COMMIT'
Expand Down Expand Up @@ -73,7 +74,7 @@ def docker_cmd() -> Tuple[str, ...]: # pragma: win32 no cover
# https://docs.docker.com/engine/reference/commandline/run/#mount-volumes-from-container-volumes-from
# The `Z` option tells Docker to label the content with a private
# unshared label. Only the current container can use a private volume.
'-v', f'{os.getcwd()}:/src:rw,Z',
'-v', f'{translate_path(os.getcwd())}:/src:rw,Z',
'--workdir', '/src',
)

Expand Down
64 changes: 64 additions & 0 deletions pre_commit/util.py
@@ -1,8 +1,11 @@
import contextlib
import errno
import functools
import json
import os.path
import re
import shutil
import socket
import stat
import subprocess
import sys
Expand Down Expand Up @@ -268,3 +271,64 @@ def handle_remove_readonly(
def parse_version(s: str) -> Tuple[int, ...]:
"""poor man's version comparison"""
return tuple(int(p) for p in s.split('.'))


def in_docker() -> bool:
"""
Check if running in Docker
:return: Whether or not this is running in Docker container
"""
try:
with open('/proc/1/cgroup') as cgroup_file:
return 'docker' in cgroup_file.read()
except FileNotFoundError:
return False


def translate_path(path: str) -> str:
"""
Method to get the right path considering it can be mounted in Docker
already.
:param path: A string representing a path within the container
:return: A string representing a path on the host (or the original
path if the path is not in a bound volume)
"""
binds = get_binds()
if path in binds.keys():
return binds[path]
exps = ['(%s)/(.*)' % key for key in binds.keys()]
for exp in exps:
result = re.search(exp, path)
if result:
return f'{binds[result.group(1)]}/{result.group(2)}'
raise ValueError(f'Path {path} not present in a bind mount. ' +
'Volume mount will fail when running this in Docker.')


def get_current_container() -> Dict:
"""
Will raise ValueError if there is no container with the same hostname as
the environment this is running in.
Which indicates that this is not a docker container, or that
/var/run/docker.sock is not bind mounted to /var/run/docker.sock on the
host (i.e. this is a container which is also a docker host).
:return: A dictionary containing information about the container this
is running in obtained using docker api
"""
hostname = socket.gethostname()
try:
output = subprocess.check_output(('docker', 'inspect', hostname))
except CalledProcessError:
raise ValueError('Not running in Docker container')

return json.loads(output)[0]


def get_binds() -> Dict:
"""
:return: A dictionary with paths in the container as keys and paths
on the host as values
"""
container = get_current_container()
return {bind.split(':')[1]: bind.split(':')[0]
for bind in container['HostConfig']['Binds']}

0 comments on commit c845189

Please sign in to comment.