mirror of https://github.com/hashicorp/consul
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
101 lines
2.2 KiB
101 lines
2.2 KiB
1 year ago
|
// Copyright (c) HashiCorp, Inc.
|
||
|
// SPDX-License-Identifier: BUSL-1.1
|
||
|
|
||
|
package controller
|
||
|
|
||
|
import (
|
||
|
"fmt"
|
||
|
"sort"
|
||
|
"strings"
|
||
|
|
||
|
"github.com/hashicorp/go-multierror"
|
||
|
|
||
|
"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 := make([]string, 0, len(c.watches))
|
||
|
for _, w := range c.watches {
|
||
|
watches = append(watches, typeToString(w.watchedType))
|
||
|
}
|
||
|
|
||
|
out[typeToString(c.managedType)] = 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
|
||
|
}
|