Skip to content

Instantly share code, notes, and snippets.

@therealmitchconnors
Created August 12, 2019 23:25
Show Gist options
  • Save therealmitchconnors/15d77de7d4a50fbf69166387afad968e to your computer and use it in GitHub Desktop.
Save therealmitchconnors/15d77de7d4a50fbf69166387afad968e to your computer and use it in GitHub Desktop.
Candidate Viperize Functions. Test Failing.
// 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)
}
}
// 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