From 261ac0faf4c51f68ae24bb723bd44fa91b056555 Mon Sep 17 00:00:00 2001 From: Thorsten Klein Date: Fri, 14 May 2021 14:01:25 +0200 Subject: [PATCH] [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 migration --- cmd/cluster/clusterCreate.go | 86 ++++-- cmd/config/config.go | 1 + cmd/config/configInit.go | 2 +- cmd/config/configMigrate.go | 111 ++++++++ docgen/README.md | 2 + docs/usage/commands/k3d_cluster_create.md | 4 +- docs/usage/commands/k3d_config.md | 1 + .../{k3d_docgen.md => k3d_config_migrate.md} | 10 +- docs/usage/commands/k3d_node_create.md | 17 +- docs/usage/configfile.md | 15 +- docs/usage/guides/registries.md | 2 +- pkg/client/cluster.go | 2 +- pkg/config/config.go | 73 +++-- pkg/config/config_test.go | 27 +- pkg/config/jsonschema_test.go | 6 +- pkg/config/merge.go | 2 +- pkg/config/merge_test.go | 9 +- pkg/config/migrate.go | 40 +++ pkg/config/process.go | 2 +- pkg/config/process_test.go | 5 +- .../test_assets/config_test_cluster.yaml | 2 +- .../test_assets/config_test_cluster_list.yaml | 2 +- .../test_assets/config_test_simple.yaml | 13 +- .../test_assets/config_test_simple_2.yaml | 2 +- .../config_test_simple_invalid_servers.yaml | 9 +- .../test_assets/config_test_unknown.yaml | 2 +- pkg/config/transform.go | 22 +- pkg/config/transform_test.go | 5 +- pkg/config/types/types.go | 34 +++ pkg/config/v1alpha2/types.go | 99 ++++--- pkg/config/v1alpha3/migrations.go | 84 ++++++ pkg/config/v1alpha3/schema.json | 254 ++++++++++++++++++ pkg/config/v1alpha3/types.go | 203 ++++++++++++++ pkg/config/validate.go | 2 +- pkg/config/validate_test.go | 2 +- pkg/types/types.go | 2 - tests/assets/config_test_simple.yaml | 9 +- ...config_test_simple_migration_v1alpha2.yaml | 51 ++++ ...config_test_simple_migration_v1alpha3.yaml | 52 ++++ tests/test_config_file_migration.sh | 27 ++ 40 files changed, 1129 insertions(+), 164 deletions(-) create mode 100644 cmd/config/configMigrate.go rename docs/usage/commands/{k3d_docgen.md => k3d_config_migrate.md} (63%) create mode 100644 pkg/config/migrate.go create mode 100644 pkg/config/types/types.go create mode 100644 pkg/config/v1alpha3/migrations.go create mode 100644 pkg/config/v1alpha3/schema.json create mode 100644 pkg/config/v1alpha3/types.go create mode 100755 tests/assets/config_test_simple_migration_v1alpha2.yaml create mode 100755 tests/assets/config_test_simple_migration_v1alpha3.yaml create mode 100755 tests/test_config_file_migration.sh diff --git a/cmd/cluster/clusterCreate.go b/cmd/cluster/clusterCreate.go index f5fc4af5..574db83d 100644 --- a/cmd/cluster/clusterCreate.go +++ b/cmd/cluster/clusterCreate.go @@ -38,7 +38,7 @@ import ( cliutil "github.com/rancher/k3d/v4/cmd/util" k3dCluster "github.com/rancher/k3d/v4/pkg/client" "github.com/rancher/k3d/v4/pkg/config" - conf "github.com/rancher/k3d/v4/pkg/config/v1alpha2" + conf "github.com/rancher/k3d/v4/pkg/config/v1alpha3" "github.com/rancher/k3d/v4/pkg/runtimes" k3d "github.com/rancher/k3d/v4/pkg/types" "github.com/rancher/k3d/v4/version" @@ -77,11 +77,6 @@ func initConfig() { if _, err := os.Stat(configFile); err != nil { log.Fatalf("Failed to stat config file %s: %+v", configFile, err) } - log.Tracef("Schema: %+v", conf.JSONSchema) - - if err := config.ValidateSchemaFile(configFile, []byte(conf.JSONSchema)); err != nil { - log.Fatalf("Schema Validation failed for config file %s: %+v", configFile, err) - } // try to read config into memory (viper map structure) if err := cfgViper.ReadInConfig(); err != nil { @@ -92,7 +87,16 @@ func initConfig() { log.Fatalf("Failed to read config file %s: %+v", configFile, err) } - log.Infof("Using config file %s", cfgViper.ConfigFileUsed()) + 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"))) } if log.GetLevel() >= log.DebugLevel { c, _ := yaml.Marshal(cfgViper.AllSettings()) @@ -121,19 +125,35 @@ func NewCmdClusterCreate() *cobra.Command { /************************* * Compute Configuration * *************************/ - cfg, err := config.FromViperSimple(cfgViper) + if cfgViper.GetString("apiversion") == "" { + cfgViper.Set("apiversion", config.DefaultConfigApiVersion) + } + if cfgViper.GetString("kind") == "" { + cfgViper.Set("kind", "Simple") + } + cfg, err := config.FromViper(cfgViper) if err != nil { log.Fatalln(err) } - log.Debugf("========== Simple Config ==========\n%+v\n==========================\n", cfg) + if cfg.GetAPIVersion() != config.DefaultConfigApiVersion { + log.Warnf("Default config apiVersion is '%s', but you're using '%s': consider migrating.", config.DefaultConfigApiVersion, cfg.GetAPIVersion()) + cfg, err = config.Migrate(cfg, config.DefaultConfigApiVersion) + if err != nil { + log.Fatalln(err) + } + } - cfg, err = applyCLIOverrides(cfg) + simpleCfg := cfg.(conf.SimpleConfig) + + log.Debugf("========== Simple Config ==========\n%+v\n==========================\n", simpleCfg) + + simpleCfg, err = applyCLIOverrides(simpleCfg) if err != nil { log.Fatalf("Failed to apply CLI overrides: %+v", err) } - log.Debugf("========== Merged Simple Config ==========\n%+v\n==========================\n", cfg) + log.Debugf("========== Merged Simple Config ==========\n%+v\n==========================\n", simpleCfg) /************************************** * Transform, Process & Validate Configuration * @@ -141,10 +161,10 @@ func NewCmdClusterCreate() *cobra.Command { // Set the name if len(args) != 0 { - cfg.Name = args[0] + simpleCfg.Name = args[0] } - clusterConfig, err := config.TransformSimpleToClusterConfig(cmd.Context(), runtimes.SelectedRuntime, cfg) + clusterConfig, err := config.TransformSimpleToClusterConfig(cmd.Context(), runtimes.SelectedRuntime, simpleCfg) if err != nil { log.Fatalln(err) } @@ -178,7 +198,7 @@ func NewCmdClusterCreate() *cobra.Command { if err := k3dCluster.ClusterRun(cmd.Context(), runtimes.SelectedRuntime, clusterConfig); err != nil { // rollback if creation failed log.Errorln(err) - if cfg.Options.K3dOptions.NoRollback { // TODO: move rollback mechanics to pkg/ + if simpleCfg.Options.K3dOptions.NoRollback { // TODO: move rollback mechanics to pkg/ log.Fatalln("Cluster creation FAILED, rollback deactivated.") } // rollback if creation failed @@ -202,7 +222,7 @@ func NewCmdClusterCreate() *cobra.Command { if clusterConfig.KubeconfigOpts.UpdateDefaultKubeconfig { log.Debugf("Updating default kubeconfig with a new context for cluster %s", clusterConfig.Cluster.Name) - if _, err := k3dCluster.KubeconfigGetWrite(cmd.Context(), runtimes.SelectedRuntime, &clusterConfig.Cluster, "", &k3dCluster.WriteKubeConfigOptions{UpdateExisting: true, OverwriteExisting: false, UpdateCurrentContext: cfg.Options.KubeconfigOptions.SwitchCurrentContext}); err != nil { + if _, err := k3dCluster.KubeconfigGetWrite(cmd.Context(), runtimes.SelectedRuntime, &clusterConfig.Cluster, "", &k3dCluster.WriteKubeConfigOptions{UpdateExisting: true, OverwriteExisting: false, UpdateCurrentContext: simpleCfg.Options.KubeconfigOptions.SwitchCurrentContext}); err != nil { log.Warningln(err) } } @@ -266,6 +286,10 @@ func NewCmdClusterCreate() *cobra.Command { cmd.Flags().StringArrayP("label", "l", nil, "Add label to node container (Format: `KEY[=VALUE][@NODEFILTER[;NODEFILTER...]]`\n - Example: `k3d cluster create --agents 2 -l \"my.label@agent[0,1]\" -l \"other.label=somevalue@server[0]\"`") _ = ppViper.BindPFlag("cli.labels", cmd.Flags().Lookup("label")) + /* k3s */ + cmd.Flags().StringArray("k3s-arg", nil, "Additional args passed to k3s command (Format: `ARG@NODEFILTER[;@NODEFILTER]`)\n - Example: `k3d cluster create --k3s-arg \"--disable=traefik@server[0]\"") + _ = cfgViper.BindPFlag("cli.k3sargs", cmd.Flags().Lookup("k3s-arg")) + /****************** * "Normal" Flags * ****************** @@ -340,13 +364,6 @@ func NewCmdClusterCreate() *cobra.Command { cmd.Flags().String("registry-config", "", "Specify path to an extra registries.yaml file") _ = cfgViper.BindPFlag("registries.config", cmd.Flags().Lookup("registry-config")) - /* k3s */ - cmd.Flags().StringArray("k3s-server-arg", nil, "Additional args passed to the `k3s server` command on server nodes (new flag per arg)") - _ = cfgViper.BindPFlag("options.k3s.extraserverargs", cmd.Flags().Lookup("k3s-server-arg")) - - cmd.Flags().StringArray("k3s-agent-arg", nil, "Additional args passed to the `k3s agent` command on agent nodes (new flag per arg)") - _ = cfgViper.BindPFlag("options.k3s.extraagentargs", cmd.Flags().Lookup("k3s-agent-arg")) - /* Subcommands */ // done @@ -520,5 +537,30 @@ func applyCLIOverrides(cfg conf.SimpleConfig) (conf.SimpleConfig, error) { log.Tracef("EnvFilterMap: %+v", envFilterMap) + // --k3s-arg + argFilterMap := make(map[string][]string, 1) + for _, argFlag := range ppViper.GetStringSlice("cli.k3sargs") { + + // split node filter from the specified arg + arg, filters, err := cliutil.SplitFiltersFromFlag(argFlag) + if err != nil { + log.Fatalln(err) + } + + // create new entry or append filter to existing entry + if _, exists := argFilterMap[arg]; exists { + argFilterMap[arg] = append(argFilterMap[arg], filters...) + } else { + argFilterMap[arg] = filters + } + } + + for arg, nodeFilters := range argFilterMap { + cfg.Options.K3sOptions.ExtraArgs = append(cfg.Options.K3sOptions.ExtraArgs, conf.K3sArgWithNodeFilters{ + Arg: arg, + NodeFilters: nodeFilters, + }) + } + return cfg, nil } diff --git a/cmd/config/config.go b/cmd/config/config.go index 2a49986a..7156c576 100644 --- a/cmd/config/config.go +++ b/cmd/config/config.go @@ -41,6 +41,7 @@ func NewCmdConfig() *cobra.Command { } cmd.AddCommand(NewCmdConfigInit()) + cmd.AddCommand(NewCmdConfigMigrate()) return cmd } diff --git a/cmd/config/configInit.go b/cmd/config/configInit.go index 34b27b91..b85b754b 100644 --- a/cmd/config/configInit.go +++ b/cmd/config/configInit.go @@ -25,7 +25,7 @@ import ( "fmt" "os" - config "github.com/rancher/k3d/v4/pkg/config/v1alpha2" + config "github.com/rancher/k3d/v4/pkg/config/v1alpha3" log "github.com/sirupsen/logrus" "github.com/spf13/cobra" ) diff --git a/cmd/config/configMigrate.go b/cmd/config/configMigrate.go new file mode 100644 index 00000000..e18b19bc --- /dev/null +++ b/cmd/config/configMigrate.go @@ -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 +} diff --git a/docgen/README.md b/docgen/README.md index b65264ef..26f1be62 100644 --- a/docgen/README.md +++ b/docgen/README.md @@ -6,6 +6,8 @@ The code will output files in [`../docs/usage/commands/`](../docs/usage/commands ## Run +- may required a `replace github.com/rancher/k3d/v4 => PATH/TO/LOCAL/REPO` in the `go.mod` + ```bash # ensure that you're in the docgen dir, as the relative path to the docs/ dir is hardcoded cd docgen diff --git a/docs/usage/commands/k3d_cluster_create.md b/docs/usage/commands/k3d_cluster_create.md index 7ef87cfb..f784462d 100644 --- a/docs/usage/commands/k3d_cluster_create.md +++ b/docs/usage/commands/k3d_cluster_create.md @@ -29,8 +29,8 @@ k3d cluster create NAME [flags] --gpus string GPU devices to add to the cluster node containers ('all' to pass all GPUs) [From docker] -h, --help help for create -i, --image string Specify k3s image that you want to use for the nodes - --k3s-agent-arg k3s agent Additional args passed to the k3s agent command on agent nodes (new flag per arg) - --k3s-server-arg k3s server Additional args passed to the k3s server command on server nodes (new flag per arg) + --k3s-arg ARG@NODEFILTER[;@NODEFILTER] Additional args passed to k3s command (Format: ARG@NODEFILTER[;@NODEFILTER]) + - Example: `k3d cluster create --k3s-arg "--disable=traefik@server[0]" --kubeconfig-switch-context Directly switch the default kubeconfig's current-context to the new cluster's context (requires --kubeconfig-update-default) (default true) --kubeconfig-update-default Directly update the default kubeconfig with the new cluster's context (default true) -l, --label KEY[=VALUE][@NODEFILTER[;NODEFILTER...]] Add label to node container (Format: KEY[=VALUE][@NODEFILTER[;NODEFILTER...]] diff --git a/docs/usage/commands/k3d_config.md b/docs/usage/commands/k3d_config.md index c42923e5..8f8f076e 100644 --- a/docs/usage/commands/k3d_config.md +++ b/docs/usage/commands/k3d_config.md @@ -28,4 +28,5 @@ k3d config [flags] * [k3d](k3d.md) - https://k3d.io/ -> Run k3s in Docker! * [k3d config init](k3d_config_init.md) - +* [k3d config migrate](k3d_config_migrate.md) - diff --git a/docs/usage/commands/k3d_docgen.md b/docs/usage/commands/k3d_config_migrate.md similarity index 63% rename from docs/usage/commands/k3d_docgen.md rename to docs/usage/commands/k3d_config_migrate.md index 4767bdb6..902687ad 100644 --- a/docs/usage/commands/k3d_docgen.md +++ b/docs/usage/commands/k3d_config_migrate.md @@ -1,15 +1,15 @@ -## k3d docgen +## k3d config migrate + -Generate command docs ``` -k3d docgen [flags] +k3d config migrate INPUT OUTPUT [flags] ``` ### Options ``` - -h, --help help for docgen + -h, --help help for migrate ``` ### Options inherited from parent commands @@ -22,5 +22,5 @@ k3d docgen [flags] ### SEE ALSO -* [k3d](k3d.md) - https://k3d.io/ -> Run k3s in Docker! +* [k3d config](k3d_config.md) - Work with config file(s) diff --git a/docs/usage/commands/k3d_node_create.md b/docs/usage/commands/k3d_node_create.md index 041bb5cb..808cbe13 100644 --- a/docs/usage/commands/k3d_node_create.md +++ b/docs/usage/commands/k3d_node_create.md @@ -13,14 +13,15 @@ k3d node create NAME [flags] ### Options ``` - -c, --cluster string Select the cluster that the node shall connect to. (default "k3s-default") - -h, --help help for create - -i, --image string Specify k3s image used for the node(s) (default "docker.io/rancher/k3s:v1.20.0-k3s2") - --memory string Memory limit imposed on the node [From docker] - --replicas int Number of replicas of this node specification. (default 1) - --role string Specify node role [server, agent] (default "agent") - --timeout duration Maximum waiting time for '--wait' before canceling/returning. - --wait Wait for the node(s) to be ready before returning. + -c, --cluster string Select the cluster that the node shall connect to. (default "k3s-default") + -h, --help help for create + -i, --image string Specify k3s image used for the node(s) (default "docker.io/rancher/k3s:v1.20.0-k3s2") + --k3s-node-label strings Specify k3s node labels in format "foo=bar" + --memory string Memory limit imposed on the node [From docker] + --replicas int Number of replicas of this node specification. (default 1) + --role string Specify node role [server, agent] (default "agent") + --timeout duration Maximum waiting time for '--wait' before canceling/returning. + --wait Wait for the node(s) to be ready before returning. ``` ### Options inherited from parent commands diff --git a/docs/usage/configfile.md b/docs/usage/configfile.md index 051b4edb..97fa45c0 100644 --- a/docs/usage/configfile.md +++ b/docs/usage/configfile.md @@ -25,13 +25,13 @@ Using a config file is as easy as putting it in a well-known place in your file As of the time of writing this documentation, the config file only **requires** you to define two fields: -- `apiVersion` to match the version of the config file that you want to use (at this time it would be `apiVersion: k3d.io/v1alpha2`) +- `apiVersion` to match the version of the config file that you want to use (at this time it would be `apiVersion: k3d.io/v1alpha3`) - `kind` to define the kind of config file that you want to use (currently we only have the `Simple` config) So this would be the minimal config file, which configures absolutely nothing: ```yaml -apiVersion: k3d.io/v1alpha2 +apiVersion: k3d.io/v1alpha3 kind: Simple ``` @@ -43,7 +43,7 @@ Currently, the config file is still in an Alpha-State, meaning, that it is subje !!! info "Validation via JSON-Schema" k3d uses a [JSON-Schema](https://json-schema.org/) to describe the expected format and fields of the configuration file. This schema is also used to [validate](https://github.com/xeipuuv/gojsonschema#validation) a user-given config file. - This JSON-Schema can be found in the specific config version sub-directory in the repository (e.g. [here for `v1alpha2`](https://github.com/rancher/k3d/blob/main/pkg/config/v1alpha2/schema.json)) and could be used to lookup supported fields or by linters to validate the config file, e.g. in your code editor. + This JSON-Schema can be found in the specific config version sub-directory in the repository (e.g. [here for `v1alpha3`](https://github.com/rancher/k3d/blob/main/pkg/config/v1alpha3/schema.json)) and could be used to lookup supported fields or by linters to validate the config file, e.g. in your code editor. ### All Options: Example @@ -51,7 +51,7 @@ Since the config options and the config file are changing quite a bit, it's hard ```yaml # k3d configuration file, saved as e.g. /home/me/myk3dcluster.yaml -apiVersion: k3d.io/v1alpha2 # this will change in the future as we make everything more stable +apiVersion: k3d.io/v1alpha3 # 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-`) servers: 1 # same as `--servers 1` @@ -98,9 +98,10 @@ options: disableRollback: false # same as `--no-Rollback` disableHostIPInjection: false # same as `--no-hostip` k3s: # options passed on to K3s itself - extraServerArgs: # additional arguments passed to the `k3s server` command; same as `--k3s-server-arg` - - --tls-san=my.host.domain - extraAgentArgs: [] # addditional arguments passed to the `k3s agent` command; same as `--k3s-agent-arg` + extraArgs: # additional arguments passed to the `k3s server|agent` command; same as `--k3s-arg` + - arg: --tls-san=my.host.domain + nodeFilters: + - server[*] kubeconfig: updateDefaultKubeconfig: true # add new cluster to your default Kubeconfig; same as `--kubeconfig-update-default` (default: true) switchCurrentContext: true # also set current-context to the new cluster's context; same as `--kubeconfig-switch-context` (default: true) diff --git a/docs/usage/guides/registries.md b/docs/usage/guides/registries.md index 04411dc8..11a485ab 100644 --- a/docs/usage/guides/registries.md +++ b/docs/usage/guides/registries.md @@ -29,7 +29,7 @@ This file can also be used for providing additional information necessary for ac If you're using a `SimpleConfig` file to configure your k3d cluster, you may as well embed the registries.yaml in there directly: ```yaml -apiVersion: k3d.io/v1alpha2 +apiVersion: k3d.io/v1alpha3 kind: Simple name: test servers: 1 diff --git a/pkg/client/cluster.go b/pkg/client/cluster.go index ec6cf801..4aa29fcb 100644 --- a/pkg/client/cluster.go +++ b/pkg/client/cluster.go @@ -36,7 +36,7 @@ import ( "github.com/docker/go-connections/nat" "github.com/imdario/mergo" "github.com/rancher/k3d/v4/pkg/actions" - config "github.com/rancher/k3d/v4/pkg/config/v1alpha2" + config "github.com/rancher/k3d/v4/pkg/config/v1alpha3" k3drt "github.com/rancher/k3d/v4/pkg/runtimes" "github.com/rancher/k3d/v4/pkg/runtimes/docker" runtimeErr "github.com/rancher/k3d/v4/pkg/runtimes/errors" diff --git a/pkg/config/config.go b/pkg/config/config.go index 9cf51e23..efd5b267 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -29,43 +29,51 @@ import ( "github.com/spf13/viper" - conf "github.com/rancher/k3d/v4/pkg/config/v1alpha2" + "github.com/rancher/k3d/v4/pkg/config/v1alpha2" + "github.com/rancher/k3d/v4/pkg/config/v1alpha3" + defaultConfig "github.com/rancher/k3d/v4/pkg/config/v1alpha3" + + types "github.com/rancher/k3d/v4/pkg/config/types" ) -func FromViperSimple(config *viper.Viper) (conf.SimpleConfig, error) { +const DefaultConfigApiVersion = defaultConfig.ApiVersion - var cfg conf.SimpleConfig - - // determine config kind - if config.GetString("kind") != "" && strings.ToLower(config.GetString("kind")) != "simple" { - return cfg, fmt.Errorf("Wrong `kind` '%s' != 'simple' in config file", config.GetString("kind")) - } - - if err := config.Unmarshal(&cfg); err != nil { - log.Errorln("Failed to unmarshal File config") - - return cfg, err - } - - return cfg, nil +var Schemas = map[string]string{ + v1alpha2.ApiVersion: v1alpha2.JSONSchema, + v1alpha3.ApiVersion: v1alpha3.JSONSchema, } -func FromViper(config *viper.Viper) (conf.Config, error) { +func GetSchemaByVersion(apiVersion string) ([]byte, error) { + schema, ok := Schemas[strings.ToLower(apiVersion)] + if !ok { + return nil, fmt.Errorf("unsupported apiVersion '%s'", apiVersion) + } + return []byte(schema), nil +} - var cfg conf.Config +func FromViper(config *viper.Viper) (types.Config, error) { - // determine config kind - switch strings.ToLower(config.GetString("kind")) { - case "simple": - cfg = conf.SimpleConfig{} - case "cluster": - cfg = conf.ClusterConfig{} - case "clusterlist": - cfg = conf.ClusterListConfig{} + var cfg types.Config + var err error + + apiVersion := strings.ToLower(config.GetString("apiversion")) + kind := strings.ToLower(config.GetString("kind")) + + log.Tracef("Trying to read config apiVersion='%s', kind='%s'", apiVersion, kind) + + switch apiVersion { + case "k3d.io/v1alpha2": + cfg, err = v1alpha2.GetConfigByKind(kind) + case "k3d.io/v1alpha3": + cfg, err = v1alpha3.GetConfigByKind(kind) case "": - return nil, fmt.Errorf("Missing `kind` in config file") + cfg, err = defaultConfig.GetConfigByKind(kind) default: - return nil, fmt.Errorf("Unknown `kind` '%s' in config file", config.GetString("kind")) + return nil, fmt.Errorf("cannot read config with apiversion '%s'", config.GetString("apiversion")) + } + + if err != nil { + return nil, err } if err := config.Unmarshal(&cfg); err != nil { @@ -76,3 +84,12 @@ func FromViper(config *viper.Viper) (conf.Config, error) { return cfg, nil } + +func getMigrations(version string) map[string]func(types.Config) (types.Config, error) { + switch version { + case v1alpha3.ApiVersion: + return v1alpha3.Migrations + default: + return nil + } +} diff --git a/pkg/config/config_test.go b/pkg/config/config_test.go index 9f6e05d7..eb15f152 100644 --- a/pkg/config/config_test.go +++ b/pkg/config/config_test.go @@ -26,7 +26,8 @@ import ( "time" "github.com/go-test/deep" - conf "github.com/rancher/k3d/v4/pkg/config/v1alpha2" + configtypes "github.com/rancher/k3d/v4/pkg/config/types" + conf "github.com/rancher/k3d/v4/pkg/config/v1alpha3" "github.com/spf13/viper" k3d "github.com/rancher/k3d/v4/pkg/types" @@ -39,8 +40,8 @@ func TestReadSimpleConfig(t *testing.T) { exposedAPI.HostPort = "6443" expectedConfig := conf.SimpleConfig{ - TypeMeta: conf.TypeMeta{ - APIVersion: "k3d.io/v1alpha2", + TypeMeta: configtypes.TypeMeta{ + APIVersion: "k3d.io/v1alpha3", Kind: "Simple", }, Name: "test", @@ -83,8 +84,12 @@ func TestReadSimpleConfig(t *testing.T) { DisableImageVolume: false, }, K3sOptions: conf.SimpleConfigOptionsK3s{ - ExtraServerArgs: []string{"--tls-san=127.0.0.1"}, - ExtraAgentArgs: []string{}, + ExtraArgs: []conf.K3sArgWithNodeFilters{ + { + Arg: "--tls-san=127.0.0.1", + NodeFilters: []string{"server[*]"}, + }, + }, }, KubeconfigOptions: conf.SimpleConfigOptionsKubeconfig{ UpdateDefaultKubeconfig: true, @@ -107,7 +112,7 @@ func TestReadSimpleConfig(t *testing.T) { t.Error(err) } - cfg, err := FromViperSimple(config) + cfg, err := FromViper(config) if err != nil { t.Error(err) } @@ -123,8 +128,8 @@ func TestReadSimpleConfig(t *testing.T) { func TestReadClusterConfig(t *testing.T) { expectedConfig := conf.ClusterConfig{ - TypeMeta: conf.TypeMeta{ - APIVersion: "k3d.io/v1alpha2", + TypeMeta: configtypes.TypeMeta{ + APIVersion: "k3d.io/v1alpha3", Kind: "Cluster", }, Cluster: k3d.Cluster{ @@ -168,8 +173,8 @@ func TestReadClusterConfig(t *testing.T) { func TestReadClusterListConfig(t *testing.T) { expectedConfig := conf.ClusterListConfig{ - TypeMeta: conf.TypeMeta{ - APIVersion: "k3d.io/v1alpha2", + TypeMeta: configtypes.TypeMeta{ + APIVersion: "k3d.io/v1alpha3", Kind: "ClusterList", }, Clusters: []k3d.Cluster{ @@ -237,7 +242,7 @@ func TestReadUnknownConfig(t *testing.T) { t.Error(err) } - _, err := FromViperSimple(config) + _, err := FromViper(config) if err == nil { t.Fail() } diff --git a/pkg/config/jsonschema_test.go b/pkg/config/jsonschema_test.go index 5ece4e79..f3ec75b6 100644 --- a/pkg/config/jsonschema_test.go +++ b/pkg/config/jsonschema_test.go @@ -24,14 +24,14 @@ package config import ( "testing" - "github.com/rancher/k3d/v4/pkg/config/v1alpha2" + "github.com/rancher/k3d/v4/pkg/config/v1alpha3" ) func TestValidateSchema(t *testing.T) { cfgPath := "./test_assets/config_test_simple.yaml" - if err := ValidateSchemaFile(cfgPath, []byte(v1alpha2.JSONSchema)); err != nil { + if err := ValidateSchemaFile(cfgPath, []byte(v1alpha3.JSONSchema)); err != nil { t.Errorf("Validation of config file %s against the default schema failed: %+v", cfgPath, err) } @@ -42,7 +42,7 @@ func TestValidateSchemaFail(t *testing.T) { cfgPath := "./test_assets/config_test_simple_invalid_servers.yaml" var err error - if err = ValidateSchemaFile(cfgPath, []byte(v1alpha2.JSONSchema)); err == nil { + if err = ValidateSchemaFile(cfgPath, []byte(v1alpha3.JSONSchema)); err == nil { t.Errorf("Validation of config file %s against the default schema passed where we expected a failure", cfgPath) } diff --git a/pkg/config/merge.go b/pkg/config/merge.go index 390269dc..c86c0eaa 100644 --- a/pkg/config/merge.go +++ b/pkg/config/merge.go @@ -24,7 +24,7 @@ package config import ( "github.com/imdario/mergo" - conf "github.com/rancher/k3d/v4/pkg/config/v1alpha2" + conf "github.com/rancher/k3d/v4/pkg/config/v1alpha3" log "github.com/sirupsen/logrus" ) diff --git a/pkg/config/merge_test.go b/pkg/config/merge_test.go index 349f7d4b..cfb73b72 100644 --- a/pkg/config/merge_test.go +++ b/pkg/config/merge_test.go @@ -25,7 +25,8 @@ package config import ( "testing" - conf "github.com/rancher/k3d/v4/pkg/config/v1alpha2" + configtypes "github.com/rancher/k3d/v4/pkg/config/types" + conf "github.com/rancher/k3d/v4/pkg/config/v1alpha3" "github.com/spf13/viper" "gotest.tools/assert" ) @@ -34,7 +35,7 @@ func TestMergeSimpleConfig(t *testing.T) { srcConfig := "./test_assets/config_test_simple.yaml" destConfig := "./test_assets/config_test_simple_2.yaml" - var src, dest conf.Config + var src, dest configtypes.Config var err error cfg1 := viper.New() @@ -45,11 +46,11 @@ func TestMergeSimpleConfig(t *testing.T) { cfg2.SetConfigFile(destConfig) _ = cfg2.ReadInConfig() - if src, err = FromViperSimple(cfg1); err != nil { + if src, err = FromViper(cfg1); err != nil { t.Fatal(err) } - if dest, err = FromViperSimple(cfg2); err != nil { + if dest, err = FromViper(cfg2); err != nil { t.Fatal(err) } diff --git a/pkg/config/migrate.go b/pkg/config/migrate.go new file mode 100644 index 00000000..28fb5d1f --- /dev/null +++ b/pkg/config/migrate.go @@ -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) + +} diff --git a/pkg/config/process.go b/pkg/config/process.go index 199a77cf..d67d55ae 100644 --- a/pkg/config/process.go +++ b/pkg/config/process.go @@ -23,7 +23,7 @@ THE SOFTWARE. package config import ( - conf "github.com/rancher/k3d/v4/pkg/config/v1alpha2" + conf "github.com/rancher/k3d/v4/pkg/config/v1alpha3" log "github.com/sirupsen/logrus" ) diff --git a/pkg/config/process_test.go b/pkg/config/process_test.go index 5326ca53..c4d890ed 100644 --- a/pkg/config/process_test.go +++ b/pkg/config/process_test.go @@ -26,6 +26,7 @@ import ( "context" "testing" + conf "github.com/rancher/k3d/v4/pkg/config/v1alpha3" "github.com/rancher/k3d/v4/pkg/runtimes" "github.com/spf13/viper" "gotest.tools/assert" @@ -38,14 +39,14 @@ func TestProcessClusterConfig(t *testing.T) { vip.SetConfigFile(cfgFile) _ = vip.ReadInConfig() - cfg, err := FromViperSimple(vip) + cfg, err := FromViper(vip) if err != nil { t.Error(err) } t.Logf("\n========== Read Config and transform to cluster ==========\n%+v\n=================================\n", cfg) - clusterCfg, err := TransformSimpleToClusterConfig(context.Background(), runtimes.Docker, cfg) + clusterCfg, err := TransformSimpleToClusterConfig(context.Background(), runtimes.Docker, cfg.(conf.SimpleConfig)) if err != nil { t.Error(err) } diff --git a/pkg/config/test_assets/config_test_cluster.yaml b/pkg/config/test_assets/config_test_cluster.yaml index e90f30b2..f1a8438c 100644 --- a/pkg/config/test_assets/config_test_cluster.yaml +++ b/pkg/config/test_assets/config_test_cluster.yaml @@ -1,4 +1,4 @@ -apiVersion: k3d.io/v1alpha2 +apiVersion: k3d.io/v1alpha3 kind: Cluster name: foo nodes: diff --git a/pkg/config/test_assets/config_test_cluster_list.yaml b/pkg/config/test_assets/config_test_cluster_list.yaml index 0eba2c22..9d2e55ca 100644 --- a/pkg/config/test_assets/config_test_cluster_list.yaml +++ b/pkg/config/test_assets/config_test_cluster_list.yaml @@ -1,5 +1,5 @@ --- -apiVersion: k3d.io/v1alpha2 +apiVersion: k3d.io/v1alpha3 kind: ClusterList clusters: - name: foo diff --git a/pkg/config/test_assets/config_test_simple.yaml b/pkg/config/test_assets/config_test_simple.yaml index e264bd9a..4e132176 100644 --- a/pkg/config/test_assets/config_test_simple.yaml +++ b/pkg/config/test_assets/config_test_simple.yaml @@ -1,4 +1,4 @@ -apiVersion: k3d.io/v1alpha2 +apiVersion: k3d.io/v1alpha3 kind: Simple name: test servers: 1 @@ -25,7 +25,7 @@ env: labels: - label: foo=bar nodeFilters: - - server[0] + - "server[0]" - loadbalancer options: @@ -35,9 +35,10 @@ options: disableLoadbalancer: false disableImageVolume: false k3s: - extraServerArgs: - - --tls-san=127.0.0.1 - extraAgentArgs: [] + extraArgs: + - arg: --tls-san=127.0.0.1 + nodeFilters: + - "server[*]" kubeconfig: updateDefaultKubeconfig: true - switchCurrentContext: true \ No newline at end of file + switchCurrentContext: true diff --git a/pkg/config/test_assets/config_test_simple_2.yaml b/pkg/config/test_assets/config_test_simple_2.yaml index 0d5293ee..a849e322 100644 --- a/pkg/config/test_assets/config_test_simple_2.yaml +++ b/pkg/config/test_assets/config_test_simple_2.yaml @@ -1,4 +1,4 @@ -apiVersion: k3d.io/v1alpha2 +apiVersion: k3d.io/v1alpha3 kind: Simple name: supertest agents: 8 \ No newline at end of file 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 7f2442c3..b9e75fb6 100644 --- a/pkg/config/test_assets/config_test_simple_invalid_servers.yaml +++ b/pkg/config/test_assets/config_test_simple_invalid_servers.yaml @@ -1,4 +1,4 @@ -apiVersion: k3d.io/v1alpha2 +apiVersion: k3d.io/v1alpha3 kind: Simple name: 1234 servers: 1 @@ -35,9 +35,10 @@ options: disableLoadbalancer: false disableImageVolume: false k3s: - extraServerArgs: - - --tls-san=127.0.0.1 - extraAgentArgs: [] + extraArgs: + - arg: --tls-san=127.0.0.1 + nodeFilters: + - "server[*]" kubeconfig: updateDefaultKubeconfig: true switchCurrentContext: true \ No newline at end of file diff --git a/pkg/config/test_assets/config_test_unknown.yaml b/pkg/config/test_assets/config_test_unknown.yaml index 356972ce..66fe0c0f 100644 --- a/pkg/config/test_assets/config_test_unknown.yaml +++ b/pkg/config/test_assets/config_test_unknown.yaml @@ -1,3 +1,3 @@ -apiVersion: k3d.io/v1alpha2 +apiVersion: k3d.io/v1alpha3 kind: Unknown foo: bar \ No newline at end of file diff --git a/pkg/config/transform.go b/pkg/config/transform.go index 76f9751e..0ec43686 100644 --- a/pkg/config/transform.go +++ b/pkg/config/transform.go @@ -31,7 +31,7 @@ import ( "github.com/docker/go-connections/nat" cliutil "github.com/rancher/k3d/v4/cmd/util" // TODO: move parseapiport to pkg - conf "github.com/rancher/k3d/v4/pkg/config/v1alpha2" + conf "github.com/rancher/k3d/v4/pkg/config/v1alpha3" "github.com/rancher/k3d/v4/pkg/runtimes" k3d "github.com/rancher/k3d/v4/pkg/types" "github.com/rancher/k3d/v4/pkg/types/k3s" @@ -117,7 +117,6 @@ func TransformSimpleToClusterConfig(ctx context.Context, runtime runtimes.Runtim serverNode := k3d.Node{ Role: k3d.ServerRole, Image: simpleConfig.Image, - Args: simpleConfig.Options.K3sOptions.ExtraServerArgs, ServerOpts: k3d.ServerOpts{}, Memory: simpleConfig.Options.Runtime.ServersMemory, } @@ -135,7 +134,6 @@ func TransformSimpleToClusterConfig(ctx context.Context, runtime runtimes.Runtim agentNode := k3d.Node{ Role: k3d.AgentRole, Image: simpleConfig.Image, - Args: simpleConfig.Options.K3sOptions.ExtraAgentArgs, Memory: simpleConfig.Options.Runtime.AgentsMemory, } newCluster.Nodes = append(newCluster.Nodes, &agentNode) @@ -228,6 +226,22 @@ func TransformSimpleToClusterConfig(ctx context.Context, runtime runtimes.Runtim } } + // -> ARGS + for _, argWithNodeFilters := range simpleConfig.Options.K3sOptions.ExtraArgs { + if len(argWithNodeFilters.NodeFilters) == 0 && nodeCount > 1 { + return nil, fmt.Errorf("K3sExtraArg '%s' lacks a node filter, but there's more than one node", argWithNodeFilters.Arg) + } + + nodes, err := util.FilterNodes(nodeList, argWithNodeFilters.NodeFilters) + if err != nil { + return nil, err + } + + for _, node := range nodes { + node.Args = append(node.Args, argWithNodeFilters.Arg) + } + } + /************************** * Cluster Create Options * **************************/ @@ -238,8 +252,6 @@ func TransformSimpleToClusterConfig(ctx context.Context, runtime runtimes.Runtim WaitForServer: simpleConfig.Options.K3dOptions.Wait, Timeout: simpleConfig.Options.K3dOptions.Timeout, DisableLoadBalancer: simpleConfig.Options.K3dOptions.DisableLoadbalancer, - K3sServerArgs: simpleConfig.Options.K3sOptions.ExtraServerArgs, - K3sAgentArgs: simpleConfig.Options.K3sOptions.ExtraAgentArgs, GPURequest: simpleConfig.Options.Runtime.GPURequest, ServersMemory: simpleConfig.Options.Runtime.ServersMemory, AgentsMemory: simpleConfig.Options.Runtime.AgentsMemory, diff --git a/pkg/config/transform_test.go b/pkg/config/transform_test.go index 6cfb336d..495f499c 100644 --- a/pkg/config/transform_test.go +++ b/pkg/config/transform_test.go @@ -26,6 +26,7 @@ import ( "context" "testing" + conf "github.com/rancher/k3d/v4/pkg/config/v1alpha3" "github.com/rancher/k3d/v4/pkg/runtimes" "github.com/spf13/viper" ) @@ -37,14 +38,14 @@ func TestTransformSimpleConfigToClusterConfig(t *testing.T) { vip.SetConfigFile(cfgFile) _ = vip.ReadInConfig() - cfg, err := FromViperSimple(vip) + cfg, err := FromViper(vip) if err != nil { t.Error(err) } t.Logf("\n========== Read Config ==========\n%+v\n=================================\n", cfg) - clusterCfg, err := TransformSimpleToClusterConfig(context.Background(), runtimes.Docker, cfg) + clusterCfg, err := TransformSimpleToClusterConfig(context.Background(), runtimes.Docker, cfg.(conf.SimpleConfig)) if err != nil { t.Error(err) } diff --git a/pkg/config/types/types.go b/pkg/config/types/types.go new file mode 100644 index 00000000..ff0e26e5 --- /dev/null +++ b/pkg/config/types/types.go @@ -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 +} diff --git a/pkg/config/v1alpha2/types.go b/pkg/config/v1alpha2/types.go index 0679cfd4..9f2375d1 100644 --- a/pkg/config/v1alpha2/types.go +++ b/pkg/config/v1alpha2/types.go @@ -27,6 +27,7 @@ import ( "fmt" "time" + configtypes "github.com/rancher/k3d/v4/pkg/config/types" k3d "github.com/rancher/k3d/v4/pkg/types" "github.com/rancher/k3d/v4/version" ) @@ -35,9 +36,11 @@ import ( //go:embed schema.json var JSONSchema string +const ApiVersion = "k3d.io/v1alpha2" + // DefaultConfigTpl for printing const DefaultConfigTpl = `--- -apiVersion: k3d.io/v1alpha2 +apiVersion: %s kind: Simple name: %s servers: 1 @@ -48,21 +51,11 @@ image: %s // DefaultConfig templated DefaultConfigTpl var DefaultConfig = fmt.Sprintf( DefaultConfigTpl, + ApiVersion, k3d.DefaultClusterName, fmt.Sprintf("%s:%s", k3d.DefaultK3sImageRepo, version.GetK3sVersion(false)), ) -// 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 -} - type VolumeWithNodeFilters struct { Volume string `mapstructure:"volume" yaml:"volume" json:"volume,omitempty"` NodeFilters []string `mapstructure:"nodeFilters" yaml:"nodeFilters" json:"nodeFilters,omitempty"` @@ -119,21 +112,21 @@ type SimpleConfigOptionsK3s struct { // SimpleConfig describes the toplevel k3d configuration file. type SimpleConfig struct { - 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 { + configtypes.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) @@ -147,30 +140,60 @@ type SimpleExposureOpts struct { HostPort string `mapstructure:"hostPort" yaml:"hostPort,omitempty" json:"hostPort,omitempty"` } -// GetKind implements Config.GetKind +// Kind implements Config.Kind func (c SimpleConfig) GetKind() string { - return "Cluster" + return "Simple" +} + +func (c SimpleConfig) GetAPIVersion() string { + return ApiVersion } // ClusterConfig describes a single cluster config type ClusterConfig struct { - 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"` + configtypes.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 +// Kind implements Config.Kind func (c ClusterConfig) GetKind() string { - return "Cluster" + return "Simple" +} + +func (c ClusterConfig) GetAPIVersion() string { + return ApiVersion } // ClusterListConfig describes a list of clusters type ClusterListConfig struct { - TypeMeta `mapstructure:",squash" yaml:",inline"` - Clusters []k3d.Cluster `mapstructure:"clusters" yaml:"clusters"` + configtypes.TypeMeta `mapstructure:",squash" yaml:",inline"` + Clusters []k3d.Cluster `mapstructure:"clusters" yaml:"clusters"` } func (c ClusterListConfig) GetKind() string { - return "ClusterList" + return "Simple" +} + +func (c ClusterListConfig) GetAPIVersion() string { + return ApiVersion +} + +func GetConfigByKind(kind string) (configtypes.Config, error) { + + // determine config kind + switch 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) + } + } diff --git a/pkg/config/v1alpha3/migrations.go b/pkg/config/v1alpha3/migrations.go new file mode 100644 index 00000000..5c894ca1 --- /dev/null +++ b/pkg/config/v1alpha3/migrations.go @@ -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 + +} diff --git a/pkg/config/v1alpha3/schema.json b/pkg/config/v1alpha3/schema.json new file mode 100644 index 00000000..2b07c02c --- /dev/null +++ b/pkg/config/v1alpha3/schema.json @@ -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" + ] + } + } +} \ No newline at end of file diff --git a/pkg/config/v1alpha3/types.go b/pkg/config/v1alpha3/types.go new file mode 100644 index 00000000..22465c71 --- /dev/null +++ b/pkg/config/v1alpha3/types.go @@ -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) + } + +} diff --git a/pkg/config/validate.go b/pkg/config/validate.go index bb81ba41..c647e4f5 100644 --- a/pkg/config/validate.go +++ b/pkg/config/validate.go @@ -27,7 +27,7 @@ import ( "time" k3dc "github.com/rancher/k3d/v4/pkg/client" - conf "github.com/rancher/k3d/v4/pkg/config/v1alpha2" + conf "github.com/rancher/k3d/v4/pkg/config/v1alpha3" "github.com/rancher/k3d/v4/pkg/runtimes" runtimeutil "github.com/rancher/k3d/v4/pkg/runtimes/util" k3d "github.com/rancher/k3d/v4/pkg/types" diff --git a/pkg/config/validate_test.go b/pkg/config/validate_test.go index 45e736e3..88177808 100644 --- a/pkg/config/validate_test.go +++ b/pkg/config/validate_test.go @@ -26,7 +26,7 @@ import ( "context" "testing" - conf "github.com/rancher/k3d/v4/pkg/config/v1alpha2" + conf "github.com/rancher/k3d/v4/pkg/config/v1alpha3" "github.com/rancher/k3d/v4/pkg/runtimes" "github.com/spf13/viper" ) diff --git a/pkg/types/types.go b/pkg/types/types.go index 31675511..23667855 100644 --- a/pkg/types/types.go +++ b/pkg/types/types.go @@ -184,8 +184,6 @@ type ClusterCreateOpts struct { WaitForServer bool `yaml:"waitForServer" json:"waitForServer,omitempty"` Timeout time.Duration `yaml:"timeout" json:"timeout,omitempty"` DisableLoadBalancer bool `yaml:"disableLoadbalancer" json:"disableLoadbalancer,omitempty"` - K3sServerArgs []string `yaml:"k3sServerArgs" json:"k3sServerArgs,omitempty"` - K3sAgentArgs []string `yaml:"k3sAgentArgs" json:"k3sAgentArgs,omitempty"` GPURequest string `yaml:"gpuRequest" json:"gpuRequest,omitempty"` ServersMemory string `yaml:"serversMemory" json:"serversMemory,omitempty"` AgentsMemory string `yaml:"agentsMemory" json:"agentsMemory,omitempty"` diff --git a/tests/assets/config_test_simple.yaml b/tests/assets/config_test_simple.yaml index 4d9f7255..c1d05e2b 100755 --- a/tests/assets/config_test_simple.yaml +++ b/tests/assets/config_test_simple.yaml @@ -1,4 +1,4 @@ -apiVersion: k3d.io/v1alpha2 +apiVersion: k3d.io/v1alpha3 kind: Simple name: test servers: 3 @@ -43,9 +43,10 @@ options: disableLoadbalancer: false disableImageVolume: false k3s: - extraServerArgs: - - --tls-san=127.0.0.1 - extraAgentArgs: [] + extraArgs: + - arg: --tls-san=127.0.0.1 + nodeFilters: + - server[*] kubeconfig: updateDefaultKubeconfig: true switchCurrentContext: true \ No newline at end of file diff --git a/tests/assets/config_test_simple_migration_v1alpha2.yaml b/tests/assets/config_test_simple_migration_v1alpha2.yaml new file mode 100755 index 00000000..4d9f7255 --- /dev/null +++ b/tests/assets/config_test_simple_migration_v1alpha2.yaml @@ -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 \ No newline at end of file diff --git a/tests/assets/config_test_simple_migration_v1alpha3.yaml b/tests/assets/config_test_simple_migration_v1alpha3.yaml new file mode 100755 index 00000000..c1d05e2b --- /dev/null +++ b/tests/assets/config_test_simple_migration_v1alpha3.yaml @@ -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 \ No newline at end of file diff --git a/tests/test_config_file_migration.sh b/tests/test_config_file_migration.sh new file mode 100755 index 00000000..8f4be093 --- /dev/null +++ b/tests/test_config_file_migration.sh @@ -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 + +