Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions cmd/ddev/cmd/stop.go
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,12 @@ ddev stop --remove-data`,
util.Success("Project %s has been stopped.", project.GetName())
}

// Clear the router healthcheck to ensure status reflects current state
err = ddevapp.ClearRouterHealthcheck()
if err != nil {
util.Warning("Failed to clear router healthcheck: %v", err)
}

if stopSSHAgent {
if err := ddevapp.RemoveSSHAgentContainer(); err != nil {
util.Error("Failed to remove ddev-ssh-agent: %v", err)
Expand Down
38 changes: 21 additions & 17 deletions containers/ddev-traefik-router/traefik_healthcheck.sh
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@ clear_warnings() {
fi
}

# Get count of routers loaded via file provider from Traefik API
get_file_router_count() {
# Get count of routers from Traefik API (all are from file provider)
get_traefiks_router_count() {
curl -sf "http://127.0.0.1:${TRAEFIK_MONITOR_PORT}/api/http/routers?per_page=10000" 2>/dev/null \
| jq '[.[] | select(.provider == "file")] | length' 2>/dev/null || echo 0
}
Expand All @@ -33,6 +33,7 @@ get_error_count() {
}

# Calculate expected router count by parsing config files
# that we've given to traefik
get_expected_router_count() {
local count=0
if [ -d "${config_dir}" ]; then
Expand Down Expand Up @@ -80,7 +81,7 @@ wait_for_routers() {
if [ "$expected_router_count" -gt 0 ] && [ "$error_count" -eq 0 ] && [ "$file_router_count" -ne "$expected_router_count" ]; then
for _ in $(seq 1 $max_retries); do
sleep 1
file_router_count=$(get_file_router_count)
file_router_count=$(get_traefiks_router_count)
error_count=$(get_error_count)

# Success: counts match and still no errors.
Expand All @@ -101,17 +102,19 @@ wait_for_routers() {
}

# Generate appropriate warning message based on state
# Pass to it
# $1=expected_router_count
# $2=actual_router_count
# #3=error count
generate_warning_message() {
local expected="$1"
local actual="$2"
local expected_router_count="$1"
local actual_router_count="$2"
local errors="$3"

if [ "$expected" -eq 0 ]; then
echo "WARNING: No config files found or no routers expected"
elif [ "$errors" -gt 0 ]; then
if [ "$errors" -gt 0 ]; then
echo "WARNING: Detected ${errors} configuration error(s)"
elif [ "$actual" -ne "$expected" ]; then
echo "WARNING: Router count mismatch: ${actual} loaded, ${expected} expected"
elif [ "${expected_router_count}" -gt 0 ] && [ "${actual_router_count}" -ne "${expected_router_count}" ]; then
echo "WARNING: Router count mismatch: ${actual_router_count} loaded, ${expected_router_count} expected"
else
echo "WARNING: Unknown issue detected"
fi
Expand Down Expand Up @@ -152,7 +155,7 @@ fi

# Get expected and actual router counts
expected_router_count=$(get_expected_router_count)
file_router_count=$(get_file_router_count)
file_router_count=$(get_traefiks_router_count)
error_count=$(get_error_count)

# Wait for traefik to finish loading if needed (avoids false warnings during startup)
Expand All @@ -161,12 +164,13 @@ file_router_count=$ROUTER_COUNT
error_count=$ERROR_COUNT

# Check if configuration is healthy:
# 1. Expected routers > 0 (config files found)
# 2. Actual router count matches expected count
# 3. No config errors
if [ "$expected_router_count" -gt 0 ] && \
[ "$file_router_count" -eq "$expected_router_count" ] && \
[ "$error_count" -eq 0 ]; then
# 1. No config errors
# 2. Either:
# a. No config files expected (all projects stopped) OR
# b. Expected routers > 0 AND actual router count matches expected count
if [ "$error_count" -eq 0 ] && \
{ [ "$expected_router_count" -eq 0 ] || \
{ [ "$expected_router_count" -gt 0 ] && [ "$file_router_count" -eq "$expected_router_count" ]; }; }; then
clear_warnings
mark_healthy "${check}"
fi
Expand Down
31 changes: 21 additions & 10 deletions pkg/ddevapp/router.go
Original file line number Diff line number Diff line change
Expand Up @@ -149,17 +149,9 @@ func StartDdevRouter() error {
}

// Force the healthcheck to run and wait for Traefik to load the new config.
// Remove /tmp/healthy so the healthcheck doesn't sleep for 59 seconds,
// then run the healthcheck which waits for routers to load.
router, err = FindDdevRouter()
err = ClearRouterHealthcheck()
if err != nil {
return fmt.Errorf("failed to find router for healthcheck: %v", err)
}
util.Debug("Forcing router healthcheck to verify new config is loaded")
uid, _, _ := dockerutil.GetContainerUser()
_, _, err = dockerutil.Exec(router.ID, "rm -f /tmp/healthy ddev-traefik-errors.txt && /healthcheck.sh", uid)
if err != nil {
return fmt.Errorf("router healthcheck failed: %v", err)
return err
}
}

Expand Down Expand Up @@ -365,6 +357,25 @@ func GetRouterConfigErrors() string {
return strings.TrimSpace(traefikErr)
}

// ClearRouterHealthcheck forces the router healthcheck to run immediately by
// removing the healthy marker and error file, then executing the healthcheck.
// This ensures the router status reflects the current configuration state.
func ClearRouterHealthcheck() error {
router, err := FindDdevRouter()
if err != nil || router == nil {
// Router not found or error - nothing to clear
return nil
}

util.Debug("Forcing router healthcheck to clear status")
uid, _, _ := dockerutil.GetContainerUser()
_, _, err = dockerutil.Exec(router.ID, "rm -f /tmp/healthy /tmp/ddev-traefik-errors.txt && /healthcheck.sh", uid)
if err != nil {
return fmt.Errorf("router healthcheck failed: %v", err)
}
return nil
}

// determineRouterHostnames returns a list of all hostnames for all active projects
func determineRouterHostnames(activeApps []*DdevApp) []string {
var routerHostnames []string
Expand Down
2 changes: 1 addition & 1 deletion pkg/versionconstants/versionconstants.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ var BaseDBTag = "v1.25.0"
var TraefikRouterImage = "ddev/ddev-traefik-router"

// TraefikRouterTag is traefik router tag
var TraefikRouterTag = "v1.25.0"
var TraefikRouterTag = "20260130_copilot_router_healthcheck"

// SSHAuthImage is image for agent
var SSHAuthImage = "ddev/ddev-ssh-agent"
Expand Down