mirror of
https://github.com/Such-Software/adaptive-multi-protocol-gateway.git
synced 2026-10-17 15:41:41 +02:00
138 lines
3.7 KiB
Python
138 lines
3.7 KiB
Python
from __future__ import annotations
|
|
|
|
from dataclasses import dataclass
|
|
from pathlib import Path
|
|
import shutil
|
|
|
|
from .config import GatewayConfig
|
|
from .install_plan import InstallStateCopy, install_state_copies
|
|
from .platforms import PlatformProvider
|
|
from .state_contract import gateway_state_root
|
|
from .status import DaemonProbeFunc
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class StateApplyResult:
|
|
site_id: str
|
|
protocol: str
|
|
platform: str
|
|
kind: str
|
|
source: Path
|
|
target: Path
|
|
status: str
|
|
mode: str
|
|
message: str
|
|
|
|
|
|
def apply_state(
|
|
config: GatewayConfig,
|
|
*,
|
|
dry_run: bool,
|
|
platform_provider: PlatformProvider | None = None,
|
|
daemon_probe: DaemonProbeFunc | None = None,
|
|
) -> list[StateApplyResult]:
|
|
copies = install_state_copies(
|
|
config,
|
|
platform_provider=platform_provider,
|
|
daemon_probe=daemon_probe,
|
|
)
|
|
results: list[StateApplyResult] = []
|
|
for copy in copies:
|
|
validation = _validate_state_copy(config, copy, dry_run=dry_run)
|
|
if validation.status == "blocked":
|
|
results.append(validation)
|
|
continue
|
|
if not dry_run:
|
|
copy.target.parent.mkdir(parents=True, exist_ok=True)
|
|
shutil.copy2(copy.source, copy.target)
|
|
results.append(
|
|
StateApplyResult(
|
|
site_id=copy.site_id,
|
|
protocol=copy.protocol,
|
|
platform=copy.platform,
|
|
kind=copy.kind,
|
|
source=copy.source,
|
|
target=copy.target,
|
|
status="planned" if dry_run else "written",
|
|
mode="dry-run" if dry_run else "live",
|
|
message=(
|
|
"would copy approved artifact into AMPG-owned state"
|
|
if dry_run
|
|
else "copied approved artifact into AMPG-owned state"
|
|
),
|
|
)
|
|
)
|
|
return results
|
|
|
|
|
|
def _validate_state_copy(
|
|
config: GatewayConfig,
|
|
copy: InstallStateCopy,
|
|
*,
|
|
dry_run: bool,
|
|
) -> StateApplyResult:
|
|
if copy.status != "planned":
|
|
return _result(
|
|
copy,
|
|
status="blocked",
|
|
mode="dry-run" if dry_run else "live",
|
|
message=copy.message,
|
|
)
|
|
state_root = gateway_state_root(config).resolve()
|
|
target = copy.target.resolve()
|
|
if not _is_relative_to(target, state_root):
|
|
return _result(
|
|
copy,
|
|
status="blocked",
|
|
mode="dry-run" if dry_run else "live",
|
|
message=f"target is outside AMPG state root: {target}",
|
|
)
|
|
if not copy.source.exists():
|
|
return _result(
|
|
copy,
|
|
status="blocked",
|
|
mode="dry-run" if dry_run else "live",
|
|
message="approved source artifact is missing",
|
|
)
|
|
if copy.source.is_dir():
|
|
return _result(
|
|
copy,
|
|
status="blocked",
|
|
mode="dry-run" if dry_run else "live",
|
|
message="approved source artifact is a directory",
|
|
)
|
|
return _result(
|
|
copy,
|
|
status="ready",
|
|
mode="dry-run" if dry_run else "live",
|
|
message="state copy passed safety checks",
|
|
)
|
|
|
|
|
|
def _result(
|
|
copy: InstallStateCopy,
|
|
*,
|
|
status: str,
|
|
mode: str,
|
|
message: str,
|
|
) -> StateApplyResult:
|
|
return StateApplyResult(
|
|
site_id=copy.site_id,
|
|
protocol=copy.protocol,
|
|
platform=copy.platform,
|
|
kind=copy.kind,
|
|
source=copy.source,
|
|
target=copy.target,
|
|
status=status,
|
|
mode=mode,
|
|
message=message,
|
|
)
|
|
|
|
|
|
def _is_relative_to(path: Path, parent: Path) -> bool:
|
|
try:
|
|
path.relative_to(parent)
|
|
except ValueError:
|
|
return False
|
|
return True
|