mirror of https://github.com/k3d-io/k3d
[BREAKING] Config File Enhancements: v1alpha3, migrations, generic k3s-args (#605)
Excerpt: - new version v1alpha3 with k3s extraArgs using node filters - reflected in CLI via --k3s-arg - new migration option to migrate (internally and via cli) from v1alpha2 to v1alpha3 - enhancements to how config files are being read - e2e tests for config file migrationpull/670/head
parent
296f24c9b7
commit
261ac0faf4
@ -0,0 +1,111 @@ |
|||||||
|
/* |
||||||
|
Copyright © 2020 The k3d Author(s) |
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy |
||||||
|
of this software and associated documentation files (the "Software"), to deal |
||||||
|
in the Software without restriction, including without limitation the rights |
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
||||||
|
copies of the Software, and to permit persons to whom the Software is |
||||||
|
furnished to do so, subject to the following conditions: |
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in |
||||||
|
all copies or substantial portions of the Software. |
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN |
||||||
|
THE SOFTWARE. |
||||||
|
*/ |
||||||
|
package config |
||||||
|
|
||||||
|
import ( |
||||||
|
"os" |
||||||
|
"strings" |
||||||
|
|
||||||
|
"github.com/rancher/k3d/v4/pkg/config" |
||||||
|
log "github.com/sirupsen/logrus" |
||||||
|
"github.com/spf13/cobra" |
||||||
|
"github.com/spf13/viper" |
||||||
|
"gopkg.in/yaml.v2" |
||||||
|
) |
||||||
|
|
||||||
|
// NewCmdConfigMigrate returns a new cobra command
|
||||||
|
func NewCmdConfigMigrate() *cobra.Command { |
||||||
|
|
||||||
|
cmd := &cobra.Command{ |
||||||
|
Use: "migrate INPUT OUTPUT", |
||||||
|
Args: cobra.RangeArgs(1, 2), |
||||||
|
Run: func(cmd *cobra.Command, args []string) { |
||||||
|
|
||||||
|
configFile := args[0] |
||||||
|
|
||||||
|
if _, err := os.Stat(configFile); err != nil { |
||||||
|
log.Fatalf("Failed to stat config file %s: %+v", configFile, err) |
||||||
|
} |
||||||
|
|
||||||
|
cfgViper := viper.New() |
||||||
|
cfgViper.SetConfigType("yaml") |
||||||
|
|
||||||
|
cfgViper.SetConfigFile(configFile) |
||||||
|
|
||||||
|
// try to read config into memory (viper map structure)
|
||||||
|
if err := cfgViper.ReadInConfig(); err != nil { |
||||||
|
if _, ok := err.(viper.ConfigFileNotFoundError); ok { |
||||||
|
log.Fatalf("Config file %s not found: %+v", configFile, err) |
||||||
|
} |
||||||
|
// config file found but some other error happened
|
||||||
|
log.Fatalf("Failed to read config file %s: %+v", configFile, err) |
||||||
|
} |
||||||
|
|
||||||
|
schema, err := config.GetSchemaByVersion(cfgViper.GetString("apiVersion")) |
||||||
|
if err != nil { |
||||||
|
log.Fatalf("Cannot validate config file %s: %+v", configFile, err) |
||||||
|
} |
||||||
|
|
||||||
|
if err := config.ValidateSchemaFile(configFile, schema); err != nil { |
||||||
|
log.Fatalf("Schema Validation failed for config file %s: %+v", configFile, err) |
||||||
|
} |
||||||
|
|
||||||
|
log.Infof("Using config file %s (%s#%s)", cfgViper.ConfigFileUsed(), strings.ToLower(cfgViper.GetString("apiVersion")), strings.ToLower(cfgViper.GetString("kind"))) |
||||||
|
|
||||||
|
cfg, err := config.FromViper(cfgViper) |
||||||
|
if err != nil { |
||||||
|
log.Fatalln(err) |
||||||
|
} |
||||||
|
|
||||||
|
if cfg.GetAPIVersion() != config.DefaultConfigApiVersion { |
||||||
|
cfg, err = config.Migrate(cfg, config.DefaultConfigApiVersion) |
||||||
|
if err != nil { |
||||||
|
log.Fatalln(err) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
yamlout, err := yaml.Marshal(cfg) |
||||||
|
if err != nil { |
||||||
|
log.Fatalln(err) |
||||||
|
} |
||||||
|
|
||||||
|
output := "-" |
||||||
|
|
||||||
|
if len(args) > 1 { |
||||||
|
output = args[1] |
||||||
|
} |
||||||
|
|
||||||
|
if output == "-" { |
||||||
|
if _, err := os.Stdout.Write(yamlout); err != nil { |
||||||
|
log.Fatalln(err) |
||||||
|
} |
||||||
|
} else { |
||||||
|
if err := os.WriteFile(output, yamlout, os.ModeAppend); err != nil { |
||||||
|
log.Fatalln(err) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
}, |
||||||
|
} |
||||||
|
|
||||||
|
return cmd |
||||||
|
} |
@ -0,0 +1,40 @@ |
|||||||
|
/* |
||||||
|
Copyright © 2020 The k3d Author(s) |
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy |
||||||
|
of this software and associated documentation files (the "Software"), to deal |
||||||
|
in the Software without restriction, including without limitation the rights |
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
||||||
|
copies of the Software, and to permit persons to whom the Software is |
||||||
|
furnished to do so, subject to the following conditions: |
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in |
||||||
|
all copies or substantial portions of the Software. |
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN |
||||||
|
THE SOFTWARE. |
||||||
|
*/ |
||||||
|
|
||||||
|
package config |
||||||
|
|
||||||
|
import ( |
||||||
|
"fmt" |
||||||
|
|
||||||
|
types "github.com/rancher/k3d/v4/pkg/config/types" |
||||||
|
) |
||||||
|
|
||||||
|
func Migrate(config types.Config, targetVersion string) (types.Config, error) { |
||||||
|
|
||||||
|
migration, ok := getMigrations(targetVersion)[config.GetAPIVersion()] |
||||||
|
if !ok { |
||||||
|
return nil, fmt.Errorf("no migration possible from '%s' to '%s'", config.GetAPIVersion(), targetVersion) |
||||||
|
} |
||||||
|
|
||||||
|
return migration(config) |
||||||
|
|
||||||
|
} |
@ -1,4 +1,4 @@ |
|||||||
apiVersion: k3d.io/v1alpha2 |
apiVersion: k3d.io/v1alpha3 |
||||||
kind: Simple |
kind: Simple |
||||||
name: supertest |
name: supertest |
||||||
agents: 8 |
agents: 8 |
@ -1,3 +1,3 @@ |
|||||||
apiVersion: k3d.io/v1alpha2 |
apiVersion: k3d.io/v1alpha3 |
||||||
kind: Unknown |
kind: Unknown |
||||||
foo: bar |
foo: bar |
@ -0,0 +1,34 @@ |
|||||||
|
/* |
||||||
|
Copyright © 2020 The k3d Author(s) |
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy |
||||||
|
of this software and associated documentation files (the "Software"), to deal |
||||||
|
in the Software without restriction, including without limitation the rights |
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
||||||
|
copies of the Software, and to permit persons to whom the Software is |
||||||
|
furnished to do so, subject to the following conditions: |
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in |
||||||
|
all copies or substantial portions of the Software. |
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN |
||||||
|
THE SOFTWARE. |
||||||
|
*/ |
||||||
|
package types |
||||||
|
|
||||||
|
// TypeMeta is basically copied from https://github.com/kubernetes/apimachinery/blob/a3b564b22db316a41e94fdcffcf9995424fe924c/pkg/apis/meta/v1/types.go#L36-L56
|
||||||
|
type TypeMeta struct { |
||||||
|
Kind string `mapstructure:"kind,omitempty" yaml:"kind,omitempty" json:"kind,omitempty"` |
||||||
|
APIVersion string `mapstructure:"apiVersion,omitempty" yaml:"apiVersion,omitempty" json:"apiVersion,omitempty"` |
||||||
|
} |
||||||
|
|
||||||
|
// Config interface.
|
||||||
|
type Config interface { |
||||||
|
GetKind() string |
||||||
|
GetAPIVersion() string |
||||||
|
} |
@ -0,0 +1,84 @@ |
|||||||
|
/* |
||||||
|
Copyright © 2020 The k3d Author(s) |
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy |
||||||
|
of this software and associated documentation files (the "Software"), to deal |
||||||
|
in the Software without restriction, including without limitation the rights |
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
||||||
|
copies of the Software, and to permit persons to whom the Software is |
||||||
|
furnished to do so, subject to the following conditions: |
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in |
||||||
|
all copies or substantial portions of the Software. |
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN |
||||||
|
THE SOFTWARE. |
||||||
|
*/ |
||||||
|
|
||||||
|
package v1alpha3 |
||||||
|
|
||||||
|
import ( |
||||||
|
"encoding/json" |
||||||
|
|
||||||
|
configtypes "github.com/rancher/k3d/v4/pkg/config/types" |
||||||
|
"github.com/rancher/k3d/v4/pkg/config/v1alpha2" |
||||||
|
log "github.com/sirupsen/logrus" |
||||||
|
) |
||||||
|
|
||||||
|
var Migrations = map[string]func(configtypes.Config) (configtypes.Config, error){ |
||||||
|
v1alpha2.ApiVersion: MigrateV1Alpha2, |
||||||
|
} |
||||||
|
|
||||||
|
func MigrateV1Alpha2(input configtypes.Config) (configtypes.Config, error) { |
||||||
|
log.Debugln("Migrating v1alpha2 to v1alpha3") |
||||||
|
|
||||||
|
injson, err := json.Marshal(input) |
||||||
|
if err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
|
||||||
|
if input.GetKind() == "Simple" { |
||||||
|
cfg := SimpleConfig{} |
||||||
|
|
||||||
|
if err := json.Unmarshal(injson, &cfg); err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
|
||||||
|
cfg.Options.K3sOptions.ExtraArgs = []K3sArgWithNodeFilters{} |
||||||
|
|
||||||
|
for _, arg := range input.(v1alpha2.SimpleConfig).Options.K3sOptions.ExtraServerArgs { |
||||||
|
cfg.Options.K3sOptions.ExtraArgs = append(cfg.Options.K3sOptions.ExtraArgs, K3sArgWithNodeFilters{ |
||||||
|
Arg: arg, |
||||||
|
NodeFilters: []string{ |
||||||
|
"server[*]", |
||||||
|
}, |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
for _, arg := range input.(v1alpha2.SimpleConfig).Options.K3sOptions.ExtraAgentArgs { |
||||||
|
cfg.Options.K3sOptions.ExtraArgs = append(cfg.Options.K3sOptions.ExtraArgs, K3sArgWithNodeFilters{ |
||||||
|
Arg: arg, |
||||||
|
NodeFilters: []string{ |
||||||
|
"agent[*]", |
||||||
|
}, |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
cfg.APIVersion = ApiVersion |
||||||
|
|
||||||
|
log.Debugf("Migrated config: %+v", cfg) |
||||||
|
|
||||||
|
return cfg, nil |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
log.Debugf("No migration needed for %s#%s -> %s#%s", input.GetAPIVersion(), input.GetKind(), ApiVersion, input.GetKind()) |
||||||
|
|
||||||
|
return input, nil |
||||||
|
|
||||||
|
} |
@ -0,0 +1,254 @@ |
|||||||
|
{ |
||||||
|
"$schema": "http://json-schema.org/draft-07/schema#", |
||||||
|
"title": "SimpleConfig", |
||||||
|
"type": "object", |
||||||
|
"required": [ |
||||||
|
"apiVersion", |
||||||
|
"kind" |
||||||
|
], |
||||||
|
"properties": { |
||||||
|
"apiVersion": { |
||||||
|
"type": "string", |
||||||
|
"enum": [ |
||||||
|
"k3d.io/v1alpha3" |
||||||
|
], |
||||||
|
"default": "k3d.io/v1alpha3" |
||||||
|
}, |
||||||
|
"kind": { |
||||||
|
"type": "string", |
||||||
|
"enum": [ |
||||||
|
"Simple" |
||||||
|
], |
||||||
|
"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" |
||||||
|
}, |
||||||
|
"servers": { |
||||||
|
"type": "number", |
||||||
|
"minimum": 1 |
||||||
|
}, |
||||||
|
"agents": { |
||||||
|
"type": "number", |
||||||
|
"minimum": 0 |
||||||
|
}, |
||||||
|
"kubeAPI": { |
||||||
|
"type": "object", |
||||||
|
"properties": { |
||||||
|
"host": { |
||||||
|
"type": "string", |
||||||
|
"format": "hostname" |
||||||
|
}, |
||||||
|
"hostIP": { |
||||||
|
"type": "string", |
||||||
|
"format": "ipv4", |
||||||
|
"examples": [ |
||||||
|
"0.0.0.0", |
||||||
|
"192.168.178.55" |
||||||
|
] |
||||||
|
}, |
||||||
|
"hostPort": { |
||||||
|
"type":"string", |
||||||
|
"examples": [ |
||||||
|
"6443" |
||||||
|
] |
||||||
|
} |
||||||
|
}, |
||||||
|
"additionalProperties": false |
||||||
|
}, |
||||||
|
"image": { |
||||||
|
"type": "string", |
||||||
|
"examples": [ |
||||||
|
"rancher/k3s:latest" |
||||||
|
] |
||||||
|
}, |
||||||
|
"network": { |
||||||
|
"type": "string" |
||||||
|
}, |
||||||
|
"subnet": { |
||||||
|
"type": "string", |
||||||
|
"default": "auto", |
||||||
|
"examples": [ |
||||||
|
"172.28.0.0/16", |
||||||
|
"192.162.0.0/16" |
||||||
|
] |
||||||
|
}, |
||||||
|
"token": { |
||||||
|
"type": "string" |
||||||
|
}, |
||||||
|
"volumes": { |
||||||
|
"type": "array", |
||||||
|
"items": { |
||||||
|
"type": "object", |
||||||
|
"properties": { |
||||||
|
"volume": { |
||||||
|
"type": "string" |
||||||
|
}, |
||||||
|
"nodeFilters": { |
||||||
|
"$ref": "#/definitions/nodeFilters" |
||||||
|
} |
||||||
|
}, |
||||||
|
"additionalProperties": false |
||||||
|
} |
||||||
|
}, |
||||||
|
"ports": { |
||||||
|
"type": "array", |
||||||
|
"items": { |
||||||
|
"type": "object", |
||||||
|
"properties": { |
||||||
|
"port": { |
||||||
|
"type": "string" |
||||||
|
}, |
||||||
|
"nodeFilters": { |
||||||
|
"$ref": "#/definitions/nodeFilters" |
||||||
|
} |
||||||
|
}, |
||||||
|
"additionalProperties": false |
||||||
|
} |
||||||
|
}, |
||||||
|
"labels": { |
||||||
|
"type": "array", |
||||||
|
"items": { |
||||||
|
"type": "object", |
||||||
|
"properties": { |
||||||
|
"label": { |
||||||
|
"type": "string" |
||||||
|
}, |
||||||
|
"nodeFilters": { |
||||||
|
"$ref": "#/definitions/nodeFilters" |
||||||
|
} |
||||||
|
}, |
||||||
|
"additionalProperties": false |
||||||
|
} |
||||||
|
}, |
||||||
|
"options": { |
||||||
|
"type": "object", |
||||||
|
"properties": { |
||||||
|
"k3d": { |
||||||
|
"type": "object", |
||||||
|
"properties": { |
||||||
|
"wait": { |
||||||
|
"type": "boolean", |
||||||
|
"default": true |
||||||
|
}, |
||||||
|
"timeout": { |
||||||
|
"type": "string", |
||||||
|
"examples": [ |
||||||
|
"60s", |
||||||
|
"1m", |
||||||
|
"1m30s" |
||||||
|
] |
||||||
|
}, |
||||||
|
"disableLoadbalancer": { |
||||||
|
"type": "boolean", |
||||||
|
"default": false |
||||||
|
}, |
||||||
|
"disableImageVolume": { |
||||||
|
"type": "boolean", |
||||||
|
"default": false |
||||||
|
}, |
||||||
|
"disableRollback": { |
||||||
|
"type": "boolean", |
||||||
|
"default": false |
||||||
|
}, |
||||||
|
"disableHostIPInjection": { |
||||||
|
"type": "boolean", |
||||||
|
"default": false |
||||||
|
} |
||||||
|
}, |
||||||
|
"additionalProperties": false |
||||||
|
}, |
||||||
|
"k3s": { |
||||||
|
"type": "object", |
||||||
|
"properties": { |
||||||
|
"extraArgs": { |
||||||
|
"type": "array", |
||||||
|
"items": { |
||||||
|
"type": "object", |
||||||
|
"properties": { |
||||||
|
"arg": { |
||||||
|
"type": "string", |
||||||
|
"examples": [ |
||||||
|
"--tls-san=127.0.0.1", |
||||||
|
"--disable=traefik" |
||||||
|
] |
||||||
|
}, |
||||||
|
"nodeFilters": { |
||||||
|
"$ref": "#/definitions/nodeFilters" |
||||||
|
} |
||||||
|
}, |
||||||
|
"additionalProperties": false |
||||||
|
} |
||||||
|
} |
||||||
|
}, |
||||||
|
"additionalProperties": false |
||||||
|
}, |
||||||
|
"kubeconfig": { |
||||||
|
"type": "object", |
||||||
|
"properties": { |
||||||
|
"updateDefaultKubeconfig": { |
||||||
|
"type": "boolean", |
||||||
|
"default": true |
||||||
|
}, |
||||||
|
"switchCurrentContext": { |
||||||
|
"type": "boolean", |
||||||
|
"default": true |
||||||
|
} |
||||||
|
}, |
||||||
|
"additionalProperties": false |
||||||
|
}, |
||||||
|
"runtime": { |
||||||
|
"type": "object", |
||||||
|
"properties": { |
||||||
|
"gpuRequest": { |
||||||
|
"type": "string" |
||||||
|
}, |
||||||
|
"serversMemory": { |
||||||
|
"type": "string" |
||||||
|
}, |
||||||
|
"agentsMemory": { |
||||||
|
"type": "string" |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
}, |
||||||
|
"additionalProperties": false |
||||||
|
}, |
||||||
|
"env": { |
||||||
|
"type": "array", |
||||||
|
"items": { |
||||||
|
"type": "object", |
||||||
|
"properties": { |
||||||
|
"envVar": { |
||||||
|
"type": "string" |
||||||
|
}, |
||||||
|
"nodeFilters": { |
||||||
|
"$ref": "#/definitions/nodeFilters" |
||||||
|
} |
||||||
|
}, |
||||||
|
"additionalProperties": false |
||||||
|
} |
||||||
|
}, |
||||||
|
"registries": { |
||||||
|
"type": "object" |
||||||
|
} |
||||||
|
}, |
||||||
|
"additionalProperties": false, |
||||||
|
"definitions": { |
||||||
|
"nodeFilters": { |
||||||
|
"type": "array", |
||||||
|
"items": { |
||||||
|
"type": "string" |
||||||
|
}, |
||||||
|
"examples": [ |
||||||
|
"loadbalancer", |
||||||
|
"server[*]", |
||||||
|
"server[0]", |
||||||
|
"agent[1]", |
||||||
|
"all" |
||||||
|
] |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,203 @@ |
|||||||
|
/* |
||||||
|
Copyright © 2020 The k3d Author(s) |
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy |
||||||
|
of this software and associated documentation files (the "Software"), to deal |
||||||
|
in the Software without restriction, including without limitation the rights |
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
||||||
|
copies of the Software, and to permit persons to whom the Software is |
||||||
|
furnished to do so, subject to the following conditions: |
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in |
||||||
|
all copies or substantial portions of the Software. |
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN |
||||||
|
THE SOFTWARE. |
||||||
|
*/ |
||||||
|
|
||||||
|
package v1alpha3 |
||||||
|
|
||||||
|
import ( |
||||||
|
_ "embed" |
||||||
|
"fmt" |
||||||
|
"strings" |
||||||
|
"time" |
||||||
|
|
||||||
|
config "github.com/rancher/k3d/v4/pkg/config/types" |
||||||
|
k3d "github.com/rancher/k3d/v4/pkg/types" |
||||||
|
"github.com/rancher/k3d/v4/version" |
||||||
|
) |
||||||
|
|
||||||
|
const ApiVersion = "k3d.io/v1alpha3" |
||||||
|
|
||||||
|
// JSONSchema describes the schema used to validate config files
|
||||||
|
//go:embed schema.json
|
||||||
|
var JSONSchema string |
||||||
|
|
||||||
|
// DefaultConfigTpl for printing
|
||||||
|
const DefaultConfigTpl = `--- |
||||||
|
apiVersion: k3d.io/v1alpha3 |
||||||
|
kind: Simple |
||||||
|
name: %s |
||||||
|
servers: 1 |
||||||
|
agents: 0 |
||||||
|
image: %s |
||||||
|
` |
||||||
|
|
||||||
|
// DefaultConfig templated DefaultConfigTpl
|
||||||
|
var DefaultConfig = fmt.Sprintf( |
||||||
|
DefaultConfigTpl, |
||||||
|
k3d.DefaultClusterName, |
||||||
|
fmt.Sprintf("%s:%s", k3d.DefaultK3sImageRepo, version.GetK3sVersion(false)), |
||||||
|
) |
||||||
|
|
||||||
|
type VolumeWithNodeFilters struct { |
||||||
|
Volume string `mapstructure:"volume" yaml:"volume" json:"volume,omitempty"` |
||||||
|
NodeFilters []string `mapstructure:"nodeFilters" yaml:"nodeFilters" json:"nodeFilters,omitempty"` |
||||||
|
} |
||||||
|
|
||||||
|
type PortWithNodeFilters struct { |
||||||
|
Port string `mapstructure:"port" yaml:"port" json:"port,omitempty"` |
||||||
|
NodeFilters []string `mapstructure:"nodeFilters" yaml:"nodeFilters" json:"nodeFilters,omitempty"` |
||||||
|
} |
||||||
|
|
||||||
|
type LabelWithNodeFilters struct { |
||||||
|
Label string `mapstructure:"label" yaml:"label" json:"label,omitempty"` |
||||||
|
NodeFilters []string `mapstructure:"nodeFilters" yaml:"nodeFilters" json:"nodeFilters,omitempty"` |
||||||
|
} |
||||||
|
|
||||||
|
type EnvVarWithNodeFilters struct { |
||||||
|
EnvVar string `mapstructure:"envVar" yaml:"envVar" json:"envVar,omitempty"` |
||||||
|
NodeFilters []string `mapstructure:"nodeFilters" yaml:"nodeFilters" json:"nodeFilters,omitempty"` |
||||||
|
} |
||||||
|
|
||||||
|
type K3sArgWithNodeFilters struct { |
||||||
|
Arg string `mapstructure:"arg" yaml:"arg" json:"arg,omitempty"` |
||||||
|
NodeFilters []string `mapstructure:"nodeFilters" yaml:"nodeFilters" json:"nodeFilters,omitempty"` |
||||||
|
} |
||||||
|
|
||||||
|
// SimpleConfigOptionsKubeconfig describes the set of options referring to the kubeconfig during cluster creation.
|
||||||
|
type SimpleConfigOptionsKubeconfig struct { |
||||||
|
UpdateDefaultKubeconfig bool `mapstructure:"updateDefaultKubeconfig" yaml:"updateDefaultKubeconfig" json:"updateDefaultKubeconfig,omitempty"` // default: true
|
||||||
|
SwitchCurrentContext bool `mapstructure:"switchCurrentContext" yaml:"switchCurrentContext" json:"switchCurrentContext,omitempty"` //nolint:lll // default: true
|
||||||
|
} |
||||||
|
|
||||||
|
type SimpleConfigOptions struct { |
||||||
|
K3dOptions SimpleConfigOptionsK3d `mapstructure:"k3d" yaml:"k3d"` |
||||||
|
K3sOptions SimpleConfigOptionsK3s `mapstructure:"k3s" yaml:"k3s"` |
||||||
|
KubeconfigOptions SimpleConfigOptionsKubeconfig `mapstructure:"kubeconfig" yaml:"kubeconfig"` |
||||||
|
Runtime SimpleConfigOptionsRuntime `mapstructure:"runtime" yaml:"runtime"` |
||||||
|
} |
||||||
|
|
||||||
|
type SimpleConfigOptionsRuntime struct { |
||||||
|
GPURequest string `mapstructure:"gpuRequest" yaml:"gpuRequest"` |
||||||
|
ServersMemory string `mapstructure:"serversMemory" yaml:"serversMemory"` |
||||||
|
AgentsMemory string `mapstructure:"agentsMemory" yaml:"agentsMemory"` |
||||||
|
} |
||||||
|
|
||||||
|
type SimpleConfigOptionsK3d struct { |
||||||
|
Wait bool `mapstructure:"wait" yaml:"wait"` |
||||||
|
Timeout time.Duration `mapstructure:"timeout" yaml:"timeout"` |
||||||
|
DisableLoadbalancer bool `mapstructure:"disableLoadbalancer" yaml:"disableLoadbalancer"` |
||||||
|
DisableImageVolume bool `mapstructure:"disableImageVolume" yaml:"disableImageVolume"` |
||||||
|
NoRollback bool `mapstructure:"disableRollback" yaml:"disableRollback"` |
||||||
|
PrepDisableHostIPInjection bool `mapstructure:"disableHostIPInjection" yaml:"disableHostIPInjection"` |
||||||
|
NodeHookActions []k3d.NodeHookAction `mapstructure:"nodeHookActions" yaml:"nodeHookActions,omitempty"` |
||||||
|
} |
||||||
|
|
||||||
|
type SimpleConfigOptionsK3s struct { |
||||||
|
ExtraArgs []K3sArgWithNodeFilters `mapstructure:"extraArgs" yaml:"extraArgs"` |
||||||
|
} |
||||||
|
|
||||||
|
// SimpleConfig describes the toplevel k3d configuration file.
|
||||||
|
type SimpleConfig struct { |
||||||
|
config.TypeMeta `mapstructure:",squash" yaml:",inline"` |
||||||
|
Name string `mapstructure:"name" yaml:"name" json:"name,omitempty"` |
||||||
|
Servers int `mapstructure:"servers" yaml:"servers" json:"servers,omitempty"` //nolint:lll // default 1
|
||||||
|
Agents int `mapstructure:"agents" yaml:"agents" json:"agents,omitempty"` //nolint:lll // default 0
|
||||||
|
ExposeAPI SimpleExposureOpts `mapstructure:"kubeAPI" yaml:"kubeAPI" json:"kubeAPI,omitempty"` |
||||||
|
Image string `mapstructure:"image" yaml:"image" json:"image,omitempty"` |
||||||
|
Network string `mapstructure:"network" yaml:"network" json:"network,omitempty"` |
||||||
|
Subnet string `mapstructure:"subnet" yaml:"subnet" json:"subnet,omitempty"` |
||||||
|
ClusterToken string `mapstructure:"token" yaml:"clusterToken" json:"clusterToken,omitempty"` // default: auto-generated
|
||||||
|
Volumes []VolumeWithNodeFilters `mapstructure:"volumes" yaml:"volumes" json:"volumes,omitempty"` |
||||||
|
Ports []PortWithNodeFilters `mapstructure:"ports" yaml:"ports" json:"ports,omitempty"` |
||||||
|
Labels []LabelWithNodeFilters `mapstructure:"labels" yaml:"labels" json:"labels,omitempty"` |
||||||
|
Options SimpleConfigOptions `mapstructure:"options" yaml:"options" json:"options,omitempty"` |
||||||
|
Env []EnvVarWithNodeFilters `mapstructure:"env" yaml:"env" json:"env,omitempty"` |
||||||
|
Registries struct { |
||||||
|
Use []string `mapstructure:"use" yaml:"use,omitempty" json:"use,omitempty"` |
||||||
|
Create bool `mapstructure:"create" yaml:"create,omitempty" json:"create,omitempty"` |
||||||
|
Config string `mapstructure:"config" yaml:"config,omitempty" json:"config,omitempty"` // registries.yaml (k3s config for containerd registry override)
|
||||||
|
} `mapstructure:"registries" yaml:"registries,omitempty" json:"registries,omitempty"` |
||||||
|
} |
||||||
|
|
||||||
|
// SimpleExposureOpts provides a simplified syntax compared to the original k3d.ExposureOpts
|
||||||
|
type SimpleExposureOpts struct { |
||||||
|
Host string `mapstructure:"host" yaml:"host,omitempty" json:"host,omitempty"` |
||||||
|
HostIP string `mapstructure:"hostIP" yaml:"hostIP,omitempty" json:"hostIP,omitempty"` |
||||||
|
HostPort string `mapstructure:"hostPort" yaml:"hostPort,omitempty" json:"hostPort,omitempty"` |
||||||
|
} |
||||||
|
|
||||||
|
// GetKind implements Config.GetKind
|
||||||
|
func (c SimpleConfig) GetKind() string { |
||||||
|
return "Simple" |
||||||
|
} |
||||||
|
|
||||||
|
func (c SimpleConfig) GetAPIVersion() string { |
||||||
|
return ApiVersion |
||||||
|
} |
||||||
|
|
||||||
|
// ClusterConfig describes a single cluster config
|
||||||
|
type ClusterConfig struct { |
||||||
|
config.TypeMeta `mapstructure:",squash" yaml:",inline"` |
||||||
|
Cluster k3d.Cluster `mapstructure:",squash" yaml:",inline"` |
||||||
|
ClusterCreateOpts k3d.ClusterCreateOpts `mapstructure:"options" yaml:"options"` |
||||||
|
KubeconfigOpts SimpleConfigOptionsKubeconfig `mapstructure:"kubeconfig" yaml:"kubeconfig"` |
||||||
|
} |
||||||
|
|
||||||
|
// GetKind implements Config.GetKind
|
||||||
|
func (c ClusterConfig) GetKind() string { |
||||||
|
return "Simple" |
||||||
|
} |
||||||
|
|
||||||
|
func (c ClusterConfig) GetAPIVersion() string { |
||||||
|
return ApiVersion |
||||||
|
} |
||||||
|
|
||||||
|
// ClusterListConfig describes a list of clusters
|
||||||
|
type ClusterListConfig struct { |
||||||
|
config.TypeMeta `mapstructure:",squash" yaml:",inline"` |
||||||
|
Clusters []k3d.Cluster `mapstructure:"clusters" yaml:"clusters"` |
||||||
|
} |
||||||
|
|
||||||
|
func (c ClusterListConfig) GetKind() string { |
||||||
|
return "Simple" |
||||||
|
} |
||||||
|
|
||||||
|
func (c ClusterListConfig) GetAPIVersion() string { |
||||||
|
return ApiVersion |
||||||
|
} |
||||||
|
|
||||||
|
func GetConfigByKind(kind string) (config.Config, error) { |
||||||
|
|
||||||
|
// determine config kind
|
||||||
|
switch strings.ToLower(kind) { |
||||||
|
case "simple": |
||||||
|
return SimpleConfig{}, nil |
||||||
|
case "cluster": |
||||||
|
return ClusterConfig{}, nil |
||||||
|
case "clusterlist": |
||||||
|
return ClusterListConfig{}, nil |
||||||
|
case "": |
||||||
|
return nil, fmt.Errorf("missing `kind` in config file") |
||||||
|
default: |
||||||
|
return nil, fmt.Errorf("unknown `kind` '%s' in config file", kind) |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -0,0 +1,51 @@ |
|||||||
|
apiVersion: k3d.io/v1alpha2 |
||||||
|
kind: Simple |
||||||
|
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 |
||||||
|
labels: |
||||||
|
- label: foo=bar |
||||||
|
nodeFilters: |
||||||
|
- server[0] |
||||||
|
- loadbalancer |
||||||
|
registries: |
||||||
|
create: true |
||||||
|
use: [] |
||||||
|
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: |
||||||
|
extraServerArgs: |
||||||
|
- --tls-san=127.0.0.1 |
||||||
|
extraAgentArgs: [] |
||||||
|
kubeconfig: |
||||||
|
updateDefaultKubeconfig: true |
||||||
|
switchCurrentContext: true |
@ -0,0 +1,52 @@ |
|||||||
|
apiVersion: k3d.io/v1alpha3 |
||||||
|
kind: Simple |
||||||
|
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 |
||||||
|
labels: |
||||||
|
- label: foo=bar |
||||||
|
nodeFilters: |
||||||
|
- server[0] |
||||||
|
- loadbalancer |
||||||
|
registries: |
||||||
|
create: true |
||||||
|
use: [] |
||||||
|
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 |
@ -0,0 +1,27 @@ |
|||||||
|
#!/bin/bash |
||||||
|
|
||||||
|
CURR_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" |
||||||
|
[ -d "$CURR_DIR" ] || { echo "FATAL: no current dir (maybe running in zsh?)"; exit 1; } |
||||||
|
|
||||||
|
# shellcheck source=./common.sh |
||||||
|
source "$CURR_DIR/common.sh" |
||||||
|
|
||||||
|
|
||||||
|
export CURRENT_STAGE="Test | config-file-migration" |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
highlight "[START] ConfigMigrateTest" |
||||||
|
|
||||||
|
tempdir=$(mktemp -d) |
||||||
|
$EXE config migrate "$CURR_DIR/assets/config_test_simple_migration_v1alpha2.yaml" "$tempdir/expected.yaml" || failed "failed on $CURR_DIR/assets/config_test_simple.yaml" |
||||||
|
$EXE config migrate "$CURR_DIR/assets/config_test_simple_migration_v1alpha3.yaml" "$tempdir/actual.yaml" || failed "failed on $CURR_DIR/assets/config_test_simple_migrate.yaml" |
||||||
|
|
||||||
|
diff "$tempdir/actual.yaml" "$tempdir/expected.yaml" || failed "config migration failed" && passed "config migration succeeded" |
||||||
|
|
||||||
|
|
||||||
|
highlight "[DONE] ConfigMigrateTest" |
||||||
|
|
||||||
|
exit 0 |
||||||
|
|
||||||
|
|
Loading…
Reference in new issue