cli: fix iam rollback (#1148)

* AB#2897 rename DestroyCluster

* #AB2897 error if terraform dir exists

* AB#2897 reword DestroyResources
This commit is contained in:
Moritz Sanft 2023-02-13 08:42:54 +01:00 committed by GitHub
parent 94245416ca
commit 7410cf8038
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 92 additions and 32 deletions

View File

@ -19,7 +19,7 @@ type terraformClient interface {
PrepareWorkspace(path string, input terraform.Variables) error PrepareWorkspace(path string, input terraform.Variables) error
CreateCluster(ctx context.Context) (terraform.CreateOutput, error) CreateCluster(ctx context.Context) (terraform.CreateOutput, error)
CreateIAMConfig(ctx context.Context, provider cloudprovider.Provider) (terraform.IAMOutput, error) CreateIAMConfig(ctx context.Context, provider cloudprovider.Provider) (terraform.IAMOutput, error)
DestroyCluster(ctx context.Context) error Destroy(ctx context.Context) error
CleanUpWorkspace() error CleanUpWorkspace() error
RemoveInstaller() RemoveInstaller()
} }

View File

@ -32,9 +32,9 @@ type stubTerraformClient struct {
uid string uid string
cleanUpWorkspaceCalled bool cleanUpWorkspaceCalled bool
removeInstallerCalled bool removeInstallerCalled bool
destroyClusterCalled bool destroyCalled bool
createClusterErr error createClusterErr error
destroyClusterErr error destroyErr error
prepareWorkspaceErr error prepareWorkspaceErr error
cleanUpWorkspaceErr error cleanUpWorkspaceErr error
iamOutputErr error iamOutputErr error
@ -56,9 +56,9 @@ func (c *stubTerraformClient) PrepareWorkspace(path string, input terraform.Vari
return c.prepareWorkspaceErr return c.prepareWorkspaceErr
} }
func (c *stubTerraformClient) DestroyCluster(ctx context.Context) error { func (c *stubTerraformClient) Destroy(ctx context.Context) error {
c.destroyClusterCalled = true c.destroyCalled = true
return c.destroyClusterErr return c.destroyErr
} }
func (c *stubTerraformClient) CleanUpWorkspace() error { func (c *stubTerraformClient) CleanUpWorkspace() error {

View File

@ -121,7 +121,7 @@ func TestCreator(t *testing.T) {
if tc.wantRollback { if tc.wantRollback {
cl := tc.tfClient.(*stubTerraformClient) cl := tc.tfClient.(*stubTerraformClient)
if tc.wantTerraformRollback { if tc.wantTerraformRollback {
assert.True(cl.destroyClusterCalled) assert.True(cl.destroyCalled)
} }
assert.True(cl.cleanUpWorkspaceCalled) assert.True(cl.cleanUpWorkspaceCalled)
if tc.provider == cloudprovider.QEMU { if tc.provider == cloudprovider.QEMU {

View File

@ -40,7 +40,7 @@ type rollbackerTerraform struct {
func (r *rollbackerTerraform) rollback(ctx context.Context) error { func (r *rollbackerTerraform) rollback(ctx context.Context) error {
var err error var err error
err = multierr.Append(err, r.client.DestroyCluster(ctx)) err = multierr.Append(err, r.client.Destroy(ctx))
if err == nil { if err == nil {
err = multierr.Append(err, r.client.CleanUpWorkspace()) err = multierr.Append(err, r.client.CleanUpWorkspace())
} }
@ -56,7 +56,7 @@ type rollbackerQEMU struct {
func (r *rollbackerQEMU) rollback(ctx context.Context) error { func (r *rollbackerQEMU) rollback(ctx context.Context) error {
var err error var err error
if r.createdWorkspace { 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)) err = multierr.Append(err, r.libvirt.Stop(ctx))
if err == nil { if err == nil {

View File

@ -25,7 +25,7 @@ func TestRollbackTerraform(t *testing.T) {
tfClient: &stubTerraformClient{}, tfClient: &stubTerraformClient{},
}, },
"destroy cluster error": { "destroy cluster error": {
tfClient: &stubTerraformClient{destroyClusterErr: someErr}, tfClient: &stubTerraformClient{destroyErr: someErr},
wantErr: true, wantErr: true,
}, },
"clean up workspace error": { "clean up workspace error": {
@ -51,7 +51,7 @@ func TestRollbackTerraform(t *testing.T) {
return return
} }
assert.NoError(err) assert.NoError(err)
assert.True(tc.tfClient.destroyClusterCalled) assert.True(tc.tfClient.destroyCalled)
assert.True(tc.tfClient.cleanUpWorkspaceCalled) assert.True(tc.tfClient.cleanUpWorkspaceCalled)
}) })
} }
@ -78,7 +78,7 @@ func TestRollbackQEMU(t *testing.T) {
}, },
"destroy cluster error": { "destroy cluster error": {
libvirt: &stubLibvirtRunner{stopErr: someErr}, libvirt: &stubLibvirtRunner{stopErr: someErr},
tfClient: &stubTerraformClient{destroyClusterErr: someErr}, tfClient: &stubTerraformClient{destroyErr: someErr},
wantErr: true, wantErr: true,
}, },
"clean up workspace error": { "clean up workspace error": {
@ -109,9 +109,9 @@ func TestRollbackQEMU(t *testing.T) {
assert.NoError(err) assert.NoError(err)
assert.True(tc.libvirt.stopCalled) assert.True(tc.libvirt.stopCalled)
if tc.createdWorkspace { if tc.createdWorkspace {
assert.True(tc.tfClient.destroyClusterCalled) assert.True(tc.tfClient.destroyCalled)
} else { } else {
assert.False(tc.tfClient.destroyClusterCalled) assert.False(tc.tfClient.destroyCalled)
} }
assert.True(tc.tfClient.cleanUpWorkspaceCalled) assert.True(tc.tfClient.cleanUpWorkspaceCalled)
}) })

View File

@ -50,7 +50,7 @@ func (t *Terminator) Terminate(ctx context.Context) (retErr error) {
} }
func (t *Terminator) terminateTerraform(ctx context.Context, cl terraformClient) 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 err
} }
return cl.CleanUpWorkspace() return cl.CleanUpWorkspace()

View File

@ -33,7 +33,7 @@ func TestTerminator(t *testing.T) {
wantErr: true, wantErr: true,
}, },
"destroy cluster error": { "destroy cluster error": {
tfClient: &stubTerraformClient{destroyClusterErr: someErr}, tfClient: &stubTerraformClient{destroyErr: someErr},
libvirt: &stubLibvirtRunner{}, libvirt: &stubLibvirtRunner{},
wantErr: true, wantErr: true,
}, },
@ -70,7 +70,7 @@ func TestTerminator(t *testing.T) {
} }
assert.NoError(err) assert.NoError(err)
cl := tc.tfClient.(*stubTerraformClient) cl := tc.tfClient.(*stubTerraformClient)
assert.True(cl.destroyClusterCalled) assert.True(cl.destroyCalled)
assert.True(cl.removeInstallerCalled) assert.True(cl.removeInstallerCalled)
assert.True(tc.libvirt.stopCalled) assert.True(tc.libvirt.stopCalled)
}) })

