diff --git a/cli/internal/cmd/upgradeapply.go b/cli/internal/cmd/upgradeapply.go index 7ea9607407..6fa6e6fdc3 100644 --- a/cli/internal/cmd/upgradeapply.go +++ b/cli/internal/cmd/upgradeapply.go @@ -43,8 +43,8 @@ const ( skipInfrastructurePhase skipPhase = "infrastructure" // skipHelmPhase skips the helm upgrade of the upgrade process. skipHelmPhase skipPhase = "helm" - // skipNodePhase skips the node upgrade of the upgrade process. - skipNodePhase skipPhase = "node" + // skipImagePhase skips the image upgrade of the upgrade process. + skipImagePhase skipPhase = "image" // skipK8sPhase skips the k8s upgrade of the upgrade process. skipK8sPhase skipPhase = "k8s" ) @@ -69,7 +69,7 @@ func newUpgradeApplyCmd() *cobra.Command { cmd.Flags().Bool("conformance", false, "enable conformance mode") cmd.Flags().Bool("skip-helm-wait", false, "install helm charts without waiting for deployments to be ready") cmd.Flags().StringSlice("skip-phases", nil, "comma-separated list of upgrade phases to skip\n"+ - "one or multiple of { infrastructure | helm | node | k8s }") + "one or multiple of { infrastructure | helm | image | k8s }") if err := cmd.Flags().MarkHidden("timeout"); err != nil { panic(err) } @@ -234,8 +234,10 @@ func (u *upgradeApplyCmd) upgradeApply(cmd *cobra.Command, upgradeDir string, fl } } - if !flags.skipPhases.contains(skipNodePhase) { - err = u.kubeUpgrader.UpgradeNodeVersion(cmd.Context(), conf, flags.force) + skipImageUpgrade := flags.skipPhases.contains(skipImagePhase) + skipK8sUpgrade := flags.skipPhases.contains(skipK8sPhase) + if !(skipImageUpgrade && skipK8sUpgrade) { + err = u.kubeUpgrader.UpgradeNodeVersion(cmd.Context(), conf, flags.force, skipImageUpgrade, skipK8sUpgrade) switch { case errors.Is(err, kubecmd.ErrInProgress): cmd.PrintErrln("Skipping image and Kubernetes upgrades. Another upgrade is in progress.") @@ -552,7 +554,7 @@ func parseUpgradeApplyFlags(cmd *cobra.Command) (upgradeApplyFlags, error) { var skipPhases []skipPhase for _, phase := range rawSkipPhases { switch skipPhase(phase) { - case skipInfrastructurePhase, skipHelmPhase, skipNodePhase, skipK8sPhase: + case skipInfrastructurePhase, skipHelmPhase, skipImagePhase, skipK8sPhase: skipPhases = append(skipPhases, skipPhase(phase)) default: return upgradeApplyFlags{}, fmt.Errorf("invalid phase %s", phase) @@ -619,7 +621,7 @@ func (s skipPhases) contains(phase skipPhase) bool { } type kubernetesUpgrader interface { - UpgradeNodeVersion(ctx context.Context, conf *config.Config, force bool) error + UpgradeNodeVersion(ctx context.Context, conf *config.Config, force, skipImage, skipK8s bool) error ExtendClusterConfigCertSANs(ctx context.Context, alternativeNames []string) error GetClusterAttestationConfig(ctx context.Context, variant variant.Variant) (config.AttestationCfg, error) ApplyJoinConfig(ctx context.Context, newAttestConfig config.AttestationCfg, measurementSalt []byte) error diff --git a/cli/internal/cmd/upgradeapply_test.go b/cli/internal/cmd/upgradeapply_test.go index 9e50ca1956..965a875e43 100644 --- a/cli/internal/cmd/upgradeapply_test.go +++ b/cli/internal/cmd/upgradeapply_test.go @@ -111,7 +111,7 @@ func TestUpgradeApply(t *testing.T) { helmUpgrader: &mockApplier{}, // mocks ensure that no methods are called terraformUpgrader: &mockTerraformUpgrader{}, flags: upgradeApplyFlags{ - skipPhases: []skipPhase{skipInfrastructurePhase, skipHelmPhase, skipK8sPhase, skipNodePhase}, + skipPhases: []skipPhase{skipInfrastructurePhase, skipHelmPhase, skipK8sPhase, skipImagePhase}, yes: true, }, }, @@ -159,7 +159,7 @@ func TestUpgradeApply(t *testing.T) { return } assert.NoError(err) - assert.Equal(!tc.flags.skipPhases.contains(skipNodePhase), tc.kubeUpgrader.calledNodeUpgrade, + assert.Equal(!tc.flags.skipPhases.contains(skipImagePhase), tc.kubeUpgrader.calledNodeUpgrade, "incorrect node upgrade skipping behavior") }) } @@ -175,7 +175,7 @@ func TestUpgradeApplyFlagsForSkipPhases(t *testing.T) { if err != nil { t.Fatalf("Error while parsing flags: %v", err) } - assert.ElementsMatch(t, []skipPhase{skipInfrastructurePhase, skipHelmPhase, skipK8sPhase, skipNodePhase}, result.skipPhases) + assert.ElementsMatch(t, []skipPhase{skipInfrastructurePhase, skipHelmPhase, skipK8sPhase, skipImagePhase}, result.skipPhases) } type stubKubernetesUpgrader struct { @@ -192,7 +192,7 @@ func (u *stubKubernetesUpgrader) BackupCRs(_ context.Context, _ []apiextensionsv return nil } -func (u *stubKubernetesUpgrader) UpgradeNodeVersion(_ context.Context, _ *config.Config, _ bool) error { +func (u *stubKubernetesUpgrader) UpgradeNodeVersion(_ context.Context, _ *config.Config, _, _, _ bool) error { u.calledNodeUpgrade = true return u.nodeVersionErr } diff --git a/cli/internal/kubecmd/kubecmd.go b/cli/internal/kubecmd/kubecmd.go index 41c88c6336..fee16e32e0 100644 --- a/cli/internal/kubecmd/kubecmd.go +++ b/cli/internal/kubecmd/kubecmd.go @@ -99,7 +99,8 @@ func New(outWriter io.Writer, kubeConfigPath string, fileHandler file.Handler, l // UpgradeNodeVersion upgrades the cluster's NodeVersion object and in turn triggers image & k8s version upgrades. // The versions set in the config are validated against the versions running in the cluster. -func (k *KubeCmd) UpgradeNodeVersion(ctx context.Context, conf *config.Config, force bool) error { +// TODO(elchead): AB#3434 Split K8s and image upgrade of UpgradeNodeVersion. +func (k *KubeCmd) UpgradeNodeVersion(ctx context.Context, conf *config.Config, force, skipImage, skipK8s bool) error { provider := conf.GetProvider() attestationVariant := conf.GetAttestationConfig().GetVariant() region := conf.GetRegion() @@ -120,40 +121,43 @@ func (k *KubeCmd) UpgradeNodeVersion(ctx context.Context, conf *config.Config, f upgradeErrs := []error{} var upgradeErr *compatibility.InvalidUpgradeError - - err = k.isValidImageUpgrade(nodeVersion, imageVersion.Version(), force) - switch { - case errors.As(err, &upgradeErr): - upgradeErrs = append(upgradeErrs, fmt.Errorf("skipping image upgrades: %w", err)) - case err != nil: - return fmt.Errorf("updating image version: %w", err) - } - k.log.Debugf("Updating local copy of nodeVersion image version from %s to %s", nodeVersion.Spec.ImageVersion, imageVersion.Version()) - nodeVersion.Spec.ImageReference = imageReference - nodeVersion.Spec.ImageVersion = imageVersion.Version() - - // 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) + if !skipImage { + err = k.isValidImageUpgrade(nodeVersion, imageVersion.Version(), force) + switch { + case errors.As(err, &upgradeErr): + upgradeErrs = append(upgradeErrs, fmt.Errorf("skipping image upgrades: %w", err)) + case err != nil: + return fmt.Errorf("updating image version: %w", err) + } + k.log.Debugf("Updating local copy of nodeVersion image version from %s to %s", nodeVersion.Spec.ImageVersion, imageVersion.Version()) + nodeVersion.Spec.ImageReference = imageReference + nodeVersion.Spec.ImageVersion = imageVersion.Version() } - switch { - case err == nil: - err := k.applyComponentsCM(ctx, components) + if !skipK8s { + // 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 { - return fmt.Errorf("applying k8s components ConfigMap: %w", err) + 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) + } + + switch { + case err == nil: + err := k.applyComponentsCM(ctx, components) + if err != nil { + return fmt.Errorf("applying k8s components ConfigMap: %w", err) + } + case errors.As(err, &upgradeErr): + upgradeErrs = append(upgradeErrs, fmt.Errorf("skipping Kubernetes upgrades: %w", err)) + default: + return fmt.Errorf("updating Kubernetes version: %w", err) } - case errors.As(err, &upgradeErr): - upgradeErrs = append(upgradeErrs, fmt.Errorf("skipping Kubernetes upgrades: %w", err)) - default: - return fmt.Errorf("updating Kubernetes version: %w", err) } if len(upgradeErrs) == 2 { diff --git a/cli/internal/kubecmd/kubecmd_test.go b/cli/internal/kubecmd/kubecmd_test.go index 312d2c8335..ec24a274bd 100644 --- a/cli/internal/kubecmd/kubecmd_test.go +++ b/cli/internal/kubecmd/kubecmd_test.go @@ -332,7 +332,7 @@ func TestUpgradeNodeVersion(t *testing.T) { outWriter: io.Discard, } - err = upgrader.UpgradeNodeVersion(context.Background(), tc.conf, tc.force) + err = upgrader.UpgradeNodeVersion(context.Background(), tc.conf, tc.force, false, false) // Check upgrades first because if we checked err first, UpgradeImage may error due to other reasons and still trigger an upgrade. if tc.wantUpdate { assert.NotNil(unstructuredClient.updatedObject) diff --git a/docs/docs/reference/cli.md b/docs/docs/reference/cli.md index f438a3f7b1..2d9a8ebff3 100644 --- a/docs/docs/reference/cli.md +++ b/docs/docs/reference/cli.md @@ -475,8 +475,8 @@ constellation upgrade apply [flags] --conformance enable conformance mode -h, --help help for apply --skip-helm-wait install helm charts without waiting for deployments to be ready - --skip-phases strings skip one or multiple phases of the upgrade process {infrastructure|helm|node|k8s} - + --skip-phases strings comma-separated list of upgrade phases to skip + one or multiple of { infrastructure | helm | image | k8s } -y, --yes run upgrades without further confirmation WARNING: might delete your resources in case you are using cert-manager in your cluster. Please read the docs. WARNING: might unintentionally overwrite measurements in the running cluster.