From 6d62617de00121dec047922f5bdf8fae143ce1a2 Mon Sep 17 00:00:00 2001 From: Scott Winkler Date: Thu, 21 Dec 2023 08:10:32 -0800 Subject: [PATCH] add grant_database_role resource --- .../snowflake_grant_database_role/import.sh | 2 + .../snowflake_grant_database_role/resource.tf | 45 ++++ pkg/provider/provider.go | 1 + pkg/resources/grant_database_role.go | 228 ++++++++++++++++++ .../grant_database_role_acceptance_test.go | 176 ++++++++++++++ .../account_role/test.tf | 25 ++ .../database_role/test.tf | 26 ++ .../TestAcc_GrantDatabaseRole/share/test.tf | 27 +++ pkg/sdk/grants.go | 7 +- pkg/sdk/grants_test.go | 10 + pkg/validation/validation.go | 10 +- 11 files changed, 549 insertions(+), 8 deletions(-) create mode 100644 examples/resources/snowflake_grant_database_role/import.sh create mode 100644 examples/resources/snowflake_grant_database_role/resource.tf create mode 100644 pkg/resources/grant_database_role.go create mode 100644 pkg/resources/grant_database_role_acceptance_test.go create mode 100644 pkg/resources/testdata/TestAcc_GrantDatabaseRole/account_role/test.tf create mode 100644 pkg/resources/testdata/TestAcc_GrantDatabaseRole/database_role/test.tf create mode 100644 pkg/resources/testdata/TestAcc_GrantDatabaseRole/share/test.tf diff --git a/examples/resources/snowflake_grant_database_role/import.sh b/examples/resources/snowflake_grant_database_role/import.sh new file mode 100644 index 0000000000..878d3c901a --- /dev/null +++ b/examples/resources/snowflake_grant_database_role/import.sh @@ -0,0 +1,2 @@ +# format is database_role_name (string) | object_type (ROLE|DATABASE ROLE|SHARE) | grantee_name (string) +terraform import "\"ABC\".\"test_db_role\"|ROLE|\"test_parent_role\"" diff --git a/examples/resources/snowflake_grant_database_role/resource.tf b/examples/resources/snowflake_grant_database_role/resource.tf new file mode 100644 index 0000000000..f3f331cf05 --- /dev/null +++ b/examples/resources/snowflake_grant_database_role/resource.tf @@ -0,0 +1,45 @@ +################################## +### grant database role to account role +################################## + +resource "snowflake_database_role" "database_role" { + database = var.database + name = var.database_role_name +} + +resource "snowflake_role" "parent_role" { + name = var.parent_role_name +} + +resource "snowflake_grant_database_role" "g" { + database_role_name = "\"${var.database}\".\"${snowflake_database_role.database_role.name}\"" + parent_role_name = snowflake_role.parent_role.name +} + +################################## +### grant database role to database role +################################## + +resource "snowflake_database_role" "database_role" { + database = var.database + name = var.database_role_name +} + +resource "snowflake_database_role" "parent_database_role" { + database = var.database + name = var.parent_database_role_name +} + +resource "snowflake_grant_database_role" "g" { + database_role_name = "\"${var.database}\".\"${snowflake_database_role.database_role.name}\"" + parent_database_role_name = "\"${var.database}\".\"${snowflake_database_role.parent_database_role.name}\"" +} + +################################## +### grant database role to share +################################## + +resource "snowflake_grant_database_role" "g" { + database_role_name = "\"${var.database}\".\"${snowflake_database_role.database_role.name}\"" + share_name = snowflake_share.share.name +} diff --git a/pkg/provider/provider.go b/pkg/provider/provider.go index f51cb5e706..994c89fbae 100644 --- a/pkg/provider/provider.go +++ b/pkg/provider/provider.go @@ -443,6 +443,7 @@ func getResources() map[string]*schema.Resource { "snowflake_failover_group": resources.FailoverGroup(), "snowflake_file_format": resources.FileFormat(), "snowflake_function": resources.Function(), + "snowflake_grant_database_role": resources.GrantDatabaseRole(), "snowflake_grant_privileges_to_role": resources.GrantPrivilegesToRole(), "snowflake_managed_account": resources.ManagedAccount(), "snowflake_masking_policy": resources.MaskingPolicy(), diff --git a/pkg/resources/grant_database_role.go b/pkg/resources/grant_database_role.go new file mode 100644 index 0000000000..98e5eedc46 --- /dev/null +++ b/pkg/resources/grant_database_role.go @@ -0,0 +1,228 @@ +package resources + +import ( + "context" + "database/sql" + "fmt" + "log" + "strings" + + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/helpers" + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +var grantDatabaseRoleSchema = map[string]*schema.Schema{ + "database_role_name": { + Type: schema.TypeString, + Required: true, + Description: "The fully qualified name of the database role which will be granted to share or parent role.", + ForceNew: true, + ValidateDiagFunc: IsValidIdentifier[sdk.DatabaseObjectIdentifier](), + }, + "parent_role_name": { + Type: schema.TypeString, + Optional: true, + Description: "The fully qualified name of the parent account role which will create a parent-child relationship between the roles.", + ForceNew: true, + ValidateDiagFunc: IsValidIdentifier[sdk.AccountObjectIdentifier](), + ExactlyOneOf: []string{ + "parent_role_name", + "parent_database_role_name", + "share_name", + }, + }, + "parent_database_role_name": { + Type: schema.TypeString, + Optional: true, + Description: "The fully qualified name of the parent database role which will create a parent-child relationship between the roles. If the identifier is not fully qualified (in the form of .≤database_role_name>), the command looks for the database role in the current database for the session.", + ForceNew: true, + ValidateDiagFunc: IsValidIdentifier[sdk.DatabaseObjectIdentifier](), + ExactlyOneOf: []string{ + "parent_role_name", + "parent_database_role_name", + "share_name", + }, + }, + "share_name": { + Type: schema.TypeString, + Optional: true, + Description: "The fully qualified name of the share on which privileges will be granted.", + ForceNew: true, + ValidateDiagFunc: IsValidIdentifier[sdk.AccountObjectIdentifier](), + ExactlyOneOf: []string{ + "parent_role_name", + "parent_database_role_name", + "share_name", + }, + }, +} + +// DynamicTable returns a pointer to the resource representing a dynamic table. +func GrantDatabaseRole() *schema.Resource { + return &schema.Resource{ + Create: CreateGrantDatabaseRole, + Read: ReadGrantDatabaseRole, + Delete: DeleteGrantDatabaseRole, + Schema: grantDatabaseRoleSchema, + Importer: &schema.ResourceImporter{ + StateContext: func(ctx context.Context, d *schema.ResourceData, m interface{}) ([]*schema.ResourceData, error) { + parts := strings.Split(d.Id(), helpers.IDDelimiter) + if len(parts) != 3 { + return nil, fmt.Errorf("invalid ID specified: %v, expected ||", d.Id()) + } + if err := d.Set("database_role_name", parts[0]); err != nil { + return nil, err + } + switch parts[1] { + case "ROLE": + if err := d.Set("parent_role_name", parts[2]); err != nil { + return nil, err + } + case "DATABASE ROLE": + if err := d.Set("parent_database_role_name", parts[2]); err != nil { + return nil, err + } + case "SHARE": + if err := d.Set("share_name", parts[2]); err != nil { + return nil, err + } + default: + return nil, fmt.Errorf("invalid object type specified: %v, expected ROLE, DATABASE ROLE, or SHARE", parts[1]) + } + + return []*schema.ResourceData{d}, nil + }, + }, + } +} + +// CreateGrantDatabaseRole implements schema.CreateFunc. +func CreateGrantDatabaseRole(d *schema.ResourceData, meta interface{}) error { + db := meta.(*sql.DB) + client := sdk.NewClientFromDB(db) + ctx := context.Background() + databaseRoleName := d.Get("database_role_name").(string) + databaseRoleIdentifier := sdk.NewDatabaseObjectIdentifierFromFullyQualifiedName(databaseRoleName) + // format of snowflakeResourceID is || + var snowflakeResourceID string + if parentRoleName, ok := d.GetOk("parent_role_name"); ok && parentRoleName.(string) != "" { + parentRoleIdentifier := sdk.NewAccountObjectIdentifierFromFullyQualifiedName(parentRoleName.(string)) + snowflakeResourceID = helpers.EncodeSnowflakeID(databaseRoleIdentifier.FullyQualifiedName(), sdk.ObjectTypeRole.String(), parentRoleIdentifier.FullyQualifiedName()) + req := sdk.NewGrantDatabaseRoleRequest(databaseRoleIdentifier).WithAccountRole(parentRoleIdentifier) + if err := client.DatabaseRoles.Grant(ctx, req); err != nil { + return err + } + } else if parentDatabaseRoleName, ok := d.GetOk("parent_database_role_name"); ok && parentDatabaseRoleName.(string) != "" { + parentRoleIdentifier := sdk.NewDatabaseObjectIdentifierFromFullyQualifiedName(parentDatabaseRoleName.(string)) + snowflakeResourceID = helpers.EncodeSnowflakeID(databaseRoleIdentifier.FullyQualifiedName(), sdk.ObjectTypeDatabaseRole.String(), parentRoleIdentifier.FullyQualifiedName()) + req := sdk.NewGrantDatabaseRoleRequest(databaseRoleIdentifier).WithDatabaseRole(parentRoleIdentifier) + if err := client.DatabaseRoles.Grant(ctx, req); err != nil { + return err + } + } else if shareName, ok := d.GetOk("share_name"); ok && shareName.(string) != "" { + shareIdentifier := sdk.NewAccountObjectIdentifierFromFullyQualifiedName(shareName.(string)) + snowflakeResourceID = helpers.EncodeSnowflakeID(databaseRoleIdentifier.FullyQualifiedName(), sdk.ObjectTypeShare.String(), shareIdentifier.FullyQualifiedName()) + req := sdk.NewGrantDatabaseRoleToShareRequest(databaseRoleIdentifier, shareIdentifier) + if err := client.DatabaseRoles.GrantToShare(ctx, req); err != nil { + return err + } + } + d.SetId(snowflakeResourceID) + return ReadGrantDatabaseRole(d, meta) +} + +// ReadGrantDatabaseRole implements schema.ReadFunc. +func ReadGrantDatabaseRole(d *schema.ResourceData, meta interface{}) error { + db := meta.(*sql.DB) + client := sdk.NewClientFromDB(db) + parts := strings.Split(d.Id(), helpers.IDDelimiter) + databaseRoleName := parts[0] + databaseRoleIdentifier := sdk.NewDatabaseObjectIdentifierFromFullyQualifiedName(databaseRoleName) + objectType := parts[1] + targetIdentifier := parts[2] + ctx := context.Background() + grants, err := client.Grants.Show(ctx, &sdk.ShowGrantOptions{ + Of: &sdk.ShowGrantsOf{ + DatabaseRole: databaseRoleIdentifier, + }, + }) + if err != nil { + log.Printf("[DEBUG] database role (%s) not found", databaseRoleIdentifier.FullyQualifiedName()) + d.SetId("") + return nil + } + + var found bool + for _, grant := range grants { + if grant.GrantedTo == sdk.ObjectType(objectType) { + if grant.GrantedTo == sdk.ObjectTypeRole || grant.GrantedTo == sdk.ObjectTypeShare { + if grant.GranteeName.FullyQualifiedName() == targetIdentifier { + found = true + break + } + } else { + /* + note that grantee_name is not saved as a valid identifier in the + SHOW GRANTS OF DATABASE ROLE command + for example, "ABC"."test_parent_role" is saved as ABC."test_parent_role" + or "ABC"."test_parent_role" is saved as ABC.test_parent_role + and our internal mapper thereby fails to parse it correctly, returning "ABC."test_parent_role" + so this funny string replacement is needed to make it work + */ + s := grant.GranteeName.FullyQualifiedName() + if !strings.Contains(s, "\"") { + parts := strings.Split(s, ".") + s = sdk.NewDatabaseObjectIdentifier(parts[0], parts[1]).FullyQualifiedName() + } else { + parts := strings.Split(s, "\".\"") + if len(parts) < 2 { + parts = strings.Split(s, "\".") + if len(parts) < 2 { + parts = strings.Split(s, ".\"") + } + } + s = sdk.NewDatabaseObjectIdentifier(parts[0], parts[1]).FullyQualifiedName() + } + if s == targetIdentifier { + found = true + break + } + } + } + } + if !found { + log.Printf("[DEBUG] database role grant (%s) not found", d.Id()) + d.SetId("") + } + + return nil +} + +// DeleteGrantDatabaseRole implements schema.DeleteFunc. +func DeleteGrantDatabaseRole(d *schema.ResourceData, meta interface{}) error { + db := meta.(*sql.DB) + client := sdk.NewClientFromDB(db) + + parts := strings.Split(d.Id(), "|") + id := sdk.NewDatabaseObjectIdentifierFromFullyQualifiedName(parts[0]) + objectType := parts[1] + granteeName := parts[2] + ctx := context.Background() + switch objectType { + case "ROLE": + if err := client.DatabaseRoles.Revoke(ctx, sdk.NewRevokeDatabaseRoleRequest(id).WithAccountRole(sdk.NewAccountObjectIdentifierFromFullyQualifiedName(granteeName))); err != nil { + return err + } + case "DATABASE ROLE": + if err := client.DatabaseRoles.Revoke(ctx, sdk.NewRevokeDatabaseRoleRequest(id).WithDatabaseRole(sdk.NewDatabaseObjectIdentifierFromFullyQualifiedName(granteeName))); err != nil { + return err + } + case "SHARE": + if err := client.DatabaseRoles.RevokeFromShare(ctx, sdk.NewRevokeDatabaseRoleFromShareRequest(id, sdk.NewAccountObjectIdentifierFromFullyQualifiedName(granteeName))); err != nil { + return err + } + } + d.SetId("") + return nil +} diff --git a/pkg/resources/grant_database_role_acceptance_test.go b/pkg/resources/grant_database_role_acceptance_test.go new file mode 100644 index 0000000000..fcacbdf76b --- /dev/null +++ b/pkg/resources/grant_database_role_acceptance_test.go @@ -0,0 +1,176 @@ +package resources_test + +import ( + "context" + "database/sql" + "fmt" + "strings" + "testing" + + acc "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/acceptance" + + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk" + "github.com/hashicorp/terraform-plugin-testing/config" + "github.com/hashicorp/terraform-plugin-testing/helper/acctest" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/terraform" + "github.com/hashicorp/terraform-plugin-testing/tfversion" +) + +func TestAcc_GrantDataseRole_databaseRole(t *testing.T) { + databaseRoleName := strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha)) + parentDatabaseRoleName := strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha)) + resourceName := "snowflake_grant_database_role.g" + m := func() map[string]config.Variable { + return map[string]config.Variable{ + "database": config.StringVariable(acc.TestDatabaseName), + "database_role_name": config.StringVariable(databaseRoleName), + "parent_database_role_name": config.StringVariable(parentDatabaseRoleName), + } + } + resource.Test(t, resource.TestCase{ + ProtoV6ProviderFactories: acc.TestAccProtoV6ProviderFactories, + PreCheck: func() { acc.TestAccPreCheck(t) }, + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.RequireAbove(tfversion.Version1_5_0), + }, + CheckDestroy: testAccCheckDynamicTableDestroy, + Steps: []resource.TestStep{ + { + ConfigDirectory: config.StaticDirectory("testdata/TestAcc_GrantDatabaseRole/database_role"), + ConfigVariables: m(), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "database_role_name", fmt.Sprintf(`"%v"."%v"`, acc.TestDatabaseName, databaseRoleName)), + resource.TestCheckResourceAttr(resourceName, "parent_database_role_name", fmt.Sprintf(`"%v"."%v"`, acc.TestDatabaseName, parentDatabaseRoleName)), + resource.TestCheckResourceAttr(resourceName, "id", fmt.Sprintf(`"%v"."%v"|DATABASE ROLE|"%v"."%v"`, acc.TestDatabaseName, databaseRoleName, acc.TestDatabaseName, parentDatabaseRoleName)), + ), + }, + // test import + { + ConfigDirectory: config.StaticDirectory("testdata/TestAcc_GrantDatabaseRole/database_role"), + ConfigVariables: m(), + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAcc_GrantDataseRole_accountRole(t *testing.T) { + databaseRoleName := strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha)) + parentRoleName := strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha)) + resourceName := "snowflake_grant_database_role.g" + m := func() map[string]config.Variable { + return map[string]config.Variable{ + "database": config.StringVariable(acc.TestDatabaseName), + "database_role_name": config.StringVariable(databaseRoleName), + "parent_role_name": config.StringVariable(parentRoleName), + } + } + resource.Test(t, resource.TestCase{ + ProtoV6ProviderFactories: acc.TestAccProtoV6ProviderFactories, + PreCheck: func() { acc.TestAccPreCheck(t) }, + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.RequireAbove(tfversion.Version1_5_0), + }, + CheckDestroy: testAccCheckDynamicTableDestroy, + Steps: []resource.TestStep{ + { + ConfigDirectory: config.StaticDirectory("testdata/TestAcc_GrantDatabaseRole/account_role"), + ConfigVariables: m(), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "database_role_name", fmt.Sprintf(`"%v"."%v"`, acc.TestDatabaseName, databaseRoleName)), + resource.TestCheckResourceAttr(resourceName, "parent_role_name", fmt.Sprintf("%v", parentRoleName)), + resource.TestCheckResourceAttr(resourceName, "id", fmt.Sprintf(`"%v"."%v"|ROLE|"%v"`, acc.TestDatabaseName, databaseRoleName, parentRoleName)), + ), + }, + // test import + { + ConfigDirectory: config.StaticDirectory("testdata/TestAcc_GrantDatabaseRole/account_role"), + ConfigVariables: m(), + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{ + "parent_role_name", + }, + }, + }, + }) +} + +/* +todo: once snowflake_grant_privileges_to_share is implemented. Cannot test this without having 'GRANT USAGE ON DATABASE TO SHARE ', +func TestAcc_GrantDataseRole_share(t *testing.T) { + databaseRoleName := strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha)) + shareName := strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha)) + resourceName := "snowflake_grant_database_role.g" + m := func() map[string]config.Variable { + return map[string]config.Variable{ + "database": config.StringVariable(acc.TestDatabaseName), + "database_role_name": config.StringVariable(databaseRoleName), + "share_name": config.StringVariable(shareName), + } + } + resource.Test(t, resource.TestCase{ + ProtoV6ProviderFactories: acc.TestAccProtoV6ProviderFactories, + PreCheck: func() { acc.TestAccPreCheck(t) }, + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.RequireAbove(tfversion.Version1_5_0), + }, + CheckDestroy: testAccCheckDynamicTableDestroy, + Steps: []resource.TestStep{ + { + ConfigDirectory: config.StaticDirectory("testdata/TestAcc_GrantDatabaseRole/share"), + ConfigVariables: m(), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "database_role_name", databaseRoleName), + resource.TestCheckResourceAttr(resourceName, "share_name", shareName), + resource.TestCheckResourceAttr(resourceName, "id", fmt.Sprintf(`%v|%v|%v`, databaseRoleName, "SHARE", shareName)), + ), + }, + // test import + { + ConfigDirectory: config.StaticDirectory("testdata/TestAcc_GrantDatabaseRole/share"), + ConfigVariables: m(), + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} +*/ + +func testAccCheckGrantDataseRoleDestroy(s *terraform.State) error { + db := acc.TestAccProvider.Meta().(*sql.DB) + client := sdk.NewClientFromDB(db) + for _, rs := range s.RootModule().Resources { + if rs.Type != "snowflake_grant_database_role" { + continue + } + ctx := context.Background() + id := rs.Primary.ID + ids := strings.Split(id, "|") + databaseRoleName := ids[0] + objectType := ids[1] + parentRoleName := ids[2] + grants, err := client.Grants.Show(ctx, &sdk.ShowGrantOptions{ + Of: &sdk.ShowGrantsOf{ + DatabaseRole: sdk.NewDatabaseObjectIdentifierFromFullyQualifiedName(databaseRoleName), + }, + }) + if err != nil { + return fmt.Errorf("database role (%s) not found", id) + } + for _, grant := range grants { + if grant.GrantedTo == sdk.ObjectType(objectType) { + if grant.GranteeName.FullyQualifiedName() == parentRoleName { + return fmt.Errorf("database role grant %v still exists", grant) + } + } + } + } + return nil +} diff --git a/pkg/resources/testdata/TestAcc_GrantDatabaseRole/account_role/test.tf b/pkg/resources/testdata/TestAcc_GrantDatabaseRole/account_role/test.tf new file mode 100644 index 0000000000..4ed7e1936b --- /dev/null +++ b/pkg/resources/testdata/TestAcc_GrantDatabaseRole/account_role/test.tf @@ -0,0 +1,25 @@ +variable "database_role_name" { + type = string +} + +variable "parent_role_name" { + type = string +} + +variable "database" { + type = string +} + +resource "snowflake_database_role" "database_role" { + database = var.database + name = var.database_role_name +} + +resource "snowflake_role" "parent_role" { + name = var.parent_role_name +} + +resource "snowflake_grant_database_role" "g" { + database_role_name = "\"${var.database}\".\"${snowflake_database_role.database_role.name}\"" + parent_role_name = snowflake_role.parent_role.name +} diff --git a/pkg/resources/testdata/TestAcc_GrantDatabaseRole/database_role/test.tf b/pkg/resources/testdata/TestAcc_GrantDatabaseRole/database_role/test.tf new file mode 100644 index 0000000000..e78bbd2af2 --- /dev/null +++ b/pkg/resources/testdata/TestAcc_GrantDatabaseRole/database_role/test.tf @@ -0,0 +1,26 @@ +variable "database_role_name" { + type = string +} + +variable "parent_database_role_name" { + type = string +} + +variable "database" { + type = string +} + +resource "snowflake_database_role" "database_role" { + database = var.database + name = var.database_role_name +} + +resource "snowflake_database_role" "parent_database_role" { + database = var.database + name = var.parent_database_role_name +} + +resource "snowflake_grant_database_role" "g" { + database_role_name = "\"${var.database}\".\"${snowflake_database_role.database_role.name}\"" + parent_database_role_name = "\"${var.database}\".\"${snowflake_database_role.parent_database_role.name}\"" +} diff --git a/pkg/resources/testdata/TestAcc_GrantDatabaseRole/share/test.tf b/pkg/resources/testdata/TestAcc_GrantDatabaseRole/share/test.tf new file mode 100644 index 0000000000..ef0003088a --- /dev/null +++ b/pkg/resources/testdata/TestAcc_GrantDatabaseRole/share/test.tf @@ -0,0 +1,27 @@ +variable "database_role_name" { + type = string +} + +variable "share_name" { + type = string +} + +variable "database" { + type = string +} + +resource "snowflake_database_role" "database_role" { + database = var.database + name = var.database_role_name +} + +resource "snowflake_share" "share" { + name = var.share_name +} + +// todo: add grant_privileges_to_share resource + +resource "snowflake_grant_database_role" "g" { + database_role_name = snowflake_database_role.database_role.name + share_name = snowflake_share.name +} diff --git a/pkg/sdk/grants.go b/pkg/sdk/grants.go index f4dc79fcb4..ce8b77169f 100644 --- a/pkg/sdk/grants.go +++ b/pkg/sdk/grants.go @@ -186,8 +186,9 @@ type ShowGrantsTo struct { } type ShowGrantsOf struct { - Role AccountObjectIdentifier `ddl:"identifier" sql:"ROLE"` - Share AccountObjectIdentifier `ddl:"identifier" sql:"SHARE"` + Role AccountObjectIdentifier `ddl:"identifier" sql:"ROLE"` + DatabaseRole DatabaseObjectIdentifier `ddl:"identifier" sql:"DATABASE ROLE"` + Share AccountObjectIdentifier `ddl:"identifier" sql:"SHARE"` } type grantRow struct { @@ -211,7 +212,7 @@ type Grant struct { Name ObjectIdentifier GrantedTo ObjectType GrantTo ObjectType - GranteeName AccountObjectIdentifier + GranteeName ObjectIdentifier GrantOption bool GrantedBy AccountObjectIdentifier } diff --git a/pkg/sdk/grants_test.go b/pkg/sdk/grants_test.go index 927c954bcd..f7e0af5e2b 100644 --- a/pkg/sdk/grants_test.go +++ b/pkg/sdk/grants_test.go @@ -1009,6 +1009,16 @@ func TestGrantShow(t *testing.T) { assertOptsValidAndSQLEquals(t, opts, "SHOW GRANTS OF ROLE %s", roleID.FullyQualifiedName()) }) + t.Run("of database role", func(t *testing.T) { + roleID := RandomDatabaseObjectIdentifier() + opts := &ShowGrantOptions{ + Of: &ShowGrantsOf{ + DatabaseRole: roleID, + }, + } + assertOptsValidAndSQLEquals(t, opts, "SHOW GRANTS OF DATABASE ROLE %s", roleID.FullyQualifiedName()) + }) + t.Run("of share", func(t *testing.T) { shareID := RandomAccountObjectIdentifier() opts := &ShowGrantOptions{ diff --git a/pkg/validation/validation.go b/pkg/validation/validation.go index 1ea65cb8d6..49c61742d4 100644 --- a/pkg/validation/validation.go +++ b/pkg/validation/validation.go @@ -158,17 +158,17 @@ func ValidateFullyQualifiedObjectID(i interface{}, _ string) (s []string, errors v, _ := i.(string) if strings.Contains(v, ".") { //nolint:gocritic // todo: please fix this tagArray := strings.Split(v, ".") - if len(tagArray) != 3 { - errors = append(errors, fmt.Errorf("%v, is not a valid id. If using period delimiter, three parts must be specified ..", v)) + if len(tagArray) > 3 { + errors = append(errors, fmt.Errorf("%v, is not a valid id. If using period delimiter, no morethan three parts must be specified ..", v)) } } else if strings.Contains(v, "|") { tagArray := strings.Split(v, "|") - if len(tagArray) != 3 { - errors = append(errors, fmt.Errorf("%v, is not a valid id. If using pipe delimiter, three parts must be specified ||", v)) + if len(tagArray) > 3 { + errors = append(errors, fmt.Errorf("%v, is not a valid id. If using pipe delimiter, nomorethan three parts must be specified ||", v)) } } else { errors = append(errors, fmt.Errorf("%v, is not a valid id. please use one of the following formats:"+ - "\n''.''.'' or ||", v)) + "\n\"\".\"\".\"\" or ''.''.'' or ||", v)) } return }