View File

@ -197,6 +197,10 @@ func (c *iamCreator) create(ctx context.Context) error {
} }
c.log.Debugf("Using flags: %+v", flags) c.log.Debugf("Using flags: %+v", flags)
if err := c.checkWorkingDir(); err != nil {
return err
}
if !flags.yesFlag { if !flags.yesFlag {
c.cmd.Printf("The following IAM configuration will be created:\n\n") c.cmd.Printf("The following IAM configuration will be created:\n\n")
c.providerCreator.printConfirmValues(c.cmd, flags) c.providerCreator.printConfirmValues(c.cmd, flags)
@ -275,6 +279,17 @@ func (c *iamCreator) parseFlagsAndSetupConfig() (iamFlags, error) {
return flags, nil 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. // iamFlags contains the parsed flags of the iam create command, including the parsed flags of the selected cloud provider.
type iamFlags struct { type iamFlags struct {
aws awsFlags aws awsFlags

View File

@ -67,15 +67,18 @@ func TestParseIDFile(t *testing.T) {
} }
func TestIAMCreateAWS(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() fs := afero.NewMemMapFs()
fileHandler := file.NewHandler(fs) fileHandler := file.NewHandler(fs)
for _, f := range existingFiles { for _, f := range existingFiles {
require.NoError(fileHandler.Write(f, []byte{1, 2, 3}, file.OptNone)) require.NoError(fileHandler.Write(f, []byte{1, 2, 3}, file.OptNone))
} }
for _, d := range existingDirs {
require.NoError(fs.MkdirAll(d, 0o755))
}
return fs 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()) fs := afero.NewReadOnlyFs(afero.NewMemMapFs())
return fs return fs
} }
@ -88,7 +91,7 @@ func TestIAMCreateAWS(t *testing.T) {
} }
testCases := map[string]struct { 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 creator *stubIAMCreator
provider cloudprovider.Provider provider cloudprovider.Provider
zoneFlag string zoneFlag string
@ -97,6 +100,7 @@ func TestIAMCreateAWS(t *testing.T) {
generateConfigFlag bool generateConfigFlag bool
configFlag string configFlag string
existingFiles []string existingFiles []string
existingDirs []string
stdin string stdin string
wantAbort bool wantAbort bool
wantErr bool wantErr bool
@ -153,6 +157,16 @@ func TestIAMCreateAWS(t *testing.T) {
configFlag: "custom-config.yaml", configFlag: "custom-config.yaml",
existingFiles: []string{"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": { "interactive": {
setupFs: defaultFs, setupFs: defaultFs,
creator: &stubIAMCreator{id: validIAMIDFile}, creator: &stubIAMCreator{id: validIAMIDFile},
@ -244,7 +258,7 @@ func TestIAMCreateAWS(t *testing.T) {
require.NoError(cmd.Flags().Set("config", tc.configFlag)) 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{ iamCreator := &iamCreator{
cmd: cmd, cmd: cmd,
@ -285,15 +299,18 @@ func TestIAMCreateAWS(t *testing.T) {
} }
func TestIAMCreateAzure(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() fs := afero.NewMemMapFs()
fileHandler := file.NewHandler(fs) fileHandler := file.NewHandler(fs)
for _, f := range existingFiles { for _, f := range existingFiles {
require.NoError(fileHandler.Write(f, []byte{1, 2, 3}, file.OptNone)) require.NoError(fileHandler.Write(f, []byte{1, 2, 3}, file.OptNone))
} }
for _, d := range existingDirs {
require.NoError(fs.MkdirAll(d, 0o755))
}
return fs 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()) fs := afero.NewReadOnlyFs(afero.NewMemMapFs())
return fs return fs
} }
@ -309,7 +326,7 @@ func TestIAMCreateAzure(t *testing.T) {
} }
testCases := map[string]struct { 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 creator *stubIAMCreator
provider cloudprovider.Provider provider cloudprovider.Provider
regionFlag string regionFlag string
@ -319,6 +336,7 @@ func TestIAMCreateAzure(t *testing.T) {
generateConfigFlag bool generateConfigFlag bool
configFlag string configFlag string
existingFiles []string existingFiles []string
existingDirs []string
stdin string stdin string
wantAbort bool wantAbort bool
wantErr bool wantErr bool
@ -380,6 +398,17 @@ func TestIAMCreateAzure(t *testing.T) {
yesFlag: true, yesFlag: true,
wantErr: 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": { "interactive": {
setupFs: defaultFs, setupFs: defaultFs,
creator: &stubIAMCreator{id: validIAMIDFile}, creator: &stubIAMCreator{id: validIAMIDFile},
@ -469,7 +498,7 @@ func TestIAMCreateAzure(t *testing.T) {
require.NoError(cmd.Flags().Set("config", tc.configFlag)) 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{ iamCreator := &iamCreator{
cmd: cmd, cmd: cmd,
@ -513,15 +542,18 @@ func TestIAMCreateAzure(t *testing.T) {
} }
func TestIAMCreateGCP(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() fs := afero.NewMemMapFs()
fileHandler := file.NewHandler(fs) fileHandler := file.NewHandler(fs)
for _, f := range existingFiles { for _, f := range existingFiles {
require.NoError(fileHandler.Write(f, []byte{1, 2, 3}, file.OptNone)) require.NoError(fileHandler.Write(f, []byte{1, 2, 3}, file.OptNone))
} }
for _, d := range existingDirs {
require.NoError(fs.MkdirAll(d, 0o755))
}
return fs 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()) fs := afero.NewReadOnlyFs(afero.NewMemMapFs())
return fs return fs
} }
@ -539,7 +571,7 @@ func TestIAMCreateGCP(t *testing.T) {
} }
testCases := map[string]struct { 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 creator *stubIAMCreator
provider cloudprovider.Provider provider cloudprovider.Provider
zoneFlag string zoneFlag string
@ -549,6 +581,7 @@ func TestIAMCreateGCP(t *testing.T) {
generateConfigFlag bool generateConfigFlag bool
configFlag string configFlag string
existingFiles []string existingFiles []string
existingDirs []string
stdin string stdin string
wantAbort bool wantAbort bool
wantErr bool wantErr bool
@ -610,6 +643,18 @@ func TestIAMCreateGCP(t *testing.T) {
yesFlag: true, yesFlag: true,
wantErr: 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": { "iam create gcp invalid flags": {
setupFs: defaultFs, setupFs: defaultFs,
creator: &stubIAMCreator{id: validIAMIDFile}, creator: &stubIAMCreator{id: validIAMIDFile},
@ -718,7 +763,7 @@ func TestIAMCreateGCP(t *testing.T) {
require.NoError(cmd.Flags().Set("config", tc.configFlag)) 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{ iamCreator := &iamCreator{
cmd: cmd, cmd: cmd,

View File

@ -272,8 +272,8 @@ func (c *Client) CreateIAMConfig(ctx context.Context, provider cloudprovider.Pro
} }
} }
// DestroyCluster destroys a Constellation cluster using Terraform. // Destroy destroys Terraform-created cloud resources.
func (c *Client) DestroyCluster(ctx context.Context) error { func (c *Client) Destroy(ctx context.Context) error {
if err := c.tf.Init(ctx); err != nil { if err := c.tf.Init(ctx); err != nil {
return err return err
} }

View File

@ -660,7 +660,7 @@ func TestDestroyInstances(t *testing.T) {
tf: tc.tf, tf: tc.tf,
} }
err := c.DestroyCluster(context.Background()) err := c.Destroy(context.Background())
if tc.wantErr { if tc.wantErr {
assert.Error(err) assert.Error(err)
return return