Skip to content

Commit d6d07c8

Browse files
VihasMakwanamauri870mx-psievan-bradley
authored
[service]: add new subcommand to examine the initial configuration (#11775)
<!--Ex. Fixing a bug - Describe the bug and how this fixes the issue. Ex. Adding a feature - Explain what this achieves.--> #### Description **_Why is this useful?_** - Users might want to examine the config if they face any errors at boot time. This subcommand prints the configuration on stdout after merging and resolving from all the `--config` sources. - The command prints the configuration even if it's invalid. This can help user to track down the faulty piece of config. **_Usage_** ```bash ./otelcorecol examine --config=file:file.yaml --config=http://remote:8080/config --config=file:file2.yaml ``` <!-- Issue number if applicable --> #### Link to tracking issue Fixes ##11479 <!--Describe what testing was performed and which tests were added.--> #### Testing Added unit test cases. <!--Describe the documentation added.--> #### Documentation Updated readme. <!--Please delete paragraphs that you did not use before submitting.--> --------- Co-authored-by: Mauri de Souza Meneguzzo <[email protected]> Co-authored-by: Pablo Baeyens <[email protected]> Co-authored-by: Pablo Baeyens <[email protected]> Co-authored-by: Evan Bradley <[email protected]>
1 parent 56fbf4c commit d6d07c8

File tree

12 files changed

+327
-0
lines changed

12 files changed

+327
-0
lines changed

.chloggen/examine-subcommand.yaml

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
# Use this changelog template to create an entry for release notes.
2+
3+
# One of 'breaking', 'deprecation', 'new_component', 'enhancement', 'bug_fix'
4+
change_type: enhancement
5+
6+
# The name of the component, or a single word describing the area of concern, (e.g. otlpreceiver)
7+
component: service
8+
9+
# A brief description of the change. Surround your text with quotes ("") if it needs to start with a backtick (`).
10+
note: Create a new subcommand to dump the initial configuration after resolving/merging.
11+
12+
# One or more tracking issues or pull requests related to the change
13+
issues: [11479]
14+
15+
# (Optional) One or more lines of additional information to render under the primary note.
16+
# These lines will be padded with 2 spaces and then inserted directly into the document.
17+
# Use pipe (|) for multiline entries.
18+
subtext: |
19+
To use the `print-initial-config` subcommand, invoke the Collector with the subcommand and corresponding feature gate: `otelcol print-initial-config --feature-gates=otelcol.printInitialConfig --config=config.yaml`.
20+
Note that the feature gate enabling this flag is currently in alpha stability, and the subcommand may
21+
be changed in the future.
22+
23+
# Optional: The change log or logs in which this entry should be included.
24+
# e.g. '[user]' or '[user, api]'
25+
# Include 'user' if the change is relevant to end users.
26+
# Include 'api' if there is a change to a library API.
27+
# Default: '[user]'
28+
change_logs: []

internal/e2e/go.mod

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -249,3 +249,7 @@ replace go.opentelemetry.io/collector/extension/auth/authtest => ../../extension
249249
replace go.opentelemetry.io/collector/extension/xextension => ../../extension/xextension
250250

251251
replace go.opentelemetry.io/collector/otelcol => ../../otelcol
252+
253+
replace go.opentelemetry.io/collector/confmap/provider/yamlprovider => ../../confmap/provider/yamlprovider
254+
255+
replace go.opentelemetry.io/collector/confmap/provider/fileprovider => ../../confmap/provider/fileprovider

otelcol/command.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ func NewCommand(set CollectorSettings) *cobra.Command {
4242
rootCmd.AddCommand(newFeatureGateCommand())
4343
rootCmd.AddCommand(newComponentsCommand(set))
4444
rootCmd.AddCommand(newValidateSubCommand(set, flagSet))
45+
rootCmd.AddCommand(newConfigPrintSubCommand(set, flagSet))
4546
rootCmd.Flags().AddGoFlagSet(flagSet)
4647
return rootCmd
4748
}

otelcol/command_print.go

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
// Copyright The OpenTelemetry Authors
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package otelcol // import "go.opentelemetry.io/collector/otelcol"
5+
6+
import (
7+
"errors"
8+
"flag"
9+
"fmt"
10+
11+
"github.com/spf13/cobra"
12+
"gopkg.in/yaml.v3"
13+
14+
"go.opentelemetry.io/collector/confmap"
15+
"go.opentelemetry.io/collector/featuregate"
16+
)
17+
18+
var printCommandFeatureFlag = featuregate.GlobalRegistry().MustRegister(
19+
"otelcol.printInitialConfig",
20+
featuregate.StageAlpha,
21+
featuregate.WithRegisterFromVersion("v0.120.0"),
22+
featuregate.WithRegisterDescription("if set to true, turns on the print-initial-config command"),
23+
)
24+
25+
// newConfigPrintSubCommand constructs a new config print sub command using the given CollectorSettings.
26+
func newConfigPrintSubCommand(set CollectorSettings, flagSet *flag.FlagSet) *cobra.Command {
27+
cmd := &cobra.Command{
28+
Use: "print-initial-config",
29+
Short: "Prints the Collector's configuration in YAML format after all config sources are resolved and merged",
30+
Long: `Note: This command prints the final yaml configuration before it is unmarshaled into config structs, which may contain sensitive values.`,
31+
Args: cobra.ExactArgs(0),
32+
RunE: func(cmd *cobra.Command, _ []string) error {
33+
if !printCommandFeatureFlag.IsEnabled() {
34+
return errors.New("print-initial-config is currently experimental, use the otelcol.printInitialConfig feature gate to enable this command")
35+
}
36+
err := updateSettingsUsingFlags(&set, flagSet)
37+
if err != nil {
38+
return err
39+
}
40+
resolver, err := confmap.NewResolver(set.ConfigProviderSettings.ResolverSettings)
41+
if err != nil {
42+
return fmt.Errorf("failed to create new resolver: %w", err)
43+
}
44+
conf, err := resolver.Resolve(cmd.Context())
45+
if err != nil {
46+
return fmt.Errorf("error while resolving config: %w", err)
47+
}
48+
b, err := yaml.Marshal(conf.ToStringMap())
49+
if err != nil {
50+
return fmt.Errorf("error while marshaling to YAML: %w", err)
51+
}
52+
fmt.Printf("%s\n", b)
53+
return nil
54+
},
55+
}
56+
cmd.Flags().AddGoFlagSet(flagSet)
57+
return cmd
58+
}

otelcol/command_print_test.go

Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
// Copyright The OpenTelemetry Authors
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package otelcol // import "go.opentelemetry.io/collector/otelcol"
5+
6+
import (
7+
"bytes"
8+
"os"
9+
"testing"
10+
11+
"github.com/stretchr/testify/require"
12+
"gopkg.in/yaml.v3"
13+
14+
"go.opentelemetry.io/collector/confmap"
15+
"go.opentelemetry.io/collector/confmap/provider/fileprovider"
16+
"go.opentelemetry.io/collector/confmap/provider/yamlprovider"
17+
"go.opentelemetry.io/collector/featuregate"
18+
)
19+
20+
func TestPrintCommand(t *testing.T) {
21+
tests := []struct {
22+
name string
23+
set confmap.ResolverSettings
24+
errString string
25+
}{
26+
{
27+
name: "no URIs",
28+
set: confmap.ResolverSettings{},
29+
errString: "at least one config flag must be provided",
30+
},
31+
{
32+
name: "valid URI - file not found",
33+
set: confmap.ResolverSettings{
34+
URIs: []string{"file:blabla.yaml"},
35+
ProviderFactories: []confmap.ProviderFactory{
36+
fileprovider.NewFactory(),
37+
},
38+
DefaultScheme: "file",
39+
},
40+
errString: "cannot retrieve the configuration: unable to read the file",
41+
},
42+
{
43+
name: "valid URI",
44+
set: confmap.ResolverSettings{
45+
URIs: []string{"yaml:processors::batch/foo::timeout: 3s"},
46+
ProviderFactories: []confmap.ProviderFactory{
47+
yamlprovider.NewFactory(),
48+
},
49+
DefaultScheme: "yaml",
50+
},
51+
},
52+
{
53+
name: "valid URI - no provider set",
54+
set: confmap.ResolverSettings{
55+
URIs: []string{"yaml:processors::batch/foo::timeout: 3s"},
56+
DefaultScheme: "yaml",
57+
},
58+
errString: "at least one Provider must be supplied",
59+
},
60+
{
61+
name: "valid URI - invalid scheme name",
62+
set: confmap.ResolverSettings{
63+
URIs: []string{"yaml:processors::batch/foo::timeout: 3s"},
64+
DefaultScheme: "foo",
65+
ProviderFactories: []confmap.ProviderFactory{
66+
yamlprovider.NewFactory(),
67+
},
68+
},
69+
errString: "configuration: DefaultScheme not found in providers list",
70+
},
71+
}
72+
for _, test := range tests {
73+
t.Run(test.name, func(t *testing.T) {
74+
err := featuregate.GlobalRegistry().Set(printCommandFeatureFlag.ID(), true)
75+
require.NoError(t, err)
76+
defer func() {
77+
_ = featuregate.GlobalRegistry().Set(printCommandFeatureFlag.ID(), false)
78+
}()
79+
80+
set := ConfigProviderSettings{
81+
ResolverSettings: test.set,
82+
}
83+
cmd := newConfigPrintSubCommand(CollectorSettings{ConfigProviderSettings: set}, flags(featuregate.GlobalRegistry()))
84+
err = cmd.Execute()
85+
if test.errString != "" {
86+
require.ErrorContains(t, err, test.errString)
87+
} else {
88+
require.NoError(t, err)
89+
}
90+
})
91+
}
92+
}
93+
94+
func TestPrintCommandFeaturegateDisabled(t *testing.T) {
95+
cmd := newConfigPrintSubCommand(CollectorSettings{ConfigProviderSettings: ConfigProviderSettings{
96+
ResolverSettings: confmap.ResolverSettings{
97+
URIs: []string{"yaml:processors::batch/foo::timeout: 3s"},
98+
DefaultScheme: "foo",
99+
ProviderFactories: []confmap.ProviderFactory{
100+
yamlprovider.NewFactory(),
101+
},
102+
},
103+
}}, flags(featuregate.GlobalRegistry()))
104+
err := cmd.Execute()
105+
require.ErrorContains(t, err, "print-initial-config is currently experimental, use the otelcol.printInitialConfig feature gate to enable this command")
106+
}
107+
108+
func TestConfig(t *testing.T) {
109+
tests := []struct {
110+
name string
111+
configs []string
112+
finalConfig string
113+
}{
114+
{
115+
name: "two-configs",
116+
configs: []string{
117+
"file:testdata/configs/1-config-first.yaml",
118+
"file:testdata/configs/1-config-second.yaml",
119+
},
120+
finalConfig: "testdata/configs/1-config-output.yaml",
121+
},
122+
{
123+
name: "two-configs-yaml",
124+
configs: []string{
125+
"file:testdata/configs/1-config-first.yaml",
126+
"file:testdata/configs/1-config-second.yaml",
127+
"yaml:service::pipelines::logs::receivers: [foo,bar]",
128+
"yaml:service::pipelines::logs::exporters: [foo,bar]",
129+
},
130+
finalConfig: "testdata/configs/2-config-output.yaml",
131+
},
132+
}
133+
for _, test := range tests {
134+
t.Run(test.name, func(t *testing.T) {
135+
err := featuregate.GlobalRegistry().Set(printCommandFeatureFlag.ID(), true)
136+
require.NoError(t, err)
137+
defer func() {
138+
_ = featuregate.GlobalRegistry().Set(printCommandFeatureFlag.ID(), false)
139+
}()
140+
set := ConfigProviderSettings{
141+
ResolverSettings: confmap.ResolverSettings{
142+
URIs: test.configs,
143+
ProviderFactories: []confmap.ProviderFactory{
144+
fileprovider.NewFactory(),
145+
yamlprovider.NewFactory(),
146+
},
147+
DefaultScheme: "file",
148+
},
149+
}
150+
tmpFile, err := os.CreateTemp(t.TempDir(), "*")
151+
require.NoError(t, err)
152+
t.Cleanup(func() { _ = tmpFile.Close() })
153+
154+
// save the os.Stdout and temporarily set it to temp file
155+
oldStdout := os.Stdout
156+
os.Stdout = tmpFile
157+
158+
cmd := newConfigPrintSubCommand(CollectorSettings{ConfigProviderSettings: set}, flags(featuregate.GlobalRegistry()))
159+
require.NoError(t, cmd.Execute())
160+
161+
// restore os.Stdout
162+
os.Stdout = oldStdout
163+
164+
expectedOutput, err := os.ReadFile(test.finalConfig)
165+
require.NoError(t, err)
166+
167+
actualOutput, err := os.ReadFile(tmpFile.Name())
168+
require.NoError(t, err)
169+
170+
actualConfig := make(map[string]any, 0)
171+
expectedConfig := make(map[string]any, 0)
172+
173+
require.NoError(t, yaml.Unmarshal(bytes.TrimSpace(actualOutput), actualConfig))
174+
require.NoError(t, yaml.Unmarshal(bytes.TrimSpace(expectedOutput), expectedConfig))
175+
176+
require.Equal(t, expectedConfig, actualConfig)
177+
})
178+
}
179+
}

otelcol/go.mod

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ require (
99
go.opentelemetry.io/collector/component/componentstatus v0.120.0
1010
go.opentelemetry.io/collector/config/configtelemetry v0.120.0
1111
go.opentelemetry.io/collector/confmap v1.26.0
12+
go.opentelemetry.io/collector/confmap/provider/fileprovider v1.25.0
13+
go.opentelemetry.io/collector/confmap/provider/yamlprovider v1.25.0
1214
go.opentelemetry.io/collector/confmap/xconfmap v0.120.0
1315
go.opentelemetry.io/collector/connector v0.120.0
1416
go.opentelemetry.io/collector/connector/connectortest v0.120.0
@@ -209,4 +211,8 @@ replace go.opentelemetry.io/collector/extension/auth/authtest => ../extension/au
209211

210212
replace go.opentelemetry.io/collector/extension/xextension => ../extension/xextension
211213

214+
replace go.opentelemetry.io/collector/confmap/provider/fileprovider => ../confmap/provider/fileprovider
215+
216+
replace go.opentelemetry.io/collector/confmap/provider/yamlprovider => ../confmap/provider/yamlprovider
217+
212218
replace go.opentelemetry.io/collector/internal/telemetry => ../internal/telemetry
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
receivers:
2+
bar:
3+
key: val
4+
exporters:
5+
bar:
6+
key: val
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
exporters:
2+
bar:
3+
key: val
4+
foo:
5+
key: val
6+
receivers:
7+
bar:
8+
key: val
9+
foo:
10+
key: val
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
receivers:
2+
foo:
3+
key: val
4+
exporters:
5+
foo:
6+
key: val
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
exporters:
2+
bar:
3+
key: val
4+
foo:
5+
key: val
6+
receivers:
7+
bar:
8+
key: val
9+
foo:
10+
key: val
11+
service:
12+
pipelines:
13+
logs:
14+
exporters:
15+
- foo
16+
- bar
17+
receivers:
18+
- foo
19+
- bar

service/README.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,3 +149,9 @@ extensions:
149149
```bash
150150
./otelcorecol validate --config=file:examples/local/otel-config.yaml
151151
```
152+
153+
## How to examine the final configuration after merging and resolving from various sources?
154+
155+
```bash
156+
./otelcorecol print-initial-config --config=file:file.yaml --config=http:http://remote:8080/config --config=file:file2.yaml
157+
```

service/go.mod

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -226,3 +226,7 @@ replace go.opentelemetry.io/collector/extension/auth/authtest => ../extension/au
226226
replace go.opentelemetry.io/collector/extension/xextension => ../extension/xextension
227227

228228
replace go.opentelemetry.io/collector/otelcol => ../otelcol
229+
230+
replace go.opentelemetry.io/collector/confmap/provider/yamlprovider => ../confmap/provider/yamlprovider
231+
232+
replace go.opentelemetry.io/collector/confmap/provider/fileprovider => ../confmap/provider/fileprovider

0 commit comments

Comments
 (0)