clonebox 1.1.14__py3-none-any.whl → 1.1.15__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
@@ -34,13 +34,21 @@ class VMValidator:
34
34
  self.results = {
35
35
  "mounts": {"passed": 0, "failed": 0, "total": 0, "details": []},
36
36
  "packages": {"passed": 0, "failed": 0, "total": 0, "details": []},
37
- "snap_packages": {"passed": 0, "failed": 0, "total": 0, "details": []},
37
+ "snap_packages": {
38
+ "passed": 0,
39
+ "failed": 0,
40
+ "skipped": 0,
41
+ "total": 0,
42
+ "details": [],
43
+ },
38
44
  "services": {"passed": 0, "failed": 0, "total": 0, "details": []},
39
- "apps": {"passed": 0, "failed": 0, "total": 0, "details": []},
40
- "smoke": {"passed": 0, "failed": 0, "total": 0, "details": []},
45
+ "apps": {"passed": 0, "failed": 0, "skipped": 0, "total": 0, "details": []},
46
+ "smoke": {"passed": 0, "failed": 0, "skipped": 0, "total": 0, "details": []},
41
47
  "overall": "unknown",
42
48
  }
43
49
 
50
+ self._setup_in_progress_cache: Optional[bool] = None
51
+
44
52
  def _exec_in_vm(self, command: str, timeout: int = 10) -> Optional[str]:
45
53
  """Execute command in VM using QEMU guest agent."""
46
54
  try:
@@ -105,14 +113,32 @@ class VMValidator:
105
113
  except Exception:
106
114
  return None
107
115
 
116
+ def _setup_in_progress(self) -> Optional[bool]:
117
+ if self._setup_in_progress_cache is not None:
118
+ return self._setup_in_progress_cache
119
+
120
+ out = self._exec_in_vm(
121
+ "test -f /var/lib/cloud/instance/boot-finished && echo no || echo yes",
122
+ timeout=10,
123
+ )
124
+ if out is None:
125
+ self._setup_in_progress_cache = None
126
+ return None
127
+
128
+ self._setup_in_progress_cache = out.strip() == "yes"
129
+ return self._setup_in_progress_cache
130
+
108
131
  def validate_mounts(self) -> Dict:
109
132
  """Validate all mount points and copied data paths."""
110
133
  self.console.print("\n[bold]💾 Validating Mounts & Data...[/]")
111
134
 
112
135
  paths = self.config.get("paths", {})
113
- app_data_paths = self.config.get("app_data_paths", {})
136
+ # Support both v1 (app_data_paths) and v2 (copy_paths) config formats
137
+ copy_paths = self.config.get("copy_paths", None)
138
+ if not isinstance(copy_paths, dict) or not copy_paths:
139
+ copy_paths = self.config.get("app_data_paths", {})
114
140
 
115
- if not paths and not app_data_paths:
141
+ if not paths and not copy_paths:
116
142
  self.console.print("[dim]No mounts or data paths configured[/]")
117
143
  return self.results["mounts"]
118
144
 
@@ -171,8 +197,8 @@ class VMValidator:
171
197
  "status": status
172
198
  })
173
199
 
174
- # Validate copied paths (app_data_paths)
175
- for host_path, guest_path in app_data_paths.items():
200
+ # Validate copied paths (copy_paths / app_data_paths)
201
+ for host_path, guest_path in copy_paths.items():
176
202
  self.results["mounts"]["total"] += 1
177
203
 
178
204
  # Check if exists and has content
@@ -270,6 +296,8 @@ class VMValidator:
270
296
  snap_table.add_column("Status", justify="center")
271
297
  snap_table.add_column("Version", style="dim")
272
298
 
299
+ setup_in_progress = self._setup_in_progress() is True
300
+
273
301
  for package in snap_packages:
274
302
  self.results["snap_packages"]["total"] += 1
275
303
 
@@ -284,16 +312,29 @@ class VMValidator:
284
312
  {"package": package, "installed": True, "version": version}
