clonebox 1.1.17__py3-none-any.whl → 1.1.19__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- clonebox/backends/libvirt_backend.py +3 -1
- clonebox/cli.py +591 -544
- clonebox/cloner.py +498 -422
- clonebox/health/probes.py +14 -0
- clonebox/policies/__init__.py +13 -0
- clonebox/policies/engine.py +112 -0
- clonebox/policies/models.py +55 -0
- clonebox/policies/validators.py +26 -0
- clonebox/validator.py +228 -51
- {clonebox-1.1.17.dist-info → clonebox-1.1.19.dist-info}/METADATA +1 -1
- {clonebox-1.1.17.dist-info → clonebox-1.1.19.dist-info}/RECORD +15 -11
- {clonebox-1.1.17.dist-info → clonebox-1.1.19.dist-info}/WHEEL +0 -0
- {clonebox-1.1.17.dist-info → clonebox-1.1.19.dist-info}/entry_points.txt +0 -0
- {clonebox-1.1.17.dist-info → clonebox-1.1.19.dist-info}/licenses/LICENSE +0 -0
- {clonebox-1.1.17.dist-info → clonebox-1.1.19.dist-info}/top_level.txt +0 -0
clonebox/validator.py
CHANGED
|
@@ -32,8 +32,8 @@ class VMValidator:
|
|
|
32
32
|
self.require_running_apps = require_running_apps
|
|
33
33
|
self.smoke_test = smoke_test
|
|
34
34
|
self.results = {
|
|
35
|
-
"mounts": {"passed": 0, "failed": 0, "total": 0, "details": []},
|
|
36
|
-
"packages": {"passed": 0, "failed": 0, "total": 0, "details": []},
|
|
35
|
+
"mounts": {"passed": 0, "failed": 0, "skipped": 0, "total": 0, "details": []},
|
|
36
|
+
"packages": {"passed": 0, "failed": 0, "skipped": 0, "total": 0, "details": []},
|
|
37
37
|
"snap_packages": {
|
|
38
38
|
"passed": 0,
|
|
39
39
|
"failed": 0,
|
|
@@ -41,7 +41,8 @@ class VMValidator:
|
|
|
41
41
|
"total": 0,
|
|
42
42
|
"details": [],
|
|
43
43
|
},
|
|
44
|
-
"services": {"passed": 0, "failed": 0, "total": 0, "details": []},
|
|
44
|
+
"services": {"passed": 0, "failed": 0, "skipped": 0, "total": 0, "details": []},
|
|
45
|
+
"disk": {"usage_pct": 0, "avail": "0", "total": "0"},
|
|
45
46
|
"apps": {"passed": 0, "failed": 0, "skipped": 0, "total": 0, "details": []},
|
|
46
47
|
"smoke": {"passed": 0, "failed": 0, "skipped": 0, "total": 0, "details": []},
|
|
47
48
|
"overall": "unknown",
|
|
@@ -76,39 +77,38 @@ class VMValidator:
|
|
|
76
77
|
|
|
77
78
|
pid = response["return"]["pid"]
|
|
78
79
|
|
|
79
|
-
|
|
80
|
-
time.
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
timeout=5,
|
|
95
|
-
)
|
|
80
|
+
deadline = time.time() + timeout
|
|
81
|
+
while time.time() < deadline:
|
|
82
|
+
status_result = subprocess.run(
|
|
83
|
+
[
|
|
84
|
+
"virsh",
|
|
85
|
+
"--connect",
|
|
86
|
+
self.conn_uri,
|
|
87
|
+
"qemu-agent-command",
|
|
88
|
+
self.vm_name,
|
|
89
|
+
f'{{"execute":"guest-exec-status","arguments":{{"pid":{pid}}}}}',
|
|
90
|
+
],
|
|
91
|
+
capture_output=True,
|
|
92
|
+
text=True,
|
|
93
|
+
timeout=5,
|
|
94
|
+
)
|
|
96
95
|
|
|
97
|
-
|
|
98
|
-
|
|
96
|
+
if status_result.returncode != 0:
|
|
97
|
+
return None
|
|
99
98
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
99
|
+
status_resp = json.loads(status_result.stdout)
|
|
100
|
+
if "return" not in status_resp:
|
|
101
|
+
return None
|
|
103
102
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
103
|
+
ret = status_resp["return"]
|
|
104
|
+
if ret.get("exited", False):
|
|
105
|
+
if "out-data" in ret:
|
|
106
|
+
return base64.b64decode(ret["out-data"]).decode().strip()
|
|
107
|
+
return ""
|
|
107
108
|
|
|
108
|
-
|
|
109
|
-
return base64.b64decode(ret["out-data"]).decode().strip()
|
|
109
|
+
time.sleep(0.2)
|
|
110
110
|
|
|
111
|
-
return
|
|
111
|
+
return None
|
|
112
112
|
|
|
113
113
|
except Exception:
|
|
114
114
|
return None
|
|
@@ -130,6 +130,7 @@ class VMValidator:
|
|
|
130
130
|
|
|
131
131
|
def validate_mounts(self) -> Dict:
|
|
132
132
|
"""Validate all mount points and copied data paths."""
|
|
133
|
+
setup_in_progress = self._setup_in_progress_cache is True
|
|
133
134
|
self.console.print("\n[bold]💾 Validating Mounts & Data...[/]")
|
|
134
135
|
|
|
135
136
|
paths = self.config.get("paths", {})
|
|
@@ -146,7 +147,13 @@ class VMValidator:
|
|
|
146
147
|
mount_output = self._exec_in_vm("mount | grep 9p")
|
|
147
148
|
mounted_paths = []
|
|
148
149
|
if mount_output:
|
|
149
|
-
|
|
150
|
+
for line in mount_output.split("\n"):
|
|
151
|
+
line = line.strip()
|
|
152
|
+
if not line:
|
|
153
|
+
continue
|
|
154
|
+
parts = line.split()
|
|
155
|
+
if len(parts) >= 3:
|
|
156
|
+
mounted_paths.append(parts[2])
|
|
150
157
|
|
|
151
158
|
mount_table = Table(title="Data Validation", border_style="cyan")
|
|
152
159
|
mount_table.add_column("Guest Path", style="bold")
|
|
@@ -155,7 +162,6 @@ class VMValidator:
|
|
|
155
162
|
mount_table.add_column("Files", justify="right")
|
|
156
163
|
|
|
157
164
|
# Validate bind mounts (paths)
|
|
158
|
-
setup_in_progress = self._setup_in_progress() is True
|
|
159
165
|
for host_path, guest_path in paths.items():
|
|
160
166
|
self.results["mounts"]["total"] += 1
|
|
161
167
|
|
|
@@ -186,6 +192,7 @@ class VMValidator:
|
|
|
186
192
|
elif setup_in_progress:
|
|
187
193
|
status_icon = "[yellow]⏳ Pending[/]"
|
|
188
194
|
status = "pending"
|
|
195
|
+
self.results["mounts"]["skipped"] += 1
|
|
189
196
|
else:
|
|
190
197
|
status_icon = "[red]❌ Not Mounted[/]"
|
|
191
198
|
self.results["mounts"]["failed"] += 1
|
|
@@ -226,6 +233,7 @@ class VMValidator:
|
|
|
226
233
|
elif setup_in_progress:
|
|
227
234
|
status_icon = "[yellow]⏳ Pending[/]"
|
|
228
235
|
status = "pending"
|
|
236
|
+
self.results["mounts"]["skipped"] += 1
|
|
229
237
|
else:
|
|
230
238
|
status_icon = "[red]❌ Missing[/]"
|
|
231
239
|
self.results["mounts"]["failed"] += 1
|
|
@@ -250,6 +258,7 @@ class VMValidator:
|
|
|
250
258
|
|
|
251
259
|
def validate_packages(self) -> Dict:
|
|
252
260
|
"""Validate APT packages are installed."""
|
|
261
|
+
setup_in_progress = self._setup_in_progress() is True
|
|
253
262
|
self.console.print("\n[bold]📦 Validating APT Packages...[/]")
|
|
254
263
|
|
|
255
264
|
packages = self.config.get("packages", [])
|
|
@@ -262,8 +271,6 @@ class VMValidator:
|
|
|
262
271
|
pkg_table.add_column("Status", justify="center")
|
|
263
272
|
pkg_table.add_column("Version", style="dim")
|
|
264
273
|
|
|
265
|
-
setup_in_progress = self._setup_in_progress() is True
|
|
266
|
-
|
|
267
274
|
for package in packages:
|
|
268
275
|
self.results["packages"]["total"] += 1
|
|
269
276
|
|
|
@@ -280,6 +287,7 @@ class VMValidator:
|
|
|
280
287
|
else:
|
|
281
288
|
if setup_in_progress:
|
|
282
289
|
pkg_table.add_row(package, "[yellow]⏳ Pending[/]", "")
|
|
290
|
+
self.results["packages"]["skipped"] += 1
|
|
283
291
|
self.results["packages"]["details"].append(
|
|
284
292
|
{"package": package, "installed": False, "version": None, "pending": True}
|
|
285
293
|
)
|
|
@@ -299,6 +307,7 @@ class VMValidator:
|
|
|
299
307
|
|
|
300
308
|
def validate_snap_packages(self) -> Dict:
|
|
301
309
|
"""Validate snap packages are installed."""
|
|
310
|
+
setup_in_progress = self._setup_in_progress() is True
|
|
302
311
|
self.console.print("\n[bold]📦 Validating Snap Packages...[/]")
|
|
303
312
|
|
|
304
313
|
snap_packages = self.config.get("snap_packages", [])
|
|
@@ -311,8 +320,6 @@ class VMValidator:
|
|
|
311
320
|
snap_table.add_column("Status", justify="center")
|
|
312
321
|
snap_table.add_column("Version", style="dim")
|
|
313
322
|
|
|
314
|
-
setup_in_progress = self._setup_in_progress() is True
|
|
315
|
-
|
|
316
323
|
for package in snap_packages:
|
|
317
324
|
self.results["snap_packages"]["total"] += 1
|
|
318
325
|
|
|
@@ -380,6 +387,7 @@ class VMValidator:
|
|
|
380
387
|
|
|
381
388
|
def validate_services(self) -> Dict:
|
|
382
389
|
"""Validate services are enabled and running."""
|
|
390
|
+
setup_in_progress = self._setup_in_progress() is True
|
|
383
391
|
self.console.print("\n[bold]⚙️ Validating Services...[/]")
|
|
384
392
|
|
|
385
393
|
services = self.config.get("services", [])
|
|
@@ -441,7 +449,9 @@ class VMValidator:
|
|
|
441
449
|
|
|
442
450
|
if is_enabled and is_running:
|
|
443
451
|
self.results["services"]["passed"] += 1
|
|
444
|
-
elif
|
|
452
|
+
elif setup_in_progress:
|
|
453
|
+
self.results["services"]["skipped"] += 1
|
|
454
|
+
else:
|
|
445
455
|
self.results["services"]["failed"] += 1
|
|
446
456
|
|
|
447
457
|
self.results["services"]["details"].append(
|
|
@@ -464,6 +474,7 @@ class VMValidator:
|
|
|
464
474
|
return self.results["services"]
|
|
465
475
|
|
|
466
476
|
def validate_apps(self) -> Dict:
|
|
477
|
+
setup_in_progress = self._setup_in_progress() is True
|
|
467
478
|
packages = self.config.get("packages", [])
|
|
468
479
|
snap_packages = self.config.get("snap_packages", [])
|
|
469
480
|
# Support both v1 (app_data_paths) and v2 (copy_paths) config formats
|
|
@@ -649,8 +660,6 @@ class VMValidator:
|
|
|
649
660
|
note = ""
|
|
650
661
|
pending = False
|
|
651
662
|
|
|
652
|
-
setup_in_progress = self._setup_in_progress() is True
|
|
653
|
-
|
|
654
663
|
if app == "firefox":
|
|
655
664
|
installed = (
|
|
656
665
|
self._exec_in_vm("command -v firefox >/dev/null 2>&1 && echo yes || echo no")
|
|
@@ -716,6 +725,9 @@ class VMValidator:
|
|
|
716
725
|
if setup_in_progress and not installed:
|
|
717
726
|
pending = True
|
|
718
727
|
note = note or "setup in progress"
|
|
728
|
+
elif setup_in_progress and not profile_ok:
|
|
729
|
+
pending = True
|
|
730
|
+
note = note or "profile import in progress"
|
|
719
731
|
|
|
720
732
|
running_icon = (
|
|
721
733
|
"[dim]—[/]"
|
|
@@ -766,6 +778,7 @@ class VMValidator:
|
|
|
766
778
|
return self.results["apps"]
|
|
767
779
|
|
|
768
780
|
def validate_smoke_tests(self) -> Dict:
|
|
781
|
+
setup_in_progress = self._setup_in_progress() is True
|
|
769
782
|
packages = self.config.get("packages", [])
|
|
770
783
|
snap_packages = self.config.get("snap_packages", [])
|
|
771
784
|
# Support both v1 (app_data_paths) and v2 (copy_paths) config formats
|
|
@@ -888,7 +901,6 @@ class VMValidator:
|
|
|
888
901
|
table.add_column("Launch", justify="center")
|
|
889
902
|
table.add_column("Note", style="dim")
|
|
890
903
|
|
|
891
|
-
setup_in_progress = self._setup_in_progress() is True
|
|
892
904
|
for app in expected:
|
|
893
905
|
self.results["smoke"]["total"] += 1
|
|
894
906
|
installed = _installed(app)
|
|
@@ -970,8 +982,142 @@ class VMValidator:
|
|
|
970
982
|
except Exception:
|
|
971
983
|
return False
|
|
972
984
|
|
|
985
|
+
def validate_disk_space(self) -> Dict:
|
|
986
|
+
"""Validate disk space on root filesystem."""
|
|
987
|
+
setup_in_progress = self._setup_in_progress() is True
|
|
988
|
+
self.console.print("\n[bold]💾 Validating Disk Space...[/]")
|
|
989
|
+
|
|
990
|
+
df_output = self._exec_in_vm("df -h / --output=pcent,avail,size | tail -n 1", timeout=20)
|
|
991
|
+
if not df_output:
|
|
992
|
+
self.console.print("[red]❌ Could not check disk space[/]")
|
|
993
|
+
return {"status": "error"}
|
|
994
|
+
|
|
995
|
+
try:
|
|
996
|
+
# Format: pcent avail size
|
|
997
|
+
# Example: 98% 100M 30G
|
|
998
|
+
parts = df_output.split()
|
|
999
|
+
usage_pct = int(parts[0].replace('%', ''))
|
|
1000
|
+
avail = parts[1]
|
|
1001
|
+
total = parts[2]
|
|
1002
|
+
|
|
1003
|
+
self.results["disk"] = {
|
|
1004
|
+
"usage_pct": usage_pct,
|
|
1005
|
+
"avail": avail,
|
|
1006
|
+
"total": total
|
|
1007
|
+
}
|
|
1008
|
+
|
|
1009
|
+
if usage_pct > 90:
|
|
1010
|
+
self.console.print(f"[red]❌ Disk nearly full: {usage_pct}% used ({avail} available of {total})[/]")
|
|
1011
|
+
status = "fail"
|
|
1012
|
+
elif usage_pct > 85:
|
|
1013
|
+
self.console.print(f"[yellow]⚠️ Disk usage high: {usage_pct}% used ({avail} available of {total})[/]")
|
|
1014
|
+
status = "warning"
|
|
1015
|
+
else:
|
|
1016
|
+
self.console.print(f"[green]✅ Disk space OK: {usage_pct}% used ({avail} available of {total})[/]")
|
|
1017
|
+
status = "pass"
|
|
1018
|
+
|
|
1019
|
+
if usage_pct > 80:
|
|
1020
|
+
self._print_disk_usage_breakdown()
|
|
1021
|
+
|
|
1022
|
+
return self.results["disk"]
|
|
1023
|
+
except Exception as e:
|
|
1024
|
+
self.console.print(f"[red]❌ Error parsing df output: {e}[/]")
|
|
1025
|
+
return {"status": "error"}
|
|
1026
|
+
|
|
1027
|
+
def _print_disk_usage_breakdown(self) -> None:
|
|
1028
|
+
def _parse_du_lines(out: Optional[str]) -> List[Tuple[str, str]]:
|
|
1029
|
+
if not out:
|
|
1030
|
+
return []
|
|
1031
|
+
rows: List[Tuple[str, str]] = []
|
|
1032
|
+
for line in out.splitlines():
|
|
1033
|
+
line = line.strip()
|
|
1034
|
+
if not line:
|
|
1035
|
+
continue
|
|
1036
|
+
parts = line.split(maxsplit=1)
|
|
1037
|
+
if len(parts) != 2:
|
|
1038
|
+
continue
|
|
1039
|
+
size, path = parts
|
|
1040
|
+
rows.append((path, size))
|
|
1041
|
+
return rows
|
|
1042
|
+
|
|
1043
|
+
def _dir_size(path: str, timeout: int = 30) -> Optional[str]:
|
|
1044
|
+
out = self._exec_in_vm(f"du -x -s -h {path} 2>/dev/null | head -n 1 | cut -f1", timeout=timeout)
|
|
1045
|
+
return out.strip() if out else None
|
|
1046
|
+
|
|
1047
|
+
self.console.print("\n[bold]📁 Disk usage breakdown (largest directories)[/]")
|
|
1048
|
+
|
|
1049
|
+
top_level = self._exec_in_vm(
|
|
1050
|
+
"du -x -h --max-depth=1 / 2>/dev/null | sort -hr | head -n 15",
|
|
1051
|
+
timeout=60,
|
|
1052
|
+
)
|
|
1053
|
+
top_rows = _parse_du_lines(top_level)
|
|
1054
|
+
|
|
1055
|
+
if top_rows:
|
|
1056
|
+
table = Table(title="Disk Usage: / (Top 15)", border_style="cyan")
|
|
1057
|
+
table.add_column("Path", style="bold")
|
|
1058
|
+
table.add_column("Size", justify="right")
|
|
1059
|
+
for path, size in top_rows:
|
|
1060
|
+
table.add_row(path, size)
|
|
1061
|
+
self.console.print(table)
|
|
1062
|
+
else:
|
|
1063
|
+
self.console.print("[dim]Could not compute top-level directory sizes (du may be busy)[/]")
|
|
1064
|
+
|
|
1065
|
+
var_sz = _dir_size("/var")
|
|
1066
|
+
home_sz = _dir_size("/home")
|
|
1067
|
+
if var_sz or home_sz:
|
|
1068
|
+
sum_table = Table(title="Disk Usage: Key Directories", border_style="cyan")
|
|
1069
|
+
sum_table.add_column("Path", style="bold")
|
|
1070
|
+
sum_table.add_column("Size", justify="right")
|
|
1071
|
+
for p in ["/var", "/var/lib", "/var/log", "/var/cache", "/var/lib/snapd", "/home", "/home/ubuntu", "/tmp"]:
|
|
1072
|
+
sz = _dir_size(p, timeout=30)
|
|
1073
|
+
if sz:
|
|
1074
|
+
sum_table.add_row(p, sz)
|
|
1075
|
+
self.console.print(sum_table)
|
|
1076
|
+
|
|
1077
|
+
var_breakdown = self._exec_in_vm(
|
|
1078
|
+
"du -x -h --max-depth=1 /var 2>/dev/null | sort -hr | head -n 12",
|
|
1079
|
+
timeout=60,
|
|
1080
|
+
)
|
|
1081
|
+
var_rows = _parse_du_lines(var_breakdown)
|
|
1082
|
+
if var_rows:
|
|
1083
|
+
vtable = Table(title="Disk Usage: /var (Top 12)", border_style="cyan")
|
|
1084
|
+
vtable.add_column("Path", style="bold")
|
|
1085
|
+
vtable.add_column("Size", justify="right")
|
|
1086
|
+
for path, size in var_rows:
|
|
1087
|
+
vtable.add_row(path, size)
|
|
1088
|
+
self.console.print(vtable)
|
|
1089
|
+
|
|
1090
|
+
home_breakdown = self._exec_in_vm(
|
|
1091
|
+
"du -x -h --max-depth=2 /home/ubuntu 2>/dev/null | sort -hr | head -n 12",
|
|
1092
|
+
timeout=60,
|
|
1093
|
+
)
|
|
1094
|
+
home_rows = _parse_du_lines(home_breakdown)
|
|
1095
|
+
if home_rows:
|
|
1096
|
+
htable = Table(title="Disk Usage: /home/ubuntu (Top 12)", border_style="cyan")
|
|
1097
|
+
htable.add_column("Path", style="bold")
|
|
1098
|
+
htable.add_column("Size", justify="right")
|
|
1099
|
+
for path, size in home_rows:
|
|
1100
|
+
htable.add_row(path, size)
|
|
1101
|
+
self.console.print(htable)
|
|
1102
|
+
|
|
1103
|
+
copy_paths = self.config.get("copy_paths", None)
|
|
1104
|
+
if not isinstance(copy_paths, dict) or not copy_paths:
|
|
1105
|
+
copy_paths = self.config.get("app_data_paths", {})
|
|
1106
|
+
if copy_paths:
|
|
1107
|
+
ctable = Table(title="Disk Usage: Configured Imported Paths", border_style="cyan")
|
|
1108
|
+
ctable.add_column("Guest Path", style="bold")
|
|
1109
|
+
ctable.add_column("Size", justify="right")
|
|
1110
|
+
for _, guest_path in copy_paths.items():
|
|
1111
|
+
sz = _dir_size(guest_path, timeout=30)
|
|
1112
|
+
if sz:
|
|
1113
|
+
ctable.add_row(str(guest_path), sz)
|
|
1114
|
+
else:
|
|
1115
|
+
ctable.add_row(str(guest_path), "—")
|
|
1116
|
+
self.console.print(ctable)
|
|
1117
|
+
|
|
973
1118
|
def validate_all(self) -> Dict:
|
|
974
1119
|
"""Run all validations and return comprehensive results."""
|
|
1120
|
+
setup_in_progress = self._setup_in_progress() is True
|
|
975
1121
|
self.console.print("[bold cyan]🔍 Running Full Validation...[/]")
|
|
976
1122
|
|
|
977
1123
|
# Check if VM is running
|
|
@@ -995,6 +1141,14 @@ class VMValidator:
|
|
|
995
1141
|
return self.results
|
|
996
1142
|
|
|
997
1143
|
# Check QEMU Guest Agent
|
|
1144
|
+
if not self._check_qga_ready():
|
|
1145
|
+
wait_deadline = time.time() + 180
|
|
1146
|
+
self.console.print("[yellow]⏳ Waiting for QEMU Guest Agent (up to 180s)...[/]")
|
|
1147
|
+
while time.time() < wait_deadline:
|
|
1148
|
+
time.sleep(5)
|
|
1149
|
+
if self._check_qga_ready():
|
|
1150
|
+
break
|
|
1151
|
+
|
|
998
1152
|
if not self._check_qga_ready():
|
|
999
1153
|
self.console.print("[red]❌ QEMU Guest Agent not responding[/]")
|
|
1000
1154
|
self.console.print("\n[bold]🔧 Troubleshooting QGA:[/]")
|
|
@@ -1009,7 +1163,6 @@ class VMValidator:
|
|
|
1009
1163
|
return self.results
|
|
1010
1164
|
|
|
1011
1165
|
ci_status = self._exec_in_vm("cloud-init status --long 2>/dev/null || cloud-init status 2>/dev/null || true", timeout=20)
|
|
1012
|
-
setup_in_progress = False
|
|
1013
1166
|
if ci_status:
|
|
1014
1167
|
ci_lower = ci_status.lower()
|
|
1015
1168
|
if "running" in ci_lower:
|
|
@@ -1026,6 +1179,7 @@ class VMValidator:
|
|
|
1026
1179
|
)
|
|
1027
1180
|
|
|
1028
1181
|
# Run all validations
|
|
1182
|
+
self.validate_disk_space()
|
|
1029
1183
|
self.validate_mounts()
|
|
1030
1184
|
self.validate_packages()
|
|
1031
1185
|
self.validate_snap_packages()
|
|
@@ -1045,8 +1199,10 @@ class VMValidator:
|
|
|
1045
1199
|
)
|
|
1046
1200
|
|
|
1047
1201
|
# Calculate overall status
|
|
1202
|
+
disk_failed = 1 if self.results.get("disk", {}).get("usage_pct", 0) > 90 else 0
|
|
1048
1203
|
total_checks = (
|
|
1049
|
-
|
|
1204
|
+
1 # Disk space check
|
|
1205
|
+
+ self.results["mounts"]["total"]
|
|
1050
1206
|
+ self.results["packages"]["total"]
|
|
1051
1207
|
+ self.results["snap_packages"]["total"]
|
|
1052
1208
|
+ self.results["services"]["total"]
|
|
@@ -1055,7 +1211,8 @@ class VMValidator:
|
|
|
1055
1211
|
)
|
|
1056
1212
|
|
|
1057
1213
|
total_passed = (
|
|
1058
|
-
|
|
1214
|
+
(1 - disk_failed)
|
|
1215
|
+
+ self.results["mounts"]["passed"]
|
|
1059
1216
|
+ self.results["packages"]["passed"]
|
|
1060
1217
|
+ self.results["snap_packages"]["passed"]
|
|
1061
1218
|
+ self.results["services"]["passed"]
|
|
@@ -1064,7 +1221,8 @@ class VMValidator:
|
|
|
1064
1221
|
)
|
|
1065
1222
|
|
|
1066
1223
|
total_failed = (
|
|
1067
|
-
|
|
1224
|
+
disk_failed
|
|
1225
|
+
+ self.results["mounts"]["failed"]
|
|
1068
1226
|
+ self.results["packages"]["failed"]
|
|
1069
1227
|
+ self.results["snap_packages"]["failed"]
|
|
1070
1228
|
+ self.results["services"]["failed"]
|
|
@@ -1072,12 +1230,14 @@ class VMValidator:
|
|
|
1072
1230
|
+ (self.results["smoke"]["failed"] if self.smoke_test else 0)
|
|
1073
1231
|
)
|
|
1074
1232
|
|
|
1075
|
-
# Get skipped
|
|
1233
|
+
# Get skipped counts
|
|
1234
|
+
skipped_mounts = self.results["mounts"].get("skipped", 0)
|
|
1235
|
+
skipped_packages = self.results["packages"].get("skipped", 0)
|
|
1076
1236
|
skipped_services = self.results["services"].get("skipped", 0)
|
|
1077
1237
|
skipped_snaps = self.results["snap_packages"].get("skipped", 0)
|
|
1078
1238
|
skipped_apps = self.results["apps"].get("skipped", 0)
|
|
1079
1239
|
skipped_smoke = self.results["smoke"].get("skipped", 0) if self.smoke_test else 0
|
|
1080
|
-
total_skipped = skipped_services + skipped_snaps + skipped_apps + skipped_smoke
|
|
1240
|
+
total_skipped = skipped_mounts + skipped_packages + skipped_services + skipped_snaps + skipped_apps + skipped_smoke
|
|
1081
1241
|
|
|
1082
1242
|
# Print summary
|
|
1083
1243
|
self.console.print("\n[bold]📊 Validation Summary[/]")
|
|
@@ -1085,21 +1245,38 @@ class VMValidator:
|
|
|
1085
1245
|
summary_table.add_column("Category", style="bold")
|
|
1086
1246
|
summary_table.add_column("Passed", justify="right", style="green")
|
|
1087
1247
|
summary_table.add_column("Failed", justify="right", style="red")
|
|
1088
|
-
summary_table.add_column("Skipped", justify="right", style="dim")
|
|
1248
|
+
summary_table.add_column("Skipped/Pending", justify="right", style="dim")
|
|
1089
1249
|
summary_table.add_column("Total", justify="right")
|
|
1090
1250
|
|
|
1251
|
+
# Add Disk Space row
|
|
1252
|
+
disk_usage_pct = self.results.get("disk", {}).get("usage_pct", 0)
|
|
1253
|
+
disk_avail = self.results.get("disk", {}).get("avail", "?")
|
|
1254
|
+
disk_total = self.results.get("disk", {}).get("total", "?")
|
|
1255
|
+
|
|
1256
|
+
# Calculate used space if possible
|
|
1257
|
+
disk_status_passed = "[green]OK[/]" if disk_usage_pct <= 90 else "—"
|
|
1258
|
+
disk_status_failed = "—" if disk_usage_pct <= 90 else f"[red]FULL ({disk_usage_pct}%)[/]"
|
|
1259
|
+
|
|
1260
|
+
summary_table.add_row(
|
|
1261
|
+
"Disk Space",
|
|
1262
|
+
disk_status_passed,
|
|
1263
|
+
disk_status_failed,
|
|
1264
|
+
"—",
|
|
1265
|
+
f"{disk_usage_pct}% of {disk_total} ({disk_avail} free)",
|
|
1266
|
+
)
|
|
1267
|
+
|
|
1091
1268
|
summary_table.add_row(
|
|
1092
1269
|
"Mounts",
|
|
1093
1270
|
str(self.results["mounts"]["passed"]),
|
|
1094
1271
|
str(self.results["mounts"]["failed"]),
|
|
1095
|
-
"—",
|
|
1272
|
+
str(skipped_mounts) if skipped_mounts else "—",
|
|
1096
1273
|
str(self.results["mounts"]["total"]),
|
|
1097
1274
|
)
|
|
1098
1275
|
summary_table.add_row(
|
|
1099
1276
|
"APT Packages",
|
|
1100
1277
|
str(self.results["packages"]["passed"]),
|
|
1101
1278
|
str(self.results["packages"]["failed"]),
|
|
1102
|
-
"—",
|
|
1279
|
+
str(skipped_packages) if skipped_packages else "—",
|
|
1103
1280
|
str(self.results["packages"]["total"]),
|
|
1104
1281
|
)
|
|
1105
1282
|
summary_table.add_row(
|
|
@@ -1113,7 +1290,7 @@ class VMValidator:
|
|
|
1113
1290
|
"Services",
|
|
1114
1291
|
str(self.results["services"]["passed"]),
|
|
1115
1292
|
str(self.results["services"]["failed"]),
|
|
1116
|
-
str(skipped_services),
|
|
1293
|
+
str(skipped_services) if skipped_services else "—",
|
|
1117
1294
|
str(self.results["services"]["total"]),
|
|
1118
1295
|
)
|
|
1119
1296
|
summary_table.add_row(
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
clonebox/__init__.py,sha256=CyfHVVq6KqBr4CNERBpXk_O6Q5B35q03YpdQbokVvvI,408
|
|
2
2
|
clonebox/__main__.py,sha256=Fcoyzwwyz5-eC_sBlQk5a5RbKx8uodQz5sKJ190U0NU,135
|
|
3
3
|
clonebox/audit.py,sha256=1W9vaIjB0A--_p7CgE3cIP5RNckJG1RxJrL-tOb-QmU,14298
|
|
4
|
-
clonebox/cli.py,sha256=
|
|
5
|
-
clonebox/cloner.py,sha256=
|
|
4
|
+
clonebox/cli.py,sha256=yxGj733N1OUt8m4UzqxGV9jBZCdehychpV8KRi7zZJU,179262
|
|
5
|
+
clonebox/cloner.py,sha256=rU5MERLn2JwayjLM2RsMeOWdY0xJSkCVzXl7dj9uIVM,111622
|
|
6
6
|
clonebox/container.py,sha256=tiYK1ZB-DhdD6A2FuMA0h_sRNkUI7KfYcJ0tFOcdyeM,6105
|
|
7
7
|
clonebox/dashboard.py,sha256=dMY6odvPq3j6FronhRRsX7aY3qdCwznB-aCWKEmHDNw,5768
|
|
8
8
|
clonebox/detector.py,sha256=vS65cvFNPmUBCX1Y_TMTnSRljw6r1Ae9dlVtACs5XFc,23075
|
|
@@ -20,14 +20,14 @@ clonebox/resource_monitor.py,sha256=lDR9KyPbVtImeeOkOBPPVP-5yCgoL5hsVFPZ_UqsY0w,
|
|
|
20
20
|
clonebox/resources.py,sha256=IkuM4OdSDV4qhyc0eIynwbAHBTv0aVSxxW-gghsnCAs,6815
|
|
21
21
|
clonebox/rollback.py,sha256=hpwO-8Ehe1pW0wHuZvJkC_qxZ6yEo9otCJRhGIUArCo,5711
|
|
22
22
|
clonebox/secrets.py,sha256=l1jwJcEPB1qMoGNLPjyrkKKr1khh9VmftFJI9BWhgK0,10628
|
|
23
|
-
clonebox/validator.py,sha256=
|
|
24
|
-
clonebox/backends/libvirt_backend.py,sha256=
|
|
23
|
+
clonebox/validator.py,sha256=2d14DHSo7Im-fFJaaDeACN1H82728FmwcZDSPVKbG44,53368
|
|
24
|
+
clonebox/backends/libvirt_backend.py,sha256=_HxB2itduhDsXrWoNTqelUqHEXnPqbnBuFNu8XJkNFA,7269
|
|
25
25
|
clonebox/backends/qemu_disk.py,sha256=YsGjYX5sbEf35Y4yjTpNkZat73a4RGBxY-KTVzJhqIs,1687
|
|
26
26
|
clonebox/backends/subprocess_runner.py,sha256=c-IyaMxM1cmUu64h654oAvulm83K5Mu-VQxXJ_0BOds,1506
|
|
27
27
|
clonebox/health/__init__.py,sha256=aKJJPQwJLnoCY728QuKUxYx1TZyooGEnyUVOegZ58Ok,422
|
|
28
28
|
clonebox/health/manager.py,sha256=6nn0a8QtxeEuuafDbn5ZBqHQdaJ2qg7yTstyAGPJWP0,9987
|
|
29
29
|
clonebox/health/models.py,sha256=sPumwj8S-88KgzSGw1Kq9bBbPVRd2RR0R87Z8hKJ_28,6001
|
|
30
|
-
clonebox/health/probes.py,sha256=
|
|
30
|
+
clonebox/health/probes.py,sha256=1tu0wi5TZ3Nk8z_cVDUZ7DVDrt7sY0AHJHqi9qn3ChA,11874
|
|
31
31
|
clonebox/interfaces/disk.py,sha256=F7Xzj2dq5UTZ2KGCuThDM8bwTps6chFbquOUmfLREjI,985
|
|
32
32
|
clonebox/interfaces/hypervisor.py,sha256=8ms4kZLA-5Ba1e_n68mCucwP_K9mufbmTBlo7XzURn4,1991
|
|
33
33
|
clonebox/interfaces/network.py,sha256=YPIquxEB7sZHczbpuopcZpffTjWYI6cKmAu3wAEFllk,853
|
|
@@ -35,14 +35,18 @@ clonebox/interfaces/process.py,sha256=njvAIZw_TCjw01KpyVQKIDoRvhTwl0FfVGbQ6mxTRO
|
|
|
35
35
|
clonebox/plugins/__init__.py,sha256=3cxlz159nokZCOL2c017WqTwt5z00yyn-o-SemP1g6c,416
|
|
36
36
|
clonebox/plugins/base.py,sha256=A2H-2vrYUczNZCDioQ8cAtvaSob4YpXutx7FWMjksC4,10133
|
|
37
37
|
clonebox/plugins/manager.py,sha256=W2ithedEEOh9iWSq3_M5_g2SQWl85aI5qrvrjOKv02I,16842
|
|
38
|
+
clonebox/policies/__init__.py,sha256=I7mDDU_gyZKb2pdyp7WUoN53p5qLxRxBOgHSNuPfNr8,365
|
|
39
|
+
clonebox/policies/engine.py,sha256=Wf6uG3-yf2YNOjLnpvACp5fUM5elNpK68ta_2ccmk_g,3573
|
|
40
|
+
clonebox/policies/models.py,sha256=BO5rFv21YUOjgfSgcgJUhBwUo5_0z2If1c4Io3ruo0k,1648
|
|
41
|
+
clonebox/policies/validators.py,sha256=rVAsbIMxKLpU1WeRDXkY4i282e0H25kaK6WshGmPxoQ,756
|
|
38
42
|
clonebox/snapshots/__init__.py,sha256=ndlrIavPAiA8z4Ep3-D_EPhOcjNKYFnP3rIpEKaGdb8,273
|
|
39
43
|
clonebox/snapshots/manager.py,sha256=hGzM8V6ZJPXjTqj47c4Kr8idlE-c1Q3gPUvuw1HvS1A,11393
|
|
40
44
|
clonebox/snapshots/models.py,sha256=sRnn3OZE8JG9FZJlRuA3ihO-JXoPCQ3nD3SQytflAao,6206
|
|
41
45
|
clonebox/templates/profiles/ml-dev.yaml,sha256=w07MToGh31xtxpjbeXTBk9BkpAN8A3gv8HeA3ESKG9M,461
|
|
42
46
|
clonebox/templates/profiles/web-stack.yaml,sha256=EBnnGMzML5vAjXmIUbCpbTCwmRaNJiuWd3EcL43DOK8,485
|
|
43
|
-
clonebox-1.1.
|
|
44
|
-
clonebox-1.1.
|
|
45
|
-
clonebox-1.1.
|
|
46
|
-
clonebox-1.1.
|
|
47
|
-
clonebox-1.1.
|
|
48
|
-
clonebox-1.1.
|
|
47
|
+
clonebox-1.1.19.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
|
|
48
|
+
clonebox-1.1.19.dist-info/METADATA,sha256=mJT8xFHNlOOcqtT7eINj2-QmcIaB4Otz32bUdcATmic,49052
|
|
49
|
+
clonebox-1.1.19.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
|
|
50
|
+
clonebox-1.1.19.dist-info/entry_points.txt,sha256=FES95Vi3btfViLEEoHdb8nikNxTqzaooi9ehZw9ZfWI,47
|
|
51
|
+
clonebox-1.1.19.dist-info/top_level.txt,sha256=LdMo2cvCrEcRGH2M8JgQNVsCoszLV0xug6kx1JnaRjo,9
|
|
52
|
+
clonebox-1.1.19.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|