ci: single Gitea Actions pipeline, uv-based image, scoped docker cleanup

Replace lint/test/docker workflows with ci.yml (gitleaks, lint, test-unit, test-integration, test-mcp, build-and-push, docs-check, security). Rebuild the Dockerfile as a digest-pinned multi-stage uv build running as uid 1000, add .dockerignore, a hardened compose file and a gitleaks config. The integration harness now falls back to /etc/hostname and /proc/self to find its own container.
This commit is contained in:
Latte committed 2026-10-14 15:59:19 +02:00
1 parent 9e50859133
commit 1a039e32ab
16 files changed
+729 -404

No files matched your search

+59 -3
View File
@@ -14,6 +14,7 @@ from __future__ import annotations
import atexit
import contextlib
import os
import re
import secrets
import signal
import socket
@@ -21,7 +22,9 @@ import subprocess
import sys
import threading
import time
from collections.abc import Mapping
from dataclasses import dataclass, field
from pathlib import Path
from types import FrameType
from typing import Any
@@ -76,6 +79,61 @@ def run_docker(
return proc
_MOUNTINFO_ID_RE = re.compile(r"/containers/([0-9a-f]{64})/")
_CGROUP_ID_RE = re.compile(r"(?<![0-9a-f])([0-9a-f]{64})(?![0-9a-f])")
def container_id_candidates(
environ: Mapping[str, str] | None = None,
proc_root: Path = Path("/"),
) -> list[str]:
"""Return references that may name the container this process runs in.
Order: ``$HOSTNAME``, ``/etc/hostname``, then 64-hex container ids found in
``/proc/self/mountinfo`` and ``/proc/self/cgroup``. On act_runner the hostname
is normally the container id, but a runner may set another hostname or use host
networking settings, so the other sources act as fallbacks.
"""
env = os.environ if environ is None else environ
candidates: list[str] = []
def add(value: str) -> None:
value = value.strip()
if value and value not in candidates:
candidates.append(value)
add(env.get("HOSTNAME", ""))
for rel in ("etc/hostname",):
with contextlib.suppress(OSError):
add((proc_root / rel).read_text(encoding="utf-8"))
for rel, pattern in (
("proc/self/mountinfo", _MOUNTINFO_ID_RE),
("proc/self/cgroup", _CGROUP_ID_RE),
):
with contextlib.suppress(OSError):
for match in pattern.finditer((proc_root / rel).read_text(encoding="utf-8")):
add(match.group(1))
return candidates
def job_container_ref() -> str:
"""Return a reference docker accepts for the container running the tests.
Each candidate from ``container_id_candidates`` is checked with
``docker inspect``; the first one docker resolves wins.
"""
for candidate in container_id_candidates():
proc = run_docker(
"container", "inspect", "--format", "{{.Id}}", candidate, check=False, timeout=30
)
if proc.returncode == 0 and proc.stdout.strip():
return candidate
raise HarnessError(
"AEGIS_IT_NETWORK_MODE=container: could not identify the container running the "
"tests ($HOSTNAME, /etc/hostname and /proc/self did not resolve to a container)"
)
def require_docker() -> None:
"""Fail with a clear message when the docker daemon is not usable."""
try:
@@ -417,9 +475,7 @@ def start_gitea() -> GiteaInstance:
try:
base_url = _start_container(res, container_mode)
if container_mode:
job_container = os.environ.get("HOSTNAME")
if not job_container:
raise HarnessError("AEGIS_IT_NETWORK_MODE=container requires $HOSTNAME")
job_container = job_container_ref()
run_docker("network", "connect", res.network, job_container)
res.attached_container = job_container
_wait_ready(base_url, res.container)