portacode 1.4.16.dev5__py3-none-any.whl → 1.4.16.dev8__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.dev8'
32
+ __version_tuple__ = version_tuple = (1, 4, 16, 'dev8')
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.
@@ -259,6 +259,17 @@ def _bytes_to_gib(value: Any) -> int:
259
259
  return int(round(_normalize_bytes(value) / (1024**3)))
260
260
 
261
261
 
262
+ def _extract_storage(value: Any) -> str:
263
+ if not value:
264
+ return "unknown"
265
+ text = str(value).strip()
266
+ if not text:
267
+ return "unknown"
268
+ primary = text.split(",", 1)[0]
269
+ storage = primary.split(":", 1)[0].strip()
270
+ return storage or "unknown"
271
+
272
+
262
273
  def _normalize_storage_name(name: Any) -> str:
263
274
  if not name:
264
275
  return ""
@@ -464,9 +475,44 @@ def _extract_host_totals(node_status: Dict[str, Any] | None) -> Tuple[Optional[i
464
475
  return host_ram, host_disk, host_cpu
465
476
 
466
477
 
467
- def _build_unmanaged_container_entry(ct: Dict[str, Any], cfg: Dict[str, Any], vmid: str) -> Dict[str, Any]:
478
+ def _parse_disk_reference(value: Any) -> Tuple[str | None, int]:
479
+ if not value:
480
+ return None, 0
481
+ text = str(value)
482
+ primary = text.split(",", 1)[0]
483
+ if ":" not in primary:
484
+ return primary.strip() or None, 0
485
+ storage_name, size_part = primary.split(":", 1)
486
+ storage_name = storage_name.strip() or None
487
+ size_text = size_part.strip()
488
+ if not size_text:
489
+ return storage_name, 0
490
+ unit = size_text[-1].upper()
491
+ number = size_text[:-1] if unit in {"G", "M"} else size_text
492
+ try:
493
+ value_num = float(number)
494
+ except ValueError:
495
+ return storage_name, 0
496
+ if unit == "M":
497
+ gib = value_num / 1024.0
498
+ else:
499
+ gib = value_num
500
+ return storage_name, int(round(gib))
501
+
502
+
503
+ def _build_unmanaged_container_entry(
504
+ ct: Dict[str, Any],
505
+ cfg: Dict[str, Any],
506
+ vmid: str,
507
+ default_storage: str | None,
508
+ *,
509
+ disk_gib_override: int | None = None,
510
+ storage_override: str | None = None,
511
+ entry_type: str = "lxc",
512
+ ) -> Dict[str, Any]:
468
513
  ram_mib = _to_int(cfg.get("memory")) or _bytes_to_mib(ct.get("maxmem"))
469
- disk_gib = _bytes_to_gib(ct.get("maxdisk"))
514
+ disk_source = cfg.get("disk") or ct.get("disk") or ct.get("maxdisk")
515
+ disk_gib = disk_gib_override if disk_gib_override is not None else _bytes_to_gib(disk_source)
470
516
  cpu_share = _to_float(
471
517
  cfg.get("cpulimit")
472
518
  or cfg.get("cpus")
@@ -475,9 +521,13 @@ def _build_unmanaged_container_entry(ct: Dict[str, Any], cfg: Dict[str, Any], vm
475
521
  or ct.get("cpu")
476
522
  )
477
523
  hostname = ct.get("name") or cfg.get("hostname") or f"ct{vmid}"
478
- storage = cfg.get("storage") or ct.get("storage")
524
+ storage_source = (
525
+ storage_override or cfg.get("storage") or ct.get("storage") or ct.get("rootfs")
526
+ )
527
+ storage = _extract_storage(storage_source)
479
528
  status = (ct.get("status") or "unknown").lower()
480
529
  reserved = _parse_bool_flag(cfg.get("onboot"))
530
+ storage_matches_default = _storage_matches_default(storage, default_storage)
481
531
  return {
482
532
  "vmid": vmid,
483
533
  "hostname": hostname,
@@ -487,6 +537,8 @@ def _build_unmanaged_container_entry(ct: Dict[str, Any], cfg: Dict[str, Any], vm
487
537
  "ram_mib": ram_mib,
488
538
  "cpu_share": cpu_share,
489
539
  "reserve_on_boot": reserved,
540
+ "matches_default_storage": storage_matches_default,
541
+ "type": entry_type,
490
542
  "status": status,
491
543
  "managed": False,
492
544
  }
@@ -511,11 +563,33 @@ def _get_storage_snapshot(proxmox: Any, node: str, storage_name: str | None) ->
511
563
  }
512
564
 
513
565
 
566
+ def _extract_qemu_disk_info(cfg: Dict[str, Any]) -> Tuple[str | None, int]:
567
+ disk_keys = sorted(
568
+ key
569
+ for key in cfg.keys()
570
+ if (
571
+ key.startswith("ide")
572
+ or key.startswith("sata")
573
+ or key.startswith("scsi")
574
+ or key.startswith("virtio")
575
+ or key.startswith("efidisk")
576
+ or key.startswith("raid")
577
+ )
578
+ and cfg.get(key)
579
+ )
580
+ for key in disk_keys:
581
+ storage_name, size_gib = _parse_disk_reference(cfg.get(key))
582
+ if storage_name or size_gib:
583
+ return storage_name, size_gib
584
+ return None, 0
585
+
514
586
 
515
587
  def _storage_matches_default(storage_name: Any, default_storage: str | None) -> bool:
516
588
  if not default_storage:
517
589
  return True
518
- return _normalize_storage_name(storage_name) == _normalize_storage_name(default_storage)
590
+ normalized_storage = _normalize_storage_name(_extract_storage(storage_name))
591
+ normalized_default = _normalize_storage_name(_extract_storage(default_storage))
592
+ return normalized_storage == normalized_default
519
593
 
520
594
 
521
595
  def _build_managed_containers_summary(
@@ -551,7 +625,7 @@ def _build_managed_containers_summary(
551
625
  "created_at": record.get("created_at"),
552
626
  "status": status,
553
627
  "managed": True,
554
- "uses_default_storage": _storage_matches_default(record.get("storage"), default_storage),
628
+ "matches_default_storage": _storage_matches_default(record.get("storage"), default_storage),
555
629
  }
556
630
  )
557
631
 
@@ -563,8 +637,7 @@ def _build_managed_containers_summary(
563
637
  unmanaged_total_disk = sum(
564
638
  _to_int(entry.get("disk_gib"))
565
639
  for entry in unmanaged_records
566
- if entry.get("reserve_on_boot")
567
- and _storage_matches_default(entry.get("storage"), default_storage)
640
+ if entry.get("matches_default_storage")
568
641
  )
569
642
  unmanaged_total_cpu = sum(
570
643
  _to_float(entry.get("cpu_share"))
@@ -640,7 +713,37 @@ def _get_managed_containers_summary(force: bool = False) -> Dict[str, Any]:
640
713
  description = (cfg.get("description") or "")
641
714
  if MANAGED_MARKER in description:
642
715
  continue
643
- unmanaged.append(_build_unmanaged_container_entry(ct, cfg, vmid_key))
716
+ unmanaged.append(
717
+ _build_unmanaged_container_entry(ct, cfg, vmid_key, default_storage, entry_type="lxc")
718
+ )
719
+ for vm in proxmox.nodes(node).qemu.get():
720
+ vmid_val = vm.get("vmid")
721
+ if vmid_val is None:
722
+ continue
723
+ vmid_key = str(_to_int(vmid_val))
724
+ statuses[vmid_key] = (vm.get("status") or "unknown").lower()
725
+ if vmid_key in managed_vmids:
726
+ continue
727
+ cfg: Dict[str, Any] = {}
728
+ try:
729
+ cfg = proxmox.nodes(node).qemu(vmid_key).config.get() or {}
730
+ except Exception as exc:
731
+ logger.debug("Failed to read config for VM %s: %s", vmid_key, exc)
732
+ description = (cfg.get("description") or "")
733
+ if MANAGED_MARKER in description:
734
+ continue
735
+ storage_name, disk_gib_override = _extract_qemu_disk_info(cfg)
736
+ unmanaged.append(
737
+ _build_unmanaged_container_entry(
738
+ vm,
739
+ cfg,
740
+ vmid_key,
741
+ default_storage,
742
+ disk_gib_override=disk_gib_override,
743
+ storage_override=storage_name,
744
+ entry_type="qemu",
745
+ )
746
+ )
644
747
  except Exception as exc: # pragma: no cover - best effort
645
748
  logger.debug("Failed to refresh container statuses: %s", exc)
646
749
  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.dev8
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=VGaY-LcOZhWVvp6-lz1Mul_hv8BD77OfZQu3NhYJfE8,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=CDrPWs-WLVMd95AKRukeUwZkW4J0kcWh3Vb3oLNNDCU,84394
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.dev8.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.dev8.dist-info/METADATA,sha256=oXg32kxrBFgBp8Che47OV2D20d5zz0Vdyc517TAdzCU,13051
95
+ portacode-1.4.16.dev8.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
96
+ portacode-1.4.16.dev8.dist-info/entry_points.txt,sha256=lLUUL-BM6_wwe44Xv0__5NQ1BnAz6jWjSMFvZdWW3zU,48
97
+ portacode-1.4.16.dev8.dist-info/top_level.txt,sha256=TGhTYUxfW8SyVZc_zGgzjzc24gGT7nSw8Qf73liVRKM,41
98
+ portacode-1.4.16.dev8.dist-info/RECORD,,