Skip to content

Commit

Permalink
fix: fix path mounting when running in Docker
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.

Fixes #1387
  • Loading branch information
Oleg Kainov committed Apr 21, 2021
1 parent 24d9dc7 commit 1791cbf
Show file tree
Hide file tree
Showing 2 changed files with 74 additions and 1 deletion.
31 changes: 30 additions & 1 deletion pre_commit/languages/docker.py
@@ -1,5 +1,8 @@
import hashlib
import json
import os
import socket
import subprocess
from typing import Sequence
from typing import Tuple

Expand All @@ -15,6 +18,32 @@
healthy = helpers.basic_healthy


def is_in_docker() -> bool:
try:
with open('/proc/1/cgroup') as f:
return 'docker' in f.read()
except FileNotFoundError:
return False


def get_docker_path(path: str) -> str:
if not is_in_docker():
return path
hostname = socket.gethostname()
docker_output = json.loads(
subprocess.check_output(['docker', 'inspect', hostname]),
)
for mount_path in docker_output[0]['HostConfig']['Binds']:
src_path, to_path = mount_path.split(':')
if to_path == path:
return src_path
elif path.startswith(to_path):
return path.replace(to_path, src_path)
# we're in Docker, but the path is not mounted, cannot really do anything,
# so fall back to original path
return path


def md5(s: str) -> str: # pragma: win32 no cover
return hashlib.md5(s.encode()).hexdigest()

Expand Down Expand Up @@ -73,7 +102,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'{get_docker_path(os.getcwd())}:/src:rw,Z',
'--workdir', '/src',
)

Expand Down
44 changes: 44 additions & 0 deletions tests/languages/docker_test.py
@@ -1,3 +1,6 @@
import json
import subprocess
from typing import List
from unittest import mock

from pre_commit.languages import docker
Expand All @@ -12,3 +15,44 @@ def invalid_attribute():
getgid=invalid_attribute,
):
assert docker.get_docker_user() == ()


class TestDockerPath:
@mock.patch.object(docker, 'is_in_docker', return_value=False)
def test_not_in_docker_returns_same(self, _):
assert docker.get_docker_path('abc') == 'abc'

@mock.patch.object(docker, 'is_in_docker', return_value=True)
def test_in_docker_no_binds_same_path(self, _):
binds_list: List[str] = []
output_string = json.dumps([{'HostConfig': {'Binds': binds_list}}])
with mock.patch.object(
subprocess, 'check_output',
return_value=output_string,
):
assert docker.get_docker_path('abc') == 'abc'

@mock.patch.object(docker, 'is_in_docker', return_value=True)
def test_in_docker_binds_path_equal(self, _):
binds_list = [
'/opt/my_code:/project',
]
output_string = json.dumps([{'HostConfig': {'Binds': binds_list}}])
with mock.patch.object(
subprocess, 'check_output',
return_value=output_string,
):
assert docker.get_docker_path('/project') == '/opt/my_code'

@mock.patch.object(docker, 'is_in_docker', return_value=True)
def test_in_docker_binds_path_complex(self, _):
binds_list = [
'/opt/my_code:/project',
]
output_string = json.dumps([{'HostConfig': {'Binds': binds_list}}])
with mock.patch.object(
subprocess, 'check_output',
return_value=output_string,
):
assert docker.get_docker_path('/project/test/something') == \
'/opt/my_code/test/something'

0 comments on commit 1791cbf

Please sign in to comment.