mirror of
				https://github.com/edgelesssys/constellation.git
				synced 2025-10-31 03:39:04 -04:00 
			
		
		
		
	 d1ace13713
			
		
	
	
		d1ace13713
		
			
		
	
	
	
	
		
			
			* Remove `--config` and `--master-secret` falgs * Add `--workspace` flag * In CLI, only work on files with paths created from `cli/internal/cmd` * Properly print values for GCP on IAM create when not directly updating the config --------- Signed-off-by: Daniel Weiße <dw@edgeless.systems>
		
			
				
	
	
		
			204 lines
		
	
	
	
		
			7.5 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			204 lines
		
	
	
	
		
			7.5 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| /*
 | |
| Copyright (c) Edgeless Systems GmbH
 | |
| 
 | |
| SPDX-License-Identifier: AGPL-3.0-only
 | |
| */
 | |
| 
 | |
| package upgrade
 | |
| 
 | |
| import (
 | |
| 	"context"
 | |
| 	"fmt"
 | |
| 	"io"
 | |
| 	"os"
 | |
| 	"path/filepath"
 | |
| 	"strings"
 | |
| 
 | |
| 	"github.com/edgelesssys/constellation/v2/cli/internal/cloudcmd"
 | |
| 	"github.com/edgelesssys/constellation/v2/cli/internal/clusterid"
 | |
| 	"github.com/edgelesssys/constellation/v2/cli/internal/terraform"
 | |
| 	"github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
 | |
| 	"github.com/edgelesssys/constellation/v2/internal/constants"
 | |
| 	"github.com/edgelesssys/constellation/v2/internal/file"
 | |
| )
 | |
| 
 | |
| // NewTerraformUpgrader returns a new TerraformUpgrader.
 | |
| func NewTerraformUpgrader(tfClient tfResourceClient, outWriter io.Writer, fileHandler file.Handler) (*TerraformUpgrader, error) {
 | |
| 	return &TerraformUpgrader{
 | |
| 		tf:            tfClient,
 | |
| 		policyPatcher: cloudcmd.NewAzurePolicyPatcher(),
 | |
| 		outWriter:     outWriter,
 | |
| 		fileHandler:   fileHandler,
 | |
| 	}, nil
 | |
| }
 | |
| 
 | |
| // TerraformUpgrader is responsible for performing Terraform migrations on cluster upgrades.
 | |
| type TerraformUpgrader struct {
 | |
| 	tf            tfResourceClient
 | |
| 	policyPatcher policyPatcher
 | |
| 	outWriter     io.Writer
 | |
| 	fileHandler   file.Handler
 | |
| }
 | |
| 
 | |
| // TerraformUpgradeOptions are the options used for the Terraform upgrade.
 | |
| type TerraformUpgradeOptions struct {
 | |
| 	// LogLevel is the log level used for Terraform.
 | |
| 	LogLevel terraform.LogLevel
 | |
| 	// CSP is the cloud provider to perform the upgrade on.
 | |
| 	CSP cloudprovider.Provider
 | |
| 	// Vars are the Terraform variables used for the upgrade.
 | |
| 	Vars             terraform.Variables
 | |
| 	TFWorkspace      string
 | |
| 	UpgradeWorkspace string
 | |
| }
 | |
| 
 | |
| // CheckTerraformMigrations checks whether Terraform migrations are possible in the current workspace.
 | |
