Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[FEATURE] Config file compatible with Kustomize #945

Merged
merged 1 commit into from
Jan 31, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion docs/usage/configfile.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
3 changes: 2 additions & 1 deletion docs/usage/registries.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
8 changes: 6 additions & 2 deletions pkg/config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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{
Expand Down
2 changes: 1 addition & 1 deletion pkg/config/jsonschema_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
97 changes: 57 additions & 40 deletions pkg/config/migrate_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
})
}
}
3 changes: 2 additions & 1 deletion pkg/config/test_assets/config_test_registries.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
apiVersion: k3d.io/v1alpha4
kind: Simple
name: test
metadata:
name: test
servers: 1
agents: 1
registries:
Expand Down
3 changes: 2 additions & 1 deletion pkg/config/test_assets/config_test_simple.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
apiVersion: k3d.io/v1alpha4
kind: Simple
name: test
metadata:
name: test
servers: 1
agents: 2
kubeAPI:
Expand Down
3 changes: 2 additions & 1 deletion pkg/config/test_assets/config_test_simple_2.yaml
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
apiVersion: k3d.io/v1alpha4
kind: Simple
name: supertest
metadata:
name: supertest
agents: 8
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
apiVersion: k3d.io/v1alpha4
kind: Simple
name: 1234
metadata:
name: 1234
servers: 1
agents: 2
kubeAPI:
Expand Down
56 changes: 56 additions & 0 deletions pkg/config/test_assets/config_test_simple_migration_v1alpha4.yaml
Original file line number Diff line number Diff line change
@@ -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
5 changes: 5 additions & 0 deletions pkg/config/types/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
44 changes: 44 additions & 0 deletions pkg/config/v1alpha4/migrations.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ THE SOFTWARE.
package v1alpha4

import (
"encoding/json"
"fmt"

configtypes "github.com/rancher/k3d/v5/pkg/config/types"
Expand Down Expand Up @@ -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
}
16 changes: 11 additions & 5 deletions pkg/config/v1alpha4/schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -220,7 +226,7 @@
"type": "string"
},
"hostPidMode": {
"type":"boolean",
"type": "boolean",
"default": false
},
"labels": {
Expand Down
30 changes: 15 additions & 15 deletions pkg/config/v1alpha4/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading