diff --git a/docs/usage/configfile.md b/docs/usage/configfile.md index 80abbf42..c9b59568 100644 --- a/docs/usage/configfile.md +++ b/docs/usage/configfile.md @@ -52,7 +52,8 @@ Since the config options and the config file are changing quite a bit, it's hard # k3d configuration file, saved as e.g. /home/me/myk3dcluster.yaml apiVersion: k3d.io/v1alpha4 # this will change in the future as we make everything more stable kind: Simple # internally, we also have a Cluster config, which is not yet available externally -name: mycluster # name that you want to give to your cluster (will still be prefixed with `k3d-`) +metadata: + name: mycluster # name that you want to give to your cluster (will still be prefixed with `k3d-`) servers: 1 # same as `--servers 1` agents: 2 # same as `--agents 2` kubeAPI: # same as `--api-port myhost.my.domain:6445` (where the name would resolve to 127.0.0.1) diff --git a/docs/usage/registries.md b/docs/usage/registries.md index 86bd8937..7bdb1acf 100644 --- a/docs/usage/registries.md +++ b/docs/usage/registries.md @@ -25,7 +25,8 @@ If you're using a `SimpleConfig` file to configure your k3d cluster, you may as ```yaml apiVersion: k3d.io/v1alpha4 kind: Simple -name: test +metadata: + name: test servers: 1 agents: 2 registries: diff --git a/pkg/config/config_test.go b/pkg/config/config_test.go index e7a87d79..c1971b10 100644 --- a/pkg/config/config_test.go +++ b/pkg/config/config_test.go @@ -44,7 +44,9 @@ func TestReadSimpleConfig(t *testing.T) { APIVersion: "k3d.io/v1alpha4", Kind: "Simple", }, - Name: "test", + ObjectMeta: configtypes.ObjectMeta{ + Name: "test", + }, Servers: 1, Agents: 2, ExposeAPI: exposedAPI, @@ -268,7 +270,9 @@ func TestReadSimpleConfigRegistries(t *testing.T) { APIVersion: "k3d.io/v1alpha4", Kind: "Simple", }, - Name: "test", + ObjectMeta: configtypes.ObjectMeta{ + Name: "test", + }, Servers: 1, Agents: 1, Registries: conf.SimpleConfigRegistries{ diff --git a/pkg/config/jsonschema_test.go b/pkg/config/jsonschema_test.go index ddeaa655..d1fa8694 100644 --- a/pkg/config/jsonschema_test.go +++ b/pkg/config/jsonschema_test.go @@ -46,7 +46,7 @@ func TestValidateSchemaFail(t *testing.T) { t.Errorf("Validation of config file %s against the default schema passed where we expected a failure", cfgPath) } - expectedErrorText := `- name: Invalid type. Expected: string, given: integer + expectedErrorText := `- metadata.name: Invalid type. Expected: string, given: integer ` if err.Error() != expectedErrorText { diff --git a/pkg/config/migrate_test.go b/pkg/config/migrate_test.go index 3fedfd7b..7f893b21 100644 --- a/pkg/config/migrate_test.go +++ b/pkg/config/migrate_test.go @@ -26,59 +26,76 @@ import ( "testing" "github.com/go-test/deep" + "github.com/rancher/k3d/v5/pkg/config/v1alpha3" + "github.com/rancher/k3d/v5/pkg/config/v1alpha4" l "github.com/rancher/k3d/v5/pkg/logger" "github.com/spf13/viper" ) -func TestMigrateV1Alpha2ToV1Alpha3(t *testing.T) { - - actualPath := "test_assets/config_test_simple_migration_v1alpha2.yaml" - expectedPath := "test_assets/config_test_simple_migration_v1alpha3.yaml" - - actualViper := viper.New() - expectedViper := viper.New() +func TestMigrate(t *testing.T) { + tests := map[string]struct { + targetVersion string + actualPath string + expectedPath string + }{ + "V1Alpha2ToV1Alpha3": { + targetVersion: v1alpha3.ApiVersion, + actualPath: "test_assets/config_test_simple_migration_v1alpha2.yaml", + expectedPath: "test_assets/config_test_simple_migration_v1alpha3.yaml", + }, + "V1Alpha2ToV1Alpha4": { + targetVersion: v1alpha4.ApiVersion, + actualPath: "test_assets/config_test_simple_migration_v1alpha2.yaml", + expectedPath: "test_assets/config_test_simple_migration_v1alpha4.yaml", + }, + "V1Alpha3ToV1Alpha4": { + targetVersion: v1alpha4.ApiVersion, + actualPath: "test_assets/config_test_simple_migration_v1alpha3.yaml", + expectedPath: "test_assets/config_test_simple_migration_v1alpha4.yaml", + }, + } - actualViper.SetConfigType("yaml") - expectedViper.SetConfigType("yaml") + for name, tc := range tests { + t.Run(name, func(t *testing.T) { - actualViper.SetConfigFile(actualPath) - expectedViper.SetConfigFile(expectedPath) + actualViper := viper.New() + expectedViper := viper.New() - if err := actualViper.ReadInConfig(); err != nil { - t.Fatal(err) - } + actualViper.SetConfigType("yaml") + expectedViper.SetConfigType("yaml") - if err := expectedViper.ReadInConfig(); err != nil { - t.Fatal(err) - } + actualViper.SetConfigFile(tc.actualPath) + expectedViper.SetConfigFile(tc.expectedPath) - actualCfg, err := FromViper(actualViper) - if err != nil { - t.Fatal(err) - } + if err := actualViper.ReadInConfig(); err != nil { + t.Fatal(err) + } - if actualCfg.GetAPIVersion() != DefaultConfigApiVersion { - actualCfg, err = Migrate(actualCfg, DefaultConfigApiVersion) - if err != nil { - l.Log().Fatalln(err) - } - } + if err := expectedViper.ReadInConfig(); err != nil { + t.Fatal(err) + } - expectedCfg, err := FromViper(expectedViper) - if err != nil { - t.Fatal(err) - } + actualCfg, err := FromViper(actualViper) + if err != nil { + t.Fatal(err) + } - if diff := deep.Equal(actualCfg, expectedCfg); diff != nil { - t.Fatalf("Actual\n%#v\ndoes not match expected\n%+v\nDiff:\n%#v", actualCfg, expectedCfg, diff) - } + if actualCfg.GetAPIVersion() != tc.targetVersion { + actualCfg, err = Migrate(actualCfg, tc.targetVersion) + if err != nil { + l.Log().Fatalln(err) + } + } -} + expectedCfg, err := FromViper(expectedViper) + if err != nil { + t.Fatal(err) + } -func TestMigrateV1Alpha2ToV1Alpha4(t *testing.T) { - t.Log("not implemented") // TODO: test migration v1alpha2 to v1alpha4 -} + if diff := deep.Equal(actualCfg, expectedCfg); diff != nil { + t.Fatalf("Actual\n%#v\ndoes not match expected\n%+v\nDiff:\n%#v", actualCfg, expectedCfg, diff) + } -func TestMigrateV1Alpha3ToV1Alpha4(t *testing.T) { - t.Log("not implemented") // TODO: test migration v1alpha3 to v1alpha4 + }) + } } diff --git a/pkg/config/test_assets/config_test_registries.yaml b/pkg/config/test_assets/config_test_registries.yaml index 79c84d49..d63e2cec 100644 --- a/pkg/config/test_assets/config_test_registries.yaml +++ b/pkg/config/test_assets/config_test_registries.yaml @@ -1,6 +1,7 @@ apiVersion: k3d.io/v1alpha4 kind: Simple -name: test +metadata: + name: test servers: 1 agents: 1 registries: diff --git a/pkg/config/test_assets/config_test_simple.yaml b/pkg/config/test_assets/config_test_simple.yaml index 2f2532e6..e3cf981a 100644 --- a/pkg/config/test_assets/config_test_simple.yaml +++ b/pkg/config/test_assets/config_test_simple.yaml @@ -1,6 +1,7 @@ apiVersion: k3d.io/v1alpha4 kind: Simple -name: test +metadata: + name: test servers: 1 agents: 2 kubeAPI: diff --git a/pkg/config/test_assets/config_test_simple_2.yaml b/pkg/config/test_assets/config_test_simple_2.yaml index 35a187c6..4e3451ba 100644 --- a/pkg/config/test_assets/config_test_simple_2.yaml +++ b/pkg/config/test_assets/config_test_simple_2.yaml @@ -1,4 +1,5 @@ apiVersion: k3d.io/v1alpha4 kind: Simple -name: supertest +metadata: + name: supertest agents: 8 diff --git a/pkg/config/test_assets/config_test_simple_invalid_servers.yaml b/pkg/config/test_assets/config_test_simple_invalid_servers.yaml index 569e20be..576c7d06 100644 --- a/pkg/config/test_assets/config_test_simple_invalid_servers.yaml +++ b/pkg/config/test_assets/config_test_simple_invalid_servers.yaml @@ -1,6 +1,7 @@ apiVersion: k3d.io/v1alpha4 kind: Simple -name: 1234 +metadata: + name: 1234 servers: 1 agents: 2 kubeAPI: diff --git a/pkg/config/test_assets/config_test_simple_migration_v1alpha4.yaml b/pkg/config/test_assets/config_test_simple_migration_v1alpha4.yaml new file mode 100755 index 00000000..1b4c7642 --- /dev/null +++ b/pkg/config/test_assets/config_test_simple_migration_v1alpha4.yaml @@ -0,0 +1,56 @@ +apiVersion: k3d.io/v1alpha4 +kind: Simple +metadata: + name: test +servers: 3 +agents: 2 +kubeAPI: + hostIP: "0.0.0.0" + hostPort: "6446" +#image: rancher/k3s:latest +volumes: + - volume: /my/path:/some/path + nodeFilters: + - all +ports: + - port: 80:80 + nodeFilters: + - loadbalancer + - port: 0.0.0.0:443:443 + nodeFilters: + - loadbalancer +env: + - envVar: bar=baz,bob + nodeFilters: + - all +registries: + create: + name: k3d-test-registry + host: "0.0.0.0" + hostPort: random + config: | + mirrors: + "my.company.registry": + endpoint: + - http://my.company.registry:5000 + +options: + k3d: + wait: true + timeout: "360s" # should be pretty high for multi-server clusters to allow for a proper startup routine + disableLoadbalancer: false + disableImageVolume: false + k3s: + extraArgs: + - arg: --tls-san=127.0.0.1 + nodeFilters: + - server:* + kubeconfig: + updateDefaultKubeconfig: true + switchCurrentContext: true + runtime: + labels: + - label: foo=bar + nodeFilters: + - server:0 + - loadbalancer diff --git a/pkg/config/types/types.go b/pkg/config/types/types.go index 9f406b3c..9856722f 100644 --- a/pkg/config/types/types.go +++ b/pkg/config/types/types.go @@ -27,6 +27,11 @@ type TypeMeta struct { APIVersion string `mapstructure:"apiVersion,omitempty" yaml:"apiVersion,omitempty" json:"apiVersion,omitempty"` } +// ObjectMeta got its name from the Kubernetes counterpart. +type ObjectMeta struct { + Name string `mapstructure:"name,omitempty" yaml:"name,omitempty" json:"name,omitempty"` +} + // Config interface. type Config interface { GetKind() string diff --git a/pkg/config/v1alpha4/migrations.go b/pkg/config/v1alpha4/migrations.go index cc081876..c5e8e2c9 100644 --- a/pkg/config/v1alpha4/migrations.go +++ b/pkg/config/v1alpha4/migrations.go @@ -23,6 +23,7 @@ THE SOFTWARE. package v1alpha4 import ( + "encoding/json" "fmt" configtypes "github.com/rancher/k3d/v5/pkg/config/types" @@ -52,5 +53,48 @@ func MigrateV1Alpha2(input configtypes.Config) (configtypes.Config, error) { func MigrateV1Alpha3(input configtypes.Config) (configtypes.Config, error) { l.Log().Debugln("Migrating v1alpha3 to v1alpha4") + /* + * We're migrating matching fields between versions by marshalling to JSON and back + */ + injson, err := json.Marshal(input) + if err != nil { + return nil, err + } + + /* + * Migrate config of `kind: Simple` + */ + if input.GetKind() == "Simple" { + cfgIntermediate := v1alpha3.SimpleConfig{} + + if err := json.Unmarshal(injson, &cfgIntermediate); err != nil { + return nil, err + } + + intermediateJSON, err := json.Marshal(cfgIntermediate) + if err != nil { + return nil, err + } + cfg := SimpleConfig{} + if err := json.Unmarshal(intermediateJSON, &cfg); err != nil { + return nil, err + } + + cfg.Name = cfgIntermediate.Name + + /* + * Finalizing + */ + + cfg.APIVersion = ApiVersion + + l.Log().Debugf("Migrated config: %+v", cfg) + + return cfg, nil + + } + + l.Log().Debugf("No migration needed for %s#%s -> %s#%s", input.GetAPIVersion(), input.GetKind(), ApiVersion, input.GetKind()) + return input, nil } diff --git a/pkg/config/v1alpha4/schema.json b/pkg/config/v1alpha4/schema.json index 3bd541cf..37c99eaf 100644 --- a/pkg/config/v1alpha4/schema.json +++ b/pkg/config/v1alpha4/schema.json @@ -21,10 +21,16 @@ ], "default": "Simple" }, - "name": { - "description": "Name of the cluster (must be a valid hostname and will be prefixed with 'k3d-'). Example: 'mycluster'.", - "type": "string", - "format": "hostname" + "metadata": { + "type": "object", + "properties": { + "name": { + "description": "Name of the cluster (must be a valid hostname and will be prefixed with 'k3d-'). Example: 'mycluster'.", + "type": "string", + "format": "hostname" + } + }, + "additionalProperties": false }, "servers": { "type": "number", @@ -220,7 +226,7 @@ "type": "string" }, "hostPidMode": { - "type":"boolean", + "type": "boolean", "default": false }, "labels": { diff --git a/pkg/config/v1alpha4/types.go b/pkg/config/v1alpha4/types.go index db85ef20..e4bdee25 100644 --- a/pkg/config/v1alpha4/types.go +++ b/pkg/config/v1alpha4/types.go @@ -135,21 +135,21 @@ type SimpleConfigRegistries struct { // SimpleConfig describes the toplevel k3d configuration file. type SimpleConfig struct { - config.TypeMeta `mapstructure:",squash" yaml:",inline"` - Name string `mapstructure:"name" yaml:"name,omitempty" json:"name,omitempty"` - Servers int `mapstructure:"servers" yaml:"servers,omitempty" json:"servers,omitempty"` //nolint:lll // default 1 - Agents int `mapstructure:"agents" yaml:"agents,omitempty" json:"agents,omitempty"` //nolint:lll // default 0 - ExposeAPI SimpleExposureOpts `mapstructure:"kubeAPI" yaml:"kubeAPI,omitempty" json:"kubeAPI,omitempty"` - Image string `mapstructure:"image" yaml:"image,omitempty" json:"image,omitempty"` - Network string `mapstructure:"network" yaml:"network,omitempty" json:"network,omitempty"` - Subnet string `mapstructure:"subnet" yaml:"subnet,omitempty" json:"subnet,omitempty"` - ClusterToken string `mapstructure:"token" yaml:"clusterToken,omitempty" json:"clusterToken,omitempty"` // default: auto-generated - Volumes []VolumeWithNodeFilters `mapstructure:"volumes" yaml:"volumes,omitempty" json:"volumes,omitempty"` - Ports []PortWithNodeFilters `mapstructure:"ports" yaml:"ports,omitempty" json:"ports,omitempty"` - Options SimpleConfigOptions `mapstructure:"options" yaml:"options,omitempty" json:"options,omitempty"` - Env []EnvVarWithNodeFilters `mapstructure:"env" yaml:"env,omitempty" json:"env,omitempty"` - Registries SimpleConfigRegistries `mapstructure:"registries" yaml:"registries,omitempty" json:"registries,omitempty"` - HostAliases []k3d.HostAlias `mapstructure:"hostAliases" yaml:"hostAliases,omitempty" json:"hostAliases,omitempty"` + config.TypeMeta `mapstructure:",squash" yaml:",inline"` + config.ObjectMeta `mapstructure:"metadata" yaml:"metadata,omitempty" json:"metadata,omitempty"` + Servers int `mapstructure:"servers" yaml:"servers,omitempty" json:"servers,omitempty"` //nolint:lll // default 1 + Agents int `mapstructure:"agents" yaml:"agents,omitempty" json:"agents,omitempty"` //nolint:lll // default 0 + ExposeAPI SimpleExposureOpts `mapstructure:"kubeAPI" yaml:"kubeAPI,omitempty" json:"kubeAPI,omitempty"` + Image string `mapstructure:"image" yaml:"image,omitempty" json:"image,omitempty"` + Network string `mapstructure:"network" yaml:"network,omitempty" json:"network,omitempty"` + Subnet string `mapstructure:"subnet" yaml:"subnet,omitempty" json:"subnet,omitempty"` + ClusterToken string `mapstructure:"token" yaml:"clusterToken,omitempty" json:"clusterToken,omitempty"` // default: auto-generated + Volumes []VolumeWithNodeFilters `mapstructure:"volumes" yaml:"volumes,omitempty" json:"volumes,omitempty"` + Ports []PortWithNodeFilters `mapstructure:"ports" yaml:"ports,omitempty" json:"ports,omitempty"` + Options SimpleConfigOptions `mapstructure:"options" yaml:"options,omitempty" json:"options,omitempty"` + Env []EnvVarWithNodeFilters `mapstructure:"env" yaml:"env,omitempty" json:"env,omitempty"` + Registries SimpleConfigRegistries `mapstructure:"registries" yaml:"registries,omitempty" json:"registries,omitempty"` + HostAliases []k3d.HostAlias `mapstructure:"hostAliases" yaml:"hostAliases,omitempty" json:"hostAliases,omitempty"` } // SimpleExposureOpts provides a simplified syntax compared to the original k3d.ExposureOpts diff --git a/tests/assets/config_env.yaml b/tests/assets/config_env.yaml index f6245489..c03897ed 100644 --- a/tests/assets/config_env.yaml +++ b/tests/assets/config_env.yaml @@ -1,4 +1,5 @@ apiVersion: k3d.io/v1alpha4 kind: Simple -name: testenvexpand +metadata: + name: testenvexpand servers: ${K3D_TEST_SERVERS} diff --git a/tests/assets/config_test_simple.yaml b/tests/assets/config_test_simple.yaml index 120319d9..03ca50f2 100755 --- a/tests/assets/config_test_simple.yaml +++ b/tests/assets/config_test_simple.yaml @@ -1,6 +1,7 @@ apiVersion: k3d.io/v1alpha4 kind: Simple -name: test +metadata: + name: test servers: 3 agents: 2 #image: rancher/k3s:latest