portacode 1.4.16__py3-none-any.whl → 1.4.16.dev0__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'
32
- __version_tuple__ = version_tuple = (1, 4, 16)
31
+ __version__ = version = '1.4.16.dev0'
32
+ __version_tuple__ = version_tuple = (1, 4, 16, 'dev0')
33
33
 
34
34
  __commit_id__ = commit_id = None
@@ -381,7 +381,6 @@ Starts a previously provisioned, Portacode-managed LXC container. Handled by [`S
381
381
  **Payload Fields:**
382
382
 
383
383
  * `ctid` (string, required): Identifier of the container to start.
384
- * `child_device_id` (string, required): Dashboard `Device.id` of the container that triggered the request; the handler validates the CT belongs to that device before issuing the start.
385
384
 
386
385
  **Responses:**
387
386
 
@@ -395,7 +394,6 @@ Stops a running Portacode-managed container. Handled by [`StopProxmoxContainerHa
395
394
  **Payload Fields:**
396
395
 
397
396
  * `ctid` (string, required): Identifier of the container to stop.
398
- * `child_device_id` (string, required): Dashboard `Device.id` that owns the container; the handler rejects the request if the CT is mapped to another device.
399
397
 
400
398
  **Responses:**
401
399
 
@@ -409,7 +407,6 @@ Deletes a managed container from Proxmox (stopping it first if necessary) and re
409
407
  **Payload Fields:**
410
408
 
411
409
  * `ctid` (string, required): Identifier of the container to delete.
412
- * `child_device_id` (string, required): Dashboard `Device.id` that should own the container metadata being purged.
413
410
 
414
411
  **Responses:**
415
412
 
@@ -1136,43 +1133,22 @@ Provides system information in response to a `system_info` action. Handled by [`
1136
1133
  * `bridge` (string): The bridge interface configured (typically `vmbr1`).
1137
1134
  * `health` (string|null): `"healthy"` when the connectivity verification succeeded.
1138
1135
  * `node_status` (object|null): Status response returned by the Proxmox API when validating the token.
1139
- * `managed_containers` (object): Cached summary of the Portacode-managed containers:
1140
- * `updated_at` (string): ISO timestamp when this snapshot was last refreshed.
1141
- * `count` (integer): Number of managed containers.
1142
- * `total_ram_mib` (integer): RAM footprint summed across all containers.
1143
- * `total_disk_gib` (integer): Disk footprint summed across all containers.
1144
- * `total_cpu_share` (number): CPU shares requested across all containers.
1145
- * `containers` (array[object]): Container summaries with the following fields:
1146
- * `vmid` (string|null): Numeric CT ID.
1147
- * `hostname` (string|null): Hostname configured in the CT.
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
- * `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.
1160
- * `unmanaged_count` (integer): Number of unmanaged containers detected on the node.
1161
- * `allocated_ram_mib` (integer): Total RAM reserved by both managed and unmanaged containers.
1162
- * `allocated_disk_gib` (integer): Total disk reserved by both managed and unmanaged containers.
1163
- * `allocated_cpu_share` (number): Total CPU shares requested by both managed and unmanaged containers.
1164
- * `available_ram_mib` (integer|null): Remaining RAM after subtracting all reservations from the host total (null when unavailable).
1165
- * `available_disk_gib` (integer|null): Remaining disk GB after subtracting allocations from the host total.
1166
- * `available_cpu_share` (number|null): Remaining CPU shares after allocations.
1167
- * `host_total_ram_mib` (integer|null): Host memory capacity observed via Proxmox.
1168
- * `host_total_disk_gib` (integer|null): Host disk capacity observed via Proxmox.
1169
- * `host_total_cpu_cores` (integer|null): Number of CPU cores reported by Proxmox.
1170
- * `default_storage` (string|null): Storage pool name selected during infrastructure configuration.
1171
- * `default_storage_snapshot` (object|null): Fresh stats for the default storage pool:
1172
- * `storage` (string): Storage pool identifier.
1173
- * `total_gib` (integer|null): Capacity of the storage pool.
1174
- * `avail_gib` (integer|null): Available space remaining.
1175
- * `used_gib` (integer|null): Space already consumed.
1136
+ * `managed_containers` (object): Cached summary of the Portacode-managed containers:
1137
+ * `updated_at` (string): ISO timestamp when this snapshot was last refreshed.
1138
+ * `count` (integer): Number of managed containers.
1139
+ * `total_ram_mib` (integer): RAM footprint summed across all containers.
1140
+ * `total_disk_gib` (integer): Disk footprint summed across all containers.
1141
+ * `total_cpu_share` (number): CPU shares requested across all containers.
1142
+ * `containers` (array[object]): Container summaries with the following fields:
1143
+ * `vmid` (string|null): Numeric CT ID.
1144
+ * `hostname` (string|null): Hostname configured in the CT.
1145
+ * `template` (string|null): Template identifier used.
1146
+ * `storage` (string|null): Storage pool backing the rootfs.
1147
+ * `disk_gib` (integer): Rootfs size in GiB.
1148
+ * `ram_mib` (integer): Memory size in MiB.
1149
+ * `cpu_share` (number): vCPU-equivalent share requested at creation.
1150
+ * `status` (string): Lowercase lifecycle status (e.g., `running`, `stopped`, `deleted`).
1151
+ * `created_at` (string|null): ISO timestamp recorded when the CT was provisioned.
1176
1152
  * `portacode_version` (string): Installed CLI version returned by `portacode.__version__`.
1177
1153
 
1178
1154
  ### `proxmox_infra_configured`
@@ -7,7 +7,6 @@ import json
7
7
  import logging
8
8
  import math
9
9
  import os
10
- import re
11
10
  import secrets
12
11
  import shlex
13
12
  import shutil
@@ -249,217 +248,6 @@ def _pick_storage(storages: Iterable[Dict[str, Any]]) -> str:
249
248
  return candidates[0].get("storage", "")
250
249
 
251
250
 
252
- def _bytes_to_gib(value: Any) -> float:
253
- try:
254
- return float(value) / 1024**3
255
- except (TypeError, ValueError):
256
- return 0.0
257
-
258
-
259
- def _bytes_to_mib(value: Any) -> float:
260
- try:
261
- return float(value) / 1024**2
262
- except (TypeError, ValueError):
263
- return 0.0
264
-
265
-
266
- def _size_token_to_gib(token: str) -> float:
267
- match = re.match(r"^\s*([0-9]+(?:\.[0-9]+)?)\s*([KMGTP])?([iI]?[bB])?\s*$", token)
268
- if not match:
269
- return 0.0
270
- number = float(match.group(1))
271
- unit = (match.group(2) or "").upper()
272
- scale = {
273
- "": 1,
274
- "K": 1024**1,
275
- "M": 1024**2,
276
- "G": 1024**3,
277
- "T": 1024**4,
278
- "P": 1024**5,
279
- }.get(unit, 1)
280
- return (number * scale) / 1024**3
281
-
282
-
283
- def _extract_size_gib(value: Any) -> float:
284
- if not value:
285
- return 0.0
286
- text = str(value)
287
- for part in text.split(","):
288
- if "size=" in part:
289
- token = part.split("=", 1)[1]
290
- return _size_token_to_gib(token)
291
- return _size_token_to_gib(text)
292
-
293
-
294
- def _extract_storage_token(value: Any) -> str:
295
- if not value:
296
- return "unknown"
297
- text = str(value)
298
- if ":" in text:
299
- return text.split(":", 1)[0].strip() or "unknown"
300
- return text.strip() or "unknown"
301
-
302
-
303
- def _storage_from_lxc(cfg: Dict[str, Any], entry: Dict[str, Any]) -> str:
304
- rootfs = cfg.get("rootfs") or entry.get("rootfs")
305
- storage = _extract_storage_token(rootfs)
306
- if storage != "unknown":
307
- return storage
308
- for idx in range(0, 10):
309
- mp_value = cfg.get(f"mp{idx}")
310
- storage = _extract_storage_token(mp_value)
311
- if storage != "unknown":
312
- return storage
313
- return "unknown"
314
-
315
-
316
- def _storage_from_qemu(cfg: Dict[str, Any]) -> str:
317
- preferred_keys: List[str] = []
318
- for prefix in ("scsi", "virtio", "sata", "ide"):
319
- preferred_keys.extend(f"{prefix}{idx}" for idx in range(0, 6))
320
- seen = set()
321
- for key in preferred_keys:
322
- value = cfg.get(key)
323
- if value is None:
324
- continue
325
- seen.add(key)
326
- text = str(value)
327
- if "media=cdrom" in text or "cloudinit" in text:
328
- continue
329
- storage = _extract_storage_token(text)
330
- if storage != "unknown":
331
- return storage
332
- for key in sorted(cfg.keys()):
333
- if key in seen:
334
- continue
335
- if not any(key.startswith(prefix) for prefix in ("scsi", "virtio", "sata", "ide")):
336
- continue
337
- value = cfg.get(key)
338
- if value is None:
339
- continue
340
- text = str(value)
341
- if "media=cdrom" in text or "cloudinit" in text:
342
- continue
343
- storage = _extract_storage_token(text)
344
- if storage != "unknown":
345
- return storage
346
- for key in ("efidisk0", "tpmstate0"):
347
- storage = _extract_storage_token(cfg.get(key))
348
- if storage != "unknown":
349
- return storage
350
- return "unknown"
351
-
352
-
353
- def _primary_lxc_disk(cfg: Dict[str, Any], entry: Dict[str, Any]) -> str:
354
- return str(cfg.get("rootfs") or entry.get("rootfs") or "")
355
-
356
-
357
- def _primary_qemu_disk(cfg: Dict[str, Any]) -> str:
358
- preferred_keys: List[str] = []
359
- for prefix in ("scsi", "virtio", "sata", "ide"):
360
- preferred_keys.extend(f"{prefix}{idx}" for idx in range(0, 6))
361
- seen = set()
362
- for key in preferred_keys:
363
- value = cfg.get(key)
364
- if value is None:
365
- continue
366
- seen.add(key)
367
- text = str(value)
368
- if "media=cdrom" in text or "cloudinit" in text:
369
- continue
370
- return text
371
- for key in sorted(cfg.keys()):
372
- if key in seen:
373
- continue
374
- if not any(key.startswith(prefix) for prefix in ("scsi", "virtio", "sata", "ide")):
375
- continue
376
- value = cfg.get(key)
377
- if value is None:
378
- continue
379
- text = str(value)
380
- if "media=cdrom" in text or "cloudinit" in text:
381
- continue
382
- return text
383
- return ""
384
-
385
-
386
- def _pick_container_storage(kind: str, cfg: Dict[str, Any], entry: Dict[str, Any]) -> str:
387
- storage = _extract_storage_token(cfg.get("storage") or entry.get("storage"))
388
- if storage != "unknown":
389
- return storage
390
- if kind == "lxc":
391
- return _storage_from_lxc(cfg, entry)
392
- return _storage_from_qemu(cfg)
393
-
394
-
395
- def _pick_container_disk_gib(kind: str, cfg: Dict[str, Any], entry: Dict[str, Any]) -> float:
396
- if kind == "lxc":
397
- size = _extract_size_gib(_primary_lxc_disk(cfg, entry))
398
- if size:
399
- return size
400
- else:
401
- size = _extract_size_gib(_primary_qemu_disk(cfg))
402
- if size:
403
- return size
404
- for candidate in (entry.get("maxdisk"), entry.get("disk"), cfg.get("disk")):
405
- if candidate is None or candidate == 0:
406
- continue
407
- return _bytes_to_gib(candidate)
408
- return 0.0
409
-
410
-
411
- def _to_mib(value: Any) -> float:
412
- try:
413
- val = float(value)
414
- except (TypeError, ValueError):
415
- return 0.0
416
- if val <= 0:
417
- return 0.0
418
- # Heuristic: large values are bytes, smaller ones are already MiB.
419
- return _bytes_to_mib(val) if val > 10000 else val
420
-
421
-
422
- def _pick_container_ram_mib(kind: str, cfg: Dict[str, Any], entry: Dict[str, Any]) -> float:
423
- for candidate in (cfg.get("memory"), entry.get("maxmem"), entry.get("mem")):
424
- ram = _to_mib(candidate)
425
- if ram:
426
- return ram
427
- return 0.0
428
-
429
-
430
- def _safe_float(value: Any) -> float:
431
- try:
432
- return float(value)
433
- except (TypeError, ValueError):
434
- return 0.0
435
-
436
-
437
- def _pick_container_cpu_share(kind: str, cfg: Dict[str, Any], entry: Dict[str, Any]) -> float:
438
- if kind == "lxc":
439
- for key in ("cpulimit", "cores", "cpus"):
440
- val = _safe_float(cfg.get(key))
441
- if val:
442
- return val
443
- return _safe_float(entry.get("cpus"))
444
-
445
- cores = _safe_float(cfg.get("cores"))
446
- sockets = _safe_float(cfg.get("sockets")) or 1.0
447
- if cores:
448
- return cores * sockets
449
- val = _safe_float(cfg.get("vcpus"))
450
- if val:
451
- return val
452
- val = _safe_float(entry.get("cpus") or entry.get("maxcpu"))
453
- if val:
454
- return val
455
- return 0.0
456
-
457
-
458
- def _parse_onboot_flag(value: Any) -> bool:
459
- text = str(value).strip().lower()
460
- return text in {"1", "true", "yes", "on"}
461
-
462
-
463
251
  def _write_bridge_config(bridge: str) -> None:
464
252
  begin = f"# Portacode INFRA BEGIN {bridge}"
465
253
  end = f"# Portacode INFRA END {bridge}"
@@ -644,139 +432,6 @@ def _build_managed_containers_summary(records: List[Dict[str, Any]]) -> Dict[str
644
432
  }
645
433
 
646
434
 
647
- def _build_full_container_summary(records: List[Dict[str, Any]], config: Dict[str, Any]) -> Dict[str, Any]:
648
- base_summary = _build_managed_containers_summary(records)
649
- if not config or not config.get("token_value"):
650
- return base_summary
651
-
652
- try:
653
- proxmox = _connect_proxmox(config)
654
- node = _get_node_from_config(config)
655
- except Exception as exc: # pragma: no cover - best effort
656
- logger.debug("Unable to extend container summary with Proxmox data: %s", exc)
657
- return base_summary
658
-
659
- default_storage = (config.get("default_storage") or "").strip()
660
- record_map: Dict[str, Dict[str, Any]] = {}
661
- for record in records:
662
- vmid = record.get("vmid")
663
- if vmid is None:
664
- continue
665
- try:
666
- vmid_key = str(int(vmid))
667
- except (ValueError, TypeError):
668
- continue
669
- record_map[vmid_key] = record
670
-
671
- managed_entries: List[Dict[str, Any]] = []
672
- unmanaged_entries: List[Dict[str, Any]] = []
673
- allocated_ram = 0.0
674
- allocated_disk = 0.0
675
- allocated_cpu = 0.0
676
-
677
- def _process_entries(kind: str, getter: str) -> None:
678
- nonlocal allocated_ram, allocated_disk, allocated_cpu
679
- entries = getattr(proxmox.nodes(node), getter).get()
680
- for entry in entries:
681
- vmid = entry.get("vmid")
682
- if vmid is None:
683
- continue
684
- vmid_str = str(vmid)
685
- cfg: Dict[str, Any] = {}
686
- try:
687
- cfg = getattr(proxmox.nodes(node), getter)(vmid_str).config.get() or {}
688
- except Exception as exc: # pragma: no cover - best effort
689
- logger.debug("Failed to load %s config for %s: %s", kind, vmid_str, exc)
690
- cfg = {}
691
-
692
- record = record_map.get(vmid_str)
693
- description = cfg.get("description") or ""
694
- managed = bool(record) or MANAGED_MARKER in description
695
- hostname = entry.get("name") or cfg.get("hostname") or (record.get("hostname") if record else None)
696
- storage = _pick_container_storage(kind, cfg, entry)
697
- disk_gib = _pick_container_disk_gib(kind, cfg, entry)
698
- ram_mib = _pick_container_ram_mib(kind, cfg, entry)
699
- cpu_share = _pick_container_cpu_share(kind, cfg, entry)
700
- reserve_on_boot = _parse_onboot_flag(cfg.get("onboot"))
701
- matches_default_storage = bool(default_storage and storage and storage.lower() == default_storage.lower())
702
-
703
- base_entry = {
704
- "type": kind,
705
- "vmid": vmid_str,
706
- "hostname": hostname,
707
- "status": (entry.get("status") or "unknown").lower(),
708
- "storage": storage,
709
- "disk_gib": disk_gib,
710
- "ram_mib": ram_mib,
711
- "cpu_share": cpu_share,
712
- "reserve_on_boot": reserve_on_boot,
713
- "matches_default_storage": matches_default_storage,
714
- "managed": managed,
715
- }
716
-
717
- if managed:
718
- merged = base_entry | {
719
- "device_id": record.get("device_id") if record else None,
720
- "template": record.get("template") if record else None,
721
- "created_at": record.get("created_at") if record else None,
722
- }
723
- managed_entries.append(merged)
724
- else:
725
- unmanaged_entries.append(base_entry)
726
-
727
- if managed or reserve_on_boot:
728
- allocated_ram += ram_mib
729
- allocated_cpu += cpu_share
730
- if managed or matches_default_storage:
731
- allocated_disk += disk_gib
732
-
733
- _process_entries("lxc", "lxc")
734
- _process_entries("qemu", "qemu")
735
-
736
- memory_info = {}
737
- cpu_info = {}
738
- try:
739
- node_status = proxmox.nodes(node).status.get()
740
- memory_info = node_status.get("memory") or {}
741
- cpu_info = node_status.get("cpuinfo") or {}
742
- except Exception as exc: # pragma: no cover - best effort
743
- logger.debug("Unable to read node status for resource totals: %s", exc)
744
-
745
- host_total_ram_mib = _bytes_to_mib(memory_info.get("total"))
746
- used_ram_mib = _bytes_to_mib(memory_info.get("used"))
747
- available_ram_mib = max(host_total_ram_mib - used_ram_mib, 0.0) if host_total_ram_mib else None
748
- host_total_cpu_cores = _safe_float(cpu_info.get("cores"))
749
- available_cpu_share = max(host_total_cpu_cores - allocated_cpu, 0.0) if host_total_cpu_cores else None
750
-
751
- host_total_disk_gib = None
752
- available_disk_gib = None
753
- if default_storage:
754
- try:
755
- storage_status = proxmox.nodes(node).storage(default_storage).status.get()
756
- host_total_disk_gib = _bytes_to_gib(storage_status.get("total"))
757
- available_disk_gib = _bytes_to_gib(storage_status.get("avail"))
758
- except Exception as exc: # pragma: no cover - best effort
759
- logger.debug("Unable to read storage status for %s: %s", default_storage, exc)
760
-
761
- summary = base_summary.copy()
762
- summary["containers"] = managed_entries
763
- summary["count"] = len(managed_entries)
764
- summary["total_ram_mib"] = int(sum(entry.get("ram_mib") or 0 for entry in managed_entries))
765
- summary["total_disk_gib"] = int(sum(entry.get("disk_gib") or 0 for entry in managed_entries))
766
- summary["total_cpu_share"] = round(sum(entry.get("cpu_share") or 0 for entry in managed_entries), 2)
767
- summary["unmanaged_containers"] = unmanaged_entries
768
- summary["allocated_ram_mib"] = round(allocated_ram, 2)
769
- summary["allocated_disk_gib"] = round(allocated_disk, 2)
770
- summary["allocated_cpu_share"] = round(allocated_cpu, 2)
771
- summary["host_total_ram_mib"] = int(host_total_ram_mib) if host_total_ram_mib else None
772
- summary["host_total_disk_gib"] = host_total_disk_gib
773
- summary["host_total_cpu_cores"] = host_total_cpu_cores if host_total_cpu_cores else None
774
- summary["available_ram_mib"] = int(available_ram_mib) if available_ram_mib is not None else None
775
- summary["available_disk_gib"] = available_disk_gib
776
- summary["available_cpu_share"] = available_cpu_share if available_cpu_share is not None else None
777
- return summary
778
-
779
-
780
435
  def _get_managed_containers_summary(force: bool = False) -> Dict[str, Any]:
781
436
  def _refresh_container_statuses(records: List[Dict[str, Any]], config: Dict[str, Any] | None) -> None:
782
437
  if not records or not config:
@@ -812,7 +467,7 @@ def _get_managed_containers_summary(force: bool = False) -> Dict[str, Any]:
812
467
  config = _load_config()
813
468
  records = _load_managed_container_records()
814
469
  _refresh_container_statuses(records, config)
815
- summary = _build_full_container_summary(records, config)
470
+ summary = _build_managed_containers_summary(records)
816
471
  with _MANAGED_CONTAINERS_CACHE_LOCK:
817
472
  _MANAGED_CONTAINERS_CACHE["timestamp"] = now
818
473
  _MANAGED_CONTAINERS_CACHE["summary"] = summary
@@ -2186,13 +1841,13 @@ class StartProxmoxContainerHandler(SyncHandler):
2186
1841
 
2187
1842
  def execute(self, message: Dict[str, Any]) -> Dict[str, Any]:
2188
1843
  vmid = _parse_ctid(message)
2189
- child_device_id = (message.get("child_device_id") or "").strip()
2190
- if not child_device_id:
2191
- raise ValueError("child_device_id is required for start_proxmox_container")
1844
+ on_behalf_of_device = (message.get("on_behalf_of_device") or "").strip()
1845
+ if not on_behalf_of_device:
1846
+ raise ValueError("on_behalf_of_device is required for start_proxmox_container")
2192
1847
  config = _ensure_infra_configured()
2193
1848
  proxmox = _connect_proxmox(config)
2194
1849
  node = _get_node_from_config(config)
2195
- _ensure_container_managed(proxmox, node, vmid, device_id=child_device_id)
1850
+ _ensure_container_managed(proxmox, node, vmid, device_id=on_behalf_of_device)
2196
1851
 
2197
1852
  status, elapsed = _start_container(proxmox, node, vmid)
2198
1853
  _update_container_record(vmid, {"status": "running"})
@@ -2207,6 +1862,7 @@ class StartProxmoxContainerHandler(SyncHandler):
2207
1862
  "details": {"exitstatus": status.get("exitstatus")},
2208
1863
  "status": status.get("status"),
2209
1864
  "infra": infra,
1865
+ "on_behalf_of_device": on_behalf_of_device,
2210
1866
  }
2211
1867
 
2212
1868
 
@@ -2219,13 +1875,13 @@ class StopProxmoxContainerHandler(SyncHandler):
2219
1875
 
2220
1876
  def execute(self, message: Dict[str, Any]) -> Dict[str, Any]:
2221
1877
  vmid = _parse_ctid(message)
2222
- child_device_id = (message.get("child_device_id") or "").strip()
2223
- if not child_device_id:
2224
- raise ValueError("child_device_id is required for stop_proxmox_container")
1878
+ on_behalf_of_device = (message.get("on_behalf_of_device") or "").strip()
1879
+ if not on_behalf_of_device:
1880
+ raise ValueError("on_behalf_of_device is required for stop_proxmox_container")
2225
1881
  config = _ensure_infra_configured()
2226
1882
  proxmox = _connect_proxmox(config)
2227
1883
  node = _get_node_from_config(config)
2228
- _ensure_container_managed(proxmox, node, vmid, device_id=child_device_id)
1884
+ _ensure_container_managed(proxmox, node, vmid, device_id=on_behalf_of_device)
2229
1885
 
2230
1886
  status, elapsed = _stop_container(proxmox, node, vmid)
2231
1887
  final_status = status.get("status") or "stopped"
@@ -2246,6 +1902,7 @@ class StopProxmoxContainerHandler(SyncHandler):
2246
1902
  "details": {"exitstatus": status.get("exitstatus")},
2247
1903
  "status": final_status,
2248
1904
  "infra": infra,
1905
+ "on_behalf_of_device": on_behalf_of_device,
2249
1906
  }
