portacode 1.4.15.dev11__py3-none-any.whl → 1.4.15.dev13__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 +102 -36
- {portacode-1.4.15.dev11.dist-info → portacode-1.4.15.dev13.dist-info}/METADATA +1 -1
- {portacode-1.4.15.dev11.dist-info → portacode-1.4.15.dev13.dist-info}/RECORD +8 -8
- {portacode-1.4.15.dev11.dist-info → portacode-1.4.15.dev13.dist-info}/WHEEL +0 -0
- {portacode-1.4.15.dev11.dist-info → portacode-1.4.15.dev13.dist-info}/entry_points.txt +0 -0
- {portacode-1.4.15.dev11.dist-info → portacode-1.4.15.dev13.dist-info}/licenses/LICENSE +0 -0
- {portacode-1.4.15.dev11.dist-info → portacode-1.4.15.dev13.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.dev13'
|
|
32
|
+
__version_tuple__ = version_tuple = (1, 4, 15, 'dev13')
|
|
33
33
|
|
|
34
34
|
__commit_id__ = commit_id = None
|
|
@@ -47,7 +47,7 @@ UNIT_DIR = Path("/etc/systemd/system")
|
|
|
47
47
|
_MANAGED_CONTAINERS_CACHE_TTL_S = 30.0
|
|
48
48
|
_MANAGED_CONTAINERS_CACHE: Dict[str, Any] = {"timestamp": 0.0, "summary": None}
|
|
49
49
|
_MANAGED_CONTAINERS_CACHE_LOCK = threading.Lock()
|
|
50
|
-
|
|
50
|
+
TEMPLATES_REFRESH_INTERVAL_S = 300
|
|
51
51
|
|
|
52
52
|
ProgressCallback = Callable[[int, int, Dict[str, Any], str, Optional[Dict[str, Any]]], None]
|
|
53
53
|
|
|
@@ -197,10 +197,33 @@ def _build_proxmox_client_from_config(config: Dict[str, Any]):
|
|
|
197
197
|
)
|
|
198
198
|
|
|
199
199
|
|
|
200
|
+
def _current_time_iso() -> str:
|
|
201
|
+
return datetime.utcnow().isoformat() + "Z"
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
def _parse_iso_timestamp(value: str) -> Optional[datetime]:
|
|
205
|
+
if not value:
|
|
206
|
+
return None
|
|
207
|
+
text = value
|
|
208
|
+
if text.endswith("Z"):
|
|
209
|
+
text = text[:-1] + "+00:00"
|
|
210
|
+
try:
|
|
211
|
+
return datetime.fromisoformat(text)
|
|
212
|
+
except ValueError:
|
|
213
|
+
return None
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
def _templates_need_refresh(config: Dict[str, Any]) -> bool:
|
|
217
|
+
if not config or not config.get("token_value"):
|
|
218
|
+
return False
|
|
219
|
+
last = _parse_iso_timestamp(config.get("templates_last_refreshed") or "")
|
|
220
|
+
if not last:
|
|
221
|
+
return True
|
|
222
|
+
return (datetime.utcnow() - last).total_seconds() >= TEMPLATES_REFRESH_INTERVAL_S
|
|
223
|
+
|
|
224
|
+
|
|
200
225
|
def _ensure_templates_refreshed_on_startup(config: Dict[str, Any]) -> None:
|
|
201
|
-
|
|
202
|
-
if _STARTUP_TEMPLATES_REFRESHED or not config or not config.get("token_value"):
|
|
203
|
-
_STARTUP_TEMPLATES_REFRESHED = True
|
|
226
|
+
if not _templates_need_refresh(config):
|
|
204
227
|
return
|
|
205
228
|
try:
|
|
206
229
|
client = _build_proxmox_client_from_config(config)
|
|
@@ -209,10 +232,10 @@ def _ensure_templates_refreshed_on_startup(config: Dict[str, Any]) -> None:
|
|
|
209
232
|
templates = _list_templates(client, node, storages)
|
|
210
233
|
if templates:
|
|
211
234
|
config["templates"] = templates
|
|
235
|
+
config["templates_last_refreshed"] = _current_time_iso()
|
|
236
|
+
_save_config(config)
|
|
212
237
|
except Exception as exc:
|
|
213
238
|
logger.warning("Unable to refresh Proxmox templates on startup: %s", exc)
|
|
214
|
-
finally:
|
|
215
|
-
_STARTUP_TEMPLATES_REFRESHED = True
|
|
216
239
|
|
|
217
240
|
|
|
218
241
|
def _pick_storage(storages: Iterable[Dict[str, Any]]) -> str:
|
|
@@ -618,6 +641,24 @@ def _guess_package_manager_from_template(template: str) -> str:
|
|
|
618
641
|
return "apt"
|
|
619
642
|
|
|
620
643
|
|
|
644
|
+
def _detect_package_manager(vmid: int) -> str:
|
|
645
|
+
candidates = [
|
|
646
|
+
("apt", "apt-get"),
|
|
647
|
+
("dnf", "dnf"),
|
|
648
|
+
("yum", "yum"),
|
|
649
|
+
("apk", "apk"),
|
|
650
|
+
("pacman", "pacman"),
|
|
651
|
+
("zypper", "zypper"),
|
|
652
|
+
]
|
|
653
|
+
for name, binary in candidates:
|
|
654
|
+
res = _run_pct(vmid, f"command -v {binary} >/dev/null 2>&1")
|
|
655
|
+
if res.get("returncode") == 0:
|
|
656
|
+
logger.debug("Detected package manager %s inside container %s", name, vmid)
|
|
657
|
+
return name
|
|
658
|
+
logger.warning("Unable to detect package manager inside container %s; defaulting to apt", vmid)
|
|
659
|
+
return "apt"
|
|
660
|
+
|
|
661
|
+
|
|
621
662
|
def _get_storage_type(storages: Iterable[Dict[str, Any]], storage_name: str) -> str:
|
|
622
663
|
for entry in storages:
|
|
623
664
|
if entry.get("storage") == storage_name:
|
|
@@ -1095,36 +1136,60 @@ def _bootstrap_portacode(
|
|
|
1095
1136
|
total_steps: Optional[int] = None,
|
|
1096
1137
|
default_public_key: Optional[str] = None,
|
|
1097
1138
|
) -> Tuple[str, List[Dict[str, Any]]]:
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
if
|
|
1117
|
-
if
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1139
|
+
if steps is not None:
|
|
1140
|
+
actual_steps = steps
|
|
1141
|
+
else:
|
|
1142
|
+
detected_manager = _detect_package_manager(vmid)
|
|
1143
|
+
actual_steps = _build_bootstrap_steps(
|
|
1144
|
+
user,
|
|
1145
|
+
password,
|
|
1146
|
+
ssh_key,
|
|
1147
|
+
package_manager=detected_manager,
|
|
1148
|
+
)
|
|
1149
|
+
results, ok = _run_setup_steps(
|
|
1150
|
+
vmid,
|
|
1151
|
+
actual_steps,
|
|
1152
|
+
user,
|
|
1153
|
+
progress_callback=progress_callback,
|
|
1154
|
+
start_index=start_index,
|
|
1155
|
+
total_steps=total_steps,
|
|
1156
|
+
)
|
|
1157
|
+
if not ok:
|
|
1158
|
+
details = results[-1] if results else {}
|
|
1159
|
+
summary = details.get("error_summary") or details.get("stderr") or details.get("stdout") or details.get("name")
|
|
1160
|
+
history = details.get("history")
|
|
1161
|
+
history_snippet = ""
|
|
1162
|
+
if isinstance(history, list) and history:
|
|
1163
|
+
history_snippet = f" history={history[-3:]}"
|
|
1164
|
+
command = details.get("cmd")
|
|
1165
|
+
command_text = ""
|
|
1166
|
+
if command:
|
|
1167
|
+
if isinstance(command, (list, tuple)):
|
|
1168
|
+
command_text = shlex.join(str(entry) for entry in command)
|
|
1169
|
+
else:
|
|
1170
|
+
command_text = str(command)
|
|
1171
|
+
command_suffix = f" command={command_text}" if command_text else ""
|
|
1172
|
+
stdout = details.get("stdout")
|
|
1173
|
+
stderr = details.get("stderr")
|
|
1174
|
+
if stdout or stderr:
|
|
1175
|
+
logger.debug(
|
|
1176
|
+
"Bootstrap command output%s%s%s",
|
|
1177
|
+
f" stdout={stdout!r}" if stdout else "",
|
|
1178
|
+
" " if stdout and stderr else "",
|
|
1179
|
+
f"stderr={stderr!r}" if stderr else "",
|
|
1180
|
+
)
|
|
1181
|
+
if summary:
|
|
1182
|
+
logger.warning(
|
|
1183
|
+
"Portacode bootstrap failure summary=%s%s%s",
|
|
1184
|
+
summary,
|
|
1185
|
+
f" history_len={len(history)}" if history else "",
|
|
1186
|
+
f" command={command_text}" if command_text else "",
|
|
1187
|
+
)
|
|
1188
|
+
logger.error(
|
|
1189
|
+
"Portacode bootstrap command failed%s%s%s",
|
|
1127
1190
|
f" command={command_text}" if command_text else "",
|
|
1191
|
+
f" stdout={stdout!r}" if stdout else "",
|
|
1192
|
+
f" stderr={stderr!r}" if stderr else "",
|
|
1128
1193
|
)
|
|
1129
1194
|
raise RuntimeError(
|
|
1130
1195
|
f"Portacode bootstrap steps failed: {summary}{history_snippet}{command_suffix}"
|
|
@@ -1196,8 +1261,9 @@ def configure_infrastructure(token_identifier: str, token_value: str, verify_ssl
|
|
|
1196
1261
|
"token_value": token_value,
|
|
1197
1262
|
"verify_ssl": verify_ssl,
|
|
1198
1263
|
"default_storage": default_storage,
|
|
1199
|
-
"templates": templates,
|
|
1200
1264
|
"last_verified": datetime.utcnow().isoformat() + "Z",
|
|
1265
|
+
"templates": templates,
|
|
1266
|
+
"templates_last_refreshed": _current_time_iso(),
|
|
1201
1267
|
"network": network,
|
|
1202
1268
|
"node_status": status,
|
|
1203
1269
|
}
|
|
@@ -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=FcKIi3ar9MBpxd5ZXOee5GCUDMFfUL8Remra4zRx4uo,721
|
|
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=nkOuMT1vNKhGs7sHYrqDBju7qGAVC7PWasgd-QNqDUI,69529
|
|
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.dev13.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.dev13.dist-info/METADATA,sha256=Hz8hOfUj5NBoq6aU7rG1ceRGPkwPlbqnD9of5XRWgQw,13052
|
|
95
|
+
portacode-1.4.15.dev13.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
|
|
96
|
+
portacode-1.4.15.dev13.dist-info/entry_points.txt,sha256=lLUUL-BM6_wwe44Xv0__5NQ1BnAz6jWjSMFvZdWW3zU,48
|
|
97
|
+
portacode-1.4.15.dev13.dist-info/top_level.txt,sha256=TGhTYUxfW8SyVZc_zGgzjzc24gGT7nSw8Qf73liVRKM,41
|
|
98
|
+
portacode-1.4.15.dev13.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|