285
313
  )
286
314
  else:
287
- snap_table.add_row(package, "[red]❌ Missing[/]", "")
288
- self.results["snap_packages"]["failed"] += 1
289
- self.results["snap_packages"]["details"].append(
290
- {"package": package, "installed": False, "version": None}
291
- )
315
+ if setup_in_progress:
316
+ snap_table.add_row(package, "[yellow]⏳ Pending[/]", "")
317
+ self.results["snap_packages"]["skipped"] += 1
318
+ self.results["snap_packages"]["details"].append(
319
+ {
320
+ "package": package,
321
+ "installed": False,
322
+ "version": None,
323
+ "pending": True,
324
+ }
325
+ )
326
+ else:
327
+ snap_table.add_row(package, "[red]❌ Missing[/]", "")
328
+ self.results["snap_packages"]["failed"] += 1
329
+ self.results["snap_packages"]["details"].append(
330
+ {"package": package, "installed": False, "version": None}
331
+ )
292
332
 
293
333
  self.console.print(snap_table)
294
- self.console.print(
295
- f"[dim]{self.results['snap_packages']['passed']}/{self.results['snap_packages']['total']} snap packages installed[/]"
296
- )
334
+ msg = f"{self.results['snap_packages']['passed']}/{self.results['snap_packages']['total']} snap packages installed"
335
+ if self.results["snap_packages"].get("skipped", 0) > 0:
336
+ msg += f" ({self.results['snap_packages']['skipped']} pending)"
337
+ self.console.print(f"[dim]{msg}[/]")
297
338
 
298
339
  return self.results["snap_packages"]
299
340
 
@@ -410,7 +451,10 @@ class VMValidator:
410
451
  def validate_apps(self) -> Dict:
411
452
  packages = self.config.get("packages", [])
412
453
  snap_packages = self.config.get("snap_packages", [])
413
- app_data_paths = self.config.get("app_data_paths", {})
454
+ # Support both v1 (app_data_paths) and v2 (copy_paths) config formats
455
+ copy_paths = self.config.get("copy_paths", None)
456
+ if not isinstance(copy_paths, dict) or not copy_paths:
457
+ copy_paths = self.config.get("app_data_paths", {})
414
458
  vm_user = self.config.get("vm", {}).get("username", "ubuntu")
415
459
 
