From 92a6cbaedd876d495223c57877397a05662d1e28 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Cie=C5=9Blak?= Date: Thu, 14 Dec 2023 13:50:55 +0100 Subject: [PATCH 01/16] wip --- pkg/provider/provider.go | 1 + .../grant_privileges_to_database_role.go | 155 ++++++++++++++++++ pkg/sdk/validators.go | 1 + 3 files changed, 157 insertions(+) create mode 100644 pkg/resources/grant_privileges_to_database_role.go create mode 100644 pkg/sdk/validators.go diff --git a/pkg/provider/provider.go b/pkg/provider/provider.go index f51cb5e706..6091561a00 100644 --- a/pkg/provider/provider.go +++ b/pkg/provider/provider.go @@ -444,6 +444,7 @@ func getResources() map[string]*schema.Resource { "snowflake_file_format": resources.FileFormat(), "snowflake_function": resources.Function(), "snowflake_grant_privileges_to_role": resources.GrantPrivilegesToRole(), + "snowflake_grant_privileges_to_database_role": resources.GrantPrivilegesToDatabaseRole(), "snowflake_managed_account": resources.ManagedAccount(), "snowflake_masking_policy": resources.MaskingPolicy(), "snowflake_materialized_view": resources.MaterializedView(), diff --git a/pkg/resources/grant_privileges_to_database_role.go b/pkg/resources/grant_privileges_to_database_role.go new file mode 100644 index 0000000000..fcfa7ae589 --- /dev/null +++ b/pkg/resources/grant_privileges_to_database_role.go @@ -0,0 +1,155 @@ +package resources + +import ( + "context" + "github.com/hashicorp/go-cty/cty" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "slices" + "strings" +) + +var grantPrivilegesToDatabaseRoleSchema = map[string]*schema.Schema{ + "database_role_name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + Description: "The fully qualified name of the database role to which privileges will be granted.", + }, + "privileges": { + Type: schema.TypeSet, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + Optional: true, + ValidateDiagFunc: doesNotContainOwnershipGrant(), + ExactlyOneOf: []string{ + "privileges", + "all_privileges", + }, + }, + "all_privileges": { + Type: schema.TypeBool, + Optional: true, + Default: false, + ExactlyOneOf: []string{ + "privileges", + "all_privileges", + }, + }, + "with_grant_option": { + Type: schema.TypeBool, + Optional: true, + Default: false, + }, + "database_name": { + Type: schema.TypeString, + Optional: true, + Default: false, + }, + "on_schema": { + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "schema_name": { + Type: schema.TypeString, + Optional: true, + }, + "all_schemas_in_database": { + Type: schema.TypeString, + Optional: true, + }, + "future_schemas_in_database": { + Type: schema.TypeString, + Optional: true, + }, + }, + }, + }, + "on_schema_object": { + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "object_name": { + Type: schema.TypeString, + Optional: true, + }, + "object_type": { + Type: schema.TypeString, + Optional: true, + }, + "all": { + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: grantPrivilegesOnDatabaseRoleBulkOperationSchema, + }, + }, + "future": { + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: grantPrivilegesOnDatabaseRoleBulkOperationSchema, + }, + }, + }, + }, + }, +} + +var grantPrivilegesOnDatabaseRoleBulkOperationSchema = map[string]*schema.Schema{ + "object_type_plural": { + Type: schema.TypeString, + Required: true, + }, + "in_database": { + Type: schema.TypeString, + Optional: true, + }, + "in_schema": { + Type: schema.TypeString, + Optional: true, + }, +} + +func doesNotContainOwnershipGrant() func(value any, path cty.Path) diag.Diagnostics { + return func(value any, path cty.Path) diag.Diagnostics { + var diags diag.Diagnostics + if privileges, ok := value.([]string); ok { + if slices.ContainsFunc(privileges, func(privilege string) bool { + return strings.ToUpper(privilege) == "OWNERSHIP" + }) { + diags = append(diags, diag.Diagnostic{ + Severity: diag.Error, + Summary: "Unsupported privilege type 'OWNERSHIP'.", + // TODO: Change when a new resource for granting ownership will be available + Detail: "Granting ownership is only allowed in dedicated resources (snowflake_user_ownership_grant, snowflake_role_ownership_grant)", + AttributePath: nil, + }) + } + } + return diags + } +} + +func GrantPrivilegesToDatabaseRole() *schema.Resource { + return &schema.Resource{ + Create: CreateGrantPrivilegesToRole, + Read: ReadGrantPrivilegesToRole, + Delete: DeleteGrantPrivilegesToRole, + Update: UpdateGrantPrivilegesToRole, + + Schema: grantPrivilegesToRoleSchema, + Importer: &schema.ResourceImporter{ + StateContext: func(ctx context.Context, d *schema.ResourceData, m interface{}) ([]*schema.ResourceData, error) { + return nil, nil + }, + }, + } +} diff --git a/pkg/sdk/validators.go b/pkg/sdk/validators.go new file mode 100644 index 0000000000..0919fd1b06 --- /dev/null +++ b/pkg/sdk/validators.go @@ -0,0 +1 @@ +package sdk From 9a55c8c452cf44f4f4c5ecbe72b6b1bdc9c1ac54 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Cie=C5=9Blak?= Date: Mon, 18 Dec 2023 15:27:40 +0100 Subject: [PATCH 02/16] parse internal tf identifier --- .../grant_privileges_to_database_role.go | 411 +++++++++++++++--- ..._privileges_to_database_role_identifier.go | 160 +++++++ ...ileges_to_database_role_identifier_test.go | 250 +++++++++++ pkg/resources/grant_privileges_to_role.go | 43 +- pkg/resources/helpers.go | 14 + pkg/sdk/grants.go | 1 + pkg/sdk/grants_test.go | 8 + pkg/sdk/grants_validations.go | 4 +- 8 files changed, 816 insertions(+), 75 deletions(-) create mode 100644 pkg/resources/grant_privileges_to_database_role_identifier.go create mode 100644 pkg/resources/grant_privileges_to_database_role_identifier_test.go diff --git a/pkg/resources/grant_privileges_to_database_role.go b/pkg/resources/grant_privileges_to_database_role.go index fcfa7ae589..a5c6227a06 100644 --- a/pkg/resources/grant_privileges_to_database_role.go +++ b/pkg/resources/grant_privileges_to_database_role.go @@ -2,101 +2,221 @@ package resources import ( "context" + "database/sql" + "fmt" + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk" "github.com/hashicorp/go-cty/cty" "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" "slices" "strings" ) var grantPrivilegesToDatabaseRoleSchema = map[string]*schema.Schema{ "database_role_name": { - Type: schema.TypeString, - Required: true, - ForceNew: true, - Description: "The fully qualified name of the database role to which privileges will be granted.", + Type: schema.TypeString, + Required: true, + ForceNew: true, + Description: "The fully qualified name of the database role to which privileges will be granted.", + ValidateDiagFunc: IsValidIdentifier[sdk.DatabaseObjectIdentifier](), }, "privileges": { - Type: schema.TypeSet, - Elem: &schema.Schema{ - Type: schema.TypeString, - }, + Type: schema.TypeSet, Optional: true, + Description: "The privileges to grant on the database role.", ValidateDiagFunc: doesNotContainOwnershipGrant(), ExactlyOneOf: []string{ "privileges", "all_privileges", }, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, }, "all_privileges": { - Type: schema.TypeBool, - Optional: true, - Default: false, + Type: schema.TypeBool, + Optional: true, + Default: false, + Description: "Grant all privileges on the database role.", ExactlyOneOf: []string{ "privileges", "all_privileges", }, }, "with_grant_option": { - Type: schema.TypeBool, - Optional: true, - Default: false, + Type: schema.TypeBool, + Optional: true, + Default: false, + ForceNew: true, + Description: "Specifies whether the grantee can grant the privileges to other users.", }, - "database_name": { - Type: schema.TypeString, - Optional: true, - Default: false, + "on_database": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + Description: "The fully qualified name of the database on which privileges will be granted. If the identifier is not fully qualified (in the form of .≤database_role_name>), the command looks for the database role in the current database for the session. All privileges are limited to the database that contains the database role, as well as other objects in the same database.", + ValidateDiagFunc: IsValidIdentifier[sdk.AccountObjectIdentifier](), + ExactlyOneOf: []string{ + "on_database", + "on_schema", + "on_schema_object", + }, }, "on_schema": { - Type: schema.TypeList, - Optional: true, - MaxItems: 1, + Type: schema.TypeList, + Optional: true, + ForceNew: true, + Description: "Specifies the schema on which privileges will be granted.", + MaxItems: 1, + ExactlyOneOf: []string{ + "on_database", + "on_schema", + "on_schema_object", + }, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "schema_name": { - Type: schema.TypeString, - Optional: true, + Type: schema.TypeString, + Optional: true, + ForceNew: true, + Description: "The fully qualified name of the schema.", + ValidateDiagFunc: IsValidIdentifier[sdk.DatabaseObjectIdentifier](), + ExactlyOneOf: []string{ + "on_schema.0.schema_name", + "on_schema.0.all_schemas_in_database", + "on_schema.0.future_schemas_in_database", + }, }, "all_schemas_in_database": { - Type: schema.TypeString, - Optional: true, + Type: schema.TypeString, + Optional: true, + ForceNew: true, + Description: "The fully qualified name of the database.", + ValidateDiagFunc: IsValidIdentifier[sdk.AccountObjectIdentifier](), + ExactlyOneOf: []string{ + "on_schema.0.schema_name", + "on_schema.0.all_schemas_in_database", + "on_schema.0.future_schemas_in_database", + }, }, "future_schemas_in_database": { - Type: schema.TypeString, - Optional: true, + Type: schema.TypeString, + Optional: true, + ForceNew: true, + Description: "The fully qualified name of the database.", + ValidateDiagFunc: IsValidIdentifier[sdk.AccountObjectIdentifier](), + ExactlyOneOf: []string{ + "on_schema.0.schema_name", + "on_schema.0.all_schemas_in_database", + "on_schema.0.future_schemas_in_database", + }, }, }, }, }, "on_schema_object": { - Type: schema.TypeList, - Optional: true, - MaxItems: 1, + Type: schema.TypeList, + Optional: true, + ForceNew: true, + Description: "Specifies the schema object on which privileges will be granted.", + MaxItems: 1, + ExactlyOneOf: []string{ + "on_database", + "on_schema", + "on_schema_object", + }, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ - "object_name": { - Type: schema.TypeString, - Optional: true, - }, "object_type": { - Type: schema.TypeString, - Optional: true, + Type: schema.TypeString, + Optional: true, + ForceNew: true, + Description: "The object type of the schema object on which privileges will be granted. Valid values are: ALERT | DYNAMIC TABLE | EVENT TABLE | FILE FORMAT | FUNCTION | PROCEDURE | SECRET | SEQUENCE | PIPE | MASKING POLICY | PASSWORD POLICY | ROW ACCESS POLICY | SESSION POLICY | TAG | STAGE | STREAM | TABLE | EXTERNAL TABLE | TASK | VIEW | MATERIALIZED VIEW | NETWORK RULE | PACKAGES POLICY | ICEBERG TABLE", + RequiredWith: []string{ + "on_schema_object.0.object_name", + }, + ConflictsWith: []string{ + "on_schema_object.0.all", + "on_schema_object.0.future", + }, + ValidateFunc: validation.StringInSlice([]string{ + "ALERT", + "DYNAMIC TABLE", + "EVENT TABLE", + "FILE FORMAT", + "FUNCTION", + "PROCEDURE", + "SECRET", + "SEQUENCE", + "PIPE", + "MASKING POLICY", + "PASSWORD POLICY", + "ROW ACCESS POLICY", + "SESSION POLICY", + "TAG", + "STAGE", + "STREAM", + "TABLE", + "EXTERNAL TABLE", + "TASK", + "VIEW", + "MATERIALIZED VIEW", + "NETWORK RULE", + "PACKAGES POLICY", + "ICEBERG TABLE", + }, true), + }, + "object_name": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + Description: "The fully qualified name of the object on which privileges will be granted.", + RequiredWith: []string{ + "on_schema_object.0.object_type", + }, + ExactlyOneOf: []string{ + "on_schema_object.0.object_name", + "on_schema_object.0.all", + "on_schema_object.0.future", + }, + ValidateDiagFunc: IsValidIdentifier[sdk.SchemaObjectIdentifier](), }, "all": { - Type: schema.TypeList, - Optional: true, - MaxItems: 1, + 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: grantPrivilegesOnDatabaseRoleBulkOperationSchema, }, + ConflictsWith: []string{ + "on_schema_object.0.object_type", + }, + ExactlyOneOf: []string{ + "on_schema_object.0.object_name", + "on_schema_object.0.all", + "on_schema_object.0.future", + }, }, "future": { - Type: schema.TypeList, - Optional: true, - MaxItems: 1, + Type: schema.TypeList, + Optional: true, + ForceNew: true, + Description: "Configures the privilege to be granted on future objects in either a database or schema.", + MaxItems: 1, Elem: &schema.Resource{ Schema: grantPrivilegesOnDatabaseRoleBulkOperationSchema, }, + ConflictsWith: []string{ + "on_schema_object.0.object_type", + }, + ExactlyOneOf: []string{ + "on_schema_object.0.object_name", + "on_schema_object.0.all", + "on_schema_object.0.future", + }, }, }, }, @@ -105,16 +225,48 @@ var grantPrivilegesToDatabaseRoleSchema = map[string]*schema.Schema{ var grantPrivilegesOnDatabaseRoleBulkOperationSchema = map[string]*schema.Schema{ "object_type_plural": { - Type: schema.TypeString, - Required: true, + Type: schema.TypeString, + Required: true, + ForceNew: true, + Description: "The plural object type of the schema object on which privileges will be granted. Valid values are: ALERTS | DYNAMIC TABLES | EVENT TABLES | FILE FORMATS | FUNCTIONS | PROCEDURES | SECRETS | SEQUENCES | PIPES | MASKING POLICIES | PASSWORD POLICIES | ROW ACCESS POLICIES | SESSION POLICIES | TAGS | STAGES | STREAMS | TABLES | EXTERNAL TABLES | TASKS | VIEWS | MATERIALIZED VIEWS | NETWORK RULES | PACKAGES POLICIES | ICEBERG TABLES", + ValidateFunc: validation.StringInSlice([]string{ + "ALERTS", + "DYNAMIC TABLES", + "EVENT TABLES", + "FILE FORMATS", + "FUNCTIONS", + "PROCEDURES", + "SECRETS", + "SEQUENCES", + "PIPES", + "MASKING POLICIES", + "PASSWORD POLICIES", + "ROW ACCESS POLICIES", + "SESSION POLICIES", + "TAGS", + "STAGES", + "STREAMS", + "TABLES", + "EXTERNAL TABLES", + "TASKS", + "VIEWS", + "MATERIALIZED VIEWS", + "NETWORK RULES", + "PACKAGES POLICIES", + "ICEBERG TABLES", + }, true), }, "in_database": { - Type: schema.TypeString, - Optional: true, + Type: schema.TypeString, + Optional: true, + ForceNew: true, + ValidateDiagFunc: IsValidIdentifier[sdk.AccountObjectIdentifier](), }, "in_schema": { - Type: schema.TypeString, - Optional: true, + Type: schema.TypeString, + Optional: true, + ForceNew: true, + ValidateDiagFunc: IsValidIdentifier[sdk.DatabaseObjectIdentifier](), }, } @@ -140,12 +292,12 @@ func doesNotContainOwnershipGrant() func(value any, path cty.Path) diag.Diagnost func GrantPrivilegesToDatabaseRole() *schema.Resource { return &schema.Resource{ - Create: CreateGrantPrivilegesToRole, - Read: ReadGrantPrivilegesToRole, - Delete: DeleteGrantPrivilegesToRole, - Update: UpdateGrantPrivilegesToRole, + CreateContext: CreateGrantPrivilegesToDatabaseRole, + ReadContext: ReadGrantPrivilegesToDatabaseRole, + //Delete: DeleteGrantPrivilegesToRole, + //Update: UpdateGrantPrivilegesToRole, - Schema: grantPrivilegesToRoleSchema, + Schema: grantPrivilegesToDatabaseRoleSchema, Importer: &schema.ResourceImporter{ StateContext: func(ctx context.Context, d *schema.ResourceData, m interface{}) ([]*schema.ResourceData, error) { return nil, nil @@ -153,3 +305,156 @@ func GrantPrivilegesToDatabaseRole() *schema.Resource { }, } } + +func CreateGrantPrivilegesToDatabaseRole(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics { + var diags diag.Diagnostics + + db := meta.(*sql.DB) + client := sdk.NewClientFromDB(db) + + databaseRoleName := sdk.NewDatabaseObjectIdentifierFromFullyQualifiedName(d.Get("database_role_name").(string)) + err := client.Grants.GrantPrivilegesToDatabaseRole( + ctx, + getDatabaseRolePrivileges(d, getDatabaseRoleGrantOn(d)), + getDatabaseRoleGrantOn(d), + databaseRoleName, + &sdk.GrantPrivilegesToDatabaseRoleOptions{ + WithGrantOption: GetPropertyAsPointer[bool](d, "with_grant_option"), + }, + ) + if err != nil { + diags = append(diags, diag.Diagnostic{ + Severity: diag.Error, + Summary: fmt.Sprintf("An error occurred when granting privileges to database role (%s)", databaseRoleName), + Detail: fmt.Sprintf("Error: %s", err.Error()), + }) + return diags + } + + // TODO: Identifier d.SetId() + + return ReadGrantPrivilegesToDatabaseRole(ctx, d, meta) +} + +func ReadGrantPrivilegesToDatabaseRole(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics { + var diags diag.Diagnostics + + opts := &sdk.ShowGrantOptions{ + Future: nil, + On: nil, + To: nil, + Of: nil, + In: nil, + } + + db := meta.(*sql.DB) + client := sdk.NewClientFromDB(db) + client.Grants.Show(ctx, opts) + + return diags +} + +func getDatabaseRolePrivileges(d *schema.ResourceData, on *sdk.DatabaseRoleGrantOn) *sdk.DatabaseRoleGrantPrivileges { + var databaseRoleGrantPrivileges *sdk.DatabaseRoleGrantPrivileges + + if d.Get("all_privileges").(bool) { + databaseRoleGrantPrivileges.AllPrivileges = sdk.Bool(true) + return databaseRoleGrantPrivileges + } + + var privileges []string + if p, ok := d.GetOk("privileges"); ok { + privileges = expandStringList(p.(*schema.Set).List()) + } + + switch { + case on.Database != nil: + databasePrivileges := make([]sdk.AccountObjectPrivilege, len(privileges)) + for i, privilege := range privileges { + databasePrivileges[i] = sdk.AccountObjectPrivilege(privilege) + } + databaseRoleGrantPrivileges.DatabasePrivileges = databasePrivileges + case on.Schema != nil: + schemaPrivileges := make([]sdk.SchemaPrivilege, len(privileges)) + for i, privilege := range privileges { + schemaPrivileges[i] = sdk.SchemaPrivilege(privilege) + } + databaseRoleGrantPrivileges.SchemaPrivileges = schemaPrivileges + case on.SchemaObject != nil: + schemaObjectPrivileges := make([]sdk.SchemaObjectPrivilege, len(privileges)) + for i, privilege := range privileges { + schemaObjectPrivileges[i] = sdk.SchemaObjectPrivilege(privilege) + } + databaseRoleGrantPrivileges.SchemaObjectPrivileges = schemaObjectPrivileges + } + + return databaseRoleGrantPrivileges +} + +func getDatabaseRoleGrantOn(d *schema.ResourceData) *sdk.DatabaseRoleGrantOn { + onDatabase, onDatabaseOk := GetProperty[string](d, "on_database") + onSchema, onSchemaOk := GetProperty[map[string]any](d, "on_schema") + onSchemaObject, onSchemaObjectOk := GetProperty[map[string]any](d, "on_schema_object") + var on *sdk.DatabaseRoleGrantOn + + switch { + case onDatabaseOk: + on.Database = sdk.Pointer(sdk.NewAccountObjectIdentifierFromFullyQualifiedName(onDatabase)) + case onSchemaOk: + var grantOnSchema sdk.GrantOnSchema + + schemaName, schemaNameOk := onSchema["schema_name"] + allSchemasInDatabase, allSchemasInDatabaseOk := onSchema["all_schemas_in_database"] + futureSchemasInDatabase, futureSchemasInDatabaseOk := onSchema["future_schemas_in_database"] + + switch { + case schemaNameOk: + grantOnSchema.Schema = sdk.Pointer(sdk.NewDatabaseObjectIdentifierFromFullyQualifiedName(schemaName.(string))) + case allSchemasInDatabaseOk: + grantOnSchema.AllSchemasInDatabase = sdk.Pointer(sdk.NewAccountObjectIdentifierFromFullyQualifiedName(allSchemasInDatabase.(string))) + case futureSchemasInDatabaseOk: + grantOnSchema.FutureSchemasInDatabase = sdk.Pointer(sdk.NewAccountObjectIdentifierFromFullyQualifiedName(futureSchemasInDatabase.(string))) + } + + on.Schema = &grantOnSchema + case onSchemaObjectOk: + var grantOnSchemaObject sdk.GrantOnSchemaObject + + objectType, objectTypeOk := onSchemaObject["object_type"] + objectName, objectNameOk := onSchemaObject["object_name"] + all, allOk := onSchemaObject["all"] + future, futureOk := onSchemaObject["future"] + + switch { + case objectTypeOk && objectNameOk: + grantOnSchemaObject.SchemaObject = &sdk.Object{ + ObjectType: sdk.ObjectType(objectType.(string)), // TODO: Should we validate it or just cast it + Name: sdk.NewSchemaObjectIdentifierFromFullyQualifiedName(objectName.(string)), + } + case allOk: + grantOnSchemaObject.All = getGrantOnSchemaObjectIn(all.(map[string]any)) + case futureOk: + grantOnSchemaObject.Future = getGrantOnSchemaObjectIn(future.(map[string]any)) + } + + on.SchemaObject = &grantOnSchemaObject + } + + return on +} + +func getGrantOnSchemaObjectIn(m map[string]any) *sdk.GrantOnSchemaObjectIn { + grantOnSchemaObjectIn := &sdk.GrantOnSchemaObjectIn{ + PluralObjectType: sdk.PluralObjectType(m["object_type_plural"].(string)), + } + + if inDatabase, inDatabaseOk := m["in_database"]; inDatabaseOk { + grantOnSchemaObjectIn.InDatabase = sdk.Pointer(sdk.NewAccountObjectIdentifierFromFullyQualifiedName(inDatabase.(string))) + } + + if inSchema, inSchemaOk := m["in_schema"]; inSchemaOk { + grantOnSchemaObjectIn.InSchema = sdk.Pointer(sdk.NewDatabaseObjectIdentifierFromFullyQualifiedName(inSchema.(string))) + } + + return grantOnSchemaObjectIn +} diff --git a/pkg/resources/grant_privileges_to_database_role_identifier.go b/pkg/resources/grant_privileges_to_database_role_identifier.go new file mode 100644 index 0000000000..e3c3d0f69c --- /dev/null +++ b/pkg/resources/grant_privileges_to_database_role_identifier.go @@ -0,0 +1,160 @@ +package resources + +import ( + "fmt" + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/helpers" + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk" + "strings" +) + +// TODO: Add unit tests for marshaling / unmarshalling + +type DatabaseRoleGrantKind string + +const ( + OnDatabaseDatabaseRoleGrantKind DatabaseRoleGrantKind = "OnDatabase" + OnSchemaDatabaseRoleGrantKind DatabaseRoleGrantKind = "OnSchema" + OnSchemaObjectDatabaseRoleGrantKind DatabaseRoleGrantKind = "OnSchemaObject" +) + +// TODO: Move to the shareable file between this and grant_priv_to_role.go file +type OnSchemaGrantKind string + +const ( + OnSchemaSchemaGrantKind OnSchemaGrantKind = "OnSchema" + OnAllSchemasInDatabaseSchemaGrantKind OnSchemaGrantKind = "OnAllSchemasInDatabase" + OnFutureSchemasInDatabaseSchemaGrantKind OnSchemaGrantKind = "OnFutureSchemasInDatabase" +) + +// TODO: Move to the shareable file between this and grant_priv_to_role.go file +type OnSchemaObjectGrantKind string + +const ( + OnObjectSchemaObjectGrantKind OnSchemaObjectGrantKind = "OnObject" + OnAllSchemaObjectGrantKind OnSchemaObjectGrantKind = "OnAll" + OnFutureSchemaObjectGrantKind OnSchemaObjectGrantKind = "OnFuture" +) + +type GrantPrivilegesToDatabaseRoleId struct { + DatabaseRoleName sdk.AccountObjectIdentifier + WithGrantOption bool + Privileges []string + Kind DatabaseRoleGrantKind + Data any +} + +type OnDatabaseGrantData struct { + DatabaseName sdk.AccountObjectIdentifier +} + +type OnSchemaGrantData struct { + Kind OnSchemaGrantKind + SchemaName *sdk.DatabaseObjectIdentifier + DatabaseName *sdk.AccountObjectIdentifier +} + +type OnSchemaObjectGrantData struct { + Kind OnSchemaObjectGrantKind + Object *sdk.Object + OnAllOrFuture *BulkOperationGrantData +} + +type BulkOperationGrantKind string + +const ( + InDatabaseBulkOperationGrantKind BulkOperationGrantKind = "InDatabase" + InSchemaBulkOperationGrantKind BulkOperationGrantKind = "InSchema" +) + +type BulkOperationGrantData struct { + ObjectNamePlural sdk.PluralObjectType + Kind *BulkOperationGrantKind + Database *sdk.AccountObjectIdentifier + Schema *sdk.DatabaseObjectIdentifier +} + +// TODO: Describe how to put a right identifier in the documentation (so the users will be able to use it in the import) +func ParseGrantPrivilegesToDatabaseRoleId(id string) (GrantPrivilegesToDatabaseRoleId, error) { + var databaseRoleId GrantPrivilegesToDatabaseRoleId + + parts := strings.Split(id, helpers.IDDelimiter) + if len(parts) < 5 { + return databaseRoleId, sdk.NewError(`database role identifier should hold at least 4 parts "||||"`) + } + + databaseRoleId.DatabaseRoleName = sdk.NewAccountObjectIdentifierFromFullyQualifiedName(parts[0]) + databaseRoleId.WithGrantOption = parts[1] == "true" + privileges := strings.Split(parts[2], ",") + if len(privileges) == 1 && privileges[0] == "" { + privileges = []string{} + } + // TODO: All privileges + databaseRoleId.Privileges = privileges + databaseRoleId.Kind = DatabaseRoleGrantKind(parts[3]) + + switch databaseRoleId.Kind { + case OnDatabaseDatabaseRoleGrantKind: + databaseRoleId.Data = OnDatabaseGrantData{ + DatabaseName: sdk.NewAccountObjectIdentifierFromFullyQualifiedName(parts[4]), + } + case OnSchemaDatabaseRoleGrantKind: + if len(parts) < 6 { + return databaseRoleId, sdk.NewError(`database role identifier should hold at least 6 parts "|||||..."`) + } + onSchemaGrantData := OnSchemaGrantData{ + Kind: OnSchemaGrantKind(parts[4]), + } + switch onSchemaGrantData.Kind { + case OnSchemaSchemaGrantKind: + onSchemaGrantData.SchemaName = sdk.Pointer(sdk.NewDatabaseObjectIdentifierFromFullyQualifiedName(parts[5])) + case OnAllSchemasInDatabaseSchemaGrantKind, OnFutureSchemasInDatabaseSchemaGrantKind: + onSchemaGrantData.DatabaseName = sdk.Pointer(sdk.NewAccountObjectIdentifier(parts[5])) + default: + return databaseRoleId, sdk.NewError(fmt.Sprintf("invalid OnSchemaGrantKind: %s", onSchemaGrantData.Kind)) + } + databaseRoleId.Data = onSchemaGrantData + case OnSchemaObjectDatabaseRoleGrantKind: + if len(parts) < 6 { + return databaseRoleId, sdk.NewError(`database role identifier should hold at least 6 parts "|||||..."`) + } + onSchemaObjectGrantData := OnSchemaObjectGrantData{ + Kind: OnSchemaObjectGrantKind(parts[4]), + } + switch onSchemaObjectGrantData.Kind { + case OnObjectSchemaObjectGrantKind: + if len(parts) != 7 { + return databaseRoleId, sdk.NewError(`database role identifier should hold 7 parts "|||OnSchemaObject|OnObject||"`) + } + onSchemaObjectGrantData.Object = &sdk.Object{ + ObjectType: sdk.ObjectType(parts[5]), + Name: sdk.NewSchemaObjectIdentifierFromFullyQualifiedName(parts[6]), + } + case OnAllSchemaObjectGrantKind, OnFutureSchemaObjectGrantKind: + bulkOperationGrantData := &BulkOperationGrantData{ + ObjectNamePlural: sdk.PluralObjectType(parts[5]), + } + if len(parts) > 6 { + if len(parts) != 8 { + return databaseRoleId, sdk.NewError(`database role identifier should hold 8 parts "|||OnSchemaObject|On[All or Future]||In[Database or Schema]|"`) + } + bulkOperationGrantData.Kind = sdk.Pointer(BulkOperationGrantKind(parts[6])) + switch *bulkOperationGrantData.Kind { + case InDatabaseBulkOperationGrantKind: + bulkOperationGrantData.Database = sdk.Pointer(sdk.NewAccountObjectIdentifierFromFullyQualifiedName(parts[7])) + case InSchemaBulkOperationGrantKind: + bulkOperationGrantData.Schema = sdk.Pointer(sdk.NewDatabaseObjectIdentifierFromFullyQualifiedName(parts[7])) + default: + return databaseRoleId, sdk.NewError(fmt.Sprintf("invalid BulkOperationGrantKind: %s", *bulkOperationGrantData.Kind)) + } + } + onSchemaObjectGrantData.OnAllOrFuture = bulkOperationGrantData + default: + return databaseRoleId, sdk.NewError(fmt.Sprintf("invalid OnSchemaObjectGrantKind: %s", onSchemaObjectGrantData.Kind)) + } + databaseRoleId.Data = onSchemaObjectGrantData + default: + return databaseRoleId, sdk.NewError(fmt.Sprintf("invalid DatabaseRoleGrantKind: %s", databaseRoleId.Kind)) + } + + return databaseRoleId, nil +} diff --git a/pkg/resources/grant_privileges_to_database_role_identifier_test.go b/pkg/resources/grant_privileges_to_database_role_identifier_test.go new file mode 100644 index 0000000000..7c9b2a2665 --- /dev/null +++ b/pkg/resources/grant_privileges_to_database_role_identifier_test.go @@ -0,0 +1,250 @@ +package resources + +import ( + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk" + "github.com/stretchr/testify/assert" + "testing" +) + +func TestParseGrantPrivilegesToDatabaseRoleId(t *testing.T) { + testCases := []struct { + Name string + Identifier string + Expected GrantPrivilegesToDatabaseRoleId + Error string + }{ + { + Name: "grant database role on database", + Identifier: `"database-name"|false|CREATE SCHEMA,USAGE,MONITOR|OnDatabase|"on-database-name"`, + Expected: GrantPrivilegesToDatabaseRoleId{ + DatabaseRoleName: sdk.NewAccountObjectIdentifier("database-name"), + WithGrantOption: false, + Privileges: []string{"CREATE SCHEMA", "USAGE", "MONITOR"}, + Kind: OnDatabaseDatabaseRoleGrantKind, + Data: OnDatabaseGrantData{ + DatabaseName: sdk.NewAccountObjectIdentifier("on-database-name"), + }, + }, + }, + { + Name: "grant database role on schema with schema name", + Identifier: `"database-name"|false|CREATE SCHEMA,USAGE,MONITOR|OnSchema|OnSchema|"database-name"."schema-name"`, // TODO: OnSchema OnSchema x2 + Expected: GrantPrivilegesToDatabaseRoleId{ + DatabaseRoleName: sdk.NewAccountObjectIdentifier("database-name"), + WithGrantOption: false, + Privileges: []string{"CREATE SCHEMA", "USAGE", "MONITOR"}, + Kind: OnSchemaDatabaseRoleGrantKind, + Data: OnSchemaGrantData{ + Kind: OnSchemaSchemaGrantKind, + SchemaName: sdk.Pointer(sdk.NewDatabaseObjectIdentifier("database-name", "schema-name")), + }, + }, + }, + { + Name: "grant database role on all schemas in database", + Identifier: `"database-name"|false|CREATE SCHEMA,USAGE,MONITOR|OnSchema|OnAllSchemasInDatabase|"database-name-123"`, + Expected: GrantPrivilegesToDatabaseRoleId{ + DatabaseRoleName: sdk.NewAccountObjectIdentifier("database-name"), + WithGrantOption: false, + Privileges: []string{"CREATE SCHEMA", "USAGE", "MONITOR"}, + Kind: OnSchemaDatabaseRoleGrantKind, + Data: OnSchemaGrantData{ + Kind: OnAllSchemasInDatabaseSchemaGrantKind, + DatabaseName: sdk.Pointer(sdk.NewAccountObjectIdentifier("database-name-123")), + }, + }, + }, + { + Name: "grant database role on future schemas in database", + Identifier: `"database-name"|false|CREATE SCHEMA,USAGE,MONITOR|OnSchema|OnFutureSchemasInDatabase|"database-name-123"`, + Expected: GrantPrivilegesToDatabaseRoleId{ + DatabaseRoleName: sdk.NewAccountObjectIdentifier("database-name"), + WithGrantOption: false, + Privileges: []string{"CREATE SCHEMA", "USAGE", "MONITOR"}, + Kind: OnSchemaDatabaseRoleGrantKind, + Data: OnSchemaGrantData{ + Kind: OnFutureSchemasInDatabaseSchemaGrantKind, + DatabaseName: sdk.Pointer(sdk.NewAccountObjectIdentifier("database-name-123")), + }, + }, + }, + { + Name: "grant database role on schema object with on object option", + Identifier: `"database-name"|false|CREATE SCHEMA,USAGE,MONITOR|OnSchemaObject|OnObject|TABLE|"database-name"."schema-name"."table-name"`, + Expected: GrantPrivilegesToDatabaseRoleId{ + DatabaseRoleName: sdk.NewAccountObjectIdentifier("database-name"), + WithGrantOption: false, + Privileges: []string{"CREATE SCHEMA", "USAGE", "MONITOR"}, + Kind: OnSchemaObjectDatabaseRoleGrantKind, + Data: OnSchemaObjectGrantData{ + Kind: OnObjectSchemaObjectGrantKind, + Object: &sdk.Object{ + ObjectType: sdk.ObjectTypeTable, + Name: sdk.NewSchemaObjectIdentifier("database-name", "schema-name", "table-name"), + }, + }, + }, + }, + { + Name: "grant database role on schema object with on all option", + Identifier: `"database-name"|false|CREATE SCHEMA,USAGE,MONITOR|OnSchemaObject|OnAll|TABLES`, + Expected: GrantPrivilegesToDatabaseRoleId{ + DatabaseRoleName: sdk.NewAccountObjectIdentifier("database-name"), + WithGrantOption: false, + Privileges: []string{"CREATE SCHEMA", "USAGE", "MONITOR"}, + Kind: OnSchemaObjectDatabaseRoleGrantKind, + Data: OnSchemaObjectGrantData{ + Kind: OnAllSchemaObjectGrantKind, + OnAllOrFuture: &BulkOperationGrantData{ + ObjectNamePlural: "TABLES", + }, + }, + }, + }, + { + Name: "grant database role on schema object with on all option in database", + Identifier: `"database-name"|false|CREATE SCHEMA,USAGE,MONITOR|OnSchemaObject|OnAll|TABLES|InDatabase|"database-name-123"`, + Expected: GrantPrivilegesToDatabaseRoleId{ + DatabaseRoleName: sdk.NewAccountObjectIdentifier("database-name"), + WithGrantOption: false, + Privileges: []string{"CREATE SCHEMA", "USAGE", "MONITOR"}, + Kind: OnSchemaObjectDatabaseRoleGrantKind, + Data: OnSchemaObjectGrantData{ + Kind: OnAllSchemaObjectGrantKind, + OnAllOrFuture: &BulkOperationGrantData{ + ObjectNamePlural: "TABLES", + Kind: sdk.Pointer(InDatabaseBulkOperationGrantKind), + Database: sdk.Pointer(sdk.NewAccountObjectIdentifier("database-name-123")), + }, + }, + }, + }, + { + Name: "grant database role on schema object with on all option in schema", + Identifier: `"database-name"|false|CREATE SCHEMA,USAGE,MONITOR|OnSchemaObject|OnAll|TABLES|InSchema|"database-name"."schema-name"`, + Expected: GrantPrivilegesToDatabaseRoleId{ + DatabaseRoleName: sdk.NewAccountObjectIdentifier("database-name"), + WithGrantOption: false, + Privileges: []string{"CREATE SCHEMA", "USAGE", "MONITOR"}, + Kind: OnSchemaObjectDatabaseRoleGrantKind, + Data: OnSchemaObjectGrantData{ + Kind: OnAllSchemaObjectGrantKind, + OnAllOrFuture: &BulkOperationGrantData{ + ObjectNamePlural: "TABLES", + Kind: sdk.Pointer(InSchemaBulkOperationGrantKind), + Schema: sdk.Pointer(sdk.NewDatabaseObjectIdentifier("database-name", "schema-name")), + }, + }, + }, + }, + { + Name: "grant database role on schema object with on future option", + Identifier: `"database-name"|false|CREATE SCHEMA,USAGE,MONITOR|OnSchemaObject|OnFuture|TABLES`, + Expected: GrantPrivilegesToDatabaseRoleId{ + DatabaseRoleName: sdk.NewAccountObjectIdentifier("database-name"), + WithGrantOption: false, + Privileges: []string{"CREATE SCHEMA", "USAGE", "MONITOR"}, + Kind: OnSchemaObjectDatabaseRoleGrantKind, + Data: OnSchemaObjectGrantData{ + Kind: OnFutureSchemaObjectGrantKind, + OnAllOrFuture: &BulkOperationGrantData{ + ObjectNamePlural: "TABLES", + }, + }, + }, + }, + { + Name: "grant database role on schema object with on all option in database", + Identifier: `"database-name"|false|CREATE SCHEMA,USAGE,MONITOR|OnSchemaObject|OnFuture|TABLES|InDatabase|"database-name-123"`, + Expected: GrantPrivilegesToDatabaseRoleId{ + DatabaseRoleName: sdk.NewAccountObjectIdentifier("database-name"), + WithGrantOption: false, + Privileges: []string{"CREATE SCHEMA", "USAGE", "MONITOR"}, + Kind: OnSchemaObjectDatabaseRoleGrantKind, + Data: OnSchemaObjectGrantData{ + Kind: OnFutureSchemaObjectGrantKind, + OnAllOrFuture: &BulkOperationGrantData{ + ObjectNamePlural: "TABLES", + Kind: sdk.Pointer(InDatabaseBulkOperationGrantKind), + Database: sdk.Pointer(sdk.NewAccountObjectIdentifier("database-name-123")), + }, + }, + }, + }, + { + Name: "grant database role on schema object with on all option in schema", + Identifier: `"database-name"|false|CREATE SCHEMA,USAGE,MONITOR|OnSchemaObject|OnFuture|TABLES|InSchema|"database-name"."schema-name"`, + Expected: GrantPrivilegesToDatabaseRoleId{ + DatabaseRoleName: sdk.NewAccountObjectIdentifier("database-name"), + WithGrantOption: false, + Privileges: []string{"CREATE SCHEMA", "USAGE", "MONITOR"}, + Kind: OnSchemaObjectDatabaseRoleGrantKind, + Data: OnSchemaObjectGrantData{ + Kind: OnFutureSchemaObjectGrantKind, + OnAllOrFuture: &BulkOperationGrantData{ + ObjectNamePlural: "TABLES", + Kind: sdk.Pointer(InSchemaBulkOperationGrantKind), + Schema: sdk.Pointer(sdk.NewDatabaseObjectIdentifier("database-name", "schema-name")), + }, + }, + }, + }, + { + Name: "validation: grant database role not enough parts", + Identifier: `"database-name"|false`, + Error: "database role identifier should hold at least 4 parts", + }, + { + Name: "validation: grant database role not enough parts for OnDatabase kind", + Identifier: `"database-name"|false|CREATE SCHEMA,USAGE,MONITOR|OnDatabase`, + Error: "database role identifier should hold at least 4 parts", + }, + { + Name: "validation: grant database role not enough parts for OnSchema kind", + Identifier: `"database-name"|false|CREATE SCHEMA,USAGE,MONITOR|OnSchema|OnAllSchemasInDatabase`, + Error: "database role identifier should hold at least 6 parts", + }, + { + Name: "validation: grant database role not enough parts for OnSchemaObject kind", + Identifier: `"database-name"|false|CREATE SCHEMA,USAGE,MONITOR|OnSchemaObject|OnObject`, + Error: "database role identifier should hold at least 6 parts", + }, + { + Name: "validation: grant database role not enough parts for OnSchemaObject kind", + Identifier: `"database-name"|false|CREATE SCHEMA,USAGE,MONITOR|OnSchemaObject|OnObject|TABLE`, + Error: "database role identifier should hold 7 parts", + }, + { + Name: "validation: grant database role not enough parts for OnSchemaObject.InDatabase kind", + Identifier: `"database-name"|false|CREATE SCHEMA,USAGE,MONITOR|OnSchemaObject|OnAll|TABLES|InDatabase`, + Error: "database role identifier should hold 8 parts", + }, + { + Name: "validation: grant database role invalid DatabaseRoleGrantKind kind", + Identifier: `"database-name"|false|CREATE SCHEMA,USAGE,MONITOR|some-kind|some-data`, + Error: "invalid DatabaseRoleGrantKind: some-kind", + }, + { + Name: "validation: grant database role invalid OnSchemaGrantKind kind", + Identifier: `"database-name"|false|CREATE SCHEMA,USAGE,MONITOR|OnSchema|some-kind|some-data`, + Error: "invalid OnSchemaGrantKind: some-kind", + }, + { + Name: "validation: grant database role invalid OnSchemaObjectGrantKind kind", + Identifier: `"database-name"|false|CREATE SCHEMA,USAGE,MONITOR|OnSchemaObject|some-kind|some-data`, + Error: "invalid OnSchemaObjectGrantKind: some-kind", + }, + } + + for _, tt := range testCases { + t.Run(tt.Name, func(t *testing.T) { + id, err := ParseGrantPrivilegesToDatabaseRoleId(tt.Identifier) + if tt.Error == "" { + assert.NoError(t, err) + assert.Equal(t, tt.Expected, id) + } else { + assert.ErrorContains(t, err, tt.Error) + } + }) + } +} diff --git a/pkg/resources/grant_privileges_to_role.go b/pkg/resources/grant_privileges_to_role.go index 8de01aa7ae..9e2eca57ca 100644 --- a/pkg/resources/grant_privileges_to_role.go +++ b/pkg/resources/grant_privileges_to_role.go @@ -40,17 +40,17 @@ var grantPrivilegesToRoleSchema = map[string]*schema.Schema{ Type: schema.TypeBool, Optional: true, Default: false, + ForceNew: true, Description: "If true, the privileges will be granted on the account.", ConflictsWith: []string{"on_account_object", "on_schema", "on_schema_object"}, - ForceNew: true, }, "on_account_object": { Type: schema.TypeList, Optional: true, + ForceNew: true, MaxItems: 1, ConflictsWith: []string{"on_account", "on_schema", "on_schema_object"}, Description: "Specifies the account object on which privileges will be granted ", - ForceNew: true, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "object_type": { @@ -91,22 +91,24 @@ var grantPrivilegesToRoleSchema = map[string]*schema.Schema{ Optional: true, Description: "The fully qualified name of the schema.", ConflictsWith: []string{"on_schema.0.all_schemas_in_database", "on_schema.0.future_schemas_in_database"}, - ValidateDiagFunc: IsValidIdentifier[sdk.DatabaseObjectIdentifier](), ForceNew: true, + ValidateDiagFunc: IsValidIdentifier[sdk.DatabaseObjectIdentifier](), }, "all_schemas_in_database": { - Type: schema.TypeString, - Optional: true, - Description: "The fully qualified name of the database.", - ConflictsWith: []string{"on_schema.0.schema_name", "on_schema.0.future_schemas_in_database"}, - ForceNew: true, + Type: schema.TypeString, + Optional: true, + Description: "The fully qualified name of the database.", + ConflictsWith: []string{"on_schema.0.schema_name", "on_schema.0.future_schemas_in_database"}, + ForceNew: true, + ValidateDiagFunc: IsValidIdentifier[sdk.AccountObjectIdentifier](), }, "future_schemas_in_database": { - Type: schema.TypeString, - Optional: true, - Description: "The fully qualified name of the database.", - ConflictsWith: []string{"on_schema.0.schema_name", "on_schema.0.all_schemas_in_database"}, - ForceNew: true, + Type: schema.TypeString, + Optional: true, + Description: "The fully qualified name of the database.", + ConflictsWith: []string{"on_schema.0.schema_name", "on_schema.0.all_schemas_in_database"}, + ForceNew: true, + ValidateDiagFunc: IsValidIdentifier[sdk.AccountObjectIdentifier](), }, }, }, @@ -155,11 +157,11 @@ var grantPrivilegesToRoleSchema = map[string]*schema.Schema{ "object_name": { Type: schema.TypeString, Optional: true, + ForceNew: true, Description: "The fully qualified name of the object on which privileges will be granted.", RequiredWith: []string{"on_schema_object.0.object_type"}, ConflictsWith: []string{"on_schema_object.0.all", "on_schema_object.0.future"}, ValidateDiagFunc: IsValidIdentifier[sdk.SchemaObjectIdentifier](), - ForceNew: true, }, "all": { Type: schema.TypeList, @@ -202,18 +204,18 @@ var grantPrivilegesToRoleSchema = map[string]*schema.Schema{ "in_database": { Type: schema.TypeString, Optional: true, + ForceNew: true, Description: "The fully qualified name of the database.", ConflictsWith: []string{"on_schema_object.0.all.in_schema"}, ValidateDiagFunc: IsValidIdentifier[sdk.AccountObjectIdentifier](), - ForceNew: true, }, "in_schema": { Type: schema.TypeString, Optional: true, + ForceNew: true, Description: "The fully qualified name of the schema.", ConflictsWith: []string{"on_schema_object.0.all.in_database"}, ValidateDiagFunc: IsValidIdentifier[sdk.DatabaseObjectIdentifier](), - ForceNew: true, }, }, }, @@ -279,10 +281,11 @@ var grantPrivilegesToRoleSchema = map[string]*schema.Schema{ }, }, "role_name": { - Type: schema.TypeString, - Required: true, - Description: "The fully qualified name of the role to which privileges will be granted.", - ForceNew: true, + Type: schema.TypeString, + Required: true, + ForceNew: true, + Description: "The fully qualified name of the role to which privileges will be granted.", + ValidateDiagFunc: IsValidIdentifier[sdk.AccountObjectIdentifier](), }, "with_grant_option": { Type: schema.TypeBool, diff --git a/pkg/resources/helpers.go b/pkg/resources/helpers.go index 08890b354f..3f6e7cfca1 100644 --- a/pkg/resources/helpers.go +++ b/pkg/resources/helpers.go @@ -125,3 +125,17 @@ func GetPropertyAsPointer[T any](d *schema.ResourceData, property string) *T { } return &typedValue } + +func GetProperty[T any](d *schema.ResourceData, property string) (T, bool) { + value, ok := d.GetOk(property) + if !ok { + var defaultValue T + return defaultValue, false + } + typedValue, ok := value.(T) + if !ok { + var defaultValue T + return defaultValue, false + } + return typedValue, true +} diff --git a/pkg/sdk/grants.go b/pkg/sdk/grants.go index f4dc79fcb4..9ce8e48d93 100644 --- a/pkg/sdk/grants.go +++ b/pkg/sdk/grants.go @@ -97,6 +97,7 @@ type DatabaseRoleGrantPrivileges struct { DatabasePrivileges []AccountObjectPrivilege `ddl:"-"` SchemaPrivileges []SchemaPrivilege `ddl:"-"` SchemaObjectPrivileges []SchemaObjectPrivilege `ddl:"-"` + AllPrivileges *bool `ddl:"keyword" sql:"ALL PRIVILEGES"` } type DatabaseRoleGrantOn struct { diff --git a/pkg/sdk/grants_test.go b/pkg/sdk/grants_test.go index 927c954bcd..6c0e081af8 100644 --- a/pkg/sdk/grants_test.go +++ b/pkg/sdk/grants_test.go @@ -480,6 +480,14 @@ func TestGrants_GrantPrivilegesToDatabaseRole(t *testing.T) { } assertOptsValidAndSQLEquals(t, opts, `GRANT APPLY ON FUTURE TABLES IN SCHEMA "db1"."schema1" TO DATABASE ROLE "db1"."role1"`) }) + + t.Run("grant all privileges", func(t *testing.T) { + opts := defaultGrantsForSchemaObject() + opts.privileges = &DatabaseRoleGrantPrivileges{ + AllPrivileges: Bool(true), + } + assertOptsValidAndSQLEquals(t, opts, `GRANT ALL PRIVILEGES ON TABLE "db1"."schema1"."table1" TO DATABASE ROLE "db1"."role1"`) + }) } func TestGrants_RevokePrivilegesFromDatabaseRoleRole(t *testing.T) { diff --git a/pkg/sdk/grants_validations.go b/pkg/sdk/grants_validations.go index cfddde6a30..339a78a5e8 100644 --- a/pkg/sdk/grants_validations.go +++ b/pkg/sdk/grants_validations.go @@ -160,8 +160,8 @@ func (opts *GrantPrivilegesToDatabaseRoleOptions) validate() error { func (v *DatabaseRoleGrantPrivileges) validate() error { var errs []error - if !exactlyOneValueSet(v.DatabasePrivileges, v.SchemaPrivileges, v.SchemaObjectPrivileges) { - errs = append(errs, errExactlyOneOf("DatabaseRoleGrantPrivileges", "DatabasePrivileges", "SchemaPrivileges", "SchemaObjectPrivileges")) + if !exactlyOneValueSet(v.DatabasePrivileges, v.SchemaPrivileges, v.SchemaObjectPrivileges, v.AllPrivileges) { + errs = append(errs, errExactlyOneOf("DatabaseRoleGrantPrivileges", "DatabasePrivileges", "SchemaPrivileges", "SchemaObjectPrivileges", "AllPrivileges")) } if valueSet(v.DatabasePrivileges) { allowedPrivileges := []AccountObjectPrivilege{ From fa3d2f20b0729a6f8599ad5ceb8754f0649db667 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Cie=C5=9Blak?= Date: Tue, 19 Dec 2023 12:10:35 +0100 Subject: [PATCH 03/16] wip --- .../grant_privileges_to_database_role.go | 473 ++++++++++++++++-- ...ileges_to_database_role_acceptance_test.go | 110 ++++ ..._privileges_to_database_role_identifier.go | 82 ++- ...ileges_to_database_role_identifier_test.go | 229 +++++++-- .../test.tf | 6 + .../variables.tf | 15 + 6 files changed, 813 insertions(+), 102 deletions(-) create mode 100644 pkg/resources/grant_privileges_to_database_role_acceptance_test.go create mode 100644 pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole_basic/test.tf create mode 100644 pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole_basic/variables.tf diff --git a/pkg/resources/grant_privileges_to_database_role.go b/pkg/resources/grant_privileges_to_database_role.go index a5c6227a06..8b5330e431 100644 --- a/pkg/resources/grant_privileges_to_database_role.go +++ b/pkg/resources/grant_privileges_to_database_role.go @@ -13,6 +13,7 @@ import ( "strings" ) +// TODO: Add always_apply var grantPrivilegesToDatabaseRoleSchema = map[string]*schema.Schema{ "database_role_name": { Type: schema.TypeString, @@ -22,10 +23,10 @@ var grantPrivilegesToDatabaseRoleSchema = map[string]*schema.Schema{ ValidateDiagFunc: IsValidIdentifier[sdk.DatabaseObjectIdentifier](), }, "privileges": { - Type: schema.TypeSet, - Optional: true, - Description: "The privileges to grant on the database role.", - ValidateDiagFunc: doesNotContainOwnershipGrant(), + Type: schema.TypeSet, + Optional: true, + Description: "The privileges to grant on the database role.", + // TODO: Not yet supported by Terraform - ValidateDiagFunc: doesNotContainOwnershipGrant(), ExactlyOneOf: []string{ "privileges", "all_privileges", @@ -49,7 +50,7 @@ var grantPrivilegesToDatabaseRoleSchema = map[string]*schema.Schema{ Optional: true, Default: false, ForceNew: true, - Description: "Specifies whether the grantee can grant the privileges to other users.", + Description: "If specified, allows the recipient role to grant the privileges to other roles.", }, "on_database": { Type: schema.TypeString, @@ -294,44 +295,129 @@ func GrantPrivilegesToDatabaseRole() *schema.Resource { return &schema.Resource{ CreateContext: CreateGrantPrivilegesToDatabaseRole, ReadContext: ReadGrantPrivilegesToDatabaseRole, - //Delete: DeleteGrantPrivilegesToRole, - //Update: UpdateGrantPrivilegesToRole, + DeleteContext: DeleteGrantPrivilegesToDatabaseRole, + UpdateContext: UpdateGrantPrivilegesToDatabaseRole, Schema: grantPrivilegesToDatabaseRoleSchema, Importer: &schema.ResourceImporter{ - StateContext: func(ctx context.Context, d *schema.ResourceData, m interface{}) ([]*schema.ResourceData, error) { - return nil, nil - }, + StateContext: ImportGrantPrivilegesToDatabaseRole, }, } } +func ImportGrantPrivilegesToDatabaseRole(ctx context.Context, d *schema.ResourceData, meta any) ([]*schema.ResourceData, error) { + id, err := ParseGrantPrivilegesToDatabaseRoleId(d.Id()) + if err != nil { + return nil, err + } + if err := d.Set("database_role_name", id.DatabaseRoleName); err != nil { + return nil, err + } + if err := d.Set("with_grant_option", id.WithGrantOption); err != nil { + return nil, err + } + if err := d.Set("all_privileges", id.AllPrivileges); err != nil { + return nil, err + } + if err := d.Set("privileges", id.Privileges); err != nil { + return nil, err + } + switch id.Kind { + case OnDatabaseDatabaseRoleGrantKind: + if err := d.Set("on_database", id.Data.(*OnDatabaseGrantData).DatabaseName.FullyQualifiedName()); err != nil { + return nil, err + } + case OnSchemaDatabaseRoleGrantKind: + data := id.Data.(*OnSchemaGrantData) + var onSchema map[string]any + + switch data.Kind { + case OnSchemaSchemaGrantKind: + onSchema["schema_name"] = data.SchemaName.FullyQualifiedName() + case OnAllSchemasInDatabaseSchemaGrantKind: + onSchema["all_schemas_in_database"] = data.DatabaseName.FullyQualifiedName() + case OnFutureSchemasInDatabaseSchemaGrantKind: + onSchema["future_schemas_in_database"] = data.DatabaseName.FullyQualifiedName() + } + + if err := d.Set("on_schema", []any{onSchema}); err != nil { + return nil, err + } + case OnSchemaObjectDatabaseRoleGrantKind: + data := id.Data.(*OnSchemaObjectGrantData) + var onSchemaObject map[string]any + + switch data.Kind { + case OnObjectSchemaObjectGrantKind: + onSchemaObject["object_type"] = data.Object.ObjectType.String() + onSchemaObject["object_name"] = data.Object.Name.FullyQualifiedName() + case OnAllSchemaObjectGrantKind: + var onAll map[string]any + + onAll["object_name_plural"] = data.OnAllOrFuture.ObjectNamePlural.String() + switch data.OnAllOrFuture.Kind { + case InDatabaseBulkOperationGrantKind: + onAll["in_database"] = data.OnAllOrFuture.Database.FullyQualifiedName() + case InSchemaBulkOperationGrantKind: + onAll["in_schema"] = data.OnAllOrFuture.Schema.FullyQualifiedName() + } + + onSchemaObject["all"] = []any{onAll} + case OnFutureSchemaObjectGrantKind: + var onFuture map[string]any + + onFuture["object_name_plural"] = data.OnAllOrFuture.ObjectNamePlural.String() + switch data.OnAllOrFuture.Kind { + case InDatabaseBulkOperationGrantKind: + onFuture["in_database"] = data.OnAllOrFuture.Database.FullyQualifiedName() + case InSchemaBulkOperationGrantKind: + onFuture["in_schema"] = data.OnAllOrFuture.Schema.FullyQualifiedName() + } + + onSchemaObject["future"] = []any{onFuture} + } + + if err := d.Set("on_schema_object", []any{onSchemaObject}); err != nil { + return nil, err + } + } + + return []*schema.ResourceData{d}, nil +} + func CreateGrantPrivilegesToDatabaseRole(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics { var diags diag.Diagnostics db := meta.(*sql.DB) client := sdk.NewClientFromDB(db) + id := new(GrantPrivilegesToDatabaseRoleId) databaseRoleName := sdk.NewDatabaseObjectIdentifierFromFullyQualifiedName(d.Get("database_role_name").(string)) + id.DatabaseRoleName = databaseRoleName + id.AllPrivileges = d.Get("all_privileges").(bool) + if p, ok := d.GetOk("privileges"); ok { + id.Privileges = expandStringList(p.(*schema.Set).List()) + } + + on := getDatabaseRoleGrantOn(id, d) // TODO: It shouldn't modify err := client.Grants.GrantPrivilegesToDatabaseRole( ctx, - getDatabaseRolePrivileges(d, getDatabaseRoleGrantOn(d)), - getDatabaseRoleGrantOn(d), + getDatabaseRolePrivileges(id.Kind, id.AllPrivileges, id.Privileges), + on, databaseRoleName, &sdk.GrantPrivilegesToDatabaseRoleOptions{ WithGrantOption: GetPropertyAsPointer[bool](d, "with_grant_option"), }, ) if err != nil { - diags = append(diags, diag.Diagnostic{ + return append(diags, diag.Diagnostic{ Severity: diag.Error, - Summary: fmt.Sprintf("An error occurred when granting privileges to database role (%s)", databaseRoleName), - Detail: fmt.Sprintf("Error: %s", err.Error()), + Summary: "An error occurred when granting privileges to database role", + Detail: fmt.Sprintf("Id: %s\nError: %s", id.DatabaseRoleName, err.Error()), }) - return diags } - // TODO: Identifier d.SetId() + d.SetId(id.String()) return ReadGrantPrivilegesToDatabaseRole(ctx, d, meta) } @@ -339,48 +425,283 @@ func CreateGrantPrivilegesToDatabaseRole(ctx context.Context, d *schema.Resource func ReadGrantPrivilegesToDatabaseRole(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics { var diags diag.Diagnostics - opts := &sdk.ShowGrantOptions{ - Future: nil, - On: nil, - To: nil, - Of: nil, - In: nil, + id, err := ParseGrantPrivilegesToDatabaseRoleId(d.Id()) + if err != nil { + return append(diags, diag.Diagnostic{ + Severity: diag.Error, + Summary: "Failed to parse internal identifier", + Detail: fmt.Sprintf("Id: %s\nErr: %s", d.Id(), err.Error()), // TODO: link to the documentation (?). It should describe how the identifier looks. + }) + } + + if id.AllPrivileges { + return append(diags, diag.Diagnostic{ + Severity: diag.Warning, + Summary: "Show with all_privileges option is skipped for now.", // TODO: Details + Detail: "", // TODO: link to the design decisions doc + }) + } + + opts, grantedOn, diagnostics := prepareShowGrantsRequest(id) + if len(diagnostics) != 0 { + return append(diags, diagnostics...) } db := meta.(*sql.DB) client := sdk.NewClientFromDB(db) - client.Grants.Show(ctx, opts) + grants, err := client.Grants.Show(ctx, opts) + if err != nil { + return append(diags, diag.Diagnostic{ + Severity: diag.Error, + Summary: "Failed to retrieve grants", + Detail: fmt.Sprintf("Id: %s\nErr: %s", d.Id(), err.Error()), // TODO: link to the documentation (?). It should describe how the identifier looks. + }) + } + + var privileges []string + + // TODO: Refactor - check if correct with new conventions + // TODO: Compare privileges + for _, grant := range grants { + // TODO: What about all_privileges, right now we cannot assure that the list of privileges is correct + // Only consider privileges that are already present in the ID so we + // don't delete privileges managed by other resources. + if !slices.Contains(id.Privileges, grant.Privilege) { + continue + } + // TODO: What about GranteeName with database roles is it fully qualified or not ? if yes, refactor GranteeName. + if id.WithGrantOption == grant.GrantOption && id.DatabaseRoleName.Name() == grant.GranteeName.Name() { + // future grants do not have grantedBy, only current grants do. If grantedby + // is an empty string it means the grant could not have been created by terraform + if opts.Future != nil && *opts.Future == true && 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 { + privileges = append(privileges, grant.Privilege) + } + } + } + + if err := d.Set("privileges", privileges); err != nil { + return append(diags, diag.Diagnostic{ + Severity: diag.Error, + Summary: "Error setting privileges for database role", + Detail: fmt.Sprintf("Id: %s\nErr: %s", d.Id(), err.Error()), // TODO: link to the documentation (?). It should describe how the identifier looks. + }) + } return diags } -func getDatabaseRolePrivileges(d *schema.ResourceData, on *sdk.DatabaseRoleGrantOn) *sdk.DatabaseRoleGrantPrivileges { - var databaseRoleGrantPrivileges *sdk.DatabaseRoleGrantPrivileges +func DeleteGrantPrivilegesToDatabaseRole(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics { + var diags diag.Diagnostics - if d.Get("all_privileges").(bool) { - databaseRoleGrantPrivileges.AllPrivileges = sdk.Bool(true) - return databaseRoleGrantPrivileges + db := meta.(*sql.DB) + client := sdk.NewClientFromDB(db) + id, err := ParseGrantPrivilegesToDatabaseRoleId(d.Id()) + if err != nil { + return append(diags, diag.Diagnostic{ + Severity: diag.Error, + Summary: "Failed to parse internal identifier", + Detail: fmt.Sprintf("Id: %s\nErr: %s", d.Id(), err.Error()), // TODO: link to the documentation (?). It should describe how the identifier looks. + }) } - var privileges []string - if p, ok := d.GetOk("privileges"); ok { - privileges = expandStringList(p.(*schema.Set).List()) + err = client.Grants.RevokePrivilegesFromDatabaseRole( + ctx, + getDatabaseRolePrivileges(id.Kind, id.AllPrivileges, id.Privileges), + getDatabaseRoleGrantOn(&id, d), + id.DatabaseRoleName, + &sdk.RevokePrivilegesFromDatabaseRoleOptions{}, + ) + if err != nil { + return append(diags, diag.Diagnostic{ + Severity: diag.Error, + Summary: "An error occurred when revoking privileges from database role", + Detail: fmt.Sprintf("Id: %s\nError: %s", id.DatabaseRoleName, err.Error()), + }) } - switch { - case on.Database != nil: + d.SetId("") + + return diags +} + +func UpdateGrantPrivilegesToDatabaseRole(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics { + var diags diag.Diagnostics + + db := meta.(*sql.DB) + client := sdk.NewClientFromDB(db) + id, err := ParseGrantPrivilegesToDatabaseRoleId(d.Id()) + if err != nil { + return append(diags, diag.Diagnostic{ + Severity: diag.Error, + Summary: "Failed to parse internal identifier", + Detail: fmt.Sprintf("Id: %s\nErr: %s", d.Id(), err.Error()), // TODO: link to the documentation (?). It should describe how the identifier looks. + }) + } + + if d.HasChange("privileges") { + before, after := d.GetChange("privileges") + privilegesBeforeChange := expandStringList(before.(*schema.Set).List()) + privilegesAfterChange := expandStringList(after.(*schema.Set).List()) + + var privilegesToAdd, privilegesToRemove []string + + for _, privilegeBeforeChange := range privilegesBeforeChange { + if !slices.Contains(privilegesAfterChange, privilegeBeforeChange) { + privilegesToRemove = append(privilegesToRemove, privilegeBeforeChange) + } + } + + for _, privilegeAfterChange := range privilegesAfterChange { + if !slices.Contains(privilegesBeforeChange, privilegeAfterChange) { + privilegesToAdd = append(privilegesToAdd, privilegeAfterChange) + } + } + + grantOn := getDatabaseRoleGrantOn(&id, d) + + if len(privilegesToAdd) > 0 { + err = client.Grants.GrantPrivilegesToDatabaseRole( + ctx, + getDatabaseRolePrivileges(id.Kind, false, privilegesToAdd), + grantOn, + id.DatabaseRoleName, + new(sdk.GrantPrivilegesToDatabaseRoleOptions), + ) + if err != nil { + return append(diags, diag.Diagnostic{ + Severity: diag.Error, + Summary: "Failed to grant added privileges", + Detail: fmt.Sprintf("Id: %s\nErr: %s", d.Id(), err.Error()), // TODO: link to the documentation (?). It should describe how the identifier looks. + }) + } + } + + if len(privilegesToRemove) > 0 { + err = client.Grants.RevokePrivilegesFromDatabaseRole( + ctx, + getDatabaseRolePrivileges(id.Kind, false, privilegesToRemove), + grantOn, + id.DatabaseRoleName, + new(sdk.RevokePrivilegesFromDatabaseRoleOptions), + ) + if err != nil { + return append(diags, diag.Diagnostic{ + Severity: diag.Error, + Summary: "Failed to revoke removed privileges", + Detail: fmt.Sprintf("Id: %s\nErr: %s", d.Id(), err.Error()), // TODO: link to the documentation (?). It should describe how the identifier looks. + }) + } + } + + id.Privileges = privilegesAfterChange + d.SetId(id.String()) + } + + return ReadGrantPrivilegesToDatabaseRole(ctx, d, meta) +} + +func prepareShowGrantsRequest(id GrantPrivilegesToDatabaseRoleId) (*sdk.ShowGrantOptions, sdk.ObjectType, diag.Diagnostics) { + opts := new(sdk.ShowGrantOptions) + var grantedOn sdk.ObjectType + var diags diag.Diagnostics + + switch id.Kind { + case OnDatabaseDatabaseRoleGrantKind: + grantedOn = sdk.ObjectTypeDatabase + data := id.Data.(*OnDatabaseGrantData) + opts.On = &sdk.ShowGrantsOn{ + Object: &sdk.Object{ + ObjectType: sdk.ObjectTypeDatabase, + Name: data.DatabaseName, + }, + } + case OnSchemaDatabaseRoleGrantKind: + grantedOn = sdk.ObjectTypeSchema + data := id.Data.(*OnSchemaGrantData) + + switch data.Kind { + case OnSchemaSchemaGrantKind: + opts.On = &sdk.ShowGrantsOn{ + Object: &sdk.Object{ + ObjectType: sdk.ObjectTypeSchema, + Name: data.SchemaName, + }, + } + case OnAllSchemasInDatabaseSchemaGrantKind: + // TODO: Document + return nil, "", append(diags, diag.Diagnostic{ + Severity: diag.Warning, + Summary: "Skipping", + Detail: "TODO", + }) + case OnFutureSchemasInDatabaseSchemaGrantKind: + // TODO: show future on database (collisions with other on future triggers and over fetching is ok ?) + //opts.Future = sdk.Bool(true) + //opts.In = &sdk.ShowGrantsIn{ + //} + } + case OnSchemaObjectDatabaseRoleGrantKind: + data := id.Data.(*OnSchemaObjectGrantData) + + switch data.Kind { + case OnObjectSchemaObjectGrantKind: + grantedOn = data.Object.ObjectType + opts.On = &sdk.ShowGrantsOn{ + Object: data.Object, + } + case OnAllSchemaObjectGrantKind: + // TODO: Document + return nil, "", append(diags, diag.Diagnostic{ + Severity: diag.Warning, + Summary: "Skipping", + Detail: "TODO", + }) + case OnFutureSchemaObjectGrantKind: + grantedOn = data.OnAllOrFuture.ObjectNamePlural.Singular() + opts.Future = sdk.Bool(true) + + switch data.OnAllOrFuture.Kind { + case InDatabaseBulkOperationGrantKind: + opts.In = &sdk.ShowGrantsIn{ + Database: data.OnAllOrFuture.Database, + } + case InSchemaBulkOperationGrantKind: + opts.In = &sdk.ShowGrantsIn{ + Schema: data.OnAllOrFuture.Schema, + } + } + } + } + + return opts, grantedOn, diags +} + +func getDatabaseRolePrivileges(kind DatabaseRoleGrantKind, allPrivileges bool, privileges []string) *sdk.DatabaseRoleGrantPrivileges { + databaseRoleGrantPrivileges := new(sdk.DatabaseRoleGrantPrivileges) + + if allPrivileges { + databaseRoleGrantPrivileges.AllPrivileges = sdk.Bool(true) + return databaseRoleGrantPrivileges + } + + switch kind { + case OnDatabaseDatabaseRoleGrantKind: databasePrivileges := make([]sdk.AccountObjectPrivilege, len(privileges)) for i, privilege := range privileges { databasePrivileges[i] = sdk.AccountObjectPrivilege(privilege) } databaseRoleGrantPrivileges.DatabasePrivileges = databasePrivileges - case on.Schema != nil: + case OnSchemaDatabaseRoleGrantKind: schemaPrivileges := make([]sdk.SchemaPrivilege, len(privileges)) for i, privilege := range privileges { schemaPrivileges[i] = sdk.SchemaPrivilege(privilege) } databaseRoleGrantPrivileges.SchemaPrivileges = schemaPrivileges - case on.SchemaObject != nil: + case OnSchemaObjectDatabaseRoleGrantKind: schemaObjectPrivileges := make([]sdk.SchemaObjectPrivilege, len(privileges)) for i, privilege := range privileges { schemaObjectPrivileges[i] = sdk.SchemaObjectPrivilege(privilege) @@ -391,17 +712,27 @@ func getDatabaseRolePrivileges(d *schema.ResourceData, on *sdk.DatabaseRoleGrant return databaseRoleGrantPrivileges } -func getDatabaseRoleGrantOn(d *schema.ResourceData) *sdk.DatabaseRoleGrantOn { +// TODO: This should not set anything - remove id +func getDatabaseRoleGrantOn(id *GrantPrivilegesToDatabaseRoleId, d *schema.ResourceData) *sdk.DatabaseRoleGrantOn { onDatabase, onDatabaseOk := GetProperty[string](d, "on_database") onSchema, onSchemaOk := GetProperty[map[string]any](d, "on_schema") onSchemaObject, onSchemaObjectOk := GetProperty[map[string]any](d, "on_schema_object") - var on *sdk.DatabaseRoleGrantOn + on := new(sdk.DatabaseRoleGrantOn) switch { case onDatabaseOk: - on.Database = sdk.Pointer(sdk.NewAccountObjectIdentifierFromFullyQualifiedName(onDatabase)) + databaseId := sdk.NewAccountObjectIdentifierFromFullyQualifiedName(onDatabase) + + id.Kind = OnDatabaseDatabaseRoleGrantKind + id.Data = &OnDatabaseGrantData{ + DatabaseName: databaseId, + } + on.Database = &databaseId case onSchemaOk: - var grantOnSchema sdk.GrantOnSchema + id.Kind = OnSchemaDatabaseRoleGrantKind + + onSchemaGrantData := new(OnSchemaGrantData) + grantOnSchema := new(sdk.GrantOnSchema) schemaName, schemaNameOk := onSchema["schema_name"] allSchemasInDatabase, allSchemasInDatabaseOk := onSchema["all_schemas_in_database"] @@ -409,16 +740,32 @@ func getDatabaseRoleGrantOn(d *schema.ResourceData) *sdk.DatabaseRoleGrantOn { switch { case schemaNameOk: - grantOnSchema.Schema = sdk.Pointer(sdk.NewDatabaseObjectIdentifierFromFullyQualifiedName(schemaName.(string))) + schemaId := sdk.NewDatabaseObjectIdentifierFromFullyQualifiedName(schemaName.(string)) + + onSchemaGrantData.Kind = OnSchemaSchemaGrantKind + onSchemaGrantData.SchemaName = &schemaId + grantOnSchema.Schema = &schemaId case allSchemasInDatabaseOk: - grantOnSchema.AllSchemasInDatabase = sdk.Pointer(sdk.NewAccountObjectIdentifierFromFullyQualifiedName(allSchemasInDatabase.(string))) + databaseId := sdk.NewAccountObjectIdentifierFromFullyQualifiedName(allSchemasInDatabase.(string)) + + onSchemaGrantData.Kind = OnAllSchemasInDatabaseSchemaGrantKind + onSchemaGrantData.DatabaseName = &databaseId + grantOnSchema.AllSchemasInDatabase = &databaseId case futureSchemasInDatabaseOk: - grantOnSchema.FutureSchemasInDatabase = sdk.Pointer(sdk.NewAccountObjectIdentifierFromFullyQualifiedName(futureSchemasInDatabase.(string))) + databaseId := sdk.NewAccountObjectIdentifierFromFullyQualifiedName(futureSchemasInDatabase.(string)) + + onSchemaGrantData.Kind = OnFutureSchemasInDatabaseSchemaGrantKind + onSchemaGrantData.DatabaseName = &databaseId + grantOnSchema.FutureSchemasInDatabase = &databaseId } - on.Schema = &grantOnSchema + id.Data = onSchemaGrantData + on.Schema = grantOnSchema case onSchemaObjectOk: - var grantOnSchemaObject sdk.GrantOnSchemaObject + id.Kind = OnSchemaDatabaseRoleGrantKind + + onSchemaObjectGrantData := new(OnSchemaObjectGrantData) + grantOnSchemaObject := new(sdk.GrantOnSchemaObject) objectType, objectTypeOk := onSchemaObject["object_type"] objectName, objectNameOk := onSchemaObject["object_name"] @@ -427,34 +774,54 @@ func getDatabaseRoleGrantOn(d *schema.ResourceData) *sdk.DatabaseRoleGrantOn { switch { case objectTypeOk && objectNameOk: - grantOnSchemaObject.SchemaObject = &sdk.Object{ + object := &sdk.Object{ ObjectType: sdk.ObjectType(objectType.(string)), // TODO: Should we validate it or just cast it Name: sdk.NewSchemaObjectIdentifierFromFullyQualifiedName(objectName.(string)), } + + onSchemaObjectGrantData.Kind = OnObjectSchemaObjectGrantKind + onSchemaObjectGrantData.Object = object + grantOnSchemaObject.SchemaObject = object case allOk: - grantOnSchemaObject.All = getGrantOnSchemaObjectIn(all.(map[string]any)) + onSchemaObjectGrantData.Kind = OnAllSchemaObjectGrantKind + grantOnSchemaObject.All = getGrantOnSchemaObjectIn(onSchemaObjectGrantData, all.(map[string]any)) case futureOk: - grantOnSchemaObject.Future = getGrantOnSchemaObjectIn(future.(map[string]any)) + onSchemaObjectGrantData.Kind = OnFutureSchemaObjectGrantKind + grantOnSchemaObject.Future = getGrantOnSchemaObjectIn(onSchemaObjectGrantData, future.(map[string]any)) } - on.SchemaObject = &grantOnSchemaObject + id.Data = onSchemaObjectGrantData + on.SchemaObject = grantOnSchemaObject } return on } -func getGrantOnSchemaObjectIn(m map[string]any) *sdk.GrantOnSchemaObjectIn { +func getGrantOnSchemaObjectIn(onSchemaObjectGrantData *OnSchemaObjectGrantData, m map[string]any) *sdk.GrantOnSchemaObjectIn { + pluralObjectType := sdk.PluralObjectType(m["object_type_plural"].(string)) + bulkOperationGrantData := &BulkOperationGrantData{ + ObjectNamePlural: pluralObjectType, + } grantOnSchemaObjectIn := &sdk.GrantOnSchemaObjectIn{ - PluralObjectType: sdk.PluralObjectType(m["object_type_plural"].(string)), + PluralObjectType: pluralObjectType, } if inDatabase, inDatabaseOk := m["in_database"]; inDatabaseOk { - grantOnSchemaObjectIn.InDatabase = sdk.Pointer(sdk.NewAccountObjectIdentifierFromFullyQualifiedName(inDatabase.(string))) + databaseId := sdk.Pointer(sdk.NewAccountObjectIdentifierFromFullyQualifiedName(inDatabase.(string))) + + bulkOperationGrantData.Kind = InDatabaseBulkOperationGrantKind + bulkOperationGrantData.Database = databaseId + grantOnSchemaObjectIn.InDatabase = databaseId } if inSchema, inSchemaOk := m["in_schema"]; inSchemaOk { - grantOnSchemaObjectIn.InSchema = sdk.Pointer(sdk.NewDatabaseObjectIdentifierFromFullyQualifiedName(inSchema.(string))) + schemaId := sdk.Pointer(sdk.NewDatabaseObjectIdentifierFromFullyQualifiedName(inSchema.(string))) + + bulkOperationGrantData.Kind = InSchemaBulkOperationGrantKind + bulkOperationGrantData.Schema = schemaId + grantOnSchemaObjectIn.InSchema = schemaId } + onSchemaObjectGrantData.OnAllOrFuture = bulkOperationGrantData return grantOnSchemaObjectIn } diff --git a/pkg/resources/grant_privileges_to_database_role_acceptance_test.go b/pkg/resources/grant_privileges_to_database_role_acceptance_test.go new file mode 100644 index 0000000000..8f34bdd5b8 --- /dev/null +++ b/pkg/resources/grant_privileges_to_database_role_acceptance_test.go @@ -0,0 +1,110 @@ +package resources_test + +import ( + "context" + "database/sql" + "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/resource" + "github.com/hashicorp/terraform-plugin-testing/terraform" + "github.com/hashicorp/terraform-plugin-testing/tfversion" + "testing" +) + +// TODO Use cases to cover in acc tests +// - basic - check create, read and destroy +// - grant privileges on database +// - update - check update of privileges +// - privileges +// - privileges to all_privileges +// - all_privileges to privilege +// - import - check import +// - different paths to parse (on database, on schema, on schema object) + +func TestAcc_GrantPrivilegesToDatabaseRole_basic(t *testing.T) { + name := "test_database_role_name" + configVariables := config.Variables{ + "name": config.StringVariable(name), + "privileges": config.ListVariable( + config.StringVariable("CREATE SCHEMA"), + config.StringVariable("MODIFY"), + config.StringVariable("USAGE"), + ), + "database": config.StringVariable(acc.TestDatabaseName), + "with_grant_option": config.BoolVariable(true), + } + resourceName := "snowflake_grant_privileges_to_database_role.test" + + resource.Test(t, resource.TestCase{ + ProtoV6ProviderFactories: acc.TestAccProtoV6ProviderFactories, + PreCheck: func() { acc.TestAccPreCheck(t) }, + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.RequireAbove(tfversion.Version1_5_0), + }, + CheckDestroy: testAccCheckDatabaseRolePrivilegesRevoked, + Steps: []resource.TestStep{ + { + PreConfig: func() { createDatabaseRoleOutsideTerraform(t, name) }, + ConfigDirectory: config.TestNameDirectory(), + ConfigVariables: configVariables, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "database_role_name", sdk.NewDatabaseObjectIdentifier(acc.TestDatabaseName, name).FullyQualifiedName()), + resource.TestCheckResourceAttr(resourceName, "privileges.#", "3"), + resource.TestCheckResourceAttr(resourceName, "privileges.0", "CREATE SCHEMA"), + resource.TestCheckResourceAttr(resourceName, "privileges.1", "MODIFY"), + resource.TestCheckResourceAttr(resourceName, "privileges.2", "USAGE"), + resource.TestCheckResourceAttr(resourceName, "on_database", acc.TestDatabaseName), + ), + }, + }, + }) +} + +func createDatabaseRoleOutsideTerraform(t *testing.T, name string) { + t.Helper() + client, err := sdk.NewDefaultClient() + if err != nil { + t.Fatal(err) + } + ctx := context.Background() + databaseRoleId := sdk.NewDatabaseObjectIdentifier(acc.TestDatabaseName, name) + if err := client.DatabaseRoles.Create(ctx, sdk.NewCreateDatabaseRoleRequest(databaseRoleId)); err != nil { + t.Fatal(fmt.Errorf("error database role (%s): %w", databaseRoleId.FullyQualifiedName(), err)) + } + t.Cleanup(func() { + if err := client.DatabaseRoles.Drop(ctx, sdk.NewDropDatabaseRoleRequest(databaseRoleId)); err != nil { + t.Fatal(fmt.Errorf("failed to drop database role (%s): %w", databaseRoleId.FullyQualifiedName(), err)) + } + }) +} + +func testAccCheckDatabaseRolePrivilegesRevoked(s *terraform.State) error { + db := acc.TestAccProvider.Meta().(*sql.DB) + client := sdk.NewClientFromDB(db) + for _, rs := range s.RootModule().Resources { + if rs.Type != "snowflake_grant_privileges_to_database_role" { + continue + } + ctx := context.Background() + + id := sdk.NewDatabaseObjectIdentifierFromFullyQualifiedName(rs.Primary.Attributes["database_role_name"]) + grants, err := client.Grants.Show(ctx, &sdk.ShowGrantOptions{ + To: &sdk.ShowGrantsTo{ + DatabaseRole: id, + }, + }) + if err != nil { + return err + } + var grantedPrivileges []string + for _, grant := range grants { + grantedPrivileges = append(grantedPrivileges, grant.Privilege) + } + if len(grantedPrivileges) > 0 { + return fmt.Errorf("database role (%s) still grants , granted privileges %v", id.FullyQualifiedName(), grantedPrivileges) + } + } + return nil +} diff --git a/pkg/resources/grant_privileges_to_database_role_identifier.go b/pkg/resources/grant_privileges_to_database_role_identifier.go index e3c3d0f69c..ed809672fa 100644 --- a/pkg/resources/grant_privileges_to_database_role_identifier.go +++ b/pkg/resources/grant_privileges_to_database_role_identifier.go @@ -4,6 +4,7 @@ import ( "fmt" "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/helpers" "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk" + "strconv" "strings" ) @@ -36,29 +37,79 @@ const ( ) type GrantPrivilegesToDatabaseRoleId struct { - DatabaseRoleName sdk.AccountObjectIdentifier + DatabaseRoleName sdk.DatabaseObjectIdentifier WithGrantOption bool + AllPrivileges bool Privileges []string Kind DatabaseRoleGrantKind - Data any + Data fmt.Stringer +} + +func (g *GrantPrivilegesToDatabaseRoleId) String() string { + var parts []string + parts = append(parts, g.DatabaseRoleName.FullyQualifiedName()) + parts = append(parts, strconv.FormatBool(g.WithGrantOption)) + if g.AllPrivileges { + parts = append(parts, "ALL") + } else { + parts = append(parts, strings.Join(g.Privileges, ",")) + } + parts = append(parts, string(g.Kind)) + parts = append(parts, g.Data.String()) + return strings.Join(parts, helpers.IDDelimiter) } type OnDatabaseGrantData struct { DatabaseName sdk.AccountObjectIdentifier } +func (d *OnDatabaseGrantData) String() string { + return d.DatabaseName.FullyQualifiedName() +} + type OnSchemaGrantData struct { Kind OnSchemaGrantKind SchemaName *sdk.DatabaseObjectIdentifier DatabaseName *sdk.AccountObjectIdentifier } +func (d *OnSchemaGrantData) String() string { + var parts []string + parts = append(parts, string(d.Kind)) + switch d.Kind { + case OnSchemaSchemaGrantKind: + parts = append(parts, d.SchemaName.FullyQualifiedName()) + case OnAllSchemasInDatabaseSchemaGrantKind, OnFutureSchemasInDatabaseSchemaGrantKind: + parts = append(parts, d.DatabaseName.FullyQualifiedName()) + } + return strings.Join(parts, helpers.IDDelimiter) +} + type OnSchemaObjectGrantData struct { Kind OnSchemaObjectGrantKind Object *sdk.Object OnAllOrFuture *BulkOperationGrantData } +func (d *OnSchemaObjectGrantData) String() string { + var parts []string + parts = append(parts, string(d.Kind)) + switch d.Kind { + 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()) + } + } + return strings.Join(parts, helpers.IDDelimiter) +} + type BulkOperationGrantKind string const ( @@ -68,7 +119,7 @@ const ( type BulkOperationGrantData struct { ObjectNamePlural sdk.PluralObjectType - Kind *BulkOperationGrantKind + Kind BulkOperationGrantKind Database *sdk.AccountObjectIdentifier Schema *sdk.DatabaseObjectIdentifier } @@ -82,19 +133,22 @@ func ParseGrantPrivilegesToDatabaseRoleId(id string) (GrantPrivilegesToDatabaseR return databaseRoleId, sdk.NewError(`database role identifier should hold at least 4 parts "||||"`) } - databaseRoleId.DatabaseRoleName = sdk.NewAccountObjectIdentifierFromFullyQualifiedName(parts[0]) + databaseRoleId.DatabaseRoleName = sdk.NewDatabaseObjectIdentifierFromFullyQualifiedName(parts[0]) databaseRoleId.WithGrantOption = parts[1] == "true" privileges := strings.Split(parts[2], ",") - if len(privileges) == 1 && privileges[0] == "" { - privileges = []string{} + if len(privileges) == 1 && privileges[0] == "ALL" { + databaseRoleId.AllPrivileges = true + } else { + if len(privileges) == 1 && privileges[0] == "" { + privileges = []string{} + } + databaseRoleId.Privileges = privileges } - // TODO: All privileges - databaseRoleId.Privileges = privileges databaseRoleId.Kind = DatabaseRoleGrantKind(parts[3]) switch databaseRoleId.Kind { case OnDatabaseDatabaseRoleGrantKind: - databaseRoleId.Data = OnDatabaseGrantData{ + databaseRoleId.Data = &OnDatabaseGrantData{ DatabaseName: sdk.NewAccountObjectIdentifierFromFullyQualifiedName(parts[4]), } case OnSchemaDatabaseRoleGrantKind: @@ -112,7 +166,7 @@ func ParseGrantPrivilegesToDatabaseRoleId(id string) (GrantPrivilegesToDatabaseR default: return databaseRoleId, sdk.NewError(fmt.Sprintf("invalid OnSchemaGrantKind: %s", onSchemaGrantData.Kind)) } - databaseRoleId.Data = onSchemaGrantData + databaseRoleId.Data = &onSchemaGrantData case OnSchemaObjectDatabaseRoleGrantKind: if len(parts) < 6 { return databaseRoleId, sdk.NewError(`database role identifier should hold at least 6 parts "|||||..."`) @@ -137,21 +191,21 @@ func ParseGrantPrivilegesToDatabaseRoleId(id string) (GrantPrivilegesToDatabaseR if len(parts) != 8 { return databaseRoleId, sdk.NewError(`database role identifier should hold 8 parts "|||OnSchemaObject|On[All or Future]||In[Database or Schema]|"`) } - bulkOperationGrantData.Kind = sdk.Pointer(BulkOperationGrantKind(parts[6])) - switch *bulkOperationGrantData.Kind { + bulkOperationGrantData.Kind = BulkOperationGrantKind(parts[6]) + switch bulkOperationGrantData.Kind { case InDatabaseBulkOperationGrantKind: bulkOperationGrantData.Database = sdk.Pointer(sdk.NewAccountObjectIdentifierFromFullyQualifiedName(parts[7])) case InSchemaBulkOperationGrantKind: bulkOperationGrantData.Schema = sdk.Pointer(sdk.NewDatabaseObjectIdentifierFromFullyQualifiedName(parts[7])) default: - return databaseRoleId, sdk.NewError(fmt.Sprintf("invalid BulkOperationGrantKind: %s", *bulkOperationGrantData.Kind)) + return databaseRoleId, sdk.NewError(fmt.Sprintf("invalid BulkOperationGrantKind: %s", bulkOperationGrantData.Kind)) } } onSchemaObjectGrantData.OnAllOrFuture = bulkOperationGrantData default: return databaseRoleId, sdk.NewError(fmt.Sprintf("invalid OnSchemaObjectGrantKind: %s", onSchemaObjectGrantData.Kind)) } - databaseRoleId.Data = onSchemaObjectGrantData + databaseRoleId.Data = &onSchemaObjectGrantData default: return databaseRoleId, sdk.NewError(fmt.Sprintf("invalid DatabaseRoleGrantKind: %s", databaseRoleId.Kind)) } diff --git a/pkg/resources/grant_privileges_to_database_role_identifier_test.go b/pkg/resources/grant_privileges_to_database_role_identifier_test.go index 7c9b2a2665..5d9bf69c19 100644 --- a/pkg/resources/grant_privileges_to_database_role_identifier_test.go +++ b/pkg/resources/grant_privileges_to_database_role_identifier_test.go @@ -17,11 +17,25 @@ func TestParseGrantPrivilegesToDatabaseRoleId(t *testing.T) { Name: "grant database role on database", Identifier: `"database-name"|false|CREATE SCHEMA,USAGE,MONITOR|OnDatabase|"on-database-name"`, Expected: GrantPrivilegesToDatabaseRoleId{ - DatabaseRoleName: sdk.NewAccountObjectIdentifier("database-name"), + DatabaseRoleName: sdk.NewDatabaseObjectIdentifier("database-name", "database-role"), WithGrantOption: false, Privileges: []string{"CREATE SCHEMA", "USAGE", "MONITOR"}, Kind: OnDatabaseDatabaseRoleGrantKind, - Data: OnDatabaseGrantData{ + Data: &OnDatabaseGrantData{ + DatabaseName: sdk.NewAccountObjectIdentifier("on-database-name"), + }, + }, + }, + { + Name: "grant database role on database - all privileges", + Identifier: `"database-name"|false|ALL|OnDatabase|"on-database-name"`, + Expected: GrantPrivilegesToDatabaseRoleId{ + DatabaseRoleName: sdk.NewDatabaseObjectIdentifier("database-name", "database-role"), + WithGrantOption: false, + AllPrivileges: true, + Privileges: nil, + Kind: OnDatabaseDatabaseRoleGrantKind, + Data: &OnDatabaseGrantData{ DatabaseName: sdk.NewAccountObjectIdentifier("on-database-name"), }, }, @@ -30,11 +44,11 @@ func TestParseGrantPrivilegesToDatabaseRoleId(t *testing.T) { Name: "grant database role on schema with schema name", Identifier: `"database-name"|false|CREATE SCHEMA,USAGE,MONITOR|OnSchema|OnSchema|"database-name"."schema-name"`, // TODO: OnSchema OnSchema x2 Expected: GrantPrivilegesToDatabaseRoleId{ - DatabaseRoleName: sdk.NewAccountObjectIdentifier("database-name"), + DatabaseRoleName: sdk.NewDatabaseObjectIdentifier("database-name", "database-role"), WithGrantOption: false, Privileges: []string{"CREATE SCHEMA", "USAGE", "MONITOR"}, Kind: OnSchemaDatabaseRoleGrantKind, - Data: OnSchemaGrantData{ + Data: &OnSchemaGrantData{ Kind: OnSchemaSchemaGrantKind, SchemaName: sdk.Pointer(sdk.NewDatabaseObjectIdentifier("database-name", "schema-name")), }, @@ -44,11 +58,11 @@ func TestParseGrantPrivilegesToDatabaseRoleId(t *testing.T) { Name: "grant database role on all schemas in database", Identifier: `"database-name"|false|CREATE SCHEMA,USAGE,MONITOR|OnSchema|OnAllSchemasInDatabase|"database-name-123"`, Expected: GrantPrivilegesToDatabaseRoleId{ - DatabaseRoleName: sdk.NewAccountObjectIdentifier("database-name"), + DatabaseRoleName: sdk.NewDatabaseObjectIdentifier("database-name", "database-role"), WithGrantOption: false, Privileges: []string{"CREATE SCHEMA", "USAGE", "MONITOR"}, Kind: OnSchemaDatabaseRoleGrantKind, - Data: OnSchemaGrantData{ + Data: &OnSchemaGrantData{ Kind: OnAllSchemasInDatabaseSchemaGrantKind, DatabaseName: sdk.Pointer(sdk.NewAccountObjectIdentifier("database-name-123")), }, @@ -58,11 +72,11 @@ func TestParseGrantPrivilegesToDatabaseRoleId(t *testing.T) { Name: "grant database role on future schemas in database", Identifier: `"database-name"|false|CREATE SCHEMA,USAGE,MONITOR|OnSchema|OnFutureSchemasInDatabase|"database-name-123"`, Expected: GrantPrivilegesToDatabaseRoleId{ - DatabaseRoleName: sdk.NewAccountObjectIdentifier("database-name"), + DatabaseRoleName: sdk.NewDatabaseObjectIdentifier("database-name", "database-role"), WithGrantOption: false, Privileges: []string{"CREATE SCHEMA", "USAGE", "MONITOR"}, Kind: OnSchemaDatabaseRoleGrantKind, - Data: OnSchemaGrantData{ + Data: &OnSchemaGrantData{ Kind: OnFutureSchemasInDatabaseSchemaGrantKind, DatabaseName: sdk.Pointer(sdk.NewAccountObjectIdentifier("database-name-123")), }, @@ -72,11 +86,11 @@ func TestParseGrantPrivilegesToDatabaseRoleId(t *testing.T) { Name: "grant database role on schema object with on object option", Identifier: `"database-name"|false|CREATE SCHEMA,USAGE,MONITOR|OnSchemaObject|OnObject|TABLE|"database-name"."schema-name"."table-name"`, Expected: GrantPrivilegesToDatabaseRoleId{ - DatabaseRoleName: sdk.NewAccountObjectIdentifier("database-name"), + DatabaseRoleName: sdk.NewDatabaseObjectIdentifier("database-name", "database-role"), WithGrantOption: false, Privileges: []string{"CREATE SCHEMA", "USAGE", "MONITOR"}, Kind: OnSchemaObjectDatabaseRoleGrantKind, - Data: OnSchemaObjectGrantData{ + Data: &OnSchemaObjectGrantData{ Kind: OnObjectSchemaObjectGrantKind, Object: &sdk.Object{ ObjectType: sdk.ObjectTypeTable, @@ -89,11 +103,11 @@ func TestParseGrantPrivilegesToDatabaseRoleId(t *testing.T) { Name: "grant database role on schema object with on all option", Identifier: `"database-name"|false|CREATE SCHEMA,USAGE,MONITOR|OnSchemaObject|OnAll|TABLES`, Expected: GrantPrivilegesToDatabaseRoleId{ - DatabaseRoleName: sdk.NewAccountObjectIdentifier("database-name"), + DatabaseRoleName: sdk.NewDatabaseObjectIdentifier("database-name", "database-role"), WithGrantOption: false, Privileges: []string{"CREATE SCHEMA", "USAGE", "MONITOR"}, Kind: OnSchemaObjectDatabaseRoleGrantKind, - Data: OnSchemaObjectGrantData{ + Data: &OnSchemaObjectGrantData{ Kind: OnAllSchemaObjectGrantKind, OnAllOrFuture: &BulkOperationGrantData{ ObjectNamePlural: "TABLES", @@ -105,15 +119,15 @@ func TestParseGrantPrivilegesToDatabaseRoleId(t *testing.T) { Name: "grant database role on schema object with on all option in database", Identifier: `"database-name"|false|CREATE SCHEMA,USAGE,MONITOR|OnSchemaObject|OnAll|TABLES|InDatabase|"database-name-123"`, Expected: GrantPrivilegesToDatabaseRoleId{ - DatabaseRoleName: sdk.NewAccountObjectIdentifier("database-name"), + DatabaseRoleName: sdk.NewDatabaseObjectIdentifier("database-name", "database-role"), WithGrantOption: false, Privileges: []string{"CREATE SCHEMA", "USAGE", "MONITOR"}, Kind: OnSchemaObjectDatabaseRoleGrantKind, - Data: OnSchemaObjectGrantData{ + Data: &OnSchemaObjectGrantData{ Kind: OnAllSchemaObjectGrantKind, OnAllOrFuture: &BulkOperationGrantData{ ObjectNamePlural: "TABLES", - Kind: sdk.Pointer(InDatabaseBulkOperationGrantKind), + Kind: InDatabaseBulkOperationGrantKind, Database: sdk.Pointer(sdk.NewAccountObjectIdentifier("database-name-123")), }, }, @@ -123,15 +137,15 @@ func TestParseGrantPrivilegesToDatabaseRoleId(t *testing.T) { Name: "grant database role on schema object with on all option in schema", Identifier: `"database-name"|false|CREATE SCHEMA,USAGE,MONITOR|OnSchemaObject|OnAll|TABLES|InSchema|"database-name"."schema-name"`, Expected: GrantPrivilegesToDatabaseRoleId{ - DatabaseRoleName: sdk.NewAccountObjectIdentifier("database-name"), + DatabaseRoleName: sdk.NewDatabaseObjectIdentifier("database-name", "database-role"), WithGrantOption: false, Privileges: []string{"CREATE SCHEMA", "USAGE", "MONITOR"}, Kind: OnSchemaObjectDatabaseRoleGrantKind, - Data: OnSchemaObjectGrantData{ + Data: &OnSchemaObjectGrantData{ Kind: OnAllSchemaObjectGrantKind, OnAllOrFuture: &BulkOperationGrantData{ ObjectNamePlural: "TABLES", - Kind: sdk.Pointer(InSchemaBulkOperationGrantKind), + Kind: InSchemaBulkOperationGrantKind, Schema: sdk.Pointer(sdk.NewDatabaseObjectIdentifier("database-name", "schema-name")), }, }, @@ -141,11 +155,11 @@ func TestParseGrantPrivilegesToDatabaseRoleId(t *testing.T) { Name: "grant database role on schema object with on future option", Identifier: `"database-name"|false|CREATE SCHEMA,USAGE,MONITOR|OnSchemaObject|OnFuture|TABLES`, Expected: GrantPrivilegesToDatabaseRoleId{ - DatabaseRoleName: sdk.NewAccountObjectIdentifier("database-name"), + DatabaseRoleName: sdk.NewDatabaseObjectIdentifier("database-name", "database-role"), WithGrantOption: false, Privileges: []string{"CREATE SCHEMA", "USAGE", "MONITOR"}, Kind: OnSchemaObjectDatabaseRoleGrantKind, - Data: OnSchemaObjectGrantData{ + Data: &OnSchemaObjectGrantData{ Kind: OnFutureSchemaObjectGrantKind, OnAllOrFuture: &BulkOperationGrantData{ ObjectNamePlural: "TABLES", @@ -157,15 +171,15 @@ func TestParseGrantPrivilegesToDatabaseRoleId(t *testing.T) { Name: "grant database role on schema object with on all option in database", Identifier: `"database-name"|false|CREATE SCHEMA,USAGE,MONITOR|OnSchemaObject|OnFuture|TABLES|InDatabase|"database-name-123"`, Expected: GrantPrivilegesToDatabaseRoleId{ - DatabaseRoleName: sdk.NewAccountObjectIdentifier("database-name"), + DatabaseRoleName: sdk.NewDatabaseObjectIdentifier("database-name", "database-role"), WithGrantOption: false, Privileges: []string{"CREATE SCHEMA", "USAGE", "MONITOR"}, Kind: OnSchemaObjectDatabaseRoleGrantKind, - Data: OnSchemaObjectGrantData{ + Data: &OnSchemaObjectGrantData{ Kind: OnFutureSchemaObjectGrantKind, OnAllOrFuture: &BulkOperationGrantData{ ObjectNamePlural: "TABLES", - Kind: sdk.Pointer(InDatabaseBulkOperationGrantKind), + Kind: InDatabaseBulkOperationGrantKind, Database: sdk.Pointer(sdk.NewAccountObjectIdentifier("database-name-123")), }, }, @@ -175,15 +189,15 @@ func TestParseGrantPrivilegesToDatabaseRoleId(t *testing.T) { Name: "grant database role on schema object with on all option in schema", Identifier: `"database-name"|false|CREATE SCHEMA,USAGE,MONITOR|OnSchemaObject|OnFuture|TABLES|InSchema|"database-name"."schema-name"`, Expected: GrantPrivilegesToDatabaseRoleId{ - DatabaseRoleName: sdk.NewAccountObjectIdentifier("database-name"), + DatabaseRoleName: sdk.NewDatabaseObjectIdentifier("database-name", "database-role"), WithGrantOption: false, Privileges: []string{"CREATE SCHEMA", "USAGE", "MONITOR"}, Kind: OnSchemaObjectDatabaseRoleGrantKind, - Data: OnSchemaObjectGrantData{ + Data: &OnSchemaObjectGrantData{ Kind: OnFutureSchemaObjectGrantKind, OnAllOrFuture: &BulkOperationGrantData{ ObjectNamePlural: "TABLES", - Kind: sdk.Pointer(InSchemaBulkOperationGrantKind), + Kind: InSchemaBulkOperationGrantKind, Schema: sdk.Pointer(sdk.NewDatabaseObjectIdentifier("database-name", "schema-name")), }, }, @@ -191,47 +205,47 @@ func TestParseGrantPrivilegesToDatabaseRoleId(t *testing.T) { }, { Name: "validation: grant database role not enough parts", - Identifier: `"database-name"|false`, + Identifier: `"database-name"."role-name"|false`, Error: "database role identifier should hold at least 4 parts", }, { Name: "validation: grant database role not enough parts for OnDatabase kind", - Identifier: `"database-name"|false|CREATE SCHEMA,USAGE,MONITOR|OnDatabase`, + Identifier: `"database-name"."role-name"|false|CREATE SCHEMA,USAGE,MONITOR|OnDatabase`, Error: "database role identifier should hold at least 4 parts", }, { Name: "validation: grant database role not enough parts for OnSchema kind", - Identifier: `"database-name"|false|CREATE SCHEMA,USAGE,MONITOR|OnSchema|OnAllSchemasInDatabase`, + Identifier: `"database-name"."role-name"|false|CREATE SCHEMA,USAGE,MONITOR|OnSchema|OnAllSchemasInDatabase`, Error: "database role identifier should hold at least 6 parts", }, { Name: "validation: grant database role not enough parts for OnSchemaObject kind", - Identifier: `"database-name"|false|CREATE SCHEMA,USAGE,MONITOR|OnSchemaObject|OnObject`, + Identifier: `"database-name"."role-name"|false|CREATE SCHEMA,USAGE,MONITOR|OnSchemaObject|OnObject`, Error: "database role identifier should hold at least 6 parts", }, { Name: "validation: grant database role not enough parts for OnSchemaObject kind", - Identifier: `"database-name"|false|CREATE SCHEMA,USAGE,MONITOR|OnSchemaObject|OnObject|TABLE`, + Identifier: `"database-name"."role-name"|false|CREATE SCHEMA,USAGE,MONITOR|OnSchemaObject|OnObject|TABLE`, Error: "database role identifier should hold 7 parts", }, { Name: "validation: grant database role not enough parts for OnSchemaObject.InDatabase kind", - Identifier: `"database-name"|false|CREATE SCHEMA,USAGE,MONITOR|OnSchemaObject|OnAll|TABLES|InDatabase`, + Identifier: `"database-name"."role-name"|false|CREATE SCHEMA,USAGE,MONITOR|OnSchemaObject|OnAll|TABLES|InDatabase`, Error: "database role identifier should hold 8 parts", }, { Name: "validation: grant database role invalid DatabaseRoleGrantKind kind", - Identifier: `"database-name"|false|CREATE SCHEMA,USAGE,MONITOR|some-kind|some-data`, + Identifier: `"database-name"."role-name"|false|CREATE SCHEMA,USAGE,MONITOR|some-kind|some-data`, Error: "invalid DatabaseRoleGrantKind: some-kind", }, { Name: "validation: grant database role invalid OnSchemaGrantKind kind", - Identifier: `"database-name"|false|CREATE SCHEMA,USAGE,MONITOR|OnSchema|some-kind|some-data`, + Identifier: `"database-name"."role-name"|false|CREATE SCHEMA,USAGE,MONITOR|OnSchema|some-kind|some-data`, Error: "invalid OnSchemaGrantKind: some-kind", }, { Name: "validation: grant database role invalid OnSchemaObjectGrantKind kind", - Identifier: `"database-name"|false|CREATE SCHEMA,USAGE,MONITOR|OnSchemaObject|some-kind|some-data`, + Identifier: `"database-name"."role-name"|false|CREATE SCHEMA,USAGE,MONITOR|OnSchemaObject|some-kind|some-data`, Error: "invalid OnSchemaObjectGrantKind: some-kind", }, } @@ -248,3 +262,148 @@ func TestParseGrantPrivilegesToDatabaseRoleId(t *testing.T) { }) } } + +func TestGrantPrivilegesToDatabaseRoleIdString(t *testing.T) { + testCases := []struct { + Name string + Identifier GrantPrivilegesToDatabaseRoleId + Expected string + Error string + }{ + { + Name: "grant database role on database", + Identifier: GrantPrivilegesToDatabaseRoleId{ + DatabaseRoleName: sdk.NewDatabaseObjectIdentifier("database-name", "role-name"), + WithGrantOption: true, + AllPrivileges: true, + Kind: OnDatabaseDatabaseRoleGrantKind, + Data: &OnDatabaseGrantData{ + DatabaseName: sdk.NewAccountObjectIdentifier("database-name"), + }, + }, + Expected: `"database-name"."role-name"|true|ALL|OnDatabase|"database-name"`, + }, + { + Name: "grant database role on schema on schema", + Identifier: GrantPrivilegesToDatabaseRoleId{ + DatabaseRoleName: sdk.NewDatabaseObjectIdentifier("database-name", "role-name"), + WithGrantOption: false, + Privileges: []string{"CREATE SCHEMA", "USAGE", "MONITOR"}, + Kind: OnSchemaDatabaseRoleGrantKind, + Data: &OnSchemaGrantData{ + Kind: OnSchemaSchemaGrantKind, + SchemaName: sdk.Pointer(sdk.NewDatabaseObjectIdentifier("database-name", "schema-name")), + }, + }, + Expected: `"database-name"."role-name"|false|CREATE SCHEMA,USAGE,MONITOR|OnSchema|OnSchema|"database-name"."schema-name"`, + // TODO: Could be + // OnSchema|schema-name + // OnAllSchemasInDatabase|database-name + // OnFutureSchemasInDatabase|database-name + // instead of repeating OnSchema x2 + }, + { + Name: "grant database role on all schemas in database", + Identifier: GrantPrivilegesToDatabaseRoleId{ + DatabaseRoleName: sdk.NewDatabaseObjectIdentifier("database-name", "role-name"), + WithGrantOption: false, + Privileges: []string{"CREATE SCHEMA", "USAGE", "MONITOR"}, + Kind: OnSchemaDatabaseRoleGrantKind, + Data: &OnSchemaGrantData{ + Kind: OnAllSchemasInDatabaseSchemaGrantKind, + DatabaseName: sdk.Pointer(sdk.NewAccountObjectIdentifier("database-name")), + }, + }, + Expected: `"database-name"."role-name"|false|CREATE SCHEMA,USAGE,MONITOR|OnSchema|OnAllSchemasInDatabase|"database-name"`, + }, + { + Name: "grant database role on future schemas in database", + Identifier: GrantPrivilegesToDatabaseRoleId{ + DatabaseRoleName: sdk.NewDatabaseObjectIdentifier("database-name", "role-name"), + WithGrantOption: false, + Privileges: []string{"CREATE SCHEMA", "USAGE", "MONITOR"}, + Kind: OnSchemaDatabaseRoleGrantKind, + Data: &OnSchemaGrantData{ + Kind: OnFutureSchemasInDatabaseSchemaGrantKind, + DatabaseName: sdk.Pointer(sdk.NewAccountObjectIdentifier("database-name")), + }, + }, + Expected: `"database-name"."role-name"|false|CREATE SCHEMA,USAGE,MONITOR|OnSchema|OnFutureSchemasInDatabase|"database-name"`, + }, + { + Name: "grant database role on schema object on object", + Identifier: GrantPrivilegesToDatabaseRoleId{ + DatabaseRoleName: sdk.NewDatabaseObjectIdentifier("database-name", "role-name"), + WithGrantOption: false, + Privileges: []string{"CREATE SCHEMA", "USAGE", "MONITOR"}, + Kind: OnSchemaObjectDatabaseRoleGrantKind, + Data: &OnSchemaObjectGrantData{ + Kind: OnObjectSchemaObjectGrantKind, + Object: &sdk.Object{ + ObjectType: sdk.ObjectTypeTable, + Name: sdk.NewSchemaObjectIdentifier("database-name", "schema-name", "table-name"), + }, + }, + }, + Expected: `"database-name"."role-name"|false|CREATE SCHEMA,USAGE,MONITOR|OnSchemaObject|OnObject|TABLE|"database-name"."schema-name"."table-name"`, + }, + { + Name: "grant database role on schema object on all tables", + Identifier: GrantPrivilegesToDatabaseRoleId{ + DatabaseRoleName: sdk.NewDatabaseObjectIdentifier("database-name", "role-name"), + WithGrantOption: false, + Privileges: []string{"CREATE SCHEMA", "USAGE", "MONITOR"}, + Kind: OnSchemaObjectDatabaseRoleGrantKind, + Data: &OnSchemaObjectGrantData{ + Kind: OnAllSchemaObjectGrantKind, + OnAllOrFuture: &BulkOperationGrantData{ + ObjectNamePlural: sdk.PluralObjectTypeTables, + }, + }, + }, + Expected: `"database-name"."role-name"|false|CREATE SCHEMA,USAGE,MONITOR|OnSchemaObject|OnAll|TABLES`, + }, + { + Name: "grant database role on schema object on all tables in database", + Identifier: GrantPrivilegesToDatabaseRoleId{ + DatabaseRoleName: sdk.NewDatabaseObjectIdentifier("database-name", "role-name"), + WithGrantOption: false, + Privileges: []string{"CREATE SCHEMA", "USAGE", "MONITOR"}, + Kind: OnSchemaObjectDatabaseRoleGrantKind, + Data: &OnSchemaObjectGrantData{ + Kind: OnAllSchemaObjectGrantKind, + OnAllOrFuture: &BulkOperationGrantData{ + ObjectNamePlural: sdk.PluralObjectTypeTables, + Kind: InDatabaseBulkOperationGrantKind, + Database: sdk.Pointer(sdk.NewAccountObjectIdentifier("database-name")), + }, + }, + }, + Expected: `"database-name"."role-name"|false|CREATE SCHEMA,USAGE,MONITOR|OnSchemaObject|OnAll|TABLES|InDatabase|"database-name"`, + }, + { + Name: "grant database role on schema object on all tables in schema", + Identifier: GrantPrivilegesToDatabaseRoleId{ + DatabaseRoleName: sdk.NewDatabaseObjectIdentifier("database-name", "role-name"), + WithGrantOption: false, + Privileges: []string{"CREATE SCHEMA", "USAGE", "MONITOR"}, + Kind: OnSchemaObjectDatabaseRoleGrantKind, + Data: &OnSchemaObjectGrantData{ + Kind: OnAllSchemaObjectGrantKind, + OnAllOrFuture: &BulkOperationGrantData{ + ObjectNamePlural: sdk.PluralObjectTypeTables, + Kind: InSchemaBulkOperationGrantKind, + Schema: sdk.Pointer(sdk.NewDatabaseObjectIdentifier("database-name", "schema-name")), + }, + }, + }, + Expected: `"database-name"."role-name"|false|CREATE SCHEMA,USAGE,MONITOR|OnSchemaObject|OnAll|TABLES|InSchema|"database-name"."schema-name"`, + }, + } + + for _, tt := range testCases { + t.Run(tt.Name, func(t *testing.T) { + assert.Equal(t, tt.Expected, tt.Identifier.String()) + }) + } +} diff --git a/pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole_basic/test.tf b/pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole_basic/test.tf new file mode 100644 index 0000000000..d6a3bc2b57 --- /dev/null +++ b/pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole_basic/test.tf @@ -0,0 +1,6 @@ +resource "snowflake_grant_privileges_to_database_role" "test" { + database_role_name = "${var.database}.${var.name}" + privileges = var.privileges + on_database = var.database + with_grant_option = var.with_grant_option +} diff --git a/pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole_basic/variables.tf b/pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole_basic/variables.tf new file mode 100644 index 0000000000..0e22e903d7 --- /dev/null +++ b/pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole_basic/variables.tf @@ -0,0 +1,15 @@ +variable "name" { + type = string +} + +variable "privileges" { + type = list(string) +} + +variable "database" { + type = string +} + +variable "with_grant_option" { + type = bool +} From 5527209c5635da84fa73ce328296f243ba464669 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Cie=C5=9Blak?= Date: Thu, 21 Dec 2023 15:48:21 +0100 Subject: [PATCH 04/16] wip --- .../grant_privileges_to_database_role.go | 94 ++++++----- ...ileges_to_database_role_acceptance_test.go | 151 ++++++++++++++++-- pkg/resources/helpers.go | 2 +- .../test.tf | 9 ++ .../variables.tf | 0 .../test.tf | 4 +- .../test.tf | 9 ++ .../variables.tf | 19 +++ .../test.tf | 21 +++ .../variables.tf | 23 +++ 10 files changed, 278 insertions(+), 54 deletions(-) create mode 100644 pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole_OnAllSchemasInDatabase/test.tf rename pkg/resources/testdata/{TestAcc_GrantPrivilegesToDatabaseRole_basic => TestAcc_GrantPrivilegesToDatabaseRole_OnAllSchemasInDatabase}/variables.tf (100%) rename pkg/resources/testdata/{TestAcc_GrantPrivilegesToDatabaseRole_basic => TestAcc_GrantPrivilegesToDatabaseRole_OnDatabase}/test.tf (58%) create mode 100644 pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole_OnSchema/test.tf create mode 100644 pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole_OnSchema/variables.tf create mode 100644 pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole_OnSchemaObject/test.tf create mode 100644 pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole_OnSchemaObject/variables.tf diff --git a/pkg/resources/grant_privileges_to_database_role.go b/pkg/resources/grant_privileges_to_database_role.go index 8b5330e431..276a7b5f50 100644 --- a/pkg/resources/grant_privileges_to_database_role.go +++ b/pkg/resources/grant_privileges_to_database_role.go @@ -5,10 +5,10 @@ import ( "database/sql" "fmt" "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk" - "github.com/hashicorp/go-cty/cty" "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" "slices" "strings" ) @@ -271,25 +271,25 @@ var grantPrivilegesOnDatabaseRoleBulkOperationSchema = map[string]*schema.Schema }, } -func doesNotContainOwnershipGrant() func(value any, path cty.Path) diag.Diagnostics { - return func(value any, path cty.Path) diag.Diagnostics { - var diags diag.Diagnostics - if privileges, ok := value.([]string); ok { - if slices.ContainsFunc(privileges, func(privilege string) bool { - return strings.ToUpper(privilege) == "OWNERSHIP" - }) { - diags = append(diags, diag.Diagnostic{ - Severity: diag.Error, - Summary: "Unsupported privilege type 'OWNERSHIP'.", - // TODO: Change when a new resource for granting ownership will be available - Detail: "Granting ownership is only allowed in dedicated resources (snowflake_user_ownership_grant, snowflake_role_ownership_grant)", - AttributePath: nil, - }) - } - } - return diags - } -} +//func doesNotContainOwnershipGrant() func(value any, path cty.Path) diag.Diagnostics { +// return func(value any, path cty.Path) diag.Diagnostics { +// var diags diag.Diagnostics +// if privileges, ok := value.([]string); ok { +// if slices.ContainsFunc(privileges, func(privilege string) bool { +// return strings.ToUpper(privilege) == "OWNERSHIP" +// }) { +// diags = append(diags, diag.Diagnostic{ +// Severity: diag.Error, +// Summary: "Unsupported privilege type 'OWNERSHIP'.", +// // TODO: Change when a new resource for granting ownership will be available +// Detail: "Granting ownership is only allowed in dedicated resources (snowflake_user_ownership_grant, snowflake_role_ownership_grant)", +// AttributePath: nil, +// }) +// } +// } +// return diags +// } +//} func GrantPrivilegesToDatabaseRole() *schema.Resource { return &schema.Resource{ @@ -322,6 +322,7 @@ func ImportGrantPrivilegesToDatabaseRole(ctx context.Context, d *schema.Resource if err := d.Set("privileges", id.Privileges); err != nil { return nil, err } + // TODO: Error when contains ownership as privilege switch id.Kind { case OnDatabaseDatabaseRoleGrantKind: if err := d.Set("on_database", id.Data.(*OnDatabaseGrantData).DatabaseName.FullyQualifiedName()); err != nil { @@ -396,17 +397,31 @@ func CreateGrantPrivilegesToDatabaseRole(ctx context.Context, d *schema.Resource id.DatabaseRoleName = databaseRoleName id.AllPrivileges = d.Get("all_privileges").(bool) if p, ok := d.GetOk("privileges"); ok { - id.Privileges = expandStringList(p.(*schema.Set).List()) + privileges := expandStringList(p.(*schema.Set).List()) + if slices.ContainsFunc(privileges, func(s string) bool { + return strings.ToUpper(s) == "OWNERSHIP" + }) { + return append(diags, diag.Diagnostic{ + Severity: diag.Error, + Summary: "OWNERSHIP in the list of privileges", + Detail: fmt.Sprintf("Id: %s\nMessage: TODO", id.DatabaseRoleName), // TODO: Message and link to ownership resource + }) + } + id.Privileges = privileges } + withGrantOption := d.Get("with_grant_option").(bool) + id.WithGrantOption = withGrantOption on := getDatabaseRoleGrantOn(id, d) // TODO: It shouldn't modify + priv := getDatabaseRolePrivileges(id.Kind, id.AllPrivileges, id.Privileges) + log.Println("GRANTING PRIV:", priv) err := client.Grants.GrantPrivilegesToDatabaseRole( ctx, - getDatabaseRolePrivileges(id.Kind, id.AllPrivileges, id.Privileges), + priv, on, databaseRoleName, &sdk.GrantPrivilegesToDatabaseRoleOptions{ - WithGrantOption: GetPropertyAsPointer[bool](d, "with_grant_option"), + WithGrantOption: &withGrantOption, }, ) if err != nil { @@ -714,14 +729,14 @@ func getDatabaseRolePrivileges(kind DatabaseRoleGrantKind, allPrivileges bool, p // TODO: This should not set anything - remove id func getDatabaseRoleGrantOn(id *GrantPrivilegesToDatabaseRoleId, d *schema.ResourceData) *sdk.DatabaseRoleGrantOn { - onDatabase, onDatabaseOk := GetProperty[string](d, "on_database") - onSchema, onSchemaOk := GetProperty[map[string]any](d, "on_schema") - onSchemaObject, onSchemaObjectOk := GetProperty[map[string]any](d, "on_schema_object") + onDatabase, onDatabaseOk := d.GetOk("on_database") + onSchemaBlock, onSchemaOk := d.GetOk("on_schema") + onSchemaObjectBlock, onSchemaObjectOk := d.GetOk("on_schema_object") on := new(sdk.DatabaseRoleGrantOn) switch { case onDatabaseOk: - databaseId := sdk.NewAccountObjectIdentifierFromFullyQualifiedName(onDatabase) + databaseId := sdk.NewAccountObjectIdentifierFromFullyQualifiedName(onDatabase.(string)) id.Kind = OnDatabaseDatabaseRoleGrantKind id.Data = &OnDatabaseGrantData{ @@ -729,30 +744,36 @@ func getDatabaseRoleGrantOn(id *GrantPrivilegesToDatabaseRoleId, d *schema.Resou } on.Database = &databaseId case onSchemaOk: + onSchema := onSchemaBlock.([]any)[0].(map[string]any) id.Kind = OnSchemaDatabaseRoleGrantKind onSchemaGrantData := new(OnSchemaGrantData) grantOnSchema := new(sdk.GrantOnSchema) - schemaName, schemaNameOk := onSchema["schema_name"] - allSchemasInDatabase, allSchemasInDatabaseOk := onSchema["all_schemas_in_database"] - futureSchemasInDatabase, futureSchemasInDatabaseOk := onSchema["future_schemas_in_database"] + schemaName := onSchema["schema_name"].(string) + schemaNameOk := len(schemaName) > 0 + + allSchemasInDatabase := onSchema["all_schemas_in_database"].(string) + allSchemasInDatabaseOk := len(allSchemasInDatabase) > 0 + + futureSchemasInDatabase := onSchema["future_schemas_in_database"].(string) + futureSchemasInDatabaseOk := len(futureSchemasInDatabase) > 0 switch { case schemaNameOk: - schemaId := sdk.NewDatabaseObjectIdentifierFromFullyQualifiedName(schemaName.(string)) + schemaId := sdk.NewDatabaseObjectIdentifierFromFullyQualifiedName(schemaName) onSchemaGrantData.Kind = OnSchemaSchemaGrantKind onSchemaGrantData.SchemaName = &schemaId grantOnSchema.Schema = &schemaId case allSchemasInDatabaseOk: - databaseId := sdk.NewAccountObjectIdentifierFromFullyQualifiedName(allSchemasInDatabase.(string)) + databaseId := sdk.NewAccountObjectIdentifierFromFullyQualifiedName(allSchemasInDatabase) onSchemaGrantData.Kind = OnAllSchemasInDatabaseSchemaGrantKind onSchemaGrantData.DatabaseName = &databaseId grantOnSchema.AllSchemasInDatabase = &databaseId case futureSchemasInDatabaseOk: - databaseId := sdk.NewAccountObjectIdentifierFromFullyQualifiedName(futureSchemasInDatabase.(string)) + databaseId := sdk.NewAccountObjectIdentifierFromFullyQualifiedName(futureSchemasInDatabase) onSchemaGrantData.Kind = OnFutureSchemasInDatabaseSchemaGrantKind onSchemaGrantData.DatabaseName = &databaseId @@ -762,7 +783,8 @@ func getDatabaseRoleGrantOn(id *GrantPrivilegesToDatabaseRoleId, d *schema.Resou id.Data = onSchemaGrantData on.Schema = grantOnSchema case onSchemaObjectOk: - id.Kind = OnSchemaDatabaseRoleGrantKind + onSchemaObject := onSchemaObjectBlock.([]any)[0].(map[string]any) + id.Kind = OnSchemaObjectDatabaseRoleGrantKind onSchemaObjectGrantData := new(OnSchemaObjectGrantData) grantOnSchemaObject := new(sdk.GrantOnSchemaObject) @@ -784,10 +806,10 @@ func getDatabaseRoleGrantOn(id *GrantPrivilegesToDatabaseRoleId, d *schema.Resou grantOnSchemaObject.SchemaObject = object case allOk: onSchemaObjectGrantData.Kind = OnAllSchemaObjectGrantKind - grantOnSchemaObject.All = getGrantOnSchemaObjectIn(onSchemaObjectGrantData, all.(map[string]any)) + grantOnSchemaObject.All = getGrantOnSchemaObjectIn(onSchemaObjectGrantData, all.([]any)[0].(map[string]any)) case futureOk: onSchemaObjectGrantData.Kind = OnFutureSchemaObjectGrantKind - grantOnSchemaObject.Future = getGrantOnSchemaObjectIn(onSchemaObjectGrantData, future.(map[string]any)) + grantOnSchemaObject.Future = getGrantOnSchemaObjectIn(onSchemaObjectGrantData, future.([]any)[0].(map[string]any)) } id.Data = onSchemaObjectGrantData diff --git a/pkg/resources/grant_privileges_to_database_role_acceptance_test.go b/pkg/resources/grant_privileges_to_database_role_acceptance_test.go index 8f34bdd5b8..5ffefd23ef 100644 --- a/pkg/resources/grant_privileges_to_database_role_acceptance_test.go +++ b/pkg/resources/grant_privileges_to_database_role_acceptance_test.go @@ -23,14 +23,14 @@ import ( // - import - check import // - different paths to parse (on database, on schema, on schema object) -func TestAcc_GrantPrivilegesToDatabaseRole_basic(t *testing.T) { +func TestAcc_GrantPrivilegesToDatabaseRole_OnDatabase(t *testing.T) { name := "test_database_role_name" configVariables := config.Variables{ "name": config.StringVariable(name), "privileges": config.ListVariable( - config.StringVariable("CREATE SCHEMA"), - config.StringVariable("MODIFY"), - config.StringVariable("USAGE"), + config.StringVariable(string(sdk.AccountObjectPrivilegeCreateSchema)), + config.StringVariable(string(sdk.AccountObjectPrivilegeModify)), + config.StringVariable(string(sdk.AccountObjectPrivilegeUsage)), ), "database": config.StringVariable(acc.TestDatabaseName), "with_grant_option": config.BoolVariable(true), @@ -52,10 +52,133 @@ func TestAcc_GrantPrivilegesToDatabaseRole_basic(t *testing.T) { Check: resource.ComposeTestCheckFunc( resource.TestCheckResourceAttr(resourceName, "database_role_name", sdk.NewDatabaseObjectIdentifier(acc.TestDatabaseName, name).FullyQualifiedName()), resource.TestCheckResourceAttr(resourceName, "privileges.#", "3"), - resource.TestCheckResourceAttr(resourceName, "privileges.0", "CREATE SCHEMA"), - resource.TestCheckResourceAttr(resourceName, "privileges.1", "MODIFY"), - resource.TestCheckResourceAttr(resourceName, "privileges.2", "USAGE"), - resource.TestCheckResourceAttr(resourceName, "on_database", acc.TestDatabaseName), + resource.TestCheckResourceAttr(resourceName, "privileges.0", string(sdk.AccountObjectPrivilegeCreateSchema)), + resource.TestCheckResourceAttr(resourceName, "privileges.1", string(sdk.AccountObjectPrivilegeModify)), + resource.TestCheckResourceAttr(resourceName, "privileges.2", string(sdk.AccountObjectPrivilegeUsage)), + resource.TestCheckResourceAttr(resourceName, "on_database", sdk.NewAccountObjectIdentifier(acc.TestDatabaseName).FullyQualifiedName()), + resource.TestCheckResourceAttr(resourceName, "with_grant_option", "true"), + ), + }, + }, + }) +} + +func TestAcc_GrantPrivilegesToDatabaseRole_OnSchema(t *testing.T) { + name := "test_database_role_name" + configVariables := config.Variables{ + "name": config.StringVariable(name), + "privileges": config.ListVariable( + config.StringVariable(string(sdk.SchemaPrivilegeCreateTable)), + config.StringVariable(string(sdk.SchemaPrivilegeModify)), + ), + "database": config.StringVariable(acc.TestDatabaseName), + "schema": config.StringVariable(acc.TestSchemaName), + "with_grant_option": config.BoolVariable(false), + } + resourceName := "snowflake_grant_privileges_to_database_role.test" + + resource.Test(t, resource.TestCase{ + ProtoV6ProviderFactories: acc.TestAccProtoV6ProviderFactories, + PreCheck: func() { acc.TestAccPreCheck(t) }, + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.RequireAbove(tfversion.Version1_5_0), + }, + CheckDestroy: testAccCheckDatabaseRolePrivilegesRevoked, + Steps: []resource.TestStep{ + { + PreConfig: func() { createDatabaseRoleOutsideTerraform(t, name) }, + ConfigDirectory: config.TestNameDirectory(), + ConfigVariables: configVariables, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "database_role_name", sdk.NewDatabaseObjectIdentifier(acc.TestDatabaseName, name).FullyQualifiedName()), + resource.TestCheckResourceAttr(resourceName, "privileges.#", "2"), + resource.TestCheckResourceAttr(resourceName, "privileges.0", string(sdk.SchemaPrivilegeCreateTable)), + resource.TestCheckResourceAttr(resourceName, "privileges.1", string(sdk.SchemaPrivilegeModify)), + resource.TestCheckResourceAttr(resourceName, "on_schema.#", "1"), + resource.TestCheckResourceAttr(resourceName, "on_schema.0.schema_name", sdk.NewDatabaseObjectIdentifier(acc.TestDatabaseName, acc.TestSchemaName).FullyQualifiedName()), + resource.TestCheckResourceAttr(resourceName, "with_grant_option", "false"), + ), + }, + }, + }) +} + +func TestAcc_GrantPrivilegesToDatabaseRole_OnAllSchemasInDatabase(t *testing.T) { + name := "test_database_role_name" + configVariables := config.Variables{ + "name": config.StringVariable(name), + "privileges": config.ListVariable( + config.StringVariable(string(sdk.SchemaPrivilegeCreateTable)), + config.StringVariable(string(sdk.SchemaPrivilegeModify)), + ), + "database": config.StringVariable(acc.TestDatabaseName), + "with_grant_option": config.BoolVariable(false), + } + resourceName := "snowflake_grant_privileges_to_database_role.test" + + resource.Test(t, resource.TestCase{ + ProtoV6ProviderFactories: acc.TestAccProtoV6ProviderFactories, + PreCheck: func() { acc.TestAccPreCheck(t) }, + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.RequireAbove(tfversion.Version1_5_0), + }, + CheckDestroy: testAccCheckDatabaseRolePrivilegesRevoked, + Steps: []resource.TestStep{ + { + PreConfig: func() { createDatabaseRoleOutsideTerraform(t, name) }, + ConfigDirectory: config.TestNameDirectory(), + ConfigVariables: configVariables, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "database_role_name", sdk.NewDatabaseObjectIdentifier(acc.TestDatabaseName, name).FullyQualifiedName()), + resource.TestCheckResourceAttr(resourceName, "privileges.#", "2"), + resource.TestCheckResourceAttr(resourceName, "privileges.0", string(sdk.SchemaPrivilegeCreateTable)), + resource.TestCheckResourceAttr(resourceName, "privileges.1", string(sdk.SchemaPrivilegeModify)), + resource.TestCheckResourceAttr(resourceName, "on_schema.#", "1"), + resource.TestCheckResourceAttr(resourceName, "on_schema.0.all_schemas_in_database", sdk.NewAccountObjectIdentifier(acc.TestDatabaseName).FullyQualifiedName()), + resource.TestCheckResourceAttr(resourceName, "with_grant_option", "false"), + ), + }, + }, + }) +} + +func TestAcc_GrantPrivilegesToDatabaseRole_OnSchemaObject(t *testing.T) { + name := "test_database_role_name" + tableName := "test_database_role_table_name" + configVariables := config.Variables{ + "name": config.StringVariable(name), + "table_name": config.StringVariable(tableName), + "privileges": config.ListVariable( + config.StringVariable(string(sdk.SchemaObjectPrivilegeInsert)), + config.StringVariable(string(sdk.SchemaObjectPrivilegeUpdate)), + ), + "database": config.StringVariable(acc.TestDatabaseName), + "schema": config.StringVariable(acc.TestSchemaName), + "with_grant_option": config.BoolVariable(false), + } + resourceName := "snowflake_grant_privileges_to_database_role.test" + + resource.Test(t, resource.TestCase{ + ProtoV6ProviderFactories: acc.TestAccProtoV6ProviderFactories, + PreCheck: func() { acc.TestAccPreCheck(t) }, + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.RequireAbove(tfversion.Version1_5_0), + }, + CheckDestroy: testAccCheckDatabaseRolePrivilegesRevoked, + Steps: []resource.TestStep{ + { + PreConfig: func() { createDatabaseRoleOutsideTerraform(t, name) }, + ConfigDirectory: config.TestNameDirectory(), + ConfigVariables: configVariables, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "database_role_name", sdk.NewDatabaseObjectIdentifier(acc.TestDatabaseName, name).FullyQualifiedName()), + resource.TestCheckResourceAttr(resourceName, "privileges.#", "2"), + resource.TestCheckResourceAttr(resourceName, "privileges.0", string(sdk.SchemaObjectPrivilegeInsert)), + resource.TestCheckResourceAttr(resourceName, "privileges.1", string(sdk.SchemaObjectPrivilegeUpdate)), + resource.TestCheckResourceAttr(resourceName, "on_schema_object.#", "1"), + resource.TestCheckResourceAttr(resourceName, "on_schema_object.0.object_type", string(sdk.ObjectTypeTable)), + resource.TestCheckResourceAttr(resourceName, "on_schema_object.0.object_name", sdk.NewSchemaObjectIdentifier(acc.TestDatabaseName, acc.TestSchemaName, tableName).FullyQualifiedName()), + resource.TestCheckResourceAttr(resourceName, "with_grant_option", "false"), ), }, }, @@ -70,14 +193,9 @@ func createDatabaseRoleOutsideTerraform(t *testing.T, name string) { } ctx := context.Background() databaseRoleId := sdk.NewDatabaseObjectIdentifier(acc.TestDatabaseName, name) - if err := client.DatabaseRoles.Create(ctx, sdk.NewCreateDatabaseRoleRequest(databaseRoleId)); err != nil { + if err := client.DatabaseRoles.Create(ctx, sdk.NewCreateDatabaseRoleRequest(databaseRoleId).WithOrReplace(true)); err != nil { t.Fatal(fmt.Errorf("error database role (%s): %w", databaseRoleId.FullyQualifiedName(), err)) } - t.Cleanup(func() { - if err := client.DatabaseRoles.Drop(ctx, sdk.NewDropDatabaseRoleRequest(databaseRoleId)); err != nil { - t.Fatal(fmt.Errorf("failed to drop database role (%s): %w", databaseRoleId.FullyQualifiedName(), err)) - } - }) } func testAccCheckDatabaseRolePrivilegesRevoked(s *terraform.State) error { @@ -100,7 +218,10 @@ func testAccCheckDatabaseRolePrivilegesRevoked(s *terraform.State) error { } var grantedPrivileges []string for _, grant := range grants { - grantedPrivileges = append(grantedPrivileges, grant.Privilege) + // usage is the default privilege available after creation (it won't be revoked) + if grant.Privilege != "USAGE" { + grantedPrivileges = append(grantedPrivileges, grant.Privilege) + } } if len(grantedPrivileges) > 0 { return fmt.Errorf("database role (%s) still grants , granted privileges %v", id.FullyQualifiedName(), grantedPrivileges) diff --git a/pkg/resources/helpers.go b/pkg/resources/helpers.go index 3f6e7cfca1..1a06632263 100644 --- a/pkg/resources/helpers.go +++ b/pkg/resources/helpers.go @@ -126,7 +126,7 @@ func GetPropertyAsPointer[T any](d *schema.ResourceData, property string) *T { return &typedValue } -func GetProperty[T any](d *schema.ResourceData, property string) (T, bool) { +func GetPropertyOrDefault[T any](d *schema.ResourceData, property string) (T, bool) { value, ok := d.GetOk(property) if !ok { var defaultValue T diff --git a/pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole_OnAllSchemasInDatabase/test.tf b/pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole_OnAllSchemasInDatabase/test.tf new file mode 100644 index 0000000000..03aa12212f --- /dev/null +++ b/pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole_OnAllSchemasInDatabase/test.tf @@ -0,0 +1,9 @@ +resource "snowflake_grant_privileges_to_database_role" "test" { + database_role_name = "\"${var.database}\".\"${var.name}\"" + privileges = var.privileges + with_grant_option = var.with_grant_option + + on_schema { + all_schemas_in_database = "\"${var.database}\"" + } +} diff --git a/pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole_basic/variables.tf b/pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole_OnAllSchemasInDatabase/variables.tf similarity index 100% rename from pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole_basic/variables.tf rename to pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole_OnAllSchemasInDatabase/variables.tf diff --git a/pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole_basic/test.tf b/pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole_OnDatabase/test.tf similarity index 58% rename from pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole_basic/test.tf rename to pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole_OnDatabase/test.tf index d6a3bc2b57..5df1cd9eea 100644 --- a/pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole_basic/test.tf +++ b/pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole_OnDatabase/test.tf @@ -1,6 +1,6 @@ resource "snowflake_grant_privileges_to_database_role" "test" { - database_role_name = "${var.database}.${var.name}" + database_role_name = "\"${var.database}\".\"${var.name}\"" privileges = var.privileges - on_database = var.database + on_database = "\"${var.database}\"" with_grant_option = var.with_grant_option } diff --git a/pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole_OnSchema/test.tf b/pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole_OnSchema/test.tf new file mode 100644 index 0000000000..a1e8f2fd46 --- /dev/null +++ b/pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole_OnSchema/test.tf @@ -0,0 +1,9 @@ +resource "snowflake_grant_privileges_to_database_role" "test" { + database_role_name = "\"${var.database}\".\"${var.name}\"" + privileges = var.privileges + with_grant_option = var.with_grant_option + + on_schema { + schema_name = "\"${var.database}\".\"${var.schema}\"" + } +} diff --git a/pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole_OnSchema/variables.tf b/pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole_OnSchema/variables.tf new file mode 100644 index 0000000000..44c69f32ee --- /dev/null +++ b/pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole_OnSchema/variables.tf @@ -0,0 +1,19 @@ +variable "name" { + type = string +} + +variable "privileges" { + type = list(string) +} + +variable "database" { + type = string +} + +variable "schema" { + type = string +} + +variable "with_grant_option" { + type = bool +} diff --git a/pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole_OnSchemaObject/test.tf b/pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole_OnSchemaObject/test.tf new file mode 100644 index 0000000000..7ca91e5d71 --- /dev/null +++ b/pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole_OnSchemaObject/test.tf @@ -0,0 +1,21 @@ +resource "snowflake_table" "test" { + database = var.database + schema = var.schema + name = var.table_name + + column { + name = "id" + type = "int" + } +} + +resource "snowflake_grant_privileges_to_database_role" "test" { + database_role_name = "\"${var.database}\".\"${var.name}\"" + privileges = var.privileges + with_grant_option = var.with_grant_option + + on_schema_object { + object_type = "TABLE" + object_name = "\"${var.database}\".\"${var.schema}\".\"${var.table_name}\"" + } +} diff --git a/pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole_OnSchemaObject/variables.tf b/pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole_OnSchemaObject/variables.tf new file mode 100644 index 0000000000..e779508540 --- /dev/null +++ b/pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole_OnSchemaObject/variables.tf @@ -0,0 +1,23 @@ +variable "name" { + type = string +} + +variable "table_name" { + type = string +} + +variable "privileges" { + type = list(string) +} + +variable "database" { + type = string +} + +variable "schema" { + type = string +} + +variable "with_grant_option" { + type = bool +} From 720bdbed67f32edecf3d6e56441ebfc63faaa333 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Cie=C5=9Blak?= Date: Fri, 22 Dec 2023 10:12:11 +0100 Subject: [PATCH 05/16] wip --- pkg/acceptance/testing.go | 7 + .../grant_privileges_to_database_role.go | 257 ++++++++++-------- ...ileges_to_database_role_acceptance_test.go | 246 ++++++++++++++++- .../variables.tf | 15 + .../test.tf | 9 + .../variables.tf | 15 + .../test.tf | 12 + .../variables.tf | 15 + .../test.tf | 12 + .../variables.tf | 15 + .../test.tf | 3 +- .../variables.tf | 0 .../all_privileges/test.tf | 5 + .../all_privileges/variables.tf | 11 + .../privileges/test.tf | 5 + .../privileges/variables.tf | 11 + 16 files changed, 519 insertions(+), 119 deletions(-) create mode 100644 pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole_OnDatabase/variables.tf create mode 100644 pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole_OnFutureSchemasInDatabase/test.tf create mode 100644 pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole_OnFutureSchemasInDatabase/variables.tf create mode 100644 pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole_OnSchemaObject_OnAll_InDatabase/test.tf create mode 100644 pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole_OnSchemaObject_OnAll_InDatabase/variables.tf create mode 100644 pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole_OnSchemaObject_OnFuture_InDatabase/test.tf create mode 100644 pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole_OnSchemaObject_OnFuture_InDatabase/variables.tf rename pkg/resources/testdata/{TestAcc_GrantPrivilegesToDatabaseRole_OnSchemaObject => TestAcc_GrantPrivilegesToDatabaseRole_OnSchemaObject_OnObject}/test.tf (88%) rename pkg/resources/testdata/{TestAcc_GrantPrivilegesToDatabaseRole_OnSchemaObject => TestAcc_GrantPrivilegesToDatabaseRole_OnSchemaObject_OnObject}/variables.tf (100%) create mode 100644 pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole_UpdatePrivileges/all_privileges/test.tf create mode 100644 pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole_UpdatePrivileges/all_privileges/variables.tf create mode 100644 pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole_UpdatePrivileges/privileges/test.tf create mode 100644 pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole_UpdatePrivileges/privileges/variables.tf diff --git a/pkg/acceptance/testing.go b/pkg/acceptance/testing.go index 0cd29e41f4..61de4b61df 100644 --- a/pkg/acceptance/testing.go +++ b/pkg/acceptance/testing.go @@ -91,3 +91,10 @@ func ConfigurationDirectory(directory string) func(config.TestStepConfigRequest) return filepath.Join("testdata", directory) } } + +// ConfigurationInnerDirectory is similar to ConfigurationSameAsStepN, but instead of indices (steps) it's named. +func ConfigurationInnerDirectory(innerDirectory string) func(config.TestStepConfigRequest) string { + return func(req config.TestStepConfigRequest) string { + return filepath.Join("testdata", req.TestName, innerDirectory) + } +} diff --git a/pkg/resources/grant_privileges_to_database_role.go b/pkg/resources/grant_privileges_to_database_role.go index 276a7b5f50..47cfde3e2b 100644 --- a/pkg/resources/grant_privileges_to_database_role.go +++ b/pkg/resources/grant_privileges_to_database_role.go @@ -52,6 +52,17 @@ var grantPrivilegesToDatabaseRoleSchema = map[string]*schema.Schema{ ForceNew: true, Description: "If specified, allows the recipient role to grant the privileges to other roles.", }, + "always_apply": { + Type: schema.TypeBool, + Optional: true, + Default: false, + Description: "If true, the resource will always produce a “plan” and on “apply” it will re-grant defined privileges. It is supposed to be used only in “grant privileges on all X’s in database / schema Y” or “grant all privileges to X” scenarios to make sure that every new object in a given database / schema is granted by the account role and every new privilege is granted to the database role. Important note: this flag is not compliant with the Terraform assumptions of the config being eventually convergent (producing an empty plan).", + AtLeastOneOf: []string{ + "all_privileges", + "on_schema.0.all_schemas_in_database", + "on_schema_object.0.all", + }, + }, "on_database": { Type: schema.TypeString, Optional: true, @@ -437,9 +448,11 @@ func CreateGrantPrivilegesToDatabaseRole(ctx context.Context, d *schema.Resource return ReadGrantPrivilegesToDatabaseRole(ctx, d, meta) } -func ReadGrantPrivilegesToDatabaseRole(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics { +func UpdateGrantPrivilegesToDatabaseRole(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics { var diags diag.Diagnostics + db := meta.(*sql.DB) + client := sdk.NewClientFromDB(db) id, err := ParseGrantPrivilegesToDatabaseRoleId(d.Id()) if err != nil { return append(diags, diag.Diagnostic{ @@ -449,64 +462,66 @@ func ReadGrantPrivilegesToDatabaseRole(ctx context.Context, d *schema.ResourceDa }) } - if id.AllPrivileges { - return append(diags, diag.Diagnostic{ - Severity: diag.Warning, - Summary: "Show with all_privileges option is skipped for now.", // TODO: Details - Detail: "", // TODO: link to the design decisions doc - }) - } - - opts, grantedOn, diagnostics := prepareShowGrantsRequest(id) - if len(diagnostics) != 0 { - return append(diags, diagnostics...) - } + if d.HasChange("privileges") { + before, after := d.GetChange("privileges") + privilegesBeforeChange := expandStringList(before.(*schema.Set).List()) + privilegesAfterChange := expandStringList(after.(*schema.Set).List()) - db := meta.(*sql.DB) - client := sdk.NewClientFromDB(db) - grants, err := client.Grants.Show(ctx, opts) - if err != nil { - return append(diags, diag.Diagnostic{ - Severity: diag.Error, - Summary: "Failed to retrieve grants", - Detail: fmt.Sprintf("Id: %s\nErr: %s", d.Id(), err.Error()), // TODO: link to the documentation (?). It should describe how the identifier looks. - }) - } + var privilegesToAdd, privilegesToRemove []string - var privileges []string + for _, privilegeBeforeChange := range privilegesBeforeChange { + if !slices.Contains(privilegesAfterChange, privilegeBeforeChange) { + privilegesToRemove = append(privilegesToRemove, privilegeBeforeChange) + } + } - // TODO: Refactor - check if correct with new conventions - // TODO: Compare privileges - for _, grant := range grants { - // TODO: What about all_privileges, right now we cannot assure that the list of privileges is correct - // Only consider privileges that are already present in the ID so we - // don't delete privileges managed by other resources. - if !slices.Contains(id.Privileges, grant.Privilege) { - continue + for _, privilegeAfterChange := range privilegesAfterChange { + if !slices.Contains(privilegesBeforeChange, privilegeAfterChange) { + privilegesToAdd = append(privilegesToAdd, privilegeAfterChange) + } } - // TODO: What about GranteeName with database roles is it fully qualified or not ? if yes, refactor GranteeName. - if id.WithGrantOption == grant.GrantOption && id.DatabaseRoleName.Name() == grant.GranteeName.Name() { - // future grants do not have grantedBy, only current grants do. If grantedby - // is an empty string it means the grant could not have been created by terraform - if opts.Future != nil && *opts.Future == true && grant.GrantedBy.Name() == "" { - continue + + grantOn := getDatabaseRoleGrantOn(&id, d) + + if len(privilegesToAdd) > 0 { + err = client.Grants.GrantPrivilegesToDatabaseRole( + ctx, + getDatabaseRolePrivileges(id.Kind, false, privilegesToAdd), + grantOn, + id.DatabaseRoleName, + new(sdk.GrantPrivilegesToDatabaseRoleOptions), + ) + if err != nil { + return append(diags, diag.Diagnostic{ + Severity: diag.Error, + Summary: "Failed to grant added privileges", + Detail: fmt.Sprintf("Id: %s\nErr: %s", d.Id(), err.Error()), // TODO: link to the documentation (?). It should describe how the identifier looks. + }) } - // 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 { - privileges = append(privileges, grant.Privilege) + } + + if len(privilegesToRemove) > 0 { + err = client.Grants.RevokePrivilegesFromDatabaseRole( + ctx, + getDatabaseRolePrivileges(id.Kind, false, privilegesToRemove), + grantOn, + id.DatabaseRoleName, + new(sdk.RevokePrivilegesFromDatabaseRoleOptions), + ) + if err != nil { + return append(diags, diag.Diagnostic{ + Severity: diag.Error, + Summary: "Failed to revoke removed privileges", + Detail: fmt.Sprintf("Id: %s\nErr: %s", d.Id(), err.Error()), // TODO: link to the documentation (?). It should describe how the identifier looks. + }) } } - } - if err := d.Set("privileges", privileges); err != nil { - return append(diags, diag.Diagnostic{ - Severity: diag.Error, - Summary: "Error setting privileges for database role", - Detail: fmt.Sprintf("Id: %s\nErr: %s", d.Id(), err.Error()), // TODO: link to the documentation (?). It should describe how the identifier looks. - }) + id.Privileges = privilegesAfterChange + d.SetId(id.String()) } - return diags + return ReadGrantPrivilegesToDatabaseRole(ctx, d, meta) } func DeleteGrantPrivilegesToDatabaseRole(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics { @@ -543,11 +558,9 @@ func DeleteGrantPrivilegesToDatabaseRole(ctx context.Context, d *schema.Resource return diags } -func UpdateGrantPrivilegesToDatabaseRole(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics { +func ReadGrantPrivilegesToDatabaseRole(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics { var diags diag.Diagnostics - db := meta.(*sql.DB) - client := sdk.NewClientFromDB(db) id, err := ParseGrantPrivilegesToDatabaseRoleId(d.Id()) if err != nil { return append(diags, diag.Diagnostic{ @@ -557,66 +570,68 @@ func UpdateGrantPrivilegesToDatabaseRole(ctx context.Context, d *schema.Resource }) } - if d.HasChange("privileges") { - before, after := d.GetChange("privileges") - privilegesBeforeChange := expandStringList(before.(*schema.Set).List()) - privilegesAfterChange := expandStringList(after.(*schema.Set).List()) - - var privilegesToAdd, privilegesToRemove []string + if id.AllPrivileges { + return append(diags, diag.Diagnostic{ + Severity: diag.Warning, + Summary: "Show with all_privileges option is skipped for now.", // TODO: Details + Detail: "", // TODO: link to the design decisions doc + }) + } - for _, privilegeBeforeChange := range privilegesBeforeChange { - if !slices.Contains(privilegesAfterChange, privilegeBeforeChange) { - privilegesToRemove = append(privilegesToRemove, privilegeBeforeChange) - } - } + opts, grantedOn, diagnostics := prepareShowGrantsRequest(id) + if len(diagnostics) != 0 { + return append(diags, diagnostics...) + } - for _, privilegeAfterChange := range privilegesAfterChange { - if !slices.Contains(privilegesBeforeChange, privilegeAfterChange) { - privilegesToAdd = append(privilegesToAdd, privilegeAfterChange) - } - } + db := meta.(*sql.DB) + client := sdk.NewClientFromDB(db) + grants, err := client.Grants.Show(ctx, opts) + if err != nil { + return append(diags, diag.Diagnostic{ + Severity: diag.Error, + Summary: "Failed to retrieve grants", + Detail: fmt.Sprintf("Id: %s\nErr: %s", d.Id(), err.Error()), // TODO: link to the documentation (?). It should describe how the identifier looks. + }) + } - grantOn := getDatabaseRoleGrantOn(&id, d) + var privileges []string - if len(privilegesToAdd) > 0 { - err = client.Grants.GrantPrivilegesToDatabaseRole( - ctx, - getDatabaseRolePrivileges(id.Kind, false, privilegesToAdd), - grantOn, - id.DatabaseRoleName, - new(sdk.GrantPrivilegesToDatabaseRoleOptions), - ) - if err != nil { - return append(diags, diag.Diagnostic{ - Severity: diag.Error, - Summary: "Failed to grant added privileges", - Detail: fmt.Sprintf("Id: %s\nErr: %s", d.Id(), err.Error()), // TODO: link to the documentation (?). It should describe how the identifier looks. - }) - } + // TODO: Refactor - check if correct with new conventions + // TODO: Compare privileges + for _, grant := range grants { + // Accept only DATABASE ROLEs + if grant.GrantTo != sdk.ObjectTypeDatabaseRole && grant.GrantedTo != sdk.ObjectTypeDatabaseRole { + continue } - - if len(privilegesToRemove) > 0 { - err = client.Grants.RevokePrivilegesFromDatabaseRole( - ctx, - getDatabaseRolePrivileges(id.Kind, false, privilegesToRemove), - grantOn, - id.DatabaseRoleName, - new(sdk.RevokePrivilegesFromDatabaseRoleOptions), - ) - if err != nil { - return append(diags, diag.Diagnostic{ - Severity: diag.Error, - Summary: "Failed to revoke removed privileges", - Detail: fmt.Sprintf("Id: %s\nErr: %s", d.Id(), err.Error()), // TODO: link to the documentation (?). It should describe how the identifier looks. - }) + // TODO: What about all_privileges, right now we cannot assure that the list of privileges is correct + // Only consider privileges that are already present in the ID so we + // don't delete privileges managed by other resources. + if !slices.Contains(id.Privileges, grant.Privilege) { + continue + } + // TODO: What about GranteeName with database roles is it fully qualified or not ? if yes, refactor GranteeName. + if id.WithGrantOption == grant.GrantOption && id.DatabaseRoleName.Name() == grant.GranteeName.Name() { + // future grants do not have grantedBy, only current grants do. If grantedby + // is an empty string it means the grant could not have been created by terraform + if (opts.Future == nil || *opts.Future == false) && 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 { + privileges = append(privileges, grant.Privilege) } } + } - id.Privileges = privilegesAfterChange - d.SetId(id.String()) + if err := d.Set("privileges", privileges); err != nil { + return append(diags, diag.Diagnostic{ + Severity: diag.Error, + Summary: "Error setting privileges for database role", + Detail: fmt.Sprintf("Id: %s\nErr: %s", d.Id(), err.Error()), // TODO: link to the documentation (?). It should describe how the identifier looks. + }) } - return ReadGrantPrivilegesToDatabaseRole(ctx, d, meta) + return diags } func prepareShowGrantsRequest(id GrantPrivilegesToDatabaseRoleId) (*sdk.ShowGrantOptions, sdk.ObjectType, diag.Diagnostics) { @@ -655,9 +670,10 @@ func prepareShowGrantsRequest(id GrantPrivilegesToDatabaseRoleId) (*sdk.ShowGran }) case OnFutureSchemasInDatabaseSchemaGrantKind: // TODO: show future on database (collisions with other on future triggers and over fetching is ok ?) - //opts.Future = sdk.Bool(true) - //opts.In = &sdk.ShowGrantsIn{ - //} + opts.Future = sdk.Bool(true) + opts.In = &sdk.ShowGrantsIn{ + Database: data.DatabaseName, + } } case OnSchemaObjectDatabaseRoleGrantKind: data := id.Data.(*OnSchemaObjectGrantData) @@ -789,16 +805,23 @@ func getDatabaseRoleGrantOn(id *GrantPrivilegesToDatabaseRoleId, d *schema.Resou onSchemaObjectGrantData := new(OnSchemaObjectGrantData) grantOnSchemaObject := new(sdk.GrantOnSchemaObject) - objectType, objectTypeOk := onSchemaObject["object_type"] - objectName, objectNameOk := onSchemaObject["object_name"] - all, allOk := onSchemaObject["all"] - future, futureOk := onSchemaObject["future"] + objectType := onSchemaObject["object_type"].(string) + objectTypeOk := len(objectType) > 0 + + objectName := onSchemaObject["object_name"].(string) + objectNameOk := len(objectName) > 0 + + all := onSchemaObject["all"].([]any) + allOk := len(all) > 0 + + future := onSchemaObject["future"].([]any) + futureOk := len(future) > 0 switch { case objectTypeOk && objectNameOk: object := &sdk.Object{ - ObjectType: sdk.ObjectType(objectType.(string)), // TODO: Should we validate it or just cast it - Name: sdk.NewSchemaObjectIdentifierFromFullyQualifiedName(objectName.(string)), + ObjectType: sdk.ObjectType(objectType), // TODO: Should we validate it or just cast it + Name: sdk.NewSchemaObjectIdentifierFromFullyQualifiedName(objectName), } onSchemaObjectGrantData.Kind = OnObjectSchemaObjectGrantKind @@ -806,10 +829,10 @@ func getDatabaseRoleGrantOn(id *GrantPrivilegesToDatabaseRoleId, d *schema.Resou grantOnSchemaObject.SchemaObject = object case allOk: onSchemaObjectGrantData.Kind = OnAllSchemaObjectGrantKind - grantOnSchemaObject.All = getGrantOnSchemaObjectIn(onSchemaObjectGrantData, all.([]any)[0].(map[string]any)) + grantOnSchemaObject.All = getGrantOnSchemaObjectIn(onSchemaObjectGrantData, all[0].(map[string]any)) case futureOk: onSchemaObjectGrantData.Kind = OnFutureSchemaObjectGrantKind - grantOnSchemaObject.Future = getGrantOnSchemaObjectIn(onSchemaObjectGrantData, future.([]any)[0].(map[string]any)) + grantOnSchemaObject.Future = getGrantOnSchemaObjectIn(onSchemaObjectGrantData, future[0].(map[string]any)) } id.Data = onSchemaObjectGrantData @@ -828,16 +851,16 @@ func getGrantOnSchemaObjectIn(onSchemaObjectGrantData *OnSchemaObjectGrantData, PluralObjectType: pluralObjectType, } - if inDatabase, inDatabaseOk := m["in_database"]; inDatabaseOk { - databaseId := sdk.Pointer(sdk.NewAccountObjectIdentifierFromFullyQualifiedName(inDatabase.(string))) + if inDatabase, ok := m["in_database"].(string); ok && len(inDatabase) > 0 { + databaseId := sdk.Pointer(sdk.NewAccountObjectIdentifierFromFullyQualifiedName(inDatabase)) bulkOperationGrantData.Kind = InDatabaseBulkOperationGrantKind bulkOperationGrantData.Database = databaseId grantOnSchemaObjectIn.InDatabase = databaseId } - if inSchema, inSchemaOk := m["in_schema"]; inSchemaOk { - schemaId := sdk.Pointer(sdk.NewDatabaseObjectIdentifierFromFullyQualifiedName(inSchema.(string))) + if inSchema, ok := m["in_schema"].(string); ok && len(inSchema) > 0 { + schemaId := sdk.Pointer(sdk.NewDatabaseObjectIdentifierFromFullyQualifiedName(inSchema)) bulkOperationGrantData.Kind = InSchemaBulkOperationGrantKind bulkOperationGrantData.Schema = schemaId diff --git a/pkg/resources/grant_privileges_to_database_role_acceptance_test.go b/pkg/resources/grant_privileges_to_database_role_acceptance_test.go index 5ffefd23ef..ca16670368 100644 --- a/pkg/resources/grant_privileges_to_database_role_acceptance_test.go +++ b/pkg/resources/grant_privileges_to_database_role_acceptance_test.go @@ -142,7 +142,46 @@ func TestAcc_GrantPrivilegesToDatabaseRole_OnAllSchemasInDatabase(t *testing.T) }) } -func TestAcc_GrantPrivilegesToDatabaseRole_OnSchemaObject(t *testing.T) { +func TestAcc_GrantPrivilegesToDatabaseRole_OnFutureSchemasInDatabase(t *testing.T) { + name := "test_database_role_name" + configVariables := config.Variables{ + "name": config.StringVariable(name), + "privileges": config.ListVariable( + config.StringVariable(string(sdk.SchemaPrivilegeCreateTable)), + config.StringVariable(string(sdk.SchemaPrivilegeModify)), + ), + "database": config.StringVariable(acc.TestDatabaseName), + "with_grant_option": config.BoolVariable(false), + } + resourceName := "snowflake_grant_privileges_to_database_role.test" + + resource.Test(t, resource.TestCase{ + ProtoV6ProviderFactories: acc.TestAccProtoV6ProviderFactories, + PreCheck: func() { acc.TestAccPreCheck(t) }, + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.RequireAbove(tfversion.Version1_5_0), + }, + CheckDestroy: testAccCheckDatabaseRolePrivilegesRevoked, + Steps: []resource.TestStep{ + { + PreConfig: func() { createDatabaseRoleOutsideTerraform(t, name) }, + ConfigDirectory: config.TestNameDirectory(), + ConfigVariables: configVariables, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "database_role_name", sdk.NewDatabaseObjectIdentifier(acc.TestDatabaseName, name).FullyQualifiedName()), + resource.TestCheckResourceAttr(resourceName, "privileges.#", "2"), + resource.TestCheckResourceAttr(resourceName, "privileges.0", string(sdk.SchemaPrivilegeCreateTable)), + resource.TestCheckResourceAttr(resourceName, "privileges.1", string(sdk.SchemaPrivilegeModify)), + resource.TestCheckResourceAttr(resourceName, "on_schema.#", "1"), + resource.TestCheckResourceAttr(resourceName, "on_schema.0.future_schemas_in_database", sdk.NewAccountObjectIdentifier(acc.TestDatabaseName).FullyQualifiedName()), + resource.TestCheckResourceAttr(resourceName, "with_grant_option", "false"), + ), + }, + }, + }) +} + +func TestAcc_GrantPrivilegesToDatabaseRole_OnSchemaObject_OnObject(t *testing.T) { name := "test_database_role_name" tableName := "test_database_role_table_name" configVariables := config.Variables{ @@ -185,6 +224,211 @@ func TestAcc_GrantPrivilegesToDatabaseRole_OnSchemaObject(t *testing.T) { }) } +func TestAcc_GrantPrivilegesToDatabaseRole_OnSchemaObject_OnAll_InDatabase(t *testing.T) { + name := "test_database_role_name" + configVariables := config.Variables{ + "name": config.StringVariable(name), + "privileges": config.ListVariable( + config.StringVariable(string(sdk.SchemaObjectPrivilegeInsert)), + config.StringVariable(string(sdk.SchemaObjectPrivilegeUpdate)), + ), + "database": config.StringVariable(acc.TestDatabaseName), + "with_grant_option": config.BoolVariable(false), + } + resourceName := "snowflake_grant_privileges_to_database_role.test" + + resource.Test(t, resource.TestCase{ + ProtoV6ProviderFactories: acc.TestAccProtoV6ProviderFactories, + PreCheck: func() { acc.TestAccPreCheck(t) }, + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.RequireAbove(tfversion.Version1_5_0), + }, + CheckDestroy: testAccCheckDatabaseRolePrivilegesRevoked, + Steps: []resource.TestStep{ + { + PreConfig: func() { createDatabaseRoleOutsideTerraform(t, name) }, + ConfigDirectory: config.TestNameDirectory(), + ConfigVariables: configVariables, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "database_role_name", sdk.NewDatabaseObjectIdentifier(acc.TestDatabaseName, name).FullyQualifiedName()), + resource.TestCheckResourceAttr(resourceName, "privileges.#", "2"), + resource.TestCheckResourceAttr(resourceName, "privileges.0", string(sdk.SchemaObjectPrivilegeInsert)), + resource.TestCheckResourceAttr(resourceName, "privileges.1", string(sdk.SchemaObjectPrivilegeUpdate)), + resource.TestCheckResourceAttr(resourceName, "on_schema_object.#", "1"), + resource.TestCheckResourceAttr(resourceName, "on_schema_object.0.all.#", "1"), + resource.TestCheckResourceAttr(resourceName, "on_schema_object.0.all.0.object_type_plural", string(sdk.PluralObjectTypeTables)), + resource.TestCheckResourceAttr(resourceName, "on_schema_object.0.all.0.in_database", sdk.NewAccountObjectIdentifier(acc.TestDatabaseName).FullyQualifiedName()), + resource.TestCheckResourceAttr(resourceName, "with_grant_option", "false"), + ), + }, + }, + }) +} + +func TestAcc_GrantPrivilegesToDatabaseRole_OnSchemaObject_OnFuture_InDatabase(t *testing.T) { + name := "test_database_role_name" + configVariables := config.Variables{ + "name": config.StringVariable(name), + "privileges": config.ListVariable( + config.StringVariable(string(sdk.SchemaObjectPrivilegeInsert)), + config.StringVariable(string(sdk.SchemaObjectPrivilegeUpdate)), + ), + "database": config.StringVariable(acc.TestDatabaseName), + "with_grant_option": config.BoolVariable(false), + } + resourceName := "snowflake_grant_privileges_to_database_role.test" + + resource.Test(t, resource.TestCase{ + ProtoV6ProviderFactories: acc.TestAccProtoV6ProviderFactories, + PreCheck: func() { acc.TestAccPreCheck(t) }, + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.RequireAbove(tfversion.Version1_5_0), + }, + CheckDestroy: testAccCheckDatabaseRolePrivilegesRevoked, + Steps: []resource.TestStep{ + { + PreConfig: func() { createDatabaseRoleOutsideTerraform(t, name) }, + ConfigDirectory: config.TestNameDirectory(), + ConfigVariables: configVariables, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "database_role_name", sdk.NewDatabaseObjectIdentifier(acc.TestDatabaseName, name).FullyQualifiedName()), + resource.TestCheckResourceAttr(resourceName, "privileges.#", "2"), + resource.TestCheckResourceAttr(resourceName, "privileges.0", string(sdk.SchemaObjectPrivilegeInsert)), + resource.TestCheckResourceAttr(resourceName, "privileges.1", string(sdk.SchemaObjectPrivilegeUpdate)), + resource.TestCheckResourceAttr(resourceName, "on_schema_object.#", "1"), + resource.TestCheckResourceAttr(resourceName, "on_schema_object.0.future.#", "1"), + resource.TestCheckResourceAttr(resourceName, "on_schema_object.0.future.0.object_type_plural", string(sdk.PluralObjectTypeTables)), + resource.TestCheckResourceAttr(resourceName, "on_schema_object.0.future.0.in_database", sdk.NewAccountObjectIdentifier(acc.TestDatabaseName).FullyQualifiedName()), + resource.TestCheckResourceAttr(resourceName, "with_grant_option", "false"), + ), + }, + }, + }) +} + +func TestAcc_GrantPrivilegesToDatabaseRole_UpdatePrivileges(t *testing.T) { + name := "test_database_role_name" + configVariables := func(allPrivileges bool, privileges []sdk.AccountObjectPrivilege) config.Variables { + configVariables := config.Variables{ + "name": config.StringVariable(name), + "database": config.StringVariable(acc.TestDatabaseName), + } + if allPrivileges { + configVariables["all_privileges"] = config.BoolVariable(allPrivileges) + } + if len(privileges) > 0 { + configPrivileges := make([]config.Variable, len(privileges)) + for i, privilege := range privileges { + configPrivileges[i] = config.StringVariable(string(privilege)) + } + configVariables["privileges"] = config.ListVariable(configPrivileges...) + } + return configVariables + } + resourceName := "snowflake_grant_privileges_to_database_role.test" + + resource.Test(t, resource.TestCase{ + ProtoV6ProviderFactories: acc.TestAccProtoV6ProviderFactories, + PreCheck: func() { acc.TestAccPreCheck(t) }, + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.RequireAbove(tfversion.Version1_5_0), + }, + CheckDestroy: testAccCheckDatabaseRolePrivilegesRevoked, + Steps: []resource.TestStep{ + { + PreConfig: func() { createDatabaseRoleOutsideTerraform(t, name) }, + ConfigDirectory: acc.ConfigurationInnerDirectory("privileges"), + ConfigVariables: configVariables(false, []sdk.AccountObjectPrivilege{ + sdk.AccountObjectPrivilegeCreateSchema, + sdk.AccountObjectPrivilegeModify, + }), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "all_privileges", "false"), + resource.TestCheckResourceAttr(resourceName, "privileges.#", "2"), + resource.TestCheckResourceAttr(resourceName, "privileges.0", string(sdk.AccountObjectPrivilegeCreateSchema)), + resource.TestCheckResourceAttr(resourceName, "privileges.1", string(sdk.AccountObjectPrivilegeModify)), + ), + }, + { + ConfigDirectory: acc.ConfigurationInnerDirectory("privileges"), + ConfigVariables: configVariables(false, []sdk.AccountObjectPrivilege{ + sdk.AccountObjectPrivilegeCreateSchema, + sdk.AccountObjectPrivilegeMonitor, + sdk.AccountObjectPrivilegeUsage, + }), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "all_privileges", "false"), + resource.TestCheckResourceAttr(resourceName, "privileges.#", "3"), + resource.TestCheckResourceAttr(resourceName, "privileges.0", string(sdk.AccountObjectPrivilegeCreateSchema)), + resource.TestCheckResourceAttr(resourceName, "privileges.1", string(sdk.AccountObjectPrivilegeMonitor)), + resource.TestCheckResourceAttr(resourceName, "privileges.2", string(sdk.AccountObjectPrivilegeUsage)), + ), + }, + { + ConfigDirectory: acc.ConfigurationInnerDirectory("all_privileges"), + ConfigVariables: configVariables(true, []sdk.AccountObjectPrivilege{}), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "all_privileges", "true"), + resource.TestCheckResourceAttr(resourceName, "privileges.#", "0"), + ), + }, + { + ConfigDirectory: acc.ConfigurationInnerDirectory("privileges"), + ConfigVariables: configVariables(false, []sdk.AccountObjectPrivilege{ + sdk.AccountObjectPrivilegeModify, + sdk.AccountObjectPrivilegeMonitor, + }), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "all_privileges", "false"), + resource.TestCheckResourceAttr(resourceName, "privileges.#", "2"), + resource.TestCheckResourceAttr(resourceName, "privileges.0", string(sdk.AccountObjectPrivilegeModify)), + resource.TestCheckResourceAttr(resourceName, "privileges.1", string(sdk.AccountObjectPrivilegeMonitor)), + ), + }, + }, + }) +} + +func TestAcc_GrantPrivilegesToDatabaseRole_AlwaysApply(t *testing.T) { + name := "test_database_role_name" + configVariables := config.Variables{ + "name": config.StringVariable(name), + "privileges": config.ListVariable( + config.StringVariable(string(sdk.AccountObjectPrivilegeCreateSchema)), + config.StringVariable(string(sdk.AccountObjectPrivilegeModify)), + config.StringVariable(string(sdk.AccountObjectPrivilegeUsage)), + ), + "database": config.StringVariable(acc.TestDatabaseName), + "with_grant_option": config.BoolVariable(true), + } + resourceName := "snowflake_grant_privileges_to_database_role.test" + + resource.Test(t, resource.TestCase{ + ProtoV6ProviderFactories: acc.TestAccProtoV6ProviderFactories, + PreCheck: func() { acc.TestAccPreCheck(t) }, + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.RequireAbove(tfversion.Version1_5_0), + }, + CheckDestroy: testAccCheckDatabaseRolePrivilegesRevoked, + Steps: []resource.TestStep{ + { + PreConfig: func() { createDatabaseRoleOutsideTerraform(t, name) }, + ConfigDirectory: config.TestNameDirectory(), + ConfigVariables: configVariables, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "database_role_name", sdk.NewDatabaseObjectIdentifier(acc.TestDatabaseName, name).FullyQualifiedName()), + resource.TestCheckResourceAttr(resourceName, "privileges.#", "3"), + resource.TestCheckResourceAttr(resourceName, "privileges.0", string(sdk.AccountObjectPrivilegeCreateSchema)), + resource.TestCheckResourceAttr(resourceName, "privileges.1", string(sdk.AccountObjectPrivilegeModify)), + resource.TestCheckResourceAttr(resourceName, "privileges.2", string(sdk.AccountObjectPrivilegeUsage)), + resource.TestCheckResourceAttr(resourceName, "on_database", sdk.NewAccountObjectIdentifier(acc.TestDatabaseName).FullyQualifiedName()), + resource.TestCheckResourceAttr(resourceName, "with_grant_option", "true"), + ), + }, + }, + }) +} + func createDatabaseRoleOutsideTerraform(t *testing.T, name string) { t.Helper() client, err := sdk.NewDefaultClient() diff --git a/pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole_OnDatabase/variables.tf b/pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole_OnDatabase/variables.tf new file mode 100644 index 0000000000..0e22e903d7 --- /dev/null +++ b/pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole_OnDatabase/variables.tf @@ -0,0 +1,15 @@ +variable "name" { + type = string +} + +variable "privileges" { + type = list(string) +} + +variable "database" { + type = string +} + +variable "with_grant_option" { + type = bool +} diff --git a/pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole_OnFutureSchemasInDatabase/test.tf b/pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole_OnFutureSchemasInDatabase/test.tf new file mode 100644 index 0000000000..321246bba1 --- /dev/null +++ b/pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole_OnFutureSchemasInDatabase/test.tf @@ -0,0 +1,9 @@ +resource "snowflake_grant_privileges_to_database_role" "test" { + database_role_name = "\"${var.database}\".\"${var.name}\"" + privileges = var.privileges + with_grant_option = var.with_grant_option + + on_schema { + future_schemas_in_database = "\"${var.database}\"" + } +} diff --git a/pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole_OnFutureSchemasInDatabase/variables.tf b/pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole_OnFutureSchemasInDatabase/variables.tf new file mode 100644 index 0000000000..0e22e903d7 --- /dev/null +++ b/pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole_OnFutureSchemasInDatabase/variables.tf @@ -0,0 +1,15 @@ +variable "name" { + type = string +} + +variable "privileges" { + type = list(string) +} + +variable "database" { + type = string +} + +variable "with_grant_option" { + type = bool +} diff --git a/pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole_OnSchemaObject_OnAll_InDatabase/test.tf b/pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole_OnSchemaObject_OnAll_InDatabase/test.tf new file mode 100644 index 0000000000..b927be87f2 --- /dev/null +++ b/pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole_OnSchemaObject_OnAll_InDatabase/test.tf @@ -0,0 +1,12 @@ +resource "snowflake_grant_privileges_to_database_role" "test" { + database_role_name = "\"${var.database}\".\"${var.name}\"" + privileges = var.privileges + with_grant_option = var.with_grant_option + + on_schema_object { + all { + object_type_plural = "TABLES" + in_database = "\"${var.database}\"" + } + } +} diff --git a/pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole_OnSchemaObject_OnAll_InDatabase/variables.tf b/pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole_OnSchemaObject_OnAll_InDatabase/variables.tf new file mode 100644 index 0000000000..0e22e903d7 --- /dev/null +++ b/pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole_OnSchemaObject_OnAll_InDatabase/variables.tf @@ -0,0 +1,15 @@ +variable "name" { + type = string +} + +variable "privileges" { + type = list(string) +} + +variable "database" { + type = string +} + +variable "with_grant_option" { + type = bool +} diff --git a/pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole_OnSchemaObject_OnFuture_InDatabase/test.tf b/pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole_OnSchemaObject_OnFuture_InDatabase/test.tf new file mode 100644 index 0000000000..3345531127 --- /dev/null +++ b/pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole_OnSchemaObject_OnFuture_InDatabase/test.tf @@ -0,0 +1,12 @@ +resource "snowflake_grant_privileges_to_database_role" "test" { + database_role_name = "\"${var.database}\".\"${var.name}\"" + privileges = var.privileges + with_grant_option = var.with_grant_option + + on_schema_object { + future { + object_type_plural = "TABLES" + in_database = "\"${var.database}\"" + } + } +} diff --git a/pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole_OnSchemaObject_OnFuture_InDatabase/variables.tf b/pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole_OnSchemaObject_OnFuture_InDatabase/variables.tf new file mode 100644 index 0000000000..0e22e903d7 --- /dev/null +++ b/pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole_OnSchemaObject_OnFuture_InDatabase/variables.tf @@ -0,0 +1,15 @@ +variable "name" { + type = string +} + +variable "privileges" { + type = list(string) +} + +variable "database" { + type = string +} + +variable "with_grant_option" { + type = bool +} diff --git a/pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole_OnSchemaObject/test.tf b/pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole_OnSchemaObject_OnObject/test.tf similarity index 88% rename from pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole_OnSchemaObject/test.tf rename to pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole_OnSchemaObject_OnObject/test.tf index 7ca91e5d71..19cd9a72bf 100644 --- a/pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole_OnSchemaObject/test.tf +++ b/pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole_OnSchemaObject_OnObject/test.tf @@ -5,11 +5,12 @@ resource "snowflake_table" "test" { column { name = "id" - type = "int" + type = "NUMBER(38,0)" } } resource "snowflake_grant_privileges_to_database_role" "test" { + depends_on = [snowflake_table.test] database_role_name = "\"${var.database}\".\"${var.name}\"" privileges = var.privileges with_grant_option = var.with_grant_option diff --git a/pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole_OnSchemaObject/variables.tf b/pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole_OnSchemaObject_OnObject/variables.tf similarity index 100% rename from pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole_OnSchemaObject/variables.tf rename to pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole_OnSchemaObject_OnObject/variables.tf diff --git a/pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole_UpdatePrivileges/all_privileges/test.tf b/pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole_UpdatePrivileges/all_privileges/test.tf new file mode 100644 index 0000000000..6613c3bcb3 --- /dev/null +++ b/pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole_UpdatePrivileges/all_privileges/test.tf @@ -0,0 +1,5 @@ +resource "snowflake_grant_privileges_to_database_role" "test" { + database_role_name = "\"${var.database}\".\"${var.name}\"" + all_privileges = var.all_privileges + on_database = "\"${var.database}\"" +} diff --git a/pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole_UpdatePrivileges/all_privileges/variables.tf b/pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole_UpdatePrivileges/all_privileges/variables.tf new file mode 100644 index 0000000000..cb4441bfce --- /dev/null +++ b/pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole_UpdatePrivileges/all_privileges/variables.tf @@ -0,0 +1,11 @@ +variable "name" { + type = string +} + +variable "all_privileges" { + type = bool +} + +variable "database" { + type = string +} diff --git a/pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole_UpdatePrivileges/privileges/test.tf b/pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole_UpdatePrivileges/privileges/test.tf new file mode 100644 index 0000000000..22ebfef3aa --- /dev/null +++ b/pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole_UpdatePrivileges/privileges/test.tf @@ -0,0 +1,5 @@ +resource "snowflake_grant_privileges_to_database_role" "test" { + database_role_name = "\"${var.database}\".\"${var.name}\"" + privileges = var.privileges + on_database = "\"${var.database}\"" +} diff --git a/pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole_UpdatePrivileges/privileges/variables.tf b/pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole_UpdatePrivileges/privileges/variables.tf new file mode 100644 index 0000000000..27eccc7883 --- /dev/null +++ b/pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole_UpdatePrivileges/privileges/variables.tf @@ -0,0 +1,11 @@ +variable "name" { + type = string +} + +variable "privileges" { + type = list(string) +} + +variable "database" { + type = string +} From 2b8b244278fd02972e1b42d35babe680e19f46c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Cie=C5=9Blak?= Date: Fri, 22 Dec 2023 16:09:58 +0100 Subject: [PATCH 06/16] wip --- pkg/acceptance/testing.go | 3 +- .../grant_privileges_to_database_role.go | 336 +++++++++++------- ...ileges_to_database_role_acceptance_test.go | 121 +++++-- ..._privileges_to_database_role_identifier.go | 51 +-- ...ileges_to_database_role_identifier_test.go | 68 ++-- .../test.tf | 6 + .../variables.tf | 15 + 7 files changed, 402 insertions(+), 198 deletions(-) create mode 100644 pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole_AlwaysApply/test.tf create mode 100644 pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole_AlwaysApply/variables.tf diff --git a/pkg/acceptance/testing.go b/pkg/acceptance/testing.go index 61de4b61df..d6725ca491 100644 --- a/pkg/acceptance/testing.go +++ b/pkg/acceptance/testing.go @@ -92,7 +92,8 @@ func ConfigurationDirectory(directory string) func(config.TestStepConfigRequest) } } -// ConfigurationInnerDirectory is similar to ConfigurationSameAsStepN, but instead of indices (steps) it's named. +// ConfigurationInnerDirectory is similar to ConfigurationSameAsStepN, but instead of index-based directories, +// you can choose a particular one by name. func ConfigurationInnerDirectory(innerDirectory string) func(config.TestStepConfigRequest) string { return func(req config.TestStepConfigRequest) string { return filepath.Join("testdata", req.TestName, innerDirectory) diff --git a/pkg/resources/grant_privileges_to_database_role.go b/pkg/resources/grant_privileges_to_database_role.go index 47cfde3e2b..5cbe46c1cd 100644 --- a/pkg/resources/grant_privileges_to_database_role.go +++ b/pkg/resources/grant_privileges_to_database_role.go @@ -5,6 +5,8 @@ import ( "database/sql" "fmt" "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk" + "github.com/hashicorp/go-cty/cty" + "github.com/hashicorp/go-uuid" "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" @@ -13,7 +15,13 @@ import ( "strings" ) -// TODO: Add always_apply +// TODO: +// - resolve todos +// - remove logs +// - make error messages consistent +// - write documentation (document on_all, always_apply etc.) +// - test import + var grantPrivilegesToDatabaseRoleSchema = map[string]*schema.Schema{ "database_role_name": { Type: schema.TypeString, @@ -26,13 +34,13 @@ var grantPrivilegesToDatabaseRoleSchema = map[string]*schema.Schema{ Type: schema.TypeSet, Optional: true, Description: "The privileges to grant on the database role.", - // TODO: Not yet supported by Terraform - ValidateDiagFunc: doesNotContainOwnershipGrant(), ExactlyOneOf: []string{ "privileges", "all_privileges", }, Elem: &schema.Schema{ - Type: schema.TypeString, + Type: schema.TypeString, + ValidateDiagFunc: isNotOwnershipGrant(), }, }, "all_privileges": { @@ -57,11 +65,19 @@ var grantPrivilegesToDatabaseRoleSchema = map[string]*schema.Schema{ Optional: true, Default: false, Description: "If true, the resource will always produce a “plan” and on “apply” it will re-grant defined privileges. It is supposed to be used only in “grant privileges on all X’s in database / schema Y” or “grant all privileges to X” scenarios to make sure that every new object in a given database / schema is granted by the account role and every new privilege is granted to the database role. Important note: this flag is not compliant with the Terraform assumptions of the config being eventually convergent (producing an empty plan).", - AtLeastOneOf: []string{ - "all_privileges", - "on_schema.0.all_schemas_in_database", - "on_schema_object.0.all", - }, + // TODO: conflicts with + //AtLeastOneOf: []string{ + // "all_privileges", + // "on_schema.0.all_schemas_in_database", + // "on_schema_object.0.all", + //}, + }, + "always_apply_trigger": { + Type: schema.TypeString, + Optional: true, + Default: "", + // TODO: Fix desc + Description: "This field should not be set and its main purpose is to achieve the functionality described by always_apply field. This is value will be flipped to the opposite value on every terraform apply, thus creating a new plan that will re-apply grants.", }, "on_database": { Type: schema.TypeString, @@ -282,25 +298,21 @@ var grantPrivilegesOnDatabaseRoleBulkOperationSchema = map[string]*schema.Schema }, } -//func doesNotContainOwnershipGrant() func(value any, path cty.Path) diag.Diagnostics { -// return func(value any, path cty.Path) diag.Diagnostics { -// var diags diag.Diagnostics -// if privileges, ok := value.([]string); ok { -// if slices.ContainsFunc(privileges, func(privilege string) bool { -// return strings.ToUpper(privilege) == "OWNERSHIP" -// }) { -// diags = append(diags, diag.Diagnostic{ -// Severity: diag.Error, -// Summary: "Unsupported privilege type 'OWNERSHIP'.", -// // TODO: Change when a new resource for granting ownership will be available -// Detail: "Granting ownership is only allowed in dedicated resources (snowflake_user_ownership_grant, snowflake_role_ownership_grant)", -// AttributePath: nil, -// }) -// } -// } -// return diags -// } -//} +func isNotOwnershipGrant() func(value any, path cty.Path) diag.Diagnostics { + return func(value any, path cty.Path) diag.Diagnostics { + var diags diag.Diagnostics + if privilege, ok := value.(string); ok && strings.ToUpper(privilege) == "OWNERSHIP" { + diags = append(diags, diag.Diagnostic{ + Severity: diag.Error, + Summary: "Unsupported privilege 'OWNERSHIP'", + // TODO: Change when a new resource for granting ownership will be available + Detail: "Granting ownership is only allowed in dedicated resources (snowflake_user_ownership_grant, snowflake_role_ownership_grant)", + AttributePath: nil, + }) + } + return diags + } +} func GrantPrivilegesToDatabaseRole() *schema.Resource { return &schema.Resource{ @@ -327,13 +339,16 @@ func ImportGrantPrivilegesToDatabaseRole(ctx context.Context, d *schema.Resource if err := d.Set("with_grant_option", id.WithGrantOption); err != nil { return nil, err } + if err := d.Set("always_apply", id.AlwaysApply); err != nil { + return nil, err + } if err := d.Set("all_privileges", id.AllPrivileges); err != nil { return nil, err } if err := d.Set("privileges", id.Privileges); err != nil { return nil, err } - // TODO: Error when contains ownership as privilege + switch id.Kind { case OnDatabaseDatabaseRoleGrantKind: if err := d.Set("on_database", id.Data.(*OnDatabaseGrantData).DatabaseName.FullyQualifiedName()); err != nil { @@ -403,36 +418,14 @@ func CreateGrantPrivilegesToDatabaseRole(ctx context.Context, d *schema.Resource db := meta.(*sql.DB) client := sdk.NewClientFromDB(db) - id := new(GrantPrivilegesToDatabaseRoleId) - databaseRoleName := sdk.NewDatabaseObjectIdentifierFromFullyQualifiedName(d.Get("database_role_name").(string)) - id.DatabaseRoleName = databaseRoleName - id.AllPrivileges = d.Get("all_privileges").(bool) - if p, ok := d.GetOk("privileges"); ok { - privileges := expandStringList(p.(*schema.Set).List()) - if slices.ContainsFunc(privileges, func(s string) bool { - return strings.ToUpper(s) == "OWNERSHIP" - }) { - return append(diags, diag.Diagnostic{ - Severity: diag.Error, - Summary: "OWNERSHIP in the list of privileges", - Detail: fmt.Sprintf("Id: %s\nMessage: TODO", id.DatabaseRoleName), // TODO: Message and link to ownership resource - }) - } - id.Privileges = privileges - } - withGrantOption := d.Get("with_grant_option").(bool) - id.WithGrantOption = withGrantOption - - on := getDatabaseRoleGrantOn(id, d) // TODO: It shouldn't modify - priv := getDatabaseRolePrivileges(id.Kind, id.AllPrivileges, id.Privileges) - log.Println("GRANTING PRIV:", priv) + id := createGrantPrivilegesToDatabaseRoleIdFromSchema(d) err := client.Grants.GrantPrivilegesToDatabaseRole( ctx, - priv, - on, - databaseRoleName, + getDatabaseRolePrivilegesFromSchema(d), + getDatabaseRoleGrantOn(d), + sdk.NewDatabaseObjectIdentifierFromFullyQualifiedName(d.Get("database_role_name").(string)), &sdk.GrantPrivilegesToDatabaseRoleOptions{ - WithGrantOption: &withGrantOption, + WithGrantOption: sdk.Bool(d.Get("with_grant_option").(bool)), }, ) if err != nil { @@ -481,12 +474,19 @@ func UpdateGrantPrivilegesToDatabaseRole(ctx context.Context, d *schema.Resource } } - grantOn := getDatabaseRoleGrantOn(&id, d) + log.Println("PRIVILEGES TO CHANGE:", privilegesToAdd, privilegesToRemove) + grantOn := getDatabaseRoleGrantOn(d) if len(privilegesToAdd) > 0 { err = client.Grants.GrantPrivilegesToDatabaseRole( ctx, - getDatabaseRolePrivileges(id.Kind, false, privilegesToAdd), + getDatabaseRolePrivileges( + false, + privilegesToAdd, + id.Kind == OnDatabaseDatabaseRoleGrantKind, + id.Kind == OnSchemaDatabaseRoleGrantKind, + id.Kind == OnSchemaObjectDatabaseRoleGrantKind, + ), grantOn, id.DatabaseRoleName, new(sdk.GrantPrivilegesToDatabaseRoleOptions), @@ -503,7 +503,13 @@ func UpdateGrantPrivilegesToDatabaseRole(ctx context.Context, d *schema.Resource if len(privilegesToRemove) > 0 { err = client.Grants.RevokePrivilegesFromDatabaseRole( ctx, - getDatabaseRolePrivileges(id.Kind, false, privilegesToRemove), + getDatabaseRolePrivileges( + false, + privilegesToRemove, + id.Kind == OnDatabaseDatabaseRoleGrantKind, + id.Kind == OnSchemaDatabaseRoleGrantKind, + id.Kind == OnSchemaObjectDatabaseRoleGrantKind, + ), grantOn, id.DatabaseRoleName, new(sdk.RevokePrivilegesFromDatabaseRoleOptions), @@ -518,9 +524,36 @@ func UpdateGrantPrivilegesToDatabaseRole(ctx context.Context, d *schema.Resource } id.Privileges = privilegesAfterChange - d.SetId(id.String()) } + log.Println("UPDATE always_apply has change:", d.HasChange("always_apply")) + if d.HasChange("always_apply") { + log.Println("UPDATE always_apply get:", d.Get("always_apply")) + id.AlwaysApply = d.Get("always_apply").(bool) + } + + if id.AlwaysApply { + log.Println("UPDATE applying grants") + err := client.Grants.GrantPrivilegesToDatabaseRole( + ctx, + getDatabaseRolePrivilegesFromSchema(d), + getDatabaseRoleGrantOn(d), + id.DatabaseRoleName, + &sdk.GrantPrivilegesToDatabaseRoleOptions{ + WithGrantOption: &id.WithGrantOption, + }, + ) + if err != nil { + return append(diags, diag.Diagnostic{ + Severity: diag.Error, + Summary: "An error occurred when granting privileges to database role", + Detail: fmt.Sprintf("Id: %s\nError: %s", id.DatabaseRoleName, err.Error()), + }) + } + } + + d.SetId(id.String()) + return ReadGrantPrivilegesToDatabaseRole(ctx, d, meta) } @@ -540,8 +573,8 @@ func DeleteGrantPrivilegesToDatabaseRole(ctx context.Context, d *schema.Resource err = client.Grants.RevokePrivilegesFromDatabaseRole( ctx, - getDatabaseRolePrivileges(id.Kind, id.AllPrivileges, id.Privileges), - getDatabaseRoleGrantOn(&id, d), + getDatabaseRolePrivilegesFromSchema(d), + getDatabaseRoleGrantOn(d), id.DatabaseRoleName, &sdk.RevokePrivilegesFromDatabaseRoleOptions{}, ) @@ -559,6 +592,7 @@ func DeleteGrantPrivilegesToDatabaseRole(ctx context.Context, d *schema.Resource } func ReadGrantPrivilegesToDatabaseRole(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics { + log.Println("READ BEGINS") var diags diag.Diagnostics id, err := ParseGrantPrivilegesToDatabaseRoleId(d.Id()) @@ -570,6 +604,20 @@ func ReadGrantPrivilegesToDatabaseRole(ctx context.Context, d *schema.ResourceDa }) } + log.Println("READ id.AlwaysApply:", id.AlwaysApply) + if id.AlwaysApply { + // Change the value of always_apply_trigger to produce a plan + triggerId, _ := uuid.GenerateUUID() // TODO handle error + log.Printf("READ applying triggerId: %s, was: %s\n", triggerId, d.Get("always_apply_trigger")) + if err := d.Set("always_apply_trigger", triggerId); err != nil { + return append(diags, diag.Diagnostic{ + Severity: diag.Error, + Summary: "Error setting always_apply_trigger for database role", + Detail: fmt.Sprintf("Id: %s\nErr: %s", d.Id(), err.Error()), // TODO: link to the documentation (?). It should describe how the identifier looks. + }) + } + } + if id.AllPrivileges { return append(diags, diag.Diagnostic{ Severity: diag.Warning, @@ -599,6 +647,7 @@ func ReadGrantPrivilegesToDatabaseRole(ctx context.Context, d *schema.ResourceDa // TODO: Refactor - check if correct with new conventions // TODO: Compare privileges for _, grant := range grants { + log.Println("GRANT:", grant) // Accept only DATABASE ROLEs if grant.GrantTo != sdk.ObjectTypeDatabaseRole && grant.GrantedTo != sdk.ObjectTypeDatabaseRole { continue @@ -623,6 +672,7 @@ func ReadGrantPrivilegesToDatabaseRole(ctx context.Context, d *schema.ResourceDa } } + log.Println("PRIVILEGES:", id.Privileges, expandStringList(d.Get("privileges").(*schema.Set).List()), privileges) if err := d.Set("privileges", privileges); err != nil { return append(diags, diag.Diagnostic{ Severity: diag.Error, @@ -711,7 +761,21 @@ func prepareShowGrantsRequest(id GrantPrivilegesToDatabaseRoleId) (*sdk.ShowGran return opts, grantedOn, diags } -func getDatabaseRolePrivileges(kind DatabaseRoleGrantKind, allPrivileges bool, privileges []string) *sdk.DatabaseRoleGrantPrivileges { +func getDatabaseRolePrivilegesFromSchema(d *schema.ResourceData) *sdk.DatabaseRoleGrantPrivileges { + _, onDatabaseOk := d.GetOk("on_database") + _, onSchemaOk := d.GetOk("on_schema") + _, onSchemaObjectOk := d.GetOk("on_schema_object") + + return getDatabaseRolePrivileges( + d.Get("all_privileges").(bool), + expandStringList(d.Get("privileges").(*schema.Set).List()), + onDatabaseOk, + onSchemaOk, + onSchemaObjectOk, + ) +} + +func getDatabaseRolePrivileges(allPrivileges bool, privileges []string, onDatabase bool, onSchema bool, onSchemaObject bool) *sdk.DatabaseRoleGrantPrivileges { databaseRoleGrantPrivileges := new(sdk.DatabaseRoleGrantPrivileges) if allPrivileges { @@ -719,20 +783,20 @@ func getDatabaseRolePrivileges(kind DatabaseRoleGrantKind, allPrivileges bool, p return databaseRoleGrantPrivileges } - switch kind { - case OnDatabaseDatabaseRoleGrantKind: + switch { + case onDatabase: databasePrivileges := make([]sdk.AccountObjectPrivilege, len(privileges)) for i, privilege := range privileges { databasePrivileges[i] = sdk.AccountObjectPrivilege(privilege) } databaseRoleGrantPrivileges.DatabasePrivileges = databasePrivileges - case OnSchemaDatabaseRoleGrantKind: + case onSchema: schemaPrivileges := make([]sdk.SchemaPrivilege, len(privileges)) for i, privilege := range privileges { schemaPrivileges[i] = sdk.SchemaPrivilege(privilege) } databaseRoleGrantPrivileges.SchemaPrivileges = schemaPrivileges - case OnSchemaObjectDatabaseRoleGrantKind: + case onSchemaObject: schemaObjectPrivileges := make([]sdk.SchemaObjectPrivilege, len(privileges)) for i, privilege := range privileges { schemaObjectPrivileges[i] = sdk.SchemaObjectPrivilege(privilege) @@ -743,8 +807,7 @@ func getDatabaseRolePrivileges(kind DatabaseRoleGrantKind, allPrivileges bool, p return databaseRoleGrantPrivileges } -// TODO: This should not set anything - remove id -func getDatabaseRoleGrantOn(id *GrantPrivilegesToDatabaseRoleId, d *schema.ResourceData) *sdk.DatabaseRoleGrantOn { +func getDatabaseRoleGrantOn(d *schema.ResourceData) *sdk.DatabaseRoleGrantOn { onDatabase, onDatabaseOk := d.GetOk("on_database") onSchemaBlock, onSchemaOk := d.GetOk("on_schema") onSchemaObjectBlock, onSchemaObjectOk := d.GetOk("on_schema_object") @@ -752,18 +815,10 @@ func getDatabaseRoleGrantOn(id *GrantPrivilegesToDatabaseRoleId, d *schema.Resou switch { case onDatabaseOk: - databaseId := sdk.NewAccountObjectIdentifierFromFullyQualifiedName(onDatabase.(string)) - - id.Kind = OnDatabaseDatabaseRoleGrantKind - id.Data = &OnDatabaseGrantData{ - DatabaseName: databaseId, - } - on.Database = &databaseId + on.Database = sdk.Pointer(sdk.NewAccountObjectIdentifierFromFullyQualifiedName(onDatabase.(string))) case onSchemaOk: onSchema := onSchemaBlock.([]any)[0].(map[string]any) - id.Kind = OnSchemaDatabaseRoleGrantKind - onSchemaGrantData := new(OnSchemaGrantData) grantOnSchema := new(sdk.GrantOnSchema) schemaName := onSchema["schema_name"].(string) @@ -777,32 +832,17 @@ func getDatabaseRoleGrantOn(id *GrantPrivilegesToDatabaseRoleId, d *schema.Resou switch { case schemaNameOk: - schemaId := sdk.NewDatabaseObjectIdentifierFromFullyQualifiedName(schemaName) - - onSchemaGrantData.Kind = OnSchemaSchemaGrantKind - onSchemaGrantData.SchemaName = &schemaId - grantOnSchema.Schema = &schemaId + grantOnSchema.Schema = sdk.Pointer(sdk.NewDatabaseObjectIdentifierFromFullyQualifiedName(schemaName)) case allSchemasInDatabaseOk: - databaseId := sdk.NewAccountObjectIdentifierFromFullyQualifiedName(allSchemasInDatabase) - - onSchemaGrantData.Kind = OnAllSchemasInDatabaseSchemaGrantKind - onSchemaGrantData.DatabaseName = &databaseId - grantOnSchema.AllSchemasInDatabase = &databaseId + grantOnSchema.AllSchemasInDatabase = sdk.Pointer(sdk.NewAccountObjectIdentifierFromFullyQualifiedName(allSchemasInDatabase)) case futureSchemasInDatabaseOk: - databaseId := sdk.NewAccountObjectIdentifierFromFullyQualifiedName(futureSchemasInDatabase) - - onSchemaGrantData.Kind = OnFutureSchemasInDatabaseSchemaGrantKind - onSchemaGrantData.DatabaseName = &databaseId - grantOnSchema.FutureSchemasInDatabase = &databaseId + grantOnSchema.FutureSchemasInDatabase = sdk.Pointer(sdk.NewAccountObjectIdentifierFromFullyQualifiedName(futureSchemasInDatabase)) } - id.Data = onSchemaGrantData on.Schema = grantOnSchema case onSchemaObjectOk: onSchemaObject := onSchemaObjectBlock.([]any)[0].(map[string]any) - id.Kind = OnSchemaObjectDatabaseRoleGrantKind - onSchemaObjectGrantData := new(OnSchemaObjectGrantData) grantOnSchemaObject := new(sdk.GrantOnSchemaObject) objectType := onSchemaObject["object_type"].(string) @@ -819,54 +859,108 @@ func getDatabaseRoleGrantOn(id *GrantPrivilegesToDatabaseRoleId, d *schema.Resou switch { case objectTypeOk && objectNameOk: - object := &sdk.Object{ + grantOnSchemaObject.SchemaObject = &sdk.Object{ ObjectType: sdk.ObjectType(objectType), // TODO: Should we validate it or just cast it Name: sdk.NewSchemaObjectIdentifierFromFullyQualifiedName(objectName), } - - onSchemaObjectGrantData.Kind = OnObjectSchemaObjectGrantKind - onSchemaObjectGrantData.Object = object - grantOnSchemaObject.SchemaObject = object case allOk: - onSchemaObjectGrantData.Kind = OnAllSchemaObjectGrantKind - grantOnSchemaObject.All = getGrantOnSchemaObjectIn(onSchemaObjectGrantData, all[0].(map[string]any)) + grantOnSchemaObject.All = getGrantOnSchemaObjectIn(all[0].(map[string]any)) case futureOk: - onSchemaObjectGrantData.Kind = OnFutureSchemaObjectGrantKind - grantOnSchemaObject.Future = getGrantOnSchemaObjectIn(onSchemaObjectGrantData, future[0].(map[string]any)) + grantOnSchemaObject.Future = getGrantOnSchemaObjectIn(future[0].(map[string]any)) } - id.Data = onSchemaObjectGrantData on.SchemaObject = grantOnSchemaObject } return on } -func getGrantOnSchemaObjectIn(onSchemaObjectGrantData *OnSchemaObjectGrantData, m map[string]any) *sdk.GrantOnSchemaObjectIn { - pluralObjectType := sdk.PluralObjectType(m["object_type_plural"].(string)) - bulkOperationGrantData := &BulkOperationGrantData{ - ObjectNamePlural: pluralObjectType, - } +func getGrantOnSchemaObjectIn(allOrFuture map[string]any) *sdk.GrantOnSchemaObjectIn { + pluralObjectType := sdk.PluralObjectType(allOrFuture["object_type_plural"].(string)) grantOnSchemaObjectIn := &sdk.GrantOnSchemaObjectIn{ PluralObjectType: pluralObjectType, } - if inDatabase, ok := m["in_database"].(string); ok && len(inDatabase) > 0 { - databaseId := sdk.Pointer(sdk.NewAccountObjectIdentifierFromFullyQualifiedName(inDatabase)) + if inDatabase, ok := allOrFuture["in_database"].(string); ok && len(inDatabase) > 0 { + grantOnSchemaObjectIn.InDatabase = sdk.Pointer(sdk.NewAccountObjectIdentifierFromFullyQualifiedName(inDatabase)) + } - bulkOperationGrantData.Kind = InDatabaseBulkOperationGrantKind - bulkOperationGrantData.Database = databaseId - grantOnSchemaObjectIn.InDatabase = databaseId + if inSchema, ok := allOrFuture["in_schema"].(string); ok && len(inSchema) > 0 { + grantOnSchemaObjectIn.InSchema = sdk.Pointer(sdk.NewDatabaseObjectIdentifierFromFullyQualifiedName(inSchema)) + } + + return grantOnSchemaObjectIn +} + +func createGrantPrivilegesToDatabaseRoleIdFromSchema(d *schema.ResourceData) *GrantPrivilegesToDatabaseRoleId { + id := new(GrantPrivilegesToDatabaseRoleId) + id.DatabaseRoleName = sdk.NewDatabaseObjectIdentifierFromFullyQualifiedName(d.Get("database_role_name").(string)) + id.AllPrivileges = d.Get("all_privileges").(bool) + if p, ok := d.GetOk("privileges"); ok { + id.Privileges = expandStringList(p.(*schema.Set).List()) } + id.WithGrantOption = d.Get("with_grant_option").(bool) - if inSchema, ok := m["in_schema"].(string); ok && len(inSchema) > 0 { - schemaId := sdk.Pointer(sdk.NewDatabaseObjectIdentifierFromFullyQualifiedName(inSchema)) + on := getDatabaseRoleGrantOn(d) + switch { + case on.Database != nil: + id.Kind = OnDatabaseDatabaseRoleGrantKind + id.Data = &OnDatabaseGrantData{ + DatabaseName: *on.Database, + } + case on.Schema != nil: + onSchemaGrantData := new(OnSchemaGrantData) + switch { + case on.Schema.Schema != nil: + onSchemaGrantData.Kind = OnSchemaSchemaGrantKind + onSchemaGrantData.SchemaName = on.Schema.Schema + case on.Schema.AllSchemasInDatabase != nil: + onSchemaGrantData.Kind = OnAllSchemasInDatabaseSchemaGrantKind + onSchemaGrantData.DatabaseName = on.Schema.AllSchemasInDatabase + case on.Schema.FutureSchemasInDatabase != nil: + onSchemaGrantData.Kind = OnFutureSchemasInDatabaseSchemaGrantKind + onSchemaGrantData.DatabaseName = on.Schema.FutureSchemasInDatabase + } + + id.Kind = OnSchemaDatabaseRoleGrantKind + id.Data = onSchemaGrantData + case on.SchemaObject != nil: + onSchemaObjectGrantData := new(OnSchemaObjectGrantData) + + switch { + case on.SchemaObject.SchemaObject != nil: + onSchemaObjectGrantData.Kind = OnObjectSchemaObjectGrantKind + onSchemaObjectGrantData.Object = on.SchemaObject.SchemaObject + case on.SchemaObject.All != nil: + onSchemaObjectGrantData.Kind = OnAllSchemaObjectGrantKind + onSchemaObjectGrantData.OnAllOrFuture = getBulkOperationGrantData(on.SchemaObject.All) + case on.SchemaObject.Future != nil: + onSchemaObjectGrantData.Kind = OnFutureSchemaObjectGrantKind + onSchemaObjectGrantData.OnAllOrFuture = getBulkOperationGrantData(on.SchemaObject.Future) + } + + id.Kind = OnSchemaObjectDatabaseRoleGrantKind + id.Data = onSchemaObjectGrantData + } + + return id +} + +func getBulkOperationGrantData(in *sdk.GrantOnSchemaObjectIn) *BulkOperationGrantData { + bulkOperationGrantData := &BulkOperationGrantData{ + ObjectNamePlural: in.PluralObjectType, + } + + if in.InDatabase != nil { + bulkOperationGrantData.Kind = InDatabaseBulkOperationGrantKind + bulkOperationGrantData.Database = in.InDatabase + } + + if in.InSchema != nil { bulkOperationGrantData.Kind = InSchemaBulkOperationGrantKind - bulkOperationGrantData.Schema = schemaId - grantOnSchemaObjectIn.InSchema = schemaId + bulkOperationGrantData.Schema = in.InSchema } - onSchemaObjectGrantData.OnAllOrFuture = bulkOperationGrantData - return grantOnSchemaObjectIn + return bulkOperationGrantData } diff --git a/pkg/resources/grant_privileges_to_database_role_acceptance_test.go b/pkg/resources/grant_privileges_to_database_role_acceptance_test.go index ca16670368..f8a0ad7ac8 100644 --- a/pkg/resources/grant_privileges_to_database_role_acceptance_test.go +++ b/pkg/resources/grant_privileges_to_database_role_acceptance_test.go @@ -8,18 +8,14 @@ import ( "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk" "github.com/hashicorp/terraform-plugin-testing/config" "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/plancheck" "github.com/hashicorp/terraform-plugin-testing/terraform" "github.com/hashicorp/terraform-plugin-testing/tfversion" + "regexp" "testing" ) // TODO Use cases to cover in acc tests -// - basic - check create, read and destroy -// - grant privileges on database -// - update - check update of privileges -// - privileges -// - privileges to all_privileges -// - all_privileges to privilege // - import - check import // - different paths to parse (on database, on schema, on schema object) @@ -224,6 +220,38 @@ func TestAcc_GrantPrivilegesToDatabaseRole_OnSchemaObject_OnObject(t *testing.T) }) } +func TestAcc_GrantPrivilegesToDatabaseRole_OnSchemaObject_OnObject_OwnershipPrivilege(t *testing.T) { + name := "test_database_role_name" + tableName := "test_database_role_table_name" + configVariables := config.Variables{ + "name": config.StringVariable(name), + "table_name": config.StringVariable(tableName), + "privileges": config.ListVariable( + config.StringVariable(string(sdk.SchemaObjectOwnership)), + ), + "database": config.StringVariable(acc.TestDatabaseName), + "schema": config.StringVariable(acc.TestSchemaName), + "with_grant_option": config.BoolVariable(false), + } + + resource.Test(t, resource.TestCase{ + ProtoV6ProviderFactories: acc.TestAccProtoV6ProviderFactories, + PreCheck: func() { acc.TestAccPreCheck(t) }, + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.RequireAbove(tfversion.Version1_5_0), + }, + CheckDestroy: testAccCheckDatabaseRolePrivilegesRevoked, + Steps: []resource.TestStep{ + { + PreConfig: func() { createDatabaseRoleOutsideTerraform(t, name) }, + ConfigDirectory: acc.ConfigurationDirectory("TestAcc_GrantPrivilegesToDatabaseRole_OnSchemaObject_OnObject"), + ConfigVariables: configVariables, + ExpectError: regexp.MustCompile("Unsupported privilege 'OWNERSHIP'"), + }, + }, + }) +} + func TestAcc_GrantPrivilegesToDatabaseRole_OnSchemaObject_OnAll_InDatabase(t *testing.T) { name := "test_database_role_name" configVariables := config.Variables{ @@ -391,15 +419,13 @@ func TestAcc_GrantPrivilegesToDatabaseRole_UpdatePrivileges(t *testing.T) { func TestAcc_GrantPrivilegesToDatabaseRole_AlwaysApply(t *testing.T) { name := "test_database_role_name" - configVariables := config.Variables{ - "name": config.StringVariable(name), - "privileges": config.ListVariable( - config.StringVariable(string(sdk.AccountObjectPrivilegeCreateSchema)), - config.StringVariable(string(sdk.AccountObjectPrivilegeModify)), - config.StringVariable(string(sdk.AccountObjectPrivilegeUsage)), - ), - "database": config.StringVariable(acc.TestDatabaseName), - "with_grant_option": config.BoolVariable(true), + configVariables := func(alwaysApply bool) config.Variables { + return config.Variables{ + "name": config.StringVariable(name), + "all_privileges": config.BoolVariable(true), + "database": config.StringVariable(acc.TestDatabaseName), + "always_apply": config.BoolVariable(alwaysApply), + } } resourceName := "snowflake_grant_privileges_to_database_role.test" @@ -414,16 +440,61 @@ func TestAcc_GrantPrivilegesToDatabaseRole_AlwaysApply(t *testing.T) { { PreConfig: func() { createDatabaseRoleOutsideTerraform(t, name) }, ConfigDirectory: config.TestNameDirectory(), - ConfigVariables: configVariables, - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr(resourceName, "database_role_name", sdk.NewDatabaseObjectIdentifier(acc.TestDatabaseName, name).FullyQualifiedName()), - resource.TestCheckResourceAttr(resourceName, "privileges.#", "3"), - resource.TestCheckResourceAttr(resourceName, "privileges.0", string(sdk.AccountObjectPrivilegeCreateSchema)), - resource.TestCheckResourceAttr(resourceName, "privileges.1", string(sdk.AccountObjectPrivilegeModify)), - resource.TestCheckResourceAttr(resourceName, "privileges.2", string(sdk.AccountObjectPrivilegeUsage)), - resource.TestCheckResourceAttr(resourceName, "on_database", sdk.NewAccountObjectIdentifier(acc.TestDatabaseName).FullyQualifiedName()), - resource.TestCheckResourceAttr(resourceName, "with_grant_option", "true"), - ), + ConfigVariables: configVariables(false), + ConfigPlanChecks: resource.ConfigPlanChecks{ + PostApplyPostRefresh: []plancheck.PlanCheck{ + plancheck.ExpectEmptyPlan(), + }, + }, + Check: resource.TestCheckResourceAttr(resourceName, "always_apply", "false"), + }, + { + ConfigDirectory: config.TestNameDirectory(), + ConfigVariables: configVariables(false), + ConfigPlanChecks: resource.ConfigPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectEmptyPlan(), + }, + }, + Check: resource.TestCheckResourceAttr(resourceName, "always_apply", "false"), + }, + { + ConfigDirectory: config.TestNameDirectory(), + ConfigVariables: configVariables(true), + Check: resource.TestCheckResourceAttr(resourceName, "always_apply", "true"), + ExpectNonEmptyPlan: true, + }, + { + ConfigDirectory: config.TestNameDirectory(), + ConfigVariables: configVariables(true), + ConfigPlanChecks: resource.ConfigPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectNonEmptyPlan(), + }, + }, + Check: resource.TestCheckResourceAttr(resourceName, "always_apply", "true"), + ExpectNonEmptyPlan: true, + }, + { + ConfigDirectory: config.TestNameDirectory(), + ConfigVariables: configVariables(true), + ConfigPlanChecks: resource.ConfigPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectNonEmptyPlan(), + }, + }, + Check: resource.TestCheckResourceAttr(resourceName, "always_apply", "true"), + ExpectNonEmptyPlan: true, + }, + { + ConfigDirectory: config.TestNameDirectory(), + ConfigVariables: configVariables(false), + ConfigPlanChecks: resource.ConfigPlanChecks{ + PostApplyPostRefresh: []plancheck.PlanCheck{ + plancheck.ExpectEmptyPlan(), + }, + }, + Check: resource.TestCheckResourceAttr(resourceName, "always_apply", "false"), }, }, }) diff --git a/pkg/resources/grant_privileges_to_database_role_identifier.go b/pkg/resources/grant_privileges_to_database_role_identifier.go index ed809672fa..29f3c9d2b8 100644 --- a/pkg/resources/grant_privileges_to_database_role_identifier.go +++ b/pkg/resources/grant_privileges_to_database_role_identifier.go @@ -39,6 +39,7 @@ const ( type GrantPrivilegesToDatabaseRoleId struct { DatabaseRoleName sdk.DatabaseObjectIdentifier WithGrantOption bool + AlwaysApply bool AllPrivileges bool Privileges []string Kind DatabaseRoleGrantKind @@ -49,6 +50,7 @@ func (g *GrantPrivilegesToDatabaseRoleId) String() string { var parts []string parts = append(parts, g.DatabaseRoleName.FullyQualifiedName()) parts = append(parts, strconv.FormatBool(g.WithGrantOption)) + parts = append(parts, strconv.FormatBool(g.AlwaysApply)) if g.AllPrivileges { parts = append(parts, "ALL") } else { @@ -129,13 +131,14 @@ func ParseGrantPrivilegesToDatabaseRoleId(id string) (GrantPrivilegesToDatabaseR var databaseRoleId GrantPrivilegesToDatabaseRoleId parts := strings.Split(id, helpers.IDDelimiter) - if len(parts) < 5 { - return databaseRoleId, sdk.NewError(`database role identifier should hold at least 4 parts "||||"`) + if len(parts) < 6 { + return databaseRoleId, sdk.NewError(`database role identifier should hold at least 5 parts "|||||"`) } databaseRoleId.DatabaseRoleName = sdk.NewDatabaseObjectIdentifierFromFullyQualifiedName(parts[0]) databaseRoleId.WithGrantOption = parts[1] == "true" - privileges := strings.Split(parts[2], ",") + databaseRoleId.AlwaysApply = parts[2] == "true" + privileges := strings.Split(parts[3], ",") if len(privileges) == 1 && privileges[0] == "ALL" { databaseRoleId.AllPrivileges = true } else { @@ -144,59 +147,59 @@ func ParseGrantPrivilegesToDatabaseRoleId(id string) (GrantPrivilegesToDatabaseR } databaseRoleId.Privileges = privileges } - databaseRoleId.Kind = DatabaseRoleGrantKind(parts[3]) + databaseRoleId.Kind = DatabaseRoleGrantKind(parts[4]) switch databaseRoleId.Kind { case OnDatabaseDatabaseRoleGrantKind: databaseRoleId.Data = &OnDatabaseGrantData{ - DatabaseName: sdk.NewAccountObjectIdentifierFromFullyQualifiedName(parts[4]), + DatabaseName: sdk.NewAccountObjectIdentifierFromFullyQualifiedName(parts[5]), } case OnSchemaDatabaseRoleGrantKind: - if len(parts) < 6 { - return databaseRoleId, sdk.NewError(`database role identifier should hold at least 6 parts "|||||..."`) + if len(parts) < 7 { + return databaseRoleId, sdk.NewError(`database role identifier should hold at least 7 parts "||||||..."`) } onSchemaGrantData := OnSchemaGrantData{ - Kind: OnSchemaGrantKind(parts[4]), + Kind: OnSchemaGrantKind(parts[5]), } switch onSchemaGrantData.Kind { case OnSchemaSchemaGrantKind: - onSchemaGrantData.SchemaName = sdk.Pointer(sdk.NewDatabaseObjectIdentifierFromFullyQualifiedName(parts[5])) + onSchemaGrantData.SchemaName = sdk.Pointer(sdk.NewDatabaseObjectIdentifierFromFullyQualifiedName(parts[6])) case OnAllSchemasInDatabaseSchemaGrantKind, OnFutureSchemasInDatabaseSchemaGrantKind: - onSchemaGrantData.DatabaseName = sdk.Pointer(sdk.NewAccountObjectIdentifier(parts[5])) + onSchemaGrantData.DatabaseName = sdk.Pointer(sdk.NewAccountObjectIdentifier(parts[6])) default: return databaseRoleId, sdk.NewError(fmt.Sprintf("invalid OnSchemaGrantKind: %s", onSchemaGrantData.Kind)) } databaseRoleId.Data = &onSchemaGrantData case OnSchemaObjectDatabaseRoleGrantKind: - if len(parts) < 6 { - return databaseRoleId, sdk.NewError(`database role identifier should hold at least 6 parts "|||||..."`) + if len(parts) < 7 { + return databaseRoleId, sdk.NewError(`database role identifier should hold at least 7 parts "||||||..."`) } onSchemaObjectGrantData := OnSchemaObjectGrantData{ - Kind: OnSchemaObjectGrantKind(parts[4]), + Kind: OnSchemaObjectGrantKind(parts[5]), } switch onSchemaObjectGrantData.Kind { case OnObjectSchemaObjectGrantKind: - if len(parts) != 7 { - return databaseRoleId, sdk.NewError(`database role identifier should hold 7 parts "|||OnSchemaObject|OnObject||"`) + if len(parts) != 8 { + return databaseRoleId, sdk.NewError(`database role identifier should hold 8 parts "||||OnSchemaObject|OnObject||"`) } onSchemaObjectGrantData.Object = &sdk.Object{ - ObjectType: sdk.ObjectType(parts[5]), - Name: sdk.NewSchemaObjectIdentifierFromFullyQualifiedName(parts[6]), + ObjectType: sdk.ObjectType(parts[6]), + Name: sdk.NewSchemaObjectIdentifierFromFullyQualifiedName(parts[7]), } case OnAllSchemaObjectGrantKind, OnFutureSchemaObjectGrantKind: bulkOperationGrantData := &BulkOperationGrantData{ - ObjectNamePlural: sdk.PluralObjectType(parts[5]), + ObjectNamePlural: sdk.PluralObjectType(parts[6]), } - if len(parts) > 6 { - if len(parts) != 8 { - return databaseRoleId, sdk.NewError(`database role identifier should hold 8 parts "|||OnSchemaObject|On[All or Future]||In[Database or Schema]|"`) + if len(parts) > 7 { + if len(parts) != 9 { + return databaseRoleId, sdk.NewError(`database role identifier should hold 9 parts "||||OnSchemaObject|On[All or Future]||In[Database or Schema]|"`) } - bulkOperationGrantData.Kind = BulkOperationGrantKind(parts[6]) + bulkOperationGrantData.Kind = BulkOperationGrantKind(parts[7]) switch bulkOperationGrantData.Kind { case InDatabaseBulkOperationGrantKind: - bulkOperationGrantData.Database = sdk.Pointer(sdk.NewAccountObjectIdentifierFromFullyQualifiedName(parts[7])) + bulkOperationGrantData.Database = sdk.Pointer(sdk.NewAccountObjectIdentifierFromFullyQualifiedName(parts[8])) case InSchemaBulkOperationGrantKind: - bulkOperationGrantData.Schema = sdk.Pointer(sdk.NewDatabaseObjectIdentifierFromFullyQualifiedName(parts[7])) + bulkOperationGrantData.Schema = sdk.Pointer(sdk.NewDatabaseObjectIdentifierFromFullyQualifiedName(parts[8])) default: return databaseRoleId, sdk.NewError(fmt.Sprintf("invalid BulkOperationGrantKind: %s", bulkOperationGrantData.Kind)) } diff --git a/pkg/resources/grant_privileges_to_database_role_identifier_test.go b/pkg/resources/grant_privileges_to_database_role_identifier_test.go index 5d9bf69c19..0c6f6fb908 100644 --- a/pkg/resources/grant_privileges_to_database_role_identifier_test.go +++ b/pkg/resources/grant_privileges_to_database_role_identifier_test.go @@ -15,7 +15,7 @@ func TestParseGrantPrivilegesToDatabaseRoleId(t *testing.T) { }{ { Name: "grant database role on database", - Identifier: `"database-name"|false|CREATE SCHEMA,USAGE,MONITOR|OnDatabase|"on-database-name"`, + Identifier: `"database-name"."database-role"|false|false|CREATE SCHEMA,USAGE,MONITOR|OnDatabase|"on-database-name"`, Expected: GrantPrivilegesToDatabaseRoleId{ DatabaseRoleName: sdk.NewDatabaseObjectIdentifier("database-name", "database-role"), WithGrantOption: false, @@ -26,9 +26,23 @@ func TestParseGrantPrivilegesToDatabaseRoleId(t *testing.T) { }, }, }, + { + Name: "grant database role on database - always apply with grant option", + Identifier: `"database-name"."database-role"|true|true|CREATE SCHEMA,USAGE,MONITOR|OnDatabase|"on-database-name"`, + Expected: GrantPrivilegesToDatabaseRoleId{ + DatabaseRoleName: sdk.NewDatabaseObjectIdentifier("database-name", "database-role"), + WithGrantOption: true, + AlwaysApply: true, + Privileges: []string{"CREATE SCHEMA", "USAGE", "MONITOR"}, + Kind: OnDatabaseDatabaseRoleGrantKind, + Data: &OnDatabaseGrantData{ + DatabaseName: sdk.NewAccountObjectIdentifier("on-database-name"), + }, + }, + }, { Name: "grant database role on database - all privileges", - Identifier: `"database-name"|false|ALL|OnDatabase|"on-database-name"`, + Identifier: `"database-name"."database-role"|false|false|ALL|OnDatabase|"on-database-name"`, Expected: GrantPrivilegesToDatabaseRoleId{ DatabaseRoleName: sdk.NewDatabaseObjectIdentifier("database-name", "database-role"), WithGrantOption: false, @@ -42,7 +56,7 @@ func TestParseGrantPrivilegesToDatabaseRoleId(t *testing.T) { }, { Name: "grant database role on schema with schema name", - Identifier: `"database-name"|false|CREATE SCHEMA,USAGE,MONITOR|OnSchema|OnSchema|"database-name"."schema-name"`, // TODO: OnSchema OnSchema x2 + Identifier: `"database-name"."database-role"|false|false|CREATE SCHEMA,USAGE,MONITOR|OnSchema|OnSchema|"database-name"."schema-name"`, // TODO: OnSchema OnSchema x2 Expected: GrantPrivilegesToDatabaseRoleId{ DatabaseRoleName: sdk.NewDatabaseObjectIdentifier("database-name", "database-role"), WithGrantOption: false, @@ -56,7 +70,7 @@ func TestParseGrantPrivilegesToDatabaseRoleId(t *testing.T) { }, { Name: "grant database role on all schemas in database", - Identifier: `"database-name"|false|CREATE SCHEMA,USAGE,MONITOR|OnSchema|OnAllSchemasInDatabase|"database-name-123"`, + Identifier: `"database-name"."database-role"|false|false|CREATE SCHEMA,USAGE,MONITOR|OnSchema|OnAllSchemasInDatabase|"database-name-123"`, Expected: GrantPrivilegesToDatabaseRoleId{ DatabaseRoleName: sdk.NewDatabaseObjectIdentifier("database-name", "database-role"), WithGrantOption: false, @@ -70,7 +84,7 @@ func TestParseGrantPrivilegesToDatabaseRoleId(t *testing.T) { }, { Name: "grant database role on future schemas in database", - Identifier: `"database-name"|false|CREATE SCHEMA,USAGE,MONITOR|OnSchema|OnFutureSchemasInDatabase|"database-name-123"`, + Identifier: `"database-name"."database-role"|false|false|CREATE SCHEMA,USAGE,MONITOR|OnSchema|OnFutureSchemasInDatabase|"database-name-123"`, Expected: GrantPrivilegesToDatabaseRoleId{ DatabaseRoleName: sdk.NewDatabaseObjectIdentifier("database-name", "database-role"), WithGrantOption: false, @@ -84,7 +98,7 @@ func TestParseGrantPrivilegesToDatabaseRoleId(t *testing.T) { }, { Name: "grant database role on schema object with on object option", - Identifier: `"database-name"|false|CREATE SCHEMA,USAGE,MONITOR|OnSchemaObject|OnObject|TABLE|"database-name"."schema-name"."table-name"`, + Identifier: `"database-name"."database-role"|false|false|CREATE SCHEMA,USAGE,MONITOR|OnSchemaObject|OnObject|TABLE|"database-name"."schema-name"."table-name"`, Expected: GrantPrivilegesToDatabaseRoleId{ DatabaseRoleName: sdk.NewDatabaseObjectIdentifier("database-name", "database-role"), WithGrantOption: false, @@ -101,7 +115,7 @@ func TestParseGrantPrivilegesToDatabaseRoleId(t *testing.T) { }, { Name: "grant database role on schema object with on all option", - Identifier: `"database-name"|false|CREATE SCHEMA,USAGE,MONITOR|OnSchemaObject|OnAll|TABLES`, + Identifier: `"database-name"."database-role"|false|false|CREATE SCHEMA,USAGE,MONITOR|OnSchemaObject|OnAll|TABLES`, Expected: GrantPrivilegesToDatabaseRoleId{ DatabaseRoleName: sdk.NewDatabaseObjectIdentifier("database-name", "database-role"), WithGrantOption: false, @@ -117,7 +131,7 @@ func TestParseGrantPrivilegesToDatabaseRoleId(t *testing.T) { }, { Name: "grant database role on schema object with on all option in database", - Identifier: `"database-name"|false|CREATE SCHEMA,USAGE,MONITOR|OnSchemaObject|OnAll|TABLES|InDatabase|"database-name-123"`, + Identifier: `"database-name"."database-role"|false|false|CREATE SCHEMA,USAGE,MONITOR|OnSchemaObject|OnAll|TABLES|InDatabase|"database-name-123"`, Expected: GrantPrivilegesToDatabaseRoleId{ DatabaseRoleName: sdk.NewDatabaseObjectIdentifier("database-name", "database-role"), WithGrantOption: false, @@ -135,7 +149,7 @@ func TestParseGrantPrivilegesToDatabaseRoleId(t *testing.T) { }, { Name: "grant database role on schema object with on all option in schema", - Identifier: `"database-name"|false|CREATE SCHEMA,USAGE,MONITOR|OnSchemaObject|OnAll|TABLES|InSchema|"database-name"."schema-name"`, + Identifier: `"database-name"."database-role"|false|false|CREATE SCHEMA,USAGE,MONITOR|OnSchemaObject|OnAll|TABLES|InSchema|"database-name"."schema-name"`, Expected: GrantPrivilegesToDatabaseRoleId{ DatabaseRoleName: sdk.NewDatabaseObjectIdentifier("database-name", "database-role"), WithGrantOption: false, @@ -153,7 +167,7 @@ func TestParseGrantPrivilegesToDatabaseRoleId(t *testing.T) { }, { Name: "grant database role on schema object with on future option", - Identifier: `"database-name"|false|CREATE SCHEMA,USAGE,MONITOR|OnSchemaObject|OnFuture|TABLES`, + Identifier: `"database-name"."database-role"|false|false|CREATE SCHEMA,USAGE,MONITOR|OnSchemaObject|OnFuture|TABLES`, Expected: GrantPrivilegesToDatabaseRoleId{ DatabaseRoleName: sdk.NewDatabaseObjectIdentifier("database-name", "database-role"), WithGrantOption: false, @@ -169,7 +183,7 @@ func TestParseGrantPrivilegesToDatabaseRoleId(t *testing.T) { }, { Name: "grant database role on schema object with on all option in database", - Identifier: `"database-name"|false|CREATE SCHEMA,USAGE,MONITOR|OnSchemaObject|OnFuture|TABLES|InDatabase|"database-name-123"`, + Identifier: `"database-name"."database-role"|false|false|CREATE SCHEMA,USAGE,MONITOR|OnSchemaObject|OnFuture|TABLES|InDatabase|"database-name-123"`, Expected: GrantPrivilegesToDatabaseRoleId{ DatabaseRoleName: sdk.NewDatabaseObjectIdentifier("database-name", "database-role"), WithGrantOption: false, @@ -187,7 +201,7 @@ func TestParseGrantPrivilegesToDatabaseRoleId(t *testing.T) { }, { Name: "grant database role on schema object with on all option in schema", - Identifier: `"database-name"|false|CREATE SCHEMA,USAGE,MONITOR|OnSchemaObject|OnFuture|TABLES|InSchema|"database-name"."schema-name"`, + Identifier: `"database-name"."database-role"|false|false|CREATE SCHEMA,USAGE,MONITOR|OnSchemaObject|OnFuture|TABLES|InSchema|"database-name"."schema-name"`, Expected: GrantPrivilegesToDatabaseRoleId{ DatabaseRoleName: sdk.NewDatabaseObjectIdentifier("database-name", "database-role"), WithGrantOption: false, @@ -205,47 +219,47 @@ func TestParseGrantPrivilegesToDatabaseRoleId(t *testing.T) { }, { Name: "validation: grant database role not enough parts", - Identifier: `"database-name"."role-name"|false`, - Error: "database role identifier should hold at least 4 parts", + Identifier: `"database-name"."role-name"|false|false`, + Error: "database role identifier should hold at least 5 parts", }, { Name: "validation: grant database role not enough parts for OnDatabase kind", - Identifier: `"database-name"."role-name"|false|CREATE SCHEMA,USAGE,MONITOR|OnDatabase`, - Error: "database role identifier should hold at least 4 parts", + Identifier: `"database-name"."role-name"|false|false|CREATE SCHEMA,USAGE,MONITOR|OnDatabase`, + Error: "database role identifier should hold at least 5 parts", }, { Name: "validation: grant database role not enough parts for OnSchema kind", - Identifier: `"database-name"."role-name"|false|CREATE SCHEMA,USAGE,MONITOR|OnSchema|OnAllSchemasInDatabase`, - Error: "database role identifier should hold at least 6 parts", + Identifier: `"database-name"."role-name"|false|false|CREATE SCHEMA,USAGE,MONITOR|OnSchema|OnAllSchemasInDatabase`, + Error: "database role identifier should hold at least 7 parts", }, { Name: "validation: grant database role not enough parts for OnSchemaObject kind", - Identifier: `"database-name"."role-name"|false|CREATE SCHEMA,USAGE,MONITOR|OnSchemaObject|OnObject`, - Error: "database role identifier should hold at least 6 parts", + Identifier: `"database-name"."role-name"|false|false|CREATE SCHEMA,USAGE,MONITOR|OnSchemaObject|OnObject`, + Error: "database role identifier should hold at least 7 parts", }, { Name: "validation: grant database role not enough parts for OnSchemaObject kind", - Identifier: `"database-name"."role-name"|false|CREATE SCHEMA,USAGE,MONITOR|OnSchemaObject|OnObject|TABLE`, - Error: "database role identifier should hold 7 parts", + Identifier: `"database-name"."role-name"|false|false|CREATE SCHEMA,USAGE,MONITOR|OnSchemaObject|OnObject|TABLE`, + Error: "database role identifier should hold 8 parts", }, { Name: "validation: grant database role not enough parts for OnSchemaObject.InDatabase kind", - Identifier: `"database-name"."role-name"|false|CREATE SCHEMA,USAGE,MONITOR|OnSchemaObject|OnAll|TABLES|InDatabase`, - Error: "database role identifier should hold 8 parts", + Identifier: `"database-name"."role-name"|false|false|CREATE SCHEMA,USAGE,MONITOR|OnSchemaObject|OnAll|TABLES|InDatabase`, + Error: "database role identifier should hold 9 parts", }, { Name: "validation: grant database role invalid DatabaseRoleGrantKind kind", - Identifier: `"database-name"."role-name"|false|CREATE SCHEMA,USAGE,MONITOR|some-kind|some-data`, + Identifier: `"database-name"."role-name"|false|false|CREATE SCHEMA,USAGE,MONITOR|some-kind|some-data`, Error: "invalid DatabaseRoleGrantKind: some-kind", }, { Name: "validation: grant database role invalid OnSchemaGrantKind kind", - Identifier: `"database-name"."role-name"|false|CREATE SCHEMA,USAGE,MONITOR|OnSchema|some-kind|some-data`, + Identifier: `"database-name"."role-name"|false|false|CREATE SCHEMA,USAGE,MONITOR|OnSchema|some-kind|some-data`, Error: "invalid OnSchemaGrantKind: some-kind", }, { Name: "validation: grant database role invalid OnSchemaObjectGrantKind kind", - Identifier: `"database-name"."role-name"|false|CREATE SCHEMA,USAGE,MONITOR|OnSchemaObject|some-kind|some-data`, + Identifier: `"database-name"."role-name"|false|false|CREATE SCHEMA,USAGE,MONITOR|OnSchemaObject|some-kind|some-data`, Error: "invalid OnSchemaObjectGrantKind: some-kind", }, } diff --git a/pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole_AlwaysApply/test.tf b/pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole_AlwaysApply/test.tf new file mode 100644 index 0000000000..dda4fa75aa --- /dev/null +++ b/pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole_AlwaysApply/test.tf @@ -0,0 +1,6 @@ +resource "snowflake_grant_privileges_to_database_role" "test" { + database_role_name = "\"${var.database}\".\"${var.name}\"" + all_privileges = var.all_privileges + on_database = "\"${var.database}\"" + always_apply = var.always_apply +} diff --git a/pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole_AlwaysApply/variables.tf b/pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole_AlwaysApply/variables.tf new file mode 100644 index 0000000000..563945ecc7 --- /dev/null +++ b/pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole_AlwaysApply/variables.tf @@ -0,0 +1,15 @@ +variable "name" { + type = string +} + +variable "all_privileges" { + type = bool +} + +variable "database" { + type = string +} + +variable "always_apply" { + type = bool +} From 1bd2069f2db0656a7ed0486de0f9feedba4a70a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Cie=C5=9Blak?= Date: Wed, 3 Jan 2024 14:05:09 +0100 Subject: [PATCH 07/16] wip --- .../grant_privileges_to_database_role.md | 303 ++++++++++++++++++ .../resource.tf | 146 +++++++++ .../grant_privileges_to_database_role.go | 112 +++---- ...ileges_to_database_role_acceptance_test.go | 53 ++- ..._privileges_to_database_role_identifier.go | 7 +- .../grant_privileges_to_database_role.md.tmpl | 93 ++++++ 6 files changed, 639 insertions(+), 75 deletions(-) create mode 100644 docs/resources/grant_privileges_to_database_role.md create mode 100644 examples/resources/snowflake_grant_privileges_to_database_role/resource.tf create mode 100644 templates/resources/grant_privileges_to_database_role.md.tmpl diff --git a/docs/resources/grant_privileges_to_database_role.md b/docs/resources/grant_privileges_to_database_role.md new file mode 100644 index 0000000000..00000c2397 --- /dev/null +++ b/docs/resources/grant_privileges_to_database_role.md @@ -0,0 +1,303 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "snowflake_grant_privileges_to_database_role Resource - terraform-provider-snowflake" +subcategory: "" +description: |- + +--- + +# snowflake_grant_privileges_to_database_role (Resource) + + + +## Example Usage + +```terraform +resource "snowflake_database_role" "db_role" { + database = "database" + name = "db_role_name" +} + +################################## +### on database privileges +################################## + +# list of privileges +resource "snowflake_grant_privileges_to_database_role" "example" { + privileges = ["CREATE", "MONITOR"] + database_role_name = "\"${snowflake_database_role.db_role.database}\".\"${snowflake_database_role.db_role.name}\"" + on_database = snowflake_database_role.db_role.database +} + +# all privileges + grant option +resource "snowflake_grant_privileges_to_database_role" "example" { + database_role_name = "\"${snowflake_database_role.db_role.database}\".\"${snowflake_database_role.db_role.name}\"" + on_database = snowflake_database_role.db_role.database + all_privileges = true + with_grant_option = true +} + +# all privileges + grant option + always apply +resource "snowflake_grant_privileges_to_database_role" "example" { + database_role_name = "\"${snowflake_database_role.db_role.database}\".\"${snowflake_database_role.db_role.name}\"" + on_database = snowflake_database_role.db_role.database + always_apply = true + all_privileges = true + with_grant_option = true +} + +################################## +### schema privileges +################################## + +# list of privileges +resource "snowflake_grant_privileges_to_database_role" "example" { + privileges = ["MODIFY", "CREATE TABLE"] + database_role_name = "\"${snowflake_database_role.db_role.database}\".\"${snowflake_database_role.db_role.name}\"" + on_schema { + schema_name = "\"${snowflake_database_role.db_role.database}\".\"my_schema\"" # note this is a fully qualified name! + } +} + +# all privileges + grant option +resource "snowflake_grant_privileges_to_database_role" "example" { + database_role_name = "\"${snowflake_database_role.db_role.database}\".\"${snowflake_database_role.db_role.name}\"" + on_schema { + schema_name = "\"${snowflake_database_role.db_role.database}\".\"my_schema\"" # note this is a fully qualified name! + } + all_privileges = true + with_grant_option = true +} + +# all schemas in database +resource "snowflake_grant_privileges_to_database_role" "example" { + privileges = ["MODIFY", "CREATE TABLE"] + database_role_name = "\"${snowflake_database_role.db_role.database}\".\"${snowflake_database_role.db_role.name}\"" + on_schema { + all_schemas_in_database = snowflake_database_role.db_role.database + } +} + +# future schemas in database +resource "snowflake_grant_privileges_to_database_role" "example" { + privileges = ["MODIFY", "CREATE TABLE"] + database_role_name = "\"${snowflake_database_role.db_role.database}\".\"${snowflake_database_role.db_role.name}\"" + on_schema { + future_schemas_in_database = snowflake_database_role.db_role.database + } +} + +################################## +### schema object privileges +################################## + +# list of privileges +resource "snowflake_grant_privileges_to_database_role" "example" { + privileges = ["SELECT", "REFERENCES"] + database_role_name = "\"${snowflake_database_role.db_role.database}\".\"${snowflake_database_role.db_role.name}\"" + on_schema_object { + object_type = "VIEW" + object_name = "\"${snowflake_database_role.db_role.database}\".\"my_schema\".\"my_view\"" # note this is a fully qualified name! + } +} + +# all privileges + grant option +resource "snowflake_grant_privileges_to_database_role" "example" { + database_role_name = "\"${snowflake_database_role.db_role.database}\".\"${snowflake_database_role.db_role.name}\"" + on_schema_object { + object_type = "VIEW" + object_name = "\"${snowflake_database_role.db_role.database}\".\"my_schema\".\"my_view\"" # note this is a fully qualified name! + } + all_privileges = true + with_grant_option = true +} + +# all in database +resource "snowflake_grant_privileges_to_database_role" "example" { + privileges = ["SELECT", "INSERT"] + database_role_name = "\"${snowflake_database_role.db_role.database}\".\"${snowflake_database_role.db_role.name}\"" + on_schema_object { + all { + object_type_plural = "TABLES" + in_database = snowflake_database_role.db_role.database + } + } +} + +# all in schema +resource "snowflake_grant_privileges_to_database_role" "example" { + privileges = ["SELECT", "INSERT"] + database_role_name = "\"${snowflake_database_role.db_role.database}\".\"${snowflake_database_role.db_role.name}\"" + on_schema_object { + all { + object_type_plural = "TABLES" + in_schema = "\"${snowflake_database_role.db_role.database}\".\"my_schema\"" # note this is a fully qualified name! + } + } +} + +# future in database +resource "snowflake_grant_privileges_to_database_role" "example" { + privileges = ["SELECT", "INSERT"] + database_role_name = "\"${snowflake_database_role.db_role.database}\".\"${snowflake_database_role.db_role.name}\"" + on_schema_object { + future { + object_type_plural = "TABLES" + in_database = snowflake_database_role.db_role.database + } + } +} + +# future in schema +resource "snowflake_grant_privileges_to_database_role" "example" { + privileges = ["SELECT", "INSERT"] + database_role_name = "\"${snowflake_database_role.db_role.database}\".\"${snowflake_database_role.db_role.name}\"" + on_schema_object { + future { + object_type_plural = "TABLES" + in_schema = "\"${snowflake_database_role.db_role.database}\".\"my_schema\"" # note this is a fully qualified name! + } + } +} +``` + + +## Schema + +### Required + +- `database_role_name` (String) The fully qualified name of the database role to which privileges will be granted. + +### Optional + +- `all_privileges` (Boolean) Grant all privileges on the database role. +- `always_apply` (Boolean) If true, the resource will always produce a “plan” and on “apply” it will re-grant defined privileges. It is supposed to be used only in “grant privileges on all X’s in database / schema Y” or “grant all privileges to X” scenarios to make sure that every new object in a given database / schema is granted by the account role and every new privilege is granted to the database role. Important note: this flag is not compliant with the Terraform assumptions of the config being eventually convergent (producing an empty plan). +- `always_apply_trigger` (String) This field should not be set and its main purpose is to achieve the functionality described by always_apply field. This is value will be flipped to the opposite value on every terraform apply, thus creating a new plan that will re-apply grants. +- `on_database` (String) The fully qualified name of the database on which privileges will be granted. If the identifier is not fully qualified (in the form of .≤database_role_name>), the command looks for the database role in the current database for the session. All privileges are limited to the database that contains the database role, as well as other objects in the same database. +- `on_schema` (Block List, Max: 1) Specifies the schema on which privileges will be granted. (see [below for nested schema](#nestedblock--on_schema)) +- `on_schema_object` (Block List, Max: 1) Specifies the schema object on which privileges will be granted. (see [below for nested schema](#nestedblock--on_schema_object)) +- `privileges` (Set of String) The privileges to grant on the database role. +- `with_grant_option` (Boolean) If specified, allows the recipient role to grant the privileges to other roles. + +### Read-Only + +- `id` (String) The ID of this resource. + + +### Nested Schema for `on_schema` + +Optional: + +- `all_schemas_in_database` (String) The fully qualified name of the database. +- `future_schemas_in_database` (String) The fully qualified name of the database. +- `schema_name` (String) The fully qualified name of the schema. + + + +### Nested Schema for `on_schema_object` + +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_schema_object--all)) +- `future` (Block List, Max: 1) Configures the privilege to be granted on future objects in either a database or schema. (see [below for nested schema](#nestedblock--on_schema_object--future)) +- `object_name` (String) The fully qualified name of the object on which privileges will be granted. +- `object_type` (String) The object type of the schema object on which privileges will be granted. Valid values are: ALERT | DYNAMIC TABLE | EVENT TABLE | FILE FORMAT | FUNCTION | PROCEDURE | SECRET | SEQUENCE | PIPE | MASKING POLICY | PASSWORD POLICY | ROW ACCESS POLICY | SESSION POLICY | TAG | STAGE | STREAM | TABLE | EXTERNAL TABLE | TASK | VIEW | MATERIALIZED VIEW | NETWORK RULE | PACKAGES POLICY | ICEBERG TABLE + + +### Nested Schema for `on_schema_object.all` + +Required: + +- `object_type_plural` (String) The plural object type of the schema object on which privileges will be granted. Valid values are: ALERTS | DYNAMIC TABLES | EVENT TABLES | FILE FORMATS | FUNCTIONS | PROCEDURES | SECRETS | SEQUENCES | PIPES | MASKING POLICIES | PASSWORD POLICIES | ROW ACCESS POLICIES | SESSION POLICIES | TAGS | STAGES | STREAMS | TABLES | EXTERNAL TABLES | TASKS | VIEWS | MATERIALIZED VIEWS | NETWORK RULES | PACKAGES POLICIES | ICEBERG TABLES + +Optional: + +- `in_database` (String) +- `in_schema` (String) + + + +### Nested Schema for `on_schema_object.future` + +Required: + +- `object_type_plural` (String) The plural object type of the schema object on which privileges will be granted. Valid values are: ALERTS | DYNAMIC TABLES | EVENT TABLES | FILE FORMATS | FUNCTIONS | PROCEDURES | SECRETS | SEQUENCES | PIPES | MASKING POLICIES | PASSWORD POLICIES | ROW ACCESS POLICIES | SESSION POLICIES | TAGS | STAGES | STREAMS | TABLES | EXTERNAL TABLES | TASKS | VIEWS | MATERIALIZED VIEWS | NETWORK RULES | PACKAGES POLICIES | ICEBERG TABLES + +Optional: + +- `in_database` (String) +- `in_schema` (String) + +## Import + +~> **Note** All the ..._name parts should be fully qualified names, 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: + +`terraform import "|||||"` + +where: +- database_role_name - fully qualified identifier +- with_grant_option - boolean +- always_apply - boolean +- privileges - list of privileges, comma separated; to import all_privileges write "ALL" or "ALL PRIVILEGES" +- grant_type - enum +- grant_data - enum data + +It has varying number of parts, depending on . All the possible types are: + +### OnDatabase +`terraform import "||||OnDatabase|"` + +### OnSchema + +On schema contains inner types for all options. + +#### OnSchema +`terraform import "||||OnSchema|OnSchema|"` + +#### OnAllSchemasInDatabase +`terraform import "||||OnSchema|OnAllSchemasInDatabase|"` + +#### OnFutureSchemasInDatabase +`terraform import "||||OnSchema|OnFutureSchemasInDatabase|"` + +### OnSchemaObject + +On schema object contains inner types for all options. + +#### OnObject +`terraform import "||||OnSchemaObject|OnObject||"` + +#### OnAll + +On all contains inner types for all options. + +##### InDatabase +`terraform import "||||OnSchemaObject|OnAll||InDatabase|"` + +##### InSchema +`terraform import "||||OnSchemaObject|OnAll||InSchema|"` + +#### OnFuture + +On future contains inner types for all options. + +##### InDatabase +`terraform import "||||OnSchemaObject|OnFuture||InDatabase|"` + +##### InSchema +`terraform import "||||OnSchemaObject|OnFuture||InSchema|"` + +### Import examples + +#### Grant all privileges OnDatabase +`terraform import "\"test_db\".\"test_db_role\"|false|false|ALL|OnDatabase|\"test_db\""` + +#### Grant list of privileges OnAllSchemasInDatabase +`terraform import "\"test_db\".\"test_db_role\"|false|false|CREATE TAG,CREATE TABLE|OnSchema|OnAllSchemasInDatabase|\"test_db\""` + +#### Grant list of privileges OnAll tables in schema +`terraform import "\"test_db\".\"test_db_role\"|false|false|SELECT|OnSchemaObject|OnAll|TABLES|InSchema|\"test_db\".\"test_schema\""` + diff --git a/examples/resources/snowflake_grant_privileges_to_database_role/resource.tf b/examples/resources/snowflake_grant_privileges_to_database_role/resource.tf new file mode 100644 index 0000000000..a5de908db6 --- /dev/null +++ b/examples/resources/snowflake_grant_privileges_to_database_role/resource.tf @@ -0,0 +1,146 @@ +resource "snowflake_database_role" "db_role" { + database = "database" + name = "db_role_name" +} + +################################## +### on database privileges +################################## + +# list of privileges +resource "snowflake_grant_privileges_to_database_role" "example" { + privileges = ["CREATE", "MONITOR"] + database_role_name = "\"${snowflake_database_role.db_role.database}\".\"${snowflake_database_role.db_role.name}\"" + on_database = snowflake_database_role.db_role.database +} + +# all privileges + grant option +resource "snowflake_grant_privileges_to_database_role" "example" { + database_role_name = "\"${snowflake_database_role.db_role.database}\".\"${snowflake_database_role.db_role.name}\"" + on_database = snowflake_database_role.db_role.database + all_privileges = true + with_grant_option = true +} + +# all privileges + grant option + always apply +resource "snowflake_grant_privileges_to_database_role" "example" { + database_role_name = "\"${snowflake_database_role.db_role.database}\".\"${snowflake_database_role.db_role.name}\"" + on_database = snowflake_database_role.db_role.database + always_apply = true + all_privileges = true + with_grant_option = true +} + +################################## +### schema privileges +################################## + +# list of privileges +resource "snowflake_grant_privileges_to_database_role" "example" { + privileges = ["MODIFY", "CREATE TABLE"] + database_role_name = "\"${snowflake_database_role.db_role.database}\".\"${snowflake_database_role.db_role.name}\"" + on_schema { + schema_name = "\"${snowflake_database_role.db_role.database}\".\"my_schema\"" # note this is a fully qualified name! + } +} + +# all privileges + grant option +resource "snowflake_grant_privileges_to_database_role" "example" { + database_role_name = "\"${snowflake_database_role.db_role.database}\".\"${snowflake_database_role.db_role.name}\"" + on_schema { + schema_name = "\"${snowflake_database_role.db_role.database}\".\"my_schema\"" # note this is a fully qualified name! + } + all_privileges = true + with_grant_option = true +} + +# all schemas in database +resource "snowflake_grant_privileges_to_database_role" "example" { + privileges = ["MODIFY", "CREATE TABLE"] + database_role_name = "\"${snowflake_database_role.db_role.database}\".\"${snowflake_database_role.db_role.name}\"" + on_schema { + all_schemas_in_database = snowflake_database_role.db_role.database + } +} + +# future schemas in database +resource "snowflake_grant_privileges_to_database_role" "example" { + privileges = ["MODIFY", "CREATE TABLE"] + database_role_name = "\"${snowflake_database_role.db_role.database}\".\"${snowflake_database_role.db_role.name}\"" + on_schema { + future_schemas_in_database = snowflake_database_role.db_role.database + } +} + +################################## +### schema object privileges +################################## + +# list of privileges +resource "snowflake_grant_privileges_to_database_role" "example" { + privileges = ["SELECT", "REFERENCES"] + database_role_name = "\"${snowflake_database_role.db_role.database}\".\"${snowflake_database_role.db_role.name}\"" + on_schema_object { + object_type = "VIEW" + object_name = "\"${snowflake_database_role.db_role.database}\".\"my_schema\".\"my_view\"" # note this is a fully qualified name! + } +} + +# all privileges + grant option +resource "snowflake_grant_privileges_to_database_role" "example" { + database_role_name = "\"${snowflake_database_role.db_role.database}\".\"${snowflake_database_role.db_role.name}\"" + on_schema_object { + object_type = "VIEW" + object_name = "\"${snowflake_database_role.db_role.database}\".\"my_schema\".\"my_view\"" # note this is a fully qualified name! + } + all_privileges = true + with_grant_option = true +} + +# all in database +resource "snowflake_grant_privileges_to_database_role" "example" { + privileges = ["SELECT", "INSERT"] + database_role_name = "\"${snowflake_database_role.db_role.database}\".\"${snowflake_database_role.db_role.name}\"" + on_schema_object { + all { + object_type_plural = "TABLES" + in_database = snowflake_database_role.db_role.database + } + } +} + +# all in schema +resource "snowflake_grant_privileges_to_database_role" "example" { + privileges = ["SELECT", "INSERT"] + database_role_name = "\"${snowflake_database_role.db_role.database}\".\"${snowflake_database_role.db_role.name}\"" + on_schema_object { + all { + object_type_plural = "TABLES" + in_schema = "\"${snowflake_database_role.db_role.database}\".\"my_schema\"" # note this is a fully qualified name! + } + } +} + +# future in database +resource "snowflake_grant_privileges_to_database_role" "example" { + privileges = ["SELECT", "INSERT"] + database_role_name = "\"${snowflake_database_role.db_role.database}\".\"${snowflake_database_role.db_role.name}\"" + on_schema_object { + future { + object_type_plural = "TABLES" + in_database = snowflake_database_role.db_role.database + } + } +} + +# future in schema +resource "snowflake_grant_privileges_to_database_role" "example" { + privileges = ["SELECT", "INSERT"] + database_role_name = "\"${snowflake_database_role.db_role.database}\".\"${snowflake_database_role.db_role.name}\"" + on_schema_object { + future { + object_type_plural = "TABLES" + in_schema = "\"${snowflake_database_role.db_role.database}\".\"my_schema\"" # note this is a fully qualified name! + } + } +} diff --git a/pkg/resources/grant_privileges_to_database_role.go b/pkg/resources/grant_privileges_to_database_role.go index 5cbe46c1cd..44b91bb161 100644 --- a/pkg/resources/grant_privileges_to_database_role.go +++ b/pkg/resources/grant_privileges_to_database_role.go @@ -10,17 +10,11 @@ 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" "slices" "strings" ) -// TODO: -// - resolve todos -// - remove logs -// - make error messages consistent -// - write documentation (document on_all, always_apply etc.) -// - test import +// TODO: Imported privileges (after second account will be added) var grantPrivilegesToDatabaseRoleSchema = map[string]*schema.Schema{ "database_role_name": { @@ -65,18 +59,11 @@ var grantPrivilegesToDatabaseRoleSchema = map[string]*schema.Schema{ Optional: true, Default: false, Description: "If true, the resource will always produce a “plan” and on “apply” it will re-grant defined privileges. It is supposed to be used only in “grant privileges on all X’s in database / schema Y” or “grant all privileges to X” scenarios to make sure that every new object in a given database / schema is granted by the account role and every new privilege is granted to the database role. Important note: this flag is not compliant with the Terraform assumptions of the config being eventually convergent (producing an empty plan).", - // TODO: conflicts with - //AtLeastOneOf: []string{ - // "all_privileges", - // "on_schema.0.all_schemas_in_database", - // "on_schema_object.0.all", - //}, }, "always_apply_trigger": { - Type: schema.TypeString, - Optional: true, - Default: "", - // TODO: Fix desc + Type: schema.TypeString, + Optional: true, + Default: "", Description: "This field should not be set and its main purpose is to achieve the functionality described by always_apply field. This is value will be flipped to the opposite value on every terraform apply, thus creating a new plan that will re-apply grants.", }, "on_database": { @@ -176,8 +163,7 @@ var grantPrivilegesToDatabaseRoleSchema = map[string]*schema.Schema{ "FUNCTION", "PROCEDURE", "SECRET", - "SEQUENCE", - "PIPE", + "SEQUENCE", "PIPE", "MASKING POLICY", "PASSWORD POLICY", "ROW ACCESS POLICY", @@ -333,7 +319,7 @@ func ImportGrantPrivilegesToDatabaseRole(ctx context.Context, d *schema.Resource if err != nil { return nil, err } - if err := d.Set("database_role_name", id.DatabaseRoleName); err != nil { + if err := d.Set("database_role_name", id.DatabaseRoleName.FullyQualifiedName()); err != nil { return nil, err } if err := d.Set("with_grant_option", id.WithGrantOption); err != nil { @@ -356,7 +342,7 @@ func ImportGrantPrivilegesToDatabaseRole(ctx context.Context, d *schema.Resource } case OnSchemaDatabaseRoleGrantKind: data := id.Data.(*OnSchemaGrantData) - var onSchema map[string]any + onSchema := make(map[string]any) switch data.Kind { case OnSchemaSchemaGrantKind: @@ -372,16 +358,16 @@ func ImportGrantPrivilegesToDatabaseRole(ctx context.Context, d *schema.Resource } case OnSchemaObjectDatabaseRoleGrantKind: data := id.Data.(*OnSchemaObjectGrantData) - var onSchemaObject map[string]any + onSchemaObject := make(map[string]any) switch data.Kind { case OnObjectSchemaObjectGrantKind: onSchemaObject["object_type"] = data.Object.ObjectType.String() onSchemaObject["object_name"] = data.Object.Name.FullyQualifiedName() case OnAllSchemaObjectGrantKind: - var onAll map[string]any + onAll := make(map[string]any) - onAll["object_name_plural"] = data.OnAllOrFuture.ObjectNamePlural.String() + onAll["object_type_plural"] = data.OnAllOrFuture.ObjectNamePlural.String() switch data.OnAllOrFuture.Kind { case InDatabaseBulkOperationGrantKind: onAll["in_database"] = data.OnAllOrFuture.Database.FullyQualifiedName() @@ -391,9 +377,9 @@ func ImportGrantPrivilegesToDatabaseRole(ctx context.Context, d *schema.Resource onSchemaObject["all"] = []any{onAll} case OnFutureSchemaObjectGrantKind: - var onFuture map[string]any + onFuture := make(map[string]any) - onFuture["object_name_plural"] = data.OnAllOrFuture.ObjectNamePlural.String() + onFuture["object_type_plural"] = data.OnAllOrFuture.ObjectNamePlural.String() switch data.OnAllOrFuture.Kind { case InDatabaseBulkOperationGrantKind: onFuture["in_database"] = data.OnAllOrFuture.Database.FullyQualifiedName() @@ -451,7 +437,7 @@ func UpdateGrantPrivilegesToDatabaseRole(ctx context.Context, d *schema.Resource return append(diags, diag.Diagnostic{ Severity: diag.Error, Summary: "Failed to parse internal identifier", - Detail: fmt.Sprintf("Id: %s\nErr: %s", d.Id(), err.Error()), // TODO: link to the documentation (?). It should describe how the identifier looks. + Detail: fmt.Sprintf("Id: %s\nError: %s", d.Id(), err.Error()), }) } @@ -474,7 +460,6 @@ func UpdateGrantPrivilegesToDatabaseRole(ctx context.Context, d *schema.Resource } } - log.Println("PRIVILEGES TO CHANGE:", privilegesToAdd, privilegesToRemove) grantOn := getDatabaseRoleGrantOn(d) if len(privilegesToAdd) > 0 { @@ -495,7 +480,7 @@ func UpdateGrantPrivilegesToDatabaseRole(ctx context.Context, d *schema.Resource return append(diags, diag.Diagnostic{ Severity: diag.Error, Summary: "Failed to grant added privileges", - Detail: fmt.Sprintf("Id: %s\nErr: %s", d.Id(), err.Error()), // TODO: link to the documentation (?). It should describe how the identifier looks. + Detail: fmt.Sprintf("Id: %s\nPrivileges to add: %v\nError: %s", d.Id(), privilegesToAdd, err.Error()), }) } } @@ -518,7 +503,7 @@ func UpdateGrantPrivilegesToDatabaseRole(ctx context.Context, d *schema.Resource return append(diags, diag.Diagnostic{ Severity: diag.Error, Summary: "Failed to revoke removed privileges", - Detail: fmt.Sprintf("Id: %s\nErr: %s", d.Id(), err.Error()), // TODO: link to the documentation (?). It should describe how the identifier looks. + Detail: fmt.Sprintf("Id: %s\nPrivileges to remove: %v\nError: %s", d.Id(), privilegesToRemove, err.Error()), }) } } @@ -526,14 +511,11 @@ func UpdateGrantPrivilegesToDatabaseRole(ctx context.Context, d *schema.Resource id.Privileges = privilegesAfterChange } - log.Println("UPDATE always_apply has change:", d.HasChange("always_apply")) if d.HasChange("always_apply") { - log.Println("UPDATE always_apply get:", d.Get("always_apply")) id.AlwaysApply = d.Get("always_apply").(bool) } if id.AlwaysApply { - log.Println("UPDATE applying grants") err := client.Grants.GrantPrivilegesToDatabaseRole( ctx, getDatabaseRolePrivilegesFromSchema(d), @@ -546,8 +528,8 @@ func UpdateGrantPrivilegesToDatabaseRole(ctx context.Context, d *schema.Resource if err != nil { return append(diags, diag.Diagnostic{ Severity: diag.Error, - Summary: "An error occurred when granting privileges to database role", - Detail: fmt.Sprintf("Id: %s\nError: %s", id.DatabaseRoleName, err.Error()), + Summary: "Always apply. An error occurred when granting privileges to database role", + Detail: fmt.Sprintf("Id: %s\nDatabase role name: %s\nError: %s", d.Id(), id.DatabaseRoleName, err.Error()), }) } } @@ -567,7 +549,7 @@ func DeleteGrantPrivilegesToDatabaseRole(ctx context.Context, d *schema.Resource return append(diags, diag.Diagnostic{ Severity: diag.Error, Summary: "Failed to parse internal identifier", - Detail: fmt.Sprintf("Id: %s\nErr: %s", d.Id(), err.Error()), // TODO: link to the documentation (?). It should describe how the identifier looks. + Detail: fmt.Sprintf("Id: %s\nError: %s", d.Id(), err.Error()), }) } @@ -582,7 +564,7 @@ func DeleteGrantPrivilegesToDatabaseRole(ctx context.Context, d *schema.Resource return append(diags, diag.Diagnostic{ Severity: diag.Error, Summary: "An error occurred when revoking privileges from database role", - Detail: fmt.Sprintf("Id: %s\nError: %s", id.DatabaseRoleName, err.Error()), + Detail: fmt.Sprintf("Id: %s\nDatabase role name: %s\nError: %s", d.Id(), id.DatabaseRoleName, err.Error()), }) } @@ -592,7 +574,6 @@ func DeleteGrantPrivilegesToDatabaseRole(ctx context.Context, d *schema.Resource } func ReadGrantPrivilegesToDatabaseRole(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics { - log.Println("READ BEGINS") var diags diag.Diagnostics id, err := ParseGrantPrivilegesToDatabaseRoleId(d.Id()) @@ -600,20 +581,26 @@ func ReadGrantPrivilegesToDatabaseRole(ctx context.Context, d *schema.ResourceDa return append(diags, diag.Diagnostic{ Severity: diag.Error, Summary: "Failed to parse internal identifier", - Detail: fmt.Sprintf("Id: %s\nErr: %s", d.Id(), err.Error()), // TODO: link to the documentation (?). It should describe how the identifier looks. + Detail: fmt.Sprintf("Id: %s\nError: %s", d.Id(), err.Error()), }) } - log.Println("READ id.AlwaysApply:", id.AlwaysApply) if id.AlwaysApply { + triggerId, err := uuid.GenerateUUID() + if err != nil { + return append(diags, diag.Diagnostic{ + Severity: diag.Error, + Summary: "Failed to generate UUID", + Detail: fmt.Sprintf("Original error: %s", err.Error()), + }) + } + // Change the value of always_apply_trigger to produce a plan - triggerId, _ := uuid.GenerateUUID() // TODO handle error - log.Printf("READ applying triggerId: %s, was: %s\n", triggerId, d.Get("always_apply_trigger")) if err := d.Set("always_apply_trigger", triggerId); err != nil { return append(diags, diag.Diagnostic{ Severity: diag.Error, Summary: "Error setting always_apply_trigger for database role", - Detail: fmt.Sprintf("Id: %s\nErr: %s", d.Id(), err.Error()), // TODO: link to the documentation (?). It should describe how the identifier looks. + Detail: fmt.Sprintf("Id: %s\nError: %s", d.Id(), err.Error()), }) } } @@ -621,8 +608,9 @@ func ReadGrantPrivilegesToDatabaseRole(ctx context.Context, d *schema.ResourceDa if id.AllPrivileges { return append(diags, diag.Diagnostic{ Severity: diag.Warning, - Summary: "Show with all_privileges option is skipped for now.", // TODO: Details - Detail: "", // TODO: link to the design decisions doc + Summary: "Show with all_privileges option is skipped.", + // TODO: link to the design decisions doc + Detail: "See our document on design decisions for grants: ", }) } @@ -638,46 +626,41 @@ func ReadGrantPrivilegesToDatabaseRole(ctx context.Context, d *schema.ResourceDa return append(diags, diag.Diagnostic{ Severity: diag.Error, Summary: "Failed to retrieve grants", - Detail: fmt.Sprintf("Id: %s\nErr: %s", d.Id(), err.Error()), // TODO: link to the documentation (?). It should describe how the identifier looks. + Detail: fmt.Sprintf("Id: %s\nError: %s", d.Id(), err.Error()), }) } var privileges []string - // TODO: Refactor - check if correct with new conventions - // TODO: Compare privileges for _, grant := range grants { - log.Println("GRANT:", grant) // Accept only DATABASE ROLEs if grant.GrantTo != sdk.ObjectTypeDatabaseRole && grant.GrantedTo != sdk.ObjectTypeDatabaseRole { continue } - // TODO: What about all_privileges, right now we cannot assure that the list of privileges is correct - // Only consider privileges that are already present in the ID so we + // Only consider privileges that are already present in the ID, so we // don't delete privileges managed by other resources. if !slices.Contains(id.Privileges, grant.Privilege) { continue } - // TODO: What about GranteeName with database roles is it fully qualified or not ? if yes, refactor GranteeName. if id.WithGrantOption == grant.GrantOption && id.DatabaseRoleName.Name() == grant.GranteeName.Name() { - // future grants do not have grantedBy, only current grants do. If grantedby - // is an empty string it means the grant could not have been created by terraform + // 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 == false) && 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 + // 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 { privileges = append(privileges, grant.Privilege) } } } - log.Println("PRIVILEGES:", id.Privileges, expandStringList(d.Get("privileges").(*schema.Set).List()), privileges) if err := d.Set("privileges", privileges); err != nil { return append(diags, diag.Diagnostic{ Severity: diag.Error, Summary: "Error setting privileges for database role", - Detail: fmt.Sprintf("Id: %s\nErr: %s", d.Id(), err.Error()), // TODO: link to the documentation (?). It should describe how the identifier looks. + Detail: fmt.Sprintf("Id: %s\nPrivileges: %v\nError: %s", d.Id(), privileges, err.Error()), }) } @@ -712,14 +695,13 @@ func prepareShowGrantsRequest(id GrantPrivilegesToDatabaseRoleId) (*sdk.ShowGran }, } case OnAllSchemasInDatabaseSchemaGrantKind: - // TODO: Document return nil, "", append(diags, diag.Diagnostic{ Severity: diag.Warning, - Summary: "Skipping", - Detail: "TODO", + Summary: "Show with OnAllSchemasInDatabase option is skipped.", + // TODO: link to the design decisions doc + Detail: "See our document on design decisions for grants: ", }) case OnFutureSchemasInDatabaseSchemaGrantKind: - // TODO: show future on database (collisions with other on future triggers and over fetching is ok ?) opts.Future = sdk.Bool(true) opts.In = &sdk.ShowGrantsIn{ Database: data.DatabaseName, @@ -735,11 +717,11 @@ func prepareShowGrantsRequest(id GrantPrivilegesToDatabaseRoleId) (*sdk.ShowGran Object: data.Object, } case OnAllSchemaObjectGrantKind: - // TODO: Document return nil, "", append(diags, diag.Diagnostic{ Severity: diag.Warning, - Summary: "Skipping", - Detail: "TODO", + Summary: "Show with OnAll option is skipped.", + // TODO: link to the design decisions doc + Detail: "See our document on design decisions for grants: ", }) case OnFutureSchemaObjectGrantKind: grantedOn = data.OnAllOrFuture.ObjectNamePlural.Singular() @@ -860,7 +842,7 @@ func getDatabaseRoleGrantOn(d *schema.ResourceData) *sdk.DatabaseRoleGrantOn { switch { case objectTypeOk && objectNameOk: grantOnSchemaObject.SchemaObject = &sdk.Object{ - ObjectType: sdk.ObjectType(objectType), // TODO: Should we validate it or just cast it + ObjectType: sdk.ObjectType(objectType), Name: sdk.NewSchemaObjectIdentifierFromFullyQualifiedName(objectName), } case allOk: diff --git a/pkg/resources/grant_privileges_to_database_role_acceptance_test.go b/pkg/resources/grant_privileges_to_database_role_acceptance_test.go index f8a0ad7ac8..4d5dde8d8c 100644 --- a/pkg/resources/grant_privileges_to_database_role_acceptance_test.go +++ b/pkg/resources/grant_privileges_to_database_role_acceptance_test.go @@ -15,10 +15,6 @@ import ( "testing" ) -// TODO Use cases to cover in acc tests -// - import - check import -// - different paths to parse (on database, on schema, on schema object) - func TestAcc_GrantPrivilegesToDatabaseRole_OnDatabase(t *testing.T) { name := "test_database_role_name" configVariables := config.Variables{ @@ -55,6 +51,13 @@ func TestAcc_GrantPrivilegesToDatabaseRole_OnDatabase(t *testing.T) { resource.TestCheckResourceAttr(resourceName, "with_grant_option", "true"), ), }, + { + ConfigDirectory: config.TestNameDirectory(), + ConfigVariables: configVariables, + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, }, }) } @@ -95,6 +98,13 @@ func TestAcc_GrantPrivilegesToDatabaseRole_OnSchema(t *testing.T) { resource.TestCheckResourceAttr(resourceName, "with_grant_option", "false"), ), }, + { + ConfigDirectory: config.TestNameDirectory(), + ConfigVariables: configVariables, + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, }, }) } @@ -134,6 +144,13 @@ func TestAcc_GrantPrivilegesToDatabaseRole_OnAllSchemasInDatabase(t *testing.T) resource.TestCheckResourceAttr(resourceName, "with_grant_option", "false"), ), }, + { + ConfigDirectory: config.TestNameDirectory(), + ConfigVariables: configVariables, + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, }, }) } @@ -173,6 +190,13 @@ func TestAcc_GrantPrivilegesToDatabaseRole_OnFutureSchemasInDatabase(t *testing. resource.TestCheckResourceAttr(resourceName, "with_grant_option", "false"), ), }, + { + ConfigDirectory: config.TestNameDirectory(), + ConfigVariables: configVariables, + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, }, }) } @@ -216,6 +240,13 @@ func TestAcc_GrantPrivilegesToDatabaseRole_OnSchemaObject_OnObject(t *testing.T) resource.TestCheckResourceAttr(resourceName, "with_grant_option", "false"), ), }, + { + ConfigDirectory: config.TestNameDirectory(), + ConfigVariables: configVariables, + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, }, }) } @@ -289,6 +320,13 @@ func TestAcc_GrantPrivilegesToDatabaseRole_OnSchemaObject_OnAll_InDatabase(t *te resource.TestCheckResourceAttr(resourceName, "with_grant_option", "false"), ), }, + { + ConfigDirectory: config.TestNameDirectory(), + ConfigVariables: configVariables, + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, }, }) } @@ -330,6 +368,13 @@ func TestAcc_GrantPrivilegesToDatabaseRole_OnSchemaObject_OnFuture_InDatabase(t resource.TestCheckResourceAttr(resourceName, "with_grant_option", "false"), ), }, + { + ConfigDirectory: config.TestNameDirectory(), + ConfigVariables: configVariables, + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, }, }) } diff --git a/pkg/resources/grant_privileges_to_database_role_identifier.go b/pkg/resources/grant_privileges_to_database_role_identifier.go index 29f3c9d2b8..cc9ec6284d 100644 --- a/pkg/resources/grant_privileges_to_database_role_identifier.go +++ b/pkg/resources/grant_privileges_to_database_role_identifier.go @@ -8,8 +8,6 @@ import ( "strings" ) -// TODO: Add unit tests for marshaling / unmarshalling - type DatabaseRoleGrantKind string const ( @@ -18,7 +16,6 @@ const ( OnSchemaObjectDatabaseRoleGrantKind DatabaseRoleGrantKind = "OnSchemaObject" ) -// TODO: Move to the shareable file between this and grant_priv_to_role.go file type OnSchemaGrantKind string const ( @@ -27,7 +24,6 @@ const ( OnFutureSchemasInDatabaseSchemaGrantKind OnSchemaGrantKind = "OnFutureSchemasInDatabase" ) -// TODO: Move to the shareable file between this and grant_priv_to_role.go file type OnSchemaObjectGrantKind string const ( @@ -126,7 +122,6 @@ type BulkOperationGrantData struct { Schema *sdk.DatabaseObjectIdentifier } -// TODO: Describe how to put a right identifier in the documentation (so the users will be able to use it in the import) func ParseGrantPrivilegesToDatabaseRoleId(id string) (GrantPrivilegesToDatabaseRoleId, error) { var databaseRoleId GrantPrivilegesToDatabaseRoleId @@ -139,7 +134,7 @@ func ParseGrantPrivilegesToDatabaseRoleId(id string) (GrantPrivilegesToDatabaseR databaseRoleId.WithGrantOption = parts[1] == "true" databaseRoleId.AlwaysApply = parts[2] == "true" privileges := strings.Split(parts[3], ",") - if len(privileges) == 1 && privileges[0] == "ALL" { + if len(privileges) == 1 && (privileges[0] == "ALL" || privileges[0] == "ALL PRIVILEGES") { databaseRoleId.AllPrivileges = true } else { if len(privileges) == 1 && privileges[0] == "" { diff --git a/templates/resources/grant_privileges_to_database_role.md.tmpl b/templates/resources/grant_privileges_to_database_role.md.tmpl new file mode 100644 index 0000000000..05bc4b9c61 --- /dev/null +++ b/templates/resources/grant_privileges_to_database_role.md.tmpl @@ -0,0 +1,93 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "{{.Name}} {{.Type}} - {{.ProviderName}}" +subcategory: "" +description: |- +{{ .Description | plainmarkdown | trimspace | prefixlines " " }} +--- + +# {{.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, 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: + +`terraform import "|||||"` + +where: +- database_role_name - fully qualified identifier +- with_grant_option - boolean +- always_apply - boolean +- privileges - list of privileges, comma separated; to import all_privileges write "ALL" or "ALL PRIVILEGES" +- grant_type - enum +- grant_data - enum data + +It has varying number of parts, depending on grant_type. All the possible types are: + +### OnDatabase +`terraform import "||||OnDatabase|"` + +### OnSchema + +On schema contains inner types for all options. + +#### OnSchema +`terraform import "||||OnSchema|OnSchema|"` + +#### OnAllSchemasInDatabase +`terraform import "||||OnSchema|OnAllSchemasInDatabase|"` + +#### OnFutureSchemasInDatabase +`terraform import "||||OnSchema|OnFutureSchemasInDatabase|"` + +### OnSchemaObject + +On schema object contains inner types for all options. + +#### OnObject +`terraform import "||||OnSchemaObject|OnObject||"` + +#### OnAll + +On all contains inner types for all options. + +##### InDatabase +`terraform import "||||OnSchemaObject|OnAll||InDatabase|"` + +##### InSchema +`terraform import "||||OnSchemaObject|OnAll||InSchema|"` + +#### OnFuture + +On future contains inner types for all options. + +##### InDatabase +`terraform import "||||OnSchemaObject|OnFuture||InDatabase|"` + +##### InSchema +`terraform import "||||OnSchemaObject|OnFuture||InSchema|"` + +### Import examples + +#### Grant all privileges OnDatabase +`terraform import "\"test_db\".\"test_db_role\"|false|false|ALL|OnDatabase|\"test_db\""` + +#### Grant list of privileges OnAllSchemasInDatabase +`terraform import "\"test_db\".\"test_db_role\"|false|false|CREATE TAG,CREATE TABLE|OnSchema|OnAllSchemasInDatabase|\"test_db\""` + +#### Grant list of privileges OnAll tables in schema +`terraform import "\"test_db\".\"test_db_role\"|false|false|SELECT|OnSchemaObject|OnAll|TABLES|InSchema|\"test_db\".\"test_schema\""` + From 870b956efbc49bad0b2d50f7e0572678ae89f485 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Cie=C5=9Blak?= Date: Wed, 3 Jan 2024 14:10:57 +0100 Subject: [PATCH 08/16] wip --- docs/resources/grant_privileges_to_database_role.md | 2 +- pkg/resources/grant_privileges_to_database_role.go | 7 ++++--- .../grant_privileges_to_database_role_acceptance_test.go | 5 +++-- .../grant_privileges_to_database_role_identifier.go | 5 +++-- .../grant_privileges_to_database_role_identifier_test.go | 3 ++- .../test.tf | 6 +++--- .../test.tf | 4 ++-- .../test.tf | 6 +++--- .../test.tf | 4 ++-- .../TestAcc_GrantPrivilegesToDatabaseRole_OnSchema/test.tf | 4 ++-- .../test.tf | 6 +++--- .../test.tf | 6 +++--- .../test.tf | 6 +++--- .../all_privileges/test.tf | 4 ++-- .../privileges/test.tf | 4 ++-- 15 files changed, 38 insertions(+), 34 deletions(-) diff --git a/docs/resources/grant_privileges_to_database_role.md b/docs/resources/grant_privileges_to_database_role.md index 00000c2397..0b8c5bbcc9 100644 --- a/docs/resources/grant_privileges_to_database_role.md +++ b/docs/resources/grant_privileges_to_database_role.md @@ -245,7 +245,7 @@ where: - grant_type - enum - grant_data - enum data -It has varying number of parts, depending on . All the possible types are: +It has varying number of parts, depending on grant_type. All the possible types are: ### OnDatabase `terraform import "||||OnDatabase|"` diff --git a/pkg/resources/grant_privileges_to_database_role.go b/pkg/resources/grant_privileges_to_database_role.go index 44b91bb161..dcfea1edc9 100644 --- a/pkg/resources/grant_privileges_to_database_role.go +++ b/pkg/resources/grant_privileges_to_database_role.go @@ -4,14 +4,15 @@ import ( "context" "database/sql" "fmt" + "slices" + "strings" + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk" "github.com/hashicorp/go-cty/cty" "github.com/hashicorp/go-uuid" "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" - "slices" - "strings" ) // TODO: Imported privileges (after second account will be added) @@ -645,7 +646,7 @@ func ReadGrantPrivilegesToDatabaseRole(ctx context.Context, d *schema.ResourceDa if id.WithGrantOption == grant.GrantOption && id.DatabaseRoleName.Name() == grant.GranteeName.Name() { // 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 == false) && grant.GrantedBy.Name() == "" { + if (opts.Future == nil || !*opts.Future) && grant.GrantedBy.Name() == "" { continue } // grant_on is for future grants, granted_on is for current grants. diff --git a/pkg/resources/grant_privileges_to_database_role_acceptance_test.go b/pkg/resources/grant_privileges_to_database_role_acceptance_test.go index 4d5dde8d8c..c2dc2461b2 100644 --- a/pkg/resources/grant_privileges_to_database_role_acceptance_test.go +++ b/pkg/resources/grant_privileges_to_database_role_acceptance_test.go @@ -4,6 +4,9 @@ import ( "context" "database/sql" "fmt" + "regexp" + "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" @@ -11,8 +14,6 @@ import ( "github.com/hashicorp/terraform-plugin-testing/plancheck" "github.com/hashicorp/terraform-plugin-testing/terraform" "github.com/hashicorp/terraform-plugin-testing/tfversion" - "regexp" - "testing" ) func TestAcc_GrantPrivilegesToDatabaseRole_OnDatabase(t *testing.T) { diff --git a/pkg/resources/grant_privileges_to_database_role_identifier.go b/pkg/resources/grant_privileges_to_database_role_identifier.go index cc9ec6284d..1c4823b840 100644 --- a/pkg/resources/grant_privileges_to_database_role_identifier.go +++ b/pkg/resources/grant_privileges_to_database_role_identifier.go @@ -2,10 +2,11 @@ package resources import ( "fmt" - "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/helpers" - "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk" "strconv" "strings" + + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/helpers" + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk" ) type DatabaseRoleGrantKind string diff --git a/pkg/resources/grant_privileges_to_database_role_identifier_test.go b/pkg/resources/grant_privileges_to_database_role_identifier_test.go index 0c6f6fb908..f9478d812f 100644 --- a/pkg/resources/grant_privileges_to_database_role_identifier_test.go +++ b/pkg/resources/grant_privileges_to_database_role_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 TestParseGrantPrivilegesToDatabaseRoleId(t *testing.T) { diff --git a/pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole_AlwaysApply/test.tf b/pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole_AlwaysApply/test.tf index dda4fa75aa..1c544f39ce 100644 --- a/pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole_AlwaysApply/test.tf +++ b/pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole_AlwaysApply/test.tf @@ -1,6 +1,6 @@ resource "snowflake_grant_privileges_to_database_role" "test" { database_role_name = "\"${var.database}\".\"${var.name}\"" - all_privileges = var.all_privileges - on_database = "\"${var.database}\"" - always_apply = var.always_apply + all_privileges = var.all_privileges + on_database = "\"${var.database}\"" + always_apply = var.always_apply } diff --git a/pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole_OnAllSchemasInDatabase/test.tf b/pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole_OnAllSchemasInDatabase/test.tf index 03aa12212f..ebde56ec07 100644 --- a/pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole_OnAllSchemasInDatabase/test.tf +++ b/pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole_OnAllSchemasInDatabase/test.tf @@ -1,7 +1,7 @@ resource "snowflake_grant_privileges_to_database_role" "test" { database_role_name = "\"${var.database}\".\"${var.name}\"" - privileges = var.privileges - with_grant_option = var.with_grant_option + privileges = var.privileges + with_grant_option = var.with_grant_option on_schema { all_schemas_in_database = "\"${var.database}\"" diff --git a/pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole_OnDatabase/test.tf b/pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole_OnDatabase/test.tf index 5df1cd9eea..83113802be 100644 --- a/pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole_OnDatabase/test.tf +++ b/pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole_OnDatabase/test.tf @@ -1,6 +1,6 @@ resource "snowflake_grant_privileges_to_database_role" "test" { database_role_name = "\"${var.database}\".\"${var.name}\"" - privileges = var.privileges - on_database = "\"${var.database}\"" - with_grant_option = var.with_grant_option + privileges = var.privileges + on_database = "\"${var.database}\"" + with_grant_option = var.with_grant_option } diff --git a/pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole_OnFutureSchemasInDatabase/test.tf b/pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole_OnFutureSchemasInDatabase/test.tf index 321246bba1..dcecbfd504 100644 --- a/pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole_OnFutureSchemasInDatabase/test.tf +++ b/pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole_OnFutureSchemasInDatabase/test.tf @@ -1,7 +1,7 @@ resource "snowflake_grant_privileges_to_database_role" "test" { database_role_name = "\"${var.database}\".\"${var.name}\"" - privileges = var.privileges - with_grant_option = var.with_grant_option + privileges = var.privileges + with_grant_option = var.with_grant_option on_schema { future_schemas_in_database = "\"${var.database}\"" diff --git a/pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole_OnSchema/test.tf b/pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole_OnSchema/test.tf index a1e8f2fd46..ef5613bda3 100644 --- a/pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole_OnSchema/test.tf +++ b/pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole_OnSchema/test.tf @@ -1,7 +1,7 @@ resource "snowflake_grant_privileges_to_database_role" "test" { database_role_name = "\"${var.database}\".\"${var.name}\"" - privileges = var.privileges - with_grant_option = var.with_grant_option + privileges = var.privileges + with_grant_option = var.with_grant_option on_schema { schema_name = "\"${var.database}\".\"${var.schema}\"" diff --git a/pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole_OnSchemaObject_OnAll_InDatabase/test.tf b/pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole_OnSchemaObject_OnAll_InDatabase/test.tf index b927be87f2..230a702d23 100644 --- a/pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole_OnSchemaObject_OnAll_InDatabase/test.tf +++ b/pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole_OnSchemaObject_OnAll_InDatabase/test.tf @@ -1,12 +1,12 @@ resource "snowflake_grant_privileges_to_database_role" "test" { database_role_name = "\"${var.database}\".\"${var.name}\"" - privileges = var.privileges - with_grant_option = var.with_grant_option + privileges = var.privileges + with_grant_option = var.with_grant_option on_schema_object { all { object_type_plural = "TABLES" - in_database = "\"${var.database}\"" + in_database = "\"${var.database}\"" } } } diff --git a/pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole_OnSchemaObject_OnFuture_InDatabase/test.tf b/pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole_OnSchemaObject_OnFuture_InDatabase/test.tf index 3345531127..3463a24a8f 100644 --- a/pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole_OnSchemaObject_OnFuture_InDatabase/test.tf +++ b/pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole_OnSchemaObject_OnFuture_InDatabase/test.tf @@ -1,12 +1,12 @@ resource "snowflake_grant_privileges_to_database_role" "test" { database_role_name = "\"${var.database}\".\"${var.name}\"" - privileges = var.privileges - with_grant_option = var.with_grant_option + privileges = var.privileges + with_grant_option = var.with_grant_option on_schema_object { future { object_type_plural = "TABLES" - in_database = "\"${var.database}\"" + in_database = "\"${var.database}\"" } } } diff --git a/pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole_OnSchemaObject_OnObject/test.tf b/pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole_OnSchemaObject_OnObject/test.tf index 19cd9a72bf..2d480820ae 100644 --- a/pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole_OnSchemaObject_OnObject/test.tf +++ b/pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole_OnSchemaObject_OnObject/test.tf @@ -10,10 +10,10 @@ resource "snowflake_table" "test" { } resource "snowflake_grant_privileges_to_database_role" "test" { - depends_on = [snowflake_table.test] + depends_on = [snowflake_table.test] database_role_name = "\"${var.database}\".\"${var.name}\"" - privileges = var.privileges - with_grant_option = var.with_grant_option + privileges = var.privileges + with_grant_option = var.with_grant_option on_schema_object { object_type = "TABLE" diff --git a/pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole_UpdatePrivileges/all_privileges/test.tf b/pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole_UpdatePrivileges/all_privileges/test.tf index 6613c3bcb3..3fc26c3028 100644 --- a/pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole_UpdatePrivileges/all_privileges/test.tf +++ b/pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole_UpdatePrivileges/all_privileges/test.tf @@ -1,5 +1,5 @@ resource "snowflake_grant_privileges_to_database_role" "test" { database_role_name = "\"${var.database}\".\"${var.name}\"" - all_privileges = var.all_privileges - on_database = "\"${var.database}\"" + all_privileges = var.all_privileges + on_database = "\"${var.database}\"" } diff --git a/pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole_UpdatePrivileges/privileges/test.tf b/pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole_UpdatePrivileges/privileges/test.tf index 22ebfef3aa..c1ea0cb24f 100644 --- a/pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole_UpdatePrivileges/privileges/test.tf +++ b/pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole_UpdatePrivileges/privileges/test.tf @@ -1,5 +1,5 @@ resource "snowflake_grant_privileges_to_database_role" "test" { database_role_name = "\"${var.database}\".\"${var.name}\"" - privileges = var.privileges - on_database = "\"${var.database}\"" + privileges = var.privileges + on_database = "\"${var.database}\"" } From c6942edc86a28adcc9a6721b1fd8960ce05d6ab6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Cie=C5=9Blak?= Date: Wed, 3 Jan 2024 14:36:26 +0100 Subject: [PATCH 09/16] wip --- .../grant_privileges_to_database_role.go | 181 ++++++++++-------- ...ileges_to_database_role_identifier_test.go | 38 +--- pkg/resources/helpers.go | 14 -- pkg/sdk/grants_test.go | 8 +- pkg/sdk/validators.go | 1 - 5 files changed, 112 insertions(+), 130 deletions(-) delete mode 100644 pkg/sdk/validators.go diff --git a/pkg/resources/grant_privileges_to_database_role.go b/pkg/resources/grant_privileges_to_database_role.go index dcfea1edc9..0fd162d41d 100644 --- a/pkg/resources/grant_privileges_to_database_role.go +++ b/pkg/resources/grant_privileges_to_database_role.go @@ -400,8 +400,6 @@ func ImportGrantPrivilegesToDatabaseRole(ctx context.Context, d *schema.Resource } func CreateGrantPrivilegesToDatabaseRole(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics { - var diags diag.Diagnostics - db := meta.(*sql.DB) client := sdk.NewClientFromDB(db) @@ -416,11 +414,13 @@ func CreateGrantPrivilegesToDatabaseRole(ctx context.Context, d *schema.Resource }, ) if err != nil { - return append(diags, diag.Diagnostic{ - Severity: diag.Error, - Summary: "An error occurred when granting privileges to database role", - Detail: fmt.Sprintf("Id: %s\nError: %s", id.DatabaseRoleName, err.Error()), - }) + return diag.Diagnostics{ + diag.Diagnostic{ + Severity: diag.Error, + Summary: "An error occurred when granting privileges to database role", + Detail: fmt.Sprintf("Id: %s\nError: %s", id.DatabaseRoleName, err.Error()), + }, + } } d.SetId(id.String()) @@ -429,17 +429,17 @@ func CreateGrantPrivilegesToDatabaseRole(ctx context.Context, d *schema.Resource } func UpdateGrantPrivilegesToDatabaseRole(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics { - var diags diag.Diagnostics - db := meta.(*sql.DB) client := sdk.NewClientFromDB(db) id, err := ParseGrantPrivilegesToDatabaseRoleId(d.Id()) if err != nil { - return append(diags, diag.Diagnostic{ - Severity: diag.Error, - Summary: "Failed to parse internal identifier", - Detail: fmt.Sprintf("Id: %s\nError: %s", d.Id(), err.Error()), - }) + 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()), + }, + } } if d.HasChange("privileges") { @@ -478,11 +478,13 @@ func UpdateGrantPrivilegesToDatabaseRole(ctx context.Context, d *schema.Resource new(sdk.GrantPrivilegesToDatabaseRoleOptions), ) if err != nil { - return append(diags, 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()), - }) + return diag.Diagnostics{ + 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()), + }, + } } } @@ -501,11 +503,13 @@ func UpdateGrantPrivilegesToDatabaseRole(ctx context.Context, d *schema.Resource new(sdk.RevokePrivilegesFromDatabaseRoleOptions), ) if err != nil { - return append(diags, 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()), - }) + return diag.Diagnostics{ + 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()), + }, + } } } @@ -527,11 +531,13 @@ func UpdateGrantPrivilegesToDatabaseRole(ctx context.Context, d *schema.Resource }, ) if err != nil { - return append(diags, diag.Diagnostic{ - Severity: diag.Error, - Summary: "Always apply. An error occurred when granting privileges to database role", - Detail: fmt.Sprintf("Id: %s\nDatabase role name: %s\nError: %s", d.Id(), id.DatabaseRoleName, err.Error()), - }) + return diag.Diagnostics{ + diag.Diagnostic{ + Severity: diag.Error, + Summary: "Always apply. An error occurred when granting privileges to database role", + Detail: fmt.Sprintf("Id: %s\nDatabase role name: %s\nError: %s", d.Id(), id.DatabaseRoleName, err.Error()), + }, + } } } @@ -541,17 +547,17 @@ func UpdateGrantPrivilegesToDatabaseRole(ctx context.Context, d *schema.Resource } func DeleteGrantPrivilegesToDatabaseRole(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics { - var diags diag.Diagnostics - db := meta.(*sql.DB) client := sdk.NewClientFromDB(db) id, err := ParseGrantPrivilegesToDatabaseRoleId(d.Id()) if err != nil { - return append(diags, diag.Diagnostic{ - Severity: diag.Error, - Summary: "Failed to parse internal identifier", - Detail: fmt.Sprintf("Id: %s\nError: %s", d.Id(), err.Error()), - }) + 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( @@ -562,62 +568,70 @@ func DeleteGrantPrivilegesToDatabaseRole(ctx context.Context, d *schema.Resource &sdk.RevokePrivilegesFromDatabaseRoleOptions{}, ) if err != nil { - return append(diags, 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()), - }) + 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 diags + return nil } func ReadGrantPrivilegesToDatabaseRole(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics { - var diags diag.Diagnostics - id, err := ParseGrantPrivilegesToDatabaseRoleId(d.Id()) if err != nil { - return append(diags, diag.Diagnostic{ - Severity: diag.Error, - Summary: "Failed to parse internal identifier", - Detail: fmt.Sprintf("Id: %s\nError: %s", d.Id(), err.Error()), - }) + 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()), + }, + } } if id.AlwaysApply { triggerId, err := uuid.GenerateUUID() if err != nil { - return append(diags, diag.Diagnostic{ - Severity: diag.Error, - Summary: "Failed to generate UUID", - Detail: fmt.Sprintf("Original error: %s", err.Error()), - }) + return diag.Diagnostics{ + diag.Diagnostic{ + Severity: diag.Error, + Summary: "Failed to generate UUID", + Detail: fmt.Sprintf("Original error: %s", err.Error()), + }, + } } // Change the value of always_apply_trigger to produce a plan if err := d.Set("always_apply_trigger", triggerId); err != nil { - return append(diags, 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()), - }) + return diag.Diagnostics{ + 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()), + }, + } } } if id.AllPrivileges { - return append(diags, diag.Diagnostic{ - Severity: diag.Warning, - Summary: "Show with all_privileges option is skipped.", - // TODO: link to the design decisions doc - Detail: "See our document on design decisions for grants: ", - }) + return diag.Diagnostics{ + diag.Diagnostic{ + Severity: diag.Warning, + Summary: "Show with all_privileges option is skipped.", + // TODO: link to the design decisions doc + Detail: "See our document on design decisions for grants: ", + }, + } } - opts, grantedOn, diagnostics := prepareShowGrantsRequest(id) - if len(diagnostics) != 0 { - return append(diags, diagnostics...) + opts, grantedOn, diags := prepareShowGrantsRequest(id) + if diags != nil && len(diags) != 0 { + return diags } db := meta.(*sql.DB) @@ -671,7 +685,6 @@ func ReadGrantPrivilegesToDatabaseRole(ctx context.Context, d *schema.ResourceDa func prepareShowGrantsRequest(id GrantPrivilegesToDatabaseRoleId) (*sdk.ShowGrantOptions, sdk.ObjectType, diag.Diagnostics) { opts := new(sdk.ShowGrantOptions) var grantedOn sdk.ObjectType - var diags diag.Diagnostics switch id.Kind { case OnDatabaseDatabaseRoleGrantKind: @@ -696,12 +709,14 @@ func prepareShowGrantsRequest(id GrantPrivilegesToDatabaseRoleId) (*sdk.ShowGran }, } case OnAllSchemasInDatabaseSchemaGrantKind: - return nil, "", append(diags, diag.Diagnostic{ - Severity: diag.Warning, - Summary: "Show with OnAllSchemasInDatabase option is skipped.", - // TODO: link to the design decisions doc - Detail: "See our document on design decisions for grants: ", - }) + return nil, "", diag.Diagnostics{ + diag.Diagnostic{ + Severity: diag.Warning, + Summary: "Show with OnAllSchemasInDatabase option is skipped.", + // TODO: link to the design decisions doc + Detail: "See our document on design decisions for grants: ", + }, + } case OnFutureSchemasInDatabaseSchemaGrantKind: opts.Future = sdk.Bool(true) opts.In = &sdk.ShowGrantsIn{ @@ -718,12 +733,14 @@ func prepareShowGrantsRequest(id GrantPrivilegesToDatabaseRoleId) (*sdk.ShowGran Object: data.Object, } case OnAllSchemaObjectGrantKind: - return nil, "", append(diags, diag.Diagnostic{ - Severity: diag.Warning, - Summary: "Show with OnAll option is skipped.", - // TODO: link to the design decisions doc - Detail: "See our document on design decisions for grants: ", - }) + return nil, "", diag.Diagnostics{ + diag.Diagnostic{ + Severity: diag.Warning, + Summary: "Show with OnAll option is skipped.", + // TODO: link to the design decisions doc + Detail: "See our document on design decisions for grants: ", + }, + } case OnFutureSchemaObjectGrantKind: grantedOn = data.OnAllOrFuture.ObjectNamePlural.Singular() opts.Future = sdk.Bool(true) @@ -741,7 +758,7 @@ func prepareShowGrantsRequest(id GrantPrivilegesToDatabaseRoleId) (*sdk.ShowGran } } - return opts, grantedOn, diags + return opts, grantedOn, nil } func getDatabaseRolePrivilegesFromSchema(d *schema.ResourceData) *sdk.DatabaseRoleGrantPrivileges { diff --git a/pkg/resources/grant_privileges_to_database_role_identifier_test.go b/pkg/resources/grant_privileges_to_database_role_identifier_test.go index f9478d812f..ce364b438e 100644 --- a/pkg/resources/grant_privileges_to_database_role_identifier_test.go +++ b/pkg/resources/grant_privileges_to_database_role_identifier_test.go @@ -57,7 +57,7 @@ func TestParseGrantPrivilegesToDatabaseRoleId(t *testing.T) { }, { Name: "grant database role on schema with schema name", - Identifier: `"database-name"."database-role"|false|false|CREATE SCHEMA,USAGE,MONITOR|OnSchema|OnSchema|"database-name"."schema-name"`, // TODO: OnSchema OnSchema x2 + Identifier: `"database-name"."database-role"|false|false|CREATE SCHEMA,USAGE,MONITOR|OnSchema|OnSchema|"database-name"."schema-name"`, Expected: GrantPrivilegesToDatabaseRoleId{ DatabaseRoleName: sdk.NewDatabaseObjectIdentifier("database-name", "database-role"), WithGrantOption: false, @@ -292,11 +292,12 @@ func TestGrantPrivilegesToDatabaseRoleIdString(t *testing.T) { WithGrantOption: true, AllPrivileges: true, Kind: OnDatabaseDatabaseRoleGrantKind, + AlwaysApply: true, Data: &OnDatabaseGrantData{ DatabaseName: sdk.NewAccountObjectIdentifier("database-name"), }, }, - Expected: `"database-name"."role-name"|true|ALL|OnDatabase|"database-name"`, + Expected: `"database-name"."role-name"|true|true|ALL|OnDatabase|"database-name"`, }, { Name: "grant database role on schema on schema", @@ -310,12 +311,7 @@ func TestGrantPrivilegesToDatabaseRoleIdString(t *testing.T) { SchemaName: sdk.Pointer(sdk.NewDatabaseObjectIdentifier("database-name", "schema-name")), }, }, - Expected: `"database-name"."role-name"|false|CREATE SCHEMA,USAGE,MONITOR|OnSchema|OnSchema|"database-name"."schema-name"`, - // TODO: Could be - // OnSchema|schema-name - // OnAllSchemasInDatabase|database-name - // OnFutureSchemasInDatabase|database-name - // instead of repeating OnSchema x2 + Expected: `"database-name"."role-name"|false|false|CREATE SCHEMA,USAGE,MONITOR|OnSchema|OnSchema|"database-name"."schema-name"`, }, { Name: "grant database role on all schemas in database", @@ -329,7 +325,7 @@ func TestGrantPrivilegesToDatabaseRoleIdString(t *testing.T) { DatabaseName: sdk.Pointer(sdk.NewAccountObjectIdentifier("database-name")), }, }, - Expected: `"database-name"."role-name"|false|CREATE SCHEMA,USAGE,MONITOR|OnSchema|OnAllSchemasInDatabase|"database-name"`, + Expected: `"database-name"."role-name"|false|false|CREATE SCHEMA,USAGE,MONITOR|OnSchema|OnAllSchemasInDatabase|"database-name"`, }, { Name: "grant database role on future schemas in database", @@ -343,7 +339,7 @@ func TestGrantPrivilegesToDatabaseRoleIdString(t *testing.T) { DatabaseName: sdk.Pointer(sdk.NewAccountObjectIdentifier("database-name")), }, }, - Expected: `"database-name"."role-name"|false|CREATE SCHEMA,USAGE,MONITOR|OnSchema|OnFutureSchemasInDatabase|"database-name"`, + Expected: `"database-name"."role-name"|false|false|CREATE SCHEMA,USAGE,MONITOR|OnSchema|OnFutureSchemasInDatabase|"database-name"`, }, { Name: "grant database role on schema object on object", @@ -360,23 +356,7 @@ func TestGrantPrivilegesToDatabaseRoleIdString(t *testing.T) { }, }, }, - Expected: `"database-name"."role-name"|false|CREATE SCHEMA,USAGE,MONITOR|OnSchemaObject|OnObject|TABLE|"database-name"."schema-name"."table-name"`, - }, - { - Name: "grant database role on schema object on all tables", - Identifier: GrantPrivilegesToDatabaseRoleId{ - DatabaseRoleName: sdk.NewDatabaseObjectIdentifier("database-name", "role-name"), - WithGrantOption: false, - Privileges: []string{"CREATE SCHEMA", "USAGE", "MONITOR"}, - Kind: OnSchemaObjectDatabaseRoleGrantKind, - Data: &OnSchemaObjectGrantData{ - Kind: OnAllSchemaObjectGrantKind, - OnAllOrFuture: &BulkOperationGrantData{ - ObjectNamePlural: sdk.PluralObjectTypeTables, - }, - }, - }, - Expected: `"database-name"."role-name"|false|CREATE SCHEMA,USAGE,MONITOR|OnSchemaObject|OnAll|TABLES`, + Expected: `"database-name"."role-name"|false|false|CREATE SCHEMA,USAGE,MONITOR|OnSchemaObject|OnObject|TABLE|"database-name"."schema-name"."table-name"`, }, { Name: "grant database role on schema object on all tables in database", @@ -394,7 +374,7 @@ func TestGrantPrivilegesToDatabaseRoleIdString(t *testing.T) { }, }, }, - Expected: `"database-name"."role-name"|false|CREATE SCHEMA,USAGE,MONITOR|OnSchemaObject|OnAll|TABLES|InDatabase|"database-name"`, + Expected: `"database-name"."role-name"|false|false|CREATE SCHEMA,USAGE,MONITOR|OnSchemaObject|OnAll|TABLES|InDatabase|"database-name"`, }, { Name: "grant database role on schema object on all tables in schema", @@ -412,7 +392,7 @@ func TestGrantPrivilegesToDatabaseRoleIdString(t *testing.T) { }, }, }, - Expected: `"database-name"."role-name"|false|CREATE SCHEMA,USAGE,MONITOR|OnSchemaObject|OnAll|TABLES|InSchema|"database-name"."schema-name"`, + Expected: `"database-name"."role-name"|false|false|CREATE SCHEMA,USAGE,MONITOR|OnSchemaObject|OnAll|TABLES|InSchema|"database-name"."schema-name"`, }, } diff --git a/pkg/resources/helpers.go b/pkg/resources/helpers.go index 1a06632263..08890b354f 100644 --- a/pkg/resources/helpers.go +++ b/pkg/resources/helpers.go @@ -125,17 +125,3 @@ func GetPropertyAsPointer[T any](d *schema.ResourceData, property string) *T { } return &typedValue } - -func GetPropertyOrDefault[T any](d *schema.ResourceData, property string) (T, bool) { - value, ok := d.GetOk(property) - if !ok { - var defaultValue T - return defaultValue, false - } - typedValue, ok := value.(T) - if !ok { - var defaultValue T - return defaultValue, false - } - return typedValue, true -} diff --git a/pkg/sdk/grants_test.go b/pkg/sdk/grants_test.go index 6c0e081af8..260c6cca7e 100644 --- a/pkg/sdk/grants_test.go +++ b/pkg/sdk/grants_test.go @@ -351,7 +351,7 @@ func TestGrants_GrantPrivilegesToDatabaseRole(t *testing.T) { t.Run("validation: no privileges set", func(t *testing.T) { opts := defaultGrantsForDb() opts.privileges = &DatabaseRoleGrantPrivileges{} - assertOptsInvalidJoinedErrors(t, opts, errExactlyOneOf("DatabaseRoleGrantPrivileges", "DatabasePrivileges", "SchemaPrivileges", "SchemaObjectPrivileges")) + assertOptsInvalidJoinedErrors(t, opts, errExactlyOneOf("DatabaseRoleGrantPrivileges", "DatabasePrivileges", "SchemaPrivileges", "SchemaObjectPrivileges", "AllPrivileges")) }) t.Run("validation: too many privileges set", func(t *testing.T) { @@ -360,7 +360,7 @@ func TestGrants_GrantPrivilegesToDatabaseRole(t *testing.T) { DatabasePrivileges: []AccountObjectPrivilege{AccountObjectPrivilegeCreateSchema}, SchemaPrivileges: []SchemaPrivilege{SchemaPrivilegeCreateAlert}, } - assertOptsInvalidJoinedErrors(t, opts, errExactlyOneOf("DatabaseRoleGrantPrivileges", "DatabasePrivileges", "SchemaPrivileges", "SchemaObjectPrivileges")) + assertOptsInvalidJoinedErrors(t, opts, errExactlyOneOf("DatabaseRoleGrantPrivileges", "DatabasePrivileges", "SchemaPrivileges", "SchemaObjectPrivileges", "AllPrivileges")) }) t.Run("validation: no on set", func(t *testing.T) { @@ -545,7 +545,7 @@ func TestGrants_RevokePrivilegesFromDatabaseRoleRole(t *testing.T) { t.Run("validation: no privileges set", func(t *testing.T) { opts := defaultGrantsForDb() opts.privileges = &DatabaseRoleGrantPrivileges{} - assertOptsInvalidJoinedErrors(t, opts, errExactlyOneOf("DatabaseRoleGrantPrivileges", "DatabasePrivileges", "SchemaPrivileges", "SchemaObjectPrivileges")) + assertOptsInvalidJoinedErrors(t, opts, errExactlyOneOf("DatabaseRoleGrantPrivileges", "DatabasePrivileges", "SchemaPrivileges", "SchemaObjectPrivileges", "AllPrivileges")) }) t.Run("validation: too many privileges set", func(t *testing.T) { @@ -554,7 +554,7 @@ func TestGrants_RevokePrivilegesFromDatabaseRoleRole(t *testing.T) { DatabasePrivileges: []AccountObjectPrivilege{AccountObjectPrivilegeCreateSchema}, SchemaPrivileges: []SchemaPrivilege{SchemaPrivilegeCreateAlert}, } - assertOptsInvalidJoinedErrors(t, opts, errExactlyOneOf("DatabaseRoleGrantPrivileges", "DatabasePrivileges", "SchemaPrivileges", "SchemaObjectPrivileges")) + assertOptsInvalidJoinedErrors(t, opts, errExactlyOneOf("DatabaseRoleGrantPrivileges", "DatabasePrivileges", "SchemaPrivileges", "SchemaObjectPrivileges", "AllPrivileges")) }) t.Run("validation: nil on set", func(t *testing.T) { diff --git a/pkg/sdk/validators.go b/pkg/sdk/validators.go deleted file mode 100644 index 0919fd1b06..0000000000 --- a/pkg/sdk/validators.go +++ /dev/null @@ -1 +0,0 @@ -package sdk From 2efb1b57d0b5e5b550fbce8922de51ab4a51c274 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Cie=C5=9Blak?= Date: Wed, 3 Jan 2024 15:11:33 +0100 Subject: [PATCH 10/16] wip --- pkg/resources/grant_privileges_to_database_role.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/resources/grant_privileges_to_database_role.go b/pkg/resources/grant_privileges_to_database_role.go index 0fd162d41d..9c227ae2a0 100644 --- a/pkg/resources/grant_privileges_to_database_role.go +++ b/pkg/resources/grant_privileges_to_database_role.go @@ -630,7 +630,7 @@ func ReadGrantPrivilegesToDatabaseRole(ctx context.Context, d *schema.ResourceDa } opts, grantedOn, diags := prepareShowGrantsRequest(id) - if diags != nil && len(diags) != 0 { + if len(diags) != 0 { return diags } From 3a7fe88e5ee3dac235e3e6a51ccf7733294c9cc0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Cie=C5=9Blak?= Date: Thu, 4 Jan 2024 10:14:53 +0100 Subject: [PATCH 11/16] wip --- .../grant_privileges_to_database_role.go | 5 + ...ileges_to_database_role_acceptance_test.go | 104 ++++++++++++++---- 2 files changed, 85 insertions(+), 24 deletions(-) diff --git a/pkg/resources/grant_privileges_to_database_role.go b/pkg/resources/grant_privileges_to_database_role.go index 9c227ae2a0..8247944c74 100644 --- a/pkg/resources/grant_privileges_to_database_role.go +++ b/pkg/resources/grant_privileges_to_database_role.go @@ -442,6 +442,11 @@ func UpdateGrantPrivilegesToDatabaseRole(ctx context.Context, d *schema.Resource } } + if d.HasChange("all_privileges") { + _, now := d.GetChange("all_privileges") + id.AllPrivileges = now.(bool) + } + if d.HasChange("privileges") { before, after := d.GetChange("privileges") privilegesBeforeChange := expandStringList(before.(*schema.Set).List()) diff --git a/pkg/resources/grant_privileges_to_database_role_acceptance_test.go b/pkg/resources/grant_privileges_to_database_role_acceptance_test.go index c2dc2461b2..1c23cca7f2 100644 --- a/pkg/resources/grant_privileges_to_database_role_acceptance_test.go +++ b/pkg/resources/grant_privileges_to_database_role_acceptance_test.go @@ -30,6 +30,9 @@ func TestAcc_GrantPrivilegesToDatabaseRole_OnDatabase(t *testing.T) { } resourceName := "snowflake_grant_privileges_to_database_role.test" + databaseRoleName := sdk.NewDatabaseObjectIdentifier(acc.TestDatabaseName, name).FullyQualifiedName() + databaseName := sdk.NewAccountObjectIdentifier(acc.TestDatabaseName).FullyQualifiedName() + resource.Test(t, resource.TestCase{ ProtoV6ProviderFactories: acc.TestAccProtoV6ProviderFactories, PreCheck: func() { acc.TestAccPreCheck(t) }, @@ -43,13 +46,14 @@ func TestAcc_GrantPrivilegesToDatabaseRole_OnDatabase(t *testing.T) { ConfigDirectory: config.TestNameDirectory(), ConfigVariables: configVariables, Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr(resourceName, "database_role_name", sdk.NewDatabaseObjectIdentifier(acc.TestDatabaseName, name).FullyQualifiedName()), + resource.TestCheckResourceAttr(resourceName, "database_role_name", databaseRoleName), resource.TestCheckResourceAttr(resourceName, "privileges.#", "3"), resource.TestCheckResourceAttr(resourceName, "privileges.0", string(sdk.AccountObjectPrivilegeCreateSchema)), resource.TestCheckResourceAttr(resourceName, "privileges.1", string(sdk.AccountObjectPrivilegeModify)), resource.TestCheckResourceAttr(resourceName, "privileges.2", string(sdk.AccountObjectPrivilegeUsage)), - resource.TestCheckResourceAttr(resourceName, "on_database", sdk.NewAccountObjectIdentifier(acc.TestDatabaseName).FullyQualifiedName()), + resource.TestCheckResourceAttr(resourceName, "on_database", databaseName), resource.TestCheckResourceAttr(resourceName, "with_grant_option", "true"), + resource.TestCheckResourceAttr(resourceName, "id", fmt.Sprintf("%s|true|false|CREATE SCHEMA,MODIFY,USAGE|OnDatabase|%s", databaseRoleName, databaseName)), ), }, { @@ -77,6 +81,9 @@ func TestAcc_GrantPrivilegesToDatabaseRole_OnSchema(t *testing.T) { } resourceName := "snowflake_grant_privileges_to_database_role.test" + databaseRoleName := sdk.NewDatabaseObjectIdentifier(acc.TestDatabaseName, name).FullyQualifiedName() + schemaName := sdk.NewDatabaseObjectIdentifier(acc.TestDatabaseName, acc.TestSchemaName).FullyQualifiedName() + resource.Test(t, resource.TestCase{ ProtoV6ProviderFactories: acc.TestAccProtoV6ProviderFactories, PreCheck: func() { acc.TestAccPreCheck(t) }, @@ -90,13 +97,14 @@ func TestAcc_GrantPrivilegesToDatabaseRole_OnSchema(t *testing.T) { ConfigDirectory: config.TestNameDirectory(), ConfigVariables: configVariables, Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr(resourceName, "database_role_name", sdk.NewDatabaseObjectIdentifier(acc.TestDatabaseName, name).FullyQualifiedName()), + resource.TestCheckResourceAttr(resourceName, "database_role_name", databaseRoleName), resource.TestCheckResourceAttr(resourceName, "privileges.#", "2"), resource.TestCheckResourceAttr(resourceName, "privileges.0", string(sdk.SchemaPrivilegeCreateTable)), resource.TestCheckResourceAttr(resourceName, "privileges.1", string(sdk.SchemaPrivilegeModify)), resource.TestCheckResourceAttr(resourceName, "on_schema.#", "1"), - resource.TestCheckResourceAttr(resourceName, "on_schema.0.schema_name", sdk.NewDatabaseObjectIdentifier(acc.TestDatabaseName, acc.TestSchemaName).FullyQualifiedName()), + resource.TestCheckResourceAttr(resourceName, "on_schema.0.schema_name", schemaName), resource.TestCheckResourceAttr(resourceName, "with_grant_option", "false"), + resource.TestCheckResourceAttr(resourceName, "id", fmt.Sprintf("%s|false|false|CREATE TABLE,MODIFY|OnSchema|OnSchema|%s", databaseRoleName, schemaName)), ), }, { @@ -123,6 +131,9 @@ func TestAcc_GrantPrivilegesToDatabaseRole_OnAllSchemasInDatabase(t *testing.T) } resourceName := "snowflake_grant_privileges_to_database_role.test" + databaseRoleName := sdk.NewDatabaseObjectIdentifier(acc.TestDatabaseName, name).FullyQualifiedName() + databaseName := sdk.NewAccountObjectIdentifier(acc.TestDatabaseName).FullyQualifiedName() + resource.Test(t, resource.TestCase{ ProtoV6ProviderFactories: acc.TestAccProtoV6ProviderFactories, PreCheck: func() { acc.TestAccPreCheck(t) }, @@ -136,13 +147,14 @@ func TestAcc_GrantPrivilegesToDatabaseRole_OnAllSchemasInDatabase(t *testing.T) ConfigDirectory: config.TestNameDirectory(), ConfigVariables: configVariables, Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr(resourceName, "database_role_name", sdk.NewDatabaseObjectIdentifier(acc.TestDatabaseName, name).FullyQualifiedName()), + resource.TestCheckResourceAttr(resourceName, "database_role_name", databaseRoleName), resource.TestCheckResourceAttr(resourceName, "privileges.#", "2"), resource.TestCheckResourceAttr(resourceName, "privileges.0", string(sdk.SchemaPrivilegeCreateTable)), resource.TestCheckResourceAttr(resourceName, "privileges.1", string(sdk.SchemaPrivilegeModify)), resource.TestCheckResourceAttr(resourceName, "on_schema.#", "1"), - resource.TestCheckResourceAttr(resourceName, "on_schema.0.all_schemas_in_database", sdk.NewAccountObjectIdentifier(acc.TestDatabaseName).FullyQualifiedName()), + resource.TestCheckResourceAttr(resourceName, "on_schema.0.all_schemas_in_database", databaseName), resource.TestCheckResourceAttr(resourceName, "with_grant_option", "false"), + resource.TestCheckResourceAttr(resourceName, "id", fmt.Sprintf("%s|false|false|CREATE TABLE,MODIFY|OnSchema|OnAllSchemasInDatabase|%s", databaseRoleName, databaseName)), ), }, { @@ -169,6 +181,9 @@ func TestAcc_GrantPrivilegesToDatabaseRole_OnFutureSchemasInDatabase(t *testing. } resourceName := "snowflake_grant_privileges_to_database_role.test" + databaseRoleName := sdk.NewDatabaseObjectIdentifier(acc.TestDatabaseName, name).FullyQualifiedName() + databaseName := sdk.NewAccountObjectIdentifier(acc.TestDatabaseName).FullyQualifiedName() + resource.Test(t, resource.TestCase{ ProtoV6ProviderFactories: acc.TestAccProtoV6ProviderFactories, PreCheck: func() { acc.TestAccPreCheck(t) }, @@ -182,13 +197,14 @@ func TestAcc_GrantPrivilegesToDatabaseRole_OnFutureSchemasInDatabase(t *testing. ConfigDirectory: config.TestNameDirectory(), ConfigVariables: configVariables, Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr(resourceName, "database_role_name", sdk.NewDatabaseObjectIdentifier(acc.TestDatabaseName, name).FullyQualifiedName()), + resource.TestCheckResourceAttr(resourceName, "database_role_name", databaseRoleName), resource.TestCheckResourceAttr(resourceName, "privileges.#", "2"), resource.TestCheckResourceAttr(resourceName, "privileges.0", string(sdk.SchemaPrivilegeCreateTable)), resource.TestCheckResourceAttr(resourceName, "privileges.1", string(sdk.SchemaPrivilegeModify)), resource.TestCheckResourceAttr(resourceName, "on_schema.#", "1"), - resource.TestCheckResourceAttr(resourceName, "on_schema.0.future_schemas_in_database", sdk.NewAccountObjectIdentifier(acc.TestDatabaseName).FullyQualifiedName()), + resource.TestCheckResourceAttr(resourceName, "on_schema.0.future_schemas_in_database", databaseName), resource.TestCheckResourceAttr(resourceName, "with_grant_option", "false"), + resource.TestCheckResourceAttr(resourceName, "id", fmt.Sprintf("%s|false|false|CREATE TABLE,MODIFY|OnSchema|OnFutureSchemasInDatabase|%s", databaseRoleName, databaseName)), ), }, { @@ -204,10 +220,10 @@ func TestAcc_GrantPrivilegesToDatabaseRole_OnFutureSchemasInDatabase(t *testing. func TestAcc_GrantPrivilegesToDatabaseRole_OnSchemaObject_OnObject(t *testing.T) { name := "test_database_role_name" - tableName := "test_database_role_table_name" + tblName := "test_database_role_table_name" configVariables := config.Variables{ "name": config.StringVariable(name), - "table_name": config.StringVariable(tableName), + "table_name": config.StringVariable(tblName), "privileges": config.ListVariable( config.StringVariable(string(sdk.SchemaObjectPrivilegeInsert)), config.StringVariable(string(sdk.SchemaObjectPrivilegeUpdate)), @@ -218,6 +234,9 @@ func TestAcc_GrantPrivilegesToDatabaseRole_OnSchemaObject_OnObject(t *testing.T) } resourceName := "snowflake_grant_privileges_to_database_role.test" + databaseRoleName := sdk.NewDatabaseObjectIdentifier(acc.TestDatabaseName, name).FullyQualifiedName() + tableName := sdk.NewSchemaObjectIdentifier(acc.TestDatabaseName, acc.TestSchemaName, tblName).FullyQualifiedName() + resource.Test(t, resource.TestCase{ ProtoV6ProviderFactories: acc.TestAccProtoV6ProviderFactories, PreCheck: func() { acc.TestAccPreCheck(t) }, @@ -231,14 +250,15 @@ func TestAcc_GrantPrivilegesToDatabaseRole_OnSchemaObject_OnObject(t *testing.T) ConfigDirectory: config.TestNameDirectory(), ConfigVariables: configVariables, Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr(resourceName, "database_role_name", sdk.NewDatabaseObjectIdentifier(acc.TestDatabaseName, name).FullyQualifiedName()), + resource.TestCheckResourceAttr(resourceName, "database_role_name", databaseRoleName), resource.TestCheckResourceAttr(resourceName, "privileges.#", "2"), resource.TestCheckResourceAttr(resourceName, "privileges.0", string(sdk.SchemaObjectPrivilegeInsert)), resource.TestCheckResourceAttr(resourceName, "privileges.1", string(sdk.SchemaObjectPrivilegeUpdate)), resource.TestCheckResourceAttr(resourceName, "on_schema_object.#", "1"), resource.TestCheckResourceAttr(resourceName, "on_schema_object.0.object_type", string(sdk.ObjectTypeTable)), - resource.TestCheckResourceAttr(resourceName, "on_schema_object.0.object_name", sdk.NewSchemaObjectIdentifier(acc.TestDatabaseName, acc.TestSchemaName, tableName).FullyQualifiedName()), + resource.TestCheckResourceAttr(resourceName, "on_schema_object.0.object_name", tableName), resource.TestCheckResourceAttr(resourceName, "with_grant_option", "false"), + resource.TestCheckResourceAttr(resourceName, "id", fmt.Sprintf("%s|false|false|INSERT,UPDATE|OnSchemaObject|OnObject|TABLE|%s", databaseRoleName, tableName)), ), }, { @@ -297,6 +317,9 @@ func TestAcc_GrantPrivilegesToDatabaseRole_OnSchemaObject_OnAll_InDatabase(t *te } resourceName := "snowflake_grant_privileges_to_database_role.test" + databaseRoleName := sdk.NewDatabaseObjectIdentifier(acc.TestDatabaseName, name).FullyQualifiedName() + databaseName := sdk.NewAccountObjectIdentifier(acc.TestDatabaseName).FullyQualifiedName() + resource.Test(t, resource.TestCase{ ProtoV6ProviderFactories: acc.TestAccProtoV6ProviderFactories, PreCheck: func() { acc.TestAccPreCheck(t) }, @@ -310,15 +333,16 @@ func TestAcc_GrantPrivilegesToDatabaseRole_OnSchemaObject_OnAll_InDatabase(t *te ConfigDirectory: config.TestNameDirectory(), ConfigVariables: configVariables, Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr(resourceName, "database_role_name", sdk.NewDatabaseObjectIdentifier(acc.TestDatabaseName, name).FullyQualifiedName()), + resource.TestCheckResourceAttr(resourceName, "database_role_name", databaseRoleName), resource.TestCheckResourceAttr(resourceName, "privileges.#", "2"), resource.TestCheckResourceAttr(resourceName, "privileges.0", string(sdk.SchemaObjectPrivilegeInsert)), resource.TestCheckResourceAttr(resourceName, "privileges.1", string(sdk.SchemaObjectPrivilegeUpdate)), resource.TestCheckResourceAttr(resourceName, "on_schema_object.#", "1"), resource.TestCheckResourceAttr(resourceName, "on_schema_object.0.all.#", "1"), resource.TestCheckResourceAttr(resourceName, "on_schema_object.0.all.0.object_type_plural", string(sdk.PluralObjectTypeTables)), - resource.TestCheckResourceAttr(resourceName, "on_schema_object.0.all.0.in_database", sdk.NewAccountObjectIdentifier(acc.TestDatabaseName).FullyQualifiedName()), + resource.TestCheckResourceAttr(resourceName, "on_schema_object.0.all.0.in_database", databaseName), resource.TestCheckResourceAttr(resourceName, "with_grant_option", "false"), + resource.TestCheckResourceAttr(resourceName, "id", fmt.Sprintf("%s|false|false|INSERT,UPDATE|OnSchemaObject|OnAll|TABLES|InDatabase|%s", databaseRoleName, databaseName)), ), }, { @@ -345,6 +369,9 @@ func TestAcc_GrantPrivilegesToDatabaseRole_OnSchemaObject_OnFuture_InDatabase(t } resourceName := "snowflake_grant_privileges_to_database_role.test" + databaseRoleName := sdk.NewDatabaseObjectIdentifier(acc.TestDatabaseName, name).FullyQualifiedName() + databaseName := sdk.NewAccountObjectIdentifier(acc.TestDatabaseName).FullyQualifiedName() + resource.Test(t, resource.TestCase{ ProtoV6ProviderFactories: acc.TestAccProtoV6ProviderFactories, PreCheck: func() { acc.TestAccPreCheck(t) }, @@ -358,15 +385,16 @@ func TestAcc_GrantPrivilegesToDatabaseRole_OnSchemaObject_OnFuture_InDatabase(t ConfigDirectory: config.TestNameDirectory(), ConfigVariables: configVariables, Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr(resourceName, "database_role_name", sdk.NewDatabaseObjectIdentifier(acc.TestDatabaseName, name).FullyQualifiedName()), + resource.TestCheckResourceAttr(resourceName, "database_role_name", databaseRoleName), resource.TestCheckResourceAttr(resourceName, "privileges.#", "2"), resource.TestCheckResourceAttr(resourceName, "privileges.0", string(sdk.SchemaObjectPrivilegeInsert)), resource.TestCheckResourceAttr(resourceName, "privileges.1", string(sdk.SchemaObjectPrivilegeUpdate)), resource.TestCheckResourceAttr(resourceName, "on_schema_object.#", "1"), resource.TestCheckResourceAttr(resourceName, "on_schema_object.0.future.#", "1"), resource.TestCheckResourceAttr(resourceName, "on_schema_object.0.future.0.object_type_plural", string(sdk.PluralObjectTypeTables)), - resource.TestCheckResourceAttr(resourceName, "on_schema_object.0.future.0.in_database", sdk.NewAccountObjectIdentifier(acc.TestDatabaseName).FullyQualifiedName()), + resource.TestCheckResourceAttr(resourceName, "on_schema_object.0.future.0.in_database", databaseName), resource.TestCheckResourceAttr(resourceName, "with_grant_option", "false"), + resource.TestCheckResourceAttr(resourceName, "id", fmt.Sprintf("%s|false|false|INSERT,UPDATE|OnSchemaObject|OnFuture|TABLES|InDatabase|%s", databaseRoleName, databaseName)), ), }, { @@ -401,6 +429,9 @@ func TestAcc_GrantPrivilegesToDatabaseRole_UpdatePrivileges(t *testing.T) { } resourceName := "snowflake_grant_privileges_to_database_role.test" + databaseRoleName := sdk.NewDatabaseObjectIdentifier(acc.TestDatabaseName, name).FullyQualifiedName() + databaseName := sdk.NewAccountObjectIdentifier(acc.TestDatabaseName).FullyQualifiedName() + resource.Test(t, resource.TestCase{ ProtoV6ProviderFactories: acc.TestAccProtoV6ProviderFactories, PreCheck: func() { acc.TestAccPreCheck(t) }, @@ -421,6 +452,7 @@ func TestAcc_GrantPrivilegesToDatabaseRole_UpdatePrivileges(t *testing.T) { resource.TestCheckResourceAttr(resourceName, "privileges.#", "2"), resource.TestCheckResourceAttr(resourceName, "privileges.0", string(sdk.AccountObjectPrivilegeCreateSchema)), resource.TestCheckResourceAttr(resourceName, "privileges.1", string(sdk.AccountObjectPrivilegeModify)), + resource.TestCheckResourceAttr(resourceName, "id", fmt.Sprintf("%s|false|false|CREATE SCHEMA,MODIFY|OnDatabase|%s", databaseRoleName, databaseName)), ), }, { @@ -436,6 +468,7 @@ func TestAcc_GrantPrivilegesToDatabaseRole_UpdatePrivileges(t *testing.T) { resource.TestCheckResourceAttr(resourceName, "privileges.0", string(sdk.AccountObjectPrivilegeCreateSchema)), resource.TestCheckResourceAttr(resourceName, "privileges.1", string(sdk.AccountObjectPrivilegeMonitor)), resource.TestCheckResourceAttr(resourceName, "privileges.2", string(sdk.AccountObjectPrivilegeUsage)), + resource.TestCheckResourceAttr(resourceName, "id", fmt.Sprintf("%s|false|false|CREATE SCHEMA,USAGE,MONITOR|OnDatabase|%s", databaseRoleName, databaseName)), ), }, { @@ -444,6 +477,7 @@ func TestAcc_GrantPrivilegesToDatabaseRole_UpdatePrivileges(t *testing.T) { Check: resource.ComposeTestCheckFunc( resource.TestCheckResourceAttr(resourceName, "all_privileges", "true"), resource.TestCheckResourceAttr(resourceName, "privileges.#", "0"), + resource.TestCheckResourceAttr(resourceName, "id", fmt.Sprintf("%s|false|false|ALL|OnDatabase|%s", databaseRoleName, databaseName)), ), }, { @@ -457,6 +491,7 @@ func TestAcc_GrantPrivilegesToDatabaseRole_UpdatePrivileges(t *testing.T) { resource.TestCheckResourceAttr(resourceName, "privileges.#", "2"), resource.TestCheckResourceAttr(resourceName, "privileges.0", string(sdk.AccountObjectPrivilegeModify)), resource.TestCheckResourceAttr(resourceName, "privileges.1", string(sdk.AccountObjectPrivilegeMonitor)), + resource.TestCheckResourceAttr(resourceName, "id", fmt.Sprintf("%s|false|false|MODIFY,MONITOR|OnDatabase|%s", databaseRoleName, databaseName)), ), }, }, @@ -475,6 +510,9 @@ func TestAcc_GrantPrivilegesToDatabaseRole_AlwaysApply(t *testing.T) { } resourceName := "snowflake_grant_privileges_to_database_role.test" + databaseRoleName := sdk.NewDatabaseObjectIdentifier(acc.TestDatabaseName, name).FullyQualifiedName() + databaseName := sdk.NewAccountObjectIdentifier(acc.TestDatabaseName).FullyQualifiedName() + resource.Test(t, resource.TestCase{ ProtoV6ProviderFactories: acc.TestAccProtoV6ProviderFactories, PreCheck: func() { acc.TestAccPreCheck(t) }, @@ -492,7 +530,10 @@ func TestAcc_GrantPrivilegesToDatabaseRole_AlwaysApply(t *testing.T) { plancheck.ExpectEmptyPlan(), }, }, - Check: resource.TestCheckResourceAttr(resourceName, "always_apply", "false"), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "always_apply", "false"), + resource.TestCheckResourceAttr(resourceName, "id", fmt.Sprintf("%s|false|false|ALL|OnDatabase|%s", databaseRoleName, databaseName)), + ), }, { ConfigDirectory: config.TestNameDirectory(), @@ -502,12 +543,18 @@ func TestAcc_GrantPrivilegesToDatabaseRole_AlwaysApply(t *testing.T) { plancheck.ExpectEmptyPlan(), }, }, - Check: resource.TestCheckResourceAttr(resourceName, "always_apply", "false"), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "always_apply", "false"), + resource.TestCheckResourceAttr(resourceName, "id", fmt.Sprintf("%s|false|false|ALL|OnDatabase|%s", databaseRoleName, databaseName)), + ), }, { - ConfigDirectory: config.TestNameDirectory(), - ConfigVariables: configVariables(true), - Check: resource.TestCheckResourceAttr(resourceName, "always_apply", "true"), + ConfigDirectory: config.TestNameDirectory(), + ConfigVariables: configVariables(true), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "always_apply", "true"), + resource.TestCheckResourceAttr(resourceName, "id", fmt.Sprintf("%s|false|true|ALL|OnDatabase|%s", databaseRoleName, databaseName)), + ), ExpectNonEmptyPlan: true, }, { @@ -518,7 +565,10 @@ func TestAcc_GrantPrivilegesToDatabaseRole_AlwaysApply(t *testing.T) { plancheck.ExpectNonEmptyPlan(), }, }, - Check: resource.TestCheckResourceAttr(resourceName, "always_apply", "true"), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "always_apply", "true"), + resource.TestCheckResourceAttr(resourceName, "id", fmt.Sprintf("%s|false|true|ALL|OnDatabase|%s", databaseRoleName, databaseName)), + ), ExpectNonEmptyPlan: true, }, { @@ -529,7 +579,10 @@ func TestAcc_GrantPrivilegesToDatabaseRole_AlwaysApply(t *testing.T) { plancheck.ExpectNonEmptyPlan(), }, }, - Check: resource.TestCheckResourceAttr(resourceName, "always_apply", "true"), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "always_apply", "true"), + resource.TestCheckResourceAttr(resourceName, "id", fmt.Sprintf("%s|false|true|ALL|OnDatabase|%s", databaseRoleName, databaseName)), + ), ExpectNonEmptyPlan: true, }, { @@ -540,7 +593,10 @@ func TestAcc_GrantPrivilegesToDatabaseRole_AlwaysApply(t *testing.T) { plancheck.ExpectEmptyPlan(), }, }, - Check: resource.TestCheckResourceAttr(resourceName, "always_apply", "false"), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "always_apply", "false"), + resource.TestCheckResourceAttr(resourceName, "id", fmt.Sprintf("%s|false|false|ALL|OnDatabase|%s", databaseRoleName, databaseName)), + ), }, }, }) From 6ace2fcafbbe802af9db727078b37a28d7a099fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Cie=C5=9Blak?= Date: Thu, 4 Jan 2024 17:30:30 +0100 Subject: [PATCH 12/16] changes after review --- .../grant_privileges_to_database_role.go | 270 +++++++++++------- ...ileges_to_database_role_acceptance_test.go | 161 ++++++++++- ..._privileges_to_database_role_identifier.go | 27 +- ...ileges_to_database_role_identifier_test.go | 25 ++ .../test.tf | 4 +- .../test.tf | 9 + .../all_privileges/test.tf | 5 + .../all_privileges/variables.tf | 11 + .../on_schema/test.tf | 13 + .../on_schema/variables.tf | 15 + .../privileges/test.tf | 5 + .../privileges/variables.tf | 11 + pkg/sdk/object_types.go | 6 + pkg/sdk/parameters_test.go | 17 ++ .../grant_privileges_to_database_role.md.tmpl | 3 + 15 files changed, 469 insertions(+), 113 deletions(-) create mode 100644 pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole_OnSchema_ExactlyOneOf/test.tf create mode 100644 pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole_UpdatePrivileges_SnowflakeChecked/all_privileges/test.tf create mode 100644 pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole_UpdatePrivileges_SnowflakeChecked/all_privileges/variables.tf create mode 100644 pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole_UpdatePrivileges_SnowflakeChecked/on_schema/test.tf create mode 100644 pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole_UpdatePrivileges_SnowflakeChecked/on_schema/variables.tf create mode 100644 pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole_UpdatePrivileges_SnowflakeChecked/privileges/test.tf create mode 100644 pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole_UpdatePrivileges_SnowflakeChecked/privileges/variables.tf diff --git a/pkg/resources/grant_privileges_to_database_role.go b/pkg/resources/grant_privileges_to_database_role.go index 8247944c74..cc1e7b449f 100644 --- a/pkg/resources/grant_privileges_to_database_role.go +++ b/pkg/resources/grant_privileges_to_database_role.go @@ -15,7 +15,7 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" ) -// TODO: Imported privileges (after second account will be added) +// TODO: Handle IMPORTED PRIVILEGES privilege (after second account will be added - SNOW-976501) var grantPrivilegesToDatabaseRoleSchema = map[string]*schema.Schema{ "database_role_name": { @@ -71,7 +71,7 @@ var grantPrivilegesToDatabaseRoleSchema = map[string]*schema.Schema{ Type: schema.TypeString, Optional: true, ForceNew: true, - Description: "The fully qualified name of the database on which privileges will be granted. If the identifier is not fully qualified (in the form of .≤database_role_name>), the command looks for the database role in the current database for the session. All privileges are limited to the database that contains the database role, as well as other objects in the same database.", + Description: "The fully qualified name of the database on which privileges will be granted.", ValidateDiagFunc: IsValidIdentifier[sdk.AccountObjectIdentifier](), ExactlyOneOf: []string{ "on_database", @@ -157,29 +157,30 @@ var grantPrivilegesToDatabaseRoleSchema = map[string]*schema.Schema{ "on_schema_object.0.future", }, ValidateFunc: validation.StringInSlice([]string{ - "ALERT", - "DYNAMIC TABLE", - "EVENT TABLE", - "FILE FORMAT", - "FUNCTION", - "PROCEDURE", - "SECRET", - "SEQUENCE", "PIPE", - "MASKING POLICY", - "PASSWORD POLICY", - "ROW ACCESS POLICY", - "SESSION POLICY", - "TAG", - "STAGE", - "STREAM", - "TABLE", - "EXTERNAL TABLE", - "TASK", - "VIEW", - "MATERIALIZED VIEW", - "NETWORK RULE", - "PACKAGES POLICY", - "ICEBERG TABLE", + sdk.ObjectTypeAlert.String(), + sdk.ObjectTypeDynamicTable.String(), + sdk.ObjectTypeEventTable.String(), + sdk.ObjectTypeFileFormat.String(), + sdk.ObjectTypeFunction.String(), + sdk.ObjectTypeProcedure.String(), + sdk.ObjectTypeSecret.String(), + sdk.ObjectTypeSequence.String(), + sdk.ObjectTypePipe.String(), + sdk.ObjectTypeMaskingPolicy.String(), + sdk.ObjectTypePasswordPolicy.String(), + sdk.ObjectTypeRowAccessPolicy.String(), + sdk.ObjectTypeSessionPolicy.String(), + sdk.ObjectTypeTag.String(), + sdk.ObjectTypeStage.String(), + sdk.ObjectTypeStream.String(), + sdk.ObjectTypeTable.String(), + sdk.ObjectTypeExternalTable.String(), + sdk.ObjectTypeTask.String(), + sdk.ObjectTypeView.String(), + sdk.ObjectTypeMaterializedView.String(), + sdk.ObjectTypeNetworkRule.String(), + sdk.ObjectTypePackagesPolicy.String(), + sdk.ObjectTypeIcebergTable.String(), }, true), }, "object_name": { @@ -245,30 +246,30 @@ var grantPrivilegesOnDatabaseRoleBulkOperationSchema = map[string]*schema.Schema ForceNew: true, Description: "The plural object type of the schema object on which privileges will be granted. Valid values are: ALERTS | DYNAMIC TABLES | EVENT TABLES | FILE FORMATS | FUNCTIONS | PROCEDURES | SECRETS | SEQUENCES | PIPES | MASKING POLICIES | PASSWORD POLICIES | ROW ACCESS POLICIES | SESSION POLICIES | TAGS | STAGES | STREAMS | TABLES | EXTERNAL TABLES | TASKS | VIEWS | MATERIALIZED VIEWS | NETWORK RULES | PACKAGES POLICIES | ICEBERG TABLES", ValidateFunc: validation.StringInSlice([]string{ - "ALERTS", - "DYNAMIC TABLES", - "EVENT TABLES", - "FILE FORMATS", - "FUNCTIONS", - "PROCEDURES", - "SECRETS", - "SEQUENCES", - "PIPES", - "MASKING POLICIES", - "PASSWORD POLICIES", - "ROW ACCESS POLICIES", - "SESSION POLICIES", - "TAGS", - "STAGES", - "STREAMS", - "TABLES", - "EXTERNAL TABLES", - "TASKS", - "VIEWS", - "MATERIALIZED VIEWS", - "NETWORK RULES", - "PACKAGES POLICIES", - "ICEBERG TABLES", + sdk.PluralObjectTypeAlerts.String(), + sdk.PluralObjectTypeDynamicTables.String(), + sdk.PluralObjectTypeEventTables.String(), + sdk.PluralObjectTypeFileFormats.String(), + sdk.PluralObjectTypeFunctions.String(), + sdk.PluralObjectTypeProcedures.String(), + sdk.PluralObjectTypeSecrets.String(), + sdk.PluralObjectTypeSequences.String(), + sdk.PluralObjectTypePipes.String(), + sdk.PluralObjectTypeMaskingPolicies.String(), + sdk.PluralObjectTypePasswordPolicies.String(), + sdk.PluralObjectTypeRowAccessPolicies.String(), + sdk.PluralObjectTypeSessionPolicies.String(), + sdk.PluralObjectTypeTags.String(), + sdk.PluralObjectTypeStages.String(), + sdk.PluralObjectTypeStreams.String(), + sdk.PluralObjectTypeTables.String(), + sdk.PluralObjectTypeExternalTables.String(), + sdk.PluralObjectTypeTasks.String(), + sdk.PluralObjectTypeViews.String(), + sdk.PluralObjectTypeMaterializedViews.String(), + sdk.PluralObjectTypeNetworkRules.String(), + sdk.PluralObjectTypePackagesPolicies.String(), + sdk.PluralObjectTypeIcebergTables.String(), }, true), }, "in_database": { @@ -292,7 +293,7 @@ func isNotOwnershipGrant() func(value any, path cty.Path) diag.Diagnostics { diags = append(diags, diag.Diagnostic{ Severity: diag.Error, Summary: "Unsupported privilege 'OWNERSHIP'", - // TODO: Change when a new resource for granting ownership will be available + // TODO: Change when a new resource for granting ownership will be available (SNOW-991423) Detail: "Granting ownership is only allowed in dedicated resources (snowflake_user_ownership_grant, snowflake_role_ownership_grant)", AttributePath: nil, }) @@ -418,7 +419,7 @@ func CreateGrantPrivilegesToDatabaseRole(ctx context.Context, d *schema.Resource diag.Diagnostic{ Severity: diag.Error, Summary: "An error occurred when granting privileges to database role", - Detail: fmt.Sprintf("Id: %s\nError: %s", id.DatabaseRoleName, err.Error()), + Detail: fmt.Sprintf("Id: %s\nDatabase role name: %s\nError: %s", id.String(), id.DatabaseRoleName, err.Error()), }, } } @@ -443,82 +444,141 @@ func UpdateGrantPrivilegesToDatabaseRole(ctx context.Context, d *schema.Resource } if d.HasChange("all_privileges") { - _, now := d.GetChange("all_privileges") - id.AllPrivileges = now.(bool) + _, allPrivileges := d.GetChange("all_privileges") + + if !allPrivileges.(bool) { + err = client.Grants.RevokePrivilegesFromDatabaseRole(ctx, &sdk.DatabaseRoleGrantPrivileges{ + AllPrivileges: sdk.Bool(true), + }, + getDatabaseRoleGrantOn(d), + id.DatabaseRoleName, + new(sdk.RevokePrivilegesFromDatabaseRoleOptions), + ) + + if err != nil { + return diag.Diagnostics{ + diag.Diagnostic{ + Severity: diag.Error, + Summary: "Failed to revoke all privileges", + Detail: fmt.Sprintf("Id: %s\nError: %s", d.Id(), err.Error()), + }, + } + } + } + + id.AllPrivileges = allPrivileges.(bool) } if d.HasChange("privileges") { - before, after := d.GetChange("privileges") - privilegesBeforeChange := expandStringList(before.(*schema.Set).List()) - privilegesAfterChange := expandStringList(after.(*schema.Set).List()) - - var privilegesToAdd, privilegesToRemove []string + shouldGrantAndRevoke := true - for _, privilegeBeforeChange := range privilegesBeforeChange { - if !slices.Contains(privilegesAfterChange, privilegeBeforeChange) { - privilegesToRemove = append(privilegesToRemove, privilegeBeforeChange) + // Skip if all_privileges was set to true + if d.HasChange("all_privileges") { + if _, allPrivileges := d.GetChange("all_privileges"); allPrivileges.(bool) { + shouldGrantAndRevoke = false + //id.Privileges = []string{} } } - for _, privilegeAfterChange := range privilegesAfterChange { - if !slices.Contains(privilegesBeforeChange, privilegeAfterChange) { - privilegesToAdd = append(privilegesToAdd, privilegeAfterChange) + if shouldGrantAndRevoke { + before, after := d.GetChange("privileges") + privilegesBeforeChange := expandStringList(before.(*schema.Set).List()) + privilegesAfterChange := expandStringList(after.(*schema.Set).List()) + + var privilegesToAdd, privilegesToRemove []string + + for _, privilegeBeforeChange := range privilegesBeforeChange { + if !slices.Contains(privilegesAfterChange, privilegeBeforeChange) { + privilegesToRemove = append(privilegesToRemove, privilegeBeforeChange) + } } - } - grantOn := getDatabaseRoleGrantOn(d) + for _, privilegeAfterChange := range privilegesAfterChange { + if !slices.Contains(privilegesBeforeChange, privilegeAfterChange) { + privilegesToAdd = append(privilegesToAdd, privilegeAfterChange) + } + } - if len(privilegesToAdd) > 0 { - err = client.Grants.GrantPrivilegesToDatabaseRole( - ctx, - getDatabaseRolePrivileges( - false, - privilegesToAdd, - id.Kind == OnDatabaseDatabaseRoleGrantKind, - id.Kind == OnSchemaDatabaseRoleGrantKind, - id.Kind == OnSchemaObjectDatabaseRoleGrantKind, - ), - grantOn, - id.DatabaseRoleName, - new(sdk.GrantPrivilegesToDatabaseRoleOptions), - ) - if err != nil { - return diag.Diagnostics{ - 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()), - }, + grantOn := getDatabaseRoleGrantOn(d) + + if len(privilegesToAdd) > 0 { + err = client.Grants.GrantPrivilegesToDatabaseRole( + ctx, + getDatabaseRolePrivileges( + false, + privilegesToAdd, + id.Kind == OnDatabaseDatabaseRoleGrantKind, + id.Kind == OnSchemaDatabaseRoleGrantKind, + id.Kind == OnSchemaObjectDatabaseRoleGrantKind, + ), + grantOn, + id.DatabaseRoleName, + new(sdk.GrantPrivilegesToDatabaseRoleOptions), + ) + if err != nil { + return diag.Diagnostics{ + 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()), + }, + } + } + } + + if len(privilegesToRemove) > 0 { + err = client.Grants.RevokePrivilegesFromDatabaseRole( + ctx, + getDatabaseRolePrivileges( + false, + privilegesToRemove, + id.Kind == OnDatabaseDatabaseRoleGrantKind, + id.Kind == OnSchemaDatabaseRoleGrantKind, + id.Kind == OnSchemaObjectDatabaseRoleGrantKind, + ), + grantOn, + id.DatabaseRoleName, + new(sdk.RevokePrivilegesFromDatabaseRoleOptions), + ) + if err != nil { + return diag.Diagnostics{ + 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()), + }, + } } } + + id.Privileges = privilegesAfterChange } + } + + if d.HasChange("all_privileges") { + _, allPrivileges := d.GetChange("all_privileges") - if len(privilegesToRemove) > 0 { - err = client.Grants.RevokePrivilegesFromDatabaseRole( - ctx, - getDatabaseRolePrivileges( - false, - privilegesToRemove, - id.Kind == OnDatabaseDatabaseRoleGrantKind, - id.Kind == OnSchemaDatabaseRoleGrantKind, - id.Kind == OnSchemaObjectDatabaseRoleGrantKind, - ), - grantOn, + if allPrivileges.(bool) { + err = client.Grants.GrantPrivilegesToDatabaseRole(ctx, &sdk.DatabaseRoleGrantPrivileges{ + AllPrivileges: sdk.Bool(true), + }, + getDatabaseRoleGrantOn(d), id.DatabaseRoleName, - new(sdk.RevokePrivilegesFromDatabaseRoleOptions), + new(sdk.GrantPrivilegesToDatabaseRoleOptions), ) + if err != nil { return diag.Diagnostics{ 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()), + Summary: "Failed to grant all privileges", + Detail: fmt.Sprintf("Id: %s\nError: %s", d.Id(), err.Error()), }, } } } - id.Privileges = privilegesAfterChange + id.AllPrivileges = allPrivileges.(bool) } if d.HasChange("always_apply") { @@ -628,7 +688,7 @@ func ReadGrantPrivilegesToDatabaseRole(ctx context.Context, d *schema.ResourceDa diag.Diagnostic{ Severity: diag.Warning, Summary: "Show with all_privileges option is skipped.", - // TODO: link to the design decisions doc + // TODO: link to the design decisions doc (SNOW-990811) Detail: "See our document on design decisions for grants: ", }, } @@ -718,7 +778,7 @@ func prepareShowGrantsRequest(id GrantPrivilegesToDatabaseRoleId) (*sdk.ShowGran diag.Diagnostic{ Severity: diag.Warning, Summary: "Show with OnAllSchemasInDatabase option is skipped.", - // TODO: link to the design decisions doc + // TODO: link to the design decisions doc (SNOW-990811) Detail: "See our document on design decisions for grants: ", }, } @@ -742,7 +802,7 @@ func prepareShowGrantsRequest(id GrantPrivilegesToDatabaseRoleId) (*sdk.ShowGran diag.Diagnostic{ Severity: diag.Warning, Summary: "Show with OnAll option is skipped.", - // TODO: link to the design decisions doc + // TODO: link to the design decisions doc (SNOW-990811) Detail: "See our document on design decisions for grants: ", }, } diff --git a/pkg/resources/grant_privileges_to_database_role_acceptance_test.go b/pkg/resources/grant_privileges_to_database_role_acceptance_test.go index 1c23cca7f2..0b69ffe49a 100644 --- a/pkg/resources/grant_privileges_to_database_role_acceptance_test.go +++ b/pkg/resources/grant_privileges_to_database_role_acceptance_test.go @@ -5,6 +5,7 @@ import ( "database/sql" "fmt" "regexp" + "slices" "testing" acc "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/acceptance" @@ -118,6 +119,24 @@ func TestAcc_GrantPrivilegesToDatabaseRole_OnSchema(t *testing.T) { }) } +func TestAcc_GrantPrivilegesToDatabaseRole_OnSchema_ExactlyOneOf(t *testing.T) { + resource.Test(t, resource.TestCase{ + ProtoV6ProviderFactories: acc.TestAccProtoV6ProviderFactories, + PreCheck: func() { acc.TestAccPreCheck(t) }, + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.RequireAbove(tfversion.Version1_5_0), + }, + CheckDestroy: testAccCheckDatabaseRolePrivilegesRevoked, + Steps: []resource.TestStep{ + { + ConfigDirectory: config.TestNameDirectory(), + PlanOnly: true, + ExpectError: regexp.MustCompile("Error: Invalid combination of arguments"), + }, + }, + }) +} + func TestAcc_GrantPrivilegesToDatabaseRole_OnAllSchemasInDatabase(t *testing.T) { name := "test_database_role_name" configVariables := config.Variables{ @@ -202,8 +221,7 @@ func TestAcc_GrantPrivilegesToDatabaseRole_OnFutureSchemasInDatabase(t *testing. resource.TestCheckResourceAttr(resourceName, "privileges.0", string(sdk.SchemaPrivilegeCreateTable)), resource.TestCheckResourceAttr(resourceName, "privileges.1", string(sdk.SchemaPrivilegeModify)), resource.TestCheckResourceAttr(resourceName, "on_schema.#", "1"), - resource.TestCheckResourceAttr(resourceName, "on_schema.0.future_schemas_in_database", databaseName), - resource.TestCheckResourceAttr(resourceName, "with_grant_option", "false"), + resource.TestCheckResourceAttr(resourceName, "on_schema.0.future_schemas_in_database", databaseName), resource.TestCheckResourceAttr(resourceName, "with_grant_option", "false"), resource.TestCheckResourceAttr(resourceName, "id", fmt.Sprintf("%s|false|false|CREATE TABLE,MODIFY|OnSchema|OnFutureSchemasInDatabase|%s", databaseRoleName, databaseName)), ), }, @@ -498,6 +516,93 @@ func TestAcc_GrantPrivilegesToDatabaseRole_UpdatePrivileges(t *testing.T) { }) } +func TestAcc_GrantPrivilegesToDatabaseRole_UpdatePrivileges_SnowflakeChecked(t *testing.T) { + name := "test_database_role_name" + schemaName := "test_database_role_schema_name" + configVariables := func(allPrivileges bool, privileges []string, schemaName string) config.Variables { + configVariables := config.Variables{ + "name": config.StringVariable(name), + "database": config.StringVariable(acc.TestDatabaseName), + } + if allPrivileges { + configVariables["all_privileges"] = config.BoolVariable(allPrivileges) + } + if len(privileges) > 0 { + configPrivileges := make([]config.Variable, len(privileges)) + for i, privilege := range privileges { + configPrivileges[i] = config.StringVariable(privilege) + } + configVariables["privileges"] = config.ListVariable(configPrivileges...) + } + if len(schemaName) > 0 { + configVariables["schema_name"] = config.StringVariable(schemaName) + } + return configVariables + } + + databaseRoleName := sdk.NewDatabaseObjectIdentifier(acc.TestDatabaseName, name) + + resource.Test(t, resource.TestCase{ + ProtoV6ProviderFactories: acc.TestAccProtoV6ProviderFactories, + PreCheck: func() { acc.TestAccPreCheck(t) }, + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.RequireAbove(tfversion.Version1_5_0), + }, + CheckDestroy: testAccCheckDatabaseRolePrivilegesRevoked, + Steps: []resource.TestStep{ + { + PreConfig: func() { createDatabaseRoleOutsideTerraform(t, name) }, + ConfigDirectory: acc.ConfigurationInnerDirectory("privileges"), + ConfigVariables: configVariables(false, []string{ + sdk.AccountObjectPrivilegeCreateSchema.String(), + sdk.AccountObjectPrivilegeModify.String(), + }, ""), + Check: queriedPrivilegesEqualTo( + databaseRoleName, + sdk.AccountObjectPrivilegeCreateSchema.String(), + sdk.AccountObjectPrivilegeModify.String(), + ), + }, + { + ConfigDirectory: acc.ConfigurationInnerDirectory("all_privileges"), + ConfigVariables: configVariables(true, []string{}, ""), + Check: queriedPrivilegesContainAtLeast( + databaseRoleName, + sdk.AccountObjectPrivilegeCreateDatabaseRole.String(), + sdk.AccountObjectPrivilegeCreateSchema.String(), + sdk.AccountObjectPrivilegeModify.String(), + sdk.AccountObjectPrivilegeMonitor.String(), + sdk.AccountObjectPrivilegeUsage.String(), + ), + }, + { + ConfigDirectory: acc.ConfigurationInnerDirectory("privileges"), + ConfigVariables: configVariables(false, []string{ + sdk.AccountObjectPrivilegeModify.String(), + sdk.AccountObjectPrivilegeMonitor.String(), + }, ""), + Check: queriedPrivilegesEqualTo( + databaseRoleName, + sdk.AccountObjectPrivilegeModify.String(), + sdk.AccountObjectPrivilegeMonitor.String(), + ), + }, + { + ConfigDirectory: acc.ConfigurationInnerDirectory("on_schema"), + ConfigVariables: configVariables(false, []string{ + sdk.SchemaPrivilegeCreateTask.String(), + sdk.SchemaPrivilegeCreateExternalTable.String(), + }, schemaName), + Check: queriedPrivilegesEqualTo( + databaseRoleName, + sdk.SchemaPrivilegeCreateTask.String(), + sdk.SchemaPrivilegeCreateExternalTable.String(), + ), + }, + }, + }) +} + func TestAcc_GrantPrivilegesToDatabaseRole_AlwaysApply(t *testing.T) { name := "test_database_role_name" configVariables := func(alwaysApply bool) config.Variables { @@ -615,6 +720,58 @@ func createDatabaseRoleOutsideTerraform(t *testing.T, name string) { } } +// queriedPrivilegesEqualTo will check if all the privileges specified in the argument are granted in Snowflake. +// Any additional grants (other than usage and ownership) will be treated as an error. +func queriedPrivilegesEqualTo(databaseRoleName sdk.DatabaseObjectIdentifier, privileges ...string) func(s *terraform.State) error { + return func(s *terraform.State) error { + db := acc.TestAccProvider.Meta().(*sql.DB) + ctx := context.Background() + client := sdk.NewClientFromDB(db) + grants, err := client.Grants.Show(ctx, &sdk.ShowGrantOptions{ + To: &sdk.ShowGrantsTo{ + DatabaseRole: databaseRoleName, + }, + }) + if err != nil { + return err + } + for _, grant := range grants { + if !slices.Contains(privileges, grant.Privilege) && grant.Privilege != "USAGE" && grant.Privilege != "OWNERSHIP" { + return fmt.Errorf("grant not expected, grant: %v, not in %v", grants, privileges) + } + } + + return nil + } +} + +// queriedPrivilegesContainAtLeast will check if all the privileges specified in the argument are granted in Snowflake. +// Any additional grants will be ignored. +func queriedPrivilegesContainAtLeast(databaseRoleName sdk.DatabaseObjectIdentifier, privileges ...string) func(s *terraform.State) error { + return func(s *terraform.State) error { + db := acc.TestAccProvider.Meta().(*sql.DB) + ctx := context.Background() + client := sdk.NewClientFromDB(db) + grants, err := client.Grants.Show(ctx, &sdk.ShowGrantOptions{ + To: &sdk.ShowGrantsTo{ + DatabaseRole: databaseRoleName, + }, + }) + if err != nil { + return err + } + var grantedPrivileges []string + for _, grant := range grants { + grantedPrivileges = append(grantedPrivileges, grant.Privilege) + } + if len(grantedPrivileges) < len(privileges) { + return fmt.Errorf("not every privilege from the list: %v was found in grant privileges: %v, for database role name: %s", privileges, grantedPrivileges, databaseRoleName.FullyQualifiedName()) + } + + return nil + } +} + func testAccCheckDatabaseRolePrivilegesRevoked(s *terraform.State) error { db := acc.TestAccProvider.Meta().(*sql.DB) client := sdk.NewClientFromDB(db) diff --git a/pkg/resources/grant_privileges_to_database_role_identifier.go b/pkg/resources/grant_privileges_to_database_role_identifier.go index 1c4823b840..97a663ac4a 100644 --- a/pkg/resources/grant_privileges_to_database_role_identifier.go +++ b/pkg/resources/grant_privileges_to_database_role_identifier.go @@ -131,16 +131,35 @@ func ParseGrantPrivilegesToDatabaseRoleId(id string) (GrantPrivilegesToDatabaseR return databaseRoleId, sdk.NewError(`database role identifier should hold at least 5 parts "|||||"`) } - databaseRoleId.DatabaseRoleName = sdk.NewDatabaseObjectIdentifierFromFullyQualifiedName(parts[0]) + databaseRoleNameParts := strings.Split(parts[0], ".") + if len(databaseRoleNameParts) == 0 || + (len(databaseRoleNameParts) == 1 && databaseRoleNameParts[0] == "") || + (len(databaseRoleNameParts) == 2 && databaseRoleNameParts[1] == "") || + len(databaseRoleNameParts) > 2 { + return databaseRoleId, sdk.NewError(fmt.Sprintf(`invalid DatabaseRoleName value: %s, should be a fully qualified name of database object .`, parts[0])) + } + databaseRoleId.DatabaseRoleName = sdk.NewDatabaseObjectIdentifier( + strings.Trim(databaseRoleNameParts[0], `"`), + strings.Trim(databaseRoleNameParts[1], `"`), + ) + + if parts[1] != "false" && parts[1] != "true" { + return databaseRoleId, sdk.NewError(fmt.Sprintf(`invalid WithGrantOption value: %s, should be either "true" or "false"`, parts[1])) + } databaseRoleId.WithGrantOption = parts[1] == "true" + + if parts[2] != "false" && parts[2] != "true" { + return databaseRoleId, sdk.NewError(fmt.Sprintf(`invalid AlwaysApply value: %s, should be either "true" or "false"`, parts[2])) + } databaseRoleId.AlwaysApply = parts[2] == "true" + privileges := strings.Split(parts[3], ",") + if len(privileges) == 0 || (len(privileges) == 1 && privileges[0] == "") { + return databaseRoleId, sdk.NewError(fmt.Sprintf(`invalid Privileges value: %s, should be either a comma seperated list of privileges or "ALL" / "ALL PRIVILEGES" for all privileges`, parts[3])) + } if len(privileges) == 1 && (privileges[0] == "ALL" || privileges[0] == "ALL PRIVILEGES") { databaseRoleId.AllPrivileges = true } else { - if len(privileges) == 1 && privileges[0] == "" { - privileges = []string{} - } databaseRoleId.Privileges = privileges } databaseRoleId.Kind = DatabaseRoleGrantKind(parts[4]) diff --git a/pkg/resources/grant_privileges_to_database_role_identifier_test.go b/pkg/resources/grant_privileges_to_database_role_identifier_test.go index ce364b438e..456c6fc68f 100644 --- a/pkg/resources/grant_privileges_to_database_role_identifier_test.go +++ b/pkg/resources/grant_privileges_to_database_role_identifier_test.go @@ -263,6 +263,31 @@ func TestParseGrantPrivilegesToDatabaseRoleId(t *testing.T) { Identifier: `"database-name"."role-name"|false|false|CREATE SCHEMA,USAGE,MONITOR|OnSchemaObject|some-kind|some-data`, Error: "invalid OnSchemaObjectGrantKind: some-kind", }, + { + Name: "validation: grant database role empty privileges", + Identifier: `"database-name"."database-role"|false|false||OnDatabase|"on-database-name"`, + Error: `invalid Privileges value: , should be either a comma seperated list of privileges or "ALL" / "ALL PRIVILEGES" for all privileges`, + }, + { + Name: "validation: grant database role empty with grant option", + Identifier: `"database-name"."database-role"||false|ALL PRIVILEGES|OnDatabase|"on-database-name"`, + Error: `invalid WithGrantOption value: , should be either "true" or "false"`, + }, + { + Name: "validation: grant database role empty always apply", + Identifier: `"database-name"."database-role"|false||ALL PRIVILEGES|OnDatabase|"on-database-name"`, + Error: `invalid AlwaysApply value: , should be either "true" or "false"`, + }, + { + Name: "validation: grant database role empty database role name", + Identifier: `|false|false|ALL PRIVILEGES|OnDatabase|"on-database-name"`, + Error: "invalid DatabaseRoleName value: , should be a fully qualified name of database object .", + }, + { + Name: "validation: grant database role empty type", + Identifier: `"database-name"."database-role"|false|false|ALL PRIVILEGES||"on-database-name"`, + Error: "invalid DatabaseRoleGrantKind: ", + }, } for _, tt := range testCases { diff --git a/pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole_AlwaysApply/test.tf b/pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole_AlwaysApply/test.tf index 1c544f39ce..4b855bb708 100644 --- a/pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole_AlwaysApply/test.tf +++ b/pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole_AlwaysApply/test.tf @@ -1,6 +1,6 @@ resource "snowflake_grant_privileges_to_database_role" "test" { - database_role_name = "\"${var.database}\".\"${var.name}\"" + database_role_name = "${var.database}.${var.name}" all_privileges = var.all_privileges - on_database = "\"${var.database}\"" + on_database = var.database always_apply = var.always_apply } diff --git a/pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole_OnSchema_ExactlyOneOf/test.tf b/pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole_OnSchema_ExactlyOneOf/test.tf new file mode 100644 index 0000000000..b144b1c5b6 --- /dev/null +++ b/pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole_OnSchema_ExactlyOneOf/test.tf @@ -0,0 +1,9 @@ +resource "snowflake_grant_privileges_to_database_role" "test" { + database_role_name = "some_database.role_name" + privileges = ["USAGE"] + + on_schema { + schema_name = "some_database.schema_name" + all_schemas_in_database = "some_database" + } +} diff --git a/pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole_UpdatePrivileges_SnowflakeChecked/all_privileges/test.tf b/pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole_UpdatePrivileges_SnowflakeChecked/all_privileges/test.tf new file mode 100644 index 0000000000..3fc26c3028 --- /dev/null +++ b/pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole_UpdatePrivileges_SnowflakeChecked/all_privileges/test.tf @@ -0,0 +1,5 @@ +resource "snowflake_grant_privileges_to_database_role" "test" { + database_role_name = "\"${var.database}\".\"${var.name}\"" + all_privileges = var.all_privileges + on_database = "\"${var.database}\"" +} diff --git a/pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole_UpdatePrivileges_SnowflakeChecked/all_privileges/variables.tf b/pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole_UpdatePrivileges_SnowflakeChecked/all_privileges/variables.tf new file mode 100644 index 0000000000..cb4441bfce --- /dev/null +++ b/pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole_UpdatePrivileges_SnowflakeChecked/all_privileges/variables.tf @@ -0,0 +1,11 @@ +variable "name" { + type = string +} + +variable "all_privileges" { + type = bool +} + +variable "database" { + type = string +} diff --git a/pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole_UpdatePrivileges_SnowflakeChecked/on_schema/test.tf b/pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole_UpdatePrivileges_SnowflakeChecked/on_schema/test.tf new file mode 100644 index 0000000000..665d600c66 --- /dev/null +++ b/pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole_UpdatePrivileges_SnowflakeChecked/on_schema/test.tf @@ -0,0 +1,13 @@ +resource "snowflake_schema" "test" { + database = var.database + name = var.schema_name +} + +resource "snowflake_grant_privileges_to_database_role" "test" { + depends_on = [snowflake_schema.test] + database_role_name = "\"${var.database}\".\"${var.name}\"" + privileges = var.privileges + on_schema { + schema_name = "${var.database}.${var.schema_name}" + } +} diff --git a/pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole_UpdatePrivileges_SnowflakeChecked/on_schema/variables.tf b/pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole_UpdatePrivileges_SnowflakeChecked/on_schema/variables.tf new file mode 100644 index 0000000000..90d9c04448 --- /dev/null +++ b/pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole_UpdatePrivileges_SnowflakeChecked/on_schema/variables.tf @@ -0,0 +1,15 @@ +variable "name" { + type = string +} + +variable "privileges" { + type = list(string) +} + +variable "database" { + type = string +} + +variable "schema_name" { + type = string +} diff --git a/pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole_UpdatePrivileges_SnowflakeChecked/privileges/test.tf b/pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole_UpdatePrivileges_SnowflakeChecked/privileges/test.tf new file mode 100644 index 0000000000..c1ea0cb24f --- /dev/null +++ b/pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole_UpdatePrivileges_SnowflakeChecked/privileges/test.tf @@ -0,0 +1,5 @@ +resource "snowflake_grant_privileges_to_database_role" "test" { + database_role_name = "\"${var.database}\".\"${var.name}\"" + privileges = var.privileges + on_database = "\"${var.database}\"" +} diff --git a/pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole_UpdatePrivileges_SnowflakeChecked/privileges/variables.tf b/pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole_UpdatePrivileges_SnowflakeChecked/privileges/variables.tf new file mode 100644 index 0000000000..27eccc7883 --- /dev/null +++ b/pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole_UpdatePrivileges_SnowflakeChecked/privileges/variables.tf @@ -0,0 +1,11 @@ +variable "name" { + type = string +} + +variable "privileges" { + type = list(string) +} + +variable "database" { + type = string +} diff --git a/pkg/sdk/object_types.go b/pkg/sdk/object_types.go index 6b9ea5b19a..ea858b9441 100644 --- a/pkg/sdk/object_types.go +++ b/pkg/sdk/object_types.go @@ -60,6 +60,8 @@ const ( ObjectTypeColumn ObjectType = "COLUMN" ObjectTypeIcebergTable ObjectType = "ICEBERG TABLE" ObjectTypeExternalVolume ObjectType = "EXTERNAL VOLUME" + ObjectTypeNetworkRule ObjectType = "NETWORK RULE" + ObjectTypePackagesPolicy ObjectType = "PACKAGES POLICY" ) func (o ObjectType) String() string { @@ -112,6 +114,8 @@ func objectTypeSingularToPluralMap() map[ObjectType]PluralObjectType { ObjectTypeStreamlit: PluralObjectTypeStreamlits, ObjectTypeIcebergTable: PluralObjectTypeIcebergTables, ObjectTypeExternalVolume: PluralObjectTypeExternalVolumes, + ObjectTypeNetworkRule: PluralObjectTypeNetworkRules, + ObjectTypePackagesPolicy: PluralObjectTypePackagesPolicies, } } @@ -204,6 +208,8 @@ const ( PluralObjectTypeStreamlits PluralObjectType = "STREAMLITS" PluralObjectTypeIcebergTables PluralObjectType = "ICEBERG TABLES" PluralObjectTypeExternalVolumes PluralObjectType = "EXTERNAL VOLUMES" + PluralObjectTypeNetworkRules PluralObjectType = "NETWORK RULES" + PluralObjectTypePackagesPolicies PluralObjectType = "PACKAGES POLICIES" ) func (p PluralObjectType) String() string { diff --git a/pkg/sdk/parameters_test.go b/pkg/sdk/parameters_test.go index 876290a4bd..d1a2de57c8 100644 --- a/pkg/sdk/parameters_test.go +++ b/pkg/sdk/parameters_test.go @@ -1,6 +1,7 @@ package sdk import ( + "log" "testing" ) @@ -22,3 +23,19 @@ func TestSetObjectParameterOnObject(t *testing.T) { assertOptsValidAndSQLEquals(t, opts, "ALTER USER %s SET ENABLE_UNREDACTED_QUERY_SYNTAX_ERROR = TRUE", id.FullyQualifiedName()) }) } + +func (o ObjectType) Check() bool { + var m map[ObjectType]bool + if _, ok := m[o]; ok { + return true + } + return false +} + +func Test(t *testing.T) { + a := "abc" + b := ObjectTypeDatabase + + log.Println(ObjectType(a).Check()) + log.Println(b.Check()) +} diff --git a/templates/resources/grant_privileges_to_database_role.md.tmpl b/templates/resources/grant_privileges_to_database_role.md.tmpl index 05bc4b9c61..a820281db9 100644 --- a/templates/resources/grant_privileges_to_database_role.md.tmpl +++ b/templates/resources/grant_privileges_to_database_role.md.tmpl @@ -6,6 +6,9 @@ description: |- {{ .Description | plainmarkdown | trimspace | prefixlines " " }} --- +{{/* SNOW-990811 */}} +!> **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 }} From 0b625c7b45faa27013a8587f96027e3e232fce2a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Cie=C5=9Blak?= Date: Mon, 8 Jan 2024 11:00:30 +0100 Subject: [PATCH 13/16] make pre-push --- .../grant_privileges_to_database_role.md | 5 ++++- .../grant_privileges_to_database_role.go | 2 +- ...ivileges_to_database_role_acceptance_test.go | 3 ++- ...nt_privileges_to_database_role_identifier.go | 4 +++- ...ivileges_to_database_role_identifier_test.go | 2 +- .../test.tf | 2 +- .../on_schema/test.tf | 2 +- pkg/sdk/parameters_test.go | 17 ----------------- 8 files changed, 13 insertions(+), 24 deletions(-) diff --git a/docs/resources/grant_privileges_to_database_role.md b/docs/resources/grant_privileges_to_database_role.md index 0b8c5bbcc9..f0510c0a30 100644 --- a/docs/resources/grant_privileges_to_database_role.md +++ b/docs/resources/grant_privileges_to_database_role.md @@ -6,6 +6,9 @@ description: |- --- + +!> **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_privileges_to_database_role (Resource) @@ -173,7 +176,7 @@ resource "snowflake_grant_privileges_to_database_role" "example" { - `all_privileges` (Boolean) Grant all privileges on the database role. - `always_apply` (Boolean) If true, the resource will always produce a “plan” and on “apply” it will re-grant defined privileges. It is supposed to be used only in “grant privileges on all X’s in database / schema Y” or “grant all privileges to X” scenarios to make sure that every new object in a given database / schema is granted by the account role and every new privilege is granted to the database role. Important note: this flag is not compliant with the Terraform assumptions of the config being eventually convergent (producing an empty plan). - `always_apply_trigger` (String) This field should not be set and its main purpose is to achieve the functionality described by always_apply field. This is value will be flipped to the opposite value on every terraform apply, thus creating a new plan that will re-apply grants. -- `on_database` (String) The fully qualified name of the database on which privileges will be granted. If the identifier is not fully qualified (in the form of .≤database_role_name>), the command looks for the database role in the current database for the session. All privileges are limited to the database that contains the database role, as well as other objects in the same database. +- `on_database` (String) The fully qualified name of the database on which privileges will be granted. - `on_schema` (Block List, Max: 1) Specifies the schema on which privileges will be granted. (see [below for nested schema](#nestedblock--on_schema)) - `on_schema_object` (Block List, Max: 1) Specifies the schema object on which privileges will be granted. (see [below for nested schema](#nestedblock--on_schema_object)) - `privileges` (Set of String) The privileges to grant on the database role. diff --git a/pkg/resources/grant_privileges_to_database_role.go b/pkg/resources/grant_privileges_to_database_role.go index cc1e7b449f..0c4e2ff57e 100644 --- a/pkg/resources/grant_privileges_to_database_role.go +++ b/pkg/resources/grant_privileges_to_database_role.go @@ -476,7 +476,7 @@ func UpdateGrantPrivilegesToDatabaseRole(ctx context.Context, d *schema.Resource if d.HasChange("all_privileges") { if _, allPrivileges := d.GetChange("all_privileges"); allPrivileges.(bool) { shouldGrantAndRevoke = false - //id.Privileges = []string{} + id.Privileges = []string{} } } diff --git a/pkg/resources/grant_privileges_to_database_role_acceptance_test.go b/pkg/resources/grant_privileges_to_database_role_acceptance_test.go index 0b69ffe49a..c901dc3c70 100644 --- a/pkg/resources/grant_privileges_to_database_role_acceptance_test.go +++ b/pkg/resources/grant_privileges_to_database_role_acceptance_test.go @@ -221,7 +221,8 @@ func TestAcc_GrantPrivilegesToDatabaseRole_OnFutureSchemasInDatabase(t *testing. resource.TestCheckResourceAttr(resourceName, "privileges.0", string(sdk.SchemaPrivilegeCreateTable)), resource.TestCheckResourceAttr(resourceName, "privileges.1", string(sdk.SchemaPrivilegeModify)), resource.TestCheckResourceAttr(resourceName, "on_schema.#", "1"), - resource.TestCheckResourceAttr(resourceName, "on_schema.0.future_schemas_in_database", databaseName), resource.TestCheckResourceAttr(resourceName, "with_grant_option", "false"), + resource.TestCheckResourceAttr(resourceName, "on_schema.0.future_schemas_in_database", databaseName), + resource.TestCheckResourceAttr(resourceName, "with_grant_option", "false"), resource.TestCheckResourceAttr(resourceName, "id", fmt.Sprintf("%s|false|false|CREATE TABLE,MODIFY|OnSchema|OnFutureSchemasInDatabase|%s", databaseRoleName, databaseName)), ), }, diff --git a/pkg/resources/grant_privileges_to_database_role_identifier.go b/pkg/resources/grant_privileges_to_database_role_identifier.go index 97a663ac4a..f297e968d8 100644 --- a/pkg/resources/grant_privileges_to_database_role_identifier.go +++ b/pkg/resources/grant_privileges_to_database_role_identifier.go @@ -131,6 +131,8 @@ func ParseGrantPrivilegesToDatabaseRoleId(id string) (GrantPrivilegesToDatabaseR return databaseRoleId, sdk.NewError(`database role identifier should hold at least 5 parts "|||||"`) } + // TODO: Identifier parsing should be replaced with better version introduced in SNOW-999049. + // Right now, it's same as sdk.NewDatabaseObjectIdentifierFromFullyQualifiedName, but with error handling. databaseRoleNameParts := strings.Split(parts[0], ".") if len(databaseRoleNameParts) == 0 || (len(databaseRoleNameParts) == 1 && databaseRoleNameParts[0] == "") || @@ -155,7 +157,7 @@ func ParseGrantPrivilegesToDatabaseRoleId(id string) (GrantPrivilegesToDatabaseR privileges := strings.Split(parts[3], ",") if len(privileges) == 0 || (len(privileges) == 1 && privileges[0] == "") { - return databaseRoleId, sdk.NewError(fmt.Sprintf(`invalid Privileges value: %s, should be either a comma seperated list of privileges or "ALL" / "ALL PRIVILEGES" for all privileges`, parts[3])) + return databaseRoleId, sdk.NewError(fmt.Sprintf(`invalid Privileges value: %s, should be either a comma separated list of privileges or "ALL" / "ALL PRIVILEGES" for all privileges`, parts[3])) } if len(privileges) == 1 && (privileges[0] == "ALL" || privileges[0] == "ALL PRIVILEGES") { databaseRoleId.AllPrivileges = true diff --git a/pkg/resources/grant_privileges_to_database_role_identifier_test.go b/pkg/resources/grant_privileges_to_database_role_identifier_test.go index 456c6fc68f..6d6668816b 100644 --- a/pkg/resources/grant_privileges_to_database_role_identifier_test.go +++ b/pkg/resources/grant_privileges_to_database_role_identifier_test.go @@ -266,7 +266,7 @@ func TestParseGrantPrivilegesToDatabaseRoleId(t *testing.T) { { Name: "validation: grant database role empty privileges", Identifier: `"database-name"."database-role"|false|false||OnDatabase|"on-database-name"`, - Error: `invalid Privileges value: , should be either a comma seperated list of privileges or "ALL" / "ALL PRIVILEGES" for all privileges`, + Error: `invalid Privileges value: , should be either a comma separated list of privileges or "ALL" / "ALL PRIVILEGES" for all privileges`, }, { Name: "validation: grant database role empty with grant option", diff --git a/pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole_OnSchema_ExactlyOneOf/test.tf b/pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole_OnSchema_ExactlyOneOf/test.tf index b144b1c5b6..aea96bd446 100644 --- a/pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole_OnSchema_ExactlyOneOf/test.tf +++ b/pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole_OnSchema_ExactlyOneOf/test.tf @@ -3,7 +3,7 @@ resource "snowflake_grant_privileges_to_database_role" "test" { privileges = ["USAGE"] on_schema { - schema_name = "some_database.schema_name" + schema_name = "some_database.schema_name" all_schemas_in_database = "some_database" } } diff --git a/pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole_UpdatePrivileges_SnowflakeChecked/on_schema/test.tf b/pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole_UpdatePrivileges_SnowflakeChecked/on_schema/test.tf index 665d600c66..f7bd4d9f19 100644 --- a/pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole_UpdatePrivileges_SnowflakeChecked/on_schema/test.tf +++ b/pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole_UpdatePrivileges_SnowflakeChecked/on_schema/test.tf @@ -4,7 +4,7 @@ resource "snowflake_schema" "test" { } resource "snowflake_grant_privileges_to_database_role" "test" { - depends_on = [snowflake_schema.test] + depends_on = [snowflake_schema.test] database_role_name = "\"${var.database}\".\"${var.name}\"" privileges = var.privileges on_schema { diff --git a/pkg/sdk/parameters_test.go b/pkg/sdk/parameters_test.go index d1a2de57c8..876290a4bd 100644 --- a/pkg/sdk/parameters_test.go +++ b/pkg/sdk/parameters_test.go @@ -1,7 +1,6 @@ package sdk import ( - "log" "testing" ) @@ -23,19 +22,3 @@ func TestSetObjectParameterOnObject(t *testing.T) { assertOptsValidAndSQLEquals(t, opts, "ALTER USER %s SET ENABLE_UNREDACTED_QUERY_SYNTAX_ERROR = TRUE", id.FullyQualifiedName()) }) } - -func (o ObjectType) Check() bool { - var m map[ObjectType]bool - if _, ok := m[o]; ok { - return true - } - return false -} - -func Test(t *testing.T) { - a := "abc" - b := ObjectTypeDatabase - - log.Println(ObjectType(a).Check()) - log.Println(b.Check()) -} From eacccb0f09af6eb2b2d608d064e82c8eadc49354 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Cie=C5=9Blak?= Date: Tue, 9 Jan 2024 11:02:28 +0100 Subject: [PATCH 14/16] changes after review --- ...ileges_to_database_role_acceptance_test.go | 69 +++++++++++++++---- 1 file changed, 55 insertions(+), 14 deletions(-) diff --git a/pkg/resources/grant_privileges_to_database_role_acceptance_test.go b/pkg/resources/grant_privileges_to_database_role_acceptance_test.go index c901dc3c70..7f595fb265 100644 --- a/pkg/resources/grant_privileges_to_database_role_acceptance_test.go +++ b/pkg/resources/grant_privileges_to_database_role_acceptance_test.go @@ -68,6 +68,57 @@ func TestAcc_GrantPrivilegesToDatabaseRole_OnDatabase(t *testing.T) { }) } +func TestAcc_GrantPrivilegesToDatabaseRole_OnDatabase_PrivilegesReversed(t *testing.T) { + name := "test_database_role_name" + configVariables := config.Variables{ + "name": config.StringVariable(name), + "privileges": config.ListVariable( + config.StringVariable(string(sdk.AccountObjectPrivilegeUsage)), + config.StringVariable(string(sdk.AccountObjectPrivilegeModify)), + config.StringVariable(string(sdk.AccountObjectPrivilegeCreateSchema)), + ), + "database": config.StringVariable(acc.TestDatabaseName), + "with_grant_option": config.BoolVariable(true), + } + resourceName := "snowflake_grant_privileges_to_database_role.test" + + databaseRoleName := sdk.NewDatabaseObjectIdentifier(acc.TestDatabaseName, name).FullyQualifiedName() + databaseName := sdk.NewAccountObjectIdentifier(acc.TestDatabaseName).FullyQualifiedName() + + resource.Test(t, resource.TestCase{ + ProtoV6ProviderFactories: acc.TestAccProtoV6ProviderFactories, + PreCheck: func() { acc.TestAccPreCheck(t) }, + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.RequireAbove(tfversion.Version1_5_0), + }, + CheckDestroy: testAccCheckDatabaseRolePrivilegesRevoked, + Steps: []resource.TestStep{ + { + PreConfig: func() { createDatabaseRoleOutsideTerraform(t, name) }, + ConfigDirectory: acc.ConfigurationDirectory("TestAcc_GrantPrivilegesToDatabaseRole_OnDatabase"), + ConfigVariables: configVariables, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "database_role_name", databaseRoleName), + resource.TestCheckResourceAttr(resourceName, "privileges.#", "3"), + resource.TestCheckResourceAttr(resourceName, "privileges.0", string(sdk.AccountObjectPrivilegeCreateSchema)), + resource.TestCheckResourceAttr(resourceName, "privileges.1", string(sdk.AccountObjectPrivilegeModify)), + resource.TestCheckResourceAttr(resourceName, "privileges.2", string(sdk.AccountObjectPrivilegeUsage)), + resource.TestCheckResourceAttr(resourceName, "on_database", databaseName), + resource.TestCheckResourceAttr(resourceName, "with_grant_option", "true"), + resource.TestCheckResourceAttr(resourceName, "id", fmt.Sprintf("%s|true|false|CREATE SCHEMA,MODIFY,USAGE|OnDatabase|%s", databaseRoleName, databaseName)), + ), + }, + { + ConfigDirectory: acc.ConfigurationDirectory("TestAcc_GrantPrivilegesToDatabaseRole_OnDatabase"), + ConfigVariables: configVariables, + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + func TestAcc_GrantPrivilegesToDatabaseRole_OnSchema(t *testing.T) { name := "test_database_role_name" configVariables := config.Variables{ @@ -641,19 +692,6 @@ func TestAcc_GrantPrivilegesToDatabaseRole_AlwaysApply(t *testing.T) { resource.TestCheckResourceAttr(resourceName, "id", fmt.Sprintf("%s|false|false|ALL|OnDatabase|%s", databaseRoleName, databaseName)), ), }, - { - ConfigDirectory: config.TestNameDirectory(), - ConfigVariables: configVariables(false), - ConfigPlanChecks: resource.ConfigPlanChecks{ - PreApply: []plancheck.PlanCheck{ - plancheck.ExpectEmptyPlan(), - }, - }, - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr(resourceName, "always_apply", "false"), - resource.TestCheckResourceAttr(resourceName, "id", fmt.Sprintf("%s|false|false|ALL|OnDatabase|%s", databaseRoleName, databaseName)), - ), - }, { ConfigDirectory: config.TestNameDirectory(), ConfigVariables: configVariables(true), @@ -765,7 +803,10 @@ func queriedPrivilegesContainAtLeast(databaseRoleName sdk.DatabaseObjectIdentifi for _, grant := range grants { grantedPrivileges = append(grantedPrivileges, grant.Privilege) } - if len(grantedPrivileges) < len(privileges) { + notAllPrivilegesInGrantedPrivileges := slices.ContainsFunc(privileges, func(privilege string) bool { + return !slices.Contains(grantedPrivileges, privilege) + }) + if notAllPrivilegesInGrantedPrivileges { return fmt.Errorf("not every privilege from the list: %v was found in grant privileges: %v, for database role name: %s", privileges, grantedPrivileges, databaseRoleName.FullyQualifiedName()) } From 7657134a02cd000866ea877e5bc7d38168fae612 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Cie=C5=9Blak?= Date: Tue, 9 Jan 2024 11:51:42 +0100 Subject: [PATCH 15/16] change after review --- .../grant_privileges_to_database_role_acceptance_test.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/pkg/resources/grant_privileges_to_database_role_acceptance_test.go b/pkg/resources/grant_privileges_to_database_role_acceptance_test.go index 7f595fb265..7d9e335eda 100644 --- a/pkg/resources/grant_privileges_to_database_role_acceptance_test.go +++ b/pkg/resources/grant_privileges_to_database_role_acceptance_test.go @@ -4,6 +4,7 @@ import ( "context" "database/sql" "fmt" + "log" "regexp" "slices" "testing" @@ -775,7 +776,11 @@ func queriedPrivilegesEqualTo(databaseRoleName sdk.DatabaseObjectIdentifier, pri return err } for _, grant := range grants { - if !slices.Contains(privileges, grant.Privilege) && grant.Privilege != "USAGE" && grant.Privilege != "OWNERSHIP" { + if grant.Privilege == "USAGE" || grant.Privilege == "OWNERSHIP" { + log.Printf("Skipping check for %s privilege as its one of the privileges that are implicitly granted by Snowflake", grant.Privilege) + continue + } + if !slices.Contains(privileges, grant.Privilege) { return fmt.Errorf("grant not expected, grant: %v, not in %v", grants, privileges) } } From cc39e2c096990fb6a2b8fe0ed4e4971ef13d7d28 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Cie=C5=9Blak?= Date: Wed, 10 Jan 2024 11:22:10 +0100 Subject: [PATCH 16/16] changes after review + lint fix --- .../grant_privileges_to_database_role.md | 5 +- .../grant_privileges_to_database_role.go | 63 +---------- ...ileges_to_database_role_acceptance_test.go | 62 +++++------ pkg/resources/grant_privileges_to_role.go | 103 +++--------------- .../AlwaysApply}/test.tf | 0 .../AlwaysApply}/variables.tf | 0 .../OnAllSchemasInDatabase}/test.tf | 0 .../OnAllSchemasInDatabase}/variables.tf | 0 .../OnDatabase}/test.tf | 0 .../OnDatabase}/variables.tf | 0 .../OnFutureSchemasInDatabase}/test.tf | 0 .../OnFutureSchemasInDatabase}/variables.tf | 0 .../OnSchema}/test.tf | 0 .../OnSchema}/variables.tf | 0 .../OnSchemaObject_OnAll_InDatabase}/test.tf | 0 .../variables.tf | 0 .../test.tf | 0 .../variables.tf | 0 .../OnSchemaObject_OnObject}/test.tf | 0 .../OnSchemaObject_OnObject}/variables.tf | 0 .../OnSchema_ExactlyOneOf}/test.tf | 0 .../UpdatePrivileges}/all_privileges/test.tf | 0 .../all_privileges/variables.tf | 0 .../UpdatePrivileges}/privileges/test.tf | 0 .../UpdatePrivileges}/privileges/variables.tf | 0 .../all_privileges/test.tf | 0 .../all_privileges/variables.tf | 0 .../on_schema/test.tf | 0 .../on_schema/variables.tf | 0 .../privileges/test.tf | 0 .../privileges/variables.tf | 0 pkg/resources/validators.go | 78 +++++++++++++ .../grant_privileges_to_database_role.md.tmpl | 5 +- 33 files changed, 140 insertions(+), 176 deletions(-) rename pkg/resources/testdata/{TestAcc_GrantPrivilegesToDatabaseRole_AlwaysApply => TestAcc_GrantPrivilegesToDatabaseRole/AlwaysApply}/test.tf (100%) rename pkg/resources/testdata/{TestAcc_GrantPrivilegesToDatabaseRole_AlwaysApply => TestAcc_GrantPrivilegesToDatabaseRole/AlwaysApply}/variables.tf (100%) rename pkg/resources/testdata/{TestAcc_GrantPrivilegesToDatabaseRole_OnAllSchemasInDatabase => TestAcc_GrantPrivilegesToDatabaseRole/OnAllSchemasInDatabase}/test.tf (100%) rename pkg/resources/testdata/{TestAcc_GrantPrivilegesToDatabaseRole_OnAllSchemasInDatabase => TestAcc_GrantPrivilegesToDatabaseRole/OnAllSchemasInDatabase}/variables.tf (100%) rename pkg/resources/testdata/{TestAcc_GrantPrivilegesToDatabaseRole_OnDatabase => TestAcc_GrantPrivilegesToDatabaseRole/OnDatabase}/test.tf (100%) rename pkg/resources/testdata/{TestAcc_GrantPrivilegesToDatabaseRole_OnDatabase => TestAcc_GrantPrivilegesToDatabaseRole/OnDatabase}/variables.tf (100%) rename pkg/resources/testdata/{TestAcc_GrantPrivilegesToDatabaseRole_OnFutureSchemasInDatabase => TestAcc_GrantPrivilegesToDatabaseRole/OnFutureSchemasInDatabase}/test.tf (100%) rename pkg/resources/testdata/{TestAcc_GrantPrivilegesToDatabaseRole_OnFutureSchemasInDatabase => TestAcc_GrantPrivilegesToDatabaseRole/OnFutureSchemasInDatabase}/variables.tf (100%) rename pkg/resources/testdata/{TestAcc_GrantPrivilegesToDatabaseRole_OnSchema => TestAcc_GrantPrivilegesToDatabaseRole/OnSchema}/test.tf (100%) rename pkg/resources/testdata/{TestAcc_GrantPrivilegesToDatabaseRole_OnSchema => TestAcc_GrantPrivilegesToDatabaseRole/OnSchema}/variables.tf (100%) rename pkg/resources/testdata/{TestAcc_GrantPrivilegesToDatabaseRole_OnSchemaObject_OnAll_InDatabase => TestAcc_GrantPrivilegesToDatabaseRole/OnSchemaObject_OnAll_InDatabase}/test.tf (100%) rename pkg/resources/testdata/{TestAcc_GrantPrivilegesToDatabaseRole_OnSchemaObject_OnAll_InDatabase => TestAcc_GrantPrivilegesToDatabaseRole/OnSchemaObject_OnAll_InDatabase}/variables.tf (100%) rename pkg/resources/testdata/{TestAcc_GrantPrivilegesToDatabaseRole_OnSchemaObject_OnFuture_InDatabase => TestAcc_GrantPrivilegesToDatabaseRole/OnSchemaObject_OnFuture_InDatabase}/test.tf (100%) rename pkg/resources/testdata/{TestAcc_GrantPrivilegesToDatabaseRole_OnSchemaObject_OnFuture_InDatabase => TestAcc_GrantPrivilegesToDatabaseRole/OnSchemaObject_OnFuture_InDatabase}/variables.tf (100%) rename pkg/resources/testdata/{TestAcc_GrantPrivilegesToDatabaseRole_OnSchemaObject_OnObject => TestAcc_GrantPrivilegesToDatabaseRole/OnSchemaObject_OnObject}/test.tf (100%) rename pkg/resources/testdata/{TestAcc_GrantPrivilegesToDatabaseRole_OnSchemaObject_OnObject => TestAcc_GrantPrivilegesToDatabaseRole/OnSchemaObject_OnObject}/variables.tf (100%) rename pkg/resources/testdata/{TestAcc_GrantPrivilegesToDatabaseRole_OnSchema_ExactlyOneOf => TestAcc_GrantPrivilegesToDatabaseRole/OnSchema_ExactlyOneOf}/test.tf (100%) rename pkg/resources/testdata/{TestAcc_GrantPrivilegesToDatabaseRole_UpdatePrivileges => TestAcc_GrantPrivilegesToDatabaseRole/UpdatePrivileges}/all_privileges/test.tf (100%) rename pkg/resources/testdata/{TestAcc_GrantPrivilegesToDatabaseRole_UpdatePrivileges => TestAcc_GrantPrivilegesToDatabaseRole/UpdatePrivileges}/all_privileges/variables.tf (100%) rename pkg/resources/testdata/{TestAcc_GrantPrivilegesToDatabaseRole_UpdatePrivileges => TestAcc_GrantPrivilegesToDatabaseRole/UpdatePrivileges}/privileges/test.tf (100%) rename pkg/resources/testdata/{TestAcc_GrantPrivilegesToDatabaseRole_UpdatePrivileges => TestAcc_GrantPrivilegesToDatabaseRole/UpdatePrivileges}/privileges/variables.tf (100%) rename pkg/resources/testdata/{TestAcc_GrantPrivilegesToDatabaseRole_UpdatePrivileges_SnowflakeChecked => TestAcc_GrantPrivilegesToDatabaseRole/UpdatePrivileges_SnowflakeChecked}/all_privileges/test.tf (100%) rename pkg/resources/testdata/{TestAcc_GrantPrivilegesToDatabaseRole_UpdatePrivileges_SnowflakeChecked => TestAcc_GrantPrivilegesToDatabaseRole/UpdatePrivileges_SnowflakeChecked}/all_privileges/variables.tf (100%) rename pkg/resources/testdata/{TestAcc_GrantPrivilegesToDatabaseRole_UpdatePrivileges_SnowflakeChecked => TestAcc_GrantPrivilegesToDatabaseRole/UpdatePrivileges_SnowflakeChecked}/on_schema/test.tf (100%) rename pkg/resources/testdata/{TestAcc_GrantPrivilegesToDatabaseRole_UpdatePrivileges_SnowflakeChecked => TestAcc_GrantPrivilegesToDatabaseRole/UpdatePrivileges_SnowflakeChecked}/on_schema/variables.tf (100%) rename pkg/resources/testdata/{TestAcc_GrantPrivilegesToDatabaseRole_UpdatePrivileges_SnowflakeChecked => TestAcc_GrantPrivilegesToDatabaseRole/UpdatePrivileges_SnowflakeChecked}/privileges/test.tf (100%) rename pkg/resources/testdata/{TestAcc_GrantPrivilegesToDatabaseRole_UpdatePrivileges_SnowflakeChecked => TestAcc_GrantPrivilegesToDatabaseRole/UpdatePrivileges_SnowflakeChecked}/privileges/variables.tf (100%) diff --git a/docs/resources/grant_privileges_to_database_role.md b/docs/resources/grant_privileges_to_database_role.md index f0510c0a30..067ee9caea 100644 --- a/docs/resources/grant_privileges_to_database_role.md +++ b/docs/resources/grant_privileges_to_database_role.md @@ -301,6 +301,9 @@ On future contains inner types for all options. #### Grant list of privileges OnAllSchemasInDatabase `terraform import "\"test_db\".\"test_db_role\"|false|false|CREATE TAG,CREATE TABLE|OnSchema|OnAllSchemasInDatabase|\"test_db\""` +#### Grant list of privileges on table +`terraform import "\"test_db\".\"test_db_role\"|false|false|SELECT,DELETE,INSERT|OnSchemaObject|OnObject|TABLE|\"test_db\".\"test_schema\".\"test_table\""` + #### Grant list of privileges OnAll tables in schema -`terraform import "\"test_db\".\"test_db_role\"|false|false|SELECT|OnSchemaObject|OnAll|TABLES|InSchema|\"test_db\".\"test_schema\""` +`terraform import "\"test_db\".\"test_db_role\"|false|false|SELECT,DELETE,INSERT|OnSchemaObject|OnAll|TABLES|InSchema|\"test_db\".\"test_schema\""` diff --git a/pkg/resources/grant_privileges_to_database_role.go b/pkg/resources/grant_privileges_to_database_role.go index 0c4e2ff57e..8f4b9f0cb3 100644 --- a/pkg/resources/grant_privileges_to_database_role.go +++ b/pkg/resources/grant_privileges_to_database_role.go @@ -12,7 +12,6 @@ import ( "github.com/hashicorp/go-uuid" "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" ) // TODO: Handle IMPORTED PRIVILEGES privilege (after second account will be added - SNOW-976501) @@ -156,32 +155,7 @@ var grantPrivilegesToDatabaseRoleSchema = map[string]*schema.Schema{ "on_schema_object.0.all", "on_schema_object.0.future", }, - ValidateFunc: validation.StringInSlice([]string{ - sdk.ObjectTypeAlert.String(), - sdk.ObjectTypeDynamicTable.String(), - sdk.ObjectTypeEventTable.String(), - sdk.ObjectTypeFileFormat.String(), - sdk.ObjectTypeFunction.String(), - sdk.ObjectTypeProcedure.String(), - sdk.ObjectTypeSecret.String(), - sdk.ObjectTypeSequence.String(), - sdk.ObjectTypePipe.String(), - sdk.ObjectTypeMaskingPolicy.String(), - sdk.ObjectTypePasswordPolicy.String(), - sdk.ObjectTypeRowAccessPolicy.String(), - sdk.ObjectTypeSessionPolicy.String(), - sdk.ObjectTypeTag.String(), - sdk.ObjectTypeStage.String(), - sdk.ObjectTypeStream.String(), - sdk.ObjectTypeTable.String(), - sdk.ObjectTypeExternalTable.String(), - sdk.ObjectTypeTask.String(), - sdk.ObjectTypeView.String(), - sdk.ObjectTypeMaterializedView.String(), - sdk.ObjectTypeNetworkRule.String(), - sdk.ObjectTypePackagesPolicy.String(), - sdk.ObjectTypeIcebergTable.String(), - }, true), + ValidateDiagFunc: ValidObjectType(), }, "object_name": { Type: schema.TypeString, @@ -241,36 +215,11 @@ var grantPrivilegesToDatabaseRoleSchema = map[string]*schema.Schema{ var grantPrivilegesOnDatabaseRoleBulkOperationSchema = map[string]*schema.Schema{ "object_type_plural": { - Type: schema.TypeString, - Required: true, - ForceNew: true, - Description: "The plural object type of the schema object on which privileges will be granted. Valid values are: ALERTS | DYNAMIC TABLES | EVENT TABLES | FILE FORMATS | FUNCTIONS | PROCEDURES | SECRETS | SEQUENCES | PIPES | MASKING POLICIES | PASSWORD POLICIES | ROW ACCESS POLICIES | SESSION POLICIES | TAGS | STAGES | STREAMS | TABLES | EXTERNAL TABLES | TASKS | VIEWS | MATERIALIZED VIEWS | NETWORK RULES | PACKAGES POLICIES | ICEBERG TABLES", - ValidateFunc: validation.StringInSlice([]string{ - sdk.PluralObjectTypeAlerts.String(), - sdk.PluralObjectTypeDynamicTables.String(), - sdk.PluralObjectTypeEventTables.String(), - sdk.PluralObjectTypeFileFormats.String(), - sdk.PluralObjectTypeFunctions.String(), - sdk.PluralObjectTypeProcedures.String(), - sdk.PluralObjectTypeSecrets.String(), - sdk.PluralObjectTypeSequences.String(), - sdk.PluralObjectTypePipes.String(), - sdk.PluralObjectTypeMaskingPolicies.String(), - sdk.PluralObjectTypePasswordPolicies.String(), - sdk.PluralObjectTypeRowAccessPolicies.String(), - sdk.PluralObjectTypeSessionPolicies.String(), - sdk.PluralObjectTypeTags.String(), - sdk.PluralObjectTypeStages.String(), - sdk.PluralObjectTypeStreams.String(), - sdk.PluralObjectTypeTables.String(), - sdk.PluralObjectTypeExternalTables.String(), - sdk.PluralObjectTypeTasks.String(), - sdk.PluralObjectTypeViews.String(), - sdk.PluralObjectTypeMaterializedViews.String(), - sdk.PluralObjectTypeNetworkRules.String(), - sdk.PluralObjectTypePackagesPolicies.String(), - sdk.PluralObjectTypeIcebergTables.String(), - }, true), + Type: schema.TypeString, + Required: true, + ForceNew: true, + Description: "The plural object type of the schema object on which privileges will be granted. Valid values are: ALERTS | DYNAMIC TABLES | EVENT TABLES | FILE FORMATS | FUNCTIONS | PROCEDURES | SECRETS | SEQUENCES | PIPES | MASKING POLICIES | PASSWORD POLICIES | ROW ACCESS POLICIES | SESSION POLICIES | TAGS | STAGES | STREAMS | TABLES | EXTERNAL TABLES | TASKS | VIEWS | MATERIALIZED VIEWS | NETWORK RULES | PACKAGES POLICIES | ICEBERG TABLES", + ValidateDiagFunc: ValidPluralObjectType(), }, "in_database": { Type: schema.TypeString, diff --git a/pkg/resources/grant_privileges_to_database_role_acceptance_test.go b/pkg/resources/grant_privileges_to_database_role_acceptance_test.go index 7d9e335eda..451c66f048 100644 --- a/pkg/resources/grant_privileges_to_database_role_acceptance_test.go +++ b/pkg/resources/grant_privileges_to_database_role_acceptance_test.go @@ -45,7 +45,7 @@ func TestAcc_GrantPrivilegesToDatabaseRole_OnDatabase(t *testing.T) { Steps: []resource.TestStep{ { PreConfig: func() { createDatabaseRoleOutsideTerraform(t, name) }, - ConfigDirectory: config.TestNameDirectory(), + ConfigDirectory: acc.ConfigurationDirectory("TestAcc_GrantPrivilegesToDatabaseRole/OnDatabase"), ConfigVariables: configVariables, Check: resource.ComposeTestCheckFunc( resource.TestCheckResourceAttr(resourceName, "database_role_name", databaseRoleName), @@ -59,7 +59,7 @@ func TestAcc_GrantPrivilegesToDatabaseRole_OnDatabase(t *testing.T) { ), }, { - ConfigDirectory: config.TestNameDirectory(), + ConfigDirectory: acc.ConfigurationDirectory("TestAcc_GrantPrivilegesToDatabaseRole/OnDatabase"), ConfigVariables: configVariables, ResourceName: resourceName, ImportState: true, @@ -96,7 +96,7 @@ func TestAcc_GrantPrivilegesToDatabaseRole_OnDatabase_PrivilegesReversed(t *test Steps: []resource.TestStep{ { PreConfig: func() { createDatabaseRoleOutsideTerraform(t, name) }, - ConfigDirectory: acc.ConfigurationDirectory("TestAcc_GrantPrivilegesToDatabaseRole_OnDatabase"), + ConfigDirectory: acc.ConfigurationDirectory("TestAcc_GrantPrivilegesToDatabaseRole/OnDatabase"), ConfigVariables: configVariables, Check: resource.ComposeTestCheckFunc( resource.TestCheckResourceAttr(resourceName, "database_role_name", databaseRoleName), @@ -110,7 +110,7 @@ func TestAcc_GrantPrivilegesToDatabaseRole_OnDatabase_PrivilegesReversed(t *test ), }, { - ConfigDirectory: acc.ConfigurationDirectory("TestAcc_GrantPrivilegesToDatabaseRole_OnDatabase"), + ConfigDirectory: acc.ConfigurationDirectory("TestAcc_GrantPrivilegesToDatabaseRole/OnDatabase"), ConfigVariables: configVariables, ResourceName: resourceName, ImportState: true, @@ -147,7 +147,7 @@ func TestAcc_GrantPrivilegesToDatabaseRole_OnSchema(t *testing.T) { Steps: []resource.TestStep{ { PreConfig: func() { createDatabaseRoleOutsideTerraform(t, name) }, - ConfigDirectory: config.TestNameDirectory(), + ConfigDirectory: acc.ConfigurationDirectory("TestAcc_GrantPrivilegesToDatabaseRole/OnSchema"), ConfigVariables: configVariables, Check: resource.ComposeTestCheckFunc( resource.TestCheckResourceAttr(resourceName, "database_role_name", databaseRoleName), @@ -161,7 +161,7 @@ func TestAcc_GrantPrivilegesToDatabaseRole_OnSchema(t *testing.T) { ), }, { - ConfigDirectory: config.TestNameDirectory(), + ConfigDirectory: acc.ConfigurationDirectory("TestAcc_GrantPrivilegesToDatabaseRole/OnSchema"), ConfigVariables: configVariables, ResourceName: resourceName, ImportState: true, @@ -181,7 +181,7 @@ func TestAcc_GrantPrivilegesToDatabaseRole_OnSchema_ExactlyOneOf(t *testing.T) { CheckDestroy: testAccCheckDatabaseRolePrivilegesRevoked, Steps: []resource.TestStep{ { - ConfigDirectory: config.TestNameDirectory(), + ConfigDirectory: acc.ConfigurationDirectory("TestAcc_GrantPrivilegesToDatabaseRole/OnSchema_ExactlyOneOf"), PlanOnly: true, ExpectError: regexp.MustCompile("Error: Invalid combination of arguments"), }, @@ -215,7 +215,7 @@ func TestAcc_GrantPrivilegesToDatabaseRole_OnAllSchemasInDatabase(t *testing.T) Steps: []resource.TestStep{ { PreConfig: func() { createDatabaseRoleOutsideTerraform(t, name) }, - ConfigDirectory: config.TestNameDirectory(), + ConfigDirectory: acc.ConfigurationDirectory("TestAcc_GrantPrivilegesToDatabaseRole/OnAllSchemasInDatabase"), ConfigVariables: configVariables, Check: resource.ComposeTestCheckFunc( resource.TestCheckResourceAttr(resourceName, "database_role_name", databaseRoleName), @@ -229,7 +229,7 @@ func TestAcc_GrantPrivilegesToDatabaseRole_OnAllSchemasInDatabase(t *testing.T) ), }, { - ConfigDirectory: config.TestNameDirectory(), + ConfigDirectory: acc.ConfigurationDirectory("TestAcc_GrantPrivilegesToDatabaseRole/OnAllSchemasInDatabase"), ConfigVariables: configVariables, ResourceName: resourceName, ImportState: true, @@ -265,7 +265,7 @@ func TestAcc_GrantPrivilegesToDatabaseRole_OnFutureSchemasInDatabase(t *testing. Steps: []resource.TestStep{ { PreConfig: func() { createDatabaseRoleOutsideTerraform(t, name) }, - ConfigDirectory: config.TestNameDirectory(), + ConfigDirectory: acc.ConfigurationDirectory("TestAcc_GrantPrivilegesToDatabaseRole/OnFutureSchemasInDatabase"), ConfigVariables: configVariables, Check: resource.ComposeTestCheckFunc( resource.TestCheckResourceAttr(resourceName, "database_role_name", databaseRoleName), @@ -279,7 +279,7 @@ func TestAcc_GrantPrivilegesToDatabaseRole_OnFutureSchemasInDatabase(t *testing. ), }, { - ConfigDirectory: config.TestNameDirectory(), + ConfigDirectory: acc.ConfigurationDirectory("TestAcc_GrantPrivilegesToDatabaseRole/OnFutureSchemasInDatabase"), ConfigVariables: configVariables, ResourceName: resourceName, ImportState: true, @@ -318,7 +318,7 @@ func TestAcc_GrantPrivilegesToDatabaseRole_OnSchemaObject_OnObject(t *testing.T) Steps: []resource.TestStep{ { PreConfig: func() { createDatabaseRoleOutsideTerraform(t, name) }, - ConfigDirectory: config.TestNameDirectory(), + ConfigDirectory: acc.ConfigurationDirectory("TestAcc_GrantPrivilegesToDatabaseRole/OnSchemaObject_OnObject"), ConfigVariables: configVariables, Check: resource.ComposeTestCheckFunc( resource.TestCheckResourceAttr(resourceName, "database_role_name", databaseRoleName), @@ -333,7 +333,7 @@ func TestAcc_GrantPrivilegesToDatabaseRole_OnSchemaObject_OnObject(t *testing.T) ), }, { - ConfigDirectory: config.TestNameDirectory(), + ConfigDirectory: acc.ConfigurationDirectory("TestAcc_GrantPrivilegesToDatabaseRole/OnSchemaObject_OnObject"), ConfigVariables: configVariables, ResourceName: resourceName, ImportState: true, @@ -367,7 +367,7 @@ func TestAcc_GrantPrivilegesToDatabaseRole_OnSchemaObject_OnObject_OwnershipPriv Steps: []resource.TestStep{ { PreConfig: func() { createDatabaseRoleOutsideTerraform(t, name) }, - ConfigDirectory: acc.ConfigurationDirectory("TestAcc_GrantPrivilegesToDatabaseRole_OnSchemaObject_OnObject"), + ConfigDirectory: acc.ConfigurationDirectory("TestAcc_GrantPrivilegesToDatabaseRole/OnSchemaObject_OnObject"), ConfigVariables: configVariables, ExpectError: regexp.MustCompile("Unsupported privilege 'OWNERSHIP'"), }, @@ -401,7 +401,7 @@ func TestAcc_GrantPrivilegesToDatabaseRole_OnSchemaObject_OnAll_InDatabase(t *te Steps: []resource.TestStep{ { PreConfig: func() { createDatabaseRoleOutsideTerraform(t, name) }, - ConfigDirectory: config.TestNameDirectory(), + ConfigDirectory: acc.ConfigurationDirectory("TestAcc_GrantPrivilegesToDatabaseRole/OnSchemaObject_OnAll_InDatabase"), ConfigVariables: configVariables, Check: resource.ComposeTestCheckFunc( resource.TestCheckResourceAttr(resourceName, "database_role_name", databaseRoleName), @@ -417,7 +417,7 @@ func TestAcc_GrantPrivilegesToDatabaseRole_OnSchemaObject_OnAll_InDatabase(t *te ), }, { - ConfigDirectory: config.TestNameDirectory(), + ConfigDirectory: acc.ConfigurationDirectory("TestAcc_GrantPrivilegesToDatabaseRole/OnSchemaObject_OnAll_InDatabase"), ConfigVariables: configVariables, ResourceName: resourceName, ImportState: true, @@ -453,7 +453,7 @@ func TestAcc_GrantPrivilegesToDatabaseRole_OnSchemaObject_OnFuture_InDatabase(t Steps: []resource.TestStep{ { PreConfig: func() { createDatabaseRoleOutsideTerraform(t, name) }, - ConfigDirectory: config.TestNameDirectory(), + ConfigDirectory: acc.ConfigurationDirectory("TestAcc_GrantPrivilegesToDatabaseRole/OnSchemaObject_OnFuture_InDatabase"), ConfigVariables: configVariables, Check: resource.ComposeTestCheckFunc( resource.TestCheckResourceAttr(resourceName, "database_role_name", databaseRoleName), @@ -469,7 +469,7 @@ func TestAcc_GrantPrivilegesToDatabaseRole_OnSchemaObject_OnFuture_InDatabase(t ), }, { - ConfigDirectory: config.TestNameDirectory(), + ConfigDirectory: acc.ConfigurationDirectory("TestAcc_GrantPrivilegesToDatabaseRole/OnSchemaObject_OnFuture_InDatabase"), ConfigVariables: configVariables, ResourceName: resourceName, ImportState: true, @@ -513,7 +513,7 @@ func TestAcc_GrantPrivilegesToDatabaseRole_UpdatePrivileges(t *testing.T) { Steps: []resource.TestStep{ { PreConfig: func() { createDatabaseRoleOutsideTerraform(t, name) }, - ConfigDirectory: acc.ConfigurationInnerDirectory("privileges"), + ConfigDirectory: acc.ConfigurationDirectory("TestAcc_GrantPrivilegesToDatabaseRole/UpdatePrivileges/privileges"), ConfigVariables: configVariables(false, []sdk.AccountObjectPrivilege{ sdk.AccountObjectPrivilegeCreateSchema, sdk.AccountObjectPrivilegeModify, @@ -527,7 +527,7 @@ func TestAcc_GrantPrivilegesToDatabaseRole_UpdatePrivileges(t *testing.T) { ), }, { - ConfigDirectory: acc.ConfigurationInnerDirectory("privileges"), + ConfigDirectory: acc.ConfigurationDirectory("TestAcc_GrantPrivilegesToDatabaseRole/UpdatePrivileges/privileges"), ConfigVariables: configVariables(false, []sdk.AccountObjectPrivilege{ sdk.AccountObjectPrivilegeCreateSchema, sdk.AccountObjectPrivilegeMonitor, @@ -543,7 +543,7 @@ func TestAcc_GrantPrivilegesToDatabaseRole_UpdatePrivileges(t *testing.T) { ), }, { - ConfigDirectory: acc.ConfigurationInnerDirectory("all_privileges"), + ConfigDirectory: acc.ConfigurationDirectory("TestAcc_GrantPrivilegesToDatabaseRole/UpdatePrivileges/all_privileges"), ConfigVariables: configVariables(true, []sdk.AccountObjectPrivilege{}), Check: resource.ComposeTestCheckFunc( resource.TestCheckResourceAttr(resourceName, "all_privileges", "true"), @@ -552,7 +552,7 @@ func TestAcc_GrantPrivilegesToDatabaseRole_UpdatePrivileges(t *testing.T) { ), }, { - ConfigDirectory: acc.ConfigurationInnerDirectory("privileges"), + ConfigDirectory: acc.ConfigurationDirectory("TestAcc_GrantPrivilegesToDatabaseRole/UpdatePrivileges/privileges"), ConfigVariables: configVariables(false, []sdk.AccountObjectPrivilege{ sdk.AccountObjectPrivilegeModify, sdk.AccountObjectPrivilegeMonitor, @@ -605,7 +605,7 @@ func TestAcc_GrantPrivilegesToDatabaseRole_UpdatePrivileges_SnowflakeChecked(t * Steps: []resource.TestStep{ { PreConfig: func() { createDatabaseRoleOutsideTerraform(t, name) }, - ConfigDirectory: acc.ConfigurationInnerDirectory("privileges"), + ConfigDirectory: acc.ConfigurationDirectory("TestAcc_GrantPrivilegesToDatabaseRole/UpdatePrivileges_SnowflakeChecked/privileges"), ConfigVariables: configVariables(false, []string{ sdk.AccountObjectPrivilegeCreateSchema.String(), sdk.AccountObjectPrivilegeModify.String(), @@ -617,7 +617,7 @@ func TestAcc_GrantPrivilegesToDatabaseRole_UpdatePrivileges_SnowflakeChecked(t * ), }, { - ConfigDirectory: acc.ConfigurationInnerDirectory("all_privileges"), + ConfigDirectory: acc.ConfigurationDirectory("TestAcc_GrantPrivilegesToDatabaseRole/UpdatePrivileges_SnowflakeChecked/all_privileges"), ConfigVariables: configVariables(true, []string{}, ""), Check: queriedPrivilegesContainAtLeast( databaseRoleName, @@ -629,7 +629,7 @@ func TestAcc_GrantPrivilegesToDatabaseRole_UpdatePrivileges_SnowflakeChecked(t * ), }, { - ConfigDirectory: acc.ConfigurationInnerDirectory("privileges"), + ConfigDirectory: acc.ConfigurationDirectory("TestAcc_GrantPrivilegesToDatabaseRole/UpdatePrivileges_SnowflakeChecked/privileges"), ConfigVariables: configVariables(false, []string{ sdk.AccountObjectPrivilegeModify.String(), sdk.AccountObjectPrivilegeMonitor.String(), @@ -641,7 +641,7 @@ func TestAcc_GrantPrivilegesToDatabaseRole_UpdatePrivileges_SnowflakeChecked(t * ), }, { - ConfigDirectory: acc.ConfigurationInnerDirectory("on_schema"), + ConfigDirectory: acc.ConfigurationDirectory("TestAcc_GrantPrivilegesToDatabaseRole/UpdatePrivileges_SnowflakeChecked/on_schema"), ConfigVariables: configVariables(false, []string{ sdk.SchemaPrivilegeCreateTask.String(), sdk.SchemaPrivilegeCreateExternalTable.String(), @@ -681,7 +681,7 @@ func TestAcc_GrantPrivilegesToDatabaseRole_AlwaysApply(t *testing.T) { Steps: []resource.TestStep{ { PreConfig: func() { createDatabaseRoleOutsideTerraform(t, name) }, - ConfigDirectory: config.TestNameDirectory(), + ConfigDirectory: acc.ConfigurationDirectory("TestAcc_GrantPrivilegesToDatabaseRole/AlwaysApply"), ConfigVariables: configVariables(false), ConfigPlanChecks: resource.ConfigPlanChecks{ PostApplyPostRefresh: []plancheck.PlanCheck{ @@ -694,7 +694,7 @@ func TestAcc_GrantPrivilegesToDatabaseRole_AlwaysApply(t *testing.T) { ), }, { - ConfigDirectory: config.TestNameDirectory(), + ConfigDirectory: acc.ConfigurationDirectory("TestAcc_GrantPrivilegesToDatabaseRole/AlwaysApply"), ConfigVariables: configVariables(true), Check: resource.ComposeTestCheckFunc( resource.TestCheckResourceAttr(resourceName, "always_apply", "true"), @@ -703,7 +703,7 @@ func TestAcc_GrantPrivilegesToDatabaseRole_AlwaysApply(t *testing.T) { ExpectNonEmptyPlan: true, }, { - ConfigDirectory: config.TestNameDirectory(), + ConfigDirectory: acc.ConfigurationDirectory("TestAcc_GrantPrivilegesToDatabaseRole/AlwaysApply"), ConfigVariables: configVariables(true), ConfigPlanChecks: resource.ConfigPlanChecks{ PreApply: []plancheck.PlanCheck{ @@ -717,7 +717,7 @@ func TestAcc_GrantPrivilegesToDatabaseRole_AlwaysApply(t *testing.T) { ExpectNonEmptyPlan: true, }, { - ConfigDirectory: config.TestNameDirectory(), + ConfigDirectory: acc.ConfigurationDirectory("TestAcc_GrantPrivilegesToDatabaseRole/AlwaysApply"), ConfigVariables: configVariables(true), ConfigPlanChecks: resource.ConfigPlanChecks{ PreApply: []plancheck.PlanCheck{ @@ -731,7 +731,7 @@ func TestAcc_GrantPrivilegesToDatabaseRole_AlwaysApply(t *testing.T) { ExpectNonEmptyPlan: true, }, { - ConfigDirectory: config.TestNameDirectory(), + ConfigDirectory: acc.ConfigurationDirectory("TestAcc_GrantPrivilegesToDatabaseRole/AlwaysApply"), ConfigVariables: configVariables(false), ConfigPlanChecks: resource.ConfigPlanChecks{ PostApplyPostRefresh: []plancheck.PlanCheck{ diff --git a/pkg/resources/grant_privileges_to_role.go b/pkg/resources/grant_privileges_to_role.go index 9e2eca57ca..25deef01bc 100644 --- a/pkg/resources/grant_privileges_to_role.go +++ b/pkg/resources/grant_privileges_to_role.go @@ -123,36 +123,13 @@ var grantPrivilegesToRoleSchema = map[string]*schema.Schema{ Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "object_type": { - Type: schema.TypeString, - Optional: true, - Description: "The object type of the schema object on which privileges will be granted. Valid values are: ALERT | DYNAMIC TABLE | EVENT TABLE | FILE FORMAT | FUNCTION | ICEBERG TABLE | PROCEDURE | SECRET | SEQUENCE | PIPE | MASKING POLICY | PASSWORD POLICY | ROW ACCESS POLICY | SESSION POLICY | TAG | STAGE | STREAM | TABLE | EXTERNAL TABLE | TASK | VIEW | MATERIALIZED VIEW", - RequiredWith: []string{"on_schema_object.0.object_name"}, - ConflictsWith: []string{"on_schema_object.0.all", "on_schema_object.0.future"}, - ForceNew: true, - ValidateFunc: validation.StringInSlice([]string{ - "ALERT", - "DYNAMIC TABLE", - "EVENT TABLE", - "FILE FORMAT", - "FUNCTION", - "ICEBERG TABLE", - "PROCEDURE", - "SECRET", - "SEQUENCE", - "PIPE", - "MASKING POLICY", - "PASSWORD POLICY", - "ROW ACCESS POLICY", - "SESSION POLICY", - "TAG", - "STAGE", - "STREAM", - "TABLE", - "EXTERNAL TABLE", - "TASK", - "VIEW", - "MATERIALIZED VIEW", - }, true), + Type: schema.TypeString, + Optional: true, + Description: "The object type of the schema object on which privileges will be granted. Valid values are: ALERT | DYNAMIC TABLE | EVENT TABLE | FILE FORMAT | FUNCTION | ICEBERG TABLE | PROCEDURE | SECRET | SEQUENCE | PIPE | MASKING POLICY | PASSWORD POLICY | ROW ACCESS POLICY | SESSION POLICY | TAG | STAGE | STREAM | TABLE | EXTERNAL TABLE | TASK | VIEW | MATERIALIZED VIEW", + RequiredWith: []string{"on_schema_object.0.object_name"}, + ConflictsWith: []string{"on_schema_object.0.all", "on_schema_object.0.future"}, + ForceNew: true, + ValidateDiagFunc: ValidObjectType(), }, "object_name": { Type: schema.TypeString, @@ -172,34 +149,11 @@ var grantPrivilegesToRoleSchema = map[string]*schema.Schema{ Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "object_type_plural": { - Type: schema.TypeString, - Required: true, - Description: "The plural object type of the schema object on which privileges will be granted. Valid values are: ALERTS | DYNAMIC TABLES | EVENT TABLES | FILE FORMATS | FUNCTIONS | ICEBERG TABLES | PROCEDURES | SECRETS | SEQUENCES | PIPES | MASKING POLICIES | PASSWORD POLICIES | ROW ACCESS POLICIES | SESSION POLICIES | TAGS | STAGES | STREAMS | TABLES | EXTERNAL TABLES | TASKS | VIEWS | MATERIALIZED VIEWS", - ForceNew: true, - ValidateFunc: validation.StringInSlice([]string{ - "ALERTS", - "DYNAMIC TABLES", - "EVENT TABLES", - "FILE FORMATS", - "FUNCTIONS", - "ICEBERG TABLES", - "PROCEDURES", - "SECRETS", - "SEQUENCES", - "PIPES", - "MASKING POLICIES", - "PASSWORD POLICIES", - "ROW ACCESS POLICIES", - "SESSION POLICIES", - "TAGS", - "STAGES", - "STREAMS", - "TABLES", - "EXTERNAL TABLES", - "TASKS", - "VIEWS", - "MATERIALIZED VIEWS", - }, true), + Type: schema.TypeString, + Required: true, + Description: "The plural object type of the schema object on which privileges will be granted. Valid values are: ALERTS | DYNAMIC TABLES | EVENT TABLES | FILE FORMATS | FUNCTIONS | ICEBERG TABLES | PROCEDURES | SECRETS | SEQUENCES | PIPES | MASKING POLICIES | PASSWORD POLICIES | ROW ACCESS POLICIES | SESSION POLICIES | TAGS | STAGES | STREAMS | TABLES | EXTERNAL TABLES | TASKS | VIEWS | MATERIALIZED VIEWS", + ForceNew: true, + ValidateDiagFunc: ValidPluralObjectType(), }, "in_database": { Type: schema.TypeString, @@ -229,34 +183,11 @@ var grantPrivilegesToRoleSchema = map[string]*schema.Schema{ Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "object_type_plural": { - Type: schema.TypeString, - Required: true, - Description: "The plural object type of the schema object on which privileges will be granted. Valid values are: ALERTS | DYNAMIC TABLES | EVENT TABLES | FILE FORMATS | FUNCTIONS | ICEBERG TABLES | PROCEDURES | SECRETS | SEQUENCES | PIPES | MASKING POLICIES | PASSWORD POLICIES | ROW ACCESS POLICIES | SESSION POLICIES | TAGS | STAGES | STREAMS | TABLES | EXTERNAL TABLES | TASKS | VIEWS | MATERIALIZED VIEWS", - ForceNew: true, - ValidateFunc: validation.StringInSlice([]string{ - "ALERTS", - "DYNAMIC TABLES", - "EVENT TABLES", - "FILE FORMATS", - "FUNCTIONS", - "ICEBERG TABLES", - "PROCEDURES", - "SECRETS", - "SEQUENCES", - "PIPES", - "MASKING POLICIES", - "PASSWORD POLICIES", - "ROW ACCESS POLICIES", - "SESSION POLICIES", - "TAGS", - "STAGES", - "STREAMS", - "TABLES", - "EXTERNAL TABLES", - "TASKS", - "VIEWS", - "MATERIALIZED VIEWS", - }, true), + Type: schema.TypeString, + Required: true, + Description: "The plural object type of the schema object on which privileges will be granted. Valid values are: ALERTS | DYNAMIC TABLES | EVENT TABLES | FILE FORMATS | FUNCTIONS | ICEBERG TABLES | PROCEDURES | SECRETS | SEQUENCES | PIPES | MASKING POLICIES | PASSWORD POLICIES | ROW ACCESS POLICIES | SESSION POLICIES | TAGS | STAGES | STREAMS | TABLES | EXTERNAL TABLES | TASKS | VIEWS | MATERIALIZED VIEWS", + ForceNew: true, + ValidateDiagFunc: ValidPluralObjectType(), }, "in_database": { Type: schema.TypeString, diff --git a/pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole_AlwaysApply/test.tf b/pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole/AlwaysApply/test.tf similarity index 100% rename from pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole_AlwaysApply/test.tf rename to pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole/AlwaysApply/test.tf diff --git a/pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole_AlwaysApply/variables.tf b/pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole/AlwaysApply/variables.tf similarity index 100% rename from pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole_AlwaysApply/variables.tf rename to pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole/AlwaysApply/variables.tf diff --git a/pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole_OnAllSchemasInDatabase/test.tf b/pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole/OnAllSchemasInDatabase/test.tf similarity index 100% rename from pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole_OnAllSchemasInDatabase/test.tf rename to pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole/OnAllSchemasInDatabase/test.tf diff --git a/pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole_OnAllSchemasInDatabase/variables.tf b/pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole/OnAllSchemasInDatabase/variables.tf similarity index 100% rename from pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole_OnAllSchemasInDatabase/variables.tf rename to pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole/OnAllSchemasInDatabase/variables.tf diff --git a/pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole_OnDatabase/test.tf b/pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole/OnDatabase/test.tf similarity index 100% rename from pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole_OnDatabase/test.tf rename to pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole/OnDatabase/test.tf diff --git a/pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole_OnDatabase/variables.tf b/pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole/OnDatabase/variables.tf similarity index 100% rename from pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole_OnDatabase/variables.tf rename to pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole/OnDatabase/variables.tf diff --git a/pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole_OnFutureSchemasInDatabase/test.tf b/pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole/OnFutureSchemasInDatabase/test.tf similarity index 100% rename from pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole_OnFutureSchemasInDatabase/test.tf rename to pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole/OnFutureSchemasInDatabase/test.tf diff --git a/pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole_OnFutureSchemasInDatabase/variables.tf b/pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole/OnFutureSchemasInDatabase/variables.tf similarity index 100% rename from pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole_OnFutureSchemasInDatabase/variables.tf rename to pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole/OnFutureSchemasInDatabase/variables.tf diff --git a/pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole_OnSchema/test.tf b/pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole/OnSchema/test.tf similarity index 100% rename from pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole_OnSchema/test.tf rename to pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole/OnSchema/test.tf diff --git a/pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole_OnSchema/variables.tf b/pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole/OnSchema/variables.tf similarity index 100% rename from pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole_OnSchema/variables.tf rename to pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole/OnSchema/variables.tf diff --git a/pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole_OnSchemaObject_OnAll_InDatabase/test.tf b/pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole/OnSchemaObject_OnAll_InDatabase/test.tf similarity index 100% rename from pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole_OnSchemaObject_OnAll_InDatabase/test.tf rename to pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole/OnSchemaObject_OnAll_InDatabase/test.tf diff --git a/pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole_OnSchemaObject_OnAll_InDatabase/variables.tf b/pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole/OnSchemaObject_OnAll_InDatabase/variables.tf similarity index 100% rename from pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole_OnSchemaObject_OnAll_InDatabase/variables.tf rename to pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole/OnSchemaObject_OnAll_InDatabase/variables.tf diff --git a/pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole_OnSchemaObject_OnFuture_InDatabase/test.tf b/pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole/OnSchemaObject_OnFuture_InDatabase/test.tf similarity index 100% rename from pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole_OnSchemaObject_OnFuture_InDatabase/test.tf rename to pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole/OnSchemaObject_OnFuture_InDatabase/test.tf diff --git a/pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole_OnSchemaObject_OnFuture_InDatabase/variables.tf b/pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole/OnSchemaObject_OnFuture_InDatabase/variables.tf similarity index 100% rename from pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole_OnSchemaObject_OnFuture_InDatabase/variables.tf rename to pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole/OnSchemaObject_OnFuture_InDatabase/variables.tf diff --git a/pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole_OnSchemaObject_OnObject/test.tf b/pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole/OnSchemaObject_OnObject/test.tf similarity index 100% rename from pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole_OnSchemaObject_OnObject/test.tf rename to pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole/OnSchemaObject_OnObject/test.tf diff --git a/pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole_OnSchemaObject_OnObject/variables.tf b/pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole/OnSchemaObject_OnObject/variables.tf similarity index 100% rename from pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole_OnSchemaObject_OnObject/variables.tf rename to pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole/OnSchemaObject_OnObject/variables.tf diff --git a/pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole_OnSchema_ExactlyOneOf/test.tf b/pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole/OnSchema_ExactlyOneOf/test.tf similarity index 100% rename from pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole_OnSchema_ExactlyOneOf/test.tf rename to pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole/OnSchema_ExactlyOneOf/test.tf diff --git a/pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole_UpdatePrivileges/all_privileges/test.tf b/pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole/UpdatePrivileges/all_privileges/test.tf similarity index 100% rename from pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole_UpdatePrivileges/all_privileges/test.tf rename to pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole/UpdatePrivileges/all_privileges/test.tf diff --git a/pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole_UpdatePrivileges/all_privileges/variables.tf b/pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole/UpdatePrivileges/all_privileges/variables.tf similarity index 100% rename from pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole_UpdatePrivileges/all_privileges/variables.tf rename to pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole/UpdatePrivileges/all_privileges/variables.tf diff --git a/pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole_UpdatePrivileges/privileges/test.tf b/pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole/UpdatePrivileges/privileges/test.tf similarity index 100% rename from pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole_UpdatePrivileges/privileges/test.tf rename to pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole/UpdatePrivileges/privileges/test.tf diff --git a/pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole_UpdatePrivileges/privileges/variables.tf b/pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole/UpdatePrivileges/privileges/variables.tf similarity index 100% rename from pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole_UpdatePrivileges/privileges/variables.tf rename to pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole/UpdatePrivileges/privileges/variables.tf diff --git a/pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole_UpdatePrivileges_SnowflakeChecked/all_privileges/test.tf b/pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole/UpdatePrivileges_SnowflakeChecked/all_privileges/test.tf similarity index 100% rename from pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole_UpdatePrivileges_SnowflakeChecked/all_privileges/test.tf rename to pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole/UpdatePrivileges_SnowflakeChecked/all_privileges/test.tf diff --git a/pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole_UpdatePrivileges_SnowflakeChecked/all_privileges/variables.tf b/pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole/UpdatePrivileges_SnowflakeChecked/all_privileges/variables.tf similarity index 100% rename from pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole_UpdatePrivileges_SnowflakeChecked/all_privileges/variables.tf rename to pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole/UpdatePrivileges_SnowflakeChecked/all_privileges/variables.tf diff --git a/pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole_UpdatePrivileges_SnowflakeChecked/on_schema/test.tf b/pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole/UpdatePrivileges_SnowflakeChecked/on_schema/test.tf similarity index 100% rename from pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole_UpdatePrivileges_SnowflakeChecked/on_schema/test.tf rename to pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole/UpdatePrivileges_SnowflakeChecked/on_schema/test.tf diff --git a/pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole_UpdatePrivileges_SnowflakeChecked/on_schema/variables.tf b/pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole/UpdatePrivileges_SnowflakeChecked/on_schema/variables.tf similarity index 100% rename from pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole_UpdatePrivileges_SnowflakeChecked/on_schema/variables.tf rename to pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole/UpdatePrivileges_SnowflakeChecked/on_schema/variables.tf diff --git a/pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole_UpdatePrivileges_SnowflakeChecked/privileges/test.tf b/pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole/UpdatePrivileges_SnowflakeChecked/privileges/test.tf similarity index 100% rename from pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole_UpdatePrivileges_SnowflakeChecked/privileges/test.tf rename to pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole/UpdatePrivileges_SnowflakeChecked/privileges/test.tf diff --git a/pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole_UpdatePrivileges_SnowflakeChecked/privileges/variables.tf b/pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole/UpdatePrivileges_SnowflakeChecked/privileges/variables.tf similarity index 100% rename from pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole_UpdatePrivileges_SnowflakeChecked/privileges/variables.tf rename to pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole/UpdatePrivileges_SnowflakeChecked/privileges/variables.tf diff --git a/pkg/resources/validators.go b/pkg/resources/validators.go index a517a7e0ab..b14e122d7f 100644 --- a/pkg/resources/validators.go +++ b/pkg/resources/validators.go @@ -3,6 +3,7 @@ package resources import ( "fmt" "reflect" + "strings" "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/helpers" "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk" @@ -108,3 +109,80 @@ func getExpectedIdentifierForm(id any) string { } return "" } + +func ValidObjectType() schema.SchemaValidateDiagFunc { + return StringInSlice([]string{ + sdk.ObjectTypeAlert.String(), + sdk.ObjectTypeDynamicTable.String(), + sdk.ObjectTypeEventTable.String(), + sdk.ObjectTypeFileFormat.String(), + sdk.ObjectTypeFunction.String(), + sdk.ObjectTypeProcedure.String(), + sdk.ObjectTypeSecret.String(), + sdk.ObjectTypeSequence.String(), + sdk.ObjectTypePipe.String(), + sdk.ObjectTypeMaskingPolicy.String(), + sdk.ObjectTypePasswordPolicy.String(), + sdk.ObjectTypeRowAccessPolicy.String(), + sdk.ObjectTypeSessionPolicy.String(), + sdk.ObjectTypeTag.String(), + sdk.ObjectTypeStage.String(), + sdk.ObjectTypeStream.String(), + sdk.ObjectTypeTable.String(), + sdk.ObjectTypeExternalTable.String(), + sdk.ObjectTypeTask.String(), + sdk.ObjectTypeView.String(), + sdk.ObjectTypeMaterializedView.String(), + sdk.ObjectTypeNetworkRule.String(), + sdk.ObjectTypePackagesPolicy.String(), + sdk.ObjectTypeIcebergTable.String(), + }, true) +} + +func ValidPluralObjectType() schema.SchemaValidateDiagFunc { + return StringInSlice( + []string{ + sdk.PluralObjectTypeAlerts.String(), + sdk.PluralObjectTypeDynamicTables.String(), + sdk.PluralObjectTypeEventTables.String(), + sdk.PluralObjectTypeFileFormats.String(), + sdk.PluralObjectTypeFunctions.String(), + sdk.PluralObjectTypeProcedures.String(), + sdk.PluralObjectTypeSecrets.String(), + sdk.PluralObjectTypeSequences.String(), + sdk.PluralObjectTypePipes.String(), + sdk.PluralObjectTypeMaskingPolicies.String(), + sdk.PluralObjectTypePasswordPolicies.String(), + sdk.PluralObjectTypeRowAccessPolicies.String(), + sdk.PluralObjectTypeSessionPolicies.String(), + sdk.PluralObjectTypeTags.String(), + sdk.PluralObjectTypeStages.String(), + sdk.PluralObjectTypeStreams.String(), + sdk.PluralObjectTypeTables.String(), + sdk.PluralObjectTypeExternalTables.String(), + sdk.PluralObjectTypeTasks.String(), + sdk.PluralObjectTypeViews.String(), + sdk.PluralObjectTypeMaterializedViews.String(), + sdk.PluralObjectTypeNetworkRules.String(), + sdk.PluralObjectTypePackagesPolicies.String(), + sdk.PluralObjectTypeIcebergTables.String(), + }, true) +} + +// StringInSlice has the same implementation as validation.StringInSlice, but adapted to schema.SchemaValidateDiagFunc +func StringInSlice(valid []string, ignoreCase bool) schema.SchemaValidateDiagFunc { + return func(i interface{}, path cty.Path) diag.Diagnostics { + v, ok := i.(string) + if !ok { + return diag.Errorf("expected type of %v to be string", path) + } + + for _, str := range valid { + if v == str || (ignoreCase && strings.EqualFold(v, str)) { + return nil + } + } + + return diag.Errorf("expected %v to be one of %q, got %s", path, valid, v) + } +} diff --git a/templates/resources/grant_privileges_to_database_role.md.tmpl b/templates/resources/grant_privileges_to_database_role.md.tmpl index a820281db9..6423a74cab 100644 --- a/templates/resources/grant_privileges_to_database_role.md.tmpl +++ b/templates/resources/grant_privileges_to_database_role.md.tmpl @@ -91,6 +91,9 @@ On future contains inner types for all options. #### Grant list of privileges OnAllSchemasInDatabase `terraform import "\"test_db\".\"test_db_role\"|false|false|CREATE TAG,CREATE TABLE|OnSchema|OnAllSchemasInDatabase|\"test_db\""` +#### Grant list of privileges on table +`terraform import "\"test_db\".\"test_db_role\"|false|false|SELECT,DELETE,INSERT|OnSchemaObject|OnObject|TABLE|\"test_db\".\"test_schema\".\"test_table\""` + #### Grant list of privileges OnAll tables in schema -`terraform import "\"test_db\".\"test_db_role\"|false|false|SELECT|OnSchemaObject|OnAll|TABLES|InSchema|\"test_db\".\"test_schema\""` +`terraform import "\"test_db\".\"test_db_role\"|false|false|SELECT,DELETE,INSERT|OnSchemaObject|OnAll|TABLES|InSchema|\"test_db\".\"test_schema\""`