Skip to content

Commit

Permalink
Headers configuration
Browse files Browse the repository at this point in the history
  • Loading branch information
olegbespalov committed Jun 19, 2024
1 parent 4cdf100 commit 62ac853
Show file tree
Hide file tree
Showing 4 changed files with 70 additions and 5 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ It's worth to mention that the extension is using the [OpenTelemetry Go SDK](htt

* `K6_OTEL_EXPORT_INTERVAL` - configures the intervening time between metrics exports. Default is `10s`.
* `K6_OTEL_EXPORTER_TYPE` - metric exporter type. Default is `grpc`.
* `K6_OTEL_HEADERS` - headers in W3C Correlation-Context format without additional semi-colon delimited metadata (i.e. "k1=v1,k2=v2"). Passes the headers to the exporter.

#### TLS configuration

Expand Down
7 changes: 7 additions & 0 deletions pkg/opentelemetry/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@ type Config struct {
// ExportInterval configures the intervening time between metrics exports
ExportInterval types.NullDuration `json:"exportInterval" envconfig:"K6_OTEL_EXPORT_INTERVAL"`

// Headers in W3C Correlation-Context format without additional semi-colon delimited metadata (i.e. "k1=v1,k2=v2")
Headers null.String `json:"headers" envconfig:"K6_OTEL_HEADERS"`

// TLSInsecureSkipVerify disables verification of the server's certificate chain
TLSInsecureSkipVerify null.Bool `json:"tlsInsecureSkipVerify" envconfig:"K6_OTEL_TLS_INSECURE_SKIP_VERIFY"`
// TLSCertificate is the path to the certificate file (rootCAs) to use for the exporter's TLS connection
Expand Down Expand Up @@ -179,6 +182,10 @@ func (cfg Config) Apply(v Config) Config {
cfg.TLSClientKey = v.TLSClientKey
}

if v.Headers.Valid {
cfg.Headers = v.Headers
}

return cfg
}

Expand Down
6 changes: 5 additions & 1 deletion pkg/opentelemetry/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ func TestConfig(t *testing.T) {
"K6_OTEL_TLS_CERTIFICATE": "cert_path",
"K6_OTEL_TLS_CLIENT_CERTIFICATE": "client_cert_path",
"K6_OTEL_TLS_CLIENT_KEY": "client_key_path",
"K6_OTEL_HEADERS": "key1=value1,key2=value2",
},
expectedConfig: Config{
ServiceName: null.StringFrom("foo"),
Expand All @@ -84,6 +85,7 @@ func TestConfig(t *testing.T) {
TLSCertificate: null.StringFrom("cert_path"),
TLSClientCertificate: null.StringFrom("client_cert_path"),
TLSClientKey: null.StringFrom("client_key_path"),
Headers: null.StringFrom("key1=value1,key2=value2"),
},
},

Expand All @@ -103,7 +105,8 @@ func TestConfig(t *testing.T) {
`"tlsInsecureSkipVerify":true,` +
`"tlsCertificate":"cert_path",` +
`"tlsClientCertificate":"client_cert_path",` +
`"tlsClientKey":"client_key_path"` +
`"tlsClientKey":"client_key_path",` +
`"headers":"key1=value1,key2=value2"` +
`}`,
),
expectedConfig: Config{
Expand All @@ -121,6 +124,7 @@ func TestConfig(t *testing.T) {
TLSCertificate: null.StringFrom("cert_path"),
TLSClientCertificate: null.StringFrom("client_cert_path"),
TLSClientKey: null.StringFrom("client_key_path"),
Headers: null.StringFrom("key1=value1,key2=value2"),
},
},

Expand Down
61 changes: 57 additions & 4 deletions pkg/opentelemetry/exporter.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ import (
"context"
"crypto/tls"
"errors"
"fmt"
"net/url"
"strings"

"go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc"
"go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp"
Expand All @@ -27,20 +30,33 @@ func getExporter(cfg Config) (metric.Exporter, error) {
return nil, err
}

var headers map[string]string
if cfg.Headers.Valid {
headers, err = parseHeaders(cfg.Headers.String)
if err != nil {
return nil, fmt.Errorf("failed to parse headers: %w", err)
}
}

exporterType := cfg.ExporterType.String

if exporterType == grpcExporterType {
return buildGRPCExporter(ctx, cfg, tlsConfig)
return buildGRPCExporter(ctx, cfg, tlsConfig, headers)
}

if exporterType == httpExporterType {
return buildHTTPExporter(ctx, cfg, tlsConfig)
return buildHTTPExporter(ctx, cfg, tlsConfig, headers)
}

return nil, errors.New("unsupported exporter type " + exporterType + " specified")
}

func buildHTTPExporter(ctx context.Context, cfg Config, tlsConfig *tls.Config) (metric.Exporter, error) {
func buildHTTPExporter(
ctx context.Context,
cfg Config,
tlsConfig *tls.Config,
headers map[string]string,
) (metric.Exporter, error) {
opts := []otlpmetrichttp.Option{
otlpmetrichttp.WithEndpoint(cfg.HTTPExporterEndpoint.String),
otlpmetrichttp.WithURLPath(cfg.HTTPExporterURLPath.String),
Expand All @@ -50,14 +66,23 @@ func buildHTTPExporter(ctx context.Context, cfg Config, tlsConfig *tls.Config) (
opts = append(opts, otlpmetrichttp.WithInsecure())
}

if len(headers) > 0 {
opts = append(opts, otlpmetrichttp.WithHeaders(headers))
}

if tlsConfig != nil {
opts = append(opts, otlpmetrichttp.WithTLSClientConfig(tlsConfig))
}

return otlpmetrichttp.New(ctx, opts...)
}

func buildGRPCExporter(ctx context.Context, cfg Config, tlsConfig *tls.Config) (metric.Exporter, error) {
func buildGRPCExporter(
ctx context.Context,
cfg Config,
tlsConfig *tls.Config,
headers map[string]string,
) (metric.Exporter, error) {
opt := []otlpmetricgrpc.Option{
otlpmetricgrpc.WithEndpoint(cfg.GRPCExporterEndpoint.String),
}
Expand All @@ -66,9 +91,37 @@ func buildGRPCExporter(ctx context.Context, cfg Config, tlsConfig *tls.Config) (
opt = append(opt, otlpmetricgrpc.WithInsecure())
}

if len(headers) > 0 {
opt = append(opt, otlpmetricgrpc.WithHeaders(headers))
}

if tlsConfig != nil {
opt = append(opt, otlpmetricgrpc.WithTLSCredentials(credentials.NewTLS(tlsConfig)))
}

return otlpmetricgrpc.New(ctx, opt...)
}

func parseHeaders(raw string) (map[string]string, error) {
headers := make(map[string]string)
for _, header := range strings.Split(raw, ",") {
rawKey, rawValue, ok := strings.Cut(header, "=")
if !ok {
return nil, fmt.Errorf("invalid header %q, expected format key=value", header)
}

key, err := url.PathUnescape(rawKey)
if err != nil {
return nil, fmt.Errorf("failed to unescape header key %q: %w", rawKey, err)
}

value, err := url.PathUnescape(rawValue)
if err != nil {
return nil, fmt.Errorf("failed to unescape header value %q: %w", rawValue, err)
}

headers[key] = value
}

return headers, nil
}

0 comments on commit 62ac853

Please sign in to comment.