Skip to content

Commit

Permalink
add grant role resource
Browse files Browse the repository at this point in the history
  • Loading branch information
sfc-gh-swinkler committed Dec 22, 2023
1 parent b1a740a commit eb6d1db
Show file tree
Hide file tree
Showing 7 changed files with 326 additions and 0 deletions.
2 changes: 2 additions & 0 deletions examples/resources/snowflake_grant_role/import.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# format is role_name (string) | grantee_object_type (ROLE|USER) | grantee_name (string)
terraform import "\"test_role\"|ROLE|\"test_parent_role\""
34 changes: 34 additions & 0 deletions examples/resources/snowflake_grant_role/resource.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
##################################
### grant account role to account role
##################################

resource "snowflake_role" "role" {
name = var.role_name
}

resource "snowflake_role" "parent_role" {
name = var.parent_role_name
}

resource "snowflake_grant_role" "g" {
role_name = snowflake_role.role.name
parent_role_name = snowflake_role.parent_role.name
}


##################################
### grant account role to user
##################################

resource "snowflake_role" "role" {
name = var.role_name
}

resource "snowflake_user" "user" {
name = var.user_name
}

resource "snowflake_grant_role" "g" {
role_name = snowflake_role.role.name
user_name = snowflake_user.user.name
}
1 change: 1 addition & 0 deletions pkg/provider/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -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_role": resources.GrantRole(),
"snowflake_grant_privileges_to_role": resources.GrantPrivilegesToRole(),
"snowflake_managed_account": resources.ManagedAccount(),
"snowflake_masking_policy": resources.MaskingPolicy(),
Expand Down
175 changes: 175 additions & 0 deletions pkg/resources/grant_role.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
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 grantRoleSchema = map[string]*schema.Schema{
"role_name": {
Type: schema.TypeString,
Required: true,
Description: "The fully qualified name of the role which will be granted to the user or parent role.",
ForceNew: true,
ValidateDiagFunc: IsValidIdentifier[sdk.AccountObjectIdentifier](),
},
"user_name": {
Type: schema.TypeString,
Optional: true,
Description: "The fully qualified name of the user on which specified role will be granted.",
ForceNew: true,
ValidateDiagFunc: IsValidIdentifier[sdk.AccountObjectIdentifier](),
ExactlyOneOf: []string{
"user_name",
"parent_role_name",
},
},
"parent_role_name": {
Type: schema.TypeString,
Optional: true,
Description: "The fully qualified name of the parent role which will create a parent-child relationship between the roles.",
ForceNew: true,
ValidateDiagFunc: IsValidIdentifier[sdk.AccountObjectIdentifier](),
ExactlyOneOf: []string{
"user_name",
"parent_role_name",
},
},
}

func GrantRole() *schema.Resource {
return &schema.Resource{
Create: CreateGrantRole,
Read: ReadGrantRole,
Delete: DeleteGrantRole,
Schema: grantRoleSchema,
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 <role_name>|<grantee_object_type>|<grantee_identifier>", d.Id())
}
if err := d.Set("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 "USER":
if err := d.Set("usere_name", parts[2]); err != nil {
return nil, err
}
default:
return nil, fmt.Errorf("invalid object type specified: %v, expected ROLE or USER", parts[1])
}

return []*schema.ResourceData{d}, nil
},
},
}
}

// CreateGrantRole implements schema.CreateFunc.
func CreateGrantRole(d *schema.ResourceData, meta interface{}) error {
db := meta.(*sql.DB)
client := sdk.NewClientFromDB(db)
ctx := context.Background()
roleName := d.Get("role_name").(string)
roleIdentifier := sdk.NewAccountObjectIdentifierFromFullyQualifiedName(roleName)
// format of snowflakeResourceID is <role_identifier>|<object type>|<target_identifier>
var snowflakeResourceID string
if parentRoleName, ok := d.GetOk("parent_role_name"); ok && parentRoleName.(string) != "" {
parentRoleIdentifier := sdk.NewAccountObjectIdentifierFromFullyQualifiedName(parentRoleName.(string))
snowflakeResourceID = helpers.EncodeSnowflakeID(roleIdentifier.FullyQualifiedName(), sdk.ObjectTypeRole.String(), parentRoleIdentifier.FullyQualifiedName())
req := sdk.NewGrantRoleRequest(roleIdentifier, sdk.GrantRole{
Role: &parentRoleIdentifier,
})
if err := client.Roles.Grant(ctx, req); err != nil {
return err
}
} else if userName, ok := d.GetOk("user_name"); ok && userName.(string) != "" {
userIdentifier := sdk.NewAccountObjectIdentifierFromFullyQualifiedName(userName.(string))
snowflakeResourceID = helpers.EncodeSnowflakeID(roleIdentifier.FullyQualifiedName(), sdk.ObjectTypeUser.String(), userIdentifier.FullyQualifiedName())
req := sdk.NewGrantRoleRequest(roleIdentifier, sdk.GrantRole{
User: &userIdentifier,
})
if err := client.Roles.Grant(ctx, req); err != nil {
return err
}
} else {
return fmt.Errorf("invalid role grant specified: %v", d)
}
d.SetId(snowflakeResourceID)
return ReadGrantRole(d, meta)
}

