import json import sys def validate_plan(plan_path): try: with open(plan_path, 'r') as f: plan = json.load(f) except FileNotFoundError: print(f"Error: Plan file {plan_path} not found.") sys.exit(1) resource_changes = plan.get('resource_changes', []) # 1. Validation: Gitea Server Protection # Gitea server ID is 114, so expected address is proxmox_lxc.lxc["114"] # We also check for name just in case. gitea_deleted = False # 2. Validation: Runner Availability # We need to ensure at least one runner exists after apply. active_runners = 0 print("Validating Plan...") for change in resource_changes: address = change.get('address', '') actions = change.get('change', {}).get('actions', []) after = change.get('change', {}).get('after', {}) # Check if it is being deleted is_deleted = "delete" in actions and "create" not in actions # pure delete # Check for Gitea Server (ID 114) if 'proxmox_lxc.lxc["114"]' in address: if is_deleted: print(f"[FAIL] Plan attempts to destroy the Gitea Server ({address})!") gitea_deleted = True else: print(f"[PASS] Gitea Server ({address}) is safe (Actions: {actions}).") # Check for Gitea Runners # We identify runners by tag "gitea-runner" or name convention if tag isn't available in `after` (e.g. unknown). # However, `after` should contain tags if it's not a delete. # If it is a delete, we check `before`. is_runner = False tags = "" # Check 'after' state for surviving/new resources if not is_deleted and after: tags = after.get('tags', '') # Check 'before' state for deleted resources (just for logging/logic if needed) # Logic: We only care about what remains or is created. if not is_deleted: # If tags are computed/unknown, we might need to fallback to name or address # Runners are usually created via loop in locals, so names are predictable "gitea-runner-X" # Address format: proxmox_lxc.lxc["900"], proxmox_lxc.lxc["901"], etc. name = after.get('name', '') if after else '' if "gitea-runner" in tags or "gitea-runner" in name: is_runner = True if is_runner: active_runners += 1 if gitea_deleted: sys.exit(1) print(f"Active runners after apply: {active_runners}") if active_runners < 1: print("[FAIL] Plan results in zero active Gitea Runners!") sys.exit(1) print("[PASS] Plan validation successful.") if __name__ == "__main__": if len(sys.argv) < 2: print("Usage: python validate_plan.py ") sys.exit(1) validate_plan(sys.argv[1])