From bfadd243a70f1df99426d99c265a12befa93f5e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Cie=C5=9Blak?= Date: Thu, 14 Mar 2024 10:31:09 +0100 Subject: [PATCH] feat: Add snowflake grant ownership resource (#2604) The first part of the implementation of the `snowflake_grant_ownership` resource. This is a "basic" version of this resource providing baseline functionalities needed to transfer ownership in Terraform. In the next pull request, I'll add all of the edge cases we have to cover (most of them are described [here](https://docs.snowflake.com/en/sql-reference/sql/grant-ownership#usage-notes)). Changes made: - Created a new `snowflake_grant_ownership` resource with CRUD operations implemented (still there are TODOs left for discussion) - Added examples and documentation needed for the resource and its identifier Things to do before the merge: - remove `snowflake_grant_ownership` from the provider.go TODO in the next pr(s): - Add deprecation messages to old grant resources specifically made for granting ownership - Add edge cases and test them (and if needed describe them in the documentation and add examples) - Add `setId("")` in read and forcefully grant ownership in Create operation - Referring to [comment](https://github.com/Snowflake-Labs/terraform-provider-snowflake/pull/2604#discussion_r1519628668), test different cases where the Delete operation may struggle with - Test outside of Terraform interactions to see how it behaves in different situations ## Test Plan * [x] acceptance tests * [x] unit tests for the resource identifier conversions from/to String representation * [x] unit tests for the helper functions needed by resource CRUD operations ## References * [GRANT OWNERSHIP](https://docs.snowflake.com/en/sql-reference/sql/grant-ownership) ## Mentioned in A list of issues requesting this resource (a big probability there's more); notify after part 2 will be done. - #2549 - #2199 - #2084 - #1942 - #1875 --- .../grant_privileges_to_account_role.md | 2 +- .../grant_privileges_to_database_role.md | 10 +- .../resource_migration.md | 2 +- pkg/resources/grant_ownership.go | 612 ++++++++++++++++++ .../grant_ownership_acceptance_test.go | 606 +++++++++++++++++ pkg/resources/grant_ownership_identifier.go | 151 +++++ .../grant_ownership_identifier_test.go | 497 ++++++++++++++ pkg/resources/grant_ownership_test.go | 527 +++++++++++++++ .../grant_privileges_identifier_commons.go | 25 +- .../grant_privileges_to_account_role.go | 28 +- .../grant_privileges_to_database_role.go | 2 + .../test.tf | 15 + .../variables.tf | 7 + .../test.tf | 25 + .../variables.tf | 7 + .../OnAll_InDatabase_ToAccountRole/test.tf | 45 ++ .../variables.tf | 19 + .../OnAll_InSchema_ToAccountRole/test.tf | 45 ++ .../OnAll_InSchema_ToAccountRole/variables.tf | 19 + .../OnFuture_InDatabase_ToAccountRole/test.tf | 17 + .../variables.tf | 7 + .../OnFuture_InSchema_ToAccountRole/test.tf | 22 + .../variables.tf | 11 + .../OnObject_Database_ToAccountRole/test.tf | 15 + .../variables.tf | 7 + .../OnObject_Schema_ToAccountRole/test.tf | 20 + .../variables.tf | 11 + .../OnObject_Schema_ToDatabaseRole/test.tf | 21 + .../variables.tf | 11 + .../OnObject_Table_ToAccountRole/test.tf | 31 + .../OnObject_Table_ToAccountRole/variables.tf | 15 + .../OnObject_Table_ToDatabaseRole/test.tf | 32 + .../variables.tf | 15 + pkg/sdk/grants_validations.go | 56 ++ pkg/sdk/object_types.go | 299 +++++---- pkg/sdk/testint/helpers_test.go | 2 +- .../testint/stages_gen_integration_test.go | 5 + .../grant_privileges_to_account_role.md.tmpl | 2 +- .../grant_privileges_to_database_role.md.tmpl | 2 +- tmp/grant_ownership.md.tmpl | 51 ++ tmp/snowflake_grant_ownership/import.sh | 41 ++ tmp/snowflake_grant_ownership/resource.tf | 151 +++++ 42 files changed, 3312 insertions(+), 176 deletions(-) create mode 100644 pkg/resources/grant_ownership.go create mode 100644 pkg/resources/grant_ownership_acceptance_test.go create mode 100644 pkg/resources/grant_ownership_identifier.go create mode 100644 pkg/resources/grant_ownership_identifier_test.go create mode 100644 pkg/resources/grant_ownership_test.go create mode 100644 pkg/resources/testdata/TestAcc_GrantOwnership/InvalidConfiguration_EmptyObjectType/test.tf create mode 100644 pkg/resources/testdata/TestAcc_GrantOwnership/InvalidConfiguration_EmptyObjectType/variables.tf create mode 100644 pkg/resources/testdata/TestAcc_GrantOwnership/InvalidConfiguration_MultipleTargets/test.tf create mode 100644 pkg/resources/testdata/TestAcc_GrantOwnership/InvalidConfiguration_MultipleTargets/variables.tf create mode 100644 pkg/resources/testdata/TestAcc_GrantOwnership/OnAll_InDatabase_ToAccountRole/test.tf create mode 100644 pkg/resources/testdata/TestAcc_GrantOwnership/OnAll_InDatabase_ToAccountRole/variables.tf create mode 100644 pkg/resources/testdata/TestAcc_GrantOwnership/OnAll_InSchema_ToAccountRole/test.tf create mode 100644 pkg/resources/testdata/TestAcc_GrantOwnership/OnAll_InSchema_ToAccountRole/variables.tf create mode 100644 pkg/resources/testdata/TestAcc_GrantOwnership/OnFuture_InDatabase_ToAccountRole/test.tf create mode 100644 pkg/resources/testdata/TestAcc_GrantOwnership/OnFuture_InDatabase_ToAccountRole/variables.tf create mode 100644 pkg/resources/testdata/TestAcc_GrantOwnership/OnFuture_InSchema_ToAccountRole/test.tf create mode 100644 pkg/resources/testdata/TestAcc_GrantOwnership/OnFuture_InSchema_ToAccountRole/variables.tf create mode 100644 pkg/resources/testdata/TestAcc_GrantOwnership/OnObject_Database_ToAccountRole/test.tf create mode 100644 pkg/resources/testdata/TestAcc_GrantOwnership/OnObject_Database_ToAccountRole/variables.tf create mode 100644 pkg/resources/testdata/TestAcc_GrantOwnership/OnObject_Schema_ToAccountRole/test.tf create mode 100644 pkg/resources/testdata/TestAcc_GrantOwnership/OnObject_Schema_ToAccountRole/variables.tf create mode 100644 pkg/resources/testdata/TestAcc_GrantOwnership/OnObject_Schema_ToDatabaseRole/test.tf create mode 100644 pkg/resources/testdata/TestAcc_GrantOwnership/OnObject_Schema_ToDatabaseRole/variables.tf create mode 100644 pkg/resources/testdata/TestAcc_GrantOwnership/OnObject_Table_ToAccountRole/test.tf create mode 100644 pkg/resources/testdata/TestAcc_GrantOwnership/OnObject_Table_ToAccountRole/variables.tf create mode 100644 pkg/resources/testdata/TestAcc_GrantOwnership/OnObject_Table_ToDatabaseRole/test.tf create mode 100644 pkg/resources/testdata/TestAcc_GrantOwnership/OnObject_Table_ToDatabaseRole/variables.tf create mode 100644 tmp/grant_ownership.md.tmpl create mode 100644 tmp/snowflake_grant_ownership/import.sh create mode 100644 tmp/snowflake_grant_ownership/resource.tf diff --git a/docs/resources/grant_privileges_to_account_role.md b/docs/resources/grant_privileges_to_account_role.md index 5572b3c289..64af3480f7 100644 --- a/docs/resources/grant_privileges_to_account_role.md +++ b/docs/resources/grant_privileges_to_account_role.md @@ -331,7 +331,7 @@ Optional: ## Import -~> **Note** All the ..._name parts should be fully qualified names, e.g. for schema object it is `""."".""` +~> **Note** All the ..._name parts should be fully qualified names (where every part is quoted), e.g. for schema object it is `""."".""` ~> **Note** To import all_privileges write ALL or ALL PRIVILEGES in place of `` Import is supported using the following syntax: diff --git a/docs/resources/grant_privileges_to_database_role.md b/docs/resources/grant_privileges_to_database_role.md index 104bcdc9ab..e853927cc6 100644 --- a/docs/resources/grant_privileges_to_database_role.md +++ b/docs/resources/grant_privileges_to_database_role.md @@ -217,8 +217,8 @@ Required: Optional: -- `in_database` (String) -- `in_schema` (String) +- `in_database` (String) The fully qualified name of the database. +- `in_schema` (String) The fully qualified name of the schema. @@ -230,12 +230,12 @@ Required: Optional: -- `in_database` (String) -- `in_schema` (String) +- `in_database` (String) The fully qualified name of the database. +- `in_schema` (String) The fully qualified name of the schema. ## Import -~> **Note** All the ..._name parts should be fully qualified names, e.g. for database object it is `"".""` +~> **Note** All the ..._name parts should be fully qualified names (where every part is quoted), e.g. for database object it is `"".""` ~> **Note** To import all_privileges write ALL or ALL PRIVILEGES in place of `` Import is supported using the following syntax: diff --git a/docs/technical-documentation/resource_migration.md b/docs/technical-documentation/resource_migration.md index 8afc3c83dc..d98ce1898e 100644 --- a/docs/technical-documentation/resource_migration.md +++ b/docs/technical-documentation/resource_migration.md @@ -46,7 +46,7 @@ resource "snowflake_grant_privileges_to_account_role" "new_resource" { depends_on = [snowflake_database.test, snowflake_role.a, snowflake_role.b] for_each = toset([snowflake_role.a.name, snowflake_role.b.name]) privileges = ["USAGE"] - role_name = each.key + account_role_name = each.key on_account_object { object_type = "DATABASE" object_name = snowflake_database.test.name diff --git a/pkg/resources/grant_ownership.go b/pkg/resources/grant_ownership.go new file mode 100644 index 0000000000..99e0a771a8 --- /dev/null +++ b/pkg/resources/grant_ownership.go @@ -0,0 +1,612 @@ +package resources + +import ( + "context" + "fmt" + "log" + "strings" + + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/helpers" + + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/internal/logging" + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/internal/provider" + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" +) + +var grantOwnershipSchema = map[string]*schema.Schema{ + "account_role_name": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + Description: "The fully qualified name of the account role to which privileges will be granted.", + ValidateDiagFunc: IsValidIdentifier[sdk.AccountObjectIdentifier](), + ExactlyOneOf: []string{ + "account_role_name", + "database_role_name", + }, + }, + "database_role_name": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + Description: "The fully qualified name of the database role to which privileges will be granted.", + ValidateDiagFunc: IsValidIdentifier[sdk.DatabaseObjectIdentifier](), + ExactlyOneOf: []string{ + "account_role_name", + "database_role_name", + }, + }, + "outbound_privileges": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + Description: "Specifies whether to remove or transfer all existing outbound privileges on the object when ownership is transferred to a new role. Available options are: REVOKE for removing existing privileges and COPY to transfer them with ownership. For more information head over to [Snowflake documentation](https://docs.snowflake.com/en/sql-reference/sql/grant-ownership#optional-parameters).", + ValidateFunc: validation.StringInSlice([]string{ + "COPY", + "REVOKE", + }, true), + }, + "on": { + Type: schema.TypeList, + Required: true, + ForceNew: true, + Description: "Configures which object(s) should transfer their ownership to the specified role.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "object_type": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + Description: fmt.Sprintf("Specifies the type of object on which you are transferring ownership. Available values are: %s", strings.Join(sdk.ValidGrantOwnershipObjectTypesString, " | ")), + RequiredWith: []string{ + "on.0.object_name", + }, + ValidateFunc: validation.StringInSlice(sdk.ValidGrantOwnershipObjectTypesString, true), + }, + "object_name": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + Description: "Specifies the identifier for the object on which you are transferring ownership.", + RequiredWith: []string{ + "on.0.object_type", + }, + ExactlyOneOf: []string{ + "on.0.object_name", + "on.0.all", + "on.0.future", + }, + }, + "all": { + Type: schema.TypeList, + Optional: true, + ForceNew: true, + Description: "Configures the privilege to be granted on all objects in either a database or schema.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: grantOwnershipBulkOperationSchema("all"), + }, + ExactlyOneOf: []string{ + "on.0.object_name", + "on.0.all", + "on.0.future", + }, + }, + "future": { + Type: schema.TypeList, + Optional: true, + ForceNew: true, + Description: "Configures the privilege to be granted on all objects in either a database or schema.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: grantOwnershipBulkOperationSchema("future"), + }, + ExactlyOneOf: []string{ + "on.0.object_name", + "on.0.all", + "on.0.future", + }, + }, + }, + }, + }, +} + +func grantOwnershipBulkOperationSchema(branchName string) map[string]*schema.Schema { + return map[string]*schema.Schema{ + "object_type_plural": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + Description: fmt.Sprintf("Specifies the type of object in plural form on which you are transferring ownership. Available values are: %s. For more information head over to [Snowflake documentation](https://docs.snowflake.com/en/sql-reference/sql/grant-ownership#required-parameters).", strings.Join(sdk.ValidGrantOwnershipPluralObjectTypesString, " | ")), + ValidateFunc: validation.StringInSlice(sdk.ValidGrantOwnershipPluralObjectTypesString, true), + }, + "in_database": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + Description: "The fully qualified name of the database.", + ValidateDiagFunc: IsValidIdentifier[sdk.AccountObjectIdentifier](), + ExactlyOneOf: []string{ + fmt.Sprintf("on.0.%s.0.in_database", branchName), + fmt.Sprintf("on.0.%s.0.in_schema", branchName), + }, + }, + "in_schema": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + Description: "The fully qualified name of the schema.", + ValidateDiagFunc: IsValidIdentifier[sdk.DatabaseObjectIdentifier](), + ExactlyOneOf: []string{ + fmt.Sprintf("on.0.%s.0.in_database", branchName), + fmt.Sprintf("on.0.%s.0.in_schema", branchName), + }, + }, + } +} + +func GrantOwnership() *schema.Resource { + return &schema.Resource{ + CreateContext: CreateGrantOwnership, + // There's no Update, because every field is marked as ForceNew + DeleteContext: DeleteGrantOwnership, + ReadContext: ReadGrantOwnership, + + Schema: grantOwnershipSchema, + Importer: &schema.ResourceImporter{ + StateContext: ImportGrantOwnership(), + }, + } +} + +func ImportGrantOwnership() schema.StateContextFunc { + return func(ctx context.Context, d *schema.ResourceData, meta any) ([]*schema.ResourceData, error) { + logging.DebugLogger.Printf("[DEBUG] Entering import grant privileges to account role") + id, err := ParseGrantOwnershipId(d.Id()) + if err != nil { + return nil, err + } + logging.DebugLogger.Printf("[DEBUG] Imported identifier: %s", id.String()) + + switch id.GrantOwnershipTargetRoleKind { + case ToAccountGrantOwnershipTargetRoleKind: + if err := d.Set("account_role_name", id.AccountRoleName.Name()); err != nil { + return nil, err + } + case ToDatabaseGrantOwnershipTargetRoleKind: + if err := d.Set("database_role_name", id.DatabaseRoleName.FullyQualifiedName()); err != nil { + return nil, err + } + } + + if id.OutboundPrivilegesBehavior != nil { + if err := d.Set("outbound_privileges", *id.OutboundPrivilegesBehavior); err != nil { + return nil, err + } + } + + switch id.Kind { + case OnObjectGrantOwnershipKind: + data := id.Data.(*OnObjectGrantOwnershipData) + + onObject := make(map[string]any) + onObject["object_type"] = data.ObjectType.String() + if objectName, ok := any(data.ObjectName).(sdk.AccountObjectIdentifier); ok { + onObject["object_name"] = objectName.Name() + } else { + onObject["object_name"] = data.ObjectName.FullyQualifiedName() + } + + if err := d.Set("on", []any{onObject}); err != nil { + return nil, err + } + case OnAllGrantOwnershipKind, OnFutureGrantOwnershipKind: + data := id.Data.(*BulkOperationGrantData) + + on := make(map[string]any) + onAllOrFuture := make(map[string]any) + onAllOrFuture["object_type_plural"] = data.ObjectNamePlural.String() + switch data.Kind { + case InDatabaseBulkOperationGrantKind: + onAllOrFuture["in_database"] = data.Database.Name() + case InSchemaBulkOperationGrantKind: + onAllOrFuture["in_schema"] = data.Schema.FullyQualifiedName() + } + + switch id.Kind { + case OnAllGrantOwnershipKind: + on["all"] = []any{onAllOrFuture} + case OnFutureGrantOwnershipKind: + on["future"] = []any{onAllOrFuture} + } + + if err := d.Set("on", []any{on}); err != nil { + return nil, err + } + } + + return []*schema.ResourceData{d}, nil + } +} + +func CreateGrantOwnership(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics { + client := meta.(*provider.Context).Client + + id, err := createGrantOwnershipIdFromSchema(d) + if err != nil { + return diag.FromErr(err) + } + logging.DebugLogger.Printf("[DEBUG] created identifier from schema: %s", id.String()) + + grantOn, err := getOwnershipGrantOn(d) + if err != nil { + return diag.FromErr(err) + } + + err = client.Grants.GrantOwnership( + ctx, + *grantOn, + getOwnershipGrantTo(d), + getOwnershipGrantOpts(id), + ) + if err != nil { + return diag.Diagnostics{ + diag.Diagnostic{ + Severity: diag.Error, + Summary: "An error occurred during grant ownership", + Detail: fmt.Sprintf("Id: %s\nError: %s", id.String(), err), + }, + } + } + + logging.DebugLogger.Printf("[DEBUG] Setting identifier to %s", id.String()) + d.SetId(id.String()) + + return ReadGrantOwnership(ctx, d, meta) +} + +func DeleteGrantOwnership(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics { + client := meta.(*provider.Context).Client + + id, err := ParseGrantOwnershipId(d.Id()) + if err != nil { + return diag.Diagnostics{ + diag.Diagnostic{ + Severity: diag.Error, + Summary: "Failed to parse internal identifier", + Detail: fmt.Sprintf("Id: %s\nError: %s", d.Id(), err), + }, + } + } + + grantOn, err := getOwnershipGrantOn(d) + if err != nil { + return diag.FromErr(err) + } + + if grantOn.Future != nil { + // TODO (SNOW-1182623): Still waiting for the response on the behavior/SQL syntax we should use here + } else { + accountRoleName, err := client.ContextFunctions.CurrentRole(ctx) + if err != nil { + return diag.FromErr(err) + } + + err = client.Grants.GrantOwnership( // TODO: Should we always set outbound privileges to COPY in delete operation or set it to the config value? + ctx, + *grantOn, + sdk.OwnershipGrantTo{ + AccountRoleName: sdk.Pointer(sdk.NewAccountObjectIdentifier(accountRoleName)), + }, + getOwnershipGrantOpts(id), + ) + if err != nil { + return diag.Diagnostics{ + diag.Diagnostic{ + Severity: diag.Error, + Summary: "An error occurred when transferring ownership back to the original role", + Detail: fmt.Sprintf("Id: %s\nError: %s", d.Id(), err), + }, + } + } + } + + d.SetId("") + + return nil +} + +func ReadGrantOwnership(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics { + id, err := ParseGrantOwnershipId(d.Id()) + if err != nil { + return diag.Diagnostics{ + diag.Diagnostic{ + Severity: diag.Error, + Summary: "Failed to parse internal identifier", + Detail: fmt.Sprintf("Id: %s\nError: %s", d.Id(), err), + }, + } + } + + opts, grantedOn := prepareShowGrantsRequestForGrantOwnership(id) + if opts == nil { + return nil + } + + client := meta.(*provider.Context).Client + + grants, err := client.Grants.Show(ctx, opts) + if err != nil { + return diag.Diagnostics{ + diag.Diagnostic{ + Severity: diag.Error, + Summary: "Failed to retrieve grants", + Detail: fmt.Sprintf("Id: %s\nError: %s", d.Id(), err), + }, + } + } + + ownershipFound := false + + for _, grant := range grants { + if grant.Privilege != "OWNERSHIP" { + continue + } + + // Future grants do not have grantedBy, only current grants do. + // If grantedby is an empty string, it means terraform could not have created the grant + if (opts.Future == nil || !*opts.Future) && grant.GrantedBy.Name() == "" { + continue + } + + // grant_on is for future grants, granted_on is for current grants. + // They function the same way though in a test for matching the object type + if grantedOn != grant.GrantedOn && grantedOn != grant.GrantOn { + continue + } + + switch id.GrantOwnershipTargetRoleKind { + case ToAccountGrantOwnershipTargetRoleKind: + if grant.GranteeName.Name() == id.AccountRoleName.Name() { + ownershipFound = true + } + case ToDatabaseGrantOwnershipTargetRoleKind: + if grant.GranteeName.Name() == id.DatabaseRoleName.Name() { + ownershipFound = true + } + } + } + + if !ownershipFound { + return diag.Diagnostics{ + diag.Diagnostic{ + Severity: diag.Error, + Summary: "Couldn't find OWNERSHIP privilege on target object", + Detail: fmt.Sprintf("Id: %s", d.Id()), + }, + } + } + + return nil +} + +// TODO(SNOW-1229218): Make sdk.ObjectType + string objectName to sdk.ObjectIdentifier mapping available in the sdk (for all object types). +func getOnObjectIdentifier(objectType sdk.ObjectType, objectName string) (sdk.ObjectIdentifier, error) { + identifier, err := helpers.DecodeSnowflakeParameterID(objectName) + if err != nil { + return nil, err + } + + switch objectType { + case sdk.ObjectTypeComputePool, + sdk.ObjectTypeDatabase, + sdk.ObjectTypeExternalVolume, + sdk.ObjectTypeFailoverGroup, + sdk.ObjectTypeIntegration, + sdk.ObjectTypeNetworkPolicy, + sdk.ObjectTypeReplicationGroup, + sdk.ObjectTypeRole, + sdk.ObjectTypeUser, + sdk.ObjectTypeWarehouse: + return sdk.NewAccountObjectIdentifier(objectName), nil + case sdk.ObjectTypeDatabaseRole, + sdk.ObjectTypeSchema: + if _, ok := identifier.(sdk.DatabaseObjectIdentifier); !ok { + return nil, sdk.NewError(fmt.Sprintf("invalid object_name %s, expected database object identifier", objectName)) + } + case sdk.ObjectTypeAggregationPolicy, + sdk.ObjectTypeAlert, + sdk.ObjectTypeAuthenticationPolicy, + sdk.ObjectTypeDynamicTable, + sdk.ObjectTypeEventTable, + sdk.ObjectTypeExternalTable, + sdk.ObjectTypeFileFormat, + sdk.ObjectTypeFunction, + sdk.ObjectTypeHybridTable, + sdk.ObjectTypeIcebergTable, + sdk.ObjectTypeImageRepository, + sdk.ObjectTypeMaterializedView, + sdk.ObjectTypeNetworkRule, + sdk.ObjectTypePackagesPolicy, + sdk.ObjectTypePipe, + sdk.ObjectTypeProcedure, + sdk.ObjectTypeMaskingPolicy, + sdk.ObjectTypePasswordPolicy, + sdk.ObjectTypeProjectionPolicy, + sdk.ObjectTypeRowAccessPolicy, + sdk.ObjectTypeSessionPolicy, + sdk.ObjectTypeSecret, + sdk.ObjectTypeSequence, + sdk.ObjectTypeStage, + sdk.ObjectTypeStream, + sdk.ObjectTypeTable, + sdk.ObjectTypeTag, + sdk.ObjectTypeTask, + sdk.ObjectTypeView: + if _, ok := identifier.(sdk.SchemaObjectIdentifier); !ok { + return nil, sdk.NewError(fmt.Sprintf("invalid object_name %s, expected schema object identifier", objectName)) + } + default: + return nil, sdk.NewError(fmt.Sprintf("object_type %s is not supported, please create a feature request for the provider if given object_type should be supported", objectType)) + } + + return identifier, nil +} + +func getOwnershipGrantOn(d *schema.ResourceData) (*sdk.OwnershipGrantOn, error) { + ownershipGrantOn := new(sdk.OwnershipGrantOn) + + on := d.Get("on").([]any)[0].(map[string]any) + onObjectType := on["object_type"].(string) + onObjectName := on["object_name"].(string) + onAll := on["all"].([]any) + onFuture := on["future"].([]any) + + switch { + case len(onObjectType) > 0 && len(onObjectName) > 0: + objectType := sdk.ObjectType(strings.ToUpper(onObjectType)) + objectName, err := getOnObjectIdentifier(objectType, onObjectName) + if err != nil { + return nil, err + } + ownershipGrantOn.Object = &sdk.Object{ + ObjectType: objectType, + Name: objectName, + } + case len(onAll) > 0: + ownershipGrantOn.All = getGrantOnSchemaObjectIn(onAll[0].(map[string]any)) + case len(onFuture) > 0: + ownershipGrantOn.Future = getGrantOnSchemaObjectIn(onFuture[0].(map[string]any)) + } + + return ownershipGrantOn, nil +} + +func getOwnershipGrantTo(d *schema.ResourceData) sdk.OwnershipGrantTo { + var ownershipGrantTo sdk.OwnershipGrantTo + + if accountRoleName, ok := d.GetOk("account_role_name"); ok { + ownershipGrantTo.AccountRoleName = sdk.Pointer(sdk.NewAccountObjectIdentifierFromFullyQualifiedName(accountRoleName.(string))) + } + + if databaseRoleName, ok := d.GetOk("database_role_name"); ok { + ownershipGrantTo.DatabaseRoleName = sdk.Pointer(sdk.NewDatabaseObjectIdentifierFromFullyQualifiedName(databaseRoleName.(string))) + } + + return ownershipGrantTo +} + +func getOwnershipGrantOpts(id *GrantOwnershipId) *sdk.GrantOwnershipOptions { + opts := new(sdk.GrantOwnershipOptions) + + if id != nil && id.OutboundPrivilegesBehavior != nil { + outboundPrivileges := id.OutboundPrivilegesBehavior.ToOwnershipCurrentGrantsOutboundPrivileges() + if outboundPrivileges != nil { + opts.CurrentGrants = &sdk.OwnershipCurrentGrants{ + OutboundPrivileges: *outboundPrivileges, + } + } + } + + return opts +} + +func prepareShowGrantsRequestForGrantOwnership(id *GrantOwnershipId) (*sdk.ShowGrantOptions, sdk.ObjectType) { + opts := new(sdk.ShowGrantOptions) + var grantedOn sdk.ObjectType + + switch id.Kind { + case OnObjectGrantOwnershipKind: + data := id.Data.(*OnObjectGrantOwnershipData) + grantedOn = data.ObjectType + opts.On = &sdk.ShowGrantsOn{ + Object: &sdk.Object{ + ObjectType: data.ObjectType, + Name: data.ObjectName, + }, + } + case OnAllGrantOwnershipKind: // TODO: discuss if we want to let users do this (lose control over ownership for all objects in x during delete operation - we can also add a flag that would skip delete operation when on_all is set) + switch data := id.Data.(*BulkOperationGrantData); data.Kind { + case InDatabaseBulkOperationGrantKind: + log.Printf("[INFO] Show with on.all option is skipped. No changes in ownership on all %s in database %s in Snowflake will be detected.", data.ObjectNamePlural, data.Database) + case InSchemaBulkOperationGrantKind: + log.Printf("[INFO] Show with on.all option is skipped. No changes in ownership on all %s in schema %s in Snowflake will be detected.", data.ObjectNamePlural, data.Schema) + } + return nil, "" + case OnFutureGrantOwnershipKind: + data := id.Data.(*BulkOperationGrantData) + grantedOn = data.ObjectNamePlural.Singular() + opts.Future = sdk.Bool(true) + + switch data.Kind { + case InDatabaseBulkOperationGrantKind: + opts.In = &sdk.ShowGrantsIn{ + Database: data.Database, + } + case InSchemaBulkOperationGrantKind: + opts.In = &sdk.ShowGrantsIn{ + Schema: data.Schema, + } + } + } + + return opts, grantedOn +} + +func createGrantOwnershipIdFromSchema(d *schema.ResourceData) (*GrantOwnershipId, error) { + id := new(GrantOwnershipId) + accountRoleName, accountRoleNameOk := d.GetOk("account_role_name") + databaseRoleName, databaseRoleNameOk := d.GetOk("database_role_name") + + switch { + case accountRoleNameOk: + id.GrantOwnershipTargetRoleKind = ToAccountGrantOwnershipTargetRoleKind + id.AccountRoleName = sdk.NewAccountObjectIdentifierFromFullyQualifiedName(accountRoleName.(string)) + case databaseRoleNameOk: + id.GrantOwnershipTargetRoleKind = ToDatabaseGrantOwnershipTargetRoleKind + id.DatabaseRoleName = sdk.NewDatabaseObjectIdentifierFromFullyQualifiedName(databaseRoleName.(string)) + } + + outboundPrivileges, outboundPrivilegesOk := d.GetOk("outbound_privileges") + if outboundPrivilegesOk { + switch OutboundPrivilegesBehavior(outboundPrivileges.(string)) { + case CopyOutboundPrivilegesBehavior: + id.OutboundPrivilegesBehavior = sdk.Pointer(CopyOutboundPrivilegesBehavior) + case RevokeOutboundPrivilegesBehavior: + id.OutboundPrivilegesBehavior = sdk.Pointer(RevokeOutboundPrivilegesBehavior) + } + } + + grantedOn := d.Get("on").([]any)[0].(map[string]any) + objectType := grantedOn["object_type"].(string) + objectName := grantedOn["object_name"].(string) + all := grantedOn["all"].([]any) + future := grantedOn["future"].([]any) + + switch { + case len(objectType) > 0 && len(objectName) > 0: + id.Kind = OnObjectGrantOwnershipKind + objectType := sdk.ObjectType(objectType) + objectName, err := getOnObjectIdentifier(objectType, objectName) + if err != nil { + return nil, err + } + id.Data = &OnObjectGrantOwnershipData{ + ObjectType: objectType, + ObjectName: objectName, + } + case len(all) > 0: + id.Kind = OnAllGrantOwnershipKind + id.Data = getBulkOperationGrantData(getGrantOnSchemaObjectIn(all[0].(map[string]any))) + case len(future) > 0: + id.Kind = OnFutureGrantOwnershipKind + id.Data = getBulkOperationGrantData(getGrantOnSchemaObjectIn(future[0].(map[string]any))) + } + + return id, nil +} diff --git a/pkg/resources/grant_ownership_acceptance_test.go b/pkg/resources/grant_ownership_acceptance_test.go new file mode 100644 index 0000000000..009433a67e --- /dev/null +++ b/pkg/resources/grant_ownership_acceptance_test.go @@ -0,0 +1,606 @@ +package resources_test + +import ( + "context" + "fmt" + "regexp" + "slices" + "strings" + "testing" + + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/internal/provider" + "github.com/hashicorp/terraform-plugin-testing/terraform" + + acc "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/acceptance" + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk" + "github.com/hashicorp/terraform-plugin-testing/config" + "github.com/hashicorp/terraform-plugin-testing/helper/acctest" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/tfversion" +) + +func TestAcc_GrantOwnership_OnObject_Database_ToAccountRole(t *testing.T) { + t.Skip("will be unskipped in the following grant ownership prs") + + databaseName := strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha)) + databaseFullyQualifiedName := sdk.NewAccountObjectIdentifier(databaseName).FullyQualifiedName() + + accountRoleName := strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha)) + accountRoleFullyQualifiedName := sdk.NewAccountObjectIdentifier(accountRoleName).FullyQualifiedName() + + configVariables := config.Variables{ + "account_role_name": config.StringVariable(accountRoleName), + "database_name": config.StringVariable(databaseName), + } + resourceName := "snowflake_grant_ownership.test" + + resource.Test(t, resource.TestCase{ + ProtoV6ProviderFactories: acc.TestAccProtoV6ProviderFactories, + PreCheck: func() { acc.TestAccPreCheck(t) }, + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.RequireAbove(tfversion.Version1_5_0), + }, + Steps: []resource.TestStep{ + { + ConfigDirectory: acc.ConfigurationDirectory("TestAcc_GrantOwnership/OnObject_Database_ToAccountRole"), + ConfigVariables: configVariables, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "account_role_name", accountRoleName), + resource.TestCheckResourceAttr(resourceName, "on.0.object_type", "DATABASE"), + resource.TestCheckResourceAttr(resourceName, "on.0.object_name", databaseName), + resource.TestCheckResourceAttr(resourceName, "id", fmt.Sprintf("ToAccountRole|%s||OnObject|DATABASE|%s", accountRoleFullyQualifiedName, databaseFullyQualifiedName)), + checkResourceOwnershipIsGranted(&sdk.ShowGrantOptions{ + To: &sdk.ShowGrantsTo{ + Role: sdk.NewAccountObjectIdentifier(accountRoleName), + }, + }, sdk.ObjectTypeDatabase, accountRoleName, databaseName), + ), + }, + { + ConfigDirectory: acc.ConfigurationDirectory("TestAcc_GrantOwnership/OnObject_Database_ToAccountRole"), + ConfigVariables: configVariables, + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAcc_GrantOwnership_OnObject_Database_IdentifiersWithDots(t *testing.T) { + t.Skip("will be unskipped in the following grant ownership prs") + + databaseName := strings.ToUpper(acctest.RandStringFromCharSet(5, acctest.CharSetAlpha) + "." + acctest.RandStringFromCharSet(5, acctest.CharSetAlpha)) + databaseFullyQualifiedName := sdk.NewAccountObjectIdentifier(databaseName).FullyQualifiedName() + + accountRoleName := strings.ToUpper(acctest.RandStringFromCharSet(5, acctest.CharSetAlpha) + "." + acctest.RandStringFromCharSet(5, acctest.CharSetAlpha)) + accountRoleFullyQualifiedName := sdk.NewAccountObjectIdentifier(accountRoleName).FullyQualifiedName() + + configVariables := config.Variables{ + "account_role_name": config.StringVariable(accountRoleName), + "database_name": config.StringVariable(databaseName), + } + resourceName := "snowflake_grant_ownership.test" + + resource.Test(t, resource.TestCase{ + ProtoV6ProviderFactories: acc.TestAccProtoV6ProviderFactories, + PreCheck: func() { acc.TestAccPreCheck(t) }, + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.RequireAbove(tfversion.Version1_5_0), + }, + Steps: []resource.TestStep{ + { + ConfigDirectory: acc.ConfigurationDirectory("TestAcc_GrantOwnership/OnObject_Database_ToAccountRole"), + ConfigVariables: configVariables, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "account_role_name", accountRoleName), + resource.TestCheckResourceAttr(resourceName, "on.0.object_type", "DATABASE"), + resource.TestCheckResourceAttr(resourceName, "on.0.object_name", databaseName), + resource.TestCheckResourceAttr(resourceName, "id", fmt.Sprintf("ToAccountRole|%s||OnObject|DATABASE|%s", accountRoleFullyQualifiedName, databaseFullyQualifiedName)), + checkResourceOwnershipIsGranted(&sdk.ShowGrantOptions{ + To: &sdk.ShowGrantsTo{ + Role: sdk.NewAccountObjectIdentifier(accountRoleName), + }, + }, sdk.ObjectTypeDatabase, accountRoleName, databaseName), + ), + }, + { + ConfigDirectory: acc.ConfigurationDirectory("TestAcc_GrantOwnership/OnObject_Database_ToAccountRole"), + ConfigVariables: configVariables, + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAcc_GrantOwnership_OnObject_Schema_ToAccountRole(t *testing.T) { + t.Skip("will be unskipped in the following grant ownership prs") + + databaseName := strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha)) + schemaName := strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha)) + schemaFullyQualifiedName := sdk.NewDatabaseObjectIdentifier(databaseName, schemaName).FullyQualifiedName() + + accountRoleName := strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha)) + accountRoleFullyQualifiedName := sdk.NewAccountObjectIdentifier(accountRoleName).FullyQualifiedName() + + configVariables := config.Variables{ + "account_role_name": config.StringVariable(accountRoleName), + "database_name": config.StringVariable(databaseName), + "schema_name": config.StringVariable(schemaName), + } + resourceName := "snowflake_grant_ownership.test" + + resource.Test(t, resource.TestCase{ + ProtoV6ProviderFactories: acc.TestAccProtoV6ProviderFactories, + PreCheck: func() { acc.TestAccPreCheck(t) }, + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.RequireAbove(tfversion.Version1_5_0), + }, + Steps: []resource.TestStep{ + { + ConfigDirectory: acc.ConfigurationDirectory("TestAcc_GrantOwnership/OnObject_Schema_ToAccountRole"), + ConfigVariables: configVariables, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "account_role_name", accountRoleName), + resource.TestCheckResourceAttr(resourceName, "on.0.object_type", "SCHEMA"), + resource.TestCheckResourceAttr(resourceName, "on.0.object_name", schemaFullyQualifiedName), + resource.TestCheckResourceAttr(resourceName, "id", fmt.Sprintf("ToAccountRole|%s||OnObject|SCHEMA|%s", accountRoleFullyQualifiedName, schemaFullyQualifiedName)), + checkResourceOwnershipIsGranted(&sdk.ShowGrantOptions{ + To: &sdk.ShowGrantsTo{ + Role: sdk.NewAccountObjectIdentifier(accountRoleName), + }, + }, sdk.ObjectTypeSchema, accountRoleName, fmt.Sprintf("%s.%s", databaseName, schemaName)), + ), + }, + { + ConfigDirectory: acc.ConfigurationDirectory("TestAcc_GrantOwnership/OnObject_Schema_ToAccountRole"), + ConfigVariables: configVariables, + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAcc_GrantOwnership_OnObject_Schema_ToDatabaseRole(t *testing.T) { + t.Skip("will be unskipped in the following grant ownership prs") + + databaseName := strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha)) + schemaName := strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha)) + schemaFullyQualifiedName := sdk.NewDatabaseObjectIdentifier(databaseName, schemaName).FullyQualifiedName() + + databaseRoleName := strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha)) + databaseRoleFullyQualifiedName := sdk.NewDatabaseObjectIdentifier(databaseName, databaseRoleName).FullyQualifiedName() + + configVariables := config.Variables{ + "database_role_name": config.StringVariable(databaseRoleName), + "database_name": config.StringVariable(databaseName), + "schema_name": config.StringVariable(schemaName), + } + resourceName := "snowflake_grant_ownership.test" + + resource.Test(t, resource.TestCase{ + ProtoV6ProviderFactories: acc.TestAccProtoV6ProviderFactories, + PreCheck: func() { acc.TestAccPreCheck(t) }, + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.RequireAbove(tfversion.Version1_5_0), + }, + Steps: []resource.TestStep{ + { + ConfigDirectory: acc.ConfigurationDirectory("TestAcc_GrantOwnership/OnObject_Schema_ToDatabaseRole"), + ConfigVariables: configVariables, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "database_role_name", databaseRoleFullyQualifiedName), + resource.TestCheckResourceAttr(resourceName, "on.0.object_type", "SCHEMA"), + resource.TestCheckResourceAttr(resourceName, "on.0.object_name", schemaFullyQualifiedName), + resource.TestCheckResourceAttr(resourceName, "id", fmt.Sprintf("ToDatabaseRole|%s||OnObject|SCHEMA|%s", databaseRoleFullyQualifiedName, schemaFullyQualifiedName)), + checkResourceOwnershipIsGranted(&sdk.ShowGrantOptions{ + To: &sdk.ShowGrantsTo{ + DatabaseRole: sdk.NewDatabaseObjectIdentifier(databaseName, databaseRoleName), + }, + }, sdk.ObjectTypeSchema, databaseRoleName, fmt.Sprintf("%s.%s", databaseName, schemaName)), + ), + }, + { + ConfigDirectory: acc.ConfigurationDirectory("TestAcc_GrantOwnership/OnObject_Schema_ToDatabaseRole"), + ConfigVariables: configVariables, + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAcc_GrantOwnership_OnObject_Table_ToAccountRole(t *testing.T) { + t.Skip("will be unskipped in the following grant ownership prs") + + databaseName := strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha)) + schemaName := strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha)) + tableName := strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha)) + tableFullyQualifiedName := sdk.NewSchemaObjectIdentifier(databaseName, schemaName, tableName).FullyQualifiedName() + + accountRoleName := strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha)) + accountRoleFullyQualifiedName := sdk.NewAccountObjectIdentifier(accountRoleName).FullyQualifiedName() + + configVariables := config.Variables{ + "account_role_name": config.StringVariable(accountRoleName), + "database_name": config.StringVariable(databaseName), + "schema_name": config.StringVariable(schemaName), + "table_name": config.StringVariable(tableName), + } + resourceName := "snowflake_grant_ownership.test" + + resource.Test(t, resource.TestCase{ + ProtoV6ProviderFactories: acc.TestAccProtoV6ProviderFactories, + PreCheck: func() { acc.TestAccPreCheck(t) }, + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.RequireAbove(tfversion.Version1_5_0), + }, + Steps: []resource.TestStep{ + { + ConfigDirectory: acc.ConfigurationDirectory("TestAcc_GrantOwnership/OnObject_Table_ToAccountRole"), + ConfigVariables: configVariables, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "account_role_name", accountRoleName), + resource.TestCheckResourceAttr(resourceName, "on.0.object_type", "TABLE"), + resource.TestCheckResourceAttr(resourceName, "on.0.object_name", tableFullyQualifiedName), + resource.TestCheckResourceAttr(resourceName, "id", fmt.Sprintf("ToAccountRole|%s||OnObject|TABLE|%s", accountRoleFullyQualifiedName, tableFullyQualifiedName)), + checkResourceOwnershipIsGranted(&sdk.ShowGrantOptions{ + To: &sdk.ShowGrantsTo{ + Role: sdk.NewAccountObjectIdentifier(accountRoleName), + }, + }, sdk.ObjectTypeTable, accountRoleName, fmt.Sprintf("%s.%s.%s", databaseName, schemaName, tableName)), + ), + }, + { + ConfigDirectory: acc.ConfigurationDirectory("TestAcc_GrantOwnership/OnObject_Table_ToAccountRole"), + ConfigVariables: configVariables, + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAcc_GrantOwnership_OnObject_Table_ToDatabaseRole(t *testing.T) { + t.Skip("will be unskipped in the following grant ownership prs") + + databaseName := strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha)) + schemaName := strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha)) + tableName := strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha)) + tableFullyQualifiedName := sdk.NewSchemaObjectIdentifier(databaseName, schemaName, tableName).FullyQualifiedName() + + databaseRoleName := strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha)) + databaseRoleFullyQualifiedName := sdk.NewDatabaseObjectIdentifier(databaseName, databaseRoleName).FullyQualifiedName() + + configVariables := config.Variables{ + "database_role_name": config.StringVariable(databaseRoleName), + "database_name": config.StringVariable(databaseName), + "schema_name": config.StringVariable(schemaName), + "table_name": config.StringVariable(tableName), + } + resourceName := "snowflake_grant_ownership.test" + + resource.Test(t, resource.TestCase{ + ProtoV6ProviderFactories: acc.TestAccProtoV6ProviderFactories, + PreCheck: func() { acc.TestAccPreCheck(t) }, + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.RequireAbove(tfversion.Version1_5_0), + }, + Steps: []resource.TestStep{ + { + ConfigDirectory: acc.ConfigurationDirectory("TestAcc_GrantOwnership/OnObject_Table_ToDatabaseRole"), + ConfigVariables: configVariables, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "database_role_name", databaseRoleFullyQualifiedName), + resource.TestCheckResourceAttr(resourceName, "on.0.object_type", "TABLE"), + resource.TestCheckResourceAttr(resourceName, "on.0.object_name", tableFullyQualifiedName), + resource.TestCheckResourceAttr(resourceName, "id", fmt.Sprintf("ToDatabaseRole|%s||OnObject|TABLE|%s", databaseRoleFullyQualifiedName, tableFullyQualifiedName)), + checkResourceOwnershipIsGranted(&sdk.ShowGrantOptions{ + To: &sdk.ShowGrantsTo{ + DatabaseRole: sdk.NewDatabaseObjectIdentifier(databaseName, databaseRoleName), + }, + }, sdk.ObjectTypeTable, databaseRoleName, fmt.Sprintf("%s.%s.%s", databaseName, schemaName, tableName)), + ), + }, + { + ConfigDirectory: acc.ConfigurationDirectory("TestAcc_GrantOwnership/OnObject_Table_ToDatabaseRole"), + ConfigVariables: configVariables, + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAcc_GrantOwnership_OnAll_InDatabase_ToAccountRole(t *testing.T) { + t.Skip("will be unskipped in the following grant ownership prs") + + databaseName := strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha)) + databaseFullyQualifiedName := sdk.NewAccountObjectIdentifier(databaseName).FullyQualifiedName() + + schemaName := strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha)) + tableName := strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha)) + secondTableName := strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha)) + + accountRoleName := strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha)) + accountRoleFullyQualifiedName := sdk.NewAccountObjectIdentifier(accountRoleName).FullyQualifiedName() + + configVariables := config.Variables{ + "account_role_name": config.StringVariable(accountRoleName), + "database_name": config.StringVariable(databaseName), + "schema_name": config.StringVariable(schemaName), + "table_name": config.StringVariable(tableName), + "second_table_name": config.StringVariable(secondTableName), + } + resourceName := "snowflake_grant_ownership.test" + + resource.Test(t, resource.TestCase{ + ProtoV6ProviderFactories: acc.TestAccProtoV6ProviderFactories, + PreCheck: func() { acc.TestAccPreCheck(t) }, + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.RequireAbove(tfversion.Version1_5_0), + }, + Steps: []resource.TestStep{ + { + ConfigDirectory: acc.ConfigurationDirectory("TestAcc_GrantOwnership/OnAll_InDatabase_ToAccountRole"), + ConfigVariables: configVariables, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "account_role_name", accountRoleName), + resource.TestCheckResourceAttr(resourceName, "on.0.all.0.object_type_plural", "TABLES"), + resource.TestCheckResourceAttr(resourceName, "on.0.all.0.in_database", databaseName), + resource.TestCheckResourceAttr(resourceName, "id", fmt.Sprintf("ToAccountRole|%s||OnAll|TABLES|InDatabase|%s", accountRoleFullyQualifiedName, databaseFullyQualifiedName)), + checkResourceOwnershipIsGranted(&sdk.ShowGrantOptions{ + To: &sdk.ShowGrantsTo{ + Role: sdk.NewAccountObjectIdentifier(accountRoleName), + }, + }, sdk.ObjectTypeTable, accountRoleName, fmt.Sprintf("%s.%s.%s", databaseName, schemaName, tableName), fmt.Sprintf("%s.%s.%s", databaseName, schemaName, secondTableName)), + ), + }, + { + ConfigDirectory: acc.ConfigurationDirectory("TestAcc_GrantOwnership/OnAll_InDatabase_ToAccountRole"), + ConfigVariables: configVariables, + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAcc_GrantOwnership_OnAll_InSchema_ToAccountRole(t *testing.T) { + t.Skip("will be unskipped in the following grant ownership prs") + + databaseName := strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha)) + schemaName := strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha)) + schemaFullyQualifiedName := sdk.NewDatabaseObjectIdentifier(databaseName, schemaName).FullyQualifiedName() + + tableName := strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha)) + secondTableName := strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha)) + + accountRoleName := strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha)) + accountRoleFullyQualifiedName := sdk.NewAccountObjectIdentifier(accountRoleName).FullyQualifiedName() + + configVariables := config.Variables{ + "account_role_name": config.StringVariable(accountRoleName), + "database_name": config.StringVariable(databaseName), + "schema_name": config.StringVariable(schemaName), + "table_name": config.StringVariable(tableName), + "second_table_name": config.StringVariable(secondTableName), + } + resourceName := "snowflake_grant_ownership.test" + + resource.Test(t, resource.TestCase{ + ProtoV6ProviderFactories: acc.TestAccProtoV6ProviderFactories, + PreCheck: func() { acc.TestAccPreCheck(t) }, + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.RequireAbove(tfversion.Version1_5_0), + }, + Steps: []resource.TestStep{ + { + ConfigDirectory: acc.ConfigurationDirectory("TestAcc_GrantOwnership/OnAll_InSchema_ToAccountRole"), + ConfigVariables: configVariables, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "account_role_name", accountRoleName), + resource.TestCheckResourceAttr(resourceName, "on.0.all.0.object_type_plural", "TABLES"), + resource.TestCheckResourceAttr(resourceName, "on.0.all.0.in_schema", schemaFullyQualifiedName), + resource.TestCheckResourceAttr(resourceName, "id", fmt.Sprintf("ToAccountRole|%s||OnAll|TABLES|InSchema|%s", accountRoleFullyQualifiedName, schemaFullyQualifiedName)), + checkResourceOwnershipIsGranted(&sdk.ShowGrantOptions{ + To: &sdk.ShowGrantsTo{ + Role: sdk.NewAccountObjectIdentifier(accountRoleName), + }, + }, sdk.ObjectTypeTable, accountRoleName, fmt.Sprintf("%s.%s.%s", databaseName, schemaName, tableName), fmt.Sprintf("%s.%s.%s", databaseName, schemaName, secondTableName)), + ), + }, + { + ConfigDirectory: acc.ConfigurationDirectory("TestAcc_GrantOwnership/OnAll_InSchema_ToAccountRole"), + ConfigVariables: configVariables, + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAcc_GrantOwnership_OnFuture_InDatabase_ToAccountRole(t *testing.T) { + t.Skip("will be unskipped in the following grant ownership prs") + + databaseName := strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha)) + databaseFullyQualifiedName := sdk.NewAccountObjectIdentifier(databaseName).FullyQualifiedName() + + accountRoleName := strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha)) + accountRoleFullyQualifiedName := sdk.NewAccountObjectIdentifier(accountRoleName).FullyQualifiedName() + + configVariables := config.Variables{ + "account_role_name": config.StringVariable(accountRoleName), + "database_name": config.StringVariable(databaseName), + } + resourceName := "snowflake_grant_ownership.test" + + resource.Test(t, resource.TestCase{ + ProtoV6ProviderFactories: acc.TestAccProtoV6ProviderFactories, + PreCheck: func() { acc.TestAccPreCheck(t) }, + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.RequireAbove(tfversion.Version1_5_0), + }, + Steps: []resource.TestStep{ + { + ConfigDirectory: acc.ConfigurationDirectory("TestAcc_GrantOwnership/OnFuture_InDatabase_ToAccountRole"), + ConfigVariables: configVariables, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "account_role_name", accountRoleName), + resource.TestCheckResourceAttr(resourceName, "on.0.future.0.object_type_plural", "TABLES"), + resource.TestCheckResourceAttr(resourceName, "on.0.future.0.in_database", databaseName), + resource.TestCheckResourceAttr(resourceName, "id", fmt.Sprintf("ToAccountRole|%s||OnFuture|TABLES|InDatabase|%s", accountRoleFullyQualifiedName, databaseFullyQualifiedName)), + checkResourceOwnershipIsGranted(&sdk.ShowGrantOptions{ + Future: sdk.Bool(true), + In: &sdk.ShowGrantsIn{ + Database: sdk.Pointer(sdk.NewAccountObjectIdentifier(databaseName)), + }, + }, sdk.ObjectTypeTable, accountRoleName, fmt.Sprintf("%s.", databaseName)), + ), + }, + { + ConfigDirectory: acc.ConfigurationDirectory("TestAcc_GrantOwnership/OnFuture_InDatabase_ToAccountRole"), + ConfigVariables: configVariables, + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAcc_GrantOwnership_OnFuture_InSchema_ToAccountRole(t *testing.T) { + t.Skip("will be unskipped in the following grant ownership prs") + + databaseName := strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha)) + schemaName := strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha)) + schemaFullyQualifiedName := sdk.NewDatabaseObjectIdentifier(databaseName, schemaName).FullyQualifiedName() + + accountRoleName := strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha)) + accountRoleFullyQualifiedName := sdk.NewAccountObjectIdentifier(accountRoleName).FullyQualifiedName() + + configVariables := config.Variables{ + "account_role_name": config.StringVariable(accountRoleName), + "database_name": config.StringVariable(databaseName), + "schema_name": config.StringVariable(schemaName), + } + resourceName := "snowflake_grant_ownership.test" + + resource.Test(t, resource.TestCase{ + ProtoV6ProviderFactories: acc.TestAccProtoV6ProviderFactories, + PreCheck: func() { acc.TestAccPreCheck(t) }, + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.RequireAbove(tfversion.Version1_5_0), + }, + Steps: []resource.TestStep{ + { + ConfigDirectory: acc.ConfigurationDirectory("TestAcc_GrantOwnership/OnFuture_InSchema_ToAccountRole"), + ConfigVariables: configVariables, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "account_role_name", accountRoleName), + resource.TestCheckResourceAttr(resourceName, "on.0.future.0.object_type_plural", "TABLES"), + resource.TestCheckResourceAttr(resourceName, "on.0.future.0.in_schema", schemaFullyQualifiedName), + resource.TestCheckResourceAttr(resourceName, "id", fmt.Sprintf("ToAccountRole|%s||OnFuture|TABLES|InSchema|%s", accountRoleFullyQualifiedName, schemaFullyQualifiedName)), + checkResourceOwnershipIsGranted(&sdk.ShowGrantOptions{ + Future: sdk.Bool(true), + In: &sdk.ShowGrantsIn{ + Schema: sdk.Pointer(sdk.NewDatabaseObjectIdentifier(databaseName, schemaName)), + }, + }, sdk.ObjectTypeTable, accountRoleName, fmt.Sprintf("%s.%s.
", databaseName, schemaName)), + ), + }, + { + ConfigDirectory: acc.ConfigurationDirectory("TestAcc_GrantOwnership/OnFuture_InSchema_ToAccountRole"), + ConfigVariables: configVariables, + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAcc_GrantOwnership_InvalidConfiguration_EmptyObjectType(t *testing.T) { + t.Skip("will be unskipped in the following grant ownership prs") + + configVariables := config.Variables{ + "account_role_name": config.StringVariable(strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha))), + "database_name": config.StringVariable(strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha))), + } + + resource.Test(t, resource.TestCase{ + ProtoV6ProviderFactories: acc.TestAccProtoV6ProviderFactories, + PreCheck: func() { acc.TestAccPreCheck(t) }, + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.RequireAbove(tfversion.Version1_5_0), + }, + Steps: []resource.TestStep{ + { + ConfigDirectory: acc.ConfigurationDirectory("TestAcc_GrantOwnership/InvalidConfiguration_EmptyObjectType"), + ConfigVariables: configVariables, + ExpectError: regexp.MustCompile("expected on.0.object_type to be one of"), + }, + }, + }) +} + +func TestAcc_GrantOwnership_InvalidConfiguration_MultipleTargets(t *testing.T) { + t.Skip("will be unskipped in the following grant ownership prs") + + configVariables := config.Variables{ + "account_role_name": config.StringVariable(strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha))), + "database_name": config.StringVariable(strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha))), + } + + resource.Test(t, resource.TestCase{ + ProtoV6ProviderFactories: acc.TestAccProtoV6ProviderFactories, + PreCheck: func() { acc.TestAccPreCheck(t) }, + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.RequireAbove(tfversion.Version1_5_0), + }, + Steps: []resource.TestStep{ + { + ConfigDirectory: acc.ConfigurationDirectory("TestAcc_GrantOwnership/InvalidConfiguration_MultipleTargets"), + ConfigVariables: configVariables, + ExpectError: regexp.MustCompile("only one of `on.0.all,on.0.future,on.0.object_name`"), + }, + }, + }) +} + +func checkResourceOwnershipIsGranted(opts *sdk.ShowGrantOptions, grantOn sdk.ObjectType, roleName string, objectNames ...string) func(s *terraform.State) error { + return func(s *terraform.State) error { + client := acc.TestAccProvider.Meta().(*provider.Context).Client + ctx := context.Background() + + grants, err := client.Grants.Show(ctx, opts) + if err != nil { + return err + } + + found := make([]string, 0) + for _, grant := range grants { + if grant.Privilege == "OWNERSHIP" && + (grant.GrantedOn == grantOn || grant.GrantOn == grantOn) && + grant.GranteeName.Name() == roleName && + slices.Contains(objectNames, grant.Name.Name()) { + found = append(found, grant.Name.Name()) + } + } + + if len(found) != len(objectNames) { + return fmt.Errorf("unable to find ownership privilege on %s granted to %s, expected names: %v, found: %v", grantOn, roleName, objectNames, found) + } + + return nil + } +} diff --git a/pkg/resources/grant_ownership_identifier.go b/pkg/resources/grant_ownership_identifier.go new file mode 100644 index 0000000000..a6bc3277c3 --- /dev/null +++ b/pkg/resources/grant_ownership_identifier.go @@ -0,0 +1,151 @@ +package resources + +import ( + "fmt" + "strings" + + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/helpers" + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk" +) + +type GrantOwnershipTargetRoleKind string + +const ( + ToAccountGrantOwnershipTargetRoleKind GrantOwnershipTargetRoleKind = "ToAccountRole" + ToDatabaseGrantOwnershipTargetRoleKind GrantOwnershipTargetRoleKind = "ToDatabaseRole" +) + +type OutboundPrivilegesBehavior string + +const ( + CopyOutboundPrivilegesBehavior OutboundPrivilegesBehavior = "COPY" + RevokeOutboundPrivilegesBehavior OutboundPrivilegesBehavior = "REVOKE" +) + +func (o OutboundPrivilegesBehavior) ToOwnershipCurrentGrantsOutboundPrivileges() *sdk.OwnershipCurrentGrantsOutboundPrivileges { + switch o { + case CopyOutboundPrivilegesBehavior: + return sdk.Pointer(sdk.Copy) + case RevokeOutboundPrivilegesBehavior: + return sdk.Pointer(sdk.Revoke) + default: + return nil + } +} + +type GrantOwnershipKind string + +const ( + OnObjectGrantOwnershipKind GrantOwnershipKind = "OnObject" + OnAllGrantOwnershipKind GrantOwnershipKind = "OnAll" + OnFutureGrantOwnershipKind GrantOwnershipKind = "OnFuture" +) + +type GrantOwnershipId struct { + GrantOwnershipTargetRoleKind GrantOwnershipTargetRoleKind + AccountRoleName sdk.AccountObjectIdentifier + DatabaseRoleName sdk.DatabaseObjectIdentifier + OutboundPrivilegesBehavior *OutboundPrivilegesBehavior + Kind GrantOwnershipKind + Data fmt.Stringer +} + +type OnObjectGrantOwnershipData struct { + ObjectType sdk.ObjectType + ObjectName sdk.ObjectIdentifier +} + +func (g *OnObjectGrantOwnershipData) String() string { + var parts []string + parts = append(parts, g.ObjectType.String()) + parts = append(parts, g.ObjectName.FullyQualifiedName()) + return strings.Join(parts, helpers.IDDelimiter) +} + +func (g *GrantOwnershipId) String() string { + var parts []string + parts = append(parts, string(g.GrantOwnershipTargetRoleKind)) + switch g.GrantOwnershipTargetRoleKind { + case ToAccountGrantOwnershipTargetRoleKind: + parts = append(parts, g.AccountRoleName.FullyQualifiedName()) + case ToDatabaseGrantOwnershipTargetRoleKind: + parts = append(parts, g.DatabaseRoleName.FullyQualifiedName()) + } + if g.OutboundPrivilegesBehavior != nil { + parts = append(parts, string(*g.OutboundPrivilegesBehavior)) + } else { + parts = append(parts, "") + } + parts = append(parts, string(g.Kind)) + data := g.Data.String() + if len(data) > 0 { + parts = append(parts, data) + } + return strings.Join(parts, helpers.IDDelimiter) +} + +func ParseGrantOwnershipId(id string) (*GrantOwnershipId, error) { + grantOwnershipId := new(GrantOwnershipId) + + parts := strings.Split(id, helpers.IDDelimiter) + if len(parts) < 5 { + return grantOwnershipId, sdk.NewError(`grant ownership identifier should hold at least 5 parts "||||"`) + } + + grantOwnershipId.GrantOwnershipTargetRoleKind = GrantOwnershipTargetRoleKind(parts[0]) + switch grantOwnershipId.GrantOwnershipTargetRoleKind { + case ToAccountGrantOwnershipTargetRoleKind: + grantOwnershipId.AccountRoleName = sdk.NewAccountObjectIdentifierFromFullyQualifiedName(parts[1]) + case ToDatabaseGrantOwnershipTargetRoleKind: + grantOwnershipId.DatabaseRoleName = sdk.NewDatabaseObjectIdentifierFromFullyQualifiedName(parts[1]) + default: + return grantOwnershipId, sdk.NewError(fmt.Sprintf("unknown GrantOwnershipTargetRoleKind: %v, valid options are %v | %v", grantOwnershipId.GrantOwnershipTargetRoleKind, ToAccountGrantOwnershipTargetRoleKind, ToDatabaseGrantOwnershipTargetRoleKind)) + } + + if len(parts[2]) > 0 { + switch outboundPrivilegesBehavior := OutboundPrivilegesBehavior(parts[2]); outboundPrivilegesBehavior { + case CopyOutboundPrivilegesBehavior, RevokeOutboundPrivilegesBehavior: + grantOwnershipId.OutboundPrivilegesBehavior = sdk.Pointer(outboundPrivilegesBehavior) + default: + return grantOwnershipId, sdk.NewError(fmt.Sprintf("unknown OutboundPrivilegesBehavior: %v, valid options are %v | %v", outboundPrivilegesBehavior, CopyOutboundPrivilegesBehavior, RevokeOutboundPrivilegesBehavior)) + } + } + + grantOwnershipId.Kind = GrantOwnershipKind(parts[3]) + switch grantOwnershipId.Kind { + case OnObjectGrantOwnershipKind: + if len(parts) != 6 { + return grantOwnershipId, sdk.NewError(`grant ownership identifier should consist of 6 parts "|||OnObject||"`) + } + objectType := sdk.ObjectType(parts[4]) + objectName, err := getOnObjectIdentifier(objectType, parts[5]) + if err != nil { + return nil, err + } + grantOwnershipId.Data = &OnObjectGrantOwnershipData{ + ObjectType: objectType, + ObjectName: objectName, + } + case OnAllGrantOwnershipKind, OnFutureGrantOwnershipKind: + bulkOperationGrantData := &BulkOperationGrantData{ + ObjectNamePlural: sdk.PluralObjectType(parts[4]), + } + if len(parts) != 7 { + return grantOwnershipId, sdk.NewError(`grant ownership identifier should consist of 7 parts "|||On[All or Future]||In[Database or Schema]|"`) + } + bulkOperationGrantData.Kind = BulkOperationGrantKind(parts[5]) + switch bulkOperationGrantData.Kind { + case InDatabaseBulkOperationGrantKind: + bulkOperationGrantData.Database = sdk.Pointer(sdk.NewAccountObjectIdentifierFromFullyQualifiedName(parts[6])) + case InSchemaBulkOperationGrantKind: + bulkOperationGrantData.Schema = sdk.Pointer(sdk.NewDatabaseObjectIdentifierFromFullyQualifiedName(parts[6])) + default: + return grantOwnershipId, sdk.NewError(fmt.Sprintf("invalid BulkOperationGrantKind: %s, valid options are %v | %v", bulkOperationGrantData.Kind, InDatabaseBulkOperationGrantKind, InSchemaBulkOperationGrantKind)) + } + grantOwnershipId.Data = bulkOperationGrantData + default: + return grantOwnershipId, sdk.NewError(fmt.Sprintf("unknown GrantOwnershipKind: %v", grantOwnershipId.Kind)) + } + + return grantOwnershipId, nil +} diff --git a/pkg/resources/grant_ownership_identifier_test.go b/pkg/resources/grant_ownership_identifier_test.go new file mode 100644 index 0000000000..e0345209ef --- /dev/null +++ b/pkg/resources/grant_ownership_identifier_test.go @@ -0,0 +1,497 @@ +package resources + +import ( + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk" + "github.com/stretchr/testify/assert" +) + +func TestParseGrantOwnershipId(t *testing.T) { + testCases := []struct { + Name string + Identifier string + Expected GrantOwnershipId + Error string + }{ + { + Name: "grant ownership on database to account role", + Identifier: `ToAccountRole|"account-role"|COPY|OnObject|DATABASE|"database-name"`, + Expected: GrantOwnershipId{ + GrantOwnershipTargetRoleKind: ToAccountGrantOwnershipTargetRoleKind, + AccountRoleName: sdk.NewAccountObjectIdentifier("account-role"), + OutboundPrivilegesBehavior: sdk.Pointer(CopyOutboundPrivilegesBehavior), + Kind: OnObjectGrantOwnershipKind, + Data: &OnObjectGrantOwnershipData{ + ObjectType: sdk.ObjectTypeDatabase, + ObjectName: sdk.NewAccountObjectIdentifier("database-name"), + }, + }, + }, + { + Name: "grant ownership on schema to account role", + Identifier: `ToAccountRole|"account-role"|COPY|OnObject|SCHEMA|"database-name"."schema-name"`, + Expected: GrantOwnershipId{ + GrantOwnershipTargetRoleKind: ToAccountGrantOwnershipTargetRoleKind, + AccountRoleName: sdk.NewAccountObjectIdentifier("account-role"), + OutboundPrivilegesBehavior: sdk.Pointer(CopyOutboundPrivilegesBehavior), + Kind: OnObjectGrantOwnershipKind, + Data: &OnObjectGrantOwnershipData{ + ObjectType: sdk.ObjectTypeSchema, + ObjectName: sdk.NewDatabaseObjectIdentifier("database-name", "schema-name"), + }, + }, + }, + { + Name: "grant ownership on table to account role", + Identifier: `ToAccountRole|"account-role"|COPY|OnObject|TABLE|"database-name"."schema-name"."table-name"`, + Expected: GrantOwnershipId{ + GrantOwnershipTargetRoleKind: ToAccountGrantOwnershipTargetRoleKind, + AccountRoleName: sdk.NewAccountObjectIdentifier("account-role"), + OutboundPrivilegesBehavior: sdk.Pointer(CopyOutboundPrivilegesBehavior), + Kind: OnObjectGrantOwnershipKind, + Data: &OnObjectGrantOwnershipData{ + ObjectType: sdk.ObjectTypeTable, + ObjectName: sdk.NewSchemaObjectIdentifier("database-name", "schema-name", "table-name"), + }, + }, + }, + { + Name: "grant ownership on schema to database role", + Identifier: `ToDatabaseRole|"database-name"."database-role"|REVOKE|OnObject|SCHEMA|"database-name"."schema-name"`, + Expected: GrantOwnershipId{ + GrantOwnershipTargetRoleKind: ToDatabaseGrantOwnershipTargetRoleKind, + DatabaseRoleName: sdk.NewDatabaseObjectIdentifier("database-name", "database-role"), + OutboundPrivilegesBehavior: sdk.Pointer(RevokeOutboundPrivilegesBehavior), + Kind: OnObjectGrantOwnershipKind, + Data: &OnObjectGrantOwnershipData{ + ObjectType: sdk.ObjectTypeSchema, + ObjectName: sdk.NewDatabaseObjectIdentifier("database-name", "schema-name"), + }, + }, + }, + { + Name: "grant ownership on all tables in database to account role", + Identifier: `ToAccountRole|"account-role"||OnAll|TABLES|InDatabase|"database-name"`, + Expected: GrantOwnershipId{ + GrantOwnershipTargetRoleKind: ToAccountGrantOwnershipTargetRoleKind, + AccountRoleName: sdk.NewAccountObjectIdentifier("account-role"), + Kind: OnAllGrantOwnershipKind, + Data: &BulkOperationGrantData{ + ObjectNamePlural: sdk.PluralObjectTypeTables, + Kind: InDatabaseBulkOperationGrantKind, + Database: sdk.Pointer(sdk.NewAccountObjectIdentifier("database-name")), + }, + }, + }, + { + Name: "grant ownership on all tables in schema to account role", + Identifier: `ToAccountRole|"account-role"||OnAll|TABLES|InSchema|"database-name"."schema-name"`, + Expected: GrantOwnershipId{ + GrantOwnershipTargetRoleKind: ToAccountGrantOwnershipTargetRoleKind, + AccountRoleName: sdk.NewAccountObjectIdentifier("account-role"), + Kind: OnAllGrantOwnershipKind, + Data: &BulkOperationGrantData{ + ObjectNamePlural: sdk.PluralObjectTypeTables, + Kind: InSchemaBulkOperationGrantKind, + Schema: sdk.Pointer(sdk.NewDatabaseObjectIdentifier("database-name", "schema-name")), + }, + }, + }, + { + Name: "grant ownership on future tables in database to account role", + Identifier: `ToAccountRole|"account-role"|COPY|OnFuture|TABLES|InDatabase|"database-name"`, + Expected: GrantOwnershipId{ + GrantOwnershipTargetRoleKind: ToAccountGrantOwnershipTargetRoleKind, + AccountRoleName: sdk.NewAccountObjectIdentifier("account-role"), + OutboundPrivilegesBehavior: sdk.Pointer(CopyOutboundPrivilegesBehavior), + Kind: OnFutureGrantOwnershipKind, + Data: &BulkOperationGrantData{ + ObjectNamePlural: sdk.PluralObjectTypeTables, + Kind: InDatabaseBulkOperationGrantKind, + Database: sdk.Pointer(sdk.NewAccountObjectIdentifier("database-name")), + }, + }, + }, + { + Name: "grant ownership on future tables in schema to account role", + Identifier: `ToAccountRole|"account-role"|COPY|OnFuture|TABLES|InSchema|"database-name"."schema-name"`, + Expected: GrantOwnershipId{ + GrantOwnershipTargetRoleKind: ToAccountGrantOwnershipTargetRoleKind, + AccountRoleName: sdk.NewAccountObjectIdentifier("account-role"), + OutboundPrivilegesBehavior: sdk.Pointer(CopyOutboundPrivilegesBehavior), + Kind: OnFutureGrantOwnershipKind, + Data: &BulkOperationGrantData{ + ObjectNamePlural: sdk.PluralObjectTypeTables, + Kind: InSchemaBulkOperationGrantKind, + Schema: sdk.Pointer(sdk.NewDatabaseObjectIdentifier("database-name", "schema-name")), + }, + }, + }, + { + Name: "validation: not enough parts", + Identifier: `ToDatabaseRole|"database-name"."role-name"|`, + Error: "ownership identifier should hold at least 5 parts", + }, + { + Name: "validation: invalid to role enum", + Identifier: `SomeInvalidEnum|"database-name"."role-name"|OnObject|DATABASE|"some-database"`, + Error: "unknown GrantOwnershipTargetRoleKind: SomeInvalidEnum, valid options are ToAccountRole | ToDatabaseRole", + }, + { + Name: "invalid outbound privilege option resulting in no outbound privileges option set", + Identifier: `ToAccountRole|"account-role"|InvalidOption|OnFuture|TABLES|InSchema|"database-name"."schema-name"`, + Error: `unknown OutboundPrivilegesBehavior: InvalidOption, valid options are COPY | REVOKE`, + }, + { + Name: "validation: not enough parts for OnObject kind", + Identifier: `ToAccountRole|"account-role"|COPY|OnObject|DATABASE`, + Error: `grant ownership identifier should consist of 6 parts`, + }, + { + Name: "validation: not enough parts for OnAll kind", + Identifier: `ToAccountRole|"account-role"|COPY|OnAll|TABLES|InDatabase`, + Error: `grant ownership identifier should consist of 7 parts`, + }, + { + Name: "validation: OnAll in InvalidOption", + Identifier: `ToAccountRole|"account-role"|COPY|OnAll|TABLES|InvalidOption|"some-identifier"`, + Error: "invalid BulkOperationGrantKind: InvalidOption, valid options are InDatabase | InSchema", + }, + // { + // Name: "TODO(SNOW-999049 - no error because of bad identifiers): validation: OnAll in database - missing database identifier", + // Identifier: `ToAccountRole|"account-role"|COPY|OnAll|InvalidTarget|InDatabase|`, + // Error: "TODO", + // }, + // { + // Name: "TODO(SNOW-999049 - panic because of bad identifiers): validation: OnAll in database - missing schema identifier", + // Identifier: `ToAccountRole|"account-role"|COPY|OnAll|InvalidTarget|InSchema|`, + // Error: "TODO", + // }, + { + Name: "validation: not enough parts for OnFuture kind", + Identifier: `ToAccountRole|"account-role"|COPY|OnFuture|TABLES`, + Error: `grant ownership identifier should consist of 7 parts`, + }, + { + Name: "validation: OnFuture in InvalidOption", + Identifier: `ToAccountRole|"account-role"|COPY|OnFuture|TABLES|InvalidOption|"some-identifier"`, + Error: "invalid BulkOperationGrantKind: InvalidOption, valid options are InDatabase | InSchema", + }, + // { + // Name: "TODO(SNOW-999049 - no error because of bad identifiers): validation: OnFuture in database - missing database identifier", + // Identifier: `ToAccountRole|"account-role"|COPY|OnFuture|InvalidTarget|InDatabase|`, + // Error: "TODO", + // }, + // { + // Name: "TODO(SNOW-999049 - panic because of bad identifiers): validation: OnFuture in database - missing schema identifier", + // Identifier: `ToAccountRole|"account-role"|COPY|OnFuture|InvalidTarget|InSchema|`, + // Error: "TODO", + // }, + } + + for _, tt := range testCases { + tt := tt + t.Run(tt.Name, func(t *testing.T) { + id, err := ParseGrantOwnershipId(tt.Identifier) + if tt.Error == "" { + assert.NoError(t, err) + assert.NotNil(t, id) + assert.Equal(t, tt.Expected, *id) + } else { + assert.ErrorContains(t, err, tt.Error) + } + }) + } +} + +func TestGrantOwnershipIdString(t *testing.T) { + testCases := []struct { + Name string + Identifier GrantOwnershipId + Expected string + Error string + }{ + { + Name: "grant ownership on database to account role", + Identifier: GrantOwnershipId{ + GrantOwnershipTargetRoleKind: ToAccountGrantOwnershipTargetRoleKind, + AccountRoleName: sdk.NewAccountObjectIdentifier("account_role"), + OutboundPrivilegesBehavior: sdk.Pointer(CopyOutboundPrivilegesBehavior), + Kind: OnObjectGrantOwnershipKind, + Data: &OnObjectGrantOwnershipData{ + ObjectType: sdk.ObjectTypeDatabase, + ObjectName: sdk.NewAccountObjectIdentifier("database_name"), + }, + }, + Expected: `ToAccountRole|"account_role"|COPY|OnObject|DATABASE|"database_name"`, + }, + { + Name: "grant ownership on schema to account role", + Identifier: GrantOwnershipId{ + GrantOwnershipTargetRoleKind: ToAccountGrantOwnershipTargetRoleKind, + AccountRoleName: sdk.NewAccountObjectIdentifier("account_role"), + OutboundPrivilegesBehavior: sdk.Pointer(RevokeOutboundPrivilegesBehavior), + Kind: OnObjectGrantOwnershipKind, + Data: &OnObjectGrantOwnershipData{ + ObjectType: sdk.ObjectTypeSchema, + ObjectName: sdk.NewDatabaseObjectIdentifier("database_name", "schema_name"), + }, + }, + Expected: `ToAccountRole|"account_role"|REVOKE|OnObject|SCHEMA|"database_name"."schema_name"`, + }, + { + Name: "grant ownership on table to account role", + Identifier: GrantOwnershipId{ + GrantOwnershipTargetRoleKind: ToAccountGrantOwnershipTargetRoleKind, + AccountRoleName: sdk.NewAccountObjectIdentifier("account_role"), + Kind: OnObjectGrantOwnershipKind, + Data: &OnObjectGrantOwnershipData{ + ObjectType: sdk.ObjectTypeTable, + ObjectName: sdk.NewSchemaObjectIdentifier("database_name", "schema_name", "table_name"), + }, + }, + Expected: `ToAccountRole|"account_role"||OnObject|TABLE|"database_name"."schema_name"."table_name"`, + }, + { + Name: "grant ownership on all tables in database to database role", + Identifier: GrantOwnershipId{ + GrantOwnershipTargetRoleKind: ToDatabaseGrantOwnershipTargetRoleKind, + DatabaseRoleName: sdk.NewDatabaseObjectIdentifier("database_name", "database_role_name"), + Kind: OnAllGrantOwnershipKind, + Data: &BulkOperationGrantData{ + ObjectNamePlural: sdk.PluralObjectTypeTables, + Kind: InDatabaseBulkOperationGrantKind, + Database: sdk.Pointer(sdk.NewAccountObjectIdentifier("database_name")), + }, + }, + Expected: `ToDatabaseRole|"database_name"."database_role_name"||OnAll|TABLES|InDatabase|"database_name"`, + }, + { + Name: "grant ownership on all tables in schema to database role", + Identifier: GrantOwnershipId{ + GrantOwnershipTargetRoleKind: ToDatabaseGrantOwnershipTargetRoleKind, + DatabaseRoleName: sdk.NewDatabaseObjectIdentifier("database_name", "database_role_name"), + Kind: OnAllGrantOwnershipKind, + Data: &BulkOperationGrantData{ + ObjectNamePlural: sdk.PluralObjectTypeTables, + Kind: InSchemaBulkOperationGrantKind, + Schema: sdk.Pointer(sdk.NewDatabaseObjectIdentifier("database_name", "schema_name")), + }, + }, + Expected: `ToDatabaseRole|"database_name"."database_role_name"||OnAll|TABLES|InSchema|"database_name"."schema_name"`, + }, + { + Name: "grant ownership on future tables in database to database role", + Identifier: GrantOwnershipId{ + GrantOwnershipTargetRoleKind: ToDatabaseGrantOwnershipTargetRoleKind, + DatabaseRoleName: sdk.NewDatabaseObjectIdentifier("database_name", "database_role_name"), + Kind: OnFutureGrantOwnershipKind, + Data: &BulkOperationGrantData{ + ObjectNamePlural: sdk.PluralObjectTypeTables, + Kind: InDatabaseBulkOperationGrantKind, + Database: sdk.Pointer(sdk.NewAccountObjectIdentifier("database_name")), + }, + }, + Expected: `ToDatabaseRole|"database_name"."database_role_name"||OnFuture|TABLES|InDatabase|"database_name"`, + }, + { + Name: "grant ownership on future tables in schema to database role", + Identifier: GrantOwnershipId{ + GrantOwnershipTargetRoleKind: ToDatabaseGrantOwnershipTargetRoleKind, + DatabaseRoleName: sdk.NewDatabaseObjectIdentifier("database_name", "database_role_name"), + Kind: OnFutureGrantOwnershipKind, + Data: &BulkOperationGrantData{ + ObjectNamePlural: sdk.PluralObjectTypeTables, + Kind: InSchemaBulkOperationGrantKind, + Schema: sdk.Pointer(sdk.NewDatabaseObjectIdentifier("database_name", "schema_name")), + }, + }, + Expected: `ToDatabaseRole|"database_name"."database_role_name"||OnFuture|TABLES|InSchema|"database_name"."schema_name"`, + }, + } + + for _, tt := range testCases { + tt := tt + t.Run(tt.Name, func(t *testing.T) { + assert.Equal(t, tt.Expected, tt.Identifier.String()) + }) + } +} + +func TestCreateGrantOwnershipIdFromSchema(t *testing.T) { + testCases := []struct { + Name string + Config map[string]any + Expected GrantOwnershipId + }{ + { + Name: "grant ownership on schema to account role with copied outbound privileges", + Config: map[string]any{ + "account_role_name": "test_acc_role_name", + "outbound_privileges": "COPY", + "on": []any{ + map[string]any{ + "object_type": "SCHEMA", + "object_name": "\"test_database\".\"test_schema\"", + }, + }, + }, + Expected: GrantOwnershipId{ + GrantOwnershipTargetRoleKind: ToAccountGrantOwnershipTargetRoleKind, + AccountRoleName: sdk.NewAccountObjectIdentifier("test_acc_role_name"), + OutboundPrivilegesBehavior: sdk.Pointer(CopyOutboundPrivilegesBehavior), + Kind: OnObjectGrantOwnershipKind, + Data: &OnObjectGrantOwnershipData{ + ObjectType: sdk.ObjectTypeSchema, + ObjectName: sdk.NewDatabaseObjectIdentifier("test_database", "test_schema"), + }, + }, + }, + { + Name: "grant ownership on schema to database role with revoked outbound privileges", + Config: map[string]any{ + "database_role_name": "\"test_database\".\"test_database_role\"", + "outbound_privileges": "REVOKE", + "on": []any{ + map[string]any{ + "object_type": "SCHEMA", + "object_name": "\"test_database\".\"test_schema\"", + }, + }, + }, + Expected: GrantOwnershipId{ + GrantOwnershipTargetRoleKind: ToDatabaseGrantOwnershipTargetRoleKind, + DatabaseRoleName: sdk.NewDatabaseObjectIdentifier("test_database", "test_database_role"), + OutboundPrivilegesBehavior: sdk.Pointer(RevokeOutboundPrivilegesBehavior), + Kind: OnObjectGrantOwnershipKind, + Data: &OnObjectGrantOwnershipData{ + ObjectType: sdk.ObjectTypeSchema, + ObjectName: sdk.NewDatabaseObjectIdentifier("test_database", "test_schema"), + }, + }, + }, + { + Name: "grant ownership on all tables in database to account role", + Config: map[string]any{ + "account_role_name": "test_acc_role", + "on": []any{ + map[string]any{ + "all": []any{ + map[string]any{ + "object_type_plural": "tables", + "in_database": "test_database", + }, + }, + }, + }, + }, + Expected: GrantOwnershipId{ + GrantOwnershipTargetRoleKind: ToAccountGrantOwnershipTargetRoleKind, + AccountRoleName: sdk.NewAccountObjectIdentifier("test_acc_role"), + Kind: OnAllGrantOwnershipKind, + Data: &BulkOperationGrantData{ + ObjectNamePlural: sdk.PluralObjectTypeTables, + Kind: InDatabaseBulkOperationGrantKind, + Database: sdk.Pointer(sdk.NewAccountObjectIdentifier("test_database")), + }, + }, + }, + { + Name: "grant ownership on all tables in schema to account role", + Config: map[string]any{ + "account_role_name": "test_acc_role", + "on": []any{ + map[string]any{ + "all": []any{ + map[string]any{ + "object_type_plural": "tables", + "in_schema": "\"test_database\".\"test_schema\"", + }, + }, + }, + }, + }, + Expected: GrantOwnershipId{ + GrantOwnershipTargetRoleKind: ToAccountGrantOwnershipTargetRoleKind, + AccountRoleName: sdk.NewAccountObjectIdentifier("test_acc_role"), + Kind: OnAllGrantOwnershipKind, + Data: &BulkOperationGrantData{ + ObjectNamePlural: sdk.PluralObjectTypeTables, + Kind: InSchemaBulkOperationGrantKind, + Schema: sdk.Pointer(sdk.NewDatabaseObjectIdentifier("test_database", "test_schema")), + }, + }, + }, + { + Name: "grant ownership on future tables in database to account role", + Config: map[string]any{ + "account_role_name": "test_acc_role", + "on": []any{ + map[string]any{ + "future": []any{ + map[string]any{ + "object_type_plural": "tables", + "in_database": "test_database", + }, + }, + }, + }, + }, + Expected: GrantOwnershipId{ + GrantOwnershipTargetRoleKind: ToAccountGrantOwnershipTargetRoleKind, + AccountRoleName: sdk.NewAccountObjectIdentifier("test_acc_role"), + Kind: OnFutureGrantOwnershipKind, + Data: &BulkOperationGrantData{ + ObjectNamePlural: sdk.PluralObjectTypeTables, + Kind: InDatabaseBulkOperationGrantKind, + Database: sdk.Pointer(sdk.NewAccountObjectIdentifier("test_database")), + }, + }, + }, + { + Name: "grant ownership on future tables in schema to account role", + Config: map[string]any{ + "account_role_name": "test_acc_role", + "on": []any{ + map[string]any{ + "future": []any{ + map[string]any{ + "object_type_plural": "tables", + "in_schema": "\"test_database\".\"test_schema\"", + }, + }, + }, + }, + }, + Expected: GrantOwnershipId{ + GrantOwnershipTargetRoleKind: ToAccountGrantOwnershipTargetRoleKind, + AccountRoleName: sdk.NewAccountObjectIdentifier("test_acc_role"), + Kind: OnFutureGrantOwnershipKind, + Data: &BulkOperationGrantData{ + ObjectNamePlural: sdk.PluralObjectTypeTables, + Kind: InSchemaBulkOperationGrantKind, + Schema: sdk.Pointer(sdk.NewDatabaseObjectIdentifier("test_database", "test_schema")), + }, + }, + }, + } + + for _, tt := range testCases { + tt := tt + t.Run(tt.Name, func(t *testing.T) { + d := schema.TestResourceDataRaw(t, grantOwnershipSchema, tt.Config) + id, err := createGrantOwnershipIdFromSchema(d) + assert.NoError(t, err) + assert.NotNil(t, id) + assert.Equal(t, tt.Expected.GrantOwnershipTargetRoleKind, id.GrantOwnershipTargetRoleKind) + assert.Equal(t, tt.Expected.AccountRoleName, id.AccountRoleName) + assert.Equal(t, tt.Expected.DatabaseRoleName, id.DatabaseRoleName) + assert.Equal(t, tt.Expected.OutboundPrivilegesBehavior, id.OutboundPrivilegesBehavior) + assert.Equal(t, tt.Expected.Kind, id.Kind) + assert.Equal(t, tt.Expected.Data.String(), id.Data.String()) + }) + } +} diff --git a/pkg/resources/grant_ownership_test.go b/pkg/resources/grant_ownership_test.go new file mode 100644 index 0000000000..25d898b9a0 --- /dev/null +++ b/pkg/resources/grant_ownership_test.go @@ -0,0 +1,527 @@ +package resources + +import ( + "testing" + + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/stretchr/testify/assert" +) + +func TestGetOnObjectIdentifier(t *testing.T) { + testCases := []struct { + Name string + ObjectType sdk.ObjectType + ObjectName string + Expected sdk.ObjectIdentifier + Error string + }{ + { + Name: "database - account object identifier", + ObjectType: sdk.ObjectTypeDatabase, + ObjectName: "test_database", + Expected: sdk.NewAccountObjectIdentifier("test_database"), + }, + { + Name: "database - account object identifier - quoted", + ObjectType: sdk.ObjectTypeDatabase, + ObjectName: "\"test_database\"", + Expected: sdk.NewAccountObjectIdentifier("test_database"), + }, + { + Name: "schema - database object identifier", + ObjectType: sdk.ObjectTypeSchema, + ObjectName: "test_database.test_schema", + Expected: sdk.NewDatabaseObjectIdentifier("test_database", "test_schema"), + }, + { + Name: "schema - database object identifier - quoted", + ObjectType: sdk.ObjectTypeSchema, + ObjectName: "\"test_database\".\"test_schema\"", + Expected: sdk.NewDatabaseObjectIdentifier("test_database", "test_schema"), + }, + { + Name: "table - schema object identifier", + ObjectType: sdk.ObjectTypeTable, + ObjectName: "test_database.test_schema.test_table", + Expected: sdk.NewSchemaObjectIdentifier("test_database", "test_schema", "test_table"), + }, + { + Name: "table - schema object identifier - quoted", + ObjectType: sdk.ObjectTypeTable, + ObjectName: "\"test_database\".\"test_schema\".\"test_table\"", + Expected: sdk.NewSchemaObjectIdentifier("test_database", "test_schema", "test_table"), + }, + { + Name: "account object identifier with dots", + ObjectType: sdk.ObjectTypeDatabase, + ObjectName: "database.name.with.dots", + Expected: sdk.NewAccountObjectIdentifier("database.name.with.dots"), + }, + { + Name: "validation - valid identifier", + ObjectType: sdk.ObjectTypeDatabase, + ObjectName: "to.many.parts.in.this.identifier", + Error: "unable to classify identifier", + }, + { + Name: "validation - unsupported type", + ObjectType: sdk.ObjectTypeShare, + ObjectName: "some_share", + Error: "object_type SHARE is not supported", + }, + { + Name: "validation - invalid database object identifier", + ObjectType: sdk.ObjectTypeSchema, + ObjectName: "test_database.test_schema.test_table", + Error: "invalid object_name test_database.test_schema.test_table, expected database object identifier", + }, + { + Name: "validation - invalid schema object identifier", + ObjectType: sdk.ObjectTypeTable, + ObjectName: "test_database.test_schema.test_table.column_name", + Error: "invalid object_name test_database.test_schema.test_table.column_name, expected schema object identifier", + }, + } + + for _, tt := range testCases { + tt := tt + t.Run(tt.Name, func(t *testing.T) { + id, err := getOnObjectIdentifier(tt.ObjectType, tt.ObjectName) + if tt.Error == "" { + assert.NoError(t, err) + assert.Equal(t, tt.Expected, id) + } else { + assert.ErrorContains(t, err, tt.Error) + } + }) + } +} + +func TestGetOwnershipGrantOn(t *testing.T) { + testCases := []struct { + Name string + On map[string]any + Expected sdk.OwnershipGrantOn + Error string + }{ + { + Name: "database object type", + On: map[string]any{ + "object_type": "DATABASE", + "object_name": "test_database", + }, + Expected: sdk.OwnershipGrantOn{ + Object: &sdk.Object{ + ObjectType: sdk.ObjectTypeDatabase, + Name: sdk.NewAccountObjectIdentifier("test_database"), + }, + }, + }, + { + Name: "schema object type", + On: map[string]any{ + "object_type": "SCHEMA", + "object_name": "test_database.test_schema", + }, + Expected: sdk.OwnershipGrantOn{ + Object: &sdk.Object{ + ObjectType: sdk.ObjectTypeSchema, + Name: sdk.NewDatabaseObjectIdentifier("test_database", "test_schema"), + }, + }, + }, + { + Name: "table object type", + On: map[string]any{ + "object_type": "TABLE", + "object_name": "test_database.test_schema.test_table", + }, + Expected: sdk.OwnershipGrantOn{ + Object: &sdk.Object{ + ObjectType: sdk.ObjectTypeTable, + Name: sdk.NewSchemaObjectIdentifier("test_database", "test_schema", "test_table"), + }, + }, + }, + { + Name: "on all tables in database", + On: map[string]any{ + "all": []any{ + map[string]any{ + "object_type_plural": "TABLES", + "in_database": "test_database", + }, + }, + }, + Expected: sdk.OwnershipGrantOn{ + All: &sdk.GrantOnSchemaObjectIn{ + PluralObjectType: sdk.PluralObjectTypeTables, + InDatabase: sdk.Pointer(sdk.NewAccountObjectIdentifier("test_database")), + }, + }, + }, + { + Name: "on all tables in schema", + On: map[string]any{ + "all": []any{ + map[string]any{ + "object_type_plural": "TABLES", + "in_schema": "test_database.test_schema", + }, + }, + }, + Expected: sdk.OwnershipGrantOn{ + All: &sdk.GrantOnSchemaObjectIn{ + PluralObjectType: sdk.PluralObjectTypeTables, + InSchema: sdk.Pointer(sdk.NewDatabaseObjectIdentifier("test_database", "test_schema")), + }, + }, + }, + { + Name: "on future tables in database", + On: map[string]any{ + "future": []any{ + map[string]any{ + "object_type_plural": "TABLES", + "in_database": "test_database", + }, + }, + }, + Expected: sdk.OwnershipGrantOn{ + Future: &sdk.GrantOnSchemaObjectIn{ + PluralObjectType: sdk.PluralObjectTypeTables, + InDatabase: sdk.Pointer(sdk.NewAccountObjectIdentifier("test_database")), + }, + }, + }, + { + Name: "on future tables in schema", + On: map[string]any{ + "future": []any{ + map[string]any{ + "object_type_plural": "TABLES", + "in_schema": "test_database.test_schema", + }, + }, + }, + Expected: sdk.OwnershipGrantOn{ + Future: &sdk.GrantOnSchemaObjectIn{ + PluralObjectType: sdk.PluralObjectTypeTables, + InSchema: sdk.Pointer(sdk.NewDatabaseObjectIdentifier("test_database", "test_schema")), + }, + }, + }, + { + Name: "database object type in lowercase", + On: map[string]any{ + "object_type": "database", + "object_name": "test_database", + }, + Expected: sdk.OwnershipGrantOn{ + Object: &sdk.Object{ + ObjectType: sdk.ObjectTypeDatabase, + Name: sdk.NewAccountObjectIdentifier("test_database"), + }, + }, + }, + { + Name: "grant all in database plural object type in lowercase", + On: map[string]any{ + "future": []any{ + map[string]any{ + "object_type_plural": "tables", + "in_schema": "test_database.test_schema", + }, + }, + }, + Expected: sdk.OwnershipGrantOn{ + Future: &sdk.GrantOnSchemaObjectIn{ + PluralObjectType: sdk.PluralObjectTypeTables, + InSchema: sdk.Pointer(sdk.NewDatabaseObjectIdentifier("test_database", "test_schema")), + }, + }, + }, + { + Name: "validation - invalid schema object type", + On: map[string]any{ + "object_type": "SCHEMA", + "object_name": "test_database.test_schema.test_table", + }, + Error: "invalid object_name test_database.test_schema.test_table, expected database object identifier", + }, + } + + for _, tt := range testCases { + tt := tt + t.Run(tt.Name, func(t *testing.T) { + d := schema.TestResourceDataRaw(t, grantOwnershipSchema, map[string]any{ + "on": []any{tt.On}, + }) + grantOn, err := getOwnershipGrantOn(d) + if tt.Error == "" { + assert.NoError(t, err) + assert.NotNil(t, grantOn) + assert.Equal(t, tt.Expected, *grantOn) + } else { + assert.ErrorContains(t, err, tt.Error) + } + }) + } +} + +func TestPrepareShowGrantsRequestForGrantOwnership(t *testing.T) { + testCases := []struct { + Name string + Identifier GrantOwnershipId + ExpectedShowGrantsOpts *sdk.ShowGrantOptions + ExpectedGrantedOn sdk.ObjectType + }{ + { + Name: "show for object - database", + Identifier: GrantOwnershipId{ + Kind: OnObjectGrantOwnershipKind, + Data: &OnObjectGrantOwnershipData{ + ObjectType: sdk.ObjectTypeDatabase, + ObjectName: sdk.NewAccountObjectIdentifier("test_database"), + }, + }, + ExpectedShowGrantsOpts: &sdk.ShowGrantOptions{ + On: &sdk.ShowGrantsOn{ + Object: &sdk.Object{ + ObjectType: sdk.ObjectTypeDatabase, + Name: sdk.NewAccountObjectIdentifier("test_database"), + }, + }, + }, + ExpectedGrantedOn: sdk.ObjectTypeDatabase, + }, + { + Name: "show for object - schema", + Identifier: GrantOwnershipId{ + Kind: OnObjectGrantOwnershipKind, + Data: &OnObjectGrantOwnershipData{ + ObjectType: sdk.ObjectTypeSchema, + ObjectName: sdk.NewDatabaseObjectIdentifier("test_database", "test_schema"), + }, + }, + ExpectedShowGrantsOpts: &sdk.ShowGrantOptions{ + On: &sdk.ShowGrantsOn{ + Object: &sdk.Object{ + ObjectType: sdk.ObjectTypeSchema, + Name: sdk.NewDatabaseObjectIdentifier("test_database", "test_schema"), + }, + }, + }, + ExpectedGrantedOn: sdk.ObjectTypeSchema, + }, + { + Name: "show for all in database", + Identifier: GrantOwnershipId{ + Kind: OnAllGrantOwnershipKind, + Data: &BulkOperationGrantData{ + ObjectNamePlural: sdk.PluralObjectTypeTables, + Kind: InDatabaseBulkOperationGrantKind, + Database: sdk.Pointer(sdk.NewAccountObjectIdentifier("test_database")), + }, + }, + ExpectedShowGrantsOpts: nil, + ExpectedGrantedOn: "", + }, + { + Name: "show for all in schema", + Identifier: GrantOwnershipId{ + Kind: OnAllGrantOwnershipKind, + Data: &BulkOperationGrantData{ + ObjectNamePlural: sdk.PluralObjectTypeTables, + Kind: InSchemaBulkOperationGrantKind, + Schema: sdk.Pointer(sdk.NewDatabaseObjectIdentifier("test_database", "test_schema")), + }, + }, + ExpectedShowGrantsOpts: nil, + ExpectedGrantedOn: "", + }, + { + Name: "show for future in database", + Identifier: GrantOwnershipId{ + Kind: OnFutureGrantOwnershipKind, + Data: &BulkOperationGrantData{ + ObjectNamePlural: sdk.PluralObjectTypeTables, + Kind: InDatabaseBulkOperationGrantKind, + Database: sdk.Pointer(sdk.NewAccountObjectIdentifier("test_database")), + }, + }, + ExpectedShowGrantsOpts: &sdk.ShowGrantOptions{ + Future: sdk.Bool(true), + In: &sdk.ShowGrantsIn{ + Database: sdk.Pointer(sdk.NewAccountObjectIdentifier("test_database")), + }, + }, + ExpectedGrantedOn: sdk.ObjectTypeTable, + }, + { + Name: "show for future in schema", + Identifier: GrantOwnershipId{ + Kind: OnFutureGrantOwnershipKind, + Data: &BulkOperationGrantData{ + ObjectNamePlural: sdk.PluralObjectTypeTables, + Kind: InSchemaBulkOperationGrantKind, + Schema: sdk.Pointer(sdk.NewDatabaseObjectIdentifier("test_database", "test_schema")), + }, + }, + ExpectedShowGrantsOpts: &sdk.ShowGrantOptions{ + Future: sdk.Bool(true), + In: &sdk.ShowGrantsIn{ + Schema: sdk.Pointer(sdk.NewDatabaseObjectIdentifier("test_database", "test_schema")), + }, + }, + ExpectedGrantedOn: sdk.ObjectTypeTable, + }, + } + + for _, tt := range testCases { + tt := tt + t.Run(tt.Name, func(t *testing.T) { + opts, grantedOn := prepareShowGrantsRequestForGrantOwnership(&tt.Identifier) + if tt.ExpectedShowGrantsOpts == nil { + assert.Nil(t, opts) + } else { + assert.NotNil(t, opts) + assert.Equal(t, *tt.ExpectedShowGrantsOpts, *opts) + } + assert.Equal(t, tt.ExpectedGrantedOn, grantedOn) + }) + } +} + +func TestValidAccountRoleNameGetOwnershipGrantTo(t *testing.T) { + testCases := []struct { + Name string + AccountRole *string + Expected sdk.OwnershipGrantTo + }{ + { + Name: "account role name", + AccountRole: sdk.String("account_role_name"), + Expected: sdk.OwnershipGrantTo{ + AccountRoleName: sdk.Pointer(sdk.NewAccountObjectIdentifier("account_role_name")), + }, + }, + { + Name: "account role name - quoted", + AccountRole: sdk.String("\"account_role_name\""), + Expected: sdk.OwnershipGrantTo{ + AccountRoleName: sdk.Pointer(sdk.NewAccountObjectIdentifier("account_role_name")), + }, + }, + { + Name: "account role name - with dots", + AccountRole: sdk.String("account.role.with.dots"), + Expected: sdk.OwnershipGrantTo{ + AccountRoleName: sdk.Pointer(sdk.NewAccountObjectIdentifier("account.role.with.dots")), + }, + }, + } + + for _, tt := range testCases { + tt := tt + t.Run(tt.Name, func(t *testing.T) { + grantTo := getOwnershipGrantTo(schema.TestResourceDataRaw(t, grantOwnershipSchema, map[string]any{ + "account_role_name": *tt.AccountRole, + })) + + assert.Equal(t, *tt.Expected.AccountRoleName, *grantTo.AccountRoleName) + }) + } +} + +func TestValidDatabaseRoleNameGetOwnershipGrantTo(t *testing.T) { + testCases := []struct { + Name string + DatabaseRole *string + Expected sdk.OwnershipGrantTo + }{ + { + Name: "database role name", + DatabaseRole: sdk.String("test_database.database_role_name"), + Expected: sdk.OwnershipGrantTo{ + DatabaseRoleName: sdk.Pointer(sdk.NewDatabaseObjectIdentifier("test_database", "database_role_name")), + }, + }, + { + Name: "database role name - quoted", + DatabaseRole: sdk.String("\"test_database\".\"database_role_name\""), + Expected: sdk.OwnershipGrantTo{ + DatabaseRoleName: sdk.Pointer(sdk.NewDatabaseObjectIdentifier("test_database", "database_role_name")), + }, + }, + } + + for _, tt := range testCases { + tt := tt + t.Run(tt.Name, func(t *testing.T) { + grantTo := getOwnershipGrantTo(schema.TestResourceDataRaw(t, grantOwnershipSchema, map[string]any{ + "database_role_name": *tt.DatabaseRole, + })) + + assert.Equal(t, *tt.Expected.DatabaseRoleName, *grantTo.DatabaseRoleName) + }) + } +} + +func TestInvalidDatabaseRoleGetOwnershipGrantTo(t *testing.T) { + d := schema.TestResourceDataRaw(t, grantOwnershipSchema, map[string]any{ + "database_role_name": "account_role_name", + }) + + assert.Panics(t, func() { + _ = getOwnershipGrantTo(d) + }) +} + +func TestGetOwnershipGrantOpts(t *testing.T) { + testCases := []struct { + Name string + Identifier GrantOwnershipId + Expected *sdk.GrantOwnershipOptions + }{ + { + Name: "outbound privileges copy", + Identifier: GrantOwnershipId{ + OutboundPrivilegesBehavior: sdk.Pointer(CopyOutboundPrivilegesBehavior), + }, + Expected: &sdk.GrantOwnershipOptions{ + CurrentGrants: &sdk.OwnershipCurrentGrants{ + OutboundPrivileges: sdk.Copy, + }, + }, + }, + { + Name: "outbound privileges revoke", + Identifier: GrantOwnershipId{ + OutboundPrivilegesBehavior: sdk.Pointer(RevokeOutboundPrivilegesBehavior), + }, + Expected: &sdk.GrantOwnershipOptions{ + CurrentGrants: &sdk.OwnershipCurrentGrants{ + OutboundPrivileges: sdk.Revoke, + }, + }, + }, + { + Name: "no outbound privileges option", + Identifier: GrantOwnershipId{ + OutboundPrivilegesBehavior: nil, + }, + Expected: &sdk.GrantOwnershipOptions{}, + }, + } + + for _, tt := range testCases { + tt := tt + t.Run(tt.Name, func(t *testing.T) { + opts := getOwnershipGrantOpts(&tt.Identifier) + assert.NotNil(t, opts) + assert.Equal(t, *tt.Expected, *opts) + }) + } +} diff --git a/pkg/resources/grant_privileges_identifier_commons.go b/pkg/resources/grant_privileges_identifier_commons.go index e8057f7dfb..885b5d61c5 100644 --- a/pkg/resources/grant_privileges_identifier_commons.go +++ b/pkg/resources/grant_privileges_identifier_commons.go @@ -55,14 +55,7 @@ func (d *OnSchemaObjectGrantData) String() string { case OnObjectSchemaObjectGrantKind: parts = append(parts, fmt.Sprintf("%s|%s", d.Object.ObjectType, d.Object.Name.FullyQualifiedName())) case OnAllSchemaObjectGrantKind, OnFutureSchemaObjectGrantKind: - parts = append(parts, d.OnAllOrFuture.ObjectNamePlural.String()) - parts = append(parts, string(d.OnAllOrFuture.Kind)) - switch d.OnAllOrFuture.Kind { - case InDatabaseBulkOperationGrantKind: - parts = append(parts, d.OnAllOrFuture.Database.FullyQualifiedName()) - case InSchemaBulkOperationGrantKind: - parts = append(parts, d.OnAllOrFuture.Schema.FullyQualifiedName()) - } + parts = append(parts, d.OnAllOrFuture.String()) } return strings.Join(parts, helpers.IDDelimiter) } @@ -81,6 +74,19 @@ type BulkOperationGrantData struct { Schema *sdk.DatabaseObjectIdentifier } +func (d *BulkOperationGrantData) String() string { + var parts []string + parts = append(parts, d.ObjectNamePlural.String()) + parts = append(parts, string(d.Kind)) + switch d.Kind { + case InDatabaseBulkOperationGrantKind: + parts = append(parts, d.Database.FullyQualifiedName()) + case InSchemaBulkOperationGrantKind: + parts = append(parts, d.Schema.FullyQualifiedName()) + } + return strings.Join(parts, helpers.IDDelimiter) +} + func getBulkOperationGrantData(in *sdk.GrantOnSchemaObjectIn) *BulkOperationGrantData { bulkOperationGrantData := &BulkOperationGrantData{ ObjectNamePlural: in.PluralObjectType, @@ -100,9 +106,8 @@ func getBulkOperationGrantData(in *sdk.GrantOnSchemaObjectIn) *BulkOperationGran } func getGrantOnSchemaObjectIn(allOrFuture map[string]any) *sdk.GrantOnSchemaObjectIn { - pluralObjectType := sdk.PluralObjectType(allOrFuture["object_type_plural"].(string)) grantOnSchemaObjectIn := &sdk.GrantOnSchemaObjectIn{ - PluralObjectType: pluralObjectType, + PluralObjectType: sdk.PluralObjectType(strings.ToUpper(allOrFuture["object_type_plural"].(string))), } if inDatabase, ok := allOrFuture["in_database"].(string); ok && len(inDatabase) > 0 { diff --git a/pkg/resources/grant_privileges_to_account_role.go b/pkg/resources/grant_privileges_to_account_role.go index 42f3136860..6095a5fb21 100644 --- a/pkg/resources/grant_privileges_to_account_role.go +++ b/pkg/resources/grant_privileges_to_account_role.go @@ -419,7 +419,7 @@ func CreateGrantPrivilegesToAccountRole(ctx context.Context, d *schema.ResourceD diag.Diagnostic{ Severity: diag.Error, Summary: "An error occurred when granting privileges to account role", - Detail: fmt.Sprintf("Id: %s\nAccount role name: %s\nError: %s", id.String(), id.RoleName, err.Error()), + Detail: fmt.Sprintf("Id: %s\nAccount role name: %s\nError: %s", id.String(), id.RoleName, err), }, } } @@ -440,7 +440,7 @@ func UpdateGrantPrivilegesToAccountRole(ctx context.Context, d *schema.ResourceD diag.Diagnostic{ Severity: diag.Error, Summary: "Failed to parse internal identifier", - Detail: fmt.Sprintf("Id: %s\nError: %s", d.Id(), err.Error()), + Detail: fmt.Sprintf("Id: %s\nError: %s", d.Id(), err), }, } } @@ -465,7 +465,7 @@ func UpdateGrantPrivilegesToAccountRole(ctx context.Context, d *schema.ResourceD diag.Diagnostic{ Severity: diag.Error, Summary: "Failed to revoke all privileges", - Detail: fmt.Sprintf("Id: %s\nError: %s", d.Id(), err.Error()), + Detail: fmt.Sprintf("Id: %s\nError: %s", d.Id(), err), }, } } @@ -529,7 +529,7 @@ func UpdateGrantPrivilegesToAccountRole(ctx context.Context, d *schema.ResourceD diag.Diagnostic{ Severity: diag.Error, Summary: "Failed to grant added privileges", - Detail: fmt.Sprintf("Id: %s\nPrivileges to add: %v\nError: %s", d.Id(), privilegesToAdd, err.Error()), + Detail: fmt.Sprintf("Id: %s\nPrivileges to add: %v\nError: %s", d.Id(), privilegesToAdd, err), }, } } @@ -556,7 +556,7 @@ func UpdateGrantPrivilegesToAccountRole(ctx context.Context, d *schema.ResourceD diag.Diagnostic{ Severity: diag.Error, Summary: "Failed to revoke removed privileges", - Detail: fmt.Sprintf("Id: %s\nPrivileges to remove: %v\nError: %s", d.Id(), privilegesToRemove, err.Error()), + Detail: fmt.Sprintf("Id: %s\nPrivileges to remove: %v\nError: %s", d.Id(), privilegesToRemove, err), }, } } @@ -585,7 +585,7 @@ func UpdateGrantPrivilegesToAccountRole(ctx context.Context, d *schema.ResourceD diag.Diagnostic{ Severity: diag.Error, Summary: "Failed to grant all privileges", - Detail: fmt.Sprintf("Id: %s\nError: %s", d.Id(), err.Error()), + Detail: fmt.Sprintf("Id: %s\nError: %s", d.Id(), err), }, } } @@ -614,7 +614,7 @@ func UpdateGrantPrivilegesToAccountRole(ctx context.Context, d *schema.ResourceD diag.Diagnostic{ Severity: diag.Error, Summary: "Always apply. An error occurred when granting privileges to account role", - Detail: fmt.Sprintf("Id: %s\nAccount role name: %s\nError: %s", d.Id(), id.RoleName, err.Error()), + Detail: fmt.Sprintf("Id: %s\nAccount role name: %s\nError: %s", d.Id(), id.RoleName, err), }, } } @@ -636,7 +636,7 @@ func DeleteGrantPrivilegesToAccountRole(ctx context.Context, d *schema.ResourceD diag.Diagnostic{ Severity: diag.Error, Summary: "Failed to parse internal identifier", - Detail: fmt.Sprintf("Id: %s\nError: %s", d.Id(), err.Error()), + Detail: fmt.Sprintf("Id: %s\nError: %s", d.Id(), err), }, } } @@ -654,7 +654,7 @@ func DeleteGrantPrivilegesToAccountRole(ctx context.Context, d *schema.ResourceD diag.Diagnostic{ Severity: diag.Error, Summary: "An error occurred when revoking privileges from account role", - Detail: fmt.Sprintf("Id: %s\nAccount role name: %s\nError: %s", d.Id(), id.RoleName.FullyQualifiedName(), err.Error()), + Detail: fmt.Sprintf("Id: %s\nAccount role name: %s\nError: %s", d.Id(), id.RoleName.FullyQualifiedName(), err), }, } } @@ -672,7 +672,7 @@ func ReadGrantPrivilegesToAccountRole(ctx context.Context, d *schema.ResourceDat diag.Diagnostic{ Severity: diag.Error, Summary: "Failed to parse internal identifier", - Detail: fmt.Sprintf("Id: %s\nError: %s", d.Id(), err.Error()), + Detail: fmt.Sprintf("Id: %s\nError: %s", d.Id(), err), }, } } @@ -695,7 +695,7 @@ func ReadGrantPrivilegesToAccountRole(ctx context.Context, d *schema.ResourceDat diag.Diagnostic{ Severity: diag.Error, Summary: "Failed to generate UUID", - Detail: fmt.Sprintf("Original error: %s", err.Error()), + Detail: fmt.Sprintf("Original error: %s", err), }, } } @@ -706,7 +706,7 @@ func ReadGrantPrivilegesToAccountRole(ctx context.Context, d *schema.ResourceDat diag.Diagnostic{ Severity: diag.Error, Summary: "Error setting always_apply_trigger for database role", - Detail: fmt.Sprintf("Id: %s\nError: %s", d.Id(), err.Error()), + Detail: fmt.Sprintf("Id: %s\nError: %s", d.Id(), err), }, } } @@ -731,7 +731,7 @@ func ReadGrantPrivilegesToAccountRole(ctx context.Context, d *schema.ResourceDat diag.Diagnostic{ Severity: diag.Error, Summary: "Failed to retrieve grants", - Detail: fmt.Sprintf("Id: %s\nError: %s", d.Id(), err.Error()), + Detail: fmt.Sprintf("Id: %s\nError: %s", d.Id(), err), }, } } @@ -794,7 +794,7 @@ func ReadGrantPrivilegesToAccountRole(ctx context.Context, d *schema.ResourceDat diag.Diagnostic{ Severity: diag.Error, Summary: "Error setting privileges for account role", - Detail: fmt.Sprintf("Id: %s\nPrivileges: %v\nError: %s", d.Id(), actualPrivileges, err.Error()), + Detail: fmt.Sprintf("Id: %s\nPrivileges: %v\nError: %s", d.Id(), actualPrivileges, err), }, } } diff --git a/pkg/resources/grant_privileges_to_database_role.go b/pkg/resources/grant_privileges_to_database_role.go index 6608aa10d1..6dc873d323 100644 --- a/pkg/resources/grant_privileges_to_database_role.go +++ b/pkg/resources/grant_privileges_to_database_role.go @@ -225,12 +225,14 @@ var grantPrivilegesOnDatabaseRoleBulkOperationSchema = map[string]*schema.Schema Type: schema.TypeString, Optional: true, ForceNew: true, + Description: "The fully qualified name of the database.", ValidateDiagFunc: IsValidIdentifier[sdk.AccountObjectIdentifier](), }, "in_schema": { Type: schema.TypeString, Optional: true, ForceNew: true, + Description: "The fully qualified name of the schema.", ValidateDiagFunc: IsValidIdentifier[sdk.DatabaseObjectIdentifier](), }, } diff --git a/pkg/resources/testdata/TestAcc_GrantOwnership/InvalidConfiguration_EmptyObjectType/test.tf b/pkg/resources/testdata/TestAcc_GrantOwnership/InvalidConfiguration_EmptyObjectType/test.tf new file mode 100644 index 0000000000..989b68472d --- /dev/null +++ b/pkg/resources/testdata/TestAcc_GrantOwnership/InvalidConfiguration_EmptyObjectType/test.tf @@ -0,0 +1,15 @@ +resource "snowflake_role" "test" { + name = var.account_role_name +} + +resource "snowflake_database" "test" { + name = var.database_name +} + +resource "snowflake_grant_ownership" "test" { + account_role_name = snowflake_role.test.name + on { + object_type = "" + object_name = snowflake_database.test.name + } +} diff --git a/pkg/resources/testdata/TestAcc_GrantOwnership/InvalidConfiguration_EmptyObjectType/variables.tf b/pkg/resources/testdata/TestAcc_GrantOwnership/InvalidConfiguration_EmptyObjectType/variables.tf new file mode 100644 index 0000000000..8af99038ae --- /dev/null +++ b/pkg/resources/testdata/TestAcc_GrantOwnership/InvalidConfiguration_EmptyObjectType/variables.tf @@ -0,0 +1,7 @@ +variable "account_role_name" { + type = string +} + +variable "database_name" { + type = string +} diff --git a/pkg/resources/testdata/TestAcc_GrantOwnership/InvalidConfiguration_MultipleTargets/test.tf b/pkg/resources/testdata/TestAcc_GrantOwnership/InvalidConfiguration_MultipleTargets/test.tf new file mode 100644 index 0000000000..a1dd26c2e9 --- /dev/null +++ b/pkg/resources/testdata/TestAcc_GrantOwnership/InvalidConfiguration_MultipleTargets/test.tf @@ -0,0 +1,25 @@ +resource "snowflake_role" "test" { + name = var.account_role_name +} + +resource "snowflake_database" "test" { + name = var.database_name +} + +resource "snowflake_grant_ownership" "test" { + account_role_name = snowflake_role.test.name + on { + object_type = "DATABASE" + object_name = snowflake_database.test.name + + all { + object_type_plural = "TABLES" + in_database = snowflake_database.test.name + } + + future { + object_type_plural = "TABLES" + in_database = snowflake_database.test.name + } + } +} diff --git a/pkg/resources/testdata/TestAcc_GrantOwnership/InvalidConfiguration_MultipleTargets/variables.tf b/pkg/resources/testdata/TestAcc_GrantOwnership/InvalidConfiguration_MultipleTargets/variables.tf new file mode 100644 index 0000000000..8af99038ae --- /dev/null +++ b/pkg/resources/testdata/TestAcc_GrantOwnership/InvalidConfiguration_MultipleTargets/variables.tf @@ -0,0 +1,7 @@ +variable "account_role_name" { + type = string +} + +variable "database_name" { + type = string +} diff --git a/pkg/resources/testdata/TestAcc_GrantOwnership/OnAll_InDatabase_ToAccountRole/test.tf b/pkg/resources/testdata/TestAcc_GrantOwnership/OnAll_InDatabase_ToAccountRole/test.tf new file mode 100644 index 0000000000..122ad22fdb --- /dev/null +++ b/pkg/resources/testdata/TestAcc_GrantOwnership/OnAll_InDatabase_ToAccountRole/test.tf @@ -0,0 +1,45 @@ +resource "snowflake_role" "test" { + name = var.account_role_name +} + +resource "snowflake_database" "test" { + name = var.database_name +} + +resource "snowflake_schema" "test" { + name = var.schema_name + database = snowflake_database.test.name +} + +resource "snowflake_table" "test" { + name = var.table_name + database = snowflake_database.test.name + schema = snowflake_schema.test.name + + column { + name = "id" + type = "NUMBER(38,0)" + } +} + +resource "snowflake_table" "test2" { + name = var.second_table_name + database = snowflake_database.test.name + schema = snowflake_schema.test.name + + column { + name = "id" + type = "NUMBER(38,0)" + } +} + +resource "snowflake_grant_ownership" "test" { + depends_on = [snowflake_table.test, snowflake_table.test2] + account_role_name = snowflake_role.test.name + on { + all { + object_type_plural = "TABLES" + in_database = snowflake_database.test.name + } + } +} diff --git a/pkg/resources/testdata/TestAcc_GrantOwnership/OnAll_InDatabase_ToAccountRole/variables.tf b/pkg/resources/testdata/TestAcc_GrantOwnership/OnAll_InDatabase_ToAccountRole/variables.tf new file mode 100644 index 0000000000..59056d2670 --- /dev/null +++ b/pkg/resources/testdata/TestAcc_GrantOwnership/OnAll_InDatabase_ToAccountRole/variables.tf @@ -0,0 +1,19 @@ +variable "account_role_name" { + type = string +} + +variable "database_name" { + type = string +} + +variable "schema_name" { + type = string +} + +variable "table_name" { + type = string +} + +variable "second_table_name" { + type = string +} diff --git a/pkg/resources/testdata/TestAcc_GrantOwnership/OnAll_InSchema_ToAccountRole/test.tf b/pkg/resources/testdata/TestAcc_GrantOwnership/OnAll_InSchema_ToAccountRole/test.tf new file mode 100644 index 0000000000..b77eb8f5cf --- /dev/null +++ b/pkg/resources/testdata/TestAcc_GrantOwnership/OnAll_InSchema_ToAccountRole/test.tf @@ -0,0 +1,45 @@ +resource "snowflake_role" "test" { + name = var.account_role_name +} + +resource "snowflake_database" "test" { + name = var.database_name +} + +resource "snowflake_schema" "test" { + name = var.schema_name + database = snowflake_database.test.name +} + +resource "snowflake_table" "test" { + name = var.table_name + database = snowflake_database.test.name + schema = snowflake_schema.test.name + + column { + name = "id" + type = "NUMBER(38,0)" + } +} + +resource "snowflake_table" "test2" { + name = var.second_table_name + database = snowflake_database.test.name + schema = snowflake_schema.test.name + + column { + name = "id" + type = "NUMBER(38,0)" + } +} + +resource "snowflake_grant_ownership" "test" { + depends_on = [snowflake_table.test, snowflake_table.test2] + account_role_name = snowflake_role.test.name + on { + all { + object_type_plural = "TABLES" + in_schema = "\"${snowflake_database.test.name}\".\"${snowflake_schema.test.name}\"" + } + } +} diff --git a/pkg/resources/testdata/TestAcc_GrantOwnership/OnAll_InSchema_ToAccountRole/variables.tf b/pkg/resources/testdata/TestAcc_GrantOwnership/OnAll_InSchema_ToAccountRole/variables.tf new file mode 100644 index 0000000000..59056d2670 --- /dev/null +++ b/pkg/resources/testdata/TestAcc_GrantOwnership/OnAll_InSchema_ToAccountRole/variables.tf @@ -0,0 +1,19 @@ +variable "account_role_name" { + type = string +} + +variable "database_name" { + type = string +} + +variable "schema_name" { + type = string +} + +variable "table_name" { + type = string +} + +variable "second_table_name" { + type = string +} diff --git a/pkg/resources/testdata/TestAcc_GrantOwnership/OnFuture_InDatabase_ToAccountRole/test.tf b/pkg/resources/testdata/TestAcc_GrantOwnership/OnFuture_InDatabase_ToAccountRole/test.tf new file mode 100644 index 0000000000..8fd47cd829 --- /dev/null +++ b/pkg/resources/testdata/TestAcc_GrantOwnership/OnFuture_InDatabase_ToAccountRole/test.tf @@ -0,0 +1,17 @@ +resource "snowflake_role" "test" { + name = var.account_role_name +} + +resource "snowflake_database" "test" { + name = var.database_name +} + +resource "snowflake_grant_ownership" "test" { + account_role_name = snowflake_role.test.name + on { + future { + object_type_plural = "TABLES" + in_database = snowflake_database.test.name + } + } +} diff --git a/pkg/resources/testdata/TestAcc_GrantOwnership/OnFuture_InDatabase_ToAccountRole/variables.tf b/pkg/resources/testdata/TestAcc_GrantOwnership/OnFuture_InDatabase_ToAccountRole/variables.tf new file mode 100644 index 0000000000..8af99038ae --- /dev/null +++ b/pkg/resources/testdata/TestAcc_GrantOwnership/OnFuture_InDatabase_ToAccountRole/variables.tf @@ -0,0 +1,7 @@ +variable "account_role_name" { + type = string +} + +variable "database_name" { + type = string +} diff --git a/pkg/resources/testdata/TestAcc_GrantOwnership/OnFuture_InSchema_ToAccountRole/test.tf b/pkg/resources/testdata/TestAcc_GrantOwnership/OnFuture_InSchema_ToAccountRole/test.tf new file mode 100644 index 0000000000..db037510a9 --- /dev/null +++ b/pkg/resources/testdata/TestAcc_GrantOwnership/OnFuture_InSchema_ToAccountRole/test.tf @@ -0,0 +1,22 @@ +resource "snowflake_role" "test" { + name = var.account_role_name +} + +resource "snowflake_database" "test" { + name = var.database_name +} + +resource "snowflake_schema" "test" { + name = var.schema_name + database = snowflake_database.test.name +} + +resource "snowflake_grant_ownership" "test" { + account_role_name = snowflake_role.test.name + on { + future { + object_type_plural = "TABLES" + in_schema = "\"${snowflake_database.test.name}\".\"${snowflake_schema.test.name}\"" + } + } +} diff --git a/pkg/resources/testdata/TestAcc_GrantOwnership/OnFuture_InSchema_ToAccountRole/variables.tf b/pkg/resources/testdata/TestAcc_GrantOwnership/OnFuture_InSchema_ToAccountRole/variables.tf new file mode 100644 index 0000000000..e86f7da400 --- /dev/null +++ b/pkg/resources/testdata/TestAcc_GrantOwnership/OnFuture_InSchema_ToAccountRole/variables.tf @@ -0,0 +1,11 @@ +variable "account_role_name" { + type = string +} + +variable "database_name" { + type = string +} + +variable "schema_name" { + type = string +} diff --git a/pkg/resources/testdata/TestAcc_GrantOwnership/OnObject_Database_ToAccountRole/test.tf b/pkg/resources/testdata/TestAcc_GrantOwnership/OnObject_Database_ToAccountRole/test.tf new file mode 100644 index 0000000000..a2c97c0a92 --- /dev/null +++ b/pkg/resources/testdata/TestAcc_GrantOwnership/OnObject_Database_ToAccountRole/test.tf @@ -0,0 +1,15 @@ +resource "snowflake_role" "test" { + name = var.account_role_name +} + +resource "snowflake_database" "test" { + name = var.database_name +} + +resource "snowflake_grant_ownership" "test" { + account_role_name = snowflake_role.test.name + on { + object_type = "DATABASE" + object_name = snowflake_database.test.name + } +} diff --git a/pkg/resources/testdata/TestAcc_GrantOwnership/OnObject_Database_ToAccountRole/variables.tf b/pkg/resources/testdata/TestAcc_GrantOwnership/OnObject_Database_ToAccountRole/variables.tf new file mode 100644 index 0000000000..8af99038ae --- /dev/null +++ b/pkg/resources/testdata/TestAcc_GrantOwnership/OnObject_Database_ToAccountRole/variables.tf @@ -0,0 +1,7 @@ +variable "account_role_name" { + type = string +} + +variable "database_name" { + type = string +} diff --git a/pkg/resources/testdata/TestAcc_GrantOwnership/OnObject_Schema_ToAccountRole/test.tf b/pkg/resources/testdata/TestAcc_GrantOwnership/OnObject_Schema_ToAccountRole/test.tf new file mode 100644 index 0000000000..20e43e8375 --- /dev/null +++ b/pkg/resources/testdata/TestAcc_GrantOwnership/OnObject_Schema_ToAccountRole/test.tf @@ -0,0 +1,20 @@ +resource "snowflake_role" "test" { + name = var.account_role_name +} + +resource "snowflake_database" "test" { + name = var.database_name +} + +resource "snowflake_schema" "test" { + name = var.schema_name + database = snowflake_database.test.name +} + +resource "snowflake_grant_ownership" "test" { + account_role_name = snowflake_role.test.name + on { + object_type = "SCHEMA" + object_name = "\"${snowflake_database.test.name}\".\"${snowflake_schema.test.name}\"" + } +} diff --git a/pkg/resources/testdata/TestAcc_GrantOwnership/OnObject_Schema_ToAccountRole/variables.tf b/pkg/resources/testdata/TestAcc_GrantOwnership/OnObject_Schema_ToAccountRole/variables.tf new file mode 100644 index 0000000000..e86f7da400 --- /dev/null +++ b/pkg/resources/testdata/TestAcc_GrantOwnership/OnObject_Schema_ToAccountRole/variables.tf @@ -0,0 +1,11 @@ +variable "account_role_name" { + type = string +} + +variable "database_name" { + type = string +} + +variable "schema_name" { + type = string +} diff --git a/pkg/resources/testdata/TestAcc_GrantOwnership/OnObject_Schema_ToDatabaseRole/test.tf b/pkg/resources/testdata/TestAcc_GrantOwnership/OnObject_Schema_ToDatabaseRole/test.tf new file mode 100644 index 0000000000..4b823e16a7 --- /dev/null +++ b/pkg/resources/testdata/TestAcc_GrantOwnership/OnObject_Schema_ToDatabaseRole/test.tf @@ -0,0 +1,21 @@ +resource "snowflake_database" "test" { + name = var.database_name +} + +resource "snowflake_database_role" "test" { + name = var.database_role_name + database = snowflake_database.test.name +} + +resource "snowflake_schema" "test" { + name = var.schema_name + database = snowflake_database.test.name +} + +resource "snowflake_grant_ownership" "test" { + database_role_name = "\"${snowflake_database.test.name}\".\"${snowflake_database_role.test.name}\"" + on { + object_type = "SCHEMA" + object_name = "\"${snowflake_database.test.name}\".\"${snowflake_schema.test.name}\"" + } +} diff --git a/pkg/resources/testdata/TestAcc_GrantOwnership/OnObject_Schema_ToDatabaseRole/variables.tf b/pkg/resources/testdata/TestAcc_GrantOwnership/OnObject_Schema_ToDatabaseRole/variables.tf new file mode 100644 index 0000000000..16d7031f51 --- /dev/null +++ b/pkg/resources/testdata/TestAcc_GrantOwnership/OnObject_Schema_ToDatabaseRole/variables.tf @@ -0,0 +1,11 @@ +variable "database_role_name" { + type = string +} + +variable "database_name" { + type = string +} + +variable "schema_name" { + type = string +} diff --git a/pkg/resources/testdata/TestAcc_GrantOwnership/OnObject_Table_ToAccountRole/test.tf b/pkg/resources/testdata/TestAcc_GrantOwnership/OnObject_Table_ToAccountRole/test.tf new file mode 100644 index 0000000000..cb0b31622e --- /dev/null +++ b/pkg/resources/testdata/TestAcc_GrantOwnership/OnObject_Table_ToAccountRole/test.tf @@ -0,0 +1,31 @@ +resource "snowflake_role" "test" { + name = var.account_role_name +} + +resource "snowflake_database" "test" { + name = var.database_name +} + +resource "snowflake_schema" "test" { + name = var.schema_name + database = snowflake_database.test.name +} + +resource "snowflake_table" "test" { + name = var.table_name + database = snowflake_database.test.name + schema = snowflake_schema.test.name + + column { + name = "id" + type = "NUMBER(38,0)" + } +} + +resource "snowflake_grant_ownership" "test" { + account_role_name = snowflake_role.test.name + on { + object_type = "TABLE" + object_name = "\"${snowflake_database.test.name}\".\"${snowflake_schema.test.name}\".\"${snowflake_table.test.name}\"" + } +} diff --git a/pkg/resources/testdata/TestAcc_GrantOwnership/OnObject_Table_ToAccountRole/variables.tf b/pkg/resources/testdata/TestAcc_GrantOwnership/OnObject_Table_ToAccountRole/variables.tf new file mode 100644 index 0000000000..4c7dd0d3d5 --- /dev/null +++ b/pkg/resources/testdata/TestAcc_GrantOwnership/OnObject_Table_ToAccountRole/variables.tf @@ -0,0 +1,15 @@ +variable "account_role_name" { + type = string +} + +variable "database_name" { + type = string +} + +variable "schema_name" { + type = string +} + +variable "table_name" { + type = string +} diff --git a/pkg/resources/testdata/TestAcc_GrantOwnership/OnObject_Table_ToDatabaseRole/test.tf b/pkg/resources/testdata/TestAcc_GrantOwnership/OnObject_Table_ToDatabaseRole/test.tf new file mode 100644 index 0000000000..d1646c0435 --- /dev/null +++ b/pkg/resources/testdata/TestAcc_GrantOwnership/OnObject_Table_ToDatabaseRole/test.tf @@ -0,0 +1,32 @@ +resource "snowflake_database" "test" { + name = var.database_name +} + +resource "snowflake_database_role" "test" { + name = var.database_role_name + database = snowflake_database.test.name +} + +resource "snowflake_schema" "test" { + name = var.schema_name + database = snowflake_database.test.name +} + +resource "snowflake_table" "test" { + name = var.table_name + database = snowflake_database.test.name + schema = snowflake_schema.test.name + + column { + name = "id" + type = "NUMBER(38,0)" + } +} + +resource "snowflake_grant_ownership" "test" { + database_role_name = "\"${snowflake_database.test.name}\".\"${snowflake_database_role.test.name}\"" + on { + object_type = "TABLE" + object_name = "\"${snowflake_database.test.name}\".\"${snowflake_schema.test.name}\".\"${snowflake_table.test.name}\"" + } +} diff --git a/pkg/resources/testdata/TestAcc_GrantOwnership/OnObject_Table_ToDatabaseRole/variables.tf b/pkg/resources/testdata/TestAcc_GrantOwnership/OnObject_Table_ToDatabaseRole/variables.tf new file mode 100644 index 0000000000..07cb81cba3 --- /dev/null +++ b/pkg/resources/testdata/TestAcc_GrantOwnership/OnObject_Table_ToDatabaseRole/variables.tf @@ -0,0 +1,15 @@ +variable "database_role_name" { + type = string +} + +variable "database_name" { + type = string +} + +variable "schema_name" { + type = string +} + +variable "table_name" { + type = string +} diff --git a/pkg/sdk/grants_validations.go b/pkg/sdk/grants_validations.go index 844580cc87..1df10d643e 100644 --- a/pkg/sdk/grants_validations.go +++ b/pkg/sdk/grants_validations.go @@ -17,6 +17,62 @@ var ( _ validatable = new(ShowGrantOptions) ) +var validGrantOwnershipObjectTypes = []ObjectType{ + ObjectTypeAggregationPolicy, + ObjectTypeAlert, + ObjectTypeAuthenticationPolicy, + ObjectTypeComputePool, + ObjectTypeDatabase, + ObjectTypeDatabaseRole, + ObjectTypeDynamicTable, + ObjectTypeEventTable, + ObjectTypeExternalTable, + ObjectTypeExternalVolume, + ObjectTypeFailoverGroup, + ObjectTypeFileFormat, + ObjectTypeFunction, + ObjectTypeHybridTable, + ObjectTypeIcebergTable, + ObjectTypeImageRepository, + ObjectTypeIntegration, + ObjectTypeMaterializedView, + ObjectTypeNetworkPolicy, + ObjectTypeNetworkRule, + ObjectTypePackagesPolicy, + ObjectTypePipe, + ObjectTypeProcedure, + ObjectTypeMaskingPolicy, + ObjectTypePasswordPolicy, + ObjectTypeProjectionPolicy, + ObjectTypeReplicationGroup, + ObjectTypeRole, + ObjectTypeRowAccessPolicy, + ObjectTypeSchema, + ObjectTypeSessionPolicy, + ObjectTypeSecret, + ObjectTypeSequence, + ObjectTypeStage, + ObjectTypeStream, + ObjectTypeTable, + ObjectTypeTag, + ObjectTypeTask, + ObjectTypeUser, + ObjectTypeView, + ObjectTypeWarehouse, +} + +var ( + ValidGrantOwnershipObjectTypesString = make([]string, len(validGrantOwnershipObjectTypes)) + ValidGrantOwnershipPluralObjectTypesString = make([]string, len(validGrantOwnershipObjectTypes)) +) + +func init() { + for i, objectType := range validGrantOwnershipObjectTypes { + ValidGrantOwnershipObjectTypesString[i] = objectType.String() + ValidGrantOwnershipPluralObjectTypesString[i] = objectType.Plural().String() + } +} + func (opts *GrantPrivilegesToAccountRoleOptions) validate() error { if opts == nil { return errors.Join(ErrNilOptions) diff --git a/pkg/sdk/object_types.go b/pkg/sdk/object_types.go index c829419eb3..bea1cc9381 100644 --- a/pkg/sdk/object_types.go +++ b/pkg/sdk/object_types.go @@ -15,54 +15,59 @@ type Object struct { type ObjectType string const ( - ObjectTypeAccount ObjectType = "ACCOUNT" - ObjectTypeManagedAccount ObjectType = "MANAGED ACCOUNT" - ObjectTypeUser ObjectType = "USER" - ObjectTypeDatabaseRole ObjectType = "DATABASE ROLE" - ObjectTypeRole ObjectType = "ROLE" - ObjectTypeIntegration ObjectType = "INTEGRATION" - ObjectTypeNetworkPolicy ObjectType = "NETWORK POLICY" - ObjectTypePasswordPolicy ObjectType = "PASSWORD POLICY" - ObjectTypeSessionPolicy ObjectType = "SESSION POLICY" - ObjectTypeReplicationGroup ObjectType = "REPLICATION GROUP" - ObjectTypeFailoverGroup ObjectType = "FAILOVER GROUP" - ObjectTypeConnection ObjectType = "CONNECTION" - ObjectTypeParameter ObjectType = "PARAMETER" - ObjectTypeWarehouse ObjectType = "WAREHOUSE" - ObjectTypeResourceMonitor ObjectType = "RESOURCE MONITOR" - ObjectTypeDatabase ObjectType = "DATABASE" - ObjectTypeSchema ObjectType = "SCHEMA" - ObjectTypeShare ObjectType = "SHARE" - ObjectTypeTable ObjectType = "TABLE" - ObjectTypeDynamicTable ObjectType = "DYNAMIC TABLE" - ObjectTypeExternalTable ObjectType = "EXTERNAL TABLE" - ObjectTypeEventTable ObjectType = "EVENT TABLE" - ObjectTypeView ObjectType = "VIEW" - ObjectTypeMaterializedView ObjectType = "MATERIALIZED VIEW" - ObjectTypeSequence ObjectType = "SEQUENCE" - ObjectTypeFunction ObjectType = "FUNCTION" - ObjectTypeExternalFunction ObjectType = "EXTERNAL FUNCTION" - ObjectTypeProcedure ObjectType = "PROCEDURE" - ObjectTypeStream ObjectType = "STREAM" - ObjectTypeTask ObjectType = "TASK" - ObjectTypeMaskingPolicy ObjectType = "MASKING POLICY" - ObjectTypeRowAccessPolicy ObjectType = "ROW ACCESS POLICY" - ObjectTypeTag ObjectType = "TAG" - ObjectTypeSecret ObjectType = "SECRET" - ObjectTypeStage ObjectType = "STAGE" - ObjectTypeFileFormat ObjectType = "FILE FORMAT" - ObjectTypePipe ObjectType = "PIPE" - ObjectTypeAlert ObjectType = "ALERT" - ObjectTypeApplication ObjectType = "APPLICATION" - ObjectTypeApplicationPackage ObjectType = "APPLICATION PACKAGE" - ObjectTypeApplicationRole ObjectType = "APPLICATION ROLE" - ObjectTypeStreamlit ObjectType = "STREAMLIT" - ObjectTypeColumn ObjectType = "COLUMN" - ObjectTypeIcebergTable ObjectType = "ICEBERG TABLE" - ObjectTypeExternalVolume ObjectType = "EXTERNAL VOLUME" - ObjectTypeNetworkRule ObjectType = "NETWORK RULE" - ObjectTypePackagesPolicy ObjectType = "PACKAGES POLICY" - ObjectTypeComputePool ObjectType = "COMPUTE POOL" + ObjectTypeAccount ObjectType = "ACCOUNT" + ObjectTypeManagedAccount ObjectType = "MANAGED ACCOUNT" + ObjectTypeUser ObjectType = "USER" + ObjectTypeDatabaseRole ObjectType = "DATABASE ROLE" + ObjectTypeRole ObjectType = "ROLE" + ObjectTypeIntegration ObjectType = "INTEGRATION" + ObjectTypeNetworkPolicy ObjectType = "NETWORK POLICY" + ObjectTypePasswordPolicy ObjectType = "PASSWORD POLICY" + ObjectTypeSessionPolicy ObjectType = "SESSION POLICY" + ObjectTypeReplicationGroup ObjectType = "REPLICATION GROUP" + ObjectTypeFailoverGroup ObjectType = "FAILOVER GROUP" + ObjectTypeConnection ObjectType = "CONNECTION" + ObjectTypeParameter ObjectType = "PARAMETER" + ObjectTypeWarehouse ObjectType = "WAREHOUSE" + ObjectTypeResourceMonitor ObjectType = "RESOURCE MONITOR" + ObjectTypeDatabase ObjectType = "DATABASE" + ObjectTypeSchema ObjectType = "SCHEMA" + ObjectTypeShare ObjectType = "SHARE" + ObjectTypeTable ObjectType = "TABLE" + ObjectTypeDynamicTable ObjectType = "DYNAMIC TABLE" + ObjectTypeExternalTable ObjectType = "EXTERNAL TABLE" + ObjectTypeEventTable ObjectType = "EVENT TABLE" + ObjectTypeView ObjectType = "VIEW" + ObjectTypeMaterializedView ObjectType = "MATERIALIZED VIEW" + ObjectTypeSequence ObjectType = "SEQUENCE" + ObjectTypeFunction ObjectType = "FUNCTION" + ObjectTypeExternalFunction ObjectType = "EXTERNAL FUNCTION" + ObjectTypeProcedure ObjectType = "PROCEDURE" + ObjectTypeStream ObjectType = "STREAM" + ObjectTypeTask ObjectType = "TASK" + ObjectTypeMaskingPolicy ObjectType = "MASKING POLICY" + ObjectTypeRowAccessPolicy ObjectType = "ROW ACCESS POLICY" + ObjectTypeTag ObjectType = "TAG" + ObjectTypeSecret ObjectType = "SECRET" + ObjectTypeStage ObjectType = "STAGE" + ObjectTypeFileFormat ObjectType = "FILE FORMAT" + ObjectTypePipe ObjectType = "PIPE" + ObjectTypeAlert ObjectType = "ALERT" + ObjectTypeApplication ObjectType = "APPLICATION" + ObjectTypeApplicationPackage ObjectType = "APPLICATION PACKAGE" + ObjectTypeApplicationRole ObjectType = "APPLICATION ROLE" + ObjectTypeStreamlit ObjectType = "STREAMLIT" + ObjectTypeColumn ObjectType = "COLUMN" + ObjectTypeIcebergTable ObjectType = "ICEBERG TABLE" + ObjectTypeExternalVolume ObjectType = "EXTERNAL VOLUME" + ObjectTypeNetworkRule ObjectType = "NETWORK RULE" + ObjectTypePackagesPolicy ObjectType = "PACKAGES POLICY" + ObjectTypeComputePool ObjectType = "COMPUTE POOL" + ObjectTypeAggregationPolicy ObjectType = "AGGREGATION POLICY" + ObjectTypeAuthenticationPolicy ObjectType = "AUTHENTICATION POLICY" + ObjectTypeHybridTable ObjectType = "HYBRID TABLE" + ObjectTypeImageRepository ObjectType = "IMAGE REPOSITORY" + ObjectTypeProjectionPolicy ObjectType = "PROJECTION POLICY" ) func (o ObjectType) String() string { @@ -71,53 +76,58 @@ func (o ObjectType) String() string { func objectTypeSingularToPluralMap() map[ObjectType]PluralObjectType { return map[ObjectType]PluralObjectType{ - ObjectTypeAccount: PluralObjectTypeAccounts, - ObjectTypeManagedAccount: PluralObjectTypeManagedAccounts, - ObjectTypeUser: PluralObjectTypeUsers, - ObjectTypeDatabaseRole: PluralObjectTypeDatabaseRoles, - ObjectTypeRole: PluralObjectTypeRoles, - ObjectTypeIntegration: PluralObjectTypeIntegrations, - ObjectTypeNetworkPolicy: PluralObjectTypeNetworkPolicies, - ObjectTypePasswordPolicy: PluralObjectTypePasswordPolicies, - ObjectTypeSessionPolicy: PluralObjectTypeSessionPolicies, - ObjectTypeReplicationGroup: PluralObjectTypeReplicationGroups, - ObjectTypeFailoverGroup: PluralObjectTypeFailoverGroups, - ObjectTypeConnection: PluralObjectTypeConnections, - ObjectTypeParameter: PluralObjectTypeParameters, - ObjectTypeWarehouse: PluralObjectTypeWarehouses, - ObjectTypeResourceMonitor: PluralObjectTypeResourceMonitors, - ObjectTypeDatabase: PluralObjectTypeDatabases, - ObjectTypeSchema: PluralObjectTypeSchemas, - ObjectTypeShare: PluralObjectTypeShares, - ObjectTypeTable: PluralObjectTypeTables, - ObjectTypeDynamicTable: PluralObjectTypeDynamicTables, - ObjectTypeExternalTable: PluralObjectTypeExternalTables, - ObjectTypeEventTable: PluralObjectTypeEventTables, - ObjectTypeView: PluralObjectTypeViews, - ObjectTypeMaterializedView: PluralObjectTypeMaterializedViews, - ObjectTypeSequence: PluralObjectTypeSequences, - ObjectTypeFunction: PluralObjectTypeFunctions, - ObjectTypeExternalFunction: PluralObjectTypeExternalFunctions, - ObjectTypeProcedure: PluralObjectTypeProcedures, - ObjectTypeStream: PluralObjectTypeStreams, - ObjectTypeTask: PluralObjectTypeTasks, - ObjectTypeMaskingPolicy: PluralObjectTypeMaskingPolicies, - ObjectTypeRowAccessPolicy: PluralObjectTypeRowAccessPolicies, - ObjectTypeTag: PluralObjectTypeTags, - ObjectTypeSecret: PluralObjectTypeSecrets, - ObjectTypeStage: PluralObjectTypeStages, - ObjectTypeFileFormat: PluralObjectTypeFileFormats, - ObjectTypePipe: PluralObjectTypePipes, - ObjectTypeAlert: PluralObjectTypeAlerts, - ObjectTypeApplication: PluralObjectTypeApplications, - ObjectTypeApplicationPackage: PluralObjectTypeApplicationPackages, - ObjectTypeApplicationRole: PluralObjectTypeApplicationRoles, - ObjectTypeStreamlit: PluralObjectTypeStreamlits, - ObjectTypeIcebergTable: PluralObjectTypeIcebergTables, - ObjectTypeExternalVolume: PluralObjectTypeExternalVolumes, - ObjectTypeNetworkRule: PluralObjectTypeNetworkRules, - ObjectTypePackagesPolicy: PluralObjectTypePackagesPolicies, - ObjectTypeComputePool: PluralObjectTypeComputePool, + ObjectTypeAccount: PluralObjectTypeAccounts, + ObjectTypeManagedAccount: PluralObjectTypeManagedAccounts, + ObjectTypeUser: PluralObjectTypeUsers, + ObjectTypeDatabaseRole: PluralObjectTypeDatabaseRoles, + ObjectTypeRole: PluralObjectTypeRoles, + ObjectTypeIntegration: PluralObjectTypeIntegrations, + ObjectTypeNetworkPolicy: PluralObjectTypeNetworkPolicies, + ObjectTypePasswordPolicy: PluralObjectTypePasswordPolicies, + ObjectTypeSessionPolicy: PluralObjectTypeSessionPolicies, + ObjectTypeReplicationGroup: PluralObjectTypeReplicationGroups, + ObjectTypeFailoverGroup: PluralObjectTypeFailoverGroups, + ObjectTypeConnection: PluralObjectTypeConnections, + ObjectTypeParameter: PluralObjectTypeParameters, + ObjectTypeWarehouse: PluralObjectTypeWarehouses, + ObjectTypeResourceMonitor: PluralObjectTypeResourceMonitors, + ObjectTypeDatabase: PluralObjectTypeDatabases, + ObjectTypeSchema: PluralObjectTypeSchemas, + ObjectTypeShare: PluralObjectTypeShares, + ObjectTypeTable: PluralObjectTypeTables, + ObjectTypeDynamicTable: PluralObjectTypeDynamicTables, + ObjectTypeExternalTable: PluralObjectTypeExternalTables, + ObjectTypeEventTable: PluralObjectTypeEventTables, + ObjectTypeView: PluralObjectTypeViews, + ObjectTypeMaterializedView: PluralObjectTypeMaterializedViews, + ObjectTypeSequence: PluralObjectTypeSequences, + ObjectTypeFunction: PluralObjectTypeFunctions, + ObjectTypeExternalFunction: PluralObjectTypeExternalFunctions, + ObjectTypeProcedure: PluralObjectTypeProcedures, + ObjectTypeStream: PluralObjectTypeStreams, + ObjectTypeTask: PluralObjectTypeTasks, + ObjectTypeMaskingPolicy: PluralObjectTypeMaskingPolicies, + ObjectTypeRowAccessPolicy: PluralObjectTypeRowAccessPolicies, + ObjectTypeTag: PluralObjectTypeTags, + ObjectTypeSecret: PluralObjectTypeSecrets, + ObjectTypeStage: PluralObjectTypeStages, + ObjectTypeFileFormat: PluralObjectTypeFileFormats, + ObjectTypePipe: PluralObjectTypePipes, + ObjectTypeAlert: PluralObjectTypeAlerts, + ObjectTypeApplication: PluralObjectTypeApplications, + ObjectTypeApplicationPackage: PluralObjectTypeApplicationPackages, + ObjectTypeApplicationRole: PluralObjectTypeApplicationRoles, + ObjectTypeStreamlit: PluralObjectTypeStreamlits, + ObjectTypeIcebergTable: PluralObjectTypeIcebergTables, + ObjectTypeExternalVolume: PluralObjectTypeExternalVolumes, + ObjectTypeNetworkRule: PluralObjectTypeNetworkRules, + ObjectTypePackagesPolicy: PluralObjectTypePackagesPolicies, + ObjectTypeComputePool: PluralObjectTypeComputePool, + ObjectTypeAggregationPolicy: PluralObjectTypeAggregationPolicies, + ObjectTypeAuthenticationPolicy: PluralObjectTypeAuthenticationPolicies, + ObjectTypeHybridTable: PluralObjectTypeHybridTables, + ObjectTypeImageRepository: PluralObjectTypeImageRepositories, + ObjectTypeProjectionPolicy: PluralObjectTypeProjectionPolicies, } } @@ -166,53 +176,58 @@ func (o ObjectType) GetObjectIdentifier(fullyQualifiedName string) ObjectIdentif type PluralObjectType string const ( - PluralObjectTypeAccounts PluralObjectType = "ACCOUNTS" - PluralObjectTypeManagedAccounts PluralObjectType = "MANAGED ACCOUNTS" - PluralObjectTypeUsers PluralObjectType = "USERS" - PluralObjectTypeDatabaseRoles PluralObjectType = "DATABASE ROLES" - PluralObjectTypeRoles PluralObjectType = "ROLES" - PluralObjectTypeIntegrations PluralObjectType = "INTEGRATIONS" - PluralObjectTypeNetworkPolicies PluralObjectType = "NETWORK POLICIES" - PluralObjectTypePasswordPolicies PluralObjectType = "PASSWORD POLICIES" - PluralObjectTypeSessionPolicies PluralObjectType = "SESSION POLICIES" - PluralObjectTypeReplicationGroups PluralObjectType = "REPLICATION GROUPS" - PluralObjectTypeFailoverGroups PluralObjectType = "FAILOVER GROUPS" - PluralObjectTypeConnections PluralObjectType = "CONNECTIONS" - PluralObjectTypeParameters PluralObjectType = "PARAMETERS" - PluralObjectTypeWarehouses PluralObjectType = "WAREHOUSES" - PluralObjectTypeResourceMonitors PluralObjectType = "RESOURCE MONITORS" - PluralObjectTypeDatabases PluralObjectType = "DATABASES" - PluralObjectTypeSchemas PluralObjectType = "SCHEMAS" - PluralObjectTypeShares PluralObjectType = "SHARES" - PluralObjectTypeTables PluralObjectType = "TABLES" - PluralObjectTypeDynamicTables PluralObjectType = "DYNAMIC TABLES" - PluralObjectTypeExternalTables PluralObjectType = "EXTERNAL TABLES" - PluralObjectTypeEventTables PluralObjectType = "EVENT TABLES" - PluralObjectTypeViews PluralObjectType = "VIEWS" - PluralObjectTypeMaterializedViews PluralObjectType = "MATERIALIZED VIEWS" - PluralObjectTypeSequences PluralObjectType = "SEQUENCES" - PluralObjectTypeFunctions PluralObjectType = "FUNCTIONS" - PluralObjectTypeExternalFunctions PluralObjectType = "EXTERNAL FUNCTIONS" - PluralObjectTypeProcedures PluralObjectType = "PROCEDURES" - PluralObjectTypeStreams PluralObjectType = "STREAMS" - PluralObjectTypeTasks PluralObjectType = "TASKS" - PluralObjectTypeMaskingPolicies PluralObjectType = "MASKING POLICIES" - PluralObjectTypeRowAccessPolicies PluralObjectType = "ROW ACCESS POLICIES" - PluralObjectTypeTags PluralObjectType = "TAGS" - PluralObjectTypeSecrets PluralObjectType = "SECRETS" - PluralObjectTypeStages PluralObjectType = "STAGES" - PluralObjectTypeFileFormats PluralObjectType = "FILE FORMATS" - PluralObjectTypePipes PluralObjectType = "PIPES" - PluralObjectTypeAlerts PluralObjectType = "ALERTS" - PluralObjectTypeApplications PluralObjectType = "APPLICATIONS" - PluralObjectTypeApplicationPackages PluralObjectType = "APPLICATION PACKAGES" - PluralObjectTypeApplicationRoles PluralObjectType = "APPLICATION ROLES" - PluralObjectTypeStreamlits PluralObjectType = "STREAMLITS" - PluralObjectTypeIcebergTables PluralObjectType = "ICEBERG TABLES" - PluralObjectTypeExternalVolumes PluralObjectType = "EXTERNAL VOLUMES" - PluralObjectTypeNetworkRules PluralObjectType = "NETWORK RULES" - PluralObjectTypePackagesPolicies PluralObjectType = "PACKAGES POLICIES" - PluralObjectTypeComputePool PluralObjectType = "COMPUTE POOLS" + PluralObjectTypeAccounts PluralObjectType = "ACCOUNTS" + PluralObjectTypeManagedAccounts PluralObjectType = "MANAGED ACCOUNTS" + PluralObjectTypeUsers PluralObjectType = "USERS" + PluralObjectTypeDatabaseRoles PluralObjectType = "DATABASE ROLES" + PluralObjectTypeRoles PluralObjectType = "ROLES" + PluralObjectTypeIntegrations PluralObjectType = "INTEGRATIONS" + PluralObjectTypeNetworkPolicies PluralObjectType = "NETWORK POLICIES" + PluralObjectTypePasswordPolicies PluralObjectType = "PASSWORD POLICIES" + PluralObjectTypeSessionPolicies PluralObjectType = "SESSION POLICIES" + PluralObjectTypeReplicationGroups PluralObjectType = "REPLICATION GROUPS" + PluralObjectTypeFailoverGroups PluralObjectType = "FAILOVER GROUPS" + PluralObjectTypeConnections PluralObjectType = "CONNECTIONS" + PluralObjectTypeParameters PluralObjectType = "PARAMETERS" + PluralObjectTypeWarehouses PluralObjectType = "WAREHOUSES" + PluralObjectTypeResourceMonitors PluralObjectType = "RESOURCE MONITORS" + PluralObjectTypeDatabases PluralObjectType = "DATABASES" + PluralObjectTypeSchemas PluralObjectType = "SCHEMAS" + PluralObjectTypeShares PluralObjectType = "SHARES" + PluralObjectTypeTables PluralObjectType = "TABLES" + PluralObjectTypeDynamicTables PluralObjectType = "DYNAMIC TABLES" + PluralObjectTypeExternalTables PluralObjectType = "EXTERNAL TABLES" + PluralObjectTypeEventTables PluralObjectType = "EVENT TABLES" + PluralObjectTypeViews PluralObjectType = "VIEWS" + PluralObjectTypeMaterializedViews PluralObjectType = "MATERIALIZED VIEWS" + PluralObjectTypeSequences PluralObjectType = "SEQUENCES" + PluralObjectTypeFunctions PluralObjectType = "FUNCTIONS" + PluralObjectTypeExternalFunctions PluralObjectType = "EXTERNAL FUNCTIONS" + PluralObjectTypeProcedures PluralObjectType = "PROCEDURES" + PluralObjectTypeStreams PluralObjectType = "STREAMS" + PluralObjectTypeTasks PluralObjectType = "TASKS" + PluralObjectTypeMaskingPolicies PluralObjectType = "MASKING POLICIES" + PluralObjectTypeRowAccessPolicies PluralObjectType = "ROW ACCESS POLICIES" + PluralObjectTypeTags PluralObjectType = "TAGS" + PluralObjectTypeSecrets PluralObjectType = "SECRETS" + PluralObjectTypeStages PluralObjectType = "STAGES" + PluralObjectTypeFileFormats PluralObjectType = "FILE FORMATS" + PluralObjectTypePipes PluralObjectType = "PIPES" + PluralObjectTypeAlerts PluralObjectType = "ALERTS" + PluralObjectTypeApplications PluralObjectType = "APPLICATIONS" + PluralObjectTypeApplicationPackages PluralObjectType = "APPLICATION PACKAGES" + PluralObjectTypeApplicationRoles PluralObjectType = "APPLICATION ROLES" + PluralObjectTypeStreamlits PluralObjectType = "STREAMLITS" + PluralObjectTypeIcebergTables PluralObjectType = "ICEBERG TABLES" + PluralObjectTypeExternalVolumes PluralObjectType = "EXTERNAL VOLUMES" + PluralObjectTypeNetworkRules PluralObjectType = "NETWORK RULES" + PluralObjectTypePackagesPolicies PluralObjectType = "PACKAGES POLICIES" + PluralObjectTypeComputePool PluralObjectType = "COMPUTE POOLS" + PluralObjectTypeAggregationPolicies PluralObjectType = "AGGREGATION POLICIES" + PluralObjectTypeAuthenticationPolicies PluralObjectType = "AUTHENTICATION POLICIES" + PluralObjectTypeHybridTables PluralObjectType = "HYBRID TABLES" + PluralObjectTypeImageRepositories PluralObjectType = "IMAGE REPOSITORIES" + PluralObjectTypeProjectionPolicies PluralObjectType = "PROJECTION POLICIES" ) func (p PluralObjectType) String() string { diff --git a/pkg/sdk/testint/helpers_test.go b/pkg/sdk/testint/helpers_test.go index 7f3033b8f3..f03257904f 100644 --- a/pkg/sdk/testint/helpers_test.go +++ b/pkg/sdk/testint/helpers_test.go @@ -620,7 +620,7 @@ func createFailoverGroup(t *testing.T, client *sdk.Client) (*sdk.FailoverGroup, func createFailoverGroupWithOptions(t *testing.T, client *sdk.Client, objectTypes []sdk.PluralObjectType, allowedAccounts []sdk.AccountIdentifier, opts *sdk.CreateFailoverGroupOptions) (*sdk.FailoverGroup, func()) { t.Helper() - id := sdk.RandomAccountObjectIdentifier() + id := sdk.RandomAlphanumericAccountObjectIdentifier() ctx := context.Background() err := client.FailoverGroups.Create(ctx, id, objectTypes, allowedAccounts, opts) require.NoError(t, err) diff --git a/pkg/sdk/testint/stages_gen_integration_test.go b/pkg/sdk/testint/stages_gen_integration_test.go index 1ff1327a69..08726549f4 100644 --- a/pkg/sdk/testint/stages_gen_integration_test.go +++ b/pkg/sdk/testint/stages_gen_integration_test.go @@ -346,6 +346,11 @@ func TestInt_Stages(t *testing.T) { stage, err := client.Stages.ShowByID(ctx, id) require.NoError(t, err) assertStage(t, stage, id, "EXTERNAL", "Updated comment", "AWS", awsBucketUrl, s3StorageIntegration.Name) + + props, err := client.Stages.Describe(ctx, id) + + require.NoError(t, err) + require.NotNil(t, props) }) t.Run("AlterExternalGCSStage", func(t *testing.T) { diff --git a/templates/resources/grant_privileges_to_account_role.md.tmpl b/templates/resources/grant_privileges_to_account_role.md.tmpl index d5168f46c6..f591bf021c 100644 --- a/templates/resources/grant_privileges_to_account_role.md.tmpl +++ b/templates/resources/grant_privileges_to_account_role.md.tmpl @@ -31,7 +31,7 @@ description: |- ## Import -~> **Note** All the ..._name parts should be fully qualified names, e.g. for schema object it is `""."".""` +~> **Note** All the ..._name parts should be fully qualified names (where every part is quoted), e.g. for schema object it is `""."".""` ~> **Note** To import all_privileges write ALL or ALL PRIVILEGES in place of `` Import is supported using the following syntax: diff --git a/templates/resources/grant_privileges_to_database_role.md.tmpl b/templates/resources/grant_privileges_to_database_role.md.tmpl index 471eb2f001..1f31f6ac18 100644 --- a/templates/resources/grant_privileges_to_database_role.md.tmpl +++ b/templates/resources/grant_privileges_to_database_role.md.tmpl @@ -29,7 +29,7 @@ description: |- ## Import -~> **Note** All the ..._name parts should be fully qualified names, e.g. for database object it is `"".""` +~> **Note** All the ..._name parts should be fully qualified names (where every part is quoted), e.g. for database object it is `"".""` ~> **Note** To import all_privileges write ALL or ALL PRIVILEGES in place of `` Import is supported using the following syntax: diff --git a/tmp/grant_ownership.md.tmpl b/tmp/grant_ownership.md.tmpl new file mode 100644 index 0000000000..3de22b6887 --- /dev/null +++ b/tmp/grant_ownership.md.tmpl @@ -0,0 +1,51 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "{{.Name}} {{.Type}} - {{.ProviderName}}" +subcategory: "" +description: |- +{{ if gt (len (split .Description "")) 1 -}} +{{ index (split .Description "") 1 | plainmarkdown | trimspace | prefixlines " " }} +{{- else -}} +{{ .Description | plainmarkdown | trimspace | prefixlines " " }} +{{- end }} +--- + +{{/* TODO: Cannot mark the documentation page as draft, so remove this after the resource is available */}} +!> **Warning** We're in a process of implementing this resource, so it's not available yet. + +~> **Note** This is a preview resource. It's ready for general use. In case of any errors, please file an issue in our GitHub repository. +~> **Note** For more details about granting ownership, please visit [`GRANT OWNERSHIP` Snowflake documentation page](https://docs.snowflake.com/en/sql-reference/sql/grant-ownership). + +{{/* TODO: Add note on how on_future works - could be also added to other grant resources with on_future option */}} +{{/* TODO: Add warnings If on_all option will be available - during delete user could transfer ownership of not intended objects due to it's nature */}} + +# {{.Name}} ({{.Type}}) + +{{ .Description | trimspace }} + +{{ if .HasExample -}} +## Example Usage + +{{ tffile (printf "examples/resources/%s/resource.tf" .Name)}} +{{- end }} + +{{ .SchemaMarkdown | trimspace }} + +## Import + +~> **Note** All the ..._name parts should be fully qualified names (where every part is quoted), e.g. for schema object it is `""."".""` + +Import is supported using the following syntax: + +`terraform import "||||"` + +where: +- role_type - string - type of granted role (either ToAccountRole or ToDatabaseRole) +- role_name - string - fully qualified identifier for either account role or database role (depending on the role_type) +- outbound_privileges_behavior - string - behavior specified for existing roles (can be either COPY or REVOKE) +- grant_type - enum +- grant_data - data dependent on grant_type + +It has varying number of parts, depending on grant_type. All the possible types are: + +{{ index (split (codefile "" .ImportFile) "```") 1 | trimspace }} diff --git a/tmp/snowflake_grant_ownership/import.sh b/tmp/snowflake_grant_ownership/import.sh new file mode 100644 index 0000000000..794f5b79e2 --- /dev/null +++ b/tmp/snowflake_grant_ownership/import.sh @@ -0,0 +1,41 @@ +### OnObject +`terraform import "|||OnObject||"` + +### OnAll (contains inner types: InDatabase | InSchema) + +#### InDatabase +`terraform import "|||OnAll||InDatabase|"` + +#### InSchema +`terraform import "|||OnAll||InSchema|"` + +### OnFuture (contains inner types: InDatabase | InSchema) + +#### InDatabase +`terraform import "|||OnFuture||InDatabase|"` + +#### InSchema +`terraform import "|||OnFuture||InSchema|"` + +### Import examples + +#### OnObject on Schema ToAccountRole +`terraform import "ToAccountRole|\"account_role\"|COPY|OnObject|SCHEMA|\"database_name\".\"schema_name\""` + +#### OnObject on Schema ToDatabaseRole +`terraform import "ToDatabaseRole|\"database_name\".\"database_role_name\"|COPY|OnObject|SCHEMA|\"database_name\".\"schema_name\""` + +#### OnObject on Table +`terraform import "ToAccountRole|\"account_role\"|COPY|OnObject|TABLE|\"database_name\".\"schema_name\".\"table_name\""` + +#### OnAll InDatabase +`terraform import "ToAccountRole|\"account_role\"|REVOKE|OnAll|TABLES|InDatabase|\"database_name\""` + +#### OnAll InSchema +`terraform import "ToAccountRole|\"account_role\"||OnAll|TABLES|InSchema|\"database_name\".\"schema_name\""` + +#### OnFuture InDatabase +`terraform import "ToAccountRole|\"account_role\"||OnFuture|TABLES|InDatabase|\"database_name\""` + +#### OnFuture InSchema +`terraform import "ToAccountRole|\"account_role\"|COPY|OnFuture|TABLES|InSchema|\"database_name\".\"schema_name\""` diff --git a/tmp/snowflake_grant_ownership/resource.tf b/tmp/snowflake_grant_ownership/resource.tf new file mode 100644 index 0000000000..e6c4f1cf51 --- /dev/null +++ b/tmp/snowflake_grant_ownership/resource.tf @@ -0,0 +1,151 @@ +################################## +### on object to account role +################################## + +resource "snowflake_role" "test" { + name = "test_role" +} + +resource "snowflake_database" "test" { + name = "test_database" +} + +resource "snowflake_schema" "test" { + name = "test_schema" + database = snowflake_database.test.name +} + +resource "snowflake_grant_ownership" "test" { + account_role_name = snowflake_role.test.name + outbound_privileges = "COPY" + on { + object_type = "SCHEMA" + object_name = "\"${snowflake_database.test.name}\".\"${snowflake_schema.test.name}\"" + } +} + +################################## +### on object to database role +################################## + +resource "snowflake_database" "test" { + name = "test_database" +} + +resource "snowflake_schema" "test" { + name = "test_schema" + database = snowflake_database.test.name +} + +resource "snowflake_database_role" "test" { + name = "test_database_role" + database = snowflake_database.test.name +} + +resource "snowflake_grant_ownership" "test" { + database_role_name = "\"${snowflake_database_role.test.database}\".\"${snowflake_database_role.test.name}\"" + outbound_privileges = "REVOKE" + on { + object_type = "SCHEMA" + object_name = "\"${snowflake_database.test.name}\".\"${snowflake_schema.test.name}\"" + } +} + +################################## +### on all tables in database to account role +################################## + +resource "snowflake_role" "test" { + name = "test_role" +} + +resource "snowflake_database" "test" { + name = "test_database" +} + +resource "snowflake_grant_ownership" "test" { + account_role_name = snowflake_role.test.name + on { + all { + plural_object_type = "TABLES" + in_database = snowflake_database.test.name + } + } +} + +################################## +### on all tables in schema to account role +################################## + +resource "snowflake_role" "test" { + name = "test_role" +} + +resource "snowflake_database" "test" { + name = "test_database" +} + +resource "snowflake_schema" "test" { + name = "test_schema" + database = snowflake_database.test.name +} + +resource "snowflake_grant_ownership" "test" { + account_role_name = snowflake_role.test.name + on { + all { + plural_object_type = "TABLES" + in_schema = "\"${snowflake_database.test.name}\".\"${snowflake_schema.test.name}\"" + } + } +} + +################################## +### on future tables in database to account role +################################## + +resource "snowflake_role" "test" { + name = "test_role" +} + +resource "snowflake_database" "test" { + name = "test_database" +} + +resource "snowflake_grant_ownership" "test" { + account_role_name = snowflake_role.test.name + on { + future { + plural_object_type = "TABLES" + in_database = snowflake_database.test.name + } + } +} + +################################## +### on future tables in schema to account role +################################## + +resource "snowflake_role" "test" { + name = "test_role" +} + +resource "snowflake_database" "test" { + name = "test_database" +} + +resource "snowflake_schema" "test" { + name = "test_schema" + database = snowflake_database.test.name +} + +resource "snowflake_grant_ownership" "test" { + account_role_name = snowflake_role.test.name + on { + future { + plural_object_type = "TABLES" + in_schema = "\"${snowflake_database.test.name}\".\"${snowflake_schema.test.name}\"" + } + } +} +