Skip to content

Commit

Permalink
config: add OTLP configuration types
Browse files Browse the repository at this point in the history
Signed-off-by: Hank Donnay <hdonnay@redhat.com>
  • Loading branch information
hdonnay committed Oct 27, 2023
1 parent b0497e5 commit 33cc3e5
Show file tree
Hide file tree
Showing 6 changed files with 247 additions and 19 deletions.
7 changes: 4 additions & 3 deletions config/doc.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
// Package config is the configuration package for Clair's binaries. See the
// Config type for the main entry point.
// [Config] type for the main entry point.
//
// It's currently meant for reading configs and tested against YAML and JSON.
// It's currently meant for reading configurations and tested against YAML and
// JSON.
//
// # Version Scheme
//
Expand All @@ -16,7 +17,7 @@
// changes on a program importing the module.
package config

// This pakcage can't use "omitempty" tags on slices because "not present" and
// This package can't use "omitempty" tags on slices because "not present" and
// "empty" aren't distinguished. This would be much easier if code didn't
// serialize our config struct. It's impossible to implement custom YAML
// marshalling without importing the yaml.v3 package.
29 changes: 21 additions & 8 deletions config/introspection.go
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -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 {
Expand All @@ -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{{
Expand Down
180 changes: 180 additions & 0 deletions config/otlp.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
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 for a submission.
//
// The default is 10 seconds.
Timeout *Duration `yaml:"timeout,omitempty" json:"timeout,omitempty"`
// ClientTLS configures client TLS certificates, meaning a user should
// ignore the "RootCA" member and look only at the "Cert" and "Key" members.
//
// See the documentation for the TLS struct for recommendations on
// 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
)
25 changes: 25 additions & 0 deletions config/otlpcompressor_string.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 9 additions & 3 deletions config/tags_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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:
Expand Down
13 changes: 8 additions & 5 deletions config/tls.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,19 +10,22 @@ import (

// TLS describes some TLS settings.
//
// These are currently only used in the Notifier. Using the environment
// variables "SSL_CERT_DIR" or "SSL_CERT_FILE" or modifying the system's trust
// store are the ways to modify root CAs for all outgoing TLS connections.
// Some uses of this type ignore the RootCA member; see the documentation at the
// use site to determine if that's the case.
//
// Using the environment variables "SSL_CERT_DIR" or "SSL_CERT_FILE" or
// modifying the system's trust store are the ways to modify root CAs for all
// outgoing TLS connections.
type TLS struct {
// The filesystem path where a root CA can be read.
//
// This can also be controlled by the SSL_CERT_FILE and SSL_CERT_DIR
// environment variables, or adding the relevant certs to the system trust
// store.
RootCA string `yaml:"root_ca" json:"root_ca"`
// The filesystem path where a tls certificate can be read.
// The filesystem path where a TLS certificate can be read.
Cert string `yaml:"cert" json:"cert"`
// The filesystem path where a tls private key can be read.
// The filesystem path where a TLS private key can be read.
Key string `yaml:"key" json:"key"`
}

Expand Down

0 comments on commit 33cc3e5

Please sign in to comment.