Некоторые проверки не удались
CI / test (push) Failing after 6s
Docker / Build and publish worker image (push) Failing after 8s
Add an internal/compose package that discovers Compose projects via `docker compose ls` + `docker ps` labels (grouped by com.docker.compose.project/service) and enriches each container with `docker inspect` ports/mounts and Traefik router labels. Management runs `docker compose` in each project's working directory for up/down/stop/restart/pull plus per-service variants and log tails. Wire it into the webapp: a 60s ComposeRefresher (constructed in New, started in Start, stopped in Close), a /compose list + detail + logs HTML surface, and /web/api/compose/* JSON endpoints (list, detail, logs, project/service lifecycle). Browser lifecycle POSTs are session+CSRF protected; the /web/api/* variants accept HTTP basic auth. WORKER_COMPOSE_ENABLED defaults on (false to disable). Tests cover discovery parsing/grouping/traefik/summary, the action allowlists, and the full handler surface (list/detail/logs HTML+API, CSRF enforcement, disabled/unknown-action rejection, audit writes, success+failure exec paths) via a stub Docker binary.
88 строки
2.6 KiB
Go
88 строки
2.6 KiB
Go
package compose
|
|
|
|
import (
|
|
"regexp"
|
|
"strings"
|
|
)
|
|
|
|
// Traefik label parsing. Compose projects fronted by Traefik encode
|
|
// their router rules as `traefik.http.routers.<name>.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.<n>.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)
|
|
}
|