diff --git a/cli/internal/cloudcmd/clients.go b/cli/internal/cloudcmd/clients.go index a3a3ef4eb..ab0c39b03 100644 --- a/cli/internal/cloudcmd/clients.go +++ b/cli/internal/cloudcmd/clients.go @@ -19,7 +19,7 @@ type terraformClient interface { PrepareWorkspace(path string, input terraform.Variables) error CreateCluster(ctx context.Context) (terraform.CreateOutput, error) CreateIAMConfig(ctx context.Context, provider cloudprovider.Provider) (terraform.IAMOutput, error) - DestroyCluster(ctx context.Context) error + Destroy(ctx context.Context) error CleanUpWorkspace() error RemoveInstaller() } diff --git a/cli/internal/cloudcmd/clients_test.go b/cli/internal/cloudcmd/clients_test.go index f279aa6cf..708f6054c 100644 --- a/cli/internal/cloudcmd/clients_test.go +++ b/cli/internal/cloudcmd/clients_test.go @@ -32,9 +32,9 @@ type stubTerraformClient struct { uid string cleanUpWorkspaceCalled bool removeInstallerCalled bool - destroyClusterCalled bool + destroyCalled bool createClusterErr error - destroyClusterErr error + destroyErr error prepareWorkspaceErr error cleanUpWorkspaceErr error iamOutputErr error @@ -56,9 +56,9 @@ func (c *stubTerraformClient) PrepareWorkspace(path string, input terraform.Vari return c.prepareWorkspaceErr } -func (c *stubTerraformClient) DestroyCluster(ctx context.Context) error { - c.destroyClusterCalled = true - return c.destroyClusterErr +func (c *stubTerraformClient) Destroy(ctx context.Context) error { + c.destroyCalled = true + return c.destroyErr } func (c *stubTerraformClient) CleanUpWorkspace() error { diff --git a/cli/internal/cloudcmd/create_test.go b/cli/internal/cloudcmd/create_test.go index c9699f4db..0aa186fc5 100644 --- a/cli/internal/cloudcmd/create_test.go +++ b/cli/internal/cloudcmd/create_test.go @@ -121,7 +121,7 @@ func TestCreator(t *testing.T) { if tc.wantRollback { cl := tc.tfClient.(*stubTerraformClient) if tc.wantTerraformRollback { - assert.True(cl.destroyClusterCalled) + assert.True(cl.destroyCalled) } assert.True(cl.cleanUpWorkspaceCalled) if tc.provider == cloudprovider.QEMU { diff --git a/cli/internal/cloudcmd/rollback.go b/cli/internal/cloudcmd/rollback.go index da9e4b653..3be6baad7 100644 --- a/cli/internal/cloudcmd/rollback.go +++ b/cli/internal/cloudcmd/rollback.go @@ -40,7 +40,7 @@ type rollbackerTerraform struct { func (r *rollbackerTerraform) rollback(ctx context.Context) error { var err error - err = multierr.Append(err, r.client.DestroyCluster(ctx)) + err = multierr.Append(err, r.client.Destroy(ctx)) if err == nil { err = multierr.Append(err, r.client.CleanUpWorkspace()) } @@ -56,7 +56,7 @@ type rollbackerQEMU struct { func (r *rollbackerQEMU) rollback(ctx context.Context) error { var err error if r.createdWorkspace { - err = multierr.Append(err, r.client.DestroyCluster(ctx)) + err = multierr.Append(err, r.client.Destroy(ctx)) } err = multierr.Append(err, r.libvirt.Stop(ctx)) if err == nil { diff --git a/cli/internal/cloudcmd/rollback_test.go b/cli/internal/cloudcmd/rollback_test.go index 81998f971..42ce2c37e 100644 --- a/cli/internal/cloudcmd/rollback_test.go +++ b/cli/internal/cloudcmd/rollback_test.go @@ -25,7 +25,7 @@ func TestRollbackTerraform(t *testing.T) { tfClient: &stubTerraformClient{}, }, "destroy cluster error": { - tfClient: &stubTerraformClient{destroyClusterErr: someErr}, + tfClient: &stubTerraformClient{destroyErr: someErr}, wantErr: true, }, "clean up workspace error": { @@ -51,7 +51,7 @@ func TestRollbackTerraform(t *testing.T) { return } assert.NoError(err) - assert.True(tc.tfClient.destroyClusterCalled) + assert.True(tc.tfClient.destroyCalled) assert.True(tc.tfClient.cleanUpWorkspaceCalled) }) } @@ -78,7 +78,7 @@ func TestRollbackQEMU(t *testing.T) { }, "destroy cluster error": { libvirt: &stubLibvirtRunner{stopErr: someErr}, - tfClient: &stubTerraformClient{destroyClusterErr: someErr}, + tfClient: &stubTerraformClient{destroyErr: someErr}, wantErr: true, }, "clean up workspace error": { @@ -109,9 +109,9 @@ func TestRollbackQEMU(t *testing.T) { assert.NoError(err) assert.True(tc.libvirt.stopCalled) if tc.createdWorkspace { - assert.True(tc.tfClient.destroyClusterCalled) + assert.True(tc.tfClient.destroyCalled) } else { - assert.False(tc.tfClient.destroyClusterCalled) + assert.False(tc.tfClient.destroyCalled) } assert.True(tc.tfClient.cleanUpWorkspaceCalled) }) diff --git a/cli/internal/cloudcmd/terminate.go b/cli/internal/cloudcmd/terminate.go index d84a70f49..9918c6c99 100644 --- a/cli/internal/cloudcmd/terminate.go +++ b/cli/internal/cloudcmd/terminate.go @@ -50,7 +50,7 @@ func (t *Terminator) Terminate(ctx context.Context) (retErr error) { } func (t *Terminator) terminateTerraform(ctx context.Context, cl terraformClient) error { - if err := cl.DestroyCluster(ctx); err != nil { + if err := cl.Destroy(ctx); err != nil { return err } return cl.CleanUpWorkspace() diff --git a/cli/internal/cloudcmd/terminate_test.go b/cli/internal/cloudcmd/terminate_test.go index 711d4881e..f2ca53c53 100644 --- a/cli/internal/cloudcmd/terminate_test.go +++ b/cli/internal/cloudcmd/terminate_test.go @@ -33,7 +33,7 @@ func TestTerminator(t *testing.T) { wantErr: true, }, "destroy cluster error": { - tfClient: &stubTerraformClient{destroyClusterErr: someErr}, + tfClient: &stubTerraformClient{destroyErr: someErr}, libvirt: &stubLibvirtRunner{}, wantErr: true, }, @@ -70,7 +70,7 @@ func TestTerminator(t *testing.T) { } assert.NoError(err) cl := tc.tfClient.(*stubTerraformClient) - assert.True(cl.destroyClusterCalled) + assert.True(cl.destroyCalled) assert.True(cl.removeInstallerCalled) assert.True(tc.libvirt.stopCalled) }) diff --git a/cli/internal/cmd/iam.go b/cli/internal/cmd/iam.go index a277973ce..fae016560 100644 --- a/cli/internal/cmd/iam.go +++ b/cli/internal/cmd/iam.go @@ -184,6 +184,10 @@ func (c *iamCreator) create(ctx context.Context) error { return err } + if err := c.checkWorkingDir(); err != nil { + return err + } + if !flags.yesFlag { c.cmd.Printf("The following IAM configuration will be created:\n\n") c.providerCreator.printConfirmValues(c.cmd, flags) @@ -260,6 +264,17 @@ func (c *iamCreator) parseFlagsAndSetupConfig() (iamFlags, error) { return flags, nil } +// checkWorkingDir checks if the current working directory already contains a Terraform dir or a Constellation config file. +func (c *iamCreator) checkWorkingDir() error { + if _, err := c.fileHandler.Stat(constants.TerraformIAMWorkingDir); err == nil { + return fmt.Errorf("the current working directory already contains the %s directory. Please run the command in a different directory", constants.TerraformIAMWorkingDir) + } + if _, err := c.fileHandler.Stat(constants.ConfigFilename); err == nil { + return fmt.Errorf("the current working directory already contains the %s file. Please run the command in a different directory", constants.ConfigFilename) + } + return nil +} + // iamFlags contains the parsed flags of the iam create command, including the parsed flags of the selected cloud provider. type iamFlags struct { aws awsFlags diff --git a/cli/internal/cmd/iam_test.go b/cli/internal/cmd/iam_test.go index efd0f6b56..def1139f0 100644 --- a/cli/internal/cmd/iam_test.go +++ b/cli/internal/cmd/iam_test.go @@ -66,15 +66,18 @@ func TestParseIDFile(t *testing.T) { } func TestIAMCreateAWS(t *testing.T) { - defaultFs := func(require *require.Assertions, provider cloudprovider.Provider, existingFiles []string) afero.Fs { + defaultFs := func(require *require.Assertions, provider cloudprovider.Provider, existingFiles []string, existingDirs []string) afero.Fs { fs := afero.NewMemMapFs() fileHandler := file.NewHandler(fs) for _, f := range existingFiles { require.NoError(fileHandler.Write(f, []byte{1, 2, 3}, file.OptNone)) } + for _, d := range existingDirs { + require.NoError(fs.MkdirAll(d, 0o755)) + } return fs } - readOnlyFs := func(require *require.Assertions, provider cloudprovider.Provider, existingFiles []string) afero.Fs { + readOnlyFs := func(require *require.Assertions, provider cloudprovider.Provider, existingFiles []string, existingDirs []string) afero.Fs { fs := afero.NewReadOnlyFs(afero.NewMemMapFs()) return fs } @@ -87,7 +90,7 @@ func TestIAMCreateAWS(t *testing.T) { } testCases := map[string]struct { - setupFs func(require *require.Assertions, provider cloudprovider.Provider, existingFiles []string) afero.Fs + setupFs func(require *require.Assertions, provider cloudprovider.Provider, existingFiles []string, existingDirs []string) afero.Fs creator *stubIAMCreator provider cloudprovider.Provider zoneFlag string @@ -96,6 +99,7 @@ func TestIAMCreateAWS(t *testing.T) { generateConfigFlag bool configFlag string existingFiles []string + existingDirs []string stdin string wantAbort bool wantErr bool @@ -152,6 +156,16 @@ func TestIAMCreateAWS(t *testing.T) { configFlag: "custom-config.yaml", existingFiles: []string{"custom-config.yaml"}, }, + "iam create aws existing terraform dir": { + setupFs: defaultFs, + creator: &stubIAMCreator{id: validIAMIDFile}, + provider: cloudprovider.AWS, + zoneFlag: "us-east-2a", + prefixFlag: "test", + yesFlag: true, + wantErr: true, + existingDirs: []string{constants.TerraformIAMWorkingDir}, + }, "interactive": { setupFs: defaultFs, creator: &stubIAMCreator{id: validIAMIDFile}, @@ -242,7 +256,7 @@ func TestIAMCreateAWS(t *testing.T) { require.NoError(cmd.Flags().Set("config", tc.configFlag)) } - fileHandler := file.NewHandler(tc.setupFs(require, tc.provider, tc.existingFiles)) + fileHandler := file.NewHandler(tc.setupFs(require, tc.provider, tc.existingFiles, tc.existingDirs)) iamCreator := &iamCreator{ cmd: cmd, @@ -282,15 +296,18 @@ func TestIAMCreateAWS(t *testing.T) { } func TestIAMCreateAzure(t *testing.T) { - defaultFs := func(require *require.Assertions, provider cloudprovider.Provider, existingFiles []string) afero.Fs { + defaultFs := func(require *require.Assertions, provider cloudprovider.Provider, existingFiles []string, existingDirs []string) afero.Fs { fs := afero.NewMemMapFs() fileHandler := file.NewHandler(fs) for _, f := range existingFiles { require.NoError(fileHandler.Write(f, []byte{1, 2, 3}, file.OptNone)) } + for _, d := range existingDirs { + require.NoError(fs.MkdirAll(d, 0o755)) + } return fs } - readOnlyFs := func(require *require.Assertions, provider cloudprovider.Provider, existingFiles []string) afero.Fs { + readOnlyFs := func(require *require.Assertions, provider cloudprovider.Provider, existingFiles []string, existingDirs []string) afero.Fs { fs := afero.NewReadOnlyFs(afero.NewMemMapFs()) return fs } @@ -306,7 +323,7 @@ func TestIAMCreateAzure(t *testing.T) { } testCases := map[string]struct { - setupFs func(require *require.Assertions, provider cloudprovider.Provider, existingFiles []string) afero.Fs + setupFs func(require *require.Assertions, provider cloudprovider.Provider, existingFiles []string, existingDirs []string) afero.Fs creator *stubIAMCreator provider cloudprovider.Provider regionFlag string @@ -316,6 +333,7 @@ func TestIAMCreateAzure(t *testing.T) { generateConfigFlag bool configFlag string existingFiles []string + existingDirs []string stdin string wantAbort bool wantErr bool @@ -377,6 +395,17 @@ func TestIAMCreateAzure(t *testing.T) { yesFlag: true, wantErr: true, }, + "iam create azure existing terraform dir": { + setupFs: defaultFs, + creator: &stubIAMCreator{id: validIAMIDFile}, + provider: cloudprovider.Azure, + regionFlag: "westus", + servicePrincipalFlag: "constell-test-sp", + resourceGroupFlag: "constell-test-rg", + yesFlag: true, + wantErr: true, + existingDirs: []string{constants.TerraformIAMWorkingDir}, + }, "interactive": { setupFs: defaultFs, creator: &stubIAMCreator{id: validIAMIDFile}, @@ -465,7 +494,7 @@ func TestIAMCreateAzure(t *testing.T) { require.NoError(cmd.Flags().Set("config", tc.configFlag)) } - fileHandler := file.NewHandler(tc.setupFs(require, tc.provider, tc.existingFiles)) + fileHandler := file.NewHandler(tc.setupFs(require, tc.provider, tc.existingFiles, tc.existingDirs)) iamCreator := &iamCreator{ cmd: cmd, @@ -508,15 +537,18 @@ func TestIAMCreateAzure(t *testing.T) { } func TestIAMCreateGCP(t *testing.T) { - defaultFs := func(require *require.Assertions, provider cloudprovider.Provider, existingFiles []string) afero.Fs { + defaultFs := func(require *require.Assertions, provider cloudprovider.Provider, existingFiles []string, existingDirs []string) afero.Fs { fs := afero.NewMemMapFs() fileHandler := file.NewHandler(fs) for _, f := range existingFiles { require.NoError(fileHandler.Write(f, []byte{1, 2, 3}, file.OptNone)) } + for _, d := range existingDirs { + require.NoError(fs.MkdirAll(d, 0o755)) + } return fs } - readOnlyFs := func(require *require.Assertions, provider cloudprovider.Provider, existingFiles []string) afero.Fs { + readOnlyFs := func(require *require.Assertions, provider cloudprovider.Provider, existingFiles []string, existingDirs []string) afero.Fs { fs := afero.NewReadOnlyFs(afero.NewMemMapFs()) return fs } @@ -534,7 +566,7 @@ func TestIAMCreateGCP(t *testing.T) { } testCases := map[string]struct { - setupFs func(require *require.Assertions, provider cloudprovider.Provider, existingFiles []string) afero.Fs + setupFs func(require *require.Assertions, provider cloudprovider.Provider, existingFiles []string, existingDirs []string) afero.Fs creator *stubIAMCreator provider cloudprovider.Provider zoneFlag string @@ -544,6 +576,7 @@ func TestIAMCreateGCP(t *testing.T) { generateConfigFlag bool configFlag string existingFiles []string + existingDirs []string stdin string wantAbort bool wantErr bool @@ -605,6 +638,18 @@ func TestIAMCreateGCP(t *testing.T) { yesFlag: true, wantErr: true, }, + "iam create gcp existing terraform dir": { + setupFs: defaultFs, + creator: &stubIAMCreator{id: validIAMIDFile}, + provider: cloudprovider.GCP, + zoneFlag: "europe-west1-a", + serviceAccountIDFlag: "constell-test", + projectIDFlag: "constell-1234", + + existingDirs: []string{constants.TerraformIAMWorkingDir}, + yesFlag: true, + wantErr: true, + }, "iam create gcp invalid flags": { setupFs: defaultFs, creator: &stubIAMCreator{id: validIAMIDFile}, @@ -712,7 +757,7 @@ func TestIAMCreateGCP(t *testing.T) { require.NoError(cmd.Flags().Set("config", tc.configFlag)) } - fileHandler := file.NewHandler(tc.setupFs(require, tc.provider, tc.existingFiles)) + fileHandler := file.NewHandler(tc.setupFs(require, tc.provider, tc.existingFiles, tc.existingDirs)) iamCreator := &iamCreator{ cmd: cmd, diff --git a/cli/internal/terraform/terraform.go b/cli/internal/terraform/terraform.go index d523986e8..1b3eac30a 100644 --- a/cli/internal/terraform/terraform.go +++ b/cli/internal/terraform/terraform.go @@ -272,8 +272,8 @@ func (c *Client) CreateIAMConfig(ctx context.Context, provider cloudprovider.Pro } } -// DestroyCluster destroys a Constellation cluster using Terraform. -func (c *Client) DestroyCluster(ctx context.Context) error { +// Destroy destroys Terraform-created cloud resources. +func (c *Client) Destroy(ctx context.Context) error { if err := c.tf.Init(ctx); err != nil { return err } diff --git a/cli/internal/terraform/terraform_test.go b/cli/internal/terraform/terraform_test.go index 89cb265b7..d8f0df20c 100644 --- a/cli/internal/terraform/terraform_test.go +++ b/cli/internal/terraform/terraform_test.go @@ -660,7 +660,7 @@ func TestDestroyInstances(t *testing.T) { tf: tc.tf, } - err := c.DestroyCluster(context.Background()) + err := c.Destroy(context.Background()) if tc.wantErr { assert.Error(err) return