cli: iam destroy (#946)

This commit is contained in:
miampf 2023-02-24 11:36:41 +01:00 committed by GitHub
parent f1b331bbbd
commit 5137e9fa57
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 659 additions and 9 deletions

View File

@ -13,6 +13,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"
tfjson "github.com/hashicorp/terraform-json"
) )
type terraformClient interface { type terraformClient interface {
@ -22,6 +23,7 @@ type terraformClient interface {
Destroy(ctx context.Context) error Destroy(ctx context.Context) error
CleanUpWorkspace() error CleanUpWorkspace() error
RemoveInstaller() RemoveInstaller()
Show(ctx context.Context) (*tfjson.State, error)
} }
type libvirtRunner interface { type libvirtRunner interface {

View File

@ -14,6 +14,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"
tfjson "github.com/hashicorp/terraform-json"
"go.uber.org/goleak" "go.uber.org/goleak"
) )
@ -30,14 +31,17 @@ type stubTerraformClient struct {
initSecret string initSecret string
iamOutput terraform.IAMOutput iamOutput terraform.IAMOutput
uid string uid string
tfjsonState *tfjson.State
cleanUpWorkspaceCalled bool cleanUpWorkspaceCalled bool
removeInstallerCalled bool removeInstallerCalled bool
destroyCalled bool destroyCalled bool
showCalled bool
createClusterErr error createClusterErr error
destroyErr error destroyErr error
prepareWorkspaceErr error prepareWorkspaceErr error
cleanUpWorkspaceErr error cleanUpWorkspaceErr error
iamOutputErr error iamOutputErr error
showErr error
} }
func (c *stubTerraformClient) CreateCluster(ctx context.Context) (terraform.CreateOutput, error) { func (c *stubTerraformClient) CreateCluster(ctx context.Context) (terraform.CreateOutput, error) {
@ -70,6 +74,11 @@ func (c *stubTerraformClient) RemoveInstaller() {
c.removeInstallerCalled = true c.removeInstallerCalled = true
} }
func (c *stubTerraformClient) Show(ctx context.Context) (*tfjson.State, error) {
c.showCalled = true
return c.tfjsonState, c.showErr
}
type stubLibvirtRunner struct { type stubLibvirtRunner struct {
startCalled bool startCalled bool
stopCalled bool stopCalled bool

View File

@ -7,6 +7,9 @@ package cloudcmd
import ( import (
"context" "context"
"encoding/base64"
"encoding/json"
"errors"
"fmt" "fmt"
"io" "io"
"path" "path"
@ -15,9 +18,65 @@ import (
"github.com/edgelesssys/constellation/v2/cli/internal/iamid" "github.com/edgelesssys/constellation/v2/cli/internal/iamid"
"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/constants" "github.com/edgelesssys/constellation/v2/internal/constants"
) )
// IAMDestroyer destroys an IAM configuration.
type IAMDestroyer struct {
client terraformClient
}
// NewIAMDestroyer creates a new IAM Destroyer.
func NewIAMDestroyer(ctx context.Context) (*IAMDestroyer, error) {
cl, err := terraform.New(ctx, constants.TerraformIAMWorkingDir)
if err != nil {
return nil, err
}
return &IAMDestroyer{client: cl}, nil
}
// GetTfstateServiceAccountKey returns the sa_key output from the terraform state.
func (d *IAMDestroyer) GetTfstateServiceAccountKey(ctx context.Context) (gcpshared.ServiceAccountKey, error) {
tfState, err := d.client.Show(ctx)
if err != nil {
return gcpshared.ServiceAccountKey{}, err
}
if tfState.Values == nil {
return gcpshared.ServiceAccountKey{}, errors.New("no Values field in terraform state")
}
saKeyJSON := tfState.Values.Outputs["sa_key"]
if saKeyJSON == nil {
return gcpshared.ServiceAccountKey{}, errors.New("no sa_key in outputs of the terraform state")
}
saKeyString, ok := saKeyJSON.Value.(string)
if !ok {
return gcpshared.ServiceAccountKey{}, errors.New("sa_key field in terraform state is not a string")
}
saKey, err := base64.StdEncoding.DecodeString(saKeyString)
if err != nil {
return gcpshared.ServiceAccountKey{}, err
}
var tfSaKey gcpshared.ServiceAccountKey
if err := json.Unmarshal(saKey, &tfSaKey); err != nil {
return gcpshared.ServiceAccountKey{}, err
}
return tfSaKey, nil
}
// DestroyIAMConfiguration destroys the previously created IAM configuration and deletes the local IAM terraform files.
func (d *IAMDestroyer) DestroyIAMConfiguration(ctx context.Context) error {
if err := d.client.Destroy(ctx); err != nil {
return err
}
return d.client.CleanUpWorkspace()
}
// IAMCreator creates the IAM configuration on the cloud provider. // IAMCreator creates the IAM configuration on the cloud provider.
type IAMCreator struct { type IAMCreator struct {
out io.Writer out io.Writer

View File

@ -8,13 +8,18 @@ package cloudcmd
import ( import (
"bytes" "bytes"
"context" "context"
"encoding/base64"
"encoding/json"
"errors" "errors"
"testing" "testing"
"github.com/edgelesssys/constellation/v2/cli/internal/iamid" "github.com/edgelesssys/constellation/v2/cli/internal/iamid"
"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"
tfjson "github.com/hashicorp/terraform-json"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
) )
func TestIAMCreator(t *testing.T) { func TestIAMCreator(t *testing.T) {
@ -148,3 +153,186 @@ func TestIAMCreator(t *testing.T) {
}) })
} }
} }
func TestDestroyIAMConfiguration(t *testing.T) {
newError := func() error {
return errors.New("failed")
}
testCases := map[string]struct {
tfClient *stubTerraformClient
wantErr bool
wantDestroyCalled bool
wantCleanupWorkspaceCalled bool
}{
"destroy error": {
tfClient: &stubTerraformClient{destroyErr: newError()},
wantErr: true,
wantDestroyCalled: true,
},
"destroy": {
tfClient: &stubTerraformClient{},
wantDestroyCalled: true,
wantCleanupWorkspaceCalled: true,
},
"cleanup error": {
tfClient: &stubTerraformClient{cleanUpWorkspaceErr: newError()},
wantErr: true,
wantDestroyCalled: true,
wantCleanupWorkspaceCalled: true,
},
}
for name, tc := range testCases {
t.Run(name, func(t *testing.T) {
assert := assert.New(t)
destroyer := &IAMDestroyer{client: tc.tfClient}
err := destroyer.DestroyIAMConfiguration(context.Background())
if tc.wantErr {
assert.Error(err)
} else {
assert.NoError(err)
}
assert.Equal(tc.wantDestroyCalled, tc.tfClient.destroyCalled)
assert.Equal(tc.wantCleanupWorkspaceCalled, tc.tfClient.cleanUpWorkspaceCalled)
})
}
}
func TestGetTfstateServiceAccountKey(t *testing.T) {
someError := errors.New("failed")
gcpFile := `
{
"auth_provider_x509_cert_url": "",
"auth_uri": "",
"client_email": "",
"client_id": "",
"client_x509_cert_url": "",
"private_key": "",
"private_key_id": "",
"project_id": "",
"token_uri": "",
"type": ""
}
`
gcpFileB64 := base64.StdEncoding.EncodeToString([]byte(gcpFile))
testCases := map[string]struct {
cl *stubTerraformClient
wantValidSaKey bool
wantErr bool
wantShowCalled bool
}{
"valid": {
cl: &stubTerraformClient{
tfjsonState: &tfjson.State{
Values: &tfjson.StateValues{
Outputs: map[string]*tfjson.StateOutput{
"sa_key": {
Value: gcpFileB64,
},
},
},
},
},
wantValidSaKey: true,
wantShowCalled: true,
},
"show error": {
cl: &stubTerraformClient{
showErr: someError,
},
wantErr: true,
wantShowCalled: true,
},
"nil tfstate values": {
cl: &stubTerraformClient{
tfjsonState: &tfjson.State{
Values: nil,
},
},
wantErr: true,
wantShowCalled: true,
},
"no key": {
cl: &stubTerraformClient{
tfjsonState: &tfjson.State{
Values: &tfjson.StateValues{},
},
},
wantErr: true,
wantShowCalled: true,
},
"invalid base64": {
cl: &stubTerraformClient{
tfjsonState: &tfjson.State{
Values: &tfjson.StateValues{
Outputs: map[string]*tfjson.StateOutput{
"sa_key": {
Value: "iamnotvalid",
},
},
},
},
},
wantErr: true,
wantShowCalled: true,
},
"valid base64 invalid json": {
cl: &stubTerraformClient{
tfjsonState: &tfjson.State{
Values: &tfjson.StateValues{
Outputs: map[string]*tfjson.StateOutput{
"sa_key": {
Value: base64.StdEncoding.EncodeToString([]byte("asdf")),
},
},
},
},
},
wantErr: true,
wantShowCalled: true,
},
"not string": {
cl: &stubTerraformClient{
tfjsonState: &tfjson.State{
Values: &tfjson.StateValues{
Outputs: map[string]*tfjson.StateOutput{
"sa_key": {
Value: 1,
},
},
},
},
},
wantErr: true,
wantShowCalled: true,
},
}
for name, tc := range testCases {
t.Run(name, func(t *testing.T) {
assert := assert.New(t)
destroyer := IAMDestroyer{client: tc.cl}
saKey, err := destroyer.GetTfstateServiceAccountKey(context.Background())
if tc.wantErr {
assert.Error(err)
return
}
assert.NoError(err)
var saKeyComp gcpshared.ServiceAccountKey
require.NoError(t, json.Unmarshal([]byte(gcpFile), &saKeyComp))
assert.Equal(saKey, saKeyComp)
assert.Equal(tc.wantShowCalled, tc.cl.showCalled)
})
}
}

View File

@ -13,6 +13,7 @@ import (
"github.com/edgelesssys/constellation/v2/cli/internal/clusterid" "github.com/edgelesssys/constellation/v2/cli/internal/clusterid"
"github.com/edgelesssys/constellation/v2/cli/internal/iamid" "github.com/edgelesssys/constellation/v2/cli/internal/iamid"
"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/config" "github.com/edgelesssys/constellation/v2/internal/config"
) )
@ -34,6 +35,11 @@ type cloudIAMCreator interface {
) (iamid.File, error) ) (iamid.File, error)
} }
type iamDestroyer interface {
DestroyIAMConfiguration(ctx context.Context) error
GetTfstateServiceAccountKey(ctx context.Context) (gcpshared.ServiceAccountKey, error)
}
type cloudTerminator interface { type cloudTerminator interface {
Terminate(context.Context) error Terminate(context.Context) error
} }

