fix: cloudflare tunnel connects to localhost:host_port instead of container_name:container_port
Root cause: start_cloudflared_tunnel was trying to connect to http://{container_name}:{container_port}, but: 1. The host OS cannot resolve Docker container names 2. cloudflared runs on the host, so it needs the host-mapped port Changes: - start_cloudflared_tunnel: changed signature to accept host_port only - Connects cloudflared to localhost:{host_port} via Docker port mapping - Connectivity check uses localhost:{host_port} - recreate_tunnel updated to match new signature - Callers in tool_instances.py pass instance.port (host port) Quality gates: pytest 42 passed
This commit is contained in:
@@ -1818,8 +1818,7 @@ async def start_instance(
|
|||||||
instance_port,
|
instance_port,
|
||||||
)
|
)
|
||||||
tunnel_info = start_cloudflared_tunnel(
|
tunnel_info = start_cloudflared_tunnel(
|
||||||
container_name=instance.container_name or instance.name,
|
host_port=instance.port,
|
||||||
port=instance_port,
|
|
||||||
)
|
)
|
||||||
instance.tunnel_id = tunnel_info["pid"]
|
instance.tunnel_id = tunnel_info["pid"]
|
||||||
instance.public_url = tunnel_info["url"]
|
instance.public_url = tunnel_info["url"]
|
||||||
@@ -2026,15 +2025,12 @@ async def restart_instance(
|
|||||||
"error": f"Tool type '{tool_type.name if tool_type else 'unknown'}' has no port configured",
|
"error": f"Tool type '{tool_type.name if tool_type else 'unknown'}' has no port configured",
|
||||||
}
|
}
|
||||||
|
|
||||||
instance_port = tool_type.default_port
|
|
||||||
|
|
||||||
# Only create tunnel for web-enabled tools
|
# Only create tunnel for web-enabled tools
|
||||||
if tool_type.interface_type == "web":
|
if tool_type.interface_type == "web":
|
||||||
# Create new temporary tunnel
|
# Create new temporary tunnel
|
||||||
try:
|
try:
|
||||||
tunnel_info = start_cloudflared_tunnel(
|
tunnel_info = start_cloudflared_tunnel(
|
||||||
container_name=instance.container_name or instance.name,
|
host_port=instance.port or 0,
|
||||||
port=instance_port,
|
|
||||||
)
|
)
|
||||||
instance.tunnel_id = tunnel_info["pid"]
|
instance.tunnel_id = tunnel_info["pid"]
|
||||||
instance.public_url = tunnel_info["url"]
|
instance.public_url = tunnel_info["url"]
|
||||||
@@ -2267,16 +2263,9 @@ async def recreate_tunnel_endpoint(
|
|||||||
"message": "Tunnel is already healthy",
|
"message": "Tunnel is already healthy",
|
||||||
}
|
}
|
||||||
|
|
||||||
# Get tool type for default port
|
|
||||||
tool_type = await session.get(ToolType, instance.tool_type_id)
|
|
||||||
instance_port = (
|
|
||||||
tool_type.default_port if tool_type and tool_type.default_port else 8080
|
|
||||||
)
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
tunnel_info = recreate_tunnel(
|
tunnel_info = recreate_tunnel(
|
||||||
container_name=instance.container_name or instance.name,
|
host_port=instance.port or 0,
|
||||||
port=instance_port,
|
|
||||||
old_pid=instance.tunnel_id,
|
old_pid=instance.tunnel_id,
|
||||||
)
|
)
|
||||||
instance.tunnel_id = tunnel_info["pid"]
|
instance.tunnel_id = tunnel_info["pid"]
|
||||||
|
|||||||
@@ -385,16 +385,15 @@ def find_free_port(start: int = 10000, end: int = 20000) -> int:
|
|||||||
|
|
||||||
|
|
||||||
def start_cloudflared_tunnel(
|
def start_cloudflared_tunnel(
|
||||||
container_name: str, port: int, timeout: int = 30
|
host_port: int, timeout: int = 30
|
||||||
) -> dict[str, str]:
|
) -> dict[str, str]:
|
||||||
"""Start a temporary Cloudflare tunnel for a container.
|
"""Start a temporary Cloudflare tunnel to localhost.
|
||||||
|
|
||||||
Uses 'cloudflared tunnel --url' to create a temporary tunnel
|
Uses 'cloudflared tunnel --url' to create a temporary tunnel
|
||||||
with a random trycloudflare.com URL.
|
with a random trycloudflare.com URL.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
container_name: Name of the Docker container to tunnel to
|
host_port: Host-mapped port number (e.g. from find_free_port)
|
||||||
port: Port number the container listens on
|
|
||||||
timeout: Maximum seconds to wait for tunnel URL
|
timeout: Maximum seconds to wait for tunnel URL
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
@@ -405,8 +404,8 @@ def start_cloudflared_tunnel(
|
|||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
# First verify the container is accessible
|
# First verify the container is accessible via the host-mapped port
|
||||||
logger.info("Checking connectivity to %s:%d...", container_name, port)
|
logger.info("Checking connectivity to localhost:%d...", host_port)
|
||||||
for attempt in range(10):
|
for attempt in range(10):
|
||||||
check = subprocess.run(
|
check = subprocess.run(
|
||||||
[
|
[
|
||||||
@@ -416,7 +415,7 @@ def start_cloudflared_tunnel(
|
|||||||
"/dev/null",
|
"/dev/null",
|
||||||
"-w",
|
"-w",
|
||||||
"%{http_code}",
|
"%{http_code}",
|
||||||
f"http://{container_name}:{port}",
|
f"http://localhost:{host_port}",
|
||||||
],
|
],
|
||||||
capture_output=True,
|
capture_output=True,
|
||||||
text=True,
|
text=True,
|
||||||
@@ -430,13 +429,13 @@ def start_cloudflared_tunnel(
|
|||||||
time.sleep(1)
|
time.sleep(1)
|
||||||
else:
|
else:
|
||||||
logger.warning(
|
logger.warning(
|
||||||
"Container %s:%d not responding to curl checks", container_name, port
|
"localhost:%d not responding to curl checks", host_port
|
||||||
)
|
)
|
||||||
|
|
||||||
# Run cloudflared in background, capture output
|
# Run cloudflared in background, capture output
|
||||||
logger.info("Starting cloudflared tunnel to http://%s:%d", container_name, port)
|
logger.info("Starting cloudflared tunnel to http://localhost:%d", host_port)
|
||||||
proc = subprocess.Popen(
|
proc = subprocess.Popen(
|
||||||
["cloudflared", "tunnel", "--url", f"http://{container_name}:{port}"],
|
["cloudflared", "tunnel", "--url", f"http://localhost:{host_port}"],
|
||||||
stdout=subprocess.PIPE,
|
stdout=subprocess.PIPE,
|
||||||
stderr=subprocess.STDOUT,
|
stderr=subprocess.STDOUT,
|
||||||
text=True,
|
text=True,
|
||||||
@@ -491,15 +490,14 @@ def stop_cloudflared_tunnel(pid: str) -> None:
|
|||||||
|
|
||||||
|
|
||||||
def recreate_tunnel(
|
def recreate_tunnel(
|
||||||
container_name: str, port: int, old_pid: str | None = None
|
host_port: int, old_pid: str | None = None
|
||||||
) -> dict[str, str]:
|
) -> dict[str, str]:
|
||||||
"""Recreate a temporary Cloudflare tunnel.
|
"""Recreate a temporary Cloudflare tunnel.
|
||||||
|
|
||||||
Stops the old tunnel (if pid provided) and starts a new one.
|
Stops the old tunnel (if pid provided) and starts a new one.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
container_name: Name of the Docker container to tunnel to
|
host_port: Host-mapped port number (e.g. from find_free_port)
|
||||||
port: Port number the container listens on
|
|
||||||
old_pid: Optional PID of the old tunnel process to stop
|
old_pid: Optional PID of the old tunnel process to stop
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
@@ -508,7 +506,7 @@ def recreate_tunnel(
|
|||||||
if old_pid:
|
if old_pid:
|
||||||
stop_cloudflared_tunnel(old_pid)
|
stop_cloudflared_tunnel(old_pid)
|
||||||
|
|
||||||
return start_cloudflared_tunnel(container_name, port)
|
return start_cloudflared_tunnel(host_port)
|
||||||
|
|
||||||
|
|
||||||
def check_tunnel_health(url: str, timeout: int = 10) -> dict[str, Any]:
|
def check_tunnel_health(url: str, timeout: int = 10) -> dict[str, Any]:
|
||||||
|
|||||||
@@ -120,9 +120,9 @@ async def publish_lifecycle_event(
|
|||||||
# Create notification for instance owner (fire-and-forget)
|
# Create notification for instance owner (fire-and-forget)
|
||||||
# Skip intermediate "starting" notifications — only notify on terminal states
|
# Skip intermediate "starting" notifications — only notify on terminal states
|
||||||
# (failed or successful attempts)
|
# (failed or successful attempts)
|
||||||
_is_starting_intermediate = event_type == "instance.started" and (
|
_is_starting_intermediate = (
|
||||||
status or instance.status
|
event_type == "instance.started" and (status or instance.status) == "starting"
|
||||||
) == "starting"
|
)
|
||||||
if _is_starting_intermediate:
|
if _is_starting_intermediate:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user