portacode 1.4.16.dev3__py3-none-any.whl → 1.4.16.dev5__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.
portacode/_version.py CHANGED
@@ -28,7 +28,7 @@ version_tuple: VERSION_TUPLE
28
28
  commit_id: COMMIT_ID
29
29
  __commit_id__: COMMIT_ID
30
30
 
31
- __version__ = version = '1.4.16.dev3'
32
- __version_tuple__ = version_tuple = (1, 4, 16, 'dev3')
31
+ __version__ = version = '1.4.16.dev5'
32
+ __version_tuple__ = version_tuple = (1, 4, 16, 'dev5')
33
33
 
34
34
  __commit_id__ = commit_id = None
@@ -1152,18 +1152,24 @@ Provides system information in response to a `system_info` action. Handled by [`
1152
1152
  * `cpu_share` (number): vCPU-equivalent share requested at creation.
1153
1153
  * `status` (string): Lowercase lifecycle status (e.g., `running`, `stopped`, `deleted`).
1154
1154
  * `created_at` (string|null): ISO timestamp recorded when the CT was provisioned.
1155
- * `managed` (boolean): `true` for Portacode-managed entries.
1156
- * `unmanaged_containers` (array[object]): Facts about containers Portacode did not provision; fields mirror the managed list but are marked `managed=false`.
1157
- * `unmanaged_count` (integer): Number of unmanaged containers detected on the node.
1158
- * `allocated_ram_mib` (integer): Total RAM reserved by both managed and unmanaged containers.
1159
- * `allocated_disk_gib` (integer): Total disk reserved by both managed and unmanaged containers.
1160
- * `allocated_cpu_share` (number): Total CPU shares requested by both managed and unmanaged containers.
1161
- * `available_ram_mib` (integer|null): Remaining RAM after subtracting all reservations from the host total (null when unavailable).
1162
- * `available_disk_gib` (integer|null): Remaining disk GB after subtracting allocations from the host total.
1163
- * `available_cpu_share` (number|null): Remaining CPU shares after allocations.
1164
- * `host_total_ram_mib` (integer|null): Host memory capacity observed via Proxmox.
1165
- * `host_total_disk_gib` (integer|null): Host disk capacity observed via Proxmox.
1166
- * `host_total_cpu_cores` (integer|null): Number of CPU cores reported by Proxmox.
1155
+ * `managed` (boolean): `true` for Portacode-managed entries.
1156
+ * `unmanaged_containers` (array[object]): Facts about containers Portacode did not provision; fields mirror the managed list but are marked `managed=false`.
1157
+ * `unmanaged_count` (integer): Number of unmanaged containers detected on the node.
1158
+ * `allocated_ram_mib` (integer): Total RAM reserved by both managed and unmanaged containers.
1159
+ * `allocated_disk_gib` (integer): Total disk reserved by both managed and unmanaged containers.
1160
+ * `allocated_cpu_share` (number): Total CPU shares requested by both managed and unmanaged containers.
1161
+ * `available_ram_mib` (integer|null): Remaining RAM after subtracting all reservations from the host total (null when unavailable).
1162
+ * `available_disk_gib` (integer|null): Remaining disk GB after subtracting allocations from the host total.
1163
+ * `available_cpu_share` (number|null): Remaining CPU shares after allocations.
1164
+ * `host_total_ram_mib` (integer|null): Host memory capacity observed via Proxmox.
1165
+ * `host_total_disk_gib` (integer|null): Host disk capacity observed via Proxmox.
1166
+ * `host_total_cpu_cores` (integer|null): Number of CPU cores reported by Proxmox.
1167
+ * `default_storage` (string|null): Storage pool name selected during infrastructure configuration.
1168
+ * `default_storage_snapshot` (object|null): Fresh stats for the default storage pool:
1169
+ * `storage` (string): Storage pool identifier.
1170
+ * `total_gib` (integer|null): Capacity of the storage pool.
1171
+ * `avail_gib` (integer|null): Available space remaining.
1172
+ * `used_gib` (integer|null): Space already consumed.
1167
1173
  * `portacode_version` (string): Installed CLI version returned by `portacode.__version__`.
1168
1174
 
1169
1175
  ### `proxmox_infra_configured`
@@ -259,6 +259,19 @@ def _bytes_to_gib(value: Any) -> int:
259
259
  return int(round(_normalize_bytes(value) / (1024**3)))
260
260
 
261
261
 
262
+ def _normalize_storage_name(name: Any) -> str:
263
+ if not name:
264
+ return ""
265
+ return str(name).strip().lower()
266
+
267
+
268
+ def _parse_bool_flag(value: Any) -> bool:
269
+ if isinstance(value, bool):
270
+ return value
271
+ text = str(value or "").strip().lower()
272
+ return text in {"1", "true", "yes", "on"}
273
+
274
+
262
275
  def _parse_iso_timestamp(value: str) -> Optional[datetime]:
263
276
  if not value:
264
277
  return None
@@ -464,6 +477,7 @@ def _build_unmanaged_container_entry(ct: Dict[str, Any], cfg: Dict[str, Any], vm
464
477
  hostname = ct.get("name") or cfg.get("hostname") or f"ct{vmid}"
465
478
  storage = cfg.get("storage") or ct.get("storage")
466
479
  status = (ct.get("status") or "unknown").lower()
480
+ reserved = _parse_bool_flag(cfg.get("onboot"))
467
481
  return {
468
482
  "vmid": vmid,
469
483
  "hostname": hostname,
@@ -472,16 +486,44 @@ def _build_unmanaged_container_entry(ct: Dict[str, Any], cfg: Dict[str, Any], vm
472
486
  "disk_gib": disk_gib,
473
487
  "ram_mib": ram_mib,
474
488
  "cpu_share": cpu_share,
489
+ "reserve_on_boot": reserved,
475
490
  "status": status,
476
491
  "managed": False,
477
492
  }
478
493
 
479
494
 
495
+ def _get_storage_snapshot(proxmox: Any, node: str, storage_name: str | None) -> Dict[str, Any] | None:
496
+ if not storage_name:
497
+ return None
498
+ try:
499
+ storage = proxmox.nodes(node).storage(storage_name).status.get()
500
+ except Exception as exc:
501
+ logger.debug("Unable to read storage status %s:%s: %s", node, storage_name, exc)
502
+ return None
503
+ total_bytes = storage.get("total")
504
+ avail_bytes = storage.get("avail")
505
+ used_bytes = storage.get("used")
506
+ return {
507
+ "storage": storage_name,
508
+ "total_gib": _bytes_to_gib(total_bytes) if total_bytes is not None else None,
509
+ "avail_gib": _bytes_to_gib(avail_bytes) if avail_bytes is not None else None,
510
+ "used_gib": _bytes_to_gib(used_bytes) if used_bytes is not None else None,
511
+ }
512
+
513
+
514
+
515
+ def _storage_matches_default(storage_name: Any, default_storage: str | None) -> bool:
516
+ if not default_storage:
517
+ return True
518
+ return _normalize_storage_name(storage_name) == _normalize_storage_name(default_storage)
519
+
480
520
 
481
521
  def _build_managed_containers_summary(
482
522
  records: List[Dict[str, Any]],
483
523
  unmanaged_records: List[Dict[str, Any]],
484
524
  node_status: Dict[str, Any] | None,
525
+ storage_snapshot: Dict[str, Any] | None,
526
+ default_storage: str | None,
485
527
  ) -> Dict[str, Any]:
486
528
  managed_containers: List[Dict[str, Any]] = []
487
529
  total_ram = 0
@@ -509,18 +551,37 @@ def _build_managed_containers_summary(
509
551
  "created_at": record.get("created_at"),
510
552
  "status": status,
511
553
  "managed": True,
554
+ "uses_default_storage": _storage_matches_default(record.get("storage"), default_storage),
512
555
  }
513
556
  )
514
557
 
515
- unmanaged_total_ram = sum(_to_int(entry.get("ram_mib")) for entry in unmanaged_records)
516
- unmanaged_total_disk = sum(_to_int(entry.get("disk_gib")) for entry in unmanaged_records)
517
- unmanaged_total_cpu = sum(_to_float(entry.get("cpu_share")) for entry in unmanaged_records)
558
+ unmanaged_total_ram = sum(
559
+ _to_int(entry.get("ram_mib"))
560
+ for entry in unmanaged_records
561
+ if entry.get("reserve_on_boot")
562
+ )
563
+ unmanaged_total_disk = sum(
564
+ _to_int(entry.get("disk_gib"))
565
+ for entry in unmanaged_records
566
+ if entry.get("reserve_on_boot")
567
+ and _storage_matches_default(entry.get("storage"), default_storage)
568
+ )
569
+ unmanaged_total_cpu = sum(
570
+ _to_float(entry.get("cpu_share"))
571
+ for entry in unmanaged_records
572
+ if entry.get("reserve_on_boot")
573
+ )
518
574
 
519
575
  allocated_ram = total_ram + unmanaged_total_ram
520
576
  allocated_disk = total_disk + unmanaged_total_disk
521
577
  allocated_cpu = total_cpu_share + unmanaged_total_cpu
522
578
 
523
- host_ram, host_disk, host_cpu = _extract_host_totals(node_status)
579
+ host_ram_node, host_disk_node, host_cpu_node = _extract_host_totals(node_status)
580
+ storage_host_disk = storage_snapshot.get("total_gib") if storage_snapshot else None
581
+ host_ram = host_ram_node
582
+ host_disk = storage_host_disk if storage_host_disk is not None else host_disk_node
583
+ host_cpu = host_cpu_node
584
+
524
585
 
525
586
  return {
526
587
  "updated_at": datetime.utcnow().isoformat() + "Z",
@@ -540,6 +601,8 @@ def _build_managed_containers_summary(
540
601
  "host_total_ram_mib": host_ram,
541
602
  "host_total_disk_gib": host_disk,
542
603
  "host_total_cpu_cores": host_cpu,
604
+ "default_storage": default_storage,
605
+ "default_storage_snapshot": storage_snapshot,
543
606
  }
544
607
 
545
608
 
@@ -548,12 +611,15 @@ def _get_managed_containers_summary(force: bool = False) -> Dict[str, Any]:
548
611
  records: List[Dict[str, Any]],
549
612
  config: Dict[str, Any] | None,
550
613
  managed_vmids: Set[str],
551
- ) -> Tuple[Dict[str, str], List[Dict[str, Any]], Dict[str, Any] | None]:
614
+ default_storage: str | None,
615
+ ) -> Tuple[Dict[str, str], List[Dict[str, Any]], Dict[str, Any] | None, Dict[str, Any] | None]:
552
616
  statuses: Dict[str, str] = {}
553
617
  unmanaged: List[Dict[str, Any]] = []
554
618
  node_status: Dict[str, Any] | None = None
555
619
  if not config:
556
- return statuses, unmanaged, node_status
620
+ return statuses, unmanaged, node_status, None
621
+ proxmox = None
622
+ node = None
557
623
  try:
558
624
  proxmox = _connect_proxmox(config)
559
625
  node = _get_node_from_config(config)
@@ -574,10 +640,15 @@ def _get_managed_containers_summary(force: bool = False) -> Dict[str, Any]:
574
640
  description = (cfg.get("description") or "")
575
641
  if MANAGED_MARKER in description:
576
642
  continue
577
- unmanaged.append(_build_unmanaged_container_entry(ct, cfg, vmid_key))
643
+ unmanaged.append(_build_unmanaged_container_entry(ct, cfg, vmid_key))
578
644
  except Exception as exc: # pragma: no cover - best effort
579
645
  logger.debug("Failed to refresh container statuses: %s", exc)
580
- return statuses, unmanaged, node_status
646
+ storage_snapshot = (
647
+ _get_storage_snapshot(proxmox, node, default_storage)
648
+ if proxmox and node
649
+ else None
650
+ )
651
+ return statuses, unmanaged, node_status, storage_snapshot
581
652
 
582
653
  now = time.monotonic()
583
654
  with _MANAGED_CONTAINERS_CACHE_LOCK:
@@ -590,7 +661,10 @@ def _get_managed_containers_summary(force: bool = False) -> Dict[str, Any]:
590
661
  managed_vmids: Set[str] = {
591
662
  str(_to_int(record.get("vmid"))) for record in records if record.get("vmid") is not None
592
663
  }
593
- statuses, unmanaged, node_status = _refresh_container_statuses(records, config, managed_vmids)
664
+ default_storage = config.get("default_storage") if config else None
665
+ statuses, unmanaged, node_status, storage_snapshot = _refresh_container_statuses(
666
+ records, config, managed_vmids, default_storage
667
+ )
594
668
  for record in records:
595
669
  vmid = record.get("vmid")
596
670
  if vmid is None:
@@ -599,7 +673,7 @@ def _get_managed_containers_summary(force: bool = False) -> Dict[str, Any]:
599
673
  status = statuses.get(vmid_key)
600
674
  if status:
601
675
  record["status"] = status
602
- summary = _build_managed_containers_summary(records, unmanaged, node_status)
676
+ summary = _build_managed_containers_summary(records, unmanaged, node_status, storage_snapshot, default_storage)
603
677
  with _MANAGED_CONTAINERS_CACHE_LOCK:
604
678
  _MANAGED_CONTAINERS_CACHE["timestamp"] = now
605
679
  _MANAGED_CONTAINERS_CACHE["summary"] = summary
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: portacode
3
- Version: 1.4.16.dev3
3
+ Version: 1.4.16.dev5
4
4
  Summary: Portacode CLI client and SDK
5
5
  Home-page: https://github.com/portacode/portacode
6
6
  Author: Meena Erian
@@ -1,7 +1,7 @@
1
1
  portacode/README.md,sha256=4dKtpvR8LNgZPVz37GmkQCMWIr_u25Ao63iW56s7Ke4,775
2
2
  portacode/__init__.py,sha256=oB3sV1wXr-um-RXio73UG8E5Xx6cF2ZVJveqjNmC-vQ,1086
3
3
  portacode/__main__.py,sha256=jmHTGC1hzmo9iKJLv-SSYe9BSIbPPZ2IOpecI03PlTs,296
4
- portacode/_version.py,sha256=HLH9O1BtoKVO_ZLCnGRXJ_xMQe43OyaD07QzQgNnelQ,719
4
+ portacode/_version.py,sha256=7ZUO3SboTD37oV4BJWi-Sx9y4MGlZpUHiLsekZex9ds,719
5
5
  portacode/cli.py,sha256=mGLKoZ-T2FBF7IA9wUq0zyG0X9__-A1ao7gajjcVRH8,21828
6
6
  portacode/data.py,sha256=5-s291bv8J354myaHm1Y7CQZTZyRzMU3TGe5U4hb-FA,1591
7
7
  portacode/keypair.py,sha256=0OO4vHDcF1XMxCDqce61xFTlFwlTcmqe5HyGsXFEt7s,5838
@@ -14,7 +14,7 @@ portacode/connection/client.py,sha256=jtLb9_YufqPkzi9t8VQH3iz_JEMisbtY6a8L9U5wei
14
14
  portacode/connection/multiplex.py,sha256=L-TxqJ_ZEbfNEfu1cwxgJ5vUdyRzZjsMy2Kx1diiZys,5237
15
15
  portacode/connection/terminal.py,sha256=n1Uu92JacV5K6d1Qwx94Tw9OB2Tpke5HqsW2NDn76Ls,49032
16
16
  portacode/connection/handlers/README.md,sha256=HsLZG1QK1JNm67HsgL6WoDg9nxzKXxwkc5fJPFJdX5g,12169
17
- portacode/connection/handlers/WEBSOCKET_PROTOCOL.md,sha256=U7zkUpwscWf5aqimNIP3uYXvg-t9S0MhyaZ3ygF-ADk,102593
17
+ portacode/connection/handlers/WEBSOCKET_PROTOCOL.md,sha256=K7ZjHIWwscX0nBLinpEclO2TGSAiZPBr_JMJUGrn0To,103095
18
18
  portacode/connection/handlers/__init__.py,sha256=WSeBmi65GWFQPYt9M3E10rn0uZ_EPCJzNJOzSf2HZyw,2921
19
19
  portacode/connection/handlers/base.py,sha256=oENFb-Fcfzwk99Qx8gJQriEMiwSxwygwjOiuCH36hM4,10231
20
20
  portacode/connection/handlers/chunked_content.py,sha256=h6hXRmxSeOgnIxoU8CkmvEf2Odv-ajPrpHIe_W3GKcA,9251
@@ -22,7 +22,7 @@ portacode/connection/handlers/diff_handlers.py,sha256=iYTIRCcpEQ03vIPKZCsMTE5aZb
22
22
  portacode/connection/handlers/file_handlers.py,sha256=nAJH8nXnX07xxD28ngLpgIUzcTuRwZBNpEGEKdRqohw,39507
23
23
  portacode/connection/handlers/project_aware_file_handlers.py,sha256=AqgMnDqX2893T2NsrvUSCwjN5VKj4Pb2TN0S_SuboOE,9803
24
24
  portacode/connection/handlers/project_state_handlers.py,sha256=v6ZefGW9i7n1aZLq2jOGumJIjYb6aHlPI4m1jkYewm8,1686
25
- portacode/connection/handlers/proxmox_infra.py,sha256=IG8qp607grx4v9jSWL5TawUrIN0VElIR2swjA_rpn9U,78132
25
+ portacode/connection/handlers/proxmox_infra.py,sha256=Z7C0ekRkTB7g4oeye2pE6K5EYdIg6QF6OAnmq1dBLBQ,80780
26
26
  portacode/connection/handlers/registry.py,sha256=qXGE60sYEWg6ZtVQzFcZ5YI2XWR6lMgw4hAL9x5qR1I,6181
27
27
  portacode/connection/handlers/session.py,sha256=uNGfiO_1B9-_yjJKkpvmbiJhIl6b-UXlT86UTfd6WYE,42219
28
28
  portacode/connection/handlers/system_handlers.py,sha256=fr12QpOr_Z8KYGUU-AYrTQwRPAcrLK85hvj3SEq1Kw8,14757
@@ -65,7 +65,7 @@ portacode/utils/__init__.py,sha256=NgBlWTuNJESfIYJzP_3adI1yJQJR0XJLRpSdVNaBAN0,3
65
65
  portacode/utils/diff_apply.py,sha256=4Oi7ft3VUCKmiUE4VM-OeqO7Gk6H7PF3WnN4WHXtjxI,15157
66
66
  portacode/utils/diff_renderer.py,sha256=S76StnQ2DLfsz4Gg0m07UwPfRp8270PuzbNaQq-rmYk,13850
67
67
  portacode/utils/ntp_clock.py,sha256=VqCnWCTehCufE43W23oB-WUdAZGeCcLxkmIOPwInYHc,2499
68
- portacode-1.4.16.dev3.dist-info/licenses/LICENSE,sha256=2FGbCnUDgRYuQTkB1O1dUUpu5CVAjK1j4_p6ack9Z54,1066
68
+ portacode-1.4.16.dev5.dist-info/licenses/LICENSE,sha256=2FGbCnUDgRYuQTkB1O1dUUpu5CVAjK1j4_p6ack9Z54,1066
69
69
  test_modules/README.md,sha256=Do_agkm9WhSzueXjRAkV_xEj6Emy5zB3N3VKY5Roce8,9274
70
70
  test_modules/__init__.py,sha256=1LcbHodIHsB0g-g4NGjSn6AMuCoGbymvXPYLOb6Z7F0,53
71
71
  test_modules/test_device_online.py,sha256=QtYq0Dq9vME8Gp2O4fGSheqVf8LUtpsSKosXXk56gGM,1654
@@ -91,8 +91,8 @@ testing_framework/core/playwright_manager.py,sha256=Tw46qwxIhOFkS48C2IWIQHHNpEe-
91
91
  testing_framework/core/runner.py,sha256=j2QwNJmAxVBmJvcbVS7DgPJUKPNzqfLmt_4NNdaKmZU,19297
92
92
  testing_framework/core/shared_cli_manager.py,sha256=BESSNtyQb7BOlaOvZmm04T8Uezjms4KCBs2MzTxvzYQ,8790
93
93
  testing_framework/core/test_discovery.py,sha256=2FZ9fJ8Dp5dloA-fkgXoJ_gCMC_nYPBnA3Hs2xlagzM,4928
94
- portacode-1.4.16.dev3.dist-info/METADATA,sha256=ulkuZ2NkbFWXncNI8ZPHvWD2ThvRJ3-rFMYBNuFHpG8,13051
95
- portacode-1.4.16.dev3.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
96
- portacode-1.4.16.dev3.dist-info/entry_points.txt,sha256=lLUUL-BM6_wwe44Xv0__5NQ1BnAz6jWjSMFvZdWW3zU,48
97
- portacode-1.4.16.dev3.dist-info/top_level.txt,sha256=TGhTYUxfW8SyVZc_zGgzjzc24gGT7nSw8Qf73liVRKM,41
98
- portacode-1.4.16.dev3.dist-info/RECORD,,
94
+ portacode-1.4.16.dev5.dist-info/METADATA,sha256=qKD5PeIEODD6P9-ODtKSE5q5Tqg1UCk0e7N3xt_Ws-4,13051
95
+ portacode-1.4.16.dev5.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
96
+ portacode-1.4.16.dev5.dist-info/entry_points.txt,sha256=lLUUL-BM6_wwe44Xv0__5NQ1BnAz6jWjSMFvZdWW3zU,48
97
+ portacode-1.4.16.dev5.dist-info/top_level.txt,sha256=TGhTYUxfW8SyVZc_zGgzjzc24gGT7nSw8Qf73liVRKM,41
98
+ portacode-1.4.16.dev5.dist-info/RECORD,,