From 4da0b0e75dfaeb68edba43ba49aa2bd4ad7d98d5 Mon Sep 17 00:00:00 2001 From: Matt Rickard Date: Tue, 23 Aug 2016 07:46:31 -0700 Subject: [PATCH] WIP --- cmd/minikube/cmd/config/config.go | 125 ++++++++++++++++++++ cmd/minikube/cmd/config/config_test.go | 69 +++++++++++ cmd/minikube/cmd/config/get.go | 33 ++++++ cmd/minikube/cmd/config/set.go | 59 +++++++++ cmd/minikube/cmd/config/set_test.go | 10 ++ cmd/minikube/cmd/config/unset.go | 31 +++++ cmd/minikube/cmd/config/util.go | 56 +++++++++ cmd/minikube/cmd/config/util_test.go | 61 ++++++++++ cmd/minikube/cmd/config/validations.go | 68 +++++++++++ cmd/minikube/cmd/config/validations_test.go | 80 +++++++++++++ cmd/minikube/cmd/root.go | 7 +- docs/minikube.md | 1 + docs/minikube_config.md | 30 +++++ pkg/minikube/constants/constants.go | 1 + 14 files changed, 628 insertions(+), 3 deletions(-) create mode 100644 cmd/minikube/cmd/config/config.go create mode 100644 cmd/minikube/cmd/config/config_test.go create mode 100644 cmd/minikube/cmd/config/get.go create mode 100644 cmd/minikube/cmd/config/set.go create mode 100644 cmd/minikube/cmd/config/set_test.go create mode 100644 cmd/minikube/cmd/config/unset.go create mode 100644 cmd/minikube/cmd/config/util.go create mode 100644 cmd/minikube/cmd/config/util_test.go create mode 100644 cmd/minikube/cmd/config/validations.go create mode 100644 cmd/minikube/cmd/config/validations_test.go create mode 100644 docs/minikube_config.md diff --git a/cmd/minikube/cmd/config/config.go b/cmd/minikube/cmd/config/config.go new file mode 100644 index 000000000000..cd85532fcd2e --- /dev/null +++ b/cmd/minikube/cmd/config/config.go @@ -0,0 +1,125 @@ +package config + +import ( + "encoding/json" + "io" + + "os" + + "github.com/golang/glog" + "github.com/spf13/cobra" + "k8s.io/minikube/pkg/minikube/constants" +) + +type configFile interface { + io.ReadWriter +} + +type setFn func(string, string) error +type MinikubeConfig map[string]interface{} + +type Setting struct { + name string + set func(MinikubeConfig, string, string) error + validations []setFn + callbacks []setFn +} + +// These are all the settings that are configurable +// and their validation and callback fn run on Set +var settings []Setting = []Setting{ + Setting{ + name: "vm-driver", + set: SetString, + validations: []setFn{IsValidDriver}, + callbacks: []setFn{RequiresRestartMsg}, + }, + Setting{ + name: "cpus", + set: SetInt, + validations: []setFn{IsPositive}, + callbacks: []setFn{RequiresRestartMsg}, + }, + Setting{ + name: "disk-size", + set: SetString, + validations: []setFn{IsValidDiskSize}, + callbacks: []setFn{RequiresRestartMsg}, + }, + Setting{ + name: "host-only-cidr", + set: SetString, + validations: []setFn{IsValidCIDR}, + }, + Setting{ + name: "memory", + set: SetInt, + validations: []setFn{IsPositive}, + callbacks: []setFn{RequiresRestartMsg}, + }, + Setting{ + name: "show-libmachine-logs", + set: SetBool, + }, + Setting{ + name: "log_dir", + set: SetString, + validations: []setFn{IsValidPath}, + }, + Setting{ + name: "kubernetes-version", + set: SetString, + }, +} + +var ConfigCmd = &cobra.Command{ + Use: "config SUBCOMMAND [flags]", + Short: "Modify minikube config", + Long: `Modify minikube config using subcommands like "minikube config set verbosity 100"`, + Run: func(cmd *cobra.Command, args []string) { + cmd.Help() + }, +} + +// Reads in the JSON minikube config +func ReadConfig() map[string]interface{} { + f, err := os.Open(constants.ConfigFile) + if err != nil { + if os.IsNotExist(err) { + return make(map[string]interface{}) + } + glog.Errorf("Could not open file %s: %s", constants.ConfigFile, err) + } + var m map[string]interface{} + m, err = decode(f) + if err != nil { + glog.Errorf("Could not decode config %s: %s", constants.ConfigFile, err) + } + + return m +} + +// Writes a minikube config to the JSON file +func WriteConfig(m map[string]interface{}) { + f, err := os.Create(constants.ConfigFile) + if err != nil { + glog.Errorf("Could not open file %s: %s", constants.ConfigFile, err) + } + defer f.Close() + err = encode(f, m) + if err != nil { + glog.Errorf("Error encoding config %s: %s", constants.ConfigFile, err) + } +} + +func decode(r io.Reader) (map[string]interface{}, error) { + var data map[string]interface{} + err := json.NewDecoder(r).Decode(&data) + return data, err +} + +func encode(w io.Writer, m map[string]interface{}) error { + enc := json.NewEncoder(w) + enc.SetIndent("", " ") + return enc.Encode(m) +} diff --git a/cmd/minikube/cmd/config/config_test.go b/cmd/minikube/cmd/config/config_test.go new file mode 100644 index 000000000000..7d99a1f47f43 --- /dev/null +++ b/cmd/minikube/cmd/config/config_test.go @@ -0,0 +1,69 @@ +package config + +import ( + "bytes" + "reflect" + "testing" +) + +type configTestCase struct { + data string + config map[string]interface{} +} + +var configTestCases = []configTestCase{ + { + data: `{ + "memory": 2 +} +`, + config: map[string]interface{}{ + "memory": 2, + }, + }, + { + data: `{ + "ReminderWaitPeriodInHours": 99, + "cpus": 4, + "disk-size": "20g", + "log_dir": "/etc/hosts", + "show-libmachine-logs": true, + "v": 5, + "vm-driver": "kvm" +} +`, + config: map[string]interface{}{ + "vm-driver": "kvm", + "cpus": 4, + "disk-size": "20g", + "v": 5, + "show-libmachine-logs": true, + "log_dir": "/etc/hosts", + "ReminderWaitPeriodInHours": 99, + }, + }, +} + +func TestReadConfig(t *testing.T) { + for _, tt := range configTestCases { + r := bytes.NewBufferString(tt.data) + config, err := decode(r) + if reflect.DeepEqual(config, tt.config) || err != nil { + t.Errorf("Did not read config correctly,\n\n wanted %+v, \n\n got %+v", tt.config, config) + } + } +} + +func TestWriteConfig(t *testing.T) { + var b bytes.Buffer + for _, tt := range configTestCases { + err := encode(&b, tt.config) + if err != nil { + t.Errorf("Error encoding: %s", err) + } + if b.String() != tt.data { + t.Errorf("Did not write config correctly, \n\n expected:\n %+v \n\n actual:\n %+v", tt.data, b.String()) + } + b.Reset() + } +} diff --git a/cmd/minikube/cmd/config/get.go b/cmd/minikube/cmd/config/get.go new file mode 100644 index 000000000000..93ac915ad38b --- /dev/null +++ b/cmd/minikube/cmd/config/get.go @@ -0,0 +1,33 @@ +package config + +import ( + "fmt" + "github.com/spf13/cobra" + "os" +) + +var configGetCmd = &cobra.Command{ + Use: "get PROPERTY_NAME", + Short: "Gets the value of PROPERTY_NAME from the minikube config file", + Long: "Returns the value of PROPERTY_NAME from the minikube config file. Can be overwritten by flags or environmental variables.", + Run: func(cmd *cobra.Command, args []string) { + if len(args) != 1 { + fmt.Fprintln(os.Stderr, "Please specify a PROPERTY_NAME") + os.Exit(1) + } + + val := get(args[0]) + if val != "" { + fmt.Fprintln(os.Stdout, val) + } + }, +} + +func init() { + ConfigCmd.AddCommand(configGetCmd) +} + +func get(name string) string { + m := ReadConfig() + return fmt.Sprintf("%v", m[name]) +} diff --git a/cmd/minikube/cmd/config/set.go b/cmd/minikube/cmd/config/set.go new file mode 100644 index 000000000000..47cf8150d9df --- /dev/null +++ b/cmd/minikube/cmd/config/set.go @@ -0,0 +1,59 @@ +package config + +import ( + "fmt" + "github.com/spf13/cobra" + "os" +) + +var configSetCmd = &cobra.Command{ + Use: "set PROPERTY_NAME PROPERTY_VALUE", + Short: "Sets an individual value in a minikube config file", + Long: `Sets the PROPERTY_NAME config value to PROPERTY_VALUE + These values can be overwritten by flags or environment variables.`, + Run: func(cmd *cobra.Command, args []string) { + if len(args) != 2 { + fmt.Fprintln(os.Stderr, "usage: set PROPERTY_NAME PROPERTY") + os.Exit(1) + } + err := set(args[0], args[1]) + if err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(1) + } + }, +} + +func init() { + ConfigCmd.AddCommand(configSetCmd) +} + +func set(name string, value string) error { + s, err := findSetting(name) + if err != nil { + return err + } + // Validate the new value + err = run(name, value, s.validations) + if err != nil { + return err + } + + // Set the value + config := ReadConfig() + err = s.set(config, name, value) + if err != nil { + return err + } + + // Write the value + WriteConfig(config) + + // Run any callbacks for this property + err = run(name, value, s.callbacks) + if err != nil { + return err + } + + return nil +} diff --git a/cmd/minikube/cmd/config/set_test.go b/cmd/minikube/cmd/config/set_test.go new file mode 100644 index 000000000000..ea1b55898a1b --- /dev/null +++ b/cmd/minikube/cmd/config/set_test.go @@ -0,0 +1,10 @@ +package config + +import "testing" + +func TestNotFound(t *testing.T) { + err := set("nonexistant", "10") + if err == nil { + t.Fatalf("Set did not return error for unknown property") + } +} diff --git a/cmd/minikube/cmd/config/unset.go b/cmd/minikube/cmd/config/unset.go new file mode 100644 index 000000000000..778db276f99e --- /dev/null +++ b/cmd/minikube/cmd/config/unset.go @@ -0,0 +1,31 @@ +package config + +import ( + "fmt" + "os" + + "github.com/spf13/cobra" +) + +var configUnsetCmd = &cobra.Command{ + Use: "unset PROPERTY_NAME", + Short: "unsets an individual value in a minikube config file", + Long: "unsets PROPERTY_NAME from the minikube config file. Can be overwritten by flags or environmental variables", + Run: func(cmd *cobra.Command, args []string) { + if len(args) != 1 { + fmt.Fprintf(os.Stdout, "usage: minikube config unset PROPERTY_NAME") + os.Exit(1) + } + unset(args[0]) + }, +} + +func init() { + ConfigCmd.AddCommand(configUnsetCmd) +} + +func unset(name string) { + m := ReadConfig() + delete(m, name) + WriteConfig(m) +} diff --git a/cmd/minikube/cmd/config/util.go b/cmd/minikube/cmd/config/util.go new file mode 100644 index 000000000000..3512e16ff7db --- /dev/null +++ b/cmd/minikube/cmd/config/util.go @@ -0,0 +1,56 @@ +package config + +import ( + "fmt" + "strconv" +) + +// Runs all the validation or callback functions and collects errors +func run(name string, value string, fns []setFn) error { + var errors []error + for _, fn := range fns { + err := fn(name, value) + if err != nil { + errors = append(errors, err) + } + } + if len(errors) > 0 { + return fmt.Errorf("%v", errors) + } else { + return nil + } +} + +func findSetting(name string) (Setting, error) { + for _, s := range settings { + if name == s.name { + return s, nil + } + } + return Setting{}, fmt.Errorf("Property name %s not found", name) +} + +// Set Functions + +func SetString(m MinikubeConfig, name string, val string) error { + m[name] = val + return nil +} + +func SetInt(m MinikubeConfig, name string, val string) error { + i, err := strconv.Atoi(val) + if err != nil { + return err + } + m[name] = i + return nil +} + +func SetBool(m MinikubeConfig, name string, val string) error { + b, err := strconv.ParseBool(val) + if err != nil { + return err + } + m[name] = b + return nil +} diff --git a/cmd/minikube/cmd/config/util_test.go b/cmd/minikube/cmd/config/util_test.go new file mode 100644 index 000000000000..51a6b6d917db --- /dev/null +++ b/cmd/minikube/cmd/config/util_test.go @@ -0,0 +1,61 @@ +package config + +import "testing" + +var minikubeConfig = MinikubeConfig{ + "vm-driver": "kvm", + "cpus": 12, + "show-libmachine-logs": true, +} + +func TestFindSettingNotFound(t *testing.T) { + s, err := findSetting("nonexistant") + if err == nil { + t.Fatalf("Shouldn't have found setting, but did. [%+v]", s) + } +} + +func TestFindSetting(t *testing.T) { + s, err := findSetting("vm-driver") + if err != nil { + t.Fatalf("Couldn't find setting, vm-driver: %s", err) + } + if s.name != "vm-driver" { + t.Fatalf("Found wrong setting, expected vm-driver, got %s", s.name) + } +} + +func TestSetString(t *testing.T) { + err := SetString(minikubeConfig, "vm-driver", "virtualbox") + if err != nil { + t.Fatalf("Couldnt set string: %s", err) + } +} + +func TestSetInt(t *testing.T) { + err := SetInt(minikubeConfig, "cpus", "22") + if err != nil { + t.Fatalf("Couldn't set int in config: %s", err) + } + val, ok := minikubeConfig["cpus"].(int) + if !ok { + t.Fatalf("Type not set to int") + } + if val != 22 { + t.Fatalf("SetInt set wrong value") + } +} + +func TestSetBool(t *testing.T) { + err := SetBool(minikubeConfig, "show-libmachine-logs", "true") + if err != nil { + t.Fatalf("Couldn't set bool in config: %s", err) + } + val, ok := minikubeConfig["show-libmachine-logs"].(bool) + if !ok { + t.Fatalf("Type not set to bool") + } + if !val { + t.Fatalf("SetBool set wrong value") + } +} diff --git a/cmd/minikube/cmd/config/validations.go b/cmd/minikube/cmd/config/validations.go new file mode 100644 index 000000000000..4a9275d49ce3 --- /dev/null +++ b/cmd/minikube/cmd/config/validations.go @@ -0,0 +1,68 @@ +package config + +import ( + "fmt" + "os" + "regexp" + "strconv" + + units "github.com/docker/go-units" + "github.com/golang/glog" + "k8s.io/minikube/pkg/minikube/constants" +) + +// A generous cidr regex +// validates general format, but will allow some invalid values of the form xxx.xxx.xxx.xxx/xx +const CIDRregex = `^([0-9]{1,3}\.){3}[0-9]{1,3}(\/[0-9]|[1-2][0-9]3[0-2])?$` + +func IsValidDriver(string, driver string) error { + for _, d := range constants.SupportedVMDrivers { + if driver == d { + return nil + } + } + return fmt.Errorf("Driver %s is not supported", driver) +} + +func RequiresRestartMsg(string, string) error { + fmt.Fprintln(os.Stdout, "These changes will take effect upon a restart") + return nil +} + +func IsValidDiskSize(name string, disksize string) error { + _, err := units.FromHumanSize(disksize) + if err != nil { + return fmt.Errorf("Not valid disk size: %v", err) + } + return nil +} + +func IsPositive(name string, val string) error { + i, err := strconv.Atoi(val) + if err != nil { + return fmt.Errorf("%s:%v", name, err) + } + if i <= 0 { + return fmt.Errorf("%s must be > 0", name) + } + return nil +} + +func IsValidCIDR(name string, cidr string) error { + r, err := regexp.Compile(CIDRregex) + if err != nil { + glog.Errorf("Error compiling regex: %v", err) + } + if !r.MatchString(cidr) { + return fmt.Errorf("%s is not a valid CIDR", cidr) + } + return nil +} + +func IsValidPath(name string, path string) error { + _, err := os.Stat(path) + if err != nil { + return fmt.Errorf("%s path is not valid: %v", name, err) + } + return nil +} diff --git a/cmd/minikube/cmd/config/validations_test.go b/cmd/minikube/cmd/config/validations_test.go new file mode 100644 index 000000000000..3e6ef332c860 --- /dev/null +++ b/cmd/minikube/cmd/config/validations_test.go @@ -0,0 +1,80 @@ +package config + +import "testing" + +type validationTest struct { + value string + shouldErr bool +} + +func runValidations(t *testing.T, tests []validationTest, name string, f func(string, string) error) { + for _, tt := range tests { + err := f(name, tt.value) + if err != nil && !tt.shouldErr { + t.Errorf("%s", tt.value, err) + } + if err == nil && tt.shouldErr { + t.Errorf("%s", tt.value, err) + } + } +} + +func TestDriver(t *testing.T) { + + var tests = []validationTest{ + { + value: "kvm", + shouldErr: false, + }, + { + value: "vkasdhfasjdf", + shouldErr: true, + }, + { + value: "", + shouldErr: true, + }, + } + + runValidations(t, tests, "vm-driver", IsValidDriver) + +} + +func TestValidCIDR(t *testing.T) { + var tests = []validationTest{ + { + value: "192.168.1.1", + shouldErr: false, + }, + { + value: "1.1.1.1", + shouldErr: false, + }, + { + value: "255.255.255.255/1", + shouldErr: false, + }, + { + value: "8.8.8.8/33", + shouldErr: true, + }, + { + value: "12.1", + shouldErr: true, + }, + { + value: "1", + shouldErr: true, + }, + { + value: "a string!", + shouldErr: true, + }, + { + value: "192.168.1.1/8/", + shouldErr: true, + }, + } + + runValidations(t, tests, "cidr", IsValidCIDR) +} diff --git a/cmd/minikube/cmd/root.go b/cmd/minikube/cmd/root.go index 827010bb6f65..9024c2a6f81f 100644 --- a/cmd/minikube/cmd/root.go +++ b/cmd/minikube/cmd/root.go @@ -26,6 +26,7 @@ import ( "github.com/spf13/cobra" "github.com/spf13/pflag" "github.com/spf13/viper" + configCmd "k8s.io/minikube/cmd/minikube/cmd/config" "k8s.io/minikube/pkg/minikube/config" "k8s.io/minikube/pkg/minikube/constants" "k8s.io/minikube/pkg/minikube/notify" @@ -108,6 +109,7 @@ func setFlagsUsingViper() { func init() { RootCmd.PersistentFlags().Bool(showLibmachineLogs, false, "Whether or not to show logs from libmachine.") + RootCmd.AddCommand(configCmd.ConfigCmd) pflag.CommandLine.AddGoFlagSet(goflag.CommandLine) viper.BindPFlags(RootCmd.PersistentFlags()) cobra.OnInitialize(initConfig) @@ -115,9 +117,8 @@ func init() { // initConfig reads in config file and ENV variables if set. func initConfig() { - configPath := constants.MakeMiniPath("config") - viper.SetConfigName("config") - viper.AddConfigPath(configPath) + configPath := constants.ConfigFile + viper.SetConfigFile(configPath) err := viper.ReadInConfig() if err != nil { glog.Warningf("Error reading config file at %s: %s", configPath, err) diff --git a/docs/minikube.md b/docs/minikube.md index 64ab797560a3..af37f7277e36 100644 --- a/docs/minikube.md +++ b/docs/minikube.md @@ -22,6 +22,7 @@ Minikube is a CLI tool that provisions and manages single-node Kubernetes cluste ``` ### SEE ALSO +* [minikube config](minikube_config.md) - Modify minikube config * [minikube dashboard](minikube_dashboard.md) - Opens/displays the kubernetes dashboard URL for your local cluster * [minikube delete](minikube_delete.md) - Deletes a local kubernetes cluster. * [minikube docker-env](minikube_docker-env.md) - sets up docker env variables; similar to '$(docker-machine env)' diff --git a/docs/minikube_config.md b/docs/minikube_config.md new file mode 100644 index 000000000000..65ec476401f6 --- /dev/null +++ b/docs/minikube_config.md @@ -0,0 +1,30 @@ +## minikube config + +Modify minikube config + +### Synopsis + + +Modify minikube config using subcommands like "minikube config set verbosity 100" + +``` +minikube config SUBCOMMAND [flags] +``` + +### Options inherited from parent commands + +``` + --alsologtostderr[=false]: log to standard error as well as files + --log-flush-frequency=5s: Maximum number of seconds between log flushes + --log_backtrace_at=:0: when logging hits line file:N, emit a stack trace + --log_dir="": If non-empty, write log files in this directory + --logtostderr[=false]: log to standard error instead of files + --show-libmachine-logs[=false]: Whether or not to show logs from libmachine. + --stderrthreshold=2: logs at or above this threshold go to stderr + --v=0: log level for V logs + --vmodule=: comma-separated list of pattern=N settings for file-filtered logging +``` + +### SEE ALSO +* [minikube](minikube.md) - Minikube is a tool for managing local Kubernetes clusters. + diff --git a/pkg/minikube/constants/constants.go b/pkg/minikube/constants/constants.go index a78489b85482..cec68a6c8f39 100644 --- a/pkg/minikube/constants/constants.go +++ b/pkg/minikube/constants/constants.go @@ -72,6 +72,7 @@ const ( ) var ConfigFilePath = MakeMiniPath("config") +var ConfigFile = MakeMiniPath("config", "config.json") var LocalkubeDownloadURLPrefix = "https://storage.googleapis.com/minikube/k8sReleases/" var LocalkubeLinuxFilename = "localkube-linux-amd64"