cli: refactor terraform code to be update/create agnostic (#2501)

* Move upgrade specific functions out of Terraform module
* Always allow overwriting Terraform files
* Ensure constellation-terraform dir does not exist on create

---------

Signed-off-by: Daniel Weiße <dw@edgeless.systems>
This commit is contained in:
Daniel Weiße 2023-10-26 10:55:50 +02:00 committed by GitHub
parent f9989728f7
commit ec424b260d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
15 changed files with 158 additions and 322 deletions

View file

@ -46,7 +46,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, backupDir string, vars terraform.Variables) error PrepareWorkspace(path string, vars terraform.Variables) error
} }
type tfIAMUpgradeClient interface { type tfIAMUpgradeClient interface {

View file

@ -36,7 +36,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, constants.TerraformWorkingDir) tfClient, err := terraform.New(ctx, existingWorkspace)
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,7 +57,8 @@ func (u *ClusterUpgrader) PlanClusterUpgrade(ctx context.Context, outWriter io.W
) (bool, error) { ) (bool, error) {
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(constants.TerraformEmbeddedDir, strings.ToLower(csp.String())),
u.existingWorkspace,
filepath.Join(u.upgradeWorkspace, constants.TerraformUpgradeBackupDir), filepath.Join(u.upgradeWorkspace, constants.TerraformUpgradeBackupDir),
) )
} }

View file

@ -24,12 +24,12 @@ import (
func TestPlanClusterUpgrade(t *testing.T) { func TestPlanClusterUpgrade(t *testing.T) {
setUpFilesystem := func(existingFiles []string) file.Handler { setUpFilesystem := func(existingFiles []string) file.Handler {
fs := afero.NewMemMapFs() fs := file.NewHandler(afero.NewMemMapFs())
require.NoError(t, fs.MkdirAll("test"))
for _, f := range existingFiles { for _, f := range existingFiles {
require.NoError(t, afero.WriteFile(fs, f, []byte{}, 0o644)) require.NoError(t, fs.Write(f, []byte{}))
} }
return fs
return file.NewHandler(fs)
} }
testCases := map[string]struct { testCases := map[string]struct {
@ -199,6 +199,6 @@ func (t *tfClusterUpgradeStub) ApplyCluster(_ context.Context, _ cloudprovider.P
return state.Infrastructure{}, t.applyErr return state.Infrastructure{}, t.applyErr
} }
func (t *tfClusterUpgradeStub) PrepareUpgradeWorkspace(_, _ string, _ terraform.Variables) error { func (t *tfClusterUpgradeStub) PrepareWorkspace(_ string, _ terraform.Variables) error {
return t.prepareWorkspaceErr return t.prepareWorkspaceErr
} }

View file

@ -23,6 +23,7 @@ import (
"github.com/edgelesssys/constellation/v2/cli/internal/terraform" "github.com/edgelesssys/constellation/v2/cli/internal/terraform"
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider" "github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
"github.com/edgelesssys/constellation/v2/internal/config" "github.com/edgelesssys/constellation/v2/internal/config"
"github.com/edgelesssys/constellation/v2/internal/constants"
"github.com/edgelesssys/constellation/v2/internal/imagefetcher" "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) { 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 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) 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) return state.Infrastructure{}, fmt.Errorf("prepare workspace: %w", err)
} }

View file

@ -18,6 +18,7 @@ import (
"github.com/edgelesssys/constellation/v2/cli/internal/terraform" "github.com/edgelesssys/constellation/v2/cli/internal/terraform"
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider" "github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
"github.com/edgelesssys/constellation/v2/internal/cloud/gcpshared" "github.com/edgelesssys/constellation/v2/internal/cloud/gcpshared"
"github.com/edgelesssys/constellation/v2/internal/constants"
) )
// IAMDestroyer destroys an IAM configuration. // IAMDestroyer destroys an IAM configuration.
@ -144,7 +145,7 @@ func (c *IAMCreator) createGCP(ctx context.Context, cl tfIAMClient, opts *IAMCon
Zone: opts.GCP.Zone, 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 return IAMOutput{}, err
} }
@ -171,7 +172,7 @@ func (c *IAMCreator) createAzure(ctx context.Context, cl tfIAMClient, opts *IAMC
ServicePrincipal: opts.Azure.ServicePrincipal, 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 return IAMOutput{}, err
} }
@ -199,7 +200,7 @@ func (c *IAMCreator) createAWS(ctx context.Context, cl tfIAMClient, opts *IAMCon
Prefix: opts.AWS.Prefix, 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 return IAMOutput{}, err
} }

View file

@ -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, constants.TerraformIAMWorkingDir) tfClient, err := terraform.New(ctx, existingWorkspace)
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)
} }
@ -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) { func (u *IAMUpgrader) PlanIAMUpgrade(ctx context.Context, outWriter io.Writer, vars terraform.Variables, csp cloudprovider.Provider) (bool, error) {
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(constants.TerraformEmbeddedDir, "iam", strings.ToLower(csp.String())),
u.existingWorkspace,
filepath.Join(u.upgradeWorkspace, constants.TerraformIAMUpgradeBackupDir), filepath.Join(u.upgradeWorkspace, constants.TerraformIAMUpgradeBackupDir),
) )
} }

View file

