Create separate Terraform workspace directory

This commit is contained in:
Nils Hanke 2022-11-14 18:18:58 +01:00 committed by Nils Hanke
parent 7f5a1dd901
commit 4a2cba988c
10 changed files with 50 additions and 60 deletions

View File

@ -21,6 +21,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"
) )
// Creator creates cloud resources. // Creator creates cloud resources.
@ -35,7 +36,7 @@ func NewCreator(out io.Writer) *Creator {
return &Creator{ return &Creator{
out: out, out: out,
newTerraformClient: func(ctx context.Context) (terraformClient, error) { newTerraformClient: func(ctx context.Context) (terraformClient, error) {
return terraform.New(ctx) return terraform.New(ctx, constants.TerraformWorkingDir)
}, },
newLibvirtRunner: func() libvirtRunner { newLibvirtRunner: func() libvirtRunner {
return libvirt.New() return libvirt.New()

View File

@ -11,6 +11,7 @@ import (
"github.com/edgelesssys/constellation/v2/cli/internal/libvirt" "github.com/edgelesssys/constellation/v2/cli/internal/libvirt"
"github.com/edgelesssys/constellation/v2/cli/internal/terraform" "github.com/edgelesssys/constellation/v2/cli/internal/terraform"
"github.com/edgelesssys/constellation/v2/internal/constants"
) )
// Terminator deletes cloud provider resources. // Terminator deletes cloud provider resources.
@ -23,7 +24,7 @@ type Terminator struct {
func NewTerminator() *Terminator { func NewTerminator() *Terminator {
return &Terminator{ return &Terminator{
newTerraformClient: func(ctx context.Context) (terraformClient, error) { newTerraformClient: func(ctx context.Context) (terraformClient, error) {
return terraform.New(ctx) return terraform.New(ctx, constants.TerraformWorkingDir)
}, },
newLibvirtRunner: func() libvirtRunner { newLibvirtRunner: func() libvirtRunner {
return libvirt.New() return libvirt.New()

View File

@ -53,7 +53,6 @@ func terminate(cmd *cobra.Command, terminator cloudTerminator, fileHandler file.
if !yesFlag { if !yesFlag {
cmd.Println("You are about to terminate a Constellation cluster.") cmd.Println("You are about to terminate a Constellation cluster.")
cmd.Println("All of its associated resources will be DESTROYED.") cmd.Println("All of its associated resources will be DESTROYED.")
cmd.Println("This includes any other Terraform workspace in the current directory.")
cmd.Println("This action is irreversible and ALL DATA WILL BE LOST.") cmd.Println("This action is irreversible and ALL DATA WILL BE LOST.")
ok, err := askToConfirm(cmd, "Do you want to continue?") ok, err := askToConfirm(cmd, "Do you want to continue?")
if err != nil { if err != nil {

View File

@ -11,6 +11,7 @@ import (
"errors" "errors"
"io/fs" "io/fs"
"path" "path"
"path/filepath"
"strings" "strings"
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider" "github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
@ -24,7 +25,7 @@ var terraformFS embed.FS
// 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(fileHandler file.Handler, provider cloudprovider.Provider) error { func prepareWorkspace(fileHandler file.Handler, provider cloudprovider.Provider, workingDir string) error {
// use path.Join to ensure no forward slashes are used to read the embedded FS // use path.Join to ensure no forward slashes are used to read the embedded FS
rootDir := path.Join("terraform", strings.ToLower(provider.String())) rootDir := path.Join("terraform", strings.ToLower(provider.String()))
return fs.WalkDir(terraformFS, rootDir, func(path string, d fs.DirEntry, err error) error { return fs.WalkDir(terraformFS, rootDir, func(path string, d fs.DirEntry, err error) error {
@ -39,27 +40,13 @@ func prepareWorkspace(fileHandler file.Handler, provider cloudprovider.Provider)
if err != nil { if err != nil {
return err return err
} }
fileName := strings.TrimPrefix(path, rootDir+"/") fileName := strings.Replace(filepath.Join(workingDir, path), rootDir+"/", "", 1)
return fileHandler.Write(fileName, content, file.OptMkdirAll) return fileHandler.Write(fileName, content, file.OptMkdirAll)
}) })
} }
// cleanUpWorkspace removes files that were loaded into the workspace. func cleanUpWorkspace(fileHandler file.Handler, workingDir string) error {
func cleanUpWorkspace(fileHandler file.Handler) error { return ignoreFileNotFoundErr(fileHandler.RemoveAll(workingDir))
// try to remove any terraform files in the workspace
for _, csp := range []string{"aws", "azure", "gcp", "qemu"} {
rootDir := path.Join("terraform", csp)
if err := fs.WalkDir(terraformFS, rootDir, func(path string, d fs.DirEntry, err error) error {
if err != nil {
return err
}
fileName := strings.TrimPrefix(path, rootDir+"/")
return ignoreFileNotFoundErr(fileHandler.RemoveAll(fileName))
}); err != nil {
return err
}
}
return nil
} }
// ignoreFileNotFoundErr ignores the error if it is a file not found error. // ignoreFileNotFoundErr ignores the error if it is a file not found error.

View File

@ -8,9 +8,11 @@ package terraform
import ( import (
"io/fs" "io/fs"
"path/filepath"
"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"
@ -58,12 +60,12 @@ func TestLoader(t *testing.T) {
file := file.NewHandler(afero.NewMemMapFs()) file := file.NewHandler(afero.NewMemMapFs())
err := prepareWorkspace(file, tc.provider) err := prepareWorkspace(file, tc.provider, constants.TerraformWorkingDir)
require.NoError(err) require.NoError(err)
checkFiles(t, file, func(err error) { assert.NoError(err) }, tc.fileList) checkFiles(t, file, func(err error) { assert.NoError(err) }, tc.fileList)
err = cleanUpWorkspace(file) err = cleanUpWorkspace(file, constants.TerraformWorkingDir)
require.NoError(err) require.NoError(err)
checkFiles(t, file, func(err error) { assert.ErrorIs(err, fs.ErrNotExist) }, tc.fileList) checkFiles(t, file, func(err error) { assert.ErrorIs(err, fs.ErrNotExist) }, tc.fileList)
@ -74,7 +76,8 @@ func TestLoader(t *testing.T) {
func checkFiles(t *testing.T, file file.Handler, assertion func(error), files []string) { func checkFiles(t *testing.T, file file.Handler, assertion func(error), files []string) {
t.Helper() t.Helper()
for _, f := range files { for _, f := range files {
_, err := file.Stat(f) path := filepath.Join(constants.TerraformWorkingDir, f)
_, err := file.Stat(path)
assertion(err) assertion(err)
} }
} }

View File

@ -9,6 +9,7 @@ package terraform
import ( import (
"context" "context"
"errors" "errors"
"path/filepath"
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider" "github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
"github.com/edgelesssys/constellation/v2/internal/file" "github.com/edgelesssys/constellation/v2/internal/file"
@ -33,22 +34,26 @@ type Client struct {
tf tfInterface tf tfInterface
file file.Handler file file.Handler
workingDir string
remove func() remove func()
} }
// New sets up a new Client for Terraform. // New sets up a new Client for Terraform.
func New(ctx context.Context) (*Client, error) { func New(ctx context.Context, workingDir string) (*Client, error) {
tf, remove, err := GetExecutable(ctx, ".") file := file.NewHandler(afero.NewOsFs())
if err := file.MkdirAll(workingDir); err != nil {
return nil, err
}
tf, remove, err := GetExecutable(ctx, workingDir)
if err != nil { if err != nil {
return nil, err return nil, err
} }
file := file.NewHandler(afero.NewOsFs())
return &Client{ return &Client{
tf: tf, tf: tf,
remove: remove, remove: remove,
file: file, file: file,
workingDir: workingDir,
}, nil }, nil
} }
@ -56,7 +61,7 @@ func New(ctx context.Context) (*Client, error) {
func (c *Client) CreateCluster( func (c *Client) CreateCluster(
ctx context.Context, provider cloudprovider.Provider, name string, vars Variables, ctx context.Context, provider cloudprovider.Provider, name string, vars Variables,
) (string, error) { ) (string, error) {
if err := prepareWorkspace(c.file, provider); err != nil { if err := prepareWorkspace(c.file, provider, c.workingDir); err != nil {
return "", err return "", err
} }
@ -64,7 +69,7 @@ func (c *Client) CreateCluster(
return "", err return "", err
} }
if err := c.file.Write(terraformVarsFile, []byte(vars.String())); err != nil { if err := c.file.Write(filepath.Join(c.workingDir, terraformVarsFile), []byte(vars.String())); err != nil {
return "", err return "", err
} }
@ -101,23 +106,7 @@ func (c *Client) RemoveInstaller() {
// CleanUpWorkspace removes terraform files from the current directory. // CleanUpWorkspace removes terraform files from the current directory.
func (c *Client) CleanUpWorkspace() error { func (c *Client) CleanUpWorkspace() error {
if err := cleanUpWorkspace(c.file); err != nil { if err := cleanUpWorkspace(c.file, c.workingDir); err != nil {
return err
}
if err := ignoreFileNotFoundErr(c.file.Remove("terraform.tfvars")); err != nil {
return err
}
if err := ignoreFileNotFoundErr(c.file.Remove("terraform.tfstate")); err != nil {
return err
}
if err := ignoreFileNotFoundErr(c.file.Remove("terraform.tfstate.backup")); err != nil {
return err
}
if err := ignoreFileNotFoundErr(c.file.Remove(".terraform.lock.hcl")); err != nil {
return err
}
if err := ignoreFileNotFoundErr(c.file.RemoveAll(".terraform")); err != nil {
return err return err
} }

View File

@ -10,9 +10,11 @@ import (
"context" "context"
"errors" "errors"
"io/fs" "io/fs"
"path/filepath"
"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/hashicorp/terraform-exec/tfexec" "github.com/hashicorp/terraform-exec/tfexec"
tfjson "github.com/hashicorp/terraform-json" tfjson "github.com/hashicorp/terraform-json"
@ -124,6 +126,7 @@ func TestCreateCluster(t *testing.T) {
c := &Client{ c := &Client{
tf: tc.tf, tf: tc.tf,
file: file.NewHandler(tc.fs), file: file.NewHandler(tc.fs),
workingDir: constants.TerraformWorkingDir,
} }
ip, err := c.CreateCluster(context.Background(), tc.provider, "test", tc.vars) ip, err := c.CreateCluster(context.Background(), tc.provider, "test", tc.vars)
@ -207,6 +210,7 @@ func TestCleanupWorkspace(t *testing.T) {
c := &Client{ c := &Client{
file: file, file: file,
tf: &stubTerraform{}, tf: &stubTerraform{},
workingDir: constants.TerraformWorkingDir,
} }
err := c.CleanUpWorkspace() err := c.CleanUpWorkspace()
@ -215,11 +219,11 @@ func TestCleanupWorkspace(t *testing.T) {
return return
} }
assert.NoError(err) assert.NoError(err)
_, err = file.Stat("terraform.tfvars") _, err = file.Stat(filepath.Join(c.workingDir, "terraform.tfvars"))
assert.ErrorIs(err, fs.ErrNotExist) assert.ErrorIs(err, fs.ErrNotExist)
_, err = file.Stat("terraform.tfstate") _, err = file.Stat(filepath.Join(c.workingDir, "terraform.tfstate"))
assert.ErrorIs(err, fs.ErrNotExist) assert.ErrorIs(err, fs.ErrNotExist)
_, err = file.Stat("terraform.tfstate.backup") _, err = file.Stat(filepath.Join(c.workingDir, "terraform.tfstate.backup"))
assert.ErrorIs(err, fs.ErrNotExist) assert.ErrorIs(err, fs.ErrNotExist)
}) })
} }

View File

@ -325,7 +325,6 @@ This should give the following output:
$ constellation terminate $ constellation terminate
You are about to terminate a Constellation cluster. You are about to terminate a Constellation cluster.
All of its associated resources will be DESTROYED. All of its associated resources will be DESTROYED.
This includes any other Terraform workspace in the current directory.
This action is irreversible and ALL DATA WILL BE LOST. This action is irreversible and ALL DATA WILL BE LOST.
Do you want to continue? [y/n]: Do you want to continue? [y/n]:
``` ```

View File

@ -77,6 +77,8 @@ const (
AdminConfFilename = "constellation-admin.conf" AdminConfFilename = "constellation-admin.conf"
// MasterSecretFilename filename of Constellation mastersecret. // MasterSecretFilename filename of Constellation mastersecret.
MasterSecretFilename = "constellation-mastersecret.json" MasterSecretFilename = "constellation-mastersecret.json"
// TerraformWorkingDir is the directory name for the TerraformClient workspace.
TerraformWorkingDir = "constellation-terraform"
// ControlPlaneAdminConfFilename filepath to control plane kubernetes admin config. // ControlPlaneAdminConfFilename filepath to control plane kubernetes admin config.
ControlPlaneAdminConfFilename = "/etc/kubernetes/admin.conf" ControlPlaneAdminConfFilename = "/etc/kubernetes/admin.conf"
// KubectlPath path to kubectl binary. // KubectlPath path to kubectl binary.

View File

@ -170,3 +170,8 @@ func (h *Handler) RemoveAll(name string) error {
func (h *Handler) Stat(name string) (fs.FileInfo, error) { func (h *Handler) Stat(name string) (fs.FileInfo, error) {
return h.fs.Stat(name) return h.fs.Stat(name)
} }
// MkdirAll creates a directory path and all parents that does not exist yet.
func (h *Handler) MkdirAll(name string) error {
return h.fs.MkdirAll(name, 0o700)
}