From 7fa09cc8a296003a09c25a1e8b88f997f6307416 Mon Sep 17 00:00:00 2001 From: Artur Sawicki Date: Thu, 28 Mar 2024 09:08:01 +0100 Subject: [PATCH] feat: Fix issues 2651 2656 (#2659) - Prove errors with granting ML privileges (references: #2651): - currently the test only - known issues section added to the docs - the case will be followed up on Snowflake side - Add possibility to grant privileges to STREAMLIT (references #2656): - moved the validations to SDK - descriptions made more general - added tests inside SDK to prove that given streamlit an ALL works, but the FUTURE results in error - added tests on resources side (no tests added in old snowflake_grant_privilege_to_role resource) - split ALL and FUTURE to have correct validation and description - the case will be followed up on Snowflake side --- .../grant_privileges_to_account_role.md | 11 +- .../grant_privileges_to_database_role.md | 6 +- docs/resources/grant_privileges_to_role.md | 6 +- docs/resources/grant_privileges_to_share.md | 3 + pkg/resources/grant_helpers.go | 64 +------- .../grant_privileges_to_account_role.go | 52 +++---- ...vileges_to_account_role_acceptance_test.go | 139 +++++++++++++++++- .../grant_privileges_to_database_role.go | 55 +++---- ...ileges_to_database_role_acceptance_test.go | 138 ++++++++++++++++- pkg/resources/grant_privileges_to_role.go | 12 +- .../OnSchemaObject_OnAll_InDatabase/test.tf | 2 +- .../variables.tf | 4 + .../test.tf | 2 +- .../variables.tf | 4 + .../OnSchemaObject_OnAll_InDatabase/test.tf | 2 +- .../variables.tf | 4 + .../test.tf | 2 +- .../variables.tf | 4 + pkg/sdk/grants_validations.go | 42 ++++++ .../testint/streamlits_integration_test.go | 134 +++++++++++++++++ .../grant_privileges_to_account_role.md.tmpl | 3 + .../grant_privileges_to_share.md.tmpl | 3 + 22 files changed, 550 insertions(+), 142 deletions(-) diff --git a/docs/resources/grant_privileges_to_account_role.md b/docs/resources/grant_privileges_to_account_role.md index 64af3480f7..9358449ece 100644 --- a/docs/resources/grant_privileges_to_account_role.md +++ b/docs/resources/grant_privileges_to_account_role.md @@ -300,16 +300,16 @@ Optional: 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 all objects in either a database or schema. (see [below for nested schema](#nestedblock--on_schema_object--future)) +- `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 +- `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 | STREAMLIT | 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 +- `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 | STREAMLITS | ICEBERG TABLES. Optional: @@ -322,13 +322,16 @@ Optional: 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 +- `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) +## Known limitations +- Setting the `CREATE SNOWFLAKE.ML.ANOMALY_DETECTION` or `CREATE SNOWFLAKE.ML.FORECAST` privileges on schema results in a permadiff because of the probably incorrect Snowflake's behavior of `SHOW GRANTS ON `. More in the [comment](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/2651#issuecomment-2022634952). + ## Import ~> **Note** All the ..._name parts should be fully qualified names (where every part is quoted), e.g. for schema object it is `""."".""` diff --git a/docs/resources/grant_privileges_to_database_role.md b/docs/resources/grant_privileges_to_database_role.md index e853927cc6..b3e8b16564 100644 --- a/docs/resources/grant_privileges_to_database_role.md +++ b/docs/resources/grant_privileges_to_database_role.md @@ -206,14 +206,14 @@ 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 +- `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 | STREAMLIT | 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 +- `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 | STREAMLITS | ICEBERG TABLES. Optional: @@ -226,7 +226,7 @@ Optional: 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 +- `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: diff --git a/docs/resources/grant_privileges_to_role.md b/docs/resources/grant_privileges_to_role.md index f6983d3de4..2275a332cc 100644 --- a/docs/resources/grant_privileges_to_role.md +++ b/docs/resources/grant_privileges_to_role.md @@ -219,14 +219,14 @@ Optional: - `all` (Block List, Max: 1) Configures the privilege to be granted on all objects in eihter 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 eihter 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 | 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 +- `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 | STREAMLIT | 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 | 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 +- `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 | STREAMLITS | ICEBERG TABLES Optional: @@ -239,7 +239,7 @@ Optional: 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 | 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 +- `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: diff --git a/docs/resources/grant_privileges_to_share.md b/docs/resources/grant_privileges_to_share.md index a9e4c07377..6fa78c3e08 100644 --- a/docs/resources/grant_privileges_to_share.md +++ b/docs/resources/grant_privileges_to_share.md @@ -119,6 +119,9 @@ resource "snowflake_grant_privileges_to_share" "example" { - `id` (String) The ID of this resource. +## Known limitations +- Setting the `CREATE SNOWFLAKE.ML.ANOMALY_DETECTION` or `CREATE SNOWFLAKE.ML.FORECAST` privileges on schema results in a permadiff because of the probably incorrect Snowflake's behavior of `SHOW GRANTS ON `. More in the [comment](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/2651#issuecomment-2022634952). + ## Import ~> **Note** All the ..._name parts should be fully qualified names, e.g. for database object it is `"".""` diff --git a/pkg/resources/grant_helpers.go b/pkg/resources/grant_helpers.go index d4d6afeaa5..6217041957 100644 --- a/pkg/resources/grant_helpers.go +++ b/pkg/resources/grant_helpers.go @@ -7,12 +7,9 @@ import ( "time" "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/internal/provider" - - "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk" + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/snowflake" "github.com/hashicorp/go-cty/cty" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" - - "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/snowflake" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/jmoiron/sqlx" "github.com/snowflakedb/gosnowflake" @@ -411,62 +408,3 @@ func isNotOwnershipGrant() func(value any, path cty.Path) diag.Diagnostics { return diags } } - -func ValidGrantedObjectType() 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 ValidGrantedPluralObjectType() 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) -} diff --git a/pkg/resources/grant_privileges_to_account_role.go b/pkg/resources/grant_privileges_to_account_role.go index 6095a5fb21..14c988c8db 100644 --- a/pkg/resources/grant_privileges_to_account_role.go +++ b/pkg/resources/grant_privileges_to_account_role.go @@ -196,7 +196,7 @@ var grantPrivilegesToAccountRoleSchema = map[string]*schema.Schema{ 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", + Description: fmt.Sprintf("The object type of the schema object on which privileges will be granted. Valid values are: %s", strings.Join(sdk.ValidGrantToObjectTypesString, " | ")), RequiredWith: []string{ "on_schema_object.0.object_name", }, @@ -204,7 +204,7 @@ var grantPrivilegesToAccountRoleSchema = map[string]*schema.Schema{ "on_schema_object.0.all", "on_schema_object.0.future", }, - ValidateDiagFunc: ValidGrantedObjectType(), + ValidateDiagFunc: StringInSlice(sdk.ValidGrantToObjectTypesString, true), }, "object_name": { Type: schema.TypeString, @@ -228,7 +228,7 @@ var grantPrivilegesToAccountRoleSchema = map[string]*schema.Schema{ Description: "Configures the privilege to be granted on all objects in either a database or schema.", MaxItems: 1, Elem: &schema.Resource{ - Schema: grantPrivilegesOnAccountRoleBulkOperationSchema, + Schema: getGrantPrivilegesOnAccountRoleBulkOperationSchema(sdk.ValidGrantToPluralObjectTypesString), }, ConflictsWith: []string{ "on_schema_object.0.object_type", @@ -243,10 +243,10 @@ var grantPrivilegesToAccountRoleSchema = map[string]*schema.Schema{ Type: schema.TypeList, Optional: true, ForceNew: true, - Description: "Configures the privilege to be granted on all objects in either a database or schema.", + Description: "Configures the privilege to be granted on future objects in either a database or schema.", MaxItems: 1, Elem: &schema.Resource{ - Schema: grantPrivilegesOnAccountRoleBulkOperationSchema, + Schema: getGrantPrivilegesOnAccountRoleBulkOperationSchema(sdk.ValidGrantToFuturePluralObjectTypesString), }, ConflictsWith: []string{ "on_schema_object.0.object_type", @@ -262,26 +262,28 @@ var grantPrivilegesToAccountRoleSchema = map[string]*schema.Schema{ }, } -var grantPrivilegesOnAccountRoleBulkOperationSchema = 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", - ValidateDiagFunc: ValidGrantedPluralObjectType(), - }, - "in_database": { - Type: schema.TypeString, - Optional: true, - ForceNew: true, - ValidateDiagFunc: IsValidIdentifier[sdk.AccountObjectIdentifier](), - }, - "in_schema": { - Type: schema.TypeString, - Optional: true, - ForceNew: true, - ValidateDiagFunc: IsValidIdentifier[sdk.DatabaseObjectIdentifier](), - }, +func getGrantPrivilegesOnAccountRoleBulkOperationSchema(validGrantToObjectTypes []string) map[string]*schema.Schema { + return map[string]*schema.Schema{ + "object_type_plural": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + Description: fmt.Sprintf("The plural object type of the schema object on which privileges will be granted. Valid values are: %s.", strings.Join(validGrantToObjectTypes, " | ")), + ValidateDiagFunc: StringInSlice(validGrantToObjectTypes, true), + }, + "in_database": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + ValidateDiagFunc: IsValidIdentifier[sdk.AccountObjectIdentifier](), + }, + "in_schema": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + ValidateDiagFunc: IsValidIdentifier[sdk.DatabaseObjectIdentifier](), + }, + } } func GrantPrivilegesToAccountRole() *schema.Resource { diff --git a/pkg/resources/grant_privileges_to_account_role_acceptance_test.go b/pkg/resources/grant_privileges_to_account_role_acceptance_test.go index 309d92aa09..61efcbc081 100644 --- a/pkg/resources/grant_privileges_to_account_role_acceptance_test.go +++ b/pkg/resources/grant_privileges_to_account_role_acceptance_test.go @@ -469,8 +469,9 @@ func TestAcc_GrantPrivilegesToAccountRole_OnSchemaObject_OnAll_InDatabase(t *tes config.StringVariable(string(sdk.SchemaObjectPrivilegeInsert)), config.StringVariable(string(sdk.SchemaObjectPrivilegeUpdate)), ), - "database": config.StringVariable(databaseName), - "with_grant_option": config.BoolVariable(false), + "database": config.StringVariable(databaseName), + "object_type_plural": config.StringVariable(sdk.PluralObjectTypeTables.String()), + "with_grant_option": config.BoolVariable(false), } resourceName := "snowflake_grant_privileges_to_account_role.test" @@ -569,8 +570,9 @@ func TestAcc_GrantPrivilegesToAccountRole_OnSchemaObject_OnFuture_InDatabase(t * config.StringVariable(string(sdk.SchemaObjectPrivilegeInsert)), config.StringVariable(string(sdk.SchemaObjectPrivilegeUpdate)), ), - "database": config.StringVariable(databaseName), - "with_grant_option": config.BoolVariable(false), + "database": config.StringVariable(databaseName), + "object_type_plural": config.StringVariable(sdk.PluralObjectTypeTables.String()), + "with_grant_option": config.BoolVariable(false), } resourceName := "snowflake_grant_privileges_to_account_role.test" @@ -610,6 +612,83 @@ func TestAcc_GrantPrivilegesToAccountRole_OnSchemaObject_OnFuture_InDatabase(t * }) } +// TODO [SNOW-1272222]: fix the test when it starts working on Snowflake side +func TestAcc_GrantPrivilegesToAccountRole_OnSchemaObject_OnFuture_Streamlits_InDatabase(t *testing.T) { + t.Skip("Fix after it starts working on Snowflake side, reference: SNOW-1272222") + name := strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha)) + roleName := sdk.NewAccountObjectIdentifier(name).FullyQualifiedName() + databaseName := sdk.NewAccountObjectIdentifier(acc.TestDatabaseName).FullyQualifiedName() + configVariables := config.Variables{ + "name": config.StringVariable(roleName), + "privileges": config.ListVariable( + config.StringVariable(string(sdk.SchemaObjectPrivilegeUsage)), + ), + "database": config.StringVariable(databaseName), + "object_type_plural": config.StringVariable(sdk.PluralObjectTypeStreamlits.String()), + "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: testAccCheckAccountRolePrivilegesRevoked(name), + Steps: []resource.TestStep{ + { + PreConfig: func() { createAccountRoleOutsideTerraform(t, name) }, + ConfigDirectory: acc.ConfigurationDirectory("TestAcc_GrantPrivilegesToAccountRole/OnSchemaObject_OnFuture_InDatabase"), + ConfigVariables: configVariables, + ExpectError: regexp.MustCompile("Unsupported feature 'STREAMLIT'"), + }, + }, + }) +} + +func TestAcc_GrantPrivilegesToAccountRole_OnSchemaObject_OnAll_Streamlits_InDatabase(t *testing.T) { + name := strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha)) + roleName := sdk.NewAccountObjectIdentifier(name).FullyQualifiedName() + databaseName := sdk.NewAccountObjectIdentifier(acc.TestDatabaseName).FullyQualifiedName() + configVariables := config.Variables{ + "name": config.StringVariable(roleName), + "privileges": config.ListVariable( + config.StringVariable(string(sdk.SchemaObjectPrivilegeUsage)), + ), + "database": config.StringVariable(databaseName), + "object_type_plural": config.StringVariable(sdk.PluralObjectTypeStreamlits.String()), + "with_grant_option": config.BoolVariable(false), + } + resourceName := "snowflake_grant_privileges_to_account_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: testAccCheckAccountRolePrivilegesRevoked(name), + Steps: []resource.TestStep{ + { + PreConfig: func() { createAccountRoleOutsideTerraform(t, name) }, + ConfigDirectory: acc.ConfigurationDirectory("TestAcc_GrantPrivilegesToAccountRole/OnSchemaObject_OnAll_InDatabase"), + ConfigVariables: configVariables, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "account_role_name", roleName), + resource.TestCheckResourceAttr(resourceName, "privileges.#", "1"), + resource.TestCheckResourceAttr(resourceName, "privileges.0", string(sdk.SchemaObjectPrivilegeUsage)), + 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.PluralObjectTypeStreamlits)), + 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|USAGE|OnSchemaObject|OnAll|STREAMLITS|InDatabase|%s", roleName, databaseName)), + ), + }, + }, + }) +} + func TestAcc_GrantPrivilegesToAccountRole_UpdatePrivileges(t *testing.T) { name := strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha)) roleName := sdk.NewAccountObjectIdentifier(name).FullyQualifiedName() @@ -1059,6 +1138,58 @@ func TestAcc_GrantPrivilegesToAccountRole_OnExternalVolume(t *testing.T) { }) } +// proves https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/2651 +// TODO [SNOW-1270457]: This seems to be a Snowflake error, we are waiting for the confirmation. Alter the test when the behavior is fixed. Update the resource documentation (section known issues). +func TestAcc_GrantPrivilegesToAccountRole_MLPrivileges(t *testing.T) { + name := strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha)) + roleName := sdk.NewAccountObjectIdentifier(name).FullyQualifiedName() + configVariables := config.Variables{ + "name": config.StringVariable(roleName), + "privileges": config.ListVariable( + config.StringVariable(string(sdk.SchemaPrivilegeCreateSnowflakeMlAnomalyDetection)), + config.StringVariable(string(sdk.SchemaPrivilegeCreateSnowflakeMlForecast)), + ), + "database": config.StringVariable(acc.TestDatabaseName), + "schema": config.StringVariable(acc.TestSchemaName), + "with_grant_option": config.BoolVariable(false), + } + resourceName := "snowflake_grant_privileges_to_account_role.test" + + schemaName := sdk.NewDatabaseObjectIdentifier(acc.TestDatabaseName, acc.TestSchemaName).FullyQualifiedName() + + resource.Test(t, resource.TestCase{ + ProtoV6ProviderFactories: acc.TestAccProtoV6ProviderFactories, + PreCheck: func() { acc.TestAccPreCheck(t) }, + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.RequireAbove(tfversion.Version1_5_0), + }, + CheckDestroy: testAccCheckAccountRolePrivilegesRevoked(name), + Steps: []resource.TestStep{ + { + PreConfig: func() { createAccountRoleOutsideTerraform(t, name) }, + ConfigDirectory: acc.ConfigurationDirectory("TestAcc_GrantPrivilegesToAccountRole/OnSchema"), + ConfigVariables: configVariables, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "account_role_name", roleName), + resource.TestCheckResourceAttr(resourceName, "privileges.#", "2"), + resource.TestCheckResourceAttr(resourceName, "privileges.0", string(sdk.SchemaPrivilegeCreateSnowflakeMlAnomalyDetection)), + resource.TestCheckResourceAttr(resourceName, "privileges.1", string(sdk.SchemaPrivilegeCreateSnowflakeMlForecast)), + resource.TestCheckResourceAttr(resourceName, "on_schema.#", "1"), + 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 SNOWFLAKE.ML.ANOMALY_DETECTION,CREATE SNOWFLAKE.ML.FORECAST|OnSchema|OnSchema|%s", roleName, schemaName)), + ), + ExpectNonEmptyPlan: true, + ConfigPlanChecks: resource.ConfigPlanChecks{ + PostApplyPostRefresh: []plancheck.PlanCheck{ + plancheck.ExpectNonEmptyPlan(), + }, + }, + }, + }, + }) +} + func getSecondaryAccountName(t *testing.T) (string, error) { t.Helper() config, err := sdk.ProfileConfig(testprofiles.Secondary) diff --git a/pkg/resources/grant_privileges_to_database_role.go b/pkg/resources/grant_privileges_to_database_role.go index 6dc873d323..9834ca7fc0 100644 --- a/pkg/resources/grant_privileges_to_database_role.go +++ b/pkg/resources/grant_privileges_to_database_role.go @@ -5,6 +5,7 @@ import ( "fmt" "log" "slices" + "strings" "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/internal/provider" @@ -147,7 +148,7 @@ var grantPrivilegesToDatabaseRoleSchema = map[string]*schema.Schema{ 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", + Description: fmt.Sprintf("The object type of the schema object on which privileges will be granted. Valid values are: %s", strings.Join(sdk.ValidGrantToObjectTypesString, " | ")), RequiredWith: []string{ "on_schema_object.0.object_name", }, @@ -155,7 +156,7 @@ var grantPrivilegesToDatabaseRoleSchema = map[string]*schema.Schema{ "on_schema_object.0.all", "on_schema_object.0.future", }, - ValidateDiagFunc: ValidGrantedObjectType(), + ValidateDiagFunc: StringInSlice(sdk.ValidGrantToObjectTypesString, true), }, "object_name": { Type: schema.TypeString, @@ -179,7 +180,7 @@ var grantPrivilegesToDatabaseRoleSchema = map[string]*schema.Schema{ Description: "Configures the privilege to be granted on all objects in either a database or schema.", MaxItems: 1, Elem: &schema.Resource{ - Schema: grantPrivilegesOnDatabaseRoleBulkOperationSchema, + Schema: getGrantPrivilegesOnDatabaseRoleBulkOperationSchema(sdk.ValidGrantToPluralObjectTypesString), }, ConflictsWith: []string{ "on_schema_object.0.object_type", @@ -197,7 +198,7 @@ var grantPrivilegesToDatabaseRoleSchema = map[string]*schema.Schema{ Description: "Configures the privilege to be granted on future objects in either a database or schema.", MaxItems: 1, Elem: &schema.Resource{ - Schema: grantPrivilegesOnDatabaseRoleBulkOperationSchema, + Schema: getGrantPrivilegesOnDatabaseRoleBulkOperationSchema(sdk.ValidGrantToFuturePluralObjectTypesString), }, ConflictsWith: []string{ "on_schema_object.0.object_type", @@ -213,28 +214,30 @@ 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", - ValidateDiagFunc: ValidGrantedPluralObjectType(), - }, - "in_database": { - Type: schema.TypeString, - Optional: true, - ForceNew: true, - Description: "The fully qualified name of the database.", - ValidateDiagFunc: IsValidIdentifier[sdk.AccountObjectIdentifier](), - }, - "in_schema": { - Type: schema.TypeString, - Optional: true, - ForceNew: true, - Description: "The fully qualified name of the schema.", - ValidateDiagFunc: IsValidIdentifier[sdk.DatabaseObjectIdentifier](), - }, +func getGrantPrivilegesOnDatabaseRoleBulkOperationSchema(validGrantToObjectTypes []string) map[string]*schema.Schema { + return map[string]*schema.Schema{ + "object_type_plural": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + Description: fmt.Sprintf("The plural object type of the schema object on which privileges will be granted. Valid values are: %s.", strings.Join(validGrantToObjectTypes, " | ")), + ValidateDiagFunc: StringInSlice(validGrantToObjectTypes, true), + }, + "in_database": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + Description: "The fully qualified name of the database.", + ValidateDiagFunc: IsValidIdentifier[sdk.AccountObjectIdentifier](), + }, + "in_schema": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + Description: "The fully qualified name of the schema.", + ValidateDiagFunc: IsValidIdentifier[sdk.DatabaseObjectIdentifier](), + }, + } } func GrantPrivilegesToDatabaseRole() *schema.Resource { 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 f3c9497428..0de8ff0c69 100644 --- a/pkg/resources/grant_privileges_to_database_role_acceptance_test.go +++ b/pkg/resources/grant_privileges_to_database_role_acceptance_test.go @@ -385,8 +385,9 @@ func TestAcc_GrantPrivilegesToDatabaseRole_OnSchemaObject_OnAll_InDatabase(t *te config.StringVariable(string(sdk.SchemaObjectPrivilegeInsert)), config.StringVariable(string(sdk.SchemaObjectPrivilegeUpdate)), ), - "database": config.StringVariable(acc.TestDatabaseName), - "with_grant_option": config.BoolVariable(false), + "database": config.StringVariable(acc.TestDatabaseName), + "object_type_plural": config.StringVariable(sdk.PluralObjectTypeTables.String()), + "with_grant_option": config.BoolVariable(false), } resourceName := "snowflake_grant_privileges_to_database_role.test" @@ -487,8 +488,9 @@ func TestAcc_GrantPrivilegesToDatabaseRole_OnSchemaObject_OnFuture_InDatabase(t config.StringVariable(string(sdk.SchemaObjectPrivilegeInsert)), config.StringVariable(string(sdk.SchemaObjectPrivilegeUpdate)), ), - "database": config.StringVariable(acc.TestDatabaseName), - "with_grant_option": config.BoolVariable(false), + "database": config.StringVariable(acc.TestDatabaseName), + "object_type_plural": config.StringVariable(sdk.PluralObjectTypeTables.String()), + "with_grant_option": config.BoolVariable(false), } resourceName := "snowflake_grant_privileges_to_database_role.test" @@ -531,6 +533,82 @@ func TestAcc_GrantPrivilegesToDatabaseRole_OnSchemaObject_OnFuture_InDatabase(t }) } +// TODO [SNOW-1272222]: fix the test when it starts working on Snowflake side +func TestAcc_GrantPrivilegesToDatabaseRole_OnSchemaObject_OnFuture_Streamlits_InDatabase(t *testing.T) { + t.Skip("Fix after it starts working on Snowflake side, reference: SNOW-1272222") + name := strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha)) + configVariables := config.Variables{ + "name": config.StringVariable(name), + "privileges": config.ListVariable( + config.StringVariable(string(sdk.SchemaObjectPrivilegeUsage)), + ), + "database": config.StringVariable(acc.TestDatabaseName), + "object_type_plural": config.StringVariable(sdk.PluralObjectTypeStreamlits.String()), + "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_OnFuture_InDatabase"), + ConfigVariables: configVariables, + ExpectError: regexp.MustCompile("Unsupported feature 'STREAMLIT'"), + }, + }, + }) +} + +func TestAcc_GrantPrivilegesToDatabaseRole_OnSchemaObject_OnAll_Streamlits_InDatabase(t *testing.T) { + name := strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha)) + configVariables := config.Variables{ + "name": config.StringVariable(name), + "privileges": config.ListVariable( + config.StringVariable(string(sdk.SchemaObjectPrivilegeUsage)), + ), + "database": config.StringVariable(acc.TestDatabaseName), + "object_type_plural": config.StringVariable(sdk.PluralObjectTypeStreamlits.String()), + "with_grant_option": config.BoolVariable(false), + } + 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: testAccCheckAccountRolePrivilegesRevoked(name), + Steps: []resource.TestStep{ + { + PreConfig: func() { createDatabaseRoleOutsideTerraform(t, name) }, + ConfigDirectory: acc.ConfigurationDirectory("TestAcc_GrantPrivilegesToDatabaseRole/OnSchemaObject_OnAll_InDatabase"), + ConfigVariables: configVariables, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "database_role_name", databaseRoleName), + resource.TestCheckResourceAttr(resourceName, "privileges.#", "1"), + resource.TestCheckResourceAttr(resourceName, "privileges.0", string(sdk.SchemaObjectPrivilegeUsage)), + 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.PluralObjectTypeStreamlits)), + 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|USAGE|OnSchemaObject|OnAll|STREAMLITS|InDatabase|%s", databaseRoleName, databaseName)), + ), + }, + }, + }) +} + func TestAcc_GrantPrivilegesToDatabaseRole_UpdatePrivileges(t *testing.T) { name := strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha)) configVariables := func(allPrivileges bool, privileges []sdk.AccountObjectPrivilege) config.Variables { @@ -799,6 +877,58 @@ func TestAcc_GrantPrivilegesToDatabaseRole_AlwaysApply(t *testing.T) { }) } +// proves https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/2651 +// TODO [SNOW-1270457]: This seems to be a Snowflake error, we are waiting for the confirmation. Alter the test when the behavior is fixed. Update the resource documentation (section known issues). +func TestAcc_GrantPrivilegesToDatabaseRole_MLPrivileges(t *testing.T) { + name := strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha)) + configVariables := config.Variables{ + "name": config.StringVariable(name), + "privileges": config.ListVariable( + config.StringVariable(string(sdk.SchemaPrivilegeCreateSnowflakeMlAnomalyDetection)), + config.StringVariable(string(sdk.SchemaPrivilegeCreateSnowflakeMlForecast)), + ), + "database": config.StringVariable(acc.TestDatabaseName), + "schema": config.StringVariable(acc.TestSchemaName), + "with_grant_option": config.BoolVariable(false), + } + 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) }, + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.RequireAbove(tfversion.Version1_5_0), + }, + CheckDestroy: testAccCheckAccountRolePrivilegesRevoked(name), + Steps: []resource.TestStep{ + { + PreConfig: func() { createDatabaseRoleOutsideTerraform(t, name) }, + ConfigDirectory: acc.ConfigurationDirectory("TestAcc_GrantPrivilegesToDatabaseRole/OnSchema"), + ConfigVariables: configVariables, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "database_role_name", databaseRoleName), + resource.TestCheckResourceAttr(resourceName, "privileges.#", "2"), + resource.TestCheckResourceAttr(resourceName, "privileges.0", string(sdk.SchemaPrivilegeCreateSnowflakeMlAnomalyDetection)), + resource.TestCheckResourceAttr(resourceName, "privileges.1", string(sdk.SchemaPrivilegeCreateSnowflakeMlForecast)), + resource.TestCheckResourceAttr(resourceName, "on_schema.#", "1"), + 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 SNOWFLAKE.ML.ANOMALY_DETECTION,CREATE SNOWFLAKE.ML.FORECAST|OnSchema|OnSchema|%s", databaseRoleName, schemaName)), + ), + ExpectNonEmptyPlan: true, + ConfigPlanChecks: resource.ConfigPlanChecks{ + PostApplyPostRefresh: []plancheck.PlanCheck{ + plancheck.ExpectNonEmptyPlan(), + }, + }, + }, + }, + }) +} + func createDatabaseRoleOutsideTerraform(t *testing.T, name string) { t.Helper() client, err := sdk.NewDefaultClient() diff --git a/pkg/resources/grant_privileges_to_role.go b/pkg/resources/grant_privileges_to_role.go index 109385bb82..c54ed16fe3 100644 --- a/pkg/resources/grant_privileges_to_role.go +++ b/pkg/resources/grant_privileges_to_role.go @@ -130,11 +130,11 @@ var grantPrivilegesToRoleSchema = 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", + Description: fmt.Sprintf("The object type of the schema object on which privileges will be granted. Valid values are: %s", strings.Join(sdk.ValidGrantToObjectTypesString, " | ")), RequiredWith: []string{"on_schema_object.0.object_name"}, ConflictsWith: []string{"on_schema_object.0.all", "on_schema_object.0.future"}, ForceNew: true, - ValidateDiagFunc: ValidGrantedObjectType(), + ValidateDiagFunc: StringInSlice(sdk.ValidGrantToObjectTypesString, true), }, "object_name": { Type: schema.TypeString, @@ -156,9 +156,9 @@ var grantPrivilegesToRoleSchema = 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", + Description: fmt.Sprintf("The plural object type of the schema object on which privileges will be granted. Valid values are: %s", strings.Join(sdk.ValidGrantToPluralObjectTypesString, " | ")), ForceNew: true, - ValidateDiagFunc: ValidGrantedPluralObjectType(), + ValidateDiagFunc: StringInSlice(sdk.ValidGrantToPluralObjectTypesString, true), }, "in_database": { Type: schema.TypeString, @@ -190,9 +190,9 @@ var grantPrivilegesToRoleSchema = 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", + Description: fmt.Sprintf("The plural object type of the schema object on which privileges will be granted. Valid values are: %s", strings.Join(sdk.ValidGrantToFuturePluralObjectTypesString, " | ")), ForceNew: true, - ValidateDiagFunc: ValidGrantedPluralObjectType(), + ValidateDiagFunc: StringInSlice(sdk.ValidGrantToFuturePluralObjectTypesString, true), }, "in_database": { Type: schema.TypeString, diff --git a/pkg/resources/testdata/TestAcc_GrantPrivilegesToAccountRole/OnSchemaObject_OnAll_InDatabase/test.tf b/pkg/resources/testdata/TestAcc_GrantPrivilegesToAccountRole/OnSchemaObject_OnAll_InDatabase/test.tf index 7fa86e21f7..b787a89214 100644 --- a/pkg/resources/testdata/TestAcc_GrantPrivilegesToAccountRole/OnSchemaObject_OnAll_InDatabase/test.tf +++ b/pkg/resources/testdata/TestAcc_GrantPrivilegesToAccountRole/OnSchemaObject_OnAll_InDatabase/test.tf @@ -5,7 +5,7 @@ resource "snowflake_grant_privileges_to_account_role" "test" { on_schema_object { all { - object_type_plural = "TABLES" + object_type_plural = var.object_type_plural in_database = var.database } } diff --git a/pkg/resources/testdata/TestAcc_GrantPrivilegesToAccountRole/OnSchemaObject_OnAll_InDatabase/variables.tf b/pkg/resources/testdata/TestAcc_GrantPrivilegesToAccountRole/OnSchemaObject_OnAll_InDatabase/variables.tf index 0e22e903d7..fde4549568 100644 --- a/pkg/resources/testdata/TestAcc_GrantPrivilegesToAccountRole/OnSchemaObject_OnAll_InDatabase/variables.tf +++ b/pkg/resources/testdata/TestAcc_GrantPrivilegesToAccountRole/OnSchemaObject_OnAll_InDatabase/variables.tf @@ -10,6 +10,10 @@ variable "database" { type = string } +variable "object_type_plural" { + type = string +} + variable "with_grant_option" { type = bool } diff --git a/pkg/resources/testdata/TestAcc_GrantPrivilegesToAccountRole/OnSchemaObject_OnFuture_InDatabase/test.tf b/pkg/resources/testdata/TestAcc_GrantPrivilegesToAccountRole/OnSchemaObject_OnFuture_InDatabase/test.tf index ed7804ce4b..4da613126e 100644 --- a/pkg/resources/testdata/TestAcc_GrantPrivilegesToAccountRole/OnSchemaObject_OnFuture_InDatabase/test.tf +++ b/pkg/resources/testdata/TestAcc_GrantPrivilegesToAccountRole/OnSchemaObject_OnFuture_InDatabase/test.tf @@ -5,7 +5,7 @@ resource "snowflake_grant_privileges_to_account_role" "test" { on_schema_object { future { - object_type_plural = "TABLES" + object_type_plural = var.object_type_plural in_database = var.database } } diff --git a/pkg/resources/testdata/TestAcc_GrantPrivilegesToAccountRole/OnSchemaObject_OnFuture_InDatabase/variables.tf b/pkg/resources/testdata/TestAcc_GrantPrivilegesToAccountRole/OnSchemaObject_OnFuture_InDatabase/variables.tf index 0e22e903d7..fde4549568 100644 --- a/pkg/resources/testdata/TestAcc_GrantPrivilegesToAccountRole/OnSchemaObject_OnFuture_InDatabase/variables.tf +++ b/pkg/resources/testdata/TestAcc_GrantPrivilegesToAccountRole/OnSchemaObject_OnFuture_InDatabase/variables.tf @@ -10,6 +10,10 @@ variable "database" { type = string } +variable "object_type_plural" { + 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 index 230a702d23..75e38e11c6 100644 --- a/pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole/OnSchemaObject_OnAll_InDatabase/test.tf +++ b/pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole/OnSchemaObject_OnAll_InDatabase/test.tf @@ -5,7 +5,7 @@ resource "snowflake_grant_privileges_to_database_role" "test" { on_schema_object { all { - object_type_plural = "TABLES" + object_type_plural = var.object_type_plural 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 index 0e22e903d7..fde4549568 100644 --- a/pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole/OnSchemaObject_OnAll_InDatabase/variables.tf +++ b/pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole/OnSchemaObject_OnAll_InDatabase/variables.tf @@ -10,6 +10,10 @@ variable "database" { type = string } +variable "object_type_plural" { + 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 index 3463a24a8f..42b8c2a0b1 100644 --- a/pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole/OnSchemaObject_OnFuture_InDatabase/test.tf +++ b/pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole/OnSchemaObject_OnFuture_InDatabase/test.tf @@ -5,7 +5,7 @@ resource "snowflake_grant_privileges_to_database_role" "test" { on_schema_object { future { - object_type_plural = "TABLES" + object_type_plural = var.object_type_plural 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 index 0e22e903d7..fde4549568 100644 --- a/pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole/OnSchemaObject_OnFuture_InDatabase/variables.tf +++ b/pkg/resources/testdata/TestAcc_GrantPrivilegesToDatabaseRole/OnSchemaObject_OnFuture_InDatabase/variables.tf @@ -10,6 +10,10 @@ variable "database" { type = string } +variable "object_type_plural" { + type = string +} + variable "with_grant_option" { type = bool } diff --git a/pkg/sdk/grants_validations.go b/pkg/sdk/grants_validations.go index 1df10d643e..7b9e92f7c7 100644 --- a/pkg/sdk/grants_validations.go +++ b/pkg/sdk/grants_validations.go @@ -61,9 +61,44 @@ var validGrantOwnershipObjectTypes = []ObjectType{ ObjectTypeWarehouse, } +var validGrantToObjectTypes = []ObjectType{ + ObjectTypeAlert, + ObjectTypeDynamicTable, + ObjectTypeEventTable, + ObjectTypeFileFormat, + ObjectTypeFunction, + ObjectTypeProcedure, + ObjectTypeSecret, + ObjectTypeSequence, + ObjectTypePipe, + ObjectTypeMaskingPolicy, + ObjectTypePasswordPolicy, + ObjectTypeRowAccessPolicy, + ObjectTypeSessionPolicy, + ObjectTypeTag, + ObjectTypeStage, + ObjectTypeStream, + ObjectTypeTable, + ObjectTypeExternalTable, + ObjectTypeTask, + ObjectTypeView, + ObjectTypeMaterializedView, + ObjectTypeNetworkRule, + ObjectTypePackagesPolicy, + ObjectTypeStreamlit, + ObjectTypeIcebergTable, +} + +var invalidGrantToFutureObjectTypes = []ObjectType{ + ObjectTypeStreamlit, +} + var ( ValidGrantOwnershipObjectTypesString = make([]string, len(validGrantOwnershipObjectTypes)) ValidGrantOwnershipPluralObjectTypesString = make([]string, len(validGrantOwnershipObjectTypes)) + ValidGrantToObjectTypesString = make([]string, len(validGrantToObjectTypes)) + ValidGrantToPluralObjectTypesString = make([]string, len(validGrantToObjectTypes)) + ValidGrantToFuturePluralObjectTypesString = make([]string, 0) ) func init() { @@ -71,6 +106,13 @@ func init() { ValidGrantOwnershipObjectTypesString[i] = objectType.String() ValidGrantOwnershipPluralObjectTypesString[i] = objectType.Plural().String() } + for i, objectType := range validGrantToObjectTypes { + ValidGrantToObjectTypesString[i] = objectType.String() + ValidGrantToPluralObjectTypesString[i] = objectType.Plural().String() + if !slices.Contains(invalidGrantToFutureObjectTypes, objectType) { + ValidGrantToFuturePluralObjectTypesString = append(ValidGrantToFuturePluralObjectTypesString, objectType.Plural().String()) + } + } } func (opts *GrantPrivilegesToAccountRoleOptions) validate() error { diff --git a/pkg/sdk/testint/streamlits_integration_test.go b/pkg/sdk/testint/streamlits_integration_test.go index 1680389baa..ace858a844 100644 --- a/pkg/sdk/testint/streamlits_integration_test.go +++ b/pkg/sdk/testint/streamlits_integration_test.go @@ -6,6 +6,7 @@ import ( "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk" "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk/internal/collections" "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk/internal/random" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -69,6 +70,139 @@ func TestInt_Streamlits(t *testing.T) { assertStreamlit(t, id, comment, "") }) + // TODO [SNOW-1272222]: fix the test when it starts working on Snowflake side + t.Run("grant privilege to streamlits to role", func(t *testing.T) { + stage, cleanupStage := createStage(t, client, sdk.NewSchemaObjectIdentifier(TestDatabaseName, TestSchemaName, random.AlphaN(4))) + t.Cleanup(cleanupStage) + + role, roleCleanup := createRole(t, client) + t.Cleanup(roleCleanup) + + comment := random.StringN(4) + id := sdk.NewSchemaObjectIdentifier(databaseTest.Name, schemaTest.Name, random.StringN(4)) + mainFile := "manifest.yml" + request := sdk.NewCreateStreamlitRequest(id, stage.Location(), mainFile).WithComment(&comment) + err := client.Streamlits.Create(ctx, request) + require.NoError(t, err) + t.Cleanup(cleanupStreamlitHandle(id)) + + assertStreamlit(t, id, comment, "") + + privileges := &sdk.AccountRoleGrantPrivileges{ + SchemaObjectPrivileges: []sdk.SchemaObjectPrivilege{sdk.SchemaObjectPrivilegeUsage}, + } + on := &sdk.AccountRoleGrantOn{ + SchemaObject: &sdk.GrantOnSchemaObject{ + SchemaObject: &sdk.Object{ + ObjectType: sdk.ObjectTypeStreamlit, + Name: id, + }, + }, + } + err = client.Grants.GrantPrivilegesToAccountRole(ctx, privileges, on, role.ID(), nil) + require.NoError(t, err) + + grants, err := client.Grants.Show(ctx, &sdk.ShowGrantOptions{ + To: &sdk.ShowGrantsTo{ + Role: role.ID(), + }, + }) + require.NoError(t, err) + assert.Equal(t, 1, len(grants)) + assert.Equal(t, sdk.SchemaObjectPrivilegeUsage.String(), grants[0].Privilege) + assert.Equal(t, id.FullyQualifiedName(), grants[0].Name.FullyQualifiedName()) + + on = &sdk.AccountRoleGrantOn{ + SchemaObject: &sdk.GrantOnSchemaObject{ + Future: &sdk.GrantOnSchemaObjectIn{ + PluralObjectType: sdk.PluralObjectTypeStreamlits, + InDatabase: sdk.Pointer(sdk.NewAccountObjectIdentifier(TestDatabaseName)), + }, + }, + } + err = client.Grants.GrantPrivilegesToAccountRole(ctx, privileges, on, role.ID(), nil) + require.Error(t, err) + require.ErrorContains(t, err, "Unsupported feature 'STREAMLIT'") + + on = &sdk.AccountRoleGrantOn{ + SchemaObject: &sdk.GrantOnSchemaObject{ + All: &sdk.GrantOnSchemaObjectIn{ + PluralObjectType: sdk.PluralObjectTypeStreamlits, + InDatabase: sdk.Pointer(sdk.NewAccountObjectIdentifier(TestDatabaseName)), + }, + }, + } + err = client.Grants.GrantPrivilegesToAccountRole(ctx, privileges, on, role.ID(), nil) + require.NoError(t, err) + }) + + // TODO [SNOW-1272222]: fix the test when it starts working on Snowflake side + t.Run("grant privilege to streamlits to database role", func(t *testing.T) { + stage, cleanupStage := createStage(t, client, sdk.NewSchemaObjectIdentifier(TestDatabaseName, TestSchemaName, random.AlphaN(4))) + t.Cleanup(cleanupStage) + + databaseRole, databaseRoleCleanup := createDatabaseRole(t, client, testDb(t)) + t.Cleanup(databaseRoleCleanup) + + databaseRoleId := sdk.NewDatabaseObjectIdentifier(testDb(t).Name, databaseRole.Name) + + comment := random.StringN(4) + id := sdk.NewSchemaObjectIdentifier(databaseTest.Name, schemaTest.Name, random.StringN(4)) + mainFile := "manifest.yml" + request := sdk.NewCreateStreamlitRequest(id, stage.Location(), mainFile).WithComment(&comment) + err := client.Streamlits.Create(ctx, request) + require.NoError(t, err) + t.Cleanup(cleanupStreamlitHandle(id)) + + assertStreamlit(t, id, comment, "") + + privileges := &sdk.DatabaseRoleGrantPrivileges{ + SchemaObjectPrivileges: []sdk.SchemaObjectPrivilege{sdk.SchemaObjectPrivilegeUsage}, + } + on := &sdk.DatabaseRoleGrantOn{ + SchemaObject: &sdk.GrantOnSchemaObject{ + SchemaObject: &sdk.Object{ + ObjectType: sdk.ObjectTypeStreamlit, + Name: id, + }, + }, + } + err = client.Grants.GrantPrivilegesToDatabaseRole(ctx, privileges, on, databaseRoleId, nil) + require.NoError(t, err) + + grants, err := client.Grants.Show(ctx, &sdk.ShowGrantOptions{ + To: &sdk.ShowGrantsTo{ + DatabaseRole: databaseRoleId, + }, + }) + require.NoError(t, err) + // Expecting two grants because database role has usage on database by default + require.Equal(t, 2, len(grants)) + + on = &sdk.DatabaseRoleGrantOn{ + SchemaObject: &sdk.GrantOnSchemaObject{ + Future: &sdk.GrantOnSchemaObjectIn{ + PluralObjectType: sdk.PluralObjectTypeStreamlits, + InDatabase: sdk.Pointer(sdk.NewAccountObjectIdentifier(TestDatabaseName)), + }, + }, + } + err = client.Grants.GrantPrivilegesToDatabaseRole(ctx, privileges, on, databaseRoleId, nil) + require.Error(t, err) + require.ErrorContains(t, err, "Unsupported feature 'STREAMLIT'") + + on = &sdk.DatabaseRoleGrantOn{ + SchemaObject: &sdk.GrantOnSchemaObject{ + All: &sdk.GrantOnSchemaObjectIn{ + PluralObjectType: sdk.PluralObjectTypeStreamlits, + InDatabase: sdk.Pointer(sdk.NewAccountObjectIdentifier(TestDatabaseName)), + }, + }, + } + err = client.Grants.GrantPrivilegesToDatabaseRole(ctx, privileges, on, databaseRoleId, nil) + require.NoError(t, err) + }) + t.Run("alter streamlit: set", func(t *testing.T) { stage, cleanupStage := createStage(t, client, sdk.NewSchemaObjectIdentifier(TestDatabaseName, TestSchemaName, random.AlphaN(4))) t.Cleanup(cleanupStage) diff --git a/templates/resources/grant_privileges_to_account_role.md.tmpl b/templates/resources/grant_privileges_to_account_role.md.tmpl index f591bf021c..e12d772e80 100644 --- a/templates/resources/grant_privileges_to_account_role.md.tmpl +++ b/templates/resources/grant_privileges_to_account_role.md.tmpl @@ -29,6 +29,9 @@ description: |- {{ .SchemaMarkdown | trimspace }} +## Known limitations +- Setting the `CREATE SNOWFLAKE.ML.ANOMALY_DETECTION` or `CREATE SNOWFLAKE.ML.FORECAST` privileges on schema results in a permadiff because of the probably incorrect Snowflake's behavior of `SHOW GRANTS ON `. More in the [comment](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/2651#issuecomment-2022634952). + ## Import ~> **Note** All the ..._name parts should be fully qualified names (where every part is quoted), e.g. for schema object it is `""."".""` diff --git a/templates/resources/grant_privileges_to_share.md.tmpl b/templates/resources/grant_privileges_to_share.md.tmpl index ebfe2a54df..2132602c86 100644 --- a/templates/resources/grant_privileges_to_share.md.tmpl +++ b/templates/resources/grant_privileges_to_share.md.tmpl @@ -24,6 +24,9 @@ description: |- {{ .SchemaMarkdown | trimspace }} +## Known limitations +- Setting the `CREATE SNOWFLAKE.ML.ANOMALY_DETECTION` or `CREATE SNOWFLAKE.ML.FORECAST` privileges on schema results in a permadiff because of the probably incorrect Snowflake's behavior of `SHOW GRANTS ON `. More in the [comment](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/2651#issuecomment-2022634952). + ## Import ~> **Note** All the ..._name parts should be fully qualified names, e.g. for database object it is `"".""`