Assert packaged image runtime configuration
This commit is contained in:
parent
270bc9563e
commit
d980b25198
3 changed files with 115 additions and 4 deletions
|
|
@ -170,10 +170,14 @@ environment file, and no production/VPS/Telegram target. CI also builds exactly
|
|||
one `linux/amd64` image, starts it with the reviewed restrictions, requires the
|
||||
built-in zero-adapter/no-`send_message` health response, checks that an
|
||||
unsupported `send-message` entrypoint command fails with the exact redacted
|
||||
error, binds the running container to the dynamically resolved image ID, and
|
||||
removes the candidate container, tag, and visible image ID. The receipt records
|
||||
whether `linux/amd64` ran natively or through cross-platform emulation. Pinned
|
||||
base images and build cache may remain.
|
||||
error, and binds the running container to the dynamically resolved image ID.
|
||||
Before startup, the smoke also requires the built image to declare the implicit
|
||||
root bootstrap user, exact Python entrypoint, `service` command, healthcheck
|
||||
timings, and `SIGTERM` stop signal. The successful health command then proves
|
||||
PID 1 dropped to UID/GID `65532` with zero Linux capabilities. Finally, the
|
||||
smoke removes the candidate container, tag, and visible image ID. The receipt
|
||||
records whether `linux/amd64` ran natively or through cross-platform emulation.
|
||||
Pinned base images and build cache may remain.
|
||||
|
||||
The entrypoint command rejection is not a real Hermes tool invocation, and the
|
||||
bridge-network smoke does not prove general network-egress denial. These tests
|
||||
|
|
|
|||
|
|
@ -36,6 +36,16 @@ FAILURE_CLAIM_CEILING = (
|
|||
)
|
||||
ENTRYPOINT = "/opt/livingip/leoclean-nosend/entrypoint.py"
|
||||
RUNTIME_PYTHON = "/opt/livingip/leoclean-nosend/venv/bin/python"
|
||||
EXPECTED_ENTRYPOINT = [RUNTIME_PYTHON, ENTRYPOINT]
|
||||
EXPECTED_COMMAND = ["service"]
|
||||
EXPECTED_HEALTHCHECK = {
|
||||
"Test": ["CMD", RUNTIME_PYTHON, ENTRYPOINT, "health"],
|
||||
"Interval": 15_000_000_000,
|
||||
"Timeout": 10_000_000_000,
|
||||
"StartPeriod": 30_000_000_000,
|
||||
"Retries": 4,
|
||||
}
|
||||
EXPECTED_STOP_SIGNAL = "SIGTERM"
|
||||
|
||||
|
||||
class SmokeError(RuntimeError):
|
||||
|
|
@ -76,6 +86,32 @@ def _docker_json(arguments: list[str], *, timeout: float = 120.0) -> Any:
|
|||
raise SmokeError("Docker did not return JSON") from exc
|
||||
|
||||
|
||||
def _validate_image_runtime_configuration(image: dict[str, Any]) -> dict[str, Any]:
|
||||
config = image.get("Config")
|
||||
_require(isinstance(config, dict), "smoke image runtime configuration is missing")
|
||||
user = config.get("User")
|
||||
_require(user in (None, ""), "smoke image must begin as the root bootstrap user")
|
||||
_require(config.get("Entrypoint") == EXPECTED_ENTRYPOINT, "smoke image entrypoint drifted")
|
||||
_require(config.get("Cmd") == EXPECTED_COMMAND, "smoke image command drifted")
|
||||
_require(config.get("StopSignal") == EXPECTED_STOP_SIGNAL, "smoke image stop signal drifted")
|
||||
healthcheck = config.get("Healthcheck")
|
||||
_require(isinstance(healthcheck, dict), "smoke image healthcheck is missing")
|
||||
normalized_healthcheck = dict(healthcheck)
|
||||
start_interval = normalized_healthcheck.pop("StartInterval", None)
|
||||
_require(
|
||||
start_interval is None or (type(start_interval) is int and start_interval == 0),
|
||||
"smoke image healthcheck start interval drifted",
|
||||
)
|
||||
_require(normalized_healthcheck == EXPECTED_HEALTHCHECK, "smoke image healthcheck drifted")
|
||||
return {
|
||||
"bootstrap_user": "root",
|
||||
"entrypoint": EXPECTED_ENTRYPOINT,
|
||||
"command": EXPECTED_COMMAND,
|
||||
"healthcheck": {**EXPECTED_HEALTHCHECK, "StartInterval": 0},
|
||||
"stop_signal": EXPECTED_STOP_SIGNAL,
|
||||
}
|
||||
|
||||
|
||||
def _write_receipt(path: Path, value: dict[str, Any]) -> None:
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
temporary = path.with_name(f".{path.name}.{uuid.uuid4().hex}.tmp")
|
||||
|
|
@ -311,6 +347,7 @@ def run_smoke(*, repo_root: Path, artifact: Path, runtime_receipt: Path, output:
|
|||
for key, value in package.expected_image_labels(image_input).items():
|
||||
_require(labels.get(key) == value, f"smoke image label drifted: {key}")
|
||||
_require(labels.get("livingip.leoclean-nosend-smoke") == smoke_id, "smoke image label is missing")
|
||||
runtime_configuration = _validate_image_runtime_configuration(image)
|
||||
_run(_run_command(tag=tag, name=name, smoke_label=smoke_label), timeout=60)
|
||||
health_wait = _wait_healthy(name, expected_image_id=image_id)
|
||||
health = _verify_health(name)
|
||||
|
|
@ -333,6 +370,7 @@ def run_smoke(*, repo_root: Path, artifact: Path, runtime_receipt: Path, output:
|
|||
"docker_server_architecture": docker_server_arch,
|
||||
"host_machine": host_platform.machine(),
|
||||
"target_execution_mode": execution_mode,
|
||||
"image_runtime_configuration": runtime_configuration,
|
||||
"health_wait": health_wait,
|
||||
"health": health,
|
||||
"unsupported_send_message": unsupported,
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import copy
|
||||
import json
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
|
|
@ -9,6 +10,74 @@ import pytest
|
|||
from scripts import run_gcp_leoclean_nosend_oci_smoke as smoke
|
||||
|
||||
|
||||
def _runtime_image(*, user: str | None = "", start_interval: int | None = 0) -> dict:
|
||||
healthcheck = {
|
||||
**smoke.EXPECTED_HEALTHCHECK,
|
||||
"StartInterval": start_interval,
|
||||
}
|
||||
return {
|
||||
"Config": {
|
||||
"User": user,
|
||||
"Entrypoint": list(smoke.EXPECTED_ENTRYPOINT),
|
||||
"Cmd": list(smoke.EXPECTED_COMMAND),
|
||||
"Healthcheck": healthcheck,
|
||||
"StopSignal": smoke.EXPECTED_STOP_SIGNAL,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@pytest.mark.parametrize("user", [None, ""])
|
||||
@pytest.mark.parametrize("start_interval", [None, 0])
|
||||
def test_runtime_configuration_accepts_portable_unset_values(user: str | None, start_interval: int | None) -> None:
|
||||
receipt = smoke._validate_image_runtime_configuration(
|
||||
_runtime_image(user=user, start_interval=start_interval)
|
||||
)
|
||||
|
||||
assert receipt["bootstrap_user"] == "root"
|
||||
assert receipt["entrypoint"] == smoke.EXPECTED_ENTRYPOINT
|
||||
assert receipt["command"] == ["service"]
|
||||
assert receipt["stop_signal"] == "SIGTERM"
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("field", "value", "message"),
|
||||
[
|
||||
("User", "65532:65532", "root bootstrap user"),
|
||||
("Entrypoint", ["/bin/sh"], "entrypoint drifted"),
|
||||
("Cmd", ["probe"], "command drifted"),
|
||||
("StopSignal", "SIGKILL", "stop signal drifted"),
|
||||
],
|
||||
)
|
||||
def test_runtime_configuration_rejects_authoritative_field_drift(field: str, value: object, message: str) -> None:
|
||||
image = _runtime_image()
|
||||
image["Config"][field] = value
|
||||
|
||||
with pytest.raises(smoke.SmokeError, match=message):
|
||||
smoke._validate_image_runtime_configuration(image)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("field", "value"),
|
||||
[
|
||||
("Test", ["CMD-SHELL", "true"]),
|
||||
("Interval", 1),
|
||||
("Timeout", 1),
|
||||
("StartPeriod", 1),
|
||||
("Retries", 1),
|
||||
("StartInterval", 1),
|
||||
("StartInterval", False),
|
||||
("Unexpected", 1),
|
||||
],
|
||||
)
|
||||
def test_runtime_configuration_rejects_healthcheck_drift(field: str, value: object) -> None:
|
||||
image = _runtime_image()
|
||||
image["Config"]["Healthcheck"] = copy.deepcopy(image["Config"]["Healthcheck"])
|
||||
image["Config"]["Healthcheck"][field] = value
|
||||
|
||||
with pytest.raises(smoke.SmokeError, match="healthcheck"):
|
||||
smoke._validate_image_runtime_configuration(image)
|
||||
|
||||
|
||||
def test_build_command_is_one_platform_pinned_local_build(tmp_path: Path) -> None:
|
||||
command = smoke._build_command(
|
||||
context=tmp_path,
|
||||
|
|
|
|||
Loading…
Reference in a new issue