Skip to content

Commit 6e82944

Browse files
authored
[confmap] - new feature flag for append merging strategy (#12097)
Koanf's default merging strategy currently overrides static values such as slices, numbers, and strings. However, lists of components should be treated as a special case. This pull request introduces a new command line option to allow for merging lists instead of discarding the existing ones. With this new merging strategy: - All the lists are merged rather than replaced. - The merging logic is name-aware, meaning that if components with the same name appear in both lists, they will only appear once in the final merged list. <!-- Issue number if applicable --> #### Link to tracking issue Related issues: - #8754 - #8394 - #10370 <!--Describe what testing was performed and which tests were added.--> #### Testing - Added <!--Describe the documentation added.--> #### Documentation - Added under readme. ##### Example Consider the following configs, ```yaml # main.yaml receivers: otlp/in: processors: batch: exporters: otlp/out: extensions: file_storage: service: pipelines: traces: receivers: [ otlp/in ] processors: [ batch ] exporters: [ otlp/out ] extensions: [ file_storage ] ``` ```yaml # extra_extension.yaml extensions: healthcheckv2: service: extensions: [ healthcheckv2 ] ``` If you run the collector with following command, ``` otelcol --config=main.yaml --config=extra_extension.yaml --feature-gates=-confmap.enableMergeAppendOption ``` then the final configuration after config resolution will look like following: ```yaml # main.yaml receivers: otlp/in: processors: batch: exporters: otlp/out: extensions: file_storage: healthcheckv2: service: pipelines: traces: receivers: [ otlp/in ] processors: [ batch ] exporters: [ otlp/out ] extensions: [ file_storage, healthcheckv2 ] ``` For backward compatibly, the default behaviour is **not** to merge lists. Users who want to explicitly merge lists can enable the command line option. Note: I’d appreciate your feedback on this 🙏
1 parent 72878b7 commit 6e82944

File tree

48 files changed

+1286
-10
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

48 files changed

+1286
-10
lines changed

.chloggen/new-merge-strategy.yaml

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
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: enhancement
5+
6+
# The name of the component, or a single word describing the area of concern, (e.g. otlpreceiver)
7+
component: confmap
8+
9+
# A brief description of the change. Surround your text with quotes ("") if it needs to start with a backtick (`).
10+
note: Introduce a new feature flag to allow for merging lists instead of discarding the existing ones.
11+
12+
# One or more tracking issues or pull requests related to the change
13+
issues: [8394, 8754, 10370]
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+
You can enable this option via the command line by running following command:
20+
otelcol --config=main.yaml --config=extra_config.yaml --feature-gates=-confmap.enableMergeAppendOption
21+
22+
# Optional: The change log or logs in which this entry should be included.
23+
# e.g. '[user]' or '[user, api]'
24+
# Include 'user' if the change is relevant to end users.
25+
# Include 'api' if there is a change to a library API.
26+
# Default: '[user]'
27+
change_logs: []

cmd/mdatagen/go.mod

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ require (
3636
github.com/go-viper/mapstructure/v2 v2.2.1 // indirect
3737
github.com/gogo/protobuf v1.3.2 // indirect
3838
github.com/google/uuid v1.6.0 // indirect
39+
github.com/hashicorp/go-version v1.7.0 // indirect
3940
github.com/inconshreveable/mousetrap v1.1.0 // indirect
4041
github.com/json-iterator/go v1.1.12 // indirect
4142
github.com/knadh/koanf/maps v0.1.1 // indirect
@@ -51,6 +52,7 @@ require (
5152
go.opentelemetry.io/collector/component/componentstatus v0.121.0 // indirect
5253
go.opentelemetry.io/collector/consumer/consumererror v0.121.0 // indirect
5354
go.opentelemetry.io/collector/consumer/xconsumer v0.121.0 // indirect
55+
go.opentelemetry.io/collector/featuregate v1.26.0 // indirect
5456
go.opentelemetry.io/collector/pdata/pprofile v0.121.0 // indirect
5557
go.opentelemetry.io/collector/pdata/testdata v0.121.0 // indirect
5658
go.opentelemetry.io/collector/pipeline v0.121.0 // indirect
@@ -118,3 +120,5 @@ replace go.opentelemetry.io/collector/consumer/consumererror => ../../consumer/c
118120
replace go.opentelemetry.io/collector/scraper => ../../scraper
119121

120122
replace go.opentelemetry.io/collector/scraper/scrapertest => ../../scraper/scrapertest
123+
124+
replace go.opentelemetry.io/collector/featuregate => ../../featuregate

cmd/mdatagen/go.sum

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

confmap/README.md

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,91 @@ The `Resolve` method proceeds in the following steps:
9393
4. For each "Converter", call "Convert" for the "result".
9494
5. Return the "result", aka effective, configuration.
9595

96+
#### (Experimental) Append merging strategy for lists
97+
98+
You can opt-in to experimentally combine slices instead of discarding the existing ones by enabling the `confmap.enableMergeAppendOption` feature flag. Lists are appended in the order in which they appear in their configuration sources.
99+
This will **not** become the default in the future, we are still deciding how this should be configured and want your feedback on [this issue](https://github.com/open-telemetry/opentelemetry-collector/issues/8754).
100+
101+
##### Example
102+
Consider the following configs,
103+
104+
```yaml
105+
# main.yaml
106+
receivers:
107+
otlp/in:
108+
processors:
109+
attributes/example:
110+
actions:
111+
- key: key
112+
value: "value"
113+
action: upsert
114+
115+
exporters:
116+
otlp/out:
117+
extensions:
118+
file_storage:
119+
120+
service:
121+
pipelines:
122+
traces:
123+
receivers: [ otlp/in ]
124+
processors: [ attributes/example ]
125+
exporters: [ otlp/out ]
126+
extensions: [ file_storage ]
127+
```
128+
129+
130+
```yaml
131+
# extra_extension.yaml
132+
processors:
133+
batch:
134+
extensions:
135+
healthcheckv2:
136+
137+
service:
138+
extensions: [ healthcheckv2 ]
139+
pipelines:
140+
traces:
141+
processors: [ batch ]
142+
```
143+
144+
If you run the Collector with following command,
145+
```
146+
otelcol --config=main.yaml --config=extra_extension.yaml --feature-gates=confmap.enableMergeAppendOption
147+
```
148+
then the final configuration after config resolution will look like following:
149+
150+
```yaml
151+
# main.yaml
152+
receivers:
153+
otlp/in:
154+
processors:
155+
attributes/example:
156+
actions:
157+
- key: key
158+
value: "value"
159+
action: upsert
160+
batch:
161+
exporters:
162+
otlp/out:
163+
extensions:
164+
file_storage:
165+
healthcheckv2:
166+
167+
service:
168+
pipelines:
169+
traces:
170+
receivers: [ otlp/in ]
171+
processors: [ attributes/example, batch ]
172+
exporters: [ otlp/out ]
173+
extensions: [ file_storage, healthcheckv2 ]
174+
```
175+
176+
Notice that the `service::extensions` list is a combination of both configurations. By default, the value of the last configuration source passed, `extra_extension`, would be used, so the extensions list would be: `service::extensions: [healthcheckv2]`.
177+
178+
> [!NOTE]
179+
> By enabling this feature gate, all the lists in the given configuration will be merged.
180+
96181
### Watching for Updates
97182
After the configuration was processed, the `Resolver` can be used as a single point to watch for updates in the
98183
configuration retrieved via the `Provider` used to retrieve the “initial” configuration and to generate the “effective” one.

confmap/confmap.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,15 @@ func (l *Conf) Merge(in *Conf) error {
171171
return l.k.Merge(in.k)
172172
}
173173

174+
// mergeAppend merges the input given configuration into the existing config.
175+
// Note that the given map may be modified.
176+
// Additionally, mergeAppend performs deduplication when merging lists.
177+
// For example, if listA = [extension1, extension2] and listB = [extension1, extension3],
178+
// the resulting list will be [extension1, extension2, extension3].
179+
func (l *Conf) mergeAppend(in *Conf) error {
180+
return l.k.Load(confmap.Provider(in.ToStringMap(), ""), nil, koanf.WithMergeFunc(mergeAppend))
181+
}
182+
174183
// Sub returns new Conf instance representing a sub-config of this instance.
175184
// It returns an error is the sub-config is not a map[string]any (use Get()), and an empty Map if none exists.
176185
func (l *Conf) Sub(key string) (*Conf, error) {

confmap/go.mod

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ require (
88
github.com/knadh/koanf/providers/confmap v0.1.0
99
github.com/knadh/koanf/v2 v2.1.2
1010
github.com/stretchr/testify v1.10.0
11+
go.opentelemetry.io/collector/featuregate v1.25.0
1112
go.uber.org/goleak v1.3.0
1213
go.uber.org/multierr v1.11.0
1314
go.uber.org/zap v1.27.0
@@ -16,15 +17,15 @@ require (
1617

1718
require (
1819
github.com/davecgh/go-spew v1.1.1 // indirect
19-
github.com/kr/pretty v0.3.1 // indirect
20+
github.com/hashicorp/go-version v1.7.0 // indirect
2021
github.com/mitchellh/copystructure v1.2.0 // indirect
2122
github.com/mitchellh/reflectwalk v1.0.2 // indirect
2223
github.com/pmezard/go-difflib v1.0.0 // indirect
23-
github.com/rogpeppe/go-internal v1.10.0 // indirect
24-
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
2524
)
2625

2726
retract (
2827
v0.76.0 // Depends on retracted pdata v1.0.0-rc10 module, use v0.76.1
2928
v0.69.0 // Release failed, use v0.69.1
3029
)
30+
31+
replace go.opentelemetry.io/collector/featuregate => ../featuregate

confmap/go.sum

Lines changed: 2 additions & 6 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

confmap/internal/e2e/go.mod

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,14 @@ require (
1313
require (
1414
github.com/davecgh/go-spew v1.1.1 // indirect
1515
github.com/go-viper/mapstructure/v2 v2.2.1 // indirect
16+
github.com/hashicorp/go-version v1.7.0 // indirect
1617
github.com/knadh/koanf/maps v0.1.1 // indirect
1718
github.com/knadh/koanf/providers/confmap v0.1.0 // indirect
1819
github.com/knadh/koanf/v2 v2.1.2 // indirect
1920
github.com/mitchellh/copystructure v1.2.0 // indirect
2021
github.com/mitchellh/reflectwalk v1.0.2 // indirect
2122
github.com/pmezard/go-difflib v1.0.0 // indirect
23+
go.opentelemetry.io/collector/featuregate v1.25.0 // indirect
2224
go.uber.org/multierr v1.11.0 // indirect
2325
go.uber.org/zap v1.27.0 // indirect
2426
gopkg.in/yaml.v3 v3.0.1 // indirect
@@ -31,3 +33,5 @@ replace go.opentelemetry.io/collector/confmap/provider/fileprovider => ../../pro
3133
replace go.opentelemetry.io/collector/confmap/provider/envprovider => ../../provider/envprovider
3234

3335
replace go.opentelemetry.io/collector/config/configopaque => ../../../config/configopaque
36+
37+
replace go.opentelemetry.io/collector/featuregate => ../../../featuregate

confmap/internal/e2e/go.sum

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

confmap/merge.go

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
// Copyright The OpenTelemetry Authors
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package confmap // import "go.opentelemetry.io/collector/confmap"
5+
6+
import (
7+
"reflect"
8+
)
9+
10+
func mergeAppend(src, dest map[string]any) error {
11+
// mergeAppend recursively merges the src map into the dest map (left to right),
12+
// modifying and expanding the dest map in the process.
13+
// This function does not overwrite lists, and ensures that the final value is a name-aware
14+
// copy of lists from src and dest.
15+
16+
for sKey, sVal := range src {
17+
dVal, dOk := dest[sKey]
18+
if !dOk {
19+
// key is not present in destination config. Hence, add it to destination map
20+
dest[sKey] = sVal
21+
continue
22+
}
23+
24+
srcVal := reflect.ValueOf(sVal)
25+
destVal := reflect.ValueOf(dVal)
26+
27+
if destVal.Kind() != srcVal.Kind() {
28+
// different kinds. Override the destination map
29+
dest[sKey] = sVal
30+
continue
31+
}
32+
33+
switch srcVal.Kind() {
34+
case reflect.Array, reflect.Slice:
35+
// both of them are array. Merge them
36+
dest[sKey] = mergeSlice(srcVal, destVal)
37+
case reflect.Map:
38+
// both of them are maps. Recursively call the mergeAppend
39+
_ = mergeAppend(sVal.(map[string]any), dVal.(map[string]any))
40+
default:
41+
// any other datatype. Override the destination map
42+
dest[sKey] = sVal
43+
}
44+
}
45+
46+
return nil
47+
}
48+
49+
func mergeSlice(src, dest reflect.Value) any {
50+
slice := reflect.MakeSlice(src.Type(), 0, src.Cap()+dest.Cap())
51+
for i := 0; i < dest.Len(); i++ {
52+
slice = reflect.Append(slice, dest.Index(i))
53+
}
54+
55+
for i := 0; i < src.Len(); i++ {
56+
if isPresent(slice, src.Index(i)) {
57+
continue
58+
}
59+
slice = reflect.Append(slice, src.Index(i))
60+
}
61+
return slice.Interface()
62+
}
63+
64+
func isPresent(slice reflect.Value, val reflect.Value) bool {
65+
for i := 0; i < slice.Len(); i++ {
66+
if slice.Index(i).Equal(val) {
67+
return true
68+
}
69+
}
70+
return false
71+
}

confmap/provider/envprovider/go.mod

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,18 @@ require (
1212
require (
1313
github.com/davecgh/go-spew v1.1.1 // indirect
1414
github.com/go-viper/mapstructure/v2 v2.2.1 // indirect
15+
github.com/hashicorp/go-version v1.7.0 // indirect
1516
github.com/knadh/koanf/maps v0.1.1 // indirect
1617
github.com/knadh/koanf/providers/confmap v0.1.0 // indirect
1718
github.com/knadh/koanf/v2 v2.1.2 // indirect
1819
github.com/mitchellh/copystructure v1.2.0 // indirect
1920
github.com/mitchellh/reflectwalk v1.0.2 // indirect
2021
github.com/pmezard/go-difflib v1.0.0 // indirect
22+
go.opentelemetry.io/collector/featuregate v1.25.0 // indirect
2123
go.uber.org/multierr v1.11.0 // indirect
2224
gopkg.in/yaml.v3 v3.0.1 // indirect
2325
)
2426

2527
replace go.opentelemetry.io/collector/confmap => ../../
28+
29+
replace go.opentelemetry.io/collector/featuregate => ../../../featuregate

confmap/provider/envprovider/go.sum

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

confmap/provider/fileprovider/go.mod

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,15 +11,19 @@ require (
1111
require (
1212
github.com/davecgh/go-spew v1.1.1 // indirect
1313
github.com/go-viper/mapstructure/v2 v2.2.1 // indirect
14+
github.com/hashicorp/go-version v1.7.0 // indirect
1415
github.com/knadh/koanf/maps v0.1.1 // indirect
1516
github.com/knadh/koanf/providers/confmap v0.1.0 // indirect
1617
github.com/knadh/koanf/v2 v2.1.2 // indirect
1718
github.com/mitchellh/copystructure v1.2.0 // indirect
1819
github.com/mitchellh/reflectwalk v1.0.2 // indirect
1920
github.com/pmezard/go-difflib v1.0.0 // indirect
21+
go.opentelemetry.io/collector/featuregate v1.25.0 // indirect
2022
go.uber.org/multierr v1.11.0 // indirect
2123
go.uber.org/zap v1.27.0 // indirect
2224
gopkg.in/yaml.v3 v3.0.1 // indirect
2325
)
2426

2527
replace go.opentelemetry.io/collector/confmap => ../../
28+
29+
replace go.opentelemetry.io/collector/featuregate => ../../../featuregate

confmap/provider/fileprovider/go.sum

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)