Skip to content

Commit

Permalink
config refactoring: fix singlenode config option
Browse files Browse the repository at this point in the history
Signed-off-by: Karen Almog <[email protected]>
  • Loading branch information
Karen Almog committed Jan 11, 2022
1 parent 9f7cb64 commit 86ea756
Show file tree
Hide file tree
Showing 7 changed files with 160 additions and 41 deletions.
2 changes: 1 addition & 1 deletion cmd/controller/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -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())
}

Expand Down
1 change: 1 addition & 0 deletions inttest/common/footloosesuite.go
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down
13 changes: 7 additions & 6 deletions pkg/apis/k0s.k0sproject.io/v1beta1/clusterconfig_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
}
}

Expand Down Expand Up @@ -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
}
Expand All @@ -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,
Expand Down
4 changes: 2 additions & 2 deletions pkg/config/api_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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
}
Expand Down
5 changes: 1 addition & 4 deletions pkg/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
132 changes: 129 additions & 3 deletions pkg/config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import (
"context"
"fmt"
"os"
"strings"
"testing"
"time"

Expand Down Expand Up @@ -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())
}
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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())
}
Expand Down Expand Up @@ -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) {
Expand All @@ -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())
}
Expand Down
44 changes: 19 additions & 25 deletions pkg/config/file_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)
Expand All @@ -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
Expand Down Expand Up @@ -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)
Expand All @@ -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)
}
Expand All @@ -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
}

0 comments on commit 86ea756

Please sign in to comment.