Files
adaptive-multi-protocol-gat…/ampg/activation.py
T

190 lines
5.5 KiB
Python

from __future__ import annotations
from dataclasses import dataclass
from pathlib import Path
from .config import GatewayConfig, ProtocolConfig, SiteConfig
from .plan import plan_artifacts, write_plan_artifacts
from .platforms import PlatformProvider
from .status import (
DaemonProbeFunc,
TransportStatus,
doctor_gateway,
gateway_status,
)
@dataclass(frozen=True)
class ActivationStep:
site_id: str
protocol: str
stage: str
action: str
target: str
status: str
message: str
def activation_steps(
config: GatewayConfig,
*,
platform_provider: PlatformProvider | None = None,
daemon_probe: DaemonProbeFunc | None = None,
) -> list[ActivationStep]:
statuses = gateway_status(
config,
platform_provider=platform_provider,
daemon_probe=daemon_probe,
)
status_by_protocol = {
(status.site_id, status.protocol): status for status in statuses
}
steps: list[ActivationStep] = []
for issue in doctor_gateway(
config,
platform_provider=platform_provider,
daemon_probe=daemon_probe,
statuses=statuses,
):
if issue.code in {"daemon-status", "output-missing"}:
continue
steps.append(
ActivationStep(
site_id=issue.site_id,
protocol=issue.protocol,
stage="preflight",
action="fix-issue",
target="-",
status=_issue_activation_status(issue.severity),
message=f"{issue.code}: {issue.message}",
)
)
for site in config.sites:
for protocol in site.protocols.values():
if not protocol.enabled:
continue
output_ready = _output_ready(site, protocol)
transport_status = status_by_protocol[(site.id, protocol.name)]
steps.extend(
_protocol_steps(
site,
protocol,
transport_status=transport_status,
output_ready=output_ready,
)
)
return steps
def blocked_steps(steps: list[ActivationStep]) -> list[ActivationStep]:
return [step for step in steps if step.status == "blocked"]
def _protocol_steps(
site: SiteConfig,
protocol: ProtocolConfig,
*,
transport_status: TransportStatus,
output_ready: bool,
) -> list[ActivationStep]:
output_root = site.outputs.root / protocol.name
steps = [
ActivationStep(
site_id=site.id,
protocol=protocol.name,
stage="output",
action="verify-output",
target=str(output_root),
status="ready" if output_ready else "blocked",
message=(
"generated output marker exists"
if output_ready
else "generated output is missing; run build before apply"
),
)
]
artifacts = plan_artifacts(site, protocol)
if artifacts:
for artifact in artifacts:
steps.append(
ActivationStep(
site_id=site.id,
protocol=protocol.name,
stage="artifact",
action="review-config",
target=str(artifact.path),
status="review",
message="review generated config before installing or reloading services",
)
)
else:
steps.append(
ActivationStep(
site_id=site.id,
protocol=protocol.name,
stage="artifact",
action="review-config",
target="-",
status="planned",
message="no generated config artifact is available for this adapter yet",
)
)
steps.append(
ActivationStep(
site_id=site.id,
protocol=protocol.name,
stage="daemon",
action=transport_status.action,
target=protocol.daemon,
status=_transport_activation_status(transport_status),
message=transport_status.message,
)
)
steps.append(
ActivationStep(
site_id=site.id,
protocol=protocol.name,
stage="health",
action="check-transport",
target=protocol.name,
status=(
"planned"
if output_ready and transport_status.status != "error"
else "blocked"
),
message=(
"verify generated site through this transport after apply"
if output_ready and transport_status.status != "error"
else "health check is blocked until output and daemon steps are ready"
),
)
)
return steps
def _output_ready(site: SiteConfig, protocol: ProtocolConfig) -> bool:
return ((site.outputs.root / protocol.name) / ".ampg-output").exists()
def _issue_activation_status(severity: str) -> str:
if severity == "error":
return "blocked"
return "review"
def _transport_activation_status(status: TransportStatus) -> str:
if status.status == "error":
return "blocked"
if status.action == "render-only" or status.status == "warn":
return "review"
return "ready"
def write_activation_artifacts(config: GatewayConfig) -> tuple[Path, ...]:
return tuple(artifact.path for artifact in write_plan_artifacts(config))