View File

@ -14,6 +14,7 @@ import (
"github.com/edgelesssys/constellation/v2/cli/internal/clusterid" "github.com/edgelesssys/constellation/v2/cli/internal/clusterid"
"github.com/edgelesssys/constellation/v2/cli/internal/iamid" "github.com/edgelesssys/constellation/v2/cli/internal/iamid"
"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/config" "github.com/edgelesssys/constellation/v2/internal/config"
"go.uber.org/goleak" "go.uber.org/goleak"
) )
@ -72,3 +73,21 @@ func (c *stubIAMCreator) Create(
c.id.CloudProvider = provider c.id.CloudProvider = provider
return c.id, c.createErr return c.id, c.createErr
} }
type stubIAMDestroyer struct {
destroyCalled bool
getTfstateKeyCalled bool
gcpSaKey gcpshared.ServiceAccountKey
destroyErr error
getTfstateKeyErr error
}
func (d *stubIAMDestroyer) DestroyIAMConfiguration(ctx context.Context) error {
d.destroyCalled = true
return d.destroyErr
}
func (d *stubIAMDestroyer) GetTfstateServiceAccountKey(ctx context.Context) (gcpshared.ServiceAccountKey, error) {
d.getTfstateKeyCalled = true
return d.gcpSaKey, d.getTfstateKeyErr
}