func ReadGrantRole(d *schema.ResourceData, meta interface{}) error {
db := meta.(*sql.DB)
client := sdk.NewClientFromDB(db)
parts := strings.Split(d.Id(), helpers.IDDelimiter)
roleName := parts[0]
roleIdentifier := sdk.NewAccountObjectIdentifierFromFullyQualifiedName(roleName)
objectType := parts[1]
targetIdentifier := parts[2]
ctx := context.Background()
grants, err := client.Grants.Show(ctx, &sdk.ShowGrantOptions{
Of: &sdk.ShowGrantsOf{
Role: roleIdentifier,
},
})
if err != nil {
log.Printf("[DEBUG] role (%s) not found", roleIdentifier.FullyQualifiedName())
d.SetId("")
return nil
}

var found bool
for _, grant := range grants {
if grant.GrantedTo == sdk.ObjectType(objectType) {
if grant.GranteeName.FullyQualifiedName() == targetIdentifier {
found = true
break
}
}
}
if !found {
log.Printf("[DEBUG] role grant (%s) not found", d.Id())
d.SetId("")
}

return nil
}

func DeleteGrantRole(d *schema.ResourceData, meta interface{}) error {
db := meta.(*sql.DB)
client := sdk.NewClientFromDB(db)
parts := strings.Split(d.Id(), helpers.IDDelimiter)
id := sdk.NewAccountObjectIdentifierFromFullyQualifiedName(parts[0])
objectType := parts[1]
granteeName := parts[2]
ctx := context.Background()
granteeIdentifier := sdk.NewAccountObjectIdentifierFromFullyQualifiedName(granteeName)
switch objectType {
case "ROLE":
if err := client.Roles.Revoke(ctx, sdk.NewRevokeRoleRequest(id, sdk.RevokeRole{Role: &granteeIdentifier})); err != nil {
return err
}
case "USER":
if err := client.Roles.Revoke(ctx, sdk.NewRevokeRoleRequest(id, sdk.RevokeRole{User: &granteeIdentifier})); err != nil {
return err
}
default:
return fmt.Errorf("invalid object type specified: %v, expected ROLE or USER", objectType)
}
d.SetId("")
return nil
}
74 changes: 74 additions & 0 deletions pkg/resources/grant_role_acceptance_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
package resources_test

import (
"fmt"
"strings"
"testing"

acc "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/acceptance"

"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/tfversion"
)

func TestAcc_GrantRole_accountRole(t *testing.T) {
roleName := strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha))
parentRoleName := strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha))
resourceName := "snowflake_grant_role.g"
m := func() map[string]config.Variable {
return map[string]config.Variable{
"role_name": config.StringVariable(roleName),
"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),
},
Steps: []resource.TestStep{
{
ConfigDirectory: config.StaticDirectory("testdata/TestAcc_GrantRole/account_role"),
ConfigVariables: m(),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr(resourceName, "role_name", roleName),
resource.TestCheckResourceAttr(resourceName, "parent_role_name", parentRoleName),
resource.TestCheckResourceAttr(resourceName, "id", fmt.Sprintf(`"%v"|ROLE|"%v"`, roleName, parentRoleName)),
),
},
},
})
}

func TestAcc_GrantRole_user(t *testing.T) {
roleName := strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha))
userName := strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha))
resourceName := "snowflake_grant_role.g"
m := func() map[string]config.Variable {
return map[string]config.Variable{
"role_name": config.StringVariable(roleName),
"user_name": config.StringVariable(userName),
}
}
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: config.StaticDirectory("testdata/TestAcc_GrantRole/user"),
ConfigVariables: m(),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr(resourceName, "role_name", roleName),
resource.TestCheckResourceAttr(resourceName, "user_name", userName),
resource.TestCheckResourceAttr(resourceName, "id", fmt.Sprintf(`"%v"|USER|"%v"`, roleName, userName)),
),
},
},
})
}
20 changes: 20 additions & 0 deletions pkg/resources/testdata/TestAcc_GrantRole/account_role/test.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
variable "role_name" {
type = string
}

variable "parent_role_name" {
type = string
}

resource "snowflake_role" "role" {
name = var.role_name
}

resource "snowflake_role" "parent_role" {
name = var.parent_role_name
}

resource "snowflake_grant_role" "g" {
role_name = snowflake_role.role.name
parent_role_name = snowflake_role.parent_role.name
}
20 changes: 20 additions & 0 deletions pkg/resources/testdata/TestAcc_GrantRole/user/test.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
variable "role_name" {
type = string
}

variable "user_name" {
type = string
}

resource "snowflake_role" "role" {
name = var.role_name
}

resource "snowflake_user" "user" {
name = var.user_name
}

resource "snowflake_grant_role" "g" {
role_name = snowflake_role.role.name
user_name = snowflake_user.user.name
}

0 comments on commit eb6d1db

Please sign in to comment.