mirror of
https://github.com/edgelesssys/constellation.git
synced 2024-12-24 06:59:40 -05:00
cli: iam destroy (#946)
This commit is contained in:
parent
f1b331bbbd
commit
5137e9fa57
@ -13,6 +13,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"
|
||||
tfjson "github.com/hashicorp/terraform-json"
|
||||
)
|
||||
|
||||
type terraformClient interface {
|
||||
@ -22,6 +23,7 @@ type terraformClient interface {
|
||||
Destroy(ctx context.Context) error
|
||||
CleanUpWorkspace() error
|
||||
RemoveInstaller()
|
||||
Show(ctx context.Context) (*tfjson.State, error)
|
||||
}
|
||||
|
||||
type libvirtRunner interface {
|
||||
|
@ -14,6 +14,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"
|
||||
tfjson "github.com/hashicorp/terraform-json"
|
||||
|
||||
"go.uber.org/goleak"
|
||||
)
|
||||
@ -30,14 +31,17 @@ type stubTerraformClient struct {
|
||||
initSecret string
|
||||
iamOutput terraform.IAMOutput
|
||||
uid string
|
||||
tfjsonState *tfjson.State
|
||||
cleanUpWorkspaceCalled bool
|
||||
removeInstallerCalled bool
|
||||
destroyCalled bool
|
||||
showCalled bool
|
||||
createClusterErr error
|
||||
destroyErr error
|
||||
prepareWorkspaceErr error
|
||||
cleanUpWorkspaceErr error
|
||||
iamOutputErr error
|
||||
showErr error
|
||||
}
|
||||
|
||||
func (c *stubTerraformClient) CreateCluster(ctx context.Context) (terraform.CreateOutput, error) {
|
||||
@ -70,6 +74,11 @@ func (c *stubTerraformClient) RemoveInstaller() {
|
||||
c.removeInstallerCalled = true
|
||||
}
|
||||
|
||||
func (c *stubTerraformClient) Show(ctx context.Context) (*tfjson.State, error) {
|
||||
c.showCalled = true
|
||||
return c.tfjsonState, c.showErr
|
||||
}
|
||||
|
||||
type stubLibvirtRunner struct {
|
||||
startCalled bool
|
||||
stopCalled bool
|
||||
|
@ -7,6 +7,9 @@ package cloudcmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"path"
|
||||
@ -15,9 +18,65 @@ import (
|
||||
"github.com/edgelesssys/constellation/v2/cli/internal/iamid"
|
||||
"github.com/edgelesssys/constellation/v2/cli/internal/terraform"
|
||||
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
|
||||
"github.com/edgelesssys/constellation/v2/internal/cloud/gcpshared"
|
||||
"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.
|
||||
type IAMCreator struct {
|
||||
out io.Writer
|
||||
|
@ -8,13 +8,18 @@ package cloudcmd
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
"github.com/edgelesssys/constellation/v2/cli/internal/iamid"
|
||||
"github.com/edgelesssys/constellation/v2/cli/internal/terraform"
|
||||
"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/require"
|
||||
)
|
||||
|
||||
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)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -13,6 +13,7 @@ import (
|
||||
"github.com/edgelesssys/constellation/v2/cli/internal/clusterid"
|
||||
"github.com/edgelesssys/constellation/v2/cli/internal/iamid"
|
||||
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
|
||||
"github.com/edgelesssys/constellation/v2/internal/cloud/gcpshared"
|
||||
"github.com/edgelesssys/constellation/v2/internal/config"
|
||||
)
|
||||
|
||||
@ -34,6 +35,11 @@ type cloudIAMCreator interface {
|
||||
) (iamid.File, error)
|
||||
}
|
||||
|
||||
type iamDestroyer interface {
|
||||
DestroyIAMConfiguration(ctx context.Context) error
|
||||
GetTfstateServiceAccountKey(ctx context.Context) (gcpshared.ServiceAccountKey, error)
|
||||
}
|
||||
|
||||
type cloudTerminator interface {
|
||||
Terminate(context.Context) error
|
||||
}
|
||||
|
@ -14,6 +14,7 @@ import (
|
||||
"github.com/edgelesssys/constellation/v2/cli/internal/clusterid"
|
||||
"github.com/edgelesssys/constellation/v2/cli/internal/iamid"
|
||||
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
|
||||
"github.com/edgelesssys/constellation/v2/internal/cloud/gcpshared"
|
||||
"github.com/edgelesssys/constellation/v2/internal/config"
|
||||
"go.uber.org/goleak"
|
||||
)
|
||||
@ -72,3 +73,21 @@ func (c *stubIAMCreator) Create(
|
||||
c.id.CloudProvider = provider
|
||||
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
|
||||
}
|
||||
|
@ -42,6 +42,7 @@ func NewIAMCmd() *cobra.Command {
|
||||
}
|
||||
|
||||
cmd.AddCommand(newIAMCreateCmd())
|
||||
cmd.AddCommand(newIAMDestroyCmd())
|
||||
|
||||
return cmd
|
||||
}
|
||||
@ -563,3 +564,18 @@ func parseIDFile(serviceAccountKeyBase64 string) (map[string]string, error) {
|
||||
}
|
||||
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
|
||||
}
|
||||
|
142
cli/internal/cmd/iamdestroy.go
Normal file
142
cli/internal/cmd/iamdestroy.go
Normal 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
|
||||
}
|
211
cli/internal/cmd/iamdestroy_test.go
Normal file
211
cli/internal/cmd/iamdestroy_test.go
Normal 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)
|
||||
})
|
||||
}
|
||||
}
|
@ -68,6 +68,11 @@ func New(ctx context.Context, workingDir string) (*Client, error) {
|
||||
}, 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.
|
||||
func (c *Client) PrepareWorkspace(path string, vars Variables) error {
|
||||
if err := prepareWorkspace(path, c.file, c.workingDir); err != nil {
|
||||
|
@ -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.
|
||||
* 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
|
||||
# Navigate to the directory containing the terraform.tfstate file
|
||||
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
|
||||
constellation iam destroy
|
||||
```
|
||||
|
Loading…
Reference in New Issue
Block a user