portacode 1.4.15.dev10__py3-none-any.whl → 1.4.15.dev15__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 +208 -46
- {portacode-1.4.15.dev10.dist-info → portacode-1.4.15.dev15.dist-info}/METADATA +1 -1
- {portacode-1.4.15.dev10.dist-info → portacode-1.4.15.dev15.dist-info}/RECORD +8 -8
- {portacode-1.4.15.dev10.dist-info → portacode-1.4.15.dev15.dist-info}/WHEEL +0 -0
- {portacode-1.4.15.dev10.dist-info → portacode-1.4.15.dev15.dist-info}/entry_points.txt +0 -0
- {portacode-1.4.15.dev10.dist-info → portacode-1.4.15.dev15.dist-info}/licenses/LICENSE +0 -0
- {portacode-1.4.15.dev10.dist-info → portacode-1.4.15.dev15.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.dev15'
|
|
32
|
+
__version_tuple__ = version_tuple = (1, 4, 15, 'dev15')
|
|
33
33
|
|
|
34
34
|
__commit_id__ = commit_id = None
|
|
@@ -16,7 +16,7 @@ import sys
|
|
|
16
16
|
import tempfile
|
|
17
17
|
import time
|
|
18
18
|
import threading
|
|
19
|
-
from datetime import datetime
|
|
19
|
+
from datetime import datetime, timezone
|
|
20
20
|
from pathlib import Path
|
|
21
21
|
from typing import Any, Callable, Dict, Iterable, List, Optional, Sequence, Tuple
|
|
22
22
|
|
|
@@ -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.now(timezone.utc).isoformat()
|
|
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.now(timezone.utc) - 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:
|
|
@@ -473,48 +496,119 @@ def _friendly_step_label(step_name: str) -> str:
|
|
|
473
496
|
return normalized.capitalize()
|
|
474
497
|
|
|
475
498
|
|
|
499
|
+
_PACKAGE_MANAGER_PROFILES: Dict[str, Dict[str, Any]] = {
|
|
500
|
+
"apt": {
|
|
501
|
+
"update_cmd": "apt-get update -y",
|
|
502
|
+
"update_step_name": "apt_update",
|
|
503
|
+
"install_cmd": "apt-get install -y python3 python3-pip sudo --fix-missing",
|
|
504
|
+
"install_step_name": "install_deps",
|
|
505
|
+
"update_retries": 4,
|
|
506
|
+
"install_retries": 5,
|
|
507
|
+
},
|
|
508
|
+
"dnf": {
|
|
509
|
+
"update_cmd": "dnf check-update || true",
|
|
510
|
+
"update_step_name": "dnf_update",
|
|
511
|
+
"install_cmd": "dnf install -y python3 python3-pip sudo",
|
|
512
|
+
"install_step_name": "install_deps",
|
|
513
|
+
"update_retries": 3,
|
|
514
|
+
"install_retries": 5,
|
|
515
|
+
},
|
|
516
|
+
"yum": {
|
|
517
|
+
"update_cmd": "yum makecache",
|
|
518
|
+
"update_step_name": "yum_update",
|
|
519
|
+
"install_cmd": "yum install -y python3 python3-pip sudo",
|
|
520
|
+
"install_step_name": "install_deps",
|
|
521
|
+
"update_retries": 3,
|
|
522
|
+
"install_retries": 5,
|
|
523
|
+
},
|
|
524
|
+
"apk": {
|
|
525
|
+
"update_cmd": "apk update",
|
|
526
|
+
"update_step_name": "apk_update",
|
|
527
|
+
"install_cmd": "apk add --no-cache python3 py3-pip sudo",
|
|
528
|
+
"install_step_name": "install_deps",
|
|
529
|
+
"update_retries": 3,
|
|
530
|
+
"install_retries": 5,
|
|
531
|
+
},
|
|
532
|
+
"pacman": {
|
|
533
|
+
"update_cmd": "pacman -Sy --noconfirm",
|
|
534
|
+
"update_step_name": "pacman_update",
|
|
535
|
+
"install_cmd": "pacman -S --noconfirm python python-pip sudo",
|
|
536
|
+
"install_step_name": "install_deps",
|
|
537
|
+
"update_retries": 3,
|
|
538
|
+
"install_retries": 5,
|
|
539
|
+
},
|
|
540
|
+
"zypper": {
|
|
541
|
+
"update_cmd": "zypper refresh",
|
|
542
|
+
"update_step_name": "zypper_update",
|
|
543
|
+
"install_cmd": "zypper install -y python3 python3-pip sudo",
|
|
544
|
+
"install_step_name": "install_deps",
|
|
545
|
+
"update_retries": 3,
|
|
546
|
+
"install_retries": 5,
|
|
547
|
+
},
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
_UPDATE_RETRY_ON = [
|
|
551
|
+
"Temporary failure resolving",
|
|
552
|
+
"Could not resolve",
|
|
553
|
+
"Failed to fetch",
|
|
554
|
+
]
|
|
555
|
+
|
|
556
|
+
_INSTALL_RETRY_ON = [
|
|
557
|
+
"lock-frontend",
|
|
558
|
+
"Unable to acquire the dpkg frontend lock",
|
|
559
|
+
"Temporary failure resolving",
|
|
560
|
+
"Could not resolve",
|
|
561
|
+
"Failed to fetch",
|
|
562
|
+
]
|
|
563
|
+
|
|
564
|
+
|
|
476
565
|
def _build_bootstrap_steps(
|
|
477
566
|
user: str,
|
|
478
567
|
password: str,
|
|
479
568
|
ssh_key: str,
|
|
480
569
|
include_portacode_connect: bool = True,
|
|
570
|
+
package_manager: str = "apt",
|
|
481
571
|
) -> List[Dict[str, Any]]:
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
"
|
|
490
|
-
"
|
|
491
|
-
"
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
"
|
|
501
|
-
"
|
|
502
|
-
"
|
|
503
|
-
"
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
572
|
+
profile = _PACKAGE_MANAGER_PROFILES.get(package_manager, _PACKAGE_MANAGER_PROFILES["apt"])
|
|
573
|
+
steps: List[Dict[str, Any]] = []
|
|
574
|
+
update_cmd = profile.get("update_cmd")
|
|
575
|
+
if update_cmd:
|
|
576
|
+
steps.append(
|
|
577
|
+
{
|
|
578
|
+
"name": profile.get("update_step_name", "package_update"),
|
|
579
|
+
"cmd": update_cmd,
|
|
580
|
+
"retries": profile.get("update_retries", 3),
|
|
581
|
+
"retry_delay_s": 5,
|
|
582
|
+
"retry_on": _UPDATE_RETRY_ON,
|
|
583
|
+
}
|
|
584
|
+
)
|
|
585
|
+
install_cmd = profile.get("install_cmd")
|
|
586
|
+
if install_cmd:
|
|
587
|
+
steps.append(
|
|
588
|
+
{
|
|
589
|
+
"name": profile.get("install_step_name", "install_deps"),
|
|
590
|
+
"cmd": install_cmd,
|
|
591
|
+
"retries": profile.get("install_retries", 5),
|
|
592
|
+
"retry_delay_s": 5,
|
|
593
|
+
"retry_on": _INSTALL_RETRY_ON,
|
|
594
|
+
}
|
|
595
|
+
)
|
|
596
|
+
steps.extend(
|
|
597
|
+
[
|
|
598
|
+
{"name": "user_exists", "cmd": f"id -u {user} >/dev/null 2>&1 || adduser --disabled-password --gecos '' {user}", "retries": 0},
|
|
599
|
+
{"name": "add_sudo", "cmd": f"usermod -aG sudo {user}", "retries": 0},
|
|
600
|
+
]
|
|
601
|
+
)
|
|
510
602
|
if password:
|
|
511
603
|
steps.append({"name": "set_password", "cmd": f"echo '{user}:{password}' | chpasswd", "retries": 0})
|
|
512
604
|
if ssh_key:
|
|
513
|
-
steps.append(
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
605
|
+
steps.append(
|
|
606
|
+
{
|
|
607
|
+
"name": "add_ssh_key",
|
|
608
|
+
"cmd": f"install -d -m 700 /home/{user}/.ssh && echo '{ssh_key}' >> /home/{user}/.ssh/authorized_keys && chown -R {user}:{user} /home/{user}/.ssh",
|
|
609
|
+
"retries": 0,
|
|
610
|
+
}
|
|
611
|
+
)
|
|
518
612
|
steps.extend(
|
|
519
613
|
[
|
|
520
614
|
{"name": "pip_upgrade", "cmd": "python3 -m pip install --upgrade pip", "retries": 0},
|
|
@@ -526,6 +620,45 @@ def _build_bootstrap_steps(
|
|
|
526
620
|
return steps
|
|
527
621
|
|
|
528
622
|
|
|
623
|
+
def _guess_package_manager_from_template(template: str) -> str:
|
|
624
|
+
normalized = (template or "").lower()
|
|
625
|
+
if "alpine" in normalized:
|
|
626
|
+
return "apk"
|
|
627
|
+
if "archlinux" in normalized:
|
|
628
|
+
return "pacman"
|
|
629
|
+
if "centos-7" in normalized:
|
|
630
|
+
return "yum"
|
|
631
|
+
if any(keyword in normalized for keyword in ("centos-8", "centos-9", "centos-9-stream", "centos-8-stream")):
|
|
632
|
+
return "dnf"
|
|
633
|
+
if any(keyword in normalized for keyword in ("rockylinux", "almalinux", "fedora")):
|
|
634
|
+
return "dnf"
|
|
635
|
+
if "opensuse" in normalized or "suse" in normalized:
|
|
636
|
+
return "zypper"
|
|
637
|
+
if any(keyword in normalized for keyword in ("debian", "ubuntu", "devuan", "turnkeylinux")):
|
|
638
|
+
return "apt"
|
|
639
|
+
if normalized.startswith("system/") and "linux" in normalized:
|
|
640
|
+
return "apt"
|
|
641
|
+
return "apt"
|
|
642
|
+
|
|
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
|
+
|
|
529
662
|
def _get_storage_type(storages: Iterable[Dict[str, Any]], storage_name: str) -> str:
|
|
530
663
|
for entry in storages:
|
|
531
664
|
if entry.get("storage") == storage_name:
|
|
@@ -1003,7 +1136,16 @@ def _bootstrap_portacode(
|
|
|
1003
1136
|
total_steps: Optional[int] = None,
|
|
1004
1137
|
default_public_key: Optional[str] = None,
|
|
1005
1138
|
) -> Tuple[str, List[Dict[str, Any]]]:
|
|
1006
|
-
|
|
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
|
+
)
|
|
1007
1149
|
results, ok = _run_setup_steps(
|
|
1008
1150
|
vmid,
|
|
1009
1151
|
actual_steps,
|
|
@@ -1027,6 +1169,15 @@ def _bootstrap_portacode(
|
|
|
1027
1169
|
else:
|
|
1028
1170
|
command_text = str(command)
|
|
1029
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
|
+
)
|
|
1030
1181
|
if summary:
|
|
1031
1182
|
logger.warning(
|
|
1032
1183
|
"Portacode bootstrap failure summary=%s%s%s",
|
|
@@ -1034,10 +1185,15 @@ def _bootstrap_portacode(
|
|
|
1034
1185
|
f" history_len={len(history)}" if history else "",
|
|
1035
1186
|
f" command={command_text}" if command_text else "",
|
|
1036
1187
|
)
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1188
|
+
logger.error(
|
|
1189
|
+
"Portacode bootstrap command failed%s%s%s",
|
|
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 "",
|
|
1193
|
+
)
|
|
1194
|
+
raise RuntimeError(
|
|
1195
|
+
f"Portacode bootstrap steps failed: {summary}{history_snippet}{command_suffix}"
|
|
1196
|
+
)
|
|
1041
1197
|
key_step = next((entry for entry in results if entry.get("name") == "portacode_connect"), None)
|
|
1042
1198
|
public_key = key_step.get("public_key") if key_step else default_public_key
|
|
1043
1199
|
if not public_key:
|
|
@@ -1104,8 +1260,9 @@ def configure_infrastructure(token_identifier: str, token_value: str, verify_ssl
|
|
|
1104
1260
|
"token_value": token_value,
|
|
1105
1261
|
"verify_ssl": verify_ssl,
|
|
1106
1262
|
"default_storage": default_storage,
|
|
1107
|
-
"templates": templates,
|
|
1108
1263
|
"last_verified": datetime.utcnow().isoformat() + "Z",
|
|
1264
|
+
"templates": templates,
|
|
1265
|
+
"templates_last_refreshed": _current_time_iso(),
|
|
1109
1266
|
"network": network,
|
|
1110
1267
|
"node_status": status,
|
|
1111
1268
|
}
|
|
@@ -1189,12 +1346,17 @@ class CreateProxmoxContainerHandler(SyncHandler):
|
|
|
1189
1346
|
device_public_key = (message.get("device_public_key") or "").strip()
|
|
1190
1347
|
device_private_key = (message.get("device_private_key") or "").strip()
|
|
1191
1348
|
has_device_keypair = bool(device_public_key and device_private_key)
|
|
1349
|
+
config_guess = _load_config()
|
|
1350
|
+
template_candidates = config_guess.get("templates") or []
|
|
1351
|
+
template_hint = (message.get("template") or (template_candidates[0] if template_candidates else "")).strip()
|
|
1352
|
+
package_manager = _guess_package_manager_from_template(template_hint)
|
|
1192
1353
|
bootstrap_user, bootstrap_password, bootstrap_ssh_key = _get_provisioning_user_info(message)
|
|
1193
1354
|
bootstrap_steps = _build_bootstrap_steps(
|
|
1194
1355
|
bootstrap_user,
|
|
1195
1356
|
bootstrap_password,
|
|
1196
1357
|
bootstrap_ssh_key,
|
|
1197
1358
|
include_portacode_connect=not has_device_keypair,
|
|
1359
|
+
package_manager=package_manager,
|
|
1198
1360
|
)
|
|
1199
1361
|
total_steps = 3 + len(bootstrap_steps) + 2
|
|
1200
1362
|
current_step_index = 1
|
|
@@ -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=0X-Wp9VmwXeZPPl4KjWDu4REXY5P2jnbevaNhxIBWL0,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=MIkDCRkc-Tkxm4FdvCBbiGcXKYKp6OUwtLtKHoOPF-I,69295
|
|
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.dev15.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.dev15.dist-info/METADATA,sha256=XJ2f3lsNMucCNlgkAJLKlnu8TYX1_eaSzKWCJnB-2Ik,13052
|
|
95
|
+
portacode-1.4.15.dev15.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
|
|
96
|
+
portacode-1.4.15.dev15.dist-info/entry_points.txt,sha256=lLUUL-BM6_wwe44Xv0__5NQ1BnAz6jWjSMFvZdWW3zU,48
|
|
97
|
+
portacode-1.4.15.dev15.dist-info/top_level.txt,sha256=TGhTYUxfW8SyVZc_zGgzjzc24gGT7nSw8Qf73liVRKM,41
|
|
98
|
+
portacode-1.4.15.dev15.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|