Skip to content

Commit 8506809

Browse files
mx-psidmitryaxcrobert-1codeboten
authored
[otelcol] Preserve internal representation for components' configurations (#10897)
#### Description The custom unmarshalling for components copied a map without preserving the internal representation. This led to the issues mentioned on #10552 not being fully fixed (they were only fixed if they happened in the `service::telemetry` section for example). #### Link to tracking issue Fixes issues mentioned on #10552 #### Testing This adds one unit test at the `otelcol` level. Since we didn't catch this with our current `confmap/internal/e2e` tests, we likely also want to refactor those. --------- Co-authored-by: Dmitrii Anoshin <[email protected]> Co-authored-by: Curtis Robert <[email protected]> Co-authored-by: Alex Boten <[email protected]>
1 parent caae756 commit 8506809

File tree

3 files changed

+148
-2
lines changed

3 files changed

+148
-2
lines changed
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
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. otlpreceiver)
7+
component: otelcol
8+
9+
# A brief description of the change. Surround your text with quotes ("") if it needs to start with a backtick (`).
10+
note: Preserve internal representation when unmarshaling component configs
11+
12+
# One or more tracking issues or pull requests related to the change
13+
issues: [10552]
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+
20+
# Optional: The change log or logs in which this entry should be included.
21+
# e.g. '[user]' or '[user, api]'
22+
# Include 'user' if the change is relevant to end users.
23+
# Include 'api' if there is a change to a library API.
24+
# Default: '[user]'
25+
change_logs: []

otelcol/internal/configunmarshaler/configs.go

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,19 +31,25 @@ func (c *Configs[F]) Unmarshal(conf *confmap.Conf) error {
3131
// Prepare resulting map.
3232
c.cfgs = make(map[component.ID]component.Config)
3333
// Iterate over raw configs and create a config for each.
34-
for id, value := range rawCfgs {
34+
for id := range rawCfgs {
3535
// Find factory based on component kind and type that we read from config source.
3636
factory, ok := c.factories[id.Type()]
3737
if !ok {
3838
return errorUnknownType(id, maps.Keys(c.factories))
3939
}
4040

41+
// Get the configuration from the confmap.Conf to preserve internal representation.
42+
sub, err := conf.Sub(id.String())
43+
if err != nil {
44+
return errorUnmarshalError(id, err)
45+
}
46+
4147
// Create the default config for this component.
4248
cfg := factory.CreateDefaultConfig()
4349

4450
// Now that the default config struct is created we can Unmarshal into it,
4551
// and it will apply user-defined config on top of the default.
46-
if err := confmap.NewFromStringMap(value).Unmarshal(&cfg); err != nil {
52+
if err := sub.Unmarshal(&cfg); err != nil {
4753
return errorUnmarshalError(id, err)
4854
}
4955

otelcol/unmarshal_dry_run_test.go

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
// Copyright The OpenTelemetry Authors
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package otelcol
5+
6+
import (
7+
"context"
8+
"testing"
9+
10+
"github.com/stretchr/testify/require"
11+
12+
"go.opentelemetry.io/collector/component"
13+
"go.opentelemetry.io/collector/confmap"
14+
"go.opentelemetry.io/collector/receiver"
15+
)
16+
17+
var _ component.Config = (*Config)(nil)
18+
19+
type ValidateTestConfig struct {
20+
Number int `mapstructure:"number"`
21+
String string `mapstructure:"string"`
22+
}
23+
24+
var genericType component.Type = component.MustNewType("generic")
25+
26+
func NewFactories(_ *testing.T) func() (Factories, error) {
27+
return func() (Factories, error) {
28+
factories, err := nopFactories()
29+
if err != nil {
30+
return Factories{}, err
31+
}
32+
factories.Receivers[genericType] = receiver.NewFactory(
33+
genericType,
34+
func() component.Config {
35+
return &ValidateTestConfig{
36+
Number: 1,
37+
String: "default",
38+
}
39+
})
40+
41+
return factories, nil
42+
}
43+
}
44+
45+
var sampleYAMLConfig = `
46+
receivers:
47+
generic:
48+
number: ${mock:number}
49+
string: ${mock:number}
50+
51+
exporters:
52+
nop:
53+
54+
service:
55+
pipelines:
56+
traces:
57+
receivers: [generic]
58+
exporters: [nop]
59+
`
60+
61+
func TestDryRunWithExpandedValues(t *testing.T) {
62+
tests := []struct {
63+
name string
64+
yamlConfig string
65+
mockMap map[string]string
66+
expectErr bool
67+
}{
68+
{
69+
name: "string that looks like an integer",
70+
yamlConfig: sampleYAMLConfig,
71+
mockMap: map[string]string{
72+
"number": "123",
73+
},
74+
},
75+
{
76+
name: "string that looks like a bool",
77+
yamlConfig: sampleYAMLConfig,
78+
mockMap: map[string]string{
79+
"number": "true",
80+
},
81+
expectErr: true,
82+
},
83+
}
84+
85+
for _, tt := range tests {
86+
t.Run(tt.name, func(t *testing.T) {
87+
collector, err := NewCollector(CollectorSettings{
88+
Factories: NewFactories(t),
89+
ConfigProviderSettings: ConfigProviderSettings{
90+
ResolverSettings: confmap.ResolverSettings{
91+
URIs: []string{"file:file"},
92+
DefaultScheme: "mock",
93+
ProviderFactories: []confmap.ProviderFactory{
94+
newFakeProvider("mock", func(_ context.Context, uri string, _ confmap.WatcherFunc) (*confmap.Retrieved, error) {
95+
return confmap.NewRetrievedFromYAML([]byte(tt.mockMap[uri[len("mock:"):]]))
96+
}),
97+
newFakeProvider("file", func(_ context.Context, _ string, _ confmap.WatcherFunc) (*confmap.Retrieved, error) {
98+
return confmap.NewRetrievedFromYAML([]byte(tt.yamlConfig))
99+
}),
100+
},
101+
},
102+
},
103+
SkipSettingGRPCLogger: true,
104+
})
105+
require.NoError(t, err)
106+
107+
err = collector.DryRun(context.Background())
108+
if tt.expectErr {
109+
require.Error(t, err)
110+
return
111+
}
112+
require.NoError(t, err)
113+
})
114+
}
115+
}

0 commit comments

Comments
 (0)