Created
August 12, 2019 23:25
-
-
Save therealmitchconnors/15d77de7d4a50fbf69166387afad968e to your computer and use it in GitHub Desktop.
Candidate Viperize Functions. Test Failing.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Copyright 2019 Istio Authors | |
// | |
// Licensed under the Apache License, Version 2.0 (the "License"); | |
// you may not use this file except in compliance with the License. | |
// You may obtain a copy of the License at | |
// | |
// http://www.apache.org/licenses/LICENSE-2.0 | |
// | |
// Unless required by applicable law or agreed to in writing, software | |
// distributed under the License is distributed on an "AS IS" BASIS, | |
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
// See the License for the specific language governing permissions and | |
// limitations under the License. | |
package viperconfig | |
import ( | |
"fmt" | |
"os" | |
"reflect" | |
"strings" | |
"github.com/spf13/viper" | |
"github.com/spf13/cobra" | |
"github.com/spf13/pflag" | |
) | |
// AddConfigFlag appends a persistent flag for retrieving Viper config, as well as an initializer | |
// for reading that file | |
func AddConfigFlag(rootCmd *cobra.Command, viper *viper.Viper) { | |
var cfgFile string | |
rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "Config file containing args") | |
cobra.OnInitialize(func() { | |
if len(cfgFile) > 0 { | |
viper.SetConfigFile(cfgFile) | |
err := viper.ReadInConfig() // Find and read the config file | |
if err != nil { // Handle errors reading the config file | |
_, _ = os.Stderr.WriteString(fmt.Errorf("fatal error in config file: %s", err).Error()) | |
os.Exit(1) | |
} | |
} | |
}) | |
} | |
// ProcessViperConfig retrieves Viper values for each Cobra Val Flag | |
func ProcessViperConfig(cmd *cobra.Command, viper *viper.Viper) { | |
viper.SetTypeByDefaultValue(true) | |
cmd.Flags().VisitAll(func(f *pflag.Flag) { | |
k := reflect.TypeOf(viper.Get(f.Name)).Kind() | |
if k == reflect.Slice || k == reflect.Array { | |
// Viper cannot convert slices to strings, so this is our workaround. | |
_ = f.Value.Set(strings.Join(viper.GetStringSlice(f.Name), ",")) | |
} else { | |
_ = f.Value.Set(viper.GetString(f.Name)) | |
} | |
}) | |
} | |
// ViperizeRootCmd takes a root command, and ensures that all flags of all sub commands | |
// are bound to viper, with one master config flag accepting a config file. | |
// At runtime, it will then assign all cobra variables to have their viper values. | |
func ViperizeRootCmd(cmd *cobra.Command, viper *viper.Viper) { | |
AddConfigFlag(cmd, viper) | |
subCommandPreRun(cmd, viper) | |
} | |
func subCommandPreRun(cmd *cobra.Command, viper *viper.Viper) { | |
original := cmd.PreRun | |
cmd.PreRun = func(cmd *cobra.Command, args []string) { | |
if original != nil { | |
original(cmd, args) | |
} | |
_ = viper.BindPFlags(cmd.Flags()) | |
ProcessViperConfig(cmd, viper) | |
} | |
for _, c := range cmd.Commands() { | |
subCommandPreRun(c, viper) | |
} | |
} | |
//ViperizeRootCmdDefault calls ViperizeRootCmd using viper.GetViper() | |
func ViperizeRootCmdDefault(cmd *cobra.Command) { | |
ViperizeRootCmd(cmd, viper.GetViper()) | |
} | |
func Execute(cmd *cobra.Command, args []string) error { | |
// register this only for the sake of help text (since we parse the flag by hand below) | |
_ = cmd.PersistentFlags().String("config", "", "Path to a YAML file containing command-line arguments") | |
// manually look for the config file flag | |
// TODO: This needs to be smarter to deal with cases where a flag argument looks like a flag. Say "--foo --config" | |
cfgFile := "" | |
for i := 0; i < len(args); i++ { | |
if args[i] == "--config" { | |
if i+1 < len(args) { | |
cfgFile = args[i+1] | |
} else { | |
// error, will be caught by Cobra later | |
} | |
break | |
} | |
} | |
// load and apply the config file if there is one | |
if cfgFile != "" { | |
// bind all the viper stuff | |
v := viper.GetViper() | |
v.SetTypeByDefaultValue(true) | |
// Using bind() here runs the bindings immediately, before flags are merged, | |
// resulting in zero flags getting bound... | |
//bind(cmd, v) | |
subCommandPreRun(cmd, v) | |
v.SetConfigFile(cfgFile) | |
err := v.ReadInConfig() | |
if err != nil { | |
_, _ = fmt.Fprintf(os.Stderr, "Unable to parse configuration file: %v\n", err) | |
os.Exit(1) | |
} | |
} | |
cmd.SetArgs(args) | |
return cmd.Execute() | |
} | |
func bind(cmd *cobra.Command, v *viper.Viper) { | |
cmd.Flags().VisitAll(func(f *pflag.Flag) { | |
k := reflect.TypeOf(viper.Get(f.Name)).Kind() | |
if k == reflect.Slice || k == reflect.Array { | |
// Viper cannot convert slices to strings, so this is our workaround. | |
_ = f.Value.Set(strings.Join(v.GetStringSlice(f.Name), ",")) | |
} else { | |
_ = f.Value.Set(v.GetString(f.Name)) | |
} | |
}) | |
for _, sub := range cmd.Commands() { | |
bind(sub, v) | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Copyright 2019 Istio Authors | |
// | |
// Licensed under the Apache License, Version 2.0 (the "License"); | |
// you may not use this file except in compliance with the License. | |
// You may obtain a copy of the License at | |
// | |
// http://www.apache.org/licenses/LICENSE-2.0 | |
// | |
// Unless required by applicable law or agreed to in writing, software | |
// distributed under the License is distributed on an "AS IS" BASIS, | |
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
// See the License for the specific language governing permissions and | |
// limitations under the License. | |
package viperconfig | |
import ( | |
"testing" | |
"github.com/spf13/cobra" | |
"github.com/spf13/viper" | |
"github.com/stretchr/testify/assert" | |
) | |
func TestViperConfig(t *testing.T) { | |
v := viper.GetViper() | |
var foo string | |
hasRun := false | |
c := cobra.Command{Run: func(c *cobra.Command, args []string) { | |
ProcessViperConfig(c, v) | |
assert.Equal(t, foo, "expected") | |
hasRun = true | |
}, | |
PreRun: func(cmd *cobra.Command, args []string) { | |
v.BindPFlags(cmd.Flags()) | |
}} | |
AddConfigFlag(&c, v) | |
c.PersistentFlags().StringVar(&foo, "foo", "notempty", "foo is a fake flag") | |
c.SetArgs([]string{"--config", "testconfig.yaml"}) | |
c.Execute() | |
if !hasRun { | |
assert.True(t, hasRun, "command never ran") | |
} | |
} | |
func TestDuplicate(t *testing.T) { | |
t.Skip("Flag duplication is a known bug, and shouldn't cause a failure yet.") | |
var foo []string | |
hasRunRoot := false | |
c := cobra.Command{Run: func(c *cobra.Command, args []string) { | |
assert.Equal(t, foo, []string{"first", "second"}) | |
hasRunRoot = true | |
}} | |
c.PersistentFlags().StringSliceVar(&foo, "foo", []string{"notempty"}, "foo is a fake flag") | |
ViperizeRootCmdDefault(&c) | |
// run root and check | |
c.SetArgs([]string{"--foo", "first", "--foo", "second"}) | |
c.Execute() | |
assert.True(t, hasRunRoot, "root command never ran") | |
} | |
func TestViperize(t *testing.T) { | |
var foo string | |
var bar string | |
hasRunRoot := false | |
hasRunPre := false | |
hasRunSub := false | |
c := cobra.Command{Run: func(c *cobra.Command, args []string) { | |
assert.Equal(t, foo, "expected") | |
hasRunRoot = true | |
}, PreRun: func(c *cobra.Command, args []string) { | |
hasRunPre = true | |
}} | |
c.PersistentFlags().StringVar(&foo, "foo", "notempty", "foo is a fake flag") | |
sub := cobra.Command{Use: "sub", Run: func(c *cobra.Command, args []string) { | |
// this is not getting set, probably because it's not in sub's persistent flag set... | |
assert.Equal(t, foo, "expected") | |
assert.Equal(t, bar, "alsoexpected") | |
hasRunSub = true | |
}} | |
sub.PersistentFlags().StringVar(&bar, "bar", "notepmty", "bar is also a fake flag") | |
c.AddCommand(&sub) | |
ViperizeRootCmdDefault(&c) | |
// run root and check | |
c.SetArgs([]string{"--config", "testconfig.yaml"}) | |
c.Execute() | |
assert.True(t, hasRunRoot, "root command never ran") | |
assert.True(t, hasRunPre, "root command never preran") | |
assert.False(t, hasRunSub, "subcommand ran unintentionally") | |
// reset the test | |
foo = "" | |
bar = "" | |
hasRunRoot = false | |
hasRunSub = false | |
hasRunPre = false | |
// run sub and check | |
c.SetArgs([]string{"sub", "--config", "testsubconfig.yaml"}) | |
c.Execute() | |
assert.True(t, hasRunSub, "subcommand never ran") | |
assert.False(t, hasRunRoot, "root command ran unintentionally") | |
assert.False(t, hasRunPre, "root command preran unintentionally") | |
} | |
func TestMartin(t *testing.T) { | |
var foo []string | |
var bar string | |
hasRunRoot := false | |
hasRunPre := false | |
hasRunSub := false | |
c := cobra.Command{Run: func(c *cobra.Command, args []string) { | |
assert.Equal(t, foo, []string{"first", "second"}) | |
hasRunRoot = true | |
}, PreRun: func(c *cobra.Command, args []string) { | |
hasRunPre = true | |
}} | |
c.PersistentFlags().StringSliceVar(&foo, "foo", []string{"notempty"}, "foo is a fake flag") | |
sub := cobra.Command{Use: "sub", Run: func(c *cobra.Command, args []string) { | |
// this is not getting set, probably because it's not in sub's persistent flag set... | |
assert.Equal(t, foo, "expected") | |
assert.Equal(t, bar, "alsoexpected") | |
hasRunSub = true | |
}} | |
sub.PersistentFlags().StringVar(&bar, "bar", "notepmty", "bar is also a fake flag") | |
c.AddCommand(&sub) | |
//ViperizeRootCmdDefault(&c) | |
Execute(&c, []string{"--config", "testconfig.yaml", "--foo", "first", "--foo", "second"}) | |
// run root and check | |
assert.True(t, hasRunRoot, "root command never ran") | |
assert.True(t, hasRunPre, "root command never preran") | |
assert.False(t, hasRunSub, "subcommand ran unintentionally") | |
// reset the test | |
//foo = "" | |
bar = "" | |
hasRunRoot = false | |
hasRunSub = false | |
hasRunPre = false | |
// run sub and check | |
//Execute(&c, []string{"sub", "--config", "testsubconfig.yaml"}) | |
//assert.True(t, hasRunSub, "subcommand never ran") | |
//assert.False(t, hasRunRoot, "root command ran unintentionally") | |
//assert.False(t, hasRunPre, "root command preran unintentionally") | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment