diff --git a/cli/internal/cloudcmd/create.go b/cli/internal/cloudcmd/create.go index eaa71dff4..060e570cf 100644 --- a/cli/internal/cloudcmd/create.go +++ b/cli/internal/cloudcmd/create.go @@ -21,6 +21,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" ) // Creator creates cloud resources. @@ -35,7 +36,7 @@ func NewCreator(out io.Writer) *Creator { return &Creator{ out: out, newTerraformClient: func(ctx context.Context) (terraformClient, error) { - return terraform.New(ctx) + return terraform.New(ctx, constants.TerraformWorkingDir) }, newLibvirtRunner: func() libvirtRunner { return libvirt.New() diff --git a/cli/internal/cloudcmd/terminate.go b/cli/internal/cloudcmd/terminate.go index 752bf8f57..d84a70f49 100644 --- a/cli/internal/cloudcmd/terminate.go +++ b/cli/internal/cloudcmd/terminate.go @@ -11,6 +11,7 @@ import ( "github.com/edgelesssys/constellation/v2/cli/internal/libvirt" "github.com/edgelesssys/constellation/v2/cli/internal/terraform" + "github.com/edgelesssys/constellation/v2/internal/constants" ) // Terminator deletes cloud provider resources. @@ -23,7 +24,7 @@ type Terminator struct { func NewTerminator() *Terminator { return &Terminator{ newTerraformClient: func(ctx context.Context) (terraformClient, error) { - return terraform.New(ctx) + return terraform.New(ctx, constants.TerraformWorkingDir) }, newLibvirtRunner: func() libvirtRunner { return libvirt.New() diff --git a/cli/internal/cmd/terminate.go b/cli/internal/cmd/terminate.go index 9535608c9..c96f77f52 100644 --- a/cli/internal/cmd/terminate.go +++ b/cli/internal/cmd/terminate.go @@ -53,7 +53,6 @@ func terminate(cmd *cobra.Command, terminator cloudTerminator, fileHandler file. if !yesFlag { cmd.Println("You are about to terminate a Constellation cluster.") 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.") ok, err := askToConfirm(cmd, "Do you want to continue?") if err != nil { diff --git a/cli/internal/terraform/loader.go b/cli/internal/terraform/loader.go index 6bfca0711..cb18ad098 100644 --- a/cli/internal/terraform/loader.go +++ b/cli/internal/terraform/loader.go @@ -11,6 +11,7 @@ import ( "errors" "io/fs" "path" + "path/filepath" "strings" "github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider" @@ -24,7 +25,7 @@ var terraformFS embed.FS // prepareWorkspace loads the embedded Terraform files, // 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 rootDir := path.Join("terraform", strings.ToLower(provider.String())) 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 { return err } - fileName := strings.TrimPrefix(path, rootDir+"/") + fileName := strings.Replace(filepath.Join(workingDir, path), rootDir+"/", "", 1) return fileHandler.Write(fileName, content, file.OptMkdirAll) }) } -// cleanUpWorkspace removes files that were loaded into the workspace. -func cleanUpWorkspace(fileHandler file.Handler) error { - // 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 +func cleanUpWorkspace(fileHandler file.Handler, workingDir string) error { + return ignoreFileNotFoundErr(fileHandler.RemoveAll(workingDir)) } // ignoreFileNotFoundErr ignores the error if it is a file not found error. diff --git a/cli/internal/terraform/loader_test.go b/cli/internal/terraform/loader_test.go index 2c3c01593..03b96f47f 100644 --- a/cli/internal/terraform/loader_test.go +++ b/cli/internal/terraform/loader_test.go @@ -8,9 +8,11 @@ package terraform import ( "io/fs" + "path/filepath" "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" @@ -58,12 +60,12 @@ func TestLoader(t *testing.T) { file := file.NewHandler(afero.NewMemMapFs()) - err := prepareWorkspace(file, tc.provider) + err := prepareWorkspace(file, tc.provider, constants.TerraformWorkingDir) require.NoError(err) checkFiles(t, file, func(err error) { assert.NoError(err) }, tc.fileList) - err = cleanUpWorkspace(file) + err = cleanUpWorkspace(file, constants.TerraformWorkingDir) require.NoError(err) 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) { t.Helper() for _, f := range files { - _, err := file.Stat(f) + path := filepath.Join(constants.TerraformWorkingDir, f) + _, err := file.Stat(path) assertion(err) } } diff --git a/cli/internal/terraform/terraform.go b/cli/internal/terraform/terraform.go index 93f801649..57355fc39 100644 --- a/cli/internal/terraform/terraform.go +++ b/cli/internal/terraform/terraform.go @@ -9,6 +9,7 @@ package terraform import ( "context" "errors" + "path/filepath" "github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider" "github.com/edgelesssys/constellation/v2/internal/file" @@ -32,23 +33,27 @@ const ( type Client struct { tf tfInterface - file file.Handler - remove func() + file file.Handler + workingDir string + remove func() } // New sets up a new Client for Terraform. -func New(ctx context.Context) (*Client, error) { - tf, remove, err := GetExecutable(ctx, ".") +func New(ctx context.Context, workingDir string) (*Client, error) { + file := file.NewHandler(afero.NewOsFs()) + if err := file.MkdirAll(workingDir); err != nil { + return nil, err + } + tf, remove, err := GetExecutable(ctx, workingDir) if err != nil { return nil, err } - file := file.NewHandler(afero.NewOsFs()) - return &Client{ - tf: tf, - remove: remove, - file: file, + tf: tf, + remove: remove, + file: file, + workingDir: workingDir, }, nil } @@ -56,7 +61,7 @@ func New(ctx context.Context) (*Client, error) { func (c *Client) CreateCluster( ctx context.Context, provider cloudprovider.Provider, name string, vars Variables, ) (string, error) { - if err := prepareWorkspace(c.file, provider); err != nil { + if err := prepareWorkspace(c.file, provider, c.workingDir); err != nil { return "", err } @@ -64,7 +69,7 @@ func (c *Client) CreateCluster( 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 } @@ -101,23 +106,7 @@ func (c *Client) RemoveInstaller() { // CleanUpWorkspace removes terraform files from the current directory. func (c *Client) CleanUpWorkspace() error { - if err := cleanUpWorkspace(c.file); 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 { + if err := cleanUpWorkspace(c.file, c.workingDir); err != nil { return err } diff --git a/cli/internal/terraform/terraform_test.go b/cli/internal/terraform/terraform_test.go index e0a4b659e..96002074a 100644 --- a/cli/internal/terraform/terraform_test.go +++ b/cli/internal/terraform/terraform_test.go @@ -10,9 +10,11 @@ import ( "context" "errors" "io/fs" + "path/filepath" "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/hashicorp/terraform-exec/tfexec" tfjson "github.com/hashicorp/terraform-json" @@ -122,8 +124,9 @@ func TestCreateCluster(t *testing.T) { assert := assert.New(t) c := &Client{ - tf: tc.tf, - file: file.NewHandler(tc.fs), + tf: tc.tf, + file: file.NewHandler(tc.fs), + workingDir: constants.TerraformWorkingDir, } ip, err := c.CreateCluster(context.Background(), tc.provider, "test", tc.vars) @@ -205,8 +208,9 @@ func TestCleanupWorkspace(t *testing.T) { require.NoError(tc.prepareFS(file)) c := &Client{ - file: file, - tf: &stubTerraform{}, + file: file, + tf: &stubTerraform{}, + workingDir: constants.TerraformWorkingDir, } err := c.CleanUpWorkspace() @@ -215,11 +219,11 @@ func TestCleanupWorkspace(t *testing.T) { return } assert.NoError(err) - _, err = file.Stat("terraform.tfvars") + _, err = file.Stat(filepath.Join(c.workingDir, "terraform.tfvars")) assert.ErrorIs(err, fs.ErrNotExist) - _, err = file.Stat("terraform.tfstate") + _, err = file.Stat(filepath.Join(c.workingDir, "terraform.tfstate")) 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) }) } diff --git a/docs/docs/getting-started/first-steps.md b/docs/docs/getting-started/first-steps.md index 2865994c4..3f94f9d6f 100644 --- a/docs/docs/getting-started/first-steps.md +++ b/docs/docs/getting-started/first-steps.md @@ -325,7 +325,6 @@ This should give the following output: $ constellation terminate You are about to terminate a Constellation cluster. 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. Do you want to continue? [y/n]: ``` diff --git a/internal/constants/constants.go b/internal/constants/constants.go index b34dc9f2f..4ea0cbf72 100644 --- a/internal/constants/constants.go +++ b/internal/constants/constants.go @@ -77,6 +77,8 @@ const ( AdminConfFilename = "constellation-admin.conf" // MasterSecretFilename filename of Constellation mastersecret. 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 = "/etc/kubernetes/admin.conf" // KubectlPath path to kubectl binary. diff --git a/internal/file/file.go b/internal/file/file.go index ab08e5079..115042cd7 100644 --- a/internal/file/file.go +++ b/internal/file/file.go @@ -170,3 +170,8 @@ func (h *Handler) RemoveAll(name string) error { func (h *Handler) Stat(name string) (fs.FileInfo, error) { 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) +}