Skip to content

Commit 30bd539

Browse files
authored
Merge branch 'main' into ci/_rerun
2 parents 43e1ec1 + 246e428 commit 30bd539

File tree

5 files changed

+182
-8
lines changed

5 files changed

+182
-8
lines changed
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
# Use this changelog template to create an entry for release notes.
2+
3+
# One of 'breaking', 'deprecation', 'new_component', 'enhancement', 'bug_fix'
4+
change_type: 'bug_fix'
5+
6+
# The name of the component, or a single word describing the area of concern, (e.g. receiver/otlp)
7+
component: pkg/confmap
8+
9+
# A brief description of the change. Surround your text with quotes ("") if it needs to start with a backtick (`).
10+
note: 'Fix an issue where configs could fail to decode when using interpolated values in string fields.'
11+
12+
# One or more tracking issues or pull requests related to the change
13+
issues: [14413]
14+
15+
# (Optional) One or more lines of additional information to render under the primary note.
16+
# These lines will be padded with 2 spaces and then inserted directly into the document.
17+
# Use pipe (|) for multiline entries.
18+
subtext: |
19+
For example, a header can be set via an environment variable to a string that is parseable as a number, e.g. `1234`
20+
21+
# Optional: The change log or logs in which this entry should be included.
22+
# e.g. '[user]' or '[user, api]'
23+
# Include 'user' if the change is relevant to end users.
24+
# Include 'api' if there is a change to a library API.
25+
# Default: '[user]'
26+
change_logs: []

confmap/internal/decoder.go

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -93,14 +93,17 @@ func useExpandValue() mapstructure.DecodeHookFuncType {
9393
return v, nil
9494
}
9595

