Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: Handle generic check destroy in acceptance tests #2716

Merged
merged 28 commits into from
Apr 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
9434aaa
Prepare check destroy PoC (WIP)
sfc-gh-asawicki Apr 15, 2024
84c67fd
Handle check destroy for view and schema
sfc-gh-asawicki Apr 15, 2024
3c7d49e
Handle check destroy for account, alert, and api integration
sfc-gh-asawicki Apr 15, 2024
b5b36d2
Handle check destroy for database
sfc-gh-asawicki Apr 15, 2024
02b49e4
Handle check destroy for database role, dynamic table, and email noti…
sfc-gh-asawicki Apr 15, 2024
c7cac82
Handle check destroy for external function
sfc-gh-asawicki Apr 15, 2024
aace2a9
Handle check destroy for external stage and external table
sfc-gh-asawicki Apr 15, 2024
14c77be
Handle check destroy for failover group
sfc-gh-asawicki Apr 15, 2024
f5eb6d3
Handle check destroy for file format
sfc-gh-asawicki Apr 15, 2024
468061a
Handle check destroy for function
sfc-gh-asawicki Apr 15, 2024
d46bd90
Move check destroy of grant account role
sfc-gh-asawicki Apr 15, 2024
6b4681f
Move check destroy of grant database role
sfc-gh-asawicki Apr 15, 2024
fc6de22
Move check destroy of grant privilege to account role
sfc-gh-asawicki Apr 16, 2024
f6dbda2
Move check destroy of grant privilege to database role
sfc-gh-asawicki Apr 16, 2024
c7a16ea
Move check destroy of internal stage
sfc-gh-asawicki Apr 16, 2024
e9cd88e
Move check destroy of managed account, masking policy, and materializ…
sfc-gh-asawicki Apr 16, 2024
ca4c367
Move check destroy of network policy and notification integration
sfc-gh-asawicki Apr 16, 2024
fe7d597
Move check destroy of password policy, pipe, procedure, and resource …
sfc-gh-asawicki Apr 16, 2024
8e47b5f
Move check destroy of role
sfc-gh-asawicki Apr 16, 2024
cb466d3
Move check destroy of row access policy, sequence, and share
sfc-gh-asawicki Apr 16, 2024
93eec48
Move check destroy of storage integration and stream
sfc-gh-asawicki Apr 16, 2024
4ccfd37
Move check destroy of table, tag, and task
sfc-gh-asawicki Apr 16, 2024
bd20545
Move check destroy of user and warehouse
sfc-gh-asawicki Apr 16, 2024
5ccf8fd
Run make pre-push
sfc-gh-asawicki Apr 16, 2024
1d8b4d7
Fix after review
sfc-gh-asawicki Apr 17, 2024
b41624e
Merge branch 'main' into handle-generic-check-destroy-in-acceptance-t…
sfc-gh-asawicki Apr 17, 2024
ac2f33c
Run pre-push
sfc-gh-asawicki Apr 17, 2024
6d6c355
Fix test
sfc-gh-asawicki Apr 17, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
365 changes: 365 additions & 0 deletions pkg/acceptance/check_destroy.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,365 @@
package acceptance

import (
"context"
"errors"
"fmt"
"reflect"
"strings"
"testing"

"github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/helpers"
"github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/provider/resources"
"github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk"
"github.com/hashicorp/terraform-plugin-testing/terraform"
)

func CheckDestroy(t *testing.T, resource resources.Resource) func(*terraform.State) error {
t.Helper()
client := Client(t)
t.Logf("running check destroy for resource %s", resource)

return func(s *terraform.State) error {
for _, rs := range s.RootModule().Resources {
if rs.Type != resource.String() {
continue
}
t.Logf("found resource %s in state", resource)
ctx := context.Background()
id := decodeSnowflakeId(rs, resource)
if id == nil {
return fmt.Errorf("could not get the id of %s", resource)
}
showById, ok := showByIdFunctions[resource]
if !ok {
return fmt.Errorf("unsupported show by id in cleanup for %s, with id %v", resource, id.FullyQualifiedName())
}
if showById(ctx, client, id) == nil {
return fmt.Errorf("%s %v still exists", resource, id.FullyQualifiedName())
} else {
t.Logf("resource %s (%v) was dropped successfully in Snowflake", resource, id.FullyQualifiedName())
}
}
return nil
}
}

