portacode 1.4.15.dev6__py3-none-any.whl → 1.4.15.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/proxmox_infra.py +76 -5
- {portacode-1.4.15.dev6.dist-info → portacode-1.4.15.dev8.dist-info}/METADATA +1 -1
- {portacode-1.4.15.dev6.dist-info → portacode-1.4.15.dev8.dist-info}/RECORD +8 -8
- {portacode-1.4.15.dev6.dist-info → portacode-1.4.15.dev8.dist-info}/WHEEL +1 -1
- {portacode-1.4.15.dev6.dist-info → portacode-1.4.15.dev8.dist-info}/entry_points.txt +0 -0
- {portacode-1.4.15.dev6.dist-info → portacode-1.4.15.dev8.dist-info}/licenses/LICENSE +0 -0
- {portacode-1.4.15.dev6.dist-info → portacode-1.4.15.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.15.
|
|
32
|
-
__version_tuple__ = version_tuple = (1, 4, 15, '
|
|
31
|
+
__version__ = version = '1.4.15.dev8'
|
|
32
|
+
__version_tuple__ = version_tuple = (1, 4, 15, 'dev8')
|
|
33
33
|
|
|
34
34
|
__commit_id__ = commit_id = None
|
|
@@ -5,6 +5,7 @@ from __future__ import annotations
|
|
|
5
5
|
import asyncio
|
|
6
6
|
import json
|
|
7
7
|
import logging
|
|
8
|
+
import math
|
|
8
9
|
import os
|
|
9
10
|
import secrets
|
|
10
11
|
import shlex
|
|
@@ -46,6 +47,9 @@ UNIT_DIR = Path("/etc/systemd/system")
|
|
|
46
47
|
_MANAGED_CONTAINERS_CACHE_TTL_S = 30.0
|
|
47
48
|
_MANAGED_CONTAINERS_CACHE: Dict[str, Any] = {"timestamp": 0.0, "summary": None}
|
|
48
49
|
_MANAGED_CONTAINERS_CACHE_LOCK = threading.Lock()
|
|
50
|
+
_STARTUP_TEMPLATES: List[str] | None = None
|
|
51
|
+
_STARTUP_TEMPLATE_KEY: tuple[Any, ...] | None = None
|
|
52
|
+
_STARTUP_TEMPLATES_LOCK = threading.Lock()
|
|
49
53
|
|
|
50
54
|
ProgressCallback = Callable[[int, int, Dict[str, Any], str, Optional[Dict[str, Any]]], None]
|
|
51
55
|
|
|
@@ -178,6 +182,70 @@ def _list_templates(client: Any, node: str, storages: Iterable[Dict[str, Any]])
|
|
|
178
182
|
return templates
|
|
179
183
|
|
|
180
184
|
|
|
185
|
+
def _template_key(config: Dict[str, Any]) -> tuple[Any, ...]:
|
|
186
|
+
return (
|
|
187
|
+
config.get("host"),
|
|
188
|
+
config.get("node"),
|
|
189
|
+
config.get("user"),
|
|
190
|
+
config.get("token_name"),
|
|
191
|
+
config.get("verify_ssl"),
|
|
192
|
+
)
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
def _build_proxmox_client_from_config(config: Dict[str, Any]):
|
|
196
|
+
user = config.get("user")
|
|
197
|
+
token_name = config.get("token_name")
|
|
198
|
+
token_value = config.get("token_value")
|
|
199
|
+
if not user or not token_name or not token_value:
|
|
200
|
+
raise RuntimeError("Proxmox API credentials are missing")
|
|
201
|
+
ProxmoxAPI = _ensure_proxmoxer()
|
|
202
|
+
return ProxmoxAPI(
|
|
203
|
+
config.get("host", DEFAULT_HOST),
|
|
204
|
+
user=user,
|
|
205
|
+
token_name=token_name,
|
|
206
|
+
token_value=token_value,
|
|
207
|
+
verify_ssl=config.get("verify_ssl", False),
|
|
208
|
+
timeout=30,
|
|
209
|
+
)
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
def _ensure_startup_templates(config: Dict[str, Any]) -> None:
|
|
213
|
+
global _STARTUP_TEMPLATES, _STARTUP_TEMPLATE_KEY
|
|
214
|
+
if not config:
|
|
215
|
+
with _STARTUP_TEMPLATES_LOCK:
|
|
216
|
+
_STARTUP_TEMPLATES = []
|
|
217
|
+
_STARTUP_TEMPLATE_KEY = None
|
|
218
|
+
return
|
|
219
|
+
with _STARTUP_TEMPLATES_LOCK:
|
|
220
|
+
if _STARTUP_TEMPLATE_KEY == _template_key(config) and _STARTUP_TEMPLATES is not None:
|
|
221
|
+
return
|
|
222
|
+
key = _template_key(config)
|
|
223
|
+
templates: List[str] = []
|
|
224
|
+
try:
|
|
225
|
+
client = _build_proxmox_client_from_config(config)
|
|
226
|
+
node = config.get("node") or _pick_node(client)
|
|
227
|
+
storages = client.nodes(node).storage.get()
|
|
228
|
+
templates = _list_templates(client, node, storages)
|
|
229
|
+
except Exception as exc:
|
|
230
|
+
logger.warning("Unable to refresh Proxmox templates: %s", exc)
|
|
231
|
+
with _STARTUP_TEMPLATES_LOCK:
|
|
232
|
+
_STARTUP_TEMPLATES = list(templates)
|
|
233
|
+
_STARTUP_TEMPLATE_KEY = key
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
def _get_startup_templates(config: Dict[str, Any]) -> List[str]:
|
|
237
|
+
_ensure_startup_templates(config)
|
|
238
|
+
with _STARTUP_TEMPLATES_LOCK:
|
|
239
|
+
return list(_STARTUP_TEMPLATES or [])
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
def _clear_startup_templates() -> None:
|
|
243
|
+
global _STARTUP_TEMPLATES, _STARTUP_TEMPLATE_KEY
|
|
244
|
+
with _STARTUP_TEMPLATES_LOCK:
|
|
245
|
+
_STARTUP_TEMPLATES = None
|
|
246
|
+
_STARTUP_TEMPLATE_KEY = None
|
|
247
|
+
|
|
248
|
+
|
|
181
249
|
def _pick_storage(storages: Iterable[Dict[str, Any]]) -> str:
|
|
182
250
|
candidates = [s for s in storages if "rootdir" in s.get("content", "") and s.get("avail", 0) > 0]
|
|
183
251
|
if not candidates:
|
|
@@ -596,7 +664,7 @@ def _remove_container_record(vmid: int) -> None:
|
|
|
596
664
|
|
|
597
665
|
|
|
598
666
|
def _build_container_payload(message: Dict[str, Any], config: Dict[str, Any]) -> Dict[str, Any]:
|
|
599
|
-
templates = config
|
|
667
|
+
templates = _get_startup_templates(config)
|
|
600
668
|
default_template = templates[0] if templates else ""
|
|
601
669
|
template = message.get("template") or default_template
|
|
602
670
|
if not template:
|
|
@@ -1024,13 +1092,14 @@ def build_snapshot(config: Dict[str, Any]) -> Dict[str, Any]:
|
|
|
1024
1092
|
"user": config.get("user"),
|
|
1025
1093
|
"token_name": config.get("token_name"),
|
|
1026
1094
|
"default_storage": config.get("default_storage"),
|
|
1027
|
-
"templates": config
|
|
1095
|
+
"templates": _get_startup_templates(config),
|
|
1028
1096
|
"last_verified": config.get("last_verified"),
|
|
1029
1097
|
"network": base_network,
|
|
1030
1098
|
}
|
|
1031
1099
|
|
|
1032
1100
|
|
|
1033
1101
|
def configure_infrastructure(token_identifier: str, token_value: str, verify_ssl: bool = False) -> Dict[str, Any]:
|
|
1102
|
+
_clear_startup_templates()
|
|
1034
1103
|
ProxmoxAPI = _ensure_proxmoxer()
|
|
1035
1104
|
user, token_name = _parse_token(token_identifier)
|
|
1036
1105
|
client = ProxmoxAPI(
|
|
@@ -1066,12 +1135,12 @@ def configure_infrastructure(token_identifier: str, token_value: str, verify_ssl
|
|
|
1066
1135
|
"token_value": token_value,
|
|
1067
1136
|
"verify_ssl": verify_ssl,
|
|
1068
1137
|
"default_storage": default_storage,
|
|
1069
|
-
"templates": templates,
|
|
1070
1138
|
"last_verified": datetime.utcnow().isoformat() + "Z",
|
|
1071
1139
|
"network": network,
|
|
1072
1140
|
"node_status": status,
|
|
1073
1141
|
}
|
|
1074
1142
|
_save_config(config)
|
|
1143
|
+
_ensure_startup_templates(config)
|
|
1075
1144
|
snapshot = build_snapshot(config)
|
|
1076
1145
|
snapshot["node_status"] = status
|
|
1077
1146
|
snapshot["managed_containers"] = _get_managed_containers_summary(force=True)
|
|
@@ -1091,6 +1160,7 @@ def revert_infrastructure() -> Dict[str, Any]:
|
|
|
1091
1160
|
_revert_bridge()
|
|
1092
1161
|
if CONFIG_PATH.exists():
|
|
1093
1162
|
CONFIG_PATH.unlink()
|
|
1163
|
+
_clear_startup_templates()
|
|
1094
1164
|
snapshot = build_snapshot({})
|
|
1095
1165
|
snapshot["network"] = snapshot.get("network", {})
|
|
1096
1166
|
snapshot["network"]["applied"] = False
|
|
@@ -1121,7 +1191,7 @@ def _instantiate_container(proxmox: Any, node: str, payload: Dict[str, Any]) ->
|
|
|
1121
1191
|
memory=int(payload["ram_mib"]),
|
|
1122
1192
|
swap=int(payload.get("swap_mb", 0)),
|
|
1123
1193
|
cores=max(int(payload.get("cores", 1)), 1),
|
|
1124
|
-
|
|
1194
|
+
cpulimit=float(payload.get("cpulimit", payload.get("cpus", 1))),
|
|
1125
1195
|
net0=payload["net0"],
|
|
1126
1196
|
unprivileged=int(payload.get("unprivileged", 1)),
|
|
1127
1197
|
description=payload.get("description", MANAGED_MARKER),
|
|
@@ -1235,7 +1305,8 @@ class CreateProxmoxContainerHandler(SyncHandler):
|
|
|
1235
1305
|
proxmox = _connect_proxmox(config)
|
|
1236
1306
|
node = config.get("node") or DEFAULT_NODE_NAME
|
|
1237
1307
|
payload = _build_container_payload(message, config)
|
|
1238
|
-
payload["
|
|
1308
|
+
payload["cpulimit"] = float(payload["cpus"])
|
|
1309
|
+
payload["cores"] = int(max(math.ceil(payload["cpus"]), 1))
|
|
1239
1310
|
payload["memory"] = int(payload["ram_mib"])
|
|
1240
1311
|
payload["node"] = node
|
|
1241
1312
|
logger.debug(
|
|
@@ -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=wz7cM9xrg98SOlEfHeMTG7xaJCZ3Le5OiMq1M5iYGqc,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
|
|
@@ -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=QALeGkuvNdhpLidrEzZOVgowAXh3L2vk4YbdssGIHKk,64781
|
|
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.15.
|
|
68
|
+
portacode-1.4.15.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.15.
|
|
95
|
-
portacode-1.4.15.
|
|
96
|
-
portacode-1.4.15.
|
|
97
|
-
portacode-1.4.15.
|
|
98
|
-
portacode-1.4.15.
|
|
94
|
+
portacode-1.4.15.dev8.dist-info/METADATA,sha256=KL7PnPejNsSu4P3ATAsmYLVmFpw8kGml7RjJypiLvHQ,13051
|
|
95
|
+
portacode-1.4.15.dev8.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
|
|
96
|
+
portacode-1.4.15.dev8.dist-info/entry_points.txt,sha256=lLUUL-BM6_wwe44Xv0__5NQ1BnAz6jWjSMFvZdWW3zU,48
|
|
97
|
+
portacode-1.4.15.dev8.dist-info/top_level.txt,sha256=TGhTYUxfW8SyVZc_zGgzjzc24gGT7nSw8Qf73liVRKM,41
|
|
98
|
+
portacode-1.4.15.dev8.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|