portacode 1.4.16.dev6__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.dev6'
32
- __version_tuple__ = version_tuple = (1, 4, 16, 'dev6')
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
@@ -1146,16 +1146,17 @@ Provides system information in response to a `system_info` action. Handled by [`
1146
1146
  * `vmid` (string|null): Numeric CT ID.
1147
1147
  * `hostname` (string|null): Hostname configured in the CT.
1148
1148
  * `template` (string|null): Template identifier used.
1149
- * `storage` (string|null): Storage pool backing the rootfs.
1150
- * `disk_gib` (integer): Rootfs size in GiB.
1151
- * `ram_mib` (integer): Memory size in MiB.
1152
- * `cpu_share` (number): vCPU-equivalent share requested at creation.
1153
- * `status` (string): Lowercase lifecycle status (e.g., `running`, `stopped`, `deleted`).
1154
- * `created_at` (string|null): ISO timestamp recorded when the CT was provisioned.
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
- * `unmanaged_containers` (array[object]): Facts about containers Portacode did not provision; fields mirror the managed list but are marked `managed=false`.
1158
- * `reserve_on_boot` (boolean): `true` when the CT is configured to start at boot; used to determine whether its RAM/CPU allocations count toward the available totals.
1149
+ * `storage` (string|null): Storage pool backing the rootfs.
1150
+ * `disk_gib` (integer): Rootfs size in GiB.
1151
+ * `ram_mib` (integer): Memory size in MiB.
1152
+ * `cpu_share` (number): vCPU-equivalent share requested at creation.
1153
+ * `status` (string): Lowercase lifecycle status (e.g., `running`, `stopped`, `deleted`).
1154
+ * `created_at` (string|null): ISO timestamp recorded when the CT was provisioned.
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.
1159
1160
  * `unmanaged_count` (integer): Number of unmanaged containers detected on the node.
1160
1161
  * `allocated_ram_mib` (integer): Total RAM reserved by both managed and unmanaged containers.
1161
1162
  * `allocated_disk_gib` (integer): Total disk reserved by both managed and unmanaged containers.
@@ -464,11 +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 _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
+
467
492
  def _build_unmanaged_container_entry(
468
- ct: Dict[str, Any], cfg: Dict[str, Any], vmid: str, default_storage: str | None
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",
469
501
  ) -> Dict[str, Any]:
470
502
  ram_mib = _to_int(cfg.get("memory")) or _bytes_to_mib(ct.get("maxmem"))
471
- 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"))
472
504
  cpu_share = _to_float(
473
505
  cfg.get("cpulimit")
474
506
  or cfg.get("cpus")
@@ -477,7 +509,7 @@ def _build_unmanaged_container_entry(
477
509
  or ct.get("cpu")
478
510
  )
479
511
  hostname = ct.get("name") or cfg.get("hostname") or f"ct{vmid}"
480
- storage = cfg.get("storage") or ct.get("storage")
512
+ storage = storage_override or cfg.get("storage") or ct.get("storage")
481
513
  status = (ct.get("status") or "unknown").lower()
482
514
  reserved = _parse_bool_flag(cfg.get("onboot"))
483
515
  storage_matches_default = _storage_matches_default(storage, default_storage)
@@ -491,6 +523,7 @@ def _build_unmanaged_container_entry(
491
523
  "cpu_share": cpu_share,
492
524
  "reserve_on_boot": reserved,
493
525
  "matches_default_storage": storage_matches_default,
526
+ "type": entry_type,
494
527
  "status": status,
495
528
  "managed": False,
496
529
  }
@@ -515,6 +548,26 @@ def _get_storage_snapshot(proxmox: Any, node: str, storage_name: str | None) ->
515
548
  }
516
549
 
517
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
+
518
571
 
519
572
  def _storage_matches_default(storage_name: Any, default_storage: str | None) -> bool:
520
573
  if not default_storage:
@@ -567,8 +620,7 @@ def _build_managed_containers_summary(
567
620
  unmanaged_total_disk = sum(
568
621
  _to_int(entry.get("disk_gib"))
569
622
  for entry in unmanaged_records
570
- if entry.get("reserve_on_boot")
571
- and _storage_matches_default(entry.get("storage"), default_storage)
623
+ if entry.get("matches_default_storage")
572
624
  )
573
625
  unmanaged_total_cpu = sum(
574
626
  _to_float(entry.get("cpu_share"))
@@ -644,7 +696,37 @@ def _get_managed_containers_summary(force: bool = False) -> Dict[str, Any]:
644
696
  description = (cfg.get("description") or "")
645
697
  if MANAGED_MARKER in description:
646
698
  continue
647
- unmanaged.append(_build_unmanaged_container_entry(ct, cfg, vmid_key, default_storage))
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
+ )
648
730
  except Exception as exc: # pragma: no cover - best effort
649
731
  logger.debug("Failed to refresh container statuses: %s", exc)
650
732
  storage_snapshot = (
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: portacode
3
- Version: 1.4.16.dev6
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=O9VlXFqDVkjJnRmi5_IA8WvEPtVCF1ERWU-GYQROiAs,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=SceRNVQpexRO8msuvnu1DORRXZQz2eV32EfakcVhTE4,103429
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=4CQlf_r91UZT7vzU83IrZdXKLyc3Kzo5NnMShsvyCvw,80980
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.dev6.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.dev6.dist-info/METADATA,sha256=GciLY83XHuLI65RMOWElbOik_3bd7SuosoFpJjnZTtA,13051
95
- portacode-1.4.16.dev6.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
96
- portacode-1.4.16.dev6.dist-info/entry_points.txt,sha256=lLUUL-BM6_wwe44Xv0__5NQ1BnAz6jWjSMFvZdWW3zU,48
97
- portacode-1.4.16.dev6.dist-info/top_level.txt,sha256=TGhTYUxfW8SyVZc_zGgzjzc24gGT7nSw8Qf73liVRKM,41
98
- portacode-1.4.16.dev6.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,,