Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
aos
GitHub Repository: aos/grafana-agent
Path: blob/main/component/registry.go
4093 views
1
package component
2
3
import (
4
"context"
5
"fmt"
6
"net"
7
"reflect"
8
"strings"
9
10
"github.com/grafana/agent/pkg/cluster"
11
"github.com/grafana/agent/pkg/flow/logging"
12
"github.com/grafana/regexp"
13
"github.com/prometheus/client_golang/prometheus"
14
"go.opentelemetry.io/otel/trace"
15
)
16
17
// The parsedName of a component is the parts of its name ("remote.http") split
18
// by the "." delimiter.
19
type parsedName []string
20
21
// String re-joins the parsed name by the "." delimiter.
22
func (pn parsedName) String() string { return strings.Join(pn, ".") }
23
24
var (
25
// Globally registered components
26
registered = map[string]Registration{}
27
// Parsed names for components
28
parsedNames = map[string]parsedName{}
29
)
30
31
// Options are provided to a component when it is being constructed. Options
32
// are static for the lifetime of a component.
33
type Options struct {
34
// ID of the component. Guaranteed to be globally unique across all running
35
// components.
36
ID string
37
38
// Logger the component may use for logging. Logs emitted with the logger
39
// always include the component ID as a field.
40
Logger *logging.Logger
41
42
// A path to a directory with this component may use for storage. The path is
43
// guaranteed to be unique across all running components.
44
//
45
// The directory may not exist when the component is created; components
46
// should create the directory if needed.
47
DataPath string
48
49
// OnStateChange may be invoked at any time by a component whose Export value
50
// changes. The Flow controller then will queue re-processing components
51
// which depend on the changed component.
52
//
53
// OnStateChange will panic if e does not match the Exports type registered
54
// by the component; a component must use the same Exports type for its
55
// lifetime.
56
OnStateChange func(e Exports)
57
58
// Registerer allows components to add their own metrics. The registerer will
59
// come pre-wrapped with the component ID. It is not necessary for components
60
// to unregister metrics on shutdown.
61
Registerer prometheus.Registerer
62
63
// Tracer allows components to record spans. The tracer will include an
64
// attribute denoting the component ID.
65
Tracer trace.TracerProvider
66
67
// Clusterer allows components to work in a clustered fashion. The
68
// clusterer is shared between all components initialized by a Flow
69
// controller.
70
Clusterer *cluster.Clusterer
71
72
// HTTPListenAddr is the address the server is configured to listen on.
73
HTTPListenAddr string
74
75
// DialFunc is a function for components to use to properly communicate to
76
// HTTPListenAddr. If set, components which send HTTP requests to
77
// HTTPListenAddr must use this function to establish connections.
78
DialFunc func(ctx context.Context, network, address string) (net.Conn, error)
79
80
// HTTPPath is the base path that requests need in order to route to this
81
// component. Requests received by a component handler will have this already
82
// trimmed off.
83
HTTPPath string
84
}
85
86
// Registration describes a single component.
87
type Registration struct {
88
// Name of the component. Must be a list of period-delimited valid
89
// identifiers, such as "remote.s3". Components sharing a prefix must have
90
// the same number of identifiers; it is valid to register "remote.s3" and
91
// "remote.http" but not "remote".
92
//
93
// Components may not have more than 2 identifiers.
94
//
95
// Each identifier must start with a valid ASCII letter, and be followed by
96
// any number of underscores or alphanumeric ASCII characters.
97
Name string
98
99
// A singleton component only supports one instance of itself across the
100
// whole process. Normally, multiple components of the same type may be
101
// created.
102
//
103
// The fully-qualified name of a component is the combination of River block
104
// name and all of its labels. Fully-qualified names must be unique across
105
// the process. Components which are *NOT* singletons automatically support
106
// user-supplied identifiers:
107
//
108
// // Fully-qualified names: remote.s3.object-a, remote.s3.object-b
109
// remote.s3 "object-a" { ... }
110
// remote.s3 "object-b" { ... }
111
//
112
// This allows for multiple instances of the same component to be defined.
113
// However, components registered as a singleton do not support user-supplied
114
// identifiers:
115
//
116
// node_exporter { ... }
117
//
118
// This prevents the user from defining multiple instances of node_exporter
119
// with different fully-qualified names.
120
Singleton bool
121
122
// An example Arguments value that the registered component expects to
123
// receive as input. Components should provide the zero value of their
124
// Arguments type here.
125
Args Arguments
126
127
// An example Exports value that the registered component may emit as output.
128
// A component which does not expose exports must leave this set to nil.
129
Exports Exports
130
131
// Build should construct a new component from an initial Arguments and set
132
// of options.
133
Build func(opts Options, args Arguments) (Component, error)
134
}
135
136
// CloneArguments returns a new zero value of the registered Arguments type.
137
func (r Registration) CloneArguments() Arguments {
138
return reflect.New(reflect.TypeOf(r.Args)).Interface()
139
}
140
141
// Register registers a component. Register will panic if the name is in use by
142
// another component, if the name is invalid, or if the component name has a
143
// suffix length mismatch with an existing component.
144
func Register(r Registration) {
145
if _, exist := registered[r.Name]; exist {
146
panic(fmt.Sprintf("Component name %q already registered", r.Name))
147
}
148
149
parsed, err := parseComponentName(r.Name)
150
if err != nil {
151
panic(fmt.Sprintf("invalid component name %q: %s", r.Name, err))
152
}
153
if err := validatePrefixMatch(parsed, parsedNames); err != nil {
154
panic(err)
155
}
156
157
registered[r.Name] = r
158
parsedNames[r.Name] = parsed
159
}
160
161
var identifierRegex = regexp.MustCompile("^[A-Za-z][0-9A-Za-z_]*$")
162
163
// parseComponentName parses and validates name. "remote.http" will return
164
// []string{"remote", "http"}.
165
func parseComponentName(name string) (parsedName, error) {
166
parts := strings.Split(name, ".")
167
if len(parts) == 0 {
168
return nil, fmt.Errorf("missing name")
169
}
170
171
for _, part := range parts {
172
if part == "" {
173
return nil, fmt.Errorf("found empty identifier")
174
}
175
176
if !identifierRegex.MatchString(part) {
177
return nil, fmt.Errorf("identifier %q is not valid", part)
178
}
179
}
180
181
return parts, nil
182
}
183
184
// validatePrefixMatch validates that no component has a name that is solely a prefix of another.
185
//
186
// For example, this will return an error if both a "remote" and "remote.http"
187
// component are defined.
188
func validatePrefixMatch(check parsedName, against map[string]parsedName) error {
189
// add a trailing dot to each component name, so that we are always matching
190
// complete segments.
191
name := check.String() + "."
192
for _, other := range against {
193
otherName := other.String() + "."
194
// if either is a prefix of the other, we have ambiguous names.
195
if strings.HasPrefix(otherName, name) || strings.HasPrefix(name, otherName) {
196
return fmt.Errorf("%q cannot be used because it is incompatible with %q", check, other)
197
}
198
}
199
return nil
200
}
201
202
// Get finds a registered component by name.
203
func Get(name string) (Registration, bool) {
204
r, ok := registered[name]
205
return r, ok
206
}
207
208