416
460
  snap_app_specs = {
@@ -469,7 +513,7 @@ class VMValidator:
469
513
  if snap_pkg in snap_app_specs:
470
514
  expected.append(snap_pkg)
471
515
 
472
- for _, guest_path in app_data_paths.items():
516
+ for _, guest_path in copy_paths.items():
473
517
  if guest_path == "/home/ubuntu/.config/google-chrome":
474
518
  expected.append("google-chrome")
475
519
  break
@@ -588,6 +632,9 @@ class VMValidator:
588
632
  running: Optional[bool] = None
589
633
  pid: Optional[str] = None
590
634
  note = ""
635
+ pending = False
636
+
637
+ setup_in_progress = self._setup_in_progress() is True
591
638
 
592
639
  if app == "firefox":
593
640
  installed = (
@@ -651,6 +698,10 @@ class VMValidator:
651
698
  if self.require_running_apps and installed and profile_ok and running is None:
652
699
  note = note or "running unknown"
653
700
 
701
+ if setup_in_progress and not installed:
702
+ pending = True
703
+ note = note or "setup in progress"
704
+
654
705
  running_icon = (
655
706
  "[dim]—[/]"
656
707
  if not installed
@@ -663,20 +714,18 @@ class VMValidator:
663
714
 
664
715
  pid_value = "—" if not installed else ("?" if pid is None else (pid or "—"))
665
716
 
666
- table.add_row(
667
- app,
668
- "[green]✅[/]" if installed else "[red]❌[/]",
669
- "[green]✅[/]" if profile_ok else "[red]❌[/]",
670
- running_icon,
671
- pid_value,
672
- note,
673
- )
717
+ installed_icon = "[green]✅[/]" if installed else ("[yellow]⏳[/]" if pending else "[red]❌[/]")
718
+ profile_icon = "[green]✅[/]" if profile_ok else ("[yellow]⏳[/]" if pending else "[red]❌[/]")
719
+
720
+ table.add_row(app, installed_icon, profile_icon, running_icon, pid_value, note)
674
721
 
675
722
  should_pass = installed and profile_ok
676
723
  if self.require_running_apps and installed and profile_ok:
677
724
  should_pass = running is True
678
725
 
679
- if should_pass:
726
+ if pending:
727
+ self.results["apps"]["skipped"] += 1
728
+ elif should_pass:
680
729
  self.results["apps"]["passed"] += 1
681
730
  else:
682
731
  self.results["apps"]["failed"] += 1
@@ -689,6 +738,7 @@ class VMValidator:
689
738
  "running": running,
690
739
  "pid": pid,
691
740
  "note": note,
741
+ "pending": pending,
692
742
  }
693
743
  )
694
744
 
@@ -703,7 +753,10 @@ class VMValidator:
703
753
  def validate_smoke_tests(self) -> Dict:
704
754
  packages = self.config.get("packages", [])
705
755
  snap_packages = self.config.get("snap_packages", [])
706
- app_data_paths = self.config.get("app_data_paths", {})
756
+ # Support both v1 (app_data_paths) and v2 (copy_paths) config formats
757
+ copy_paths = self.config.get("copy_paths", None)
758
+ if not isinstance(copy_paths, dict) or not copy_paths:
759
+ copy_paths = self.config.get("app_data_paths", {})
707
760
  vm_user = self.config.get("vm", {}).get("username", "ubuntu")
708
761
 
709
762
  expected = []
@@ -715,7 +768,7 @@ class VMValidator:
715
768
  if snap_pkg in {"pycharm-community", "chromium", "firefox", "code"}:
716
769
  expected.append(snap_pkg)
717
770
 
718
- for _, guest_path in app_data_paths.items():
771
+ for _, guest_path in copy_paths.items():
719
772
  if guest_path == "/home/ubuntu/.config/google-chrome":
720
773
  expected.append("google-chrome")
721
774
  break
@@ -790,7 +843,7 @@ class VMValidator:
790
843
 
791
844
  if app == "firefox":
792
845
  out = self._exec_in_vm(
793
- f"{user_env} timeout 20 firefox --headless --screenshot /tmp/clonebox-firefox.png about:blank >/dev/null 2>&1 && rm -f /tmp/clonebox-firefox.png && echo yes || echo no",
846
+ f"{user_env} timeout 20 firefox --headless --version >/dev/null 2>&1 && echo yes || echo no",
794
847
  timeout=30,
795
848
  )
796
849
  return None if out is None else out.strip() == "yes"
@@ -820,40 +873,51 @@ class VMValidator:
820
873
  table.add_column("Launch", justify="center")
821
874
  table.add_column("Note", style="dim")
822
875
 
876
+ setup_in_progress = self._setup_in_progress() is True
823
877
  for app in expected:
824
878
  self.results["smoke"]["total"] += 1
825
879
  installed = _installed(app)
826
880
  launched: Optional[bool] = None
827
881
  note = ""
882
+ pending = False
828
883
 
829
884
  if installed is True:
830
885
  launched = _run_test(app)
831
886
  if launched is None:
832
887
  note = "test failed to execute"
888
+ elif launched is False and setup_in_progress:
889
+ pending = True
890
+ note = note or "setup in progress"
833
891
  elif installed is False:
834
- note = "not installed"
892
+ if setup_in_progress:
893
+ pending = True
894
+ note = "setup in progress"
895
+ else:
896
+ note = "not installed"
835
897
  else:
836
898
  note = "install status unknown"
837
899
 
838
900
  installed_icon = (
839
901
  "[green]✅[/]"
840
902
  if installed is True
841
- else "[red][/]" if installed is False else "[dim]?[/]"
903
+ else ("[yellow][/]" if pending else "[red][/]")
904
+ if installed is False
905
+ else "[dim]?[/]"
842
906
  )
843
907
  launch_icon = (
844
908
  "[green]✅[/]"
845
909
  if launched is True
846
- else (
847
- "[red]❌[/]"
848
- if launched is False
849
- else ("[dim]—[/]" if installed is not True else "[dim]?[/]")
850
- )
910
+ else ("[yellow]⏳[/]" if pending else "[red]❌[/]")
911
+ if launched is False
912
+ else ("[dim]—[/]" if installed is not True else "[dim]?[/]")
851
913
  )
852
914
 
853
915
  table.add_row(app, installed_icon, launch_icon, note)
854
916
 
855
917
  passed = installed is True and launched is True
856
- if passed:
918
+ if pending:
919
+ self.results["smoke"]["skipped"] += 1
920
+ elif passed:
857
921
  self.results["smoke"]["passed"] += 1
858
922
  else:
859
923
  self.results["smoke"]["failed"] += 1
@@ -864,6 +928,7 @@ class VMValidator:
864
928
  "installed": installed,
865
929
  "launched": launched,
866
930
  "note": note,
931
+ "pending": pending,
867
932
  }
