"""Job-local Docker producer contract without starting a container daemon.""" from __future__ import annotations import hashlib import runpy import io import json import os import shutil import subprocess import sys import tarfile from pathlib import Path import pytest ROOT = Path(__file__).resolve().parents[2] PROFILES = ("checks", "backend", "frontend", "security", "browser", "build") BASE_LABELS = { "io.buildah.version": "1.35.0", "org.opencontainers.image.authors": "Gitea", "org.opencontainers.image.created": "2026-10-01T00:01:57Z", "org.opencontainers.image.description": "Official docker images used by Gitea runner to run workflows.", "org.opencontainers.image.documentation": "https://gitea.com/gitea/runner-images/src/branch/main/README.md", "org.opencontainers.image.licenses": "MIT", "org.opencontainers.image.title": "Official Gitea Actions runner images", "org.opencontainers.image.url": "https://gitea.com/gitea/runner-images", "org.opencontainers.image.vendor": "Gitea", "org.opencontainers.image.version": "v26.10.01", } def _job_local_producer( tmp_path: Path, *, wrong_driver: bool = False, wrong_security: bool = False, job_local: bool = True, ): repo = tmp_path / "repository" repo.mkdir() shutil.copytree(ROOT / "ops" / "runner-task-images", repo / "ops" / "runner-task-images") scripts = repo / "scripts" scripts.mkdir() shutil.copyfile(ROOT / "scripts" / "normalize_ci_task_image_archive.py", scripts / "normalize_ci_task_image_archive.py") code = repo / "ops" / "gitea-deploy-broker" code.mkdir() shutil.copyfile(ROOT / "ops" / "gitea-deploy-broker" / "runner_profiles.py", code / "runner_profiles.py") for args in ( ["init", "-q"], ["add", "ops", "scripts"], ["-c", "user.name=Producer test", "-c", "user.email=producer-test@localhost", "commit", "-qm", "fixture"], ): subprocess.run(["git", *args], cwd=repo, check=True, capture_output=True) revision = subprocess.check_output(["git", "rev-parse", "HEAD"], cwd=repo, text=True).strip() fake_bin = tmp_path / "bin" fake_bin.mkdir() docker = fake_bin / "docker" docker.write_text( f"#!{sys.executable}\n" "import hashlib,io,json,os,sys,tarfile\n" "from pathlib import Path\n" "a=sys.argv[1:]; rows=Path(os.environ['FAKE_DOCKER_ROWS']); calls=Path(os.environ['FAKE_DOCKER_CALLS'])\n" "history=json.loads(calls.read_text()) if calls.exists() else []\n" "history.append({'argv':a,'host':os.environ.get('DOCKER_HOST'),'context':os.environ.get('DOCKER_CONTEXT'),'tls':os.environ.get('DOCKER_TLS_VERIFY'),'buildkit':os.environ.get('DOCKER_BUILDKIT')})\n" "calls.write_text(json.dumps(history))\n" "layerbuf=io.BytesIO()\n" "with tarfile.open(fileobj=layerbuf,mode='w:') as t:\n" " p=b'producer fixture layer'; x=tarfile.TarInfo('fixture.txt'); x.size=len(p); t.addfile(x,io.BytesIO(p))\n" "layer=layerbuf.getvalue(); layer_sha=hashlib.sha256(layer).hexdigest()\n" "if a[:1]==['info']:\n" " fmt=a[a.index('--format')+1] if '--format' in a else ''\n" f" driver={('vfs' if wrong_driver else 'overlay2')!r}\n" " security='[\"name=seccomp,profile=builtin\",\"name=rootless\",\"name=cgroupns\"]'\n" " if os.environ.get('FAKE_DOCKER_WRONG_SECURITY')=='1': security='[\"name=seccomp,profile=builtin\",\"name=rootless\",\"name=cgroupns\",\"name=unknown\"]'\n" " if fmt=='{{.Driver}}|{{.CgroupDriver}}|{{.DockerRootDir}}|{{json .SecurityOptions}}': print(driver+'|none|/home/itworx-builder/.local/share/docker|'+security); sys.exit(0)\n" " sys.exit(0)\n" "if a[0]=='build':\n" " builds=Path(os.environ['FAKE_DOCKER_BUILDS']); argv=json.loads(builds.read_text()) if builds.exists() else []\n" " pairs=[a[i+1].split('=',1) for i,x in enumerate(a) if x=='--build-arg']\n" " is_build=dict(pairs).get('PROFILE')=='build'\n" " staged=Path(a[-1]).is_dir() and all((Path(a[-1])/n).is_file() for n in ['job_docker.py','itworx-ci-job-docker-probe','normalize_ci_task_image_archive.py']) if is_build else True\n" " argv.append({'argv':a,'staged':staged}); builds.write_text(json.dumps(argv))\n" " b=dict(pairs); profile=b['PROFILE']; ref=a[a.index('--tag')+1]\n" f" labels={BASE_LABELS!r}.copy()\n" " labels.update({'org.opencontainers.image.source':'https://gitea.example.com/Jens/Unraid-AppOps-Gateway','org.opencontainers.image.revision':b['SOURCE_REVISION'],'tech.itworx.source-tree':b['SOURCE_TREE'],'tech.itworx.ci.profile':profile})\n" " config={'architecture':'amd64','os':'linux','config':{'Labels':labels},'rootfs':{'type':'layers','diff_ids':['sha256:'+layer_sha]}}\n" " config_bytes=json.dumps(config,sort_keys=True,separators=(',',':')).encode(); image_id='sha256:'+hashlib.sha256(config_bytes).hexdigest()\n" " current=json.loads(rows.read_text()) if rows.exists() else []\n" " current.append({'Id':image_id,'Os':'linux','Architecture':'amd64','RepoTags':[ref],'Config':{'Labels':labels}})\n" " Path(os.environ['FAKE_DOCKER_CONFIGS']+'/'+profile+'.json').write_bytes(config_bytes)\n" " rows.write_text(json.dumps(current)); sys.exit(0)\n" "if a[:2]==['image','inspect']: print(rows.read_text()); sys.exit(0)\n" "if a[:2]==['image','save']:\n" " selected=set(a[a.index('--output')+2:]); current=[r for r in json.loads(rows.read_text()) if r['RepoTags'][0] in selected]; items=[]; members={'blobs/sha256/'+layer_sha:layer}\n" " for row in current:\n" " profile=row['Config']['Labels']['tech.itworx.ci.profile']; config=Path(os.environ['FAKE_DOCKER_CONFIGS']+'/'+profile+'.json').read_bytes(); config_sha=hashlib.sha256(config).hexdigest(); config_path='blobs/sha256/'+config_sha; members[config_path]=config\n" " items.append({'Config':config_path,'RepoTags':row['RepoTags'],'Layers':['blobs/sha256/'+layer_sha],'LayerSources':{'sha256:'+layer_sha:{'mediaType':'application/vnd.oci.image.layer.v1.tar','size':len(layer),'digest':'sha256:'+layer_sha}}})\n" " members['manifest.json']=json.dumps(items,separators=(',',':')).encode(); members['oci-layout']=b'{\"imageLayoutVersion\":\"1.0.0\"}'; members['index.json']=b'{\"schemaVersion\":2,\"manifests\":[]}'\n" " with tarfile.open(a[a.index('--output')+1],mode='w:') as t:\n" " for name,payload in members.items():\n" " x=tarfile.TarInfo(name); x.size=len(payload); t.addfile(x,io.BytesIO(payload))\n" " sys.exit(0)\n" "sys.exit('unexpected Docker invocation')\n", encoding="utf-8", ) docker.chmod(0o755) output = tmp_path / "output" env = { **os.environ, "PATH": str(fake_bin) + os.pathsep + os.environ["PATH"], "GITHUB_EVENT_NAME": "push", "GITHUB_REF": "refs/heads/main", "GITHUB_REPOSITORY": "Jens/Unraid-AppOps-Gateway", "DOCKER_HOST": "tcp://shared-dind:2375", "DOCKER_CONTEXT": "shared-context", "DOCKER_TLS_VERIFY": "1", "FAKE_DOCKER_ROWS": str(tmp_path / "docker-rows.json"), "FAKE_DOCKER_CALLS": str(tmp_path / "docker-calls.json"), "FAKE_DOCKER_BUILDS": str(tmp_path / "docker-builds.json"), "FAKE_DOCKER_CONFIGS": str(tmp_path / "docker-configs"), "FAKE_DOCKER_WRONG_SECURITY": "1" if wrong_security else "0", } Path(env["FAKE_DOCKER_CONFIGS"]).mkdir() command = ["bash", str(ROOT / "scripts" / "build_ci_task_images.sh"), revision, "123", str(output)] if job_local: command.append("--job-local") result = subprocess.run( command, cwd=repo, env=env, capture_output=True, text=True, timeout=30, ) tree = subprocess.check_output(["git", "rev-parse", "HEAD^{tree}"], cwd=repo, text=True).strip() return result, output, tmp_path, wrong_driver, revision, tree def _oci_task_image_fixture( tmp_path: Path, *, layer_media_type: str = "application/vnd.oci.image.layer.v1.tar", profiles: tuple[str, ...] = PROFILES, ): layer_buffer = io.BytesIO() with tarfile.open(fileobj=layer_buffer, mode="w:") as layer_tar: payload = b"task image rootfs fixture" member = tarfile.TarInfo("fixture.txt") member.size = len(payload) layer_tar.addfile(member, io.BytesIO(payload)) layer_bytes = layer_buffer.getvalue() layer_sha = hashlib.sha256(layer_bytes).hexdigest() labels_base = { "org.opencontainers.image.source": "https://gitea.example.com/Jens/Unraid-AppOps-Gateway", "org.opencontainers.image.revision": "a" * 40, "tech.itworx.source-tree": "b" * 40, } rows = [] source_manifest = [] members: dict[str, bytes] = {f"blobs/sha256/{layer_sha}": layer_bytes} for profile in profiles: labels = {**labels_base, "tech.itworx.ci.profile": profile} config = { "architecture": "amd64", "os": "linux", "config": {"Labels": labels}, "rootfs": {"type": "layers", "diff_ids": [f"sha256:{layer_sha}"]}, } config_bytes = json.dumps(config, sort_keys=True, separators=(",", ":")).encode() config_sha = hashlib.sha256(config_bytes).hexdigest() config_path = f"blobs/sha256/{config_sha}" members[config_path] = config_bytes reference = f"localhost/itworx-ci-{profile}:sha-{'a' * 40}" rows.append({ "Id": f"sha256:{config_sha}", "Os": "linux", "Architecture": "amd64", "RepoTags": [reference], "Config": {"Labels": labels}, }) source_manifest.append({ "Config": config_path, "RepoTags": [reference], "Layers": [f"blobs/sha256/{layer_sha}"], "LayerSources": { f"sha256:{layer_sha}": { "mediaType": layer_media_type, "size": len(layer_bytes), "digest": f"sha256:{layer_sha}", }, }, }) members["manifest.json"] = json.dumps(source_manifest, separators=(",", ":")).encode() members["oci-layout"] = b'{"imageLayoutVersion":"1.0.0"}' members["index.json"] = b'{"schemaVersion":2,"manifests":[]}' source = tmp_path / "source-oci.tar" with tarfile.open(source, mode="w:") as archive: for name, payload in members.items(): info = tarfile.TarInfo(name) info.size = len(payload) archive.addfile(info, io.BytesIO(payload)) inspection = tmp_path / "inspection.json" inspection.write_text(json.dumps(rows), encoding="utf-8") output = tmp_path / "normalized.tar" return source, inspection, output, rows, layer_bytes @pytest.mark.skipif(os.name == "nt", reason="producer is a POSIX shell script") def test_archive_normalizer_converts_uncompressed_oci_blobs_to_legacy_docker_save_layout(tmp_path): source, inspection, output, rows, layer_bytes = _oci_task_image_fixture(tmp_path) result = subprocess.run( [sys.executable, str(ROOT / "scripts" / "normalize_ci_task_image_archive.py"), str(source), str(output), str(inspection)], capture_output=True, text=True, timeout=30, ) assert result.returncode == 0, result.stderr with tarfile.open(output, mode="r:") as archive: members = {member.name: member for member in archive} normalized = json.loads(archive.extractfile(members["manifest.json"]).read()) assert len(normalized) == len(PROFILES) assert all(set(item) == {"Config", "RepoTags", "Layers"} for item in normalized) for item, expected in zip(normalized, rows, strict=True): assert item["Config"] == expected["Id"].removeprefix("sha256:") + ".json" assert item["RepoTags"] == expected["RepoTags"] assert len(item["Layers"]) == 1 assert item["Layers"][0].endswith("/layer.tar") config_bytes = archive.extractfile(members[item["Config"]]).read() assert "sha256:" + hashlib.sha256(config_bytes).hexdigest() == expected["Id"] assert json.loads(config_bytes)["config"]["Labels"] == expected["Config"]["Labels"] assert archive.extractfile(members[item["Layers"][0]]).read() == layer_bytes assert not any(name.startswith("blobs/") for name in members) @pytest.mark.skipif(os.name == "nt", reason="producer is a POSIX shell script") def test_archive_normalizer_rejects_compressed_oci_layers_before_emitting_output(tmp_path): source, inspection, output, _rows, _layer_bytes = _oci_task_image_fixture( tmp_path, layer_media_type="application/vnd.oci.image.layer.v1.tar+gzip", ) result = subprocess.run( [sys.executable, str(ROOT / "scripts" / "normalize_ci_task_image_archive.py"), str(source), str(output), str(inspection)], capture_output=True, text=True, timeout=30, ) assert result.returncode != 0 assert "compressed or unsupported layer media type" in result.stderr assert not output.exists() @pytest.mark.skipif(os.name == "nt", reason="producer is a POSIX shell script") def test_archive_normalizer_probe_mode_accepts_and_emits_one_build_image(tmp_path): source, inspection, output, rows, _layer_bytes = _oci_task_image_fixture(tmp_path, profiles=("build",)) result = subprocess.run( [sys.executable, str(ROOT / "scripts" / "normalize_ci_task_image_archive.py"), "--probe", str(source), str(output), str(inspection)], capture_output=True, text=True, timeout=30, ) assert result.returncode == 0, result.stderr with tarfile.open(output, mode="r:") as archive: manifest = json.loads(archive.extractfile("manifest.json").read()) assert len(manifest) == 1 assert set(manifest[0]) == {"Config", "RepoTags", "Layers"} assert manifest[0]["RepoTags"] == rows[0]["RepoTags"] @pytest.mark.skipif(os.name == "nt", reason="producer is a POSIX shell script") def test_job_local_mode_builds_all_profiles_through_private_rootless_engine_without_sibling_caps(tmp_path): result, output, scratch, _wrong_driver, revision, tree = _job_local_producer(tmp_path) assert result.returncode == 0, result.stderr calls = json.loads((scratch / "docker-calls.json").read_text()) assert calls[0] == { "argv": ["info", "--format", "{{.Driver}}|{{.CgroupDriver}}|{{.DockerRootDir}}|{{json .SecurityOptions}}"], "host": "unix:///run/itworx-job-docker/docker.sock", "context": None, "tls": None, "buildkit": "1", } builds = json.loads((scratch / "docker-builds.json").read_text()) assert len(builds) == len(PROFILES) assert all(row["staged"] for row in builds) build_arguments = [row["argv"] for row in builds] assert [next(args[i + 1].split("=", 1)[1] for i, arg in enumerate(args[:-1]) if arg == "--build-arg" and args[i + 1].startswith("PROFILE=")) for args in build_arguments] == list(PROFILES) for args in build_arguments: assert not any(arg.startswith(("--cpu-period", "--cpu-quota", "--memory", "--cgroup-parent", "--cpus")) for arg in args) assert "--rm=true" in args and "--force-rm=true" in args manifest = json.loads((output / "task-image-manifest.json").read_text()) assert manifest["apiVersion"] == "itworx.runner-task-images/v1" assert manifest["sourceRevision"] == revision assert manifest["sourceTree"] == tree assert tuple(row["profile"] for row in manifest["images"]) == PROFILES assert manifest["dockerTarSha256"] == hashlib.sha256((output / "task-images.tar").read_bytes()).hexdigest() for row in manifest["images"]: assert row["labels"] == { "org.opencontainers.image.source": "https://gitea.example.com/Jens/Unraid-AppOps-Gateway", "org.opencontainers.image.revision": revision, "tech.itworx.source-tree": tree, "tech.itworx.ci.profile": row["profile"], } @pytest.mark.skipif(os.name == "nt", reason="producer is a POSIX shell script") def test_job_local_mode_rejects_non_overlay_or_non_rootless_server_before_build(tmp_path): result, output, scratch, _wrong_driver, _revision, _tree = _job_local_producer(tmp_path, wrong_driver=True) assert result.returncode != 0 assert "job-local Docker daemon verification failed" in result.stderr assert not (scratch / "docker-builds.json").exists() assert not (output / "task-image-manifest.json").exists() @pytest.mark.skipif(os.name == "nt", reason="producer is a POSIX shell script") def test_job_local_mode_rejects_unknown_daemon_security_options_before_build(tmp_path): result, output, scratch, _wrong_driver, _revision, _tree = _job_local_producer(tmp_path, wrong_security=True) assert result.returncode != 0 assert "job-local Docker daemon verification failed" in result.stderr assert not (scratch / "docker-builds.json").exists() assert not (output / "task-image-manifest.json").exists() @pytest.mark.skipif(os.name == "nt", reason="producer is a POSIX shell script") def test_bootstrap_mode_keeps_classic_shared_daemon_caps_and_archive_contract(tmp_path): result, output, scratch, _wrong_driver, _revision, _tree = _job_local_producer(tmp_path, job_local=False) assert result.returncode == 0, result.stderr calls = json.loads((scratch / "docker-calls.json").read_text()) assert calls[0]["argv"] == ["info"] assert calls[0]["host"] == "tcp://shared-dind:2375" assert calls[0]["buildkit"] == "0" builds = json.loads((scratch / "docker-builds.json").read_text()) assert len(builds) == len(PROFILES) assert all(row["staged"] for row in builds) build_arguments = [row["argv"] for row in builds] assert all(any(arg.startswith("--cpu-quota=") for arg in args) for args in build_arguments) assert all(any(arg.startswith("--memory=5120m") for arg in args) for args in build_arguments) assert (output / "task-images.tar").is_file() with tarfile.open(output / "task-images.tar", mode="r:") as archive: manifest = json.loads(archive.extractfile("manifest.json").read()) assert len(manifest) == len(PROFILES) assert all(set(item) == {"Config", "RepoTags", "Layers"} for item in manifest) def test_build_image_uses_only_minimal_file_capabilities_for_uidmap_helpers(): dockerfile = (ROOT / "ops" / "runner-task-images" / "build" / "Dockerfile").read_text(encoding="utf-8") pins = json.loads((ROOT / "ops" / "runner-task-images" / "pins.json").read_text(encoding="utf-8")) assert pins["jobLocalDocker"]["uidmapFileCapabilities"] == { "newuidmap": "cap_setuid=ep", "newgidmap": "cap_setgid=ep", "setuidBits": False, } assert '"$setcap_bin" cap_setuid=ep "$newuidmap_bin"' in dockerfile assert '"$setcap_bin" cap_setgid=ep "$newgidmap_bin"' in dockerfile assert 'test "$("$getcap_bin" "$newuidmap_bin")" = "$newuidmap_bin cap_setuid=ep"' in dockerfile assert 'test "$("$getcap_bin" "$newgidmap_bin")" = "$newgidmap_bin cap_setgid=ep"' in dockerfile assert '! test -u "$newuidmap_bin"' in dockerfile assert '! test -u "$newgidmap_bin"' in dockerfile def test_build_image_declares_exactly_one_private_daemon_data_volume(): dockerfile = (ROOT / "ops" / "runner-task-images" / "build" / "Dockerfile").read_text(encoding="utf-8") pins = json.loads((ROOT / "ops" / "runner-task-images" / "pins.json").read_text(encoding="utf-8")) volumes = [line.strip() for line in dockerfile.splitlines() if line.strip().startswith("VOLUME")] assert volumes == ['VOLUME ["/home/itworx-builder/.local/share/docker"]'] assert pins["jobLocalDocker"]["dataRoot"] == "/home/itworx-builder/.local/share/docker" assert pins["jobLocalDocker"]["anonymousDataVolume"] == volumes[0].split('"')[1] @pytest.mark.skipif(os.name == "nt", reason="rootless Docker wrapper is Linux-only") @pytest.mark.parametrize("mode", [0o700, 0o710]) def test_job_docker_accepts_private_data_root_modes_used_before_and_after_daemon_start(tmp_path, monkeypatch, mode): sys.path.insert(0, str(ROOT / "ops" / "runner-task-images" / "build")) import job_docker runtime = tmp_path / "runtime" data_root = tmp_path / "data" runtime.mkdir(mode=0o700) data_root.mkdir(mode=mode) os.chmod(runtime, 0o700) os.chmod(data_root, mode) data_info = data_root.stat() monkeypatch.setattr(job_docker, "BUILDER_UID", data_info.st_uid) monkeypatch.setattr(job_docker, "BUILDER_GID", data_info.st_gid) monkeypatch.setattr(job_docker, "RUNTIME", runtime) monkeypatch.setattr(job_docker, "DATA_ROOT", data_root) job_docker._ensure_runtime() @pytest.mark.skipif(os.name == "nt", reason="rootless Docker wrapper is Linux-only") @pytest.mark.parametrize("mode", [0o711, 0o750, 0o777]) def test_job_docker_rejects_unexpected_private_data_root_modes(tmp_path, monkeypatch, mode): sys.path.insert(0, str(ROOT / "ops" / "runner-task-images" / "build")) import job_docker runtime = tmp_path / "runtime" data_root = tmp_path / "data" runtime.mkdir(mode=0o700) data_root.mkdir(mode=mode) os.chmod(runtime, 0o700) os.chmod(data_root, mode) data_info = data_root.stat() monkeypatch.setattr(job_docker, "BUILDER_UID", data_info.st_uid) monkeypatch.setattr(job_docker, "BUILDER_GID", data_info.st_gid) monkeypatch.setattr(job_docker, "RUNTIME", runtime) monkeypatch.setattr(job_docker, "DATA_ROOT", data_root) with pytest.raises(job_docker.WrapperError, match="data directory ownership invalid"): job_docker._ensure_runtime() @pytest.mark.skipif(os.name == "nt", reason="rootless Docker wrapper is Linux-only") def test_job_docker_rejects_symlink_private_data_root(tmp_path, monkeypatch): sys.path.insert(0, str(ROOT / "ops" / "runner-task-images" / "build")) import job_docker runtime = tmp_path / "runtime" real_data_root = tmp_path / "real-data" data_root_link = tmp_path / "data-link" runtime.mkdir(mode=0o700) real_data_root.mkdir(mode=0o700) os.chmod(runtime, 0o700) os.chmod(real_data_root, 0o700) data_root_link.symlink_to(real_data_root, target_is_directory=True) real_data_info = real_data_root.stat() monkeypatch.setattr(job_docker, "BUILDER_UID", real_data_info.st_uid) monkeypatch.setattr(job_docker, "BUILDER_GID", real_data_info.st_gid) monkeypatch.setattr(job_docker, "RUNTIME", runtime) monkeypatch.setattr(job_docker, "DATA_ROOT", data_root_link) with pytest.raises(job_docker.WrapperError, match="data directory ownership invalid"): job_docker._ensure_runtime() def test_private_socket_ping_reads_the_complete_bounded_docker_response(monkeypatch): sys.path.insert(0, str(ROOT / "ops" / "runner-task-images" / "build")) import job_docker class ChunkedSocket: def __init__(self, response: bytes): self.response = response self.offset = 0 self.recv_calls = 0 def __enter__(self): return self def __exit__(self, *_args): return None def settimeout(self, _timeout): pass def connect(self, _path): pass def sendall(self, _request): pass def recv(self, size): self.recv_calls += 1 end = min(self.offset + min(size, 256), len(self.response)) chunk = self.response[self.offset:end] self.offset = end return chunk response = ( b"HTTP/1.1 200 OK\r\nX-Long-Header: " + b"x" * 300 + b"\r\nContent-Length: 2\r\n\r\nOK" ) assert len(response) > 256 wrapper_socket = ChunkedSocket(response) monkeypatch.setattr( job_docker, "socket", type("SocketModule", (), { "AF_UNIX": 1, "SOCK_STREAM": 2, "socket": lambda *_args: wrapper_socket, }), ) assert job_docker._socket_ping(Path("/run/itworx-job-docker/docker.sock")) assert wrapper_socket.recv_calls == 2 probe = runpy.run_path(str(ROOT / "ops" / "runner-task-images" / "build" / "itworx-ci-job-docker-probe")) probe_socket = ChunkedSocket(response) probe["_socket_ready"].__globals__["socket"] = type("SocketModule", (), { "AF_UNIX": 1, "SOCK_STREAM": 2, "socket": lambda *_args: probe_socket, }) assert probe["_socket_ready"]() assert probe_socket.recv_calls == 2 @pytest.mark.skipif(os.name == "nt", reason="rootless Docker wrapper is Linux-only") def test_job_docker_wrapper_sanitizes_remote_endpoint_environment_and_rejects_cli_overrides(monkeypatch): sys.path.insert(0, str(ROOT / "ops" / "runner-task-images" / "build")) import job_docker monkeypatch.setenv("DOCKER_HOST", "tcp://shared-dind:2375") monkeypatch.setenv("DOCKER_CONTEXT", "shared-context") monkeypatch.setenv("DOCKER_TLS_VERIFY", "1") monkeypatch.setenv("DOCKER_CERT_PATH", "/shared/certs") monkeypatch.setenv("DOCKER_CONFIG", "/job/config") environment = job_docker._client_environment() assert environment["DOCKER_HOST"] == "unix:///run/itworx-job-docker/docker.sock" assert environment["DOCKER_CONFIG"] == "/job/config" assert not any(name in environment for name in ("DOCKER_CONTEXT", "DOCKER_TLS_VERIFY", "DOCKER_CERT_PATH")) with pytest.raises(job_docker.WrapperError, match="endpoint override"): job_docker._reject_endpoint_override(["--host=tcp://shared-dind:2375", "build"]) with pytest.raises(job_docker.WrapperError, match="endpoint override"): job_docker._reject_endpoint_override(["--context", "shared-context", "info"]) job_docker._reject_endpoint_override(["run", "alpine", "sh", "-c", "echo --context"]) assert job_docker._is_local_version_request(["compose", "version"]) assert job_docker._is_local_version_request(["compose", "--version"]) assert job_docker._is_local_version_request(["buildx", "version"]) assert job_docker._is_local_version_request(["buildx", "--version"]) assert not job_docker._is_local_version_request(["compose", "up"]) @pytest.mark.skipif(os.name == "nt", reason="rootless Docker wrapper is Linux-only") def test_job_docker_daemon_command_uses_fixed_rootlesskit_and_unix_only_engine(): sys.path.insert(0, str(ROOT / "ops" / "runner-task-images" / "build")) import job_docker command = job_docker._daemon_command() assert command[:8] == [ "/usr/local/bin/rootlesskit", "--state-dir=/run/itworx-job-docker/rootlesskit", "--net=slirp4netns", "--mtu=1500", "--disable-host-loopback", "--port-driver=builtin", "--copy-up=/etc", "--copy-up=/run", ] assert command[8:11] == ["/usr/local/bin/docker-init", "--", "/usr/local/bin/dockerd"] daemon_args = command[11:] assert "--host=unix:///run/itworx-job-docker/docker.sock" in daemon_args assert not any(argument.startswith("--host=tcp:") for argument in daemon_args) assert "--storage-driver=overlay2" in daemon_args assert "--feature=containerd-snapshotter=false" in daemon_args assert "--data-root=/home/itworx-builder/.local/share/docker" in daemon_args def test_nested_build_subordinate_ranges_cover_mapped_nonroot_ids_only(): dockerfile = (ROOT / "ops/runner-task-images/build/Dockerfile").read_text() assert "'ubuntu:1:999' 'ubuntu:1001:64536'" in dockerfile assert "sed -i '/^ubuntu:/d'" in dockerfile pins = json.loads((ROOT / "ops/runner-task-images/pins.json").read_text()) ranges = pins["jobLocalDocker"]["subordinateIdRanges"] assert ranges == [{"start": 1, "count": 999}, {"start": 1001, "count": 64536}] subordinate = {value for row in ranges for value in range(row["start"], row["start"] + row["count"])} assert len(subordinate) == 65535 assert not {0, 1000} & subordinate assert min(subordinate) == 1 and max(subordinate) == 65536 # All normal image IDs, including nobody=65534, fit in the inner namespace. assert len(subordinate) >= 65534 @pytest.mark.skipif(os.name == "nt", reason="rootless Docker wrapper is Linux-only") @pytest.mark.parametrize("bad", ["", "0 0 4294967294\n", "0 1000 1\n1 100000 65535\n", "0 1000 1\n100000 100000 65536\n", "SECRET-CANARY\n", "0 1000 1\n1 100000 65536\n2 2 1\n", "x" * 257]) @pytest.mark.parametrize("kind", ["uid", "gid"]) def test_private_engine_refuses_wrong_parent_mapping_before_runtime_effects(tmp_path, monkeypatch, bad, kind): sys.path.insert(0, str(ROOT / "ops/runner-task-images/build")) import job_docker for name in ("uid", "gid"): path = tmp_path / (name + "_map") path.write_text(bad if name == kind else "0 1000 1\n1 100000 65536\n") monkeypatch.setattr(job_docker, name.upper() + "_MAP_PATH", path) effects = [] monkeypatch.setattr(job_docker, "_ensure_runtime", lambda: effects.append("runtime")) monkeypatch.setattr(job_docker, "_launch_daemon", lambda: effects.append("daemon")) with pytest.raises(job_docker.WrapperError) as caught: job_docker._ensure_daemon() assert str(caught.value) in {"private Docker parent ID mapping invalid", "private Docker parent ID mapping unavailable"} assert "SECRET-CANARY" not in str(caught.value) assert effects == [] @pytest.mark.skipif(os.name == "nt", reason="rootless Docker wrapper is Linux-only") @pytest.mark.parametrize("mapping", [" 0 1000 1\n 1 100000 65536\n", " 0 0 4294967295\n"]) def test_private_engine_accepts_own_supported_parent_maps_and_refuses_missing_map(tmp_path, monkeypatch, mapping): sys.path.insert(0, str(ROOT / "ops/runner-task-images/build")) import job_docker for kind in ("uid", "gid"): path = tmp_path / kind path.write_text(mapping) monkeypatch.setattr(job_docker, kind.upper() + "_MAP_PATH", path) job_docker._validate_parent_id_maps() job_docker.GID_MAP_PATH.unlink() with pytest.raises(job_docker.WrapperError, match="mapping unavailable"): job_docker._validate_parent_id_maps() @pytest.mark.skipif(os.name == "nt", reason="rootless Docker wrapper is Linux-only") @pytest.mark.parametrize("rootful_kind", ["uid", "gid"]) def test_private_engine_refuses_mixed_supported_parent_maps_before_effects(tmp_path, monkeypatch, rootful_kind): sys.path.insert(0, str(ROOT / "ops/runner-task-images/build")) import job_docker for kind in ("uid", "gid"): path = tmp_path / kind path.write_text("0 0 4294967295\n" if kind == rootful_kind else "0 1000 1\n1 100000 65536\n") monkeypatch.setattr(job_docker, kind.upper() + "_MAP_PATH", path) effects = [] monkeypatch.setattr(job_docker, "_ensure_runtime", lambda: effects.append("runtime")) monkeypatch.setattr(job_docker, "_launch_daemon", lambda: effects.append("daemon")) with pytest.raises(job_docker.WrapperError, match="mapping invalid"): job_docker._ensure_daemon() assert effects == [] @pytest.mark.skipif(os.name == "nt", reason="rootless Docker wrapper is Linux-only") def test_private_engine_accepts_actual_supported_runtime_parent_namespace(): sys.path.insert(0, str(ROOT / "ops/runner-task-images/build")) import job_docker # Real own namespace from the Linux test container; no host /proc or effects. job_docker._validate_parent_id_maps()