fix: case-insensitive container name matching for docker inspect
Docker container names are case-sensitive for 'docker inspect' but case- insensitive for Docker DNS. Compose templates may render container names with mixed case (e.g. code-server-Headquarter-abc123), causing exact-name docker inspect to fail while DNS resolution in tunnels works fine. - apps/api/src/services/docker.py: get_container_id now tries exact match first, then falls back to case-insensitive exact match via 'docker ps' - apps/api/src/api/tool_instances.py: recreate_tunnel_endpoint uses get_container_id instead of its own docker inspect call Quality gates: ruff clean
This commit is contained in:
@@ -2470,26 +2470,16 @@ async def recreate_tunnel_endpoint(
|
|||||||
tool_type.default_port,
|
tool_type.default_port,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Find the tool container — try stored ID first, then fall back to exact name
|
# Find the tool container — try stored ID first, then fall back to name lookup
|
||||||
tool_container_id = instance.container_id
|
tool_container_id = instance.container_id
|
||||||
if tool_container_id:
|
if tool_container_id:
|
||||||
logger.info("Using stored container_id: %s", tool_container_id)
|
logger.info("Using stored container_id: %s", tool_container_id)
|
||||||
else:
|
else:
|
||||||
# Use exact name match via inspect to avoid substring collisions with tunnel containers
|
tool_container_id = get_container_id(expected_name)
|
||||||
inspect_result = subprocess.run(
|
if tool_container_id:
|
||||||
["docker", "inspect", "-f", "{{.Id}}", expected_name],
|
logger.info("Found container by name: %s", tool_container_id)
|
||||||
capture_output=True,
|
|
||||||
text=True,
|
|
||||||
)
|
|
||||||
if inspect_result.returncode == 0 and inspect_result.stdout.strip():
|
|
||||||
tool_container_id = inspect_result.stdout.strip()
|
|
||||||
logger.info("Found container by exact name: %s", tool_container_id)
|
|
||||||
else:
|
else:
|
||||||
logger.error(
|
logger.error("Container %s not found", expected_name)
|
||||||
"Container %s not found via docker inspect: %s",
|
|
||||||
expected_name,
|
|
||||||
inspect_result.stderr,
|
|
||||||
)
|
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=status.HTTP_400_BAD_REQUEST,
|
status_code=status.HTTP_400_BAD_REQUEST,
|
||||||
detail="Could not find running container for this instance",
|
detail="Could not find running container for this instance",
|
||||||
|
|||||||
@@ -179,22 +179,41 @@ def execute_compose_command(
|
|||||||
def get_container_id(instance_name: str) -> str | None:
|
def get_container_id(instance_name: str) -> str | None:
|
||||||
"""Get the container ID for a compose service.
|
"""Get the container ID for a compose service.
|
||||||
|
|
||||||
Uses exact name matching via docker inspect to avoid substring collisions
|
Uses exact name matching to avoid substring collisions with tunnel
|
||||||
with tunnel containers (e.g. tunnel-code-server-... matching code-server-...).
|
containers (e.g. tunnel-code-server-... matching code-server-...).
|
||||||
|
Falls back to case-insensitive matching since Docker DNS is case-
|
||||||
|
insensitive but docker inspect is case-sensitive.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
instance_name: The exact container name (case-insensitive for Docker).
|
instance_name: The expected container name.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Container ID or None if not found.
|
Container ID or None if not found.
|
||||||
"""
|
"""
|
||||||
|
expected = instance_name.lower()
|
||||||
|
|
||||||
|
# Fast path: exact match via docker inspect
|
||||||
result = subprocess.run(
|
result = subprocess.run(
|
||||||
["docker", "inspect", "-f", "{{.Id}}", instance_name.lower()],
|
["docker", "inspect", "-f", "{{.Id}}", expected],
|
||||||
capture_output=True,
|
capture_output=True,
|
||||||
text=True,
|
text=True,
|
||||||
)
|
)
|
||||||
if result.returncode == 0 and result.stdout.strip():
|
if result.returncode == 0 and result.stdout.strip():
|
||||||
return result.stdout.strip()
|
return result.stdout.strip()
|
||||||
|
|
||||||
|
# Fallback: list all containers and do case-insensitive exact match
|
||||||
|
ps_result = subprocess.run(
|
||||||
|
["docker", "ps", "-a", "--format", "{{.Names}}\t{{.ID}}"],
|
||||||
|
capture_output=True,
|
||||||
|
text=True,
|
||||||
|
)
|
||||||
|
if ps_result.returncode == 0:
|
||||||
|
for line in ps_result.stdout.strip().splitlines():
|
||||||
|
parts = line.split("\t")
|
||||||
|
if len(parts) == 2:
|
||||||
|
name, cid = parts
|
||||||
|
if name.lower() == expected:
|
||||||
|
return cid
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user