/* Copyright (c) Edgeless Systems GmbH SPDX-License-Identifier: AGPL-3.0-only */ package cmd import ( "context" "errors" "fmt" "io" "path/filepath" "github.com/edgelesssys/constellation/v2/cli/internal/cloudcmd" "github.com/edgelesssys/constellation/v2/cli/internal/terraform" "github.com/edgelesssys/constellation/v2/internal/api/attestationconfigapi" "github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider" "github.com/edgelesssys/constellation/v2/internal/config" "github.com/edgelesssys/constellation/v2/internal/constants" "github.com/edgelesssys/constellation/v2/internal/file" "github.com/spf13/afero" "github.com/spf13/cobra" "github.com/spf13/pflag" ) func newIAMUpgradeCmd() *cobra.Command { cmd := &cobra.Command{ Use: "upgrade", Short: "Find and apply upgrades to your IAM profile", Long: "Find and apply upgrades to your IAM profile.", Args: cobra.ExactArgs(0), } cmd.AddCommand(newIAMUpgradeApplyCmd()) return cmd } func newIAMUpgradeApplyCmd() *cobra.Command { cmd := &cobra.Command{ Use: "apply", Short: "Apply an upgrade to an IAM profile", Long: "Apply an upgrade to an IAM profile.", Args: cobra.NoArgs, RunE: runIAMUpgradeApply, } cmd.Flags().BoolP("yes", "y", false, "run upgrades without further confirmation") return cmd } type iamUpgradeApplyFlags struct { rootFlags yes bool } func (f *iamUpgradeApplyFlags) parse(flags *pflag.FlagSet) error { if err := f.rootFlags.parse(flags); err != nil { return err } yes, err := flags.GetBool("yes") if err != nil { return fmt.Errorf("getting 'yes' flag: %w", err) } f.yes = yes return nil } type iamUpgradeApplyCmd struct { fileHandler file.Handler log debugLog configFetcher attestationconfigapi.Fetcher flags iamUpgradeApplyFlags } func runIAMUpgradeApply(cmd *cobra.Command, _ []string) error { fileHandler := file.NewHandler(afero.NewOsFs()) upgradeID := generateUpgradeID(upgradeCmdKindIAM) upgradeDir := filepath.Join(constants.UpgradeDir, upgradeID) configFetcher := attestationconfigapi.NewFetcher() iamMigrateCmd, err := cloudcmd.NewIAMUpgrader( cmd.Context(), constants.TerraformIAMWorkingDir, upgradeDir, terraform.LogLevelDebug, fileHandler, ) if err != nil { return fmt.Errorf("setting up IAM migration command: %w", err) } log, err := newCLILogger(cmd) if err != nil { return fmt.Errorf("setting up logger: %w", err) } i := iamUpgradeApplyCmd{ fileHandler: fileHandler, log: log, configFetcher: configFetcher, } if err := i.flags.parse(cmd.Flags()); err != nil { return err } return i.iamUpgradeApply(cmd, iamMigrateCmd, upgradeDir) } func (i iamUpgradeApplyCmd) iamUpgradeApply(cmd *cobra.Command, iamUpgrader iamUpgrader, upgradeDir string) error { conf, err := config.New(i.fileHandler, constants.ConfigFilename, i.configFetcher, i.flags.force) var configValidationErr *config.ValidationError if errors.As(err, &configValidationErr) { cmd.PrintErrln(configValidationErr.LongMessage()) } if err != nil { return err } vars, err := cloudcmd.TerraformIAMUpgradeVars(conf, i.fileHandler) if err != nil { return fmt.Errorf("getting terraform variables: %w", err) } hasDiff, err := iamUpgrader.PlanIAMUpgrade(cmd.Context(), cmd.OutOrStderr(), vars, conf.GetProvider()) if err != nil { return fmt.Errorf("planning terraform migrations: %w", err) } if !hasDiff && !i.flags.force { cmd.Println("No IAM migrations necessary.") return nil } // If there are any Terraform migrations to apply, ask for confirmation cmd.Println("The IAM upgrade requires a migration by applying an updated Terraform template. Please manually review the suggested changes.") if !i.flags.yes { ok, err := askToConfirm(cmd, "Do you want to apply the IAM upgrade?") if err != nil { return fmt.Errorf("asking for confirmation: %w", err) } if !ok { cmd.Println("Aborting upgrade.") // 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 := iamUpgrader.RestoreIAMWorkspace(); err != nil { return fmt.Errorf( "restoring Terraform workspace: %w, restore the Terraform workspace manually from %s ", err, filepath.Join(upgradeDir, constants.TerraformIAMUpgradeBackupDir), ) } return errors.New("IAM upgrade aborted by user") } } i.log.Debug("Applying Terraform IAM migrations") if err := iamUpgrader.ApplyIAMUpgrade(cmd.Context(), conf.GetProvider()); err != nil { return fmt.Errorf("applying terraform migrations: %w", err) } cmd.Println("IAM profile successfully applied.") return nil } type iamUpgrader interface { PlanIAMUpgrade(ctx context.Context, outWriter io.Writer, vars terraform.Variables, csp cloudprovider.Provider) (bool, error) ApplyIAMUpgrade(ctx context.Context, csp cloudprovider.Provider) error RestoreIAMWorkspace() error }