package traefik import ( "errors" "fmt" "reflect" ) // ErrConflict meldet zwei unterschiedliche Definitionen unter demselben Namen. var ErrConflict = errors.New("Namenskonflikt") // Merge führt die Konfigurationen mehrerer Knoten zu einer zusammen. // nil-Einträge werden übersprungen. Identische Definitionen unter demselben // Namen sind erlaubt, unterschiedliche ergeben ErrConflict; das Vergeben // eindeutiger Namen (z. B. pro Mandant/Knoten) ist Aufgabe des Aufrufers. // Die Eingaben werden nicht verändert, das Ergebnis teilt sich aber die // Router-, Service- und Middleware-Objekte mit ihnen. func Merge(configs ...*Configuration) (*Configuration, error) { out := &HTTPConfiguration{ Routers: map[string]*Router{}, Services: map[string]*Service{}, Middlewares: map[string]Middleware{}, } tcp := &TCPConfiguration{ Routers: map[string]*TCPRouter{}, Services: map[string]*TCPService{}, } var errs []error for _, c := range configs { if c == nil { continue } if c.HTTP != nil { errs = append(errs, mergeInto(out.Routers, c.HTTP.Routers, "router")...) errs = append(errs, mergeInto(out.Services, c.HTTP.Services, "service")...) errs = append(errs, mergeInto(out.Middlewares, c.HTTP.Middlewares, "middleware")...) } if c.TCP != nil { errs = append(errs, mergeInto(tcp.Routers, c.TCP.Routers, "tcp router")...) errs = append(errs, mergeInto(tcp.Services, c.TCP.Services, "tcp service")...) } } if len(errs) > 0 { return nil, errors.Join(errs...) } res := &Configuration{} if len(out.Routers) > 0 || len(out.Services) > 0 || len(out.Middlewares) > 0 { res.HTTP = out } if len(tcp.Routers) > 0 || len(tcp.Services) > 0 { res.TCP = tcp } return res, nil } func mergeInto[V any](dst, src map[string]V, kind string) []error { var errs []error for name, v := range src { existing, ok := dst[name] if !ok { dst[name] = v continue } if !reflect.DeepEqual(existing, v) { errs = append(errs, fmt.Errorf("%s %q: %w", kind, name, ErrConflict)) } } return errs }