mirror of
https://github.com/Such-Software/adaptive-multi-protocol-gateway.git
synced 2026-10-17 07:31:32 +02:00
Add fixture-based health plans
This commit is contained in:
+67
@@ -14,6 +14,7 @@ from .audit import audit_gateway
|
||||
from .build import build_gateway
|
||||
from .config import load_config
|
||||
from .docsgen import generate_docs
|
||||
from .health import HealthCheck, blocked_health_checks, health_plan
|
||||
from .install_plan import (
|
||||
InstallStep,
|
||||
blocked_install_steps,
|
||||
@@ -105,6 +106,28 @@ def main(argv: list[str] | None = None) -> int:
|
||||
action="store_true",
|
||||
help="Write managed daemon config and supervisor artifacts to the plan root.",
|
||||
)
|
||||
health_parser = subcommands.add_parser(
|
||||
"health-plan",
|
||||
help="Print fixture-based transport health checks.",
|
||||
)
|
||||
_add_target_selection(health_parser)
|
||||
health_parser.add_argument(
|
||||
"--mode",
|
||||
choices=("published", "preview"),
|
||||
default="published",
|
||||
help="Plan checks against published transport URLs or local preview URLs.",
|
||||
)
|
||||
health_parser.add_argument(
|
||||
"--base-port",
|
||||
type=int,
|
||||
default=19080,
|
||||
help="First loopback port assigned to preview health checks.",
|
||||
)
|
||||
health_parser.add_argument(
|
||||
"--host",
|
||||
default="127.0.0.1",
|
||||
help="Loopback host for preview health checks.",
|
||||
)
|
||||
build_parser = subcommands.add_parser("build", help="Build enabled protocol outputs.")
|
||||
_add_target_selection(build_parser)
|
||||
fixture_parser = subcommands.add_parser("manifest", help="Write AMPB fixture manifests.")
|
||||
@@ -195,6 +218,8 @@ def main(argv: list[str] | None = None) -> int:
|
||||
return _cmd_apply(config, args)
|
||||
if args.command == "install-plan":
|
||||
return _cmd_install_plan(config, args)
|
||||
if args.command == "health-plan":
|
||||
return _cmd_health_plan(config, args)
|
||||
if args.command == "build":
|
||||
return _cmd_build(config)
|
||||
if args.command == "manifest":
|
||||
@@ -272,6 +297,28 @@ def _cmd_install_plan(config, args) -> int:
|
||||
return 1 if blocked else 0
|
||||
|
||||
|
||||
def _cmd_health_plan(config, args) -> int:
|
||||
checks = health_plan(
|
||||
config,
|
||||
mode=args.mode,
|
||||
base_port=args.base_port,
|
||||
host=args.host,
|
||||
)
|
||||
for check in checks:
|
||||
_print_health_check(check)
|
||||
blocked = blocked_health_checks(checks)
|
||||
print(
|
||||
"AMPG_HEALTH_SUMMARY "
|
||||
f"mode={args.mode} "
|
||||
f"sites={len(config.sites)} "
|
||||
f"checks={len(checks)} "
|
||||
f"planned={sum(1 for check in checks if check.status == 'planned')} "
|
||||
f"review={sum(1 for check in checks if check.status == 'review')} "
|
||||
f"blocked={len(blocked)}"
|
||||
)
|
||||
return 1 if blocked else 0
|
||||
|
||||
|
||||
def _add_target_selection(parser) -> None:
|
||||
parser.add_argument(
|
||||
"--profile",
|
||||
@@ -388,6 +435,26 @@ def _cmd_build(config) -> int:
|
||||
return 0
|
||||
|
||||
|
||||
def _print_health_check(check: HealthCheck) -> None:
|
||||
print(
|
||||
"AMPG_HEALTH_CHECK "
|
||||
f"site={check.site_id} "
|
||||
f"protocol={check.protocol} "
|
||||
f"mode={check.mode} "
|
||||
f"route={check.route} "
|
||||
f"url=\"{_quote(check.url)}\" "
|
||||
f"output_root=\"{_quote(str(check.output_root))}\" "
|
||||
f"output_path={check.output_path} "
|
||||
f"transport={check.transport} "
|
||||
f"profile={check.profile} "
|
||||
f"address_status={check.address_status} "
|
||||
f"status={check.status} "
|
||||
f"action={check.action} "
|
||||
f"command=\"{_quote(check.command)}\" "
|
||||
f"message=\"{_quote(check.message)}\""
|
||||
)
|
||||
|
||||
|
||||
def _print_install_step(step: InstallStep) -> None:
|
||||
print(
|
||||
"AMPG_INSTALL_STEP "
|
||||
|
||||
+122
@@ -0,0 +1,122 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
from .config import GatewayConfig, SiteConfig
|
||||
from .manifest import fixture_manifest
|
||||
from .preview import preview_endpoints, preview_fixture_manifest
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class HealthCheck:
|
||||
site_id: str
|
||||
protocol: str
|
||||
mode: str
|
||||
url: str
|
||||
output_root: Path
|
||||
output_path: str
|
||||
transport: str
|
||||
profile: str
|
||||
address_status: str
|
||||
route: str
|
||||
status: str
|
||||
action: str
|
||||
command: str
|
||||
message: str
|
||||
|
||||
|
||||
def health_plan(
|
||||
config: GatewayConfig,
|
||||
*,
|
||||
mode: str = "published",
|
||||
base_port: int = 19080,
|
||||
host: str = "127.0.0.1",
|
||||
) -> list[HealthCheck]:
|
||||
if mode not in {"published", "preview"}:
|
||||
raise ValueError(f"unsupported health mode {mode!r}")
|
||||
|
||||
endpoints = preview_endpoints(config, base_port=base_port, host=host) if mode == "preview" else []
|
||||
checks: list[HealthCheck] = []
|
||||
for site in config.sites:
|
||||
manifest = _manifest_for_mode(site, mode=mode, endpoints=endpoints)
|
||||
for fixture in manifest["fixtures"]:
|
||||
checks.append(_health_check(site, fixture, mode=mode))
|
||||
return checks
|
||||
|
||||
|
||||
def blocked_health_checks(checks: list[HealthCheck]) -> list[HealthCheck]:
|
||||
return [check for check in checks if check.status == "blocked"]
|
||||
|
||||
|
||||
def _manifest_for_mode(
|
||||
site: SiteConfig,
|
||||
*,
|
||||
mode: str,
|
||||
endpoints,
|
||||
) -> dict[str, Any]:
|
||||
if mode == "preview":
|
||||
return preview_fixture_manifest(site, endpoints)
|
||||
return fixture_manifest(site)
|
||||
|
||||
|
||||
def _health_check(site: SiteConfig, fixture: dict[str, Any], *, mode: str) -> HealthCheck:
|
||||
protocol = str(fixture["protocol"])
|
||||
output_root = site.outputs.root / protocol
|
||||
route = str(fixture.get("route", {}).get("fixture_path", "/"))
|
||||
transport = str(fixture["checks"]["transport"])
|
||||
profile = str(fixture["checks"]["profile"])
|
||||
address_status = str(fixture["address_status"])
|
||||
output_ready = (output_root / ".ampg-output").exists()
|
||||
preview_status = fixture.get("preview", {}).get("status")
|
||||
|
||||
if not output_ready or preview_status == "missing-output":
|
||||
status = "blocked"
|
||||
action = "build-output"
|
||||
message = f"generated output is missing: {output_root}"
|
||||
elif address_status == "placeholder":
|
||||
status = "review"
|
||||
action = "configure-address"
|
||||
message = "fixture uses a placeholder address; update protocol URL after daemon identity exists"
|
||||
else:
|
||||
status = "planned"
|
||||
action = "check-fixture"
|
||||
message = (
|
||||
"verify fixture through the local preview server"
|
||||
if mode == "preview"
|
||||
else "verify fixture through the selected transport/profile after services start"
|
||||
)
|
||||
|
||||
return HealthCheck(
|
||||
site_id=site.id,
|
||||
protocol=protocol,
|
||||
mode=mode,
|
||||
url=str(fixture["url"]),
|
||||
output_root=output_root,
|
||||
output_path=str(fixture["output_path"]),
|
||||
transport=transport,
|
||||
profile=profile,
|
||||
address_status=address_status,
|
||||
route=route,
|
||||
status=status,
|
||||
action=action,
|
||||
command=_check_command(str(fixture["url"]), transport=transport, profile=profile),
|
||||
message=message,
|
||||
)
|
||||
|
||||
|
||||
def _check_command(url: str, *, transport: str, profile: str) -> str:
|
||||
if transport == "clearnet":
|
||||
return f"curl -fsSL {url}"
|
||||
if transport == "tor":
|
||||
return f"curl --proxy socks5h://127.0.0.1:9050 -fsSL {url}"
|
||||
if transport == "i2p":
|
||||
return f"curl --proxy http://127.0.0.1:4444 -fsSL {url}"
|
||||
if transport == "gemini":
|
||||
return f"gemini fetch {url}"
|
||||
if transport == "ipfs":
|
||||
return f"ipfs cat {url}"
|
||||
if transport == "reticulum":
|
||||
return f"rns-fetch {url}"
|
||||
return f"fetch {url} with {profile}"
|
||||
Reference in New Issue
Block a user