mirror of
https://github.com/kevinveenbirkenbach/computer-playbook.git
synced 2026-11-06 02:54:26 +00:00
Three CodeRabbit findings on PR #1265: - script.py listed the running containers with `container ps` and `container inspect` without a timeout, so a hung engine kept the daily unit active forever and blocked the next timer run. Both calls now use EXEC_TIMEOUT_SECONDS; main turns a timeout or engine error into a one-line failure and exit 1 instead of a traceback. - runtime.py's PHP_PACKAGE_PATTERN requires a digit after "php", so an image installing an unversioned interpreter package (`php-cli`) and setting no PHP_VERSION was reported as PHP-free. The interpreter packages php, php-cli, php-fpm, php-cgi, php-phpdbg and libapache2-mod-php are now recorded as PhpRuntime.packages and reported with an unknown patch level; extension and library packages (php-mysql, php-pear, php-common, phpmyadmin) and paths do not count. - php_finding returned early for a patched PHP_VERSION, so an end-of-life package line installed next to it (e.g. php7) was never reported. The version only returns a finding now; otherwise the lines are judged too. Every finding has unit tests that fail on the previous code. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
96 lines
2.9 KiB
Python
Executable File
96 lines
2.9 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
from concurrent.futures import ThreadPoolExecutor
|
|
|
|
from utils.docker.image.discovery import ImageRef, iter_role_images
|
|
from utils.docker.image.runtime import (
|
|
build_instructions,
|
|
load_php_floors,
|
|
php_finding,
|
|
php_runtime,
|
|
)
|
|
|
|
from . import PROJECT_ROOT
|
|
|
|
WORKERS = 4
|
|
OS_NAME = "linux"
|
|
ARCHITECTURE = "amd64"
|
|
|
|
|
|
def _instructions(ref: ImageRef) -> list[str] | None:
|
|
return build_instructions(
|
|
f"{ref.registry}/{ref.name}",
|
|
ref.version,
|
|
os_name=OS_NAME,
|
|
architecture=ARCHITECTURE,
|
|
)
|
|
|
|
|
|
def report(refs: list[ImageRef], probed: list, floors: dict[str, str]) -> int:
|
|
"""Print one line per image that ships PHP and return the exit code.
|
|
|
|
Args:
|
|
refs: the pinned role images.
|
|
probed: :func:`build_instructions` result per ref, in the same order.
|
|
floors: see :func:`load_php_floors`.
|
|
|
|
Returns:
|
|
``1`` when an image is below its floor or the registry gave no answer
|
|
for one, ``0`` otherwise.
|
|
"""
|
|
findings = 0
|
|
unknown = []
|
|
with_php = 0
|
|
for ref, instructions in zip(refs, probed, strict=True):
|
|
if instructions is None:
|
|
unknown.append(f"{ref.role}:{ref.service} ({ref.source})")
|
|
continue
|
|
runtime = php_runtime(instructions)
|
|
if runtime is None:
|
|
continue
|
|
with_php += 1
|
|
if runtime.version:
|
|
php = runtime.version
|
|
elif runtime.lines:
|
|
php = f"line {', '.join(runtime.lines)}"
|
|
else:
|
|
php = f"package {', '.join(runtime.packages)}"
|
|
finding = php_finding(runtime, floors)
|
|
if finding:
|
|
findings += 1
|
|
print(f"❌ {ref.role}:{ref.service} {ref.source} PHP {php}: {finding}")
|
|
elif runtime.version:
|
|
print(f"✅ {ref.role}:{ref.service} {ref.source} PHP {php}")
|
|
else:
|
|
print(
|
|
f"❔ {ref.role}:{ref.service} {ref.source} PHP {php}: "
|
|
"patch level not in the registry metadata"
|
|
)
|
|
for entry in unknown:
|
|
print(f"⚠️ {entry}: the registry gave no answer")
|
|
print(
|
|
f"\n{len(refs)} image(s): {with_php} ship PHP, {findings} below the "
|
|
f"floor, {len(unknown)} unanswered"
|
|
)
|
|
return 1 if findings or unknown else 0
|
|
|
|
|
|
def main(argv=None) -> int:
|
|
argparse.ArgumentParser(
|
|
description=(
|
|
"List the PHP runtime of every pinned role image from registry "
|
|
"metadata, without pulling, and fail on one below its security floor."
|
|
)
|
|
).parse_args(argv)
|
|
floors = load_php_floors()
|
|
refs = sorted(iter_role_images(PROJECT_ROOT), key=lambda r: (r.role, r.service))
|
|
with ThreadPoolExecutor(max_workers=WORKERS) as pool:
|
|
probed = list(pool.map(_instructions, refs))
|
|
return report(refs, probed, floors)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|