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']}