Skip to content
Open
Show file tree
Hide file tree
Changes from 12 commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
2955df1
Allow list of name/value pairs for configgrpc.ClientConfig.Headers
jade-guiton-dd Oct 9, 2025
c933de4
Add generic internal/maplist package, and specialized configoptional.…
jade-guiton-dd Oct 10, 2025
80dd3ab
Merge branch 'main' into list-map-duality
jade-guiton-dd Oct 10, 2025
cab6953
gotidy
jade-guiton-dd Oct 10, 2025
10f7315
Fix otelcorecol
jade-guiton-dd Oct 10, 2025
38ce272
Add single changelog under 'all' component
jade-guiton-dd Oct 10, 2025
d3c25ab
Fix ocb test
jade-guiton-dd Oct 10, 2025
38b451d
Add 'maplist' to spellcheck list
jade-guiton-dd Oct 10, 2025
549da85
Enforce key unicity, add `Pairs` method, and more tests
jade-guiton-dd Oct 15, 2025
aa534ad
Better test coverage
jade-guiton-dd Oct 15, 2025
41a3893
Make API closer to native map to ease migration
jade-guiton-dd Oct 20, 2025
18d6730
tidy and lint
jade-guiton-dd Oct 20, 2025
5ba5118
Remove usages of MapListFromList
mx-psi Oct 21, 2025
6046148
Reformat MapList literals
jade-guiton-dd Oct 21, 2025
464ff75
Remove superfluous methods and specialize for configopaque.String
jade-guiton-dd Oct 21, 2025
fd2c22b
Merge branch 'main' into list-map-duality
jade-guiton-dd Oct 21, 2025
a0dd48d
tidy + checkapi
jade-guiton-dd Oct 21, 2025
31225c1
Fix test and minimize diff
jade-guiton-dd Oct 21, 2025
9fc0775
Use MapList without pointer
jade-guiton-dd Oct 22, 2025
0c39a29
tidy + fixes
jade-guiton-dd Oct 22, 2025
3ac2104
Make ResponseHeaders omitempty and fix e2e test
jade-guiton-dd Oct 22, 2025
417796e
Address review feedback
jade-guiton-dd Oct 23, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 38 additions & 0 deletions .chloggen/list-map-duality.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# Use this changelog template to create an entry for release notes.

# One of 'breaking', 'deprecation', 'new_component', 'enhancement', 'bug_fix'
change_type: 'breaking'

# The name of the component, or a single word describing the area of concern, (e.g. otlpreceiver)
component: all

# A brief description of the change. Surround your text with quotes ("") if it needs to start with a backtick (`).
note: Change type of `configgrpc.ClientConfig.Headers`, `confighttp.ClientConfig.Headers`, and `confighttp.ServerConfig.ResponseHeaders`

# One or more tracking issues or pull requests related to the change
issues: [13930]

# (Optional) One or more lines of additional information to render under the primary note.
# These lines will be padded with 2 spaces and then inserted directly into the document.
# Use pipe (|) for multiline entries.
subtext: |
`configopaque.MapList` is a new alternative to `map[string]configopaque.String` which can unmarshal
both maps and lists of name/value pairs.

For example, if `headers` is a field of type `configopaque.MapList`,
then the following YAML configs will unmarshal to the same thing:
```yaml
headers:
"foo": "bar"

headers:
- name: "foo"
value: "bar"
```

