Skip to content

Commit a133b7c

Browse files
jmacdcodebotenevan-bradleymx-psi
authored
Revise the print-config command (#13679)
#### Description See #12369, #11775. Fixes #13278. cc/ @VihasMakwana This refines the print-config command with two modes, "redacted" and "unredacted", with an optional "--validate" or no, with optional --format=yaml or json. #### Link to tracking issue Fixes #11775. #### Testing ✔️ #### Documentation ✔️ --------- Co-authored-by: Alex Boten <[email protected]> Co-authored-by: Evan Bradley <[email protected]> Co-authored-by: Pablo Baeyens <[email protected]>
1 parent 995bea5 commit a133b7c

File tree

16 files changed

+487
-184
lines changed

16 files changed

+487
-184
lines changed
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
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: breaking
5+
6+
# The name of the component, or a single word describing the area of concern, (e.g. otlpreceiver)
7+
component: all
8+
9+
# A brief description of the change. Surround your text with quotes ("") if it needs to start with a backtick (`).
10+
note: Add unified print-config command with mode support (redacted, unredacted), json support (unstable), and validation support.
11+
12+
# One or more tracking issues or pull requests related to the change
13+
issues: [11775]
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: This replaces the `print-initial-config` command. See the `service` package README for more details. The original command name `print-initial-config` remains an alias, to be retired with the feature flag.
19+
20+
# Optional: The change log or logs in which this entry should be included.
21+
# e.g. '[user]' or '[user, api]'
22+
# Include 'user' if the change is relevant to end users.
23+
# Include 'api' if there is a change to a library API.
24+
# Default: '[user]'
25+
change_logs: [user]

.github/workflows/utils/cspell.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -478,6 +478,7 @@
478478
"unmarshal",
479479
"unmarshalling",
480480
"unmarshalls",
481+
"unredacted",
481482
"unshallow",
482483
"unstarted",
483484
"userfriendly",

cmd/mdatagen/go.mod

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -184,8 +184,6 @@ replace go.opentelemetry.io/collector/extension/extensionauth => ../../extension
184184

185185
replace go.opentelemetry.io/collector/service/hostcapabilities => ../../service/hostcapabilities
186186

187-
replace go.opentelemetry.io/collector/confmap/provider/yamlprovider => ../../confmap/provider/yamlprovider
188-
189187
replace go.opentelemetry.io/collector/config/configtls => ../../config/configtls
190188

191189
replace go.opentelemetry.io/collector/config/configcompression => ../../config/configcompression

otelcol/collector.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,8 @@ func (s State) String() string {
5555
// CollectorSettings holds configuration for creating a new Collector.
5656
type CollectorSettings struct {
5757
// Factories service factories.
58+
// TODO(13263) This is a dangerous "bare" function value, should define an interface
59+
// following style guidelines.
5860
Factories func() (Factories, error)
5961

6062
// BuildInfo provides collector start information.

otelcol/command_print.go

Lines changed: 168 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -4,55 +4,196 @@
44
package otelcol // import "go.opentelemetry.io/collector/otelcol"
55

66
import (
7+
"encoding/json"
78
"errors"
89
"flag"
910
"fmt"
11+
"io"
12+
"strings"
1013

1114
"github.com/spf13/cobra"
1215
yaml "go.yaml.in/yaml/v3"
1316

1417
"go.opentelemetry.io/collector/confmap"
18+
"go.opentelemetry.io/collector/confmap/xconfmap"
1519
"go.opentelemetry.io/collector/featuregate"
1620
)
1721

22+
const featureGateName = "otelcol.printInitialConfig"
23+
1824
var printCommandFeatureFlag = featuregate.GlobalRegistry().MustRegister(
19-
"otelcol.printInitialConfig",
25+
featureGateName,
2026
featuregate.StageAlpha,
2127
featuregate.WithRegisterFromVersion("v0.120.0"),
22-
featuregate.WithRegisterDescription("if set to true, turns on the print-initial-config command"),
28+
featuregate.WithRegisterDescription("if set to true, enable the print-config command"),
2329
)
2430

25-
// newConfigPrintSubCommand constructs a new config print sub command using the given CollectorSettings.
31+
// newConfigPrintSubCommand constructs a new print-config command using the given CollectorSettings.
2632
func newConfigPrintSubCommand(set CollectorSettings, flagSet *flag.FlagSet) *cobra.Command {
33+
var outputFormat string
34+
var mode string
35+
var validate bool
36+
2737
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),
38+
Use: "print-config",
39+
Aliases: []string{"print-initial-config"},
40+
Short: "Prints the Collector's configuration in the specified mode",
41+
Long: `Prints the Collector's configuration with different levels of processing:
42+
43+
- redacted: Shows the resolved configuration with sensitive data redacted (default)
44+
- unredacted: Shows the resolved configuration with all sensitive data visible
45+
46+
The output prints in YAML by default. To print JSON use --format=json,
47+
however this is considered unstable.
48+
49+
Validation is enabled by default, as a safety measure.
50+
51+
All modes are experimental, requiring the otelcol.printInitialConfig feature gate.`,
52+
Args: cobra.ExactArgs(0),
3253
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
54+
pc := printContext{
55+
cmd: cmd,
56+
stdout: cmd.OutOrStdout(),
57+
set: set,
58+
outputFormat: outputFormat,
59+
validate: validate,
3960
}
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
61+
return pc.configPrintSubCommand(flagSet, mode)
5462
},
5563
}
64+
65+
formatHelp := "Output format: yaml (default), json (unstable))"
66+
cmd.Flags().StringVar(&outputFormat, "format", "yaml", formatHelp)
67+
68+
modeHelp := "Operating mode: redacted (default), unredacted"
69+
cmd.Flags().StringVar(&mode, "mode", "redacted", modeHelp)
70+
71+
validateHelp := "Validation mode: true (default), false"
72+
cmd.Flags().BoolVar(&validate, "validate", true, validateHelp)
73+
5674
cmd.Flags().AddGoFlagSet(flagSet)
5775
return cmd
5876
}
77+
78+
type printContext struct {
79+
cmd *cobra.Command
80+
stdout io.Writer
81+
set CollectorSettings
82+
outputFormat string
83+
validate bool
84+
}
85+
86+
func (pctx *printContext) configPrintSubCommand(flagSet *flag.FlagSet, mode string) error {
87+
if !printCommandFeatureFlag.IsEnabled() {
88+
return errors.New("print-config is currently experimental, use the otelcol.printInitialConfig feature gate to enable this command")
89+
}
90+
err := updateSettingsUsingFlags(&pctx.set, flagSet)
91+
if err != nil {
92+
return err
93+
}
94+
95+
switch strings.ToLower(mode) {
96+
case "redacted":
97+
return pctx.printRedactedConfig()
98+
case "unredacted":
99+
return pctx.printUnredactedConfig()
100+
default:
101+
return fmt.Errorf("invalid mode %q: modes are: redacted, unredacted", mode)
102+
}
103+
}
104+
105+
// printConfigData formats and prints configuration data in yaml or json format.
106+
func (pctx *printContext) printConfigData(data map[string]any) error {
107+
format := pctx.outputFormat
108+
if format == "" {
109+
format = "yaml"
110+
}
111+
112+
switch {
113+
case strings.EqualFold(format, "yaml"):
114+
b, err := yaml.Marshal(data)
115+
if err != nil {
116+
return err
117+
}
118+
fmt.Fprintf(pctx.stdout, "%s\n", b)
119+
return nil
120+
121+
case strings.EqualFold(format, "json"):
122+
encoder := json.NewEncoder(pctx.stdout)
123+
encoder.SetIndent("", " ")
124+
return encoder.Encode(data)
125+
}
126+
127+
return fmt.Errorf("unrecognized print format: %s", format)
128+
}
129+
130+
func (pctx *printContext) getPrintableConfig() (any, error) {
131+
var factories Factories
132+
if pctx.set.Factories != nil {
133+
var err error
134+
factories, err = pctx.set.Factories()
135+
if err != nil {
136+
return nil, fmt.Errorf("failed to get factories: %w", err)
137+
}
138+
}
139+
140+
configProvider, err := NewConfigProvider(pctx.set.ConfigProviderSettings)
141+
if err != nil {
142+
return nil, fmt.Errorf("failed to create config provider: %w", err)
143+
}
144+
145+
cfg, err := configProvider.Get(pctx.cmd.Context(), factories)
146+
if err != nil {
147+
return nil, fmt.Errorf("failed to get config: %w", err)
148+
}
149+
return cfg, nil
150+
}
151+
152+
// printUnredactedConfig prints resolved configuration before interpreting
153+
// with the intended types for each component, thus it shows the full
154+
// configuration without considering configuopaque. Use with caution.
155+
func (pctx *printContext) printUnredactedConfig() error {
156+
if pctx.validate {
157+
// Validation serves prevent revealing invalid data.
158+
cfg, err := pctx.getPrintableConfig()
159+
if err != nil {
160+
return err
161+
}
162+
if err = xconfmap.Validate(cfg); err != nil {
163+
return fmt.Errorf("invalid configuration: %w", err)
164+
}
165+
166+
// Note: we discard the validated configuration.
167+
}
168+
resolver, err := confmap.NewResolver(pctx.set.ConfigProviderSettings.ResolverSettings)
169+
if err != nil {
170+
return fmt.Errorf("failed to create new resolver: %w", err)
171+
}
172+
conf, err := resolver.Resolve(pctx.cmd.Context())
173+
if err != nil {
174+
return fmt.Errorf("error while resolving config: %w", err)
175+
}
176+
return pctx.printConfigData(conf.ToStringMap())
177+
}
178+
179+
// printRedactedConfig prints resolved configuration with its assigned
180+
// types, but without validation. Notably, configopaque strings are printed
181+
// as "[redacted]". This is the default.
182+
func (pctx *printContext) printRedactedConfig() error {
183+
cfg, err := pctx.getPrintableConfig()
184+
if err != nil {
185+
return err
186+
}
187+
188+
if pctx.validate {
189+
if err = xconfmap.Validate(cfg); err != nil {
190+
return fmt.Errorf("invalid configuration: %w", err)
191+
}
192+
}
193+
194+
confMap := confmap.New()
195+
if err := confMap.Marshal(cfg); err != nil {
196+
return fmt.Errorf("failed to marshal config: %w", err)
197+
}
198+
return pctx.printConfigData(confMap.ToStringMap())
199+
}

0 commit comments

Comments
 (0)