868
933
  )
869
934
 
@@ -994,6 +1059,10 @@ class VMValidator:
994
1059
 
995
1060
  # Get skipped services count
996
1061
  skipped_services = self.results["services"].get("skipped", 0)
1062
+ skipped_snaps = self.results["snap_packages"].get("skipped", 0)
1063
+ skipped_apps = self.results["apps"].get("skipped", 0)
1064
+ skipped_smoke = self.results["smoke"].get("skipped", 0) if self.smoke_test else 0
1065
+ total_skipped = skipped_services + skipped_snaps + skipped_apps + skipped_smoke
997
1066
 
998
1067
  # Print summary
999
1068
  self.console.print("\n[bold]📊 Validation Summary[/]")
@@ -1022,7 +1091,7 @@ class VMValidator:
1022
1091
  "Snap Packages",
1023
1092
  str(self.results["snap_packages"]["passed"]),
1024
1093
  str(self.results["snap_packages"]["failed"]),
1025
- "—",
1094
+ str(skipped_snaps) if skipped_snaps else "—",
1026
1095
  str(self.results["snap_packages"]["total"]),
1027
1096
  )
1028
1097
  summary_table.add_row(
@@ -1036,21 +1105,24 @@ class VMValidator:
1036
1105
  "Apps",
1037
1106
  str(self.results["apps"]["passed"]),
1038
1107
  str(self.results["apps"]["failed"]),
1039
- "—",
1108
+ str(skipped_apps) if skipped_apps else "—",
1040
1109
  str(self.results["apps"]["total"]),
1041
1110
  )
1042
1111
  summary_table.add_row(
1043
1112
  "[bold]TOTAL",
1044
1113
  f"[bold green]{total_passed}",
1045
1114
  f"[bold red]{total_failed}",
1046
- f"[dim]{skipped_services}[/]",
1115
+ f"[dim]{total_skipped}[/]" if total_skipped else "[dim]0[/]",
1047
1116
  f"[bold]{total_checks}",
1048
1117
  )
1049
1118
 
1050
1119
  self.console.print(summary_table)
1051
1120
 
1052
1121
  # Determine overall status
1053
- if total_failed == 0 and total_checks > 0:
1122
+ if total_failed == 0 and total_checks > 0 and total_skipped > 0:
1123
+ self.results["overall"] = "pending"
1124
+ self.console.print("\n[bold yellow]⏳ Setup in progress - some checks are pending[/]")
1125
+ elif total_failed == 0 and total_checks > 0:
1054
1126
  self.results["overall"] = "pass"
1055
1127
  self.console.print("\n[bold green]✅ All validations passed![/]")
