Files
sultan/pkg/traefik/merge.go
T

70 lines
2.0 KiB
Go

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
}