diff --git a/cli/internal/cloudcmd/clients.go b/cli/internal/cloudcmd/clients.go index a05f2e811..2173f7fa3 100644 --- a/cli/internal/cloudcmd/clients.go +++ b/cli/internal/cloudcmd/clients.go @@ -46,7 +46,7 @@ type tfIAMClient interface { type tfUpgradePlanner interface { ShowPlan(ctx context.Context, logLevel terraform.LogLevel, output io.Writer) error Plan(ctx context.Context, logLevel terraform.LogLevel) (bool, error) - PrepareUpgradeWorkspace(embeddedPath, backupDir string, vars terraform.Variables) error + PrepareWorkspace(path string, vars terraform.Variables) error } type tfIAMUpgradeClient interface { diff --git a/cli/internal/cloudcmd/clusterupgrade.go b/cli/internal/cloudcmd/clusterupgrade.go index 0227c3686..b3c8cd054 100644 --- a/cli/internal/cloudcmd/clusterupgrade.go +++ b/cli/internal/cloudcmd/clusterupgrade.go @@ -36,7 +36,7 @@ type ClusterUpgrader struct { func NewClusterUpgrader(ctx context.Context, existingWorkspace, upgradeWorkspace string, logLevel terraform.LogLevel, fileHandler file.Handler, ) (*ClusterUpgrader, error) { - tfClient, err := terraform.New(ctx, constants.TerraformWorkingDir) + tfClient, err := terraform.New(ctx, existingWorkspace) if err != nil { return nil, fmt.Errorf("setting up terraform client: %w", err) } @@ -57,7 +57,8 @@ func (u *ClusterUpgrader) PlanClusterUpgrade(ctx context.Context, outWriter io.W ) (bool, error) { return planUpgrade( ctx, u.tf, u.fileHandler, outWriter, u.logLevel, vars, - filepath.Join("terraform", strings.ToLower(csp.String())), + filepath.Join(constants.TerraformEmbeddedDir, strings.ToLower(csp.String())), + u.existingWorkspace, filepath.Join(u.upgradeWorkspace, constants.TerraformUpgradeBackupDir), ) } diff --git a/cli/internal/cloudcmd/clusterupgrade_test.go b/cli/internal/cloudcmd/clusterupgrade_test.go index 07c011fe7..11a77e7a1 100644 --- a/cli/internal/cloudcmd/clusterupgrade_test.go +++ b/cli/internal/cloudcmd/clusterupgrade_test.go @@ -24,12 +24,12 @@ import ( func TestPlanClusterUpgrade(t *testing.T) { setUpFilesystem := func(existingFiles []string) file.Handler { - fs := afero.NewMemMapFs() + fs := file.NewHandler(afero.NewMemMapFs()) + require.NoError(t, fs.MkdirAll("test")) for _, f := range existingFiles { - require.NoError(t, afero.WriteFile(fs, f, []byte{}, 0o644)) + require.NoError(t, fs.Write(f, []byte{})) } - - return file.NewHandler(fs) + return fs } testCases := map[string]struct { @@ -199,6 +199,6 @@ func (t *tfClusterUpgradeStub) ApplyCluster(_ context.Context, _ cloudprovider.P return state.Infrastructure{}, t.applyErr } -func (t *tfClusterUpgradeStub) PrepareUpgradeWorkspace(_, _ string, _ terraform.Variables) error { +func (t *tfClusterUpgradeStub) PrepareWorkspace(_ string, _ terraform.Variables) error { return t.prepareWorkspaceErr } diff --git a/cli/internal/cloudcmd/create.go b/cli/internal/cloudcmd/create.go index ec10340c4..85031a7fc 100644 --- a/cli/internal/cloudcmd/create.go +++ b/cli/internal/cloudcmd/create.go @@ -23,6 +23,7 @@ import ( "github.com/edgelesssys/constellation/v2/cli/internal/terraform" "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/imagefetcher" ) @@ -213,7 +214,7 @@ func (c *Creator) createOpenStack(ctx context.Context, cl tfResourceClient, opts } func runTerraformCreate(ctx context.Context, cl tfResourceClient, provider cloudprovider.Provider, vars terraform.Variables, outWriter io.Writer, loglevel terraform.LogLevel) (output state.Infrastructure, retErr error) { - if err := cl.PrepareWorkspace(path.Join("terraform", strings.ToLower(provider.String())), vars); err != nil { + if err := cl.PrepareWorkspace(path.Join(constants.TerraformEmbeddedDir, strings.ToLower(provider.String())), vars); err != nil { return state.Infrastructure{}, err } @@ -284,7 +285,7 @@ func (c *Creator) createQEMU(ctx context.Context, cl tfResourceClient, lv libvir vars.Firmware = toPtr(opts.Config.Provider.QEMU.Firmware) } - if err := cl.PrepareWorkspace(path.Join("terraform", strings.ToLower(cloudprovider.QEMU.String())), vars); err != nil { + if err := cl.PrepareWorkspace(path.Join(constants.TerraformEmbeddedDir, strings.ToLower(cloudprovider.QEMU.String())), vars); err != nil { return state.Infrastructure{}, fmt.Errorf("prepare workspace: %w", err) } diff --git a/cli/internal/cloudcmd/iam.go b/cli/internal/cloudcmd/iam.go index 20f31be6e..529e329b7 100644 --- a/cli/internal/cloudcmd/iam.go +++ b/cli/internal/cloudcmd/iam.go @@ -18,6 +18,7 @@ import ( "github.com/edgelesssys/constellation/v2/cli/internal/terraform" "github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider" "github.com/edgelesssys/constellation/v2/internal/cloud/gcpshared" + "github.com/edgelesssys/constellation/v2/internal/constants" ) // IAMDestroyer destroys an IAM configuration. @@ -144,7 +145,7 @@ func (c *IAMCreator) createGCP(ctx context.Context, cl tfIAMClient, opts *IAMCon Zone: opts.GCP.Zone, } - if err := cl.PrepareWorkspace(path.Join("terraform", "iam", strings.ToLower(cloudprovider.GCP.String())), &vars); err != nil { + if err := cl.PrepareWorkspace(path.Join(constants.TerraformEmbeddedDir, "iam", strings.ToLower(cloudprovider.GCP.String())), &vars); err != nil { return IAMOutput{}, err } @@ -171,7 +172,7 @@ func (c *IAMCreator) createAzure(ctx context.Context, cl tfIAMClient, opts *IAMC ServicePrincipal: opts.Azure.ServicePrincipal, } - if err := cl.PrepareWorkspace(path.Join("terraform", "iam", strings.ToLower(cloudprovider.Azure.String())), &vars); err != nil { + if err := cl.PrepareWorkspace(path.Join(constants.TerraformEmbeddedDir, "iam", strings.ToLower(cloudprovider.Azure.String())), &vars); err != nil { return IAMOutput{}, err } @@ -199,7 +200,7 @@ func (c *IAMCreator) createAWS(ctx context.Context, cl tfIAMClient, opts *IAMCon Prefix: opts.AWS.Prefix, } - if err := cl.PrepareWorkspace(path.Join("terraform", "iam", strings.ToLower(cloudprovider.AWS.String())), &vars); err != nil { + if err := cl.PrepareWorkspace(path.Join(constants.TerraformEmbeddedDir, "iam", strings.ToLower(cloudprovider.AWS.String())), &vars); err != nil { return IAMOutput{}, err } diff --git a/cli/internal/cloudcmd/iamupgrade.go b/cli/internal/cloudcmd/iamupgrade.go index bab789ac2..dcf8b331c 100644 --- a/cli/internal/cloudcmd/iamupgrade.go +++ b/cli/internal/cloudcmd/iamupgrade.go @@ -42,7 +42,7 @@ type IAMUpgrader struct { func NewIAMUpgrader(ctx context.Context, existingWorkspace, upgradeWorkspace string, logLevel terraform.LogLevel, fileHandler file.Handler, ) (*IAMUpgrader, error) { - tfClient, err := terraform.New(ctx, constants.TerraformIAMWorkingDir) + tfClient, err := terraform.New(ctx, existingWorkspace) if err != nil { return nil, fmt.Errorf("setting up terraform client: %w", err) } @@ -61,7 +61,8 @@ func NewIAMUpgrader(ctx context.Context, existingWorkspace, upgradeWorkspace str func (u *IAMUpgrader) PlanIAMUpgrade(ctx context.Context, outWriter io.Writer, vars terraform.Variables, csp cloudprovider.Provider) (bool, error) { return planUpgrade( ctx, u.tf, u.fileHandler, outWriter, u.logLevel, vars, - filepath.Join("terraform", "iam", strings.ToLower(csp.String())), + filepath.Join(constants.TerraformEmbeddedDir, "iam", strings.ToLower(csp.String())), + u.existingWorkspace, filepath.Join(u.upgradeWorkspace, constants.TerraformIAMUpgradeBackupDir), ) } diff --git a/cli/internal/cloudcmd/tfupgrade.go b/cli/internal/cloudcmd/tfupgrade.go index 6f5f225ef..7f44f09fb 100644 --- a/cli/internal/cloudcmd/tfupgrade.go +++ b/cli/internal/cloudcmd/tfupgrade.go @@ -21,19 +21,19 @@ import ( func planUpgrade( ctx context.Context, tfClient tfUpgradePlanner, fileHandler file.Handler, outWriter io.Writer, logLevel terraform.LogLevel, vars terraform.Variables, - templateDir, backupDir string, + templateDir, existingWorkspace, backupDir string, ) (bool, error) { if err := ensureFileNotExist(fileHandler, backupDir); err != nil { return false, fmt.Errorf("backup directory %s already exists: %w", backupDir, err) } - // Backup the old Terraform workspace and move the embedded Terraform files into the workspace. - err := tfClient.PrepareUpgradeWorkspace( - templateDir, - backupDir, - vars, - ) - if err != nil { + // Backup old workspace + if err := fileHandler.CopyDir(existingWorkspace, backupDir); err != nil { + return false, fmt.Errorf("backing up old workspace: %w", err) + } + + // Move the new embedded Terraform files into the workspace. + if err := tfClient.PrepareWorkspace(templateDir, vars); err != nil { return false, fmt.Errorf("preparing terraform workspace: %w", err) } diff --git a/cli/internal/cloudcmd/tfupgrade_test.go b/cli/internal/cloudcmd/tfupgrade_test.go index 1ee0ea948..717abeb2e 100644 --- a/cli/internal/cloudcmd/tfupgrade_test.go +++ b/cli/internal/cloudcmd/tfupgrade_test.go @@ -9,6 +9,7 @@ package cloudcmd import ( "context" "io" + "path/filepath" "testing" "github.com/edgelesssys/constellation/v2/cli/internal/terraform" @@ -19,6 +20,19 @@ import ( ) func TestPlanUpgrade(t *testing.T) { + const ( + templateDir = "templateDir" + existingWorkspace = "existing" + backupDir = "backup" + testFile = "testfile" + ) + fsWithWorkspace := func(require *require.Assertions) file.Handler { + fs := file.NewHandler(afero.NewMemMapFs()) + require.NoError(fs.MkdirAll(existingWorkspace)) + require.NoError(fs.Write(filepath.Join(existingWorkspace, testFile), []byte{})) + return fs + } + testCases := map[string]struct { prepareFs func(require *require.Assertions) file.Handler tf *stubUpgradePlanner @@ -26,51 +40,48 @@ func TestPlanUpgrade(t *testing.T) { wantErr bool }{ "success no diff": { - prepareFs: func(require *require.Assertions) file.Handler { - return file.NewHandler(afero.NewMemMapFs()) - }, - tf: &stubUpgradePlanner{}, + prepareFs: fsWithWorkspace, + tf: &stubUpgradePlanner{}, }, "success diff": { - prepareFs: func(require *require.Assertions) file.Handler { - return file.NewHandler(afero.NewMemMapFs()) - }, + prepareFs: fsWithWorkspace, tf: &stubUpgradePlanner{ planDiff: true, }, wantDiff: true, }, + "workspace does not exist": { + prepareFs: func(require *require.Assertions) file.Handler { + return file.NewHandler(afero.NewMemMapFs()) + }, + tf: &stubUpgradePlanner{}, + wantErr: true, + }, "workspace not clean": { prepareFs: func(require *require.Assertions) file.Handler { - fs := file.NewHandler(afero.NewMemMapFs()) - require.NoError(fs.MkdirAll("backup")) + fs := fsWithWorkspace(require) + require.NoError(fs.MkdirAll(backupDir)) return fs }, tf: &stubUpgradePlanner{}, wantErr: true, }, "prepare workspace error": { - prepareFs: func(require *require.Assertions) file.Handler { - return file.NewHandler(afero.NewMemMapFs()) - }, + prepareFs: fsWithWorkspace, tf: &stubUpgradePlanner{ prepareWorkspaceErr: assert.AnError, }, wantErr: true, }, "plan error": { - prepareFs: func(require *require.Assertions) file.Handler { - return file.NewHandler(afero.NewMemMapFs()) - }, + prepareFs: fsWithWorkspace, tf: &stubUpgradePlanner{ planErr: assert.AnError, }, wantErr: true, }, "show plan error": { - prepareFs: func(require *require.Assertions) file.Handler { - return file.NewHandler(afero.NewMemMapFs()) - }, + prepareFs: fsWithWorkspace, tf: &stubUpgradePlanner{ planDiff: true, showPlanErr: assert.AnError, @@ -87,7 +98,7 @@ func TestPlanUpgrade(t *testing.T) { hasDiff, err := planUpgrade( context.Background(), tc.tf, fs, io.Discard, terraform.LogLevelDebug, &terraform.QEMUVariables{}, - "test", "backup", + templateDir, existingWorkspace, backupDir, ) if tc.wantErr { assert.Error(err) @@ -95,6 +106,8 @@ func TestPlanUpgrade(t *testing.T) { } assert.NoError(err) assert.Equal(tc.wantDiff, hasDiff) + _, err = fs.Stat(filepath.Join(backupDir, testFile)) + assert.NoError(err) }) } } @@ -208,7 +221,7 @@ type stubUpgradePlanner struct { showPlanErr error } -func (s *stubUpgradePlanner) PrepareUpgradeWorkspace(_, _ string, _ terraform.Variables) error { +func (s *stubUpgradePlanner) PrepareWorkspace(_ string, _ terraform.Variables) error { return s.prepareWorkspaceErr } diff --git a/cli/internal/cmd/create.go b/cli/internal/cmd/create.go index 6f92c18de..bdfc991de 100644 --- a/cli/internal/cmd/create.go +++ b/cli/internal/cmd/create.go @@ -12,9 +12,7 @@ import ( "io/fs" "github.com/edgelesssys/constellation/v2/cli/internal/cloudcmd" - "github.com/edgelesssys/constellation/v2/cli/internal/cmd/pathprefix" "github.com/edgelesssys/constellation/v2/cli/internal/state" - "github.com/edgelesssys/constellation/v2/cli/internal/terraform" "github.com/edgelesssys/constellation/v2/internal/api/attestationconfigapi" "github.com/edgelesssys/constellation/v2/internal/api/versionsapi" "github.com/edgelesssys/constellation/v2/internal/attestation/variant" @@ -187,7 +185,7 @@ func (c *createCmd) create(cmd *cobra.Command, creator cloudCreator, fileHandler infraState, err := creator.Create(cmd.Context(), opts) spinner.Stop() if err != nil { - return translateCreateErrors(cmd, c.flags.pathPrefixer, err) + return err } c.log.Debugf("Successfully created the cloud resources for the cluster") @@ -220,31 +218,17 @@ func (c *createCmd) checkDirClean(fileHandler file.Handler) error { c.flags.pathPrefixer.PrefixPrintablePath(constants.MasterSecretFilename), ) } + c.log.Debugf("Checking Terraform working directory") + if _, err := fileHandler.Stat(constants.TerraformWorkingDir); !errors.Is(err, fs.ErrNotExist) { + return fmt.Errorf( + "directory '%s' already exists in working directory, run 'constellation terminate' before creating a new one", + c.flags.pathPrefixer.PrefixPrintablePath(constants.TerraformWorkingDir), + ) + } return nil } -func translateCreateErrors(cmd *cobra.Command, pf pathprefix.PathPrefixer, err error) error { - switch { - case errors.Is(err, terraform.ErrTerraformWorkspaceDifferentFiles): - cmd.PrintErrln("\nYour current working directory contains an existing Terraform workspace which does not match the expected state.") - cmd.PrintErrln("This can be due to a mix up between providers, versions or an otherwise corrupted workspace.") - cmd.PrintErrln("Before creating a new cluster, try \"constellation terminate\".") - cmd.PrintErrf("If this does not work, either move or delete the directory %q.\n", pf.PrefixPrintablePath(constants.TerraformWorkingDir)) - cmd.PrintErrln("Please only delete the directory if you made sure that all created cloud resources have been terminated.") - return err - case errors.Is(err, terraform.ErrTerraformWorkspaceExistsWithDifferentVariables): - cmd.PrintErrln("\nYour current working directory contains an existing Terraform workspace which was initiated with different input variables.") - cmd.PrintErrln("This can be the case if you have tried to create a cluster before with different options which did not complete, or the workspace is corrupted.") - cmd.PrintErrln("Before creating a new cluster, try \"constellation terminate\".") - cmd.PrintErrf("If this does not work, either move or delete the directory %q.\n", pf.PrefixPrintablePath(constants.TerraformWorkingDir)) - cmd.PrintErrln("Please only delete the directory if you made sure that all created cloud resources have been terminated.") - return err - default: - return err - } -} - func isPlural(count int) string { if count == 1 { return "" diff --git a/cli/internal/cmd/create_test.go b/cli/internal/cmd/create_test.go index 22216c7d6..ec478acde 100644 --- a/cli/internal/cmd/create_test.go +++ b/cli/internal/cmd/create_test.go @@ -184,28 +184,26 @@ func TestCreate(t *testing.T) { func TestCheckDirClean(t *testing.T) { testCases := map[string]struct { - fileHandler file.Handler existingFiles []string wantErr bool }{ - "no file exists": { - fileHandler: file.NewHandler(afero.NewMemMapFs()), - }, + "no file exists": {}, "adminconf exists": { - fileHandler: file.NewHandler(afero.NewMemMapFs()), existingFiles: []string{constants.AdminConfFilename}, wantErr: true, }, "master secret exists": { - fileHandler: file.NewHandler(afero.NewMemMapFs()), existingFiles: []string{constants.MasterSecretFilename}, wantErr: true, }, "multiple exist": { - fileHandler: file.NewHandler(afero.NewMemMapFs()), existingFiles: []string{constants.AdminConfFilename, constants.MasterSecretFilename}, wantErr: true, }, + "terraform dir exists": { + existingFiles: []string{constants.TerraformWorkingDir}, + wantErr: true, + }, } for name, tc := range testCases { @@ -213,11 +211,12 @@ func TestCheckDirClean(t *testing.T) { assert := assert.New(t) require := require.New(t) + fh := file.NewHandler(afero.NewMemMapFs()) for _, f := range tc.existingFiles { - require.NoError(tc.fileHandler.Write(f, []byte{1, 2, 3}, file.OptNone)) + require.NoError(fh.Write(f, []byte{1, 2, 3}, file.OptNone)) } c := &createCmd{log: logger.NewTest(t)} - err := c.checkDirClean(tc.fileHandler) + err := c.checkDirClean(fh) if tc.wantErr { assert.Error(err) diff --git a/cli/internal/terraform/loader.go b/cli/internal/terraform/loader.go index 092bf28c6..7ab5436f3 100644 --- a/cli/internal/terraform/loader.go +++ b/cli/internal/terraform/loader.go @@ -7,10 +7,8 @@ SPDX-License-Identifier: AGPL-3.0-only package terraform import ( - "bytes" "embed" "errors" - "fmt" "io/fs" slashpath "path" "path/filepath" @@ -20,44 +18,19 @@ import ( "github.com/spf13/afero" ) -// ErrTerraformWorkspaceDifferentFiles is returned when a re-used existing Terraform workspace has different files than the ones to be extracted (e.g. due to a version mix-up or incomplete writes). -var ErrTerraformWorkspaceDifferentFiles = errors.New("creating cluster: trying to overwrite an existing Terraform file with a different version") - //go:embed terraform/* //go:embed terraform/*/.terraform.lock.hcl //go:embed terraform/iam/*/.terraform.lock.hcl var terraformFS embed.FS -const ( - noOverwrites overwritePolicy = iota - allowOverwrites -) - -type overwritePolicy int - // prepareWorkspace loads the embedded Terraform files, // and writes them into the workspace. func prepareWorkspace(rootDir string, fileHandler file.Handler, workingDir string) error { - return terraformCopier(fileHandler, rootDir, workingDir, noOverwrites) -} - -// prepareUpgradeWorkspace backs up the old Terraform workspace from workingDir, and -// copies the embedded Terraform files into workingDir. -func prepareUpgradeWorkspace(rootDir string, fileHandler file.Handler, workingDir, backupDir string) error { - // backup old workspace - if err := fileHandler.CopyDir( - workingDir, - backupDir, - ); err != nil { - return fmt.Errorf("backing up old workspace: %w", err) - } - - return terraformCopier(fileHandler, rootDir, workingDir, allowOverwrites) + return terraformCopier(fileHandler, rootDir, workingDir) } // terraformCopier copies the embedded Terraform files into the workspace. -// allowOverwrites allows overwriting existing files in the workspace. -func terraformCopier(fileHandler file.Handler, rootDir, workingDir string, overwritePolicy overwritePolicy) error { +func terraformCopier(fileHandler file.Handler, rootDir, workingDir string) error { goEmbedRootDir := filepath.ToSlash(rootDir) return fs.WalkDir(terraformFS, goEmbedRootDir, func(path string, d fs.DirEntry, err error) error { if err != nil { @@ -76,29 +49,14 @@ func terraformCopier(fileHandler file.Handler, rootDir, workingDir string, overw fileName := strings.Replace(slashpath.Join(workingDir, path), goEmbedRootDir+"/", "", 1) opts := []file.Option{ file.OptMkdirAll, + // Allow overwriting existing files. + // If we are creating a new cluster, the workspace must have been empty before, + // so there is no risk of overwriting existing files. + // If we are upgrading an existing cluster, we want to overwrite the existing files, + // and we have already created a backup of the existing workspace. + file.OptOverwrite, } - 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, - // provider, corrupted or manually modified in general. - existingFileContent, err := fileHandler.Read(fileName) - if err != nil { - return err - } - - if !bytes.Equal(content, existingFileContent) { - return ErrTerraformWorkspaceDifferentFiles - } - return nil - } else if err != nil { - return err - } - - return nil + return fileHandler.Write(fileName, content, opts...) }) } diff --git a/cli/internal/terraform/loader_test.go b/cli/internal/terraform/loader_test.go index 3d1b0dcc0..4734bba1d 100644 --- a/cli/internal/terraform/loader_test.go +++ b/cli/internal/terraform/loader_test.go @@ -14,14 +14,13 @@ import ( "testing" "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" ) -var oldFileContent = []byte("1234") - func TestPrepareWorkspace(t *testing.T) { testCases := map[string]struct { pathBase string @@ -30,7 +29,7 @@ func TestPrepareWorkspace(t *testing.T) { testAlreadyUnpacked bool }{ "awsCluster": { - pathBase: "terraform", + pathBase: constants.TerraformEmbeddedDir, provider: cloudprovider.AWS, fileList: []string{ "main.tf", @@ -40,7 +39,7 @@ func TestPrepareWorkspace(t *testing.T) { }, }, "gcpCluster": { - pathBase: "terraform", + pathBase: constants.TerraformEmbeddedDir, provider: cloudprovider.GCP, fileList: []string{ "main.tf", @@ -50,7 +49,7 @@ func TestPrepareWorkspace(t *testing.T) { }, }, "qemuCluster": { - pathBase: "terraform", + pathBase: constants.TerraformEmbeddedDir, provider: cloudprovider.QEMU, fileList: []string{ "main.tf", @@ -60,7 +59,7 @@ func TestPrepareWorkspace(t *testing.T) { }, }, "gcpIAM": { - pathBase: path.Join("terraform", "iam"), + pathBase: path.Join(constants.TerraformEmbeddedDir, "iam"), provider: cloudprovider.GCP, fileList: []string{ "main.tf", @@ -69,7 +68,7 @@ func TestPrepareWorkspace(t *testing.T) { }, }, "azureIAM": { - pathBase: path.Join("terraform", "iam"), + pathBase: path.Join(constants.TerraformEmbeddedDir, "iam"), provider: cloudprovider.Azure, fileList: []string{ "main.tf", @@ -78,7 +77,7 @@ func TestPrepareWorkspace(t *testing.T) { }, }, "awsIAM": { - pathBase: path.Join("terraform", "iam"), + pathBase: path.Join(constants.TerraformEmbeddedDir, "iam"), provider: cloudprovider.AWS, fileList: []string{ "main.tf", @@ -87,7 +86,7 @@ func TestPrepareWorkspace(t *testing.T) { }, }, "continue on (partially) unpacked": { - pathBase: "terraform", + pathBase: constants.TerraformEmbeddedDir, provider: cloudprovider.AWS, fileList: []string{ "main.tf", @@ -129,97 +128,6 @@ func TestPrepareWorkspace(t *testing.T) { } } -func TestPrepareUpgradeWorkspace(t *testing.T) { - testCases := map[string]struct { - pathBase string - provider cloudprovider.Provider - workingDir string - backupDir string - workspaceFiles []string - expectedFiles []string - expectedBackupFiles []string - testAlreadyUnpacked bool - wantErr bool - }{ - "works": { - pathBase: "terraform", - provider: cloudprovider.AWS, - workingDir: "working", - backupDir: "backup", - workspaceFiles: []string{"main.tf", "variables.tf", "outputs.tf"}, - expectedFiles: []string{ - "main.tf", - "variables.tf", - "outputs.tf", - }, - expectedBackupFiles: []string{ - "main.tf", - "variables.tf", - "outputs.tf", - }, - }, - "state file does not exist": { - pathBase: "terraform", - provider: cloudprovider.AWS, - workingDir: "working", - backupDir: "backup", - workspaceFiles: []string{}, - expectedFiles: []string{}, - wantErr: true, - }, - "terraform file already exists in working dir (overwrite)": { - pathBase: "terraform", - provider: cloudprovider.AWS, - workingDir: "working", - backupDir: "backup", - workspaceFiles: []string{"main.tf", "variables.tf", "outputs.tf"}, - expectedFiles: []string{ - "main.tf", - "variables.tf", - "outputs.tf", - }, - expectedBackupFiles: []string{ - "main.tf", - "variables.tf", - "outputs.tf", - }, - }, - } - - for name, tc := range testCases { - t.Run(name, func(t *testing.T) { - require := require.New(t) - assert := assert.New(t) - - file := file.NewHandler(afero.NewMemMapFs()) - - path := path.Join(tc.pathBase, strings.ToLower(tc.provider.String())) - - createFiles(t, file, tc.workspaceFiles, tc.workingDir) - - err := prepareUpgradeWorkspace(path, file, tc.workingDir, tc.backupDir) - - if tc.wantErr { - require.Error(err) - } else { - require.NoError(err) - checkFiles( - t, file, - func(err error) { assert.NoError(err) }, - func(content []byte) { assert.NotEqual(oldFileContent, content) }, - 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), contentExpection func(content []byte), dir string, files []string) { t.Helper() for _, f := range files { @@ -235,14 +143,3 @@ func checkFiles(t *testing.T, fileHandler file.Handler, assertion func(error), c } } } - -func createFiles(t *testing.T, fileHandler file.Handler, fileList []string, targetDir string) { - t.Helper() - require := require.New(t) - - for _, f := range fileList { - path := filepath.Join(targetDir, f) - err := fileHandler.Write(path, oldFileContent, file.OptOverwrite, file.OptMkdirAll) - require.NoError(err) - } -} diff --git a/cli/internal/terraform/terraform.go b/cli/internal/terraform/terraform.go index d54049b79..59d563a75 100644 --- a/cli/internal/terraform/terraform.go +++ b/cli/internal/terraform/terraform.go @@ -9,7 +9,7 @@ Package terraform handles creation/destruction of cloud and IAM resources requir Since Terraform does not provide a stable Go API, we use the `terraform-exec` package to interact with Terraform. -The Terraform templates are located in the "terraform" subdirectory. The templates are embedded into the CLI binary using `go:embed`. +The Terraform templates are located in the constants.TerraformEmbeddedDir subdirectory. The templates are embedded into the CLI binary using `go:embed`. On use the relevant template is extracted to the working directory and the user customized variables are written to a `terraform.tfvars` file. Functions in this package should be kept CSP agnostic (there should be no "CreateAzureCluster" function), @@ -48,9 +48,6 @@ const ( terraformUpgradePlanFile = "plan.zip" ) -// ErrTerraformWorkspaceExistsWithDifferentVariables is returned when existing Terraform files differ from the version the CLI wants to extract. -var ErrTerraformWorkspaceExistsWithDifferentVariables = errors.New("creating cluster: a Terraform workspace already exists with different variables") - // Client manages interaction with Terraform. type Client struct { tf tfInterface @@ -353,18 +350,7 @@ func (c *Client) PrepareWorkspace(path string, vars Variables) error { return fmt.Errorf("prepare workspace: %w", err) } - return c.writeVars(vars, noOverwrites) -} - -// PrepareUpgradeWorkspace prepares a Terraform workspace for an upgrade. -// It creates a backup of the Terraform workspace in the backupDir, and copies -// the embedded Terraform files into the workingDir. -func (c *Client) PrepareUpgradeWorkspace(path, backupDir string, vars Variables) error { - if err := prepareUpgradeWorkspace(path, c.file, c.workingDir, backupDir); err != nil { - return fmt.Errorf("prepare upgrade workspace: %w", err) - } - - return c.writeVars(vars, allowOverwrites) + return c.writeVars(vars) } // ApplyCluster applies the Terraform configuration of the workspace to create or upgrade a Constellation cluster. @@ -480,27 +466,20 @@ func (c *Client) applyManualStateMigrations(ctx context.Context) error { return nil } -// 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, overwritePolicy overwritePolicy) error { +// writeVars writes / overwrites the Terraform variables file. +func (c *Client) writeVars(vars Variables) error { if vars == nil { return errors.New("creating cluster: vars is nil") } pathToVarsFile := filepath.Join(c.workingDir, terraformVarsFile) - 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. - varsContent, err := c.file.Read(pathToVarsFile) - if err != nil { - return fmt.Errorf("read variables file: %w", err) - } - if vars.String() != string(varsContent) { - return ErrTerraformWorkspaceExistsWithDifferentVariables - } - } else if err != nil { + + // Allow overwriting existing files. + // If we are creating a new cluster, the workspace must have been empty before, + // so there is no risk of overwriting existing files. + // If we are upgrading an existing cluster, we want to overwrite the existing files, + // and we have already created a backup of the existing workspace. + if err := c.file.Write(pathToVarsFile, []byte(vars.String()), file.OptOverwrite); err != nil { return fmt.Errorf("write variables file: %w", err) } diff --git a/cli/internal/terraform/terraform_test.go b/cli/internal/terraform/terraform_test.go index 4574e80a1..0f8cddc1a 100644 --- a/cli/internal/terraform/terraform_test.go +++ b/cli/internal/terraform/terraform_test.go @@ -56,20 +56,20 @@ func TestPrepareCluster(t *testing.T) { wantErr bool }{ "qemu": { - pathBase: "terraform", + pathBase: constants.TerraformEmbeddedDir, provider: cloudprovider.QEMU, vars: qemuVars, fs: afero.NewMemMapFs(), wantErr: false, }, "no vars": { - pathBase: "terraform", + pathBase: constants.TerraformEmbeddedDir, provider: cloudprovider.QEMU, fs: afero.NewMemMapFs(), wantErr: true, }, "continue on partially extracted": { - pathBase: "terraform", + pathBase: constants.TerraformEmbeddedDir, provider: cloudprovider.QEMU, vars: qemuVars, fs: afero.NewMemMapFs(), @@ -77,7 +77,7 @@ func TestPrepareCluster(t *testing.T) { wantErr: false, }, "prepare workspace fails": { - pathBase: "terraform", + pathBase: constants.TerraformEmbeddedDir, provider: cloudprovider.QEMU, vars: qemuVars, fs: afero.NewReadOnlyFs(afero.NewMemMapFs()), @@ -138,7 +138,7 @@ func TestPrepareIAM(t *testing.T) { wantErr bool }{ "no vars": { - pathBase: path.Join("terraform", "iam"), + pathBase: path.Join(constants.TerraformEmbeddedDir, "iam"), fs: afero.NewMemMapFs(), wantErr: true, }, @@ -148,14 +148,14 @@ func TestPrepareIAM(t *testing.T) { wantErr: true, }, "gcp": { - pathBase: path.Join("terraform", "iam"), + pathBase: path.Join(constants.TerraformEmbeddedDir, "iam"), provider: cloudprovider.GCP, vars: gcpVars, fs: afero.NewMemMapFs(), wantErr: false, }, "continue on partially extracted": { - pathBase: path.Join("terraform", "iam"), + pathBase: path.Join(constants.TerraformEmbeddedDir, "iam"), provider: cloudprovider.GCP, vars: gcpVars, fs: afero.NewMemMapFs(), @@ -163,14 +163,14 @@ func TestPrepareIAM(t *testing.T) { wantErr: false, }, "azure": { - pathBase: path.Join("terraform", "iam"), + pathBase: path.Join(constants.TerraformEmbeddedDir, "iam"), provider: cloudprovider.Azure, vars: azureVars, fs: afero.NewMemMapFs(), wantErr: false, }, "aws": { - pathBase: path.Join("terraform", "iam"), + pathBase: path.Join(constants.TerraformEmbeddedDir, "iam"), provider: cloudprovider.AWS, vars: awsVars, fs: afero.NewMemMapFs(), @@ -315,14 +315,14 @@ func TestCreateCluster(t *testing.T) { wantErr bool }{ "works": { - pathBase: "terraform", + pathBase: constants.TerraformEmbeddedDir, provider: cloudprovider.QEMU, vars: qemuVars, tf: &stubTerraform{showState: newQEMUState()}, fs: afero.NewMemMapFs(), }, "init fails": { - pathBase: "terraform", + pathBase: constants.TerraformEmbeddedDir, provider: cloudprovider.QEMU, vars: qemuVars, tf: &stubTerraform{initErr: assert.AnError}, @@ -330,7 +330,7 @@ func TestCreateCluster(t *testing.T) { wantErr: true, }, "apply fails": { - pathBase: "terraform", + pathBase: constants.TerraformEmbeddedDir, provider: cloudprovider.QEMU, vars: qemuVars, tf: &stubTerraform{applyErr: assert.AnError}, @@ -338,7 +338,7 @@ func TestCreateCluster(t *testing.T) { wantErr: true, }, "show fails": { - pathBase: "terraform", + pathBase: constants.TerraformEmbeddedDir, provider: cloudprovider.QEMU, vars: qemuVars, tf: &stubTerraform{showErr: assert.AnError}, @@ -346,7 +346,7 @@ func TestCreateCluster(t *testing.T) { wantErr: true, }, "set log fails": { - pathBase: "terraform", + pathBase: constants.TerraformEmbeddedDir, provider: cloudprovider.QEMU, vars: qemuVars, tf: &stubTerraform{setLogErr: assert.AnError}, @@ -354,7 +354,7 @@ func TestCreateCluster(t *testing.T) { wantErr: true, }, "set log path fails": { - pathBase: "terraform", + pathBase: constants.TerraformEmbeddedDir, provider: cloudprovider.QEMU, vars: qemuVars, tf: &stubTerraform{setLogPathErr: assert.AnError}, @@ -362,7 +362,7 @@ func TestCreateCluster(t *testing.T) { wantErr: true, }, "no ip": { - pathBase: "terraform", + pathBase: constants.TerraformEmbeddedDir, provider: cloudprovider.QEMU, vars: qemuVars, tf: &stubTerraform{ @@ -376,7 +376,7 @@ func TestCreateCluster(t *testing.T) { wantErr: true, }, "ip has wrong type": { - pathBase: "terraform", + pathBase: constants.TerraformEmbeddedDir, provider: cloudprovider.QEMU, vars: qemuVars, tf: &stubTerraform{ @@ -390,7 +390,7 @@ func TestCreateCluster(t *testing.T) { wantErr: true, }, "no uid": { - pathBase: "terraform", + pathBase: constants.TerraformEmbeddedDir, provider: cloudprovider.QEMU, vars: qemuVars, tf: &stubTerraform{ @@ -404,7 +404,7 @@ func TestCreateCluster(t *testing.T) { wantErr: true, }, "uid has wrong type": { - pathBase: "terraform", + pathBase: constants.TerraformEmbeddedDir, provider: cloudprovider.QEMU, vars: qemuVars, tf: &stubTerraform{ @@ -418,7 +418,7 @@ func TestCreateCluster(t *testing.T) { wantErr: true, }, "name has wrong type": { - pathBase: "terraform", + pathBase: constants.TerraformEmbeddedDir, provider: cloudprovider.QEMU, vars: qemuVars, tf: &stubTerraform{ @@ -432,7 +432,7 @@ func TestCreateCluster(t *testing.T) { wantErr: true, }, "working attestation url": { - pathBase: "terraform", + pathBase: constants.TerraformEmbeddedDir, provider: cloudprovider.Azure, vars: qemuVars, // works for mocking azure vars tf: &stubTerraform{showState: newAzureState()}, @@ -440,7 +440,7 @@ func TestCreateCluster(t *testing.T) { expectedAttestationURL: "https://12345.neu.attest.azure.net", }, "no attestation url": { - pathBase: "terraform", + pathBase: constants.TerraformEmbeddedDir, provider: cloudprovider.Azure, vars: qemuVars, // works for mocking azure vars tf: &stubTerraform{ @@ -454,7 +454,7 @@ func TestCreateCluster(t *testing.T) { wantErr: true, }, "attestation url has wrong type": { - pathBase: "terraform", + pathBase: constants.TerraformEmbeddedDir, provider: cloudprovider.Azure, vars: qemuVars, // works for mocking azure vars tf: &stubTerraform{ @@ -560,7 +560,7 @@ func TestCreateIAM(t *testing.T) { want IAMOutput }{ "set log fails": { - pathBase: path.Join("terraform", "iam"), + pathBase: path.Join(constants.TerraformEmbeddedDir, "iam"), provider: cloudprovider.GCP, vars: gcpVars, tf: &stubTerraform{setLogErr: assert.AnError}, @@ -568,7 +568,7 @@ func TestCreateIAM(t *testing.T) { wantErr: true, }, "set log path fails": { - pathBase: path.Join("terraform", "iam"), + pathBase: path.Join(constants.TerraformEmbeddedDir, "iam"), provider: cloudprovider.GCP, vars: gcpVars, tf: &stubTerraform{setLogPathErr: assert.AnError}, @@ -576,7 +576,7 @@ func TestCreateIAM(t *testing.T) { wantErr: true, }, "gcp works": { - pathBase: path.Join("terraform", "iam"), + pathBase: path.Join(constants.TerraformEmbeddedDir, "iam"), provider: cloudprovider.GCP, vars: gcpVars, tf: &stubTerraform{showState: newTestState()}, @@ -584,7 +584,7 @@ func TestCreateIAM(t *testing.T) { want: IAMOutput{GCP: GCPIAMOutput{SaKey: "12345678_abcdefg"}}, }, "gcp init fails": { - pathBase: path.Join("terraform", "iam"), + pathBase: path.Join(constants.TerraformEmbeddedDir, "iam"), provider: cloudprovider.GCP, vars: gcpVars, tf: &stubTerraform{initErr: assert.AnError}, @@ -592,7 +592,7 @@ func TestCreateIAM(t *testing.T) { wantErr: true, }, "gcp apply fails": { - pathBase: path.Join("terraform", "iam"), + pathBase: path.Join(constants.TerraformEmbeddedDir, "iam"), provider: cloudprovider.GCP, vars: gcpVars, tf: &stubTerraform{applyErr: assert.AnError}, @@ -600,7 +600,7 @@ func TestCreateIAM(t *testing.T) { wantErr: true, }, "gcp show fails": { - pathBase: path.Join("terraform", "iam"), + pathBase: path.Join(constants.TerraformEmbeddedDir, "iam"), provider: cloudprovider.GCP, vars: gcpVars, tf: &stubTerraform{showErr: assert.AnError}, @@ -608,7 +608,7 @@ func TestCreateIAM(t *testing.T) { wantErr: true, }, "gcp no sa_key": { - pathBase: path.Join("terraform", "iam"), + pathBase: path.Join(constants.TerraformEmbeddedDir, "iam"), provider: cloudprovider.GCP, vars: gcpVars, tf: &stubTerraform{ @@ -622,7 +622,7 @@ func TestCreateIAM(t *testing.T) { wantErr: true, }, "gcp sa_key has wrong type": { - pathBase: path.Join("terraform", "iam"), + pathBase: path.Join(constants.TerraformEmbeddedDir, "iam"), provider: cloudprovider.GCP, vars: gcpVars, tf: &stubTerraform{ @@ -636,7 +636,7 @@ func TestCreateIAM(t *testing.T) { wantErr: true, }, "azure works": { - pathBase: path.Join("terraform", "iam"), + pathBase: path.Join(constants.TerraformEmbeddedDir, "iam"), provider: cloudprovider.Azure, vars: azureVars, tf: &stubTerraform{showState: newTestState()}, @@ -648,7 +648,7 @@ func TestCreateIAM(t *testing.T) { }}, }, "azure init fails": { - pathBase: path.Join("terraform", "iam"), + pathBase: path.Join(constants.TerraformEmbeddedDir, "iam"), provider: cloudprovider.Azure, vars: azureVars, tf: &stubTerraform{initErr: assert.AnError}, @@ -656,7 +656,7 @@ func TestCreateIAM(t *testing.T) { wantErr: true, }, "azure apply fails": { - pathBase: path.Join("terraform", "iam"), + pathBase: path.Join(constants.TerraformEmbeddedDir, "iam"), provider: cloudprovider.Azure, vars: azureVars, tf: &stubTerraform{applyErr: assert.AnError}, @@ -664,7 +664,7 @@ func TestCreateIAM(t *testing.T) { wantErr: true, }, "azure show fails": { - pathBase: path.Join("terraform", "iam"), + pathBase: path.Join(constants.TerraformEmbeddedDir, "iam"), provider: cloudprovider.Azure, vars: azureVars, tf: &stubTerraform{showErr: assert.AnError}, @@ -672,7 +672,7 @@ func TestCreateIAM(t *testing.T) { wantErr: true, }, "azure no subscription_id": { - pathBase: path.Join("terraform", "iam"), + pathBase: path.Join(constants.TerraformEmbeddedDir, "iam"), provider: cloudprovider.Azure, vars: azureVars, tf: &stubTerraform{ @@ -686,7 +686,7 @@ func TestCreateIAM(t *testing.T) { wantErr: true, }, "azure subscription_id has wrong type": { - pathBase: path.Join("terraform", "iam"), + pathBase: path.Join(constants.TerraformEmbeddedDir, "iam"), provider: cloudprovider.Azure, vars: azureVars, tf: &stubTerraform{ @@ -700,7 +700,7 @@ func TestCreateIAM(t *testing.T) { wantErr: true, }, "aws works": { - pathBase: path.Join("terraform", "iam"), + pathBase: path.Join(constants.TerraformEmbeddedDir, "iam"), provider: cloudprovider.AWS, vars: awsVars, tf: &stubTerraform{showState: newTestState()}, @@ -711,7 +711,7 @@ func TestCreateIAM(t *testing.T) { }}, }, "aws init fails": { - pathBase: path.Join("terraform", "iam"), + pathBase: path.Join(constants.TerraformEmbeddedDir, "iam"), provider: cloudprovider.AWS, vars: awsVars, tf: &stubTerraform{initErr: assert.AnError}, @@ -719,7 +719,7 @@ func TestCreateIAM(t *testing.T) { wantErr: true, }, "aws apply fails": { - pathBase: path.Join("terraform", "iam"), + pathBase: path.Join(constants.TerraformEmbeddedDir, "iam"), provider: cloudprovider.AWS, vars: awsVars, tf: &stubTerraform{applyErr: assert.AnError}, @@ -727,7 +727,7 @@ func TestCreateIAM(t *testing.T) { wantErr: true, }, "aws show fails": { - pathBase: path.Join("terraform", "iam"), + pathBase: path.Join(constants.TerraformEmbeddedDir, "iam"), provider: cloudprovider.AWS, vars: awsVars, tf: &stubTerraform{showErr: assert.AnError}, @@ -735,7 +735,7 @@ func TestCreateIAM(t *testing.T) { wantErr: true, }, "aws no control_plane_instance_profile": { - pathBase: path.Join("terraform", "iam"), + pathBase: path.Join(constants.TerraformEmbeddedDir, "iam"), provider: cloudprovider.AWS, vars: awsVars, tf: &stubTerraform{ @@ -749,7 +749,7 @@ func TestCreateIAM(t *testing.T) { wantErr: true, }, "azure control_plane_instance_profile has wrong type": { - pathBase: path.Join("terraform", "iam"), + pathBase: path.Join(constants.TerraformEmbeddedDir, "iam"), provider: cloudprovider.AWS, vars: awsVars, tf: &stubTerraform{ @@ -1003,12 +1003,12 @@ func TestPlan(t *testing.T) { wantErr bool }{ "plan succeeds": { - pathBase: "terraform", + pathBase: constants.TerraformEmbeddedDir, tf: &stubTerraform{}, fs: afero.NewMemMapFs(), }, "set log path fails": { - pathBase: "terraform", + pathBase: constants.TerraformEmbeddedDir, tf: &stubTerraform{ setLogPathErr: someError, }, @@ -1016,7 +1016,7 @@ func TestPlan(t *testing.T) { wantErr: true, }, "set log fails": { - pathBase: "terraform", + pathBase: constants.TerraformEmbeddedDir, tf: &stubTerraform{ setLogErr: someError, }, @@ -1024,7 +1024,7 @@ func TestPlan(t *testing.T) { wantErr: true, }, "plan fails": { - pathBase: "terraform", + pathBase: constants.TerraformEmbeddedDir, tf: &stubTerraform{ planJSONErr: someError, }, @@ -1032,7 +1032,7 @@ func TestPlan(t *testing.T) { wantErr: true, }, "init fails": { - pathBase: "terraform", + pathBase: constants.TerraformEmbeddedDir, tf: &stubTerraform{ initErr: someError, }, @@ -1070,12 +1070,12 @@ func TestShowPlan(t *testing.T) { wantErr bool }{ "show plan succeeds": { - pathBase: "terraform", + pathBase: constants.TerraformEmbeddedDir, tf: &stubTerraform{}, fs: afero.NewMemMapFs(), }, "set log path fails": { - pathBase: "terraform", + pathBase: constants.TerraformEmbeddedDir, tf: &stubTerraform{ setLogPathErr: someError, }, @@ -1083,7 +1083,7 @@ func TestShowPlan(t *testing.T) { wantErr: true, }, "set log fails": { - pathBase: "terraform", + pathBase: constants.TerraformEmbeddedDir, tf: &stubTerraform{ setLogErr: someError, }, @@ -1091,7 +1091,7 @@ func TestShowPlan(t *testing.T) { wantErr: true, }, "show plan file fails": { - pathBase: "terraform", + pathBase: constants.TerraformEmbeddedDir, tf: &stubTerraform{ showPlanFileErr: someError, }, diff --git a/internal/constants/constants.go b/internal/constants/constants.go index 853c45dfd..f3a14e650 100644 --- a/internal/constants/constants.go +++ b/internal/constants/constants.go @@ -171,6 +171,8 @@ const ( TerraformUpgradeBackupDir = "terraform-backup" // TerraformIAMUpgradeBackupDir is the directory name being used to backup the pre-upgrade state of iam in an upgrade. TerraformIAMUpgradeBackupDir = "terraform-iam-backup" + // TerraformEmbeddedDir is the name of the base directory embedded in the CLI binary containing the Terraform files. + TerraformEmbeddedDir = "terraform" // UpgradeDir is the name of the directory being used for cluster upgrades. UpgradeDir = "constellation-upgrade" // ControlPlaneDefault is the name of the default control plane worker group.