| func checkTerraformMigrations(file file.Handler, upgradeWorkspace, upgradeID, upgradeSubDir string) error {
 | |
| 	var existingFiles []string
 | |
| 	filesToCheck := []string{
 | |
| 		filepath.Join(upgradeWorkspace, upgradeID, upgradeSubDir),
 | |
| 	}
 | |
| 
 | |
| 	for _, f := range filesToCheck {
 | |
| 		if err := checkFileExists(file, &existingFiles, f); err != nil {
 | |
| 			return fmt.Errorf("checking terraform migrations: %w", err)
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	if len(existingFiles) > 0 {
 | |
| 		return fmt.Errorf("file(s) %s already exist", strings.Join(existingFiles, ", "))
 | |
| 	}
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| // CheckTerraformMigrations checks whether Terraform migrations are possible in the current workspace.
 | |
| // If the files that will be written during the upgrade already exist, it returns an error.
 | |
| func (u *TerraformUpgrader) CheckTerraformMigrations(upgradeWorkspace, upgradeID, upgradeSubDir string) error {
 | |
| 	return checkTerraformMigrations(u.fileHandler, upgradeWorkspace, upgradeID, upgradeSubDir)
 | |
| }
 | |
| 
 | |
| // checkFileExists checks whether a file exists and adds it to the existingFiles slice if it does.
 | |
| func checkFileExists(fileHandler file.Handler, existingFiles *[]string, filename string) error {
 | |
| 	_, err := fileHandler.Stat(filename)
 | |
| 	if err != nil {
 | |
| 		if !os.IsNotExist(err) {
 | |
| 			return fmt.Errorf("checking %s: %w", filename, err)
 | |
| 		}
 | |
| 		return nil
 | |
| 	}
 | |
| 
 | |
| 	*existingFiles = append(*existingFiles, filename)
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| // PlanTerraformMigrations prepares the upgrade workspace and plans the Terraform migrations for the Constellation upgrade.
 | |
| // If a diff exists, it's being written to the upgrader's output writer. It also returns
 | |
| // a bool indicating whether a diff exists.
 | |
| func (u *TerraformUpgrader) PlanTerraformMigrations(ctx context.Context, opts TerraformUpgradeOptions, upgradeID string) (bool, error) {
 | |
| 	// Prepare the new Terraform workspace and backup the old one
 | |
| 	err := u.tf.PrepareUpgradeWorkspace(
 | |
| 		filepath.Join("terraform", strings.ToLower(opts.CSP.String())),
 | |
| 		opts.TFWorkspace,
 | |
| 		filepath.Join(opts.UpgradeWorkspace, upgradeID, constants.TerraformUpgradeWorkingDir),
 | |
| 		filepath.Join(opts.UpgradeWorkspace, upgradeID, constants.TerraformUpgradeBackupDir),
 | |
| 		opts.Vars,
 | |
| 	)
 | |
| 	if err != nil {
 | |
| 		return false, fmt.Errorf("preparing terraform workspace: %w", err)
 | |
| 	}
 | |
| 
 | |
| 	hasDiff, err := u.tf.Plan(ctx, opts.LogLevel)
 | |
| 	if err != nil {
 | |
| 		return false, fmt.Errorf("terraform plan: %w", err)
 | |
| 	}
 | |
| 
 | |
| 	if hasDiff {
 | |
| 		if err := u.tf.ShowPlan(ctx, opts.LogLevel, u.outWriter); err != nil {
 | |
| 			return false, fmt.Errorf("terraform show plan: %w", err)
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	return hasDiff, nil
 | |
| }
 | |
| 
 | |
| // CleanUpTerraformMigrations cleans up the Terraform migration workspace, for example when an upgrade is
 | |
| // aborted by the user.
 | |
| func (u *TerraformUpgrader) CleanUpTerraformMigrations(upgradeWorkspace, upgradeID string) error {
 | |
| 	return CleanUpTerraformMigrations(upgradeWorkspace, upgradeID, u.fileHandler)
 | |
| }
 | |
| 
 | |
| // CleanUpTerraformMigrations cleans up the Terraform upgrade directory.
 | |
| func CleanUpTerraformMigrations(upgradeWorkspace, upgradeID string, fileHandler file.Handler) error {
 | |
| 	upgradeDir := filepath.Join(upgradeWorkspace, upgradeID)
 | |
| 	if err := fileHandler.RemoveAll(upgradeDir); err != nil {
 | |
| 		return fmt.Errorf("cleaning up file %s: %w", upgradeDir, err)
 | |
| 	}
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| // ApplyTerraformMigrations applies the migrations planned by PlanTerraformMigrations.
 | |
| // If PlanTerraformMigrations has not been executed before, it will return an error.
 | |
| // In case of a successful upgrade, the output will be written to the specified file and the old Terraform directory is replaced
 | |
| // By the new one.
 | |
| func (u *TerraformUpgrader) ApplyTerraformMigrations(ctx context.Context, opts TerraformUpgradeOptions, upgradeID string) (clusterid.File, error) {
 | |
| 	tfOutput, err := u.tf.CreateCluster(ctx, opts.CSP, opts.LogLevel)
 | |
| 	if err != nil {
 | |
| 		return clusterid.File{}, fmt.Errorf("terraform apply: %w", err)
 | |
| 	}
 | |
| 
 | |
| 	clusterID := clusterid.File{
 | |
| 		CloudProvider:     opts.CSP,
 | |
| 		InitSecret:        []byte(tfOutput.Secret),
 | |
| 		IP:                tfOutput.IP,
 | |
| 		APIServerCertSANs: tfOutput.APIServerCertSANs,
 | |
| 		UID:               tfOutput.UID,
 | |
| 	}
 | |
| 
 | |
| 	// Patch MAA policy if we applied an Azure upgrade.
 | |
| 	if tfOutput.Azure != nil {
 | |
| 		if err := u.policyPatcher.Patch(ctx, tfOutput.Azure.AttestationURL); err != nil {
 | |
| 			return clusterid.File{}, fmt.Errorf("patching policies: %w", err)
 | |
| 		}
 | |
| 		clusterID.AttestationURL = tfOutput.Azure.AttestationURL
 | |
| 	}
 | |
| 
 | |
| 	if err := u.fileHandler.RemoveAll(opts.TFWorkspace); err != nil {
 | |
| 		return clusterid.File{}, fmt.Errorf("removing old terraform directory: %w", err)
 | |
| 	}
 | |
| 
 | |
| 	if err := u.fileHandler.CopyDir(
 | |
| 		filepath.Join(opts.UpgradeWorkspace, upgradeID, constants.TerraformUpgradeWorkingDir),
 | |
| 		opts.TFWorkspace,
 | |
| 	); err != nil {
 | |
| 		return clusterid.File{}, fmt.Errorf("replacing old terraform directory with new one: %w", err)
 | |
| 	}
 | |
| 
 | |
| 	if err := u.fileHandler.RemoveAll(filepath.Join(opts.UpgradeWorkspace, upgradeID, constants.TerraformUpgradeWorkingDir)); err != nil {
 | |
| 		return clusterid.File{}, fmt.Errorf("removing terraform upgrade directory: %w", err)
 | |
| 	}
 | |
| 
 | |
| 	return clusterID, nil
 | |
| }
 | |
| 
 | |
| type tfClientCommon interface {
 | |
| 	ShowPlan(ctx context.Context, logLevel terraform.LogLevel, output io.Writer) error
 | |
| 	Plan(ctx context.Context, logLevel terraform.LogLevel) (bool, error)
 | |
| }
 | |
| 
 | |
| // tfResourceClient is a Terraform client for managing cluster resources.
 | |
| type tfResourceClient interface {
 | |
| 	PrepareUpgradeWorkspace(embeddedPath, oldWorkingDir, newWorkingDir, backupDir string, vars terraform.Variables) error
 | |
| 	CreateCluster(ctx context.Context, provider cloudprovider.Provider, logLevel terraform.LogLevel) (terraform.ApplyOutput, error)
 | |
| 	tfClientCommon
 | |
| }
 | |
| 
 | |
| // tfIAMClient is a Terraform client for managing IAM resources.
 | |
| type tfIAMClient interface {
 | |
| 	ApplyIAMConfig(ctx context.Context, csp cloudprovider.Provider, logLevel terraform.LogLevel) (terraform.IAMOutput, error)
 | |
| 	tfClientCommon
 | |
| }
 | |
| 
 | |
| // policyPatcher interacts with the CSP (currently only applies for Azure) to update the attestation policy.
 | |
| type policyPatcher interface {
 | |
| 	Patch(ctx context.Context, attestationURL string) error
 | |
| }
 |