From c8451898e975511da196bc2cc574d856c4d6cd74 Mon Sep 17 00:00:00 2001 From: Oleg Kainov Date: Wed, 21 Apr 2021 11:43:12 +0300 Subject: [PATCH] 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 --- pre_commit/languages/docker.py | 3 +- pre_commit/util.py | 64 ++++++++++++++++++++++++++++++++++ 2 files changed, 66 insertions(+), 1 deletion(-) diff --git a/pre_commit/languages/docker.py b/pre_commit/languages/docker.py index 9d30568c5..f0aed0cc2 100644 --- a/pre_commit/languages/docker.py +++ b/pre_commit/languages/docker.py @@ -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' @@ -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', ) diff --git a/pre_commit/util.py b/pre_commit/util.py index b5f40ada4..79c3334df 100644 --- a/pre_commit/util.py +++ b/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 @@ -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']}