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:
1 parent
9e50859133
commit
1a039e32ab
16 files changed
+729
-404
No files matched your search
@@ -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)
|
||||
|
||||
Reference in new issue
Block a user