package proxy import ( "context" "fmt" "net/http" "strings" "time" ) // TraefikProvider manages proxy routes via Docker labels. // Traefik auto-discovers containers with the appropriate labels. type TraefikProvider struct { entrypoint string certResolver string network string // Docker network for traefik.docker.network label apiURL string // Traefik API URL for health checks (optional) httpClient *http.Client } // NewTraefikProvider creates a Traefik-backed proxy provider. func NewTraefikProvider(entrypoint, certResolver, network, apiURL string) *TraefikProvider { if entrypoint == "" { entrypoint = "websecure" } return &TraefikProvider{ entrypoint: entrypoint, certResolver: certResolver, network: network, apiURL: strings.TrimRight(apiURL, "/"), httpClient: &http.Client{Timeout: 5 * time.Second}, } } func (t *TraefikProvider) Name() string { return "traefik" } // ConfigureRoute for Traefik is a no-op for deploy-managed containers. // Labels are set at container creation time via ContainerLabels(). // Returns a route ID for tracking. func (t *TraefikProvider) ConfigureRoute(_ context.Context, domain, _ string, _ int, _ RouteOptions) (string, error) { routerName := sanitizeDomain(domain) return routerName, nil } // DeleteRoute for Traefik is a no-op — removing the container removes the labels, // and Traefik automatically de-registers the route. func (t *TraefikProvider) DeleteRoute(_ context.Context, _ string) error { return nil } // RouteExists for Traefik is a no-op (always returns true) since routes are // managed via container labels and don't need separate tracking. func (t *TraefikProvider) RouteExists(_ context.Context, _ string) (bool, error) { return true, nil } // ContainerLabels returns Docker labels for Traefik auto-discovery. func (t *TraefikProvider) ContainerLabels(domain string, port int) map[string]string { name := sanitizeDomain(domain) labels := map[string]string{ "traefik.enable": "true", fmt.Sprintf("traefik.http.routers.%s.rule", name): fmt.Sprintf("Host(`%s`)", domain), fmt.Sprintf("traefik.http.routers.%s.entrypoints", name): t.entrypoint, fmt.Sprintf("traefik.http.services.%s.loadbalancer.server.port", name): fmt.Sprintf("%d", port), } if t.certResolver != "" { labels[fmt.Sprintf("traefik.http.routers.%s.tls.certresolver", name)] = t.certResolver } if t.network != "" { labels["traefik.docker.network"] = t.network } return labels } // Ping checks Traefik API connectivity if a URL is configured. func (t *TraefikProvider) Ping(ctx context.Context) error { if t.apiURL == "" { return nil // No API URL configured, skip health check. } req, err := http.NewRequestWithContext(ctx, http.MethodGet, t.apiURL+"/api/overview", nil) if err != nil { return fmt.Errorf("create traefik ping request: %w", err) } resp, err := t.httpClient.Do(req) if err != nil { return fmt.Errorf("traefik ping: %w", err) } resp.Body.Close() if resp.StatusCode >= 400 { return fmt.Errorf("traefik api returned status %d", resp.StatusCode) } return nil } // sanitizeDomain converts a domain to a safe Traefik router name. func sanitizeDomain(domain string) string { r := strings.NewReplacer(".", "-", ":", "-", "*", "wildcard") return r.Replace(strings.ToLower(domain)) }