From fbc19a591f29439a815a3e55690fcf24cd76db7b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Cie=C5=9Blak?= Date: Wed, 28 Feb 2024 08:58:52 +0100 Subject: [PATCH 01/15] wip --- pkg/resources/grant_ownership.go | 1 + pkg/resources/grant_ownership_acceptance_test.go | 1 + pkg/resources/grant_ownership_identifier.go | 1 + pkg/resources/grant_ownership_identifier_test.go | 1 + 4 files changed, 4 insertions(+) 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 diff --git a/pkg/resources/grant_ownership.go b/pkg/resources/grant_ownership.go new file mode 100644 index 0000000000..18d6395a74 --- /dev/null +++ b/pkg/resources/grant_ownership.go @@ -0,0 +1 @@ +package resources diff --git a/pkg/resources/grant_ownership_acceptance_test.go b/pkg/resources/grant_ownership_acceptance_test.go new file mode 100644 index 0000000000..98394b732b --- /dev/null +++ b/pkg/resources/grant_ownership_acceptance_test.go @@ -0,0 +1 @@ +package resources_test diff --git a/pkg/resources/grant_ownership_identifier.go b/pkg/resources/grant_ownership_identifier.go new file mode 100644 index 0000000000..18d6395a74 --- /dev/null +++ b/pkg/resources/grant_ownership_identifier.go @@ -0,0 +1 @@ +package resources diff --git a/pkg/resources/grant_ownership_identifier_test.go b/pkg/resources/grant_ownership_identifier_test.go new file mode 100644 index 0000000000..18d6395a74 --- /dev/null +++ b/pkg/resources/grant_ownership_identifier_test.go @@ -0,0 +1 @@ +package resources From 8d4bcc7ce614131a94c98db1045bb7d6bdf4fc68 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Cie=C5=9Blak?= Date: Thu, 29 Feb 2024 11:28:39 +0100 Subject: [PATCH 02/15] wip --- pkg/provider/provider.go | 1 + pkg/resources/grant_ownership.go | 264 ++++++++++++++++ pkg/resources/grant_ownership_identifier.go | 129 ++++++++ .../grant_ownership_identifier_test.go | 197 ++++++++++++ .../grant_privileges_identifier_commons.go | 22 +- .../grant_privileges_to_database_role.go | 2 + pkg/sdk/object_types.go | 299 +++++++++--------- 7 files changed, 764 insertions(+), 150 deletions(-) diff --git a/pkg/provider/provider.go b/pkg/provider/provider.go index f11d1512c7..8964488338 100644 --- a/pkg/provider/provider.go +++ b/pkg/provider/provider.go @@ -480,6 +480,7 @@ func getResources() map[string]*schema.Resource { "snowflake_notification_integration": resources.NotificationIntegration(), "snowflake_oauth_integration": resources.OAuthIntegration(), "snowflake_object_parameter": resources.ObjectParameter(), + "snowflake_ownership": resources.GrantOwnership(), "snowflake_password_policy": resources.PasswordPolicy(), "snowflake_pipe": resources.Pipe(), "snowflake_procedure": resources.Procedure(), diff --git a/pkg/resources/grant_ownership.go b/pkg/resources/grant_ownership.go index 18d6395a74..150e572a5c 100644 --- a/pkg/resources/grant_ownership.go +++ b/pkg/resources/grant_ownership.go @@ -1 +1,265 @@ package resources + +import ( + "context" + "database/sql" + "fmt" + "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 validGrantOwnershipObjectTypes = []sdk.ObjectType{ + sdk.ObjectTypeAggregationPolicy, + sdk.ObjectTypeAlert, + sdk.ObjectTypeAuthenticationPolicy, + sdk.ObjectTypeComputePool, + sdk.ObjectTypeDatabase, + sdk.ObjectTypeDatabaseRole, + sdk.ObjectTypeDynamicTable, + sdk.ObjectTypeEventTable, + sdk.ObjectTypeExternalTable, + sdk.ObjectTypeExternalVolume, + sdk.ObjectTypeFailoverGroup, + sdk.ObjectTypeFileFormat, + sdk.ObjectTypeFunction, + sdk.ObjectTypeHybridTable, + sdk.ObjectTypeIcebergTable, + sdk.ObjectTypeImageRepository, + sdk.ObjectTypeIntegration, + sdk.ObjectTypeMaterializedView, + sdk.ObjectTypeNetworkPolicy, + sdk.ObjectTypeNetworkRule, + sdk.ObjectTypePackagesPolicy, + sdk.ObjectTypePipe, + sdk.ObjectTypeProcedure, + sdk.ObjectTypeMaskingPolicy, + sdk.ObjectTypePasswordPolicy, + sdk.ObjectTypeProjectionPolicy, + sdk.ObjectTypeRole, + sdk.ObjectTypeRowAccessPolicy, + sdk.ObjectTypeSchema, + sdk.ObjectTypeSessionPolicy, + sdk.ObjectTypeSecret, + sdk.ObjectTypeSequence, + sdk.ObjectTypeStage, + sdk.ObjectTypeStream, + sdk.ObjectTypeTable, + sdk.ObjectTypeTag, + sdk.ObjectTypeTask, + sdk.ObjectTypeUser, + sdk.ObjectTypeView, + sdk.ObjectTypeWarehouse, +} + +var validGrantOwnershipObjectTypesString []string +var validGrantOwnershipPluralObjectTypesString []string + +func init() { + validGrantOwnershipObjectTypesString = make([]string, len(validGrantOwnershipObjectTypes)) + validGrantOwnershipPluralObjectTypesString = make([]string, len(validGrantOwnershipObjectTypes)) + + for i, objectType := range validGrantOwnershipObjectTypes { + validGrantOwnershipObjectTypesString[i] = objectType.String() + validGrantOwnershipObjectTypesString[i] = objectType.Plural().String() + } +} + +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.", + ValidateFunc: validation.StringInSlice([]string{ + "COPY", + "REVOKE", + }, true), + }, + "on": { + Type: schema.TypeList, + Required: true, + ForceNew: true, + Description: "TODO", + MaxItems: 1, + ExactlyOneOf: []string{ + "on.0.object_name", + "on.0.all", + "on.0.future", + }, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "object_type": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + Description: "Specifies the type of object on which you are transferring ownership. Available values are: AGGREGATION POLICY | ALERT | AUTHENTICATION POLICY | COMPUTE POOL | DATABASE | DATABASE ROLE | DYNAMIC TABLE | EVENT TABLE | EXTERNAL TABLE | EXTERNAL VOLUME | FAILOVER GROUP | FILE FORMAT | FUNCTION | HYBRID TABLE | ICEBERG TABLE | IMAGE REPOSITORY | INTEGRATION | MATERIALIZED VIEW | NETWORK POLICY | NETWORK RULE | PACKAGES POLICY | PIPE | PROCEDURE | MASKING POLICY | PASSWORD POLICY | PROJECTION POLICY | REPLICATION GROUP | ROLE | ROW ACCESS POLICY | SCHEMA | SESSION POLICY | SECRET | SEQUENCE | STAGE | STREAM | TABLE | TAG | TASK | USER | VIEW | WAREHOUSE", + RequiredWith: []string{ + "on.0.object_name", + }, + ValidateFunc: validation.StringInSlice(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", + }, + }, + "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, + }, + ExactlyOneOf: []string{ + "on.0.all.0.in_database", + "on.0.all.0.in_schema", + }, + }, + "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, + }, + ExactlyOneOf: []string{ + "on.0.future.0.in_database", + "on.0.future.0.in_schema", + }, + }, + }, + }, + }, +} + +var grantOwnershipBulkOperationSchema = map[string]*schema.Schema{ + "object_type_plural": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + Description: "Specifies the type of object in plural form on which you are transferring ownership. Available values are: AGGREGATION POLICIES | ALERTS | AUTHENTICATION POLICIES | COMPUTE POOLS | DATABASES | DATABASE ROLES | DYNAMIC TABLES | EVENT TABLES | EXTERNAL TABLES | EXTERNAL VOLUMES | FAILOVER GROUPS | FILE FORMATS | FUNCTIONS | HYBRID TABLES | ICEBERG TABLES | IMAGE REPOSITORIES | INTEGRATIONS | MATERIALIZED VIEWS | NETWORK POLICIES | NETWORK RULES | PACKAGES POLICIES | PIPES | PROCEDURES | MASKING POLICIES | PASSWORD POLICIES | PROJECTION POLICIES | REPLICATION GROUPS | ROLES | ROW ACCESS POLICIES | SCHEMAS | SESSION POLICIES | SECRETS | SEQUENCES | STAGES | STREAMS | TABLES | TAGS | TASKS | USERS | VIEWS | WAREHOUSES", + ValidateFunc: validation.StringInSlice(validGrantOwnershipPluralObjectTypesString, true), + }, + "in_database": { + 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](), + }, +} + +func GrantOwnership() *schema.Resource { + return &schema.Resource{ + CreateContext: CreateGrantOwnership, + UpdateContext: UpdateGrantOwnership, + 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) { + return nil, nil + } +} + +func CreateGrantOwnership(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics { + //db := meta.(*sql.DB) + //client := sdk.NewClientFromDB(db) + + d.SetId("some id") + + return ReadGrantOwnership(ctx, d, meta) +} + +func UpdateGrantOwnership(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics { + + return ReadGrantOwnership(ctx, d, meta) +} + +func DeleteGrantOwnership(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics { + db := meta.(*sql.DB) + client := sdk.NewClientFromDB(db) + + id, err := ParseGrantPrivilegesToDatabaseRoleId(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.Error()), + }, + } + } + + err = client.Grants.RevokePrivilegesFromDatabaseRole( + ctx, + getDatabaseRolePrivilegesFromSchema(d), + getDatabaseRoleGrantOn(d), + id.DatabaseRoleName, + &sdk.RevokePrivilegesFromDatabaseRoleOptions{}, + ) + if err != nil { + return diag.Diagnostics{ + diag.Diagnostic{ + Severity: diag.Error, + Summary: "An error occurred when revoking privileges from database role", + Detail: fmt.Sprintf("Id: %s\nDatabase role name: %s\nError: %s", d.Id(), id.DatabaseRoleName, err.Error()), + }, + } + } + + d.SetId("") + + return nil +} + +func ReadGrantOwnership(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics { + + return nil +} diff --git a/pkg/resources/grant_ownership_identifier.go b/pkg/resources/grant_ownership_identifier.go index 18d6395a74..c7e75c5e54 100644 --- a/pkg/resources/grant_ownership_identifier.go +++ b/pkg/resources/grant_ownership_identifier.go @@ -1 +1,130 @@ package resources + +import ( + "fmt" + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/helpers" + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk" + "strings" +) + +type GrantOwnershipTargetRoleKind string + +const ( + ToAccountGrantOwnershipTargetRoleKind GrantOwnershipTargetRoleKind = "ToAccountRole" + ToDatabaseGrantOwnershipTargetRoleKind GrantOwnershipTargetRoleKind = "ToDatabaseRole" +) + +type OutboundPrivilegesBehavior string + +const ( + CopyOutboundPrivilegesBehavior OutboundPrivilegesBehavior = "COPY" + RevokeOutboundPrivilegesBehavior OutboundPrivilegesBehavior = "REVOKE" +) + +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) { + var grantOwnershipId 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", grantOwnershipId.GrantOwnershipTargetRoleKind)) + } + + if len(parts[2]) > 0 { + grantOwnershipId.OutboundPrivilegesBehavior = sdk.Pointer(OutboundPrivilegesBehavior(parts[2])) + } + + grantOwnershipId.Kind = GrantOwnershipKind(parts[3]) + switch grantOwnershipId.Kind { + case OnObjectGrantOwnershipKind: + if len(parts) != 6 { + return grantOwnershipId, sdk.NewError(`grant ownership identifier should hold 6 parts "|||OnObject||"`) + } + // TODO: Custom type for OnObject grant - because ObjectName can be pretty much any of the possible identifiers + grantOwnershipId.Data = &OnObjectGrantOwnershipData{ + ObjectType: sdk.ObjectType(parts[4]), + ObjectName: sdk.NewAccountObjectIdentifierFromFullyQualifiedName(parts[5]), // TODO: Fix should accept any identifier (most likely have to handle case by case for every object type) + } + case OnAllGrantOwnershipKind, OnFutureGrantOwnershipKind: + bulkOperationGrantData := &BulkOperationGrantData{ + ObjectNamePlural: sdk.PluralObjectType(parts[4]), + } + if len(parts) != 7 { + return grantOwnershipId, sdk.NewError(`grant ownership identifier should hold 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", bulkOperationGrantData.Kind)) + } + 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 index 18d6395a74..a4da884597 100644 --- a/pkg/resources/grant_ownership_identifier_test.go +++ b/pkg/resources/grant_ownership_identifier_test.go @@ -1 +1,198 @@ package resources + +import ( + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk" + "github.com/stretchr/testify/assert" + "testing" +) + +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"), + }, + }, + }, + // TODO: + //{ + // 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"), + // }, + // }, + //}, + } + + 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.Equal(t, tt.Expected, id) + } else { + assert.ErrorContains(t, err, tt.Error) + } + }) + } +} + +//func TestGrantOwnershipIdString(t *testing.T) { +// testCases := []struct { +// Name string +// Identifier GrantPrivilegesToAccountRoleId +// Expected string +// Error string +// }{ +// { +// Name: "grant account role on account", +// Identifier: GrantPrivilegesToAccountRoleId{ +// RoleName: sdk.NewAccountObjectIdentifier("account-role"), +// WithGrantOption: true, +// AllPrivileges: true, +// Kind: OnAccountAccountRoleGrantKind, +// AlwaysApply: true, +// Data: new(OnAccountGrantData), +// }, +// Expected: `"account-role"|true|true|ALL|OnAccount`, +// }, +// { +// Name: "grant account role on account object", +// Identifier: GrantPrivilegesToAccountRoleId{ +// RoleName: sdk.NewAccountObjectIdentifier("account-role"), +// WithGrantOption: true, +// AllPrivileges: true, +// Kind: OnAccountObjectAccountRoleGrantKind, +// AlwaysApply: true, +// Data: &OnAccountObjectGrantData{ +// ObjectType: sdk.ObjectTypeDatabase, +// ObjectName: sdk.NewAccountObjectIdentifier("database-name"), +// }, +// }, +// Expected: `"account-role"|true|true|ALL|OnAccountObject|DATABASE|"database-name"`, +// }, +// { +// Name: "grant account role on schema on schema", +// Identifier: GrantPrivilegesToAccountRoleId{ +// RoleName: sdk.NewAccountObjectIdentifier("account-role"), +// WithGrantOption: false, +// Privileges: []string{"CREATE SCHEMA", "USAGE", "MONITOR"}, +// Kind: OnSchemaAccountRoleGrantKind, +// Data: &OnSchemaGrantData{ +// Kind: OnSchemaSchemaGrantKind, +// SchemaName: sdk.Pointer(sdk.NewDatabaseObjectIdentifier("database-name", "schema-name")), +// }, +// }, +// Expected: `"account-role"|false|false|CREATE SCHEMA,USAGE,MONITOR|OnSchema|OnSchema|"database-name"."schema-name"`, +// }, +// { +// Name: "grant account role on all schemas in database", +// Identifier: GrantPrivilegesToAccountRoleId{ +// RoleName: sdk.NewAccountObjectIdentifier("account-role"), +// WithGrantOption: false, +// Privileges: []string{"CREATE SCHEMA", "USAGE", "MONITOR"}, +// Kind: OnSchemaAccountRoleGrantKind, +// Data: &OnSchemaGrantData{ +// Kind: OnAllSchemasInDatabaseSchemaGrantKind, +// DatabaseName: sdk.Pointer(sdk.NewAccountObjectIdentifier("database-name")), +// }, +// }, +// Expected: `"account-role"|false|false|CREATE SCHEMA,USAGE,MONITOR|OnSchema|OnAllSchemasInDatabase|"database-name"`, +// }, +// { +// Name: "grant account role on future schemas in database", +// Identifier: GrantPrivilegesToAccountRoleId{ +// RoleName: sdk.NewAccountObjectIdentifier("account-role"), +// WithGrantOption: false, +// Privileges: []string{"CREATE SCHEMA", "USAGE", "MONITOR"}, +// Kind: OnSchemaAccountRoleGrantKind, +// Data: &OnSchemaGrantData{ +// Kind: OnFutureSchemasInDatabaseSchemaGrantKind, +// DatabaseName: sdk.Pointer(sdk.NewAccountObjectIdentifier("database-name")), +// }, +// }, +// Expected: `"account-role"|false|false|CREATE SCHEMA,USAGE,MONITOR|OnSchema|OnFutureSchemasInDatabase|"database-name"`, +// }, +// { +// Name: "grant account role on schema object on object", +// Identifier: GrantPrivilegesToAccountRoleId{ +// RoleName: sdk.NewAccountObjectIdentifier("account-role"), +// WithGrantOption: false, +// Privileges: []string{"CREATE SCHEMA", "USAGE", "MONITOR"}, +// Kind: OnSchemaObjectAccountRoleGrantKind, +// Data: &OnSchemaObjectGrantData{ +// Kind: OnObjectSchemaObjectGrantKind, +// Object: &sdk.Object{ +// ObjectType: sdk.ObjectTypeTable, +// Name: sdk.NewSchemaObjectIdentifier("database-name", "schema-name", "table-name"), +// }, +// }, +// }, +// Expected: `"account-role"|false|false|CREATE SCHEMA,USAGE,MONITOR|OnSchemaObject|OnObject|TABLE|"database-name"."schema-name"."table-name"`, +// }, +// { +// Name: "grant account role on schema object on all tables in database", +// Identifier: GrantPrivilegesToAccountRoleId{ +// RoleName: sdk.NewAccountObjectIdentifier("account-role"), +// WithGrantOption: false, +// Privileges: []string{"CREATE SCHEMA", "USAGE", "MONITOR"}, +// Kind: OnSchemaObjectAccountRoleGrantKind, +// Data: &OnSchemaObjectGrantData{ +// Kind: OnAllSchemaObjectGrantKind, +// OnAllOrFuture: &BulkOperationGrantData{ +// ObjectNamePlural: sdk.PluralObjectTypeTables, +// Kind: InDatabaseBulkOperationGrantKind, +// Database: sdk.Pointer(sdk.NewAccountObjectIdentifier("database-name")), +// }, +// }, +// }, +// Expected: `"account-role"|false|false|CREATE SCHEMA,USAGE,MONITOR|OnSchemaObject|OnAll|TABLES|InDatabase|"database-name"`, +// }, +// { +// Name: "grant account role on schema object on all tables in schema", +// Identifier: GrantPrivilegesToAccountRoleId{ +// RoleName: sdk.NewAccountObjectIdentifier("account-role"), +// WithGrantOption: false, +// Privileges: []string{"CREATE SCHEMA", "USAGE", "MONITOR"}, +// Kind: OnSchemaObjectAccountRoleGrantKind, +// Data: &OnSchemaObjectGrantData{ +// Kind: OnAllSchemaObjectGrantKind, +// OnAllOrFuture: &BulkOperationGrantData{ +// ObjectNamePlural: sdk.PluralObjectTypeTables, +// Kind: InSchemaBulkOperationGrantKind, +// Schema: sdk.Pointer(sdk.NewDatabaseObjectIdentifier("database-name", "schema-name")), +// }, +// }, +// }, +// Expected: `"account-role"|false|false|CREATE SCHEMA,USAGE,MONITOR|OnSchemaObject|OnAll|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()) +// }) +// } +//} diff --git a/pkg/resources/grant_privileges_identifier_commons.go b/pkg/resources/grant_privileges_identifier_commons.go index e8057f7dfb..1db7cef17e 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, diff --git a/pkg/resources/grant_privileges_to_database_role.go b/pkg/resources/grant_privileges_to_database_role.go index 6608aa10d1..24161eafde 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: "TODO", ValidateDiagFunc: IsValidIdentifier[sdk.AccountObjectIdentifier](), }, "in_schema": { Type: schema.TypeString, Optional: true, ForceNew: true, + Description: "TODO", ValidateDiagFunc: IsValidIdentifier[sdk.DatabaseObjectIdentifier](), }, } 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 { From f46a01c79f413651f7f75f78736bcbece7ffa9f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Cie=C5=9Blak?= Date: Fri, 1 Mar 2024 10:01:03 +0100 Subject: [PATCH 03/15] wip --- pkg/resources/grant_ownership.go | 74 ++++++++-- pkg/resources/grant_ownership_identifier.go | 15 +- .../grant_ownership_identifier_test.go | 135 +++++++++++++++++- 3 files changed, 210 insertions(+), 14 deletions(-) diff --git a/pkg/resources/grant_ownership.go b/pkg/resources/grant_ownership.go index 150e572a5c..dd429999b9 100644 --- a/pkg/resources/grant_ownership.go +++ b/pkg/resources/grant_ownership.go @@ -4,6 +4,7 @@ import ( "context" "database/sql" "fmt" + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/internal/logging" "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" @@ -204,7 +205,65 @@ func GrantOwnership() *schema.Resource { func ImportGrantOwnership() schema.StateContextFunc { return func(ctx context.Context, d *schema.ResourceData, meta any) ([]*schema.ResourceData, error) { - return nil, nil + 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.FullyQualifiedName()); err != nil { + return nil, err + } + case ToDatabaseGrantOwnershipTargetRoleKind: + if err := d.Set("database_role_name", id.DatabaseRoleName.FullyQualifiedName()); err != nil { + return nil, err + } + } + + 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() + 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.FullyQualifiedName() + case InSchemaBulkOperationGrantKind: + onAllOrFuture["in_schema"] = data.Schema.FullyQualifiedName() + } + + switch id.Kind { + case OnAllGrantOwnershipKind: + on["all"] = onAllOrFuture + case OnFutureGrantOwnershipKind: + on["future"] = onAllOrFuture + } + + if err := d.Set("on", []any{on}); err != nil { + return nil, err + } + } + + return []*schema.ResourceData{d}, nil } } @@ -226,7 +285,7 @@ func DeleteGrantOwnership(ctx context.Context, d *schema.ResourceData, meta any) db := meta.(*sql.DB) client := sdk.NewClientFromDB(db) - id, err := ParseGrantPrivilegesToDatabaseRoleId(d.Id()) + id, err := ParseGrantOwnershipId(d.Id()) if err != nil { return diag.Diagnostics{ diag.Diagnostic{ @@ -237,18 +296,17 @@ func DeleteGrantOwnership(ctx context.Context, d *schema.ResourceData, meta any) } } - err = client.Grants.RevokePrivilegesFromDatabaseRole( + err = client.Grants.GrantOwnership( ctx, - getDatabaseRolePrivilegesFromSchema(d), - getDatabaseRoleGrantOn(d), - id.DatabaseRoleName, - &sdk.RevokePrivilegesFromDatabaseRoleOptions{}, + sdk.OwnershipGrantOn{}, + sdk.OwnershipGrantTo{}, + &sdk.GrantOwnershipOptions{}, ) if err != nil { return diag.Diagnostics{ diag.Diagnostic{ Severity: diag.Error, - Summary: "An error occurred when revoking privileges from database role", + Summary: "An error occurred when transferring ownership back to the original role", // TODO: do we save original role on create ? Detail: fmt.Sprintf("Id: %s\nDatabase role name: %s\nError: %s", d.Id(), id.DatabaseRoleName, err.Error()), }, } diff --git a/pkg/resources/grant_ownership_identifier.go b/pkg/resources/grant_ownership_identifier.go index c7e75c5e54..bc7b82c930 100644 --- a/pkg/resources/grant_ownership_identifier.go +++ b/pkg/resources/grant_ownership_identifier.go @@ -87,18 +87,23 @@ func ParseGrantOwnershipId(id string) (GrantOwnershipId, error) { case ToDatabaseGrantOwnershipTargetRoleKind: grantOwnershipId.DatabaseRoleName = sdk.NewDatabaseObjectIdentifierFromFullyQualifiedName(parts[1]) default: - return grantOwnershipId, sdk.NewError(fmt.Sprintf("unknown GrantOwnershipTargetRoleKind: %v", grantOwnershipId.GrantOwnershipTargetRoleKind)) + return grantOwnershipId, sdk.NewError(fmt.Sprintf("unknown GrantOwnershipTargetRoleKind: %v, valid options are %v | %v", grantOwnershipId.GrantOwnershipTargetRoleKind, ToAccountGrantOwnershipTargetRoleKind, ToDatabaseGrantOwnershipTargetRoleKind)) } if len(parts[2]) > 0 { - grantOwnershipId.OutboundPrivilegesBehavior = sdk.Pointer(OutboundPrivilegesBehavior(parts[2])) + 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 hold 6 parts "|||OnObject||"`) + return grantOwnershipId, sdk.NewError(`grant ownership identifier should consist of 6 parts "|||OnObject||"`) } // TODO: Custom type for OnObject grant - because ObjectName can be pretty much any of the possible identifiers grantOwnershipId.Data = &OnObjectGrantOwnershipData{ @@ -110,7 +115,7 @@ func ParseGrantOwnershipId(id string) (GrantOwnershipId, error) { ObjectNamePlural: sdk.PluralObjectType(parts[4]), } if len(parts) != 7 { - return grantOwnershipId, sdk.NewError(`grant ownership identifier should hold 7 parts "|||On[All or Future]||In[Database or Schema]|"`) + 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 { @@ -119,7 +124,7 @@ func ParseGrantOwnershipId(id string) (GrantOwnershipId, error) { case InSchemaBulkOperationGrantKind: bulkOperationGrantData.Schema = sdk.Pointer(sdk.NewDatabaseObjectIdentifierFromFullyQualifiedName(parts[6])) default: - return grantOwnershipId, sdk.NewError(fmt.Sprintf("invalid BulkOperationGrantKind: %s", bulkOperationGrantData.Kind)) + return grantOwnershipId, sdk.NewError(fmt.Sprintf("invalid BulkOperationGrantKind: %s, valid options are %v | %v", bulkOperationGrantData.Kind, InDatabaseBulkOperationGrantKind, InSchemaBulkOperationGrantKind)) } grantOwnershipId.Data = bulkOperationGrantData default: diff --git a/pkg/resources/grant_ownership_identifier_test.go b/pkg/resources/grant_ownership_identifier_test.go index a4da884597..b4642bdcce 100644 --- a/pkg/resources/grant_ownership_identifier_test.go +++ b/pkg/resources/grant_ownership_identifier_test.go @@ -27,7 +27,7 @@ func TestParseGrantOwnershipId(t *testing.T) { }, }, }, - // TODO: + // TODO: Won't work because we can expect one type of identifiers right now (adjust to chose id case-by-case based on object type) //{ // Name: "grant ownership on schema to account role", // Identifier: `ToAccountRole|"account-role"|COPY|OnObject|SCHEMA|"database-name"."schema-name"`, @@ -42,6 +42,139 @@ func TestParseGrantOwnershipId(t *testing.T) { // }, // }, //}, + // TODO: Won't work because we can expect one type of identifiers right now (adjust to chose id case-by-case based on object type) + { + 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(panic because of bad identifiers): validation: OnAll in database - missing database identifier", + // Identifier: `ToAccountRole|"account-role"|COPY|OnAll|InvalidTarget|InDatabase|`, + // Error: "TODO", + //}, + //{ + // Name: "TODO(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(panic because of bad identifiers): validation: OnFuture in database - missing database identifier", + // Identifier: `ToAccountRole|"account-role"|COPY|OnFuture|InvalidTarget|InDatabase|`, + // Error: "TODO", + //}, + //{ + // Name: "TODO(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 { From afce5222b0bc0b873844eabb9a0c66240e0a4330 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Cie=C5=9Blak?= Date: Mon, 4 Mar 2024 08:16:21 +0100 Subject: [PATCH 04/15] wip --- pkg/resources/grant_ownership.go | 234 +++++++++++++++++- pkg/resources/grant_ownership_identifier.go | 11 + .../grant_privileges_to_account_role.go | 28 +-- 3 files changed, 247 insertions(+), 26 deletions(-) diff --git a/pkg/resources/grant_ownership.go b/pkg/resources/grant_ownership.go index dd429999b9..7afb4d32ec 100644 --- a/pkg/resources/grant_ownership.go +++ b/pkg/resources/grant_ownership.go @@ -9,6 +9,8 @@ import ( "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" + "log" + "strings" ) var validGrantOwnershipObjectTypes = []sdk.ObjectType{ @@ -192,7 +194,6 @@ var grantOwnershipBulkOperationSchema = map[string]*schema.Schema{ func GrantOwnership() *schema.Resource { return &schema.Resource{ CreateContext: CreateGrantOwnership, - UpdateContext: UpdateGrantOwnership, DeleteContext: DeleteGrantOwnership, ReadContext: ReadGrantOwnership, @@ -268,15 +269,30 @@ func ImportGrantOwnership() schema.StateContextFunc { } func CreateGrantOwnership(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics { - //db := meta.(*sql.DB) - //client := sdk.NewClientFromDB(db) + db := meta.(*sql.DB) + client := sdk.NewClientFromDB(db) - d.SetId("some id") + id := createGrantOwnershipIdFromSchema(d) + logging.DebugLogger.Printf("[DEBUG] created identifier from schema: %s", id.String()) - return ReadGrantOwnership(ctx, d, meta) -} + err := client.Grants.GrantOwnership( + ctx, + getOwnershipGrantOn(d), + getOwnershipGrantTo(d), + getOwnershipGrantOpts(id), + ) + if err != nil { + return diag.Diagnostics{ + diag.Diagnostic{ + Severity: diag.Error, + Summary: fmt.Sprintf("An error occurred when transferring ownership %s", id.GrantOwnershipTargetRoleKind), + Detail: fmt.Sprintf("Id: %s\nDatabase role name: %s\nError: %s", id.String(), id.RoleName, err), + }, + } + } -func UpdateGrantOwnership(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics { + logging.DebugLogger.Printf("[DEBUG] Setting identifier to %s", id.String()) + d.SetId(id.String()) return ReadGrantOwnership(ctx, d, meta) } @@ -291,23 +307,25 @@ func DeleteGrantOwnership(ctx context.Context, d *schema.ResourceData, meta any) 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), }, } } + // TODO: Prepare a special case for on_future branch, because then we should call `revoke ownership on future from ...` + err = client.Grants.GrantOwnership( ctx, - sdk.OwnershipGrantOn{}, - sdk.OwnershipGrantTo{}, - &sdk.GrantOwnershipOptions{}, + getOwnershipGrantOn(d), + getOwnershipGrantTo(d), + 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", // TODO: do we save original role on create ? - Detail: fmt.Sprintf("Id: %s\nDatabase role name: %s\nError: %s", d.Id(), id.DatabaseRoleName, err.Error()), + Detail: fmt.Sprintf("Id: %s\nDatabase role name: %s\nError: %s", d.Id(), id.DatabaseRoleName, err), }, } } @@ -318,6 +336,198 @@ func DeleteGrantOwnership(ctx context.Context, d *schema.ResourceData, meta any) } 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 + } + + db := meta.(*sql.DB) + logging.DebugLogger.Printf("[DEBUG] Creating new client from db") + client := sdk.NewClientFromDB(db) + + logging.DebugLogger.Printf("[DEBUG] About to show grants") + 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 } + +func getOwnershipGrantOn(d *schema.ResourceData) sdk.OwnershipGrantOn { + var ownershipGrantOn sdk.OwnershipGrantOn + + on := d.Get("on").([]any)[0].(map[string]any) + onObjectType, onObjectTypeOk := on["object_type"] + _, onObjectNameOk := on["object_name"] + onAll, onAllOk := on["all"] + onFuture, onFutureOk := on["future"] + + switch { + case onObjectTypeOk && onObjectNameOk: + ownershipGrantOn.Object = &sdk.Object{ + ObjectType: sdk.ObjectType(strings.ToUpper(onObjectType.(string))), + Name: nil, // TODO: Any identifier type + } + case onAllOk: + ownershipGrantOn.All = getGrantOnSchemaObjectIn(onAll.([]any)[0].(map[string]any)) + case onFutureOk: + ownershipGrantOn.Future = getGrantOnSchemaObjectIn(onFuture.([]any)[0].(map[string]any)) + } + + return ownershipGrantOn +} + +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 { + outboundPrivileges := id.OutboundPrivilegesBehavior.ToOwnershipCurrentGrantsOutboundPrivileges() + if id.OutboundPrivilegesBehavior != nil && outboundPrivileges != nil { + return &sdk.GrantOwnershipOptions{ + CurrentGrants: &sdk.OwnershipCurrentGrants{ + OutboundPrivileges: *outboundPrivileges, + }, + } + } + + return new(sdk.GrantOwnershipOptions) +} + +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) + 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 { + 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) + } + } + + return id +} diff --git a/pkg/resources/grant_ownership_identifier.go b/pkg/resources/grant_ownership_identifier.go index bc7b82c930..09da6e366f 100644 --- a/pkg/resources/grant_ownership_identifier.go +++ b/pkg/resources/grant_ownership_identifier.go @@ -21,6 +21,17 @@ const ( 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 ( 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), }, } } From 28b9ea5ea780593e03aebf0b408626a1a2038f40 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Cie=C5=9Blak?= Date: Mon, 4 Mar 2024 15:13:25 +0100 Subject: [PATCH 05/15] wip --- pkg/provider/provider.go | 2 +- pkg/resources/grant_ownership.go | 95 +++++++++++-------- .../grant_ownership_acceptance_test.go | 50 ++++++++++ pkg/resources/grant_ownership_identifier.go | 4 +- .../TestAcc_GrantOwnership/OnObject/test.tf | 15 +++ .../OnObject/variables.tf | 7 ++ 6 files changed, 128 insertions(+), 45 deletions(-) create mode 100644 pkg/resources/testdata/TestAcc_GrantOwnership/OnObject/test.tf create mode 100644 pkg/resources/testdata/TestAcc_GrantOwnership/OnObject/variables.tf diff --git a/pkg/provider/provider.go b/pkg/provider/provider.go index 8964488338..6a0f8042b8 100644 --- a/pkg/provider/provider.go +++ b/pkg/provider/provider.go @@ -468,6 +468,7 @@ func getResources() map[string]*schema.Resource { "snowflake_function": resources.Function(), "snowflake_grant_account_role": resources.GrantAccountRole(), "snowflake_grant_database_role": resources.GrantDatabaseRole(), + "snowflake_grant_ownership": resources.GrantOwnership(), "snowflake_grant_privileges_to_role": resources.GrantPrivilegesToRole(), "snowflake_grant_privileges_to_account_role": resources.GrantPrivilegesToAccountRole(), "snowflake_grant_privileges_to_database_role": resources.GrantPrivilegesToDatabaseRole(), @@ -480,7 +481,6 @@ func getResources() map[string]*schema.Resource { "snowflake_notification_integration": resources.NotificationIntegration(), "snowflake_oauth_integration": resources.OAuthIntegration(), "snowflake_object_parameter": resources.ObjectParameter(), - "snowflake_ownership": resources.GrantOwnership(), "snowflake_password_policy": resources.PasswordPolicy(), "snowflake_pipe": resources.Pipe(), "snowflake_procedure": resources.Procedure(), diff --git a/pkg/resources/grant_ownership.go b/pkg/resources/grant_ownership.go index 7afb4d32ec..b0a1af4fbf 100644 --- a/pkg/resources/grant_ownership.go +++ b/pkg/resources/grant_ownership.go @@ -56,16 +56,15 @@ var validGrantOwnershipObjectTypes = []sdk.ObjectType{ sdk.ObjectTypeWarehouse, } -var validGrantOwnershipObjectTypesString []string -var validGrantOwnershipPluralObjectTypesString []string - -func init() { - validGrantOwnershipObjectTypesString = make([]string, len(validGrantOwnershipObjectTypes)) +var ( + validGrantOwnershipObjectTypesString = make([]string, len(validGrantOwnershipObjectTypes)) validGrantOwnershipPluralObjectTypesString = make([]string, len(validGrantOwnershipObjectTypes)) +) +func init() { for i, objectType := range validGrantOwnershipObjectTypes { validGrantOwnershipObjectTypesString[i] = objectType.String() - validGrantOwnershipObjectTypesString[i] = objectType.Plural().String() + validGrantOwnershipPluralObjectTypesString[i] = objectType.Plural().String() } } @@ -108,11 +107,6 @@ var grantOwnershipSchema = map[string]*schema.Schema{ ForceNew: true, Description: "TODO", MaxItems: 1, - ExactlyOneOf: []string{ - "on.0.object_name", - "on.0.all", - "on.0.future", - }, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "object_type": { @@ -133,6 +127,11 @@ var grantOwnershipSchema = map[string]*schema.Schema{ RequiredWith: []string{ "on.0.object_type", }, + ExactlyOneOf: []string{ + "on.0.object_name", + "on.0.all", + "on.0.future", + }, }, "all": { Type: schema.TypeList, @@ -141,11 +140,12 @@ var grantOwnershipSchema = map[string]*schema.Schema{ Description: "Configures the privilege to be granted on all objects in either a database or schema.", MaxItems: 1, Elem: &schema.Resource{ - Schema: grantOwnershipBulkOperationSchema, + Schema: grantOwnershipBulkOperationSchema("all"), }, ExactlyOneOf: []string{ - "on.0.all.0.in_database", - "on.0.all.0.in_schema", + "on.0.object_name", + "on.0.all", + "on.0.future", }, }, "future": { @@ -155,11 +155,12 @@ var grantOwnershipSchema = map[string]*schema.Schema{ Description: "Configures the privilege to be granted on all objects in either a database or schema.", MaxItems: 1, Elem: &schema.Resource{ - Schema: grantOwnershipBulkOperationSchema, + Schema: grantOwnershipBulkOperationSchema("future"), }, ExactlyOneOf: []string{ - "on.0.future.0.in_database", - "on.0.future.0.in_schema", + "on.0.object_name", + "on.0.all", + "on.0.future", }, }, }, @@ -167,28 +168,38 @@ var grantOwnershipSchema = map[string]*schema.Schema{ }, } -var grantOwnershipBulkOperationSchema = map[string]*schema.Schema{ - "object_type_plural": { - Type: schema.TypeString, - Required: true, - ForceNew: true, - Description: "Specifies the type of object in plural form on which you are transferring ownership. Available values are: AGGREGATION POLICIES | ALERTS | AUTHENTICATION POLICIES | COMPUTE POOLS | DATABASES | DATABASE ROLES | DYNAMIC TABLES | EVENT TABLES | EXTERNAL TABLES | EXTERNAL VOLUMES | FAILOVER GROUPS | FILE FORMATS | FUNCTIONS | HYBRID TABLES | ICEBERG TABLES | IMAGE REPOSITORIES | INTEGRATIONS | MATERIALIZED VIEWS | NETWORK POLICIES | NETWORK RULES | PACKAGES POLICIES | PIPES | PROCEDURES | MASKING POLICIES | PASSWORD POLICIES | PROJECTION POLICIES | REPLICATION GROUPS | ROLES | ROW ACCESS POLICIES | SCHEMAS | SESSION POLICIES | SECRETS | SEQUENCES | STAGES | STREAMS | TABLES | TAGS | TASKS | USERS | VIEWS | WAREHOUSES", - ValidateFunc: validation.StringInSlice(validGrantOwnershipPluralObjectTypesString, true), - }, - "in_database": { - 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](), - }, +func grantOwnershipBulkOperationSchema(branchName string) map[string]*schema.Schema { + return map[string]*schema.Schema{ + "object_type_plural": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + Description: "Specifies the type of object in plural form on which you are transferring ownership. Available values are: AGGREGATION POLICIES | ALERTS | AUTHENTICATION POLICIES | COMPUTE POOLS | DATABASES | DATABASE ROLES | DYNAMIC TABLES | EVENT TABLES | EXTERNAL TABLES | EXTERNAL VOLUMES | FAILOVER GROUPS | FILE FORMATS | FUNCTIONS | HYBRID TABLES | ICEBERG TABLES | IMAGE REPOSITORIES | INTEGRATIONS | MATERIALIZED VIEWS | NETWORK POLICIES | NETWORK RULES | PACKAGES POLICIES | PIPES | PROCEDURES | MASKING POLICIES | PASSWORD POLICIES | PROJECTION POLICIES | REPLICATION GROUPS | ROLES | ROW ACCESS POLICIES | SCHEMAS | SESSION POLICIES | SECRETS | SEQUENCES | STAGES | STREAMS | TABLES | TAGS | TASKS | USERS | VIEWS | WAREHOUSES", + ValidateFunc: validation.StringInSlice(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 { @@ -285,8 +296,8 @@ func CreateGrantOwnership(ctx context.Context, d *schema.ResourceData, meta any) return diag.Diagnostics{ diag.Diagnostic{ Severity: diag.Error, - Summary: fmt.Sprintf("An error occurred when transferring ownership %s", id.GrantOwnershipTargetRoleKind), - Detail: fmt.Sprintf("Id: %s\nDatabase role name: %s\nError: %s", id.String(), id.RoleName, err), + Summary: "An error occurred during grant ownership", + Detail: fmt.Sprintf("Id: %s\nError: %s", id.String(), err), }, } } @@ -463,7 +474,7 @@ func getOwnershipGrantOpts(id *GrantOwnershipId) *sdk.GrantOwnershipOptions { return new(sdk.GrantOwnershipOptions) } -func prepareShowGrantsRequestForGrantOwnership(id GrantOwnershipId) (*sdk.ShowGrantOptions, sdk.ObjectType) { +func prepareShowGrantsRequestForGrantOwnership(id *GrantOwnershipId) (*sdk.ShowGrantOptions, sdk.ObjectType) { opts := new(sdk.ShowGrantOptions) var grantedOn sdk.ObjectType diff --git a/pkg/resources/grant_ownership_acceptance_test.go b/pkg/resources/grant_ownership_acceptance_test.go index 98394b732b..97bcbca206 100644 --- a/pkg/resources/grant_ownership_acceptance_test.go +++ b/pkg/resources/grant_ownership_acceptance_test.go @@ -1 +1,51 @@ package resources_test + +import ( + "fmt" + 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" + "strings" + "testing" +) + +func TestAcc_GrantOwnership_OnObject(t *testing.T) { + databaseName := strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha)) + accountRoleName := sdk.NewAccountObjectIdentifier(strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha))).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), + }, + CheckDestroy: nil, // TODO + Steps: []resource.TestStep{ + { + ConfigDirectory: acc.ConfigurationDirectory("TestAcc_GrantOwnership/OnObject"), + 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", accountRoleName, databaseName)), + ), + }, + { + ConfigDirectory: acc.ConfigurationDirectory("TestAcc_GrantOwnership/OnObject"), + ConfigVariables: configVariables, + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} diff --git a/pkg/resources/grant_ownership_identifier.go b/pkg/resources/grant_ownership_identifier.go index 09da6e366f..4f29417e57 100644 --- a/pkg/resources/grant_ownership_identifier.go +++ b/pkg/resources/grant_ownership_identifier.go @@ -83,8 +83,8 @@ func (g *GrantOwnershipId) String() string { return strings.Join(parts, helpers.IDDelimiter) } -func ParseGrantOwnershipId(id string) (GrantOwnershipId, error) { - var grantOwnershipId GrantOwnershipId +func ParseGrantOwnershipId(id string) (*GrantOwnershipId, error) { + grantOwnershipId := new(GrantOwnershipId) parts := strings.Split(id, helpers.IDDelimiter) if len(parts) < 5 { diff --git a/pkg/resources/testdata/TestAcc_GrantOwnership/OnObject/test.tf b/pkg/resources/testdata/TestAcc_GrantOwnership/OnObject/test.tf new file mode 100644 index 0000000000..a2c97c0a92 --- /dev/null +++ b/pkg/resources/testdata/TestAcc_GrantOwnership/OnObject/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/variables.tf b/pkg/resources/testdata/TestAcc_GrantOwnership/OnObject/variables.tf new file mode 100644 index 0000000000..8af99038ae --- /dev/null +++ b/pkg/resources/testdata/TestAcc_GrantOwnership/OnObject/variables.tf @@ -0,0 +1,7 @@ +variable "account_role_name" { + type = string +} + +variable "database_name" { + type = string +} From e3a48ee7004d9311820afc1cf60de9a999eb6cb7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Cie=C5=9Blak?= Date: Wed, 6 Mar 2024 11:18:49 +0100 Subject: [PATCH 06/15] wip --- docs/resources/grant_ownership.md | 64 ++++++ .../grant_privileges_to_database_role.md | 8 +- .../resource_migration.md | 2 +- pkg/resources/grant_ownership.go | 182 +++++++++++++----- .../grant_ownership_acceptance_test.go | 91 ++++++++- pkg/resources/grant_ownership_identifier.go | 3 +- .../grant_ownership_identifier_test.go | 3 +- .../TestAcc_GrantOwnership/OnAll/test.tf | 17 ++ .../TestAcc_GrantOwnership/OnAll/variables.tf | 7 + .../TestAcc_GrantOwnership/OnFuture/test.tf | 17 ++ .../OnFuture/variables.tf | 7 + pkg/sdk/testint/helpers_test.go | 2 +- .../testint/stages_gen_integration_test.go | 5 + 13 files changed, 349 insertions(+), 59 deletions(-) create mode 100644 docs/resources/grant_ownership.md create mode 100644 pkg/resources/testdata/TestAcc_GrantOwnership/OnAll/test.tf create mode 100644 pkg/resources/testdata/TestAcc_GrantOwnership/OnAll/variables.tf create mode 100644 pkg/resources/testdata/TestAcc_GrantOwnership/OnFuture/test.tf create mode 100644 pkg/resources/testdata/TestAcc_GrantOwnership/OnFuture/variables.tf diff --git a/docs/resources/grant_ownership.md b/docs/resources/grant_ownership.md new file mode 100644 index 0000000000..4ae87bdf1f --- /dev/null +++ b/docs/resources/grant_ownership.md @@ -0,0 +1,64 @@ +--- +page_title: "snowflake_grant_ownership Resource - terraform-provider-snowflake" +subcategory: "" +description: |- + +--- + +# snowflake_grant_ownership (Resource) + + + + + + +## Schema + +### Required + +- `on` (Block List, Min: 1, Max: 1) TODO (see [below for nested schema](#nestedblock--on)) + +### Optional + +- `account_role_name` (String) The fully qualified name of the account role to which privileges will be granted. +- `database_role_name` (String) The fully qualified name of the database role to which privileges will be granted. +- `outbound_privileges` (String) 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. + +### Read-Only + +- `id` (String) The ID of this resource. + + +### Nested Schema for `on` + +Optional: + +- `all` (Block List, Max: 1) Configures the privilege to be granted on all objects in either a database or schema. (see [below for nested schema](#nestedblock--on--all)) +- `future` (Block List, Max: 1) Configures the privilege to be granted on all objects in either a database or schema. (see [below for nested schema](#nestedblock--on--future)) +- `object_name` (String) Specifies the identifier for the object on which you are transferring ownership. +- `object_type` (String) Specifies the type of object on which you are transferring ownership. Available values are: AGGREGATION POLICY | ALERT | AUTHENTICATION POLICY | COMPUTE POOL | DATABASE | DATABASE ROLE | DYNAMIC TABLE | EVENT TABLE | EXTERNAL TABLE | EXTERNAL VOLUME | FAILOVER GROUP | FILE FORMAT | FUNCTION | HYBRID TABLE | ICEBERG TABLE | IMAGE REPOSITORY | INTEGRATION | MATERIALIZED VIEW | NETWORK POLICY | NETWORK RULE | PACKAGES POLICY | PIPE | PROCEDURE | MASKING POLICY | PASSWORD POLICY | PROJECTION POLICY | REPLICATION GROUP | ROLE | ROW ACCESS POLICY | SCHEMA | SESSION POLICY | SECRET | SEQUENCE | STAGE | STREAM | TABLE | TAG | TASK | USER | VIEW | WAREHOUSE + + +### Nested Schema for `on.all` + +Required: + +- `object_type_plural` (String) Specifies the type of object in plural form on which you are transferring ownership. Available values are: AGGREGATION POLICIES | ALERTS | AUTHENTICATION POLICIES | COMPUTE POOLS | DATABASES | DATABASE ROLES | DYNAMIC TABLES | EVENT TABLES | EXTERNAL TABLES | EXTERNAL VOLUMES | FAILOVER GROUPS | FILE FORMATS | FUNCTIONS | HYBRID TABLES | ICEBERG TABLES | IMAGE REPOSITORIES | INTEGRATIONS | MATERIALIZED VIEWS | NETWORK POLICIES | NETWORK RULES | PACKAGES POLICIES | PIPES | PROCEDURES | MASKING POLICIES | PASSWORD POLICIES | PROJECTION POLICIES | REPLICATION GROUPS | ROLES | ROW ACCESS POLICIES | SCHEMAS | SESSION POLICIES | SECRETS | SEQUENCES | STAGES | STREAMS | TABLES | TAGS | TASKS | USERS | VIEWS | WAREHOUSES + +Optional: + +- `in_database` (String) The fully qualified name of the database. +- `in_schema` (String) The fully qualified name of the schema. + + + +### Nested Schema for `on.future` + +Required: + +- `object_type_plural` (String) Specifies the type of object in plural form on which you are transferring ownership. Available values are: AGGREGATION POLICIES | ALERTS | AUTHENTICATION POLICIES | COMPUTE POOLS | DATABASES | DATABASE ROLES | DYNAMIC TABLES | EVENT TABLES | EXTERNAL TABLES | EXTERNAL VOLUMES | FAILOVER GROUPS | FILE FORMATS | FUNCTIONS | HYBRID TABLES | ICEBERG TABLES | IMAGE REPOSITORIES | INTEGRATIONS | MATERIALIZED VIEWS | NETWORK POLICIES | NETWORK RULES | PACKAGES POLICIES | PIPES | PROCEDURES | MASKING POLICIES | PASSWORD POLICIES | PROJECTION POLICIES | REPLICATION GROUPS | ROLES | ROW ACCESS POLICIES | SCHEMAS | SESSION POLICIES | SECRETS | SEQUENCES | STAGES | STREAMS | TABLES | TAGS | TASKS | USERS | VIEWS | WAREHOUSES + +Optional: + +- `in_database` (String) The fully qualified name of the database. +- `in_schema` (String) The fully qualified name of the schema. diff --git a/docs/resources/grant_privileges_to_database_role.md b/docs/resources/grant_privileges_to_database_role.md index 104bcdc9ab..d718b7121f 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) TODO +- `in_schema` (String) TODO @@ -230,8 +230,8 @@ Required: Optional: -- `in_database` (String) -- `in_schema` (String) +- `in_database` (String) TODO +- `in_schema` (String) TODO ## Import 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 index b0a1af4fbf..b3aafce71c 100644 --- a/pkg/resources/grant_ownership.go +++ b/pkg/resources/grant_ownership.go @@ -2,15 +2,16 @@ package resources import ( "context" - "database/sql" "fmt" + "log" + "strings" + "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" - "log" - "strings" ) var validGrantOwnershipObjectTypes = []sdk.ObjectType{ @@ -226,7 +227,7 @@ func ImportGrantOwnership() schema.StateContextFunc { switch id.GrantOwnershipTargetRoleKind { case ToAccountGrantOwnershipTargetRoleKind: - if err := d.Set("account_role_name", id.AccountRoleName.FullyQualifiedName()); err != nil { + if err := d.Set("account_role_name", id.AccountRoleName.Name()); err != nil { return nil, err } case ToDatabaseGrantOwnershipTargetRoleKind: @@ -235,8 +236,10 @@ func ImportGrantOwnership() schema.StateContextFunc { } } - if err := d.Set("outbound_privileges", id.OutboundPrivilegesBehavior); 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 { @@ -245,7 +248,11 @@ func ImportGrantOwnership() schema.StateContextFunc { onObject := make(map[string]any) onObject["object_type"] = data.ObjectType.String() - onObject["object_name"] = data.ObjectName.FullyQualifiedName() + 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 @@ -258,16 +265,16 @@ func ImportGrantOwnership() schema.StateContextFunc { onAllOrFuture["object_type_plural"] = data.ObjectNamePlural.String() switch data.Kind { case InDatabaseBulkOperationGrantKind: - onAllOrFuture["in_database"] = data.Database.FullyQualifiedName() + onAllOrFuture["in_database"] = data.Database.Name() case InSchemaBulkOperationGrantKind: onAllOrFuture["in_schema"] = data.Schema.FullyQualifiedName() } switch id.Kind { case OnAllGrantOwnershipKind: - on["all"] = onAllOrFuture + on["all"] = []any{onAllOrFuture} case OnFutureGrantOwnershipKind: - on["future"] = onAllOrFuture + on["future"] = []any{onAllOrFuture} } if err := d.Set("on", []any{on}); err != nil { @@ -280,8 +287,7 @@ func ImportGrantOwnership() schema.StateContextFunc { } func CreateGrantOwnership(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics { - db := meta.(*sql.DB) - client := sdk.NewClientFromDB(db) + client := meta.(*provider.Context).Client id := createGrantOwnershipIdFromSchema(d) logging.DebugLogger.Printf("[DEBUG] created identifier from schema: %s", id.String()) @@ -309,8 +315,7 @@ func CreateGrantOwnership(ctx context.Context, d *schema.ResourceData, meta any) } func DeleteGrantOwnership(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics { - db := meta.(*sql.DB) - client := sdk.NewClientFromDB(db) + client := meta.(*provider.Context).Client id, err := ParseGrantOwnershipId(d.Id()) if err != nil { @@ -325,20 +330,26 @@ func DeleteGrantOwnership(ctx context.Context, d *schema.ResourceData, meta any) // TODO: Prepare a special case for on_future branch, because then we should call `revoke ownership on future from ...` - err = client.Grants.GrantOwnership( - ctx, - getOwnershipGrantOn(d), - getOwnershipGrantTo(d), - 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", // TODO: do we save original role on create ? - Detail: fmt.Sprintf("Id: %s\nDatabase role name: %s\nError: %s", d.Id(), id.DatabaseRoleName, err), - }, + grantOwnershipOn := getOwnershipGrantOn(d) + + if grantOwnershipOn.Future != nil { + } else { + err = client.Grants.GrantOwnership( + ctx, + grantOwnershipOn, + getOwnershipGrantTo(d), + 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", // TODO: do we save original role on create ? + Detail: fmt.Sprintf("Id: %s\nRole name: %s\nError: %s", d.Id(), id.DatabaseRoleName, err), // TODO Role name + }, + } } + } d.SetId("") @@ -363,9 +374,7 @@ func ReadGrantOwnership(ctx context.Context, d *schema.ResourceData, meta any) d return nil } - db := meta.(*sql.DB) - logging.DebugLogger.Printf("[DEBUG] Creating new client from db") - client := sdk.NewClientFromDB(db) + client := meta.(*provider.Context).Client logging.DebugLogger.Printf("[DEBUG] About to show grants") grants, err := client.Grants.Show(ctx, opts) @@ -423,25 +432,81 @@ func ReadGrantOwnership(ctx context.Context, d *schema.ResourceData, meta any) d return nil } +func getOnObjectIdentifier(objectType sdk.ObjectType) (sdk.ObjectIdentifier, error) { + switch objectType { + // sdk.AccountObjectIdentifier + case sdk.ObjectTypeComputePool, + sdk.ObjectTypeDatabase, + sdk.ObjectTypeExternalVolume, + sdk.ObjectTypeFailoverGroup, + sdk.ObjectTypeIntegration, + sdk.ObjectTypeNetworkPolicy, + sdk.ObjectTypeReplicationGroup, + sdk.ObjectTypeRole, + sdk.ObjectTypeUser, + sdk.ObjectTypeWarehouse: + return nil, nil + + // sdk.DatabaseObjectIdentifier + case sdk.ObjectTypeDatabaseRole, + sdk.ObjectTypeSchema: + return nil, nil + + // sdk.SchemaObjectIdentifier + 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: + return nil, nil + default: + return nil, nil + } +} + func getOwnershipGrantOn(d *schema.ResourceData) sdk.OwnershipGrantOn { var ownershipGrantOn sdk.OwnershipGrantOn on := d.Get("on").([]any)[0].(map[string]any) - onObjectType, onObjectTypeOk := on["object_type"] - _, onObjectNameOk := on["object_name"] - onAll, onAllOk := on["all"] - onFuture, onFutureOk := on["future"] + onObjectType := on["object_type"].(string) + onObjectName := on["object_name"].(string) + onAll := on["all"].([]any) + onFuture := on["future"].([]any) switch { - case onObjectTypeOk && onObjectNameOk: + case len(onObjectType) > 0 && len(onObjectName) > 0: ownershipGrantOn.Object = &sdk.Object{ - ObjectType: sdk.ObjectType(strings.ToUpper(onObjectType.(string))), - Name: nil, // TODO: Any identifier type + ObjectType: sdk.ObjectType(strings.ToUpper(onObjectType)), + Name: sdk.NewAccountObjectIdentifier(onObjectName), // TODO: Any identifier type } - case onAllOk: - ownershipGrantOn.All = getGrantOnSchemaObjectIn(onAll.([]any)[0].(map[string]any)) - case onFutureOk: - ownershipGrantOn.Future = getGrantOnSchemaObjectIn(onFuture.([]any)[0].(map[string]any)) + 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 @@ -462,12 +527,14 @@ func getOwnershipGrantTo(d *schema.ResourceData) sdk.OwnershipGrantTo { } func getOwnershipGrantOpts(id *GrantOwnershipId) *sdk.GrantOwnershipOptions { - outboundPrivileges := id.OutboundPrivilegesBehavior.ToOwnershipCurrentGrantsOutboundPrivileges() - if id.OutboundPrivilegesBehavior != nil && outboundPrivileges != nil { - return &sdk.GrantOwnershipOptions{ - CurrentGrants: &sdk.OwnershipCurrentGrants{ - OutboundPrivileges: *outboundPrivileges, - }, + if id.OutboundPrivilegesBehavior != nil { + outboundPrivileges := id.OutboundPrivilegesBehavior.ToOwnershipCurrentGrantsOutboundPrivileges() + if outboundPrivileges != nil { + return &sdk.GrantOwnershipOptions{ + CurrentGrants: &sdk.OwnershipCurrentGrants{ + OutboundPrivileges: *outboundPrivileges, + }, + } } } @@ -540,5 +607,26 @@ func createGrantOwnershipIdFromSchema(d *schema.ResourceData) *GrantOwnershipId } } + 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 + id.Data = &OnObjectGrantOwnershipData{ + ObjectType: sdk.ObjectType(objectType), + ObjectName: sdk.NewAccountObjectIdentifier(objectName), // TODO: Other identifier types + } + case len(all) > 0: + id.Kind = OnAllGrantOwnershipKind + id.Data = getBulkOperationGrantData(getGrantOnSchemaObjectIn(all[0].(map[string]any))) // TODO Fix + case len(future) > 0: + id.Kind = OnFutureGrantOwnershipKind + id.Data = getBulkOperationGrantData(getGrantOnSchemaObjectIn(future[0].(map[string]any))) // TODO Fix + } + return id } diff --git a/pkg/resources/grant_ownership_acceptance_test.go b/pkg/resources/grant_ownership_acceptance_test.go index 97bcbca206..fa72c27a04 100644 --- a/pkg/resources/grant_ownership_acceptance_test.go +++ b/pkg/resources/grant_ownership_acceptance_test.go @@ -2,19 +2,22 @@ package resources_test import ( "fmt" + "strings" + "testing" + 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" - "strings" - "testing" ) func TestAcc_GrantOwnership_OnObject(t *testing.T) { databaseName := strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha)) - accountRoleName := sdk.NewAccountObjectIdentifier(strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha))).FullyQualifiedName() + 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), @@ -36,7 +39,7 @@ func TestAcc_GrantOwnership_OnObject(t *testing.T) { 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", accountRoleName, databaseName)), + resource.TestCheckResourceAttr(resourceName, "id", fmt.Sprintf("ToAccountRole|%s||OnObject|DATABASE|%s", accountRoleFullyQualifiedName, databaseFullyQualifiedName)), ), }, { @@ -49,3 +52,83 @@ func TestAcc_GrantOwnership_OnObject(t *testing.T) { }, }) } + +//func TestAcc_GrantOwnership_OnAll(t *testing.T) { +// 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), +// }, +// CheckDestroy: nil, // TODO +// Steps: []resource.TestStep{ +// { +// ConfigDirectory: acc.ConfigurationDirectory("TestAcc_GrantOwnership/OnAll"), +// 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.object_name", databaseName), +// resource.TestCheckResourceAttr(resourceName, "id", fmt.Sprintf("ToAccountRole|%s||OnObject|DATABASE|%s", accountRoleFullyQualifiedName, databaseFullyQualifiedName)), +// ), +// }, +// { +// ConfigDirectory: acc.ConfigurationDirectory("TestAcc_GrantOwnership/OnAll"), +// ConfigVariables: configVariables, +// ResourceName: resourceName, +// ImportState: true, +// ImportStateVerify: true, +// }, +// }, +// }) +//} + +func TestAcc_GrantOwnership_OnFuture(t *testing.T) { + 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), + }, + CheckDestroy: nil, // TODO + Steps: []resource.TestStep{ + { + ConfigDirectory: acc.ConfigurationDirectory("TestAcc_GrantOwnership/OnFuture"), + ConfigVariables: configVariables, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "account_role_name", accountRoleName), + resource.TestCheckResourceAttr(resourceName, "on.0.future.0.object_type_plural", "VIEWS"), + resource.TestCheckResourceAttr(resourceName, "on.0.future.0.in_database", databaseName), + resource.TestCheckResourceAttr(resourceName, "id", fmt.Sprintf("ToAccountRole|%s||OnFuture|VIEWS|InDatabase|%s", accountRoleFullyQualifiedName, databaseFullyQualifiedName)), + ), + }, + { + ConfigDirectory: acc.ConfigurationDirectory("TestAcc_GrantOwnership/OnFuture"), + ConfigVariables: configVariables, + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} diff --git a/pkg/resources/grant_ownership_identifier.go b/pkg/resources/grant_ownership_identifier.go index 4f29417e57..1b1260fcf6 100644 --- a/pkg/resources/grant_ownership_identifier.go +++ b/pkg/resources/grant_ownership_identifier.go @@ -2,9 +2,10 @@ package resources import ( "fmt" + "strings" + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/helpers" "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk" - "strings" ) type GrantOwnershipTargetRoleKind string diff --git a/pkg/resources/grant_ownership_identifier_test.go b/pkg/resources/grant_ownership_identifier_test.go index b4642bdcce..4bf4cc4b2a 100644 --- a/pkg/resources/grant_ownership_identifier_test.go +++ b/pkg/resources/grant_ownership_identifier_test.go @@ -1,9 +1,10 @@ package resources import ( + "testing" + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk" "github.com/stretchr/testify/assert" - "testing" ) func TestParseGrantOwnershipId(t *testing.T) { diff --git a/pkg/resources/testdata/TestAcc_GrantOwnership/OnAll/test.tf b/pkg/resources/testdata/TestAcc_GrantOwnership/OnAll/test.tf new file mode 100644 index 0000000000..f42ac1c3c5 --- /dev/null +++ b/pkg/resources/testdata/TestAcc_GrantOwnership/OnAll/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 { + all { + object_type_plural = "TABLES" + in_database = snowflake_database.test.name + } + } +} diff --git a/pkg/resources/testdata/TestAcc_GrantOwnership/OnAll/variables.tf b/pkg/resources/testdata/TestAcc_GrantOwnership/OnAll/variables.tf new file mode 100644 index 0000000000..8af99038ae --- /dev/null +++ b/pkg/resources/testdata/TestAcc_GrantOwnership/OnAll/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/test.tf b/pkg/resources/testdata/TestAcc_GrantOwnership/OnFuture/test.tf new file mode 100644 index 0000000000..630bf850d5 --- /dev/null +++ b/pkg/resources/testdata/TestAcc_GrantOwnership/OnFuture/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 = "VIEWS" + in_database = snowflake_database.test.name + } + } +} diff --git a/pkg/resources/testdata/TestAcc_GrantOwnership/OnFuture/variables.tf b/pkg/resources/testdata/TestAcc_GrantOwnership/OnFuture/variables.tf new file mode 100644 index 0000000000..8af99038ae --- /dev/null +++ b/pkg/resources/testdata/TestAcc_GrantOwnership/OnFuture/variables.tf @@ -0,0 +1,7 @@ +variable "account_role_name" { + type = string +} + +variable "database_name" { + type = string +} diff --git a/pkg/sdk/testint/helpers_test.go b/pkg/sdk/testint/helpers_test.go index 7f3033b8f3..b969f0cab1 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.NewAccountObjectIdentifier(random.AlphaN(20)) 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) { From 20364c2909597d9f8df833a9f88fdf8a07579ab3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Cie=C5=9Blak?= Date: Wed, 6 Mar 2024 14:22:22 +0100 Subject: [PATCH 07/15] wip --- pkg/resources/grant_ownership.go | 55 ++++++++++++---- pkg/resources/grant_ownership_test.go | 95 +++++++++++++++++++++++++++ 2 files changed, 136 insertions(+), 14 deletions(-) create mode 100644 pkg/resources/grant_ownership_test.go diff --git a/pkg/resources/grant_ownership.go b/pkg/resources/grant_ownership.go index b3aafce71c..9214f24cbe 100644 --- a/pkg/resources/grant_ownership.go +++ b/pkg/resources/grant_ownership.go @@ -3,6 +3,7 @@ package resources import ( "context" "fmt" + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/helpers" "log" "strings" @@ -292,9 +293,14 @@ func CreateGrantOwnership(ctx context.Context, d *schema.ResourceData, meta any) id := createGrantOwnershipIdFromSchema(d) logging.DebugLogger.Printf("[DEBUG] created identifier from schema: %s", id.String()) - err := client.Grants.GrantOwnership( + grantOn, err := getOwnershipGrantOn(d) + if err != nil { + return diag.FromErr(err) + } + + err = client.Grants.GrantOwnership( ctx, - getOwnershipGrantOn(d), + grantOn, getOwnershipGrantTo(d), getOwnershipGrantOpts(id), ) @@ -330,13 +336,16 @@ func DeleteGrantOwnership(ctx context.Context, d *schema.ResourceData, meta any) // TODO: Prepare a special case for on_future branch, because then we should call `revoke ownership on future from ...` - grantOwnershipOn := getOwnershipGrantOn(d) + grantOn, err := getOwnershipGrantOn(d) + if err != nil { + return diag.FromErr(err) + } - if grantOwnershipOn.Future != nil { + if grantOn.Future != nil { } else { err = client.Grants.GrantOwnership( ctx, - grantOwnershipOn, + grantOn, getOwnershipGrantTo(d), getOwnershipGrantOpts(id), ) @@ -432,7 +441,12 @@ func ReadGrantOwnership(ctx context.Context, d *schema.ResourceData, meta any) d return nil } -func getOnObjectIdentifier(objectType sdk.ObjectType) (sdk.ObjectIdentifier, error) { +func getOnObjectIdentifier(objectType sdk.ObjectType, objectName string) (sdk.ObjectIdentifier, error) { + identifier, err := helpers.DecodeSnowflakeParameterID(objectName) + if err != nil { + return nil, err + } + switch objectType { // sdk.AccountObjectIdentifier case sdk.ObjectTypeComputePool, @@ -445,12 +459,16 @@ func getOnObjectIdentifier(objectType sdk.ObjectType) (sdk.ObjectIdentifier, err sdk.ObjectTypeRole, sdk.ObjectTypeUser, sdk.ObjectTypeWarehouse: - return nil, nil + if _, ok := identifier.(sdk.AccountObjectIdentifier); !ok { + return nil, sdk.NewError(fmt.Sprintf("invalid object_name %s, expected account object identifier", objectName)) + } // sdk.DatabaseObjectIdentifier case sdk.ObjectTypeDatabaseRole, sdk.ObjectTypeSchema: - return nil, nil + if _, ok := identifier.(sdk.DatabaseObjectIdentifier); !ok { + return nil, sdk.NewError(fmt.Sprintf("invalid object_name %s, expected database object identifier", objectName)) + } // sdk.SchemaObjectIdentifier case sdk.ObjectTypeAggregationPolicy, @@ -482,13 +500,17 @@ func getOnObjectIdentifier(objectType sdk.ObjectType) (sdk.ObjectIdentifier, err sdk.ObjectTypeTag, sdk.ObjectTypeTask, sdk.ObjectTypeView: - return nil, nil + if _, ok := identifier.(sdk.SchemaObjectIdentifier); !ok { + return nil, sdk.NewError(fmt.Sprintf("invalid object_name %s, expected schema object identifier", objectName)) + } default: - return nil, nil + 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 { +func getOwnershipGrantOn(d *schema.ResourceData) (sdk.OwnershipGrantOn, error) { var ownershipGrantOn sdk.OwnershipGrantOn on := d.Get("on").([]any)[0].(map[string]any) @@ -499,9 +521,14 @@ func getOwnershipGrantOn(d *schema.ResourceData) sdk.OwnershipGrantOn { switch { case len(onObjectType) > 0 && len(onObjectName) > 0: + objectType := sdk.ObjectType(strings.ToUpper(onObjectType)) + objectName, err := getOnObjectIdentifier(objectType, onObjectName) + if err != nil { + return ownershipGrantOn, err + } ownershipGrantOn.Object = &sdk.Object{ - ObjectType: sdk.ObjectType(strings.ToUpper(onObjectType)), - Name: sdk.NewAccountObjectIdentifier(onObjectName), // TODO: Any identifier type + ObjectType: objectType, + Name: objectName, } case len(onAll) > 0: ownershipGrantOn.All = getGrantOnSchemaObjectIn(onAll[0].(map[string]any)) @@ -509,7 +536,7 @@ func getOwnershipGrantOn(d *schema.ResourceData) sdk.OwnershipGrantOn { ownershipGrantOn.Future = getGrantOnSchemaObjectIn(onFuture[0].(map[string]any)) } - return ownershipGrantOn + return ownershipGrantOn, nil } func getOwnershipGrantTo(d *schema.ResourceData) sdk.OwnershipGrantTo { diff --git a/pkg/resources/grant_ownership_test.go b/pkg/resources/grant_ownership_test.go new file mode 100644 index 0000000000..d03e33313b --- /dev/null +++ b/pkg/resources/grant_ownership_test.go @@ -0,0 +1,95 @@ +package resources + +import ( + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk" + "github.com/stretchr/testify/assert" + "testing" +) + +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: "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 account object identifier", + ObjectType: sdk.ObjectTypeDatabase, + ObjectName: "test_database.test_schema", + Error: "invalid object_name test_database.test_schema expected account object identifier", + }, + { + Name: "validation - invalid database account object identifier", + ObjectType: sdk.ObjectTypeSchema, + ObjectName: "test_database.test_schema.test_table", + }, + { + Name: "table - schema object identifier", + ObjectType: sdk.ObjectTypeTable, + ObjectName: "test_database.test_schema.test_table", + }, + } + + 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) + } + }) + } +} From 1ec3ac610f79c0be2854aaf2d66e06c70d4a1e1c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Cie=C5=9Blak?= Date: Thu, 7 Mar 2024 13:38:10 +0100 Subject: [PATCH 08/15] wip --- pkg/resources/grant_ownership.go | 48 +- .../grant_ownership_acceptance_test.go | 456 ++++++++++++++++-- pkg/resources/grant_ownership_identifier.go | 10 +- .../grant_ownership_identifier_test.go | 176 +++++++ pkg/resources/grant_ownership_test.go | 432 ++++++++++++++++- .../grant_privileges_identifier_commons.go | 3 +- .../OnAll_InDatabase_ToAccountRole/test.tf | 45 ++ .../variables.tf | 19 + .../OnAll_InSchema_ToAccountRole/test.tf | 45 ++ .../OnAll_InSchema_ToAccountRole/variables.tf | 19 + .../test.tf | 2 +- .../variables.tf | 0 .../test.tf | 9 +- .../variables.tf | 4 + .../test.tf | 0 .../variables.tf | 0 .../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 + 24 files changed, 1342 insertions(+), 82 deletions(-) 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 rename pkg/resources/testdata/TestAcc_GrantOwnership/{OnAll => OnFuture_InDatabase_ToAccountRole}/test.tf (96%) rename pkg/resources/testdata/TestAcc_GrantOwnership/{OnAll => OnFuture_InDatabase_ToAccountRole}/variables.tf (100%) rename pkg/resources/testdata/TestAcc_GrantOwnership/{OnFuture => OnFuture_InSchema_ToAccountRole}/test.tf (52%) rename pkg/resources/testdata/TestAcc_GrantOwnership/{OnObject => OnFuture_InSchema_ToAccountRole}/variables.tf (68%) rename pkg/resources/testdata/TestAcc_GrantOwnership/{OnObject => OnObject_Database_ToAccountRole}/test.tf (100%) rename pkg/resources/testdata/TestAcc_GrantOwnership/{OnFuture => OnObject_Database_ToAccountRole}/variables.tf (100%) 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 diff --git a/pkg/resources/grant_ownership.go b/pkg/resources/grant_ownership.go index 9214f24cbe..bc54a0f173 100644 --- a/pkg/resources/grant_ownership.go +++ b/pkg/resources/grant_ownership.go @@ -107,7 +107,7 @@ var grantOwnershipSchema = map[string]*schema.Schema{ Type: schema.TypeList, Required: true, ForceNew: true, - Description: "TODO", + Description: "Configures which object(s) should be granted with ownership privilege.", MaxItems: 1, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ @@ -290,7 +290,10 @@ func ImportGrantOwnership() schema.StateContextFunc { func CreateGrantOwnership(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics { client := meta.(*provider.Context).Client - id := createGrantOwnershipIdFromSchema(d) + 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) @@ -334,27 +337,34 @@ func DeleteGrantOwnership(ctx context.Context, d *schema.ResourceData, meta any) } } - // TODO: Prepare a special case for on_future branch, because then we should call `revoke ownership on future from ...` - 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( ctx, grantOn, - getOwnershipGrantTo(d), + sdk.OwnershipGrantTo{ + AccountRoleName: sdk.Pointer(sdk.NewAccountObjectIdentifier(accountRoleName)), // TODO: What if granted role is database role + }, 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", // TODO: do we save original role on create ? - Detail: fmt.Sprintf("Id: %s\nRole name: %s\nError: %s", d.Id(), id.DatabaseRoleName, err), // TODO Role name + Summary: "An error occurred when transferring ownership back to the original role", + Detail: fmt.Sprintf("Id: %s\nError: %s", d.Id(), err), }, } } @@ -448,7 +458,6 @@ func getOnObjectIdentifier(objectType sdk.ObjectType, objectName string) (sdk.Ob } switch objectType { - // sdk.AccountObjectIdentifier case sdk.ObjectTypeComputePool, sdk.ObjectTypeDatabase, sdk.ObjectTypeExternalVolume, @@ -462,15 +471,11 @@ func getOnObjectIdentifier(objectType sdk.ObjectType, objectName string) (sdk.Ob if _, ok := identifier.(sdk.AccountObjectIdentifier); !ok { return nil, sdk.NewError(fmt.Sprintf("invalid object_name %s, expected account object identifier", objectName)) } - - // sdk.DatabaseObjectIdentifier 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)) } - - // sdk.SchemaObjectIdentifier case sdk.ObjectTypeAggregationPolicy, sdk.ObjectTypeAlert, sdk.ObjectTypeAuthenticationPolicy, @@ -554,7 +559,7 @@ func getOwnershipGrantTo(d *schema.ResourceData) sdk.OwnershipGrantTo { } func getOwnershipGrantOpts(id *GrantOwnershipId) *sdk.GrantOwnershipOptions { - if id.OutboundPrivilegesBehavior != nil { + if id != nil && id.OutboundPrivilegesBehavior != nil { outboundPrivileges := id.OutboundPrivilegesBehavior.ToOwnershipCurrentGrantsOutboundPrivileges() if outboundPrivileges != nil { return &sdk.GrantOwnershipOptions{ @@ -610,7 +615,7 @@ func prepareShowGrantsRequestForGrantOwnership(id *GrantOwnershipId) (*sdk.ShowG return opts, grantedOn } -func createGrantOwnershipIdFromSchema(d *schema.ResourceData) *GrantOwnershipId { +func createGrantOwnershipIdFromSchema(d *schema.ResourceData) (*GrantOwnershipId, error) { id := new(GrantOwnershipId) accountRoleName, accountRoleNameOk := d.GetOk("account_role_name") databaseRoleName, databaseRoleNameOk := d.GetOk("database_role_name") @@ -643,17 +648,22 @@ func createGrantOwnershipIdFromSchema(d *schema.ResourceData) *GrantOwnershipId 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: sdk.ObjectType(objectType), - ObjectName: sdk.NewAccountObjectIdentifier(objectName), // TODO: Other identifier types + ObjectType: objectType, + ObjectName: objectName, } case len(all) > 0: id.Kind = OnAllGrantOwnershipKind - id.Data = getBulkOperationGrantData(getGrantOnSchemaObjectIn(all[0].(map[string]any))) // TODO Fix + 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))) // TODO Fix + id.Data = getBulkOperationGrantData(getGrantOnSchemaObjectIn(future[0].(map[string]any))) } - return id + return id, nil } diff --git a/pkg/resources/grant_ownership_acceptance_test.go b/pkg/resources/grant_ownership_acceptance_test.go index fa72c27a04..3654eae25c 100644 --- a/pkg/resources/grant_ownership_acceptance_test.go +++ b/pkg/resources/grant_ownership_acceptance_test.go @@ -1,7 +1,11 @@ package resources_test import ( + "context" "fmt" + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/internal/provider" + "github.com/hashicorp/terraform-plugin-testing/terraform" + "slices" "strings" "testing" @@ -13,11 +17,13 @@ import ( "github.com/hashicorp/terraform-plugin-testing/tfversion" ) -func TestAcc_GrantOwnership_OnObject(t *testing.T) { +func TestAcc_GrantOwnership_OnObject_Database_ToAccountRole(t *testing.T) { 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), @@ -30,20 +36,220 @@ func TestAcc_GrantOwnership_OnObject(t *testing.T) { TerraformVersionChecks: []tfversion.TerraformVersionCheck{ tfversion.RequireAbove(tfversion.Version1_5_0), }, - CheckDestroy: nil, // TODO Steps: []resource.TestStep{ { - ConfigDirectory: acc.ConfigurationDirectory("TestAcc_GrantOwnership/OnObject"), + 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) { + 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) { + 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) { + 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) { + 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"), + ConfigDirectory: acc.ConfigurationDirectory("TestAcc_GrantOwnership/OnObject_Table_ToDatabaseRole"), ConfigVariables: configVariables, ResourceName: resourceName, ImportState: true, @@ -53,54 +259,23 @@ func TestAcc_GrantOwnership_OnObject(t *testing.T) { }) } -//func TestAcc_GrantOwnership_OnAll(t *testing.T) { -// 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), -// }, -// CheckDestroy: nil, // TODO -// Steps: []resource.TestStep{ -// { -// ConfigDirectory: acc.ConfigurationDirectory("TestAcc_GrantOwnership/OnAll"), -// 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.object_name", databaseName), -// resource.TestCheckResourceAttr(resourceName, "id", fmt.Sprintf("ToAccountRole|%s||OnObject|DATABASE|%s", accountRoleFullyQualifiedName, databaseFullyQualifiedName)), -// ), -// }, -// { -// ConfigDirectory: acc.ConfigurationDirectory("TestAcc_GrantOwnership/OnAll"), -// ConfigVariables: configVariables, -// ResourceName: resourceName, -// ImportState: true, -// ImportStateVerify: true, -// }, -// }, -// }) -//} - -func TestAcc_GrantOwnership_OnFuture(t *testing.T) { +func TestAcc_GrantOwnership_OnAll_InDatabase_ToAccountRole(t *testing.T) { 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" @@ -110,20 +285,173 @@ func TestAcc_GrantOwnership_OnFuture(t *testing.T) { TerraformVersionChecks: []tfversion.TerraformVersionCheck{ tfversion.RequireAbove(tfversion.Version1_5_0), }, - CheckDestroy: nil, // TODO Steps: []resource.TestStep{ { - ConfigDirectory: acc.ConfigurationDirectory("TestAcc_GrantOwnership/OnFuture"), + 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.future.0.object_type_plural", "VIEWS"), + 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) { + 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) { + 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|VIEWS|InDatabase|%s", accountRoleFullyQualifiedName, databaseFullyQualifiedName)), + 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) { + 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"), + ConfigDirectory: acc.ConfigurationDirectory("TestAcc_GrantOwnership/OnFuture_InSchema_ToAccountRole"), ConfigVariables: configVariables, ResourceName: resourceName, ImportState: true, @@ -132,3 +460,31 @@ func TestAcc_GrantOwnership_OnFuture(t *testing.T) { }, }) } + +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 index 1b1260fcf6..a6bc3277c3 100644 --- a/pkg/resources/grant_ownership_identifier.go +++ b/pkg/resources/grant_ownership_identifier.go @@ -117,10 +117,14 @@ func ParseGrantOwnershipId(id string) (*GrantOwnershipId, error) { if len(parts) != 6 { return grantOwnershipId, sdk.NewError(`grant ownership identifier should consist of 6 parts "|||OnObject||"`) } - // TODO: Custom type for OnObject grant - because ObjectName can be pretty much any of the possible identifiers + objectType := sdk.ObjectType(parts[4]) + objectName, err := getOnObjectIdentifier(objectType, parts[5]) + if err != nil { + return nil, err + } grantOwnershipId.Data = &OnObjectGrantOwnershipData{ - ObjectType: sdk.ObjectType(parts[4]), - ObjectName: sdk.NewAccountObjectIdentifierFromFullyQualifiedName(parts[5]), // TODO: Fix should accept any identifier (most likely have to handle case by case for every object type) + ObjectType: objectType, + ObjectName: objectName, } case OnAllGrantOwnershipKind, OnFutureGrantOwnershipKind: bulkOperationGrantData := &BulkOperationGrantData{ diff --git a/pkg/resources/grant_ownership_identifier_test.go b/pkg/resources/grant_ownership_identifier_test.go index 4bf4cc4b2a..8bea909fdd 100644 --- a/pkg/resources/grant_ownership_identifier_test.go +++ b/pkg/resources/grant_ownership_identifier_test.go @@ -1,6 +1,7 @@ package resources import ( + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "testing" "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk" @@ -330,3 +331,178 @@ func TestParseGrantOwnershipId(t *testing.T) { // }) // } //} + +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 index d03e33313b..87d10ea0e9 100644 --- a/pkg/resources/grant_ownership_test.go +++ b/pkg/resources/grant_ownership_test.go @@ -2,6 +2,7 @@ package resources import ( "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/stretchr/testify/assert" "testing" ) @@ -63,20 +64,22 @@ func TestGetOnObjectIdentifier(t *testing.T) { Error: "object_type SHARE is not supported", }, { - Name: "validation - invalid database account object identifier", + Name: "validation - invalid account object identifier", ObjectType: sdk.ObjectTypeDatabase, ObjectName: "test_database.test_schema", - Error: "invalid object_name test_database.test_schema expected account object identifier", + Error: "invalid object_name test_database.test_schema, expected account object identifier", }, { - Name: "validation - invalid database account object identifier", + 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: "table - schema object identifier", + Name: "validation - invalid schema object identifier", ObjectType: sdk.ObjectTypeTable, - ObjectName: "test_database.test_schema.test_table", + 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", }, } @@ -93,3 +96,422 @@ func TestGetOnObjectIdentifier(t *testing.T) { }) } } + +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.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 TestGetOwnershipGrantTo(t *testing.T) { + testCases := []struct { + Name string + AccountRole *string + DatabaseRole *string + Expected sdk.OwnershipGrantTo + ExpectPanic bool + }{ + { + 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: "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")), + }, + }, + { + Name: "validation - incorrect account role name", + AccountRole: sdk.String("database_name.account_role_name"), + ExpectPanic: true, + }, + { + Name: "validation - incorrect database role name", + DatabaseRole: sdk.String("database_name.schema_name.database_role_name"), + ExpectPanic: true, + }, + } + + for _, tt := range testCases { + tt := tt + t.Run(tt.Name, func(t *testing.T) { + config := make(map[string]any) + if tt.AccountRole != nil { + config["account_role_name"] = *tt.AccountRole + } + if tt.DatabaseRole != nil { + config["database_role_name"] = *tt.DatabaseRole + } + d := schema.TestResourceDataRaw(t, grantOwnershipSchema, config) + + defer func() { + if err := recover(); err != nil { + assert.True(t, tt.ExpectPanic) + } + }() + grantTo := getOwnershipGrantTo(d) + + if tt.Expected.AccountRoleName != nil { + assert.Equal(t, *tt.Expected.AccountRoleName, *grantTo.AccountRoleName) + } + if tt.Expected.DatabaseRoleName != nil { + assert.Equal(t, *tt.Expected.DatabaseRoleName, *grantTo.DatabaseRoleName) + } + }) + } +} + +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 1db7cef17e..885b5d61c5 100644 --- a/pkg/resources/grant_privileges_identifier_commons.go +++ b/pkg/resources/grant_privileges_identifier_commons.go @@ -106,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/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..9df59f30d3 --- /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..bc3ddf6bff --- /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/OnAll/test.tf b/pkg/resources/testdata/TestAcc_GrantOwnership/OnFuture_InDatabase_ToAccountRole/test.tf similarity index 96% rename from pkg/resources/testdata/TestAcc_GrantOwnership/OnAll/test.tf rename to pkg/resources/testdata/TestAcc_GrantOwnership/OnFuture_InDatabase_ToAccountRole/test.tf index f42ac1c3c5..8fd47cd829 100644 --- a/pkg/resources/testdata/TestAcc_GrantOwnership/OnAll/test.tf +++ b/pkg/resources/testdata/TestAcc_GrantOwnership/OnFuture_InDatabase_ToAccountRole/test.tf @@ -9,7 +9,7 @@ resource "snowflake_database" "test" { resource "snowflake_grant_ownership" "test" { account_role_name = snowflake_role.test.name on { - all { + future { object_type_plural = "TABLES" in_database = snowflake_database.test.name } diff --git a/pkg/resources/testdata/TestAcc_GrantOwnership/OnAll/variables.tf b/pkg/resources/testdata/TestAcc_GrantOwnership/OnFuture_InDatabase_ToAccountRole/variables.tf similarity index 100% rename from pkg/resources/testdata/TestAcc_GrantOwnership/OnAll/variables.tf rename to pkg/resources/testdata/TestAcc_GrantOwnership/OnFuture_InDatabase_ToAccountRole/variables.tf diff --git a/pkg/resources/testdata/TestAcc_GrantOwnership/OnFuture/test.tf b/pkg/resources/testdata/TestAcc_GrantOwnership/OnFuture_InSchema_ToAccountRole/test.tf similarity index 52% rename from pkg/resources/testdata/TestAcc_GrantOwnership/OnFuture/test.tf rename to pkg/resources/testdata/TestAcc_GrantOwnership/OnFuture_InSchema_ToAccountRole/test.tf index 630bf850d5..e7d222359c 100644 --- a/pkg/resources/testdata/TestAcc_GrantOwnership/OnFuture/test.tf +++ b/pkg/resources/testdata/TestAcc_GrantOwnership/OnFuture_InSchema_ToAccountRole/test.tf @@ -6,12 +6,17 @@ 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 = "VIEWS" - in_database = snowflake_database.test.name + object_type_plural = "TABLES" + in_schema = "\"${snowflake_database.test.name}\".\"${snowflake_schema.test.name}\"" } } } diff --git a/pkg/resources/testdata/TestAcc_GrantOwnership/OnObject/variables.tf b/pkg/resources/testdata/TestAcc_GrantOwnership/OnFuture_InSchema_ToAccountRole/variables.tf similarity index 68% rename from pkg/resources/testdata/TestAcc_GrantOwnership/OnObject/variables.tf rename to pkg/resources/testdata/TestAcc_GrantOwnership/OnFuture_InSchema_ToAccountRole/variables.tf index 8af99038ae..e86f7da400 100644 --- a/pkg/resources/testdata/TestAcc_GrantOwnership/OnObject/variables.tf +++ b/pkg/resources/testdata/TestAcc_GrantOwnership/OnFuture_InSchema_ToAccountRole/variables.tf @@ -5,3 +5,7 @@ variable "account_role_name" { variable "database_name" { type = string } + +variable "schema_name" { + type = string +} diff --git a/pkg/resources/testdata/TestAcc_GrantOwnership/OnObject/test.tf b/pkg/resources/testdata/TestAcc_GrantOwnership/OnObject_Database_ToAccountRole/test.tf similarity index 100% rename from pkg/resources/testdata/TestAcc_GrantOwnership/OnObject/test.tf rename to pkg/resources/testdata/TestAcc_GrantOwnership/OnObject_Database_ToAccountRole/test.tf diff --git a/pkg/resources/testdata/TestAcc_GrantOwnership/OnFuture/variables.tf b/pkg/resources/testdata/TestAcc_GrantOwnership/OnObject_Database_ToAccountRole/variables.tf similarity index 100% rename from pkg/resources/testdata/TestAcc_GrantOwnership/OnFuture/variables.tf rename to pkg/resources/testdata/TestAcc_GrantOwnership/OnObject_Database_ToAccountRole/variables.tf 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..519b322b8b --- /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..8681055209 --- /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..82fb66e2b1 --- /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..10e276f260 --- /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 +} From 69d733495f2cedae9ab3ce2c848a4124805251a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Cie=C5=9Blak?= Date: Thu, 7 Mar 2024 13:49:00 +0100 Subject: [PATCH 09/15] wip --- docs/resources/grant_ownership.md | 2 +- pkg/resources/grant_ownership.go | 5 ++-- .../grant_ownership_acceptance_test.go | 5 ++-- .../grant_ownership_identifier_test.go | 23 ++++++++++--------- pkg/resources/grant_ownership_test.go | 3 ++- .../OnAll_InDatabase_ToAccountRole/test.tf | 12 +++++----- .../OnAll_InSchema_ToAccountRole/test.tf | 14 +++++------ .../OnFuture_InSchema_ToAccountRole/test.tf | 4 ++-- .../OnObject_Schema_ToAccountRole/test.tf | 2 +- .../OnObject_Schema_ToDatabaseRole/test.tf | 4 ++-- .../OnObject_Table_ToAccountRole/test.tf | 6 ++--- .../OnObject_Table_ToDatabaseRole/test.tf | 8 +++---- 12 files changed, 45 insertions(+), 43 deletions(-) diff --git a/docs/resources/grant_ownership.md b/docs/resources/grant_ownership.md index 4ae87bdf1f..1bbf216297 100644 --- a/docs/resources/grant_ownership.md +++ b/docs/resources/grant_ownership.md @@ -16,7 +16,7 @@ description: |- ### Required -- `on` (Block List, Min: 1, Max: 1) TODO (see [below for nested schema](#nestedblock--on)) +- `on` (Block List, Min: 1, Max: 1) Configures which object(s) should be granted with ownership privilege. (see [below for nested schema](#nestedblock--on)) ### Optional diff --git a/pkg/resources/grant_ownership.go b/pkg/resources/grant_ownership.go index bc54a0f173..532575cef2 100644 --- a/pkg/resources/grant_ownership.go +++ b/pkg/resources/grant_ownership.go @@ -3,10 +3,11 @@ package resources import ( "context" "fmt" - "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/helpers" "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" @@ -344,7 +345,6 @@ func DeleteGrantOwnership(ctx context.Context, d *schema.ResourceData, meta any) 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 { @@ -368,7 +368,6 @@ func DeleteGrantOwnership(ctx context.Context, d *schema.ResourceData, meta any) }, } } - } d.SetId("") diff --git a/pkg/resources/grant_ownership_acceptance_test.go b/pkg/resources/grant_ownership_acceptance_test.go index 3654eae25c..2e661ffd31 100644 --- a/pkg/resources/grant_ownership_acceptance_test.go +++ b/pkg/resources/grant_ownership_acceptance_test.go @@ -3,12 +3,13 @@ package resources_test import ( "context" "fmt" - "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/internal/provider" - "github.com/hashicorp/terraform-plugin-testing/terraform" "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" diff --git a/pkg/resources/grant_ownership_identifier_test.go b/pkg/resources/grant_ownership_identifier_test.go index 8bea909fdd..a5fd072440 100644 --- a/pkg/resources/grant_ownership_identifier_test.go +++ b/pkg/resources/grant_ownership_identifier_test.go @@ -1,9 +1,10 @@ package resources import ( - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "testing" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk" "github.com/stretchr/testify/assert" ) @@ -43,7 +44,7 @@ func TestParseGrantOwnershipId(t *testing.T) { // ObjectName: sdk.NewDatabaseObjectIdentifier("database-name", "schema-name"), // }, // }, - //}, + // }, // TODO: Won't work because we can expect one type of identifiers right now (adjust to chose id case-by-case based on object type) { Name: "grant ownership on schema to database role", @@ -147,16 +148,16 @@ func TestParseGrantOwnershipId(t *testing.T) { Identifier: `ToAccountRole|"account-role"|COPY|OnAll|TABLES|InvalidOption|"some-identifier"`, Error: "invalid BulkOperationGrantKind: InvalidOption, valid options are InDatabase | InSchema", }, - //{ + // { // Name: "TODO(panic because of bad identifiers): validation: OnAll in database - missing database identifier", // Identifier: `ToAccountRole|"account-role"|COPY|OnAll|InvalidTarget|InDatabase|`, // Error: "TODO", - //}, - //{ + // }, + // { // Name: "TODO(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`, @@ -167,16 +168,16 @@ func TestParseGrantOwnershipId(t *testing.T) { Identifier: `ToAccountRole|"account-role"|COPY|OnFuture|TABLES|InvalidOption|"some-identifier"`, Error: "invalid BulkOperationGrantKind: InvalidOption, valid options are InDatabase | InSchema", }, - //{ + // { // Name: "TODO(panic because of bad identifiers): validation: OnFuture in database - missing database identifier", // Identifier: `ToAccountRole|"account-role"|COPY|OnFuture|InvalidTarget|InDatabase|`, // Error: "TODO", - //}, - //{ + // }, + // { // Name: "TODO(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 { @@ -193,7 +194,7 @@ func TestParseGrantOwnershipId(t *testing.T) { } } -//func TestGrantOwnershipIdString(t *testing.T) { +// func TestGrantOwnershipIdString(t *testing.T) { // testCases := []struct { // Name string // Identifier GrantPrivilegesToAccountRoleId diff --git a/pkg/resources/grant_ownership_test.go b/pkg/resources/grant_ownership_test.go index 87d10ea0e9..705f4308de 100644 --- a/pkg/resources/grant_ownership_test.go +++ b/pkg/resources/grant_ownership_test.go @@ -1,10 +1,11 @@ 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" - "testing" ) func TestGetOnObjectIdentifier(t *testing.T) { diff --git a/pkg/resources/testdata/TestAcc_GrantOwnership/OnAll_InDatabase_ToAccountRole/test.tf b/pkg/resources/testdata/TestAcc_GrantOwnership/OnAll_InDatabase_ToAccountRole/test.tf index 9df59f30d3..122ad22fdb 100644 --- a/pkg/resources/testdata/TestAcc_GrantOwnership/OnAll_InDatabase_ToAccountRole/test.tf +++ b/pkg/resources/testdata/TestAcc_GrantOwnership/OnAll_InDatabase_ToAccountRole/test.tf @@ -7,14 +7,14 @@ resource "snowflake_database" "test" { } resource "snowflake_schema" "test" { - name = var.schema_name + name = var.schema_name database = snowflake_database.test.name } resource "snowflake_table" "test" { - name = var.table_name + name = var.table_name database = snowflake_database.test.name - schema = snowflake_schema.test.name + schema = snowflake_schema.test.name column { name = "id" @@ -23,9 +23,9 @@ resource "snowflake_table" "test" { } resource "snowflake_table" "test2" { - name = var.second_table_name + name = var.second_table_name database = snowflake_database.test.name - schema = snowflake_schema.test.name + schema = snowflake_schema.test.name column { name = "id" @@ -34,7 +34,7 @@ resource "snowflake_table" "test2" { } resource "snowflake_grant_ownership" "test" { - depends_on = [snowflake_table.test, snowflake_table.test2] + depends_on = [snowflake_table.test, snowflake_table.test2] account_role_name = snowflake_role.test.name on { all { diff --git a/pkg/resources/testdata/TestAcc_GrantOwnership/OnAll_InSchema_ToAccountRole/test.tf b/pkg/resources/testdata/TestAcc_GrantOwnership/OnAll_InSchema_ToAccountRole/test.tf index bc3ddf6bff..b77eb8f5cf 100644 --- a/pkg/resources/testdata/TestAcc_GrantOwnership/OnAll_InSchema_ToAccountRole/test.tf +++ b/pkg/resources/testdata/TestAcc_GrantOwnership/OnAll_InSchema_ToAccountRole/test.tf @@ -7,14 +7,14 @@ resource "snowflake_database" "test" { } resource "snowflake_schema" "test" { - name = var.schema_name + name = var.schema_name database = snowflake_database.test.name } resource "snowflake_table" "test" { - name = var.table_name + name = var.table_name database = snowflake_database.test.name - schema = snowflake_schema.test.name + schema = snowflake_schema.test.name column { name = "id" @@ -23,9 +23,9 @@ resource "snowflake_table" "test" { } resource "snowflake_table" "test2" { - name = var.second_table_name + name = var.second_table_name database = snowflake_database.test.name - schema = snowflake_schema.test.name + schema = snowflake_schema.test.name column { name = "id" @@ -34,12 +34,12 @@ resource "snowflake_table" "test2" { } resource "snowflake_grant_ownership" "test" { - depends_on = [snowflake_table.test, snowflake_table.test2] + 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}\"" + in_schema = "\"${snowflake_database.test.name}\".\"${snowflake_schema.test.name}\"" } } } diff --git a/pkg/resources/testdata/TestAcc_GrantOwnership/OnFuture_InSchema_ToAccountRole/test.tf b/pkg/resources/testdata/TestAcc_GrantOwnership/OnFuture_InSchema_ToAccountRole/test.tf index e7d222359c..db037510a9 100644 --- a/pkg/resources/testdata/TestAcc_GrantOwnership/OnFuture_InSchema_ToAccountRole/test.tf +++ b/pkg/resources/testdata/TestAcc_GrantOwnership/OnFuture_InSchema_ToAccountRole/test.tf @@ -7,7 +7,7 @@ resource "snowflake_database" "test" { } resource "snowflake_schema" "test" { - name = var.schema_name + name = var.schema_name database = snowflake_database.test.name } @@ -16,7 +16,7 @@ resource "snowflake_grant_ownership" "test" { on { future { object_type_plural = "TABLES" - in_schema = "\"${snowflake_database.test.name}\".\"${snowflake_schema.test.name}\"" + in_schema = "\"${snowflake_database.test.name}\".\"${snowflake_schema.test.name}\"" } } } diff --git a/pkg/resources/testdata/TestAcc_GrantOwnership/OnObject_Schema_ToAccountRole/test.tf b/pkg/resources/testdata/TestAcc_GrantOwnership/OnObject_Schema_ToAccountRole/test.tf index 519b322b8b..20e43e8375 100644 --- a/pkg/resources/testdata/TestAcc_GrantOwnership/OnObject_Schema_ToAccountRole/test.tf +++ b/pkg/resources/testdata/TestAcc_GrantOwnership/OnObject_Schema_ToAccountRole/test.tf @@ -7,7 +7,7 @@ resource "snowflake_database" "test" { } resource "snowflake_schema" "test" { - name = var.schema_name + name = var.schema_name database = snowflake_database.test.name } diff --git a/pkg/resources/testdata/TestAcc_GrantOwnership/OnObject_Schema_ToDatabaseRole/test.tf b/pkg/resources/testdata/TestAcc_GrantOwnership/OnObject_Schema_ToDatabaseRole/test.tf index 8681055209..4b823e16a7 100644 --- a/pkg/resources/testdata/TestAcc_GrantOwnership/OnObject_Schema_ToDatabaseRole/test.tf +++ b/pkg/resources/testdata/TestAcc_GrantOwnership/OnObject_Schema_ToDatabaseRole/test.tf @@ -3,12 +3,12 @@ resource "snowflake_database" "test" { } resource "snowflake_database_role" "test" { - name = var.database_role_name + name = var.database_role_name database = snowflake_database.test.name } resource "snowflake_schema" "test" { - name = var.schema_name + name = var.schema_name database = snowflake_database.test.name } diff --git a/pkg/resources/testdata/TestAcc_GrantOwnership/OnObject_Table_ToAccountRole/test.tf b/pkg/resources/testdata/TestAcc_GrantOwnership/OnObject_Table_ToAccountRole/test.tf index 82fb66e2b1..cb0b31622e 100644 --- a/pkg/resources/testdata/TestAcc_GrantOwnership/OnObject_Table_ToAccountRole/test.tf +++ b/pkg/resources/testdata/TestAcc_GrantOwnership/OnObject_Table_ToAccountRole/test.tf @@ -7,14 +7,14 @@ resource "snowflake_database" "test" { } resource "snowflake_schema" "test" { - name = var.schema_name + name = var.schema_name database = snowflake_database.test.name } resource "snowflake_table" "test" { - name = var.table_name + name = var.table_name database = snowflake_database.test.name - schema = snowflake_schema.test.name + schema = snowflake_schema.test.name column { name = "id" diff --git a/pkg/resources/testdata/TestAcc_GrantOwnership/OnObject_Table_ToDatabaseRole/test.tf b/pkg/resources/testdata/TestAcc_GrantOwnership/OnObject_Table_ToDatabaseRole/test.tf index 10e276f260..d1646c0435 100644 --- a/pkg/resources/testdata/TestAcc_GrantOwnership/OnObject_Table_ToDatabaseRole/test.tf +++ b/pkg/resources/testdata/TestAcc_GrantOwnership/OnObject_Table_ToDatabaseRole/test.tf @@ -3,19 +3,19 @@ resource "snowflake_database" "test" { } resource "snowflake_database_role" "test" { - name = var.database_role_name + name = var.database_role_name database = snowflake_database.test.name } resource "snowflake_schema" "test" { - name = var.schema_name + name = var.schema_name database = snowflake_database.test.name } resource "snowflake_table" "test" { - name = var.table_name + name = var.table_name database = snowflake_database.test.name - schema = snowflake_schema.test.name + schema = snowflake_schema.test.name column { name = "id" From 924b38600c2a088e3714915ad06259297fb4e625 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Cie=C5=9Blak?= Date: Thu, 7 Mar 2024 15:51:13 +0100 Subject: [PATCH 10/15] wip --- docs/resources/grant_ownership.md | 226 ++++++++++++- .../grant_privileges_to_account_role.md | 2 +- .../grant_privileges_to_database_role.md | 2 +- .../snowflake_grant_ownership/resource.tf | 151 +++++++++ pkg/resources/grant_ownership.go | 10 +- .../grant_ownership_identifier_test.go | 306 +++++++++--------- templates/resources/grant_ownership.md.tmpl | 92 ++++++ .../grant_privileges_to_account_role.md.tmpl | 2 +- .../grant_privileges_to_database_role.md.tmpl | 2 +- 9 files changed, 624 insertions(+), 169 deletions(-) create mode 100644 examples/resources/snowflake_grant_ownership/resource.tf create mode 100644 templates/resources/grant_ownership.md.tmpl diff --git a/docs/resources/grant_ownership.md b/docs/resources/grant_ownership.md index 1bbf216297..2e3aeeef4d 100644 --- a/docs/resources/grant_ownership.md +++ b/docs/resources/grant_ownership.md @@ -1,22 +1,182 @@ --- +# generated by https://github.com/hashicorp/terraform-plugin-docs page_title: "snowflake_grant_ownership Resource - terraform-provider-snowflake" subcategory: "" description: |- --- +~> **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. + + + +!> **Warning** Be careful when using `always_apply` field. It will always produce a plan (even when no changes were made) and can be harmful in some setups. For more details why we decided to introduce it to go our document explaining those design decisions (coming soon). + # snowflake_grant_ownership (Resource) +## Example Usage + +```terraform +################################## +### 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}\"" + } + } +} +``` ## Schema ### Required -- `on` (Block List, Min: 1, Max: 1) Configures which object(s) should be granted with ownership privilege. (see [below for nested schema](#nestedblock--on)) +- `on` (Block List, Min: 1, Max: 1) Configures which object(s) should transfer their ownership to the specified role. (see [below for nested schema](#nestedblock--on)) ### Optional @@ -62,3 +222,67 @@ Optional: - `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 (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: + +### OnObject +`terraform import "|||OnObject||"` + +### OnAll + +OnAll contains inner types for all options. + +#### InDatabase +`terraform import "|||OnAll||InDatabase|"` + +#### InSchema +`terraform import "|||OnAll||InSchema|"` + +### OnAll + +OnFuture contains inner types for all options. + +#### 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/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 d718b7121f..e221935b66 100644 --- a/docs/resources/grant_privileges_to_database_role.md +++ b/docs/resources/grant_privileges_to_database_role.md @@ -235,7 +235,7 @@ Optional: ## 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/examples/resources/snowflake_grant_ownership/resource.tf b/examples/resources/snowflake_grant_ownership/resource.tf new file mode 100644 index 0000000000..e6c4f1cf51 --- /dev/null +++ b/examples/resources/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}\"" + } + } +} + diff --git a/pkg/resources/grant_ownership.go b/pkg/resources/grant_ownership.go index 532575cef2..bcefc465a9 100644 --- a/pkg/resources/grant_ownership.go +++ b/pkg/resources/grant_ownership.go @@ -108,7 +108,7 @@ var grantOwnershipSchema = map[string]*schema.Schema{ Type: schema.TypeList, Required: true, ForceNew: true, - Description: "Configures which object(s) should be granted with ownership privilege.", + Description: "Configures which object(s) should transfer their ownership to the specified role.", MaxItems: 1, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ @@ -351,11 +351,12 @@ func DeleteGrantOwnership(ctx context.Context, d *schema.ResourceData, meta any) return diag.FromErr(err) } - err = client.Grants.GrantOwnership( + 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)), // TODO: What if granted role is database role + AccountRoleName: sdk.Pointer(sdk.NewAccountObjectIdentifier(accountRoleName)), // TODO: What if current role is database role (not a common but possible case) + // DatabaseRoleName: TODO: handle later }, getOwnershipGrantOpts(id), ) @@ -394,7 +395,6 @@ func ReadGrantOwnership(ctx context.Context, d *schema.ResourceData, meta any) d client := meta.(*provider.Context).Client - logging.DebugLogger.Printf("[DEBUG] About to show grants") grants, err := client.Grants.Show(ctx, opts) if err != nil { return diag.Diagnostics{ @@ -586,7 +586,7 @@ func prepareShowGrantsRequestForGrantOwnership(id *GrantOwnershipId) (*sdk.ShowG Name: data.ObjectName, }, } - case OnAllGrantOwnershipKind: // TODO: discuss if we want to let users do this (lose control over ownership for all objects in x) + 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) diff --git a/pkg/resources/grant_ownership_identifier_test.go b/pkg/resources/grant_ownership_identifier_test.go index a5fd072440..e0345209ef 100644 --- a/pkg/resources/grant_ownership_identifier_test.go +++ b/pkg/resources/grant_ownership_identifier_test.go @@ -30,22 +30,34 @@ func TestParseGrantOwnershipId(t *testing.T) { }, }, }, - // TODO: Won't work because we can expect one type of identifiers right now (adjust to chose id case-by-case based on object type) - //{ - // 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"), - // }, - // }, - // }, - // TODO: Won't work because we can expect one type of identifiers right now (adjust to chose id case-by-case based on object type) + { + 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"`, @@ -149,12 +161,12 @@ func TestParseGrantOwnershipId(t *testing.T) { Error: "invalid BulkOperationGrantKind: InvalidOption, valid options are InDatabase | InSchema", }, // { - // Name: "TODO(panic because of bad identifiers): validation: OnAll in database - missing database identifier", + // 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(panic because of bad identifiers): validation: OnAll in database - missing schema identifier", + // 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", // }, @@ -169,12 +181,12 @@ func TestParseGrantOwnershipId(t *testing.T) { Error: "invalid BulkOperationGrantKind: InvalidOption, valid options are InDatabase | InSchema", }, // { - // Name: "TODO(panic because of bad identifiers): validation: OnFuture in database - missing database identifier", + // 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(panic because of bad identifiers): validation: OnFuture in database - missing schema identifier", + // 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", // }, @@ -186,7 +198,8 @@ func TestParseGrantOwnershipId(t *testing.T) { id, err := ParseGrantOwnershipId(tt.Identifier) if tt.Error == "" { assert.NoError(t, err) - assert.Equal(t, tt.Expected, id) + assert.NotNil(t, id) + assert.Equal(t, tt.Expected, *id) } else { assert.ErrorContains(t, err, tt.Error) } @@ -194,144 +207,119 @@ func TestParseGrantOwnershipId(t *testing.T) { } } -// func TestGrantOwnershipIdString(t *testing.T) { -// testCases := []struct { -// Name string -// Identifier GrantPrivilegesToAccountRoleId -// Expected string -// Error string -// }{ -// { -// Name: "grant account role on account", -// Identifier: GrantPrivilegesToAccountRoleId{ -// RoleName: sdk.NewAccountObjectIdentifier("account-role"), -// WithGrantOption: true, -// AllPrivileges: true, -// Kind: OnAccountAccountRoleGrantKind, -// AlwaysApply: true, -// Data: new(OnAccountGrantData), -// }, -// Expected: `"account-role"|true|true|ALL|OnAccount`, -// }, -// { -// Name: "grant account role on account object", -// Identifier: GrantPrivilegesToAccountRoleId{ -// RoleName: sdk.NewAccountObjectIdentifier("account-role"), -// WithGrantOption: true, -// AllPrivileges: true, -// Kind: OnAccountObjectAccountRoleGrantKind, -// AlwaysApply: true, -// Data: &OnAccountObjectGrantData{ -// ObjectType: sdk.ObjectTypeDatabase, -// ObjectName: sdk.NewAccountObjectIdentifier("database-name"), -// }, -// }, -// Expected: `"account-role"|true|true|ALL|OnAccountObject|DATABASE|"database-name"`, -// }, -// { -// Name: "grant account role on schema on schema", -// Identifier: GrantPrivilegesToAccountRoleId{ -// RoleName: sdk.NewAccountObjectIdentifier("account-role"), -// WithGrantOption: false, -// Privileges: []string{"CREATE SCHEMA", "USAGE", "MONITOR"}, -// Kind: OnSchemaAccountRoleGrantKind, -// Data: &OnSchemaGrantData{ -// Kind: OnSchemaSchemaGrantKind, -// SchemaName: sdk.Pointer(sdk.NewDatabaseObjectIdentifier("database-name", "schema-name")), -// }, -// }, -// Expected: `"account-role"|false|false|CREATE SCHEMA,USAGE,MONITOR|OnSchema|OnSchema|"database-name"."schema-name"`, -// }, -// { -// Name: "grant account role on all schemas in database", -// Identifier: GrantPrivilegesToAccountRoleId{ -// RoleName: sdk.NewAccountObjectIdentifier("account-role"), -// WithGrantOption: false, -// Privileges: []string{"CREATE SCHEMA", "USAGE", "MONITOR"}, -// Kind: OnSchemaAccountRoleGrantKind, -// Data: &OnSchemaGrantData{ -// Kind: OnAllSchemasInDatabaseSchemaGrantKind, -// DatabaseName: sdk.Pointer(sdk.NewAccountObjectIdentifier("database-name")), -// }, -// }, -// Expected: `"account-role"|false|false|CREATE SCHEMA,USAGE,MONITOR|OnSchema|OnAllSchemasInDatabase|"database-name"`, -// }, -// { -// Name: "grant account role on future schemas in database", -// Identifier: GrantPrivilegesToAccountRoleId{ -// RoleName: sdk.NewAccountObjectIdentifier("account-role"), -// WithGrantOption: false, -// Privileges: []string{"CREATE SCHEMA", "USAGE", "MONITOR"}, -// Kind: OnSchemaAccountRoleGrantKind, -// Data: &OnSchemaGrantData{ -// Kind: OnFutureSchemasInDatabaseSchemaGrantKind, -// DatabaseName: sdk.Pointer(sdk.NewAccountObjectIdentifier("database-name")), -// }, -// }, -// Expected: `"account-role"|false|false|CREATE SCHEMA,USAGE,MONITOR|OnSchema|OnFutureSchemasInDatabase|"database-name"`, -// }, -// { -// Name: "grant account role on schema object on object", -// Identifier: GrantPrivilegesToAccountRoleId{ -// RoleName: sdk.NewAccountObjectIdentifier("account-role"), -// WithGrantOption: false, -// Privileges: []string{"CREATE SCHEMA", "USAGE", "MONITOR"}, -// Kind: OnSchemaObjectAccountRoleGrantKind, -// Data: &OnSchemaObjectGrantData{ -// Kind: OnObjectSchemaObjectGrantKind, -// Object: &sdk.Object{ -// ObjectType: sdk.ObjectTypeTable, -// Name: sdk.NewSchemaObjectIdentifier("database-name", "schema-name", "table-name"), -// }, -// }, -// }, -// Expected: `"account-role"|false|false|CREATE SCHEMA,USAGE,MONITOR|OnSchemaObject|OnObject|TABLE|"database-name"."schema-name"."table-name"`, -// }, -// { -// Name: "grant account role on schema object on all tables in database", -// Identifier: GrantPrivilegesToAccountRoleId{ -// RoleName: sdk.NewAccountObjectIdentifier("account-role"), -// WithGrantOption: false, -// Privileges: []string{"CREATE SCHEMA", "USAGE", "MONITOR"}, -// Kind: OnSchemaObjectAccountRoleGrantKind, -// Data: &OnSchemaObjectGrantData{ -// Kind: OnAllSchemaObjectGrantKind, -// OnAllOrFuture: &BulkOperationGrantData{ -// ObjectNamePlural: sdk.PluralObjectTypeTables, -// Kind: InDatabaseBulkOperationGrantKind, -// Database: sdk.Pointer(sdk.NewAccountObjectIdentifier("database-name")), -// }, -// }, -// }, -// Expected: `"account-role"|false|false|CREATE SCHEMA,USAGE,MONITOR|OnSchemaObject|OnAll|TABLES|InDatabase|"database-name"`, -// }, -// { -// Name: "grant account role on schema object on all tables in schema", -// Identifier: GrantPrivilegesToAccountRoleId{ -// RoleName: sdk.NewAccountObjectIdentifier("account-role"), -// WithGrantOption: false, -// Privileges: []string{"CREATE SCHEMA", "USAGE", "MONITOR"}, -// Kind: OnSchemaObjectAccountRoleGrantKind, -// Data: &OnSchemaObjectGrantData{ -// Kind: OnAllSchemaObjectGrantKind, -// OnAllOrFuture: &BulkOperationGrantData{ -// ObjectNamePlural: sdk.PluralObjectTypeTables, -// Kind: InSchemaBulkOperationGrantKind, -// Schema: sdk.Pointer(sdk.NewDatabaseObjectIdentifier("database-name", "schema-name")), -// }, -// }, -// }, -// Expected: `"account-role"|false|false|CREATE SCHEMA,USAGE,MONITOR|OnSchemaObject|OnAll|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 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 { diff --git a/templates/resources/grant_ownership.md.tmpl b/templates/resources/grant_ownership.md.tmpl new file mode 100644 index 0000000000..c98859e242 --- /dev/null +++ b/templates/resources/grant_ownership.md.tmpl @@ -0,0 +1,92 @@ +--- +# 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 }} +--- + +~> **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. + +{{/* 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 */}} +!> **Warning** Be careful when using `always_apply` field. It will always produce a plan (even when no changes were made) and can be harmful in some setups. For more details why we decided to introduce it to go our document explaining those design decisions (coming soon). + +# {{.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: + +### OnObject +`terraform import "|||OnObject||"` + +### OnAll + +OnAll contains inner types for all options. + +#### InDatabase +`terraform import "|||OnAll||InDatabase|"` + +#### InSchema +`terraform import "|||OnAll||InSchema|"` + +### OnAll + +OnFuture contains inner types for all options. + +#### 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/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: From d22e3d957e53e6ddb8deb84c717e8a80faf4832d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Cie=C5=9Blak?= Date: Thu, 7 Mar 2024 17:26:48 +0100 Subject: [PATCH 11/15] wip --- docs/resources/grant_ownership.md | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/resources/grant_ownership.md b/docs/resources/grant_ownership.md index 2e3aeeef4d..198f67bea1 100644 --- a/docs/resources/grant_ownership.md +++ b/docs/resources/grant_ownership.md @@ -225,7 +225,6 @@ Optional: ## 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: From 306e09f29127cf8bc3afbaa607b393843e9fce5f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Cie=C5=9Blak?= Date: Tue, 12 Mar 2024 13:54:37 +0100 Subject: [PATCH 12/15] changes after review --- docs/resources/grant_ownership.md | 19 ++-- .../grant_privileges_to_database_role.md | 8 +- .../snowflake_grant_ownership/import.sh | 41 +++++++ pkg/resources/grant_ownership.go | 94 ++++------------ .../grant_ownership_acceptance_test.go | 91 ++++++++++++++++ pkg/resources/grant_ownership_test.go | 100 ++++++++++-------- .../grant_privileges_to_database_role.go | 4 +- .../test.tf | 15 +++ .../variables.tf | 7 ++ .../test.tf | 25 +++++ .../variables.tf | 7 ++ pkg/sdk/grants_validations.go | 56 ++++++++++ pkg/sdk/testint/helpers_test.go | 2 +- templates/resources/grant_ownership.md.tmpl | 51 +-------- 14 files changed, 338 insertions(+), 182 deletions(-) create mode 100644 examples/resources/snowflake_grant_ownership/import.sh 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 diff --git a/docs/resources/grant_ownership.md b/docs/resources/grant_ownership.md index 198f67bea1..d28fbf3ad7 100644 --- a/docs/resources/grant_ownership.md +++ b/docs/resources/grant_ownership.md @@ -6,11 +6,14 @@ description: |- --- + +!> **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). -!> **Warning** Be careful when using `always_apply` field. It will always produce a plan (even when no changes were made) and can be harmful in some setups. For more details why we decided to introduce it to go our document explaining those design decisions (coming soon). # snowflake_grant_ownership (Resource) @@ -182,7 +185,7 @@ resource "snowflake_grant_ownership" "test" { - `account_role_name` (String) The fully qualified name of the account role to which privileges will be granted. - `database_role_name` (String) The fully qualified name of the database role to which privileges will be granted. -- `outbound_privileges` (String) 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. +- `outbound_privileges` (String) 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). ### Read-Only @@ -203,7 +206,7 @@ Optional: Required: -- `object_type_plural` (String) Specifies the type of object in plural form on which you are transferring ownership. Available values are: AGGREGATION POLICIES | ALERTS | AUTHENTICATION POLICIES | COMPUTE POOLS | DATABASES | DATABASE ROLES | DYNAMIC TABLES | EVENT TABLES | EXTERNAL TABLES | EXTERNAL VOLUMES | FAILOVER GROUPS | FILE FORMATS | FUNCTIONS | HYBRID TABLES | ICEBERG TABLES | IMAGE REPOSITORIES | INTEGRATIONS | MATERIALIZED VIEWS | NETWORK POLICIES | NETWORK RULES | PACKAGES POLICIES | PIPES | PROCEDURES | MASKING POLICIES | PASSWORD POLICIES | PROJECTION POLICIES | REPLICATION GROUPS | ROLES | ROW ACCESS POLICIES | SCHEMAS | SESSION POLICIES | SECRETS | SEQUENCES | STAGES | STREAMS | TABLES | TAGS | TASKS | USERS | VIEWS | WAREHOUSES +- `object_type_plural` (String) Specifies the type of object in plural form on which you are transferring ownership. Available values are: AGGREGATION POLICIES | ALERTS | AUTHENTICATION POLICIES | COMPUTE POOLS | DATABASES | DATABASE ROLES | DYNAMIC TABLES | EVENT TABLES | EXTERNAL TABLES | EXTERNAL VOLUMES | FAILOVER GROUPS | FILE FORMATS | FUNCTIONS | HYBRID TABLES | ICEBERG TABLES | IMAGE REPOSITORIES | INTEGRATIONS | MATERIALIZED VIEWS | NETWORK POLICIES | NETWORK RULES | PACKAGES POLICIES | PIPES | PROCEDURES | MASKING POLICIES | PASSWORD POLICIES | PROJECTION POLICIES | REPLICATION GROUPS | ROLES | ROW ACCESS POLICIES | SCHEMAS | SESSION POLICIES | SECRETS | SEQUENCES | STAGES | STREAMS | TABLES | TAGS | TASKS | USERS | VIEWS | WAREHOUSES. For more information head over to [Snowflake documentation](https://docs.snowflake.com/en/sql-reference/sql/grant-ownership#required-parameters). Optional: @@ -216,7 +219,7 @@ Optional: Required: -- `object_type_plural` (String) Specifies the type of object in plural form on which you are transferring ownership. Available values are: AGGREGATION POLICIES | ALERTS | AUTHENTICATION POLICIES | COMPUTE POOLS | DATABASES | DATABASE ROLES | DYNAMIC TABLES | EVENT TABLES | EXTERNAL TABLES | EXTERNAL VOLUMES | FAILOVER GROUPS | FILE FORMATS | FUNCTIONS | HYBRID TABLES | ICEBERG TABLES | IMAGE REPOSITORIES | INTEGRATIONS | MATERIALIZED VIEWS | NETWORK POLICIES | NETWORK RULES | PACKAGES POLICIES | PIPES | PROCEDURES | MASKING POLICIES | PASSWORD POLICIES | PROJECTION POLICIES | REPLICATION GROUPS | ROLES | ROW ACCESS POLICIES | SCHEMAS | SESSION POLICIES | SECRETS | SEQUENCES | STAGES | STREAMS | TABLES | TAGS | TASKS | USERS | VIEWS | WAREHOUSES +- `object_type_plural` (String) Specifies the type of object in plural form on which you are transferring ownership. Available values are: AGGREGATION POLICIES | ALERTS | AUTHENTICATION POLICIES | COMPUTE POOLS | DATABASES | DATABASE ROLES | DYNAMIC TABLES | EVENT TABLES | EXTERNAL TABLES | EXTERNAL VOLUMES | FAILOVER GROUPS | FILE FORMATS | FUNCTIONS | HYBRID TABLES | ICEBERG TABLES | IMAGE REPOSITORIES | INTEGRATIONS | MATERIALIZED VIEWS | NETWORK POLICIES | NETWORK RULES | PACKAGES POLICIES | PIPES | PROCEDURES | MASKING POLICIES | PASSWORD POLICIES | PROJECTION POLICIES | REPLICATION GROUPS | ROLES | ROW ACCESS POLICIES | SCHEMAS | SESSION POLICIES | SECRETS | SEQUENCES | STAGES | STREAMS | TABLES | TAGS | TASKS | USERS | VIEWS | WAREHOUSES. For more information head over to [Snowflake documentation](https://docs.snowflake.com/en/sql-reference/sql/grant-ownership#required-parameters). Optional: @@ -243,9 +246,7 @@ It has varying number of parts, depending on grant_type. All the possible types ### OnObject `terraform import "|||OnObject||"` -### OnAll - -OnAll contains inner types for all options. +### OnAll (contains inner types: InDatabase | InSchema) #### InDatabase `terraform import "|||OnAll||InDatabase|"` @@ -253,9 +254,7 @@ OnAll contains inner types for all options. #### InSchema `terraform import "|||OnAll||InSchema|"` -### OnAll - -OnFuture contains inner types for all options. +### OnFuture (contains inner types: InDatabase | InSchema) #### InDatabase `terraform import "|||OnFuture||InDatabase|"` diff --git a/docs/resources/grant_privileges_to_database_role.md b/docs/resources/grant_privileges_to_database_role.md index e221935b66..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) TODO -- `in_schema` (String) TODO +- `in_database` (String) The fully qualified name of the database. +- `in_schema` (String) The fully qualified name of the schema. @@ -230,8 +230,8 @@ Required: Optional: -- `in_database` (String) TODO -- `in_schema` (String) TODO +- `in_database` (String) The fully qualified name of the database. +- `in_schema` (String) The fully qualified name of the schema. ## Import diff --git a/examples/resources/snowflake_grant_ownership/import.sh b/examples/resources/snowflake_grant_ownership/import.sh new file mode 100644 index 0000000000..794f5b79e2 --- /dev/null +++ b/examples/resources/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/pkg/resources/grant_ownership.go b/pkg/resources/grant_ownership.go index bcefc465a9..99e0a771a8 100644 --- a/pkg/resources/grant_ownership.go +++ b/pkg/resources/grant_ownership.go @@ -16,61 +16,6 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" ) -var validGrantOwnershipObjectTypes = []sdk.ObjectType{ - sdk.ObjectTypeAggregationPolicy, - sdk.ObjectTypeAlert, - sdk.ObjectTypeAuthenticationPolicy, - sdk.ObjectTypeComputePool, - sdk.ObjectTypeDatabase, - sdk.ObjectTypeDatabaseRole, - sdk.ObjectTypeDynamicTable, - sdk.ObjectTypeEventTable, - sdk.ObjectTypeExternalTable, - sdk.ObjectTypeExternalVolume, - sdk.ObjectTypeFailoverGroup, - sdk.ObjectTypeFileFormat, - sdk.ObjectTypeFunction, - sdk.ObjectTypeHybridTable, - sdk.ObjectTypeIcebergTable, - sdk.ObjectTypeImageRepository, - sdk.ObjectTypeIntegration, - sdk.ObjectTypeMaterializedView, - sdk.ObjectTypeNetworkPolicy, - sdk.ObjectTypeNetworkRule, - sdk.ObjectTypePackagesPolicy, - sdk.ObjectTypePipe, - sdk.ObjectTypeProcedure, - sdk.ObjectTypeMaskingPolicy, - sdk.ObjectTypePasswordPolicy, - sdk.ObjectTypeProjectionPolicy, - sdk.ObjectTypeRole, - sdk.ObjectTypeRowAccessPolicy, - sdk.ObjectTypeSchema, - sdk.ObjectTypeSessionPolicy, - sdk.ObjectTypeSecret, - sdk.ObjectTypeSequence, - sdk.ObjectTypeStage, - sdk.ObjectTypeStream, - sdk.ObjectTypeTable, - sdk.ObjectTypeTag, - sdk.ObjectTypeTask, - sdk.ObjectTypeUser, - sdk.ObjectTypeView, - sdk.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() - } -} - var grantOwnershipSchema = map[string]*schema.Schema{ "account_role_name": { Type: schema.TypeString, @@ -98,7 +43,7 @@ var grantOwnershipSchema = map[string]*schema.Schema{ 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.", + 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", @@ -116,11 +61,11 @@ var grantOwnershipSchema = map[string]*schema.Schema{ Type: schema.TypeString, Optional: true, ForceNew: true, - Description: "Specifies the type of object on which you are transferring ownership. Available values are: AGGREGATION POLICY | ALERT | AUTHENTICATION POLICY | COMPUTE POOL | DATABASE | DATABASE ROLE | DYNAMIC TABLE | EVENT TABLE | EXTERNAL TABLE | EXTERNAL VOLUME | FAILOVER GROUP | FILE FORMAT | FUNCTION | HYBRID TABLE | ICEBERG TABLE | IMAGE REPOSITORY | INTEGRATION | MATERIALIZED VIEW | NETWORK POLICY | NETWORK RULE | PACKAGES POLICY | PIPE | PROCEDURE | MASKING POLICY | PASSWORD POLICY | PROJECTION POLICY | REPLICATION GROUP | ROLE | ROW ACCESS POLICY | SCHEMA | SESSION POLICY | SECRET | SEQUENCE | STAGE | STREAM | TABLE | TAG | TASK | USER | VIEW | WAREHOUSE", + 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(validGrantOwnershipObjectTypesString, true), + ValidateFunc: validation.StringInSlice(sdk.ValidGrantOwnershipObjectTypesString, true), }, "object_name": { Type: schema.TypeString, @@ -177,8 +122,8 @@ func grantOwnershipBulkOperationSchema(branchName string) map[string]*schema.Sch Type: schema.TypeString, Required: true, ForceNew: true, - Description: "Specifies the type of object in plural form on which you are transferring ownership. Available values are: AGGREGATION POLICIES | ALERTS | AUTHENTICATION POLICIES | COMPUTE POOLS | DATABASES | DATABASE ROLES | DYNAMIC TABLES | EVENT TABLES | EXTERNAL TABLES | EXTERNAL VOLUMES | FAILOVER GROUPS | FILE FORMATS | FUNCTIONS | HYBRID TABLES | ICEBERG TABLES | IMAGE REPOSITORIES | INTEGRATIONS | MATERIALIZED VIEWS | NETWORK POLICIES | NETWORK RULES | PACKAGES POLICIES | PIPES | PROCEDURES | MASKING POLICIES | PASSWORD POLICIES | PROJECTION POLICIES | REPLICATION GROUPS | ROLES | ROW ACCESS POLICIES | SCHEMAS | SESSION POLICIES | SECRETS | SEQUENCES | STAGES | STREAMS | TABLES | TAGS | TASKS | USERS | VIEWS | WAREHOUSES", - ValidateFunc: validation.StringInSlice(validGrantOwnershipPluralObjectTypesString, 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, @@ -208,6 +153,7 @@ func grantOwnershipBulkOperationSchema(branchName string) map[string]*schema.Sch func GrantOwnership() *schema.Resource { return &schema.Resource{ CreateContext: CreateGrantOwnership, + // There's no Update, because every field is marked as ForceNew DeleteContext: DeleteGrantOwnership, ReadContext: ReadGrantOwnership, @@ -304,7 +250,7 @@ func CreateGrantOwnership(ctx context.Context, d *schema.ResourceData, meta any) err = client.Grants.GrantOwnership( ctx, - grantOn, + *grantOn, getOwnershipGrantTo(d), getOwnershipGrantOpts(id), ) @@ -353,10 +299,9 @@ func DeleteGrantOwnership(ctx context.Context, d *schema.ResourceData, meta any) 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, + *grantOn, sdk.OwnershipGrantTo{ - AccountRoleName: sdk.Pointer(sdk.NewAccountObjectIdentifier(accountRoleName)), // TODO: What if current role is database role (not a common but possible case) - // DatabaseRoleName: TODO: handle later + AccountRoleName: sdk.Pointer(sdk.NewAccountObjectIdentifier(accountRoleName)), }, getOwnershipGrantOpts(id), ) @@ -450,6 +395,7 @@ func ReadGrantOwnership(ctx context.Context, d *schema.ResourceData, meta any) d 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 { @@ -467,9 +413,7 @@ func getOnObjectIdentifier(objectType sdk.ObjectType, objectName string) (sdk.Ob sdk.ObjectTypeRole, sdk.ObjectTypeUser, sdk.ObjectTypeWarehouse: - if _, ok := identifier.(sdk.AccountObjectIdentifier); !ok { - return nil, sdk.NewError(fmt.Sprintf("invalid object_name %s, expected account object identifier", objectName)) - } + return sdk.NewAccountObjectIdentifier(objectName), nil case sdk.ObjectTypeDatabaseRole, sdk.ObjectTypeSchema: if _, ok := identifier.(sdk.DatabaseObjectIdentifier); !ok { @@ -514,8 +458,8 @@ func getOnObjectIdentifier(objectType sdk.ObjectType, objectName string) (sdk.Ob return identifier, nil } -func getOwnershipGrantOn(d *schema.ResourceData) (sdk.OwnershipGrantOn, error) { - var ownershipGrantOn sdk.OwnershipGrantOn +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) @@ -528,7 +472,7 @@ func getOwnershipGrantOn(d *schema.ResourceData) (sdk.OwnershipGrantOn, error) { objectType := sdk.ObjectType(strings.ToUpper(onObjectType)) objectName, err := getOnObjectIdentifier(objectType, onObjectName) if err != nil { - return ownershipGrantOn, err + return nil, err } ownershipGrantOn.Object = &sdk.Object{ ObjectType: objectType, @@ -558,18 +502,18 @@ func getOwnershipGrantTo(d *schema.ResourceData) sdk.OwnershipGrantTo { } func getOwnershipGrantOpts(id *GrantOwnershipId) *sdk.GrantOwnershipOptions { + opts := new(sdk.GrantOwnershipOptions) + if id != nil && id.OutboundPrivilegesBehavior != nil { outboundPrivileges := id.OutboundPrivilegesBehavior.ToOwnershipCurrentGrantsOutboundPrivileges() if outboundPrivileges != nil { - return &sdk.GrantOwnershipOptions{ - CurrentGrants: &sdk.OwnershipCurrentGrants{ - OutboundPrivileges: *outboundPrivileges, - }, + opts.CurrentGrants = &sdk.OwnershipCurrentGrants{ + OutboundPrivileges: *outboundPrivileges, } } } - return new(sdk.GrantOwnershipOptions) + return opts } func prepareShowGrantsRequestForGrantOwnership(id *GrantOwnershipId) (*sdk.ShowGrantOptions, sdk.ObjectType) { diff --git a/pkg/resources/grant_ownership_acceptance_test.go b/pkg/resources/grant_ownership_acceptance_test.go index 2e661ffd31..a6aa30a5d9 100644 --- a/pkg/resources/grant_ownership_acceptance_test.go +++ b/pkg/resources/grant_ownership_acceptance_test.go @@ -3,6 +3,7 @@ package resources_test import ( "context" "fmt" + "regexp" "slices" "strings" "testing" @@ -64,6 +65,52 @@ func TestAcc_GrantOwnership_OnObject_Database_ToAccountRole(t *testing.T) { }) } +func TestAcc_GrantOwnership_OnObject_Database_IdentifiersWithDots(t *testing.T) { + 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) { databaseName := strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha)) schemaName := strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha)) @@ -462,6 +509,50 @@ func TestAcc_GrantOwnership_OnFuture_InSchema_ToAccountRole(t *testing.T) { }) } +func TestAcc_GrantOwnership_InvalidConfiguration_EmptyObjectType(t *testing.T) { + 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) { + 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` can be specified"), + }, + }, + }) +} + 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 diff --git a/pkg/resources/grant_ownership_test.go b/pkg/resources/grant_ownership_test.go index 705f4308de..878eb543c2 100644 --- a/pkg/resources/grant_ownership_test.go +++ b/pkg/resources/grant_ownership_test.go @@ -52,6 +52,12 @@ func TestGetOnObjectIdentifier(t *testing.T) { 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, @@ -64,12 +70,6 @@ func TestGetOnObjectIdentifier(t *testing.T) { ObjectName: "some_share", Error: "object_type SHARE is not supported", }, - { - Name: "validation - invalid account object identifier", - ObjectType: sdk.ObjectTypeDatabase, - ObjectName: "test_database.test_schema", - Error: "invalid object_name test_database.test_schema, expected account object identifier", - }, { Name: "validation - invalid database object identifier", ObjectType: sdk.ObjectTypeSchema, @@ -261,7 +261,8 @@ func TestGetOwnershipGrantOn(t *testing.T) { grantOn, err := getOwnershipGrantOn(d) if tt.Error == "" { assert.NoError(t, err) - assert.Equal(t, tt.Expected, grantOn) + assert.NotNil(t, grantOn) + assert.Equal(t, tt.Expected, *grantOn) } else { assert.ErrorContains(t, err, tt.Error) } @@ -393,13 +394,11 @@ func TestPrepareShowGrantsRequestForGrantOwnership(t *testing.T) { } } -func TestGetOwnershipGrantTo(t *testing.T) { +func TestValidAccountRoleNameGetOwnershipGrantTo(t *testing.T) { testCases := []struct { - Name string - AccountRole *string - DatabaseRole *string - Expected sdk.OwnershipGrantTo - ExpectPanic bool + Name string + AccountRole *string + Expected sdk.OwnershipGrantTo }{ { Name: "account role name", @@ -415,6 +414,33 @@ func TestGetOwnershipGrantTo(t *testing.T) { 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"), @@ -429,47 +455,33 @@ func TestGetOwnershipGrantTo(t *testing.T) { DatabaseRoleName: sdk.Pointer(sdk.NewDatabaseObjectIdentifier("test_database", "database_role_name")), }, }, - { - Name: "validation - incorrect account role name", - AccountRole: sdk.String("database_name.account_role_name"), - ExpectPanic: true, - }, - { - Name: "validation - incorrect database role name", - DatabaseRole: sdk.String("database_name.schema_name.database_role_name"), - ExpectPanic: true, - }, } for _, tt := range testCases { tt := tt t.Run(tt.Name, func(t *testing.T) { - config := make(map[string]any) - if tt.AccountRole != nil { - config["account_role_name"] = *tt.AccountRole - } - if tt.DatabaseRole != nil { - config["database_role_name"] = *tt.DatabaseRole - } - d := schema.TestResourceDataRaw(t, grantOwnershipSchema, config) - - defer func() { - if err := recover(); err != nil { - assert.True(t, tt.ExpectPanic) - } - }() - grantTo := getOwnershipGrantTo(d) + grantTo := getOwnershipGrantTo(schema.TestResourceDataRaw(t, grantOwnershipSchema, map[string]any{ + "database_role_name": *tt.DatabaseRole, + })) - if tt.Expected.AccountRoleName != nil { - assert.Equal(t, *tt.Expected.AccountRoleName, *grantTo.AccountRoleName) - } - if tt.Expected.DatabaseRoleName != nil { - assert.Equal(t, *tt.Expected.DatabaseRoleName, *grantTo.DatabaseRoleName) - } + 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", + }) + + defer func() { + if err := recover(); err != nil { + assert.ErrorContains(t, err.(error), "index out of range") + } + }() + _ = getOwnershipGrantTo(d) +} + func TestGetOwnershipGrantOpts(t *testing.T) { testCases := []struct { Name string diff --git a/pkg/resources/grant_privileges_to_database_role.go b/pkg/resources/grant_privileges_to_database_role.go index 24161eafde..6dc873d323 100644 --- a/pkg/resources/grant_privileges_to_database_role.go +++ b/pkg/resources/grant_privileges_to_database_role.go @@ -225,14 +225,14 @@ var grantPrivilegesOnDatabaseRoleBulkOperationSchema = map[string]*schema.Schema Type: schema.TypeString, Optional: true, ForceNew: true, - Description: "TODO", + Description: "The fully qualified name of the database.", ValidateDiagFunc: IsValidIdentifier[sdk.AccountObjectIdentifier](), }, "in_schema": { Type: schema.TypeString, Optional: true, ForceNew: true, - Description: "TODO", + 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/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/testint/helpers_test.go b/pkg/sdk/testint/helpers_test.go index b969f0cab1..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.NewAccountObjectIdentifier(random.AlphaN(20)) + id := sdk.RandomAlphanumericAccountObjectIdentifier() ctx := context.Background() err := client.FailoverGroups.Create(ctx, id, objectTypes, allowedAccounts, opts) require.NoError(t, err) diff --git a/templates/resources/grant_ownership.md.tmpl b/templates/resources/grant_ownership.md.tmpl index c98859e242..3de22b6887 100644 --- a/templates/resources/grant_ownership.md.tmpl +++ b/templates/resources/grant_ownership.md.tmpl @@ -10,11 +10,14 @@ description: |- {{- 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 */}} -!> **Warning** Be careful when using `always_apply` field. It will always produce a plan (even when no changes were made) and can be harmful in some setups. For more details why we decided to introduce it to go our document explaining those design decisions (coming soon). # {{.Name}} ({{.Type}}) @@ -45,48 +48,4 @@ where: It has varying number of parts, depending on grant_type. All the possible types are: -### OnObject -`terraform import "|||OnObject||"` - -### OnAll - -OnAll contains inner types for all options. - -#### InDatabase -`terraform import "|||OnAll||InDatabase|"` - -#### InSchema -`terraform import "|||OnAll||InSchema|"` - -### OnAll - -OnFuture contains inner types for all options. - -#### 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\""` +{{ index (split (codefile "" .ImportFile) "```") 1 | trimspace }} From 66ca523a80f6c939bfc42c4ccec09a3429f1f9ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Cie=C5=9Blak?= Date: Tue, 12 Mar 2024 15:18:59 +0100 Subject: [PATCH 13/15] changes after review --- pkg/resources/grant_ownership_acceptance_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/resources/grant_ownership_acceptance_test.go b/pkg/resources/grant_ownership_acceptance_test.go index a6aa30a5d9..c7c3e473e9 100644 --- a/pkg/resources/grant_ownership_acceptance_test.go +++ b/pkg/resources/grant_ownership_acceptance_test.go @@ -547,7 +547,7 @@ func TestAcc_GrantOwnership_InvalidConfiguration_MultipleTargets(t *testing.T) { { 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` can be specified"), + ExpectError: regexp.MustCompile("only one of `on.0.all,on.0.future,on.0.object_name`"), }, }, }) From e855a9291ff0e7f79aa609d9640a7d7e2e304273 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Cie=C5=9Blak?= Date: Wed, 13 Mar 2024 14:46:16 +0100 Subject: [PATCH 14/15] change before merge --- docs/resources/grant_ownership.md | 286 ------------------ pkg/provider/provider.go | 1 - pkg/resources/grant_ownership_test.go | 9 +- .../resources => tmp}/grant_ownership.md.tmpl | 0 .../snowflake_grant_ownership/import.sh | 0 .../snowflake_grant_ownership/resource.tf | 0 6 files changed, 3 insertions(+), 293 deletions(-) delete mode 100644 docs/resources/grant_ownership.md rename {templates/resources => tmp}/grant_ownership.md.tmpl (100%) rename {examples/resources => tmp}/snowflake_grant_ownership/import.sh (100%) rename {examples/resources => tmp}/snowflake_grant_ownership/resource.tf (100%) diff --git a/docs/resources/grant_ownership.md b/docs/resources/grant_ownership.md deleted file mode 100644 index d28fbf3ad7..0000000000 --- a/docs/resources/grant_ownership.md +++ /dev/null @@ -1,286 +0,0 @@ ---- -# generated by https://github.com/hashicorp/terraform-plugin-docs -page_title: "snowflake_grant_ownership Resource - terraform-provider-snowflake" -subcategory: "" -description: |- - ---- - - -!> **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). - - - - -# snowflake_grant_ownership (Resource) - - - -## Example Usage - -```terraform -################################## -### 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}\"" - } - } -} -``` - - -## Schema - -### Required - -- `on` (Block List, Min: 1, Max: 1) Configures which object(s) should transfer their ownership to the specified role. (see [below for nested schema](#nestedblock--on)) - -### Optional - -- `account_role_name` (String) The fully qualified name of the account role to which privileges will be granted. -- `database_role_name` (String) The fully qualified name of the database role to which privileges will be granted. -- `outbound_privileges` (String) 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). - -### Read-Only - -- `id` (String) The ID of this resource. - - -### Nested Schema for `on` - -Optional: - -- `all` (Block List, Max: 1) Configures the privilege to be granted on all objects in either a database or schema. (see [below for nested schema](#nestedblock--on--all)) -- `future` (Block List, Max: 1) Configures the privilege to be granted on all objects in either a database or schema. (see [below for nested schema](#nestedblock--on--future)) -- `object_name` (String) Specifies the identifier for the object on which you are transferring ownership. -- `object_type` (String) Specifies the type of object on which you are transferring ownership. Available values are: AGGREGATION POLICY | ALERT | AUTHENTICATION POLICY | COMPUTE POOL | DATABASE | DATABASE ROLE | DYNAMIC TABLE | EVENT TABLE | EXTERNAL TABLE | EXTERNAL VOLUME | FAILOVER GROUP | FILE FORMAT | FUNCTION | HYBRID TABLE | ICEBERG TABLE | IMAGE REPOSITORY | INTEGRATION | MATERIALIZED VIEW | NETWORK POLICY | NETWORK RULE | PACKAGES POLICY | PIPE | PROCEDURE | MASKING POLICY | PASSWORD POLICY | PROJECTION POLICY | REPLICATION GROUP | ROLE | ROW ACCESS POLICY | SCHEMA | SESSION POLICY | SECRET | SEQUENCE | STAGE | STREAM | TABLE | TAG | TASK | USER | VIEW | WAREHOUSE - - -### Nested Schema for `on.all` - -Required: - -- `object_type_plural` (String) Specifies the type of object in plural form on which you are transferring ownership. Available values are: AGGREGATION POLICIES | ALERTS | AUTHENTICATION POLICIES | COMPUTE POOLS | DATABASES | DATABASE ROLES | DYNAMIC TABLES | EVENT TABLES | EXTERNAL TABLES | EXTERNAL VOLUMES | FAILOVER GROUPS | FILE FORMATS | FUNCTIONS | HYBRID TABLES | ICEBERG TABLES | IMAGE REPOSITORIES | INTEGRATIONS | MATERIALIZED VIEWS | NETWORK POLICIES | NETWORK RULES | PACKAGES POLICIES | PIPES | PROCEDURES | MASKING POLICIES | PASSWORD POLICIES | PROJECTION POLICIES | REPLICATION GROUPS | ROLES | ROW ACCESS POLICIES | SCHEMAS | SESSION POLICIES | SECRETS | SEQUENCES | STAGES | STREAMS | TABLES | TAGS | TASKS | USERS | VIEWS | WAREHOUSES. For more information head over to [Snowflake documentation](https://docs.snowflake.com/en/sql-reference/sql/grant-ownership#required-parameters). - -Optional: - -- `in_database` (String) The fully qualified name of the database. -- `in_schema` (String) The fully qualified name of the schema. - - - -### Nested Schema for `on.future` - -Required: - -- `object_type_plural` (String) Specifies the type of object in plural form on which you are transferring ownership. Available values are: AGGREGATION POLICIES | ALERTS | AUTHENTICATION POLICIES | COMPUTE POOLS | DATABASES | DATABASE ROLES | DYNAMIC TABLES | EVENT TABLES | EXTERNAL TABLES | EXTERNAL VOLUMES | FAILOVER GROUPS | FILE FORMATS | FUNCTIONS | HYBRID TABLES | ICEBERG TABLES | IMAGE REPOSITORIES | INTEGRATIONS | MATERIALIZED VIEWS | NETWORK POLICIES | NETWORK RULES | PACKAGES POLICIES | PIPES | PROCEDURES | MASKING POLICIES | PASSWORD POLICIES | PROJECTION POLICIES | REPLICATION GROUPS | ROLES | ROW ACCESS POLICIES | SCHEMAS | SESSION POLICIES | SECRETS | SEQUENCES | STAGES | STREAMS | TABLES | TAGS | TASKS | USERS | VIEWS | WAREHOUSES. For more information head over to [Snowflake documentation](https://docs.snowflake.com/en/sql-reference/sql/grant-ownership#required-parameters). - -Optional: - -- `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 (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: - -### 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/pkg/provider/provider.go b/pkg/provider/provider.go index 6a0f8042b8..f11d1512c7 100644 --- a/pkg/provider/provider.go +++ b/pkg/provider/provider.go @@ -468,7 +468,6 @@ func getResources() map[string]*schema.Resource { "snowflake_function": resources.Function(), "snowflake_grant_account_role": resources.GrantAccountRole(), "snowflake_grant_database_role": resources.GrantDatabaseRole(), - "snowflake_grant_ownership": resources.GrantOwnership(), "snowflake_grant_privileges_to_role": resources.GrantPrivilegesToRole(), "snowflake_grant_privileges_to_account_role": resources.GrantPrivilegesToAccountRole(), "snowflake_grant_privileges_to_database_role": resources.GrantPrivilegesToDatabaseRole(), diff --git a/pkg/resources/grant_ownership_test.go b/pkg/resources/grant_ownership_test.go index 878eb543c2..25d898b9a0 100644 --- a/pkg/resources/grant_ownership_test.go +++ b/pkg/resources/grant_ownership_test.go @@ -474,12 +474,9 @@ func TestInvalidDatabaseRoleGetOwnershipGrantTo(t *testing.T) { "database_role_name": "account_role_name", }) - defer func() { - if err := recover(); err != nil { - assert.ErrorContains(t, err.(error), "index out of range") - } - }() - _ = getOwnershipGrantTo(d) + assert.Panics(t, func() { + _ = getOwnershipGrantTo(d) + }) } func TestGetOwnershipGrantOpts(t *testing.T) { diff --git a/templates/resources/grant_ownership.md.tmpl b/tmp/grant_ownership.md.tmpl similarity index 100% rename from templates/resources/grant_ownership.md.tmpl rename to tmp/grant_ownership.md.tmpl diff --git a/examples/resources/snowflake_grant_ownership/import.sh b/tmp/snowflake_grant_ownership/import.sh similarity index 100% rename from examples/resources/snowflake_grant_ownership/import.sh rename to tmp/snowflake_grant_ownership/import.sh diff --git a/examples/resources/snowflake_grant_ownership/resource.tf b/tmp/snowflake_grant_ownership/resource.tf similarity index 100% rename from examples/resources/snowflake_grant_ownership/resource.tf rename to tmp/snowflake_grant_ownership/resource.tf From 9737b07e62eec0b4503f80e99a690a80c92e9089 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Cie=C5=9Blak?= Date: Thu, 14 Mar 2024 09:39:28 +0100 Subject: [PATCH 15/15] skip ownership acceptance tests --- .../grant_ownership_acceptance_test.go | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/pkg/resources/grant_ownership_acceptance_test.go b/pkg/resources/grant_ownership_acceptance_test.go index c7c3e473e9..009433a67e 100644 --- a/pkg/resources/grant_ownership_acceptance_test.go +++ b/pkg/resources/grant_ownership_acceptance_test.go @@ -20,6 +20,8 @@ import ( ) 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() @@ -66,6 +68,8 @@ func TestAcc_GrantOwnership_OnObject_Database_ToAccountRole(t *testing.T) { } 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() @@ -112,6 +116,8 @@ func TestAcc_GrantOwnership_OnObject_Database_IdentifiersWithDots(t *testing.T) } 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() @@ -160,6 +166,8 @@ func TestAcc_GrantOwnership_OnObject_Schema_ToAccountRole(t *testing.T) { } 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() @@ -208,6 +216,8 @@ func TestAcc_GrantOwnership_OnObject_Schema_ToDatabaseRole(t *testing.T) { } 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)) @@ -258,6 +268,8 @@ func TestAcc_GrantOwnership_OnObject_Table_ToAccountRole(t *testing.T) { } 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)) @@ -308,6 +320,8 @@ func TestAcc_GrantOwnership_OnObject_Table_ToDatabaseRole(t *testing.T) { } 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() @@ -361,6 +375,8 @@ func TestAcc_GrantOwnership_OnAll_InDatabase_ToAccountRole(t *testing.T) { } 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() @@ -414,6 +430,8 @@ func TestAcc_GrantOwnership_OnAll_InSchema_ToAccountRole(t *testing.T) { } 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() @@ -461,6 +479,8 @@ func TestAcc_GrantOwnership_OnFuture_InDatabase_ToAccountRole(t *testing.T) { } 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() @@ -510,6 +530,8 @@ func TestAcc_GrantOwnership_OnFuture_InSchema_ToAccountRole(t *testing.T) { } 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))), @@ -532,6 +554,8 @@ func TestAcc_GrantOwnership_InvalidConfiguration_EmptyObjectType(t *testing.T) { } 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))),