101 lines
3.3 KiB
Python
101 lines
3.3 KiB
Python
|
|
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),
|
||
|
|
},
|
||
|
|
}
|