diff --git a/CHANGELOG.md b/CHANGELOG.md index 78089b67694..0152cfb21c0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm ### Added - Add support for configuring `ClientCertificate` and `ClientKey` field for OTLP exporters in `go.opentelemetry.io/contrib/config`. (#6378) +- Add support for configuring `HeadersList` field for OTLP exporters in `go.opentelemetry.io/contrib/config`. (TODO) ### Fixed diff --git a/config/v0.3.0/config.go b/config/v0.3.0/config.go index b4097ac2552..14d01448c49 100644 --- a/config/v0.3.0/config.go +++ b/config/v0.3.0/config.go @@ -13,6 +13,7 @@ import ( "gopkg.in/yaml.v3" + "go.opentelemetry.io/otel/baggage" "go.opentelemetry.io/otel/log" nooplog "go.opentelemetry.io/otel/log/noop" "go.opentelemetry.io/otel/metric" @@ -152,14 +153,6 @@ func ParseYAML(file []byte) (*OpenTelemetryConfiguration, error) { return &cfg, nil } -func toStringMap(pairs []NameStringValuePair) map[string]string { - output := make(map[string]string) - for _, v := range pairs { - output[v.Name] = *v.Value - } - return output -} - // createTLSConfig creates a tls.Config from certificate files. func createTLSConfig(caCertFile *string, clientCertFile *string, clientKeyFile *string) (*tls.Config, error) { tlsConfig := &tls.Config{} @@ -186,3 +179,26 @@ func createTLSConfig(caCertFile *string, clientCertFile *string, clientKeyFile * } return tlsConfig, nil } + +// createHeadersConfig combines the two header config fields and returns a map[string]string. +func createHeadersConfig(headers []NameStringValuePair, headersList *string) (map[string]string, error) { + result := make(map[string]string) + if headersList != nil { + headerslist, err := baggage.Parse(*headersList) + if err != nil { + return nil, fmt.Errorf("invalid headers list: %w", err) + } + for _, kv := range headerslist.Members() { + result[kv.Key()] = kv.Value() + } + } + // Headers take precedence over HeadersList, so this has to be after HeadersList is processed + if len(headers) > 0 { + for _, kv := range headers { + if kv.Value != nil { + result[kv.Name] = *kv.Value + } + } + } + return result, nil +} diff --git a/config/v0.3.0/config_test.go b/config/v0.3.0/config_test.go index 868d928e3df..b89a4819c59 100644 --- a/config/v0.3.0/config_test.go +++ b/config/v0.3.0/config_test.go @@ -568,6 +568,99 @@ func TestCreateTLSConfig(t *testing.T) { } } +func TestCreateHeadersConfig(t *testing.T) { + tests := []struct { + name string + headers []NameStringValuePair + headersList *string + wantHeaders map[string]string + wantErr string + }{ + { + name: "no headers", + headers: []NameStringValuePair{}, + headersList: nil, + wantHeaders: map[string]string{}, + }, + { + name: "headerslist only", + headers: []NameStringValuePair{}, + headersList: ptr("a=b,c=d"), + wantHeaders: map[string]string{ + "a": "b", + "c": "d", + }, + }, + { + name: "headers only", + headers: []NameStringValuePair{ + { + Name: "a", + Value: ptr("b"), + }, + { + Name: "c", + Value: ptr("d"), + }, + }, + headersList: nil, + wantHeaders: map[string]string{ + "a": "b", + "c": "d", + }, + }, + { + name: "both headers and headerslist", + headers: []NameStringValuePair{ + { + Name: "a", + Value: ptr("b"), + }, + }, + headersList: ptr("c=d"), + wantHeaders: map[string]string{ + "a": "b", + "c": "d", + }, + }, + { + name: "headers supercedes headerslist", + headers: []NameStringValuePair{ + { + Name: "a", + Value: ptr("b"), + }, + { + Name: "c", + Value: ptr("override"), + }, + }, + headersList: ptr("c=d"), + wantHeaders: map[string]string{ + "a": "b", + "c": "override", + }, + }, + { + name: "invalid headerslist", + headersList: ptr("==="), + wantErr: "invalid headers list: invalid key: \"\"", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + headersMap, err := createHeadersConfig(tt.headers, tt.headersList) + if tt.wantErr != "" { + require.Error(t, err) + require.Equal(t, tt.wantErr, err.Error()) + } else { + require.NoError(t, err) + } + require.Equal(t, tt.wantHeaders, headersMap) + }) + } +} + func ptr[T any](v T) *T { return &v } diff --git a/config/v0.3.0/log.go b/config/v0.3.0/log.go index bca5d235594..d4bdc2df1d9 100644 --- a/config/v0.3.0/log.go +++ b/config/v0.3.0/log.go @@ -152,8 +152,12 @@ func otlpHTTPLogExporter(ctx context.Context, otlpConfig *OTLP) (sdklog.Exporter if otlpConfig.Timeout != nil && *otlpConfig.Timeout > 0 { opts = append(opts, otlploghttp.WithTimeout(time.Millisecond*time.Duration(*otlpConfig.Timeout))) } - if len(otlpConfig.Headers) > 0 { - opts = append(opts, otlploghttp.WithHeaders(toStringMap(otlpConfig.Headers))) + headersConfig, err := createHeadersConfig(otlpConfig.Headers, otlpConfig.HeadersList) + if err != nil { + return nil, err + } + if len(headersConfig) > 0 { + opts = append(opts, otlploghttp.WithHeaders(headersConfig)) } tlsConfig, err := createTLSConfig(otlpConfig.Certificate, otlpConfig.ClientCertificate, otlpConfig.ClientKey) @@ -200,8 +204,12 @@ func otlpGRPCLogExporter(ctx context.Context, otlpConfig *OTLP) (sdklog.Exporter if otlpConfig.Timeout != nil && *otlpConfig.Timeout > 0 { opts = append(opts, otlploggrpc.WithTimeout(time.Millisecond*time.Duration(*otlpConfig.Timeout))) } - if len(otlpConfig.Headers) > 0 { - opts = append(opts, otlploggrpc.WithHeaders(toStringMap(otlpConfig.Headers))) + headersConfig, err := createHeadersConfig(otlpConfig.Headers, otlpConfig.HeadersList) + if err != nil { + return nil, err + } + if len(headersConfig) > 0 { + opts = append(opts, otlploggrpc.WithHeaders(headersConfig)) } tlsConfig, err := createTLSConfig(otlpConfig.Certificate, otlpConfig.ClientCertificate, otlpConfig.ClientKey) diff --git a/config/v0.3.0/metric.go b/config/v0.3.0/metric.go index b7d68106b43..d8af381845d 100644 --- a/config/v0.3.0/metric.go +++ b/config/v0.3.0/metric.go @@ -166,8 +166,12 @@ func otlpHTTPMetricExporter(ctx context.Context, otlpConfig *OTLPMetric) (sdkmet if otlpConfig.Timeout != nil { opts = append(opts, otlpmetrichttp.WithTimeout(time.Millisecond*time.Duration(*otlpConfig.Timeout))) } - if len(otlpConfig.Headers) > 0 { - opts = append(opts, otlpmetrichttp.WithHeaders(toStringMap(otlpConfig.Headers))) + headersConfig, err := createHeadersConfig(otlpConfig.Headers, otlpConfig.HeadersList) + if err != nil { + return nil, err + } + if len(headersConfig) > 0 { + opts = append(opts, otlpmetrichttp.WithHeaders(headersConfig)) } if otlpConfig.TemporalityPreference != nil { switch *otlpConfig.TemporalityPreference { @@ -227,8 +231,12 @@ func otlpGRPCMetricExporter(ctx context.Context, otlpConfig *OTLPMetric) (sdkmet if otlpConfig.Timeout != nil && *otlpConfig.Timeout > 0 { opts = append(opts, otlpmetricgrpc.WithTimeout(time.Millisecond*time.Duration(*otlpConfig.Timeout))) } - if len(otlpConfig.Headers) > 0 { - opts = append(opts, otlpmetricgrpc.WithHeaders(toStringMap(otlpConfig.Headers))) + headersConfig, err := createHeadersConfig(otlpConfig.Headers, otlpConfig.HeadersList) + if err != nil { + return nil, err + } + if len(headersConfig) > 0 { + opts = append(opts, otlpmetricgrpc.WithHeaders(headersConfig)) } if otlpConfig.TemporalityPreference != nil { switch *otlpConfig.TemporalityPreference { diff --git a/config/v0.3.0/trace.go b/config/v0.3.0/trace.go index 498686e15f5..03ed3ed223d 100644 --- a/config/v0.3.0/trace.go +++ b/config/v0.3.0/trace.go @@ -125,8 +125,12 @@ func otlpGRPCSpanExporter(ctx context.Context, otlpConfig *OTLP) (sdktrace.SpanE if otlpConfig.Timeout != nil && *otlpConfig.Timeout > 0 { opts = append(opts, otlptracegrpc.WithTimeout(time.Millisecond*time.Duration(*otlpConfig.Timeout))) } - if len(otlpConfig.Headers) > 0 { - opts = append(opts, otlptracegrpc.WithHeaders(toStringMap(otlpConfig.Headers))) + headersConfig, err := createHeadersConfig(otlpConfig.Headers, otlpConfig.HeadersList) + if err != nil { + return nil, err + } + if len(headersConfig) > 0 { + opts = append(opts, otlptracegrpc.WithHeaders(headersConfig)) } tlsConfig, err := createTLSConfig(otlpConfig.Certificate, otlpConfig.ClientCertificate, otlpConfig.ClientKey) @@ -168,8 +172,12 @@ func otlpHTTPSpanExporter(ctx context.Context, otlpConfig *OTLP) (sdktrace.SpanE if otlpConfig.Timeout != nil && *otlpConfig.Timeout > 0 { opts = append(opts, otlptracehttp.WithTimeout(time.Millisecond*time.Duration(*otlpConfig.Timeout))) } - if len(otlpConfig.Headers) > 0 { - opts = append(opts, otlptracehttp.WithHeaders(toStringMap(otlpConfig.Headers))) + headersConfig, err := createHeadersConfig(otlpConfig.Headers, otlpConfig.HeadersList) + if err != nil { + return nil, err + } + if len(headersConfig) > 0 { + opts = append(opts, otlptracehttp.WithHeaders(headersConfig)) } tlsConfig, err := createTLSConfig(otlpConfig.Certificate, otlpConfig.ClientCertificate, otlpConfig.ClientKey)