1056
1128
  elif total_failed > 0:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: clonebox
3
- Version: 1.1.14
3
+ Version: 1.1.15
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
@@ -49,6 +49,10 @@ Requires-Dist: fastapi>=0.100.0; extra == "test"
49
49
  Provides-Extra: dashboard
50
50
  Requires-Dist: fastapi>=0.100.0; extra == "dashboard"
51
51
  Requires-Dist: uvicorn>=0.22.0; extra == "dashboard"
52
+ Provides-Extra: secrets
53
+ Requires-Dist: hvac>=2.0.0; extra == "secrets"
54
+ Provides-Extra: full
55
+ Requires-Dist: hvac>=2.0.0; extra == "full"
52
56
  Dynamic: license-file
53
57
 
54
58
  # CloneBox 📦
@@ -1,8 +1,8 @@
1
1
  clonebox/__init__.py,sha256=CyfHVVq6KqBr4CNERBpXk_O6Q5B35q03YpdQbokVvvI,408
2
2
  clonebox/__main__.py,sha256=Fcoyzwwyz5-eC_sBlQk5a5RbKx8uodQz5sKJ190U0NU,135
3
- clonebox/audit.py,sha256=67V2ap4QYVXH41b2GmNeRw0-0BilPPTgbDJ0eA5xv2A,14119
4
- clonebox/cli.py,sha256=7QCjprKNRguPVHhxXzd2ej-t61W8kOcxlSMSNxYoXag,153643
5
- clonebox/cloner.py,sha256=Hf4ugJ0lZ52etrnY2RoopwbMaQsPctZp5d1i3_qQKsU,97470
3
+ clonebox/audit.py,sha256=1W9vaIjB0A--_p7CgE3cIP5RNckJG1RxJrL-tOb-QmU,14298
4
+ clonebox/cli.py,sha256=45-0HJ_T8O5Ggrd42VZTU1LoTOgIidRPqST7jzP23YM,174764
5
+ clonebox/cloner.py,sha256=gQeaY_Cu6cKLKh_L0z8yuhB877WWunyKBy3vOzD-IMI,99640
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
@@ -15,11 +15,12 @@ clonebox/monitor.py,sha256=zlIarNf8w_i34XI8hZGxxrg5PVZK_Yxm6FQnkhLavRI,9181
15
15
  clonebox/orchestrator.py,sha256=LvoDZIX47g2pdvMYfCW3NJ5sAiRYKKa45KGlujnJZuI,19871
16
16
  clonebox/p2p.py,sha256=6o0JnscKqF9-BftQhW5fF1W6YY1wXshY9LEklNcHGJc,5913
17
17
  clonebox/profiles.py,sha256=UP37fX_rhrG_O9ehNFJBUcULPmUtN1A8KsJ6cM44oK0,1986
18
+ clonebox/remote.py,sha256=3qd6q3yEDkbO_btOsytkqub2v9lkmsVu_92KcdmHw6U,15336
18
19
  clonebox/resource_monitor.py,sha256=lDR9KyPbVtImeeOkOBPPVP-5yCgoL5hsVFPZ_UqsY0w,5286
19
20
  clonebox/resources.py,sha256=IkuM4OdSDV4qhyc0eIynwbAHBTv0aVSxxW-gghsnCAs,6815
20
21
  clonebox/rollback.py,sha256=hpwO-8Ehe1pW0wHuZvJkC_qxZ6yEo9otCJRhGIUArCo,5711
21
- clonebox/secrets.py,sha256=QtGMIjhTKq14qQRGy-pX0TU3vKLiZt7CMloAlF2GmzQ,10515
22
- clonebox/validator.py,sha256=qKbT5dXka1Pts9GYqSt3ZEqF83gqdR0gFtFwOODWKvg,40918
22
+ clonebox/secrets.py,sha256=l1jwJcEPB1qMoGNLPjyrkKKr1khh9VmftFJI9BWhgK0,10628
23
+ clonebox/validator.py,sha256=_glRPL6VlTCnIpPTtFa8_cVApcl1h0jU_uyHCfKraUM,44501
23
24
  clonebox/backends/libvirt_backend.py,sha256=sIHFIvFO1hIOXEFR_foSkOGBgIzaJVQs-njOU8GdafA,7170