# Optional: The change log or logs in which this entry should be included.
# e.g. '[user]' or '[user, api]'
# Include 'user' if the change is relevant to end users.
# Include 'api' if there is a change to a library API.
# Default: '[user]'
change_logs: [api]
1 change: 1 addition & 0 deletions .github/workflows/utils/cspell.json
Original file line number Diff line number Diff line change
Expand Up @@ -288,6 +288,7 @@
"logstest",
"lycheeverse",
"mapconverter",
"maplist",
"mapstructure",
"marshalers",
"mdatagen",
Expand Down
1 change: 1 addition & 0 deletions cmd/builder/internal/builder/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ var replaceModules = []string{
"/extension/zpagesextension",
"/extension/xextension",
"/featuregate",
"/internal/maplist",
"/internal/memorylimiter",
"/internal/fanoutconsumer",
"/internal/sharedcomponent",
Expand Down
2 changes: 2 additions & 0 deletions cmd/mdatagen/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -203,3 +203,5 @@ replace go.opentelemetry.io/collector/pdata/xpdata => ../../pdata/xpdata
replace go.opentelemetry.io/collector/exporter/exporterhelper => ../../exporter/exporterhelper

replace go.opentelemetry.io/collector/service/telemetry/telemetrytest => ../../service/telemetry/telemetrytest

replace go.opentelemetry.io/collector/internal/maplist => ../../internal/maplist
1 change: 1 addition & 0 deletions cmd/otelcorecol/builder-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ replaces:
- go.opentelemetry.io/collector/extension/xextension => ../../extension/xextension
- go.opentelemetry.io/collector/extension/zpagesextension => ../../extension/zpagesextension
- go.opentelemetry.io/collector/featuregate => ../../featuregate
- go.opentelemetry.io/collector/internal/maplist => ../../internal/maplist
- go.opentelemetry.io/collector/internal/memorylimiter => ../../internal/memorylimiter
- go.opentelemetry.io/collector/internal/fanoutconsumer => ../../internal/fanoutconsumer
- go.opentelemetry.io/collector/internal/telemetry => ../../internal/telemetry
Expand Down
3 changes: 3 additions & 0 deletions cmd/otelcorecol/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@ require (
go.opentelemetry.io/collector/extension/xextension v0.137.0 // indirect
go.opentelemetry.io/collector/featuregate v1.43.0 // indirect
go.opentelemetry.io/collector/internal/fanoutconsumer v0.137.0 // indirect
go.opentelemetry.io/collector/internal/maplist v0.137.0 // indirect
go.opentelemetry.io/collector/internal/memorylimiter v0.137.0 // indirect
go.opentelemetry.io/collector/internal/sharedcomponent v0.137.0 // indirect
go.opentelemetry.io/collector/internal/telemetry v0.137.0 // indirect
Expand Down Expand Up @@ -282,6 +283,8 @@ replace go.opentelemetry.io/collector/extension/zpagesextension => ../../extensi

replace go.opentelemetry.io/collector/featuregate => ../../featuregate

replace go.opentelemetry.io/collector/internal/maplist => ../../internal/maplist

replace go.opentelemetry.io/collector/internal/memorylimiter => ../../internal/memorylimiter

replace go.opentelemetry.io/collector/internal/fanoutconsumer => ../../internal/fanoutconsumer
Expand Down
8 changes: 4 additions & 4 deletions config/configgrpc/configgrpc.go
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ type ClientConfig struct {
WaitForReady bool `mapstructure:"wait_for_ready,omitempty"`

// The headers associated with gRPC requests.
Headers map[string]configopaque.String `mapstructure:"headers,omitempty"`
Headers *configopaque.MapList `mapstructure:"headers,omitempty"`
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we can do without the * that would be better


// Sets the balancer in grpclb_policy to discover the servers. Default is pick_first.
// https://github.com/grpc/grpc-go/blob/master/examples/features/load_balancing/README.md
Expand Down Expand Up @@ -287,9 +287,9 @@ func (cc *ClientConfig) ToClientConn(
}

func (cc *ClientConfig) addHeadersIfAbsent(ctx context.Context) context.Context {
kv := make([]string, 0, 2*len(cc.Headers))
kv := make([]string, 0, 2*cc.Headers.Len())
existingMd, _ := metadata.FromOutgoingContext(ctx)
for k, v := range cc.Headers {
for k, v := range cc.Headers.Iter {
if len(existingMd.Get(k)) == 0 {
kv = append(kv, k, string(v))
}
Expand Down Expand Up @@ -376,7 +376,7 @@ func (cc *ClientConfig) getGrpcDialOptions(
// Enable OpenTelemetry observability plugin.
opts = append(opts, grpc.WithStatsHandler(otelgrpc.NewClientHandler(otelOpts...)))

if len(cc.Headers) > 0 {
if cc.Headers.Len() > 0 {
opts = append(opts,
grpc.WithUnaryInterceptor(func(ctx context.Context, method string, req, reply any, gcc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error {
return invoker(cc.addHeadersIfAbsent(ctx), method, req, reply, gcc, opts...)
Expand Down
20 changes: 10 additions & 10 deletions config/configgrpc/configgrpc_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -163,9 +163,9 @@ func TestAllGrpcClientSettings(t *testing.T) {
{
name: "test all with gzip compression",
settings: ClientConfig{
Headers: map[string]configopaque.String{
Headers: configopaque.MapListFromMap(map[string]configopaque.String{
"test": "test",
},
}),
Endpoint: "localhost:1234",
Compression: configcompression.TypeGzip,
TLS: configtls.ClientConfig{
Expand All @@ -192,9 +192,9 @@ func TestAllGrpcClientSettings(t *testing.T) {
{
name: "test all with snappy compression",
settings: ClientConfig{
Headers: map[string]configopaque.String{
Headers: configopaque.MapListFromMap(map[string]configopaque.String{
"test": "test",
},
}),
Endpoint: "localhost:1234",
Compression: configcompression.TypeSnappy,
TLS: configtls.ClientConfig{
Expand All @@ -221,9 +221,9 @@ func TestAllGrpcClientSettings(t *testing.T) {
{
name: "test all with zstd compression",
settings: ClientConfig{
Headers: map[string]configopaque.String{
Headers: configopaque.MapListFromMap(map[string]configopaque.String{
"test": "test",
},
}),
Endpoint: "localhost:1234",
Compression: configcompression.TypeZstd,
TLS: configtls.ClientConfig{
Expand Down Expand Up @@ -285,9 +285,9 @@ func TestHeaders(t *testing.T) {
TLS: configtls.ClientConfig{
Insecure: true,
},
Headers: map[string]configopaque.String{
Headers: configopaque.MapListFromMap(map[string]configopaque.String{
"testheader": "testvalue",
},
}),
})
require.NoError(t, errResp)
assert.NotNil(t, resp)
Expand Down Expand Up @@ -434,9 +434,9 @@ func TestGrpcServerAuthSettings(t *testing.T) {

func TestGrpcClientConfigInvalidBalancer(t *testing.T) {
settings := ClientConfig{
Headers: map[string]configopaque.String{
Headers: configopaque.MapListFromMap(map[string]configopaque.String{
"test": "test",
},
}),
Endpoint: "localhost:1234",
Compression: "gzip",
TLS: configtls.ClientConfig{
Expand Down
3 changes: 3 additions & 0 deletions config/configgrpc/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ require (
go.opentelemetry.io/collector/confmap v1.43.0 // indirect
go.opentelemetry.io/collector/confmap/xconfmap v0.137.0 // indirect
go.opentelemetry.io/collector/featuregate v1.43.0 // indirect
go.opentelemetry.io/collector/internal/maplist v0.137.0 // indirect
go.opentelemetry.io/collector/internal/telemetry v0.137.0 // indirect
go.opentelemetry.io/collector/pdata/pprofile v0.137.0 // indirect
go.opentelemetry.io/contrib/bridges/otelzap v0.13.0 // indirect
Expand Down Expand Up @@ -97,6 +98,8 @@ replace go.opentelemetry.io/collector/config/configmiddleware => ../configmiddle

replace go.opentelemetry.io/collector/extension/extensionmiddleware => ../../extension/extensionmiddleware

replace go.opentelemetry.io/collector/internal/maplist => ../../internal/maplist

replace go.opentelemetry.io/collector/pdata => ../../pdata

replace go.opentelemetry.io/collector/pdata/testdata => ../../pdata/testdata
Expand Down
12 changes: 6 additions & 6 deletions config/confighttp/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ type ClientConfig struct {
// Additional headers attached to each HTTP request sent by the client.
// Existing header values are overwritten if collision happens.
// Header values are opaque since they may be sensitive.
Headers map[string]configopaque.String `mapstructure:"headers,omitempty"`
Headers *configopaque.MapList `mapstructure:"headers,omitempty"`
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ditto


// Auth configuration for outgoing HTTP calls.
Auth configoptional.Optional[configauth.Config] `mapstructure:"auth,omitempty"`
Expand Down Expand Up @@ -130,7 +130,7 @@ func NewDefaultClientConfig() ClientConfig {
defaultTransport := http.DefaultTransport.(*http.Transport)

return ClientConfig{
Headers: map[string]configopaque.String{},
Headers: configopaque.NewMapList(),
MaxIdleConns: defaultTransport.MaxIdleConns,
IdleConnTimeout: defaultTransport.IdleConnTimeout,
ForceAttemptHTTP2: true,
Expand Down Expand Up @@ -235,7 +235,7 @@ func (cc *ClientConfig) ToClient(ctx context.Context, host component.Host, setti
}
}

if len(cc.Headers) > 0 {
if cc.Headers.Len() > 0 {
clientTransport = &headerRoundTripper{
transport: clientTransport,
headers: cc.Headers,
Expand Down Expand Up @@ -283,18 +283,18 @@ func (cc *ClientConfig) ToClient(ctx context.Context, host component.Host, setti
// Custom RoundTripper that adds headers.
type headerRoundTripper struct {
transport http.RoundTripper
headers map[string]configopaque.String
headers *configopaque.MapList
}

// RoundTrip is a custom RoundTripper that adds headers to the request.
func (interceptor *headerRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
// Set Host header if provided
hostHeader, found := interceptor.headers["Host"]
hostHeader, found := interceptor.headers.TryGet("Host")
if found && hostHeader != "" {
// `Host` field should be set to override default `Host` header value which is Endpoint
req.Host = string(hostHeader)
}
for k, v := range interceptor.headers {
for k, v := range interceptor.headers.Iter {
req.Header.Set(k, string(v))
}

Expand Down
10 changes: 5 additions & 5 deletions config/confighttp/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -429,7 +429,7 @@ func TestHTTPClientSettingWithAuthConfig(t *testing.T) {
settings: ClientConfig{
Endpoint: "localhost:1234",
Auth: configoptional.Some(configauth.Config{AuthenticatorID: mockID}),
Headers: map[string]configopaque.String{"foo": "bar"},
Headers: configopaque.MapListFromMap(map[string]configopaque.String{"foo": "bar"}),
},
shouldErr: false,
host: &mockHost{
Expand Down Expand Up @@ -530,7 +530,7 @@ func TestHttpClientHeaders(t *testing.T) {
ReadBufferSize: 0,
WriteBufferSize: 0,
Timeout: 0,
Headers: tt.headers,
Headers: configopaque.MapListFromMap(tt.headers),
}
client, _ := setting.ToClient(context.Background(), componenttest.NewNopHost(), componenttest.NewNopTelemetrySettings())
req, err := http.NewRequest(http.MethodGet, setting.Endpoint, http.NoBody)
Expand Down Expand Up @@ -566,7 +566,7 @@ func TestHttpClientHostHeader(t *testing.T) {
ReadBufferSize: 0,
WriteBufferSize: 0,
Timeout: 0,
Headers: tt.headers,
Headers: configopaque.MapListFromMap(tt.headers),
}
client, _ := setting.ToClient(context.Background(), componenttest.NewNopHost(), componenttest.NewNopTelemetrySettings())
req, err := http.NewRequest(http.MethodGet, setting.Endpoint, http.NoBody)
Expand Down Expand Up @@ -724,10 +724,10 @@ func TestClientUnmarshalYAMLComprehensiveConfig(t *testing.T) {
assert.Equal(t, "example.com", clientConfig.TLS.ServerName)

// Verify headers
expectedHeaders := map[string]configopaque.String{
expectedHeaders := configopaque.MapListFromMap(map[string]configopaque.String{
"User-Agent": "OpenTelemetry-Collector/1.0",
"X-Custom-Header": "custom-value",
}
})
assert.Equal(t, expectedHeaders, clientConfig.Headers)

// Verify middlewares
Expand Down
3 changes: 3 additions & 0 deletions config/confighttp/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ require (
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
go.opentelemetry.io/collector/confmap v1.43.0
go.opentelemetry.io/collector/confmap/xconfmap v0.137.0
go.opentelemetry.io/collector/internal/maplist v0.137.0 // indirect
go.opentelemetry.io/collector/internal/telemetry v0.137.0 // indirect
go.opentelemetry.io/collector/pdata v1.43.0 // indirect
go.opentelemetry.io/contrib/bridges/otelzap v0.13.0 // indirect
Expand Down Expand Up @@ -104,6 +105,8 @@ replace go.opentelemetry.io/collector/client => ../../client

replace go.opentelemetry.io/collector/extension/extensionauth/extensionauthtest => ../../extension/extensionauth/extensionauthtest

replace go.opentelemetry.io/collector/internal/maplist => ../../internal/maplist

replace go.opentelemetry.io/collector/internal/telemetry => ../../internal/telemetry

replace go.opentelemetry.io/collector/pipeline => ../../pipeline
Expand Down
8 changes: 4 additions & 4 deletions config/confighttp/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ type ServerConfig struct {

// Additional headers attached to each HTTP response sent to the client.
// Header values are opaque since they may be sensitive.
ResponseHeaders map[string]configopaque.String `mapstructure:"response_headers"`
ResponseHeaders *configopaque.MapList `mapstructure:"response_headers,omitempty"`

// CompressionAlgorithms configures the list of compression algorithms the server can accept. Default: ["", "gzip", "zstd", "zlib", "snappy", "deflate"]
CompressionAlgorithms []string `mapstructure:"compression_algorithms,omitempty"`
Expand Down Expand Up @@ -102,7 +102,7 @@ type ServerConfig struct {
// We encourage to use this function to create an object of ServerConfig.
func NewDefaultServerConfig() ServerConfig {
return ServerConfig{
ResponseHeaders: map[string]configopaque.String{},
ResponseHeaders: configopaque.NewMapList(),
WriteTimeout: 30 * time.Second,
ReadHeaderTimeout: 1 * time.Minute,
IdleTimeout: 1 * time.Minute,
Expand Down Expand Up @@ -287,11 +287,11 @@ func (sc *ServerConfig) ToServer(ctx context.Context, host component.Host, setti
return server, err
}

func responseHeadersHandler(handler http.Handler, headers map[string]configopaque.String) http.Handler {
func responseHeadersHandler(handler http.Handler, headers *configopaque.MapList) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
h := w.Header()

for k, v := range headers {
for k, v := range headers.Iter {
h.Set(k, string(v))
}

Expand Down
6 changes: 3 additions & 3 deletions config/confighttp/server_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -451,7 +451,7 @@ func TestHttpServerHeaders(t *testing.T) {
t.Run(tt.name, func(t *testing.T) {
sc := &ServerConfig{
Endpoint: "localhost:0",
ResponseHeaders: tt.headers,
ResponseHeaders: configopaque.MapListFromMap(tt.headers),
}

ln, err := sc.ToListener(context.Background())
Expand Down Expand Up @@ -1138,10 +1138,10 @@ func TestServerUnmarshalYAMLComprehensiveConfig(t *testing.T) {
assert.Equal(t, 7200, serverConfig.CORS.Get().MaxAge)

// Verify response headers
expectedResponseHeaders := map[string]configopaque.String{
expectedResponseHeaders := configopaque.MapListFromMap(map[string]configopaque.String{
"Server": "OpenTelemetry-Collector",
"X-Flavor": "apple",
}
})
assert.Equal(t, expectedResponseHeaders, serverConfig.ResponseHeaders)

// Verify compression algorithms
Expand Down
3 changes: 3 additions & 0 deletions config/confighttp/xconfighttp/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ require (
go.opentelemetry.io/collector/extension/extensionauth v1.43.0 // indirect
go.opentelemetry.io/collector/extension/extensionmiddleware v0.137.0 // indirect
go.opentelemetry.io/collector/featuregate v1.43.0 // indirect
go.opentelemetry.io/collector/internal/maplist v0.137.0 // indirect
go.opentelemetry.io/collector/internal/telemetry v0.137.0 // indirect
go.opentelemetry.io/collector/pdata v1.43.0 // indirect
go.opentelemetry.io/contrib/bridges/otelzap v0.13.0 // indirect
Expand Down Expand Up @@ -114,3 +115,5 @@ replace go.opentelemetry.io/collector/extension/extensionmiddleware/extensionmid
replace go.opentelemetry.io/collector/confmap => ../../../confmap

replace go.opentelemetry.io/collector/confmap/xconfmap => ../../../confmap/xconfmap

replace go.opentelemetry.io/collector/internal/maplist => ../../../internal/maplist
Loading
Loading