consul/internal/controller/dependencies.go

113 lines
2.6 KiB
Go
Raw Normal View History

// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
package controller
import (
"fmt"
"sort"
"strings"
"github.com/hashicorp/go-multierror"
"golang.org/x/exp/maps"
"github.com/hashicorp/consul/internal/resource"
"github.com/hashicorp/consul/proto-public/pbresource"
)
func (m *Manager) ValidateDependencies(registrations []resource.Registration) error {
deps := m.CalculateDependencies(registrations)
return deps.validate()
}
type Dependencies map[string][]string
func (deps Dependencies) validate() error {
var merr error
seen := make(map[string]map[string]struct{})
mkErr := func(src, dst string) error {
vals := []string{src, dst}
sort.Strings(vals)
return fmt.Errorf("circular dependency between %q and %q", vals[0], vals[1])
}
for src, dsts := range deps {
seenDsts := seen[src]
if len(seenDsts) == 0 {
seen[src] = make(map[string]struct{})
}
for _, dst := range dsts {
if _, ok := seenDsts[dst]; ok {
merr = multierror.Append(merr, mkErr(src, dst))
}
if inverseDsts := seen[dst]; len(inverseDsts) > 0 {
if _, ok := inverseDsts[src]; ok {
merr = multierror.Append(merr, mkErr(src, dst))
}
}
seen[src][dst] = struct{}{}
}
}
return merr
}
func (m *Manager) CalculateDependencies(registrations []resource.Registration) Dependencies {
typeToString := func(t *pbresource.Type) string {
return strings.ToLower(fmt.Sprintf("%s/%s/%s", t.Group, t.GroupVersion, t.Kind))
}
out := make(map[string][]string)
for _, r := range registrations {
out[typeToString(r.Type)] = nil
}
for _, c := range m.controllers {
watches := map[string]struct{}{}
// Extend existing watch list if one is present. This is necessary
// because there can be multiple controllers for a given type.
// ProxyStateTemplate, for example, is controlled by sidecar proxy and
// gateway proxy controllers.
if existing, ok := out[typeToString(c.managedTypeWatch.watchedType)]; ok {
for _, w := range existing {
watches[w] = struct{}{}
}
}
for _, w := range c.watches {
watches[typeToString(w.watchedType)] = struct{}{}
}
out[typeToString(c.managedTypeWatch.watchedType)] = maps.Keys(watches)
}
return out
}
func (deps Dependencies) ToMermaid() string {
depStrings := make([]string, 0, len(deps))
for src, dsts := range deps {
if len(dsts) == 0 {
depStrings = append(depStrings, fmt.Sprintf(" %s", src))
continue
}
for _, dst := range dsts {
depStrings = append(depStrings, fmt.Sprintf(" %s --> %s", src, dst))
}
}
sort.Slice(depStrings, func(a, b int) bool {
return depStrings[a] < depStrings[b]
})
out := "flowchart TD\n" + strings.Join(depStrings, "\n")
return out
}