func decodeSnowflakeId(rs *terraform.ResourceState, resource resources.Resource) sdk.ObjectIdentifier {
switch resource {
case resources.ExternalFunction:
return sdk.NewSchemaObjectIdentifierFromFullyQualifiedName(rs.Primary.ID)
case resources.Function:
return sdk.NewSchemaObjectIdentifierFromFullyQualifiedName(rs.Primary.ID)
case resources.Procedure:
return sdk.NewSchemaObjectIdentifierFromFullyQualifiedName(rs.Primary.ID)
default:
return helpers.DecodeSnowflakeID(rs.Primary.ID)
}
}

type showByIdFunc func(context.Context, *sdk.Client, sdk.ObjectIdentifier) error

var showByIdFunctions = map[resources.Resource]showByIdFunc{
resources.Account: func(ctx context.Context, client *sdk.Client, id sdk.ObjectIdentifier) error {
return runShowById(ctx, id, client.Accounts.ShowByID)
},
resources.Alert: func(ctx context.Context, client *sdk.Client, id sdk.ObjectIdentifier) error {
return runShowById(ctx, id, client.Alerts.ShowByID)
},
resources.ApiIntegration: func(ctx context.Context, client *sdk.Client, id sdk.ObjectIdentifier) error {
return runShowById(ctx, id, client.ApiIntegrations.ShowByID)
},
resources.Database: func(ctx context.Context, client *sdk.Client, id sdk.ObjectIdentifier) error {
return runShowById(ctx, id, client.Databases.ShowByID)
},
resources.DatabaseRole: func(ctx context.Context, client *sdk.Client, id sdk.ObjectIdentifier) error {
return runShowById(ctx, id, client.DatabaseRoles.ShowByID)
},
resources.DynamicTable: func(ctx context.Context, client *sdk.Client, id sdk.ObjectIdentifier) error {
return runShowById(ctx, id, client.DynamicTables.ShowByID)
},
resources.EmailNotificationIntegration: func(ctx context.Context, client *sdk.Client, id sdk.ObjectIdentifier) error {
return runShowById(ctx, id, client.NotificationIntegrations.ShowByID)
},
resources.ExternalFunction: func(ctx context.Context, client *sdk.Client, id sdk.ObjectIdentifier) error {
return runShowById(ctx, id, client.ExternalFunctions.ShowByID)
},
resources.ExternalTable: func(ctx context.Context, client *sdk.Client, id sdk.ObjectIdentifier) error {
return runShowById(ctx, id, client.ExternalTables.ShowByID)
},
resources.FailoverGroup: func(ctx context.Context, client *sdk.Client, id sdk.ObjectIdentifier) error {
return runShowById(ctx, id, client.FailoverGroups.ShowByID)
},
resources.FileFormat: func(ctx context.Context, client *sdk.Client, id sdk.ObjectIdentifier) error {
return runShowById(ctx, id, client.FileFormats.ShowByID)
},
resources.Function: func(ctx context.Context, client *sdk.Client, id sdk.ObjectIdentifier) error {
return runShowById(ctx, id, client.Functions.ShowByID)
},
resources.ManagedAccount: func(ctx context.Context, client *sdk.Client, id sdk.ObjectIdentifier) error {
return runShowById(ctx, id, client.ManagedAccounts.ShowByID)
},
resources.MaskingPolicy: func(ctx context.Context, client *sdk.Client, id sdk.ObjectIdentifier) error {
return runShowById(ctx, id, client.MaskingPolicies.ShowByID)
},
resources.MaterializedView: func(ctx context.Context, client *sdk.Client, id sdk.ObjectIdentifier) error {
return runShowById(ctx, id, client.MaterializedViews.ShowByID)
},
resources.NetworkPolicy: func(ctx context.Context, client *sdk.Client, id sdk.ObjectIdentifier) error {
return runShowById(ctx, id, client.NetworkPolicies.ShowByID)
},
resources.NotificationIntegration: func(ctx context.Context, client *sdk.Client, id sdk.ObjectIdentifier) error {
return runShowById(ctx, id, client.NotificationIntegrations.ShowByID)
},
resources.PasswordPolicy: func(ctx context.Context, client *sdk.Client, id sdk.ObjectIdentifier) error {
return runShowById(ctx, id, client.PasswordPolicies.ShowByID)
},
resources.Pipe: func(ctx context.Context, client *sdk.Client, id sdk.ObjectIdentifier) error {
return runShowById(ctx, id, client.Pipes.ShowByID)
},
resources.Procedure: func(ctx context.Context, client *sdk.Client, id sdk.ObjectIdentifier) error {
return runShowById(ctx, id, client.Procedures.ShowByID)
},
resources.ResourceMonitor: func(ctx context.Context, client *sdk.Client, id sdk.ObjectIdentifier) error {
return runShowById(ctx, id, client.ResourceMonitors.ShowByID)
},
resources.Role: func(ctx context.Context, client *sdk.Client, id sdk.ObjectIdentifier) error {
return runShowById(ctx, id, client.Roles.ShowByID)
},
resources.RowAccessPolicy: func(ctx context.Context, client *sdk.Client, id sdk.ObjectIdentifier) error {
return runShowById(ctx, id, client.RowAccessPolicies.ShowByID)
},
resources.Schema: func(ctx context.Context, client *sdk.Client, id sdk.ObjectIdentifier) error {
return runShowById(ctx, id, client.Schemas.ShowByID)
},
resources.Sequence: func(ctx context.Context, client *sdk.Client, id sdk.ObjectIdentifier) error {
return runShowById(ctx, id, client.Sequences.ShowByID)
},
resources.Share: func(ctx context.Context, client *sdk.Client, id sdk.ObjectIdentifier) error {
return runShowById(ctx, id, client.Shares.ShowByID)
},
resources.Stage: func(ctx context.Context, client *sdk.Client, id sdk.ObjectIdentifier) error {
return runShowById(ctx, id, client.Stages.ShowByID)
},
resources.StorageIntegration: func(ctx context.Context, client *sdk.Client, id sdk.ObjectIdentifier) error {
return runShowById(ctx, id, client.StorageIntegrations.ShowByID)
},
resources.Stream: func(ctx context.Context, client *sdk.Client, id sdk.ObjectIdentifier) error {
return runShowById(ctx, id, client.Streams.ShowByID)
},
resources.Table: func(ctx context.Context, client *sdk.Client, id sdk.ObjectIdentifier) error {
return runShowById(ctx, id, client.Tables.ShowByID)
},
resources.Tag: func(ctx context.Context, client *sdk.Client, id sdk.ObjectIdentifier) error {
return runShowById(ctx, id, client.Tags.ShowByID)
},
resources.Task: func(ctx context.Context, client *sdk.Client, id sdk.ObjectIdentifier) error {
return runShowById(ctx, id, client.Tasks.ShowByID)
},
resources.User: func(ctx context.Context, client *sdk.Client, id sdk.ObjectIdentifier) error {
return runShowById(ctx, id, client.Users.ShowByID)
},
resources.View: func(ctx context.Context, client *sdk.Client, id sdk.ObjectIdentifier) error {
return runShowById(ctx, id, client.Views.ShowByID)
},
resources.Warehouse: func(ctx context.Context, client *sdk.Client, id sdk.ObjectIdentifier) error {
return runShowById(ctx, id, client.Warehouses.ShowByID)
},
}

