1// Copyright The OpenTelemetry Authors
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//       http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15package configloader
16
17import (
18	"os"
19	"path"
20	"testing"
21
22	"github.com/stretchr/testify/assert"
23	"github.com/stretchr/testify/require"
24
25	"go.opentelemetry.io/collector/component"
26	"go.opentelemetry.io/collector/config"
27	"go.opentelemetry.io/collector/config/confignet"
28	"go.opentelemetry.io/collector/config/configparser"
29	"go.opentelemetry.io/collector/internal/testcomponents"
30)
31
32func TestDecodeConfig(t *testing.T) {
33	factories, err := testcomponents.ExampleComponents()
34	assert.NoError(t, err)
35
36	// Load the config
37	cfg, err := loadConfigFile(t, path.Join(".", "testdata", "valid-config.yaml"), factories)
38	require.NoError(t, err, "Unable to load config")
39
40	// Verify extensions.
41	assert.Equal(t, 3, len(cfg.Extensions))
42	assert.Equal(t, "some string", cfg.Extensions[config.NewIDWithName("exampleextension", "1")].(*testcomponents.ExampleExtensionCfg).ExtraSetting)
43
44	// Verify service.
45	assert.Equal(t, 2, len(cfg.Service.Extensions))
46	assert.Equal(t, config.NewIDWithName("exampleextension", "0"), cfg.Service.Extensions[0])
47	assert.Equal(t, config.NewIDWithName("exampleextension", "1"), cfg.Service.Extensions[1])
48
49	// Verify receivers
50	assert.Equal(t, 2, len(cfg.Receivers), "Incorrect receivers count")
51
52	assert.Equal(t,
53		&testcomponents.ExampleReceiver{
54			ReceiverSettings: config.NewReceiverSettings(config.NewID("examplereceiver")),
55			TCPAddr: confignet.TCPAddr{
56				Endpoint: "localhost:1000",
57			},
58			ExtraSetting: "some string",
59		},
60		cfg.Receivers[config.NewID("examplereceiver")],
61		"Did not load receiver config correctly")
62
63	assert.Equal(t,
64		&testcomponents.ExampleReceiver{
65			ReceiverSettings: config.NewReceiverSettings(config.NewIDWithName("examplereceiver", "myreceiver")),
66			TCPAddr: confignet.TCPAddr{
67				Endpoint: "localhost:12345",
68			},
69			ExtraSetting: "some string",
70		},
71		cfg.Receivers[config.NewIDWithName("examplereceiver", "myreceiver")],
72		"Did not load receiver config correctly")
73
74	// Verify exporters
75	assert.Equal(t, 2, len(cfg.Exporters), "Incorrect exporters count")
76
77	assert.Equal(t,
78		&testcomponents.ExampleExporter{
79			ExporterSettings: config.NewExporterSettings(config.NewID("exampleexporter")),
80			ExtraSetting:     "some export string",
81		},
82		cfg.Exporters[config.NewID("exampleexporter")],
83		"Did not load exporter config correctly")
84
85	assert.Equal(t,
86		&testcomponents.ExampleExporter{
87			ExporterSettings: config.NewExporterSettings(config.NewIDWithName("exampleexporter", "myexporter")),
88			ExtraSetting:     "some export string 2",
89		},
90		cfg.Exporters[config.NewIDWithName("exampleexporter", "myexporter")],
91		"Did not load exporter config correctly")
92
93	// Verify Processors
94	assert.Equal(t, 1, len(cfg.Processors), "Incorrect processors count")
95
96	assert.Equal(t,
97		&testcomponents.ExampleProcessorCfg{
98			ProcessorSettings: config.NewProcessorSettings(config.NewID("exampleprocessor")),
99			ExtraSetting:      "some export string",
100		},
101		cfg.Processors[config.NewID("exampleprocessor")],
102		"Did not load processor config correctly")
103
104	// Verify Pipelines
105	assert.Equal(t, 1, len(cfg.Service.Pipelines), "Incorrect pipelines count")
106
107	assert.Equal(t,
108		&config.Pipeline{
109			Name:       "traces",
110			InputType:  config.TracesDataType,
111			Receivers:  []config.ComponentID{config.NewID("examplereceiver")},
112			Processors: []config.ComponentID{config.NewID("exampleprocessor")},
113			Exporters:  []config.ComponentID{config.NewID("exampleexporter")},
114		},
115		cfg.Service.Pipelines["traces"],
116		"Did not load pipeline config correctly")
117}
118
119func TestSimpleConfig(t *testing.T) {
120	var testCases = []struct {
121		name string // test case name (also file name containing config yaml)
122	}{
123		{name: "simple-config-with-no-env"},
124		{name: "simple-config-with-partial-env"},
125		{name: "simple-config-with-all-env"},
126	}
127
128	const extensionExtra = "some extension string"
129	const extensionExtraMapValue = "some extension map value"
130	const extensionExtraListElement = "some extension list value"
131	assert.NoError(t, os.Setenv("EXTENSIONS_EXAMPLEEXTENSION_EXTRA", extensionExtra))
132	assert.NoError(t, os.Setenv("EXTENSIONS_EXAMPLEEXTENSION_EXTRA_MAP_EXT_VALUE_1", extensionExtraMapValue+"_1"))
133	assert.NoError(t, os.Setenv("EXTENSIONS_EXAMPLEEXTENSION_EXTRA_MAP_EXT_VALUE_2", extensionExtraMapValue+"_2"))
134	assert.NoError(t, os.Setenv("EXTENSIONS_EXAMPLEEXTENSION_EXTRA_LIST_VALUE_1", extensionExtraListElement+"_1"))
135	assert.NoError(t, os.Setenv("EXTENSIONS_EXAMPLEEXTENSION_EXTRA_LIST_VALUE_2", extensionExtraListElement+"_2"))
136
137	const receiverExtra = "some receiver string"
138	const receiverExtraMapValue = "some receiver map value"
139	const receiverExtraListElement = "some receiver list value"
140	assert.NoError(t, os.Setenv("RECEIVERS_EXAMPLERECEIVER_EXTRA", receiverExtra))
141	assert.NoError(t, os.Setenv("RECEIVERS_EXAMPLERECEIVER_EXTRA_MAP_RECV_VALUE_1", receiverExtraMapValue+"_1"))
142	assert.NoError(t, os.Setenv("RECEIVERS_EXAMPLERECEIVER_EXTRA_MAP_RECV_VALUE_2", receiverExtraMapValue+"_2"))
143	assert.NoError(t, os.Setenv("RECEIVERS_EXAMPLERECEIVER_EXTRA_LIST_VALUE_1", receiverExtraListElement+"_1"))
144	assert.NoError(t, os.Setenv("RECEIVERS_EXAMPLERECEIVER_EXTRA_LIST_VALUE_2", receiverExtraListElement+"_2"))
145
146	const processorExtra = "some processor string"
147	const processorExtraMapValue = "some processor map value"
148	const processorExtraListElement = "some processor list value"
149	assert.NoError(t, os.Setenv("PROCESSORS_EXAMPLEPROCESSOR_EXTRA", processorExtra))
150	assert.NoError(t, os.Setenv("PROCESSORS_EXAMPLEPROCESSOR_EXTRA_MAP_PROC_VALUE_1", processorExtraMapValue+"_1"))
151	assert.NoError(t, os.Setenv("PROCESSORS_EXAMPLEPROCESSOR_EXTRA_MAP_PROC_VALUE_2", processorExtraMapValue+"_2"))
152	assert.NoError(t, os.Setenv("PROCESSORS_EXAMPLEPROCESSOR_EXTRA_LIST_VALUE_1", processorExtraListElement+"_1"))
153	assert.NoError(t, os.Setenv("PROCESSORS_EXAMPLEPROCESSOR_EXTRA_LIST_VALUE_2", processorExtraListElement+"_2"))
154
155	const exporterExtra = "some exporter string"
156	const exporterExtraMapValue = "some exporter map value"
157	const exporterExtraListElement = "some exporter list value"
158	assert.NoError(t, os.Setenv("EXPORTERS_EXAMPLEEXPORTER_EXTRA_INT", "65"))
159	assert.NoError(t, os.Setenv("EXPORTERS_EXAMPLEEXPORTER_EXTRA", exporterExtra))
160	assert.NoError(t, os.Setenv("EXPORTERS_EXAMPLEEXPORTER_EXTRA_MAP_EXP_VALUE_1", exporterExtraMapValue+"_1"))
161	assert.NoError(t, os.Setenv("EXPORTERS_EXAMPLEEXPORTER_EXTRA_MAP_EXP_VALUE_2", exporterExtraMapValue+"_2"))
162	assert.NoError(t, os.Setenv("EXPORTERS_EXAMPLEEXPORTER_EXTRA_LIST_VALUE_1", exporterExtraListElement+"_1"))
163	assert.NoError(t, os.Setenv("EXPORTERS_EXAMPLEEXPORTER_EXTRA_LIST_VALUE_2", exporterExtraListElement+"_2"))
164
165	defer func() {
166		assert.NoError(t, os.Unsetenv("EXTENSIONS_EXAMPLEEXTENSION_EXTRA"))
167		assert.NoError(t, os.Unsetenv("EXTENSIONS_EXAMPLEEXTENSION_EXTRA_MAP_EXT_VALUE"))
168		assert.NoError(t, os.Unsetenv("EXTENSIONS_EXAMPLEEXTENSION_EXTRA_LIST_VALUE_1"))
169
170		assert.NoError(t, os.Unsetenv("RECEIVERS_EXAMPLERECEIVER_EXTRA"))
171		assert.NoError(t, os.Unsetenv("RECEIVERS_EXAMPLERECEIVER_EXTRA_MAP_RECV_VALUE"))
172		assert.NoError(t, os.Unsetenv("RECEIVERS_EXAMPLERECEIVER_EXTRA_LIST_VALUE_1"))
173
174		assert.NoError(t, os.Unsetenv("PROCESSORS_EXAMPLEPROCESSOR_EXTRA"))
175		assert.NoError(t, os.Unsetenv("PROCESSORS_EXAMPLEPROCESSOR_EXTRA_MAP_PROC_VALUE"))
176		assert.NoError(t, os.Unsetenv("PROCESSORS_EXAMPLEPROCESSOR_EXTRA_LIST_VALUE_1"))
177
178		assert.NoError(t, os.Unsetenv("EXPORTERS_EXAMPLEEXPORTER_EXTRA_INT"))
179		assert.NoError(t, os.Unsetenv("EXPORTERS_EXAMPLEEXPORTER_EXTRA"))
180		assert.NoError(t, os.Unsetenv("EXPORTERS_EXAMPLEEXPORTER_EXTRA_MAP_EXP_VALUE"))
181		assert.NoError(t, os.Unsetenv("EXPORTERS_EXAMPLEEXPORTER_EXTRA_LIST_VALUE_1"))
182	}()
183
184	for _, test := range testCases {
185		t.Run(test.name, func(t *testing.T) {
186			factories, err := testcomponents.ExampleComponents()
187			assert.NoError(t, err)
188
189			// Load the config
190			cfg, err := loadConfigFile(t, path.Join(".", "testdata", test.name+".yaml"), factories)
191			require.NoError(t, err, "Unable to load config")
192
193			// Verify extensions.
194			assert.Equalf(t, 1, len(cfg.Extensions), "TEST[%s]", test.name)
195			assert.Equalf(t,
196				&testcomponents.ExampleExtensionCfg{
197					ExtensionSettings: config.NewExtensionSettings(config.NewID("exampleextension")),
198					ExtraSetting:      extensionExtra,
199					ExtraMapSetting:   map[string]string{"ext-1": extensionExtraMapValue + "_1", "ext-2": extensionExtraMapValue + "_2"},
200					ExtraListSetting:  []string{extensionExtraListElement + "_1", extensionExtraListElement + "_2"},
201				},
202				cfg.Extensions[config.NewID("exampleextension")],
203				"TEST[%s] Did not load extension config correctly", test.name)
204
205			// Verify service.
206			assert.Equalf(t, 1, len(cfg.Service.Extensions), "TEST[%s]", test.name)
207			assert.Equalf(t, config.NewID("exampleextension"), cfg.Service.Extensions[0], "TEST[%s]", test.name)
208
209			// Verify receivers
210			assert.Equalf(t, 1, len(cfg.Receivers), "TEST[%s]", test.name)
211
212			assert.Equalf(t,
213				&testcomponents.ExampleReceiver{
214					ReceiverSettings: config.NewReceiverSettings(config.NewID("examplereceiver")),
215					TCPAddr: confignet.TCPAddr{
216						Endpoint: "localhost:1234",
217					},
218					ExtraSetting:     receiverExtra,
219					ExtraMapSetting:  map[string]string{"recv.1": receiverExtraMapValue + "_1", "recv.2": receiverExtraMapValue + "_2"},
220					ExtraListSetting: []string{receiverExtraListElement + "_1", receiverExtraListElement + "_2"},
221				},
222				cfg.Receivers[config.NewID("examplereceiver")],
223				"TEST[%s] Did not load receiver config correctly", test.name)
224
225			// Verify exporters
226			assert.Equalf(t, 1, len(cfg.Exporters), "TEST[%s]", test.name)
227
228			assert.Equalf(t,
229				&testcomponents.ExampleExporter{
230					ExporterSettings: config.NewExporterSettings(config.NewID("exampleexporter")),
231					ExtraInt:         65,
232					ExtraSetting:     exporterExtra,
233					ExtraMapSetting:  map[string]string{"exp_1": exporterExtraMapValue + "_1", "exp_2": exporterExtraMapValue + "_2"},
234					ExtraListSetting: []string{exporterExtraListElement + "_1", exporterExtraListElement + "_2"},
235				},
236				cfg.Exporters[config.NewID("exampleexporter")],
237				"TEST[%s] Did not load exporter config correctly", test.name)
238
239			// Verify Processors
240			assert.Equalf(t, 1, len(cfg.Processors), "TEST[%s]", test.name)
241
242			assert.Equalf(t,
243				&testcomponents.ExampleProcessorCfg{
244					ProcessorSettings: config.NewProcessorSettings(config.NewID("exampleprocessor")),
245					ExtraSetting:      processorExtra,
246					ExtraMapSetting:   map[string]string{"proc_1": processorExtraMapValue + "_1", "proc_2": processorExtraMapValue + "_2"},
247					ExtraListSetting:  []string{processorExtraListElement + "_1", processorExtraListElement + "_2"},
248				},
249				cfg.Processors[config.NewID("exampleprocessor")],
250				"TEST[%s] Did not load processor config correctly", test.name)
251
252			// Verify Pipelines
253			assert.Equalf(t, 1, len(cfg.Service.Pipelines), "TEST[%s]", test.name)
254
255			assert.Equalf(t,
256				&config.Pipeline{
257					Name:       "traces",
258					InputType:  config.TracesDataType,
259					Receivers:  []config.ComponentID{config.NewID("examplereceiver")},
260					Processors: []config.ComponentID{config.NewID("exampleprocessor")},
261					Exporters:  []config.ComponentID{config.NewID("exampleexporter")},
262				},
263				cfg.Service.Pipelines["traces"],
264				"TEST[%s] Did not load pipeline config correctly", test.name)
265		})
266	}
267}
268
269func TestEscapedEnvVars(t *testing.T) {
270	const receiverExtraMapValue = "some receiver map value"
271	assert.NoError(t, os.Setenv("RECEIVERS_EXAMPLERECEIVER_EXTRA_MAP_RECV_VALUE_2", receiverExtraMapValue))
272	defer func() {
273		assert.NoError(t, os.Unsetenv("RECEIVERS_EXAMPLERECEIVER_EXTRA_MAP_RECV_VALUE_2"))
274	}()
275
276	factories, err := testcomponents.ExampleComponents()
277	assert.NoError(t, err)
278
279	// Load the config
280	cfg, err := loadConfigFile(t, path.Join(".", "testdata", "simple-config-with-escaped-env.yaml"), factories)
281	require.NoError(t, err, "Unable to load config")
282
283	// Verify extensions.
284	assert.Equal(t, 1, len(cfg.Extensions))
285	assert.Equal(t,
286		&testcomponents.ExampleExtensionCfg{
287			ExtensionSettings: config.NewExtensionSettings(config.NewID("exampleextension")),
288			ExtraSetting:      "${EXTENSIONS_EXAMPLEEXTENSION_EXTRA}",
289			ExtraMapSetting:   map[string]string{"ext-1": "${EXTENSIONS_EXAMPLEEXTENSION_EXTRA_MAP_EXT_VALUE_1}", "ext-2": "${EXTENSIONS_EXAMPLEEXTENSION_EXTRA_MAP_EXT_VALUE_2}"},
290			ExtraListSetting:  []string{"${EXTENSIONS_EXAMPLEEXTENSION_EXTRA_LIST_VALUE_1}", "${EXTENSIONS_EXAMPLEEXTENSION_EXTRA_LIST_VALUE_2}"},
291		},
292		cfg.Extensions[config.NewID("exampleextension")],
293		"Did not load extension config correctly")
294
295	// Verify service.
296	assert.Equal(t, 1, len(cfg.Service.Extensions))
297	assert.Equal(t, config.NewID("exampleextension"), cfg.Service.Extensions[0])
298
299	// Verify receivers
300	assert.Equal(t, 1, len(cfg.Receivers))
301
302	assert.Equal(t,
303		&testcomponents.ExampleReceiver{
304			ReceiverSettings: config.NewReceiverSettings(config.NewID("examplereceiver")),
305			TCPAddr: confignet.TCPAddr{
306				Endpoint: "localhost:1234",
307			},
308			ExtraSetting: "$RECEIVERS_EXAMPLERECEIVER_EXTRA",
309			ExtraMapSetting: map[string]string{
310				// $$ -> escaped $
311				"recv.1": "$RECEIVERS_EXAMPLERECEIVER_EXTRA_MAP_RECV_VALUE_1",
312				// $$$ -> escaped $ + substituted env var
313				"recv.2": "$" + receiverExtraMapValue,
314				// $$$$ -> two escaped $
315				"recv.3": "$$RECEIVERS_EXAMPLERECEIVER_EXTRA_MAP_RECV_VALUE_3",
316				// escaped $ in the middle
317				"recv.4": "some${RECEIVERS_EXAMPLERECEIVER_EXTRA_MAP_RECV_VALUE_4}text",
318				// $$$$ -> two escaped $
319				"recv.5": "${ONE}${TWO}",
320				// trailing escaped $
321				"recv.6": "text$",
322				// escaped $ alone
323				"recv.7": "$",
324			},
325			ExtraListSetting: []string{"$RECEIVERS_EXAMPLERECEIVER_EXTRA_LIST_VALUE_1", "$RECEIVERS_EXAMPLERECEIVER_EXTRA_LIST_VALUE_2"},
326		},
327		cfg.Receivers[config.NewID("examplereceiver")],
328		"Did not load receiver config correctly")
329
330	// Verify exporters
331	assert.Equal(t, 1, len(cfg.Exporters))
332
333	assert.Equal(t,
334		&testcomponents.ExampleExporter{
335			ExporterSettings: config.NewExporterSettings(config.NewID("exampleexporter")),
336			ExtraSetting:     "${EXPORTERS_EXAMPLEEXPORTER_EXTRA}",
337			ExtraMapSetting:  map[string]string{"exp_1": "${EXPORTERS_EXAMPLEEXPORTER_EXTRA_MAP_EXP_VALUE_1}", "exp_2": "${EXPORTERS_EXAMPLEEXPORTER_EXTRA_MAP_EXP_VALUE_2}"},
338			ExtraListSetting: []string{"${EXPORTERS_EXAMPLEEXPORTER_EXTRA_LIST_VALUE_1}", "${EXPORTERS_EXAMPLEEXPORTER_EXTRA_LIST_VALUE_2}"},
339		},
340		cfg.Exporters[config.NewID("exampleexporter")],
341		"Did not load exporter config correctly")
342
343	// Verify Processors
344	assert.Equal(t, 1, len(cfg.Processors))
345
346	assert.Equal(t,
347		&testcomponents.ExampleProcessorCfg{
348			ProcessorSettings: config.NewProcessorSettings(config.NewID("exampleprocessor")),
349			ExtraSetting:      "$PROCESSORS_EXAMPLEPROCESSOR_EXTRA",
350			ExtraMapSetting:   map[string]string{"proc_1": "$PROCESSORS_EXAMPLEPROCESSOR_EXTRA_MAP_PROC_VALUE_1", "proc_2": "$PROCESSORS_EXAMPLEPROCESSOR_EXTRA_MAP_PROC_VALUE_2"},
351			ExtraListSetting:  []string{"$PROCESSORS_EXAMPLEPROCESSOR_EXTRA_LIST_VALUE_1", "$PROCESSORS_EXAMPLEPROCESSOR_EXTRA_LIST_VALUE_2"},
352		},
353		cfg.Processors[config.NewID("exampleprocessor")],
354		"Did not load processor config correctly")
355
356	// Verify Pipelines
357	assert.Equal(t, 1, len(cfg.Service.Pipelines))
358
359	assert.Equal(t,
360		&config.Pipeline{
361			Name:       "traces",
362			InputType:  config.TracesDataType,
363			Receivers:  []config.ComponentID{config.NewID("examplereceiver")},
364			Processors: []config.ComponentID{config.NewID("exampleprocessor")},
365			Exporters:  []config.ComponentID{config.NewID("exampleexporter")},
366		},
367		cfg.Service.Pipelines["traces"],
368		"Did not load pipeline config correctly")
369}
370
371func TestDecodeConfig_Invalid(t *testing.T) {
372
373	var testCases = []struct {
374		name            string          // test case name (also file name containing config yaml)
375		expected        configErrorCode // expected error (if nil any error is acceptable)
376		expectedMessage string          // string that the error must contain
377	}{
378		{name: "invalid-extension-type", expected: errInvalidTypeAndNameKey},
379		{name: "invalid-receiver-type", expected: errInvalidTypeAndNameKey},
380		{name: "invalid-exporter-type", expected: errInvalidTypeAndNameKey},
381		{name: "invalid-processor-type", expected: errInvalidTypeAndNameKey},
382		{name: "invalid-pipeline-type", expected: errInvalidTypeAndNameKey},
383
384		{name: "invalid-extension-name-after-slash", expected: errInvalidTypeAndNameKey},
385		{name: "invalid-receiver-name-after-slash", expected: errInvalidTypeAndNameKey},
386		{name: "invalid-exporter-name-after-slash", expected: errInvalidTypeAndNameKey},
387		{name: "invalid-processor-name-after-slash", expected: errInvalidTypeAndNameKey},
388		{name: "invalid-pipeline-name-after-slash", expected: errInvalidTypeAndNameKey},
389
390		{name: "unknown-extension-type", expected: errUnknownType, expectedMessage: "extensions"},
391		{name: "unknown-receiver-type", expected: errUnknownType, expectedMessage: "receivers"},
392		{name: "unknown-exporter-type", expected: errUnknownType, expectedMessage: "exporters"},
393		{name: "unknown-processor-type", expected: errUnknownType, expectedMessage: "processors"},
394		{name: "unknown-pipeline-type", expected: errUnknownType, expectedMessage: "pipelines"},
395
396		{name: "duplicate-extension", expected: errDuplicateName, expectedMessage: "extensions"},
397		{name: "duplicate-receiver", expected: errDuplicateName, expectedMessage: "receivers"},
398		{name: "duplicate-exporter", expected: errDuplicateName, expectedMessage: "exporters"},
399		{name: "duplicate-processor", expected: errDuplicateName, expectedMessage: "processors"},
400		{name: "duplicate-pipeline", expected: errDuplicateName, expectedMessage: "pipelines"},
401
402		{name: "invalid-top-level-section", expected: errUnmarshalTopLevelStructureError, expectedMessage: "top level"},
403		{name: "invalid-extension-section", expected: errUnmarshalTopLevelStructureError, expectedMessage: "extensions"},
404		{name: "invalid-receiver-section", expected: errUnmarshalTopLevelStructureError, expectedMessage: "receivers"},
405		{name: "invalid-processor-section", expected: errUnmarshalTopLevelStructureError, expectedMessage: "processors"},
406		{name: "invalid-exporter-section", expected: errUnmarshalTopLevelStructureError, expectedMessage: "exporters"},
407		{name: "invalid-service-section", expected: errUnmarshalTopLevelStructureError, expectedMessage: "service"},
408		{name: "invalid-service-extensions-section", expected: errUnmarshalTopLevelStructureError, expectedMessage: "service"},
409		{name: "invalid-pipeline-section", expected: errUnmarshalTopLevelStructureError, expectedMessage: "pipelines"},
410		{name: "invalid-sequence-value", expected: errUnmarshalTopLevelStructureError, expectedMessage: "pipelines"},
411
412		{name: "invalid-extension-sub-config", expected: errUnmarshalTopLevelStructureError},
413		{name: "invalid-exporter-sub-config", expected: errUnmarshalTopLevelStructureError},
414		{name: "invalid-processor-sub-config", expected: errUnmarshalTopLevelStructureError},
415		{name: "invalid-receiver-sub-config", expected: errUnmarshalTopLevelStructureError},
416		{name: "invalid-pipeline-sub-config", expected: errUnmarshalTopLevelStructureError},
417	}
418
419	factories, err := testcomponents.ExampleComponents()
420	assert.NoError(t, err)
421
422	for _, test := range testCases {
423		t.Run(test.name, func(t *testing.T) {
424			_, err := loadConfigFile(t, path.Join(".", "testdata", test.name+".yaml"), factories)
425			require.Error(t, err)
426			if test.expected != 0 {
427				cfgErr, ok := err.(*configError)
428				if !ok {
429					t.Errorf("expected config error code %v but got a different error '%v'", test.expected, err)
430				} else {
431					assert.Equal(t, test.expected, cfgErr.code, err)
432					if test.expectedMessage != "" {
433						assert.Contains(t, cfgErr.Error(), test.expectedMessage)
434					}
435					assert.NotEmpty(t, cfgErr.Error(), "returned config error %v with empty error message", cfgErr.code)
436				}
437			}
438		})
439	}
440}
441
442func TestLoadEmpty(t *testing.T) {
443	factories, err := testcomponents.ExampleComponents()
444	assert.NoError(t, err)
445
446	_, err = loadConfigFile(t, path.Join(".", "testdata", "empty-config.yaml"), factories)
447	assert.NoError(t, err)
448}
449
450func TestLoadEmptyAllSections(t *testing.T) {
451	factories, err := testcomponents.ExampleComponents()
452	assert.NoError(t, err)
453
454	_, err = loadConfigFile(t, path.Join(".", "testdata", "empty-all-sections.yaml"), factories)
455	assert.NoError(t, err)
456}
457
458func loadConfigFile(t *testing.T, fileName string, factories component.Factories) (*config.Config, error) {
459	v, err := configparser.NewParserFromFile(fileName)
460	require.NoError(t, err)
461
462	// Load the config from the configparser.Parser using the given factories.
463	return Load(v, factories)
464}
465
466type nestedConfig struct {
467	NestedStringValue string
468	NestedIntValue    int
469}
470
471type testConfig struct {
472	config.ExporterSettings
473
474	NestedConfigPtr   *nestedConfig
475	NestedConfigValue nestedConfig
476	StringValue       string
477	StringPtrValue    *string
478	IntValue          int
479}
480
481func TestExpandEnvLoadedConfig(t *testing.T) {
482	assert.NoError(t, os.Setenv("NESTED_VALUE", "replaced_nested_value"))
483	assert.NoError(t, os.Setenv("VALUE", "replaced_value"))
484	assert.NoError(t, os.Setenv("PTR_VALUE", "replaced_ptr_value"))
485
486	defer func() {
487		assert.NoError(t, os.Unsetenv("NESTED_VALUE"))
488		assert.NoError(t, os.Unsetenv("VALUE"))
489		assert.NoError(t, os.Unsetenv("PTR_VALUE"))
490	}()
491
492	testString := "$PTR_VALUE"
493
494	cfg := &testConfig{
495		ExporterSettings: config.NewExporterSettings(config.NewID("test")),
496		NestedConfigPtr: &nestedConfig{
497			NestedStringValue: "$NESTED_VALUE",
498			NestedIntValue:    1,
499		},
500		NestedConfigValue: nestedConfig{
501			NestedStringValue: "$NESTED_VALUE",
502			NestedIntValue:    2,
503		},
504		StringValue:    "$VALUE",
505		StringPtrValue: &testString,
506		IntValue:       3,
507	}
508
509	expandEnvLoadedConfig(cfg)
510
511	replacedTestString := "replaced_ptr_value"
512
513	assert.Equal(t, &testConfig{
514		ExporterSettings: config.NewExporterSettings(config.NewID("test")),
515		NestedConfigPtr: &nestedConfig{
516			NestedStringValue: "replaced_nested_value",
517			NestedIntValue:    1,
518		},
519		NestedConfigValue: nestedConfig{
520			NestedStringValue: "replaced_nested_value",
521			NestedIntValue:    2,
522		},
523		StringValue:    "replaced_value",
524		StringPtrValue: &replacedTestString,
525		IntValue:       3,
526	}, cfg)
527}
528
529func TestExpandEnvLoadedConfigEscapedEnv(t *testing.T) {
530	assert.NoError(t, os.Setenv("NESTED_VALUE", "replaced_nested_value"))
531	assert.NoError(t, os.Setenv("ESCAPED_VALUE", "replaced_escaped_value"))
532	assert.NoError(t, os.Setenv("ESCAPED_PTR_VALUE", "replaced_escaped_pointer_value"))
533
534	defer func() {
535		assert.NoError(t, os.Unsetenv("NESTED_VALUE"))
536		assert.NoError(t, os.Unsetenv("ESCAPED_VALUE"))
537		assert.NoError(t, os.Unsetenv("ESCAPED_PTR_VALUE"))
538	}()
539
540	testString := "$$ESCAPED_PTR_VALUE"
541
542	cfg := &testConfig{
543		ExporterSettings: config.NewExporterSettings(config.NewID("test")),
544		NestedConfigPtr: &nestedConfig{
545			NestedStringValue: "$NESTED_VALUE",
546			NestedIntValue:    1,
547		},
548		NestedConfigValue: nestedConfig{
549			NestedStringValue: "$NESTED_VALUE",
550			NestedIntValue:    2,
551		},
552		StringValue:    "$$ESCAPED_VALUE",
553		StringPtrValue: &testString,
554		IntValue:       3,
555	}
556
557	expandEnvLoadedConfig(cfg)
558
559	replacedTestString := "$ESCAPED_PTR_VALUE"
560
561	assert.Equal(t, &testConfig{
562		ExporterSettings: config.NewExporterSettings(config.NewID("test")),
563		NestedConfigPtr: &nestedConfig{
564			NestedStringValue: "replaced_nested_value",
565			NestedIntValue:    1,
566		},
567		NestedConfigValue: nestedConfig{
568			NestedStringValue: "replaced_nested_value",
569			NestedIntValue:    2,
570		},
571		StringValue:    "$ESCAPED_VALUE",
572		StringPtrValue: &replacedTestString,
573		IntValue:       3,
574	}, cfg)
575}
576
577func TestExpandEnvLoadedConfigMissingEnv(t *testing.T) {
578	assert.NoError(t, os.Setenv("NESTED_VALUE", "replaced_nested_value"))
579
580	defer func() {
581		assert.NoError(t, os.Unsetenv("NESTED_VALUE"))
582	}()
583
584	testString := "$PTR_VALUE"
585
586	cfg := &testConfig{
587		ExporterSettings: config.NewExporterSettings(config.NewID("test")),
588		NestedConfigPtr: &nestedConfig{
589			NestedStringValue: "$NESTED_VALUE",
590			NestedIntValue:    1,
591		},
592		NestedConfigValue: nestedConfig{
593			NestedStringValue: "$NESTED_VALUE",
594			NestedIntValue:    2,
595		},
596		StringValue:    "$VALUE",
597		StringPtrValue: &testString,
598		IntValue:       3,
599	}
600
601	expandEnvLoadedConfig(cfg)
602
603	replacedTestString := ""
604
605	assert.Equal(t, &testConfig{
606		ExporterSettings: config.NewExporterSettings(config.NewID("test")),
607		NestedConfigPtr: &nestedConfig{
608			NestedStringValue: "replaced_nested_value",
609			NestedIntValue:    1,
610		},
611		NestedConfigValue: nestedConfig{
612			NestedStringValue: "replaced_nested_value",
613			NestedIntValue:    2,
614		},
615		StringValue:    "",
616		StringPtrValue: &replacedTestString,
617		IntValue:       3,
618	}, cfg)
619}
620
621func TestExpandEnvLoadedConfigNil(t *testing.T) {
622	var cfg *testConfig
623
624	// This should safely do nothing
625	expandEnvLoadedConfig(cfg)
626
627	assert.Equal(t, (*testConfig)(nil), cfg)
628}
629
630func TestExpandEnvLoadedConfigNoPointer(t *testing.T) {
631	assert.NoError(t, os.Setenv("VALUE", "replaced_value"))
632
633	cfg := testConfig{
634		StringValue: "$VALUE",
635	}
636
637	// This should do nothing as cfg is not a pointer
638	expandEnvLoadedConfig(cfg)
639
640	assert.Equal(t, testConfig{StringValue: "$VALUE"}, cfg)
641}
642
643type testUnexportedConfig struct {
644	config.ExporterSettings
645
646	unexportedStringValue string
647	ExportedStringValue   string
648}
649
650func TestExpandEnvLoadedConfigUnexportedField(t *testing.T) {
651	assert.NoError(t, os.Setenv("VALUE", "replaced_value"))
652
653	defer func() {
654		assert.NoError(t, os.Unsetenv("VALUE"))
655	}()
656
657	cfg := &testUnexportedConfig{
658		unexportedStringValue: "$VALUE",
659		ExportedStringValue:   "$VALUE",
660	}
661
662	expandEnvLoadedConfig(cfg)
663
664	assert.Equal(t, &testUnexportedConfig{
665		unexportedStringValue: "$VALUE",
666		ExportedStringValue:   "replaced_value",
667	}, cfg)
668}
669