Skip to content

Commit 6b4ed90

Browse files
committed
service: Reject duplicate component IDs in configuration
1 parent faeb7ba commit 6b4ed90

File tree

5 files changed

+103
-1
lines changed

5 files changed

+103
-1
lines changed
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
change_type: bug_fix
2+
component: service
3+
note: Reject configuration with duplicate component IDs in extensions, receivers, and exporters lists.
4+
issues: [13912]

service/extensions/config.go

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,24 @@
33

44
package extensions // import "go.opentelemetry.io/collector/service/extensions"
55

6-
import "go.opentelemetry.io/collector/component"
6+
import (
7+
"fmt"
8+
9+
"go.opentelemetry.io/collector/component"
10+
)
711

812
// Config represents the ordered list of extensions configured for the service.
913
type Config []component.ID
14+
15+
// Validate checks if the configuration is valid.
16+
func (cfg Config) Validate() error {
17+
// Validate no extensions are duplicated.
18+
extSet := make(map[component.ID]struct{}, len(cfg))
19+
for _, ref := range cfg {
20+
if _, exists := extSet[ref]; exists {
21+
return fmt.Errorf("references extension %q multiple times", ref)
22+
}
23+
extSet[ref] = struct{}{}
24+
}
25+
return nil
26+
}

service/extensions/config_test.go

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
// Copyright The OpenTelemetry Authors
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package extensions
5+
6+
import (
7+
"errors"
8+
"testing"
9+
10+
"github.com/stretchr/testify/require"
11+
12+
"go.opentelemetry.io/collector/component"
13+
"go.opentelemetry.io/collector/confmap/xconfmap"
14+
)
15+
16+
func TestConfigValidate(t *testing.T) {
17+
testCases := []struct {
18+
name string
19+
cfg Config
20+
expected error
21+
}{
22+
{
23+
name: "valid",
24+
cfg: Config{component.MustNewID("nop")},
25+
expected: nil,
26+
},
27+
{
28+
name: "duplicate-extension-reference",
29+
cfg: Config{component.MustNewID("nop"), component.MustNewID("nop")},
30+
expected: errors.New(`references extension "nop" multiple times`),
31+
},
32+
}
33+
34+
for _, tt := range testCases {
35+
t.Run(tt.name, func(t *testing.T) {
36+
if tt.expected != nil {
37+
require.ErrorContains(t, xconfmap.Validate(tt.cfg), tt.expected.Error())
38+
} else {
39+
require.NoError(t, xconfmap.Validate(tt.cfg))
40+
}
41+
})
42+
}
43+
}

service/pipelines/config.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,15 @@ func (cfg *PipelineConfig) Validate() error {
7777
return errMissingServicePipelineExporters
7878
}
7979

80+
// Validate no receivers are duplicated within a pipeline.
81+
recvSet := make(map[component.ID]struct{}, len(cfg.Receivers))
82+
for _, ref := range cfg.Receivers {
83+
if _, exists := recvSet[ref]; exists {
84+
return fmt.Errorf("references receiver %q multiple times", ref)
85+
}
86+
recvSet[ref] = struct{}{}
87+
}
88+
8089
// Validate no processors are duplicated within a pipeline.
8190
procSet := make(map[component.ID]struct{}, len(cfg.Processors))
8291
for _, ref := range cfg.Processors {
@@ -87,5 +96,14 @@ func (cfg *PipelineConfig) Validate() error {
8796
procSet[ref] = struct{}{}
8897
}
8998

99+
// Validate no exporters are duplicated within a pipeline.
100+
expSet := make(map[component.ID]struct{}, len(cfg.Exporters))
101+
for _, ref := range cfg.Exporters {
102+
if _, exists := expSet[ref]; exists {
103+
return fmt.Errorf("references exporter %q multiple times", ref)
104+
}
105+
expSet[ref] = struct{}{}
106+
}
107+
90108
return nil
91109
}

service/pipelines/config_test.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,26 @@ func TestConfigValidate(t *testing.T) {
3737
},
3838
expected: errors.New(`references processor "nop" multiple times`),
3939
},
40+
{
41+
name: "duplicate-receiver-reference",
42+
cfgFn: func(*testing.T) Config {
43+
cfg := generateConfig(t)
44+
pipe := cfg[pipeline.NewID(pipeline.SignalTraces)]
45+
pipe.Receivers = append(pipe.Receivers, pipe.Receivers...)
46+
return cfg
47+
},
48+
expected: errors.New(`references receiver "nop" multiple times`),
49+
},
50+
{
51+
name: "duplicate-exporter-reference",
52+
cfgFn: func(*testing.T) Config {
53+
cfg := generateConfig(t)
54+
pipe := cfg[pipeline.NewID(pipeline.SignalTraces)]
55+
pipe.Exporters = append(pipe.Exporters, pipe.Exporters...)
56+
return cfg
57+
},
58+
expected: errors.New(`references exporter "nop" multiple times`),
59+
},
4060
{
4161
name: "missing-pipeline-receivers",
4262
cfgFn: func(*testing.T) Config {

0 commit comments

Comments
 (0)