24
25
  clonebox/backends/qemu_disk.py,sha256=YsGjYX5sbEf35Y4yjTpNkZat73a4RGBxY-KTVzJhqIs,1687
25
26
  clonebox/backends/subprocess_runner.py,sha256=c-IyaMxM1cmUu64h654oAvulm83K5Mu-VQxXJ_0BOds,1506
@@ -33,15 +34,15 @@ clonebox/interfaces/network.py,sha256=YPIquxEB7sZHczbpuopcZpffTjWYI6cKmAu3wAEFll
33
34
  clonebox/interfaces/process.py,sha256=njvAIZw_TCjw01KpyVQKIDoRvhTwl0FfVGbQ6mxTROk,1024
34
35
  clonebox/plugins/__init__.py,sha256=3cxlz159nokZCOL2c017WqTwt5z00yyn-o-SemP1g6c,416
35
36
  clonebox/plugins/base.py,sha256=A2H-2vrYUczNZCDioQ8cAtvaSob4YpXutx7FWMjksC4,10133
36
- clonebox/plugins/manager.py,sha256=gotjHloAiiADLJjATOvdBxrasU3Rc8h2fW0MwVGIhCI,14105
37
+ clonebox/plugins/manager.py,sha256=W2ithedEEOh9iWSq3_M5_g2SQWl85aI5qrvrjOKv02I,16842
37
38
  clonebox/snapshots/__init__.py,sha256=ndlrIavPAiA8z4Ep3-D_EPhOcjNKYFnP3rIpEKaGdb8,273
38
39
  clonebox/snapshots/manager.py,sha256=hGzM8V6ZJPXjTqj47c4Kr8idlE-c1Q3gPUvuw1HvS1A,11393
39
40
  clonebox/snapshots/models.py,sha256=sRnn3OZE8JG9FZJlRuA3ihO-JXoPCQ3nD3SQytflAao,6206
40
41
  clonebox/templates/profiles/ml-dev.yaml,sha256=w07MToGh31xtxpjbeXTBk9BkpAN8A3gv8HeA3ESKG9M,461
41
42
  clonebox/templates/profiles/web-stack.yaml,sha256=EBnnGMzML5vAjXmIUbCpbTCwmRaNJiuWd3EcL43DOK8,485
42
- clonebox-1.1.14.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
43
- clonebox-1.1.14.dist-info/METADATA,sha256=MCTVRt4sr9hDpl7N0x_DwR0067cNXqL-Ti1huPFVU60,48916
44
- clonebox-1.1.14.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
45
- clonebox-1.1.14.dist-info/entry_points.txt,sha256=FES95Vi3btfViLEEoHdb8nikNxTqzaooi9ehZw9ZfWI,47
46
- clonebox-1.1.14.dist-info/top_level.txt,sha256=LdMo2cvCrEcRGH2M8JgQNVsCoszLV0xug6kx1JnaRjo,9
47
- clonebox-1.1.14.dist-info/RECORD,,
43
+ clonebox-1.1.15.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
44
+ clonebox-1.1.15.dist-info/METADATA,sha256=4gxhtiBARsY1QcnwjPU705upAAXQPvTXNiXHzkZaGpI,49052
45
+ clonebox-1.1.15.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
46
+ clonebox-1.1.15.dist-info/entry_points.txt,sha256=FES95Vi3btfViLEEoHdb8nikNxTqzaooi9ehZw9ZfWI,47
47
+ clonebox-1.1.15.dist-info/top_level.txt,sha256=LdMo2cvCrEcRGH2M8JgQNVsCoszLV0xug6kx1JnaRjo,9
48
+ clonebox-1.1.15.dist-info/RECORD,,