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 +2 -2
- portacode/connection/handlers/WEBSOCKET_PROTOCOL.md +5 -2
- portacode/connection/handlers/proxmox_infra.py +111 -8
- {portacode-1.4.16.dev5.dist-info → portacode-1.4.16.dev8.dist-info}/METADATA +1 -1
- {portacode-1.4.16.dev5.dist-info → portacode-1.4.16.dev8.dist-info}/RECORD +9 -9
- {portacode-1.4.16.dev5.dist-info → portacode-1.4.16.dev8.dist-info}/WHEEL +0 -0
- {portacode-1.4.16.dev5.dist-info → portacode-1.4.16.dev8.dist-info}/entry_points.txt +0 -0
- {portacode-1.4.16.dev5.dist-info → portacode-1.4.16.dev8.dist-info}/licenses/LICENSE +0 -0
- {portacode-1.4.16.dev5.dist-info → portacode-1.4.16.dev8.dist-info}/top_level.txt +0 -0
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.
|
|
32
|
-
__version_tuple__ = version_tuple = (1, 4, 16, '
|
|
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
|
-
|
|
1156
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
"
|
|
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("
|
|
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
|
-
|
|
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,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=
|
|
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=
|
|
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=
|
|
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.
|
|
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.
|
|
95
|
-
portacode-1.4.16.
|
|
96
|
-
portacode-1.4.16.
|
|
97
|
-
portacode-1.4.16.
|
|
98
|
-
portacode-1.4.16.
|
|
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,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|