From 0ac887e6b7377a064d36b36f6d145bf63c85289d Mon Sep 17 00:00:00 2001 From: Evan Bradley <11745660+evan-bradley@users.noreply.github.com> Date: Tue, 4 Feb 2025 17:25:59 -0500 Subject: [PATCH] [xconfmap] Create module and add validation facilities (#12226) #### Description Builds on https://github.com/open-telemetry/opentelemetry-collector/pull/12224 and starts the move of config validation from component to confmap. We can keep this in `xconfmap` while we determine whether to add the ability to validate using struct field tags. I think this has the following advantages: 1. Everything configuration-related is now in confmap instead of split between confmap and component. 2. We can share things like the mapstructure tag and config key separator as constants between unmarshaling and validation without creating dependencies between confmap and component. ~The one uncertainty this creates is what to do with `component.Config`, which would now be used as a thin alias for `any` without any meaningful usage in component.~ #### Link to tracking issue Fixes https://github.com/open-telemetry/opentelemetry-collector/issues/11524 --- .chloggen/create-confmap-validate.yaml | 25 ++ .chloggen/deprecate-component-validate.yaml | 25 ++ cmd/builder/internal/builder/main_test.go | 1 + cmd/otelcorecol/builder-config.yaml | 1 + cmd/otelcorecol/go.mod | 3 + component/config.go | 6 +- confmap/confmap.go | 10 +- confmap/xconfmap/Makefile | 1 + confmap/xconfmap/config.go | 199 ++++++++++++ confmap/xconfmap/config_test.go | 323 ++++++++++++++++++++ confmap/xconfmap/go.mod | 24 ++ confmap/xconfmap/go.sum | 35 +++ exporter/otlpexporter/config_test.go | 3 +- exporter/otlpexporter/go.mod | 3 + exporter/otlphttpexporter/config_test.go | 4 +- exporter/otlphttpexporter/go.mod | 3 + internal/e2e/go.mod | 2 + otelcol/collector.go | 5 +- otelcol/config_test.go | 3 +- otelcol/go.mod | 3 + otelcol/otelcoltest/config.go | 4 +- otelcol/otelcoltest/go.mod | 3 + receiver/otlpreceiver/config_test.go | 5 +- receiver/otlpreceiver/go.mod | 3 + service/config_test.go | 3 +- service/go.mod | 3 + service/pipelines/config_test.go | 5 +- versions.yaml | 1 + 28 files changed, 690 insertions(+), 16 deletions(-) create mode 100644 .chloggen/create-confmap-validate.yaml create mode 100644 .chloggen/deprecate-component-validate.yaml create mode 100644 confmap/xconfmap/Makefile create mode 100644 confmap/xconfmap/config.go create mode 100644 confmap/xconfmap/config_test.go create mode 100644 confmap/xconfmap/go.mod create mode 100644 confmap/xconfmap/go.sum diff --git a/.chloggen/create-confmap-validate.yaml b/.chloggen/create-confmap-validate.yaml new file mode 100644 index 00000000000..bd195645a89 --- /dev/null +++ b/.chloggen/create-confmap-validate.yaml @@ -0,0 +1,25 @@ +# Use this changelog template to create an entry for release notes. + +# One of 'breaking', 'deprecation', 'new_component', 'enhancement', 'bug_fix' +change_type: new_component + +# The name of the component, or a single word describing the area of concern, (e.g. otlpreceiver) +component: xconfmap + +# A brief description of the change. Surround your text with quotes ("") if it needs to start with a backtick (`). +note: Create the xconfmap module and add the `Validator` interface and `Validate` function to facilitate config validation + +# One or more tracking issues or pull requests related to the change +issues: [11524] + +# (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: + +# 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] diff --git a/.chloggen/deprecate-component-validate.yaml b/.chloggen/deprecate-component-validate.yaml new file mode 100644 index 00000000000..d2da4e05266 --- /dev/null +++ b/.chloggen/deprecate-component-validate.yaml @@ -0,0 +1,25 @@ +# Use this changelog template to create an entry for release notes. + +# One of 'breaking', 'deprecation', 'new_component', 'enhancement', 'bug_fix' +change_type: deprecation + +# The name of the component, or a single word describing the area of concern, (e.g. otlpreceiver) +component: component + +# A brief description of the change. Surround your text with quotes ("") if it needs to start with a backtick (`). +note: Deprecate `ConfigValidator` and `ValidateConfig` + +# One or more tracking issues or pull requests related to the change +issues: [11524] + +# (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: Please use `Validator` and `Validate` respectively from `xconfmap`. + +# 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] diff --git a/cmd/builder/internal/builder/main_test.go b/cmd/builder/internal/builder/main_test.go index 41c06fea3bb..19cf5b2edc1 100644 --- a/cmd/builder/internal/builder/main_test.go +++ b/cmd/builder/internal/builder/main_test.go @@ -53,6 +53,7 @@ var replaceModules = []string{ "/config/configtelemetry", "/config/configtls", "/confmap", + "/confmap/xconfmap", "/confmap/provider/envprovider", "/confmap/provider/fileprovider", "/confmap/provider/httpprovider", diff --git a/cmd/otelcorecol/builder-config.yaml b/cmd/otelcorecol/builder-config.yaml index f98ba9eae43..cbb282e19eb 100644 --- a/cmd/otelcorecol/builder-config.yaml +++ b/cmd/otelcorecol/builder-config.yaml @@ -52,6 +52,7 @@ replaces: - go.opentelemetry.io/collector/config/configtelemetry => ../../config/configtelemetry - go.opentelemetry.io/collector/config/configtls => ../../config/configtls - go.opentelemetry.io/collector/confmap => ../../confmap + - go.opentelemetry.io/collector/confmap/xconfmap => ../../confmap/xconfmap - go.opentelemetry.io/collector/confmap/provider/envprovider => ../../confmap/provider/envprovider - go.opentelemetry.io/collector/confmap/provider/fileprovider => ../../confmap/provider/fileprovider - go.opentelemetry.io/collector/confmap/provider/httpprovider => ../../confmap/provider/httpprovider diff --git a/cmd/otelcorecol/go.mod b/cmd/otelcorecol/go.mod index e8f6a2b3c66..cb634c10d37 100644 --- a/cmd/otelcorecol/go.mod +++ b/cmd/otelcorecol/go.mod @@ -93,6 +93,7 @@ require ( go.opentelemetry.io/collector/config/configretry v1.25.0 // indirect go.opentelemetry.io/collector/config/configtelemetry v0.119.0 // indirect go.opentelemetry.io/collector/config/configtls v1.25.0 // indirect + go.opentelemetry.io/collector/confmap/xconfmap v0.0.0-00010101000000-000000000000 // indirect go.opentelemetry.io/collector/connector/connectortest v0.119.0 // indirect go.opentelemetry.io/collector/connector/xconnector v0.119.0 // indirect go.opentelemetry.io/collector/consumer v1.25.0 // indirect @@ -190,6 +191,8 @@ replace go.opentelemetry.io/collector/config/configtls => ../../config/configtls replace go.opentelemetry.io/collector/confmap => ../../confmap +replace go.opentelemetry.io/collector/confmap/xconfmap => ../../confmap/xconfmap + replace go.opentelemetry.io/collector/confmap/provider/envprovider => ../../confmap/provider/envprovider replace go.opentelemetry.io/collector/confmap/provider/fileprovider => ../../confmap/provider/fileprovider diff --git a/component/config.go b/component/config.go index a5862798fdd..6e5a785c349 100644 --- a/component/config.go +++ b/component/config.go @@ -14,7 +14,7 @@ import ( // Config defines the configuration for a component.Component. // // Implementations and/or any sub-configs (other types embedded or included in the Config implementation) -// MUST implement the ConfigValidator if any validation is required for that part of the configuration +// MUST implement xconfmap.Validator if any validation is required for that part of the configuration // (e.g. check if a required field is present). // // A valid implementation MUST pass the check componenttest.CheckConfigStruct (return nil error). @@ -25,6 +25,8 @@ type Config any var configValidatorType = reflect.TypeOf((*ConfigValidator)(nil)).Elem() // ConfigValidator defines an optional interface for configurations to implement to do validation. +// +// Deprecated: [v0.120.0] use xconfmap.Validator. type ConfigValidator interface { // Validate the configuration and returns an error if invalid. Validate() error @@ -32,6 +34,8 @@ type ConfigValidator interface { // ValidateConfig validates a config, by doing this: // - Call Validate on the config itself if the config implements ConfigValidator. +// +// Deprecated: [v0.120.0] use xconfmap.Validate. func ValidateConfig(cfg Config) error { var err error diff --git a/confmap/confmap.go b/confmap/confmap.go index aab1730687b..b60634ade65 100644 --- a/confmap/confmap.go +++ b/confmap/confmap.go @@ -24,6 +24,12 @@ const ( KeyDelimiter = "::" ) +const ( + // MapstructureTag is the struct field tag used to record marshaling/unmarshaling settings. + // See https://pkg.go.dev/github.com/go-viper/mapstructure/v2 for supported values. + MapstructureTag = "mapstructure" +) + // New creates a new empty confmap.Conf instance. func New() *Conf { return &Conf{k: koanf.New(KeyDelimiter)} @@ -205,7 +211,7 @@ func decodeConfig(m *Conf, result any, errorUnused bool, skipTopLevelUnmarshaler dc := &mapstructure.DecoderConfig{ ErrorUnused: errorUnused, Result: result, - TagName: "mapstructure", + TagName: MapstructureTag, WeaklyTypedInput: false, MatchName: caseSensitiveMatchName, DecodeHook: mapstructure.ComposeDecodeHookFunc( @@ -407,7 +413,7 @@ func unmarshalerEmbeddedStructsHookFunc() mapstructure.DecodeHookFuncValue { for i := 0; i < to.Type().NumField(); i++ { // embedded structs passed in via `squash` cannot be pointers. We just check if they are structs: f := to.Type().Field(i) - if f.IsExported() && slices.Contains(strings.Split(f.Tag.Get("mapstructure"), ","), "squash") { + if f.IsExported() && slices.Contains(strings.Split(f.Tag.Get(MapstructureTag), ","), "squash") { if unmarshaler, ok := to.Field(i).Addr().Interface().(Unmarshaler); ok { c := NewFromStringMap(fromAsMap) c.skipTopLevelUnmarshaler = true diff --git a/confmap/xconfmap/Makefile b/confmap/xconfmap/Makefile new file mode 100644 index 00000000000..ded7a36092d --- /dev/null +++ b/confmap/xconfmap/Makefile @@ -0,0 +1 @@ +include ../../Makefile.Common diff --git a/confmap/xconfmap/config.go b/confmap/xconfmap/config.go new file mode 100644 index 00000000000..b9e8edbbaa5 --- /dev/null +++ b/confmap/xconfmap/config.go @@ -0,0 +1,199 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package xconfmap // import "go.opentelemetry.io/collector/confmap/xconfmap" + +import ( + "errors" + "fmt" + "reflect" + "strconv" + "strings" + + "go.opentelemetry.io/collector/confmap" +) + +// As interface types are only used for static typing, a common idiom to find the reflection Type +// for an interface type Foo is to use a *Foo value. +var configValidatorType = reflect.TypeOf((*Validator)(nil)).Elem() + +// Validator defines an optional interface for configurations to implement to do validation. +type Validator interface { + // Validate the configuration and returns an error if invalid. + Validate() error +} + +// Validate validates a config, by doing this: +// - Call Validate on the config itself if the config implements ConfigValidator. +func Validate(cfg any) error { + var err error + + for _, validationErr := range validate(reflect.ValueOf(cfg)) { + err = errors.Join(err, validationErr) + } + + return err +} + +type pathError struct { + err error + path []string +} + +func (pe pathError) Error() string { + if len(pe.path) > 0 { + var path string + sb := strings.Builder{} + + _, _ = sb.WriteString(pe.path[len(pe.path)-1]) + for i := len(pe.path) - 2; i >= 0; i-- { + _, _ = sb.WriteString(confmap.KeyDelimiter) + _, _ = sb.WriteString(pe.path[i]) + } + path = sb.String() + + return fmt.Sprintf("%s: %s", path, pe.err) + } + + return pe.err.Error() +} + +func (pe pathError) Unwrap() error { + return pe.err +} + +func validate(v reflect.Value) []pathError { + errs := []pathError{} + // Validate the value itself. + switch v.Kind() { + case reflect.Invalid: + return nil + case reflect.Ptr, reflect.Interface: + return validate(v.Elem()) + case reflect.Struct: + err := callValidateIfPossible(v) + if err != nil { + errs = append(errs, pathError{err: err}) + } + + // Reflect on the pointed data and check each of its fields. + for i := 0; i < v.NumField(); i++ { + if !v.Type().Field(i).IsExported() { + continue + } + field := v.Type().Field(i) + path := fieldName(field) + + subpathErrs := validate(v.Field(i)) + for _, err := range subpathErrs { + errs = append(errs, pathError{ + err: err.err, + path: append(err.path, path), + }) + } + } + return errs + case reflect.Slice, reflect.Array: + err := callValidateIfPossible(v) + if err != nil { + errs = append(errs, pathError{err: err}) + } + + // Reflect on the pointed data and check each of its fields. + for i := 0; i < v.Len(); i++ { + subPathErrs := validate(v.Index(i)) + + for _, err := range subPathErrs { + errs = append(errs, pathError{ + err: err.err, + path: append(err.path, strconv.Itoa(i)), + }) + } + } + return errs + case reflect.Map: + err := callValidateIfPossible(v) + if err != nil { + errs = append(errs, pathError{err: err}) + } + + iter := v.MapRange() + for iter.Next() { + keyErrs := validate(iter.Key()) + valueErrs := validate(iter.Value()) + key := stringifyMapKey(iter.Key()) + + for _, err := range keyErrs { + errs = append(errs, pathError{err: err.err, path: append(err.path, key)}) + } + + for _, err := range valueErrs { + errs = append(errs, pathError{err: err.err, path: append(err.path, key)}) + } + } + return errs + default: + err := callValidateIfPossible(v) + if err != nil { + return []pathError{{err: err}} + } + + return nil + } +} + +func callValidateIfPossible(v reflect.Value) error { + // If the value type implements ConfigValidator just call Validate + if v.Type().Implements(configValidatorType) { + return v.Interface().(Validator).Validate() + } + + // If the pointer type implements ConfigValidator call Validate on the pointer to the current value. + if reflect.PointerTo(v.Type()).Implements(configValidatorType) { + // If not addressable, then create a new *V pointer and set the value to current v. + if !v.CanAddr() { + pv := reflect.New(reflect.PointerTo(v.Type()).Elem()) + pv.Elem().Set(v) + v = pv.Elem() + } + return v.Addr().Interface().(Validator).Validate() + } + + return nil +} + +func fieldName(field reflect.StructField) string { + var fieldName string + if tag, ok := field.Tag.Lookup(confmap.MapstructureTag); ok { + tags := strings.Split(tag, ",") + if len(tags) > 0 { + fieldName = tags[0] + } + } + // Even if the mapstructure tag exists, the field name may not + // be available, so set it if it is still blank. + if len(fieldName) == 0 { + fieldName = strings.ToLower(field.Name) + } + + return fieldName +} + +func stringifyMapKey(val reflect.Value) string { + var key string + + if str, ok := val.Interface().(string); ok { + key = str + } else if stringer, ok := val.Interface().(fmt.Stringer); ok { + key = stringer.String() + } else { + switch val.Kind() { + case reflect.Ptr, reflect.Interface, reflect.Struct, reflect.Slice, reflect.Array, reflect.Map: + key = fmt.Sprintf("[%T key]", val.Interface()) + default: + key = fmt.Sprintf("%v", val.Interface()) + } + } + + return key +} diff --git a/confmap/xconfmap/config_test.go b/confmap/xconfmap/config_test.go new file mode 100644 index 00000000000..0b45779d0fa --- /dev/null +++ b/confmap/xconfmap/config_test.go @@ -0,0 +1,323 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package xconfmap + +import ( + "errors" + "testing" + + "github.com/stretchr/testify/assert" +) + +type configChildStruct struct { + Child errValidateConfig + ChildPtr *errValidateConfig +} + +type configChildSlice struct { + Child []errValidateConfig + ChildPtr []*errValidateConfig +} + +type configChildMapValue struct { + Child map[string]errValidateConfig + ChildPtr map[string]*errValidateConfig +} + +type configChildMapKey struct { + Child map[errType]string + ChildPtr map[*errType]string +} + +type configChildTypeDef struct { + Child errType + ChildPtr *errType +} + +type config any + +type configChildInterface struct { + Child config +} + +type errValidateConfig struct { + err error +} + +func (e *errValidateConfig) Validate() error { + return e.err +} + +type errType string + +func (e errType) Validate() error { + if e == "" { + return nil + } + return errors.New(string(e)) +} + +func newErrType(etStr string) *errType { + et := errType(etStr) + return &et +} + +type errMapType map[string]string + +func (e errMapType) Validate() error { + return errors.New(e["err"]) +} + +type structKey struct { + k string + e error +} + +func (s structKey) String() string { + return s.k +} + +func (s structKey) Validate() error { + return s.e +} + +type configChildMapCustomKey struct { + Child map[structKey]errValidateConfig +} + +func newErrMapType() *errMapType { + et := errMapType(nil) + return &et +} + +type configMapstructure struct { + Valid *errValidateConfig `mapstructure:"validtag,omitempty"` + NoData *errValidateConfig `mapstructure:""` + NoName *errValidateConfig `mapstructure:",remain"` +} + +type configDeeplyNested struct { + MapKeyChild map[configChildStruct]string + MapValueChild map[string]configChildStruct + SliceChild []configChildSlice + MapIntKey map[int]errValidateConfig + MapFloatKey map[float64]errValidateConfig +} + +type sliceTypeAlias []configChildSlice + +func (sliceTypeAlias) Validate() error { + return errors.New("sliceTypeAlias error") +} + +func TestValidateConfig(t *testing.T) { + tests := []struct { + name string + cfg any + expected error + }{ + { + name: "struct", + cfg: errValidateConfig{err: errors.New("struct")}, + expected: errors.New("struct"), + }, + { + name: "pointer struct", + cfg: &errValidateConfig{err: errors.New("pointer struct")}, + expected: errors.New("pointer struct"), + }, + { + name: "type", + cfg: errType("type"), + expected: errors.New("type"), + }, + { + name: "pointer child", + cfg: newErrType("pointer type"), + expected: errors.New("pointer type"), + }, + { + name: "child interface with nil", + cfg: configChildInterface{}, + expected: nil, + }, + { + name: "pointer to child interface with nil", + cfg: &configChildInterface{}, + expected: nil, + }, + { + name: "nil", + cfg: nil, + expected: nil, + }, + { + name: "nil map type", + cfg: errMapType(nil), + expected: errors.New(""), + }, + { + name: "nil pointer map type", + cfg: newErrMapType(), + expected: errors.New(""), + }, + { + name: "child struct", + cfg: configChildStruct{Child: errValidateConfig{err: errors.New("child struct")}}, + expected: errors.New("child: child struct"), + }, + { + name: "pointer child struct", + cfg: &configChildStruct{Child: errValidateConfig{err: errors.New("pointer child struct")}}, + expected: errors.New("child: pointer child struct"), + }, + { + name: "child struct pointer", + cfg: &configChildStruct{ChildPtr: &errValidateConfig{err: errors.New("child struct pointer")}}, + expected: errors.New("childptr: child struct pointer"), + }, + { + name: "child interface", + cfg: configChildInterface{Child: errValidateConfig{err: errors.New("child interface")}}, + expected: errors.New("child: child interface"), + }, + { + name: "pointer to child interface", + cfg: &configChildInterface{Child: errValidateConfig{err: errors.New("pointer to child interface")}}, + expected: errors.New("child: pointer to child interface"), + }, + { + name: "child interface with pointer", + cfg: configChildInterface{Child: &errValidateConfig{err: errors.New("child interface with pointer")}}, + expected: errors.New("child: child interface with pointer"), + }, + { + name: "pointer to child interface with pointer", + cfg: &configChildInterface{Child: &errValidateConfig{err: errors.New("pointer to child interface with pointer")}}, + expected: errors.New("child: pointer to child interface with pointer"), + }, + { + name: "child slice", + cfg: configChildSlice{Child: []errValidateConfig{{}, {err: errors.New("child slice")}}}, + expected: errors.New("child::1: child slice"), + }, + { + name: "pointer child slice", + cfg: &configChildSlice{Child: []errValidateConfig{{}, {err: errors.New("pointer child slice")}}}, + expected: errors.New("child::1: pointer child slice"), + }, + { + name: "child slice pointer", + cfg: &configChildSlice{ChildPtr: []*errValidateConfig{{}, {err: errors.New("child slice pointer")}}}, + expected: errors.New("childptr::1: child slice pointer"), + }, + { + name: "child map value", + cfg: configChildMapValue{Child: map[string]errValidateConfig{"test": {err: errors.New("child map")}}}, + expected: errors.New("child::test: child map"), + }, + { + name: "pointer child map value", + cfg: &configChildMapValue{Child: map[string]errValidateConfig{"test": {err: errors.New("pointer child map")}}}, + expected: errors.New("child::test: pointer child map"), + }, + { + name: "child map value pointer", + cfg: &configChildMapValue{ChildPtr: map[string]*errValidateConfig{"test": {err: errors.New("child map pointer")}}}, + expected: errors.New("childptr::test: child map pointer"), + }, + { + name: "child map key", + cfg: configChildMapKey{Child: map[errType]string{"child_map_key": ""}}, + expected: errors.New("child::child_map_key: child_map_key"), + }, + { + name: "pointer child map key", + cfg: &configChildMapKey{Child: map[errType]string{"pointer_child_map_key": ""}}, + expected: errors.New("child::pointer_child_map_key: pointer_child_map_key"), + }, + { + name: "child map key pointer", + cfg: &configChildMapKey{ChildPtr: map[*errType]string{newErrType("child map key pointer"): ""}}, + expected: errors.New("childptr::[*xconfmap.errType key]: child map key pointer"), + }, + { + name: "map with stringified non-string key type", + cfg: &configChildMapCustomKey{Child: map[structKey]errValidateConfig{{k: "struct_key", e: errors.New("custom key error")}: {err: errors.New("value error")}}}, + expected: errors.New("child::struct_key: custom key error\nchild::struct_key: value error"), + }, + { + name: "child type", + cfg: configChildTypeDef{Child: "child type"}, + expected: errors.New("child: child type"), + }, + { + name: "pointer child type", + cfg: &configChildTypeDef{Child: "pointer child type"}, + expected: errors.New("child: pointer child type"), + }, + { + name: "child type pointer", + cfg: &configChildTypeDef{ChildPtr: newErrType("child type pointer")}, + expected: errors.New("childptr: child type pointer"), + }, + { + name: "valid mapstructure tag", + cfg: configMapstructure{Valid: &errValidateConfig{errors.New("test")}}, + expected: errors.New("validtag: test"), + }, + { + name: "zero-length mapstructure tag", + cfg: configMapstructure{NoData: &errValidateConfig{errors.New("test")}}, + expected: errors.New("nodata: test"), + }, + { + name: "no field name in mapstructure tag", + cfg: configMapstructure{NoName: &errValidateConfig{errors.New("test")}}, + expected: errors.New("noname: test"), + }, + { + name: "nested map key error", + cfg: configDeeplyNested{MapKeyChild: map[configChildStruct]string{{Child: errValidateConfig{err: errors.New("child key error")}}: "val"}}, + expected: errors.New("mapkeychild::[xconfmap.configChildStruct key]::child: child key error"), + }, + { + name: "nested map value error", + cfg: configDeeplyNested{MapValueChild: map[string]configChildStruct{"key": {Child: errValidateConfig{err: errors.New("child key error")}}}}, + expected: errors.New("mapvaluechild::key::child: child key error"), + }, + { + name: "nested slice value error", + cfg: configDeeplyNested{SliceChild: []configChildSlice{{Child: []errValidateConfig{{err: errors.New("child key error")}}}}}, + expected: errors.New("slicechild::0::child::0: child key error"), + }, + { + name: "nested map with int key", + cfg: configDeeplyNested{MapIntKey: map[int]errValidateConfig{1: {err: errors.New("int key error")}}}, + expected: errors.New("mapintkey::1: int key error"), + }, + { + name: "nested map with float key", + cfg: configDeeplyNested{MapFloatKey: map[float64]errValidateConfig{1.2: {err: errors.New("float key error")}}}, + expected: errors.New("mapfloatkey::1.2: float key error"), + }, + { + name: "slice type alias", + cfg: sliceTypeAlias{}, + expected: errors.New("sliceTypeAlias error"), + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err := Validate(tt.cfg) + + if tt.expected != nil { + assert.EqualError(t, err, tt.expected.Error()) + } else { + assert.NoError(t, err) + } + }) + } +} diff --git a/confmap/xconfmap/go.mod b/confmap/xconfmap/go.mod new file mode 100644 index 00000000000..4b6c8e836aa --- /dev/null +++ b/confmap/xconfmap/go.mod @@ -0,0 +1,24 @@ +module go.opentelemetry.io/collector/confmap/xconfmap + +go 1.22.0 + +require ( + github.com/stretchr/testify v1.10.0 + go.opentelemetry.io/collector/confmap v1.25.0 +) + +require ( + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/go-viper/mapstructure/v2 v2.2.1 // indirect + github.com/knadh/koanf/maps v0.1.1 // indirect + github.com/knadh/koanf/providers/confmap v0.1.0 // indirect + github.com/knadh/koanf/v2 v2.1.2 // indirect + github.com/mitchellh/copystructure v1.2.0 // indirect + github.com/mitchellh/reflectwalk v1.0.2 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + go.uber.org/multierr v1.11.0 // indirect + go.uber.org/zap v1.27.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) + +replace go.opentelemetry.io/collector/confmap => ../ diff --git a/confmap/xconfmap/go.sum b/confmap/xconfmap/go.sum new file mode 100644 index 00000000000..7739cd77111 --- /dev/null +++ b/confmap/xconfmap/go.sum @@ -0,0 +1,35 @@ +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/go-viper/mapstructure/v2 v2.2.1 h1:ZAaOCxANMuZx5RCeg0mBdEZk7DZasvvZIxtHqx8aGss= +github.com/go-viper/mapstructure/v2 v2.2.1/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= +github.com/knadh/koanf/maps v0.1.1 h1:G5TjmUh2D7G2YWf5SQQqSiHRJEjaicvU0KpypqB3NIs= +github.com/knadh/koanf/maps v0.1.1/go.mod h1:npD/QZY3V6ghQDdcQzl1W4ICNVTkohC8E73eI2xW4yI= +github.com/knadh/koanf/providers/confmap v0.1.0 h1:gOkxhHkemwG4LezxxN8DMOFopOPghxRVp7JbIvdvqzU= +github.com/knadh/koanf/providers/confmap v0.1.0/go.mod h1:2uLhxQzJnyHKfxG927awZC7+fyHFdQkd697K4MdLnIU= +github.com/knadh/koanf/v2 v2.1.2 h1:I2rtLRqXRy1p01m/utEtpZSSA6dcJbgGVuE27kW2PzQ= +github.com/knadh/koanf/v2 v2.1.2/go.mod h1:Gphfaen0q1Fc1HTgJgSTC4oRX9R2R5ErYMZJy8fLJBo= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= +github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= +github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= +github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= +github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= +go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= +go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= +go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/exporter/otlpexporter/config_test.go b/exporter/otlpexporter/config_test.go index ae351535df8..099d67958a4 100644 --- a/exporter/otlpexporter/config_test.go +++ b/exporter/otlpexporter/config_test.go @@ -19,6 +19,7 @@ import ( "go.opentelemetry.io/collector/config/configtls" "go.opentelemetry.io/collector/confmap" "go.opentelemetry.io/collector/confmap/confmaptest" + "go.opentelemetry.io/collector/confmap/xconfmap" "go.opentelemetry.io/collector/exporter/exporterbatcher" "go.opentelemetry.io/collector/exporter/exporterhelper" ) @@ -136,7 +137,7 @@ func TestUnmarshalInvalidConfig(t *testing.T) { sub, err := cm.Sub(tt.name) require.NoError(t, err) assert.NoError(t, sub.Unmarshal(&cfg)) - assert.ErrorContains(t, component.ValidateConfig(cfg), tt.errorMsg) + assert.ErrorContains(t, xconfmap.Validate(cfg), tt.errorMsg) }) } } diff --git a/exporter/otlpexporter/go.mod b/exporter/otlpexporter/go.mod index cddecb49fe9..fa2199c21c8 100644 --- a/exporter/otlpexporter/go.mod +++ b/exporter/otlpexporter/go.mod @@ -14,6 +14,7 @@ require ( go.opentelemetry.io/collector/config/configretry v1.25.0 go.opentelemetry.io/collector/config/configtls v1.25.0 go.opentelemetry.io/collector/confmap v1.25.0 + go.opentelemetry.io/collector/confmap/xconfmap v0.0.0-00010101000000-000000000000 go.opentelemetry.io/collector/consumer v1.25.0 go.opentelemetry.io/collector/consumer/consumererror v0.119.0 go.opentelemetry.io/collector/exporter v0.119.0 @@ -98,6 +99,8 @@ replace go.opentelemetry.io/collector/config/configtls => ../../config/configtls replace go.opentelemetry.io/collector/confmap => ../../confmap +replace go.opentelemetry.io/collector/confmap/xconfmap => ../../confmap/xconfmap + replace go.opentelemetry.io/collector/exporter => ../ replace go.opentelemetry.io/collector/extension => ../../extension diff --git a/exporter/otlphttpexporter/config_test.go b/exporter/otlphttpexporter/config_test.go index e10f198960d..9d0c78a7d28 100644 --- a/exporter/otlphttpexporter/config_test.go +++ b/exporter/otlphttpexporter/config_test.go @@ -12,13 +12,13 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "go.opentelemetry.io/collector/component" "go.opentelemetry.io/collector/config/confighttp" "go.opentelemetry.io/collector/config/configopaque" "go.opentelemetry.io/collector/config/configretry" "go.opentelemetry.io/collector/config/configtls" "go.opentelemetry.io/collector/confmap" "go.opentelemetry.io/collector/confmap/confmaptest" + "go.opentelemetry.io/collector/confmap/xconfmap" "go.opentelemetry.io/collector/exporter/exporterhelper" ) @@ -28,7 +28,7 @@ func TestUnmarshalDefaultConfig(t *testing.T) { require.NoError(t, confmap.New().Unmarshal(&cfg)) assert.Equal(t, factory.CreateDefaultConfig(), cfg) // Default/Empty config is invalid. - assert.Error(t, component.ValidateConfig(cfg)) + assert.Error(t, xconfmap.Validate(cfg)) } func TestUnmarshalConfig(t *testing.T) { diff --git a/exporter/otlphttpexporter/go.mod b/exporter/otlphttpexporter/go.mod index 3ec41ccd788..13832747ef9 100644 --- a/exporter/otlphttpexporter/go.mod +++ b/exporter/otlphttpexporter/go.mod @@ -13,6 +13,7 @@ require ( go.opentelemetry.io/collector/config/configretry v1.25.0 go.opentelemetry.io/collector/config/configtls v1.25.0 go.opentelemetry.io/collector/confmap v1.25.0 + go.opentelemetry.io/collector/confmap/xconfmap v0.0.0-00010101000000-000000000000 go.opentelemetry.io/collector/consumer v1.25.0 go.opentelemetry.io/collector/consumer/consumererror v0.119.0 go.opentelemetry.io/collector/exporter v0.119.0 @@ -98,6 +99,8 @@ replace go.opentelemetry.io/collector/config/configtls => ../../config/configtls replace go.opentelemetry.io/collector/confmap => ../../confmap +replace go.opentelemetry.io/collector/confmap/xconfmap => ../../confmap/xconfmap + replace go.opentelemetry.io/collector/exporter => ../ replace go.opentelemetry.io/collector/extension => ../../extension diff --git a/internal/e2e/go.mod b/internal/e2e/go.mod index 07447d0a7fc..a778424f711 100644 --- a/internal/e2e/go.mod +++ b/internal/e2e/go.mod @@ -177,6 +177,8 @@ replace go.opentelemetry.io/collector/extension => ../../extension replace go.opentelemetry.io/collector/confmap => ../../confmap +replace go.opentelemetry.io/collector/confmap/xconfmap => ../../confmap/xconfmap + replace go.opentelemetry.io/collector/component => ../../component replace go.opentelemetry.io/collector/component/componenttest => ../../component/componenttest diff --git a/otelcol/collector.go b/otelcol/collector.go index a0b191053a1..1c46be61819 100644 --- a/otelcol/collector.go +++ b/otelcol/collector.go @@ -21,6 +21,7 @@ import ( "go.opentelemetry.io/collector/component" "go.opentelemetry.io/collector/confmap" + "go.opentelemetry.io/collector/confmap/xconfmap" "go.opentelemetry.io/collector/extension" "go.opentelemetry.io/collector/otelcol/internal/grpclog" "go.opentelemetry.io/collector/service" @@ -171,7 +172,7 @@ func (col *Collector) setupConfigurationComponents(ctx context.Context) error { return fmt.Errorf("failed to get config: %w", err) } - if err = component.ValidateConfig(cfg); err != nil { + if err = xconfmap.Validate(cfg); err != nil { return fmt.Errorf("invalid configuration: %w", err) } @@ -261,7 +262,7 @@ func (col *Collector) DryRun(ctx context.Context) error { return fmt.Errorf("failed to get config: %w", err) } - return component.ValidateConfig(cfg) + return xconfmap.Validate(cfg) } func newFallbackLogger(options []zap.Option) (*zap.Logger, error) { diff --git a/otelcol/config_test.go b/otelcol/config_test.go index e9c766fca0a..d9adc5ce5e7 100644 --- a/otelcol/config_test.go +++ b/otelcol/config_test.go @@ -14,6 +14,7 @@ import ( "go.opentelemetry.io/collector/component" "go.opentelemetry.io/collector/config/configtelemetry" + "go.opentelemetry.io/collector/confmap/xconfmap" "go.opentelemetry.io/collector/pipeline" "go.opentelemetry.io/collector/service" "go.opentelemetry.io/collector/service/pipelines" @@ -241,7 +242,7 @@ func TestConfigValidate(t *testing.T) { for _, tt := range testCases { t.Run(tt.name, func(t *testing.T) { cfg := tt.cfgFn() - err := component.ValidateConfig(cfg) + err := xconfmap.Validate(cfg) if tt.expected != nil { assert.EqualError(t, err, tt.expected.Error()) } else { diff --git a/otelcol/go.mod b/otelcol/go.mod index 34886a562ea..66d9efe2c02 100644 --- a/otelcol/go.mod +++ b/otelcol/go.mod @@ -9,6 +9,7 @@ require ( go.opentelemetry.io/collector/component/componentstatus v0.119.0 go.opentelemetry.io/collector/config/configtelemetry v0.119.0 go.opentelemetry.io/collector/confmap v1.25.0 + go.opentelemetry.io/collector/confmap/xconfmap v0.0.0-00010101000000-000000000000 go.opentelemetry.io/collector/connector v0.119.0 go.opentelemetry.io/collector/connector/connectortest v0.119.0 go.opentelemetry.io/collector/exporter v0.119.0 @@ -141,6 +142,8 @@ replace go.opentelemetry.io/collector/exporter => ../exporter replace go.opentelemetry.io/collector/confmap => ../confmap +replace go.opentelemetry.io/collector/confmap/xconfmap => ../confmap/xconfmap + replace go.opentelemetry.io/collector/config/configtelemetry => ../config/configtelemetry replace go.opentelemetry.io/collector/processor => ../processor diff --git a/otelcol/otelcoltest/config.go b/otelcol/otelcoltest/config.go index 8e414829266..55087dfda29 100644 --- a/otelcol/otelcoltest/config.go +++ b/otelcol/otelcoltest/config.go @@ -6,12 +6,12 @@ package otelcoltest // import "go.opentelemetry.io/collector/otelcol/otelcoltest import ( "context" - "go.opentelemetry.io/collector/component" "go.opentelemetry.io/collector/confmap" "go.opentelemetry.io/collector/confmap/provider/envprovider" "go.opentelemetry.io/collector/confmap/provider/fileprovider" "go.opentelemetry.io/collector/confmap/provider/httpprovider" "go.opentelemetry.io/collector/confmap/provider/yamlprovider" + "go.opentelemetry.io/collector/confmap/xconfmap" "go.opentelemetry.io/collector/otelcol" ) @@ -41,5 +41,5 @@ func LoadConfigAndValidate(fileName string, factories otelcol.Factories) (*otelc if err != nil { return nil, err } - return cfg, component.ValidateConfig(cfg) + return cfg, xconfmap.Validate(cfg) } diff --git a/otelcol/otelcoltest/go.mod b/otelcol/otelcoltest/go.mod index f80c9559509..e657ff5dbd0 100644 --- a/otelcol/otelcoltest/go.mod +++ b/otelcol/otelcoltest/go.mod @@ -10,6 +10,7 @@ require ( go.opentelemetry.io/collector/confmap/provider/fileprovider v1.25.0 go.opentelemetry.io/collector/confmap/provider/httpprovider v1.25.0 go.opentelemetry.io/collector/confmap/provider/yamlprovider v1.25.0 + go.opentelemetry.io/collector/confmap/xconfmap v0.0.0-00010101000000-000000000000 go.opentelemetry.io/collector/connector v0.119.0 go.opentelemetry.io/collector/connector/connectortest v0.119.0 go.opentelemetry.io/collector/exporter v0.119.0 @@ -216,6 +217,8 @@ replace go.opentelemetry.io/collector/extension/xextension => ../../extension/xe replace go.opentelemetry.io/collector/confmap => ../../confmap +replace go.opentelemetry.io/collector/confmap/xconfmap => ../../confmap/xconfmap + replace go.opentelemetry.io/collector/processor/processortest => ../../processor/processortest replace go.opentelemetry.io/collector/pdata/testdata => ../../pdata/testdata diff --git a/receiver/otlpreceiver/config_test.go b/receiver/otlpreceiver/config_test.go index dc00872dd29..5261b60600a 100644 --- a/receiver/otlpreceiver/config_test.go +++ b/receiver/otlpreceiver/config_test.go @@ -19,6 +19,7 @@ import ( "go.opentelemetry.io/collector/config/configtls" "go.opentelemetry.io/collector/confmap" "go.opentelemetry.io/collector/confmap/confmaptest" + "go.opentelemetry.io/collector/confmap/xconfmap" ) func TestUnmarshalDefaultConfig(t *testing.T) { @@ -193,7 +194,7 @@ func TestUnmarshalConfigEmptyProtocols(t *testing.T) { factory := NewFactory() cfg := factory.CreateDefaultConfig() require.NoError(t, cm.Unmarshal(&cfg)) - assert.EqualError(t, component.ValidateConfig(cfg), "must specify at least one protocol when using the OTLP receiver") + assert.EqualError(t, xconfmap.Validate(cfg), "must specify at least one protocol when using the OTLP receiver") } func TestUnmarshalConfigInvalidSignalPath(t *testing.T) { @@ -230,5 +231,5 @@ func TestUnmarshalConfigEmpty(t *testing.T) { factory := NewFactory() cfg := factory.CreateDefaultConfig() require.NoError(t, confmap.New().Unmarshal(&cfg)) - assert.EqualError(t, component.ValidateConfig(cfg), "must specify at least one protocol when using the OTLP receiver") + assert.EqualError(t, xconfmap.Validate(cfg), "must specify at least one protocol when using the OTLP receiver") } diff --git a/receiver/otlpreceiver/go.mod b/receiver/otlpreceiver/go.mod index 2898b1154e9..c3b1ebefc3f 100644 --- a/receiver/otlpreceiver/go.mod +++ b/receiver/otlpreceiver/go.mod @@ -16,6 +16,7 @@ require ( go.opentelemetry.io/collector/config/confignet v1.25.0 go.opentelemetry.io/collector/config/configtls v1.25.0 go.opentelemetry.io/collector/confmap v1.25.0 + go.opentelemetry.io/collector/confmap/xconfmap v0.0.0-00010101000000-000000000000 go.opentelemetry.io/collector/consumer v1.25.0 go.opentelemetry.io/collector/consumer/consumererror v0.119.0 go.opentelemetry.io/collector/consumer/consumertest v0.119.0 @@ -98,6 +99,8 @@ replace go.opentelemetry.io/collector/config/configtls => ../../config/configtls replace go.opentelemetry.io/collector/confmap => ../../confmap +replace go.opentelemetry.io/collector/confmap/xconfmap => ../../confmap/xconfmap + replace go.opentelemetry.io/collector/extension => ../../extension replace go.opentelemetry.io/collector/extension/auth => ../../extension/auth diff --git a/service/config_test.go b/service/config_test.go index 8ce13a7ec33..0032ac5d7eb 100644 --- a/service/config_test.go +++ b/service/config_test.go @@ -13,6 +13,7 @@ import ( "go.opentelemetry.io/collector/component" "go.opentelemetry.io/collector/config/configtelemetry" + "go.opentelemetry.io/collector/confmap/xconfmap" "go.opentelemetry.io/collector/pipeline" "go.opentelemetry.io/collector/service/extensions" "go.opentelemetry.io/collector/service/pipelines" @@ -77,7 +78,7 @@ func TestConfigValidate(t *testing.T) { for _, tt := range testCases { t.Run(tt.name, func(t *testing.T) { cfg := tt.cfgFn() - err := component.ValidateConfig(cfg) + err := xconfmap.Validate(cfg) if tt.expected != nil { assert.ErrorContains(t, err, tt.expected.Error()) } else { diff --git a/service/go.mod b/service/go.mod index d706b1de331..1a1a51b84e6 100644 --- a/service/go.mod +++ b/service/go.mod @@ -15,6 +15,7 @@ require ( go.opentelemetry.io/collector/config/confighttp v0.119.0 go.opentelemetry.io/collector/config/configtelemetry v0.119.0 go.opentelemetry.io/collector/confmap v1.25.0 + go.opentelemetry.io/collector/confmap/xconfmap v0.0.0-00010101000000-000000000000 go.opentelemetry.io/collector/connector v0.119.0 go.opentelemetry.io/collector/connector/connectortest v0.119.0 go.opentelemetry.io/collector/connector/xconnector v0.119.0 @@ -150,6 +151,8 @@ replace go.opentelemetry.io/collector/exporter => ../exporter replace go.opentelemetry.io/collector/confmap => ../confmap +replace go.opentelemetry.io/collector/confmap/xconfmap => ../confmap/xconfmap + replace go.opentelemetry.io/collector/config/configtelemetry => ../config/configtelemetry replace go.opentelemetry.io/collector/pipeline => ../pipeline diff --git a/service/pipelines/config_test.go b/service/pipelines/config_test.go index 19ca0be6d4a..c24f8c744c9 100644 --- a/service/pipelines/config_test.go +++ b/service/pipelines/config_test.go @@ -10,6 +10,7 @@ import ( "github.com/stretchr/testify/require" "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/confmap/xconfmap" "go.opentelemetry.io/collector/featuregate" "go.opentelemetry.io/collector/pipeline" "go.opentelemetry.io/collector/pipeline/xpipeline" @@ -108,9 +109,9 @@ func TestConfigValidate(t *testing.T) { t.Run(tt.name, func(t *testing.T) { cfg := tt.cfgFn(t) if tt.expected != nil { - require.ErrorContains(t, component.ValidateConfig(cfg), tt.expected.Error()) + require.ErrorContains(t, xconfmap.Validate(cfg), tt.expected.Error()) } else { - require.NoError(t, component.ValidateConfig(cfg)) + require.NoError(t, xconfmap.Validate(cfg)) } // Clean up the profiles support gate, which may have been enabled in `cfgFn`. diff --git a/versions.yaml b/versions.yaml index b524ee3cf42..64aa6c8e6af 100644 --- a/versions.yaml +++ b/versions.yaml @@ -32,6 +32,7 @@ module-sets: - go.opentelemetry.io/collector/component - go.opentelemetry.io/collector/component/componenttest - go.opentelemetry.io/collector/component/componentstatus + - go.opentelemetry.io/collector/confmap/xconfmap - go.opentelemetry.io/collector/config/configauth - go.opentelemetry.io/collector/config/configgrpc - go.opentelemetry.io/collector/config/confighttp