View File

@ -42,6 +42,7 @@ func NewIAMCmd() *cobra.Command {
} }
cmd.AddCommand(newIAMCreateCmd()) cmd.AddCommand(newIAMCreateCmd())
cmd.AddCommand(newIAMDestroyCmd())
return cmd return cmd
} }
@ -563,3 +564,18 @@ func parseIDFile(serviceAccountKeyBase64 string) (map[string]string, error) {
} }
return out, nil return out, nil
} }
// NewIAMDestroyCmd returns a new cobra.Command for the iam destroy subcommand.
func newIAMDestroyCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "destroy",
Short: "Destroy an IAM configuration and delete local terraform files",
Long: "Destroy an IAM configuration and delete local terraform files.",
Args: cobra.ExactArgs(0),
RunE: runIAMDestroy,
}
cmd.Flags().BoolP("yes", "y", false, "destroy the IAM configuration without asking for confirmation")
return cmd
}

View File

@ -0,0 +1,142 @@
/*
Copyright (c) Edgeless Systems GmbH
SPDX-License-Identifier: AGPL-3.0-only
*/
package cmd
import (
"errors"
"fmt"
"os"
"github.com/edgelesssys/constellation/v2/cli/internal/cloudcmd"
"github.com/edgelesssys/constellation/v2/internal/cloud/gcpshared"
"github.com/edgelesssys/constellation/v2/internal/constants"
"github.com/edgelesssys/constellation/v2/internal/file"
"github.com/spf13/afero"
"github.com/spf13/cobra"
)
func runIAMDestroy(cmd *cobra.Command, _args []string) error {
log, err := newCLILogger(cmd)
if err != nil {
return fmt.Errorf("creating logger: %w", err)
}
defer log.Sync()
spinner := newSpinner(cmd.ErrOrStderr())
destroyer, err := cloudcmd.NewIAMDestroyer(cmd.Context())
if err != nil {
return err
}
fsHandler := file.NewHandler(afero.NewOsFs())
c := &destroyCmd{log: log}
return c.iamDestroy(cmd, spinner, destroyer, fsHandler)
}
type destroyCmd struct {
log debugLog
}
func (c *destroyCmd) iamDestroy(cmd *cobra.Command, spinner spinnerInterf, destroyer iamDestroyer, fsHandler file.Handler) error {
// check if there is a possibility that the cluster is still running by looking out for specific files
c.log.Debugf("Checking if %q exists", constants.AdminConfFilename)
_, err := fsHandler.Stat(constants.AdminConfFilename)
if !errors.Is(err, os.ErrNotExist) {
return fmt.Errorf("file %q still exists, please make sure to terminate your cluster before destroying your IAM configuration", constants.AdminConfFilename)
}
c.log.Debugf("Checking if %q exists", constants.ClusterIDsFileName)
_, err = fsHandler.Stat(constants.ClusterIDsFileName)
if !errors.Is(err, os.ErrNotExist) {
return fmt.Errorf("file %q still exists, please make sure to terminate your cluster before destroying your IAM configuration", constants.ClusterIDsFileName)
}
yes, err := cmd.Flags().GetBool("yes")
if err != nil {
return err
}
c.log.Debugf("\"yes\" flag is set to %t", yes)
gcpFileExists := false
c.log.Debugf("Checking if %q exists", constants.GCPServiceAccountKeyFile)
_, err = fsHandler.Stat(constants.GCPServiceAccountKeyFile)
if err != nil {
if !errors.Is(err, os.ErrNotExist) {
return err
}
} else {
c.log.Debugf("%q exists", constants.GCPServiceAccountKeyFile)
gcpFileExists = true
}
if !yes {
// Confirmation
confirmString := "Do you really want to destroy your IAM configuration?"
if gcpFileExists {
confirmString += fmt.Sprintf(" (This will also delete %q)", constants.GCPServiceAccountKeyFile)
}
ok, err := askToConfirm(cmd, confirmString)
if err != nil {
return err
}
if !ok {
cmd.Println("The destruction of the IAM configuration was aborted")
return nil
}
}
if gcpFileExists {
c.log.Debugf("Starting to delete %q", constants.GCPServiceAccountKeyFile)
proceed, err := c.deleteGCPServiceAccountKeyFile(cmd, destroyer, fsHandler)
if err != nil {
return err
}
if !proceed {
cmd.Println("Destruction was aborted")
return nil
}
}
c.log.Debugf("Starting to destroy IAM configuration")
spinner.Start("Destroying IAM configuration", false)
defer spinner.Stop()
if err := destroyer.DestroyIAMConfiguration(cmd.Context()); err != nil {
return fmt.Errorf("destroying IAM configuration: %w", err)
}
spinner.Stop() // stop the spinner to print a new line
fmt.Println("Successfully destroyed IAM configuration")
return nil
}
func (c *destroyCmd) deleteGCPServiceAccountKeyFile(cmd *cobra.Command, destroyer iamDestroyer, fsHandler file.Handler) (bool, error) {
var fileSaKey gcpshared.ServiceAccountKey
c.log.Debugf("Parsing %q", constants.GCPServiceAccountKeyFile)
if err := fsHandler.ReadJSON(constants.GCPServiceAccountKeyFile, &fileSaKey); err != nil {
return false, err
}
c.log.Debugf("Getting service account key from the tfstate")
tfSaKey, err := destroyer.GetTfstateServiceAccountKey(cmd.Context())
if err != nil {
return false, err
}
c.log.Debugf("Checking if keys are the same")
if tfSaKey != fileSaKey {
cmd.Printf("The key in %q don't match up with your terraform state. %q will not be deleted.\n", constants.GCPServiceAccountKeyFile, constants.GCPServiceAccountKeyFile)
return true, nil
}
if err := fsHandler.Remove(constants.GCPServiceAccountKeyFile); err != nil {
return false, err
}
c.log.Debugf("Successfully deleted %q", constants.GCPServiceAccountKeyFile)
return true, nil
}

