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/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
- # Wait a bit for command to complete
80
- time.sleep(0.3)
81
-
82
- # Get result
83
- status_result = subprocess.run(
84
- [
85
- "virsh",
86
- "--connect",
87
- self.conn_uri,
88
- "qemu-agent-command",
89
- self.vm_name,
90
- f'{{"execute":"guest-exec-status","arguments":{{"pid":{pid}}}}}',
91
- ],
92
- capture_output=True,
93
- text=True,
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
- if status_result.returncode != 0:
98
- return None
96
+ if status_result.returncode != 0:
97
+ return None
99
98
 
100
- status_resp = json.loads(status_result.stdout)
101
- if "return" not in status_resp:
102
- return None
99
+ status_resp = json.loads(status_result.stdout)
100
+ if "return" not in status_resp:
101
+ return None
103
102
 
104
- ret = status_resp["return"]
105
- if not ret.get("exited", False):
106
- return None
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
- if "out-data" in ret:
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
- mounted_paths = [line.split()[2] for line in mount_output.split("\n") if line.strip()]
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 not setup_in_progress:
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
- self.results["mounts"]["total"]
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
- self.results["mounts"]["passed"]
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
- self.results["mounts"]["failed"]
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 services count
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,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: clonebox
3
- Version: 1.1.17
3
+ Version: 1.1.19
4
4
  Summary: Clone your workstation environment to an isolated VM with selective apps, paths and services
5
5
  Author: CloneBox Team
6
6
  License: Apache-2.0
@@ -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=k3q-JrzsiDcGPjhzHbm1jPKajxD2chYJxSGGftCbA20,178481
5
- clonebox/cloner.py,sha256=HI6un0wukNwftO61m2jASbeJ8SiowCEpWMKMb5Qsd8c,107319
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=uXBkdrWSk8oBhRnD1y4mwxRdxOPduRraroSIs14bAo8,45342
24
- clonebox/backends/libvirt_backend.py,sha256=sIHFIvFO1hIOXEFR_foSkOGBgIzaJVQs-njOU8GdafA,7170
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=CkiGcayqRRysqaBJst-YpSrvUzMdwsqD4TiQSluLt3Y,11305
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.17.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
44
- clonebox-1.1.17.dist-info/METADATA,sha256=Ns1tp6tNWuGaJWdTEi0BE4MjR7XCmb2LU5n1OZYqeSw,49052
45
- clonebox-1.1.17.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
46
- clonebox-1.1.17.dist-info/entry_points.txt,sha256=FES95Vi3btfViLEEoHdb8nikNxTqzaooi9ehZw9ZfWI,47
47
- clonebox-1.1.17.dist-info/top_level.txt,sha256=LdMo2cvCrEcRGH2M8JgQNVsCoszLV0xug6kx1JnaRjo,9
48
- clonebox-1.1.17.dist-info/RECORD,,
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,,