96-
switch to.Kind() {
97-
case reflect.Array, reflect.Slice, reflect.Map:
98-
if isStringyStructure(to) {
99-
// If the target field is a stringy structure, sanitize to use the original string value everywhere.
100-
return sanitizeToStr(data), nil
96+
if !NewExpandedValueSanitizer.IsEnabled() {
97+
switch to.Kind() {
98+
case reflect.Array, reflect.Slice, reflect.Map:
99+
if isStringyStructure(to) {
100+
// If the target field is a stringy structure, sanitize to use the original string value everywhere.
101+
return sanitizeToStr(data), nil
102+
}
103+
104+
// Otherwise, sanitize to use the parsed value everywhere.
105+
return sanitize(data), nil
101106
}
102-
// Otherwise, sanitize to use the parsed value everywhere.
103-
return sanitize(data), nil
104107
}
105108
return data, nil
106109
}

confmap/internal/e2e/go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ require (
88
go.opentelemetry.io/collector/confmap v1.50.0
99
go.opentelemetry.io/collector/confmap/provider/envprovider v1.50.0
1010
go.opentelemetry.io/collector/confmap/provider/fileprovider v1.50.0
11+
go.opentelemetry.io/collector/featuregate v1.50.0
1112
)
1213

1314
require (
@@ -22,7 +23,6 @@ require (
2223
github.com/mitchellh/reflectwalk v1.0.2 // indirect
2324
github.com/pmezard/go-difflib v1.0.0 // indirect
2425
go.opentelemetry.io/collector/confmap/xconfmap v0.144.0 // indirect
25-
go.opentelemetry.io/collector/featuregate v1.50.0 // indirect
2626
go.uber.org/multierr v1.11.0 // indirect
2727
go.uber.org/zap v1.27.1 // indirect
2828
go.yaml.in/yaml/v3 v3.0.4 // indirect
Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
// Copyright The OpenTelemetry Authors
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package e2etest
5+
6+
import (
7+
"testing"
8+
9+
"github.com/stretchr/testify/require"
10+
11+
"go.opentelemetry.io/collector/config/configopaque"
12+
"go.opentelemetry.io/collector/confmap"
13+
"go.opentelemetry.io/collector/confmap/internal"
14+
"go.opentelemetry.io/collector/featuregate"
15+
)
16+
17+
type testHeadersConfig struct {
18+
Headers configopaque.MapList `mapstructure:"headers"`
19+
}
20+
21+
// TestMapListWithExpandedValue tests that MapList can handle ExpandedValue
22+
// from environment variable expansion
23+
func TestMapListWithExpandedValue(t *testing.T) {
24+
// Simulate what happens when ${env:TOKEN} is expanded
25+
// The confmap will contain an ExpandedValue instead of a plain string
26+
data := map[string]any{
27+
"headers": []any{
28+
map[string]any{
29+
"name": "Authorization",
30+
"value": internal.ExpandedValue{
31+
Value: "Bearer secret-token",
32+
Original: "Bearer secret-token",
33+
},
34+
},
35+
},
36+
}
37+
38+
conf := confmap.NewFromStringMap(data)
39+
var tc testHeadersConfig
40+
err := conf.Unmarshal(&tc)
41+
require.NoError(t, err)
42+
43+
val, ok := tc.Headers.Get("Authorization")
44+
require.True(t, ok)
45+
require.Equal(t, configopaque.String("Bearer secret-token"), val)
46+
}
47+
48+
// TestMapListWithExpandedValueIntValue tests an ExpandedValue with an integer Value
49+
func TestMapListWithExpandedValueIntValue(t *testing.T) {
50+
// Simulate what happens when expanding a value that parses as an int
51+
data := map[string]any{
52+
"headers": []any{
53+
map[string]any{
54+
"name": "X-Port",
55+
"value": internal.ExpandedValue{
56+
Value: 8080, // Value is parsed as int
57+
Original: "8080", // Original is string
58+
},
59+
},
60+
},
61+
}
62+
63+
originalState := internal.NewExpandedValueSanitizer.IsEnabled()
64+
defer func() {
65+
require.NoError(t, featuregate.GlobalRegistry().Set(internal.NewExpandedValueSanitizer.ID(), originalState))
66+
}()
67+
68+
require.NoError(t, featuregate.GlobalRegistry().Set(internal.NewExpandedValueSanitizer.ID(), true))
69+
70+
conf := confmap.NewFromStringMap(data)
71+
var tc testHeadersConfig
72+
err := conf.Unmarshal(&tc)
73+
require.NoError(t, err)
74+
75+
val, ok := tc.Headers.Get("X-Port")
76+
require.True(t, ok)
77+
require.Equal(t, configopaque.String("8080"), val)
78+
79+
require.NoError(t, featuregate.GlobalRegistry().Set(internal.NewExpandedValueSanitizer.ID(), false))
80+
81+
// This will fail because when reverting to old behavior, ExpandedValues get decoded at collection time and doesn't
82+
// take struct collections into account.
83+
err = conf.Unmarshal(&tc)
84+
require.Error(t, err)
85+
}
86+
87+
// TestDirectConfigopaqueStringWithExpandedValueIntValue tests that direct unmarshaling works
88+
func TestDirectConfigopaqueStringWithExpandedValueIntValue(t *testing.T) {
89+
type testConfig struct {
90+
Value configopaque.String `mapstructure:"value"`
91+
}
92+
93+
// Direct configopaque.String field (not in a map/slice structure)
94+
data := map[string]any{
95+
"value": internal.ExpandedValue{
96+
Value: 8080,
97+
Original: "8080",
98+
},
99+
}
100+
101+
conf := confmap.NewFromStringMap(data)
102+
var tc testConfig
103+
err := conf.Unmarshal(&tc)
104+
// This should work because useExpandValue detects the target is a string
105+
require.NoError(t, err)
106+
require.Equal(t, configopaque.String("8080"), tc.Value)
107+
}
108+
109+
// TestStringyStructureWithExpandedValue tests the isStringyStructure path in useExpandValue
110+
func TestStringyStructureWithExpandedValue(t *testing.T) {
111+
type testConfig struct {
112+
Tags []string `mapstructure:"tags"`
113+
}
114+
115+
data := map[string]any{
116+
"tags": []any{
117+
internal.ExpandedValue{
118+
Value: 8080,
119+
Original: "8080",
120+
},
121+
},
122+
}
123+
124+
originalState := internal.NewExpandedValueSanitizer.IsEnabled()
125+
defer func() {
126+
require.NoError(t, featuregate.GlobalRegistry().Set(internal.NewExpandedValueSanitizer.ID(), originalState))
127+
}()
128+
129+
// With feature gate disabled, useExpandValue should detect []string as stringy
130+
require.NoError(t, featuregate.GlobalRegistry().Set(internal.NewExpandedValueSanitizer.ID(), false))
131+
132+
conf := confmap.NewFromStringMap(data)
133+
var tc testConfig
134+
err := conf.Unmarshal(&tc)
135+
require.NoError(t, err)
136+
require.Equal(t, []string{"8080"}, tc.Tags)
137+
}

confmap/internal/featuregates.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,11 @@ var EnableMergeAppendOption = featuregate.GlobalRegistry().MustRegister(
1212
featuregate.WithRegisterDescription("Combines lists when resolving configs from different sources. This feature gate will not be stabilized 'as is'; the current behavior will remain the default."),
1313
featuregate.WithRegisterReferenceURL("https://github.com/open-telemetry/opentelemetry-collector/issues/8754"),
1414
)
15+
16+
var NewExpandedValueSanitizer = featuregate.GlobalRegistry().MustRegister(
17+
"confmap.newExpandedValueSanitizer",
18+
featuregate.StageBeta,
19+
featuregate.WithRegisterFromVersion("v0.144.0"),
20+
featuregate.WithRegisterDescription("Fixes some types of decoding errors where environment variables are parsed as non-string types but assigned to string fields."),
21+
featuregate.WithRegisterReferenceURL("https://github.com/open-telemetry/opentelemetry-collector/pull/14413"),
22+
)

0 commit comments

Comments
 (0)