diff --git a/cmd/controller/certificates.go b/cmd/controller/certificates.go index 1f5c8f9f0075..f3d5259ea27c 100644 --- a/cmd/controller/certificates.go +++ b/cmd/controller/certificates.go @@ -136,11 +136,7 @@ func (c *Certificates) Init(ctx context.Context) error { if err != nil { return err } - if err := kubeConfig(c.K0sVars.KonnectivityKubeConfigPath, kubeConfigAPIUrl, c.CACert, konnectivityCert.Cert, konnectivityCert.Key, constant.KonnectivityServerUser); err != nil { - return err - } - - return nil + return kubeConfig(c.K0sVars.KonnectivityKubeConfigPath, kubeConfigAPIUrl, c.CACert, konnectivityCert.Cert, konnectivityCert.Key, constant.KonnectivityServerUser) }) eg.Go(func() error { diff --git a/cmd/controller/controller.go b/cmd/controller/controller.go index 1fe41da8ad91..fabaa4ce6350 100644 --- a/cmd/controller/controller.go +++ b/cmd/controller/controller.go @@ -127,7 +127,7 @@ func (c *CmdOpts) startController(ctx context.Context) error { // initialize runtime config loadingRules := config.ClientConfigLoadingRules{Nodeconfig: true} - if err := loadingRules.InitRuntimeConfig(); err != nil { + if err := loadingRules.InitRuntimeConfig(c.K0sVars); err != nil { logrus.Fatalf("failed to initialize k0s runtime config: %s", err.Error()) } diff --git a/cmd/restore/restore.go b/cmd/restore/restore.go index 9657b0beb903..9e60faf45a66 100644 --- a/cmd/restore/restore.go +++ b/cmd/restore/restore.go @@ -52,12 +52,17 @@ func NewRestoreCmd() *cobra.Command { } return c.restore(args[0]) }, - PreRunE: preRunValidateConfig, } cmd.SilenceUsage = true - cmd.Flags().StringVar(&restoredConfigPath, "config-out", "", "Specify desired name and full path for the restored k0s.yaml file (default: ${cwd}/k0s_.yaml)") - cmd.Flags().AddFlagSet(config.FileInputFlag()) + + cwd, err := os.Getwd() + if err != nil { + return nil + } + + restoredConfigPathDescription := fmt.Sprintf("Specify desired name and full path for the restored k0s.yaml file (default: %s/k0s_.yaml", cwd) + cmd.Flags().StringVar(&restoredConfigPath, "config-out", "", restoredConfigPathDescription) cmd.PersistentFlags().AddFlagSet(config.GetPersistentFlagSet()) return cmd } @@ -86,32 +91,18 @@ func (c *CmdOpts) restore(path string) error { if err != nil { return err } - // c.CfgFile, c.ClusterConfig.Spec, c.K0sVars - if restoredConfigPath == "" { restoredConfigPath = defaultConfigFileOutputPath(path) } return mgr.RunRestore(path, c.K0sVars, restoredConfigPath) } -// TODO Need to move to some common place, now it's defined in restore and backup commands -func preRunValidateConfig(_ *cobra.Command, _ []string) error { - c := CmdOpts(config.GetCmdOpts()) - - loadingRules := config.ClientConfigLoadingRules{K0sVars: c.K0sVars} - _, err := loadingRules.ParseRuntimeConfig() - if err != nil { - return fmt.Errorf("failed to get config: %v", err) - } - return nil -} - // set output config file name and path according to input archive Timestamps // the default location for the restore operation is the currently running cwd // this can be override, by using the --config-out flag func defaultConfigFileOutputPath(archivePath string) string { if archivePath == "-" { - return "-" + return constant.K0sConfigPathDefault } f := filepath.Base(archivePath) nameWithoutExt := strings.Split(f, ".")[0] diff --git a/inttest/common/footloosesuite.go b/inttest/common/footloosesuite.go index 82e33a2bf5c8..db54ac393326 100644 --- a/inttest/common/footloosesuite.go +++ b/inttest/common/footloosesuite.go @@ -564,6 +564,7 @@ func (s *FootlooseSuite) GetKubeClientConfig(node string, k0sKubeconfigArgs ...s kubeConfigCmd := fmt.Sprintf("%s kubeconfig admin %s 2>/dev/null", s.K0sFullPath, strings.Join(k0sKubeconfigArgs, " ")) kubeConf, err := ssh.ExecWithOutput(kubeConfigCmd) if err != nil { + fmt.Println(string(kubeConf)) return nil, err } cfg, err := clientcmd.Load([]byte(kubeConf)) diff --git a/pkg/apis/k0s.k0sproject.io/v1beta1/calico.go b/pkg/apis/k0s.k0sproject.io/v1beta1/calico.go index 38e258a1f2c3..ec9955756235 100644 --- a/pkg/apis/k0s.k0sproject.io/v1beta1/calico.go +++ b/pkg/apis/k0s.k0sproject.io/v1beta1/calico.go @@ -81,9 +81,5 @@ func (c *Calico) UnmarshalJSON(data []byte) error { type calico Calico jc := (*calico)(c) - if err := json.Unmarshal(data, jc); err != nil { - return err - } - - return nil + return json.Unmarshal(data, jc) } diff --git a/pkg/apis/k0s.k0sproject.io/v1beta1/clusterconfig_types.go b/pkg/apis/k0s.k0sproject.io/v1beta1/clusterconfig_types.go index db1bd5e45791..e4b2b27a67f5 100644 --- a/pkg/apis/k0s.k0sproject.io/v1beta1/clusterconfig_types.go +++ b/pkg/apis/k0s.k0sproject.io/v1beta1/clusterconfig_types.go @@ -183,13 +183,14 @@ func ConfigFromReader(r io.Reader, defaultStorage ...*StorageSpec) (*ClusterConf // DefaultClusterConfig sets the default ClusterConfig values, when none are given func DefaultClusterConfig(defaultStorage ...*StorageSpec) *ClusterConfig { + clusterSpec := DefaultClusterSpec(defaultStorage...) return &ClusterConfig{ ObjectMeta: metav1.ObjectMeta{Name: "k0s"}, TypeMeta: metav1.TypeMeta{ APIVersion: "k0s.k0sproject.io/v1beta1", Kind: "ClusterConfig", }, - Spec: DefaultClusterSpec(defaultStorage...), + Spec: clusterSpec, } } @@ -279,12 +280,12 @@ func (c *ClusterConfig) Validate() []error { } // GetBootstrappingConfig returns a ClusterConfig object stripped of Cluster-Wide Settings -func (c *ClusterConfig) GetBootstrappingConfig() *ClusterConfig { +func (c *ClusterConfig) GetBootstrappingConfig(storageSpec *StorageSpec) *ClusterConfig { var etcdConfig *EtcdConfig - if c.Spec.Storage.Type == EtcdStorageType { + if storageSpec.Type == EtcdStorageType { etcdConfig = &EtcdConfig{ - ExternalCluster: c.Spec.Storage.Etcd.ExternalCluster, - PeerAddress: c.Spec.Storage.Etcd.PeerAddress, + ExternalCluster: storageSpec.Etcd.ExternalCluster, + PeerAddress: storageSpec.Etcd.PeerAddress, } c.Spec.Storage.Etcd = etcdConfig } @@ -293,7 +294,7 @@ func (c *ClusterConfig) GetBootstrappingConfig() *ClusterConfig { TypeMeta: c.TypeMeta, Spec: &ClusterSpec{ API: c.Spec.API, - Storage: c.Spec.Storage, + Storage: storageSpec, Network: &Network{ ServiceCIDR: c.Spec.Network.ServiceCIDR, DualStack: c.Spec.Network.DualStack, diff --git a/pkg/backup/config.go b/pkg/backup/config.go index f6fa315846b5..d100e3871e03 100644 --- a/pkg/backup/config.go +++ b/pkg/backup/config.go @@ -19,6 +19,7 @@ limitations under the License. package backup import ( + "fmt" "io" "os" "path" @@ -51,7 +52,7 @@ func (c configurationStep) Restore(restoreFrom, restoreTo string) error { objectPathInArchive := path.Join(restoreFrom, "k0s.yaml") if !file.Exists(objectPathInArchive) { - logrus.Infof("%s does not exist in the backup file", objectPathInArchive) + logrus.Debugf("%s does not exist in the backup file", objectPathInArchive) return nil } logrus.Infof("Previously used k0s.yaml saved under the data directory `%s`", restoreTo) @@ -61,8 +62,12 @@ func (c configurationStep) Restore(restoreFrom, restoreTo string) error { if err != nil { return err } + if f == nil { + return fmt.Errorf("couldn't get a file handle for %s", c.restoredConfigPath) + } defer f.Close() _, err = io.Copy(f, os.Stdout) + logrus.Errorf("got error: %v", err) return err } logrus.Infof("restoring from `%s` to `%s`", objectPathInArchive, c.restoredConfigPath) diff --git a/pkg/config/api_config.go b/pkg/config/api_config.go index 218804b614c5..5bf3c042ad2a 100644 --- a/pkg/config/api_config.go +++ b/pkg/config/api_config.go @@ -64,7 +64,7 @@ func (rules *ClientConfigLoadingRules) fetchNodeConfig() (*v1beta1.ClusterConfig logrus.Errorf("failed to read config from file: %v", err) return nil, err } - return cfg.GetBootstrappingConfig(), nil + return cfg.GetBootstrappingConfig(cfg.Spec.Storage), nil } // when API config is enabled, but only node config is needed (for bootstrapping commands) @@ -77,7 +77,7 @@ func (rules *ClientConfigLoadingRules) mergeNodeAndClusterconfig(nodeConfig *v1b return nil, err } - err = mergo.Merge(clusterConfig, nodeConfig.GetBootstrappingConfig(), mergo.WithOverride) + err = mergo.Merge(clusterConfig, nodeConfig.GetBootstrappingConfig(nodeConfig.Spec.Storage), mergo.WithOverride) if err != nil { return nil, err } diff --git a/pkg/config/config.go b/pkg/config/config.go index 1e3729613241..666f84a3bb08 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -107,10 +107,7 @@ func (rules *ClientConfigLoadingRules) IsAPIConfig() bool { func (rules *ClientConfigLoadingRules) IsDefaultConfig() bool { // if no custom-value is provided as a config file, and no config-file exists in the default location // we assume we need to generate configuration defaults - if CfgFile == constant.K0sConfigPathDefault && !file.Exists(constant.K0sConfigPathDefault) { - return true - } - return false + return CfgFile == constant.K0sConfigPathDefault && !file.Exists(constant.K0sConfigPathDefault) } func (rules *ClientConfigLoadingRules) Load() (*v1beta1.ClusterConfig, error) { diff --git a/pkg/config/config_test.go b/pkg/config/config_test.go index 8a63b39d8e50..f3b9ef0a9f2f 100644 --- a/pkg/config/config_test.go +++ b/pkg/config/config_test.go @@ -19,6 +19,7 @@ import ( "context" "fmt" "os" + "strings" "testing" "time" @@ -64,7 +65,7 @@ func TestGetConfigFromFile(t *testing.T) { defer os.Remove(configPathRuntimeTest) loadingRules := ClientConfigLoadingRules{RuntimeConfigPath: configPathRuntimeTest} - err := loadingRules.InitRuntimeConfig() + err := loadingRules.InitRuntimeConfig(constant.GetConfig("")) if err != nil { t.Fatalf("failed to initialize k0s config: %s", err.Error()) } @@ -95,6 +96,52 @@ func TestGetConfigFromFile(t *testing.T) { } } +func TestExternalEtcdConfig(t *testing.T) { + yamlData := ` +spec: + storage: + type: etcd + etcd: + externalCluster: + endpoints: + - http://etcd0:2379 + etcdPrefix: k0s-tenant` + cfgFilePath := writeConfigFile(yamlData) + CfgFile = cfgFilePath + defer os.Remove(configPathRuntimeTest) + + loadingRules := ClientConfigLoadingRules{RuntimeConfigPath: configPathRuntimeTest} + err := loadingRules.InitRuntimeConfig(constant.GetConfig("")) + if err != nil { + t.Fatalf("failed to initialize k0s config: %s", err.Error()) + } + + cfg, err := loadingRules.Load() + if err != nil { + t.Fatalf("failed to load config: %s", err.Error()) + } + if cfg == nil { + t.Fatal("received an empty config! failing") + } + testCases := []struct { + name string + got string + expected string + }{ + {"Storage_Type", cfg.Spec.Storage.Type, "etcd"}, + {"External_Cluster_Endpoint", cfg.Spec.Storage.Etcd.ExternalCluster.Endpoints[0], "http://etcd0:2379"}, + {"External_Cluster_Prefix", cfg.Spec.Storage.Etcd.ExternalCluster.EtcdPrefix, "k0s-tenant"}, + } + + for _, tc := range testCases { + t.Run(fmt.Sprintf("%s eq %s", tc.name, tc.expected), func(t *testing.T) { + if tc.got != tc.expected { + t.Fatalf("expected to read '%s' for the %s test value. Got: %s", tc.expected, tc.name, tc.got) + } + }) + } +} + // Test using config from a yaml file func TestConfigFromDefaults(t *testing.T) { CfgFile = constant.K0sConfigPathDefault // this path doesn't exist, so default values should be generated @@ -138,7 +185,7 @@ func TestNodeConfigWithAPIConfig(t *testing.T) { loadingRules := ClientConfigLoadingRules{Nodeconfig: true, RuntimeConfigPath: configPathRuntimeTest} - err := loadingRules.InitRuntimeConfig() + err := loadingRules.InitRuntimeConfig(constant.GetConfig("")) if err != nil { t.Fatalf("failed to initialize k0s config: %s", err.Error()) } @@ -167,6 +214,85 @@ func TestNodeConfigWithAPIConfig(t *testing.T) { } } +func TestSingleNodeConfig(t *testing.T) { + CfgFile = constant.K0sConfigPathDefault // this path doesn't exist, so default values should be generated + defer os.Remove(configPathRuntimeTest) + + loadingRules := ClientConfigLoadingRules{RuntimeConfigPath: configPathRuntimeTest, Nodeconfig: true} + k0sVars := constant.GetConfig("") + k0sVars.DefaultStorageType = "kine" + + err := loadingRules.InitRuntimeConfig(k0sVars) + if err != nil { + t.Fatalf("failed to initialize k0s config: %s", err.Error()) + } + + cfg, err := loadingRules.Load() + if err != nil { + t.Fatalf("failed to load config: %s", err.Error()) + } + if cfg == nil { + t.Fatal("received an empty config! failing") + } + testCases := []struct { + name string + got string + expected string + }{ + {"Storage_Type", cfg.Spec.Storage.Type, "kine"}, + {"Kine_DataSource", cfg.Spec.Storage.Kine.DataSource, "sqlite:///var/lib/k0s/db/state.db"}, + } + + for _, tc := range testCases { + t.Run(fmt.Sprintf("%s eq %s", tc.name, tc.expected), func(t *testing.T) { + if !strings.Contains(tc.got, tc.expected) { + t.Fatalf("expected to read '%s' for the %s test value. Got: %s", tc.expected, tc.name, tc.got) + } + }) + } +} + +func TestSingleNodeConfigWithEtcd(t *testing.T) { + yamlData := ` +spec: + storage: + type: etcd` + + cfgFilePath := writeConfigFile(yamlData) + CfgFile = cfgFilePath + defer os.Remove(configPathRuntimeTest) + + loadingRules := ClientConfigLoadingRules{RuntimeConfigPath: configPathRuntimeTest, Nodeconfig: true} + k0sVars := constant.GetConfig("") + k0sVars.DefaultStorageType = "kine" + + err := loadingRules.InitRuntimeConfig(k0sVars) + if err != nil { + t.Fatalf("failed to initialize k0s config: %s", err.Error()) + } + + cfg, err := loadingRules.Load() + if err != nil { + t.Fatalf("failed to load config: %s", err.Error()) + } + if cfg == nil { + t.Fatal("received an empty config! failing") + } + testCases := []struct { + name string + got string + expected string + }{{"Storage_Type", cfg.Spec.Storage.Type, "etcd"}} // config file storage type trumps k0sVars.DefaultStorageType + + for _, tc := range testCases { + t.Run(fmt.Sprintf("%s eq %s", tc.name, tc.expected), func(t *testing.T) { + if tc.got != tc.expected { + t.Fatalf("expected to read '%s' for the %s test value. Got: %s", tc.expected, tc.name, tc.got) + } + }) + } +} + // when a component requests an API config, // the merged node and cluster config should be returned func TestAPIConfig(t *testing.T) { @@ -183,7 +309,7 @@ func TestAPIConfig(t *testing.T) { defer os.Remove(configPathRuntimeTest) loadingRules := ClientConfigLoadingRules{RuntimeConfigPath: configPathRuntimeTest, APIClient: client.K0sV1beta1()} - err = loadingRules.InitRuntimeConfig() + err = loadingRules.InitRuntimeConfig(constant.GetConfig("")) if err != nil { t.Fatalf("failed to initialize k0s config: %s", err.Error()) } diff --git a/pkg/config/file_config.go b/pkg/config/file_config.go index 958d6d205e00..7c8f92e31b4f 100644 --- a/pkg/config/file_config.go +++ b/pkg/config/file_config.go @@ -22,6 +22,7 @@ import ( "github.com/k0sproject/k0s/internal/pkg/file" "github.com/k0sproject/k0s/pkg/apis/k0s.k0sproject.io/v1beta1" + "github.com/k0sproject/k0s/pkg/constant" "github.com/sirupsen/logrus" "sigs.k8s.io/yaml" ) @@ -31,20 +32,18 @@ var ( ) // InitRuntimeConfig generates the runtime /run/k0s/k0s.yaml -func (rules *ClientConfigLoadingRules) InitRuntimeConfig() error { +func (rules *ClientConfigLoadingRules) InitRuntimeConfig(k0sVars constant.CfgVars) error { + rules.K0sVars = k0sVars cfg, err := rules.ParseRuntimeConfig() if err != nil { return err } - // this is used for Singlenode, where we set - // kine as default using k0sVars - cfg.Spec.Storage = rules.getStorageSpec() yamlData, err := yaml.Marshal(cfg) if err != nil { return err } - return rules.writeConfig(yamlData) + return rules.writeConfig(yamlData, cfg.Spec.Storage) } // readRuntimeConfig returns the configuration from the runtime configuration file @@ -72,26 +71,33 @@ func (rules *ClientConfigLoadingRules) ParseRuntimeConfig() (*v1beta1.ClusterCon CfgFile = rules.RuntimeConfigPath } + var storage *v1beta1.StorageSpec + if rules.K0sVars.DefaultStorageType == "kine" { + storage = &v1beta1.StorageSpec{ + Type: v1beta1.KineStorageType, + Kine: v1beta1.DefaultKineConfig(rules.K0sVars.DataDir), + } + } + switch CfgFile { // stdin input case "-": - return v1beta1.ConfigFromReader(os.Stdin, rules.getStorageSpec()) + return v1beta1.ConfigFromReader(os.Stdin, storage) default: f, err := os.Open(CfgFile) if err != nil { if os.IsNotExist(err) { - return rules.generateDefaults(), nil + return rules.generateDefaults(storage), nil } return nil, err } defer f.Close() - cfg, err = v1beta1.ConfigFromReader(f, rules.getStorageSpec()) + cfg, err = v1beta1.ConfigFromReader(f, storage) if err != nil { return nil, err } } - if cfg.Spec.Storage.Type == v1beta1.KineStorageType && cfg.Spec.Storage.Kine == nil { logrus.Warn("storage type is kine but no config given, setting up defaults") cfg.Spec.Storage.Kine = v1beta1.DefaultKineConfig(rules.K0sVars.DataDir) @@ -110,17 +116,16 @@ func (rules *ClientConfigLoadingRules) ParseRuntimeConfig() (*v1beta1.ClusterCon return nil, fmt.Errorf(strings.Join(messages, "\n")) } return cfg, nil - } // generate default config and return the config object -func (rules *ClientConfigLoadingRules) generateDefaults() (config *v1beta1.ClusterConfig) { +func (rules *ClientConfigLoadingRules) generateDefaults(defaultStorage *v1beta1.StorageSpec) (config *v1beta1.ClusterConfig) { logrus.Debugf("no config file given, using defaults") - return v1beta1.DefaultClusterConfig(rules.getStorageSpec()) + return v1beta1.DefaultClusterConfig(defaultStorage) } -func (rules *ClientConfigLoadingRules) writeConfig(yamlData []byte) error { - mergedConfig, err := v1beta1.ConfigFromString(string(yamlData), rules.getStorageSpec()) +func (rules *ClientConfigLoadingRules) writeConfig(yamlData []byte, storageSpec *v1beta1.StorageSpec) error { + mergedConfig, err := v1beta1.ConfigFromString(string(yamlData), storageSpec) if err != nil { return fmt.Errorf("unable to parse config: %v", err) } @@ -135,14 +140,3 @@ func (rules *ClientConfigLoadingRules) writeConfig(yamlData []byte) error { } return nil } - -func (rules *ClientConfigLoadingRules) getStorageSpec() *v1beta1.StorageSpec { - var storage v1beta1.StorageSpec - if rules.K0sVars.DefaultStorageType == "kine" { - storage = v1beta1.StorageSpec{ - Type: v1beta1.KineStorageType, - Kine: v1beta1.DefaultKineConfig(rules.K0sVars.DataDir), - } - } - return &storage -}