2250
1907
 
2251
1908
 
@@ -2258,13 +1915,13 @@ class RemoveProxmoxContainerHandler(SyncHandler):
2258
1915
 
2259
1916
  def execute(self, message: Dict[str, Any]) -> Dict[str, Any]:
2260
1917
  vmid = _parse_ctid(message)
2261
- child_device_id = (message.get("child_device_id") or "").strip()
2262
- if not child_device_id:
2263
- raise ValueError("child_device_id is required for remove_proxmox_container")
1918
+ on_behalf_of_device = (message.get("on_behalf_of_device") or "").strip()
1919
+ if not on_behalf_of_device:
1920
+ raise ValueError("on_behalf_of_device is required for remove_proxmox_container")
2264
1921
  config = _ensure_infra_configured()
2265
1922
  proxmox = _connect_proxmox(config)
2266
1923
  node = _get_node_from_config(config)
2267
- _ensure_container_managed(proxmox, node, vmid, device_id=child_device_id)
1924
+ _ensure_container_managed(proxmox, node, vmid, device_id=on_behalf_of_device)
2268
1925
 
2269
1926
  stop_status, stop_elapsed = _stop_container(proxmox, node, vmid)
2270
1927
  delete_status, delete_elapsed = _delete_container(proxmox, node, vmid)
