mirror of
https://github.com/edgelesssys/constellation.git
synced 2025-02-02 18:44:49 -05:00
cli: perform upgrades in-place in Terraform workspace (#2317)
* perform upgrades in-place in terraform workspace Signed-off-by: Moritz Sanft <58110325+msanft@users.noreply.github.com> * update buildfiles Signed-off-by: Moritz Sanft <58110325+msanft@users.noreply.github.com> * add iam upgrade apply test Signed-off-by: Moritz Sanft <58110325+msanft@users.noreply.github.com> * update buildfiles Signed-off-by: Moritz Sanft <58110325+msanft@users.noreply.github.com> * fix linter Signed-off-by: Moritz Sanft <58110325+msanft@users.noreply.github.com> * make config fetcher stubbable Signed-off-by: Moritz Sanft <58110325+msanft@users.noreply.github.com> * change workspace restoring behaviour Signed-off-by: Moritz Sanft <58110325+msanft@users.noreply.github.com> * allow overwriting existing Terraform files Signed-off-by: Moritz Sanft <58110325+msanft@users.noreply.github.com> * allow overwrites of TF variables Signed-off-by: Moritz Sanft <58110325+msanft@users.noreply.github.com> * fix iam upgrade apply Signed-off-by: Moritz Sanft <58110325+msanft@users.noreply.github.com> * fix embed directive Signed-off-by: Moritz Sanft <58110325+msanft@users.noreply.github.com> * make loader test less brittle Signed-off-by: Moritz Sanft <58110325+msanft@users.noreply.github.com> * pass upgrade ID to user Signed-off-by: Moritz Sanft <58110325+msanft@users.noreply.github.com> * naming nit Signed-off-by: Moritz Sanft <58110325+msanft@users.noreply.github.com> * use upgradeDir Signed-off-by: Moritz Sanft <58110325+msanft@users.noreply.github.com> * tidy Signed-off-by: Moritz Sanft <58110325+msanft@users.noreply.github.com> --------- Signed-off-by: Moritz Sanft <58110325+msanft@users.noreply.github.com>
This commit is contained in:
parent
9c54ff06e0
commit
95cf4bdf21
@ -52,7 +52,6 @@ go_test(
|
|||||||
"clusterupgrade_test.go",
|
"clusterupgrade_test.go",
|
||||||
"create_test.go",
|
"create_test.go",
|
||||||
"iam_test.go",
|
"iam_test.go",
|
||||||
"iamupgrade_test.go",
|
|
||||||
"patch_test.go",
|
"patch_test.go",
|
||||||
"rollback_test.go",
|
"rollback_test.go",
|
||||||
"terminate_test.go",
|
"terminate_test.go",
|
||||||
|
@ -45,7 +45,7 @@ type tfIAMClient interface {
|
|||||||
type tfUpgradePlanner interface {
|
type tfUpgradePlanner interface {
|
||||||
ShowPlan(ctx context.Context, logLevel terraform.LogLevel, output io.Writer) error
|
ShowPlan(ctx context.Context, logLevel terraform.LogLevel, output io.Writer) error
|
||||||
Plan(ctx context.Context, logLevel terraform.LogLevel) (bool, error)
|
Plan(ctx context.Context, logLevel terraform.LogLevel) (bool, error)
|
||||||
PrepareUpgradeWorkspace(embeddedPath, oldWorkingDir, backupDir string, vars terraform.Variables) error
|
PrepareUpgradeWorkspace(embeddedPath, backupDir string, vars terraform.Variables) error
|
||||||
}
|
}
|
||||||
|
|
||||||
type tfIAMUpgradeClient interface {
|
type tfIAMUpgradeClient interface {
|
||||||
|
@ -35,7 +35,7 @@ type ClusterUpgrader struct {
|
|||||||
func NewClusterUpgrader(ctx context.Context, existingWorkspace, upgradeWorkspace string,
|
func NewClusterUpgrader(ctx context.Context, existingWorkspace, upgradeWorkspace string,
|
||||||
logLevel terraform.LogLevel, fileHandler file.Handler,
|
logLevel terraform.LogLevel, fileHandler file.Handler,
|
||||||
) (*ClusterUpgrader, error) {
|
) (*ClusterUpgrader, error) {
|
||||||
tfClient, err := terraform.New(ctx, filepath.Join(upgradeWorkspace, constants.TerraformUpgradeWorkingDir))
|
tfClient, err := terraform.New(ctx, constants.TerraformWorkingDir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("setting up terraform client: %w", err)
|
return nil, fmt.Errorf("setting up terraform client: %w", err)
|
||||||
}
|
}
|
||||||
@ -57,11 +57,17 @@ func (u *ClusterUpgrader) PlanClusterUpgrade(ctx context.Context, outWriter io.W
|
|||||||
return planUpgrade(
|
return planUpgrade(
|
||||||
ctx, u.tf, u.fileHandler, outWriter, u.logLevel, vars,
|
ctx, u.tf, u.fileHandler, outWriter, u.logLevel, vars,
|
||||||
filepath.Join("terraform", strings.ToLower(csp.String())),
|
filepath.Join("terraform", strings.ToLower(csp.String())),
|
||||||
u.existingWorkspace,
|
|
||||||
filepath.Join(u.upgradeWorkspace, constants.TerraformUpgradeBackupDir),
|
filepath.Join(u.upgradeWorkspace, constants.TerraformUpgradeBackupDir),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RestoreClusterWorkspace rolls back the existing workspace to the backup directory created when planning an upgrade,
|
||||||
|
// when the user decides to not apply an upgrade after planning it.
|
||||||
|
// Note that this will not apply the restored state from the backup.
|
||||||
|
func (u *ClusterUpgrader) RestoreClusterWorkspace() error {
|
||||||
|
return restoreBackup(u.fileHandler, u.existingWorkspace, filepath.Join(u.upgradeWorkspace, constants.TerraformUpgradeBackupDir))
|
||||||
|
}
|
||||||
|
|
||||||
// ApplyClusterUpgrade applies the Terraform migrations planned by PlanClusterUpgrade.
|
// ApplyClusterUpgrade applies the Terraform migrations planned by PlanClusterUpgrade.
|
||||||
// On success, the workspace of the Upgrader replaces the existing Terraform workspace.
|
// On success, the workspace of the Upgrader replaces the existing Terraform workspace.
|
||||||
func (u *ClusterUpgrader) ApplyClusterUpgrade(ctx context.Context, csp cloudprovider.Provider) (terraform.ApplyOutput, error) {
|
func (u *ClusterUpgrader) ApplyClusterUpgrade(ctx context.Context, csp cloudprovider.Provider) (terraform.ApplyOutput, error) {
|
||||||
@ -75,13 +81,5 @@ func (u *ClusterUpgrader) ApplyClusterUpgrade(ctx context.Context, csp cloudprov
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := moveUpgradeToCurrent(
|
|
||||||
u.fileHandler,
|
|
||||||
u.existingWorkspace,
|
|
||||||
filepath.Join(u.upgradeWorkspace, constants.TerraformUpgradeWorkingDir),
|
|
||||||
); err != nil {
|
|
||||||
return tfOutput, fmt.Errorf("promoting upgrade workspace to current workspace: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return tfOutput, nil
|
return tfOutput, nil
|
||||||
}
|
}
|
||||||
|
@ -198,6 +198,6 @@ func (t *tfClusterUpgradeStub) ApplyCluster(_ context.Context, _ cloudprovider.P
|
|||||||
return terraform.ApplyOutput{}, t.applyErr
|
return terraform.ApplyOutput{}, t.applyErr
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *tfClusterUpgradeStub) PrepareUpgradeWorkspace(_, _, _ string, _ terraform.Variables) error {
|
func (t *tfClusterUpgradeStub) PrepareUpgradeWorkspace(_, _ string, _ terraform.Variables) error {
|
||||||
return t.prepareWorkspaceErr
|
return t.prepareWorkspaceErr
|
||||||
}
|
}
|
||||||
|
@ -42,7 +42,7 @@ type IAMUpgrader struct {
|
|||||||
func NewIAMUpgrader(ctx context.Context, existingWorkspace, upgradeWorkspace string,
|
func NewIAMUpgrader(ctx context.Context, existingWorkspace, upgradeWorkspace string,
|
||||||
logLevel terraform.LogLevel, fileHandler file.Handler,
|
logLevel terraform.LogLevel, fileHandler file.Handler,
|
||||||
) (*IAMUpgrader, error) {
|
) (*IAMUpgrader, error) {
|
||||||
tfClient, err := terraform.New(ctx, filepath.Join(upgradeWorkspace, constants.TerraformIAMUpgradeWorkingDir))
|
tfClient, err := terraform.New(ctx, constants.TerraformIAMWorkingDir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("setting up terraform client: %w", err)
|
return nil, fmt.Errorf("setting up terraform client: %w", err)
|
||||||
}
|
}
|
||||||
@ -62,11 +62,17 @@ func (u *IAMUpgrader) PlanIAMUpgrade(ctx context.Context, outWriter io.Writer, v
|
|||||||
return planUpgrade(
|
return planUpgrade(
|
||||||
ctx, u.tf, u.fileHandler, outWriter, u.logLevel, vars,
|
ctx, u.tf, u.fileHandler, outWriter, u.logLevel, vars,
|
||||||
filepath.Join("terraform", "iam", strings.ToLower(csp.String())),
|
filepath.Join("terraform", "iam", strings.ToLower(csp.String())),
|
||||||
u.existingWorkspace,
|
|
||||||
filepath.Join(u.upgradeWorkspace, constants.TerraformIAMUpgradeBackupDir),
|
filepath.Join(u.upgradeWorkspace, constants.TerraformIAMUpgradeBackupDir),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RestoreIAMWorkspace rolls back the existing workspace to the backup directory created when planning an upgrade,
|
||||||
|
// when the user decides to not apply an upgrade after planning it.
|
||||||
|
// Note that this will not apply the restored state from the backup.
|
||||||
|
func (u *IAMUpgrader) RestoreIAMWorkspace() error {
|
||||||
|
return restoreBackup(u.fileHandler, u.existingWorkspace, filepath.Join(u.upgradeWorkspace, constants.TerraformIAMUpgradeBackupDir))
|
||||||
|
}
|
||||||
|
|
||||||
// ApplyIAMUpgrade applies the Terraform IAM migrations planned by PlanIAMUpgrade.
|
// ApplyIAMUpgrade applies the Terraform IAM migrations planned by PlanIAMUpgrade.
|
||||||
// On success, the workspace of the Upgrader replaces the existing Terraform workspace.
|
// On success, the workspace of the Upgrader replaces the existing Terraform workspace.
|
||||||
func (u *IAMUpgrader) ApplyIAMUpgrade(ctx context.Context, csp cloudprovider.Provider) error {
|
func (u *IAMUpgrader) ApplyIAMUpgrade(ctx context.Context, csp cloudprovider.Provider) error {
|
||||||
@ -74,13 +80,5 @@ func (u *IAMUpgrader) ApplyIAMUpgrade(ctx context.Context, csp cloudprovider.Pro
|
|||||||
return fmt.Errorf("terraform apply: %w", err)
|
return fmt.Errorf("terraform apply: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := moveUpgradeToCurrent(
|
|
||||||
u.fileHandler,
|
|
||||||
u.existingWorkspace,
|
|
||||||
filepath.Join(u.upgradeWorkspace, constants.TerraformIAMUpgradeWorkingDir),
|
|
||||||
); err != nil {
|
|
||||||
return fmt.Errorf("promoting upgrade workspace to current workspace: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -1,136 +0,0 @@
|
|||||||
/*
|
|
||||||
Copyright (c) Edgeless Systems GmbH
|
|
||||||
|
|
||||||
SPDX-License-Identifier: AGPL-3.0-only
|
|
||||||
*/
|
|
||||||
|
|
||||||
package cloudcmd
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"io"
|
|
||||||
"path/filepath"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"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"
|
|
||||||
"github.com/spf13/afero"
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestIAMMigrate(t *testing.T) {
|
|
||||||
assert := assert.New(t)
|
|
||||||
upgradeID := "test-upgrade"
|
|
||||||
upgradeDir := filepath.Join(constants.UpgradeDir, upgradeID, constants.TerraformIAMUpgradeWorkingDir)
|
|
||||||
fs, file := setupMemFSAndFileHandler(t, []string{"terraform.tfvars", "terraform.tfstate"}, []byte("OLD"))
|
|
||||||
csp := cloudprovider.AWS
|
|
||||||
|
|
||||||
// act
|
|
||||||
fakeTfClient := &tfIAMUpgradeStub{upgradeID: upgradeID, file: file}
|
|
||||||
sut := &IAMUpgrader{
|
|
||||||
tf: fakeTfClient,
|
|
||||||
logLevel: terraform.LogLevelDebug,
|
|
||||||
existingWorkspace: constants.TerraformIAMWorkingDir,
|
|
||||||
upgradeWorkspace: filepath.Join(constants.UpgradeDir, upgradeID),
|
|
||||||
fileHandler: file,
|
|
||||||
}
|
|
||||||
hasDiff, err := sut.PlanIAMUpgrade(context.Background(), io.Discard, &terraform.QEMUVariables{}, csp)
|
|
||||||
|
|
||||||
// assert
|
|
||||||
assert.NoError(err)
|
|
||||||
assert.False(hasDiff)
|
|
||||||
assertFileExists(t, fs, filepath.Join(upgradeDir, "terraform.tfvars"))
|
|
||||||
assertFileExists(t, fs, filepath.Join(upgradeDir, "terraform.tfstate"))
|
|
||||||
|
|
||||||
// act
|
|
||||||
err = sut.ApplyIAMUpgrade(context.Background(), csp)
|
|
||||||
assert.NoError(err)
|
|
||||||
|
|
||||||
// assert
|
|
||||||
assertFileReadsContent(t, file, filepath.Join(constants.TerraformIAMWorkingDir, "terraform.tfvars"), "NEW")
|
|
||||||
assertFileReadsContent(t, file, filepath.Join(constants.TerraformIAMWorkingDir, "terraform.tfstate"), "NEW")
|
|
||||||
assertFileDoesntExist(t, fs, filepath.Join(upgradeDir))
|
|
||||||
}
|
|
||||||
|
|
||||||
func assertFileReadsContent(t *testing.T, file file.Handler, path string, expectedContent string) {
|
|
||||||
t.Helper()
|
|
||||||
bt, err := file.Read(path)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
assert.Equal(t, expectedContent, string(bt))
|
|
||||||
}
|
|
||||||
|
|
||||||
func assertFileExists(t *testing.T, fs afero.Fs, path string) {
|
|
||||||
t.Helper()
|
|
||||||
res, err := fs.Stat(path)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
assert.NotNil(t, res)
|
|
||||||
}
|
|
||||||
|
|
||||||
func assertFileDoesntExist(t *testing.T, fs afero.Fs, path string) {
|
|
||||||
t.Helper()
|
|
||||||
res, err := fs.Stat(path)
|
|
||||||
assert.Error(t, err)
|
|
||||||
assert.Nil(t, res)
|
|
||||||
}
|
|
||||||
|
|
||||||
// setupMemFSAndFileHandler sets up a file handler with a memory file system and writes the given files with the given content.
|
|
||||||
func setupMemFSAndFileHandler(t *testing.T, files []string, content []byte) (afero.Fs, file.Handler) {
|
|
||||||
fs := afero.NewMemMapFs()
|
|
||||||
file := file.NewHandler(fs)
|
|
||||||
err := file.MkdirAll(constants.TerraformIAMWorkingDir)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
for _, f := range files {
|
|
||||||
err := file.Write(filepath.Join(constants.TerraformIAMWorkingDir, f), content)
|
|
||||||
require.NoError(t, err)
|
|
||||||
}
|
|
||||||
return fs, file
|
|
||||||
}
|
|
||||||
|
|
||||||
type tfIAMUpgradeStub struct {
|
|
||||||
upgradeID string
|
|
||||||
file file.Handler
|
|
||||||
applyErr error
|
|
||||||
planErr error
|
|
||||||
planDiff bool
|
|
||||||
showErr error
|
|
||||||
prepareWorkspaceErr error
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *tfIAMUpgradeStub) Plan(_ context.Context, _ terraform.LogLevel) (bool, error) {
|
|
||||||
return t.planDiff, t.planErr
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *tfIAMUpgradeStub) ShowPlan(_ context.Context, _ terraform.LogLevel, _ io.Writer) error {
|
|
||||||
return t.showErr
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *tfIAMUpgradeStub) ApplyIAM(_ context.Context, _ cloudprovider.Provider, _ terraform.LogLevel) (terraform.IAMOutput, error) {
|
|
||||||
if t.applyErr != nil {
|
|
||||||
return terraform.IAMOutput{}, t.applyErr
|
|
||||||
}
|
|
||||||
|
|
||||||
upgradeDir := filepath.Join(constants.UpgradeDir, t.upgradeID, constants.TerraformIAMUpgradeWorkingDir)
|
|
||||||
if err := t.file.Write(filepath.Join(upgradeDir, "terraform.tfvars"), []byte("NEW"), file.OptOverwrite); err != nil {
|
|
||||||
return terraform.IAMOutput{}, err
|
|
||||||
}
|
|
||||||
if err := t.file.Write(filepath.Join(upgradeDir, "terraform.tfstate"), []byte("NEW"), file.OptOverwrite); err != nil {
|
|
||||||
return terraform.IAMOutput{}, err
|
|
||||||
}
|
|
||||||
return terraform.IAMOutput{}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *tfIAMUpgradeStub) PrepareUpgradeWorkspace(_, _, _ string, _ terraform.Variables) error {
|
|
||||||
if t.prepareWorkspaceErr != nil {
|
|
||||||
return t.prepareWorkspaceErr
|
|
||||||
}
|
|
||||||
|
|
||||||
upgradeDir := filepath.Join(constants.UpgradeDir, t.upgradeID, constants.TerraformIAMUpgradeWorkingDir)
|
|
||||||
if err := t.file.Write(filepath.Join(upgradeDir, "terraform.tfvars"), []byte("OLD")); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return t.file.Write(filepath.Join(upgradeDir, "terraform.tfstate"), []byte("OLD"))
|
|
||||||
}
|
|
@ -21,16 +21,15 @@ import (
|
|||||||
func planUpgrade(
|
func planUpgrade(
|
||||||
ctx context.Context, tfClient tfUpgradePlanner, fileHandler file.Handler,
|
ctx context.Context, tfClient tfUpgradePlanner, fileHandler file.Handler,
|
||||||
outWriter io.Writer, logLevel terraform.LogLevel, vars terraform.Variables,
|
outWriter io.Writer, logLevel terraform.LogLevel, vars terraform.Variables,
|
||||||
templateDir, existingWorkspace, backupDir string,
|
templateDir, backupDir string,
|
||||||
) (bool, error) {
|
) (bool, error) {
|
||||||
if err := ensureFileNotExist(fileHandler, backupDir); err != nil {
|
if err := ensureFileNotExist(fileHandler, backupDir); err != nil {
|
||||||
return false, fmt.Errorf("workspace is not clean: %w", err)
|
return false, fmt.Errorf("backup directory %s already exists: %w", backupDir, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Prepare the new Terraform workspace and backup the old one
|
// Backup the old Terraform workspace and move the embedded Terraform files into the workspace.
|
||||||
err := tfClient.PrepareUpgradeWorkspace(
|
err := tfClient.PrepareUpgradeWorkspace(
|
||||||
templateDir,
|
templateDir,
|
||||||
existingWorkspace,
|
|
||||||
backupDir,
|
backupDir,
|
||||||
vars,
|
vars,
|
||||||
)
|
)
|
||||||
@ -52,20 +51,20 @@ func planUpgrade(
|
|||||||
return hasDiff, nil
|
return hasDiff, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// moveUpgradeToCurrent replaces the an existing Terraform workspace with a workspace holding migrated Terraform resources.
|
// restoreBackup replaces the existing Terraform workspace with the backup.
|
||||||
func moveUpgradeToCurrent(fileHandler file.Handler, existingWorkspace, upgradeWorkingDir string) error {
|
func restoreBackup(fileHandler file.Handler, workingDir, backupDir string) error {
|
||||||
if err := fileHandler.RemoveAll(existingWorkspace); err != nil {
|
if err := fileHandler.RemoveAll(workingDir); err != nil {
|
||||||
return fmt.Errorf("removing old terraform directory: %w", err)
|
return fmt.Errorf("removing existing workspace: %w", err)
|
||||||
}
|
}
|
||||||
if err := fileHandler.CopyDir(
|
if err := fileHandler.CopyDir(
|
||||||
upgradeWorkingDir,
|
backupDir,
|
||||||
existingWorkspace,
|
workingDir,
|
||||||
); err != nil {
|
); err != nil {
|
||||||
return fmt.Errorf("replacing old terraform directory with new one: %w", err)
|
return fmt.Errorf("replacing terraform workspace with backup: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := fileHandler.RemoveAll(upgradeWorkingDir); err != nil {
|
if err := fileHandler.RemoveAll(backupDir); err != nil {
|
||||||
return fmt.Errorf("removing terraform upgrade directory: %w", err)
|
return fmt.Errorf("removing backup directory: %w", err)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -87,7 +87,7 @@ func TestPlanUpgrade(t *testing.T) {
|
|||||||
hasDiff, err := planUpgrade(
|
hasDiff, err := planUpgrade(
|
||||||
context.Background(), tc.tf, fs, io.Discard, terraform.LogLevelDebug,
|
context.Background(), tc.tf, fs, io.Discard, terraform.LogLevelDebug,
|
||||||
&terraform.QEMUVariables{},
|
&terraform.QEMUVariables{},
|
||||||
"existing", "upgrade", "backup",
|
"test", "backup",
|
||||||
)
|
)
|
||||||
if tc.wantErr {
|
if tc.wantErr {
|
||||||
assert.Error(err)
|
assert.Error(err)
|
||||||
@ -99,9 +99,9 @@ func TestPlanUpgrade(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMoveUpgradeToCurrent(t *testing.T) {
|
func TestRestoreBackup(t *testing.T) {
|
||||||
existingWorkspace := "foo"
|
existingWorkspace := "foo"
|
||||||
upgradeWorkingDir := "bar"
|
backupDir := "bar"
|
||||||
|
|
||||||
testCases := map[string]struct {
|
testCases := map[string]struct {
|
||||||
prepareFs func(require *require.Assertions) file.Handler
|
prepareFs func(require *require.Assertions) file.Handler
|
||||||
@ -111,18 +111,18 @@ func TestMoveUpgradeToCurrent(t *testing.T) {
|
|||||||
prepareFs: func(require *require.Assertions) file.Handler {
|
prepareFs: func(require *require.Assertions) file.Handler {
|
||||||
fs := file.NewHandler(afero.NewMemMapFs())
|
fs := file.NewHandler(afero.NewMemMapFs())
|
||||||
require.NoError(fs.MkdirAll(existingWorkspace))
|
require.NoError(fs.MkdirAll(existingWorkspace))
|
||||||
require.NoError(fs.MkdirAll(upgradeWorkingDir))
|
require.NoError(fs.MkdirAll(backupDir))
|
||||||
return fs
|
return fs
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"old workspace does not exist": {
|
"existing workspace does not exist": {
|
||||||
prepareFs: func(require *require.Assertions) file.Handler {
|
prepareFs: func(require *require.Assertions) file.Handler {
|
||||||
fs := file.NewHandler(afero.NewMemMapFs())
|
fs := file.NewHandler(afero.NewMemMapFs())
|
||||||
require.NoError(fs.MkdirAll(upgradeWorkingDir))
|
require.NoError(fs.MkdirAll(backupDir))
|
||||||
return fs
|
return fs
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"upgrade working dir does not exist": {
|
"backup dir does not exist": {
|
||||||
prepareFs: func(require *require.Assertions) file.Handler {
|
prepareFs: func(require *require.Assertions) file.Handler {
|
||||||
fs := file.NewHandler(afero.NewMemMapFs())
|
fs := file.NewHandler(afero.NewMemMapFs())
|
||||||
require.NoError(fs.MkdirAll(existingWorkspace))
|
require.NoError(fs.MkdirAll(existingWorkspace))
|
||||||
@ -135,8 +135,7 @@ func TestMoveUpgradeToCurrent(t *testing.T) {
|
|||||||
memFS := afero.NewMemMapFs()
|
memFS := afero.NewMemMapFs()
|
||||||
fs := file.NewHandler(memFS)
|
fs := file.NewHandler(memFS)
|
||||||
require.NoError(fs.MkdirAll(existingWorkspace))
|
require.NoError(fs.MkdirAll(existingWorkspace))
|
||||||
require.NoError(fs.MkdirAll(upgradeWorkingDir))
|
require.NoError(fs.MkdirAll(backupDir))
|
||||||
|
|
||||||
return file.NewHandler(afero.NewReadOnlyFs(memFS))
|
return file.NewHandler(afero.NewReadOnlyFs(memFS))
|
||||||
},
|
},
|
||||||
wantErr: true,
|
wantErr: true,
|
||||||
@ -148,7 +147,7 @@ func TestMoveUpgradeToCurrent(t *testing.T) {
|
|||||||
assert := assert.New(t)
|
assert := assert.New(t)
|
||||||
fs := tc.prepareFs(require.New(t))
|
fs := tc.prepareFs(require.New(t))
|
||||||
|
|
||||||
err := moveUpgradeToCurrent(fs, existingWorkspace, upgradeWorkingDir)
|
err := restoreBackup(fs, existingWorkspace, backupDir)
|
||||||
if tc.wantErr {
|
if tc.wantErr {
|
||||||
assert.Error(err)
|
assert.Error(err)
|
||||||
return
|
return
|
||||||
@ -209,7 +208,7 @@ type stubUpgradePlanner struct {
|
|||||||
showPlanErr error
|
showPlanErr error
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *stubUpgradePlanner) PrepareUpgradeWorkspace(_, _ string, _ string, _ terraform.Variables) error {
|
func (s *stubUpgradePlanner) PrepareUpgradeWorkspace(_, _ string, _ terraform.Variables) error {
|
||||||
return s.prepareWorkspaceErr
|
return s.prepareWorkspaceErr
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -115,6 +115,7 @@ go_test(
|
|||||||
"create_test.go",
|
"create_test.go",
|
||||||
"iamcreate_test.go",
|
"iamcreate_test.go",
|
||||||
"iamdestroy_test.go",
|
"iamdestroy_test.go",
|
||||||
|
"iamupgradeapply_test.go",
|
||||||
"init_test.go",
|
"init_test.go",
|
||||||
"recover_test.go",
|
"recover_test.go",
|
||||||
"spinner_test.go",
|
"spinner_test.go",
|
||||||
|
@ -48,8 +48,8 @@ func newIAMUpgradeApplyCmd() *cobra.Command {
|
|||||||
|
|
||||||
type iamUpgradeApplyCmd struct {
|
type iamUpgradeApplyCmd struct {
|
||||||
fileHandler file.Handler
|
fileHandler file.Handler
|
||||||
configFetcher attestationconfigapi.Fetcher
|
|
||||||
log debugLog
|
log debugLog
|
||||||
|
configFetcher attestationconfigapi.Fetcher
|
||||||
}
|
}
|
||||||
|
|
||||||
func runIAMUpgradeApply(cmd *cobra.Command, _ []string) error {
|
func runIAMUpgradeApply(cmd *cobra.Command, _ []string) error {
|
||||||
@ -58,10 +58,9 @@ func runIAMUpgradeApply(cmd *cobra.Command, _ []string) error {
|
|||||||
return fmt.Errorf("parsing force argument: %w", err)
|
return fmt.Errorf("parsing force argument: %w", err)
|
||||||
}
|
}
|
||||||
fileHandler := file.NewHandler(afero.NewOsFs())
|
fileHandler := file.NewHandler(afero.NewOsFs())
|
||||||
configFetcher := attestationconfigapi.NewFetcher()
|
|
||||||
|
|
||||||
upgradeID := generateUpgradeID(upgradeCmdKindIAM)
|
upgradeID := generateUpgradeID(upgradeCmdKindIAM)
|
||||||
upgradeDir := filepath.Join(constants.UpgradeDir, upgradeID)
|
upgradeDir := filepath.Join(constants.UpgradeDir, upgradeID)
|
||||||
|
configFetcher := attestationconfigapi.NewFetcher()
|
||||||
iamMigrateCmd, err := cloudcmd.NewIAMUpgrader(
|
iamMigrateCmd, err := cloudcmd.NewIAMUpgrader(
|
||||||
cmd.Context(),
|
cmd.Context(),
|
||||||
constants.TerraformIAMWorkingDir,
|
constants.TerraformIAMWorkingDir,
|
||||||
@ -85,8 +84,8 @@ func runIAMUpgradeApply(cmd *cobra.Command, _ []string) error {
|
|||||||
|
|
||||||
i := iamUpgradeApplyCmd{
|
i := iamUpgradeApplyCmd{
|
||||||
fileHandler: fileHandler,
|
fileHandler: fileHandler,
|
||||||
configFetcher: configFetcher,
|
|
||||||
log: log,
|
log: log,
|
||||||
|
configFetcher: configFetcher,
|
||||||
}
|
}
|
||||||
|
|
||||||
return i.iamUpgradeApply(cmd, iamMigrateCmd, upgradeDir, force, yes)
|
return i.iamUpgradeApply(cmd, iamMigrateCmd, upgradeDir, force, yes)
|
||||||
@ -108,7 +107,7 @@ func (i iamUpgradeApplyCmd) iamUpgradeApply(cmd *cobra.Command, iamUpgrader iamU
|
|||||||
}
|
}
|
||||||
hasDiff, err := iamUpgrader.PlanIAMUpgrade(cmd.Context(), cmd.OutOrStderr(), vars, conf.GetProvider())
|
hasDiff, err := iamUpgrader.PlanIAMUpgrade(cmd.Context(), cmd.OutOrStderr(), vars, conf.GetProvider())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return fmt.Errorf("planning terraform migrations: %w", err)
|
||||||
}
|
}
|
||||||
if !hasDiff && !force {
|
if !hasDiff && !force {
|
||||||
cmd.Println("No IAM migrations necessary.")
|
cmd.Println("No IAM migrations necessary.")
|
||||||
@ -124,9 +123,14 @@ func (i iamUpgradeApplyCmd) iamUpgradeApply(cmd *cobra.Command, iamUpgrader iamU
|
|||||||
}
|
}
|
||||||
if !ok {
|
if !ok {
|
||||||
cmd.Println("Aborting upgrade.")
|
cmd.Println("Aborting upgrade.")
|
||||||
// Remove the upgrade directory
|
// User doesn't expect to see any changes in his workspace after aborting an "upgrade apply",
|
||||||
if err := i.fileHandler.RemoveAll(upgradeDir); err != nil {
|
// therefore, roll back to the backed up state.
|
||||||
return fmt.Errorf("cleaning up upgrade directory %s: %w", upgradeDir, err)
|
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")
|
return errors.New("IAM upgrade aborted by user")
|
||||||
}
|
}
|
||||||
@ -144,4 +148,5 @@ func (i iamUpgradeApplyCmd) iamUpgradeApply(cmd *cobra.Command, iamUpgrader iamU
|
|||||||
type iamUpgrader interface {
|
type iamUpgrader interface {
|
||||||
PlanIAMUpgrade(ctx context.Context, outWriter io.Writer, vars terraform.Variables, csp cloudprovider.Provider) (bool, error)
|
PlanIAMUpgrade(ctx context.Context, outWriter io.Writer, vars terraform.Variables, csp cloudprovider.Provider) (bool, error)
|
||||||
ApplyIAMUpgrade(ctx context.Context, csp cloudprovider.Provider) error
|
ApplyIAMUpgrade(ctx context.Context, csp cloudprovider.Provider) error
|
||||||
|
RestoreIAMWorkspace() error
|
||||||
}
|
}
|
||||||
|
181
cli/internal/cmd/iamupgradeapply_test.go
Normal file
181
cli/internal/cmd/iamupgradeapply_test.go
Normal file
@ -0,0 +1,181 @@
|
|||||||
|
/*
|
||||||
|
Copyright (c) Edgeless Systems GmbH
|
||||||
|
SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"io"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"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/edgelesssys/constellation/v2/internal/logger"
|
||||||
|
"github.com/spf13/afero"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestIamUpgradeApply(t *testing.T) {
|
||||||
|
setupFs := func(createConfig, createTerraformVars bool) file.Handler {
|
||||||
|
fs := afero.NewMemMapFs()
|
||||||
|
fh := file.NewHandler(fs)
|
||||||
|
if createConfig {
|
||||||
|
cfg := defaultConfigWithExpectedMeasurements(t, config.Default(), cloudprovider.Azure)
|
||||||
|
require.NoError(t, fh.WriteYAML(constants.ConfigFilename, cfg))
|
||||||
|
}
|
||||||
|
if createTerraformVars {
|
||||||
|
require.NoError(t, fh.Write(
|
||||||
|
filepath.Join(constants.TerraformIAMWorkingDir, "terraform.tfvars"),
|
||||||
|
[]byte(
|
||||||
|
"region = \"foo\"\n"+
|
||||||
|
"resource_group_name = \"bar\"\n"+
|
||||||
|
"service_principal_name = \"baz\"\n",
|
||||||
|
),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
return fh
|
||||||
|
}
|
||||||
|
|
||||||
|
testCases := map[string]struct {
|
||||||
|
fh file.Handler
|
||||||
|
iamUpgrader *stubIamUpgrader
|
||||||
|
configFetcher *stubConfigFetcher
|
||||||
|
yesFlag bool
|
||||||
|
input string
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
"success": {
|
||||||
|
fh: setupFs(true, true),
|
||||||
|
configFetcher: &stubConfigFetcher{},
|
||||||
|
iamUpgrader: &stubIamUpgrader{},
|
||||||
|
},
|
||||||
|
"abort": {
|
||||||
|
fh: setupFs(true, true),
|
||||||
|
iamUpgrader: &stubIamUpgrader{},
|
||||||
|
configFetcher: &stubConfigFetcher{},
|
||||||
|
input: "no",
|
||||||
|
yesFlag: true,
|
||||||
|
},
|
||||||
|
"config missing": {
|
||||||
|
fh: setupFs(false, true),
|
||||||
|
iamUpgrader: &stubIamUpgrader{},
|
||||||
|
configFetcher: &stubConfigFetcher{},
|
||||||
|
yesFlag: true,
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
"iam vars missing": {
|
||||||
|
fh: setupFs(true, false),
|
||||||
|
iamUpgrader: &stubIamUpgrader{},
|
||||||
|
configFetcher: &stubConfigFetcher{},
|
||||||
|
yesFlag: true,
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
"plan error": {
|
||||||
|
fh: setupFs(true, true),
|
||||||
|
iamUpgrader: &stubIamUpgrader{
|
||||||
|
planErr: assert.AnError,
|
||||||
|
},
|
||||||
|
configFetcher: &stubConfigFetcher{},
|
||||||
|
yesFlag: true,
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
"apply error": {
|
||||||
|
fh: setupFs(true, true),
|
||||||
|
iamUpgrader: &stubIamUpgrader{
|
||||||
|
hasDiff: true,
|
||||||
|
applyErr: assert.AnError,
|
||||||
|
},
|
||||||
|
configFetcher: &stubConfigFetcher{},
|
||||||
|
yesFlag: true,
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
"restore error": {
|
||||||
|
fh: setupFs(true, true),
|
||||||
|
iamUpgrader: &stubIamUpgrader{
|
||||||
|
hasDiff: true,
|
||||||
|
rollbackErr: assert.AnError,
|
||||||
|
},
|
||||||
|
configFetcher: &stubConfigFetcher{},
|
||||||
|
input: "no\n",
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
"config fetcher err": {
|
||||||
|
fh: setupFs(true, true),
|
||||||
|
iamUpgrader: &stubIamUpgrader{
|
||||||
|
rollbackErr: assert.AnError,
|
||||||
|
},
|
||||||
|
configFetcher: &stubConfigFetcher{
|
||||||
|
fetchLatestErr: assert.AnError,
|
||||||
|
},
|
||||||
|
yesFlag: true,
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for name, tc := range testCases {
|
||||||
|
t.Run(name, func(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
|
||||||
|
cmd := newIAMUpgradeApplyCmd()
|
||||||
|
cmd.SetIn(strings.NewReader(tc.input))
|
||||||
|
|
||||||
|
iamUpgradeApplyCmd := &iamUpgradeApplyCmd{
|
||||||
|
fileHandler: tc.fh,
|
||||||
|
log: logger.NewTest(t),
|
||||||
|
configFetcher: tc.configFetcher,
|
||||||
|
}
|
||||||
|
|
||||||
|
err := iamUpgradeApplyCmd.iamUpgradeApply(cmd, tc.iamUpgrader, "", false, tc.yesFlag)
|
||||||
|
if tc.wantErr {
|
||||||
|
assert.Error(err)
|
||||||
|
} else {
|
||||||
|
assert.NoError(err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type stubIamUpgrader struct {
|
||||||
|
hasDiff bool
|
||||||
|
planErr error
|
||||||
|
applyErr error
|
||||||
|
rollbackErr error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *stubIamUpgrader) PlanIAMUpgrade(context.Context, io.Writer, terraform.Variables, cloudprovider.Provider) (bool, error) {
|
||||||
|
return u.hasDiff, u.planErr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *stubIamUpgrader) ApplyIAMUpgrade(context.Context, cloudprovider.Provider) error {
|
||||||
|
return u.applyErr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *stubIamUpgrader) RestoreIAMWorkspace() error {
|
||||||
|
return u.rollbackErr
|
||||||
|
}
|
||||||
|
|
||||||
|
type stubConfigFetcher struct {
|
||||||
|
fetchLatestErr error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *stubConfigFetcher) FetchAzureSEVSNPVersion(context.Context, attestationconfigapi.AzureSEVSNPVersionAPI) (attestationconfigapi.AzureSEVSNPVersionAPI, error) {
|
||||||
|
panic("not implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *stubConfigFetcher) FetchAzureSEVSNPVersionList(context.Context, attestationconfigapi.AzureSEVSNPVersionList) (attestationconfigapi.AzureSEVSNPVersionList, error) {
|
||||||
|
panic("not implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *stubConfigFetcher) FetchAzureSEVSNPVersionLatest(context.Context, time.Time) (attestationconfigapi.AzureSEVSNPVersionAPI, error) {
|
||||||
|
return attestationconfigapi.AzureSEVSNPVersionAPI{}, s.fetchLatestErr
|
||||||
|
}
|
@ -301,9 +301,14 @@ func (u *upgradeApplyCmd) migrateTerraform(cmd *cobra.Command, conf *config.Conf
|
|||||||
}
|
}
|
||||||
if !ok {
|
if !ok {
|
||||||
cmd.Println("Aborting upgrade.")
|
cmd.Println("Aborting upgrade.")
|
||||||
// Remove the upgrade directory
|
// User doesn't expect to see any changes in his workspace after aborting an "upgrade apply",
|
||||||
if err := u.fileHandler.RemoveAll(upgradeDir); err != nil {
|
// therefore, roll back to the backed up state.
|
||||||
return res, fmt.Errorf("cleaning up upgrade directory %s: %w", upgradeDir, err)
|
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),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
return res, fmt.Errorf("cluster upgrade aborted by user")
|
return res, fmt.Errorf("cluster upgrade aborted by user")
|
||||||
}
|
}
|
||||||
@ -636,4 +641,5 @@ type kubernetesUpgrader interface {
|
|||||||
type clusterUpgrader interface {
|
type clusterUpgrader interface {
|
||||||
PlanClusterUpgrade(ctx context.Context, outWriter io.Writer, vars terraform.Variables, csp cloudprovider.Provider) (bool, error)
|
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)
|
ApplyClusterUpgrade(ctx context.Context, csp cloudprovider.Provider) (terraform.ApplyOutput, error)
|
||||||
|
RestoreClusterWorkspace() error
|
||||||
}
|
}
|
||||||
|
@ -83,6 +83,15 @@ func TestUpgradeApply(t *testing.T) {
|
|||||||
wantErr: true,
|
wantErr: true,
|
||||||
stdin: "no\n",
|
stdin: "no\n",
|
||||||
},
|
},
|
||||||
|
"abort, restore terraform err": {
|
||||||
|
kubeUpgrader: &stubKubernetesUpgrader{
|
||||||
|
currentConfig: config.DefaultForAzureSEVSNP(),
|
||||||
|
},
|
||||||
|
helmUpgrader: stubApplier{},
|
||||||
|
terraformUpgrader: &stubTerraformUpgrader{terraformDiff: true, rollbackWorkspaceErr: assert.AnError},
|
||||||
|
wantErr: true,
|
||||||
|
stdin: "no\n",
|
||||||
|
},
|
||||||
"plan terraform error": {
|
"plan terraform error": {
|
||||||
kubeUpgrader: &stubKubernetesUpgrader{
|
kubeUpgrader: &stubKubernetesUpgrader{
|
||||||
currentConfig: config.DefaultForAzureSEVSNP(),
|
currentConfig: config.DefaultForAzureSEVSNP(),
|
||||||
@ -223,6 +232,7 @@ type stubTerraformUpgrader struct {
|
|||||||
terraformDiff bool
|
terraformDiff bool
|
||||||
planTerraformErr error
|
planTerraformErr error
|
||||||
applyTerraformErr error
|
applyTerraformErr error
|
||||||
|
rollbackWorkspaceErr error
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u stubTerraformUpgrader) PlanClusterUpgrade(_ context.Context, _ io.Writer, _ terraform.Variables, _ cloudprovider.Provider) (bool, error) {
|
func (u stubTerraformUpgrader) PlanClusterUpgrade(_ context.Context, _ io.Writer, _ terraform.Variables, _ cloudprovider.Provider) (bool, error) {
|
||||||
@ -233,6 +243,10 @@ func (u stubTerraformUpgrader) ApplyClusterUpgrade(_ context.Context, _ cloudpro
|
|||||||
return terraform.ApplyOutput{}, u.applyTerraformErr
|
return terraform.ApplyOutput{}, u.applyTerraformErr
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (u stubTerraformUpgrader) RestoreClusterWorkspace() error {
|
||||||
|
return u.rollbackWorkspaceErr
|
||||||
|
}
|
||||||
|
|
||||||
type mockTerraformUpgrader struct {
|
type mockTerraformUpgrader struct {
|
||||||
mock.Mock
|
mock.Mock
|
||||||
}
|
}
|
||||||
@ -247,6 +261,11 @@ func (m *mockTerraformUpgrader) ApplyClusterUpgrade(ctx context.Context, provide
|
|||||||
return args.Get(0).(terraform.ApplyOutput), args.Error(1)
|
return args.Get(0).(terraform.ApplyOutput), args.Error(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *mockTerraformUpgrader) RestoreClusterWorkspace() error {
|
||||||
|
args := m.Called()
|
||||||
|
return args.Error(0)
|
||||||
|
}
|
||||||
|
|
||||||
type mockApplier struct {
|
type mockApplier struct {
|
||||||
mock.Mock
|
mock.Mock
|
||||||
}
|
}
|
||||||
|
@ -108,12 +108,13 @@ func runUpgradeCheck(cmd *cobra.Command, _ []string) error {
|
|||||||
log: log,
|
log: log,
|
||||||
versionsapi: versionfetcher,
|
versionsapi: versionfetcher,
|
||||||
},
|
},
|
||||||
|
upgradeDir: upgradeDir,
|
||||||
terraformChecker: tfClient,
|
terraformChecker: tfClient,
|
||||||
fileHandler: fileHandler,
|
fileHandler: fileHandler,
|
||||||
log: log,
|
log: log,
|
||||||
}
|
}
|
||||||
|
|
||||||
return up.upgradeCheck(cmd, attestationconfigapi.NewFetcher(), upgradeDir, flags)
|
return up.upgradeCheck(cmd, attestationconfigapi.NewFetcher(), flags)
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseUpgradeCheckFlags(cmd *cobra.Command) (upgradeCheckFlags, error) {
|
func parseUpgradeCheckFlags(cmd *cobra.Command) (upgradeCheckFlags, error) {
|
||||||
@ -154,6 +155,7 @@ func parseUpgradeCheckFlags(cmd *cobra.Command) (upgradeCheckFlags, error) {
|
|||||||
|
|
||||||
type upgradeCheckCmd struct {
|
type upgradeCheckCmd struct {
|
||||||
canUpgradeCheck bool
|
canUpgradeCheck bool
|
||||||
|
upgradeDir string
|
||||||
collect collector
|
collect collector
|
||||||
terraformChecker terraformChecker
|
terraformChecker terraformChecker
|
||||||
fileHandler file.Handler
|
fileHandler file.Handler
|
||||||
@ -161,7 +163,7 @@ type upgradeCheckCmd struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// upgradePlan plans an upgrade of a Constellation cluster.
|
// upgradePlan plans an upgrade of a Constellation cluster.
|
||||||
func (u *upgradeCheckCmd) upgradeCheck(cmd *cobra.Command, fetcher attestationconfigapi.Fetcher, upgradeDir string, flags upgradeCheckFlags) error {
|
func (u *upgradeCheckCmd) upgradeCheck(cmd *cobra.Command, fetcher attestationconfigapi.Fetcher, flags upgradeCheckFlags) error {
|
||||||
conf, err := config.New(u.fileHandler, constants.ConfigFilename, fetcher, flags.force)
|
conf, err := config.New(u.fileHandler, constants.ConfigFilename, fetcher, flags.force)
|
||||||
var configValidationErr *config.ValidationError
|
var configValidationErr *config.ValidationError
|
||||||
if errors.As(err, &configValidationErr) {
|
if errors.As(err, &configValidationErr) {
|
||||||
@ -235,9 +237,14 @@ func (u *upgradeCheckCmd) upgradeCheck(cmd *cobra.Command, fetcher attestationco
|
|||||||
return fmt.Errorf("planning terraform migrations: %w", err)
|
return fmt.Errorf("planning terraform migrations: %w", err)
|
||||||
}
|
}
|
||||||
defer func() {
|
defer func() {
|
||||||
// Remove the upgrade directory
|
// User doesn't expect to see any changes in his workspace after an "upgrade plan",
|
||||||
if err := u.fileHandler.RemoveAll(upgradeDir); err != nil {
|
// therefore, roll back to the backed up state.
|
||||||
u.log.Debugf("Failed to clean up Terraform migrations: %s", err)
|
if err := u.terraformChecker.RestoreClusterWorkspace(); err != nil {
|
||||||
|
cmd.PrintErrf(
|
||||||
|
"restoring Terraform workspace: %s, restore the Terraform workspace manually from %s ",
|
||||||
|
err,
|
||||||
|
filepath.Join(u.upgradeDir, constants.TerraformUpgradeBackupDir),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
@ -728,6 +735,7 @@ type kubernetesChecker interface {
|
|||||||
|
|
||||||
type terraformChecker interface {
|
type terraformChecker interface {
|
||||||
PlanClusterUpgrade(ctx context.Context, outWriter io.Writer, vars terraform.Variables, csp cloudprovider.Provider) (bool, error)
|
PlanClusterUpgrade(ctx context.Context, outWriter io.Writer, vars terraform.Variables, csp cloudprovider.Provider) (bool, error)
|
||||||
|
RestoreClusterWorkspace() error
|
||||||
}
|
}
|
||||||
|
|
||||||
type versionListFetcher interface {
|
type versionListFetcher interface {
|
||||||
|
@ -24,7 +24,6 @@ import (
|
|||||||
"github.com/edgelesssys/constellation/v2/internal/constants"
|
"github.com/edgelesssys/constellation/v2/internal/constants"
|
||||||
"github.com/edgelesssys/constellation/v2/internal/file"
|
"github.com/edgelesssys/constellation/v2/internal/file"
|
||||||
"github.com/edgelesssys/constellation/v2/internal/logger"
|
"github.com/edgelesssys/constellation/v2/internal/logger"
|
||||||
"github.com/edgelesssys/constellation/v2/internal/semver"
|
|
||||||
consemver "github.com/edgelesssys/constellation/v2/internal/semver"
|
consemver "github.com/edgelesssys/constellation/v2/internal/semver"
|
||||||
"github.com/spf13/afero"
|
"github.com/spf13/afero"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
@ -47,8 +46,8 @@ func TestBuildString(t *testing.T) {
|
|||||||
newKubernetes: []string{"v1.24.12", "v1.25.6"},
|
newKubernetes: []string{"v1.24.12", "v1.25.6"},
|
||||||
newCLI: []consemver.Semver{consemver.NewFromInt(2, 5, 0, ""), consemver.NewFromInt(2, 6, 0, "")},
|
newCLI: []consemver.Semver{consemver.NewFromInt(2, 5, 0, ""), consemver.NewFromInt(2, 6, 0, "")},
|
||||||
currentServices: consemver.NewFromInt(2, 4, 0, ""),
|
currentServices: consemver.NewFromInt(2, 4, 0, ""),
|
||||||
currentImage: semver.NewFromInt(2, 4, 0, ""),
|
currentImage: consemver.NewFromInt(2, 4, 0, ""),
|
||||||
currentKubernetes: semver.NewFromInt(1, 24, 5, ""),
|
currentKubernetes: consemver.NewFromInt(1, 24, 5, ""),
|
||||||
currentCLI: consemver.NewFromInt(2, 4, 0, ""),
|
currentCLI: consemver.NewFromInt(2, 4, 0, ""),
|
||||||
},
|
},
|
||||||
expected: "The following updates are available with this CLI:\n Kubernetes: v1.24.5 --> v1.24.12 v1.25.6\n Images:\n v2.4.0 --> v2.5.0\n Includes these measurements:\n 4:\n expected: \"1234123412341234123412341234123412341234123412341234123412341234\"\n warnOnly: false\n 8:\n expected: \"0000000000000000000000000000000000000000000000000000000000000000\"\n warnOnly: false\n 9:\n expected: \"1234123412341234123412341234123412341234123412341234123412341234\"\n warnOnly: false\n 11:\n expected: \"0000000000000000000000000000000000000000000000000000000000000000\"\n warnOnly: false\n 12:\n expected: \"1234123412341234123412341234123412341234123412341234123412341234\"\n warnOnly: false\n 13:\n expected: \"0000000000000000000000000000000000000000000000000000000000000000\"\n warnOnly: false\n 15:\n expected: \"0000000000000000000000000000000000000000000000000000000000000000\"\n warnOnly: false\n \n Services: v2.4.0 --> v2.5.0\n",
|
expected: "The following updates are available with this CLI:\n Kubernetes: v1.24.5 --> v1.24.12 v1.25.6\n Images:\n v2.4.0 --> v2.5.0\n Includes these measurements:\n 4:\n expected: \"1234123412341234123412341234123412341234123412341234123412341234\"\n warnOnly: false\n 8:\n expected: \"0000000000000000000000000000000000000000000000000000000000000000\"\n warnOnly: false\n 9:\n expected: \"1234123412341234123412341234123412341234123412341234123412341234\"\n warnOnly: false\n 11:\n expected: \"0000000000000000000000000000000000000000000000000000000000000000\"\n warnOnly: false\n 12:\n expected: \"1234123412341234123412341234123412341234123412341234123412341234\"\n warnOnly: false\n 13:\n expected: \"0000000000000000000000000000000000000000000000000000000000000000\"\n warnOnly: false\n 15:\n expected: \"0000000000000000000000000000000000000000000000000000000000000000\"\n warnOnly: false\n \n Services: v2.4.0 --> v2.5.0\n",
|
||||||
@ -70,7 +69,7 @@ func TestBuildString(t *testing.T) {
|
|||||||
"k8s only": {
|
"k8s only": {
|
||||||
upgrade: versionUpgrade{
|
upgrade: versionUpgrade{
|
||||||
newKubernetes: []string{"v1.24.12", "v1.25.6"},
|
newKubernetes: []string{"v1.24.12", "v1.25.6"},
|
||||||
currentKubernetes: semver.NewFromInt(1, 24, 5, ""),
|
currentKubernetes: consemver.NewFromInt(1, 24, 5, ""),
|
||||||
},
|
},
|
||||||
expected: "The following updates are available with this CLI:\n Kubernetes: v1.24.5 --> v1.24.12 v1.25.6\n",
|
expected: "The following updates are available with this CLI:\n Kubernetes: v1.24.5 --> v1.24.12 v1.25.6\n",
|
||||||
},
|
},
|
||||||
@ -81,8 +80,8 @@ func TestBuildString(t *testing.T) {
|
|||||||
newKubernetes: []string{},
|
newKubernetes: []string{},
|
||||||
newCLI: []consemver.Semver{},
|
newCLI: []consemver.Semver{},
|
||||||
currentServices: consemver.NewFromInt(2, 5, 0, ""),
|
currentServices: consemver.NewFromInt(2, 5, 0, ""),
|
||||||
currentImage: semver.NewFromInt(2, 5, 0, ""),
|
currentImage: consemver.NewFromInt(2, 5, 0, ""),
|
||||||
currentKubernetes: semver.NewFromInt(1, 25, 6, ""),
|
currentKubernetes: consemver.NewFromInt(1, 25, 6, ""),
|
||||||
currentCLI: consemver.NewFromInt(2, 5, 0, ""),
|
currentCLI: consemver.NewFromInt(2, 5, 0, ""),
|
||||||
},
|
},
|
||||||
expected: "You are up to date.\n",
|
expected: "You are up to date.\n",
|
||||||
@ -165,8 +164,8 @@ func TestUpgradeCheck(t *testing.T) {
|
|||||||
},
|
},
|
||||||
supportedK8sVersions: []string{"v1.24.5", "v1.24.12", "v1.25.6"},
|
supportedK8sVersions: []string{"v1.24.5", "v1.24.12", "v1.25.6"},
|
||||||
currentServicesVersions: consemver.NewFromInt(2, 4, 0, ""),
|
currentServicesVersions: consemver.NewFromInt(2, 4, 0, ""),
|
||||||
currentImageVersion: semver.NewFromInt(2, 4, 0, ""),
|
currentImageVersion: consemver.NewFromInt(2, 4, 0, ""),
|
||||||
currentK8sVersion: semver.NewFromInt(1, 24, 5, ""),
|
currentK8sVersion: consemver.NewFromInt(1, 24, 5, ""),
|
||||||
currentCLIVersion: consemver.NewFromInt(2, 4, 0, ""),
|
currentCLIVersion: consemver.NewFromInt(2, 4, 0, ""),
|
||||||
images: []versionsapi.Version{v2_5},
|
images: []versionsapi.Version{v2_5},
|
||||||
newCLIVersionsList: []consemver.Semver{consemver.NewFromInt(2, 5, 0, ""), consemver.NewFromInt(2, 6, 0, "")},
|
newCLIVersionsList: []consemver.Semver{consemver.NewFromInt(2, 5, 0, ""), consemver.NewFromInt(2, 6, 0, "")},
|
||||||
@ -185,15 +184,23 @@ func TestUpgradeCheck(t *testing.T) {
|
|||||||
csp: cloudprovider.GCP,
|
csp: cloudprovider.GCP,
|
||||||
cliVersion: "v1.0.0",
|
cliVersion: "v1.0.0",
|
||||||
},
|
},
|
||||||
"terraform err": {
|
"terraform plan err": {
|
||||||
collector: collector,
|
collector: collector,
|
||||||
checker: stubTerraformChecker{
|
checker: stubTerraformChecker{
|
||||||
err: assert.AnError,
|
planErr: assert.AnError,
|
||||||
},
|
},
|
||||||
csp: cloudprovider.GCP,
|
csp: cloudprovider.GCP,
|
||||||
cliVersion: "v1.0.0",
|
cliVersion: "v1.0.0",
|
||||||
wantError: true,
|
wantError: true,
|
||||||
},
|
},
|
||||||
|
"terraform rollback err, log only": {
|
||||||
|
collector: collector,
|
||||||
|
checker: stubTerraformChecker{
|
||||||
|
rollbackErr: assert.AnError,
|
||||||
|
},
|
||||||
|
csp: cloudprovider.GCP,
|
||||||
|
cliVersion: "v1.0.0",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for name, tc := range testCases {
|
for name, tc := range testCases {
|
||||||
@ -214,7 +221,7 @@ func TestUpgradeCheck(t *testing.T) {
|
|||||||
|
|
||||||
cmd := newUpgradeCheckCmd()
|
cmd := newUpgradeCheckCmd()
|
||||||
|
|
||||||
err := checkCmd.upgradeCheck(cmd, stubAttestationFetcher{}, "test", upgradeCheckFlags{})
|
err := checkCmd.upgradeCheck(cmd, stubAttestationFetcher{}, upgradeCheckFlags{})
|
||||||
if tc.wantError {
|
if tc.wantError {
|
||||||
assert.Error(err)
|
assert.Error(err)
|
||||||
return
|
return
|
||||||
@ -280,11 +287,16 @@ func (s *stubVersionCollector) filterCompatibleCLIVersions(_ context.Context, _
|
|||||||
|
|
||||||
type stubTerraformChecker struct {
|
type stubTerraformChecker struct {
|
||||||
tfDiff bool
|
tfDiff bool
|
||||||
err error
|
planErr error
|
||||||
|
rollbackErr error
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s stubTerraformChecker) PlanClusterUpgrade(_ context.Context, _ io.Writer, _ terraform.Variables, _ cloudprovider.Provider) (bool, error) {
|
func (s stubTerraformChecker) PlanClusterUpgrade(_ context.Context, _ io.Writer, _ terraform.Variables, _ cloudprovider.Provider) (bool, error) {
|
||||||
return s.tfDiff, s.err
|
return s.tfDiff, s.planErr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s stubTerraformChecker) RestoreClusterWorkspace() error {
|
||||||
|
return s.rollbackErr
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNewCLIVersions(t *testing.T) {
|
func TestNewCLIVersions(t *testing.T) {
|
||||||
@ -374,7 +386,7 @@ func TestFilterCompatibleCLIVersions(t *testing.T) {
|
|||||||
t.Run(name, func(t *testing.T) {
|
t.Run(name, func(t *testing.T) {
|
||||||
require := require.New(t)
|
require := require.New(t)
|
||||||
|
|
||||||
_, err := tc.verCollector.filterCompatibleCLIVersions(context.Background(), tc.cliPatchVersions, semver.NewFromInt(1, 24, 5, ""))
|
_, err := tc.verCollector.filterCompatibleCLIVersions(context.Background(), tc.cliPatchVersions, consemver.NewFromInt(1, 24, 5, ""))
|
||||||
if tc.wantErr {
|
if tc.wantErr {
|
||||||
require.Error(err)
|
require.Error(err)
|
||||||
return
|
return
|
||||||
|
@ -70,6 +70,9 @@ go_library(
|
|||||||
"terraform/openstack/outputs.tf",
|
"terraform/openstack/outputs.tf",
|
||||||
"terraform/openstack/variables.tf",
|
"terraform/openstack/variables.tf",
|
||||||
"terraform/qemu/modules/instance_group/tdx_domain.xsl",
|
"terraform/qemu/modules/instance_group/tdx_domain.xsl",
|
||||||
|
"terraform/iam/aws/.terraform.lock.hcl",
|
||||||
|
"terraform/iam/azure/.terraform.lock.hcl",
|
||||||
|
"terraform/iam/gcp/.terraform.lock.hcl",
|
||||||
],
|
],
|
||||||
importpath = "github.com/edgelesssys/constellation/v2/cli/internal/terraform",
|
importpath = "github.com/edgelesssys/constellation/v2/cli/internal/terraform",
|
||||||
visibility = ["//cli:__subpackages__"],
|
visibility = ["//cli:__subpackages__"],
|
||||||
|
@ -25,39 +25,39 @@ var ErrTerraformWorkspaceDifferentFiles = errors.New("creating cluster: trying t
|
|||||||
|
|
||||||
//go:embed terraform/*
|
//go:embed terraform/*
|
||||||
//go:embed terraform/*/.terraform.lock.hcl
|
//go:embed terraform/*/.terraform.lock.hcl
|
||||||
|
//go:embed terraform/iam/*/.terraform.lock.hcl
|
||||||
var terraformFS embed.FS
|
var terraformFS embed.FS
|
||||||
|
|
||||||
|
const (
|
||||||
|
noOverwrites overwritePolicy = iota
|
||||||
|
allowOverwrites
|
||||||
|
)
|
||||||
|
|
||||||
|
type overwritePolicy int
|
||||||
|
|
||||||
// prepareWorkspace loads the embedded Terraform files,
|
// prepareWorkspace loads the embedded Terraform files,
|
||||||
// and writes them into the workspace.
|
// and writes them into the workspace.
|
||||||
func prepareWorkspace(rootDir string, fileHandler file.Handler, workingDir string) error {
|
func prepareWorkspace(rootDir string, fileHandler file.Handler, workingDir string) error {
|
||||||
return terraformCopier(fileHandler, rootDir, workingDir)
|
return terraformCopier(fileHandler, rootDir, workingDir, noOverwrites)
|
||||||
}
|
}
|
||||||
|
|
||||||
// prepareUpgradeWorkspace takes the Terraform state file from the old workspace and the
|
// prepareUpgradeWorkspace backs up the old Terraform workspace from workingDir, and
|
||||||
// embedded Terraform files and writes them into the new workspace.
|
// copies the embedded Terraform files into workingDir.
|
||||||
func prepareUpgradeWorkspace(rootDir string, fileHandler file.Handler, oldWorkingDir, newWorkingDir, backupDir string) error {
|
func prepareUpgradeWorkspace(rootDir string, fileHandler file.Handler, workingDir, backupDir string) error {
|
||||||
// backup old workspace
|
// backup old workspace
|
||||||
if err := fileHandler.CopyDir(
|
if err := fileHandler.CopyDir(
|
||||||
oldWorkingDir,
|
workingDir,
|
||||||
backupDir,
|
backupDir,
|
||||||
); err != nil {
|
); err != nil {
|
||||||
return fmt.Errorf("backing up old workspace: %w", err)
|
return fmt.Errorf("backing up old workspace: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// copy state file
|
return terraformCopier(fileHandler, rootDir, workingDir, allowOverwrites)
|
||||||
if err := fileHandler.CopyFile(
|
|
||||||
filepath.Join(oldWorkingDir, "terraform.tfstate"),
|
|
||||||
filepath.Join(newWorkingDir, "terraform.tfstate"),
|
|
||||||
file.OptMkdirAll,
|
|
||||||
); err != nil {
|
|
||||||
return fmt.Errorf("copying state file: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return terraformCopier(fileHandler, rootDir, newWorkingDir)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// terraformCopier copies the embedded Terraform files into the workspace.
|
// terraformCopier copies the embedded Terraform files into the workspace.
|
||||||
func terraformCopier(fileHandler file.Handler, rootDir, workingDir string) error {
|
// allowOverwrites allows overwriting existing files in the workspace.
|
||||||
|
func terraformCopier(fileHandler file.Handler, rootDir, workingDir string, overwritePolicy overwritePolicy) error {
|
||||||
goEmbedRootDir := filepath.ToSlash(rootDir)
|
goEmbedRootDir := filepath.ToSlash(rootDir)
|
||||||
return fs.WalkDir(terraformFS, goEmbedRootDir, func(path string, d fs.DirEntry, err error) error {
|
return fs.WalkDir(terraformFS, goEmbedRootDir, func(path string, d fs.DirEntry, err error) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -74,8 +74,15 @@ func terraformCopier(fileHandler file.Handler, rootDir, workingDir string) error
|
|||||||
}
|
}
|
||||||
// normalize
|
// normalize
|
||||||
fileName := strings.Replace(slashpath.Join(workingDir, path), goEmbedRootDir+"/", "", 1)
|
fileName := strings.Replace(slashpath.Join(workingDir, path), goEmbedRootDir+"/", "", 1)
|
||||||
if err := fileHandler.Write(fileName, content, file.OptMkdirAll); errors.Is(err, afero.ErrFileExists) {
|
opts := []file.Option{
|
||||||
// If a file already exists, check if it is identical. If yes, continue and don't write anything to disk.
|
file.OptMkdirAll,
|
||||||
|
}
|
||||||
|
if overwritePolicy == allowOverwrites {
|
||||||
|
opts = append(opts, file.OptOverwrite)
|
||||||
|
}
|
||||||
|
if err := fileHandler.Write(fileName, content, opts...); errors.Is(err, afero.ErrFileExists) {
|
||||||
|
// If a file already exists and overwritePolicy is set to noOverwrites,
|
||||||
|
// check if it is identical. If yes, continue and don't write anything to disk.
|
||||||
// If no, don't overwrite it and instead throw an error. The affected file could be from a different version,
|
// If no, don't overwrite it and instead throw an error. The affected file could be from a different version,
|
||||||
// provider, corrupted or manually modified in general.
|
// provider, corrupted or manually modified in general.
|
||||||
existingFileContent, err := fileHandler.Read(fileName)
|
existingFileContent, err := fileHandler.Read(fileName)
|
||||||
|
@ -20,6 +20,8 @@ import (
|
|||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var oldFileContent = []byte("1234")
|
||||||
|
|
||||||
func TestPrepareWorkspace(t *testing.T) {
|
func TestPrepareWorkspace(t *testing.T) {
|
||||||
testCases := map[string]struct {
|
testCases := map[string]struct {
|
||||||
pathBase string
|
pathBase string
|
||||||
@ -109,20 +111,20 @@ func TestPrepareWorkspace(t *testing.T) {
|
|||||||
err := prepareWorkspace(path, file, testWorkspace)
|
err := prepareWorkspace(path, file, testWorkspace)
|
||||||
|
|
||||||
require.NoError(err)
|
require.NoError(err)
|
||||||
checkFiles(t, file, func(err error) { assert.NoError(err) }, testWorkspace, tc.fileList)
|
checkFiles(t, file, func(err error) { assert.NoError(err) }, nil, testWorkspace, tc.fileList)
|
||||||
|
|
||||||
if tc.testAlreadyUnpacked {
|
if tc.testAlreadyUnpacked {
|
||||||
// Let's try the same again and check if we don't get a "file already exists" error.
|
// Let's try the same again and check if we don't get a "file already exists" error.
|
||||||
require.NoError(file.Remove(filepath.Join(testWorkspace, "variables.tf")))
|
require.NoError(file.Remove(filepath.Join(testWorkspace, "variables.tf")))
|
||||||
err := prepareWorkspace(path, file, testWorkspace)
|
err := prepareWorkspace(path, file, testWorkspace)
|
||||||
assert.NoError(err)
|
assert.NoError(err)
|
||||||
checkFiles(t, file, func(err error) { assert.NoError(err) }, testWorkspace, tc.fileList)
|
checkFiles(t, file, func(err error) { assert.NoError(err) }, nil, testWorkspace, tc.fileList)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = cleanUpWorkspace(file, testWorkspace)
|
err = cleanUpWorkspace(file, testWorkspace)
|
||||||
require.NoError(err)
|
require.NoError(err)
|
||||||
|
|
||||||
checkFiles(t, file, func(err error) { assert.ErrorIs(err, fs.ErrNotExist) }, testWorkspace, tc.fileList)
|
checkFiles(t, file, func(err error) { assert.ErrorIs(err, fs.ErrNotExist) }, nil, testWorkspace, tc.fileList)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -131,49 +133,56 @@ func TestPrepareUpgradeWorkspace(t *testing.T) {
|
|||||||
testCases := map[string]struct {
|
testCases := map[string]struct {
|
||||||
pathBase string
|
pathBase string
|
||||||
provider cloudprovider.Provider
|
provider cloudprovider.Provider
|
||||||
oldWorkingDir string
|
workingDir string
|
||||||
newWorkingDir string
|
|
||||||
backupDir string
|
backupDir string
|
||||||
oldWorkspaceFiles []string
|
workspaceFiles []string
|
||||||
newWorkspaceFiles []string
|
|
||||||
expectedFiles []string
|
expectedFiles []string
|
||||||
|
expectedBackupFiles []string
|
||||||
testAlreadyUnpacked bool
|
testAlreadyUnpacked bool
|
||||||
wantErr bool
|
wantErr bool
|
||||||
}{
|
}{
|
||||||
"works": {
|
"works": {
|
||||||
pathBase: "terraform",
|
pathBase: "terraform",
|
||||||
provider: cloudprovider.AWS,
|
provider: cloudprovider.AWS,
|
||||||
oldWorkingDir: "old",
|
workingDir: "working",
|
||||||
newWorkingDir: "new",
|
|
||||||
backupDir: "backup",
|
backupDir: "backup",
|
||||||
oldWorkspaceFiles: []string{"terraform.tfstate"},
|
workspaceFiles: []string{"main.tf", "variables.tf", "outputs.tf"},
|
||||||
expectedFiles: []string{
|
expectedFiles: []string{
|
||||||
"main.tf",
|
"main.tf",
|
||||||
"variables.tf",
|
"variables.tf",
|
||||||
"outputs.tf",
|
"outputs.tf",
|
||||||
"modules",
|
},
|
||||||
"terraform.tfstate",
|
expectedBackupFiles: []string{
|
||||||
|
"main.tf",
|
||||||
|
"variables.tf",
|
||||||
|
"outputs.tf",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"state file does not exist": {
|
"state file does not exist": {
|
||||||
pathBase: "terraform",
|
pathBase: "terraform",
|
||||||
provider: cloudprovider.AWS,
|
provider: cloudprovider.AWS,
|
||||||
oldWorkingDir: "old",
|
workingDir: "working",
|
||||||
newWorkingDir: "new",
|
|
||||||
backupDir: "backup",
|
backupDir: "backup",
|
||||||
oldWorkspaceFiles: []string{},
|
workspaceFiles: []string{},
|
||||||
expectedFiles: []string{},
|
expectedFiles: []string{},
|
||||||
wantErr: true,
|
wantErr: true,
|
||||||
},
|
},
|
||||||
"terraform files already exist in new dir": {
|
"terraform file already exists in working dir (overwrite)": {
|
||||||
pathBase: "terraform",
|
pathBase: "terraform",
|
||||||
provider: cloudprovider.AWS,
|
provider: cloudprovider.AWS,
|
||||||
oldWorkingDir: "old",
|
workingDir: "working",
|
||||||
newWorkingDir: "new",
|
|
||||||
backupDir: "backup",
|
backupDir: "backup",
|
||||||
oldWorkspaceFiles: []string{"terraform.tfstate"},
|
workspaceFiles: []string{"main.tf", "variables.tf", "outputs.tf"},
|
||||||
newWorkspaceFiles: []string{"main.tf"},
|
expectedFiles: []string{
|
||||||
wantErr: true,
|
"main.tf",
|
||||||
|
"variables.tf",
|
||||||
|
"outputs.tf",
|
||||||
|
},
|
||||||
|
expectedBackupFiles: []string{
|
||||||
|
"main.tf",
|
||||||
|
"variables.tf",
|
||||||
|
"outputs.tf",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -186,31 +195,44 @@ func TestPrepareUpgradeWorkspace(t *testing.T) {
|
|||||||
|
|
||||||
path := path.Join(tc.pathBase, strings.ToLower(tc.provider.String()))
|
path := path.Join(tc.pathBase, strings.ToLower(tc.provider.String()))
|
||||||
|
|
||||||
createFiles(t, file, tc.oldWorkspaceFiles, tc.oldWorkingDir)
|
createFiles(t, file, tc.workspaceFiles, tc.workingDir)
|
||||||
createFiles(t, file, tc.newWorkspaceFiles, tc.newWorkingDir)
|
|
||||||
|
|
||||||
err := prepareUpgradeWorkspace(path, file, tc.oldWorkingDir, tc.newWorkingDir, tc.backupDir)
|
err := prepareUpgradeWorkspace(path, file, tc.workingDir, tc.backupDir)
|
||||||
|
|
||||||
if tc.wantErr {
|
if tc.wantErr {
|
||||||
require.Error(err)
|
require.Error(err)
|
||||||
} else {
|
} else {
|
||||||
require.NoError(err)
|
require.NoError(err)
|
||||||
}
|
checkFiles(
|
||||||
checkFiles(t, file, func(err error) { assert.NoError(err) }, tc.newWorkingDir, tc.expectedFiles)
|
t, file,
|
||||||
checkFiles(t, file, func(err error) { assert.NoError(err) },
|
func(err error) { assert.NoError(err) },
|
||||||
tc.backupDir,
|
func(content []byte) { assert.NotEqual(oldFileContent, content) },
|
||||||
tc.oldWorkspaceFiles,
|
tc.workingDir, tc.expectedFiles,
|
||||||
)
|
)
|
||||||
|
checkFiles(
|
||||||
|
t, file,
|
||||||
|
func(err error) { assert.NoError(err) },
|
||||||
|
func(content []byte) { assert.Equal(oldFileContent, content) },
|
||||||
|
tc.backupDir, tc.expectedBackupFiles,
|
||||||
|
)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func checkFiles(t *testing.T, fileHandler file.Handler, assertion func(error), dir string, files []string) {
|
func checkFiles(t *testing.T, fileHandler file.Handler, assertion func(error), contentExpection func(content []byte), dir string, files []string) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
for _, f := range files {
|
for _, f := range files {
|
||||||
path := filepath.Join(dir, f)
|
path := filepath.Join(dir, f)
|
||||||
_, err := fileHandler.Stat(path)
|
_, err := fileHandler.Stat(path)
|
||||||
assertion(err)
|
assertion(err)
|
||||||
|
if err == nil {
|
||||||
|
content, err := fileHandler.Read(path)
|
||||||
|
assertion(err)
|
||||||
|
if contentExpection != nil {
|
||||||
|
contentExpection(content)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -220,7 +242,7 @@ func createFiles(t *testing.T, fileHandler file.Handler, fileList []string, targ
|
|||||||
|
|
||||||
for _, f := range fileList {
|
for _, f := range fileList {
|
||||||
path := filepath.Join(targetDir, f)
|
path := filepath.Join(targetDir, f)
|
||||||
err := fileHandler.Write(path, []byte("1234"), file.OptOverwrite, file.OptMkdirAll)
|
err := fileHandler.Write(path, oldFileContent, file.OptOverwrite, file.OptMkdirAll)
|
||||||
require.NoError(err)
|
require.NoError(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -332,19 +332,18 @@ func (c *Client) PrepareWorkspace(path string, vars Variables) error {
|
|||||||
return fmt.Errorf("prepare workspace: %w", err)
|
return fmt.Errorf("prepare workspace: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return c.writeVars(vars)
|
return c.writeVars(vars, noOverwrites)
|
||||||
}
|
}
|
||||||
|
|
||||||
// PrepareUpgradeWorkspace prepares a Terraform workspace for an upgrade.
|
// PrepareUpgradeWorkspace prepares a Terraform workspace for an upgrade.
|
||||||
// It copies the Terraform state from the old working dir and the embedded Terraform files
|
// It creates a backup of the Terraform workspace in the backupDir, and copies
|
||||||
// into the working dir of the Terraform client.
|
// the embedded Terraform files into the workingDir.
|
||||||
// Additionally, a backup of the old working dir is created in the backup dir.
|
func (c *Client) PrepareUpgradeWorkspace(path, backupDir string, vars Variables) error {
|
||||||
func (c *Client) PrepareUpgradeWorkspace(path, oldWorkingDir, backupDir string, vars Variables) error {
|
if err := prepareUpgradeWorkspace(path, c.file, c.workingDir, backupDir); err != nil {
|
||||||
if err := prepareUpgradeWorkspace(path, c.file, oldWorkingDir, c.workingDir, backupDir); err != nil {
|
|
||||||
return fmt.Errorf("prepare upgrade workspace: %w", err)
|
return fmt.Errorf("prepare upgrade workspace: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return c.writeVars(vars)
|
return c.writeVars(vars, allowOverwrites)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ApplyCluster applies the Terraform configuration of the workspace to create or upgrade a Constellation cluster.
|
// ApplyCluster applies the Terraform configuration of the workspace to create or upgrade a Constellation cluster.
|
||||||
@ -461,13 +460,17 @@ func (c *Client) applyManualStateMigrations(ctx context.Context) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// writeVars tries to write the Terraform variables file or, if it exists, checks if it is the same as we are expecting.
|
// writeVars tries to write the Terraform variables file or, if it exists, checks if it is the same as we are expecting.
|
||||||
func (c *Client) writeVars(vars Variables) error {
|
func (c *Client) writeVars(vars Variables, overwritePolicy overwritePolicy) error {
|
||||||
if vars == nil {
|
if vars == nil {
|
||||||
return errors.New("creating cluster: vars is nil")
|
return errors.New("creating cluster: vars is nil")
|
||||||
}
|
}
|
||||||
|
|
||||||
pathToVarsFile := filepath.Join(c.workingDir, terraformVarsFile)
|
pathToVarsFile := filepath.Join(c.workingDir, terraformVarsFile)
|
||||||
if err := c.file.Write(pathToVarsFile, []byte(vars.String())); errors.Is(err, afero.ErrFileExists) {
|
opts := []file.Option{}
|
||||||
|
if overwritePolicy == allowOverwrites {
|
||||||
|
opts = append(opts, file.OptOverwrite)
|
||||||
|
}
|
||||||
|
if err := c.file.Write(pathToVarsFile, []byte(vars.String()), opts...); errors.Is(err, afero.ErrFileExists) {
|
||||||
// If a variables file already exists, check if it's the same as we're expecting, so we can continue using it.
|
// If a variables file already exists, check if it's the same as we're expecting, so we can continue using it.
|
||||||
varsContent, err := c.file.Read(pathToVarsFile)
|
varsContent, err := c.file.Read(pathToVarsFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user