2023-10-18 14:55:32 +00:00
|
|
|
// Copyright (c) HashiCorp, Inc.
|
|
|
|
// SPDX-License-Identifier: BUSL-1.1
|
|
|
|
|
|
|
|
package controller
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
"sort"
|
|
|
|
"strings"
|
|
|
|
|
|
|
|
"github.com/hashicorp/go-multierror"
|
2023-12-21 21:37:47 +00:00
|
|
|
"golang.org/x/exp/maps"
|
2023-10-18 14:55:32 +00:00
|
|
|
|
|
|
|
"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 {
|
2023-12-21 21:37:47 +00:00
|
|
|
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{}{}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-10-18 14:55:32 +00:00
|
|
|
for _, w := range c.watches {
|
2023-12-21 21:37:47 +00:00
|
|
|
watches[typeToString(w.watchedType)] = struct{}{}
|
2023-10-18 14:55:32 +00:00
|
|
|
}
|
|
|
|
|
2023-12-21 21:37:47 +00:00
|
|
|
out[typeToString(c.managedTypeWatch.watchedType)] = maps.Keys(watches)
|
2023-10-18 14:55:32 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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
|
|
|
|
}
|