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

Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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]>
  • Loading branch information
3 people authored and asottile committed May 23, 2025
commit 466f6c4a3939375dc2dc7a2fc34f553c2715d738
26 changes: 26 additions & 0 deletions pre_commit/languages/docker.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from __future__ import annotations

import functools
import hashlib
import json
import os
Expand Down Expand Up @@ -101,7 +102,32 @@ def install_environment(
os.mkdir(directory)


@functools.lru_cache(maxsize=1)
def _is_rootless() -> bool: # pragma: win32 no cover
retcode, out, _ = cmd_output_b(
'docker', 'system', 'info', '--format', '{{ json . }}',
)
if retcode != 0:
return False

info = json.loads(out)
try:
return (
# docker:
# https://docs.docker.com/reference/api/engine/version/v1.48/#tag/System/operation/SystemInfo
'name=rootless' in info.get('SecurityOptions', ()) or
# podman:
# https://docs.podman.io/en/latest/_static/api.html?version=v5.4#tag/system/operation/SystemInfoLibpod
info['host']['security']['rootless']
)
except KeyError:
return False


def get_docker_user() -> tuple[str, ...]: # pragma: win32 no cover
if _is_rootless():
return ()

try:
return ('-u', f'{os.getuid()}:{os.getgid()}')
except AttributeError:
Expand Down
47 changes: 47 additions & 0 deletions tests/languages/docker_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,42 @@ def invalid_attribute():
assert docker.get_docker_user() == ()


@pytest.fixture(autouse=True)
def _avoid_cache():
with mock.patch.object(
docker,
'_is_rootless',
docker._is_rootless.__wrapped__,
):
yield


@pytest.mark.parametrize(
'info_ret',
(
(0, b'{"SecurityOptions": ["name=rootless","name=cgroupns"]}', b''),
(0, b'{"host": {"security": {"rootless": true}}}', b''),
),
)
def test_docker_user_rootless(info_ret):
with mock.patch.object(docker, 'cmd_output_b', return_value=info_ret):
assert docker.get_docker_user() == ()


@pytest.mark.parametrize(
'info_ret',
(
(0, b'{"SecurityOptions": ["name=cgroupns"]}', b''),
(0, b'{"host": {"security": {"rootless": false}}}', b''),
(0, b'{"respone_from_some_other_container_engine": true}', b''),
(1, b'', b''),
),
)
def test_docker_user_non_rootless(info_ret):
with mock.patch.object(docker, 'cmd_output_b', return_value=info_ret):
assert docker.get_docker_user() != ()


def test_in_docker_no_file():
with mock.patch.object(builtins, 'open', side_effect=FileNotFoundError):
assert docker._is_in_docker() is False
Expand Down Expand Up @@ -195,3 +231,14 @@ def test_docker_hook(tmp_path):

ret = run_language(tmp_path, docker, 'echo hello hello world')
assert ret == (0, b'hello hello world\n')


@xfailif_windows # pragma: win32 no cover
def test_docker_hook_mount_permissions(tmp_path):
dockerfile = '''\
FROM ubuntu:22.04
'''
tmp_path.joinpath('Dockerfile').write_text(dockerfile)

retcode, _ = run_language(tmp_path, docker, 'touch', ('README.md',))
assert retcode == 0