View File

@ -0,0 +1,211 @@
/*
Copyright (c) Edgeless Systems GmbH
SPDX-License-Identifier: AGPL-3.0-only
*/
package cmd
import (
"bytes"
"errors"
"testing"
"github.com/edgelesssys/constellation/v2/internal/cloud/gcpshared"
"github.com/edgelesssys/constellation/v2/internal/constants"
"github.com/edgelesssys/constellation/v2/internal/file"
"github.com/edgelesssys/constellation/v2/internal/logger"
"github.com/spf13/afero"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestIAMDestroy(t *testing.T) {
require := require.New(t)
someError := errors.New("failed")
newFsExists := func() file.Handler {
fh := file.NewHandler(afero.NewMemMapFs())
require.NoError(fh.Write(constants.GCPServiceAccountKeyFile, []byte("{}")))
return fh
}
newFsMissing := func() file.Handler {
fh := file.NewHandler(afero.NewMemMapFs())
return fh
}
newFsWithAdminConf := func() file.Handler {
fh := file.NewHandler(afero.NewMemMapFs())
require.NoError(fh.Write(constants.AdminConfFilename, []byte("")))
return fh
}
newFsWithClusterIDFile := func() file.Handler {
fh := file.NewHandler(afero.NewMemMapFs())
require.NoError(fh.Write(constants.ClusterIDsFileName, []byte("")))
return fh
}
testCases := map[string]struct {
iamDestroyer *stubIAMDestroyer
fh file.Handler
stdin string
yesFlag string
wantErr bool
wantDestroyCalled bool
}{
"cluster running admin conf": {
fh: newFsWithAdminConf(),
iamDestroyer: &stubIAMDestroyer{},
yesFlag: "false",
wantErr: true,
},
"cluster running cluster ids": {
fh: newFsWithClusterIDFile(),
iamDestroyer: &stubIAMDestroyer{},
yesFlag: "false",
wantErr: true,
},
"file missing abort": {
fh: newFsMissing(),
stdin: "n\n",
yesFlag: "false",
iamDestroyer: &stubIAMDestroyer{},
},
"file missing": {
fh: newFsMissing(),
stdin: "y\n",
yesFlag: "false",
iamDestroyer: &stubIAMDestroyer{},
wantDestroyCalled: true,
},
"file exists abort": {
fh: newFsExists(),
stdin: "n\n",
yesFlag: "false",
iamDestroyer: &stubIAMDestroyer{},
},
"error destroying user": {
fh: newFsMissing(),
stdin: "y\n",
yesFlag: "false",
iamDestroyer: &stubIAMDestroyer{destroyErr: someError},
wantErr: true,
wantDestroyCalled: true,
},
"gcp delete error": {
fh: newFsExists(),
yesFlag: "true",
iamDestroyer: &stubIAMDestroyer{getTfstateKeyErr: someError},
wantErr: true,
},
}
for name, tc := range testCases {
t.Run(name, func(t *testing.T) {
assert := assert.New(t)
cmd := newIAMDestroyCmd()
cmd.SetOut(&bytes.Buffer{})
cmd.SetErr(&bytes.Buffer{})
cmd.SetIn(bytes.NewBufferString(tc.stdin))
assert.NoError(cmd.Flags().Set("yes", tc.yesFlag))
c := &destroyCmd{log: logger.NewTest(t)}
err := c.iamDestroy(cmd, &nopSpinner{}, tc.iamDestroyer, tc.fh)
if tc.wantErr {
assert.Error(err)
} else {
assert.NoError(err)
}
assert.Equal(tc.wantDestroyCalled, tc.iamDestroyer.destroyCalled)
})
}
}
func TestDeleteGCPServiceAccountKeyFile(t *testing.T) {
require := require.New(t)
someError := errors.New("failed")
gcpFile := `
{
"auth_provider_x509_cert_url": "",
"auth_uri": "",
"client_email": "",
"client_id": "",
"client_x509_cert_url": "",
"private_key": "",
"private_key_id": "",
"project_id": "",
"token_uri": "",
"type": ""
}
`
newFs := func() file.Handler {
fs := file.NewHandler(afero.NewMemMapFs())
require.NoError(fs.Write(constants.GCPServiceAccountKeyFile, []byte(gcpFile)))
return fs
}
newFsInvalidJSON := func() file.Handler {
fh := file.NewHandler(afero.NewMemMapFs())
require.NoError(fh.Write(constants.GCPServiceAccountKeyFile, []byte("asdf")))
return fh
}
testCases := map[string]struct {
destroyer *stubIAMDestroyer
fsHandler file.Handler
stdin string
wantErr bool
wantProceed bool
wantGetSaKeyCalled bool
}{
"invalid gcp json": {
destroyer: &stubIAMDestroyer{},
fsHandler: newFsInvalidJSON(),
wantErr: true,
},
"error getting key terraform": {
destroyer: &stubIAMDestroyer{getTfstateKeyErr: someError},
fsHandler: newFs(),
wantErr: true,
wantGetSaKeyCalled: true,
},
"keys not same": {
destroyer: &stubIAMDestroyer{gcpSaKey: gcpshared.ServiceAccountKey{
Type: "somethingelse",
}},
fsHandler: newFs(),
wantGetSaKeyCalled: true,
wantProceed: true,
},
"valid": {
destroyer: &stubIAMDestroyer{},
fsHandler: newFs(),
wantGetSaKeyCalled: true,
wantProceed: true,
},
}
for name, tc := range testCases {
t.Run(name, func(t *testing.T) {
assert := assert.New(t)
cmd := newIAMDestroyCmd()
cmd.SetOut(&bytes.Buffer{})
cmd.SetErr(&bytes.Buffer{})
cmd.SetIn(bytes.NewBufferString(tc.stdin))
c := &destroyCmd{log: logger.NewTest(t)}
proceed, err := c.deleteGCPServiceAccountKeyFile(cmd, tc.destroyer, tc.fsHandler)
if tc.wantErr {
assert.Error(err)
} else {
assert.NoError(err)
}
assert.Equal(tc.wantProceed, proceed)
assert.Equal(tc.wantGetSaKeyCalled, tc.destroyer.getTfstateKeyCalled)
})
}
}

View File

@ -68,6 +68,11 @@ func New(ctx context.Context, workingDir string) (*Client, error) {
}, nil }, nil
} }
// Show reads the default state path and outputs the state.
func (c *Client) Show(ctx context.Context) (*tfjson.State, error) {
return c.tf.Show(ctx)
}
// PrepareWorkspace prepares a Terraform workspace for a Constellation cluster. // PrepareWorkspace prepares a Terraform workspace for a Constellation cluster.
func (c *Client) PrepareWorkspace(path string, vars Variables) error { func (c *Client) PrepareWorkspace(path string, vars Variables) error {
if err := prepareWorkspace(path, c.file, c.workingDir); err != nil { if err := prepareWorkspace(path, c.file, c.workingDir); err != nil {

View File

@ -256,14 +256,7 @@ You can keep created IAM configurations and reuse them for new clusters. Alterna
* [Terraform](https://developer.hashicorp.com/terraform/downloads) is installed on your machine. * [Terraform](https://developer.hashicorp.com/terraform/downloads) is installed on your machine.
* Access to the `terraform.tfstate` file created by the `constellation iam create` command. * Access to the `terraform.tfstate` file created by the `constellation iam create` command.
You can delete the IAM configuration using the following commands: You can delete the IAM configuration by executing the following command in the same directory where you executed `constellation iam create`:
```bash ```bash
# Navigate to the directory containing the terraform.tfstate file constellation iam destroy
cd constellation-iam-terraform
# Destroy the IAM configuration via Terraform
terraform destroy
# Confirm deletion by typing "yes"
# Remove the Terraform state directory
cd ..
rm -rf constellation-iam-terraform
``` ```