func runShowById[T any, U sdk.AccountObjectIdentifier | sdk.DatabaseObjectIdentifier | sdk.SchemaObjectIdentifier | sdk.TableColumnIdentifier](ctx context.Context, id sdk.ObjectIdentifier, show func(ctx context.Context, id U) (T, error)) error {
idCast, err := asId[U](id)
if err != nil {
return err
}
_, err = show(ctx, *idCast)
return err
}

func asId[T sdk.AccountObjectIdentifier | sdk.DatabaseObjectIdentifier | sdk.SchemaObjectIdentifier | sdk.TableColumnIdentifier](id sdk.ObjectIdentifier) (*T, error) {
if idCast, ok := id.(T); !ok {
return nil, fmt.Errorf("expected %s identifier type, but got: %T", reflect.TypeOf(new(T)).Elem().Name(), id)
} else {
return &idCast, nil
}
}

// CheckGrantAccountRoleDestroy is a custom checks that should be later incorporated into generic CheckDestroy
func CheckGrantAccountRoleDestroy(t *testing.T) func(*terraform.State) error {
t.Helper()
client := Client(t)

return func(s *terraform.State) error {
for _, rs := range s.RootModule().Resources {
if rs.Type != "snowflake_grant_account_role" {
continue
}
ctx := context.Background()
parts := strings.Split(rs.Primary.ID, "|")
roleName := parts[0]
roleIdentifier := sdk.NewAccountObjectIdentifierFromFullyQualifiedName(roleName)
objectType := parts[1]
targetIdentifier := parts[2]
grants, err := client.Grants.Show(ctx, &sdk.ShowGrantOptions{
Of: &sdk.ShowGrantsOf{
Role: roleIdentifier,
},
})
if err != nil {
return nil
}

var found bool
for _, grant := range grants {
if grant.GrantedTo == sdk.ObjectType(objectType) {
if grant.GranteeName.FullyQualifiedName() == targetIdentifier {
found = true
break
}
}
}
if found {
return fmt.Errorf("role grant %v still exists", rs.Primary.ID)
}
}
return nil
}
}

