2022-09-05 03:06:08 -04:00
/ *
Copyright ( c ) Edgeless Systems GmbH
SPDX - License - Identifier : AGPL - 3.0 - only
* /
2022-08-29 10:49:44 -04:00
package cmd
import (
"context"
2023-01-04 07:55:10 -05:00
"errors"
2022-12-19 10:52:15 -05:00
"fmt"
2023-08-23 04:35:42 -04:00
"io"
2023-05-22 07:31:20 -04:00
"path/filepath"
2023-09-08 08:55:07 -04:00
"strings"
2022-12-19 10:52:15 -05:00
"time"
2022-08-29 10:49:44 -04:00
2023-07-21 04:04:29 -04:00
"github.com/edgelesssys/constellation/v2/cli/internal/cloudcmd"
2023-05-03 05:11:53 -04:00
"github.com/edgelesssys/constellation/v2/cli/internal/clusterid"
2023-08-08 09:42:06 -04:00
"github.com/edgelesssys/constellation/v2/cli/internal/cmd/pathprefix"
2023-01-04 07:55:10 -05:00
"github.com/edgelesssys/constellation/v2/cli/internal/helm"
2023-08-21 10:15:32 -04:00
"github.com/edgelesssys/constellation/v2/cli/internal/kubecmd"
2023-05-22 07:31:20 -04:00
"github.com/edgelesssys/constellation/v2/cli/internal/terraform"
2023-06-07 10:16:32 -04:00
"github.com/edgelesssys/constellation/v2/internal/api/attestationconfigapi"
2023-06-09 09:41:02 -04:00
"github.com/edgelesssys/constellation/v2/internal/attestation/variant"
2023-03-24 12:07:14 -04:00
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
2023-02-28 04:23:09 -05:00
"github.com/edgelesssys/constellation/v2/internal/compatibility"
2022-09-21 07:47:57 -04:00
"github.com/edgelesssys/constellation/v2/internal/config"
2023-05-03 05:11:53 -04:00
"github.com/edgelesssys/constellation/v2/internal/constants"
2022-09-21 07:47:57 -04:00
"github.com/edgelesssys/constellation/v2/internal/file"
2023-08-11 09:18:59 -04:00
"github.com/edgelesssys/constellation/v2/internal/kms/uri"
2023-08-23 02:14:39 -04:00
"github.com/edgelesssys/constellation/v2/internal/semver"
2023-06-05 03:13:02 -04:00
"github.com/edgelesssys/constellation/v2/internal/versions"
2023-08-08 07:03:23 -04:00
"github.com/rogpeppe/go-internal/diff"
2022-08-29 10:49:44 -04:00
"github.com/spf13/afero"
"github.com/spf13/cobra"
2023-08-08 07:03:23 -04:00
"gopkg.in/yaml.v3"
2023-08-24 10:40:47 -04:00
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
2022-08-29 10:49:44 -04:00
)
2023-09-08 08:55:07 -04:00
const (
// skipInfrastructurePhase skips the terraform apply of the upgrade process.
skipInfrastructurePhase skipPhase = "infrastructure"
// skipHelmPhase skips the helm upgrade of the upgrade process.
skipHelmPhase skipPhase = "helm"
// skipImagePhase skips the image upgrade of the upgrade process.
skipImagePhase skipPhase = "image"
// skipK8sPhase skips the k8s upgrade of the upgrade process.
skipK8sPhase skipPhase = "k8s"
)
// skipPhase is a phase of the upgrade process that can be skipped.
type skipPhase string
2023-02-01 04:56:47 -05:00
func newUpgradeApplyCmd ( ) * cobra . Command {
2022-08-29 10:49:44 -04:00
cmd := & cobra . Command {
2023-02-09 09:54:12 -05:00
Use : "apply" ,
2023-02-01 04:56:47 -05:00
Short : "Apply an upgrade to a Constellation cluster" ,
Long : "Apply an upgrade to a Constellation cluster by applying the chosen configuration." ,
2022-08-29 10:49:44 -04:00
Args : cobra . NoArgs ,
2023-02-01 04:56:47 -05:00
RunE : runUpgradeApply ,
2022-08-29 10:49:44 -04:00
}
2023-01-17 08:01:56 -05:00
cmd . Flags ( ) . BoolP ( "yes" , "y" , false , "run upgrades without further confirmation\n" +
2023-03-14 13:34:58 -04:00
"WARNING: might delete your resources in case you are using cert-manager in your cluster. Please read the docs.\n" +
"WARNING: might unintentionally overwrite measurements in the running cluster." )
2023-06-06 09:22:06 -04:00
cmd . Flags ( ) . Duration ( "timeout" , 5 * time . Minute , "change helm upgrade timeout\n" +
2023-02-01 05:23:57 -05:00
"Might be useful for slow connections or big clusters." )
2023-08-11 09:18:59 -04:00
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" )
2023-09-08 08:55:07 -04:00
cmd . Flags ( ) . StringSlice ( "skip-phases" , nil , "comma-separated list of upgrade phases to skip\n" +
"one or multiple of { infrastructure | helm | image | k8s }" )
2022-12-19 10:52:15 -05:00
if err := cmd . Flags ( ) . MarkHidden ( "timeout" ) ; err != nil {
panic ( err )
}
2022-08-29 10:49:44 -04:00
return cmd
}
2023-03-20 06:03:36 -04:00
func runUpgradeApply ( cmd * cobra . Command , _ [ ] string ) error {
2023-08-23 04:35:42 -04:00
flags , err := parseUpgradeApplyFlags ( cmd )
if err != nil {
return fmt . Errorf ( "parsing flags: %w" , err )
}
2022-12-19 10:52:15 -05:00
log , err := newCLILogger ( cmd )
if err != nil {
return fmt . Errorf ( "creating logger: %w" , err )
}
defer log . Sync ( )
2023-08-16 03:59:32 -04:00
2022-08-29 10:49:44 -04:00
fileHandler := file . NewHandler ( afero . NewOsFs ( ) )
2023-08-16 03:59:32 -04:00
upgradeID := generateUpgradeID ( upgradeCmdKindApply )
2023-08-04 07:53:51 -04:00
2023-08-24 10:40:47 -04:00
kubeUpgrader , err := kubecmd . New ( cmd . OutOrStdout ( ) , constants . AdminConfFilename , fileHandler , log )
2022-08-29 10:49:44 -04:00
if err != nil {
return err
}
2023-06-07 10:16:32 -04:00
configFetcher := attestationconfigapi . NewFetcher ( )
2023-08-16 03:59:32 -04:00
2023-08-23 04:35:42 -04:00
// Set up terraform upgrader
upgradeDir := filepath . Join ( constants . UpgradeDir , upgradeID )
clusterUpgrader , err := cloudcmd . NewClusterUpgrader (
cmd . Context ( ) ,
constants . TerraformWorkingDir ,
upgradeDir ,
flags . terraformLogLevel ,
fileHandler ,
)
2023-08-16 03:59:32 -04:00
if err != nil {
2023-08-23 04:35:42 -04:00
return fmt . Errorf ( "setting up cluster upgrader: %w" , err )
2023-08-16 03:59:32 -04:00
}
2023-08-23 04:35:42 -04:00
// Set up terraform client to show existing cluster resources and information required for Helm upgrades
2023-08-16 03:59:32 -04:00
tfShower , err := terraform . New ( cmd . Context ( ) , constants . TerraformWorkingDir )
2023-08-11 09:18:59 -04:00
if err != nil {
return fmt . Errorf ( "setting up terraform client: %w" , err )
}
2023-08-24 10:40:47 -04:00
helmClient , err := helm . NewClient ( constants . AdminConfFilename , log )
if err != nil {
return fmt . Errorf ( "creating Helm client: %w" , err )
}
2023-08-08 07:03:23 -04:00
2023-08-16 03:59:32 -04:00
applyCmd := upgradeApplyCmd {
2023-08-23 04:35:42 -04:00
kubeUpgrader : kubeUpgrader ,
2023-08-24 10:40:47 -04:00
helmApplier : helmClient ,
2023-08-23 04:35:42 -04:00
clusterUpgrader : clusterUpgrader ,
configFetcher : configFetcher ,
clusterShower : tfShower ,
fileHandler : fileHandler ,
log : log ,
}
return applyCmd . upgradeApply ( cmd , upgradeDir , flags )
2022-08-29 10:49:44 -04:00
}
2023-02-09 09:54:12 -05:00
type upgradeApplyCmd struct {
2023-08-24 10:40:47 -04:00
helmApplier helmApplier
2023-08-23 04:35:42 -04:00
kubeUpgrader kubernetesUpgrader
clusterUpgrader clusterUpgrader
configFetcher attestationconfigapi . Fetcher
clusterShower clusterShower
fileHandler file . Handler
log debugLog
2023-02-09 09:54:12 -05:00
}
2023-08-23 04:35:42 -04:00
func ( u * upgradeApplyCmd ) upgradeApply ( cmd * cobra . Command , upgradeDir string , flags upgradeApplyFlags ) error {
2023-08-11 09:18:59 -04:00
conf , err := config . New ( u . fileHandler , constants . ConfigFilename , u . configFetcher , flags . force )
2023-02-07 06:56:25 -05:00
var configValidationErr * config . ValidationError
if errors . As ( err , & configValidationErr ) {
cmd . PrintErrln ( configValidationErr . LongMessage ( ) )
}
2022-08-29 10:49:44 -04:00
if err != nil {
2023-02-07 06:56:25 -05:00
return err
2022-08-29 10:49:44 -04:00
}
2023-09-07 06:10:36 -04:00
if cloudcmd . UpgradeRequiresIAMMigration ( conf . GetProvider ( ) ) {
2023-07-26 11:29:03 -04:00
cmd . Println ( "WARNING: This upgrade requires an IAM migration. Please make sure you have applied the IAM migration using `iam upgrade apply` before continuing." )
if ! flags . yes {
yes , err := askToConfirm ( cmd , "Did you upgrade the IAM resources?" )
if err != nil {
return fmt . Errorf ( "asking for confirmation: %w" , err )
}
if ! yes {
cmd . Println ( "Skipping upgrade." )
return nil
}
}
}
2023-09-19 07:50:00 -04:00
conf . KubernetesVersion , err = validK8sVersion ( cmd , string ( conf . KubernetesVersion ) , flags . yes )
2023-08-11 09:18:59 -04:00
if err != nil {
2023-06-05 03:13:02 -04:00
return err
}
2023-05-03 05:11:53 -04:00
var idFile clusterid . File
2023-08-11 09:18:59 -04:00
if err := u . fileHandler . ReadJSON ( constants . ClusterIDsFilename , & idFile ) ; err != nil {
2023-05-03 05:11:53 -04:00
return fmt . Errorf ( "reading cluster ID file: %w" , err )
}
conf . UpdateMAAURL ( idFile . AttestationURL )
2023-08-23 02:14:39 -04:00
// Apply migrations necessary for the upgrade
if err := migrateFrom2_10 ( cmd . Context ( ) , u . kubeUpgrader ) ; err != nil {
return fmt . Errorf ( "applying migration for upgrading from v2.10: %w" , err )
}
if err := migrateFrom2_11 ( cmd . Context ( ) , u . kubeUpgrader ) ; err != nil {
return fmt . Errorf ( "applying migration for upgrading from v2.11: %w" , err )
}
if err := u . confirmAndUpgradeAttestationConfig ( cmd , conf . GetAttestationConfig ( ) , idFile . MeasurementSalt , flags ) ; err != nil {
2023-05-03 05:11:53 -04:00
return fmt . Errorf ( "upgrading measurements: %w" , err )
}
2023-08-21 10:15:32 -04:00
2023-09-08 08:55:07 -04:00
var tfOutput terraform . ApplyOutput
if flags . skipPhases . contains ( skipInfrastructurePhase ) {
tfOutput , err = u . clusterShower . ShowCluster ( cmd . Context ( ) , conf . GetProvider ( ) )
if err != nil {
return fmt . Errorf ( "getting Terraform output: %w" , err )
}
} else {
tfOutput , err = u . migrateTerraform ( cmd , conf , upgradeDir , flags )
if err != nil {
return fmt . Errorf ( "performing Terraform migrations: %w" , err )
}
2023-05-22 07:31:20 -04:00
}
2023-07-21 10:43:51 -04:00
// reload idFile after terraform migration
// it might have been updated by the migration
2023-08-11 09:18:59 -04:00
if err := u . fileHandler . ReadJSON ( constants . ClusterIDsFilename , & idFile ) ; err != nil {
2023-07-21 10:43:51 -04:00
return fmt . Errorf ( "reading updated cluster ID file: %w" , err )
}
// extend the clusterConfig cert SANs with any of the supported endpoints:
// - (legacy) public IP
// - fallback endpoint
// - custom (user-provided) endpoint
sans := append ( [ ] string { idFile . IP , conf . CustomEndpoint } , idFile . APIServerCertSANs ... )
2023-08-16 03:59:32 -04:00
if err := u . kubeUpgrader . ExtendClusterConfigCertSANs ( cmd . Context ( ) , sans ) ; err != nil {
2023-07-21 10:43:51 -04:00
return fmt . Errorf ( "extending cert SANs: %w" , err )
}
2023-05-22 07:31:20 -04:00
2023-08-23 02:14:39 -04:00
if conf . GetProvider ( ) != cloudprovider . Azure && conf . GetProvider ( ) != cloudprovider . GCP && conf . GetProvider ( ) != cloudprovider . AWS {
2023-05-19 07:57:31 -04:00
cmd . PrintErrln ( "WARNING: Skipping service and image upgrades, which are currently only supported for AWS, Azure, and GCP." )
2023-08-23 02:14:39 -04:00
return nil
}
var upgradeErr * compatibility . InvalidUpgradeError
2023-09-08 08:55:07 -04:00
if ! flags . skipPhases . contains ( skipHelmPhase ) {
2023-09-19 07:50:00 -04:00
err = u . handleServiceUpgrade ( cmd , conf , idFile , tfOutput , upgradeDir , flags )
2023-09-08 08:55:07 -04:00
switch {
case errors . As ( err , & upgradeErr ) :
cmd . PrintErrln ( err )
case err == nil :
cmd . Println ( "Successfully upgraded Constellation services." )
case err != nil :
return fmt . Errorf ( "upgrading services: %w" , err )
}
2023-02-09 09:54:12 -05:00
}
2023-09-08 08:55:07 -04:00
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." )
case errors . As ( err , & upgradeErr ) :
cmd . PrintErrln ( err )
case err != nil :
return fmt . Errorf ( "upgrading NodeVersion: %w" , err )
}
}
2023-03-14 13:34:58 -04:00
return nil
}
2023-08-08 07:03:23 -04:00
func diffAttestationCfg ( currentAttestationCfg config . AttestationCfg , newAttestationCfg config . AttestationCfg ) ( string , error ) {
// cannot compare structs directly with go-cmp because of unexported fields in the attestation config
currentYml , err := yaml . Marshal ( currentAttestationCfg )
if err != nil {
return "" , fmt . Errorf ( "marshalling remote attestation config: %w" , err )
}
newYml , err := yaml . Marshal ( newAttestationCfg )
if err != nil {
return "" , fmt . Errorf ( "marshalling local attestation config: %w" , err )
}
diff := string ( diff . Diff ( "current" , currentYml , "new" , newYml ) )
return diff , nil
}
2023-05-22 07:31:20 -04:00
// migrateTerraform checks if the Constellation version the cluster is being upgraded to requires a migration
// of cloud resources with Terraform. If so, the migration is performed.
2023-08-23 04:35:42 -04:00
func ( u * upgradeApplyCmd ) migrateTerraform ( cmd * cobra . Command , conf * config . Config , upgradeDir string , flags upgradeApplyFlags ,
2023-08-11 09:18:59 -04:00
) ( res terraform . ApplyOutput , err error ) {
2023-05-22 07:31:20 -04:00
u . log . Debugf ( "Planning Terraform migrations" )
2023-08-14 09:16:07 -04:00
vars , err := cloudcmd . TerraformUpgradeVars ( conf )
2023-05-22 07:31:20 -04:00
if err != nil {
2023-08-11 09:18:59 -04:00
return res , fmt . Errorf ( "parsing upgrade variables: %w" , err )
2023-05-22 07:31:20 -04:00
}
u . log . Debugf ( "Using Terraform variables:\n%v" , vars )
// Check if there are any Terraform migrations to apply
2023-08-09 10:04:32 -04:00
// Add manual migrations here if required
//
// var manualMigrations []terraform.StateMigration
// for _, migration := range manualMigrations {
// u.log.Debugf("Adding manual Terraform migration: %s", migration.DisplayName)
// u.upgrader.AddManualStateMigration(migration)
// }
2023-08-23 04:35:42 -04:00
hasDiff , err := u . clusterUpgrader . PlanClusterUpgrade ( cmd . Context ( ) , cmd . OutOrStdout ( ) , vars , conf . GetProvider ( ) )
2023-05-22 07:31:20 -04:00
if err != nil {
2023-08-11 09:18:59 -04:00
return res , fmt . Errorf ( "planning terraform migrations: %w" , err )
2023-05-22 07:31:20 -04:00
}
if hasDiff {
// If there are any Terraform migrations to apply, ask for confirmation
2023-06-19 07:02:01 -04:00
fmt . Fprintln ( cmd . OutOrStdout ( ) , "The upgrade requires a migration of Constellation cloud resources by applying an updated Terraform template. Please manually review the suggested changes below." )
2023-05-22 07:31:20 -04:00
if ! flags . yes {
ok , err := askToConfirm ( cmd , "Do you want to apply the Terraform migrations?" )
if err != nil {
2023-08-11 09:18:59 -04:00
return res , fmt . Errorf ( "asking for confirmation: %w" , err )
2023-05-22 07:31:20 -04:00
}
if ! ok {
cmd . Println ( "Aborting upgrade." )
2023-09-14 05:51:20 -04:00
// User doesn't expect to see any changes in his workspace after aborting an "upgrade apply",
// therefore, roll back to the backed up state.
if err := u . clusterUpgrader . RestoreClusterWorkspace ( ) ; err != nil {
return res , fmt . Errorf (
"restoring Terraform workspace: %w, restore the Terraform workspace manually from %s " ,
err ,
filepath . Join ( upgradeDir , constants . TerraformUpgradeBackupDir ) ,
)
2023-05-22 07:31:20 -04:00
}
2023-08-23 04:35:42 -04:00
return res , fmt . Errorf ( "cluster upgrade aborted by user" )
2023-05-22 07:31:20 -04:00
}
}
u . log . Debugf ( "Applying Terraform migrations" )
2023-08-23 04:35:42 -04:00
tfOutput , err := u . clusterUpgrader . ApplyClusterUpgrade ( cmd . Context ( ) , conf . GetProvider ( ) )
2023-05-22 07:31:20 -04:00
if err != nil {
2023-08-11 09:18:59 -04:00
return tfOutput , fmt . Errorf ( "applying terraform migrations: %w" , err )
2023-05-22 07:31:20 -04:00
}
2023-08-11 09:18:59 -04:00
2023-08-23 04:35:42 -04:00
// Apply possible updates to cluster ID file
if err := updateClusterIDFile ( tfOutput , u . fileHandler ) ; err != nil {
2023-08-11 09:18:59 -04:00
return tfOutput , fmt . Errorf ( "merging cluster ID files: %w" , err )
2023-08-04 07:53:51 -04:00
}
2023-05-22 07:31:20 -04:00
cmd . Printf ( "Terraform migrations applied successfully and output written to: %s\n" +
2023-07-18 03:33:42 -04:00
"A backup of the pre-upgrade state has been written to: %s\n" ,
2023-08-16 09:38:40 -04:00
flags . pf . PrefixPrintablePath ( constants . ClusterIDsFilename ) ,
2023-08-23 04:35:42 -04:00
flags . pf . PrefixPrintablePath ( filepath . Join ( upgradeDir , constants . TerraformUpgradeBackupDir ) ) ,
2023-08-16 03:59:32 -04:00
)
2023-05-22 07:31:20 -04:00
} else {
u . log . Debugf ( "No Terraform diff detected" )
}
2023-08-11 09:18:59 -04:00
u . log . Debugf ( "No Terraform diff detected" )
tfOutput , err := u . clusterShower . ShowCluster ( cmd . Context ( ) , conf . GetProvider ( ) )
if err != nil {
return tfOutput , fmt . Errorf ( "getting Terraform output: %w" , err )
}
return tfOutput , nil
}
2023-05-22 07:31:20 -04:00
2023-08-11 09:18:59 -04:00
// 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 )
}
2023-06-05 03:13:02 -04:00
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 {
2023-08-11 09:18:59 -04:00
return validVersion , fmt . Errorf ( "asking for confirmation: %w" , err )
2023-06-05 03:13:02 -04:00
}
if ! confirmed {
2023-08-11 09:18:59 -04:00
return validVersion , fmt . Errorf ( "aborted by user" )
2023-06-05 03:13:02 -04:00
}
}
2023-08-11 09:18:59 -04:00
return validVersion , nil
2023-06-05 03:13:02 -04:00
}
2023-08-23 02:14:39 -04:00
// confirmAndUpgradeAttestationConfig checks if the locally configured measurements are different from the cluster's measurements.
2023-08-21 10:15:32 -04:00
// If so the function will ask the user to confirm (if --yes is not set) and upgrade the cluster's config.
2023-08-23 02:14:39 -04:00
func ( u * upgradeApplyCmd ) confirmAndUpgradeAttestationConfig (
cmd * cobra . Command , newConfig config . AttestationCfg , measurementSalt [ ] byte , flags upgradeApplyFlags ,
) error {
2023-08-16 03:59:32 -04:00
clusterAttestationConfig , err := u . kubeUpgrader . GetClusterAttestationConfig ( cmd . Context ( ) , newConfig . GetVariant ( ) )
2023-07-10 08:03:45 -04:00
if err != nil {
return fmt . Errorf ( "getting cluster attestation config: %w" , err )
2023-03-14 13:34:58 -04:00
}
2023-08-11 09:18:59 -04:00
2023-07-10 08:03:45 -04:00
// If the current config is equal, or there is an error when comparing the configs, we skip the upgrade.
2023-07-12 05:53:00 -04:00
equal , err := newConfig . EqualTo ( clusterAttestationConfig )
if err != nil {
2023-07-10 08:03:45 -04:00
return fmt . Errorf ( "comparing attestation configs: %w" , err )
2023-03-14 13:34:58 -04:00
}
2023-07-12 05:53:00 -04:00
if equal {
return nil
}
2023-08-11 09:18:59 -04:00
cmd . Println ( "The configured attestation config is different from the attestation config in the cluster." )
diffStr , err := diffAttestationCfg ( clusterAttestationConfig , newConfig )
if err != nil {
return fmt . Errorf ( "diffing attestation configs: %w" , err )
}
cmd . Println ( "The following changes will be applied to the attestation config:" )
cmd . Println ( diffStr )
2023-03-14 13:34:58 -04:00
if ! flags . yes {
2023-08-08 07:03:23 -04:00
ok , err := askToConfirm ( cmd , "Are you sure you want to change your cluster's attestation config?" )
2023-03-14 13:34:58 -04:00
if err != nil {
return fmt . Errorf ( "asking for confirmation: %w" , err )
}
if ! ok {
2023-08-11 09:18:59 -04:00
return errors . New ( "aborting upgrade since attestation config is different" )
2023-03-14 13:34:58 -04:00
}
}
2023-08-21 10:15:32 -04:00
2023-08-23 02:14:39 -04:00
if err := u . kubeUpgrader . ApplyJoinConfig ( cmd . Context ( ) , newConfig , measurementSalt ) ; err != nil {
2023-08-15 07:58:04 -04:00
return fmt . Errorf ( "updating attestation config: %w" , err )
}
2023-08-23 02:14:39 -04:00
cmd . Println ( "Successfully update the cluster's attestation config" )
2023-02-09 09:54:12 -05:00
return nil
2023-02-01 05:23:57 -05:00
}
2023-08-23 04:35:42 -04:00
func ( u * upgradeApplyCmd ) handleServiceUpgrade (
cmd * cobra . Command , conf * config . Config , idFile clusterid . File , tfOutput terraform . ApplyOutput ,
2023-09-19 07:50:00 -04:00
upgradeDir string , flags upgradeApplyFlags ,
2023-08-23 04:35:42 -04:00
) error {
2023-08-11 09:18:59 -04:00
var secret uri . MasterSecret
2023-08-16 09:38:40 -04:00
if err := u . fileHandler . ReadJSON ( constants . MasterSecretFilename , & secret ) ; err != nil {
2023-08-11 09:18:59 -04:00
return fmt . Errorf ( "reading master secret: %w" , err )
}
serviceAccURI , err := cloudcmd . GetMarshaledServiceAccountURI ( conf . GetProvider ( ) , conf , flags . pf , u . log , u . fileHandler )
if err != nil {
return fmt . Errorf ( "getting service account URI: %w" , err )
}
2023-08-24 10:40:47 -04:00
options := helm . Options {
Force : flags . force ,
Conformance : flags . conformance ,
HelmWaitMode : flags . helmWaitMode ,
}
prepareApply := func ( allowDestructive bool ) ( helm . Applier , bool , error ) {
options . AllowDestructive = allowDestructive
2023-09-19 07:50:00 -04:00
executor , includesUpgrades , err := u . helmApplier . PrepareApply ( conf , idFile , options ,
2023-08-24 10:40:47 -04:00
tfOutput , serviceAccURI , secret )
var upgradeErr * compatibility . InvalidUpgradeError
switch {
case errors . As ( err , & upgradeErr ) :
cmd . PrintErrln ( err )
case err != nil :
return nil , false , fmt . Errorf ( "getting chart executor: %w" , err )
}
return executor , includesUpgrades , nil
}
executor , includesUpgrades , err := prepareApply ( helm . DenyDestructive )
if err != nil {
if ! errors . Is ( err , helm . ErrConfirmationMissing ) {
return fmt . Errorf ( "upgrading charts with deny destructive mode: %w" , err )
}
2023-02-01 05:23:57 -05:00
if ! flags . yes {
cmd . PrintErrln ( "WARNING: Upgrading cert-manager will destroy all custom resources you have manually created that are based on the current version of cert-manager." )
ok , askErr := askToConfirm ( cmd , "Do you want to upgrade cert-manager anyway?" )
if askErr != nil {
return fmt . Errorf ( "asking for confirmation: %w" , err )
}
if ! ok {
2023-05-03 05:11:53 -04:00
cmd . Println ( "Skipping upgrade." )
2023-02-01 05:23:57 -05:00
return nil
}
}
2023-08-24 10:40:47 -04:00
executor , includesUpgrades , err = prepareApply ( helm . AllowDestructive )
if err != nil {
return fmt . Errorf ( "upgrading charts with allow destructive mode: %w" , err )
}
2023-02-01 05:23:57 -05:00
}
2023-09-08 06:02:16 -04:00
// Save the Helm charts for the upgrade to disk
chartDir := filepath . Join ( upgradeDir , "helm-charts" )
if err := executor . SaveCharts ( chartDir , u . fileHandler ) ; err != nil {
return fmt . Errorf ( "saving Helm charts to disk: %w" , err )
}
u . log . Debugf ( "Helm charts saved to %s" , chartDir )
2023-08-24 10:40:47 -04:00
if includesUpgrades {
u . log . Debugf ( "Creating backup of CRDs and CRs" )
crds , err := u . kubeUpgrader . BackupCRDs ( cmd . Context ( ) , upgradeDir )
if err != nil {
return fmt . Errorf ( "creating CRD backup: %w" , err )
}
if err := u . kubeUpgrader . BackupCRs ( cmd . Context ( ) , crds , upgradeDir ) ; err != nil {
return fmt . Errorf ( "creating CR backup: %w" , err )
}
}
if err := executor . Apply ( cmd . Context ( ) ) ; err != nil {
return fmt . Errorf ( "applying Helm charts: %w" , err )
}
return nil
2022-08-29 10:49:44 -04:00
}
2023-08-23 02:14:39 -04:00
// migrateFrom2_10 applies migrations necessary for upgrading from v2.10 to v2.11
// TODO(v2.11): Remove this function after v2.11 is released.
func migrateFrom2_10 ( ctx context . Context , kubeUpgrader kubernetesUpgrader ) error {
// Sanity check to make sure we only run migrations on upgrades with CLI version 2.10 < v < 2.12
if ! constants . BinaryVersion ( ) . MajorMinorEqual ( semver . NewFromInt ( 2 , 11 , 0 , "" ) ) {
return nil
}
if err := kubeUpgrader . RemoveAttestationConfigHelmManagement ( ctx ) ; err != nil {
return fmt . Errorf ( "removing helm management from attestation config: %w" , err )
}
return nil
}
// migrateFrom2_11 applies migrations necessary for upgrading from v2.11 to v2.12
// TODO(v2.12): Remove this function after v2.12 is released.
func migrateFrom2_11 ( ctx context . Context , kubeUpgrader kubernetesUpgrader ) error {
// Sanity check to make sure we only run migrations on upgrades with CLI version 2.11 < v < 2.13
if ! constants . BinaryVersion ( ) . MajorMinorEqual ( semver . NewFromInt ( 2 , 12 , 0 , "" ) ) {
return nil
}
if err := kubeUpgrader . RemoveHelmKeepAnnotation ( ctx ) ; err != nil {
return fmt . Errorf ( "removing helm keep annotation: %w" , err )
}
return nil
}
2023-02-01 04:56:47 -05:00
func parseUpgradeApplyFlags ( cmd * cobra . Command ) ( upgradeApplyFlags , error ) {
2023-08-08 09:42:06 -04:00
workDir , err := cmd . Flags ( ) . GetString ( "workspace" )
2023-01-04 07:55:10 -05:00
if err != nil {
2023-02-01 04:56:47 -05:00
return upgradeApplyFlags { } , err
2023-01-04 07:55:10 -05:00
}
yes , err := cmd . Flags ( ) . GetBool ( "yes" )
if err != nil {
2023-02-01 04:56:47 -05:00
return upgradeApplyFlags { } , err
2023-01-04 07:55:10 -05:00
}
timeout , err := cmd . Flags ( ) . GetDuration ( "timeout" )
if err != nil {
2023-02-01 04:56:47 -05:00
return upgradeApplyFlags { } , err
2023-01-04 07:55:10 -05:00
}
2023-01-31 05:45:31 -05:00
force , err := cmd . Flags ( ) . GetBool ( "force" )
if err != nil {
2023-02-01 04:56:47 -05:00
return upgradeApplyFlags { } , fmt . Errorf ( "parsing force argument: %w" , err )
2023-01-31 05:45:31 -05:00
}
2023-05-22 07:31:20 -04:00
logLevelString , err := cmd . Flags ( ) . GetString ( "tf-log" )
if err != nil {
return upgradeApplyFlags { } , fmt . Errorf ( "parsing tf-log string: %w" , err )
}
logLevel , err := terraform . ParseLogLevel ( logLevelString )
if err != nil {
return upgradeApplyFlags { } , fmt . Errorf ( "parsing Terraform log level %s: %w" , logLevelString , err )
}
2023-08-11 09:18:59 -04:00
conformance , err := cmd . Flags ( ) . GetBool ( "conformance" )
if err != nil {
return upgradeApplyFlags { } , fmt . Errorf ( "parsing conformance flag: %w" , err )
}
skipHelmWait , err := cmd . Flags ( ) . GetBool ( "skip-helm-wait" )
if err != nil {
return upgradeApplyFlags { } , fmt . Errorf ( "parsing skip-helm-wait flag: %w" , err )
}
helmWaitMode := helm . WaitModeAtomic
if skipHelmWait {
helmWaitMode = helm . WaitModeNone
}
2023-09-08 08:55:07 -04:00
rawSkipPhases , err := cmd . Flags ( ) . GetStringSlice ( "skip-phases" )
if err != nil {
return upgradeApplyFlags { } , fmt . Errorf ( "parsing skip-phases flag: %w" , err )
}
var skipPhases [ ] skipPhase
for _ , phase := range rawSkipPhases {
switch skipPhase ( phase ) {
case skipInfrastructurePhase , skipHelmPhase , skipImagePhase , skipK8sPhase :
skipPhases = append ( skipPhases , skipPhase ( phase ) )
default :
return upgradeApplyFlags { } , fmt . Errorf ( "invalid phase %s" , phase )
}
}
2023-05-22 07:31:20 -04:00
return upgradeApplyFlags {
2023-08-08 09:42:06 -04:00
pf : pathprefix . New ( workDir ) ,
2023-05-22 07:31:20 -04:00
yes : yes ,
upgradeTimeout : timeout ,
force : force ,
terraformLogLevel : logLevel ,
2023-08-11 09:18:59 -04:00
conformance : conformance ,
helmWaitMode : helmWaitMode ,
2023-09-08 08:55:07 -04:00
skipPhases : skipPhases ,
2023-05-22 07:31:20 -04:00
} , nil
2023-01-04 07:55:10 -05:00
}
2023-08-23 04:35:42 -04:00
func updateClusterIDFile ( tfOutput terraform . ApplyOutput , fileHandler file . Handler ) error {
newIDFile := clusterid . File {
InitSecret : [ ] byte ( tfOutput . Secret ) ,
IP : tfOutput . IP ,
APIServerCertSANs : tfOutput . APIServerCertSANs ,
UID : tfOutput . UID ,
}
if tfOutput . Azure != nil {
newIDFile . AttestationURL = tfOutput . Azure . AttestationURL
}
2023-08-04 07:53:51 -04:00
idFile := & clusterid . File { }
2023-08-23 04:35:42 -04:00
if err := fileHandler . ReadJSON ( constants . ClusterIDsFilename , idFile ) ; err != nil {
return fmt . Errorf ( "reading %s: %w" , constants . ClusterIDsFilename , err )
2023-08-04 07:53:51 -04:00
}
2023-08-23 04:35:42 -04:00
if err := fileHandler . WriteJSON ( constants . ClusterIDsFilename , idFile . Merge ( newIDFile ) , file . OptOverwrite ) ; err != nil {
return fmt . Errorf ( "writing %s: %w" , constants . ClusterIDsFilename , err )
2023-08-04 07:53:51 -04:00
}
return nil
}
2023-02-01 04:56:47 -05:00
type upgradeApplyFlags struct {
2023-08-08 09:42:06 -04:00
pf pathprefix . PathPrefixer
2023-05-22 07:31:20 -04:00
yes bool
upgradeTimeout time . Duration
force bool
terraformLogLevel terraform . LogLevel
2023-08-11 09:18:59 -04:00
conformance bool
helmWaitMode helm . WaitMode
2023-09-08 08:55:07 -04:00
skipPhases skipPhases
}
// skipPhases is a list of phases that can be skipped during the upgrade process.
type skipPhases [ ] skipPhase
// contains returns true if the list of phases contains the given phase.
func ( s skipPhases ) contains ( phase skipPhase ) bool {
for _ , p := range s {
if strings . EqualFold ( string ( p ) , string ( phase ) ) {
return true
}
}
return false
2023-01-04 07:55:10 -05:00
}
2023-08-16 03:59:32 -04:00
type kubernetesUpgrader interface {
2023-09-08 08:55:07 -04:00
UpgradeNodeVersion ( ctx context . Context , conf * config . Config , force , skipImage , skipK8s bool ) error
2023-07-21 10:43:51 -04:00
ExtendClusterConfigCertSANs ( ctx context . Context , alternativeNames [ ] string ) error
2023-08-11 09:18:59 -04:00
GetClusterAttestationConfig ( ctx context . Context , variant variant . Variant ) ( config . AttestationCfg , error )
2023-08-23 02:14:39 -04:00
ApplyJoinConfig ( ctx context . Context , newAttestConfig config . AttestationCfg , measurementSalt [ ] byte ) error
2023-08-24 10:40:47 -04:00
BackupCRs ( ctx context . Context , crds [ ] apiextensionsv1 . CustomResourceDefinition , upgradeDir string ) error
BackupCRDs ( ctx context . Context , upgradeDir string ) ( [ ] apiextensionsv1 . CustomResourceDefinition , error )
2023-08-23 02:14:39 -04:00
// TODO(v2.11): Remove this function after v2.11 is released.
RemoveAttestationConfigHelmManagement ( ctx context . Context ) error
// TODO(v2.12): Remove this function after v2.12 is released.
RemoveHelmKeepAnnotation ( ctx context . Context ) error
2023-08-16 03:59:32 -04:00
}
2023-08-23 04:35:42 -04:00
type clusterUpgrader interface {
PlanClusterUpgrade ( ctx context . Context , outWriter io . Writer , vars terraform . Variables , csp cloudprovider . Provider ) ( bool , error )
ApplyClusterUpgrade ( ctx context . Context , csp cloudprovider . Provider ) ( terraform . ApplyOutput , error )
2023-09-14 05:51:20 -04:00
RestoreClusterWorkspace ( ) error
2022-08-29 10:49:44 -04:00
}