Fix: recognise a stack's own running ports on re-deploy (0.21.2)
Port-conflict check now matches the stack's own containers via the compose project label instead of a fragile container-name prefix, so editing + deploying a running stack no longer reports false conflicts (explicit container_name or '_' name separator). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
bb29ef1c98
commit
f65ec5f268
+1
-1
@@ -55,7 +55,7 @@ async def lifespan(app: FastAPI):
|
||||
schedule_task.cancel()
|
||||
|
||||
|
||||
app = FastAPI(title="StackPilot", version="0.21.1", lifespan=lifespan)
|
||||
app = FastAPI(title="StackPilot", version="0.21.2", lifespan=lifespan)
|
||||
|
||||
app.add_middleware(
|
||||
CORSMiddleware,
|
||||
|
||||
@@ -103,12 +103,21 @@ def host_listening_ports() -> dict[str, set[int]]:
|
||||
}
|
||||
|
||||
|
||||
def docker_bound_ports() -> dict[tuple[int, str], str]:
|
||||
"""Return {(host_port, proto): container_name}."""
|
||||
out: dict[tuple[int, str], str] = {}
|
||||
COMPOSE_PROJECT_LABEL = "com.docker.compose.project"
|
||||
|
||||
|
||||
def docker_bound_ports() -> dict[tuple[int, str], tuple[str, Optional[str]]]:
|
||||
"""Return {(host_port, proto): (container_name, compose_project)}.
|
||||
|
||||
The compose project is taken from the container's compose label so a
|
||||
stack's own running containers can be reliably recognised on re-deploy,
|
||||
regardless of explicit container_name or name separator differences.
|
||||
"""
|
||||
out: dict[tuple[int, str], tuple[str, Optional[str]]] = {}
|
||||
try:
|
||||
client = get_client()
|
||||
for c in safe_call(client.containers.list):
|
||||
project = (c.labels or {}).get(COMPOSE_PROJECT_LABEL)
|
||||
bindings = (c.attrs.get("NetworkSettings") or {}).get("Ports") or {}
|
||||
for container_port, hosts in bindings.items():
|
||||
if not hosts:
|
||||
@@ -117,7 +126,7 @@ def docker_bound_ports() -> dict[tuple[int, str], str]:
|
||||
for h in hosts:
|
||||
hp = h.get("HostPort")
|
||||
if hp:
|
||||
out[(int(hp), proto)] = c.name
|
||||
out[(int(hp), proto)] = (c.name, project)
|
||||
except (DockerError, ValueError):
|
||||
pass
|
||||
return out
|
||||
@@ -140,10 +149,16 @@ def detect_conflicts(yaml_str: str, ignore_stack: Optional[str] = None) -> list[
|
||||
used_by = None
|
||||
owner = docker_ports.get((port, proto))
|
||||
if owner:
|
||||
owner_name, owner_project = owner
|
||||
# A container from the same stack (re-deploy) is not a conflict.
|
||||
if ignore_stack and owner.startswith(f"{ignore_stack}-"):
|
||||
# Match on the compose project label, falling back to the legacy
|
||||
# name-prefix check for containers without the label.
|
||||
if ignore_stack and (
|
||||
owner_project == ignore_stack
|
||||
or owner_name.startswith(f"{ignore_stack}-")
|
||||
):
|
||||
continue
|
||||
used_by = f"container {owner}"
|
||||
used_by = f"container {owner_name}"
|
||||
elif port in host_ports.get(proto, set()):
|
||||
used_by = "host process"
|
||||
if used_by:
|
||||
|
||||
Reference in New Issue
Block a user