cmd/distrogen/registry.go (162 lines of code) (raw):

// Copyright 2025 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package main import ( _ "embed" "errors" "fmt" "log/slog" "slices" "strings" "gopkg.in/yaml.v3" ) //go:embed registry.yaml var registryContent []byte var ErrComponentNotFound = errors.New("component not found") // Registry is a collection of components that can be used in // a collector distribution. type Registry struct { Receivers RegistryComponents `yaml:"receivers"` Processors RegistryComponents `yaml:"processors"` Exporters RegistryComponents `yaml:"exporters"` Connectors RegistryComponents `yaml:"connectors"` Extensions RegistryComponents `yaml:"extensions"` Providers RegistryComponents `yaml:"providers"` } // NewRegistry will create an empty registry object with the // component lists preallocated. func NewRegistry() *Registry { return &Registry{ Receivers: RegistryComponents{}, Processors: RegistryComponents{}, Exporters: RegistryComponents{}, Connectors: RegistryComponents{}, Extensions: RegistryComponents{}, Providers: RegistryComponents{}, } } // LoadEmbeddedRegistry will load the registry embedded in the // distrogen binary. func LoadEmbeddedRegistry() (*Registry, error) { var r Registry if err := yaml.Unmarshal(registryContent, &r); err != nil { return nil, err } return &r, nil } // LoadRegistry will load a registry from a yaml file. func LoadRegistry(path string) (*Registry, error) { r := NewRegistry() err := yamlUnmarshalFromFileInto(path, r) return r, err } // Merge will merge another registry into this one. If the provided // registry contains any of the same entry keys as the current // registry, it will be overridden. func (r *Registry) Merge(r2 *Registry) { mapMerge(r.Receivers, r2.Receivers) mapMerge(r.Processors, r2.Processors) mapMerge(r.Exporters, r2.Exporters) mapMerge(r.Connectors, r2.Connectors) mapMerge(r.Extensions, r2.Extensions) mapMerge(r.Providers, r2.Providers) } // GoModuleID is intended for stringifying/unmarshalling to // a Go module ID, i.e. github.com/package/name v0.0.0 format. type GoModuleID struct { URL string Tag string AllowBlankTag bool } // String outputs the GoModuleID details in proper format. func (gm *GoModuleID) String() string { tag := gm.Tag if tag == "" { // There are certain cases (like local paths) where // the tag for a Go Module ID is allowed to be blank. if gm.AllowBlankTag { return gm.URL } // Otherwise if there is no tag specified, then it is assumed that this module // will be replaced. Use the tag v0.0.0 since it will be ignored // in the replace anyway. logger.Debug("no tag detected for module, using v0.0.0", slog.String("module", gm.URL)) tag = "v0.0.0" } return fmt.Sprintf("%s %s", gm.URL, tag) } // UnmarshalYAML implements the yaml.Unmarshaler interface. // It takes a properly formed Go Module ID string and unpacks // it into the struct. func (gm *GoModuleID) UnmarshalYAML(value *yaml.Node) error { // The module ID may have a version. moduleStr := value.Value moduleComponents := strings.Split(moduleStr, " ") gm.URL = moduleComponents[0] if len(moduleComponents) > 1 { gm.Tag = moduleComponents[1] } return nil } // MarshalYAML implements the yaml.Marshaler interface. It leverages // the String method to allow outputting the value into a YAML document // in the module ID string form. func (gm *GoModuleID) MarshalYAML() (interface{}, error) { return gm.String(), nil } type otelComponentVersion struct { core string coreStable string contrib string } // RegistryComponent is the type used as a basis for Registry. // It contains all the information needed to output a type RegistryComponent struct { GoMod *GoModuleID `yaml:"gomod"` Import string `yaml:"import,omitempty"` Name string `yaml:"string,omitempty"` Path string `yaml:"path,omitempty"` Stable bool `yaml:"stable,omitempty"` StartRevision string `yaml:"start_revision,omitempty"` DocsURL string `yaml:"docs_url"` } // RenderDocsURL renders the docs URL into a template. func (c *RegistryComponent) RenderDocsURL() string { if c.DocsURL == "" { return "No docs linked for component" } return c.DocsURL } // IsContrib determines whether the module comes from the opentelemetry-collector-contrib repo. func (c *RegistryComponent) IsContrib() bool { return strings.Contains(c.GoMod.URL, "github.com/open-telemetry/opentelemetry-collector-contrib") } func (c *RegistryComponent) ApplyOTelVersion(otelVersion otelComponentVersion) { c.GoMod.Tag = "v" + otelVersion.core if c.Stable { c.GoMod.Tag = "v" + otelVersion.coreStable } else if c.IsContrib() { c.GoMod.Tag = "v" + otelVersion.contrib } } // OCBManifestComponent is a reflection of the fields for an // entry in an OCB manifest yaml. type OCBManifestComponent struct { GoMod *GoModuleID `yaml:"gomod"` Import string `yaml:"import,omitempty"` Name string `yaml:"string,omitempty"` Path string `yaml:"path,omitempty"` } // GetOCBComponent will return an OCBManifestComponent using // the details from this RegistryComponent. func (c *RegistryComponent) GetOCBComponent() OCBManifestComponent { return OCBManifestComponent{ GoMod: c.GoMod, Import: c.Import, Name: c.Name, Path: c.Path, } } // RegistryComponents is a map of registry component names to component // details. type RegistryComponents map[string]*RegistryComponent // LoadAllComponents will take a list of component names and load them // from the registry, attaching the appropriate version tag. func (rl RegistryComponents) LoadAllComponents(names []string, otelVersion otelComponentVersion) (RegistryComponents, CollectionError) { components := RegistryComponents{} errs := make(CollectionError) for _, name := range names { entry, err := rl.LoadComponent(name, otelVersion) if err != nil { errs[name] = ErrComponentNotFound continue } components[name] = entry } return components, errs } func (rl RegistryComponents) LoadComponent(name string, otelVersion otelComponentVersion) (*RegistryComponent, error) { entry, ok := rl[name] if !ok { return nil, ErrComponentNotFound } entry.ApplyOTelVersion(otelVersion) return entry, nil } // Validate is intended to be called before template rendering. // This way, calling the Render method from the template can assume // no error. func (cs RegistryComponents) Validate() error { _, err := yaml.Marshal(cs) return err } // RenderOCBComponents will render the registry entries as func (cs RegistryComponents) RenderOCBComponents() string { if len(cs) == 0 { return "" } renderComponents := []OCBManifestComponent{} for _, c := range cs { renderComponents = append(renderComponents, c.GetOCBComponent()) } // The component list is sorted here to ensure that re-generating will always // have a consistent order. slices.SortFunc(renderComponents, func(a OCBManifestComponent, b OCBManifestComponent) int { return strings.Compare(a.GoMod.URL, b.GoMod.URL) }) return renderYaml(renderComponents) }