(feature) Add host backup preflight gates
Introduce a host preflight layer that separates dry-run blockers from real backup blockers. Show the effective per-host backup configuration in Django before queueing a run. Block real backup queueing when failed host checks remain, while still allowing dry-runs when only local storage preparation is missing.
This commit is contained in:
100
src/pobsync_backend/preflight.py
Normal file
100
src/pobsync_backend/preflight.py
Normal file
@@ -0,0 +1,100 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
from typing import Any
|
||||
|
||||
from pobsync.config.merge import build_effective_config
|
||||
|
||||
from .config_repository import global_config_object_data, host_config_object_data
|
||||
from .host_ops import collect_host_checks
|
||||
from .models import GlobalConfig, HostConfig
|
||||
from .self_check import SelfCheck
|
||||
|
||||
|
||||
DRY_RUN_BLOCKING_CHECKS = {
|
||||
"Host global config",
|
||||
"Host address",
|
||||
"Host SSH key file",
|
||||
"Host effective source root",
|
||||
"Host effective SSH user",
|
||||
"Host effective SSH port",
|
||||
"Host effective SSH credential",
|
||||
"Host effective rsync recursion",
|
||||
}
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class BackupGate:
|
||||
state: str
|
||||
message: str
|
||||
checks: list[SelfCheck]
|
||||
real_blockers: list[SelfCheck]
|
||||
dry_run_blockers: list[SelfCheck]
|
||||
warnings: list[SelfCheck]
|
||||
|
||||
@property
|
||||
def can_queue_real(self) -> bool:
|
||||
return not self.real_blockers
|
||||
|
||||
@property
|
||||
def can_queue_dry_run(self) -> bool:
|
||||
return not self.dry_run_blockers
|
||||
|
||||
|
||||
def collect_backup_gate(host: HostConfig, global_config: GlobalConfig | None = None) -> BackupGate:
|
||||
checks = collect_host_checks(host, global_config)
|
||||
real_blockers = [check for check in checks if check.status == "failed"]
|
||||
dry_run_blockers = [check for check in real_blockers if check.name in DRY_RUN_BLOCKING_CHECKS]
|
||||
warnings = [check for check in checks if check.status == "warning"]
|
||||
|
||||
if real_blockers:
|
||||
state = "blocked"
|
||||
message = "Real backups are blocked until failed host checks are resolved."
|
||||
elif warnings:
|
||||
state = "warning"
|
||||
message = "Backups can run, but review the warnings first."
|
||||
else:
|
||||
state = "ready"
|
||||
message = "This host is ready for backup runs."
|
||||
|
||||
return BackupGate(
|
||||
state=state,
|
||||
message=message,
|
||||
checks=checks,
|
||||
real_blockers=real_blockers,
|
||||
dry_run_blockers=dry_run_blockers,
|
||||
warnings=warnings,
|
||||
)
|
||||
|
||||
|
||||
def effective_host_config_preview(host: HostConfig, global_config: GlobalConfig) -> dict[str, Any]:
|
||||
config = build_effective_config(global_config_object_data(global_config), host_config_object_data(host))
|
||||
credential = host.ssh_credential or global_config.default_ssh_credential
|
||||
ssh = config.get("ssh", {}) or {}
|
||||
rsync = config.get("rsync", {}) or {}
|
||||
retention = config.get("retention", {}) or {}
|
||||
|
||||
return {
|
||||
"source_root": config.get("source_root", ""),
|
||||
"destination_subdir": (config.get("defaults", {}) or {}).get("destination_subdir", ""),
|
||||
"includes": list(config.get("includes") or []),
|
||||
"excludes": list(config.get("excludes_effective") or []),
|
||||
"ssh": {
|
||||
"user": ssh.get("user", ""),
|
||||
"port": ssh.get("port", ""),
|
||||
"options": list(ssh.get("options") or []),
|
||||
"credential": str(credential) if credential else "",
|
||||
},
|
||||
"rsync": {
|
||||
"binary": rsync.get("binary", ""),
|
||||
"args": list(rsync.get("args_effective") or []),
|
||||
"timeout_seconds": rsync.get("timeout_seconds", 0),
|
||||
"bwlimit_kbps": rsync.get("bwlimit_kbps", 0),
|
||||
},
|
||||
"retention": {
|
||||
"daily": retention.get("daily", 0),
|
||||
"weekly": retention.get("weekly", 0),
|
||||
"monthly": retention.get("monthly", 0),
|
||||
"yearly": retention.get("yearly", 0),
|
||||
},
|
||||
}
|
||||
Reference in New Issue
Block a user