diff --git a/cmd/influxd/launcher/cmd.go b/cmd/influxd/launcher/cmd.go index fbcb0800c9e..1b04e3df72c 100644 --- a/cmd/influxd/launcher/cmd.go +++ b/cmd/influxd/launcher/cmd.go @@ -31,6 +31,7 @@ const ( // The `run` subcommand is set as the default to execute. func NewInfluxdCommand(ctx context.Context, v *viper.Viper) *cobra.Command { o := newOpts(v) + cliOpts := o.bindCliOpts() prog := cli.Program{ Name: "influxd", @@ -45,9 +46,10 @@ func NewInfluxdCommand(ctx context.Context, v *viper.Viper) *cobra.Command { } for _, c := range []*cobra.Command{cmd, runCmd} { setCmdDescriptions(c) - o.bindCliOpts(c) + cli.BindOptions(o.Viper, c, cliOpts) } cmd.AddCommand(runCmd) + cmd.AddCommand(NewInfluxdPrintConfigCommand(v, cliOpts)) return cmd } @@ -182,9 +184,10 @@ func newOpts(viper *viper.Viper) *InfluxdOpts { } } -// bindCliOpts configures a cobra command to set server options based on CLI args. -func (o *InfluxdOpts) bindCliOpts(cmd *cobra.Command) { - opts := []cli.Opt{ +// bindCliOpts returns a list of options which can be added to a cobra command +// in order to set options over the CLI. +func (o *InfluxdOpts) bindCliOpts() []cli.Opt { + return []cli.Opt{ { DestP: &o.LogLevel, Flag: "log-level", @@ -470,6 +473,4 @@ func (o *InfluxdOpts) bindCliOpts(cmd *cobra.Command) { Desc: "The maximum number of group by time bucket a SELECT can create. A value of zero will max the maximum number of buckets unlimited.", }, } - - cli.BindOptions(o.Viper, cmd, opts) } diff --git a/cmd/influxd/launcher/print_config.go b/cmd/influxd/launcher/print_config.go new file mode 100644 index 00000000000..82d432cd5db --- /dev/null +++ b/cmd/influxd/launcher/print_config.go @@ -0,0 +1,88 @@ +package launcher + +import ( + "fmt" + "io" + + "github.com/influxdata/influxdb/v2/kit/cli" + "github.com/spf13/cobra" + "github.com/spf13/viper" + "gopkg.in/yaml.v3" +) + +func NewInfluxdPrintConfigCommand(v *viper.Viper, influxdOpts []cli.Opt) *cobra.Command { + + var keyToPrint string + printOpts := make([]cli.Opt, len(influxdOpts)+1) + + printOpts[0] = cli.Opt{ + DestP: &keyToPrint, + Flag: "key-name", + Desc: "config key name; if set, only the resolved value of that key will be printed", + } + for i, opt := range influxdOpts { + printOpts[i+1] = cli.Opt{ + DestP: opt.DestP, + Flag: opt.Flag, + Hidden: true, + } + } + + cmd := &cobra.Command{ + Use: "print-config", + Short: "Print the full influxd config resolved from the current environment", + Long: ` +Print config (in YAML) that the influxd server would use if run with the current flags/env vars/config file. + +The order of precedence for config options are as follows (1 highest, 3 lowest): + 1. flags + 2. env vars + 3. config file + +A config file can be provided via the INFLUXD_CONFIG_PATH env var. If a file is +not provided via an env var, influxd will look in the current directory for a +config.{json|toml|yaml|yml} file. If one does not exist, then it will continue unchanged. + +See 'influxd -h' for the full list of config options supported by the server. +`, + RunE: func(cmd *cobra.Command, _ []string) error { + var err error + if keyToPrint == "" { + err = printAllConfigRunE(printOpts, cmd.OutOrStdout()) + } else { + err = printOneConfigRunE(printOpts, keyToPrint, cmd.OutOrStdout()) + } + + if err != nil { + return fmt.Errorf("failed to print config: %w", err) + } + + return nil + }, + Args: cobra.NoArgs, + } + cli.BindOptions(v, cmd, printOpts) + + return cmd +} + +func printAllConfigRunE(configOpts []cli.Opt, out io.Writer) error { + configMap := make(map[string]interface{}, len(configOpts)) + + for _, o := range configOpts { + configMap[o.Flag] = o.DestP + } + + return yaml.NewEncoder(out).Encode(configMap) +} + +func printOneConfigRunE(configOpts []cli.Opt, key string, out io.Writer) error { + for _, o := range configOpts { + if o.Flag != key { + continue + } + return yaml.NewEncoder(out).Encode(o.DestP) + } + + return fmt.Errorf("key %q not found in config", key) +} diff --git a/cmd/influxd/launcher/print_config_test.go b/cmd/influxd/launcher/print_config_test.go new file mode 100644 index 00000000000..f8f28e63299 --- /dev/null +++ b/cmd/influxd/launcher/print_config_test.go @@ -0,0 +1,130 @@ +package launcher + +import ( + "bytes" + "testing" + + "github.com/influxdata/influxdb/v2" + "github.com/influxdata/influxdb/v2/kit/cli" + "github.com/stretchr/testify/require" + "go.uber.org/zap/zapcore" +) + +// Pretend we've already used cobra/viper to write +// values into these vars. +var stringVar = "string-value" +var intVar = 12344 +var boolVar = false +var floatVar = 987.654 +var sliceVar = []string{"hello", "world"} +var mapVar = map[string]string{"foo": "bar", "baz": "qux"} +var levelVar = zapcore.InfoLevel +var idVar, _ = influxdb.IDFromString("020f755c3c082000") + +var opts = []cli.Opt{ + { + DestP: &stringVar, + Flag: "string-var", + }, + { + DestP: &intVar, + Flag: "int-var", + }, + { + DestP: &boolVar, + Flag: "bool-var", + }, + { + DestP: &floatVar, + Flag: "float-var", + }, + { + DestP: &sliceVar, + Flag: "slice-var", + }, + { + DestP: &mapVar, + Flag: "map-var", + }, + { + DestP: &levelVar, + Flag: "level-var", + }, + { + DestP: &idVar, + Flag: "id-var", + }, +} + +func Test_printAllConfig(t *testing.T) { + var out bytes.Buffer + require.NoError(t, printAllConfigRunE(opts, &out)) + + expected := `bool-var: false +float-var: 987.654 +id-var: 020f755c3c082000 +int-var: 12344 +level-var: info +map-var: + baz: qux + foo: bar +slice-var: + - hello + - world +string-var: string-value +` + + require.Equal(t, expected, out.String()) +} + +func Test_printOneConfig(t *testing.T) { + testCases := []struct { + key string + expected string + }{ + { + key: "bool-var", + expected: "false", + }, + { + key: "float-var", + expected: "987.654", + }, + { + key: "id-var", + expected: "020f755c3c082000", + }, + { + key: "level-var", + expected: "info", + }, + { + key: "map-var", + expected: `baz: qux +foo: bar`, + }, + { + key: "slice-var", + expected: `- hello +- world`, + }, + { + key: "string-var", + expected: "string-value", + }, + } + + for _, tc := range testCases { + t.Run(tc.key, func(t *testing.T) { + var out bytes.Buffer + require.NoError(t, printOneConfigRunE(opts, tc.key, &out)) + require.Equal(t, tc.expected+"\n", out.String()) + }) + } + + t.Run("bad-key", func(t *testing.T) { + var out bytes.Buffer + require.Error(t, printOneConfigRunE(opts, "bad-key", &out)) + require.Empty(t, out.String()) + }) +}