mirror of
https://github.com/Such-Software/adaptive-multi-protocol-gateway.git
synced 2026-10-17 15:41:41 +02:00
262 lines
7.9 KiB
Python
262 lines
7.9 KiB
Python
from __future__ import annotations
|
|
|
|
from dataclasses import dataclass
|
|
from pathlib import Path
|
|
import shlex
|
|
|
|
from .addresses import AddressRecord, address_registry_path, effective_address_records
|
|
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
|
|
command: 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
|
|
}
|
|
address_by_protocol = {
|
|
(record.site_id, record.protocol): record
|
|
for record in effective_address_records(config)
|
|
}
|
|
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(
|
|
config,
|
|
site,
|
|
protocol,
|
|
transport_status=transport_status,
|
|
address_record=address_by_protocol[(site.id, protocol.name)],
|
|
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(
|
|
config: GatewayConfig,
|
|
site: SiteConfig,
|
|
protocol: ProtocolConfig,
|
|
*,
|
|
transport_status: TransportStatus,
|
|
address_record: AddressRecord,
|
|
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(_address_step(config, site, protocol, address_record))
|
|
steps.append(
|
|
ActivationStep(
|
|
site_id=site.id,
|
|
protocol=protocol.name,
|
|
stage="health",
|
|
action="check-transport",
|
|
target=protocol.name,
|
|
status=_health_activation_status(
|
|
output_ready=output_ready,
|
|
transport_status=transport_status,
|
|
address_record=address_record,
|
|
),
|
|
message=_health_activation_message(
|
|
output_ready=output_ready,
|
|
transport_status=transport_status,
|
|
address_record=address_record,
|
|
),
|
|
)
|
|
)
|
|
return steps
|
|
|
|
|
|
def _address_step(
|
|
config: GatewayConfig,
|
|
site: SiteConfig,
|
|
protocol: ProtocolConfig,
|
|
address_record: AddressRecord,
|
|
) -> ActivationStep:
|
|
if address_record.address_status == "placeholder":
|
|
return ActivationStep(
|
|
site_id=site.id,
|
|
protocol=protocol.name,
|
|
stage="address",
|
|
action="capture-address",
|
|
target=str(address_registry_path(config)),
|
|
status="review",
|
|
message="generated transport address is not known yet",
|
|
command=_address_capture_command(config, protocol),
|
|
)
|
|
return ActivationStep(
|
|
site_id=site.id,
|
|
protocol=protocol.name,
|
|
stage="address",
|
|
action="use-address",
|
|
target=address_record.url,
|
|
status="ready",
|
|
message=f"using {address_record.address_status} transport address",
|
|
command="-",
|
|
)
|
|
|
|
|
|
def _address_capture_command(config: GatewayConfig, protocol: ProtocolConfig) -> str:
|
|
config_path = shlex.quote(str(config.config_path))
|
|
return f"python3 -m ampg --config {config_path} addresses capture --protocol {protocol.name}"
|
|
|
|
|
|
def _health_activation_status(
|
|
*,
|
|
output_ready: bool,
|
|
transport_status: TransportStatus,
|
|
address_record: AddressRecord,
|
|
) -> str:
|
|
if not output_ready or transport_status.status == "error":
|
|
return "blocked"
|
|
if address_record.address_status == "placeholder":
|
|
return "review"
|
|
return "planned"
|
|
|
|
|
|
def _health_activation_message(
|
|
*,
|
|
output_ready: bool,
|
|
transport_status: TransportStatus,
|
|
address_record: AddressRecord,
|
|
) -> str:
|
|
if not output_ready or transport_status.status == "error":
|
|
return "health check is blocked until output and daemon steps are ready"
|
|
if address_record.address_status == "placeholder":
|
|
return "capture or configure the generated transport address before published checks"
|
|
return "verify generated site through this transport after apply"
|
|
|
|
|
|
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))
|