style(1b): auto-format + lint-clean the whole codebase (RL1)

Mechanical, semantics-preserving cleanup so the codebase passes the new lint stage:
- ruff format: all 32 Python files (wraps long signatures, normalizes quotes/blank lines).
- nixpkgs-fmt: modules/drone-runner.nix.
- shfmt (-i 2 -ci): scripts/*.sh.

Lint fixes (reviewed, behavior-preserving — no test weakened):
- ruff SIM105: try/except-pass -> contextlib.suppress (abra.py app_config rm; lifecycle.py janitor).
- ruff SIM115: open().read() -> with open() (run_recipe_ci.py redaction-values + gitea-token).
- statix: merge repeated sops `secrets.*` keys into one `secrets = { ... }` (comments kept);
  empty fn pattern `{ ... }:` -> `_:` (packages.nix).
- deadnix: drop unused lambda args (flake `self`; configuration.nix `lib`; overlay `final` -> `_`).

Verified on cc-ci: `scripts/lint.sh` -> lint: PASS; nixosConfigurations.cc-ci evaluates;
all Python byte-compiles. The deployed bridge/dashboard/runner source changes hash (reformat),
so cc-ci will be rebuilt to the new closure in W2 before the cold D1-D10 re-verification.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-27 20:52:05 +01:00
parent a0ea2f0aa9
commit 2cede01ed7
35 changed files with 431 additions and 185 deletions

View File

@ -3,8 +3,10 @@
The teardown guarantee is sacred: a failed test must never leak an app/volume/secret into the
next run. Callers wrap deploy()/teardown() in try/finally (or a pytest finalizer).
"""
from __future__ import annotations
import contextlib
import datetime
import os
import re
@ -29,7 +31,8 @@ def _docker_names(kind: str, stack: str) -> list[str]:
"""docker <kind> ls names filtered to a stack (kind: service|volume|secret)."""
proc = subprocess.run(
["docker", kind, "ls", "--filter", f"name={stack}", "--format", "{{.Name}}"],
capture_output=True, text=True,
capture_output=True,
text=True,
)
return [n for n in proc.stdout.split("\n") if n.strip()]
@ -50,16 +53,20 @@ def _stack_age_seconds(stack: str) -> float | None:
return None
oldest = None
for s in svcs:
p = subprocess.run(["docker", "service", "inspect", s, "--format", "{{.CreatedAt}}"],
capture_output=True, text=True)
p = subprocess.run(
["docker", "service", "inspect", s, "--format", "{{.CreatedAt}}"],
capture_output=True,
text=True,
)
ts = p.stdout.strip()
try:
# docker emits e.g. 2026-05-27 00:12:33.123 +0000 UTC -> take the leading 19 chars
dt = datetime.datetime.strptime(ts[:19], "%Y-%m-%d %H:%M:%S").replace(
tzinfo=datetime.timezone.utc)
tzinfo=datetime.UTC
)
except ValueError:
continue
age = (datetime.datetime.now(datetime.timezone.utc) - dt).total_seconds()
age = (datetime.datetime.now(datetime.UTC) - dt).total_seconds()
oldest = age if oldest is None else max(oldest, age)
return oldest
@ -107,7 +114,8 @@ def services_converged(domain: str) -> bool:
stack = _stack_name(domain)
proc = subprocess.run(
["docker", "stack", "services", stack, "--format", "{{.Replicas}}"],
capture_output=True, text=True,
capture_output=True,
text=True,
)
rows = [r for r in proc.stdout.split("\n") if r.strip()]
if not rows:
@ -136,8 +144,13 @@ def http_get(domain: str, path: str = "/", timeout: int = 15) -> int:
return 0
def wait_healthy(domain: str, ok_codes=(200, 301, 302), path: str = "/",
deploy_timeout: int = 600, http_timeout: int = 300) -> None:
def wait_healthy(
domain: str,
ok_codes=(200, 301, 302),
path: str = "/",
deploy_timeout: int = 600,
http_timeout: int = 300,
) -> None:
"""Wait for stack services converged, then for the app to answer ok over HTTPS at `path`.
`path` is per-recipe (recipe_meta.HEALTH_PATH), e.g. keycloak uses /realms/master."""
deadline = time.time() + deploy_timeout
@ -181,7 +194,8 @@ def _app_container(domain: str, service: str = "app") -> str:
name = f"{_stack_name(domain)}_{service}"
proc = subprocess.run(
["docker", "ps", "--filter", f"name={name}", "--format", "{{.ID}}"],
capture_output=True, text=True,
capture_output=True,
text=True,
)
cid = proc.stdout.strip().split("\n")[0]
if not cid:
@ -221,8 +235,8 @@ def teardown_app(domain: str, verify: bool = True) -> None:
stack = _stack_name(domain)
abra.undeploy(domain)
if _docker_names("service", stack):
_force_stack_rm(stack) # fallback: abra undeploy didn't clear it
abra.volume_remove(domain) # needs the .env -> before removing it
_force_stack_rm(stack) # fallback: abra undeploy didn't clear it
abra.volume_remove(domain) # needs the .env -> before removing it
abra.secret_remove_all(domain)
# belt-and-suspenders: drop any volumes/secrets abra missed, by stack name. A volume can be
# briefly held by a just-stopped task after `stack rm`, so retry the volume removal.
@ -238,7 +252,7 @@ def teardown_app(domain: str, verify: bool = True) -> None:
time.sleep(3)
for s in _docker_names("secret", stack):
subprocess.run(["docker", "secret", "rm", s], capture_output=True, text=True)
abra.app_config_remove(domain) # only now (stack gone) drop the .env
abra.app_config_remove(domain) # only now (stack gone) drop the .env
if verify:
residual = _residual(domain)
@ -252,6 +266,7 @@ def janitor(max_age_seconds: int | None = None) -> None:
docker primitives so it works even when the .env is gone (A2/A3). Default 2h, env-overridable
via CCCI_JANITOR_MAX_AGE (e.g. 0 to reap all matching orphans immediately)."""
import os
if max_age_seconds is None:
max_age_seconds = int(os.environ.get("CCCI_JANITOR_MAX_AGE", "7200"))
seen = set()
@ -271,7 +286,5 @@ def janitor(max_age_seconds: int | None = None) -> None:
age = _stack_age_seconds(stack)
if age is not None and age < max_age_seconds:
continue # likely a concurrent in-flight run; leave it
try:
with contextlib.suppress(Exception):
teardown_app(name, verify=False)
except Exception:
pass