portacode 1.4.16.dev5__py3-none-any.whl → 1.4.16.dev7__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.dev5'
32
- __version_tuple__ = version_tuple = (1, 4, 16, 'dev5')
31
+ __version__ = version = '1.4.16.dev7'
32
+ __version_tuple__ = version_tuple = (1, 4, 16, 'dev7')
33
33
 
34
34
  __commit_id__ = commit_id = None
@@ -1152,8 +1152,11 @@ 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`.
1155
+ * `managed` (boolean): `true` for Portacode-managed entries.
1156
+ * `matches_default_storage` (boolean): `true` when this container is backed by the default storage pool used for new Portacode containers.
1157
+ * `type` (string): Either `lxc` or `qemu`, indicating whether we enumerated the container from the LXC or QEMU APIs.
1158
+ * `unmanaged_containers` (array[object]): Facts about containers Portacode did not provision; fields mirror the managed list but are marked `managed=false`.
1159
+ * `reserve_on_boot` (boolean): `true` when the CT is configured to start at boot; this flag is used to decide if its RAM and CPU allocations count toward the available totals.
1157
1160
  * `unmanaged_count` (integer): Number of unmanaged containers detected on the node.
1158
1161
  * `allocated_ram_mib` (integer): Total RAM reserved by both managed and unmanaged containers.
1159
1162
  * `allocated_disk_gib` (integer): Total disk reserved by both managed and unmanaged containers.
@@ -464,9 +464,43 @@ def _extract_host_totals(node_status: Dict[str, Any] | None) -> Tuple[Optional[i
464
464
  return host_ram, host_disk, host_cpu
465
465
 
466
466
 
467
- def _build_unmanaged_container_entry(ct: Dict[str, Any], cfg: Dict[str, Any], vmid: str) -> Dict[str, Any]:
467
+ def _parse_disk_reference(value: Any) -> Tuple[str | None, int]:
468
+ if not value:
469
+ return None, 0
470
+ text = str(value)
471
+ primary = text.split(",", 1)[0]
472
+ if ":" not in primary:
473
+ return primary.strip() or None, 0
474
+ storage_name, size_part = primary.split(":", 1)
475
+ storage_name = storage_name.strip() or None
476
+ size_text = size_part.strip()
477
+ if not size_text:
478
+ return storage_name, 0
479
+ unit = size_text[-1].upper()
480
+ number = size_text[:-1] if unit in {"G", "M"} else size_text
481
+ try:
482
+ value_num = float(number)
483
+ except ValueError:
484
+ return storage_name, 0
485
+ if unit == "M":
486
+ gib = value_num / 1024.0
487
+ else:
488
+ gib = value_num
489
+ return storage_name, int(round(gib))
490
+
491
+
492
+ def _build_unmanaged_container_entry(
493
+ ct: Dict[str, Any],
494
+ cfg: Dict[str, Any],
495
+ vmid: str,
496
+ default_storage: str | None,
497
+ *,
498
+ disk_gib_override: int | None = None,
499
+ storage_override: str | None = None,
500
+ entry_type: str = "lxc",
501
+ ) -> Dict[str, Any]:
468
502
  ram_mib = _to_int(cfg.get("memory")) or _bytes_to_mib(ct.get("maxmem"))
469
- disk_gib = _bytes_to_gib(ct.get("maxdisk"))
503
+ disk_gib = disk_gib_override if disk_gib_override is not None else _bytes_to_gib(ct.get("maxdisk"))
470
504
  cpu_share = _to_float(
471
505
  cfg.get("cpulimit")
472
506
  or cfg.get("cpus")
@@ -475,9 +509,10 @@ def _build_unmanaged_container_entry(ct: Dict[str, Any], cfg: Dict[str, Any], vm
475
509
  or ct.get("cpu")
476
510
  )
477
511
  hostname = ct.get("name") or cfg.get("hostname") or f"ct{vmid}"
478
- storage = cfg.get("storage") or ct.get("storage")
512
+ storage = storage_override or cfg.get("storage") or ct.get("storage")
479
513
  status = (ct.get("status") or "unknown").lower()
480
514
  reserved = _parse_bool_flag(cfg.get("onboot"))
515
+ storage_matches_default = _storage_matches_default(storage, default_storage)
481
516
  return {
482
517
  "vmid": vmid,
483
518
  "hostname": hostname,
@@ -487,6 +522,8 @@ def _build_unmanaged_container_entry(ct: Dict[str, Any], cfg: Dict[str, Any], vm
487
522
  "ram_mib": ram_mib,
488
523
  "cpu_share": cpu_share,
489
524
  "reserve_on_boot": reserved,
525
+ "matches_default_storage": storage_matches_default,
526
+ "type": entry_type,
490
527
  "status": status,
491
528
  "managed": False,
492
529
  }
@@ -511,6 +548,26 @@ def _get_storage_snapshot(proxmox: Any, node: str, storage_name: str | None) ->
511
548
  }
512
549
 
513
550
 
551
+ def _extract_qemu_disk_info(cfg: Dict[str, Any]) -> Tuple[str | None, int]:
552
+ disk_keys = sorted(
553
+ key
554
+ for key in cfg.keys()
555
+ if (
556
+ key.startswith("ide")
557
+ or key.startswith("sata")
558
+ or key.startswith("scsi")
559
+ or key.startswith("virtio")
560
+ or key.startswith("efidisk")
561
+ or key.startswith("raid")
562
+ )
563
+ and cfg.get(key)
564
+ )
565
+ for key in disk_keys:
566
+ storage_name, size_gib = _parse_disk_reference(cfg.get(key))
567
+ if storage_name or size_gib:
568
+ return storage_name, size_gib
569
+ return None, 0
570
+
514
571
 
515
572
  def _storage_matches_default(storage_name: Any, default_storage: str | None) -> bool:
516
573
  if not default_storage:
@@ -551,7 +608,7 @@ def _build_managed_containers_summary(
551
608
  "created_at": record.get("created_at"),
552
609
  "status": status,
553
610
  "managed": True,
554
- "uses_default_storage": _storage_matches_default(record.get("storage"), default_storage),
611
+ "matches_default_storage": _storage_matches_default(record.get("storage"), default_storage),
555
612
  }
556
613
  )
557
614
 
@@ -563,8 +620,7 @@ def _build_managed_containers_summary(
563
620
  unmanaged_total_disk = sum(
564
621
  _to_int(entry.get("disk_gib"))
565
622
  for entry in unmanaged_records
566
- if entry.get("reserve_on_boot")
567
- and _storage_matches_default(entry.get("storage"), default_storage)
623
+ if entry.get("matches_default_storage")
568
624
  )
569
625
  unmanaged_total_cpu = sum(
570
626
  _to_float(entry.get("cpu_share"))
@@ -640,7 +696,37 @@ def _get_managed_containers_summary(force: bool = False) -> Dict[str, Any]:
640
696
  description = (cfg.get("description") or "")
641
697
  if MANAGED_MARKER in description:
642
698
  continue
643
- unmanaged.append(_build_unmanaged_container_entry(ct, cfg, vmid_key))
699
+ unmanaged.append(
700
+ _build_unmanaged_container_entry(ct, cfg, vmid_key, default_storage, entry_type="lxc")
701
+ )
702
+ for vm in proxmox.nodes(node).qemu.get():
703
+ vmid_val = vm.get("vmid")
704
+ if vmid_val is None:
705
+ continue
706
+ vmid_key = str(_to_int(vmid_val))
707
+ statuses[vmid_key] = (vm.get("status") or "unknown").lower()
708
+ if vmid_key in managed_vmids:
709
+ continue
710
+ cfg: Dict[str, Any] = {}
711
+ try:
712
+ cfg = proxmox.nodes(node).qemu(vmid_key).config.get() or {}
713
+ except Exception as exc:
714
+ logger.debug("Failed to read config for VM %s: %s", vmid_key, exc)
715
+ description = (cfg.get("description") or "")
716
+ if MANAGED_MARKER in description:
717
+ continue
718
+ storage_name, disk_gib_override = _extract_qemu_disk_info(cfg)
719
+ unmanaged.append(
720
+ _build_unmanaged_container_entry(
721
+ vm,
722
+ cfg,
723
+ vmid_key,
724
+ default_storage,
725
+ disk_gib_override=disk_gib_override,
726
+ storage_override=storage_name,
727
+ entry_type="qemu",
728
+ )
729
+ )
644
730
  except Exception as exc: # pragma: no cover - best effort
645
731
  logger.debug("Failed to refresh container statuses: %s", exc)
646
732
  storage_snapshot = (
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: portacode
3
- Version: 1.4.16.dev5
3
+ Version: 1.4.16.dev7
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=7ZUO3SboTD37oV4BJWi-Sx9y4MGlZpUHiLsekZex9ds,719
4
+ portacode/_version.py,sha256=PjQCzQ14vbQAHflnIvm-t7BX8uT-4bUn2o-KdxjdxKU,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=K7ZjHIWwscX0nBLinpEclO2TGSAiZPBr_JMJUGrn0To,103095
17
+ portacode/connection/handlers/WEBSOCKET_PROTOCOL.md,sha256=R9trHDWDuADIY2Xs2LrpIw1himtxE-NDb_6CzNVNVcM,103625
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=Z7C0ekRkTB7g4oeye2pE6K5EYdIg6QF6OAnmq1dBLBQ,80780
25
+ portacode/connection/handlers/proxmox_infra.py,sha256=tWwoJeHjZaEw3_lF0VUCibN9ZfUjZM9cIQcOFA13108,83840
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.dev5.dist-info/licenses/LICENSE,sha256=2FGbCnUDgRYuQTkB1O1dUUpu5CVAjK1j4_p6ack9Z54,1066
68
+ portacode-1.4.16.dev7.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.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,,
94
+ portacode-1.4.16.dev7.dist-info/METADATA,sha256=IlkUQF4Bly5OO3F51SZ5jaNilKIGAxoV-wX9vIvRkKI,13051
95
+ portacode-1.4.16.dev7.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
96
+ portacode-1.4.16.dev7.dist-info/entry_points.txt,sha256=lLUUL-BM6_wwe44Xv0__5NQ1BnAz6jWjSMFvZdWW3zU,48
97
+ portacode-1.4.16.dev7.dist-info/top_level.txt,sha256=TGhTYUxfW8SyVZc_zGgzjzc24gGT7nSw8Qf73liVRKM,41
98
+ portacode-1.4.16.dev7.dist-info/RECORD,,