Files
Evan BradleyandGitHub 9be8cf2b64 [chore][confmap] Move functionality to the internal package (#13555)
<!--Ex. Fixing a bug - Describe the bug and how this fixes the issue.
Ex. Adding a feature - Explain what this achieves.-->
#### Description

Move substantial amounts of confmap's functionality to the internal
package. This allows us to share the functionality with xconfmap and
therefore add experimental functionality to confmap without risking
breaking changes.

I'm not a huge fan of the added indirection this introduces, but I think
this is a fairly standard pattern, is fairly straightforward to trace
from the top-level package down (e.g. confmap.Conf -> internal.Conf),
and is probably the simplest way to accomplish this.

<!-- Issue number if applicable -->
#### Link to tracking issue

Likely required for
https://github.com/open-telemetry/opentelemetry-collector/issues/13421
2025-08-06 16:10:54 +00:00

95 lines
2.3 KiB
Go

// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package internal // import "go.opentelemetry.io/collector/confmap/internal"
import (
"reflect"
"github.com/gobwas/glob"
"github.com/knadh/koanf/maps"
)
func mergeAppend(src, dest map[string]any) error {
// mergeAppend recursively merges the src map into the dest map (left to right),
// modifying and expanding the dest map in the process.
// This function does not overwrite component lists, and ensures that the
// final value is a name-aware copy of lists from src and dest.
// Compile the globs once
patterns := []string{
"service::extensions",
"service::**::receivers",
"service::**::exporters",
}
var globs []glob.Glob
for _, p := range patterns {
if g, err := glob.Compile(p); err == nil {
globs = append(globs, g)
}
}
// Flatten both source and destination maps
srcFlat, _ := maps.Flatten(src, []string{}, KeyDelimiter)
destFlat, _ := maps.Flatten(dest, []string{}, KeyDelimiter)
for sKey, sVal := range srcFlat {
if !isMatch(sKey, globs) {
continue
}
dVal, dOk := destFlat[sKey]
if !dOk {
continue // Let maps.Merge handle missing keys
}
srcVal := reflect.ValueOf(sVal)
destVal := reflect.ValueOf(dVal)
// Only merge if the value is a slice or array; let maps.Merge handle other types
if srcVal.Kind() == reflect.Slice || srcVal.Kind() == reflect.Array {
srcFlat[sKey] = mergeSlice(srcVal, destVal)
}
}
// Unflatten and merge
mergedSrc := maps.Unflatten(srcFlat, KeyDelimiter)
maps.Merge(mergedSrc, dest)
return nil
}
// isMatch checks if a key matches any glob in the list
func isMatch(key string, globs []glob.Glob) bool {
for _, g := range globs {
if g.Match(key) {
return true
}
}
return false
}
func mergeSlice(src, dest reflect.Value) any {
slice := reflect.MakeSlice(src.Type(), 0, src.Cap()+dest.Cap())
for i := 0; i < dest.Len(); i++ {
slice = reflect.Append(slice, dest.Index(i))
}
for i := 0; i < src.Len(); i++ {
if isPresent(slice, src.Index(i)) {
continue
}
slice = reflect.Append(slice, src.Index(i))
}
return slice.Interface()
}
func isPresent(slice, val reflect.Value) bool {
for i := 0; i < slice.Len(); i++ {
if reflect.DeepEqual(val.Interface(), slice.Index(i).Interface()) {
return true
}
}
return false
}