// CheckGrantDatabaseRoleDestroy is a custom checks that should be later incorporated into generic CheckDestroy
func CheckGrantDatabaseRoleDestroy(t *testing.T) func(*terraform.State) error {
t.Helper()
client := Client(t)

return func(s *terraform.State) error {
for _, rs := range s.RootModule().Resources {
if rs.Type != "snowflake_grant_database_role" {
continue
}
ctx := context.Background()
id := rs.Primary.ID
ids := strings.Split(id, "|")
databaseRoleName := ids[0]
objectType := ids[1]
parentRoleName := ids[2]
grants, err := client.Grants.Show(ctx, &sdk.ShowGrantOptions{
Of: &sdk.ShowGrantsOf{
DatabaseRole: sdk.NewDatabaseObjectIdentifierFromFullyQualifiedName(databaseRoleName),
},
})
if err != nil {
continue
}
for _, grant := range grants {
if grant.GrantedTo == sdk.ObjectType(objectType) {
if grant.GranteeName.FullyQualifiedName() == parentRoleName {
return fmt.Errorf("database role grant %v still exists", grant)
}
}
}
}
return nil
}
}

// CheckAccountRolePrivilegesRevoked is a custom checks that should be later incorporated into generic CheckDestroy
func CheckAccountRolePrivilegesRevoked(t *testing.T) func(*terraform.State) error {
t.Helper()
client := Client(t)

return func(s *terraform.State) error {
for _, rs := range s.RootModule().Resources {
if rs.Type != "snowflake_grant_privileges_to_account_role" {
continue
}
ctx := context.Background()

id := sdk.NewAccountObjectIdentifierFromFullyQualifiedName(rs.Primary.Attributes["account_role_name"])
grants, err := client.Grants.Show(ctx, &sdk.ShowGrantOptions{
To: &sdk.ShowGrantsTo{
Role: id,
},
})
if err != nil {
if errors.Is(err, sdk.ErrObjectNotExistOrAuthorized) {
continue
}
return err
}
var grantedPrivileges []string
for _, grant := range grants {
grantedPrivileges = append(grantedPrivileges, grant.Privilege)
}
if len(grantedPrivileges) > 0 {
return fmt.Errorf("account role (%s) is still granted, granted privileges %v", id.FullyQualifiedName(), grantedPrivileges)
}
}
return nil
}
}

// CheckDatabaseRolePrivilegesRevoked is a custom checks that should be later incorporated into generic CheckDestroy
func CheckDatabaseRolePrivilegesRevoked(t *testing.T) func(*terraform.State) error {
t.Helper()
client := Client(t)

return func(s *terraform.State) error {
for _, rs := range s.RootModule().Resources {
if rs.Type != "snowflake_grant_privileges_to_database_role" {
continue
}
ctx := context.Background()

id := sdk.NewDatabaseObjectIdentifierFromFullyQualifiedName(rs.Primary.Attributes["database_role_name"])
grants, err := client.Grants.Show(ctx, &sdk.ShowGrantOptions{
To: &sdk.ShowGrantsTo{
DatabaseRole: id,
},
})
if err != nil {
return err
}
var grantedPrivileges []string
for _, grant := range grants {
// usage is the default privilege available after creation (it won't be revoked)
if grant.Privilege != "USAGE" {
grantedPrivileges = append(grantedPrivileges, grant.Privilege)
}
}
if len(grantedPrivileges) > 0 {
return fmt.Errorf("database role (%s) is still granted, granted privileges %v", id.FullyQualifiedName(), grantedPrivileges)
}
}
return nil
}
}

// CheckUserPasswordPolicyAttachmentDestroy is a custom checks that should be later incorporated into generic CheckDestroy
func CheckUserPasswordPolicyAttachmentDestroy(t *testing.T) func(*terraform.State) error {
t.Helper()
client := Client(t)

return func(s *terraform.State) error {
for _, rs := range s.RootModule().Resources {
if rs.Type != "snowflake_user_password_policy_attachment" {
continue
}
ctx := context.Background()
policyReferences, err := client.PolicyReferences.GetForEntity(ctx, sdk.NewGetForEntityPolicyReferenceRequest(
sdk.NewAccountObjectIdentifierFromFullyQualifiedName(rs.Primary.Attributes["user_name"]),
sdk.PolicyEntityDomainUser,
))
if err != nil {
if strings.Contains(err.Error(), "does not exist or not authorized") {
// Note: this can happen if the Policy Reference or the User has been deleted as well; in this case, ignore the error
continue
}
return err
}
if len(policyReferences) > 0 {
return fmt.Errorf("user password policy attachment %v still exists", policyReferences[0].PolicyName)
}
}
return nil
}
}
Loading
Loading