package component12import (3"context"4"fmt"5"net"6"reflect"7"strings"89"github.com/grafana/agent/pkg/cluster"10"github.com/grafana/agent/pkg/flow/logging"11"github.com/grafana/regexp"12"github.com/prometheus/client_golang/prometheus"13"go.opentelemetry.io/otel/trace"14)1516// The parsedName of a component is the parts of its name ("remote.http") split17// by the "." delimiter.18type parsedName []string1920// String re-joins the parsed name by the "." delimiter.21func (pn parsedName) String() string { return strings.Join(pn, ".") }2223var (24// Globally registered components25registered = map[string]Registration{}26// Parsed names for components27parsedNames = map[string]parsedName{}28)2930// Options are provided to a component when it is being constructed. Options31// are static for the lifetime of a component.32type Options struct {33// ID of the component. Guaranteed to be globally unique across all running34// components.35ID string3637// Logger the component may use for logging. Logs emitted with the logger38// always include the component ID as a field.39Logger *logging.Logger4041// A path to a directory with this component may use for storage. The path is42// guaranteed to be unique across all running components.43//44// The directory may not exist when the component is created; components45// should create the directory if needed.46DataPath string4748// OnStateChange may be invoked at any time by a component whose Export value49// changes. The Flow controller then will queue re-processing components50// which depend on the changed component.51//52// OnStateChange will panic if e does not match the Exports type registered53// by the component; a component must use the same Exports type for its54// lifetime.55OnStateChange func(e Exports)5657// Registerer allows components to add their own metrics. The registerer will58// come pre-wrapped with the component ID. It is not necessary for components59// to unregister metrics on shutdown.60Registerer prometheus.Registerer6162// Tracer allows components to record spans. The tracer will include an63// attribute denoting the component ID.64Tracer trace.TracerProvider6566// Clusterer allows components to work in a clustered fashion. The67// clusterer is shared between all components initialized by a Flow68// controller.69Clusterer *cluster.Clusterer7071// HTTPListenAddr is the address the server is configured to listen on.72HTTPListenAddr string7374// DialFunc is a function for components to use to properly communicate to75// HTTPListenAddr. If set, components which send HTTP requests to76// HTTPListenAddr must use this function to establish connections.77DialFunc func(ctx context.Context, network, address string) (net.Conn, error)7879// HTTPPath is the base path that requests need in order to route to this80// component. Requests received by a component handler will have this already81// trimmed off.82HTTPPath string83}8485// Registration describes a single component.86type Registration struct {87// Name of the component. Must be a list of period-delimited valid88// identifiers, such as "remote.s3". Components sharing a prefix must have89// the same number of identifiers; it is valid to register "remote.s3" and90// "remote.http" but not "remote".91//92// Components may not have more than 2 identifiers.93//94// Each identifier must start with a valid ASCII letter, and be followed by95// any number of underscores or alphanumeric ASCII characters.96Name string9798// A singleton component only supports one instance of itself across the99// whole process. Normally, multiple components of the same type may be100// created.101//102// The fully-qualified name of a component is the combination of River block103// name and all of its labels. Fully-qualified names must be unique across104// the process. Components which are *NOT* singletons automatically support105// user-supplied identifiers:106//107// // Fully-qualified names: remote.s3.object-a, remote.s3.object-b108// remote.s3 "object-a" { ... }109// remote.s3 "object-b" { ... }110//111// This allows for multiple instances of the same component to be defined.112// However, components registered as a singleton do not support user-supplied113// identifiers:114//115// node_exporter { ... }116//117// This prevents the user from defining multiple instances of node_exporter118// with different fully-qualified names.119Singleton bool120121// An example Arguments value that the registered component expects to122// receive as input. Components should provide the zero value of their123// Arguments type here.124Args Arguments125126// An example Exports value that the registered component may emit as output.127// A component which does not expose exports must leave this set to nil.128Exports Exports129130// Build should construct a new component from an initial Arguments and set131// of options.132Build func(opts Options, args Arguments) (Component, error)133}134135// CloneArguments returns a new zero value of the registered Arguments type.136func (r Registration) CloneArguments() Arguments {137return reflect.New(reflect.TypeOf(r.Args)).Interface()138}139140// Register registers a component. Register will panic if the name is in use by141// another component, if the name is invalid, or if the component name has a142// suffix length mismatch with an existing component.143func Register(r Registration) {144if _, exist := registered[r.Name]; exist {145panic(fmt.Sprintf("Component name %q already registered", r.Name))146}147148parsed, err := parseComponentName(r.Name)149if err != nil {150panic(fmt.Sprintf("invalid component name %q: %s", r.Name, err))151}152if err := validatePrefixMatch(parsed, parsedNames); err != nil {153panic(err)154}155156registered[r.Name] = r157parsedNames[r.Name] = parsed158}159160var identifierRegex = regexp.MustCompile("^[A-Za-z][0-9A-Za-z_]*$")161162// parseComponentName parses and validates name. "remote.http" will return163// []string{"remote", "http"}.164func parseComponentName(name string) (parsedName, error) {165parts := strings.Split(name, ".")166if len(parts) == 0 {167return nil, fmt.Errorf("missing name")168}169170for _, part := range parts {171if part == "" {172return nil, fmt.Errorf("found empty identifier")173}174175if !identifierRegex.MatchString(part) {176return nil, fmt.Errorf("identifier %q is not valid", part)177}178}179180return parts, nil181}182183// validatePrefixMatch validates that no component has a name that is solely a prefix of another.184//185// For example, this will return an error if both a "remote" and "remote.http"186// component are defined.187func validatePrefixMatch(check parsedName, against map[string]parsedName) error {188// add a trailing dot to each component name, so that we are always matching189// complete segments.190name := check.String() + "."191for _, other := range against {192otherName := other.String() + "."193// if either is a prefix of the other, we have ambiguous names.194if strings.HasPrefix(otherName, name) || strings.HasPrefix(name, otherName) {195return fmt.Errorf("%q cannot be used because it is incompatible with %q", check, other)196}197}198return nil199}200201// Get finds a registered component by name.202func Get(name string) (Registration, bool) {203r, ok := registered[name]204return r, ok205}206207208