Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 7180cda

Browse files
matthewhughes934Kurt-von-Laventhemr0c
committed
Fix permission errors for mounts in rootless docker
By running containers in a rootless docker context as root. This is because user and group IDs are remapped in the user namespaces uses by rootless docker, and it's unlikely that the current user ID will map to the same ID under this remap (see docs[1] for some more details). Specifically, it means ownership of mounted volumes will not be for the current user and trying to write can result in permission errors. This change borrows heavily from an existing PR[2]. The output format of `docker system info` I don't think is documented/guaranteed anywhere, but it should corresponding to the format of a `/info` API request to Docker[3] The added test _hopes_ to avoid regressions in this behaviour, but since tests aren't run in a rootless docker context on the PR checks (and I couldn't find an easy way to make it the case) there's still a risk of regressions sneaking in. Link: https://docs.docker.com/engine/security/rootless/ [1] Link: #1484 [2] Link: https://docs.docker.com/reference/api/engine/version/v1.48/#tag/System/operation/SystemAuth [3] Co-authored-by: Kurt von Laven <[email protected]> Co-authored-by: Fabrice Flore-Thébault <[email protected]>
1 parent d2b61d0 commit 7180cda

2 files changed

Lines changed: 47 additions & 0 deletions

File tree

pre_commit/languages/docker.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from __future__ import annotations
22

3+
import functools
34
import hashlib
45
import json
56
import os
@@ -101,7 +102,23 @@ def install_environment(
101102
os.mkdir(directory)
102103

103104

105+
@functools.lru_cache(maxsize=1)
106+
def _is_rootless_docker() -> bool: # pragma: win32 no cover
107+
retcode, out, _ = cmd_output_b(
108+
'docker', 'system', 'info', '--format', '{{ json .SecurityOptions }}',
109+
)
110+
# some failures are to be expected, e.g. for 'podman' aliased as 'docker'
111+
if retcode != 0:
112+
return False
113+
114+
info = json.loads(out)
115+
return any(opt == 'name=rootless' for opt in info)
116+
117+
104118
def get_docker_user() -> tuple[str, ...]: # pragma: win32 no cover
119+
if _is_rootless_docker():
120+
return ()
121+
105122
try:
106123
return ('-u', f'{os.getuid()}:{os.getgid()}')
107124
except AttributeError:

tests/languages/docker_test.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,25 @@ def invalid_attribute():
6262
assert docker.get_docker_user() == ()
6363

6464

65+
@pytest.mark.parametrize(
66+
('info_ret', 'expect_root'),
67+
(
68+
((0, b'["name=rootless","name=cgroupns"]', b''), True),
69+
((0, b'["name=cgroupns"]', b''), False),
70+
((1, b'', b''), False),
71+
),
72+
)
73+
def test_docker_user_rootless_docker(info_ret, expect_root):
74+
with mock.patch.object(
75+
docker, 'cmd_output_b', return_value=info_ret,
76+
):
77+
if expect_root:
78+
assert docker.get_docker_user() == ()
79+
else:
80+
assert docker.get_docker_user() != ()
81+
docker._is_rootless_docker.cache_clear()
82+
83+
6584
def test_in_docker_no_file():
6685
with mock.patch.object(builtins, 'open', side_effect=FileNotFoundError):
6786
assert docker._is_in_docker() is False
@@ -195,3 +214,14 @@ def test_docker_hook(tmp_path):
195214

196215
ret = run_language(tmp_path, docker, 'echo hello hello world')
197216
assert ret == (0, b'hello hello world\n')
217+
218+
219+
@xfailif_windows # pragma: win32 no cover
220+
def test_docker_hook_mount_permissions(tmp_path):
221+
dockerfile = '''\
222+
FROM ubuntu:22.04
223+
'''
224+
tmp_path.joinpath('Dockerfile').write_text(dockerfile)
225+
226+
retcode, _ = run_language(tmp_path, docker, 'touch', ('README.md',))
227+
assert retcode == 0

0 commit comments

Comments
 (0)