From fb4a51b24dc69b9a9f7b4c71b76181266c1d383a Mon Sep 17 00:00:00 2001 From: scott Date: Thu, 5 Mar 2026 15:17:12 -0500 Subject: [PATCH] Fix iSCSI portal create: remove port from listen entries The TrueNAS API rejects port inside listen array items (iscsi_portal_create.listen.0.port: Extra inputs are not permitted). Port is a global iSCSI setting, not per-listen-IP. - _iscsi_portal_payload(): strip port from each listen entry, keeping only {"ip": "..."} - _prompt_iscsi_portals(): remove port prompt from wizard; show source IPs without port in the display Co-Authored-By: Claude Sonnet 4.6 --- truenas_migrate/cli.py | 9 +++------ truenas_migrate/migrate.py | 5 ++++- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/truenas_migrate/cli.py b/truenas_migrate/cli.py index 7695982..53d6e10 100644 --- a/truenas_migrate/cli.py +++ b/truenas_migrate/cli.py @@ -197,8 +197,7 @@ def _prompt_iscsi_portals(iscsi: dict) -> None: for portal in portals: comment = portal.get("comment", "") listen = portal.get("listen", []) - src_ips = " ".join(f"{l['ip']}:{l['port']}" for l in listen) - default_port = listen[0]["port"] if listen else 3260 + src_ips = " ".join(f"{l['ip']}" for l in listen) label = f"Portal {portal['id']}" + (f" ({comment!r})" if comment else "") print(f"\n {_bold(label)}") @@ -209,11 +208,9 @@ def _prompt_iscsi_portals(iscsi: dict) -> None: print(f" {_yellow('⚠')} No IPs entered — keeping source IPs.") continue - port_raw = _prompt(" Port", default=str(default_port)) - port = int(port_raw) if port_raw.isdigit() else default_port dest_ips = raw.split() - portal["listen"] = [{"ip": ip, "port": port} for ip in dest_ips] - print(f" {_green('✓')} Portal: {', '.join(f'{ip}:{port}' for ip in dest_ips)}") + portal["listen"] = [{"ip": ip} for ip in dest_ips] + print(f" {_green('✓')} Portal: {', '.join(dest_ips)}") print() diff --git a/truenas_migrate/migrate.py b/truenas_migrate/migrate.py index 3e9a260..67de8b2 100644 --- a/truenas_migrate/migrate.py +++ b/truenas_migrate/migrate.py @@ -179,7 +179,10 @@ def _iscsi_initiator_payload(initiator: dict) -> dict: def _iscsi_portal_payload(portal: dict) -> dict: - return {k: v for k, v in portal.items() if k not in _ISCSI_PORTAL_READONLY} + payload = {k: v for k, v in portal.items() if k not in _ISCSI_PORTAL_READONLY} + # The API only accepts {"ip": "..."} in listen entries — port is a global setting + payload["listen"] = [{"ip": l["ip"]} for l in payload.get("listen", [])] + return payload def _iscsi_target_payload(