From da7a870f54730b7925cb41c0879feaadc7f12110 Mon Sep 17 00:00:00 2001 From: Otto Bittner Date: Tue, 21 Feb 2023 14:05:41 +0100 Subject: [PATCH] cli: add `--kubernetes` flag (#1226) The flag can be used to specify a Kubernetes version in format MAJOR.MINOR and let the CLI extend the value with the patch version. --- cli/internal/cmd/configgenerate.go | 40 +++++++++++++++++++++-- cli/internal/cmd/configgenerate_test.go | 42 +++++++++++++++++++++++++ internal/config/validation.go | 36 +++++++++++++++------ 3 files changed, 105 insertions(+), 13 deletions(-) diff --git a/cli/internal/cmd/configgenerate.go b/cli/internal/cmd/configgenerate.go index 38b286609..0cd3cc792 100644 --- a/cli/internal/cmd/configgenerate.go +++ b/cli/internal/cmd/configgenerate.go @@ -8,14 +8,18 @@ package cmd import ( "fmt" + "strings" "github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider" + "github.com/edgelesssys/constellation/v2/internal/compatibility" "github.com/edgelesssys/constellation/v2/internal/config" "github.com/edgelesssys/constellation/v2/internal/constants" "github.com/edgelesssys/constellation/v2/internal/file" + "github.com/edgelesssys/constellation/v2/internal/versions" "github.com/siderolabs/talos/pkg/machinery/config/encoder" "github.com/spf13/afero" "github.com/spf13/cobra" + "golang.org/x/mod/semver" ) func newConfigGenerateCmd() *cobra.Command { @@ -31,12 +35,14 @@ func newConfigGenerateCmd() *cobra.Command { RunE: runConfigGenerate, } cmd.Flags().StringP("file", "f", constants.ConfigFilename, "path to output file, or '-' for stdout") + cmd.Flags().StringP("kubernetes", "k", semver.MajorMinor(config.Default().KubernetesVersion), "Kubernetes version to use in format MAJOR.MINOR") return cmd } type generateFlags struct { - file string + file string + k8sVersion string } type configGenerateCmd struct { @@ -64,6 +70,11 @@ func (cg *configGenerateCmd) configGenerate(cmd *cobra.Command, fileHandler file cg.log.Debugf("Parsed flags as %v", flags) cg.log.Debugf("Using cloud provider %s", provider.String()) conf := createConfig(provider) + extendedVersion := config.K8sVersionFromMajorMinor(flags.k8sVersion) + if extendedVersion == "" { + return fmt.Errorf("kubernetes (%s) does not specify a valid Kubernetes version. Supported versions: %s", strings.TrimPrefix(flags.k8sVersion, "v"), supportedVersions()) + } + conf.KubernetesVersion = extendedVersion if flags.file == "-" { content, err := encoder.NewEncoder(conf).Encode() if err != nil { @@ -101,13 +112,36 @@ func createConfig(provider cloudprovider.Provider) *config.Config { return conf } +// supportedVersions prints the supported version without v prefix and without patch version. +// Should only be used when accepting Kubernetes versions from --kubernetes. +func supportedVersions() string { + builder := strings.Builder{} + for i, version := range versions.SupportedK8sVersions() { + if i > 0 { + builder.WriteString(" ") + } + builder.WriteString(strings.TrimPrefix(semver.MajorMinor(version), "v")) + } + return builder.String() +} + func parseGenerateFlags(cmd *cobra.Command) (generateFlags, error) { file, err := cmd.Flags().GetString("file") if err != nil { - return generateFlags{}, fmt.Errorf("parsing config generate flags: %w", err) + return generateFlags{}, fmt.Errorf("parsing file flag: %w", err) } + k8sVersion, err := cmd.Flags().GetString("kubernetes") + if err != nil { + return generateFlags{}, fmt.Errorf("parsing kuberentes flag: %w", err) + } + prefixedVersion := compatibility.EnsurePrefixV(k8sVersion) + if !semver.IsValid(prefixedVersion) { + return generateFlags{}, fmt.Errorf("kubernetes flag does not specify a valid semantic version: %s", k8sVersion) + } + return generateFlags{ - file: file, + file: file, + k8sVersion: prefixedVersion, }, nil } diff --git a/cli/internal/cmd/configgenerate_test.go b/cli/internal/cmd/configgenerate_test.go index 0d29a710a..71c593a38 100644 --- a/cli/internal/cmd/configgenerate_test.go +++ b/cli/internal/cmd/configgenerate_test.go @@ -15,12 +15,54 @@ import ( "github.com/edgelesssys/constellation/v2/internal/constants" "github.com/edgelesssys/constellation/v2/internal/file" "github.com/edgelesssys/constellation/v2/internal/logger" + "github.com/edgelesssys/constellation/v2/internal/versions" "github.com/spf13/afero" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "golang.org/x/mod/semver" "gopkg.in/yaml.v3" ) +func TestConfigGenerateKubernetesVersion(t *testing.T) { + testCases := map[string]struct { + version string + wantErr bool + }{ + "success": { + version: semver.MajorMinor(string(versions.Default)), + }, + "no semver": { + version: "asdf", + wantErr: true, + }, + "not supported": { + version: "1111", + wantErr: true, + }, + } + + for name, tc := range testCases { + t.Run(name, func(t *testing.T) { + assert := assert.New(t) + require := require.New(t) + + fileHandler := file.NewHandler(afero.NewMemMapFs()) + cmd := newConfigGenerateCmd() + err := cmd.Flags().Set("kubernetes", tc.version) + require.NoError(err) + + cg := &configGenerateCmd{log: logger.NewTest(t)} + err = cg.configGenerate(cmd, fileHandler, cloudprovider.Unknown) + + if tc.wantErr { + assert.Error(err) + return + } + assert.NoError(err) + }) + } +} + func TestConfigGenerateDefault(t *testing.T) { assert := assert.New(t) require := require.New(t) diff --git a/internal/config/validation.go b/internal/config/validation.go index 780d133ea..e9cceb0dc 100644 --- a/internal/config/validation.go +++ b/internal/config/validation.go @@ -327,19 +327,35 @@ func (c *Config) validateK8sVersion(fl validator.FieldLevel) bool { if !semver.IsValid(configVersion) { return false } - var extendedVersion string - switch semver.MajorMinor(configVersion) { - case semver.MajorMinor(string(versions.V1_24)): - extendedVersion = string(versions.V1_24) - case semver.MajorMinor(string(versions.V1_25)): - extendedVersion = string(versions.V1_25) - case semver.MajorMinor(string(versions.V1_26)): - extendedVersion = string(versions.V1_26) - default: + + extendedVersion := K8sVersionFromMajorMinor(semver.MajorMinor(configVersion)) + if extendedVersion == "" { return false } + + valid := versions.IsSupportedK8sVersion(extendedVersion) + if !valid { + return false + } + c.KubernetesVersion = extendedVersion - return versions.IsSupportedK8sVersion(extendedVersion) + return true +} + +// K8sVersionFromMajorMinor takes a semver in format MAJOR.MINOR +// and returns the version in format MAJOR.MINOR.PATCH with the +// supported patch version as PATCH. +func K8sVersionFromMajorMinor(version string) string { + switch version { + case semver.MajorMinor(string(versions.V1_24)): + return string(versions.V1_24) + case semver.MajorMinor(string(versions.V1_25)): + return string(versions.V1_25) + case semver.MajorMinor(string(versions.V1_26)): + return string(versions.V1_26) + default: + return "" + } } func registerVersionCompatibilityError(ut ut.Translator) error {