feat: add json output for backup plans
This commit is contained in:
parent
2a8d7fc8b7
commit
1d2dc54964
1 changed files with 61 additions and 10 deletions
|
|
@ -1,4 +1,5 @@
|
||||||
import argparse
|
import argparse
|
||||||
|
import json
|
||||||
import sys
|
import sys
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
@ -49,7 +50,7 @@ def entry_to_dict(entry):
|
||||||
"exists": data.get("exists"),
|
"exists": data.get("exists"),
|
||||||
"reason": data.get("reason"),
|
"reason": data.get("reason"),
|
||||||
"compose_file": compose_file,
|
"compose_file": compose_file,
|
||||||
"source": data.get("source"),
|
"source": str(data.get("source")) if data.get("source") is not None else None,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -84,12 +85,23 @@ def normalize_entries(entries):
|
||||||
|
|
||||||
def build_plan(scan_root, entries, compose_count):
|
def build_plan(scan_root, entries, compose_count):
|
||||||
normalized = normalize_entries(entries)
|
normalized = normalize_entries(entries)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"root": str(scan_root),
|
"root": str(scan_root),
|
||||||
"compose_files_found": compose_count,
|
"compose_files_found": compose_count,
|
||||||
"include": [e for e in normalized if e["bucket"] == "include"],
|
"include": [e for e in normalized if e["bucket"] == "include"],
|
||||||
"review": [e for e in normalized if e["bucket"] == "review"],
|
"review": [e for e in normalized if e["bucket"] == "review"],
|
||||||
"skip": [e for e in normalized if e["bucket"] == "skip"],
|
"skip": [e for e in normalized if e["bucket"] == "skip"],
|
||||||
|
"summary": {
|
||||||
|
"include_count": sum(1 for e in normalized if e["bucket"] == "include"),
|
||||||
|
"review_count": sum(1 for e in normalized if e["bucket"] == "review"),
|
||||||
|
"skip_count": sum(1 for e in normalized if e["bucket"] == "skip"),
|
||||||
|
"missing_critical_count": sum(
|
||||||
|
1
|
||||||
|
for e in normalized
|
||||||
|
if e["bucket"] == "include" and not e.get("exists", True)
|
||||||
|
),
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -141,6 +153,25 @@ def print_warnings(plan):
|
||||||
print(f" - {item['path']} (service={item['service']})")
|
print(f" - {item['path']} (service={item['service']})")
|
||||||
|
|
||||||
|
|
||||||
|
def build_json_output(plan, borg_command=None):
|
||||||
|
output = {
|
||||||
|
"tool": "DockerVault",
|
||||||
|
"version": __version__,
|
||||||
|
"plan": plan,
|
||||||
|
"status": {
|
||||||
|
"ok": True,
|
||||||
|
"has_missing_critical": any(
|
||||||
|
not item.get("exists", True) for item in plan["include"]
|
||||||
|
),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
if borg_command is not None:
|
||||||
|
output["borg_command"] = borg_command
|
||||||
|
|
||||||
|
return output
|
||||||
|
|
||||||
|
|
||||||
def build_parser():
|
def build_parser():
|
||||||
parser = argparse.ArgumentParser(
|
parser = argparse.ArgumentParser(
|
||||||
description="DockerVault - Intelligent Docker backup discovery"
|
description="DockerVault - Intelligent Docker backup discovery"
|
||||||
|
|
@ -169,6 +200,12 @@ def build_parser():
|
||||||
help="Generate borg command",
|
help="Generate borg command",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
parser.add_argument(
|
||||||
|
"--json",
|
||||||
|
action="store_true",
|
||||||
|
help="Output machine-readable JSON",
|
||||||
|
)
|
||||||
|
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--dry-run",
|
"--dry-run",
|
||||||
action="store_true",
|
action="store_true",
|
||||||
|
|
@ -237,10 +274,7 @@ def main():
|
||||||
|
|
||||||
plan = build_plan(scan_root, entries, len(compose_files))
|
plan = build_plan(scan_root, entries, len(compose_files))
|
||||||
|
|
||||||
if not args.quiet:
|
borg_shell_command = None
|
||||||
print_plan(plan)
|
|
||||||
print_warnings(plan)
|
|
||||||
|
|
||||||
if args.repo:
|
if args.repo:
|
||||||
try:
|
try:
|
||||||
from .borg import build_borg_create_command, command_to_shell
|
from .borg import build_borg_create_command, command_to_shell
|
||||||
|
|
@ -249,16 +283,33 @@ def main():
|
||||||
sys.exit(2)
|
sys.exit(2)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
include_paths = [item["path"] for item in plan["include"] if item["path"] != "?"]
|
include_paths = [
|
||||||
|
item["path"] for item in plan["include"] if item["path"] != "?"
|
||||||
|
]
|
||||||
borg_command = build_borg_create_command(args.repo, include_paths)
|
borg_command = build_borg_create_command(args.repo, include_paths)
|
||||||
|
borg_shell_command = command_to_shell(borg_command)
|
||||||
print("\nSuggested borg create command")
|
|
||||||
print("============================")
|
|
||||||
print(command_to_shell(borg_command))
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"ERROR: Failed to build borg command: {e}")
|
print(f"ERROR: Failed to build borg command: {e}")
|
||||||
sys.exit(2)
|
sys.exit(2)
|
||||||
|
|
||||||
|
if args.json:
|
||||||
|
print(
|
||||||
|
json.dumps(
|
||||||
|
build_json_output(plan, borg_shell_command),
|
||||||
|
indent=2,
|
||||||
|
sort_keys=False,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
if not args.quiet:
|
||||||
|
print_plan(plan)
|
||||||
|
print_warnings(plan)
|
||||||
|
|
||||||
|
if borg_shell_command:
|
||||||
|
print("\nSuggested borg create command")
|
||||||
|
print("============================")
|
||||||
|
print(borg_shell_command)
|
||||||
|
|
||||||
if args.automation:
|
if args.automation:
|
||||||
has_missing = any(not item.get("exists", True) for item in plan["include"])
|
has_missing = any(not item.get("exists", True) for item in plan["include"])
|
||||||
if has_missing:
|
if has_missing:
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue