From 91426eabd15b4ad5f53f4b3f1f7807e54c1ed45c Mon Sep 17 00:00:00 2001 From: iwilltry42 Date: Mon, 6 Sep 2021 17:22:06 +0200 Subject: [PATCH] cmd: make config initialization more general - move viper initialization from k3d config file to separate util sub-package in cmd/ - use that new subpackage init function to leverage the config file in `k3d cluster delete` - cover that with an e2e test case --- cmd/cluster/clusterCreate.go | 80 +++++++---------------------- cmd/cluster/clusterDelete.go | 48 ++++++++++++++++-- cmd/util/config/config.go | 98 ++++++++++++++++++++++++++++++++++++ tests/test_config_file.sh | 13 +++-- 4 files changed, 169 insertions(+), 70 deletions(-) create mode 100644 cmd/util/config/config.go diff --git a/cmd/cluster/clusterCreate.go b/cmd/cluster/clusterCreate.go index 5ea8c9dd..61ed673f 100644 --- a/cmd/cluster/clusterCreate.go +++ b/cmd/cluster/clusterCreate.go @@ -24,9 +24,7 @@ package cluster import ( "fmt" - "io/ioutil" "os" - "path/filepath" "runtime" "strconv" "strings" @@ -40,6 +38,7 @@ import ( "gopkg.in/yaml.v2" cliutil "github.com/rancher/k3d/v4/cmd/util" + cliconfig "github.com/rancher/k3d/v4/cmd/util/config" k3dCluster "github.com/rancher/k3d/v4/pkg/client" "github.com/rancher/k3d/v4/pkg/config" conf "github.com/rancher/k3d/v4/pkg/config/v1alpha3" @@ -59,74 +58,30 @@ Every cluster will consist of one or more containers: - (optionally) 1 (or more) agent node containers (k3s) ` -var cfgViper = viper.New() -var ppViper = viper.New() +/* + * Viper for configuration handling + * we use two different instances of Viper here to handle + * - cfgViper: "static" configuration + * - ppViper: "pre-processed" configuration, where CLI input has to be pre-processed + * to be treated as part of the SImpleConfig + */ +var ( + cfgViper = viper.New() + ppViper = viper.New() +) -func initConfig() { +func initConfig() error { // Viper for pre-processed config options ppViper.SetEnvPrefix("K3D") - // viper for the general config (file, env and non pre-processed flags) - cfgViper.SetEnvPrefix("K3D") - cfgViper.AutomaticEnv() - - cfgViper.SetConfigType("yaml") - - // Set config file, if specified - if configFile != "" { - - if _, err := os.Stat(configFile); err != nil { - l.Log().Fatalf("Failed to stat config file %s: %+v", configFile, err) - } - - // create temporary file to expand environment variables in the config without writing that back to the original file - // we're doing it here, because this happens just before absolutely all other processing - tmpfile, err := os.CreateTemp(os.TempDir(), fmt.Sprintf("k3d-config-tmp-%s", filepath.Base(configFile))) - if err != nil { - l.Log().Fatalf("error creating temp copy of configfile %s for variable expansion: %v", configFile, err) - } - defer tmpfile.Close() - - originalcontent, err := ioutil.ReadFile(configFile) - if err != nil { - l.Log().Fatalf("error reading config file %s: %v", configFile, err) - } - expandedcontent := os.ExpandEnv(string(originalcontent)) - if _, err := tmpfile.WriteString(expandedcontent); err != nil { - l.Log().Fatalf("error writing expanded config file contents to temp file %s: %v", tmpfile.Name(), err) - } - - // use temp file with expanded variables - cfgViper.SetConfigFile(tmpfile.Name()) - - // try to read config into memory (viper map structure) - if err := cfgViper.ReadInConfig(); err != nil { - if _, ok := err.(viper.ConfigFileNotFoundError); ok { - l.Log().Fatalf("Config file %s not found: %+v", configFile, err) - } - // config file found but some other error happened - l.Log().Fatalf("Failed to read config file %s: %+v", configFile, err) - } - - schema, err := config.GetSchemaByVersion(cfgViper.GetString("apiVersion")) - if err != nil { - l.Log().Fatalf("Cannot validate config file %s: %+v", configFile, err) - } - - if err := config.ValidateSchemaFile(configFile, schema); err != nil { - l.Log().Fatalf("Schema Validation failed for config file %s: %+v", configFile, err) - } - - l.Log().Infof("Using config file %s (%s#%s)", configFile, strings.ToLower(cfgViper.GetString("apiVersion")), strings.ToLower(cfgViper.GetString("kind"))) - } if l.Log().GetLevel() >= logrus.DebugLevel { - c, _ := yaml.Marshal(cfgViper.AllSettings()) - l.Log().Debugf("Configuration:\n%s", c) - c, _ = yaml.Marshal(ppViper.AllSettings()) + c, _ := yaml.Marshal(ppViper.AllSettings()) l.Log().Debugf("Additional CLI Configuration:\n%s", c) } + + return cliconfig.InitViperWithConfigFile(cfgViper, configFile) } // NewCmdClusterCreate returns a new cobra command @@ -139,8 +94,7 @@ func NewCmdClusterCreate() *cobra.Command { Long: clusterCreateDescription, Args: cobra.RangeArgs(0, 1), // exactly one cluster name can be set (default: k3d.DefaultClusterName) PreRunE: func(cmd *cobra.Command, args []string) error { - initConfig() - return nil + return initConfig() }, Run: func(cmd *cobra.Command, args []string) { diff --git a/cmd/cluster/clusterDelete.go b/cmd/cluster/clusterDelete.go index 01969b33..de16aa7c 100644 --- a/cmd/cluster/clusterDelete.go +++ b/cmd/cluster/clusterDelete.go @@ -27,6 +27,7 @@ import ( "path" "github.com/rancher/k3d/v4/cmd/util" + cliconfig "github.com/rancher/k3d/v4/cmd/util/config" "github.com/rancher/k3d/v4/pkg/client" l "github.com/rancher/k3d/v4/pkg/logger" "github.com/rancher/k3d/v4/pkg/runtimes" @@ -34,8 +35,12 @@ import ( k3dutil "github.com/rancher/k3d/v4/pkg/util" "github.com/spf13/cobra" + "github.com/spf13/viper" ) +var clusterDeleteConfigFile string +var clusterDeleteCfgViper = viper.New() + // NewCmdClusterDelete returns a new cobra command func NewCmdClusterDelete() *cobra.Command { @@ -47,6 +52,9 @@ func NewCmdClusterDelete() *cobra.Command { Long: `Delete cluster(s).`, Args: cobra.MinimumNArgs(0), // 0 or n arguments; 0 = default cluster name ValidArgsFunction: util.ValidArgsAvailableClusters, + PreRunE: func(cmd *cobra.Command, args []string) error { + return cliconfig.InitViperWithConfigFile(clusterDeleteCfgViper, clusterDeleteConfigFile) + }, Run: func(cmd *cobra.Command, args []string) { clusters := parseDeleteClusterCmd(cmd, args) @@ -87,6 +95,15 @@ func NewCmdClusterDelete() *cobra.Command { // add flags cmd.Flags().BoolP("all", "a", false, "Delete all existing clusters") + /*************** + * Config File * + ***************/ + + cmd.Flags().StringVarP(&clusterDeleteConfigFile, "config", "c", "", "Path of a config file to use") + if err := cmd.MarkFlagFilename("config", "yaml", "yml"); err != nil { + l.Log().Fatalln("Failed to mark flag 'config' as filename flag") + } + // done return cmd } @@ -94,12 +111,36 @@ func NewCmdClusterDelete() *cobra.Command { // parseDeleteClusterCmd parses the command input into variables required to delete clusters func parseDeleteClusterCmd(cmd *cobra.Command, args []string) []*k3d.Cluster { - // --all var clusters []*k3d.Cluster - if all, err := cmd.Flags().GetBool("all"); err != nil { + // --all + all, err := cmd.Flags().GetBool("all") + if err != nil { l.Log().Fatalln(err) - } else if all { + } + + // --config + if clusterDeleteConfigFile != "" { + // not allowed with --all or more args + if len(args) > 0 || all { + l.Log().Fatalln("failed to delete cluster: cannot use `--config` flag with additional arguments or `--all`") + } + + if clusterDeleteCfgViper.GetString("name") == "" { + l.Log().Fatalln("failed to delete cluster via config file: no name in config file") + } + + c, err := client.ClusterGet(cmd.Context(), runtimes.SelectedRuntime, &k3d.Cluster{Name: clusterDeleteCfgViper.GetString("name")}) + if err != nil { + l.Log().Fatalf("failed to delete cluster '%s': %v", clusterDeleteCfgViper.GetString("name"), err) + } + + clusters = append(clusters, c) + return clusters + } + + // --all was set + if all { l.Log().Infoln("Deleting all clusters...") clusters, err = client.ClusterList(cmd.Context(), runtimes.SelectedRuntime) if err != nil { @@ -108,6 +149,7 @@ func parseDeleteClusterCmd(cmd *cobra.Command, args []string) []*k3d.Cluster { return clusters } + // args only clusternames := []string{k3d.DefaultClusterName} if len(args) != 0 { clusternames = args diff --git a/cmd/util/config/config.go b/cmd/util/config/config.go new file mode 100644 index 00000000..c5a37756 --- /dev/null +++ b/cmd/util/config/config.go @@ -0,0 +1,98 @@ +/* +Copyright © 2020-2021 The k3d Author(s) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ +package config + +import ( + "fmt" + "io/ioutil" + "os" + "path/filepath" + "strings" + + "github.com/rancher/k3d/v4/pkg/config" + l "github.com/rancher/k3d/v4/pkg/logger" + "github.com/sirupsen/logrus" + "github.com/spf13/viper" + "gopkg.in/yaml.v2" +) + +func InitViperWithConfigFile(cfgViper *viper.Viper, configFile string) error { + + // viper for the general config (file, env and non pre-processed flags) + cfgViper.SetEnvPrefix("K3D") + cfgViper.AutomaticEnv() + + cfgViper.SetConfigType("yaml") + + // Set config file, if specified + if configFile != "" { + + if _, err := os.Stat(configFile); err != nil { + l.Log().Fatalf("Failed to stat config file %s: %+v", configFile, err) + } + + // create temporary file to expand environment variables in the config without writing that back to the original file + // we're doing it here, because this happens just before absolutely all other processing + tmpfile, err := os.CreateTemp(os.TempDir(), fmt.Sprintf("k3d-config-tmp-%s", filepath.Base(configFile))) + if err != nil { + l.Log().Fatalf("error creating temp copy of configfile %s for variable expansion: %v", configFile, err) + } + defer tmpfile.Close() + + originalcontent, err := ioutil.ReadFile(configFile) + if err != nil { + l.Log().Fatalf("error reading config file %s: %v", configFile, err) + } + expandedcontent := os.ExpandEnv(string(originalcontent)) + if _, err := tmpfile.WriteString(expandedcontent); err != nil { + l.Log().Fatalf("error writing expanded config file contents to temp file %s: %v", tmpfile.Name(), err) + } + + // use temp file with expanded variables + cfgViper.SetConfigFile(tmpfile.Name()) + + // try to read config into memory (viper map structure) + if err := cfgViper.ReadInConfig(); err != nil { + if _, ok := err.(viper.ConfigFileNotFoundError); ok { + l.Log().Fatalf("Config file %s not found: %+v", configFile, err) + } + // config file found but some other error happened + l.Log().Fatalf("Failed to read config file %s: %+v", configFile, err) + } + + schema, err := config.GetSchemaByVersion(cfgViper.GetString("apiVersion")) + if err != nil { + l.Log().Fatalf("Cannot validate config file %s: %+v", configFile, err) + } + + if err := config.ValidateSchemaFile(configFile, schema); err != nil { + l.Log().Fatalf("Schema Validation failed for config file %s: %+v", configFile, err) + } + + l.Log().Infof("Using config file %s (%s#%s)", configFile, strings.ToLower(cfgViper.GetString("apiVersion")), strings.ToLower(cfgViper.GetString("kind"))) + } + if l.Log().GetLevel() >= logrus.DebugLevel { + c, _ := yaml.Marshal(cfgViper.AllSettings()) + l.Log().Debugf("Configuration:\n%s", c) + } + return nil +} diff --git a/tests/test_config_file.sh b/tests/test_config_file.sh index f10b988d..d459ed92 100755 --- a/tests/test_config_file.sh +++ b/tests/test_config_file.sh @@ -17,13 +17,16 @@ fi export CURRENT_STAGE="Test | config-file | $K3S_IMAGE_TAG" - +configfileoriginal="$CURR_DIR/assets/config_test_simple.yaml" +configfile="/tmp/config_test_simple-tmp_$(date -u +'%Y%m%dT%H%M%SZ').yaml" clustername="configtest" +sed -E "s/name:.+/name: $clustername/g" < "$configfileoriginal" > "$configfile" # replace cluster name in config file so we can use it in this script without running into override issues + highlight "[START] ConfigTest $EXTRA_TITLE" info "Creating cluster $clustername..." -$EXE cluster create "$clustername" --config "$CURR_DIR/assets/config_test_simple.yaml" $EXTRA_FLAG || failed "could not create cluster $clustername $EXTRA_TITLE" +$EXE cluster create "$clustername" --config "$configfile" $EXTRA_FLAG || failed "could not create cluster $clustername $EXTRA_TITLE" info "Sleeping for 5 seconds to give the cluster enough time to get ready..." sleep 5 @@ -60,8 +63,10 @@ exec_in_node "k3d-$clustername-server-0" "cat /etc/rancher/k3s/registries.yaml" # Cleanup -info "Deleting cluster $clustername..." -$EXE cluster delete "$clustername" || failed "could not delete the cluster $clustername" +info "Deleting cluster $clustername (using config file)..." +$EXE cluster delete --config "$configfile" || failed "could not delete the cluster $clustername" + +rm "$configfile" highlight "[DONE] ConfigTest $EXTRA_TITLE"