|
4 | 4 | package otelcol // import "go.opentelemetry.io/collector/otelcol" |
5 | 5 |
|
6 | 6 | import ( |
| 7 | + "encoding/json" |
7 | 8 | "errors" |
8 | 9 | "flag" |
9 | 10 | "fmt" |
| 11 | + "io" |
| 12 | + "strings" |
10 | 13 |
|
11 | 14 | "github.com/spf13/cobra" |
12 | 15 | yaml "go.yaml.in/yaml/v3" |
13 | 16 |
|
14 | 17 | "go.opentelemetry.io/collector/confmap" |
| 18 | + "go.opentelemetry.io/collector/confmap/xconfmap" |
15 | 19 | "go.opentelemetry.io/collector/featuregate" |
16 | 20 | ) |
17 | 21 |
|
| 22 | +const featureGateName = "otelcol.printInitialConfig" |
| 23 | + |
18 | 24 | var printCommandFeatureFlag = featuregate.GlobalRegistry().MustRegister( |
19 | | - "otelcol.printInitialConfig", |
| 25 | + featureGateName, |
20 | 26 | featuregate.StageAlpha, |
21 | 27 | 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"), |
23 | 29 | ) |
24 | 30 |
|
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. |
26 | 32 | func newConfigPrintSubCommand(set CollectorSettings, flagSet *flag.FlagSet) *cobra.Command { |
| 33 | + var outputFormat string |
| 34 | + var mode string |
| 35 | + var validate bool |
| 36 | + |
27 | 37 | 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), |
32 | 53 | 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, |
39 | 60 | } |
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) |
54 | 62 | }, |
55 | 63 | } |
| 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 | + |
56 | 74 | cmd.Flags().AddGoFlagSet(flagSet) |
57 | 75 | return cmd |
58 | 76 | } |
| 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