diff --git a/docs/resources/grant_privileges_to_share.md b/docs/resources/grant_privileges_to_share.md index cece122b27..10a66027cb 100644 --- a/docs/resources/grant_privileges_to_share.md +++ b/docs/resources/grant_privileges_to_share.md @@ -108,6 +108,7 @@ resource "snowflake_grant_privileges_to_share" "example" { - `on_all_tables_in_schema` (String) The fully qualified identifier for the schema for which the specified privilege will be granted for all tables. - `on_database` (String) The fully qualified name of the database on which privileges will be granted. +- `on_function` (String) The fully qualified name of the function on which privileges will be granted. - `on_schema` (String) The fully qualified name of the schema on which privileges will be granted. - `on_table` (String) The fully qualified name of the table on which privileges will be granted. - `on_tag` (String) The fully qualified name of the tag on which privileges will be granted. diff --git a/pkg/acceptance/helpers/function_client.go b/pkg/acceptance/helpers/function_client.go index cbfb4da00b..421623b17d 100644 --- a/pkg/acceptance/helpers/function_client.go +++ b/pkg/acceptance/helpers/function_client.go @@ -31,18 +31,37 @@ func (c *FunctionClient) Create(t *testing.T, arguments ...sdk.DataType) *sdk.Fu func (c *FunctionClient) CreateWithIdentifier(t *testing.T, id sdk.SchemaObjectIdentifierWithArguments) *sdk.Function { t.Helper() - ctx := context.Background() - argumentRequests := make([]sdk.FunctionArgumentRequest, len(id.ArgumentDataTypes())) - for i, argumentDataType := range id.ArgumentDataTypes() { - argumentRequests[i] = *sdk.NewFunctionArgumentRequest(c.ids.Alpha(), argumentDataType) - } - err := c.client().CreateForSQL(ctx, + + return c.CreateWithRequest(t, id, sdk.NewCreateForSQLFunctionRequest( id.SchemaObjectId(), *sdk.NewFunctionReturnsRequest().WithResultDataType(*sdk.NewFunctionReturnsResultDataTypeRequest(sdk.DataTypeInt)), "SELECT 1", - ).WithArguments(argumentRequests), + ), ) +} + +func (c *FunctionClient) CreateSecure(t *testing.T, arguments ...sdk.DataType) *sdk.Function { + t.Helper() + id := c.ids.RandomSchemaObjectIdentifierWithArguments(arguments...) + + return c.CreateWithRequest(t, id, + sdk.NewCreateForSQLFunctionRequest( + id.SchemaObjectId(), + *sdk.NewFunctionReturnsRequest().WithResultDataType(*sdk.NewFunctionReturnsResultDataTypeRequest(sdk.DataTypeInt)), + "SELECT 1", + ).WithSecure(true), + ) +} + +func (c *FunctionClient) CreateWithRequest(t *testing.T, id sdk.SchemaObjectIdentifierWithArguments, req *sdk.CreateForSQLFunctionRequest) *sdk.Function { + t.Helper() + ctx := context.Background() + argumentRequests := make([]sdk.FunctionArgumentRequest, len(id.ArgumentDataTypes())) + for i, argumentDataType := range id.ArgumentDataTypes() { + argumentRequests[i] = *sdk.NewFunctionArgumentRequest(c.ids.Alpha(), argumentDataType) + } + err := c.client().CreateForSQL(ctx, req.WithArguments(argumentRequests)) require.NoError(t, err) t.Cleanup(func() { diff --git a/pkg/acceptance/helpers/share_client.go b/pkg/acceptance/helpers/share_client.go index e61a6f53ea..0b67ac6c1b 100644 --- a/pkg/acceptance/helpers/share_client.go +++ b/pkg/acceptance/helpers/share_client.go @@ -26,7 +26,6 @@ func (c *ShareClient) client() sdk.Shares { func (c *ShareClient) CreateShare(t *testing.T) (*sdk.Share, func()) { t.Helper() - // TODO(SNOW-1058419): Try with identifier containing dot during identifiers rework return c.CreateShareWithIdentifier(t, c.ids.RandomAccountObjectIdentifier()) } diff --git a/pkg/resources/grant_ownership.go b/pkg/resources/grant_ownership.go index ff7d9ecb29..37aea7768b 100644 --- a/pkg/resources/grant_ownership.go +++ b/pkg/resources/grant_ownership.go @@ -6,8 +6,6 @@ import ( "log" "strings" - "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/helpers" - "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/internal/logging" "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/internal/provider" "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk" @@ -400,11 +398,6 @@ func ReadGrantOwnership(ctx context.Context, d *schema.ResourceData, meta any) d // TODO(SNOW-1229218): Make sdk.ObjectType + string objectName to sdk.ObjectIdentifier mapping available in the sdk (for all object types). func getOnObjectIdentifier(objectType sdk.ObjectType, objectName string) (sdk.ObjectIdentifier, error) { - identifier, err := helpers.DecodeSnowflakeParameterID(objectName) - if err != nil { - return nil, err - } - switch objectType { case sdk.ObjectTypeComputePool, sdk.ObjectTypeDatabase, @@ -416,12 +409,10 @@ func getOnObjectIdentifier(objectType sdk.ObjectType, objectName string) (sdk.Ob sdk.ObjectTypeRole, sdk.ObjectTypeUser, sdk.ObjectTypeWarehouse: - return sdk.NewAccountObjectIdentifier(objectName), nil + return sdk.ParseAccountObjectIdentifier(objectName) case sdk.ObjectTypeDatabaseRole, sdk.ObjectTypeSchema: - if _, ok := identifier.(sdk.DatabaseObjectIdentifier); !ok { - return nil, sdk.NewError(fmt.Sprintf("invalid object_name %s, expected database object identifier", objectName)) - } + return sdk.ParseDatabaseObjectIdentifier(objectName) case sdk.ObjectTypeAggregationPolicy, sdk.ObjectTypeAlert, sdk.ObjectTypeAuthenticationPolicy, @@ -430,7 +421,6 @@ func getOnObjectIdentifier(objectType sdk.ObjectType, objectName string) (sdk.Ob sdk.ObjectTypeEventTable, sdk.ObjectTypeExternalTable, sdk.ObjectTypeFileFormat, - sdk.ObjectTypeFunction, sdk.ObjectTypeGitRepository, sdk.ObjectTypeHybridTable, sdk.ObjectTypeIcebergTable, @@ -439,7 +429,6 @@ func getOnObjectIdentifier(objectType sdk.ObjectType, objectName string) (sdk.Ob sdk.ObjectTypeNetworkRule, sdk.ObjectTypePackagesPolicy, sdk.ObjectTypePipe, - sdk.ObjectTypeProcedure, sdk.ObjectTypeMaskingPolicy, sdk.ObjectTypePasswordPolicy, sdk.ObjectTypeProjectionPolicy, @@ -453,14 +442,14 @@ func getOnObjectIdentifier(objectType sdk.ObjectType, objectName string) (sdk.Ob sdk.ObjectTypeTag, sdk.ObjectTypeTask, sdk.ObjectTypeView: - if _, ok := identifier.(sdk.SchemaObjectIdentifier); !ok { - return nil, sdk.NewError(fmt.Sprintf("invalid object_name %s, expected schema object identifier", objectName)) - } + return sdk.ParseSchemaObjectIdentifier(objectName) + case sdk.ObjectTypeFunction, + sdk.ObjectTypeProcedure, + sdk.ObjectTypeExternalFunction: + return sdk.ParseSchemaObjectIdentifierWithArguments(objectName) default: return nil, sdk.NewError(fmt.Sprintf("object_type %s is not supported, please create a feature request for the provider if given object_type should be supported", objectType)) } - - return identifier, nil } func getOwnershipGrantOn(d *schema.ResourceData) (*sdk.OwnershipGrantOn, error) { diff --git a/pkg/resources/grant_ownership_acceptance_test.go b/pkg/resources/grant_ownership_acceptance_test.go index 2417174ddb..1d2357b00d 100644 --- a/pkg/resources/grant_ownership_acceptance_test.go +++ b/pkg/resources/grant_ownership_acceptance_test.go @@ -323,6 +323,101 @@ func TestAcc_GrantOwnership_OnObject_Table_ToDatabaseRole(t *testing.T) { }) } +func TestAcc_GrantOwnership_OnObject_ProcedureWithArguments_ToAccountRole(t *testing.T) { + databaseId := acc.TestClient().Ids.RandomAccountObjectIdentifier() + schemaId := acc.TestClient().Ids.RandomDatabaseObjectIdentifierInDatabase(databaseId) + procedureId := acc.TestClient().Ids.NewSchemaObjectIdentifierWithArgumentsInSchema(acc.TestClient().Ids.Alpha(), schemaId, sdk.DataTypeFloat) + accountRoleId := acc.TestClient().Ids.RandomAccountObjectIdentifier() + + configVariables := config.Variables{ + "account_role_name": config.StringVariable(accountRoleId.Name()), + "database_name": config.StringVariable(databaseId.Name()), + "schema_name": config.StringVariable(schemaId.Name()), + "procedure_name": config.StringVariable(procedureId.Name()), + } + resourceName := "snowflake_grant_ownership.test" + + resource.Test(t, resource.TestCase{ + ProtoV6ProviderFactories: acc.TestAccProtoV6ProviderFactories, + PreCheck: func() { acc.TestAccPreCheck(t) }, + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.RequireAbove(tfversion.Version1_5_0), + }, + Steps: []resource.TestStep{ + { + ConfigDirectory: acc.ConfigurationDirectory("TestAcc_GrantOwnership/OnObject_Procedure_ToAccountRole"), + ConfigVariables: configVariables, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "account_role_name", accountRoleId.Name()), + resource.TestCheckResourceAttr(resourceName, "on.0.object_type", "PROCEDURE"), + resource.TestCheckResourceAttr(resourceName, "on.0.object_name", procedureId.FullyQualifiedName()), + resource.TestCheckResourceAttr(resourceName, "id", fmt.Sprintf("ToAccountRole|%s||OnObject|PROCEDURE|%s", accountRoleId.FullyQualifiedName(), procedureId.FullyQualifiedName())), + checkResourceOwnershipIsGranted(&sdk.ShowGrantOptions{ + To: &sdk.ShowGrantsTo{ + Role: accountRoleId, + }, + }, sdk.ObjectTypeProcedure, accountRoleId.Name(), procedureId.FullyQualifiedName()), + ), + }, + { + ConfigDirectory: acc.ConfigurationDirectory("TestAcc_GrantOwnership/OnObject_Procedure_ToAccountRole"), + ConfigVariables: configVariables, + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAcc_GrantOwnership_OnObject_ProcedureWithoutArguments_ToDatabaseRole(t *testing.T) { + databaseId := acc.TestClient().Ids.RandomAccountObjectIdentifier() + schemaId := acc.TestClient().Ids.RandomDatabaseObjectIdentifierInDatabase(databaseId) + procedureId := acc.TestClient().Ids.NewSchemaObjectIdentifierWithArgumentsInSchema(acc.TestClient().Ids.Alpha(), schemaId) + + databaseRoleId := acc.TestClient().Ids.RandomDatabaseObjectIdentifierInDatabase(databaseId) + + configVariables := config.Variables{ + "database_role_name": config.StringVariable(databaseRoleId.Name()), + "database_name": config.StringVariable(databaseId.Name()), + "schema_name": config.StringVariable(schemaId.Name()), + "procedure_name": config.StringVariable(procedureId.Name()), + } + resourceName := "snowflake_grant_ownership.test" + + resource.Test(t, resource.TestCase{ + ProtoV6ProviderFactories: acc.TestAccProtoV6ProviderFactories, + PreCheck: func() { acc.TestAccPreCheck(t) }, + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.RequireAbove(tfversion.Version1_5_0), + }, + Steps: []resource.TestStep{ + { + ConfigDirectory: acc.ConfigurationDirectory("TestAcc_GrantOwnership/OnObject_Procedure_ToDatabaseRole"), + ConfigVariables: configVariables, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "database_role_name", databaseRoleId.FullyQualifiedName()), + resource.TestCheckResourceAttr(resourceName, "on.0.object_type", "PROCEDURE"), + resource.TestCheckResourceAttr(resourceName, "on.0.object_name", procedureId.FullyQualifiedName()), + resource.TestCheckResourceAttr(resourceName, "id", fmt.Sprintf("ToDatabaseRole|%s||OnObject|PROCEDURE|%s", databaseRoleId.FullyQualifiedName(), procedureId.FullyQualifiedName())), + checkResourceOwnershipIsGranted(&sdk.ShowGrantOptions{ + To: &sdk.ShowGrantsTo{ + DatabaseRole: databaseRoleId, + }, + }, sdk.ObjectTypeProcedure, databaseRoleId.Name(), procedureId.FullyQualifiedName()), + ), + }, + { + ConfigDirectory: acc.ConfigurationDirectory("TestAcc_GrantOwnership/OnObject_Procedure_ToDatabaseRole"), + ConfigVariables: configVariables, + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + func TestAcc_GrantOwnership_OnAll_InDatabase_ToAccountRole(t *testing.T) { databaseId := acc.TestClient().Ids.RandomAccountObjectIdentifier() accountRoleId := acc.TestClient().Ids.RandomAccountObjectIdentifier() diff --git a/pkg/resources/grant_ownership_identifier_test.go b/pkg/resources/grant_ownership_identifier_test.go index e0345209ef..54ea170adc 100644 --- a/pkg/resources/grant_ownership_identifier_test.go +++ b/pkg/resources/grant_ownership_identifier_test.go @@ -130,6 +130,34 @@ func TestParseGrantOwnershipId(t *testing.T) { }, }, }, + { + Name: "grant ownership on function to account role", + Identifier: `ToAccountRole|"account-role"|COPY|OnObject|FUNCTION|"database-name"."schema-name"."function-name"(FLOAT)`, + Expected: GrantOwnershipId{ + GrantOwnershipTargetRoleKind: ToAccountGrantOwnershipTargetRoleKind, + AccountRoleName: sdk.NewAccountObjectIdentifier("account-role"), + OutboundPrivilegesBehavior: sdk.Pointer(CopyOutboundPrivilegesBehavior), + Kind: OnObjectGrantOwnershipKind, + Data: &OnObjectGrantOwnershipData{ + ObjectType: sdk.ObjectTypeFunction, + ObjectName: sdk.NewSchemaObjectIdentifierWithArguments("database-name", "schema-name", "function-name", sdk.DataTypeFloat), + }, + }, + }, + { + Name: "grant ownership on function without arguments to database role", + Identifier: `ToDatabaseRole|"database-name"."database-role"|REVOKE|OnObject|FUNCTION|"database-name"."schema-name"."function-name"()`, + Expected: GrantOwnershipId{ + GrantOwnershipTargetRoleKind: ToDatabaseGrantOwnershipTargetRoleKind, + DatabaseRoleName: sdk.NewDatabaseObjectIdentifier("database-name", "database-role"), + OutboundPrivilegesBehavior: sdk.Pointer(RevokeOutboundPrivilegesBehavior), + Kind: OnObjectGrantOwnershipKind, + Data: &OnObjectGrantOwnershipData{ + ObjectType: sdk.ObjectTypeFunction, + ObjectName: sdk.NewSchemaObjectIdentifierWithArguments("database-name", "schema-name", "function-name", []sdk.DataType{}...), + }, + }, + }, { Name: "validation: not enough parts", Identifier: `ToDatabaseRole|"database-name"."role-name"|`, diff --git a/pkg/resources/grant_ownership_test.go b/pkg/resources/grant_ownership_test.go index 25d898b9a0..6c0cd938e3 100644 --- a/pkg/resources/grant_ownership_test.go +++ b/pkg/resources/grant_ownership_test.go @@ -55,14 +55,14 @@ func TestGetOnObjectIdentifier(t *testing.T) { { Name: "account object identifier with dots", ObjectType: sdk.ObjectTypeDatabase, - ObjectName: "database.name.with.dots", + ObjectName: "\"database.name.with.dots\"", Expected: sdk.NewAccountObjectIdentifier("database.name.with.dots"), }, { Name: "validation - valid identifier", ObjectType: sdk.ObjectTypeDatabase, ObjectName: "to.many.parts.in.this.identifier", - Error: "unable to classify identifier", + Error: "unexpected number of parts 6 in identifier to.many.parts.in.this.identifier, expected 1 in a form of \"\"", }, { Name: "validation - unsupported type", @@ -74,13 +74,13 @@ func TestGetOnObjectIdentifier(t *testing.T) { Name: "validation - invalid database object identifier", ObjectType: sdk.ObjectTypeSchema, ObjectName: "test_database.test_schema.test_table", - Error: "invalid object_name test_database.test_schema.test_table, expected database object identifier", + Error: "unexpected number of parts 3 in identifier test_database.test_schema.test_table, expected 2 in a form of \".\"", }, { Name: "validation - invalid schema object identifier", ObjectType: sdk.ObjectTypeTable, ObjectName: "test_database.test_schema.test_table.column_name", - Error: "invalid object_name test_database.test_schema.test_table.column_name, expected schema object identifier", + Error: "unexpected number of parts 4 in identifier test_database.test_schema.test_table.column_name, expected 3 in a form of \"..\"", }, } @@ -248,7 +248,7 @@ func TestGetOwnershipGrantOn(t *testing.T) { "object_type": "SCHEMA", "object_name": "test_database.test_schema.test_table", }, - Error: "invalid object_name test_database.test_schema.test_table, expected database object identifier", + Error: "unexpected number of parts 3 in identifier test_database.test_schema.test_table, expected 2 in a form of \".\"", }, } diff --git a/pkg/resources/grant_privileges_to_account_role.go b/pkg/resources/grant_privileges_to_account_role.go index 56fab09757..d08207c9fb 100644 --- a/pkg/resources/grant_privileges_to_account_role.go +++ b/pkg/resources/grant_privileges_to_account_role.go @@ -220,7 +220,6 @@ var grantPrivilegesToAccountRoleSchema = map[string]*schema.Schema{ "on_schema_object.0.all", "on_schema_object.0.future", }, - ValidateDiagFunc: IsValidIdentifier[sdk.SchemaObjectIdentifier](), }, "all": { Type: schema.TypeList, @@ -405,13 +404,20 @@ func CreateGrantPrivilegesToAccountRole(ctx context.Context, d *schema.ResourceD logging.DebugLogger.Printf("[DEBUG] Entering create grant privileges to account role") client := meta.(*provider.Context).Client - id := createGrantPrivilegesToAccountRoleIdFromSchema(d) + id, err := createGrantPrivilegesToAccountRoleIdFromSchema(d) + if err != nil { + return diag.FromErr(err) + } logging.DebugLogger.Printf("[DEBUG] created identifier from schema: %s", id.String()) - err := client.Grants.GrantPrivilegesToAccountRole( + grantOn, err := getAccountRoleGrantOn(d) + if err != nil { + return diag.FromErr(err) + } + err = client.Grants.GrantPrivilegesToAccountRole( ctx, getAccountRolePrivilegesFromSchema(d), - getAccountRoleGrantOn(d), + grantOn, sdk.NewAccountObjectIdentifierFromFullyQualifiedName(d.Get("account_role_name").(string)), &sdk.GrantPrivilegesToAccountRoleOptions{ WithGrantOption: sdk.Bool(d.Get("with_grant_option").(bool)), @@ -456,17 +462,20 @@ func UpdateGrantPrivilegesToAccountRole(ctx context.Context, d *schema.ResourceD // handle all_privileges -> privileges change (revoke all privileges) if d.HasChange("all_privileges") { _, allPrivileges := d.GetChange("all_privileges") + grantOn, err := getAccountRoleGrantOn(d) + if err != nil { + return diag.FromErr(err) + } if !allPrivileges.(bool) { logging.DebugLogger.Printf("[DEBUG] Revoking all privileges") err = client.Grants.RevokePrivilegesFromAccountRole(ctx, &sdk.AccountRoleGrantPrivileges{ AllPrivileges: sdk.Bool(true), }, - getAccountRoleGrantOn(d), + grantOn, id.RoleName, new(sdk.RevokePrivilegesFromAccountRoleOptions), ) - if err != nil { return diag.Diagnostics{ diag.Diagnostic{ @@ -513,7 +522,10 @@ func UpdateGrantPrivilegesToAccountRole(ctx context.Context, d *schema.ResourceD } } - grantOn := getAccountRoleGrantOn(d) + grantOn, err := getAccountRoleGrantOn(d) + if err != nil { + return diag.FromErr(err) + } if len(privilegesToAdd) > 0 { logging.DebugLogger.Printf("[DEBUG] Granting privileges: %v", privilegesToAdd) @@ -589,14 +601,17 @@ func UpdateGrantPrivilegesToAccountRole(ctx context.Context, d *schema.ResourceD if allPrivileges.(bool) { logging.DebugLogger.Printf("[DEBUG] Granting all privileges") + grantOn, err := getAccountRoleGrantOn(d) + if err != nil { + return diag.FromErr(err) + } err = client.Grants.GrantPrivilegesToAccountRole(ctx, &sdk.AccountRoleGrantPrivileges{ AllPrivileges: sdk.Bool(true), }, - getAccountRoleGrantOn(d), + grantOn, id.RoleName, new(sdk.GrantPrivilegesToAccountRoleOptions), ) - if err != nil { return diag.Diagnostics{ diag.Diagnostic{ @@ -617,10 +632,14 @@ func UpdateGrantPrivilegesToAccountRole(ctx context.Context, d *schema.ResourceD if id.AlwaysApply { logging.DebugLogger.Printf("[DEBUG] Performing always_apply re-grant") - err := client.Grants.GrantPrivilegesToAccountRole( + grantOn, err := getAccountRoleGrantOn(d) + if err != nil { + return diag.FromErr(err) + } + err = client.Grants.GrantPrivilegesToAccountRole( ctx, getAccountRolePrivilegesFromSchema(d), - getAccountRoleGrantOn(d), + grantOn, id.RoleName, &sdk.GrantPrivilegesToAccountRoleOptions{ WithGrantOption: &id.WithGrantOption, @@ -659,10 +678,15 @@ func DeleteGrantPrivilegesToAccountRole(ctx context.Context, d *schema.ResourceD } logging.DebugLogger.Printf("[DEBUG] Parsed identifier: %s", id.String()) + grantOn, err := getAccountRoleGrantOn(d) + if err != nil { + return diag.FromErr(err) + } + err = client.Grants.RevokePrivilegesFromAccountRole( ctx, getAccountRolePrivilegesFromSchema(d), - getAccountRoleGrantOn(d), + grantOn, id.RoleName, &sdk.RevokePrivilegesFromAccountRoleOptions{}, ) @@ -966,7 +990,7 @@ func getAccountRolePrivileges(allPrivileges bool, privileges []string, onAccount return accountRoleGrantPrivileges } -func getAccountRoleGrantOn(d *schema.ResourceData) *sdk.AccountRoleGrantOn { +func getAccountRoleGrantOn(d *schema.ResourceData) (*sdk.AccountRoleGrantOn, error) { _, onAccountOk := d.GetOk("on_account") onAccountObjectBlock, onAccountObjectOk := d.GetOk("on_account_object") onSchemaBlock, onSchemaOk := d.GetOk("on_schema") @@ -1050,9 +1074,21 @@ func getAccountRoleGrantOn(d *schema.ResourceData) *sdk.AccountRoleGrantOn { switch { case objectTypeOk && objectNameOk: + objectType := sdk.ObjectType(objectType) + var id sdk.ObjectIdentifier + // TODO(SNOW-1569535): use a mapper from object type to parsing function + if objectType.IsWithArguments() { + var err error + id, err = sdk.ParseSchemaObjectIdentifierWithArguments(objectName) + if err != nil { + return nil, err + } + } else { + id = sdk.NewSchemaObjectIdentifierFromFullyQualifiedName(objectName) + } grantOnSchemaObject.SchemaObject = &sdk.Object{ - ObjectType: sdk.ObjectType(objectType), - Name: sdk.NewSchemaObjectIdentifierFromFullyQualifiedName(objectName), + ObjectType: objectType, + Name: id, } case allOk: grantOnSchemaObject.All = getGrantOnSchemaObjectIn(all[0].(map[string]any)) @@ -1063,10 +1099,10 @@ func getAccountRoleGrantOn(d *schema.ResourceData) *sdk.AccountRoleGrantOn { on.SchemaObject = grantOnSchemaObject } - return on + return on, nil } -func createGrantPrivilegesToAccountRoleIdFromSchema(d *schema.ResourceData) *GrantPrivilegesToAccountRoleId { +func createGrantPrivilegesToAccountRoleIdFromSchema(d *schema.ResourceData) (*GrantPrivilegesToAccountRoleId, error) { id := new(GrantPrivilegesToAccountRoleId) id.RoleName = sdk.NewAccountObjectIdentifierFromFullyQualifiedName(d.Get("account_role_name").(string)) id.AllPrivileges = d.Get("all_privileges").(bool) @@ -1076,7 +1112,10 @@ func createGrantPrivilegesToAccountRoleIdFromSchema(d *schema.ResourceData) *Gra id.WithGrantOption = d.Get("with_grant_option").(bool) id.AlwaysApply = d.Get("always_apply").(bool) - on := getAccountRoleGrantOn(d) + on, err := getAccountRoleGrantOn(d) + if err != nil { + return nil, err + } switch { case on.Account != nil: id.Kind = OnAccountAccountRoleGrantKind @@ -1149,5 +1188,5 @@ func createGrantPrivilegesToAccountRoleIdFromSchema(d *schema.ResourceData) *Gra id.Data = onSchemaObjectGrantData } - return id + return id, nil } 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 2699b0744d..6386860639 100644 --- a/pkg/resources/grant_privileges_to_account_role_acceptance_test.go +++ b/pkg/resources/grant_privileges_to_account_role_acceptance_test.go @@ -7,6 +7,7 @@ import ( "testing" acc "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/acceptance" + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/acceptance/testenvs" "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk" "github.com/hashicorp/terraform-plugin-testing/config" @@ -445,6 +446,118 @@ func TestAcc_GrantPrivilegesToAccountRole_OnSchemaObject_OnObject(t *testing.T) }) } +func TestAcc_GrantPrivilegesToAccountRole_OnSchemaObject_OnFunctionWithArguments(t *testing.T) { + _ = testenvs.GetOrSkipTest(t, testenvs.EnableAcceptance) + acc.TestAccPreCheck(t) + + roleId := acc.TestClient().Ids.RandomAccountObjectIdentifier() + roleFullyQualifiedName := roleId.FullyQualifiedName() + function := acc.TestClient().Function.CreateSecure(t, sdk.DataTypeFloat) + configVariables := config.Variables{ + "name": config.StringVariable(roleFullyQualifiedName), + "function_name": config.StringVariable(function.ID().Name()), + "privileges": config.ListVariable( + config.StringVariable(string(sdk.SchemaObjectPrivilegeUsage)), + ), + "database": config.StringVariable(acc.TestDatabaseName), + "schema": config.StringVariable(acc.TestSchemaName), + "with_grant_option": config.BoolVariable(false), + "argument_type": config.StringVariable(string(sdk.DataTypeFloat)), + } + resourceName := "snowflake_grant_privileges_to_account_role.test" + + resource.Test(t, resource.TestCase{ + ProtoV6ProviderFactories: acc.TestAccProtoV6ProviderFactories, + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.RequireAbove(tfversion.Version1_5_0), + }, + CheckDestroy: acc.CheckAccountRolePrivilegesRevoked(t), + Steps: []resource.TestStep{ + { + PreConfig: func() { + _, roleCleanup := acc.TestClient().Role.CreateRoleWithIdentifier(t, roleId) + t.Cleanup(roleCleanup) + }, + ConfigDirectory: acc.ConfigurationDirectory("TestAcc_GrantPrivilegesToAccountRole/OnSchemaObject_OnFunction"), + ConfigVariables: configVariables, + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "account_role_name", roleFullyQualifiedName), + 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.object_type", string(sdk.ObjectTypeFunction)), + resource.TestCheckResourceAttr(resourceName, "on_schema_object.0.object_name", function.ID().FullyQualifiedName()), + resource.TestCheckResourceAttr(resourceName, "with_grant_option", "false"), + resource.TestCheckResourceAttr(resourceName, "id", fmt.Sprintf("%s|false|false|USAGE|OnSchemaObject|OnObject|FUNCTION|%s", roleFullyQualifiedName, function.ID().FullyQualifiedName())), + ), + }, + { + ConfigDirectory: acc.ConfigurationDirectory("TestAcc_GrantPrivilegesToAccountRole/OnSchemaObject_OnFunction"), + ConfigVariables: configVariables, + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAcc_GrantPrivilegesToAccountRole_OnSchemaObject_OnFunctionWithoutArguments(t *testing.T) { + _ = testenvs.GetOrSkipTest(t, testenvs.EnableAcceptance) + acc.TestAccPreCheck(t) + + roleId := acc.TestClient().Ids.RandomAccountObjectIdentifier() + roleFullyQualifiedName := roleId.FullyQualifiedName() + function := acc.TestClient().Function.CreateSecure(t) + configVariables := config.Variables{ + "name": config.StringVariable(roleFullyQualifiedName), + "function_name": config.StringVariable(function.ID().Name()), + "privileges": config.ListVariable( + config.StringVariable(string(sdk.SchemaObjectPrivilegeUsage)), + ), + "database": config.StringVariable(acc.TestDatabaseName), + "schema": config.StringVariable(acc.TestSchemaName), + "with_grant_option": config.BoolVariable(false), + "argument_type": config.StringVariable(""), + } + resourceName := "snowflake_grant_privileges_to_account_role.test" + + resource.Test(t, resource.TestCase{ + ProtoV6ProviderFactories: acc.TestAccProtoV6ProviderFactories, + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.RequireAbove(tfversion.Version1_5_0), + }, + CheckDestroy: acc.CheckAccountRolePrivilegesRevoked(t), + Steps: []resource.TestStep{ + { + PreConfig: func() { + _, roleCleanup := acc.TestClient().Role.CreateRoleWithIdentifier(t, roleId) + t.Cleanup(roleCleanup) + }, + ConfigDirectory: acc.ConfigurationDirectory("TestAcc_GrantPrivilegesToAccountRole/OnSchemaObject_OnFunction"), + ConfigVariables: configVariables, + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "account_role_name", roleFullyQualifiedName), + 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.object_type", string(sdk.ObjectTypeFunction)), + resource.TestCheckResourceAttr(resourceName, "on_schema_object.0.object_name", function.ID().FullyQualifiedName()), + resource.TestCheckResourceAttr(resourceName, "with_grant_option", "false"), + resource.TestCheckResourceAttr(resourceName, "id", fmt.Sprintf("%s|false|false|USAGE|OnSchemaObject|OnObject|FUNCTION|%s", roleFullyQualifiedName, function.ID().FullyQualifiedName())), + ), + }, + { + ConfigDirectory: acc.ConfigurationDirectory("TestAcc_GrantPrivilegesToAccountRole/OnSchemaObject_OnFunction"), + ConfigVariables: configVariables, + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + func TestAcc_GrantPrivilegesToAccountRole_OnSchemaObject_OnObject_OwnershipPrivilege(t *testing.T) { roleId := acc.TestClient().Ids.RandomAccountObjectIdentifier() name := roleId.Name() @@ -1588,8 +1701,8 @@ func createExternalVolume(t *testing.T, externalVolumeName string) func() { ctx := context.Background() _, err := client.ExecForTests(ctx, fmt.Sprintf(`create external volume "%s" storage_locations = ( ( - name = 'test' - storage_provider = 's3' + name = 'test' + storage_provider = 's3' storage_base_url = 's3://my_example_bucket/' storage_aws_role_arn = 'arn:aws:iam::123456789012:role/myrole' encryption=(type='aws_sse_kms' kms_key_id='1234abcd-12ab-34cd-56ef-1234567890ab') diff --git a/pkg/resources/grant_privileges_to_account_role_identifier.go b/pkg/resources/grant_privileges_to_account_role_identifier.go index 024e18bc11..4f6f45ce4b 100644 --- a/pkg/resources/grant_privileges_to_account_role_identifier.go +++ b/pkg/resources/grant_privileges_to_account_role_identifier.go @@ -137,9 +137,21 @@ func ParseGrantPrivilegesToAccountRoleId(id string) (GrantPrivilegesToAccountRol if len(parts) != 8 { return accountRoleId, sdk.NewError(`account role identifier should hold 8 parts "||||OnSchemaObject|OnObject||"`) } + objectType := sdk.ObjectType(parts[6]) + var id sdk.ObjectIdentifier + // TODO(SNOW-1569535): use a mapper from object type to parsing function + if objectType.IsWithArguments() { + var err error + id, err = sdk.ParseSchemaObjectIdentifierWithArguments(parts[7]) + if err != nil { + return accountRoleId, err + } + } else { + id = sdk.NewSchemaObjectIdentifierFromFullyQualifiedName(parts[7]) + } onSchemaObjectGrantData.Object = &sdk.Object{ - ObjectType: sdk.ObjectType(parts[6]), - Name: sdk.NewSchemaObjectIdentifierFromFullyQualifiedName(parts[7]), + ObjectType: objectType, + Name: id, } case OnAllSchemaObjectGrantKind, OnFutureSchemaObjectGrantKind: bulkOperationGrantData := &BulkOperationGrantData{ diff --git a/pkg/resources/grant_privileges_to_account_role_identifier_test.go b/pkg/resources/grant_privileges_to_account_role_identifier_test.go index 41905d4705..8a047021d6 100644 --- a/pkg/resources/grant_privileges_to_account_role_identifier_test.go +++ b/pkg/resources/grant_privileges_to_account_role_identifier_test.go @@ -122,6 +122,40 @@ func TestParseGrantPrivilegesToAccountRoleId(t *testing.T) { }, }, }, + { + Name: "grant account role on function", + Identifier: `"account-role"|false|false|USAGE|OnSchemaObject|OnObject|FUNCTION|"database-name"."schema-name"."function-name"(FLOAT)`, + Expected: GrantPrivilegesToAccountRoleId{ + RoleName: sdk.NewAccountObjectIdentifier("account-role"), + WithGrantOption: false, + Privileges: []string{"USAGE"}, + Kind: OnSchemaObjectAccountRoleGrantKind, + Data: &OnSchemaObjectGrantData{ + Kind: OnObjectSchemaObjectGrantKind, + Object: &sdk.Object{ + ObjectType: sdk.ObjectTypeFunction, + Name: sdk.NewSchemaObjectIdentifierWithArguments("database-name", "schema-name", "function-name", sdk.DataTypeFloat), + }, + }, + }, + }, + { + Name: "grant account role on function without arguments", + Identifier: `"account-role"|false|false|USAGE|OnSchemaObject|OnObject|FUNCTION|"database-name"."schema-name"."function-name"()`, + Expected: GrantPrivilegesToAccountRoleId{ + RoleName: sdk.NewAccountObjectIdentifier("account-role"), + WithGrantOption: false, + Privileges: []string{"USAGE"}, + Kind: OnSchemaObjectAccountRoleGrantKind, + Data: &OnSchemaObjectGrantData{ + Kind: OnObjectSchemaObjectGrantKind, + Object: &sdk.Object{ + ObjectType: sdk.ObjectTypeFunction, + Name: sdk.NewSchemaObjectIdentifierWithArguments("database-name", "schema-name", "function-name", []sdk.DataType{}...), + }, + }, + }, + }, { Name: "grant account role on schema object with on all option", Identifier: `"account-role"|false|false|CREATE SCHEMA,USAGE,MONITOR|OnSchemaObject|OnAll|TABLES`, diff --git a/pkg/resources/grant_privileges_to_database_role.go b/pkg/resources/grant_privileges_to_database_role.go index 75b151f11c..411f127e3f 100644 --- a/pkg/resources/grant_privileges_to_database_role.go +++ b/pkg/resources/grant_privileges_to_database_role.go @@ -172,7 +172,6 @@ var grantPrivilegesToDatabaseRoleSchema = map[string]*schema.Schema{ "on_schema_object.0.all", "on_schema_object.0.future", }, - ValidateDiagFunc: IsValidIdentifier[sdk.SchemaObjectIdentifier](), }, "all": { Type: schema.TypeList, @@ -342,11 +341,19 @@ func ImportGrantPrivilegesToDatabaseRole(ctx context.Context, d *schema.Resource func CreateGrantPrivilegesToDatabaseRole(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics { client := meta.(*provider.Context).Client - id := createGrantPrivilegesToDatabaseRoleIdFromSchema(d) - err := client.Grants.GrantPrivilegesToDatabaseRole( + id, err := createGrantPrivilegesToDatabaseRoleIdFromSchema(d) + if err != nil { + return diag.FromErr(err) + } + grantOn, err := getDatabaseRoleGrantOn(d) + if err != nil { + return diag.FromErr(err) + } + + err = client.Grants.GrantPrivilegesToDatabaseRole( ctx, getDatabaseRolePrivilegesFromSchema(d), - getDatabaseRoleGrantOn(d), + grantOn, sdk.NewDatabaseObjectIdentifierFromFullyQualifiedName(d.Get("database_role_name").(string)), &sdk.GrantPrivilegesToDatabaseRoleOptions{ WithGrantOption: sdk.Bool(d.Get("with_grant_option").(bool)), @@ -384,6 +391,11 @@ func UpdateGrantPrivilegesToDatabaseRole(ctx context.Context, d *schema.Resource id.WithGrantOption = d.Get("with_grant_option").(bool) } + grantOn, err := getDatabaseRoleGrantOn(d) + if err != nil { + return diag.FromErr(err) + } + // handle all_privileges -> privileges change (revoke all privileges) if d.HasChange("all_privileges") { _, allPrivileges := d.GetChange("all_privileges") @@ -392,11 +404,10 @@ func UpdateGrantPrivilegesToDatabaseRole(ctx context.Context, d *schema.Resource err = client.Grants.RevokePrivilegesFromDatabaseRole(ctx, &sdk.DatabaseRoleGrantPrivileges{ AllPrivileges: sdk.Bool(true), }, - getDatabaseRoleGrantOn(d), + grantOn, id.DatabaseRoleName, new(sdk.RevokePrivilegesFromDatabaseRoleOptions), ) - if err != nil { return diag.Diagnostics{ diag.Diagnostic{ @@ -441,8 +452,6 @@ func UpdateGrantPrivilegesToDatabaseRole(ctx context.Context, d *schema.Resource } } - grantOn := getDatabaseRoleGrantOn(d) - if len(privilegesToAdd) > 0 { privilegesToGrant := getDatabaseRolePrivileges( false, @@ -515,11 +524,10 @@ func UpdateGrantPrivilegesToDatabaseRole(ctx context.Context, d *schema.Resource err = client.Grants.GrantPrivilegesToDatabaseRole(ctx, &sdk.DatabaseRoleGrantPrivileges{ AllPrivileges: sdk.Bool(true), }, - getDatabaseRoleGrantOn(d), + grantOn, id.DatabaseRoleName, new(sdk.GrantPrivilegesToDatabaseRoleOptions), ) - if err != nil { return diag.Diagnostics{ diag.Diagnostic{ @@ -539,10 +547,10 @@ func UpdateGrantPrivilegesToDatabaseRole(ctx context.Context, d *schema.Resource } if id.AlwaysApply { - err := client.Grants.GrantPrivilegesToDatabaseRole( + err = client.Grants.GrantPrivilegesToDatabaseRole( ctx, getDatabaseRolePrivilegesFromSchema(d), - getDatabaseRoleGrantOn(d), + grantOn, id.DatabaseRoleName, &sdk.GrantPrivilegesToDatabaseRoleOptions{ WithGrantOption: &id.WithGrantOption, @@ -576,11 +584,14 @@ func DeleteGrantPrivilegesToDatabaseRole(ctx context.Context, d *schema.Resource }, } } - + grantOn, err := getDatabaseRoleGrantOn(d) + if err != nil { + return diag.FromErr(err) + } err = client.Grants.RevokePrivilegesFromDatabaseRole( ctx, getDatabaseRolePrivilegesFromSchema(d), - getDatabaseRoleGrantOn(d), + grantOn, id.DatabaseRoleName, &sdk.RevokePrivilegesFromDatabaseRoleOptions{}, ) @@ -840,7 +851,7 @@ func getDatabaseRolePrivileges(allPrivileges bool, privileges []string, onDataba return databaseRoleGrantPrivileges } -func getDatabaseRoleGrantOn(d *schema.ResourceData) *sdk.DatabaseRoleGrantOn { +func getDatabaseRoleGrantOn(d *schema.ResourceData) (*sdk.DatabaseRoleGrantOn, error) { onDatabase, onDatabaseOk := d.GetOk("on_database") onSchemaBlock, onSchemaOk := d.GetOk("on_schema") onSchemaObjectBlock, onSchemaObjectOk := d.GetOk("on_schema_object") @@ -892,9 +903,21 @@ func getDatabaseRoleGrantOn(d *schema.ResourceData) *sdk.DatabaseRoleGrantOn { switch { case objectTypeOk && objectNameOk: + objectType := sdk.ObjectType(objectType) + var id sdk.ObjectIdentifier + // TODO(SNOW-1569535): use a mapper from object type to parsing function + if objectType.IsWithArguments() { + var err error + id, err = sdk.ParseSchemaObjectIdentifierWithArguments(objectName) + if err != nil { + return nil, err + } + } else { + id = sdk.NewSchemaObjectIdentifierFromFullyQualifiedName(objectName) + } grantOnSchemaObject.SchemaObject = &sdk.Object{ - ObjectType: sdk.ObjectType(objectType), - Name: sdk.NewSchemaObjectIdentifierFromFullyQualifiedName(objectName), + ObjectType: objectType, + Name: id, } case allOk: grantOnSchemaObject.All = getGrantOnSchemaObjectIn(all[0].(map[string]any)) @@ -905,10 +928,10 @@ func getDatabaseRoleGrantOn(d *schema.ResourceData) *sdk.DatabaseRoleGrantOn { on.SchemaObject = grantOnSchemaObject } - return on + return on, nil } -func createGrantPrivilegesToDatabaseRoleIdFromSchema(d *schema.ResourceData) *GrantPrivilegesToDatabaseRoleId { +func createGrantPrivilegesToDatabaseRoleIdFromSchema(d *schema.ResourceData) (*GrantPrivilegesToDatabaseRoleId, error) { id := new(GrantPrivilegesToDatabaseRoleId) id.DatabaseRoleName = sdk.NewDatabaseObjectIdentifierFromFullyQualifiedName(d.Get("database_role_name").(string)) id.AllPrivileges = d.Get("all_privileges").(bool) @@ -918,7 +941,10 @@ func createGrantPrivilegesToDatabaseRoleIdFromSchema(d *schema.ResourceData) *Gr id.WithGrantOption = d.Get("with_grant_option").(bool) id.AlwaysApply = d.Get("always_apply").(bool) - on := getDatabaseRoleGrantOn(d) + on, err := getDatabaseRoleGrantOn(d) + if err != nil { + return nil, err + } switch { case on.Database != nil: id.Kind = OnDatabaseDatabaseRoleGrantKind @@ -961,5 +987,5 @@ func createGrantPrivilegesToDatabaseRoleIdFromSchema(d *schema.ResourceData) *Gr id.Data = onSchemaObjectGrantData } - return id + return id, nil } 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 e00b5c842c..b98d23578a 100644 --- a/pkg/resources/grant_privileges_to_database_role_acceptance_test.go +++ b/pkg/resources/grant_privileges_to_database_role_acceptance_test.go @@ -7,6 +7,7 @@ import ( "testing" acc "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/acceptance" + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/acceptance/testenvs" "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk" "github.com/hashicorp/terraform-plugin-testing/config" @@ -625,6 +626,116 @@ func TestAcc_GrantPrivilegesToDatabaseRole_OnSchemaObject_OnAll_Streamlits_InDat }) } +func TestAcc_GrantPrivilegesToDatabaseRole_OnSchemaObject_OnFunctionWithArguments(t *testing.T) { + _ = testenvs.GetOrSkipTest(t, testenvs.EnableAcceptance) + acc.TestAccPreCheck(t) + + databaseRoleId := acc.TestClient().Ids.RandomDatabaseObjectIdentifier() + function := acc.TestClient().Function.CreateSecure(t, sdk.DataTypeFloat) + configVariables := config.Variables{ + "name": config.StringVariable(databaseRoleId.FullyQualifiedName()), + "function_name": config.StringVariable(function.ID().Name()), + "privileges": config.ListVariable( + config.StringVariable(string(sdk.SchemaObjectPrivilegeUsage)), + ), + "database": config.StringVariable(acc.TestDatabaseName), + "schema": config.StringVariable(acc.TestSchemaName), + "with_grant_option": config.BoolVariable(false), + "argument_type": config.StringVariable(string(sdk.DataTypeFloat)), + } + resourceName := "snowflake_grant_privileges_to_database_role.test" + + resource.Test(t, resource.TestCase{ + ProtoV6ProviderFactories: acc.TestAccProtoV6ProviderFactories, + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.RequireAbove(tfversion.Version1_5_0), + }, + CheckDestroy: acc.CheckAccountRolePrivilegesRevoked(t), + Steps: []resource.TestStep{ + { + PreConfig: func() { + _, roleCleanup := acc.TestClient().DatabaseRole.CreateDatabaseRoleWithName(t, databaseRoleId.Name()) + t.Cleanup(roleCleanup) + }, + ConfigDirectory: acc.ConfigurationDirectory("TestAcc_GrantPrivilegesToDatabaseRole/OnSchemaObject_OnFunction"), + ConfigVariables: configVariables, + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "database_role_name", databaseRoleId.FullyQualifiedName()), + 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.object_type", string(sdk.ObjectTypeFunction)), + resource.TestCheckResourceAttr(resourceName, "on_schema_object.0.object_name", function.ID().FullyQualifiedName()), + resource.TestCheckResourceAttr(resourceName, "with_grant_option", "false"), + resource.TestCheckResourceAttr(resourceName, "id", fmt.Sprintf("%s|false|false|USAGE|OnSchemaObject|OnObject|FUNCTION|%s", databaseRoleId.FullyQualifiedName(), function.ID().FullyQualifiedName())), + ), + }, + { + ConfigDirectory: acc.ConfigurationDirectory("TestAcc_GrantPrivilegesToDatabaseRole/OnSchemaObject_OnFunction"), + ConfigVariables: configVariables, + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAcc_GrantPrivilegesToDatabaseRole_OnSchemaObject_OnFunctionWithoutArguments(t *testing.T) { + _ = testenvs.GetOrSkipTest(t, testenvs.EnableAcceptance) + acc.TestAccPreCheck(t) + + databaseRoleId := acc.TestClient().Ids.RandomDatabaseObjectIdentifier() + function := acc.TestClient().Function.CreateSecure(t) + configVariables := config.Variables{ + "name": config.StringVariable(databaseRoleId.FullyQualifiedName()), + "function_name": config.StringVariable(function.ID().Name()), + "privileges": config.ListVariable( + config.StringVariable(string(sdk.SchemaObjectPrivilegeUsage)), + ), + "database": config.StringVariable(acc.TestDatabaseName), + "schema": config.StringVariable(acc.TestSchemaName), + "with_grant_option": config.BoolVariable(false), + "argument_type": config.StringVariable(""), + } + resourceName := "snowflake_grant_privileges_to_database_role.test" + + resource.Test(t, resource.TestCase{ + ProtoV6ProviderFactories: acc.TestAccProtoV6ProviderFactories, + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.RequireAbove(tfversion.Version1_5_0), + }, + CheckDestroy: acc.CheckAccountRolePrivilegesRevoked(t), + Steps: []resource.TestStep{ + { + PreConfig: func() { + _, roleCleanup := acc.TestClient().DatabaseRole.CreateDatabaseRoleWithName(t, databaseRoleId.Name()) + t.Cleanup(roleCleanup) + }, + ConfigDirectory: acc.ConfigurationDirectory("TestAcc_GrantPrivilegesToDatabaseRole/OnSchemaObject_OnFunction"), + ConfigVariables: configVariables, + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "database_role_name", databaseRoleId.FullyQualifiedName()), + 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.object_type", string(sdk.ObjectTypeFunction)), + resource.TestCheckResourceAttr(resourceName, "on_schema_object.0.object_name", function.ID().FullyQualifiedName()), + resource.TestCheckResourceAttr(resourceName, "with_grant_option", "false"), + resource.TestCheckResourceAttr(resourceName, "id", fmt.Sprintf("%s|false|false|USAGE|OnSchemaObject|OnObject|FUNCTION|%s", databaseRoleId.FullyQualifiedName(), function.ID().FullyQualifiedName())), + ), + }, + { + ConfigDirectory: acc.ConfigurationDirectory("TestAcc_GrantPrivilegesToDatabaseRole/OnSchemaObject_OnFunction"), + ConfigVariables: configVariables, + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + func TestAcc_GrantPrivilegesToDatabaseRole_UpdatePrivileges(t *testing.T) { databaseRoleId := acc.TestClient().Ids.RandomDatabaseObjectIdentifier() diff --git a/pkg/resources/grant_privileges_to_database_role_identifier.go b/pkg/resources/grant_privileges_to_database_role_identifier.go index 973b7ab024..45ad11a432 100644 --- a/pkg/resources/grant_privileges_to_database_role_identifier.go +++ b/pkg/resources/grant_privileges_to_database_role_identifier.go @@ -127,9 +127,21 @@ func ParseGrantPrivilegesToDatabaseRoleId(id string) (GrantPrivilegesToDatabaseR if len(parts) != 8 { return databaseRoleId, sdk.NewError(`database role identifier should hold 8 parts "||||OnSchemaObject|OnObject||"`) } + objectType := sdk.ObjectType(parts[6]) + var id sdk.ObjectIdentifier + // TODO(SNOW-1569535): use a mapper from object type to parsing function + if objectType.IsWithArguments() { + var err error + id, err = sdk.ParseSchemaObjectIdentifierWithArguments(parts[7]) + if err != nil { + return databaseRoleId, err + } + } else { + id = sdk.NewSchemaObjectIdentifierFromFullyQualifiedName(parts[7]) + } onSchemaObjectGrantData.Object = &sdk.Object{ - ObjectType: sdk.ObjectType(parts[6]), - Name: sdk.NewSchemaObjectIdentifierFromFullyQualifiedName(parts[7]), + ObjectType: objectType, + Name: id, } case OnAllSchemaObjectGrantKind, OnFutureSchemaObjectGrantKind: bulkOperationGrantData := &BulkOperationGrantData{ 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 ea8cd4404b..a9ed631605 100644 --- a/pkg/resources/grant_privileges_to_database_role_identifier_test.go +++ b/pkg/resources/grant_privileges_to_database_role_identifier_test.go @@ -114,6 +114,40 @@ func TestParseGrantPrivilegesToDatabaseRoleId(t *testing.T) { }, }, }, + { + Name: "grant database role on function", + Identifier: `"database-name"."database-role"|false|false|USAGE|OnSchemaObject|OnObject|FUNCTION|"database-name"."schema-name"."function-name"(FLOAT)`, + Expected: GrantPrivilegesToDatabaseRoleId{ + DatabaseRoleName: sdk.NewDatabaseObjectIdentifier("database-name", "database-role"), + WithGrantOption: false, + Privileges: []string{"USAGE"}, + Kind: OnSchemaObjectDatabaseRoleGrantKind, + Data: &OnSchemaObjectGrantData{ + Kind: OnObjectSchemaObjectGrantKind, + Object: &sdk.Object{ + ObjectType: sdk.ObjectTypeFunction, + Name: sdk.NewSchemaObjectIdentifierWithArguments("database-name", "schema-name", "function-name", sdk.DataTypeFloat), + }, + }, + }, + }, + { + Name: "grant database role on function without arguments", + Identifier: `"database-name"."database-role"|false|false|USAGE|OnSchemaObject|OnObject|FUNCTION|"database-name"."schema-name"."function-name"()`, + Expected: GrantPrivilegesToDatabaseRoleId{ + DatabaseRoleName: sdk.NewDatabaseObjectIdentifier("database-name", "database-role"), + WithGrantOption: false, + Privileges: []string{"USAGE"}, + Kind: OnSchemaObjectDatabaseRoleGrantKind, + Data: &OnSchemaObjectGrantData{ + Kind: OnObjectSchemaObjectGrantKind, + Object: &sdk.Object{ + ObjectType: sdk.ObjectTypeFunction, + Name: sdk.NewSchemaObjectIdentifierWithArguments("database-name", "schema-name", "function-name", []sdk.DataType{}...), + }, + }, + }, + }, { Name: "grant database role on schema object with on all option", Identifier: `"database-name"."database-role"|false|false|CREATE SCHEMA,USAGE,MONITOR|OnSchemaObject|OnAll|TABLES`, diff --git a/pkg/resources/grant_privileges_to_share.go b/pkg/resources/grant_privileges_to_share.go index bc7cd6573b..6d5e232d47 100644 --- a/pkg/resources/grant_privileges_to_share.go +++ b/pkg/resources/grant_privileges_to_share.go @@ -17,7 +17,7 @@ import ( var grantPrivilegesToShareGrantExactlyOneOfValidation = []string{ "on_database", "on_schema", - // TODO(SNOW-990811): "function_name", + "on_function", "on_table", "on_all_tables_in_schema", "on_tag", @@ -54,15 +54,6 @@ var grantPrivilegesToShareSchema = map[string]*schema.Schema{ ValidateDiagFunc: IsValidIdentifier[sdk.DatabaseObjectIdentifier](), ExactlyOneOf: grantPrivilegesToShareGrantExactlyOneOfValidation, }, - // TODO(SNOW-1021686): Because function identifier contains arguments which are not supported right now - // "function_name": { - // Type: schema.TypeString, - // Optional: true, - // ForceNew: true, - // Description: "The fully qualified name of the function on which privileges will be granted.", - // ValidateDiagFunc: IsValidIdentifier[sdk.FunctionIdentifier](), - // ExactlyOneOf: grantPrivilegesToShareGrantExactlyOneOfValidation, - // }, "on_table": { Type: schema.TypeString, Optional: true, @@ -95,6 +86,13 @@ var grantPrivilegesToShareSchema = map[string]*schema.Schema{ ValidateDiagFunc: IsValidIdentifier[sdk.SchemaObjectIdentifier](), ExactlyOneOf: grantPrivilegesToShareGrantExactlyOneOfValidation, }, + "on_function": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + Description: "The fully qualified name of the function on which privileges will be granted.", + ExactlyOneOf: grantPrivilegesToShareGrantExactlyOneOfValidation, + }, } func GrantPrivilegesToShare() *schema.Resource { @@ -133,10 +131,10 @@ func ImportGrantPrivilegesToShare() func(ctx context.Context, d *schema.Resource if err := d.Set("on_schema", id.Identifier.FullyQualifiedName()); err != nil { return nil, err } - // TODO(SNOW-990811) case OnFunctionShareGrantKind: - // if err := d.Set("function_name", id.Identifier.FullyQualifiedName()); err != nil { - // return nil, err - // } + case OnFunctionShareGrantKind: + if err := d.Set("on_function", id.Identifier.FullyQualifiedName()); err != nil { + return nil, err + } case OnTableShareGrantKind: if err := d.Set("on_table", id.Identifier.FullyQualifiedName()); err != nil { return nil, err @@ -161,10 +159,17 @@ func ImportGrantPrivilegesToShare() func(ctx context.Context, d *schema.Resource func CreateGrantPrivilegesToShare(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics { client := meta.(*provider.Context).Client - id := createGrantPrivilegesToShareIdFromSchema(d) + id, err := createGrantPrivilegesToShareIdFromSchema(d) + if err != nil { + return diag.FromErr(err) + } log.Printf("[DEBUG] created identifier from schema: %s", id.String()) + grantOn, err := getShareGrantOn(d) + if err != nil { + return diag.FromErr(err) + } - err := client.Grants.GrantPrivilegeToShare(ctx, getObjectPrivilegesFromSchema(d), getShareGrantOn(d), id.ShareName) + err = client.Grants.GrantPrivilegeToShare(ctx, getObjectPrivilegesFromSchema(d), grantOn, id.ShareName) if err != nil { return diag.Diagnostics{ diag.Diagnostic{ @@ -213,7 +218,10 @@ func UpdateGrantPrivilegesToShare(ctx context.Context, d *schema.ResourceData, m } } - grantOn := getShareGrantOn(d) + grantOn, err := getShareGrantOn(d) + if err != nil { + return diag.FromErr(err) + } if len(privilegesToAdd) > 0 { err = client.Grants.GrantPrivilegeToShare( @@ -271,8 +279,12 @@ func DeleteGrantPrivilegesToShare(ctx context.Context, d *schema.ResourceData, m }, } } + grantOn, err := getShareGrantOn(d) + if err != nil { + return diag.FromErr(err) + } - err = client.Grants.RevokePrivilegeFromShare(ctx, getObjectPrivilegesFromSchema(d), getShareGrantOn(d), id.ShareName) + err = client.Grants.RevokePrivilegeFromShare(ctx, getObjectPrivilegesFromSchema(d), grantOn, id.ShareName) if err != nil { return diag.Diagnostics{ diag.Diagnostic{ @@ -375,14 +387,14 @@ func ReadGrantPrivilegesToShare(ctx context.Context, d *schema.ResourceData, met return nil } -func createGrantPrivilegesToShareIdFromSchema(d *schema.ResourceData) *GrantPrivilegesToShareId { +func createGrantPrivilegesToShareIdFromSchema(d *schema.ResourceData) (*GrantPrivilegesToShareId, error) { id := new(GrantPrivilegesToShareId) id.ShareName = sdk.NewAccountObjectIdentifier(d.Get("to_share").(string)) id.Privileges = expandStringList(d.Get("privileges").(*schema.Set).List()) databaseName, databaseNameOk := d.GetOk("on_database") schemaName, schemaNameOk := d.GetOk("on_schema") - // TODO(SNOW-990811) functionName, functionNameOk := d.GetOk("function_name") + functionName, functionNameOk := d.GetOk("on_function") tableName, tableNameOk := d.GetOk("on_table") allTablesInSchema, allTablesInSchemaOk := d.GetOk("on_all_tables_in_schema") tagName, tagNameOk := d.GetOk("on_tag") @@ -395,9 +407,13 @@ func createGrantPrivilegesToShareIdFromSchema(d *schema.ResourceData) *GrantPriv case schemaNameOk: id.Kind = OnSchemaShareGrantKind id.Identifier = sdk.NewDatabaseObjectIdentifierFromFullyQualifiedName(schemaName.(string)) - // TODO(SNOW-990811) case functionNameOk: - // id.Kind = OnFunctionShareGrantKind - // id.Identifier = sdk.NewSchemaObjectIdentifierFromFullyQualifiedName(functionName.(string)) + case functionNameOk: + id.Kind = OnFunctionShareGrantKind + parsed, err := sdk.ParseSchemaObjectIdentifierWithArguments(functionName.(string)) + if err != nil { + return nil, err + } + id.Identifier = parsed case tableNameOk: id.Kind = OnTableShareGrantKind id.Identifier = sdk.NewSchemaObjectIdentifierFromFullyQualifiedName(tableName.(string)) @@ -412,7 +428,7 @@ func createGrantPrivilegesToShareIdFromSchema(d *schema.ResourceData) *GrantPriv id.Identifier = sdk.NewSchemaObjectIdentifierFromFullyQualifiedName(viewName.(string)) } - return id + return id, nil } func getObjectPrivilegesFromSchema(d *schema.ResourceData) []sdk.ObjectPrivilege { @@ -424,12 +440,12 @@ func getObjectPrivilegesFromSchema(d *schema.ResourceData) []sdk.ObjectPrivilege return objectPrivileges } -func getShareGrantOn(d *schema.ResourceData) *sdk.ShareGrantOn { +func getShareGrantOn(d *schema.ResourceData) (*sdk.ShareGrantOn, error) { grantOn := new(sdk.ShareGrantOn) databaseName, databaseNameOk := d.GetOk("on_database") schemaName, schemaNameOk := d.GetOk("on_schema") - // TODO(SNOW-990811) functionName, functionNameOk := d.GetOk("on_function") + functionName, functionNameOk := d.GetOk("on_function") tableName, tableNameOk := d.GetOk("on_table") allTablesInSchema, allTablesInSchemaOk := d.GetOk("on_all_tables_in_schema") tagName, tagNameOk := d.GetOk("on_tag") @@ -440,8 +456,12 @@ func getShareGrantOn(d *schema.ResourceData) *sdk.ShareGrantOn { grantOn.Database = sdk.NewAccountObjectIdentifierFromFullyQualifiedName(databaseName.(string)) case len(schemaName.(string)) > 0 && schemaNameOk: grantOn.Schema = sdk.NewDatabaseObjectIdentifierFromFullyQualifiedName(schemaName.(string)) - // TODO(SNOW-990811) case len(functionName.(string)) > 0 && functionNameOk: - // grantOn.Function = sdk.NewSchemaObjectIdentifierFromFullyQualifiedName(functionName.(string)) + case len(functionName.(string)) > 0 && functionNameOk: + id, err := sdk.ParseSchemaObjectIdentifierWithArguments(functionName.(string)) + if err != nil { + return nil, err + } + grantOn.Function = id case len(tableName.(string)) > 0 && tableNameOk: grantOn.Table = &sdk.OnTable{ Name: sdk.NewSchemaObjectIdentifierFromFullyQualifiedName(tableName.(string)), @@ -456,7 +476,7 @@ func getShareGrantOn(d *schema.ResourceData) *sdk.ShareGrantOn { grantOn.View = sdk.NewSchemaObjectIdentifierFromFullyQualifiedName(viewName.(string)) } - return grantOn + return grantOn, nil } func prepareShowGrantsRequestForShare(id GrantPrivilegesToShareId) (*sdk.ShowGrantOptions, sdk.ObjectType) { @@ -477,6 +497,8 @@ func prepareShowGrantsRequestForShare(id GrantPrivilegesToShareId) (*sdk.ShowGra objectType = sdk.ObjectTypeTag case OnViewShareGrantKind: objectType = sdk.ObjectTypeView + case OnFunctionShareGrantKind: + objectType = sdk.ObjectTypeFunction } opts.On = &sdk.ShowGrantsOn{ diff --git a/pkg/resources/grant_privileges_to_share_acceptance_test.go b/pkg/resources/grant_privileges_to_share_acceptance_test.go index cefa39629d..3920ad3983 100644 --- a/pkg/resources/grant_privileges_to_share_acceptance_test.go +++ b/pkg/resources/grant_privileges_to_share_acceptance_test.go @@ -1,10 +1,12 @@ package resources_test import ( + "fmt" "regexp" "testing" acc "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/acceptance" + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/acceptance/testenvs" "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk" "github.com/hashicorp/terraform-plugin-testing/config" @@ -116,8 +118,6 @@ func TestAcc_GrantPrivilegesToShare_OnSchema(t *testing.T) { }) } -// TODO(SNOW-1021686): Add on_function test - func TestAcc_GrantPrivilegesToShare_OnTable(t *testing.T) { databaseId := acc.TestClient().Ids.RandomAccountObjectIdentifier() schemaId := acc.TestClient().Ids.RandomDatabaseObjectIdentifierInDatabase(databaseId) @@ -338,6 +338,102 @@ func TestAcc_GrantPrivilegesToShare_OnTag(t *testing.T) { }) } +func TestAcc_GrantPrivilegesToShare_OnSchemaObject_OnFunctionWithArguments(t *testing.T) { + _ = testenvs.GetOrSkipTest(t, testenvs.EnableAcceptance) + acc.TestAccPreCheck(t) + + share, shareCleanup := acc.TestClient().Share.CreateShare(t) + t.Cleanup(shareCleanup) + function := acc.TestClient().Function.CreateSecure(t, sdk.DataTypeFloat) + configVariables := config.Variables{ + "name": config.StringVariable(share.ID().Name()), + "function_name": config.StringVariable(function.ID().Name()), + "privileges": config.ListVariable( + config.StringVariable(string(sdk.SchemaObjectPrivilegeUsage)), + ), + "database": config.StringVariable(acc.TestDatabaseName), + "schema": config.StringVariable(acc.TestSchemaName), + "argument_type": config.StringVariable(string(sdk.DataTypeFloat)), + } + resourceName := "snowflake_grant_privileges_to_share.test" + + resource.Test(t, resource.TestCase{ + ProtoV6ProviderFactories: acc.TestAccProtoV6ProviderFactories, + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.RequireAbove(tfversion.Version1_5_0), + }, + CheckDestroy: acc.CheckAccountRolePrivilegesRevoked(t), + Steps: []resource.TestStep{ + { + ConfigDirectory: acc.ConfigurationDirectory("TestAcc_GrantPrivilegesToShare/OnFunction"), + ConfigVariables: configVariables, + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "to_share", share.ID().Name()), + resource.TestCheckResourceAttr(resourceName, "privileges.#", "1"), + resource.TestCheckResourceAttr(resourceName, "privileges.0", string(sdk.SchemaObjectPrivilegeUsage)), + resource.TestCheckResourceAttr(resourceName, "on_function", function.ID().FullyQualifiedName()), + resource.TestCheckResourceAttr(resourceName, "id", fmt.Sprintf("%s|USAGE|OnFunction|%s", share.ID().FullyQualifiedName(), function.ID().FullyQualifiedName())), + ), + }, + { + ConfigDirectory: acc.ConfigurationDirectory("TestAcc_GrantPrivilegesToShare/OnFunction"), + ConfigVariables: configVariables, + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAcc_GrantPrivilegesToShare_OnSchemaObject_OnFunctionWithoutArguments(t *testing.T) { + _ = testenvs.GetOrSkipTest(t, testenvs.EnableAcceptance) + acc.TestAccPreCheck(t) + + share, shareCleanup := acc.TestClient().Share.CreateShare(t) + t.Cleanup(shareCleanup) + function := acc.TestClient().Function.CreateSecure(t) + configVariables := config.Variables{ + "name": config.StringVariable(share.ID().Name()), + "function_name": config.StringVariable(function.ID().Name()), + "privileges": config.ListVariable( + config.StringVariable(string(sdk.SchemaObjectPrivilegeUsage)), + ), + "database": config.StringVariable(acc.TestDatabaseName), + "schema": config.StringVariable(acc.TestSchemaName), + "argument_type": config.StringVariable(""), + } + resourceName := "snowflake_grant_privileges_to_share.test" + + resource.Test(t, resource.TestCase{ + ProtoV6ProviderFactories: acc.TestAccProtoV6ProviderFactories, + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.RequireAbove(tfversion.Version1_5_0), + }, + CheckDestroy: acc.CheckAccountRolePrivilegesRevoked(t), + Steps: []resource.TestStep{ + { + ConfigDirectory: acc.ConfigurationDirectory("TestAcc_GrantPrivilegesToShare/OnFunction"), + ConfigVariables: configVariables, + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "to_share", share.ID().Name()), + resource.TestCheckResourceAttr(resourceName, "privileges.#", "1"), + resource.TestCheckResourceAttr(resourceName, "privileges.0", string(sdk.SchemaObjectPrivilegeUsage)), + resource.TestCheckResourceAttr(resourceName, "on_function", function.ID().FullyQualifiedName()), + resource.TestCheckResourceAttr(resourceName, "id", fmt.Sprintf("%s|USAGE|OnFunction|%s", share.ID().FullyQualifiedName(), function.ID().FullyQualifiedName())), + ), + }, + { + ConfigDirectory: acc.ConfigurationDirectory("TestAcc_GrantPrivilegesToShare/OnFunction"), + ConfigVariables: configVariables, + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + func TestAcc_GrantPrivilegesToShare_OnPrivilegeUpdate(t *testing.T) { databaseName := acc.TestClient().Ids.RandomAccountObjectIdentifier() shareName := acc.TestClient().Ids.RandomAccountObjectIdentifier() @@ -555,3 +651,58 @@ func TestAcc_GrantPrivilegesToShare_RemoveShareOutsideTerraform(t *testing.T) { }, }) } + +func TestAcc_GrantPrivilegesToShareWithNameContainingDots_OnTable(t *testing.T) { + databaseId := acc.TestClient().Ids.RandomAccountObjectIdentifier() + schemaId := acc.TestClient().Ids.RandomDatabaseObjectIdentifierInDatabase(databaseId) + tableId := acc.TestClient().Ids.RandomSchemaObjectIdentifierInSchema(schemaId) + shareId := acc.TestClient().Ids.RandomAccountObjectIdentifierContaining(".foo.bar") + + configVariables := func(withGrant bool) config.Variables { + variables := config.Variables{ + "to_share": config.StringVariable(shareId.Name()), + "database": config.StringVariable(databaseId.Name()), + "schema": config.StringVariable(schemaId.Name()), + "on_table": config.StringVariable(tableId.Name()), + } + if withGrant { + variables["privileges"] = config.ListVariable( + config.StringVariable(sdk.ObjectPrivilegeSelect.String()), + ) + } + return variables + } + resourceName := "snowflake_grant_privileges_to_share.test" + + resource.Test(t, resource.TestCase{ + ProtoV6ProviderFactories: acc.TestAccProtoV6ProviderFactories, + PreCheck: func() { acc.TestAccPreCheck(t) }, + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.RequireAbove(tfversion.Version1_5_0), + }, + Steps: []resource.TestStep{ + { + ConfigDirectory: acc.ConfigurationDirectory("TestAcc_GrantPrivilegesToShare/OnTable"), + ConfigVariables: configVariables(true), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "to_share", shareId.Name()), + resource.TestCheckResourceAttr(resourceName, "privileges.#", "1"), + resource.TestCheckResourceAttr(resourceName, "privileges.0", sdk.ObjectPrivilegeSelect.String()), + resource.TestCheckResourceAttr(resourceName, "on_table", tableId.FullyQualifiedName()), + ), + }, + { + ConfigDirectory: acc.ConfigurationDirectory("TestAcc_GrantPrivilegesToShare/OnTable"), + ConfigVariables: configVariables(true), + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + ConfigDirectory: acc.ConfigurationDirectory("TestAcc_GrantPrivilegesToShare/OnTable_NoGrant"), + ConfigVariables: configVariables(false), + Check: acc.CheckSharePrivilegesRevoked(t), + }, + }, + }) +} diff --git a/pkg/resources/grant_privileges_to_share_identifier.go b/pkg/resources/grant_privileges_to_share_identifier.go index 9996c38add..9c7bf09c22 100644 --- a/pkg/resources/grant_privileges_to_share_identifier.go +++ b/pkg/resources/grant_privileges_to_share_identifier.go @@ -11,10 +11,9 @@ import ( type ShareGrantKind string const ( - OnDatabaseShareGrantKind ShareGrantKind = "OnDatabase" - OnSchemaShareGrantKind ShareGrantKind = "OnSchema" - // TODO(SNOW-1021686): Because function identifier contains arguments which are not supported right now - // OnFunctionShareGrantKind ShareGrantKind = "OnFunction" + OnDatabaseShareGrantKind ShareGrantKind = "OnDatabase" + OnSchemaShareGrantKind ShareGrantKind = "OnSchema" + OnFunctionShareGrantKind ShareGrantKind = "OnFunction" OnTableShareGrantKind ShareGrantKind = "OnTable" OnAllTablesInSchemaShareGrantKind ShareGrantKind = "OnAllTablesInSchema" OnTagShareGrantKind ShareGrantKind = "OnTag" @@ -53,42 +52,31 @@ func ParseGrantPrivilegesToShareId(idString string) (GrantPrivilegesToShareId, e grantPrivilegesToShareId.Privileges = privileges grantPrivilegesToShareId.Kind = ShareGrantKind(parts[2]) - id, err := helpers.DecodeSnowflakeParameterID(parts[3]) - if err != nil { - return grantPrivilegesToShareId, err - } - switch grantPrivilegesToShareId.Kind { case OnDatabaseShareGrantKind: - if typedIdentifier, ok := id.(sdk.AccountObjectIdentifier); ok { - grantPrivilegesToShareId.Identifier = typedIdentifier - } else { - return grantPrivilegesToShareId, fmt.Errorf( - "invalid identifier, expected fully qualified name of account object: %s, but instead got: %s", - getExpectedIdentifierRepresentationFromGeneric[sdk.AccountObjectIdentifier](), - getExpectedIdentifierRepresentationFromParam(id), - ) + id, err := sdk.ParseAccountObjectIdentifier(parts[3]) + if err != nil { + return grantPrivilegesToShareId, sdk.NewError(fmt.Sprintf("invalid identifier, expected fully qualified name of account object%s: ", parts[3]), err) } + grantPrivilegesToShareId.Identifier = id case OnSchemaShareGrantKind, OnAllTablesInSchemaShareGrantKind: - if typedIdentifier, ok := id.(sdk.DatabaseObjectIdentifier); ok { - grantPrivilegesToShareId.Identifier = typedIdentifier - } else { - return grantPrivilegesToShareId, fmt.Errorf( - "invalid identifier, expected fully qualified name of database object: %s, but instead got: %s", - getExpectedIdentifierRepresentationFromGeneric[sdk.DatabaseObjectIdentifier](), - getExpectedIdentifierRepresentationFromParam(id), - ) + id, err := sdk.ParseDatabaseObjectIdentifier(parts[3]) + if err != nil { + return grantPrivilegesToShareId, sdk.NewError(fmt.Sprintf("could not parse database object identifier %s: ", parts[3]), err) + } + grantPrivilegesToShareId.Identifier = id + case OnTableShareGrantKind, OnViewShareGrantKind, OnTagShareGrantKind: + id, err := sdk.ParseSchemaObjectIdentifier(parts[3]) + if err != nil { + return grantPrivilegesToShareId, sdk.NewError(fmt.Sprintf("could not parse schema object identifier %s: ", parts[3]), err) } - case OnTableShareGrantKind, OnViewShareGrantKind, OnTagShareGrantKind: // TODO(SNOW-1021686) , OnFunctionShareGrantKind: - if typedIdentifier, ok := id.(sdk.SchemaObjectIdentifier); ok { - grantPrivilegesToShareId.Identifier = typedIdentifier - } else { - return grantPrivilegesToShareId, fmt.Errorf( - "invalid identifier, expected fully qualified name of schema object: %s, but instead got: %s", - getExpectedIdentifierRepresentationFromGeneric[sdk.SchemaObjectIdentifier](), - getExpectedIdentifierRepresentationFromParam(id), - ) + grantPrivilegesToShareId.Identifier = id + case OnFunctionShareGrantKind: + id, err := sdk.ParseSchemaObjectIdentifierWithArguments(parts[3]) + if err != nil { + return grantPrivilegesToShareId, sdk.NewError(fmt.Sprintf("could not parse schema object identifier with arguments %s: ", parts[3]), err) } + grantPrivilegesToShareId.Identifier = id default: return grantPrivilegesToShareId, fmt.Errorf("unexpected share grant kind: %v", grantPrivilegesToShareId.Kind) } diff --git a/pkg/resources/grant_privileges_to_share_identifier_test.go b/pkg/resources/grant_privileges_to_share_identifier_test.go index d892d2800d..2c64f3f847 100644 --- a/pkg/resources/grant_privileges_to_share_identifier_test.go +++ b/pkg/resources/grant_privileges_to_share_identifier_test.go @@ -34,17 +34,26 @@ func TestParseGrantPrivilegesToShareId(t *testing.T) { Identifier: sdk.NewDatabaseObjectIdentifier("on-database-name", "on-schema-name"), }, }, - // TODO(SNOW-1021686): This is wrong and should be fixed (function's last part of identifier cannot be enclosed with quotes like that) - //{ - // Name: "grant privileges on function to share", - // Identifier: `"share-name"|USAGE|OnFunction|"on-database-name"."on-schema-name".on-function-name(INT, VARCHAR)`, - // Expected: GrantPrivilegesToShareId{ - // ShareName: sdk.NewExternalObjectIdentifierFromFullyQualifiedName("share-name"), - // Privileges: []string{"USAGE"}, - // Kind: OnFunctionShareGrantKind, - // Identifier: sdk.NewSchemaObjectIdentifier("on-database-name", "on-schema-name", "on-function-name(INT, VARCHAR)"), - // }, - // }, + { + Name: "grant privileges on function to share", + Identifier: `"share-name"|USAGE|OnFunction|"on-database-name"."on-schema-name".on-function-name(INT, VARCHAR)`, + Expected: GrantPrivilegesToShareId{ + ShareName: sdk.NewAccountObjectIdentifier("share-name"), + Privileges: []string{"USAGE"}, + Kind: OnFunctionShareGrantKind, + Identifier: sdk.NewSchemaObjectIdentifierWithArguments("on-database-name", "on-schema-name", "on-function-name", sdk.DataTypeInt, sdk.DataTypeVARCHAR), + }, + }, + { + Name: "grant privileges on function without arguments to share", + Identifier: `"share-name"|READ|OnFunction|"on-database-name"."on-schema-name"."on-view-name"()`, + Expected: GrantPrivilegesToShareId{ + ShareName: sdk.NewAccountObjectIdentifier("share-name"), + Privileges: []string{"READ"}, + Kind: OnFunctionShareGrantKind, + Identifier: sdk.NewSchemaObjectIdentifierWithArguments("on-database-name", "on-schema-name", "on-view-name", []sdk.DataType{}...), + }, + }, { Name: "grant privileges on table to share", Identifier: `"share-name"|EVOLVE SCHEMA|OnTable|"on-database-name"."on-schema-name"."on-table-name"`, @@ -103,22 +112,22 @@ func TestParseGrantPrivilegesToShareId(t *testing.T) { { Name: "validation: invalid identifier", Identifier: `"share-name"|SELECT|OnDatabase|one.two.three.four.five.six.seven.eight.nine.ten`, - Error: `unable to classify identifier: one.two.three.four.five.six.seven.eight.nine.ten`, + Error: `unexpected number of parts 10 in identifier one.two.three.four.five.six.seven.eight.nine.ten, expected 1 in a form of ""`, }, { Name: "validation: invalid account object identifier", Identifier: `"share-name"|SELECT|OnDatabase|one.two`, - Error: `invalid identifier, expected fully qualified name of account object: , but instead got: .`, + Error: `unexpected number of parts 2 in identifier one.two, expected 1 in a form of ""`, }, { Name: "validation: invalid database object identifier", Identifier: `"share-name"|SELECT|OnSchema|one.two.three`, - Error: `invalid identifier, expected fully qualified name of database object: ., but instead got: ..`, + Error: `unexpected number of parts 3 in identifier one.two.three, expected 2 in a form of ".`, }, { Name: "validation: invalid schema object identifier", Identifier: `"share-name"|SELECT|OnTable|one`, - Error: `invalid identifier, expected fully qualified name of schema object: .., but instead got: `, + Error: `unexpected number of parts 1 in identifier one, expected 3 in a form of ".."`, }, } @@ -163,17 +172,6 @@ func TestGrantPrivilegesToShareIdString(t *testing.T) { }, Expected: `"share-name"|USAGE|OnSchema|"database-name"."schema-name"`, }, - // TODO(SNOW-1021686): This is wrong and should be fixed (function's last part of identifier cannot be enclosed with quotes like that) - //{ - // Name: "grant privileges on function to share", - // Identifier: GrantPrivilegesToShareId{ - // ShareName: sdk.NewExternalObjectIdentifierFromFullyQualifiedName("share-name"), - // Privileges: []string{"USAGE"}, - // Kind: OnFunctionShareGrantKind, - // Identifier: sdk.NewSchemaObjectIdentifier("database-name", "schema-name", "function-name(INT, VARCHAR)"), - // }, - // Expected: `"share-name"|USAGE|OnFunction|"database-name"."schema-name".\"function-name(INT, VARCHAR)\"`, - // }, { Name: "grant privileges on table to share", Identifier: GrantPrivilegesToShareId{ diff --git a/pkg/resources/testdata/TestAcc_GrantOwnership/OnObject_Procedure_ToAccountRole/test.tf b/pkg/resources/testdata/TestAcc_GrantOwnership/OnObject_Procedure_ToAccountRole/test.tf new file mode 100644 index 0000000000..d33faa2af3 --- /dev/null +++ b/pkg/resources/testdata/TestAcc_GrantOwnership/OnObject_Procedure_ToAccountRole/test.tf @@ -0,0 +1,39 @@ +resource "snowflake_account_role" "test" { + name = var.account_role_name +} + +resource "snowflake_database" "test" { + name = var.database_name +} + +resource "snowflake_schema" "test" { + name = var.schema_name + database = snowflake_database.test.name +} + +resource "snowflake_procedure" "test" { + name = var.procedure_name + database = snowflake_database.test.name + schema = snowflake_schema.test.name + language = "JAVASCRIPT" + arguments { + name = "ARG1" + type = "FLOAT" + } + return_type = "FLOAT" + execute_as = "CALLER" + return_behavior = "VOLATILE" + null_input_behavior = "RETURNS NULL ON NULL INPUT" + statement = <.."( ...):" +// Return type is not part of an identifier. +// TODO(SNOW-1625030): Remove and use ParseSchemaObjectIdentifierWithArguments instead +func ParseSchemaObjectIdentifierWithArgumentsAndReturnType(fullyQualifiedName string) (SchemaObjectIdentifierWithArguments, error) { + parts, err := ParseIdentifierStringWithOpts(fullyQualifiedName, func(r *csv.Reader) { + r.Comma = IdDelimiter + }) + if err != nil { + return SchemaObjectIdentifierWithArguments{}, err + } + functionHeader := parts[2] + leftParenthesisIndex := strings.IndexRune(functionHeader, '(') + functionName := functionHeader[:leftParenthesisIndex] + argsRaw := functionHeader[leftParenthesisIndex:] + returnTypeIndex := strings.LastIndex(argsRaw, ":") + if returnTypeIndex != -1 { + argsRaw = argsRaw[:returnTypeIndex] + } + dataTypes, err := ParseFunctionArgumentsFromString(argsRaw) + if err != nil { + return SchemaObjectIdentifierWithArguments{}, err + } + return NewSchemaObjectIdentifierWithArguments( + parts[0], + parts[1], + functionName, + dataTypes..., + ), nil +} + // ParseFunctionArgumentsFromString parses function argument from arguments string with optional argument names. // Varying types are not supported (e.g. VARCHAR(200)), because Snowflake outputs them in a shortened version // (VARCHAR in this case). The only exception is newly added type VECTOR that has the following structure @@ -192,6 +222,12 @@ func ParseFunctionArgumentsFromString(arguments string) ([]DataType, error) { // We use another buffer to peek into next data type (needed for vector parsing) peekDataType, _ := bytes.NewBufferString(stringBuffer.String()).ReadString(',') + // Skip argument name, if present + if strings.ContainsRune(peekDataType, ' ') && !strings.HasPrefix(peekDataType, "VECTOR(") { + // Ignore err, because stringBuffer always contains ' ' here + _, _ = stringBuffer.ReadString(' ') + peekDataType, _ = bytes.NewBufferString(stringBuffer.String()).ReadString(',') + } switch { // For now, only vectors need special parsing behavior @@ -239,11 +275,11 @@ func ParseFunctionArgumentsFromString(arguments string) ([]DataType, error) { } dataTypes = append(dataTypes, DataType(vectorDataType)) default: - dataType, err := stringBuffer.ReadString(',') + argument, err := stringBuffer.ReadString(',') if err == nil { - dataType = dataType[:len(dataType)-1] + argument = argument[:len(argument)-1] } - dataTypes = append(dataTypes, DataType(dataType)) + dataTypes = append(dataTypes, DataType(argument)) } } diff --git a/pkg/sdk/identifier_parsers_test.go b/pkg/sdk/identifier_parsers_test.go index cf67c499ec..58aeabb2f3 100644 --- a/pkg/sdk/identifier_parsers_test.go +++ b/pkg/sdk/identifier_parsers_test.go @@ -301,6 +301,7 @@ func Test_ParseFunctionArgumentsFromString(t *testing.T) { {Arguments: `(FLOAT, NUMBER(10, 2), TIME)`, Expected: []DataType{DataTypeFloat, DataType("NUMBER(10"), DataType("2)"), DataTypeTime}}, {Arguments: `(FLOAT, NUMBER(10, 2))`, Expected: []DataType{DataTypeFloat, DataType("NUMBER(10"), DataType("2)")}}, {Arguments: `(NUMBER(10, 2), FLOAT)`, Expected: []DataType{DataType("NUMBER(10"), DataType("2)"), DataTypeFloat}}, + {Arguments: `(ab NUMBER(10, 2), x FLOAT, FLOAT)`, Expected: []DataType{DataType("NUMBER(10"), DataType("2)"), DataTypeFloat, DataTypeFloat}}, } for _, testCase := range testCases { @@ -371,3 +372,31 @@ func TestNewSchemaObjectIdentifierWithArgumentsFromFullyQualifiedName_WithRawInp }) } } + +func TestNewSchemaObjectIdentifierWithArgumentsAndReturnTypeFromFullyQualifiedName_WithRawInput(t *testing.T) { + testCases := []struct { + RawInput string + ExpectedIdentifierStructure SchemaObjectIdentifierWithArguments + Error string + }{ + {RawInput: `abc.def.ghi()`, ExpectedIdentifierStructure: NewSchemaObjectIdentifierWithArguments(`abc`, `def`, `ghi`)}, + {RawInput: `abc.def.ghi(FLOAT, VECTOR(INT, 20))`, ExpectedIdentifierStructure: NewSchemaObjectIdentifierWithArguments(`abc`, `def`, `ghi`, DataTypeFloat, "VECTOR(INT, 20)")}, + {RawInput: `abc.def.ghi():FLOAT`, ExpectedIdentifierStructure: NewSchemaObjectIdentifierWithArguments(`abc`, `def`, `ghi`)}, + {RawInput: `abc.def."ghi(FLOAT, VECTOR(INT, 20)):NUMBER(10,2)"`, ExpectedIdentifierStructure: NewSchemaObjectIdentifierWithArguments(`abc`, `def`, `ghi`, DataTypeFloat, "VECTOR(INT, 20)")}, + {RawInput: `abc.def."ghi(FLOAT, VECTOR(INT, 20)):NUMBER"`, ExpectedIdentifierStructure: NewSchemaObjectIdentifierWithArguments(`abc`, `def`, `ghi`, DataTypeFloat, "VECTOR(INT, 20)")}, + {RawInput: `abc.def."ghi(ab FLOAT, VECTOR VECTOR(INT, 20), FLOAT):NUMBER"`, ExpectedIdentifierStructure: NewSchemaObjectIdentifierWithArguments(`abc`, `def`, `ghi`, DataTypeFloat, "VECTOR(INT, 20)", DataTypeFloat)}, + } + + for _, testCase := range testCases { + t.Run(fmt.Sprintf("processing %s", testCase.ExpectedIdentifierStructure.FullyQualifiedName()), func(t *testing.T) { + id, err := ParseSchemaObjectIdentifierWithArgumentsAndReturnType(testCase.RawInput) + + if testCase.Error != "" { + assert.ErrorContains(t, err, testCase.Error) + } else { + assert.NoError(t, err) + assert.Equal(t, testCase.ExpectedIdentifierStructure.FullyQualifiedName(), id.FullyQualifiedName()) + } + }) + } +} diff --git a/pkg/sdk/object_types.go b/pkg/sdk/object_types.go index e22da6e9b2..d54b3337fd 100644 --- a/pkg/sdk/object_types.go +++ b/pkg/sdk/object_types.go @@ -81,6 +81,10 @@ func (o ObjectType) String() string { return string(o) } +func (o ObjectType) IsWithArguments() bool { + return slices.Contains([]ObjectType{ObjectTypeExternalFunction, ObjectTypeFunction, ObjectTypeProcedure}, o) +} + func objectTypeSingularToPluralMap() map[ObjectType]PluralObjectType { return map[ObjectType]PluralObjectType{ ObjectTypeAccount: PluralObjectTypeAccounts, diff --git a/pkg/sdk/testint/grants_integration_test.go b/pkg/sdk/testint/grants_integration_test.go index 112ad3b7ec..d009e32142 100644 --- a/pkg/sdk/testint/grants_integration_test.go +++ b/pkg/sdk/testint/grants_integration_test.go @@ -845,38 +845,39 @@ func TestInt_GrantPrivilegeToShare(t *testing.T) { shareTest, shareCleanup := testClientHelper().Share.CreateShare(t) t.Cleanup(shareCleanup) - assertGrant := func(t *testing.T, grants []sdk.Grant, onId sdk.ObjectIdentifier, privilege sdk.ObjectPrivilege) { + assertGrant := func(t *testing.T, grants []sdk.Grant, onId sdk.ObjectIdentifier, privilege sdk.ObjectPrivilege, grantedOn sdk.ObjectType, granteeName sdk.ObjectIdentifier, shareName string) { t.Helper() - var shareGrant *sdk.Grant - for i, grant := range grants { - if grant.GranteeName.Name() == shareTest.ID().Name() && grant.Privilege == string(privilege) { - shareGrant = &grants[i] - break - } - } - assert.NotNil(t, shareGrant) - assert.Equal(t, sdk.ObjectTypeTable, shareGrant.GrantedOn) - assert.Equal(t, sdk.ObjectTypeShare, shareGrant.GrantedTo) - assert.Equal(t, onId.FullyQualifiedName(), shareGrant.Name.FullyQualifiedName()) + actualGrant, err := collections.FindOne(grants, func(grant sdk.Grant) bool { + return grant.GranteeName.Name() == shareName && grant.Privilege == string(privilege) + }) + require.NoError(t, err) + assert.Equal(t, grantedOn, actualGrant.GrantedOn) + assert.Equal(t, sdk.ObjectTypeShare, actualGrant.GrantedTo) + assert.Equal(t, granteeName.FullyQualifiedName(), actualGrant.GranteeName.FullyQualifiedName()) + assert.Equal(t, onId.FullyQualifiedName(), actualGrant.Name.FullyQualifiedName()) } - t.Run("with options", func(t *testing.T) { + grantShareOnDatabase := func(t *testing.T, share *sdk.Share) { + t.Helper() err := client.Grants.GrantPrivilegeToShare(ctx, []sdk.ObjectPrivilege{sdk.ObjectPrivilegeUsage}, &sdk.ShareGrantOn{ Database: testDb(t).ID(), - }, shareTest.ID()) + }, share.ID()) require.NoError(t, err) t.Cleanup(func() { err := client.Grants.RevokePrivilegeFromShare(ctx, []sdk.ObjectPrivilege{sdk.ObjectPrivilegeUsage}, &sdk.ShareGrantOn{ Database: testDb(t).ID(), - }, shareTest.ID()) + }, share.ID()) assert.NoError(t, err) }) + } + t.Run("with options", func(t *testing.T) { + grantShareOnDatabase(t, shareTest) table, tableCleanup := testClientHelper().Table.CreateTable(t) t.Cleanup(tableCleanup) - err = client.Grants.GrantPrivilegeToShare(ctx, []sdk.ObjectPrivilege{sdk.ObjectPrivilegeSelect}, &sdk.ShareGrantOn{ + err := client.Grants.GrantPrivilegeToShare(ctx, []sdk.ObjectPrivilege{sdk.ObjectPrivilegeSelect}, &sdk.ShareGrantOn{ Table: &sdk.OnTable{ AllInSchema: testSchema(t).ID(), }, @@ -892,7 +893,34 @@ func TestInt_GrantPrivilegeToShare(t *testing.T) { }, }) require.NoError(t, err) - assertGrant(t, grants, table.ID(), sdk.ObjectPrivilegeSelect) + assertGrant(t, grants, table.ID(), sdk.ObjectPrivilegeSelect, sdk.ObjectTypeTable, shareTest.ID(), shareTest.ID().Name()) + + _, err = client.Grants.Show(ctx, &sdk.ShowGrantOptions{ + To: &sdk.ShowGrantsTo{ + Share: &sdk.ShowGrantsToShare{ + Name: shareTest.ID(), + }, + }, + }) + require.NoError(t, err) + + function := testClientHelper().Function.CreateSecure(t) + + err = client.Grants.GrantPrivilegeToShare(ctx, []sdk.ObjectPrivilege{sdk.ObjectPrivilegeUsage}, &sdk.ShareGrantOn{ + Function: function.ID(), + }, shareTest.ID()) + require.NoError(t, err) + + grants, err = client.Grants.Show(ctx, &sdk.ShowGrantOptions{ + On: &sdk.ShowGrantsOn{ + Object: &sdk.Object{ + ObjectType: sdk.ObjectTypeFunction, + Name: function.ID(), + }, + }, + }) + require.NoError(t, err) + assertGrant(t, grants, function.ID(), sdk.ObjectPrivilegeUsage, sdk.ObjectTypeFunction, shareTest.ID(), shareTest.ID().Name()) _, err = client.Grants.Show(ctx, &sdk.ShowGrantOptions{ To: &sdk.ShowGrantsTo{ @@ -925,6 +953,41 @@ func TestInt_GrantPrivilegeToShare(t *testing.T) { }, shareTest.ID()) require.NoError(t, err) }) + + t.Run("with a name containing dots", func(t *testing.T) { + shareTest, shareCleanup := testClientHelper().Share.CreateShareWithIdentifier(t, testClientHelper().Ids.RandomAccountObjectIdentifierContaining(".foo.bar")) + t.Cleanup(shareCleanup) + grantShareOnDatabase(t, shareTest) + table, tableCleanup := testClientHelper().Table.CreateTable(t) + t.Cleanup(tableCleanup) + + err := client.Grants.GrantPrivilegeToShare(ctx, []sdk.ObjectPrivilege{sdk.ObjectPrivilegeSelect}, &sdk.ShareGrantOn{ + Table: &sdk.OnTable{ + AllInSchema: testSchema(t).ID(), + }, + }, shareTest.ID()) + require.NoError(t, err) + + grants, err := client.Grants.Show(ctx, &sdk.ShowGrantOptions{ + On: &sdk.ShowGrantsOn{ + Object: &sdk.Object{ + ObjectType: sdk.ObjectTypeTable, + Name: table.ID(), + }, + }, + }) + require.NoError(t, err) + assertGrant(t, grants, table.ID(), sdk.ObjectPrivilegeSelect, sdk.ObjectTypeTable, shareTest.ID(), shareTest.ID().Name()) + + _, err = client.Grants.Show(ctx, &sdk.ShowGrantOptions{ + To: &sdk.ShowGrantsTo{ + Share: &sdk.ShowGrantsToShare{ + Name: shareTest.ID(), + }, + }, + }) + require.NoError(t, err) + }) } func TestInt_RevokePrivilegeToShare(t *testing.T) { diff --git a/v1-preparations/ESSENTIAL_GA_OBJECTS.MD b/v1-preparations/ESSENTIAL_GA_OBJECTS.MD index b2536e25f9..ac0cb168e4 100644 --- a/v1-preparations/ESSENTIAL_GA_OBJECTS.MD +++ b/v1-preparations/ESSENTIAL_GA_OBJECTS.MD @@ -28,12 +28,12 @@ newer provider versions. We will address these while working on the given object | PROCEDURE | ❌ | [#2735](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/2735), [#2623](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/2623), [#2257](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/2257), [#2146](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/2146), [#1855](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/1855), [#1695](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/1695), [#1640](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/1640), [#1195](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/1195), [#1189](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/1189), [#1178](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/1178), [#1050](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/1050) | | ROW ACCESS POLICY | ❌ | [#2053](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/2053), [#1600](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/1600), [#1151](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/1151) | | SCHEMA | 🚀 | [#2826](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/2826), [#2211](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/2211), [#1243](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/1243), [#506](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/506) | -| STAGE | ❌ | [#2818](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/2818), [#2505](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/2505), [#1911](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/1911), [#1903](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/1903), [#1795](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/1795), [#1705](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/1705), [#1544](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/1544), [#1491](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/1491), [#1087](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/1087), [#265](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/265) | +| STAGE | ❌ | [#2995](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/2995), [#2818](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/2818), [#2505](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/2505), [#1911](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/1911), [#1903](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/1903), [#1795](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/1795), [#1705](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/1705), [#1544](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/1544), [#1491](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/1491), [#1087](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/1087), [#265](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/265) | | STREAM | ❌ | [#2975](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/2975), [#2413](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/2413), [#2201](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/2201), [#1150](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/1150) | | STREAMLIT | 🚀 | [#1933](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/1933) | -| TABLE | ❌ | [#2844](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/2844), [#2839](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/2839), [#2735](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/2735), [#2733](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/2733), [#2683](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/2683), [#2676](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/2676), [#2674](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/2674), [#2629](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/2629), [#2418](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/2418), [#2415](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/2415), [#2406](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/2406), [#2236](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/2236), [#2035](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/2035), [#1823](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/1823), [#1799](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/1799), [#1764](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/1764), [#1600](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/1600), [#1387](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/1387), [#1272](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/1272), [#1271](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/1271), [#1248](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/1248), [#1241](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/1241), [#1146](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/1146), [#1032](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/1032), [#420](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/420) | +| TABLE | ❌ | [#2997](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/2997), [#2844](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/2844), [#2839](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/2839), [#2735](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/2735), [#2733](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/2733), [#2683](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/2683), [#2676](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/2676), [#2674](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/2674), [#2629](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/2629), [#2418](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/2418), [#2415](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/2415), [#2406](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/2406), [#2236](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/2236), [#2035](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/2035), [#1823](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/1823), [#1799](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/1799), [#1764](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/1764), [#1600](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/1600), [#1387](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/1387), [#1272](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/1272), [#1271](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/1271), [#1248](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/1248), [#1241](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/1241), [#1146](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/1146), [#1032](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/1032), [#420](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/420) | | TAG | ❌ | [#2943](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/2902), [#2598](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/2598), [#1910](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/1910), [#1909](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/1909), [#1862](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/1862), [#1806](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/1806), [#1657](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/1657), [#1496](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/1496), [#1443](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/1443), [#1394](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/1394), [#1372](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/1372), [#1074](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/1074) | | TASK | ❌ | [#1419](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/1419), [#1250](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/1250), [#1194](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/1194), [#1088](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/1088) | -| VIEW | 👨‍💻 | [#2430](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/2430), [#2085](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/2085), [#2055](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/2055), [#2031](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/2031), [#1526](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/1526), [#1253](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/1253), [#1049](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/1049) | +| VIEW | 👨‍💻 | [#3000](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/3000), [#2430](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/2430), [#2085](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/2085), [#2055](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/2055), [#2031](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/2031), [#1526](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/1526), [#1253](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/1253), [#1049](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/1049) | | snowflake_unsafe_execute | ❌ | [#2934](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/2934) |