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