fix: fix Docker path mounting

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 #1387
This commit is contained in:
Oleg Kainov 2021-04-21 11:43:12 +03:00
parent 24d9dc7469
commit 6d31cff55f
No known key found for this signature in database
GPG key ID: 30827D5765567994
2 changed files with 72 additions and 1 deletions

View file

@ -8,6 +8,7 @@ from pre_commit.hook import Hook
from pre_commit.languages import helpers from pre_commit.languages import helpers
from pre_commit.prefix import Prefix from pre_commit.prefix import Prefix
from pre_commit.util import clean_path_on_failure from pre_commit.util import clean_path_on_failure
from pre_commit.util import translate_path
ENVIRONMENT_DIR = 'docker' ENVIRONMENT_DIR = 'docker'
PRE_COMMIT_LABEL = 'PRE_COMMIT' PRE_COMMIT_LABEL = 'PRE_COMMIT'
@ -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 # 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 # The `Z` option tells Docker to label the content with a private
# unshared label. Only the current container can use a private volume. # 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', '--workdir', '/src',
) )

View file

@ -1,8 +1,11 @@
import contextlib import contextlib
import errno import errno
import functools import functools
import json
import os.path import os.path
import re
import shutil import shutil
import socket
import stat import stat
import subprocess import subprocess
import sys import sys
@ -268,3 +271,70 @@ def rmtree(path: str) -> None:
def parse_version(s: str) -> Tuple[int, ...]: def parse_version(s: str) -> Tuple[int, ...]:
"""poor man's version comparison""" """poor man's version comparison"""
return tuple(int(p) for p in s.split('.')) 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)
"""
if not in_docker():
return path
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[str, Any]:
"""
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[str, str]:
"""
: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']
}