From b7b6f1b73bc9073d8a1a9bdd74cc4a50c2e47ecd Mon Sep 17 00:00:00 2001 From: Antoine Toulme Date: Tue, 26 Mar 2024 23:06:57 -0700 Subject: [PATCH 01/17] Remove top level block --- component/config_test.go | 11 +++++------ confmap/confmap.go | 21 +++++++++++++-------- confmap/confmap_test.go | 7 ++++--- confmap/doc_test.go | 30 ++++++++++++++++++++++++++++++ 4 files changed, 52 insertions(+), 17 deletions(-) diff --git a/component/config_test.go b/component/config_test.go index caed4dc20ab..fd40584e6d2 100644 --- a/component/config_test.go +++ b/component/config_test.go @@ -424,19 +424,18 @@ func TestNewType(t *testing.T) { } type configWithEmbeddedStruct struct { - String string `mapstructure:"string"` - Num int `mapstructure:"num"` - embeddedUnmarshallingConfig + String string `mapstructure:"string"` + Num int `mapstructure:"num"` + EmbeddedUnmarshallingConfig `mapstructure:",squash"` } -type embeddedUnmarshallingConfig struct { +type EmbeddedUnmarshallingConfig struct { } -func (euc *embeddedUnmarshallingConfig) Unmarshal(_ *confmap.Conf) error { +func (euc *EmbeddedUnmarshallingConfig) Unmarshal(c *confmap.Conf) error { return nil // do nothing. } func TestStructWithEmbeddedUnmarshaling(t *testing.T) { - t.Skip("Skipping, to be fixed with https://github.com/open-telemetry/opentelemetry-collector/issues/7102") cfgMap := confmap.NewFromStringMap(map[string]any{ "string": "foo", "num": 123, diff --git a/confmap/confmap.go b/confmap/confmap.go index f3e0471e1d6..8e3485a47f3 100644 --- a/confmap/confmap.go +++ b/confmap/confmap.go @@ -40,7 +40,8 @@ func NewFromStringMap(data map[string]any) *Conf { // Conf represents the raw configuration map for the OpenTelemetry Collector. // The confmap.Conf can be unmarshalled into the Collector's config using the "service" package. type Conf struct { - k *koanf.Koanf + k *koanf.Koanf + self any } // AllKeys returns all keys holding a value, regardless of where they are set. @@ -79,7 +80,7 @@ func (l *Conf) Unmarshal(result any, opts ...UnmarshalOption) error { for _, opt := range opts { opt.apply(&set) } - return decodeConfig(l, result, !set.ignoreUnused) + return decodeConfig(l, result, !set.ignoreUnused, l.self != result) } type marshalOption struct{} @@ -146,7 +147,7 @@ func (l *Conf) ToStringMap() map[string]any { // uniqueness of component IDs (see mapKeyStringToMapKeyTextUnmarshalerHookFunc). // Decodes time.Duration from strings. Allows custom unmarshaling for structs implementing // encoding.TextUnmarshaler. Allows custom unmarshaling for structs implementing confmap.Unmarshaler. -func decodeConfig(m *Conf, result any, errorUnused bool) error { +func decodeConfig(m *Conf, result any, errorUnused bool, topLevelUnmarshaling bool) error { dc := &mapstructure.DecoderConfig{ ErrorUnused: errorUnused, Result: result, @@ -159,7 +160,7 @@ func decodeConfig(m *Conf, result any, errorUnused bool) error { mapKeyStringToMapKeyTextUnmarshalerHookFunc(), mapstructure.StringToTimeDurationHookFunc(), mapstructure.TextUnmarshallerHookFunc(), - unmarshalerHookFunc(result), + unmarshalerHookFunc(result, topLevelUnmarshaling), // after the main unmarshaler hook is called, // we unmarshal the embedded structs if present to merge with the result: unmarshalerEmbeddedStructsHookFunc(), @@ -290,7 +291,9 @@ func unmarshalerEmbeddedStructsHookFunc() mapstructure.DecodeHookFuncValue { f := to.Type().Field(i) if f.IsExported() && slices.Contains(strings.Split(f.Tag.Get("mapstructure"), ","), "squash") { if unmarshaler, ok := to.Field(i).Addr().Interface().(Unmarshaler); ok { - if err := unmarshaler.Unmarshal(NewFromStringMap(fromAsMap)); err != nil { + c := NewFromStringMap(fromAsMap) + c.self = unmarshaler + if err := unmarshaler.Unmarshal(c); err != nil { return nil, err } // the struct we receive from this unmarshaling only contains fields related to the embedded struct. @@ -313,7 +316,7 @@ func unmarshalerEmbeddedStructsHookFunc() mapstructure.DecodeHookFuncValue { // Provides a mechanism for individual structs to define their own unmarshal logic, // by implementing the Unmarshaler interface. -func unmarshalerHookFunc(result any) mapstructure.DecodeHookFuncValue { +func unmarshalerHookFunc(result any, allowTopLevelUnmarshaler bool) mapstructure.DecodeHookFuncValue { return func(from reflect.Value, to reflect.Value) (any, error) { if !to.CanAddr() { return from.Interface(), nil @@ -321,7 +324,7 @@ func unmarshalerHookFunc(result any) mapstructure.DecodeHookFuncValue { toPtr := to.Addr().Interface() // Need to ignore the top structure to avoid circular dependency. - if toPtr == result { + if toPtr == result && !allowTopLevelUnmarshaler { return from.Interface(), nil } @@ -339,7 +342,9 @@ func unmarshalerHookFunc(result any) mapstructure.DecodeHookFuncValue { unmarshaler = reflect.New(to.Type()).Interface().(Unmarshaler) } - if err := unmarshaler.Unmarshal(NewFromStringMap(from.Interface().(map[string]any))); err != nil { + c := NewFromStringMap(from.Interface().(map[string]any)) + c.self = unmarshaler + if err := unmarshaler.Unmarshal(c); err != nil { return nil, err } diff --git a/confmap/confmap_test.go b/confmap/confmap_test.go index a1ea94e1fe4..5bec34fda90 100644 --- a/confmap/confmap_test.go +++ b/confmap/confmap_test.go @@ -485,7 +485,7 @@ func TestUnmarshaler(t *testing.T) { tc := &testConfig{} assert.NoError(t, cfgMap.Unmarshal(tc)) - assert.Equal(t, "make sure this", tc.Another) + assert.Equal(t, "make sure this is only called directly", tc.Another) assert.Equal(t, "make sure this is called", tc.Next.String) assert.Equal(t, "make sure this is also called", tc.EmbeddedConfig.Some) assert.Equal(t, "this better be also called2", tc.EmbeddedConfig2.Some2) @@ -523,6 +523,7 @@ func TestEmbeddedUnmarshalerError(t *testing.T) { } func TestEmbeddedMarshalerError(t *testing.T) { + t.Skip("This test fails because the main struct calls the embedded struct Unmarshal method, and doesn't execute the embedded struct hook.") cfgMap := NewFromStringMap(map[string]any{ "next": map[string]any{ "string": "make sure this", @@ -546,7 +547,7 @@ func TestUnmarshalerKeepAlreadyInitialized(t *testing.T) { private: "keep already configured members", }} assert.NoError(t, cfgMap.Unmarshal(tc)) - assert.Equal(t, "make sure this", tc.Another) + assert.Equal(t, "make sure this is only called directly", tc.Another) assert.Equal(t, "make sure this is called", tc.Next.String) assert.Equal(t, "keep already configured members", tc.Next.private) } @@ -563,7 +564,7 @@ func TestDirectUnmarshaler(t *testing.T) { private: "keep already configured members", }} assert.NoError(t, tc.Unmarshal(cfgMap)) - assert.Equal(t, "make sure this is only called directly", tc.Another) + assert.Equal(t, "make sure this is only called directly is only called directly", tc.Another) assert.Equal(t, "make sure this is called", tc.Next.String) assert.Equal(t, "keep already configured members", tc.Next.private) } diff --git a/confmap/doc_test.go b/confmap/doc_test.go index d026bc2d733..35492d3cae0 100644 --- a/confmap/doc_test.go +++ b/confmap/doc_test.go @@ -75,6 +75,21 @@ func (n *NetworkScrape) Unmarshal(c *confmap.Conf) error { return nil } +type ManualScrapeInfo struct { + Disk string + Scrape time.Duration +} + +func (m *ManualScrapeInfo) Unmarshal(c *confmap.Conf) error { + m.Disk = c.Get("disk").(string) + if c.Get("vinyl") == "33" { + m.Scrape = 10 * time.Second + } else { + m.Scrape = 2 * time.Second + } + return nil +} + type RouterScrape struct { NetworkScrape `mapstructure:",squash"` } @@ -95,3 +110,18 @@ func Example_embeddedManualUnmarshaling() { // Wifi: true // Enabled: true } + +func Example_manualUnmarshaling() { + conf := confmap.NewFromStringMap(map[string]any{ + "disk": "Beatles", + "vinyl": "33", + }) + scrapeInfo := &ManualScrapeInfo{} + if err := conf.Unmarshal(scrapeInfo, confmap.WithIgnoreUnused()); err != nil { + panic(err) + } + fmt.Printf("Configuration contains the following:\nDisk: %q\nScrape: %s\n", scrapeInfo.Disk, scrapeInfo.Scrape) + //Output: Configuration contains the following: + // Disk: "Beatles" + // Scrape: 10s +} From 7d683dc617f24babc10126b1d33a714140347b84 Mon Sep 17 00:00:00 2001 From: Antoine Toulme Date: Tue, 26 Mar 2024 23:34:38 -0700 Subject: [PATCH 02/17] bring back skip, to be done in next PR --- component/config_test.go | 1 + 1 file changed, 1 insertion(+) diff --git a/component/config_test.go b/component/config_test.go index fd40584e6d2..334099539b8 100644 --- a/component/config_test.go +++ b/component/config_test.go @@ -436,6 +436,7 @@ func (euc *EmbeddedUnmarshallingConfig) Unmarshal(c *confmap.Conf) error { return nil // do nothing. } func TestStructWithEmbeddedUnmarshaling(t *testing.T) { + t.Skip("Skipping, to be fixed with https://github.com/open-telemetry/opentelemetry-collector/issues/7102") cfgMap := confmap.NewFromStringMap(map[string]any{ "string": "foo", "num": 123, From 8dc069b3846b84d2a04d23a8705813bca5cd9d5a Mon Sep 17 00:00:00 2001 From: Antoine Toulme Date: Tue, 26 Mar 2024 23:35:39 -0700 Subject: [PATCH 03/17] remove unneeded change --- component/config_test.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/component/config_test.go b/component/config_test.go index 334099539b8..caed4dc20ab 100644 --- a/component/config_test.go +++ b/component/config_test.go @@ -424,15 +424,15 @@ func TestNewType(t *testing.T) { } type configWithEmbeddedStruct struct { - String string `mapstructure:"string"` - Num int `mapstructure:"num"` - EmbeddedUnmarshallingConfig `mapstructure:",squash"` + String string `mapstructure:"string"` + Num int `mapstructure:"num"` + embeddedUnmarshallingConfig } -type EmbeddedUnmarshallingConfig struct { +type embeddedUnmarshallingConfig struct { } -func (euc *EmbeddedUnmarshallingConfig) Unmarshal(c *confmap.Conf) error { +func (euc *embeddedUnmarshallingConfig) Unmarshal(_ *confmap.Conf) error { return nil // do nothing. } func TestStructWithEmbeddedUnmarshaling(t *testing.T) { From 4d16e7e51e24990421b5992e8a6570d22d9daab2 Mon Sep 17 00:00:00 2001 From: Antoine Toulme Date: Tue, 2 Apr 2024 17:00:19 -0700 Subject: [PATCH 04/17] Update confmap/confmap.go Co-authored-by: Pablo Baeyens --- confmap/confmap.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/confmap/confmap.go b/confmap/confmap.go index 8e3485a47f3..e1260781346 100644 --- a/confmap/confmap.go +++ b/confmap/confmap.go @@ -41,6 +41,10 @@ func NewFromStringMap(data map[string]any) *Conf { // The confmap.Conf can be unmarshalled into the Collector's config using the "service" package. type Conf struct { k *koanf.Koanf + + // self stores the struct where we are unmarshaling Conf. + // Conf structs built outside of the confmap package will have this set to nil. + // self it not nil on auxiliary Conf structs built in the mapstructure hooks that check for the Unmarshaler interface. self any } From 78db912d43fc17eb80c61c0d22b1bb7f8dd757ce Mon Sep 17 00:00:00 2001 From: Antoine Toulme Date: Tue, 2 Apr 2024 17:02:03 -0700 Subject: [PATCH 05/17] Update confmap/confmap.go Co-authored-by: Pablo Baeyens --- confmap/confmap.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/confmap/confmap.go b/confmap/confmap.go index e1260781346..a981364ef52 100644 --- a/confmap/confmap.go +++ b/confmap/confmap.go @@ -151,7 +151,7 @@ func (l *Conf) ToStringMap() map[string]any { // uniqueness of component IDs (see mapKeyStringToMapKeyTextUnmarshalerHookFunc). // Decodes time.Duration from strings. Allows custom unmarshaling for structs implementing // encoding.TextUnmarshaler. Allows custom unmarshaling for structs implementing confmap.Unmarshaler. -func decodeConfig(m *Conf, result any, errorUnused bool, topLevelUnmarshaling bool) error { +func decodeConfig(m *Conf, result any, errorUnused bool, skipTopLevelUnmarshaler bool) error { dc := &mapstructure.DecoderConfig{ ErrorUnused: errorUnused, Result: result, From ca76e883d554758d90899cd84d2fc956e85374e0 Mon Sep 17 00:00:00 2001 From: Antoine Toulme Date: Tue, 2 Apr 2024 17:02:08 -0700 Subject: [PATCH 06/17] Update confmap/confmap.go Co-authored-by: Pablo Baeyens --- confmap/confmap.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/confmap/confmap.go b/confmap/confmap.go index a981364ef52..b6485c8ab06 100644 --- a/confmap/confmap.go +++ b/confmap/confmap.go @@ -320,7 +320,7 @@ func unmarshalerEmbeddedStructsHookFunc() mapstructure.DecodeHookFuncValue { // Provides a mechanism for individual structs to define their own unmarshal logic, // by implementing the Unmarshaler interface. -func unmarshalerHookFunc(result any, allowTopLevelUnmarshaler bool) mapstructure.DecodeHookFuncValue { +func unmarshalerHookFunc(result any, skipTopLevelUnmarshaler bool) mapstructure.DecodeHookFuncValue { return func(from reflect.Value, to reflect.Value) (any, error) { if !to.CanAddr() { return from.Interface(), nil From 3ffba993aadef8373e90ee44c5b52a3872ea0ae3 Mon Sep 17 00:00:00 2001 From: Antoine Toulme Date: Tue, 2 Apr 2024 17:02:18 -0700 Subject: [PATCH 07/17] Update confmap/confmap.go Co-authored-by: Pablo Baeyens --- confmap/confmap.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/confmap/confmap.go b/confmap/confmap.go index b6485c8ab06..81c2e1c6c3c 100644 --- a/confmap/confmap.go +++ b/confmap/confmap.go @@ -164,7 +164,7 @@ func decodeConfig(m *Conf, result any, errorUnused bool, skipTopLevelUnmarshaler mapKeyStringToMapKeyTextUnmarshalerHookFunc(), mapstructure.StringToTimeDurationHookFunc(), mapstructure.TextUnmarshallerHookFunc(), - unmarshalerHookFunc(result, topLevelUnmarshaling), + unmarshalerHookFunc(result, skipTopLevelUnmarshaler), // after the main unmarshaler hook is called, // we unmarshal the embedded structs if present to merge with the result: unmarshalerEmbeddedStructsHookFunc(), From 8037fe5f3642b98f554a6c72592885c0b4a400dc Mon Sep 17 00:00:00 2001 From: Antoine Toulme Date: Tue, 2 Apr 2024 17:02:28 -0700 Subject: [PATCH 08/17] Update confmap/confmap.go Co-authored-by: Pablo Baeyens --- confmap/confmap.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/confmap/confmap.go b/confmap/confmap.go index 81c2e1c6c3c..b91bc7d559a 100644 --- a/confmap/confmap.go +++ b/confmap/confmap.go @@ -328,7 +328,7 @@ func unmarshalerHookFunc(result any, skipTopLevelUnmarshaler bool) mapstructure. toPtr := to.Addr().Interface() // Need to ignore the top structure to avoid circular dependency. - if toPtr == result && !allowTopLevelUnmarshaler { + if toPtr == result && skipTopLevelUnmarshaler { return from.Interface(), nil } From dca435179090e0923857061b692844499c179645 Mon Sep 17 00:00:00 2001 From: Antoine Toulme Date: Tue, 2 Apr 2024 17:02:35 -0700 Subject: [PATCH 09/17] Update confmap/confmap.go Co-authored-by: Pablo Baeyens --- confmap/confmap.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/confmap/confmap.go b/confmap/confmap.go index b91bc7d559a..94f37c139cd 100644 --- a/confmap/confmap.go +++ b/confmap/confmap.go @@ -84,7 +84,7 @@ func (l *Conf) Unmarshal(result any, opts ...UnmarshalOption) error { for _, opt := range opts { opt.apply(&set) } - return decodeConfig(l, result, !set.ignoreUnused, l.self != result) + return decodeConfig(l, result, !set.ignoreUnused, l.self == result) } type marshalOption struct{} From 1516551f5f496855a9c30de7f5ffc88ab36c526f Mon Sep 17 00:00:00 2001 From: Antoine Toulme Date: Tue, 2 Apr 2024 17:03:53 -0700 Subject: [PATCH 10/17] add changelog --- .chloggen/remove_top_level_block.yaml | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 .chloggen/remove_top_level_block.yaml diff --git a/.chloggen/remove_top_level_block.yaml b/.chloggen/remove_top_level_block.yaml new file mode 100644 index 00000000000..474c59ea01c --- /dev/null +++ b/.chloggen/remove_top_level_block.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: enhancement + +# The name of the component, or a single word describing the area of concern, (e.g. otlpreceiver) +component: confmap + +# A brief description of the change. Surround your text with quotes ("") if it needs to start with a backtick (`). +note: Remove top level lock when considering struct as Unmarshalers + +# One or more tracking issues or pull requests related to the change +issues: [7101] + +# (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: [] From 191bac6acf3ef9479b2f9eaf0569684ec5634a27 Mon Sep 17 00:00:00 2001 From: Antoine Toulme Date: Tue, 2 Apr 2024 17:14:20 -0700 Subject: [PATCH 11/17] add TestRecursiveUnmarshaling --- confmap/confmap_test.go | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/confmap/confmap_test.go b/confmap/confmap_test.go index 5bec34fda90..bc856d66369 100644 --- a/confmap/confmap_test.go +++ b/confmap/confmap_test.go @@ -828,3 +828,27 @@ func TestUnmarshalOwnThroughEmbeddedSquashedStruct(t *testing.T) { require.Equal(t, "success", cfg.Cfg.EmbeddedStructWithUnmarshal.success) require.Equal(t, "bar", cfg.Cfg.EmbeddedStructWithUnmarshal.Foo) } + +type Recursive struct { + Foo string `mapstructure:"foo"` +} + +func (r *Recursive) Unmarshal(conf *Conf) error { + newR := &Recursive{} + if err := conf.Unmarshal(newR); err != nil { + return err + } + *r = *newR + return nil +} + +// Tests that a struct can unmarshal itself by creating a new copy of itself, unmarshaling itself, and setting its value. +func TestRecursiveUnmarshaling(t *testing.T) { + t.Skip("this test fails to run because it engages in recursive unmarshaling") + conf := NewFromStringMap(map[string]any{ + "foo": "something", + }) + r := &Recursive{} + require.NoError(t, conf.Unmarshal(r)) + require.Equal(t, "something", r.Foo) +} From 1a8c6aca2cfd7570584a7992ba691602942944e3 Mon Sep 17 00:00:00 2001 From: Antoine Toulme Date: Wed, 3 Apr 2024 23:29:10 -0700 Subject: [PATCH 12/17] fix gofmt --- confmap/confmap.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/confmap/confmap.go b/confmap/confmap.go index 94f37c139cd..400e9e3b9cd 100644 --- a/confmap/confmap.go +++ b/confmap/confmap.go @@ -40,7 +40,7 @@ func NewFromStringMap(data map[string]any) *Conf { // Conf represents the raw configuration map for the OpenTelemetry Collector. // The confmap.Conf can be unmarshalled into the Collector's config using the "service" package. type Conf struct { - k *koanf.Koanf + k *koanf.Koanf // self stores the struct where we are unmarshaling Conf. // Conf structs built outside of the confmap package will have this set to nil. From bbef55f5179e40d76cab1d14bbbb5b70189fd735 Mon Sep 17 00:00:00 2001 From: Antoine Toulme Date: Wed, 3 Apr 2024 23:34:04 -0700 Subject: [PATCH 13/17] simplify, drop self and use skipTopLevelUnmarshaler --- confmap/confmap.go | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/confmap/confmap.go b/confmap/confmap.go index 400e9e3b9cd..d1446aca6bb 100644 --- a/confmap/confmap.go +++ b/confmap/confmap.go @@ -40,12 +40,8 @@ func NewFromStringMap(data map[string]any) *Conf { // Conf represents the raw configuration map for the OpenTelemetry Collector. // The confmap.Conf can be unmarshalled into the Collector's config using the "service" package. type Conf struct { - k *koanf.Koanf - - // self stores the struct where we are unmarshaling Conf. - // Conf structs built outside of the confmap package will have this set to nil. - // self it not nil on auxiliary Conf structs built in the mapstructure hooks that check for the Unmarshaler interface. - self any + k *koanf.Koanf + skipTopLevelUnmarshaler bool } // AllKeys returns all keys holding a value, regardless of where they are set. @@ -84,7 +80,7 @@ func (l *Conf) Unmarshal(result any, opts ...UnmarshalOption) error { for _, opt := range opts { opt.apply(&set) } - return decodeConfig(l, result, !set.ignoreUnused, l.self == result) + return decodeConfig(l, result, !set.ignoreUnused, l.skipTopLevelUnmarshaler) } type marshalOption struct{} @@ -296,7 +292,7 @@ func unmarshalerEmbeddedStructsHookFunc() mapstructure.DecodeHookFuncValue { if f.IsExported() && slices.Contains(strings.Split(f.Tag.Get("mapstructure"), ","), "squash") { if unmarshaler, ok := to.Field(i).Addr().Interface().(Unmarshaler); ok { c := NewFromStringMap(fromAsMap) - c.self = unmarshaler + c.skipTopLevelUnmarshaler = true if err := unmarshaler.Unmarshal(c); err != nil { return nil, err } @@ -347,7 +343,7 @@ func unmarshalerHookFunc(result any, skipTopLevelUnmarshaler bool) mapstructure. } c := NewFromStringMap(from.Interface().(map[string]any)) - c.self = unmarshaler + c.skipTopLevelUnmarshaler = true if err := unmarshaler.Unmarshal(c); err != nil { return nil, err } From fee9649de67d85da4bbc654baafcf31b23dc9325 Mon Sep 17 00:00:00 2001 From: Antoine Toulme Date: Thu, 4 Apr 2024 17:49:55 -0700 Subject: [PATCH 14/17] remove skip --- confmap/confmap_test.go | 1 - 1 file changed, 1 deletion(-) diff --git a/confmap/confmap_test.go b/confmap/confmap_test.go index bc856d66369..a8a331a6e1b 100644 --- a/confmap/confmap_test.go +++ b/confmap/confmap_test.go @@ -844,7 +844,6 @@ func (r *Recursive) Unmarshal(conf *Conf) error { // Tests that a struct can unmarshal itself by creating a new copy of itself, unmarshaling itself, and setting its value. func TestRecursiveUnmarshaling(t *testing.T) { - t.Skip("this test fails to run because it engages in recursive unmarshaling") conf := NewFromStringMap(map[string]any{ "foo": "something", }) From 4f5aa259e8284eaa1acafdc64abe68adf8aefabb Mon Sep 17 00:00:00 2001 From: Antoine Toulme Date: Wed, 24 Apr 2024 00:10:41 -0700 Subject: [PATCH 15/17] add comments --- confmap/confmap.go | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/confmap/confmap.go b/confmap/confmap.go index d1446aca6bb..5d65786b8ed 100644 --- a/confmap/confmap.go +++ b/confmap/confmap.go @@ -40,7 +40,10 @@ func NewFromStringMap(data map[string]any) *Conf { // Conf represents the raw configuration map for the OpenTelemetry Collector. // The confmap.Conf can be unmarshalled into the Collector's config using the "service" package. type Conf struct { - k *koanf.Koanf + k *koanf.Koanf + // If true, upon unmarshaling do not call the Unmarshal function on the struct + // if it implements Unmarshaler and is the top-level struct. + // This avoids running into a circular dependency. skipTopLevelUnmarshaler bool } @@ -315,7 +318,8 @@ func unmarshalerEmbeddedStructsHookFunc() mapstructure.DecodeHookFuncValue { } // Provides a mechanism for individual structs to define their own unmarshal logic, -// by implementing the Unmarshaler interface. +// by implementing the Unmarshaler interface, unless skipTopLevelUnmarshaler is +// true and the struct matches the top level object being unmarshaled. func unmarshalerHookFunc(result any, skipTopLevelUnmarshaler bool) mapstructure.DecodeHookFuncValue { return func(from reflect.Value, to reflect.Value) (any, error) { if !to.CanAddr() { @@ -381,6 +385,7 @@ func marshalerHookFunc(orig any) mapstructure.DecodeHookFuncValue { type Unmarshaler interface { // Unmarshal a Conf into the struct in a custom way. // The Conf for this specific component may be nil or empty if no config available. + // This method should only be called by decoding hooks when calling Conf.Unmarshal. Unmarshal(component *Conf) error } From 236bfa0be5233b8ae1e591d79469849be10406c7 Mon Sep 17 00:00:00 2001 From: Antoine Toulme Date: Tue, 30 Apr 2024 16:44:06 -0700 Subject: [PATCH 16/17] update comments --- confmap/confmap.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/confmap/confmap.go b/confmap/confmap.go index 5d65786b8ed..9d2d8834b19 100644 --- a/confmap/confmap.go +++ b/confmap/confmap.go @@ -43,7 +43,8 @@ type Conf struct { k *koanf.Koanf // If true, upon unmarshaling do not call the Unmarshal function on the struct // if it implements Unmarshaler and is the top-level struct. - // This avoids running into a circular dependency. + // This avoids running into an infinite recursion where Unmarshaler.Unmarshal and + // Conf.Unmarshal would call each other. skipTopLevelUnmarshaler bool } @@ -327,7 +328,8 @@ func unmarshalerHookFunc(result any, skipTopLevelUnmarshaler bool) mapstructure. } toPtr := to.Addr().Interface() - // Need to ignore the top structure to avoid circular dependency. + // Need to ignore the top structure to avoid running into an infinite recursion + // where Unmarshaler.Unmarshal and Conf.Unmarshal would call each other. if toPtr == result && skipTopLevelUnmarshaler { return from.Interface(), nil } From 33b6fa55ee6783eb45083d74f66c5b6c66168e5c Mon Sep 17 00:00:00 2001 From: Alex Boten <223565+codeboten@users.noreply.github.com> Date: Fri, 3 May 2024 07:57:19 -0700 Subject: [PATCH 17/17] Update .chloggen/remove_top_level_block.yaml --- .chloggen/remove_top_level_block.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.chloggen/remove_top_level_block.yaml b/.chloggen/remove_top_level_block.yaml index 474c59ea01c..811554a7e63 100644 --- a/.chloggen/remove_top_level_block.yaml +++ b/.chloggen/remove_top_level_block.yaml @@ -7,7 +7,7 @@ change_type: enhancement component: confmap # A brief description of the change. Surround your text with quotes ("") if it needs to start with a backtick (`). -note: Remove top level lock when considering struct as Unmarshalers +note: Remove top level condition when considering struct as Unmarshalers # One or more tracking issues or pull requests related to the change issues: [7101]