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
pull/734/head
iwilltry42 3 years ago committed by Thorsten Klein
parent 78738058c8
commit 91426eabd1
  1. 80
      cmd/cluster/clusterCreate.go
  2. 48
      cmd/cluster/clusterDelete.go
  3. 98
      cmd/util/config/config.go
  4. 13
      tests/test_config_file.sh

@ -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) {

@ -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

@ -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
}

@ -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"

Loading…
Cancel
Save