pkg/topology/types.go (99 lines of code) (raw):

package topology import ( "fmt" "os" "strings" "k8s.io/apimachinery/pkg/util/sets" "sigs.k8s.io/yaml" ) type Topology struct { // Services holds the root nodes for service dependency trees. Services []Service `json:"services,omitempty"` // Entrypoints selects specific sub-trees that are deployed together. Entrypoints []Entrypoint `json:"entrypoints,omitempty"` } // Service describes an individual service in the tree. type Service struct { // ServiceGroup is the identifier for this service. ServiceGroup string `json:"serviceGroup"` // Children holds any dependent services. Children []Service `json:"children,omitempty"` // Metadata is an extension point to store useful information for the service. Metadata map[string]string `json:"metadata,omitempty"` } // Entrypoint describes an individual pipeline in the tree. type Entrypoint struct { // Identifier is the root of the sub-tree selected by this entrypoint. Identifier string `json:"identifier"` // Metadata is an extension point to store useful information for the sub-tree. Metadata map[string]string `json:"metadata,omitempty"` } func Load(path string) (*Topology, error) { raw, err := os.ReadFile(path) if err != nil { return nil, err } var out Topology return &out, yaml.Unmarshal(raw, &out) } func (t *Topology) Validate() error { return (&validator{ seen: sets.New[string](), duplicates: sets.New[string](), }).validate(t) } type validator struct { seen sets.Set[string] duplicates sets.Set[string] } func (v *validator) validate(t *Topology) error { for _, root := range t.Services { v.walk(root) } var messages []string if v.duplicates.Len() > 0 { messages = append(messages, fmt.Sprintf("the following pipelines had duplicate entries: %v", sets.List(v.duplicates))) } for _, entrypoint := range t.Entrypoints { if entrypoint.Identifier == "" { messages = append(messages, "entrypoint identifier cannot be empty") } if !v.seen.Has(entrypoint.Identifier) { messages = append(messages, fmt.Sprintf("entrypoint %s was not found in the dependency tree", entrypoint.Identifier)) } } if len(messages) > 0 { return fmt.Errorf("dependency tree invalid: %s", strings.Join(messages, ", ")) } return nil } func (v *validator) walk(s Service) { if v.seen.Has(s.ServiceGroup) { v.duplicates.Insert(s.ServiceGroup) } v.seen.Insert(s.ServiceGroup) for _, child := range s.Children { v.walk(child) } } // ServiceNotFoundError denotes a failure in Lookup() when the requested service group is not in the tree. type ServiceNotFoundError struct { ServiceGroup string } func (e *ServiceNotFoundError) Error() string { return fmt.Sprintf("service group %s not found in service tree", e.ServiceGroup) } // Lookup determines if the serviceGroup in question is part of the service topology tree and returns a pointer to the root // node, if so. func (t *Topology) Lookup(serviceGroup string) (*Service, error) { // walk the dependency tree from the roots; if we find the serviceGroup we know it's part of build-out var service *Service for i := range t.Services { if found := find(&t.Services[i], serviceGroup); found != nil { service = found break } } if service == nil { return nil, &ServiceNotFoundError{ServiceGroup: serviceGroup} } return service, nil } func find(root *Service, identifier string) *Service { if root.ServiceGroup == identifier { return root } for i := range root.Children { if found := find(&root.Children[i], identifier); found != nil { return found } } return nil }