[FEATURE] Config file compatible with Kustomize (#945)

pull/946/head^2
Erik Godding Boye 3 years ago committed by GitHub
parent 08bf145e95
commit 9a2a3ec0ff
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 3
      docs/usage/configfile.md
  2. 3
      docs/usage/registries.md
  3. 8
      pkg/config/config_test.go
  4. 2
      pkg/config/jsonschema_test.go
  5. 97
      pkg/config/migrate_test.go
  6. 3
      pkg/config/test_assets/config_test_registries.yaml
  7. 3
      pkg/config/test_assets/config_test_simple.yaml
  8. 3
      pkg/config/test_assets/config_test_simple_2.yaml
  9. 3
      pkg/config/test_assets/config_test_simple_invalid_servers.yaml
  10. 56
      pkg/config/test_assets/config_test_simple_migration_v1alpha4.yaml
  11. 5
      pkg/config/types/types.go
  12. 44
      pkg/config/v1alpha4/migrations.go
  13. 16
      pkg/config/v1alpha4/schema.json
  14. 30
      pkg/config/v1alpha4/types.go
  15. 3
      tests/assets/config_env.yaml
  16. 3
      tests/assets/config_test_simple.yaml

@ -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 # 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 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 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` servers: 1 # same as `--servers 1`
agents: 2 # same as `--agents 2` 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) kubeAPI: # same as `--api-port myhost.my.domain:6445` (where the name would resolve to 127.0.0.1)

@ -25,7 +25,8 @@ If you're using a `SimpleConfig` file to configure your k3d cluster, you may as
```yaml ```yaml
apiVersion: k3d.io/v1alpha4 apiVersion: k3d.io/v1alpha4
kind: Simple kind: Simple
name: test metadata:
name: test
servers: 1 servers: 1
agents: 2 agents: 2
registries: registries:

@ -44,7 +44,9 @@ func TestReadSimpleConfig(t *testing.T) {
APIVersion: "k3d.io/v1alpha4", APIVersion: "k3d.io/v1alpha4",
Kind: "Simple", Kind: "Simple",
}, },
Name: "test", ObjectMeta: configtypes.ObjectMeta{
Name: "test",
},
Servers: 1, Servers: 1,
Agents: 2, Agents: 2,
ExposeAPI: exposedAPI, ExposeAPI: exposedAPI,
@ -268,7 +270,9 @@ func TestReadSimpleConfigRegistries(t *testing.T) {
APIVersion: "k3d.io/v1alpha4", APIVersion: "k3d.io/v1alpha4",
Kind: "Simple", Kind: "Simple",
}, },
Name: "test", ObjectMeta: configtypes.ObjectMeta{
Name: "test",
},
Servers: 1, Servers: 1,
Agents: 1, Agents: 1,
Registries: conf.SimpleConfigRegistries{ Registries: conf.SimpleConfigRegistries{

@ -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) 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 { if err.Error() != expectedErrorText {

@ -26,59 +26,76 @@ import (
"testing" "testing"
"github.com/go-test/deep" "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" l "github.com/rancher/k3d/v5/pkg/logger"
"github.com/spf13/viper" "github.com/spf13/viper"
) )
func TestMigrateV1Alpha2ToV1Alpha3(t *testing.T) { func TestMigrate(t *testing.T) {
tests := map[string]struct {
actualPath := "test_assets/config_test_simple_migration_v1alpha2.yaml" targetVersion string
expectedPath := "test_assets/config_test_simple_migration_v1alpha3.yaml" actualPath string
expectedPath string
actualViper := viper.New() }{
expectedViper := viper.New() "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") for name, tc := range tests {
expectedViper.SetConfigType("yaml") t.Run(name, func(t *testing.T) {
actualViper.SetConfigFile(actualPath) actualViper := viper.New()
expectedViper.SetConfigFile(expectedPath) expectedViper := viper.New()
if err := actualViper.ReadInConfig(); err != nil { actualViper.SetConfigType("yaml")
t.Fatal(err) expectedViper.SetConfigType("yaml")
}
if err := expectedViper.ReadInConfig(); err != nil { actualViper.SetConfigFile(tc.actualPath)
t.Fatal(err) expectedViper.SetConfigFile(tc.expectedPath)
}
actualCfg, err := FromViper(actualViper) if err := actualViper.ReadInConfig(); err != nil {
if err != nil { t.Fatal(err)
t.Fatal(err) }
}
if actualCfg.GetAPIVersion() != DefaultConfigApiVersion { if err := expectedViper.ReadInConfig(); err != nil {
actualCfg, err = Migrate(actualCfg, DefaultConfigApiVersion) t.Fatal(err)
if err != nil { }
l.Log().Fatalln(err)
}
}
expectedCfg, err := FromViper(expectedViper) actualCfg, err := FromViper(actualViper)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
if diff := deep.Equal(actualCfg, expectedCfg); diff != nil { if actualCfg.GetAPIVersion() != tc.targetVersion {
t.Fatalf("Actual\n%#v\ndoes not match expected\n%+v\nDiff:\n%#v", actualCfg, expectedCfg, diff) 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) { if diff := deep.Equal(actualCfg, expectedCfg); diff != nil {
t.Log("not implemented") // TODO: test migration v1alpha2 to v1alpha4 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 }
} }

@ -1,6 +1,7 @@
apiVersion: k3d.io/v1alpha4 apiVersion: k3d.io/v1alpha4
kind: Simple kind: Simple
name: test metadata:
name: test
servers: 1 servers: 1
agents: 1 agents: 1
registries: registries:

@ -1,6 +1,7 @@
apiVersion: k3d.io/v1alpha4 apiVersion: k3d.io/v1alpha4
kind: Simple kind: Simple
name: test metadata:
name: test
servers: 1 servers: 1
agents: 2 agents: 2
kubeAPI: kubeAPI:

@ -1,4 +1,5 @@
apiVersion: k3d.io/v1alpha4 apiVersion: k3d.io/v1alpha4
kind: Simple kind: Simple
name: supertest metadata:
name: supertest
agents: 8 agents: 8

@ -1,6 +1,7 @@
apiVersion: k3d.io/v1alpha4 apiVersion: k3d.io/v1alpha4
kind: Simple kind: Simple
name: 1234 metadata:
name: 1234
servers: 1 servers: 1
agents: 2 agents: 2
kubeAPI: kubeAPI:

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

@ -27,6 +27,11 @@ type TypeMeta struct {
APIVersion string `mapstructure:"apiVersion,omitempty" yaml:"apiVersion,omitempty" json:"apiVersion,omitempty"` 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. // Config interface.
type Config interface { type Config interface {
GetKind() string GetKind() string

@ -23,6 +23,7 @@ THE SOFTWARE.
package v1alpha4 package v1alpha4
import ( import (
"encoding/json"
"fmt" "fmt"
configtypes "github.com/rancher/k3d/v5/pkg/config/types" 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) { func MigrateV1Alpha3(input configtypes.Config) (configtypes.Config, error) {
l.Log().Debugln("Migrating v1alpha3 to v1alpha4") 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 return input, nil
} }

@ -21,10 +21,16 @@
], ],
"default": "Simple" "default": "Simple"
}, },
"name": { "metadata": {
"description": "Name of the cluster (must be a valid hostname and will be prefixed with 'k3d-'). Example: 'mycluster'.", "type": "object",
"type": "string", "properties": {
"format": "hostname" "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": { "servers": {
"type": "number", "type": "number",
@ -220,7 +226,7 @@
"type": "string" "type": "string"
}, },
"hostPidMode": { "hostPidMode": {
"type":"boolean", "type": "boolean",
"default": false "default": false
}, },
"labels": { "labels": {

@ -135,21 +135,21 @@ type SimpleConfigRegistries struct {
// SimpleConfig describes the toplevel k3d configuration file. // SimpleConfig describes the toplevel k3d configuration file.
type SimpleConfig struct { type SimpleConfig struct {
config.TypeMeta `mapstructure:",squash" yaml:",inline"` config.TypeMeta `mapstructure:",squash" yaml:",inline"`
Name string `mapstructure:"name" yaml:"name,omitempty" json:"name,omitempty"` config.ObjectMeta `mapstructure:"metadata" yaml:"metadata,omitempty" json:"metadata,omitempty"`
Servers int `mapstructure:"servers" yaml:"servers,omitempty" json:"servers,omitempty"` //nolint:lll // default 1 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 Agents int `mapstructure:"agents" yaml:"agents,omitempty" json:"agents,omitempty"` //nolint:lll // default 0
ExposeAPI SimpleExposureOpts `mapstructure:"kubeAPI" yaml:"kubeAPI,omitempty" json:"kubeAPI,omitempty"` ExposeAPI SimpleExposureOpts `mapstructure:"kubeAPI" yaml:"kubeAPI,omitempty" json:"kubeAPI,omitempty"`
Image string `mapstructure:"image" yaml:"image,omitempty" json:"image,omitempty"` Image string `mapstructure:"image" yaml:"image,omitempty" json:"image,omitempty"`
Network string `mapstructure:"network" yaml:"network,omitempty" json:"network,omitempty"` Network string `mapstructure:"network" yaml:"network,omitempty" json:"network,omitempty"`
Subnet string `mapstructure:"subnet" yaml:"subnet,omitempty" json:"subnet,omitempty"` Subnet string `mapstructure:"subnet" yaml:"subnet,omitempty" json:"subnet,omitempty"`
ClusterToken string `mapstructure:"token" yaml:"clusterToken,omitempty" json:"clusterToken,omitempty"` // default: auto-generated ClusterToken string `mapstructure:"token" yaml:"clusterToken,omitempty" json:"clusterToken,omitempty"` // default: auto-generated
Volumes []VolumeWithNodeFilters `mapstructure:"volumes" yaml:"volumes,omitempty" json:"volumes,omitempty"` Volumes []VolumeWithNodeFilters `mapstructure:"volumes" yaml:"volumes,omitempty" json:"volumes,omitempty"`
Ports []PortWithNodeFilters `mapstructure:"ports" yaml:"ports,omitempty" json:"ports,omitempty"` Ports []PortWithNodeFilters `mapstructure:"ports" yaml:"ports,omitempty" json:"ports,omitempty"`
Options SimpleConfigOptions `mapstructure:"options" yaml:"options,omitempty" json:"options,omitempty"` Options SimpleConfigOptions `mapstructure:"options" yaml:"options,omitempty" json:"options,omitempty"`
Env []EnvVarWithNodeFilters `mapstructure:"env" yaml:"env,omitempty" json:"env,omitempty"` Env []EnvVarWithNodeFilters `mapstructure:"env" yaml:"env,omitempty" json:"env,omitempty"`
Registries SimpleConfigRegistries `mapstructure:"registries" yaml:"registries,omitempty" json:"registries,omitempty"` Registries SimpleConfigRegistries `mapstructure:"registries" yaml:"registries,omitempty" json:"registries,omitempty"`
HostAliases []k3d.HostAlias `mapstructure:"hostAliases" yaml:"hostAliases,omitempty" json:"hostAliases,omitempty"` HostAliases []k3d.HostAlias `mapstructure:"hostAliases" yaml:"hostAliases,omitempty" json:"hostAliases,omitempty"`
} }
// SimpleExposureOpts provides a simplified syntax compared to the original k3d.ExposureOpts // SimpleExposureOpts provides a simplified syntax compared to the original k3d.ExposureOpts

@ -1,4 +1,5 @@
apiVersion: k3d.io/v1alpha4 apiVersion: k3d.io/v1alpha4
kind: Simple kind: Simple
name: testenvexpand metadata:
name: testenvexpand
servers: ${K3D_TEST_SERVERS} servers: ${K3D_TEST_SERVERS}

@ -1,6 +1,7 @@
apiVersion: k3d.io/v1alpha4 apiVersion: k3d.io/v1alpha4
kind: Simple kind: Simple
name: test metadata:
name: test
servers: 3 servers: 3
agents: 2 agents: 2
#image: rancher/k3s:latest #image: rancher/k3s:latest

Loading…
Cancel
Save