diff --git a/cli/internal/cmd/configgenerate.go b/cli/internal/cmd/configgenerate.go index 6e05a6b920..8a582dfe18 100644 --- a/cli/internal/cmd/configgenerate.go +++ b/cli/internal/cmd/configgenerate.go @@ -13,14 +13,13 @@ import ( "github.com/edgelesssys/constellation/v2/cli/internal/cmd/pathprefix" "github.com/edgelesssys/constellation/v2/internal/attestation/variant" "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/spf13/afero" "github.com/spf13/cobra" - "golang.org/x/mod/semver" ) func newConfigGenerateCmd() *cobra.Command { @@ -35,7 +34,7 @@ func newConfigGenerateCmd() *cobra.Command { ValidArgsFunction: generateCompletion, RunE: runConfigGenerate, } - cmd.Flags().StringP("kubernetes", "k", semver.MajorMinor(config.Default().KubernetesVersion), "Kubernetes version to use in format MAJOR.MINOR") + cmd.Flags().StringP("kubernetes", "k", string(config.Default().KubernetesVersion), "Kubernetes version to use in format MAJOR.MINOR") cmd.Flags().StringP("attestation", "a", "", fmt.Sprintf("attestation variant to use %s. If not specified, the default for the cloud provider is used", printFormattedSlice(variant.GetAvailableAttestationVariants()))) return cmd @@ -43,7 +42,7 @@ func newConfigGenerateCmd() *cobra.Command { type generateFlags struct { pf pathprefix.PathPrefixer - k8sVersion string + k8sVersion versions.ValidK8sVersion attestationVariant variant.Variant } @@ -124,19 +123,6 @@ func createConfig(provider cloudprovider.Provider) *config.Config { return res } -// 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) { workDir, err := cmd.Flags().GetString("workspace") if err != nil { @@ -144,11 +130,11 @@ func parseGenerateFlags(cmd *cobra.Command) (generateFlags, error) { } k8sVersion, err := cmd.Flags().GetString("kubernetes") if err != nil { - return generateFlags{}, fmt.Errorf("parsing kuberentes flag: %w", err) + return generateFlags{}, fmt.Errorf("parsing Kubernetes flag: %w", err) } - resolvedVersion, err := resolveK8sVersion(k8sVersion) + resolvedVersion, err := versions.NewValidK8sVersion(k8sVersion, true) // allow versions without specified patch if err != nil { - return generateFlags{}, fmt.Errorf("resolving kuberentes version from flag: %w", err) + return generateFlags{}, fmt.Errorf("resolving Kubernetes version from flag: %w", err) } attestationString, err := cmd.Flags().GetString("attestation") @@ -184,22 +170,6 @@ func generateCompletion(_ *cobra.Command, args []string, _ string) ([]string, co } } -// resolveK8sVersion takes the user input from --kubernetes and transforms a MAJOR.MINOR definition into a supported -// MAJOR.MINOR.PATCH release. -func resolveK8sVersion(k8sVersion string) (string, error) { - prefixedVersion := compatibility.EnsurePrefixV(k8sVersion) - if !semver.IsValid(prefixedVersion) { - return "", fmt.Errorf("kubernetes flag does not specify a valid semantic version: %s", k8sVersion) - } - - extendedVersion := config.K8sVersionFromMajorMinor(prefixedVersion) - if extendedVersion == "" { - return "", fmt.Errorf("--kubernetes (%s) does not specify a valid Kubernetes version. Supported versions: %s", strings.TrimPrefix(k8sVersion, "v"), supportedVersions()) - } - - return extendedVersion, nil -} - func printFormattedSlice[T any](input []T) string { return fmt.Sprintf("{%s}", strings.Join(toString(input), "|")) } diff --git a/cli/internal/cmd/configgenerate_test.go b/cli/internal/cmd/configgenerate_test.go index d11cd5d806..9a07c7ad04 100644 --- a/cli/internal/cmd/configgenerate_test.go +++ b/cli/internal/cmd/configgenerate_test.go @@ -8,6 +8,7 @@ package cmd import ( "fmt" + "strings" "testing" "github.com/edgelesssys/constellation/v2/internal/attestation/variant" @@ -29,9 +30,29 @@ func TestConfigGenerateKubernetesVersion(t *testing.T) { version string wantErr bool }{ - "success": { + "default version": { + version: "", + }, + "without v prefix": { + version: strings.TrimPrefix(string(versions.Default), "v"), + }, + "K8s version without patch version": { version: semver.MajorMinor(string(versions.Default)), }, + "K8s version with patch version": { + version: string(versions.Default), + }, + "K8s version with invalid patch version": { + version: func() string { + s := string(versions.Default) + return s[:len(s)-1] + "99" + }(), + wantErr: true, + }, + "outdated K8s version": { + version: "v1.0.0", + wantErr: true, + }, "no semver": { version: "asdf", wantErr: true, @@ -50,11 +71,13 @@ func TestConfigGenerateKubernetesVersion(t *testing.T) { fileHandler := file.NewHandler(afero.NewMemMapFs()) cmd := newConfigGenerateCmd() cmd.Flags().String("workspace", "", "") // register persistent flag manually - err := cmd.Flags().Set("kubernetes", tc.version) - require.NoError(err) + if tc.version != "" { + err := cmd.Flags().Set("kubernetes", tc.version) + require.NoError(err) + } cg := &configGenerateCmd{log: logger.NewTest(t)} - err = cg.configGenerate(cmd, fileHandler, cloudprovider.Unknown, "") + err := cg.configGenerate(cmd, fileHandler, cloudprovider.Unknown, "") if tc.wantErr { assert.Error(err) diff --git a/cli/internal/cmd/init.go b/cli/internal/cmd/init.go index ce8613ff83..f640ddaf1f 100644 --- a/cli/internal/cmd/init.go +++ b/cli/internal/cmd/init.go @@ -24,7 +24,6 @@ import ( "github.com/edgelesssys/constellation/v2/internal/api/attestationconfigapi" "github.com/edgelesssys/constellation/v2/internal/atls" "github.com/edgelesssys/constellation/v2/internal/attestation/variant" - "github.com/edgelesssys/constellation/v2/internal/compatibility" "github.com/spf13/afero" "github.com/spf13/cobra" @@ -170,10 +169,7 @@ func (i *initCmd) initialize( // config validation does not check k8s patch version since upgrade may accept an outdated patch version. // init only supported up-to-date versions. - k8sVersion, err := versions.NewValidK8sVersion(compatibility.EnsurePrefixV(conf.KubernetesVersion), true) - if err != nil { - return err - } + k8sVersion := conf.KubernetesVersion i.log.Debugf("Validated k8s version as %s", k8sVersion) if versions.IsPreviewK8sVersion(k8sVersion) { cmd.PrintErrf("Warning: Constellation with Kubernetes %v is still in preview. Use only for evaluation purposes.\n", k8sVersion) @@ -275,7 +271,7 @@ func (i *initCmd) initialize( if err != nil { return fmt.Errorf("creating Helm client: %w", err) } - executor, includesUpgrades, err := helmApplier.PrepareApply(conf, k8sVersion, idFile, options, output, + executor, includesUpgrades, err := helmApplier.PrepareApply(conf, idFile, options, output, serviceAccURI, masterSecret) if err != nil { return fmt.Errorf("getting Helm chart executor: %w", err) @@ -629,7 +625,7 @@ type attestationConfigApplier interface { } type helmApplier interface { - PrepareApply(conf *config.Config, validK8sversion versions.ValidK8sVersion, idFile clusterid.File, flags helm.Options, tfOutput terraform.ApplyOutput, serviceAccURI string, masterSecret uri.MasterSecret) (helm.Applier, bool, error) + PrepareApply(conf *config.Config, idFile clusterid.File, flags helm.Options, tfOutput terraform.ApplyOutput, serviceAccURI string, masterSecret uri.MasterSecret) (helm.Applier, bool, error) } type clusterShower interface { diff --git a/cli/internal/cmd/init_test.go b/cli/internal/cmd/init_test.go index af0ecbc0cf..81351e19a9 100644 --- a/cli/internal/cmd/init_test.go +++ b/cli/internal/cmd/init_test.go @@ -133,7 +133,13 @@ func TestInitialize(t *testing.T) { provider: cloudprovider.Azure, idFile: &clusterid.File{IP: "192.0.2.1"}, initServerAPI: &stubInitServer{res: &initproto.InitResponse{Kind: &initproto.InitResponse_InitSuccess{InitSuccess: testInitResp}}}, - configMutator: func(c *config.Config) { c.KubernetesVersion = strings.TrimPrefix(string(versions.Default), "v") }, + configMutator: func(c *config.Config) { + res, err := versions.NewValidK8sVersion(strings.TrimPrefix(string(versions.Default), "v"), true) + if err != nil { + panic("invalid k8s version") + } + c.KubernetesVersion = res + }, }, } @@ -222,7 +228,7 @@ type stubApplier struct { err error } -func (s stubApplier) PrepareApply(_ *config.Config, _ versions.ValidK8sVersion, _ clusterid.File, _ helm.Options, _ terraform.ApplyOutput, _ string, _ uri.MasterSecret) (helm.Applier, bool, error) { +func (s stubApplier) PrepareApply(_ *config.Config, _ clusterid.File, _ helm.Options, _ terraform.ApplyOutput, _ string, _ uri.MasterSecret) (helm.Applier, bool, error) { return stubRunner{}, false, s.err } diff --git a/cli/internal/cmd/upgradeapply.go b/cli/internal/cmd/upgradeapply.go index 7ed0234732..a2ba9da19f 100644 --- a/cli/internal/cmd/upgradeapply.go +++ b/cli/internal/cmd/upgradeapply.go @@ -149,7 +149,9 @@ func (u *upgradeApplyCmd) upgradeApply(cmd *cobra.Command, upgradeDir string, fl } } } - validK8sVersion, err := validK8sVersion(cmd, conf.KubernetesVersion, flags.yes) + if versions.IsPreviewK8sVersion(conf.KubernetesVersion) { + cmd.PrintErrf("Warning: Constellation with Kubernetes %q is still in preview. Use only for evaluation purposes.\n", conf.KubernetesVersion) + } if err != nil { return err } @@ -197,7 +199,7 @@ func (u *upgradeApplyCmd) upgradeApply(cmd *cobra.Command, upgradeDir string, fl } var upgradeErr *compatibility.InvalidUpgradeError - err = u.handleServiceUpgrade(cmd, conf, idFile, tfOutput, validK8sVersion, upgradeDir, flags) + err = u.handleServiceUpgrade(cmd, conf, idFile, tfOutput, upgradeDir, flags) switch { case errors.As(err, &upgradeErr): cmd.PrintErrln(err) @@ -305,27 +307,6 @@ func (u *upgradeApplyCmd) migrateTerraform(cmd *cobra.Command, conf *config.Conf return tfOutput, nil } -// validK8sVersion checks if the Kubernetes patch version is supported and asks for confirmation if not. -func validK8sVersion(cmd *cobra.Command, version string, yes bool) (validVersion versions.ValidK8sVersion, err error) { - validVersion, err = versions.NewValidK8sVersion(version, true) - if versions.IsPreviewK8sVersion(validVersion) { - cmd.PrintErrf("Warning: Constellation with Kubernetes %v is still in preview. Use only for evaluation purposes.\n", validVersion) - } - valid := err == nil - - if !valid && !yes { - confirmed, err := askToConfirm(cmd, fmt.Sprintf("WARNING: The Kubernetes patch version %s is not supported. If you continue, Kubernetes upgrades will be skipped. Do you want to continue anyway?", version)) - if err != nil { - return validVersion, fmt.Errorf("asking for confirmation: %w", err) - } - if !confirmed { - return validVersion, fmt.Errorf("aborted by user") - } - } - - return validVersion, nil -} - // confirmAndUpgradeAttestationConfig checks if the locally configured measurements are different from the cluster's measurements. // If so the function will ask the user to confirm (if --yes is not set) and upgrade the cluster's config. func (u *upgradeApplyCmd) confirmAndUpgradeAttestationConfig( @@ -370,7 +351,7 @@ func (u *upgradeApplyCmd) confirmAndUpgradeAttestationConfig( func (u *upgradeApplyCmd) handleServiceUpgrade( cmd *cobra.Command, conf *config.Config, idFile clusterid.File, tfOutput terraform.ApplyOutput, - validK8sVersion versions.ValidK8sVersion, upgradeDir string, flags upgradeApplyFlags, + upgradeDir string, flags upgradeApplyFlags, ) error { var secret uri.MasterSecret if err := u.fileHandler.ReadJSON(constants.MasterSecretFilename, &secret); err != nil { @@ -388,7 +369,7 @@ func (u *upgradeApplyCmd) handleServiceUpgrade( prepareApply := func(allowDestructive bool) (helm.Applier, bool, error) { options.AllowDestructive = allowDestructive - executor, includesUpgrades, err := u.helmApplier.PrepareApply(conf, validK8sVersion, idFile, options, + executor, includesUpgrades, err := u.helmApplier.PrepareApply(conf, idFile, options, tfOutput, serviceAccURI, secret) var upgradeErr *compatibility.InvalidUpgradeError switch { diff --git a/cli/internal/cmd/upgradecheck.go b/cli/internal/cmd/upgradecheck.go index d2b05eda6c..30e39a4bc8 100644 --- a/cli/internal/cmd/upgradecheck.go +++ b/cli/internal/cmd/upgradecheck.go @@ -576,7 +576,7 @@ func (v *versionUpgrade) writeConfig(conf *config.Config, fileHandler file.Handl conf.MicroserviceVersion = v.newServices } if len(v.newKubernetes) > 0 { - conf.KubernetesVersion = v.newKubernetes[0] + conf.KubernetesVersion = versions.ValidK8sVersion(v.newKubernetes[0]) } if len(v.newImages) > 0 { imageUpgrade := sortedMapKeys(v.newImages)[0] diff --git a/cli/internal/helm/BUILD.bazel b/cli/internal/helm/BUILD.bazel index 663ca1951e..bc49c8b1f1 100644 --- a/cli/internal/helm/BUILD.bazel +++ b/cli/internal/helm/BUILD.bazel @@ -469,7 +469,6 @@ go_test( "//internal/kms/uri", "//internal/logger", "//internal/semver", - "//internal/versions", "@com_github_pkg_errors//:errors", "@com_github_stretchr_testify//assert", "@com_github_stretchr_testify//mock", diff --git a/cli/internal/helm/helm.go b/cli/internal/helm/helm.go index 261b431e23..ba42139874 100644 --- a/cli/internal/helm/helm.go +++ b/cli/internal/helm/helm.go @@ -39,7 +39,6 @@ import ( "github.com/edgelesssys/constellation/v2/internal/kms/uri" "github.com/edgelesssys/constellation/v2/internal/kubernetes/kubectl" "github.com/edgelesssys/constellation/v2/internal/semver" - "github.com/edgelesssys/constellation/v2/internal/versions" ) const ( @@ -87,8 +86,8 @@ type Options struct { // PrepareApply loads the charts and returns the executor to apply them. // TODO(elchead): remove validK8sVersion by putting ValidK8sVersion into config.Config, see AB#3374. -func (h Client) PrepareApply(conf *config.Config, validK8sversion versions.ValidK8sVersion, idFile clusterid.File, flags Options, tfOutput terraform.ApplyOutput, serviceAccURI string, masterSecret uri.MasterSecret) (Applier, bool, error) { - releases, err := h.loadReleases(conf, masterSecret, validK8sversion, idFile, flags, tfOutput, serviceAccURI) +func (h Client) PrepareApply(conf *config.Config, idFile clusterid.File, flags Options, tfOutput terraform.ApplyOutput, serviceAccURI string, masterSecret uri.MasterSecret) (Applier, bool, error) { + releases, err := h.loadReleases(conf, masterSecret, idFile, flags, tfOutput, serviceAccURI) if err != nil { return nil, false, fmt.Errorf("loading Helm releases: %w", err) } @@ -97,8 +96,8 @@ func (h Client) PrepareApply(conf *config.Config, validK8sversion versions.Valid return &ChartApplyExecutor{actions: actions, log: h.log}, includesUpgrades, err } -func (h Client) loadReleases(conf *config.Config, secret uri.MasterSecret, validK8sVersion versions.ValidK8sVersion, idFile clusterid.File, flags Options, tfOutput terraform.ApplyOutput, serviceAccURI string) ([]Release, error) { - helmLoader := newLoader(conf, idFile, validK8sVersion, h.cliVersion) +func (h Client) loadReleases(conf *config.Config, secret uri.MasterSecret, idFile clusterid.File, flags Options, tfOutput terraform.ApplyOutput, serviceAccURI string) ([]Release, error) { + helmLoader := newLoader(conf, idFile, h.cliVersion) h.log.Debugf("Created new Helm loader") return helmLoader.loadReleases(flags.Conformance, flags.HelmWaitMode, secret, serviceAccURI, tfOutput) diff --git a/cli/internal/helm/helm_test.go b/cli/internal/helm/helm_test.go index f813ffbacd..5a0d6208b1 100644 --- a/cli/internal/helm/helm_test.go +++ b/cli/internal/helm/helm_test.go @@ -18,7 +18,6 @@ import ( "github.com/edgelesssys/constellation/v2/internal/kms/uri" "github.com/edgelesssys/constellation/v2/internal/logger" "github.com/edgelesssys/constellation/v2/internal/semver" - "github.com/edgelesssys/constellation/v2/internal/versions" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" "helm.sh/helm/v3/pkg/action" @@ -207,7 +206,7 @@ func TestHelmApply(t *testing.T) { helmListVersion(lister, "aws-load-balancer-controller", awsLbVersion) options.AllowDestructive = tc.allowDestructive - ex, includesUpgrade, err := sut.PrepareApply(cfg, versions.ValidK8sVersion("v1.27.4"), + ex, includesUpgrade, err := sut.PrepareApply(cfg, clusterid.File{UID: "testuid", MeasurementSalt: []byte("measurementSalt")}, options, fakeTerraformOutput(csp), fakeServiceAccURI(csp), uri.MasterSecret{Key: []byte("secret"), Salt: []byte("masterSalt")}) diff --git a/cli/internal/helm/loader.go b/cli/internal/helm/loader.go index 02047ad196..006907fb2a 100644 --- a/cli/internal/helm/loader.go +++ b/cli/internal/helm/loader.go @@ -77,11 +77,12 @@ type chartLoader struct { } // newLoader creates a new ChartLoader. -func newLoader(config *config.Config, idFile clusterid.File, k8sVersion versions.ValidK8sVersion, cliVersion semver.Semver) *chartLoader { +func newLoader(config *config.Config, idFile clusterid.File, cliVersion semver.Semver) *chartLoader { // TODO(malt3): Allow overriding container image registry + prefix for all images // (e.g. for air-gapped environments). var ccmImage, cnmImage string csp := config.GetProvider() + k8sVersion := config.KubernetesVersion switch csp { case cloudprovider.AWS: ccmImage = versions.VersionConfigs[k8sVersion].CloudControllerManagerImageAWS diff --git a/cli/internal/helm/loader_test.go b/cli/internal/helm/loader_test.go index 1eb385662d..ba1122ea28 100644 --- a/cli/internal/helm/loader_test.go +++ b/cli/internal/helm/loader_test.go @@ -31,7 +31,6 @@ import ( "github.com/edgelesssys/constellation/v2/internal/config" "github.com/edgelesssys/constellation/v2/internal/kms/uri" "github.com/edgelesssys/constellation/v2/internal/semver" - "github.com/edgelesssys/constellation/v2/internal/versions" ) func fakeServiceAccURI(provider cloudprovider.Provider) string { @@ -67,9 +66,8 @@ func TestLoadReleases(t *testing.T) { assert := assert.New(t) require := require.New(t) config := &config.Config{Provider: config.ProviderConfig{GCP: &config.GCPConfig{}}} - k8sVersion := versions.ValidK8sVersion("v1.27.4") chartLoader := newLoader(config, clusterid.File{UID: "testuid", MeasurementSalt: []byte("measurementSalt")}, - k8sVersion, semver.NewFromInt(2, 10, 0, "")) + semver.NewFromInt(2, 10, 0, "")) helmReleases, err := chartLoader.loadReleases( true, WaitModeAtomic, uri.MasterSecret{Key: []byte("secret"), Salt: []byte("masterSalt")}, diff --git a/cli/internal/kubecmd/kubecmd.go b/cli/internal/kubecmd/kubecmd.go index 4d24814e22..930e4b3b5b 100644 --- a/cli/internal/kubecmd/kubecmd.go +++ b/cli/internal/kubecmd/kubecmd.go @@ -124,17 +124,13 @@ func (k *KubeCmd) UpgradeNodeVersion(ctx context.Context, conf *config.Config, f nodeVersion.Spec.ImageReference = imageReference nodeVersion.Spec.ImageVersion = imageVersion.Version() + // TODO(elchead): why? // We have to allow users to specify outdated k8s patch versions. // Therefore, this code has to skip k8s updates if a user configures an outdated (i.e. invalid) k8s version. var components *corev1.ConfigMap - currentK8sVersion, err := versions.NewValidK8sVersion(conf.KubernetesVersion, true) - if err != nil { - innerErr := fmt.Errorf("unsupported Kubernetes version, supported versions are %s", strings.Join(versions.SupportedK8sVersions(), ", ")) - err = compatibility.NewInvalidUpgradeError(nodeVersion.Spec.KubernetesClusterVersion, conf.KubernetesVersion, innerErr) - } else { - versionConfig := versions.VersionConfigs[currentK8sVersion] - components, err = k.updateK8s(&nodeVersion, versionConfig.ClusterVersion, versionConfig.KubernetesComponents, force) - } + currentK8sVersion := conf.KubernetesVersion + versionConfig := versions.VersionConfigs[currentK8sVersion] + components, err = k.updateK8s(&nodeVersion, versionConfig.ClusterVersion, versionConfig.KubernetesComponents, force) switch { case err == nil: diff --git a/cli/internal/kubecmd/kubecmd_test.go b/cli/internal/kubecmd/kubecmd_test.go index 73ec1d60e8..3677ecd14f 100644 --- a/cli/internal/kubecmd/kubecmd_test.go +++ b/cli/internal/kubecmd/kubecmd_test.go @@ -42,7 +42,7 @@ func TestUpgradeNodeVersion(t *testing.T) { currentImageVersion string newImageReference string badImageVersion string - currentClusterVersion string + currentClusterVersion versions.ValidK8sVersion conf *config.Config force bool getCRErr error @@ -55,11 +55,11 @@ func TestUpgradeNodeVersion(t *testing.T) { conf: func() *config.Config { conf := config.Default() conf.Image = "v1.2.3" - conf.KubernetesVersion = versions.SupportedK8sVersions()[1] + conf.KubernetesVersion = versions.SupportedValidK8sVersions()[1] return conf }(), currentImageVersion: "v1.2.2", - currentClusterVersion: versions.SupportedK8sVersions()[0], + currentClusterVersion: versions.SupportedValidK8sVersions()[0], kubectl: &stubKubectl{ configMaps: map[string]*corev1.ConfigMap{ constants.JoinConfigMap: newJoinConfigMap(`{"0":{"expected":"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA","warnOnly":false}}`), @@ -71,11 +71,11 @@ func TestUpgradeNodeVersion(t *testing.T) { conf: func() *config.Config { conf := config.Default() conf.Image = "v1.2.2" - conf.KubernetesVersion = versions.SupportedK8sVersions()[1] + conf.KubernetesVersion = versions.SupportedValidK8sVersions()[1] return conf }(), currentImageVersion: "v1.2.2", - currentClusterVersion: versions.SupportedK8sVersions()[0], + currentClusterVersion: versions.SupportedValidK8sVersions()[0], kubectl: &stubKubectl{ configMaps: map[string]*corev1.ConfigMap{ constants.JoinConfigMap: newJoinConfigMap(`{"0":{"expected":"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA","warnOnly":false}}`), @@ -92,11 +92,11 @@ func TestUpgradeNodeVersion(t *testing.T) { conf: func() *config.Config { conf := config.Default() conf.Image = "v1.2.3" - conf.KubernetesVersion = versions.SupportedK8sVersions()[0] + conf.KubernetesVersion = versions.SupportedValidK8sVersions()[0] return conf }(), currentImageVersion: "v1.2.2", - currentClusterVersion: versions.SupportedK8sVersions()[0], + currentClusterVersion: versions.SupportedValidK8sVersions()[0], kubectl: &stubKubectl{ configMaps: map[string]*corev1.ConfigMap{ constants.JoinConfigMap: newJoinConfigMap(`{"0":{"expected":"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA","warnOnly":false}}`), @@ -113,11 +113,11 @@ func TestUpgradeNodeVersion(t *testing.T) { conf: func() *config.Config { conf := config.Default() conf.Image = "v1.2.2" - conf.KubernetesVersion = versions.SupportedK8sVersions()[0] + conf.KubernetesVersion = versions.SupportedValidK8sVersions()[0] return conf }(), currentImageVersion: "v1.2.2", - currentClusterVersion: versions.SupportedK8sVersions()[0], + currentClusterVersion: versions.SupportedValidK8sVersions()[0], kubectl: &stubKubectl{}, wantErr: true, assertCorrectError: func(t *testing.T, err error) bool { @@ -129,7 +129,7 @@ func TestUpgradeNodeVersion(t *testing.T) { conf: func() *config.Config { conf := config.Default() conf.Image = "v1.2.3" - conf.KubernetesVersion = versions.SupportedK8sVersions()[1] + conf.KubernetesVersion = versions.SupportedValidK8sVersions()[1] return conf }(), conditions: []metav1.Condition{{ @@ -137,7 +137,7 @@ func TestUpgradeNodeVersion(t *testing.T) { Status: metav1.ConditionTrue, }}, currentImageVersion: "v1.2.2", - currentClusterVersion: versions.SupportedK8sVersions()[0], + currentClusterVersion: versions.SupportedValidK8sVersions()[0], kubectl: &stubKubectl{}, wantErr: true, assertCorrectError: func(t *testing.T, err error) bool { @@ -148,7 +148,7 @@ func TestUpgradeNodeVersion(t *testing.T) { conf: func() *config.Config { conf := config.Default() conf.Image = "v1.2.3" - conf.KubernetesVersion = versions.SupportedK8sVersions()[1] + conf.KubernetesVersion = versions.SupportedValidK8sVersions()[1] return conf }(), conditions: []metav1.Condition{{ @@ -156,7 +156,7 @@ func TestUpgradeNodeVersion(t *testing.T) { Status: metav1.ConditionTrue, }}, currentImageVersion: "v1.2.2", - currentClusterVersion: versions.SupportedK8sVersions()[0], + currentClusterVersion: versions.SupportedValidK8sVersions()[0], kubectl: &stubKubectl{}, force: true, wantUpdate: true, @@ -165,11 +165,11 @@ func TestUpgradeNodeVersion(t *testing.T) { conf: func() *config.Config { conf := config.Default() conf.Image = "v1.2.3" - conf.KubernetesVersion = versions.SupportedK8sVersions()[1] + conf.KubernetesVersion = versions.SupportedValidK8sVersions()[1] return conf }(), currentImageVersion: "v1.2.2", - currentClusterVersion: versions.SupportedK8sVersions()[0], + currentClusterVersion: versions.SupportedValidK8sVersions()[0], kubectl: &stubKubectl{ configMaps: map[string]*corev1.ConfigMap{ constants.JoinConfigMap: newJoinConfigMap(`{"0":{"expected":"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA","warnOnly":false}}`), @@ -185,12 +185,12 @@ func TestUpgradeNodeVersion(t *testing.T) { conf: func() *config.Config { conf := config.Default() conf.Image = "v1.4.2" - conf.KubernetesVersion = versions.SupportedK8sVersions()[1] + conf.KubernetesVersion = versions.SupportedValidK8sVersions()[1] return conf }(), newImageReference: "path/to/image:v1.4.2", currentImageVersion: "v1.2.2", - currentClusterVersion: versions.SupportedK8sVersions()[0], + currentClusterVersion: versions.SupportedValidK8sVersions()[0], kubectl: &stubKubectl{ configMaps: map[string]*corev1.ConfigMap{ constants.JoinConfigMap: newJoinConfigMap(`{"0":{"expected":"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA","warnOnly":true}}`), @@ -207,12 +207,12 @@ func TestUpgradeNodeVersion(t *testing.T) { conf: func() *config.Config { conf := config.Default() conf.Image = "v1.4.2" - conf.KubernetesVersion = versions.SupportedK8sVersions()[1] + conf.KubernetesVersion = versions.SupportedValidK8sVersions()[1] return conf }(), newImageReference: "path/to/image:v1.4.2", currentImageVersion: "v1.2.2", - currentClusterVersion: versions.SupportedK8sVersions()[0], + currentClusterVersion: versions.SupportedValidK8sVersions()[0], kubectl: &stubKubectl{ configMaps: map[string]*corev1.ConfigMap{ constants.JoinConfigMap: newJoinConfigMap(`{"0":{"expected":"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA","warnOnly":false}}`), @@ -225,11 +225,11 @@ func TestUpgradeNodeVersion(t *testing.T) { conf: func() *config.Config { conf := config.Default() conf.Image = "v1.2.3" - conf.KubernetesVersion = versions.SupportedK8sVersions()[1] + conf.KubernetesVersion = versions.SupportedValidK8sVersions()[1] return conf }(), currentImageVersion: "v1.2.2", - currentClusterVersion: versions.SupportedK8sVersions()[0], + currentClusterVersion: versions.SupportedValidK8sVersions()[0], badImageVersion: "v3.2.1", kubectl: &stubKubectl{ configMaps: map[string]*corev1.ConfigMap{ @@ -251,7 +251,7 @@ func TestUpgradeNodeVersion(t *testing.T) { return conf }(), currentImageVersion: "v1.2.2", - currentClusterVersion: versions.SupportedK8sVersions()[0], + currentClusterVersion: versions.SupportedValidK8sVersions()[0], kubectl: &stubKubectl{ configMaps: map[string]*corev1.ConfigMap{ constants.JoinConfigMap: newJoinConfigMap(`{"0":{"expected":"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA","warnOnly":false}}`), @@ -268,11 +268,11 @@ func TestUpgradeNodeVersion(t *testing.T) { conf: func() *config.Config { conf := config.Default() conf.Image = "v1.2.3" - conf.KubernetesVersion = versions.SupportedK8sVersions()[1] + conf.KubernetesVersion = versions.SupportedValidK8sVersions()[1] return conf }(), currentImageVersion: "v1.2.2", - currentClusterVersion: versions.SupportedK8sVersions()[0], + currentClusterVersion: versions.SupportedValidK8sVersions()[0], kubectl: &stubKubectl{ configMaps: map[string]*corev1.ConfigMap{ constants.JoinConfigMap: newJoinConfigMap(`{"0":{"expected":"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA","warnOnly":false}}`), @@ -297,7 +297,7 @@ func TestUpgradeNodeVersion(t *testing.T) { nodeVersion := updatev1alpha1.NodeVersion{ Spec: updatev1alpha1.NodeVersionSpec{ ImageVersion: tc.currentImageVersion, - KubernetesClusterVersion: tc.currentClusterVersion, + KubernetesClusterVersion: string(tc.currentClusterVersion), }, Status: updatev1alpha1.NodeVersionStatus{ Conditions: tc.conditions, diff --git a/docs/docs/reference/cli.md b/docs/docs/reference/cli.md index 56a3344b3f..90ad1d9540 100644 --- a/docs/docs/reference/cli.md +++ b/docs/docs/reference/cli.md @@ -79,7 +79,7 @@ constellation config generate {aws|azure|gcp|openstack|qemu|stackit} [flags] ``` -a, --attestation string attestation variant to use {aws-sev-snp|aws-nitro-tpm|azure-sev-snp|azure-trustedlaunch|gcp-sev-es|qemu-vtpm}. If not specified, the default for the cloud provider is used -h, --help help for generate - -k, --kubernetes string Kubernetes version to use in format MAJOR.MINOR (default "v1.27") + -k, --kubernetes string Kubernetes version to use in format MAJOR.MINOR (default "v1.27.4") ``` ### Options inherited from parent commands diff --git a/e2e/internal/upgrade/upgrade_test.go b/e2e/internal/upgrade/upgrade_test.go index 1c5521767b..c33346bc54 100644 --- a/e2e/internal/upgrade/upgrade_test.go +++ b/e2e/internal/upgrade/upgrade_test.go @@ -273,12 +273,11 @@ func writeUpgradeConfig(require *require.Assertions, image string, kubernetes st cfg.Image = image defaultConfig := config.Default() - var kubernetesVersion semver.Semver + var kubernetesVersion versions.ValidK8sVersion if kubernetes == "" { - kubernetesVersion, err = semver.New(defaultConfig.KubernetesVersion) - require.NoError(err) + kubernetesVersion = defaultConfig.KubernetesVersion } else { - kubernetesVersion, err = semver.New(kubernetes) + kubernetesVersion, err = versions.NewValidK8sVersion(kubernetes, true) require.NoError(err) } @@ -291,8 +290,8 @@ func writeUpgradeConfig(require *require.Assertions, image string, kubernetes st microserviceVersion = version } - log.Printf("Setting K8s version: %s\n", kubernetesVersion.String()) - cfg.KubernetesVersion = kubernetesVersion.String() + log.Printf("Setting K8s version: %s\n", kubernetesVersion) + cfg.KubernetesVersion = kubernetesVersion log.Printf("Setting microservice version: %s\n", microserviceVersion) cfg.MicroserviceVersion = microserviceVersion @@ -432,13 +431,13 @@ func testNodesEventuallyHaveVersion(t *testing.T, k *kubernetes.Clientset, targe } kubeletVersion := node.Status.NodeInfo.KubeletVersion - if kubeletVersion != targetVersions.kubernetes.String() { - log.Printf("\t%s: K8s (Kubelet) %s, want %s\n", node.Name, kubeletVersion, targetVersions.kubernetes.String()) + if kubeletVersion != string(targetVersions.kubernetes) { + log.Printf("\t%s: K8s (Kubelet) %s, want %s\n", node.Name, kubeletVersion, targetVersions.kubernetes) allUpdated = false } kubeProxyVersion := node.Status.NodeInfo.KubeProxyVersion - if kubeProxyVersion != targetVersions.kubernetes.String() { - log.Printf("\t%s: K8s (Proxy) %s, want %s\n", node.Name, kubeProxyVersion, targetVersions.kubernetes.String()) + if kubeProxyVersion != string(targetVersions.kubernetes) { + log.Printf("\t%s: K8s (Proxy) %s, want %s\n", node.Name, kubeProxyVersion, targetVersions.kubernetes) allUpdated = false } } @@ -449,7 +448,7 @@ func testNodesEventuallyHaveVersion(t *testing.T, k *kubernetes.Clientset, targe type versionContainer struct { imageRef string - kubernetes semver.Semver + kubernetes versions.ValidK8sVersion microservices semver.Semver } diff --git a/internal/config/BUILD.bazel b/internal/config/BUILD.bazel index 4385f89a44..d17feaf6d1 100644 --- a/internal/config/BUILD.bazel +++ b/internal/config/BUILD.bazel @@ -62,7 +62,6 @@ go_test( "//internal/constants", "//internal/file", "//internal/semver", - "//internal/versions", "@com_github_go_playground_locales//en", "@com_github_go_playground_universal_translator//:universal-translator", "@com_github_go_playground_validator_v10//:validator", diff --git a/internal/config/config.go b/internal/config/config.go index 539e6f9d6c..fbcbac680e 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -315,7 +315,7 @@ func Default() *Config { Image: defaultImage, Name: defaultName, MicroserviceVersion: constants.BinaryVersion(), - KubernetesVersion: string(versions.Default), + KubernetesVersion: versions.Default, DebugCluster: toPtr(false), Provider: ProviderConfig{ AWS: &AWSConfig{ diff --git a/internal/config/config_doc.go b/internal/config/config_doc.go index 6a69084ea3..55bf0b8e19 100644 --- a/internal/config/config_doc.go +++ b/internal/config/config_doc.go @@ -52,7 +52,7 @@ func init() { ConfigDoc.Fields[2].Description = "Name of the cluster." ConfigDoc.Fields[2].Comments[encoder.LineComment] = "Name of the cluster." ConfigDoc.Fields[3].Name = "kubernetesVersion" - ConfigDoc.Fields[3].Type = "string" + ConfigDoc.Fields[3].Type = "ValidK8sVersion" ConfigDoc.Fields[3].Note = "" ConfigDoc.Fields[3].Description = "Kubernetes version to be installed into the cluster." ConfigDoc.Fields[3].Comments[encoder.LineComment] = "Kubernetes version to be installed into the cluster." diff --git a/internal/config/config_test.go b/internal/config/config_test.go index c066b92cf8..701fbce551 100644 --- a/internal/config/config_test.go +++ b/internal/config/config_test.go @@ -29,7 +29,6 @@ import ( "github.com/edgelesssys/constellation/v2/internal/constants" "github.com/edgelesssys/constellation/v2/internal/file" "github.com/edgelesssys/constellation/v2/internal/semver" - "github.com/edgelesssys/constellation/v2/internal/versions" ) func TestMain(m *testing.M) { @@ -249,16 +248,17 @@ func TestFromFile(t *testing.T) { configName: "wrong-name.yaml", wantErr: true, }, - "custom config from default file": { - config: &Config{ - Version: Version4, - }, - configName: constants.ConfigFilename, - wantResult: &Config{ - Version: Version4, - NodeGroups: map[string]NodeGroup{}, - }, - }, + // TODO why do we support an empty file? + //"custom config from default file": { + // config: &Config{ + // Version: Version4, + // }, + // configName: constants.ConfigFilename, + // wantResult: &Config{ + // Version: Version4, + // NodeGroups: map[string]NodeGroup{}, + // }, + //}, "modify default config": { config: func() *Config { conf := Default() @@ -321,19 +321,6 @@ func TestValidate(t *testing.T) { wantErr: true, wantErrCount: defaultErrCount, }, - "outdated k8s patch version is allowed": { - cnf: func() *Config { - cnf := Default() - cnf.Image = "" - ver, err := semver.New(versions.SupportedK8sVersions()[0]) - require.NoError(t, err) - ver = semver.NewFromInt(ver.Major(), ver.Minor(), ver.Patch()-1, "") - cnf.KubernetesVersion = ver.String() - return cnf - }(), - wantErr: true, - wantErrCount: defaultErrCount, - }, "microservices violate version drift": { cnf: func() *Config { cnf := Default() diff --git a/internal/config/migration/BUILD.bazel b/internal/config/migration/BUILD.bazel index 76c70f4230..a321caeba5 100644 --- a/internal/config/migration/BUILD.bazel +++ b/internal/config/migration/BUILD.bazel @@ -12,5 +12,6 @@ go_library( "//internal/file", "//internal/role", "//internal/semver", + "//internal/versions", ], ) diff --git a/internal/config/migration/migration.go b/internal/config/migration/migration.go index a79be8f25d..36aae39200 100644 --- a/internal/config/migration/migration.go +++ b/internal/config/migration/migration.go @@ -21,6 +21,7 @@ import ( "github.com/edgelesssys/constellation/v2/internal/file" "github.com/edgelesssys/constellation/v2/internal/role" "github.com/edgelesssys/constellation/v2/internal/semver" + "github.com/edgelesssys/constellation/v2/internal/versions" ) const ( @@ -335,7 +336,7 @@ func V3ToV4(path string, fileHandler file.Handler) error { cfgV4.Version = config.Version4 cfgV4.Image = cfgV3.Image cfgV4.Name = cfgV3.Name - cfgV4.KubernetesVersion = cfgV3.KubernetesVersion + cfgV4.KubernetesVersion = versions.ValidK8sVersion(cfgV3.KubernetesVersion) cfgV4.MicroserviceVersion = cfgV3.MicroserviceVersion cfgV4.DebugCluster = cfgV3.DebugCluster diff --git a/internal/config/validation.go b/internal/config/validation.go index 3f82e81e74..54289dee0f 100644 --- a/internal/config/validation.go +++ b/internal/config/validation.go @@ -637,22 +637,6 @@ func (c *Config) validateK8sVersion(fl validator.FieldLevel) bool { return err == nil } -// 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_26)): - return string(versions.V1_26) - case semver.MajorMinor(string(versions.V1_27)): - return string(versions.V1_27) - case semver.MajorMinor(string(versions.V1_28)): - return string(versions.V1_28) - default: - return "" - } -} - func registerImageCompatibilityError(ut ut.Translator) error { return ut.Add("image_compatibility", "{0} specifies an invalid version: {1}", true) } diff --git a/internal/versions/versions.go b/internal/versions/versions.go index b3f33f23b3..bbce5194d4 100644 --- a/internal/versions/versions.go +++ b/internal/versions/versions.go @@ -36,6 +36,14 @@ func SupportedK8sVersions() []string { return validVersionsSorted } +// SupportedValidK8sVersions returns a typed list of supported Kubernetes versions. +func SupportedValidK8sVersions() (res []ValidK8sVersion) { + for _, v := range SupportedK8sVersions() { + res = append(res, ValidK8sVersion(v)) + } + return +} + // ValidK8sVersion represents any of the three currently supported k8s versions. type ValidK8sVersion string @@ -44,21 +52,91 @@ type ValidK8sVersion string // strict controls whether the patch version is checked or not. // If strict is false, the patch version is ignored and the returned // ValidK8sVersion is a supported patch version for the given major.minor version. +// TODO(elchead): only allow strict mode? func NewValidK8sVersion(k8sVersion string, strict bool) (ValidK8sVersion, error) { + prefixedVersion := compatibility.EnsurePrefixV(k8sVersion) + parsedVersion, err := resolveK8sPatchVersion(prefixedVersion) + if err != nil { + return "", fmt.Errorf("resolving kubernetes patch version from flag: %w", err) + } + fmt.Println(parsedVersion) var supported bool if strict { - supported = isSupportedK8sVersionStrict(k8sVersion) + supported = isSupportedK8sVersionStrict(parsedVersion) } else { - supported = isSupportedK8sVersion(k8sVersion) + supported = isSupportedK8sVersion(parsedVersion) } if !supported { - return "", fmt.Errorf("invalid Kubernetes version: %s; supported versions are %v", k8sVersion, SupportedK8sVersions()) + return "", fmt.Errorf("invalid Kubernetes version: %s; supported versions are %v", parsedVersion, SupportedK8sVersions()) } if !strict { - k8sVersion, _ = supportedVersionForMajorMinor(k8sVersion) + parsedVersion, _ = supportedVersionForMajorMinor(parsedVersion) + } + + return ValidK8sVersion(parsedVersion), nil +} + +// UnmarshalYAML implements the yaml.Unmarshaler interface. +func (v *ValidK8sVersion) UnmarshalYAML(unmarshal func(interface{}) error) error { + var version string + if err := unmarshal(&version); err != nil { + return err + } + valid, err := NewValidK8sVersion(version, true) + if err != nil { + return fmt.Errorf("unsupported Kubernetes version, supported versions are %s", strings.Join(SupportedK8sVersions(), ", ")) + } + *v = valid + return nil +} + +// resolveK8sPatchVersion takes the user input from --kubernetes and transforms a MAJOR.MINOR definition into a supported +// MAJOR.MINOR.PATCH release. +// TODO(elchead): should we support this resolvement also for the config? +func resolveK8sPatchVersion(k8sVersion string) (string, error) { + if !semver.IsValid(k8sVersion) { + return "", fmt.Errorf("kubernetes flag does not specify a valid semantic version: %s", k8sVersion) + } + + if semver.MajorMinor(k8sVersion) != k8sVersion { + // patch version is specified + return k8sVersion, nil } + extendedVersion := K8sVersionFromMajorMinor(k8sVersion) + if extendedVersion == "" { + return "", fmt.Errorf("--kubernetes (%s) does not specify a valid Kubernetes version. Supported versions: %s", strings.TrimPrefix(k8sVersion, "v"), supportedVersions()) + } + + return extendedVersion, nil +} - return ValidK8sVersion(k8sVersion), nil +// 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(V1_26)): + return string(V1_26) + case semver.MajorMinor(string(V1_27)): + return string(V1_27) + case semver.MajorMinor(string(V1_28)): + return string(V1_28) + default: + return "" + } +} + +// 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 SupportedK8sVersions() { + if i > 0 { + builder.WriteString(" ") + } + builder.WriteString(strings.TrimPrefix(semver.MajorMinor(version), "v")) + } + return builder.String() } // IsSupportedK8sVersion checks if a given Kubernetes minor version is supported by Constellation.