|
12 | 12 | KeyRef, |
13 | 13 | Service, |
14 | 14 | ) |
15 | | -from local_nexus_controller.services.ports import is_port_in_use, next_available_port |
| 15 | +from local_nexus_controller.services.ports import is_controller_port, is_port_in_use, next_available_port, reserved_ports |
16 | 16 |
|
17 | 17 |
|
18 | 18 | def _now_utc() -> datetime: |
@@ -75,27 +75,60 @@ def import_bundle(session: Session, bundle: ImportBundle, host_for_port_checks: |
75 | 75 | db = _upsert_database(session, bundle.database) |
76 | 76 | database_id = db.id |
77 | 77 |
|
78 | | - # Port assignment |
| 78 | + # Port assignment with automatic conflict resolution |
79 | 79 | port: int | None = bundle.service.port |
80 | 80 | if bundle.requested_port is not None: |
81 | 81 | port = int(bundle.requested_port) |
82 | | - if port is None and bundle.auto_assign_port: |
83 | | - port = next_available_port(session, host=host_for_port_checks) |
84 | 82 |
|
85 | | - # Check conflicts |
| 83 | + # Resolve port conflicts automatically instead of silently allowing them |
86 | 84 | if port is not None: |
87 | | - # Only warn if the port is reserved by a *different* service. |
88 | | - conflicting = session.exec( |
89 | | - select(Service).where(Service.port == int(port), Service.name != bundle.service.name) |
90 | | - ).first() |
91 | | - if conflicting: |
92 | | - warnings.append(f"Port {port} is already reserved in the registry (by '{conflicting.name}').") |
93 | | - if is_port_in_use(host_for_port_checks, port): |
94 | | - warnings.append(f"Port {port} appears to be in use on {host_for_port_checks}.") |
| 85 | + needs_reassign = False |
| 86 | + |
| 87 | + # Never allow a service to use the controller's own port |
| 88 | + if is_controller_port(port): |
| 89 | + warnings.append(f"Port {port} is the Nexus Controller port; auto-reassigning.") |
| 90 | + needs_reassign = True |
| 91 | + |
| 92 | + # Check if another service already has this port |
| 93 | + if not needs_reassign: |
| 94 | + conflicting = session.exec( |
| 95 | + select(Service).where(Service.port == int(port), Service.name != bundle.service.name) |
| 96 | + ).first() |
| 97 | + if conflicting: |
| 98 | + warnings.append(f"Port {port} already reserved by '{conflicting.name}'; auto-reassigning.") |
| 99 | + needs_reassign = True |
| 100 | + |
| 101 | + # Check if the port is physically in use by something else |
| 102 | + if not needs_reassign and is_port_in_use(host_for_port_checks, port): |
| 103 | + warnings.append(f"Port {port} in use on {host_for_port_checks}; auto-reassigning.") |
| 104 | + needs_reassign = True |
| 105 | + |
| 106 | + if needs_reassign: |
| 107 | + old_port = port |
| 108 | + port = next_available_port(session, host=host_for_port_checks) |
| 109 | + warnings.append(f"Reassigned from port {old_port} to {port}.") |
| 110 | + |
| 111 | + if port is None and bundle.auto_assign_port: |
| 112 | + port = next_available_port(session, host=host_for_port_checks) |
95 | 113 |
|
96 | 114 | # Upsert service |
97 | 115 | svc_dict = bundle.service.model_dump() # type: ignore[attr-defined] |
98 | 116 | svc_dict["port"] = port |
| 117 | + |
| 118 | + # Auto-populate local_url from port when missing |
| 119 | + if not svc_dict.get("local_url") and port is not None: |
| 120 | + svc_dict["local_url"] = f"http://127.0.0.1:{port}" |
| 121 | + # If port changed from what was in the bundle, update local_url to match |
| 122 | + elif svc_dict.get("local_url") and bundle.service.port is not None and port != bundle.service.port: |
| 123 | + old_port_str = f":{bundle.service.port}" |
| 124 | + if old_port_str in svc_dict["local_url"]: |
| 125 | + svc_dict["local_url"] = svc_dict["local_url"].replace(old_port_str, f":{port}") |
| 126 | + # Same for healthcheck_url |
| 127 | + if svc_dict.get("healthcheck_url") and bundle.service.port is not None and port != bundle.service.port: |
| 128 | + old_port_str = f":{bundle.service.port}" |
| 129 | + if old_port_str in svc_dict["healthcheck_url"]: |
| 130 | + svc_dict["healthcheck_url"] = svc_dict["healthcheck_url"].replace(old_port_str, f":{port}") |
| 131 | + |
99 | 132 | if database_id is not None: |
100 | 133 | svc_dict["database_id"] = database_id |
101 | 134 | svc_dict.setdefault("status", "stopped") |
|
0 commit comments