@@ -2276,6 +1933,7 @@ class RemoveProxmoxContainerHandler(SyncHandler):
2276
1933
  "action": "remove",
2277
1934
  "success": True,
2278
1935
  "ctid": str(vmid),
1936
+ "on_behalf_of_device": on_behalf_of_device,
2279
1937
  "message": f"Deleted container {vmid} in {delete_elapsed:.1f}s.",
2280
1938
  "details": {
2281
1939
  "stop_exitstatus": stop_status.get("exitstatus"),
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: portacode
3
- Version: 1.4.16
3
+ Version: 1.4.16.dev0
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=SOLiyPSuNdvHQItH6SptPrTi2eK8Z95hVWBI2QSJqps,706
4
+ portacode/_version.py,sha256=VRcygEdkbj_kT_PkjvrdxuvM8kPIsg1rBzzADtCIQiA,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=R9trHDWDuADIY2Xs2LrpIw1himtxE-NDb_6CzNVNVcM,103625
17
+ portacode/connection/handlers/WEBSOCKET_PROTOCOL.md,sha256=OtObmoVAXlf0uU1HidTWNmyJYBS1Yl6Rpgyh6TOTjUQ,100590
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=-zAmaOTCTJaic8sclb5Z0DVCl_pU7XePo86NKohi3gc,85295
25
+ portacode/connection/handlers/proxmox_infra.py,sha256=Pe-GX5IjQhjyBPxm0hNNLoKm7NZPmOsRCPRdEIkmjhE,73134
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.dist-info/licenses/LICENSE,sha256=2FGbCnUDgRYuQTkB1O1dUUpu5CVAjK1j4_p6ack9Z54,1066
68
+ portacode-1.4.16.dev0.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.dist-info/METADATA,sha256=jlqsUgKvktqtfer6jJZnBlA7WXMLXdQCRMG6FmNyHJY,13046
95
- portacode-1.4.16.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
96
- portacode-1.4.16.dist-info/entry_points.txt,sha256=lLUUL-BM6_wwe44Xv0__5NQ1BnAz6jWjSMFvZdWW3zU,48
97
- portacode-1.4.16.dist-info/top_level.txt,sha256=TGhTYUxfW8SyVZc_zGgzjzc24gGT7nSw8Qf73liVRKM,41
98
- portacode-1.4.16.dist-info/RECORD,,
94
+ portacode-1.4.16.dev0.dist-info/METADATA,sha256=UbwKMn3EpfoXPgYPALM8B5GwR0TluW6buRsbfQZhvlc,13051
95
+ portacode-1.4.16.dev0.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
96
+ portacode-1.4.16.dev0.dist-info/entry_points.txt,sha256=lLUUL-BM6_wwe44Xv0__5NQ1BnAz6jWjSMFvZdWW3zU,48
97
+ portacode-1.4.16.dev0.dist-info/top_level.txt,sha256=TGhTYUxfW8SyVZc_zGgzjzc24gGT7nSw8Qf73liVRKM,41
98
+ portacode-1.4.16.dev0.dist-info/RECORD,,