package compose import ( "regexp" "strings" ) // Traefik label parsing. Compose projects fronted by Traefik encode // their router rules as `traefik.http.routers..rule=Host(...)`. // We surface hostnames/paths so the operator console can show which // domains a project serves without reading raw labels. const ( traefikLabelPrefix = "traefik.http.routers." traefikRuleSuffix = ".rule" ) var ( // Host(`example.com`) or Host("example.com") hostRegex = regexp.MustCompile(`Host\s*\(\s*[` + "`" + `"]([^` + "`" + `"]+)[` + "`" + `"]\s*\)`) // Path(`/api`) or Path("/api") pathRegex = regexp.MustCompile(`Path\s*\(\s*[` + "`" + `"]([^` + "`" + `"]+)[` + "`" + `"]\s*\)`) // PathPrefix(`/api`) or PathPrefix("/api") pathPrefixRegex = regexp.MustCompile(`PathPrefix\s*\(\s*[` + "`" + `"]([^` + "`" + `"]+)[` + "`" + `"]\s*\)`) ) // TraefikRoute is one decoded Traefik router derived from labels. type TraefikRoute struct { RouterName string `json:"router_name"` Hostnames []string `json:"hostnames"` Paths []string `json:"paths"` PathPrefixes []string `json:"path_prefixes"` Rule string `json:"rule"` } // ParseTraefikLabels scans a label map for `traefik.http.routers..rule` // entries and returns one TraefikRoute per router, with the rule's // Host/Path/PathPrefix operands extracted. Pure function: no I/O. func ParseTraefikLabels(labels map[string]string) []TraefikRoute { routes := map[string]*TraefikRoute{} for key, value := range labels { if !strings.HasPrefix(key, traefikLabelPrefix) { continue } suffix := strings.TrimPrefix(key, traefikLabelPrefix) idx := strings.Index(suffix, ".") if idx == -1 { continue } routerName := suffix[:idx] property := suffix[idx:] if property != traefikRuleSuffix { continue } route := routes[routerName] if route == nil { route = &TraefikRoute{RouterName: routerName} } route.Rule = value route.Hostnames = extractAll(hostRegex, value) route.Paths = extractAll(pathRegex, value) route.PathPrefixes = extractAll(pathPrefixRegex, value) routes[routerName] = route } out := make([]TraefikRoute, 0, len(routes)) for _, r := range routes { out = append(out, *r) } return out } func extractAll(re *regexp.Regexp, rule string) []string { matches := re.FindAllStringSubmatch(rule, -1) out := make([]string, 0, len(matches)) for _, m := range matches { if len(m) > 1 && m[1] != "" { out = append(out, m[1]) } } return out } // ExtractTraefikRoutes returns the Traefik routes declared by a // container's labels. func ExtractTraefikRoutes(c Container) []TraefikRoute { return ParseTraefikLabels(c.Labels) }