@ -21,19 +21,19 @@ 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, backupDir string, templateDir, existingWorkspace, backupDir string,
) (bool, error) { ) (bool, error) {
if err := ensureFileNotExist(fileHandler, backupDir); err != nil { if err := ensureFileNotExist(fileHandler, backupDir); err != nil {
return false, fmt.Errorf("backup directory %s already exists: %w", backupDir, err) 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. // Backup old workspace
err := tfClient.PrepareUpgradeWorkspace( if err := fileHandler.CopyDir(existingWorkspace, backupDir); err != nil {
templateDir, return false, fmt.Errorf("backing up old workspace: %w", err)
backupDir, }
vars,
) // Move the new embedded Terraform files into the workspace.
if err != nil { if err := tfClient.PrepareWorkspace(templateDir, vars); err != nil {
return false, fmt.Errorf("preparing terraform workspace: %w", err) return false, fmt.Errorf("preparing terraform workspace: %w", err)
} }

View file

@ -9,6 +9,7 @@ package cloudcmd
import ( import (
"context" "context"
"io" "io"
"path/filepath"
"testing" "testing"
"github.com/edgelesssys/constellation/v2/cli/internal/terraform" "github.com/edgelesssys/constellation/v2/cli/internal/terraform"
@ -19,6 +20,19 @@ import (
) )
func TestPlanUpgrade(t *testing.T) { 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 { testCases := map[string]struct {
prepareFs func(require *require.Assertions) file.Handler prepareFs func(require *require.Assertions) file.Handler
tf *stubUpgradePlanner tf *stubUpgradePlanner
@ -26,51 +40,48 @@ func TestPlanUpgrade(t *testing.T) {
wantErr bool wantErr bool
}{ }{
"success no diff": { "success no diff": {
prepareFs: func(require *require.Assertions) file.Handler { prepareFs: fsWithWorkspace,
return file.NewHandler(afero.NewMemMapFs())
},
tf: &stubUpgradePlanner{}, tf: &stubUpgradePlanner{},
}, },
"success diff": { "success diff": {
prepareFs: func(require *require.Assertions) file.Handler { prepareFs: fsWithWorkspace,
return file.NewHandler(afero.NewMemMapFs())
},
tf: &stubUpgradePlanner{ tf: &stubUpgradePlanner{
planDiff: true, planDiff: true,
}, },
wantDiff: 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": { "workspace not clean": {
prepareFs: func(require *require.Assertions) file.Handler { prepareFs: func(require *require.Assertions) file.Handler {
fs := file.NewHandler(afero.NewMemMapFs()) fs := fsWithWorkspace(require)
require.NoError(fs.MkdirAll("backup")) require.NoError(fs.MkdirAll(backupDir))
return fs return fs
}, },
tf: &stubUpgradePlanner{}, tf: &stubUpgradePlanner{},
wantErr: true, wantErr: true,
}, },
"prepare workspace error": { "prepare workspace error": {
prepareFs: func(require *require.Assertions) file.Handler { prepareFs: fsWithWorkspace,
return file.NewHandler(afero.NewMemMapFs())
},
tf: &stubUpgradePlanner{ tf: &stubUpgradePlanner{
prepareWorkspaceErr: assert.AnError, prepareWorkspaceErr: assert.AnError,
}, },
wantErr: true, wantErr: true,
}, },
"plan error": { "plan error": {
prepareFs: func(require *require.Assertions) file.Handler { prepareFs: fsWithWorkspace,
return file.NewHandler(afero.NewMemMapFs())
},
tf: &stubUpgradePlanner{ tf: &stubUpgradePlanner{
planErr: assert.AnError, planErr: assert.AnError,
}, },
wantErr: true, wantErr: true,
}, },
"show plan error": { "show plan error": {
prepareFs: func(require *require.Assertions) file.Handler { prepareFs: fsWithWorkspace,
return file.NewHandler(afero.NewMemMapFs())
},
tf: &stubUpgradePlanner{ tf: &stubUpgradePlanner{
planDiff: true, planDiff: true,
showPlanErr: assert.AnError, showPlanErr: assert.AnError,
@ -87,7 +98,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{},
"test", "backup", templateDir, existingWorkspace, backupDir,
) )
if tc.wantErr { if tc.wantErr {
assert.Error(err) assert.Error(err)
@ -95,6 +106,8 @@ func TestPlanUpgrade(t *testing.T) {
} }
assert.NoError(err) assert.NoError(err)
assert.Equal(tc.wantDiff, hasDiff) assert.Equal(tc.wantDiff, hasDiff)
_, err = fs.Stat(filepath.Join(backupDir, testFile))
assert.NoError(err)
}) })
} }
} }
@ -208,7 +221,7 @@ type stubUpgradePlanner struct {
showPlanErr error showPlanErr error
} }
func (s *stubUpgradePlanner) PrepareUpgradeWorkspace(_, _ string, _ terraform.Variables) error { func (s *stubUpgradePlanner) PrepareWorkspace(_ string, _ terraform.Variables) error {
return s.prepareWorkspaceErr return s.prepareWorkspaceErr
} }

View file

@ -12,9 +12,7 @@ import (
"io/fs" "io/fs"
"github.com/edgelesssys/constellation/v2/cli/internal/cloudcmd" "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/state"
"github.com/edgelesssys/constellation/v2/cli/internal/terraform"
"github.com/edgelesssys/constellation/v2/internal/api/attestationconfigapi" "github.com/edgelesssys/constellation/v2/internal/api/attestationconfigapi"
"github.com/edgelesssys/constellation/v2/internal/api/versionsapi" "github.com/edgelesssys/constellation/v2/internal/api/versionsapi"
"github.com/edgelesssys/constellation/v2/internal/attestation/variant" "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) infraState, err := creator.Create(cmd.Context(), opts)
spinner.Stop() spinner.Stop()
if err != nil { if err != nil {
return translateCreateErrors(cmd, c.flags.pathPrefixer, err) return err
} }
c.log.Debugf("Successfully created the cloud resources for the cluster") 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.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 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 { func isPlural(count int) string {
if count == 1 { if count == 1 {
return "" return ""

View file

@ -184,28 +184,26 @@ func TestCreate(t *testing.T) {
func TestCheckDirClean(t *testing.T) { func TestCheckDirClean(t *testing.T) {
testCases := map[string]struct { testCases := map[string]struct {
fileHandler file.Handler
existingFiles []string existingFiles []string
wantErr bool wantErr bool
}{ }{
"no file exists": { "no file exists": {},
fileHandler: file.NewHandler(afero.NewMemMapFs()),
},
"adminconf exists": { "adminconf exists": {
fileHandler: file.NewHandler(afero.NewMemMapFs()),
existingFiles: []string{constants.AdminConfFilename}, existingFiles: []string{constants.AdminConfFilename},
wantErr: true, wantErr: true,
}, },
"master secret exists": { "master secret exists": {
fileHandler: file.NewHandler(afero.NewMemMapFs()),
existingFiles: []string{constants.MasterSecretFilename}, existingFiles: []string{constants.MasterSecretFilename},
wantErr: true, wantErr: true,
}, },
"multiple exist": { "multiple exist": {
fileHandler: file.NewHandler(afero.NewMemMapFs()),
existingFiles: []string{constants.AdminConfFilename, constants.MasterSecretFilename}, existingFiles: []string{constants.AdminConfFilename, constants.MasterSecretFilename},
wantErr: true, wantErr: true,
}, },
"terraform dir exists": {
existingFiles: []string{constants.TerraformWorkingDir},
wantErr: true,
},
} }
for name, tc := range testCases { for name, tc := range testCases {
@ -213,11 +211,12 @@ func TestCheckDirClean(t *testing.T) {
assert := assert.New(t) assert := assert.New(t)
require := require.New(t) require := require.New(t)
fh := file.NewHandler(afero.NewMemMapFs())
for _, f := range tc.existingFiles { 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)} c := &createCmd{log: logger.NewTest(t)}
err := c.checkDirClean(tc.fileHandler) err := c.checkDirClean(fh)
if tc.wantErr { if tc.wantErr {
assert.Error(err) assert.Error(err)

View file

@ -7,10 +7,8 @@ SPDX-License-Identifier: AGPL-3.0-only
package terraform package terraform
import ( import (
"bytes"
"embed" "embed"
"errors" "errors"
"fmt"
"io/fs" "io/fs"
slashpath "path" slashpath "path"
"path/filepath" "path/filepath"
@ -20,44 +18,19 @@ import (
"github.com/spf13/afero" "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/*
//go:embed terraform/*/.terraform.lock.hcl //go:embed terraform/*/.terraform.lock.hcl
//go:embed terraform/iam/*/.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, noOverwrites) return terraformCopier(fileHandler, rootDir, workingDir)
}
// 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)
} }
// terraformCopier copies the embedded Terraform files into the workspace. // 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) error {
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 {
@ -76,29 +49,14 @@ func terraformCopier(fileHandler file.Handler, rootDir, workingDir string, overw
fileName := strings.Replace(slashpath.Join(workingDir, path), goEmbedRootDir+"/", "", 1) fileName := strings.Replace(slashpath.Join(workingDir, path), goEmbedRootDir+"/", "", 1)
opts := []file.Option{ opts := []file.Option{
file.OptMkdirAll, 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 { return fileHandler.Write(fileName, content, opts...)
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
}) })
} }

View file

@ -14,14 +14,13 @@ import (
"testing" "testing"
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider" "github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
"github.com/edgelesssys/constellation/v2/internal/constants"
"github.com/edgelesssys/constellation/v2/internal/file" "github.com/edgelesssys/constellation/v2/internal/file"
"github.com/spf13/afero" "github.com/spf13/afero"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"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
@ -30,7 +29,7 @@ func TestPrepareWorkspace(t *testing.T) {
testAlreadyUnpacked bool testAlreadyUnpacked bool
}{ }{
"awsCluster": { "awsCluster": {
pathBase: "terraform", pathBase: constants.TerraformEmbeddedDir,
provider: cloudprovider.AWS, provider: cloudprovider.AWS,
fileList: []string{ fileList: []string{
"main.tf", "main.tf",
@ -40,7 +39,7 @@ func TestPrepareWorkspace(t *testing.T) {
}, },
}, },
"gcpCluster": { "gcpCluster": {
pathBase: "terraform", pathBase: constants.TerraformEmbeddedDir,
provider: cloudprovider.GCP, provider: cloudprovider.GCP,
fileList: []string{ fileList: []string{
"main.tf", "main.tf",
@ -50,7 +49,7 @@ func TestPrepareWorkspace(t *testing.T) {
}, },
}, },
"qemuCluster": { "qemuCluster": {
pathBase: "terraform", pathBase: constants.TerraformEmbeddedDir,
provider: cloudprovider.QEMU, provider: cloudprovider.QEMU,
fileList: []string{ fileList: []string{
"main.tf", "main.tf",
@ -60,7 +59,7 @@ func TestPrepareWorkspace(t *testing.T) {
}, },
}, },
"gcpIAM": { "gcpIAM": {
pathBase: path.Join("terraform", "iam"), pathBase: path.Join(constants.TerraformEmbeddedDir, "iam"),
provider: cloudprovider.GCP, provider: cloudprovider.GCP,
fileList: []string{ fileList: []string{
"main.tf", "main.tf",
@ -69,7 +68,7 @@ func TestPrepareWorkspace(t *testing.T) {
}, },
}, },
"azureIAM": { "azureIAM": {
pathBase: path.Join("terraform", "iam"), pathBase: path.Join(constants.TerraformEmbeddedDir, "iam"),
provider: cloudprovider.Azure, provider: cloudprovider.Azure,
fileList: []string{ fileList: []string{
"main.tf", "main.tf",
@ -78,7 +77,7 @@ func TestPrepareWorkspace(t *testing.T) {
}, },
}, },
"awsIAM": { "awsIAM": {
pathBase: path.Join("terraform", "iam"), pathBase: path.Join(constants.TerraformEmbeddedDir, "iam"),
provider: cloudprovider.AWS, provider: cloudprovider.AWS,
fileList: []string{ fileList: []string{
"main.tf", "main.tf",
@ -87,7 +86,7 @@ func TestPrepareWorkspace(t *testing.T) {
}, },
}, },
"continue on (partially) unpacked": { "continue on (partially) unpacked": {
pathBase: "terraform", pathBase: constants.TerraformEmbeddedDir,
provider: cloudprovider.AWS, provider: cloudprovider.AWS,
fileList: []string{ fileList: []string{
"main.tf", "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) { 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 {
@ -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)
}
}

View file

@ -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. 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. 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), Functions in this package should be kept CSP agnostic (there should be no "CreateAzureCluster" function),
@ -48,9 +48,6 @@ const (
terraformUpgradePlanFile = "plan.zip" 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. // Client manages interaction with Terraform.
type Client struct { type Client struct {
tf tfInterface tf tfInterface
@ -353,18 +350,7 @@ 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, noOverwrites) return c.writeVars(vars)
}
// 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)
} }
// 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.
@ -480,27 +466,20 @@ func (c *Client) applyManualStateMigrations(ctx context.Context) error {
return nil return nil
} }
// writeVars tries to write the Terraform variables file or, if it exists, checks if it is the same as we are expecting. // writeVars writes / overwrites the Terraform variables file.
func (c *Client) writeVars(vars Variables, overwritePolicy overwritePolicy) error { func (c *Client) writeVars(vars Variables) 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)
opts := []file.Option{}
if overwritePolicy == allowOverwrites { // Allow overwriting existing files.
opts = append(opts, file.OptOverwrite) // If we are creating a new cluster, the workspace must have been empty before,
} // so there is no risk of overwriting existing files.
if err := c.file.Write(pathToVarsFile, []byte(vars.String()), opts...); errors.Is(err, afero.ErrFileExists) { // If we are upgrading an existing cluster, we want to overwrite the existing files,
// If a variables file already exists, check if it's the same as we're expecting, so we can continue using it. // and we have already created a backup of the existing workspace.
varsContent, err := c.file.Read(pathToVarsFile) if err := c.file.Write(pathToVarsFile, []byte(vars.String()), file.OptOverwrite); err != nil {
if err != nil {
return fmt.Errorf("read variables file: %w", err)
}
if vars.String() != string(varsContent) {
return ErrTerraformWorkspaceExistsWithDifferentVariables
}
} else if err != nil {
return fmt.Errorf("write variables file: %w", err) return fmt.Errorf("write variables file: %w", err)
} }

View file

@ -56,20 +56,20 @@ func TestPrepareCluster(t *testing.T) {
wantErr bool wantErr bool
}{ }{
"qemu": { "qemu": {
pathBase: "terraform", pathBase: constants.TerraformEmbeddedDir,
provider: cloudprovider.QEMU, provider: cloudprovider.QEMU,
vars: qemuVars, vars: qemuVars,
fs: afero.NewMemMapFs(), fs: afero.NewMemMapFs(),
wantErr: false, wantErr: false,
}, },
"no vars": { "no vars": {
pathBase: "terraform", pathBase: constants.TerraformEmbeddedDir,
provider: cloudprovider.QEMU, provider: cloudprovider.QEMU,
fs: afero.NewMemMapFs(), fs: afero.NewMemMapFs(),
wantErr: true, wantErr: true,
}, },
"continue on partially extracted": { "continue on partially extracted": {
pathBase: "terraform", pathBase: constants.TerraformEmbeddedDir,
provider: cloudprovider.QEMU, provider: cloudprovider.QEMU,
vars: qemuVars, vars: qemuVars,
fs: afero.NewMemMapFs(), fs: afero.NewMemMapFs(),
@ -77,7 +77,7 @@ func TestPrepareCluster(t *testing.T) {
wantErr: false, wantErr: false,
}, },
"prepare workspace fails": { "prepare workspace fails": {
pathBase: "terraform", pathBase: constants.TerraformEmbeddedDir,
provider: cloudprovider.QEMU, provider: cloudprovider.QEMU,
vars: qemuVars, vars: qemuVars,
fs: afero.NewReadOnlyFs(afero.NewMemMapFs()), fs: afero.NewReadOnlyFs(afero.NewMemMapFs()),
@ -138,7 +138,7 @@ func TestPrepareIAM(t *testing.T) {
wantErr bool wantErr bool
}{ }{
"no vars": { "no vars": {
pathBase: path.Join("terraform", "iam"), pathBase: path.Join(constants.TerraformEmbeddedDir, "iam"),
fs: afero.NewMemMapFs(), fs: afero.NewMemMapFs(),
wantErr: true, wantErr: true,
}, },
@ -148,14 +148,14 @@ func TestPrepareIAM(t *testing.T) {
wantErr: true, wantErr: true,
}, },
"gcp": { "gcp": {
pathBase: path.Join("terraform", "iam"), pathBase: path.Join(constants.TerraformEmbeddedDir, "iam"),
provider: cloudprovider.GCP, provider: cloudprovider.GCP,
vars: gcpVars, vars: gcpVars,
fs: afero.NewMemMapFs(), fs: afero.NewMemMapFs(),
wantErr: false, wantErr: false,
}, },
"continue on partially extracted": { "continue on partially extracted": {
pathBase: path.Join("terraform", "iam"), pathBase: path.Join(constants.TerraformEmbeddedDir, "iam"),
provider: cloudprovider.GCP, provider: cloudprovider.GCP,
vars: gcpVars, vars: gcpVars,
fs: afero.NewMemMapFs(), fs: afero.NewMemMapFs(),
@ -163,14 +163,14 @@ func TestPrepareIAM(t *testing.T) {
wantErr: false, wantErr: false,
}, },
"azure": { "azure": {
pathBase: path.Join("terraform", "iam"), pathBase: path.Join(constants.TerraformEmbeddedDir, "iam"),
provider: cloudprovider.Azure, provider: cloudprovider.Azure,
vars: azureVars, vars: azureVars,
fs: afero.NewMemMapFs(), fs: afero.NewMemMapFs(),
wantErr: false, wantErr: false,
}, },
"aws": { "aws": {
pathBase: path.Join("terraform", "iam"), pathBase: path.Join(constants.TerraformEmbeddedDir, "iam"),
provider: cloudprovider.AWS, provider: cloudprovider.AWS,
vars: awsVars, vars: awsVars,
fs: afero.NewMemMapFs(), fs: afero.NewMemMapFs(),
@ -315,14 +315,14 @@ func TestCreateCluster(t *testing.T) {
wantErr bool wantErr bool
}{ }{
"works": { "works": {
pathBase: "terraform", pathBase: constants.TerraformEmbeddedDir,
provider: cloudprovider.QEMU, provider: cloudprovider.QEMU,
vars: qemuVars, vars: qemuVars,
tf: &stubTerraform{showState: newQEMUState()}, tf: &stubTerraform{showState: newQEMUState()},
fs: afero.NewMemMapFs(), fs: afero.NewMemMapFs(),
}, },
"init fails": { "init fails": {
pathBase: "terraform", pathBase: constants.TerraformEmbeddedDir,
provider: cloudprovider.QEMU, provider: cloudprovider.QEMU,
vars: qemuVars, vars: qemuVars,
tf: &stubTerraform{initErr: assert.AnError}, tf: &stubTerraform{initErr: assert.AnError},
@ -330,7 +330,7 @@ func TestCreateCluster(t *testing.T) {
wantErr: true, wantErr: true,
}, },
"apply fails": { "apply fails": {
pathBase: "terraform", pathBase: constants.TerraformEmbeddedDir,
provider: cloudprovider.QEMU, provider: cloudprovider.QEMU,
vars: qemuVars, vars: qemuVars,
tf: &stubTerraform{applyErr: assert.AnError}, tf: &stubTerraform{applyErr: assert.AnError},
@ -338,7 +338,7 @@ func TestCreateCluster(t *testing.T) {
wantErr: true, wantErr: true,
}, },
"show fails": { "show fails": {
pathBase: "terraform", pathBase: constants.TerraformEmbeddedDir,
provider: cloudprovider.QEMU, provider: cloudprovider.QEMU,
vars: qemuVars, vars: qemuVars,
tf: &stubTerraform{showErr: assert.AnError}, tf: &stubTerraform{showErr: assert.AnError},
@ -346,7 +346,7 @@ func TestCreateCluster(t *testing.T) {
wantErr: true, wantErr: true,
}, },
"set log fails": { "set log fails": {
pathBase: "terraform", pathBase: constants.TerraformEmbeddedDir,
provider: cloudprovider.QEMU, provider: cloudprovider.QEMU,
vars: qemuVars, vars: qemuVars,
tf: &stubTerraform{setLogErr: assert.AnError}, tf: &stubTerraform{setLogErr: assert.AnError},
@ -354,7 +354,7 @@ func TestCreateCluster(t *testing.T) {
wantErr: true, wantErr: true,
}, },
"set log path fails": { "set log path fails": {
pathBase: "terraform", pathBase: constants.TerraformEmbeddedDir,
provider: cloudprovider.QEMU, provider: cloudprovider.QEMU,
vars: qemuVars, vars: qemuVars,
tf: &stubTerraform{setLogPathErr: assert.AnError}, tf: &stubTerraform{setLogPathErr: assert.AnError},
@ -362,7 +362,7 @@ func TestCreateCluster(t *testing.T) {
wantErr: true, wantErr: true,
}, },
"no ip": { "no ip": {
pathBase: "terraform", pathBase: constants.TerraformEmbeddedDir,
provider: cloudprovider.QEMU, provider: cloudprovider.QEMU,
vars: qemuVars, vars: qemuVars,
tf: &stubTerraform{ tf: &stubTerraform{
@ -376,7 +376,7 @@ func TestCreateCluster(t *testing.T) {
wantErr: true, wantErr: true,
}, },
"ip has wrong type": { "ip has wrong type": {
pathBase: "terraform", pathBase: constants.TerraformEmbeddedDir,
provider: cloudprovider.QEMU, provider: cloudprovider.QEMU,
vars: qemuVars, vars: qemuVars,
tf: &stubTerraform{ tf: &stubTerraform{
@ -390,7 +390,7 @@ func TestCreateCluster(t *testing.T) {
wantErr: true, wantErr: true,
}, },
"no uid": { "no uid": {
pathBase: "terraform", pathBase: constants.TerraformEmbeddedDir,
provider: cloudprovider.QEMU, provider: cloudprovider.QEMU,
vars: qemuVars, vars: qemuVars,
tf: &stubTerraform{ tf: &stubTerraform{
@ -404,7 +404,7 @@ func TestCreateCluster(t *testing.T) {
wantErr: true, wantErr: true,
}, },
"uid has wrong type": { "uid has wrong type": {
pathBase: "terraform", pathBase: constants.TerraformEmbeddedDir,
provider: cloudprovider.QEMU, provider: cloudprovider.QEMU,
vars: qemuVars, vars: qemuVars,
tf: &stubTerraform{ tf: &stubTerraform{
@ -418,7 +418,7 @@ func TestCreateCluster(t *testing.T) {
wantErr: true, wantErr: true,
}, },
"name has wrong type": { "name has wrong type": {
pathBase: "terraform", pathBase: constants.TerraformEmbeddedDir,
provider: cloudprovider.QEMU, provider: cloudprovider.QEMU,
vars: qemuVars, vars: qemuVars,
tf: &stubTerraform{ tf: &stubTerraform{
@ -432,7 +432,7 @@ func TestCreateCluster(t *testing.T) {
wantErr: true, wantErr: true,
}, },
"working attestation url": { "working attestation url": {
pathBase: "terraform", pathBase: constants.TerraformEmbeddedDir,
provider: cloudprovider.Azure, provider: cloudprovider.Azure,
vars: qemuVars, // works for mocking azure vars vars: qemuVars, // works for mocking azure vars
tf: &stubTerraform{showState: newAzureState()}, tf: &stubTerraform{showState: newAzureState()},
@ -440,7 +440,7 @@ func TestCreateCluster(t *testing.T) {
expectedAttestationURL: "https://12345.neu.attest.azure.net", expectedAttestationURL: "https://12345.neu.attest.azure.net",
}, },
"no attestation url": { "no attestation url": {
pathBase: "terraform", pathBase: constants.TerraformEmbeddedDir,
provider: cloudprovider.Azure, provider: cloudprovider.Azure,
vars: qemuVars, // works for mocking azure vars vars: qemuVars, // works for mocking azure vars
tf: &stubTerraform{ tf: &stubTerraform{
@ -454,7 +454,7 @@ func TestCreateCluster(t *testing.T) {
wantErr: true, wantErr: true,
}, },
"attestation url has wrong type": { "attestation url has wrong type": {
pathBase: "terraform", pathBase: constants.TerraformEmbeddedDir,
provider: cloudprovider.Azure, provider: cloudprovider.Azure,
vars: qemuVars, // works for mocking azure vars vars: qemuVars, // works for mocking azure vars
tf: &stubTerraform{ tf: &stubTerraform{
@ -560,7 +560,7 @@ func TestCreateIAM(t *testing.T) {
want IAMOutput want IAMOutput
}{ }{
"set log fails": { "set log fails": {
pathBase: path.Join("terraform", "iam"), pathBase: path.Join(constants.TerraformEmbeddedDir, "iam"),
provider: cloudprovider.GCP, provider: cloudprovider.GCP,
vars: gcpVars, vars: gcpVars,
tf: &stubTerraform{setLogErr: assert.AnError}, tf: &stubTerraform{setLogErr: assert.AnError},
@ -568,7 +568,7 @@ func TestCreateIAM(t *testing.T) {
wantErr: true, wantErr: true,
}, },
"set log path fails": { "set log path fails": {
pathBase: path.Join("terraform", "iam"), pathBase: path.Join(constants.TerraformEmbeddedDir, "iam"),
provider: cloudprovider.GCP, provider: cloudprovider.GCP,
vars: gcpVars, vars: gcpVars,
tf: &stubTerraform{setLogPathErr: assert.AnError}, tf: &stubTerraform{setLogPathErr: assert.AnError},
@ -576,7 +576,7 @@ func TestCreateIAM(t *testing.T) {
wantErr: true, wantErr: true,
}, },
"gcp works": { "gcp works": {
pathBase: path.Join("terraform", "iam"), pathBase: path.Join(constants.TerraformEmbeddedDir, "iam"),
provider: cloudprovider.GCP, provider: cloudprovider.GCP,
vars: gcpVars, vars: gcpVars,
tf: &stubTerraform{showState: newTestState()}, tf: &stubTerraform{showState: newTestState()},
@ -584,7 +584,7 @@ func TestCreateIAM(t *testing.T) {
want: IAMOutput{GCP: GCPIAMOutput{SaKey: "12345678_abcdefg"}}, want: IAMOutput{GCP: GCPIAMOutput{SaKey: "12345678_abcdefg"}},
}, },
"gcp init fails": { "gcp init fails": {
pathBase: path.Join("terraform", "iam"), pathBase: path.Join(constants.TerraformEmbeddedDir, "iam"),
provider: cloudprovider.GCP, provider: cloudprovider.GCP,
vars: gcpVars, vars: gcpVars,
tf: &stubTerraform{initErr: assert.AnError}, tf: &stubTerraform{initErr: assert.AnError},
@ -592,7 +592,7 @@ func TestCreateIAM(t *testing.T) {
wantErr: true, wantErr: true,
}, },
"gcp apply fails": { "gcp apply fails": {
pathBase: path.Join("terraform", "iam"), pathBase: path.Join(constants.TerraformEmbeddedDir, "iam"),
provider: cloudprovider.GCP, provider: cloudprovider.GCP,
vars: gcpVars, vars: gcpVars,
tf: &stubTerraform{applyErr: assert.AnError}, tf: &stubTerraform{applyErr: assert.AnError},
@ -600,7 +600,7 @@ func TestCreateIAM(t *testing.T) {
wantErr: true, wantErr: true,
}, },
"gcp show fails": { "gcp show fails": {
pathBase: path.Join("terraform", "iam"), pathBase: path.Join(constants.TerraformEmbeddedDir, "iam"),
provider: cloudprovider.GCP, provider: cloudprovider.GCP,
vars: gcpVars, vars: gcpVars,
tf: &stubTerraform{showErr: assert.AnError}, tf: &stubTerraform{showErr: assert.AnError},
@ -608,7 +608,7 @@ func TestCreateIAM(t *testing.T) {
wantErr: true, wantErr: true,
}, },
"gcp no sa_key": { "gcp no sa_key": {
pathBase: path.Join("terraform", "iam"), pathBase: path.Join(constants.TerraformEmbeddedDir, "iam"),
provider: cloudprovider.GCP, provider: cloudprovider.GCP,
vars: gcpVars, vars: gcpVars,
tf: &stubTerraform{ tf: &stubTerraform{
@ -622,7 +622,7 @@ func TestCreateIAM(t *testing.T) {
wantErr: true, wantErr: true,
}, },
"gcp sa_key has wrong type": { "gcp sa_key has wrong type": {
pathBase: path.Join("terraform", "iam"), pathBase: path.Join(constants.TerraformEmbeddedDir, "iam"),
provider: cloudprovider.GCP, provider: cloudprovider.GCP,
vars: gcpVars, vars: gcpVars,
tf: &stubTerraform{ tf: &stubTerraform{
@ -636,7 +636,7 @@ func TestCreateIAM(t *testing.T) {
wantErr: true, wantErr: true,
}, },
"azure works": { "azure works": {
pathBase: path.Join("terraform", "iam"), pathBase: path.Join(constants.TerraformEmbeddedDir, "iam"),
provider: cloudprovider.Azure, provider: cloudprovider.Azure,
vars: azureVars, vars: azureVars,
tf: &stubTerraform{showState: newTestState()}, tf: &stubTerraform{showState: newTestState()},
@ -648,7 +648,7 @@ func TestCreateIAM(t *testing.T) {
}}, }},
}, },
"azure init fails": { "azure init fails": {
pathBase: path.Join("terraform", "iam"), pathBase: path.Join(constants.TerraformEmbeddedDir, "iam"),
provider: cloudprovider.Azure, provider: cloudprovider.Azure,
vars: azureVars, vars: azureVars,
tf: &stubTerraform{initErr: assert.AnError}, tf: &stubTerraform{initErr: assert.AnError},
@ -656,7 +656,7 @@ func TestCreateIAM(t *testing.T) {
wantErr: true, wantErr: true,
}, },
"azure apply fails": { "azure apply fails": {
pathBase: path.Join("terraform", "iam"), pathBase: path.Join(constants.TerraformEmbeddedDir, "iam"),
provider: cloudprovider.Azure, provider: cloudprovider.Azure,
vars: azureVars, vars: azureVars,
tf: &stubTerraform{applyErr: assert.AnError}, tf: &stubTerraform{applyErr: assert.AnError},
@ -664,7 +664,7 @@ func TestCreateIAM(t *testing.T) {
wantErr: true, wantErr: true,
}, },
"azure show fails": { "azure show fails": {
pathBase: path.Join("terraform", "iam"), pathBase: path.Join(constants.TerraformEmbeddedDir, "iam"),
provider: cloudprovider.Azure, provider: cloudprovider.Azure,
vars: azureVars, vars: azureVars,
tf: &stubTerraform{showErr: assert.AnError}, tf: &stubTerraform{showErr: assert.AnError},
@ -672,7 +672,7 @@ func TestCreateIAM(t *testing.T) {
wantErr: true, wantErr: true,
}, },
"azure no subscription_id": { "azure no subscription_id": {
pathBase: path.Join("terraform", "iam"), pathBase: path.Join(constants.TerraformEmbeddedDir, "iam"),
provider: cloudprovider.Azure, provider: cloudprovider.Azure,
vars: azureVars, vars: azureVars,
tf: &stubTerraform{ tf: &stubTerraform{
@ -686,7 +686,7 @@ func TestCreateIAM(t *testing.T) {
wantErr: true, wantErr: true,
}, },
"azure subscription_id has wrong type": { "azure subscription_id has wrong type": {
pathBase: path.Join("terraform", "iam"), pathBase: path.Join(constants.TerraformEmbeddedDir, "iam"),
provider: cloudprovider.Azure, provider: cloudprovider.Azure,
vars: azureVars, vars: azureVars,
tf: &stubTerraform{ tf: &stubTerraform{
@ -700,7 +700,7 @@ func TestCreateIAM(t *testing.T) {
wantErr: true, wantErr: true,
}, },
"aws works": { "aws works": {
pathBase: path.Join("terraform", "iam"), pathBase: path.Join(constants.TerraformEmbeddedDir, "iam"),
provider: cloudprovider.AWS, provider: cloudprovider.AWS,
vars: awsVars, vars: awsVars,
tf: &stubTerraform{showState: newTestState()}, tf: &stubTerraform{showState: newTestState()},
@ -711,7 +711,7 @@ func TestCreateIAM(t *testing.T) {
}}, }},
}, },
"aws init fails": { "aws init fails": {
pathBase: path.Join("terraform", "iam"), pathBase: path.Join(constants.TerraformEmbeddedDir, "iam"),
provider: cloudprovider.AWS, provider: cloudprovider.AWS,
vars: awsVars, vars: awsVars,
tf: &stubTerraform{initErr: assert.AnError}, tf: &stubTerraform{initErr: assert.AnError},
@ -719,7 +719,7 @@ func TestCreateIAM(t *testing.T) {
wantErr: true, wantErr: true,
}, },
"aws apply fails": { "aws apply fails": {
pathBase: path.Join("terraform", "iam"), pathBase: path.Join(constants.TerraformEmbeddedDir, "iam"),
provider: cloudprovider.AWS, provider: cloudprovider.AWS,
vars: awsVars, vars: awsVars,
tf: &stubTerraform{applyErr: assert.AnError}, tf: &stubTerraform{applyErr: assert.AnError},
@ -727,7 +727,7 @@ func TestCreateIAM(t *testing.T) {
wantErr: true, wantErr: true,
}, },
"aws show fails": { "aws show fails": {
pathBase: path.Join("terraform", "iam"), pathBase: path.Join(constants.TerraformEmbeddedDir, "iam"),
provider: cloudprovider.AWS, provider: cloudprovider.AWS,
vars: awsVars, vars: awsVars,
tf: &stubTerraform{showErr: assert.AnError}, tf: &stubTerraform{showErr: assert.AnError},
@ -735,7 +735,7 @@ func TestCreateIAM(t *testing.T) {
wantErr: true, wantErr: true,
}, },
"aws no control_plane_instance_profile": { "aws no control_plane_instance_profile": {
pathBase: path.Join("terraform", "iam"), pathBase: path.Join(constants.TerraformEmbeddedDir, "iam"),
provider: cloudprovider.AWS, provider: cloudprovider.AWS,
vars: awsVars, vars: awsVars,
tf: &stubTerraform{ tf: &stubTerraform{
@ -749,7 +749,7 @@ func TestCreateIAM(t *testing.T) {
wantErr: true, wantErr: true,
}, },
"azure control_plane_instance_profile has wrong type": { "azure control_plane_instance_profile has wrong type": {
pathBase: path.Join("terraform", "iam"), pathBase: path.Join(constants.TerraformEmbeddedDir, "iam"),
provider: cloudprovider.AWS, provider: cloudprovider.AWS,
vars: awsVars, vars: awsVars,
tf: &stubTerraform{ tf: &stubTerraform{
@ -1003,12 +1003,12 @@ func TestPlan(t *testing.T) {
wantErr bool wantErr bool
}{ }{
"plan succeeds": { "plan succeeds": {
pathBase: "terraform", pathBase: constants.TerraformEmbeddedDir,
tf: &stubTerraform{}, tf: &stubTerraform{},
fs: afero.NewMemMapFs(), fs: afero.NewMemMapFs(),
}, },
"set log path fails": { "set log path fails": {
pathBase: "terraform", pathBase: constants.TerraformEmbeddedDir,
tf: &stubTerraform{ tf: &stubTerraform{
setLogPathErr: someError, setLogPathErr: someError,
}, },
@ -1016,7 +1016,7 @@ func TestPlan(t *testing.T) {
wantErr: true, wantErr: true,
}, },
"set log fails": { "set log fails": {
pathBase: "terraform", pathBase: constants.TerraformEmbeddedDir,
tf: &stubTerraform{ tf: &stubTerraform{
setLogErr: someError, setLogErr: someError,
}, },
@ -1024,7 +1024,7 @@ func TestPlan(t *testing.T) {
wantErr: true, wantErr: true,
}, },
"plan fails": { "plan fails": {
pathBase: "terraform", pathBase: constants.TerraformEmbeddedDir,
tf: &stubTerraform{ tf: &stubTerraform{
planJSONErr: someError, planJSONErr: someError,
}, },
@ -1032,7 +1032,7 @@ func TestPlan(t *testing.T) {
wantErr: true, wantErr: true,
}, },
"init fails": { "init fails": {
pathBase: "terraform", pathBase: constants.TerraformEmbeddedDir,
tf: &stubTerraform{ tf: &stubTerraform{
initErr: someError, initErr: someError,
}, },
@ -1070,12 +1070,12 @@ func TestShowPlan(t *testing.T) {
wantErr bool wantErr bool
}{ }{
"show plan succeeds": { "show plan succeeds": {
pathBase: "terraform", pathBase: constants.TerraformEmbeddedDir,
tf: &stubTerraform{}, tf: &stubTerraform{},
fs: afero.NewMemMapFs(), fs: afero.NewMemMapFs(),
}, },
"set log path fails": { "set log path fails": {
pathBase: "terraform", pathBase: constants.TerraformEmbeddedDir,
tf: &stubTerraform{ tf: &stubTerraform{
setLogPathErr: someError, setLogPathErr: someError,
}, },
@ -1083,7 +1083,7 @@ func TestShowPlan(t *testing.T) {
wantErr: true, wantErr: true,
}, },
"set log fails": { "set log fails": {
pathBase: "terraform", pathBase: constants.TerraformEmbeddedDir,
tf: &stubTerraform{ tf: &stubTerraform{
setLogErr: someError, setLogErr: someError,
}, },
@ -1091,7 +1091,7 @@ func TestShowPlan(t *testing.T) {
wantErr: true, wantErr: true,
}, },
"show plan file fails": { "show plan file fails": {
pathBase: "terraform", pathBase: constants.TerraformEmbeddedDir,
tf: &stubTerraform{ tf: &stubTerraform{
showPlanFileErr: someError, showPlanFileErr: someError,
}, },

View file

@ -171,6 +171,8 @@ const (
TerraformUpgradeBackupDir = "terraform-backup" TerraformUpgradeBackupDir = "terraform-backup"
// TerraformIAMUpgradeBackupDir is the directory name being used to backup the pre-upgrade state of iam in an upgrade. // TerraformIAMUpgradeBackupDir is the directory name being used to backup the pre-upgrade state of iam in an upgrade.
TerraformIAMUpgradeBackupDir = "terraform-iam-backup" 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 is the name of the directory being used for cluster upgrades.
UpgradeDir = "constellation-upgrade" UpgradeDir = "constellation-upgrade"
// ControlPlaneDefault is the name of the default control plane worker group. // ControlPlaneDefault is the name of the default control plane worker group.