diff --git a/config/introspection.go b/config/introspection.go index 495474d181..7eabddf39a 100644 --- a/config/introspection.go +++ b/config/introspection.go @@ -5,18 +5,22 @@ import "fmt" // Trace specifies how to configure Clair's tracing support. // // The "Name" key must match the provider to use. -// -// Currently, only "jaeger" is supported. type Trace struct { - Name string `yaml:"name" json:"name"` - Probability *float64 `yaml:"probability,omitempty" json:"probability,omitempty"` - Jaeger Jaeger `yaml:"jaeger,omitempty" json:"jaeger,omitempty"` + Name string `yaml:"name" json:"name"` + Probability *float64 `yaml:"probability,omitempty" json:"probability,omitempty"` + Jaeger Jaeger `yaml:"jaeger,omitempty" json:"jaeger,omitempty"` + OTLP TraceOTLP `yaml:"otlp,omitempty" json:"otlp,omitempty"` } func (t *Trace) lint() ([]Warning, error) { switch t.Name { case "": + case "otlp": case "jaeger": + return []Warning{{ + path: ".name", + msg: `trace provider "jaeger" is deprecated; migrate to "otlp"`, + }}, nil default: return []Warning{{ path: ".name", @@ -27,6 +31,11 @@ func (t *Trace) lint() ([]Warning, error) { } // Jaeger specific distributed tracing configuration. +// +// Deprecated: The Jaeger project recommends using their OTLP ingestion support +// and the OpenTelemetry exporter for Jaeger has since been removed. Users +// should migrate to OTLP. Clair may refuse to start when configured to emit +// Jaeger traces. type Jaeger struct { Tags map[string]string `yaml:"tags,omitempty" json:"tags,omitempty"` Agent struct { @@ -44,16 +53,20 @@ type Jaeger struct { // Metrics specifies how to configure Clair's metrics exporting. // // The "Name" key must match the provider to use. -// -// Currently, only "prometheus" is supported. type Metrics struct { - Prometheus Prometheus `yaml:"prometheus,omitempty" json:"prometheus,omitempty"` Name string `yaml:"name" json:"name"` + Prometheus Prometheus `yaml:"prometheus,omitempty" json:"prometheus,omitempty"` + OTLP MetricOTLP `yaml:"otlp,omitempty" json:"otlp,omitempty"` } func (m *Metrics) lint() ([]Warning, error) { switch m.Name { case "": + case "otlp": + return []Warning{{ + path: ".name", + msg: `please consult the documentation for the status of metrics via "otlp"`, + }}, nil case "prometheus": default: return []Warning{{ diff --git a/config/otlp.go b/config/otlp.go new file mode 100644 index 0000000000..19ad999e3c --- /dev/null +++ b/config/otlp.go @@ -0,0 +1,177 @@ +package config + +import ( + "fmt" + "path" + "strings" +) + +// OTLPCommon is common configuration options for an OTLP client. +type OTLPCommon struct { + // Compression configures payload compression. + // + // Only "gzip" is guaranteed to exist for both HTTP and gRPC. + Compression OTLPCompressor `yaml:"compression,omitempty" json:"compression,omitempty"` + // Endpoint is the host and port pair that the client should connect to. + // This is not a URL and must not have a scheme or trailing slashes. + // + // The default is "localhost:4317" for gRPC and "localhost:4318" for HTTP. + Endpoint string `yaml:"endpoint,omitempty" json:"endpoint,omitempty"` + // Headers adds additional headers to requests. + Headers map[string]string `yaml:"headers,omitempty" json:"headers,omitempty"` + // Insecure allows using an unsecured connection to the collector. + // + // For gRPC, this means certificate validation is not done. + // For HTTP, this means HTTP is used instead of HTTPS. + Insecure bool `yaml:"insecure,omitempty" json:"insecure,omitempty"` + // Timeout is the maximum amount of time + // + // The default is 10 seconds. + Timeout *Duration `yaml:"timeout,omitempty" json:"timeout,omitempty"` + // ClientTLS configures client TLS certificates. See [TLS] for configuring + // certificate authorities. + ClientTLS *TLS `yaml:"client_tls,omitempty" json:"client_tls,omitempty"` +} + +// Lint implements [linter]. +func (c *OTLPCommon) lint() (ws []Warning, _ error) { + if c.Timeout != nil && *c.Timeout == 0 { + ws = append(ws, Warning{ + path: ".timeout", + msg: "timeout of 0 is almost certainly wrong", + }) + } + return ws, nil +} + +// Validate implements [validator]. +func (c *OTLPCommon) validate(_ Mode) (ws []Warning, _ error) { + return c.lint() +} + +// OTLPHTTPCommon is common configuration options for an OTLP HTTP client. +type OTLPHTTPCommon struct { + OTLPCommon + // URLPath overrides the URL path for sending traces. If unset, the default + // is "/v1/traces". + URLPath string `yaml:"url_path,omitempty" json:"url_path,omitempty"` +} + +// Lint implements [linter]. +func (c *OTLPHTTPCommon) lint() (ws []Warning, _ error) { + if c.URLPath != "" && strings.HasSuffix(c.URLPath, "/") { + ws = append(ws, Warning{ + path: ".URLPath", + msg: fmt.Sprintf("path %q has a trailing slash; this is probably incorrect", c.URLPath), + }) + } + return ws, nil +} + +// Validate implements [validator]. +func (c *OTLPHTTPCommon) validate(_ Mode) (ws []Warning, err error) { + ws, err = c.lint() + if err != nil { + return ws, err + } + if c.URLPath != "" { + c.URLPath = path.Clean(c.URLPath) + if !path.IsAbs(c.URLPath) { + return ws, &Warning{ + path: ".URLPath", + msg: fmt.Sprintf("path %q must be absolute", c.URLPath), + } + } + } + return ws, nil +} + +// OTLPgRPCCommon is common configuration options for an OTLP gRPC client. +type OTLPgRPCCommon struct { + OTLPCommon + // Reconnect sets the minimum amount of time between connection attempts. + Reconnect *Duration `yaml:"reconnect,omitempty" json:"reconnect,omitempty"` + // ServiceConfig specifies a gRPC service config as a string containing JSON. + // See the [doc] for the format and possibilities. + // + // [doc]: https://github.com/grpc/grpc/blob/master/doc/service_config.md + ServiceConfig string `yaml:"service_config,omitempty" json:"service_config,omitempty"` +} + +// TraceOTLP is the configuration for an OTLP traces client. +// +// See the [OpenTelemetry docs] for more information on traces. +// See the Clair docs for the current status of of the instrumentation. +// +// [OpenTelemetry docs]: https://opentelemetry.io/docs/concepts/signals/traces/ +type TraceOTLP struct { + // HTTP configures OTLP via HTTP. + HTTP *TraceOTLPHTTP `yaml:"http,omitempty" json:"http,omitempty"` + // GRPC configures OTLP via gRPC. + GRPC *TraceOTLPgRPC `yaml:"grpc,omitempty" json:"grpc,omitempty"` +} + +// Lint implements [linter]. +func (t *TraceOTLP) lint() (ws []Warning, _ error) { + if t.HTTP != nil && t.GRPC != nil { + ws = append(ws, Warning{ + msg: `both "http" and "grpc" are configured, this may cause duplicate submissions`, + }) + } + return ws, nil +} + +// TraceOTLPHTTP is the configuration for an OTLP traces HTTP client. +type TraceOTLPHTTP struct { + OTLPHTTPCommon +} + +// TraceOTLPgRPC is the configuration for an OTLP traces gRPC client. +type TraceOTLPgRPC struct { + OTLPgRPCCommon +} + +// MetricOTLP is the configuration for an OTLP metrics client. +// +// See the [OpenTelemetry docs] for more information on metrics. +// See the Clair docs for the current status of of the instrumentation. +// +// [OpenTelemetry docs]: https://opentelemetry.io/docs/concepts/signals/metrics/ +type MetricOTLP struct { + // HTTP configures OTLP via HTTP. + HTTP *MetricOTLPHTTP `yaml:"http,omitempty" json:"http,omitempty"` + // GRPC configures OTLP via gRPC. + GRPC *MetricOTLPgRPC `yaml:"grpc,omitempty" json:"grpc,omitempty"` +} + +// Lint implements [linter]. +func (m *MetricOTLP) lint() (ws []Warning, _ error) { + if m.HTTP != nil && m.GRPC != nil { + ws = append(ws, Warning{ + msg: `both "http" and "grpc" are configured, this may cause duplicate submissions`, + }) + } + return ws, nil +} + +// MetricOTLPHTTP is the configuration for an OTLP metrics HTTP client. +type MetricOTLPHTTP struct { + OTLPHTTPCommon +} + +// MetricOTLPgRPC is the configuration for an OTLP metrics gRPC client. +type MetricOTLPgRPC struct { + OTLPgRPCCommon +} + +//go:generate go run golang.org/x/tools/cmd/stringer@latest -type OTLPCompressor -linecomment + +// OTLPCompressor is the valid options for compressing OTLP payloads. +type OTLPCompressor int + +// OTLPCompressor values +const ( + OTLPCompressUnset OTLPCompressor = iota // + OTLPCompressNone // none + OTLPCompressGzip // gzip +) diff --git a/config/otlpcompressor_string.go b/config/otlpcompressor_string.go new file mode 100644 index 0000000000..9da9d4b6f1 --- /dev/null +++ b/config/otlpcompressor_string.go @@ -0,0 +1,25 @@ +// Code generated by "stringer -type OTLPCompressor -linecomment"; DO NOT EDIT. + +package config + +import "strconv" + +func _() { + // An "invalid array index" compiler error signifies that the constant values have changed. + // Re-run the stringer command to generate them again. + var x [1]struct{} + _ = x[OTLPCompressUnset-0] + _ = x[OTLPCompressNone-1] + _ = x[OTLPCompressGzip-2] +} + +const _OTLPCompressor_name = "nonegzip" + +var _OTLPCompressor_index = [...]uint8{0, 0, 4, 8} + +func (i OTLPCompressor) String() string { + if i < 0 || i >= OTLPCompressor(len(_OTLPCompressor_index)-1) { + return "OTLPCompressor(" + strconv.FormatInt(int64(i), 10) + ")" + } + return _OTLPCompressor_name[_OTLPCompressor_index[i]:_OTLPCompressor_index[i+1]] +} diff --git a/config/tags_test.go b/config/tags_test.go index 4120b3c6a6..56b2fe4b90 100644 --- a/config/tags_test.go +++ b/config/tags_test.go @@ -18,15 +18,20 @@ var wanttags = []string{`json`, `yaml`} func typecheck(t *testing.T, typ reflect.Type) { for i, lim := 0, typ.NumField(); i < lim; i++ { f := typ.Field(i) - if f.PkgPath != "" { - // TODO(hank) Use the IsExported method once 1.16 support is - // dropped. + if !f.IsExported() { continue } // track the number of names for this field vals := make(map[string]struct{}) // track which tag has which name tagval := make(map[string]string) + // If embedded, there shouldn't be any tags. + if f.Anonymous { + if f.Tag != "" { + t.Errorf("%s.%s: unexpected tag %q", typ.Name(), f.Name, f.Tag) + } + goto Recurse + } for _, n := range wanttags { if v, ok := f.Tag.Lookup(n); !ok { t.Errorf("%s.%s: missing %q tag", typ.Name(), f.Name, n) @@ -38,6 +43,7 @@ func typecheck(t *testing.T, typ reflect.Type) { if len(vals) != 1 { t.Errorf("different names for %q: %v", f.Name, tagval) } + Recurse: // Recurse on structs and pointers-to-structs. switch nt := f.Type; nt.Kind() { case reflect.Ptr: