cli: store kubernetes version as strong type in config (#2287)

Co-authored-by: Otto Bittner <cobittner@posteo.net>
Co-authored-by: 3u13r <lc@edgeless.systems>
This commit is contained in:
Adrian Stobbe 2023-09-19 13:50:00 +02:00 committed by GitHub
parent 348418a4a1
commit 22c2a73ae2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 293 additions and 182 deletions

View File

@ -13,7 +13,6 @@ 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"
@ -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", semver.MajorMinor(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,15 @@ 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.ResolveK8sPatchVersion(k8sVersion)
if err != nil {
return generateFlags{}, fmt.Errorf("resolving kuberentes version from flag: %w", err)
return generateFlags{}, fmt.Errorf("resolving kubernetes patch version from flag: %w", err)
}
validK8sVersion, err := versions.NewValidK8sVersion(resolvedVersion, true)
if err != nil {
return generateFlags{}, fmt.Errorf("resolving Kubernetes version from flag: %w", err)
}
attestationString, err := cmd.Flags().GetString("attestation")
@ -168,7 +158,7 @@ func parseGenerateFlags(cmd *cobra.Command) (generateFlags, error) {
}
return generateFlags{
pf: pathprefix.New(workDir),
k8sVersion: resolvedVersion,
k8sVersion: validK8sVersion,
attestationVariant: attestationVariant,
}, nil
}
@ -184,22 +174,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), "|"))
}

View File

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

View File

@ -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"
@ -153,6 +152,11 @@ func (i *initCmd) initialize(
if err != nil {
return err
}
// cfg validation does not check k8s patch version since upgrade may accept an outdated patch version.
k8sVersion, err := versions.NewValidK8sVersion(string(conf.KubernetesVersion), true)
if err != nil {
return err
}
if !flags.force {
if err := validateCLIandConstellationVersionAreEqual(constants.BinaryVersion(), conf.Image, conf.MicroserviceVersion); err != nil {
return err
@ -168,12 +172,6 @@ func (i *initCmd) initialize(
return fmt.Errorf("reading cluster ID file: %w", err)
}
// 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
}
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 +273,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 +627,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 {

View File

@ -38,6 +38,7 @@ import (
"github.com/edgelesssys/constellation/v2/internal/kms/uri"
"github.com/edgelesssys/constellation/v2/internal/license"
"github.com/edgelesssys/constellation/v2/internal/logger"
"github.com/edgelesssys/constellation/v2/internal/semver"
"github.com/edgelesssys/constellation/v2/internal/versions"
"github.com/spf13/afero"
"github.com/stretchr/testify/assert"
@ -73,7 +74,6 @@ func TestInitialize(t *testing.T) {
ClusterId: []byte("clusterID"),
}
serviceAccPath := "/test/service-account.json"
someErr := errors.New("failed")
testCases := map[string]struct {
provider cloudprovider.Provider
@ -105,7 +105,7 @@ func TestInitialize(t *testing.T) {
"non retriable error": {
provider: cloudprovider.QEMU,
idFile: &clusterid.File{IP: "192.0.2.1"},
initServerAPI: &stubInitServer{initErr: &nonRetriableError{someErr}},
initServerAPI: &stubInitServer{initErr: &nonRetriableError{assert.AnError}},
retriable: false,
masterSecretShouldExist: true,
wantErr: true,
@ -125,7 +125,7 @@ func TestInitialize(t *testing.T) {
"init call fails": {
provider: cloudprovider.GCP,
idFile: &clusterid.File{IP: "192.0.2.1"},
initServerAPI: &stubInitServer{initErr: someErr},
initServerAPI: &stubInitServer{initErr: assert.AnError},
retriable: true,
wantErr: true,
},
@ -133,7 +133,24 @@ 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)
require.NoError(t, err)
c.KubernetesVersion = res
},
},
"outdated k8s patch version doesn't work": {
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) {
v, err := semver.New(versions.SupportedK8sVersions()[0])
require.NoError(t, err)
outdatedPatchVer := semver.NewFromInt(v.Major(), v.Minor(), v.Patch()-1, "").String()
c.KubernetesVersion = versions.ValidK8sVersion(outdatedPatchVer)
},
wantErr: true,
retriable: true, // doesn't need to show retriable error message
},
}
@ -222,7 +239,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
}

View File

@ -166,7 +166,7 @@ func (u *upgradeApplyCmd) upgradeApply(cmd *cobra.Command, upgradeDir string, fl
}
}
}
validK8sVersion, err := validK8sVersion(cmd, conf.KubernetesVersion, flags.yes)
conf.KubernetesVersion, err = validK8sVersion(cmd, string(conf.KubernetesVersion), flags.yes)
if err != nil {
return err
}
@ -223,7 +223,7 @@ func (u *upgradeApplyCmd) upgradeApply(cmd *cobra.Command, upgradeDir string, fl
var upgradeErr *compatibility.InvalidUpgradeError
if !flags.skipPhases.contains(skipHelmPhase) {
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)
@ -405,7 +405,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 {
@ -423,7 +423,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 {

View File

@ -23,6 +23,7 @@ import (
"github.com/edgelesssys/constellation/v2/internal/file"
"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/spf13/afero"
"github.com/stretchr/testify/assert"
@ -37,6 +38,7 @@ func TestUpgradeApply(t *testing.T) {
kubeUpgrader *stubKubernetesUpgrader
terraformUpgrader clusterUpgrader
wantErr bool
customK8sVersion string
flags upgradeApplyFlags
stdin string
}{
@ -113,6 +115,30 @@ func TestUpgradeApply(t *testing.T) {
wantErr: true,
flags: upgradeApplyFlags{yes: true},
},
"outdated K8s patch version": {
kubeUpgrader: &stubKubernetesUpgrader{
currentConfig: config.DefaultForAzureSEVSNP(),
},
helmUpgrader: stubApplier{},
terraformUpgrader: &stubTerraformUpgrader{},
customK8sVersion: func() string {
v, err := semver.New(versions.SupportedK8sVersions()[0])
require.NoError(t, err)
return semver.NewFromInt(v.Major(), v.Minor(), v.Patch()-1, "").String()
}(),
flags: upgradeApplyFlags{yes: true},
wantErr: false,
},
"outdated K8s version": {
kubeUpgrader: &stubKubernetesUpgrader{
currentConfig: config.DefaultForAzureSEVSNP(),
},
helmUpgrader: stubApplier{},
terraformUpgrader: &stubTerraformUpgrader{},
customK8sVersion: "v1.20.0",
flags: upgradeApplyFlags{yes: true},
wantErr: true,
},
"skip all upgrade phases": {
kubeUpgrader: &stubKubernetesUpgrader{
currentConfig: config.DefaultForAzureSEVSNP(),
@ -147,7 +173,9 @@ func TestUpgradeApply(t *testing.T) {
handler := file.NewHandler(afero.NewMemMapFs())
cfg := defaultConfigWithExpectedMeasurements(t, config.Default(), cloudprovider.Azure)
if tc.customK8sVersion != "" {
cfg.KubernetesVersion = versions.ValidK8sVersion(tc.customK8sVersion)
}
require.NoError(handler.WriteYAML(constants.ConfigFilename, cfg))
require.NoError(handler.WriteJSON(constants.ClusterIDsFilename, clusterid.File{MeasurementSalt: []byte("measurementSalt")}))
require.NoError(handler.WriteJSON(constants.MasterSecretFilename, uri.MasterSecret{}))
@ -270,7 +298,7 @@ type mockApplier struct {
mock.Mock
}
func (m *mockApplier) PrepareApply(cfg *config.Config, k8sVersion versions.ValidK8sVersion, clusterID clusterid.File, helmOpts helm.Options, terraformOut terraform.ApplyOutput, str string, masterSecret uri.MasterSecret) (helm.Applier, bool, error) {
args := m.Called(cfg, k8sVersion, clusterID, helmOpts, terraformOut, str, masterSecret)
func (m *mockApplier) PrepareApply(cfg *config.Config, clusterID clusterid.File, helmOpts helm.Options, terraformOut terraform.ApplyOutput, str string, masterSecret uri.MasterSecret) (helm.Applier, bool, error) {
args := m.Called(cfg, clusterID, helmOpts, terraformOut, str, masterSecret)
return args.Get(0).(helm.Applier), args.Bool(1), args.Error(2)
}

View File

@ -583,7 +583,11 @@ func (v *versionUpgrade) writeConfig(conf *config.Config, fileHandler file.Handl
conf.MicroserviceVersion = v.newServices
}
if len(v.newKubernetes) > 0 {
conf.KubernetesVersion = v.newKubernetes[0]
var err error
conf.KubernetesVersion, err = versions.NewValidK8sVersion(v.newKubernetes[0], true)
if err != nil {
return fmt.Errorf("parsing Kubernetes version: %w", err)
}
}
if len(v.newImages) > 0 {
imageUpgrade := sortedMapKeys(v.newImages)[0]

View File

@ -473,7 +473,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",

View File

@ -40,7 +40,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,11 +86,10 @@ 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,
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, validK8sversion, idFile, flags, tfOutput, serviceAccURI)
releases, err := h.loadReleases(conf, masterSecret, idFile, flags, tfOutput, serviceAccURI)
if err != nil {
return nil, false, fmt.Errorf("loading Helm releases: %w", err)
}
@ -100,11 +98,10 @@ func (h Client) PrepareApply(
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,
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, validK8sVersion, h.cliVersion)
helmLoader := newLoader(conf, idFile, h.cliVersion)
h.log.Debugf("Created new Helm loader")
return helmLoader.loadReleases(flags.Conformance, flags.HelmWaitMode, secret,
serviceAccURI, tfOutput)

View File

@ -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"
@ -208,7 +207,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")})

View File

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

View File

@ -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")},

View File

@ -138,13 +138,21 @@ func (k *KubeCmd) UpgradeNodeVersion(ctx context.Context, conf *config.Config, f
// 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)
_, err = versions.NewValidK8sVersion(string(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)
innerErr := fmt.Errorf("unsupported Kubernetes version, supported versions are %s",
strings.Join(versions.SupportedK8sVersions(), ", "))
err = compatibility.NewInvalidUpgradeError(nodeVersion.Spec.KubernetesClusterVersion,
string(conf.KubernetesVersion), innerErr)
} else {
versionConfig := versions.VersionConfigs[currentK8sVersion]
components, err = k.updateK8s(&nodeVersion, versionConfig.ClusterVersion, versionConfig.KubernetesComponents, force)
versionConfig, ok := versions.VersionConfigs[conf.KubernetesVersion]
if !ok {
err = compatibility.NewInvalidUpgradeError(nodeVersion.Spec.KubernetesClusterVersion,
string(conf.KubernetesVersion), fmt.Errorf("no version config matching K8s %s", conf.KubernetesVersion))
} else {
components, err = k.prepareUpdateK8s(&nodeVersion, versionConfig.ClusterVersion,
versionConfig.KubernetesComponents, force)
}
}
switch {
@ -159,7 +167,6 @@ func (k *KubeCmd) UpgradeNodeVersion(ctx context.Context, conf *config.Config, f
return fmt.Errorf("updating Kubernetes version: %w", err)
}
}
if len(upgradeErrs) == 2 {
return errors.Join(upgradeErrs...)
}
@ -421,7 +428,7 @@ func (k *KubeCmd) isValidImageUpgrade(nodeVersion updatev1alpha1.NodeVersion, ne
return nil
}
func (k *KubeCmd) updateK8s(nodeVersion *updatev1alpha1.NodeVersion, newClusterVersion string, components components.Components, force bool) (*corev1.ConfigMap, error) {
func (k *KubeCmd) prepareUpdateK8s(nodeVersion *updatev1alpha1.NodeVersion, newClusterVersion string, components components.Components, force bool) (*corev1.ConfigMap, error) {
configMap, err := internalk8s.ConstructK8sComponentsCM(components, newClusterVersion)
if err != nil {
return nil, fmt.Errorf("constructing k8s-components ConfigMap: %w", err)

View File

@ -43,7 +43,7 @@ func TestUpgradeNodeVersion(t *testing.T) {
currentImageVersion string
newImageReference string
badImageVersion string
currentClusterVersion string
currentClusterVersion versions.ValidK8sVersion
conf *config.Config
force bool
getCRErr error
@ -56,11 +56,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 = supportedValidK8sVersions()[1]
return conf
}(),
currentImageVersion: "v1.2.2",
currentClusterVersion: versions.SupportedK8sVersions()[0],
currentClusterVersion: supportedValidK8sVersions()[0],
kubectl: &stubKubectl{
configMaps: map[string]*corev1.ConfigMap{
constants.JoinConfigMap: newJoinConfigMap(`{"0":{"expected":"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA","warnOnly":false}}`),
@ -72,11 +72,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 = supportedValidK8sVersions()[1]
return conf
}(),
currentImageVersion: "v1.2.2",
currentClusterVersion: versions.SupportedK8sVersions()[0],
currentClusterVersion: supportedValidK8sVersions()[0],
kubectl: &stubKubectl{
configMaps: map[string]*corev1.ConfigMap{
constants.JoinConfigMap: newJoinConfigMap(`{"0":{"expected":"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA","warnOnly":false}}`),
@ -93,11 +93,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 = supportedValidK8sVersions()[0]
return conf
}(),
currentImageVersion: "v1.2.2",
currentClusterVersion: versions.SupportedK8sVersions()[0],
currentClusterVersion: supportedValidK8sVersions()[0],
kubectl: &stubKubectl{
configMaps: map[string]*corev1.ConfigMap{
constants.JoinConfigMap: newJoinConfigMap(`{"0":{"expected":"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA","warnOnly":false}}`),
@ -114,11 +114,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 = supportedValidK8sVersions()[0]
return conf
}(),
currentImageVersion: "v1.2.2",
currentClusterVersion: versions.SupportedK8sVersions()[0],
currentClusterVersion: supportedValidK8sVersions()[0],
kubectl: &stubKubectl{},
wantErr: true,
assertCorrectError: func(t *testing.T, err error) bool {
@ -130,7 +130,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 = supportedValidK8sVersions()[1]
return conf
}(),
conditions: []metav1.Condition{{
@ -138,7 +138,7 @@ func TestUpgradeNodeVersion(t *testing.T) {
Status: metav1.ConditionTrue,
}},
currentImageVersion: "v1.2.2",
currentClusterVersion: versions.SupportedK8sVersions()[0],
currentClusterVersion: supportedValidK8sVersions()[0],
kubectl: &stubKubectl{},
wantErr: true,
assertCorrectError: func(t *testing.T, err error) bool {
@ -149,7 +149,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 = supportedValidK8sVersions()[1]
return conf
}(),
conditions: []metav1.Condition{{
@ -157,7 +157,7 @@ func TestUpgradeNodeVersion(t *testing.T) {
Status: metav1.ConditionTrue,
}},
currentImageVersion: "v1.2.2",
currentClusterVersion: versions.SupportedK8sVersions()[0],
currentClusterVersion: supportedValidK8sVersions()[0],
kubectl: &stubKubectl{},
force: true,
wantUpdate: true,
@ -166,11 +166,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 = supportedValidK8sVersions()[1]
return conf
}(),
currentImageVersion: "v1.2.2",
currentClusterVersion: versions.SupportedK8sVersions()[0],
currentClusterVersion: supportedValidK8sVersions()[0],
kubectl: &stubKubectl{
configMaps: map[string]*corev1.ConfigMap{
constants.JoinConfigMap: newJoinConfigMap(`{"0":{"expected":"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA","warnOnly":false}}`),
@ -186,12 +186,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 = supportedValidK8sVersions()[1]
return conf
}(),
newImageReference: "path/to/image:v1.4.2",
currentImageVersion: "v1.2.2",
currentClusterVersion: versions.SupportedK8sVersions()[0],
currentClusterVersion: supportedValidK8sVersions()[0],
kubectl: &stubKubectl{
configMaps: map[string]*corev1.ConfigMap{
constants.JoinConfigMap: newJoinConfigMap(`{"0":{"expected":"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA","warnOnly":true}}`),
@ -208,12 +208,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 = supportedValidK8sVersions()[1]
return conf
}(),
newImageReference: "path/to/image:v1.4.2",
currentImageVersion: "v1.2.2",
currentClusterVersion: versions.SupportedK8sVersions()[0],
currentClusterVersion: supportedValidK8sVersions()[0],
kubectl: &stubKubectl{
configMaps: map[string]*corev1.ConfigMap{
constants.JoinConfigMap: newJoinConfigMap(`{"0":{"expected":"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA","warnOnly":false}}`),
@ -226,11 +226,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 = supportedValidK8sVersions()[1]
return conf
}(),
currentImageVersion: "v1.2.2",
currentClusterVersion: versions.SupportedK8sVersions()[0],
currentClusterVersion: supportedValidK8sVersions()[0],
badImageVersion: "v3.2.1",
kubectl: &stubKubectl{
configMaps: map[string]*corev1.ConfigMap{
@ -252,7 +252,7 @@ func TestUpgradeNodeVersion(t *testing.T) {
return conf
}(),
currentImageVersion: "v1.2.2",
currentClusterVersion: versions.SupportedK8sVersions()[0],
currentClusterVersion: supportedValidK8sVersions()[0],
kubectl: &stubKubectl{
configMaps: map[string]*corev1.ConfigMap{
constants.JoinConfigMap: newJoinConfigMap(`{"0":{"expected":"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA","warnOnly":false}}`),
@ -269,11 +269,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 = supportedValidK8sVersions()[1]
return conf
}(),
currentImageVersion: "v1.2.2",
currentClusterVersion: versions.SupportedK8sVersions()[0],
currentClusterVersion: supportedValidK8sVersions()[0],
kubectl: &stubKubectl{
configMaps: map[string]*corev1.ConfigMap{
constants.JoinConfigMap: newJoinConfigMap(`{"0":{"expected":"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA","warnOnly":false}}`),
@ -298,7 +298,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,
@ -444,7 +444,7 @@ func TestUpdateK8s(t *testing.T) {
},
}
_, err := upgrader.updateK8s(&nodeVersion, tc.newClusterVersion, components.Components{}, false)
_, err := upgrader.prepareUpdateK8s(&nodeVersion, tc.newClusterVersion, components.Components{}, false)
if tc.wantErr {
assert.Error(err)
@ -818,3 +818,11 @@ func (f *fakeConfigMapClient) CreateConfigMap(_ context.Context, configMap *core
f.configMaps[configMap.ObjectMeta.Name] = configMap
return nil
}
// supportedValidK8sVersions returns a typed list of supported Kubernetes versions.
func supportedValidK8sVersions() (res []versions.ValidK8sVersion) {
for _, v := range versions.SupportedK8sVersions() {
res = append(res, versions.ValidK8sVersion(v))
}
return
}

View File

@ -273,13 +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)
require.NoError(err)
kubernetesVersion = versions.ValidK8sVersion(kubernetes) // ignore validation because the config is only written to file
}
var microserviceVersion semver.Semver
@ -291,8 +289,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 +430,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 +447,7 @@ func testNodesEventuallyHaveVersion(t *testing.T, k *kubernetes.Clientset, targe
type versionContainer struct {
imageRef string
kubernetes semver.Semver
kubernetes versions.ValidK8sVersion
microservices semver.Semver
}

View File

@ -70,6 +70,7 @@ go_test(
"@com_github_stretchr_testify//assert",
"@com_github_stretchr_testify//require",
"@in_gopkg_yaml_v3//:yaml_v3",
"@org_golang_x_mod//semver",
"@org_uber_go_goleak//:goleak",
],
)

View File

@ -69,7 +69,7 @@ type Config struct {
Name string `yaml:"name" validate:"valid_name,required"`
// description: |
// Kubernetes version to be installed into the cluster.
KubernetesVersion string `yaml:"kubernetesVersion" validate:"required,supported_k8s_version"`
KubernetesVersion versions.ValidK8sVersion `yaml:"kubernetesVersion" validate:"required,supported_k8s_version"`
// description: |
// Microservice version to be installed into the cluster. Defaults to the version of the CLI.
MicroserviceVersion semver.Semver `yaml:"microserviceVersion" validate:"required"`
@ -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{

View File

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

View File

@ -30,6 +30,7 @@ import (
"github.com/edgelesssys/constellation/v2/internal/file"
"github.com/edgelesssys/constellation/v2/internal/semver"
"github.com/edgelesssys/constellation/v2/internal/versions"
gosemver "golang.org/x/mod/semver"
)
func TestMain(m *testing.M) {
@ -187,6 +188,44 @@ func TestReadConfigFile(t *testing.T) {
configName: constants.ConfigFilename,
wantErr: true,
},
"outdated k8s patch version is allowed": {
config: func() configMap {
conf := Default()
ver, err := semver.New(versions.SupportedK8sVersions()[0])
require.NoError(t, err)
conf.KubernetesVersion = versions.ValidK8sVersion(semver.NewFromInt(ver.Major(), ver.Minor(), ver.Patch()-1, "").String())
m := getConfigAsMap(conf, t)
return m
}(),
wantResult: func() *Config {
conf := Default()
ver, err := semver.New(versions.SupportedK8sVersions()[0])
require.NoError(t, err)
conf.KubernetesVersion = versions.ValidK8sVersion(semver.NewFromInt(ver.Major(), ver.Minor(), ver.Patch()-1, "").String())
return conf
}(),
configName: constants.ConfigFilename,
},
"outdated k8s version is not allowed": {
config: func() configMap {
conf := Default()
conf.KubernetesVersion = versions.ValidK8sVersion("v1.0.0")
m := getConfigAsMap(conf, t)
return m
}(),
wantErr: true,
configName: constants.ConfigFilename,
},
"a k8s version without specified patch is not allowed": {
config: func() configMap {
conf := Default()
conf.KubernetesVersion = versions.ValidK8sVersion(gosemver.MajorMinor(string(versions.Default)))
m := getConfigAsMap(conf, t)
return m
}(),
wantErr: true,
configName: constants.ConfigFilename,
},
"error on entering app client id": {
config: func() configMap {
conf := Default()
@ -249,16 +288,6 @@ 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{},
},
},
"modify default config": {
config: func() *Config {
conf := Default()
@ -321,19 +350,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()

View File

@ -12,5 +12,6 @@ go_library(
"//internal/file",
"//internal/role",
"//internal/semver",
"//internal/versions",
],
)

View File

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

View File

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

View File

@ -40,25 +40,87 @@ func SupportedK8sVersions() []string {
type ValidK8sVersion string
// NewValidK8sVersion validates the given string and produces a new ValidK8sVersion object.
// It accepts a full version (e.g. 1.26.7) and validates it.
// Returns an empty string if the given version is invalid.
// 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.
// If strict is false, the patch version validation is skipped.
func NewValidK8sVersion(k8sVersion string, strict bool) (ValidK8sVersion, error) {
prefixedVersion := compatibility.EnsurePrefixV(k8sVersion)
var supported bool
if strict {
supported = isSupportedK8sVersionStrict(k8sVersion)
supported = isSupportedK8sVersionStrict(prefixedVersion)
} else {
supported = isSupportedK8sVersion(k8sVersion)
supported = isSupportedK8sVersion(prefixedVersion)
}
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", prefixedVersion, SupportedK8sVersions())
}
if !strict {
k8sVersion, _ = supportedVersionForMajorMinor(k8sVersion)
return ValidK8sVersion(prefixedVersion), 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
}
if !hasPatchVersion(version) {
return fmt.Errorf("Kubernetes version %s does not specify a patch, supported versions are %s", version, strings.Join(SupportedK8sVersions(), ", "))
}
valid, err := NewValidK8sVersion(version, false) // allow any patch version to not force K8s patch upgrades
if err != nil {
return fmt.Errorf("unsupported Kubernetes version %s, supported versions are %s", version, strings.Join(SupportedK8sVersions(), ", "))
}
*v = valid
return nil
}
// ResolveK8sPatchVersion transforms a MAJOR.MINOR definition into a supported
// MAJOR.MINOR.PATCH release.
func ResolveK8sPatchVersion(k8sVersion string) (string, error) {
k8sVersion = compatibility.EnsurePrefixV(k8sVersion)
if !semver.IsValid(k8sVersion) {
return "", fmt.Errorf("Kubernetes version does not specify a valid semantic version: %s", k8sVersion)
}
if hasPatchVersion(k8sVersion) {
return k8sVersion, nil
}
extendedVersion := k8sVersionFromMajorMinor(k8sVersion)
if extendedVersion == "" {
return "", fmt.Errorf("Kubernetes version %s is not valid. Supported versions: %s",
strings.TrimPrefix(k8sVersion, "v"), supportedVersions())
}
return ValidK8sVersion(k8sVersion), nil
return extendedVersion, 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.
@ -87,14 +149,9 @@ func IsPreviewK8sVersion(_ ValidK8sVersion) bool {
return false
}
func supportedVersionForMajorMinor(majorMinor string) (string, bool) {
majorMinor = semver.MajorMinor(majorMinor)
for _, valid := range SupportedK8sVersions() {
if semver.MajorMinor(valid) == majorMinor {
return valid, true
}
}
return "", false
// hasPatchVersion returns if the given version has specified a patch version.
func hasPatchVersion(version string) bool {
return semver.MajorMinor(version) != version
}
const (