diff --git a/pkg/resources/grant_privileges_to_account_role.go b/pkg/resources/grant_privileges_to_account_role.go index 14c988c8db..6581a45839 100644 --- a/pkg/resources/grant_privileges_to_account_role.go +++ b/pkg/resources/grant_privileges_to_account_role.go @@ -448,6 +448,10 @@ func UpdateGrantPrivilegesToAccountRole(ctx context.Context, d *schema.ResourceD } logging.DebugLogger.Printf("[DEBUG] Parsed identifier to %s", id.String()) + if d.HasChange("with_grant_option") { + id.WithGrantOption = d.Get("with_grant_option").(bool) + } + // handle all_privileges -> privileges change (revoke all privileges) if d.HasChange("all_privileges") { _, allPrivileges := d.GetChange("all_privileges") @@ -512,20 +516,28 @@ func UpdateGrantPrivilegesToAccountRole(ctx context.Context, d *schema.ResourceD if len(privilegesToAdd) > 0 { logging.DebugLogger.Printf("[DEBUG] Granting privileges: %v", privilegesToAdd) - err = client.Grants.GrantPrivilegesToAccountRole( - ctx, - getAccountRolePrivileges( - false, - privilegesToAdd, - id.Kind == OnAccountAccountRoleGrantKind, - id.Kind == OnAccountObjectAccountRoleGrantKind, - id.Kind == OnSchemaAccountRoleGrantKind, - id.Kind == OnSchemaObjectAccountRoleGrantKind, - ), - grantOn, - id.RoleName, - new(sdk.GrantPrivilegesToAccountRoleOptions), + privilegesToGrant := getAccountRolePrivileges( + false, + privilegesToAdd, + id.Kind == OnAccountAccountRoleGrantKind, + id.Kind == OnAccountObjectAccountRoleGrantKind, + id.Kind == OnSchemaAccountRoleGrantKind, + id.Kind == OnSchemaObjectAccountRoleGrantKind, ) + + if !id.WithGrantOption { + if err = client.Grants.RevokePrivilegesFromAccountRole(ctx, privilegesToGrant, grantOn, id.RoleName, new(sdk.RevokePrivilegesFromAccountRoleOptions)); err != nil { + return diag.Diagnostics{ + diag.Diagnostic{ + Severity: diag.Error, + Summary: "Failed to revoke privileges to add", + Detail: fmt.Sprintf("Id: %s\nPrivileges to add: %v\nError: %s", d.Id(), privilegesToAdd, err.Error()), + }, + } + } + } + + err = client.Grants.GrantPrivilegesToAccountRole(ctx, privilegesToGrant, grantOn, id.RoleName, &sdk.GrantPrivilegesToAccountRoleOptions{WithGrantOption: sdk.Bool(id.WithGrantOption)}) if err != nil { return diag.Diagnostics{ diag.Diagnostic{ 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 61efcbc081..bda96ed19d 100644 --- a/pkg/resources/grant_privileges_to_account_role_acceptance_test.go +++ b/pkg/resources/grant_privileges_to_account_role_acceptance_test.go @@ -1190,6 +1190,174 @@ func TestAcc_GrantPrivilegesToAccountRole_MLPrivileges(t *testing.T) { }) } +// proves https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/2459 is fixed +func TestAcc_GrantPrivilegesToAccountRole_ChangeWithGrantOptionsOutsideOfTerraform_WithGrantOptions(t *testing.T) { + name := strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha)) + roleName := sdk.NewAccountObjectIdentifier(name).FullyQualifiedName() + tableName := strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha)) + + configVariables := config.Variables{ + "name": config.StringVariable(roleName), + "table_name": config.StringVariable(tableName), + "privileges": config.ListVariable( + config.StringVariable(sdk.SchemaObjectPrivilegeTruncate.String()), + ), + "database": config.StringVariable(acc.TestDatabaseName), + "schema": config.StringVariable(acc.TestSchemaName), + "with_grant_option": config.BoolVariable(true), + } + + resource.Test(t, resource.TestCase{ + ProtoV6ProviderFactories: acc.TestAccProtoV6ProviderFactories, + PreCheck: func() { acc.TestAccPreCheck(t) }, + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.RequireAbove(tfversion.Version1_5_0), + }, + CheckDestroy: testAccCheckAccountRolePrivilegesRevoked(name), + Steps: []resource.TestStep{ + { + PreConfig: func() { createAccountRoleOutsideTerraform(t, name) }, + ConfigPlanChecks: resource.ConfigPlanChecks{ + PostApplyPostRefresh: []plancheck.PlanCheck{ + plancheck.ExpectEmptyPlan(), + }, + }, + ConfigDirectory: acc.ConfigurationDirectory("TestAcc_GrantPrivilegesToAccountRole/OnSchemaObject_OnObject"), + ConfigVariables: configVariables, + }, + { + PreConfig: func() { + revokeAndGrantPrivilegesOnTableToAccountRole( + t, name, + sdk.NewSchemaObjectIdentifier(acc.TestDatabaseName, acc.TestSchemaName, tableName), + []sdk.SchemaObjectPrivilege{sdk.SchemaObjectPrivilegeTruncate}, + false, + ) + }, + ConfigPlanChecks: resource.ConfigPlanChecks{ + PostApplyPostRefresh: []plancheck.PlanCheck{ + plancheck.ExpectEmptyPlan(), + }, + }, + ConfigDirectory: acc.ConfigurationDirectory("TestAcc_GrantPrivilegesToAccountRole/OnSchemaObject_OnObject"), + ConfigVariables: configVariables, + }, + }, + }) +} + +// proves https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/2459 is fixed +func TestAcc_GrantPrivilegesToAccountRole_ChangeWithGrantOptionsOutsideOfTerraform_WithoutGrantOptions(t *testing.T) { + name := strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha)) + roleName := sdk.NewAccountObjectIdentifier(name).FullyQualifiedName() + tableName := strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha)) + + configVariables := config.Variables{ + "name": config.StringVariable(roleName), + "table_name": config.StringVariable(tableName), + "privileges": config.ListVariable( + config.StringVariable(sdk.SchemaObjectPrivilegeTruncate.String()), + ), + "database": config.StringVariable(acc.TestDatabaseName), + "schema": config.StringVariable(acc.TestSchemaName), + "with_grant_option": config.BoolVariable(false), + } + + resource.Test(t, resource.TestCase{ + ProtoV6ProviderFactories: acc.TestAccProtoV6ProviderFactories, + PreCheck: func() { acc.TestAccPreCheck(t) }, + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.RequireAbove(tfversion.Version1_5_0), + }, + CheckDestroy: testAccCheckAccountRolePrivilegesRevoked(name), + Steps: []resource.TestStep{ + { + PreConfig: func() { createAccountRoleOutsideTerraform(t, name) }, + ConfigPlanChecks: resource.ConfigPlanChecks{ + PostApplyPostRefresh: []plancheck.PlanCheck{ + plancheck.ExpectEmptyPlan(), + }, + }, + ConfigDirectory: acc.ConfigurationDirectory("TestAcc_GrantPrivilegesToAccountRole/OnSchemaObject_OnObject"), + ConfigVariables: configVariables, + }, + { + PreConfig: func() { + revokeAndGrantPrivilegesOnTableToAccountRole( + t, name, + sdk.NewSchemaObjectIdentifier(acc.TestDatabaseName, acc.TestSchemaName, tableName), + []sdk.SchemaObjectPrivilege{sdk.SchemaObjectPrivilegeTruncate}, + true, + ) + }, + ConfigPlanChecks: resource.ConfigPlanChecks{ + PostApplyPostRefresh: []plancheck.PlanCheck{ + plancheck.ExpectEmptyPlan(), + }, + }, + ConfigDirectory: acc.ConfigurationDirectory("TestAcc_GrantPrivilegesToAccountRole/OnSchemaObject_OnObject"), + ConfigVariables: configVariables, + }, + }, + }) +} + +func revokeAndGrantPrivilegesOnTableToAccountRole( + t *testing.T, + accountRoleName string, + tableName sdk.SchemaObjectIdentifier, + privileges []sdk.SchemaObjectPrivilege, + withGrantOption bool, +) { + t.Helper() + client, err := sdk.NewDefaultClient() + if err != nil { + t.Fatal(err) + } + ctx := context.Background() + err = client.Grants.RevokePrivilegesFromAccountRole( + ctx, + &sdk.AccountRoleGrantPrivileges{ + SchemaObjectPrivileges: privileges, + }, + &sdk.AccountRoleGrantOn{ + SchemaObject: &sdk.GrantOnSchemaObject{ + SchemaObject: &sdk.Object{ + ObjectType: sdk.ObjectTypeTable, + Name: tableName, + }, + }, + }, + sdk.NewAccountObjectIdentifier(accountRoleName), + new(sdk.RevokePrivilegesFromAccountRoleOptions), + ) + if err != nil { + t.Fatal(err) + } + + err = client.Grants.GrantPrivilegesToAccountRole( + ctx, + &sdk.AccountRoleGrantPrivileges{ + SchemaObjectPrivileges: privileges, + }, + &sdk.AccountRoleGrantOn{ + SchemaObject: &sdk.GrantOnSchemaObject{ + SchemaObject: &sdk.Object{ + ObjectType: sdk.ObjectTypeTable, + Name: tableName, + }, + }, + }, + sdk.NewAccountObjectIdentifier(accountRoleName), + &sdk.GrantPrivilegesToAccountRoleOptions{ + WithGrantOption: sdk.Bool(withGrantOption), + }, + ) + if err != nil { + t.Fatal(err) + } +} + func getSecondaryAccountName(t *testing.T) (string, error) { t.Helper() config, err := sdk.ProfileConfig(testprofiles.Secondary) diff --git a/pkg/resources/grant_privileges_to_database_role.go b/pkg/resources/grant_privileges_to_database_role.go index 9834ca7fc0..13f1de48db 100644 --- a/pkg/resources/grant_privileges_to_database_role.go +++ b/pkg/resources/grant_privileges_to_database_role.go @@ -379,6 +379,10 @@ func UpdateGrantPrivilegesToDatabaseRole(ctx context.Context, d *schema.Resource } } + if d.HasChange("with_grant_option") { + id.WithGrantOption = d.Get("with_grant_option").(bool) + } + // handle all_privileges -> privileges change (revoke all privileges) if d.HasChange("all_privileges") { _, allPrivileges := d.GetChange("all_privileges") @@ -439,19 +443,27 @@ func UpdateGrantPrivilegesToDatabaseRole(ctx context.Context, d *schema.Resource grantOn := getDatabaseRoleGrantOn(d) if len(privilegesToAdd) > 0 { - err = client.Grants.GrantPrivilegesToDatabaseRole( - ctx, - getDatabaseRolePrivileges( - false, - privilegesToAdd, - id.Kind == OnDatabaseDatabaseRoleGrantKind, - id.Kind == OnSchemaDatabaseRoleGrantKind, - id.Kind == OnSchemaObjectDatabaseRoleGrantKind, - ), - grantOn, - id.DatabaseRoleName, - new(sdk.GrantPrivilegesToDatabaseRoleOptions), + privilegesToGrant := getDatabaseRolePrivileges( + false, + privilegesToAdd, + id.Kind == OnDatabaseDatabaseRoleGrantKind, + id.Kind == OnSchemaDatabaseRoleGrantKind, + id.Kind == OnSchemaObjectDatabaseRoleGrantKind, ) + + if !id.WithGrantOption { + if err = client.Grants.RevokePrivilegesFromDatabaseRole(ctx, privilegesToGrant, grantOn, id.DatabaseRoleName, new(sdk.RevokePrivilegesFromDatabaseRoleOptions)); err != nil { + return diag.Diagnostics{ + diag.Diagnostic{ + Severity: diag.Error, + Summary: "Failed to revoke privileges to add", + Detail: fmt.Sprintf("Id: %s\nPrivileges to add: %v\nError: %s", d.Id(), privilegesToAdd, err.Error()), + }, + } + } + } + + err = client.Grants.GrantPrivilegesToDatabaseRole(ctx, privilegesToGrant, grantOn, id.DatabaseRoleName, &sdk.GrantPrivilegesToDatabaseRoleOptions{WithGrantOption: sdk.Bool(id.WithGrantOption)}) if err != nil { return diag.Diagnostics{ diag.Diagnostic{ 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 0de8ff0c69..bbb478b486 100644 --- a/pkg/resources/grant_privileges_to_database_role_acceptance_test.go +++ b/pkg/resources/grant_privileges_to_database_role_acceptance_test.go @@ -929,6 +929,108 @@ func TestAcc_GrantPrivilegesToDatabaseRole_MLPrivileges(t *testing.T) { }) } +// proves https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/2459 is fixed +func TestAcc_GrantPrivilegesToDatabaseRole_ChangeWithGrantOptionsOutsideOfTerraform_WithGrantOptions(t *testing.T) { + name := strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha)) + configVariables := config.Variables{ + "name": config.StringVariable(name), + "privileges": config.ListVariable( + config.StringVariable(string(sdk.AccountObjectPrivilegeCreateSchema)), + ), + "database": config.StringVariable(acc.TestDatabaseName), + "with_grant_option": config.BoolVariable(true), + } + + resource.Test(t, resource.TestCase{ + ProtoV6ProviderFactories: acc.TestAccProtoV6ProviderFactories, + PreCheck: func() { acc.TestAccPreCheck(t) }, + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.RequireAbove(tfversion.Version1_5_0), + }, + CheckDestroy: testAccCheckDatabaseRolePrivilegesRevoked, + Steps: []resource.TestStep{ + { + PreConfig: func() { createDatabaseRoleOutsideTerraform(t, name) }, + ConfigPlanChecks: resource.ConfigPlanChecks{ + PostApplyPostRefresh: []plancheck.PlanCheck{ + plancheck.ExpectEmptyPlan(), + }, + }, + ConfigDirectory: acc.ConfigurationDirectory("TestAcc_GrantPrivilegesToDatabaseRole/OnDatabase"), + ConfigVariables: configVariables, + }, + { + PreConfig: func() { + revokeAndGrantPrivilegesOnDatabaseToDatabaseRole( + t, sdk.NewDatabaseObjectIdentifier(acc.TestDatabaseName, name), + acc.TestDatabaseName, + []sdk.AccountObjectPrivilege{sdk.AccountObjectPrivilegeCreateSchema}, + false, + ) + }, + ConfigPlanChecks: resource.ConfigPlanChecks{ + PostApplyPostRefresh: []plancheck.PlanCheck{ + plancheck.ExpectEmptyPlan(), + }, + }, + ConfigDirectory: acc.ConfigurationDirectory("TestAcc_GrantPrivilegesToDatabaseRole/OnDatabase"), + ConfigVariables: configVariables, + }, + }, + }) +} + +// proves https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/2459 is fixed +func TestAcc_GrantPrivilegesToDatabaseRole_ChangeWithGrantOptionsOutsideOfTerraform_WithoutGrantOptions(t *testing.T) { + name := strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha)) + configVariables := config.Variables{ + "name": config.StringVariable(name), + "privileges": config.ListVariable( + config.StringVariable(string(sdk.AccountObjectPrivilegeCreateSchema)), + ), + "database": config.StringVariable(acc.TestDatabaseName), + "with_grant_option": config.BoolVariable(false), + } + + resource.Test(t, resource.TestCase{ + ProtoV6ProviderFactories: acc.TestAccProtoV6ProviderFactories, + PreCheck: func() { acc.TestAccPreCheck(t) }, + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.RequireAbove(tfversion.Version1_5_0), + }, + CheckDestroy: testAccCheckDatabaseRolePrivilegesRevoked, + Steps: []resource.TestStep{ + { + PreConfig: func() { createDatabaseRoleOutsideTerraform(t, name) }, + ConfigPlanChecks: resource.ConfigPlanChecks{ + PostApplyPostRefresh: []plancheck.PlanCheck{ + plancheck.ExpectEmptyPlan(), + }, + }, + ConfigDirectory: acc.ConfigurationDirectory("TestAcc_GrantPrivilegesToDatabaseRole/OnDatabase"), + ConfigVariables: configVariables, + }, + { + PreConfig: func() { + revokeAndGrantPrivilegesOnDatabaseToDatabaseRole( + t, sdk.NewDatabaseObjectIdentifier(acc.TestDatabaseName, name), + acc.TestDatabaseName, + []sdk.AccountObjectPrivilege{sdk.AccountObjectPrivilegeCreateSchema}, + true, + ) + }, + ConfigPlanChecks: resource.ConfigPlanChecks{ + PostApplyPostRefresh: []plancheck.PlanCheck{ + plancheck.ExpectEmptyPlan(), + }, + }, + ConfigDirectory: acc.ConfigurationDirectory("TestAcc_GrantPrivilegesToDatabaseRole/OnDatabase"), + ConfigVariables: configVariables, + }, + }, + }) +} + func createDatabaseRoleOutsideTerraform(t *testing.T, name string) { t.Helper() client, err := sdk.NewDefaultClient() @@ -992,3 +1094,49 @@ func testAccCheckDatabaseRolePrivilegesRevoked(s *terraform.State) error { } return nil } + +func revokeAndGrantPrivilegesOnDatabaseToDatabaseRole( + t *testing.T, + databaseRoleName sdk.DatabaseObjectIdentifier, + databaseName string, + privileges []sdk.AccountObjectPrivilege, + withGrantOption bool, +) { + t.Helper() + client, err := sdk.NewDefaultClient() + if err != nil { + t.Fatal(err) + } + ctx := context.Background() + err = client.Grants.RevokePrivilegesFromDatabaseRole( + ctx, + &sdk.DatabaseRoleGrantPrivileges{ + DatabasePrivileges: privileges, + }, + &sdk.DatabaseRoleGrantOn{ + Database: sdk.Pointer(sdk.NewAccountObjectIdentifier(databaseName)), + }, + databaseRoleName, + new(sdk.RevokePrivilegesFromDatabaseRoleOptions), + ) + if err != nil { + t.Fatal(err) + } + + err = client.Grants.GrantPrivilegesToDatabaseRole( + ctx, + &sdk.DatabaseRoleGrantPrivileges{ + DatabasePrivileges: privileges, + }, + &sdk.DatabaseRoleGrantOn{ + Database: sdk.Pointer(sdk.NewAccountObjectIdentifier(databaseName)), + }, + databaseRoleName, + &sdk.GrantPrivilegesToDatabaseRoleOptions{ + WithGrantOption: sdk.Bool(withGrantOption), + }, + ) + if err != nil { + t.Fatal(err) + } +}