Skip to content

Commit

Permalink
feat: roles support numbers (#1585)
Browse files Browse the repository at this point in the history
* roles support numbers

* update roles

* update roles

* update roles

* update roles

* update acc test

* update acc test

* update acc test
  • Loading branch information
sfc-gh-swinkler authored Mar 2, 2023
1 parent 23b64fa commit d72dee8
Show file tree
Hide file tree
Showing 16 changed files with 281 additions and 164 deletions.
5 changes: 1 addition & 4 deletions pkg/datasources/role.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package datasources
import (
"database/sql"
"errors"
"fmt"
"log"

"github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/snowflake"
Expand Down Expand Up @@ -38,9 +37,7 @@ func Role() *schema.Resource {
func ReadRole(d *schema.ResourceData, meta interface{}) error {
db := meta.(*sql.DB)
roleName := d.Get("name").(string)

row := snowflake.QueryRow(db, fmt.Sprintf("SHOW ROLES LIKE '%s'", roleName))
role, err := snowflake.ScanRole(row)
role, err := snowflake.NewRoleBuilder(db, roleName).Show()

if errors.Is(err, sql.ErrNoRows) {
log.Printf("[DEBUG] role (%s) not found", roleName)
Expand Down
7 changes: 7 additions & 0 deletions pkg/resources/function_grant.go
Original file line number Diff line number Diff line change
Expand Up @@ -348,6 +348,13 @@ func ParseFunctionGrantID(s string) (*FunctionGrantID, error) {
if len(objectNameParts) > 1 {
argumentDataTypes = helpers.SplitStringToSlice(objectNameParts[1], ",")
}
// remove the param names from the argument (if present)
for i, argumentDataType := range argumentDataTypes {
parts := strings.Split(argumentDataType, " ")
if len(parts) > 1 {
argumentDataTypes[i] = parts[1]
}
}
return &FunctionGrantID{
DatabaseName: idParts[0],
SchemaName: idParts[1],
Expand Down
20 changes: 20 additions & 0 deletions pkg/resources/function_grant_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ func TestParseFunctionGrantIDWithArgs(t *testing.T) {
r.Equal("MY_SCHEMA", grantID.SchemaName)
r.Equal("MY_FUNCTION", grantID.ObjectName)
r.Equal(2, len(grantID.ArgumentDataTypes))
r.Equal("string", grantID.ArgumentDataTypes[0])
r.Equal("string", grantID.ArgumentDataTypes[1])
r.Equal("privilege_name", grantID.Privilege)
r.Equal(2, len(grantID.Roles))
r.Equal(2, len(grantID.Shares))
Expand Down Expand Up @@ -57,6 +59,24 @@ func TestParseFunctionGrantOldIDWithArgs(t *testing.T) {
r.Equal("MY_SCHEMA", grantID.SchemaName)
r.Equal("MY_FUNCTION", grantID.ObjectName)
r.Equal(2, len(grantID.ArgumentDataTypes))
r.Equal("string", grantID.ArgumentDataTypes[0])
r.Equal("string", grantID.ArgumentDataTypes[1])
r.Equal("privilege_name", grantID.Privilege)
r.Equal(2, len(grantID.Roles))
r.Equal(0, len(grantID.Shares))
r.Equal(false, grantID.WithGrantOption)
}

func TestParseFunctionGrantOldIDWithArgsAndNames(t *testing.T) {
r := require.New(t)
grantID, err := resources.ParseFunctionGrantID("MY_DATABASE|MY_SCHEMA|MY_FUNCTION(A string, B string)|privilege_name|role1,role2|false")
r.NoError(err)
r.Equal("MY_DATABASE", grantID.DatabaseName)
r.Equal("MY_SCHEMA", grantID.SchemaName)
r.Equal("MY_FUNCTION", grantID.ObjectName)
r.Equal(2, len(grantID.ArgumentDataTypes))
r.Equal("string", grantID.ArgumentDataTypes[0])
r.Equal("string", grantID.ArgumentDataTypes[1])
r.Equal("privilege_name", grantID.Privilege)
r.Equal(2, len(grantID.Roles))
r.Equal(0, len(grantID.Shares))
Expand Down
7 changes: 7 additions & 0 deletions pkg/resources/procedure_grant.go
Original file line number Diff line number Diff line change
Expand Up @@ -357,6 +357,13 @@ func ParseProcedureGrantID(s string) (*ProcedureGrantID, error) {
argumentDataTypes := []string{}
if len(objectNameParts) > 1 {
argumentDataTypes = helpers.SplitStringToSlice(objectNameParts[1], ",")
// remove the param names from the argument (if present)
for i, argumentDataType := range argumentDataTypes {
parts := strings.Split(argumentDataType, " ")
if len(parts) > 1 {
argumentDataTypes[i] = parts[1]
}
}
}
return &ProcedureGrantID{
DatabaseName: idParts[0],
Expand Down
20 changes: 20 additions & 0 deletions pkg/resources/procedure_grant_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ func TestParseProcedureGrantIDWithArgs(t *testing.T) {
r.Equal("MY_SCHEMA", grantID.SchemaName)
r.Equal("MY_PROCEDURE", grantID.ObjectName)
r.Equal(2, len(grantID.ArgumentDataTypes))
r.Equal("string", grantID.ArgumentDataTypes[0])
r.Equal("string", grantID.ArgumentDataTypes[1])
r.Equal("privilege_name", grantID.Privilege)
r.Equal(2, len(grantID.Roles))
r.Equal(2, len(grantID.Shares))
Expand Down Expand Up @@ -57,6 +59,24 @@ func TestParseProcedureGrantOldIDWithArgs(t *testing.T) {
r.Equal("MY_SCHEMA", grantID.SchemaName)
r.Equal("MY_PROCEDURE", grantID.ObjectName)
r.Equal(2, len(grantID.ArgumentDataTypes))
r.Equal("string", grantID.ArgumentDataTypes[0])
r.Equal("string", grantID.ArgumentDataTypes[1])
r.Equal("privilege_name", grantID.Privilege)
r.Equal(2, len(grantID.Roles))
r.Equal(0, len(grantID.Shares))
r.Equal(false, grantID.WithGrantOption)
}

func TestParseProcedureGrantOldIDWithArgsAndNames(t *testing.T) {
r := require.New(t)
grantID, err := resources.ParseFunctionGrantID("MY_DATABASE|MY_SCHEMA|MY_PROCEDURE(A string, B string)|privilege_name|role1,role2|false")
r.NoError(err)
r.Equal("MY_DATABASE", grantID.DatabaseName)
r.Equal("MY_SCHEMA", grantID.SchemaName)
r.Equal("MY_PROCEDURE", grantID.ObjectName)
r.Equal(2, len(grantID.ArgumentDataTypes))
r.Equal("string", grantID.ArgumentDataTypes[0])
r.Equal("string", grantID.ArgumentDataTypes[1])
r.Equal("privilege_name", grantID.Privilege)
r.Equal(2, len(grantID.Roles))
r.Equal(0, len(grantID.Shares))
Expand Down
132 changes: 100 additions & 32 deletions pkg/resources/role.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,32 +3,28 @@ package resources
import (
"database/sql"
"errors"
"fmt"
"log"

"github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/snowflake"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
)

var (
roleProperties = []string{"comment"}
roleSchema = map[string]*schema.Schema{
"name": {
Type: schema.TypeString,
Required: true,
ValidateFunc: func(val interface{}, key string) ([]string, []error) {
additionalCharsToIgnoreValidation := []string{".", " ", ":", "(", ")"}
return snowflake.ValidateIdentifier(val, additionalCharsToIgnoreValidation)
},
var roleSchema = map[string]*schema.Schema{
"name": {
Type: schema.TypeString,
Required: true,
ValidateFunc: func(val interface{}, key string) ([]string, []error) {
additionalCharsToIgnoreValidation := []string{".", " ", ":", "(", ")"}
return snowflake.ValidateIdentifier(val, additionalCharsToIgnoreValidation)
},
"comment": {
Type: schema.TypeString,
Optional: true,
// TODO validation
},
"tag": tagReferenceSchema,
}
)
},
"comment": {
Type: schema.TypeString,
Optional: true,
// TODO validation
},
"tag": tagReferenceSchema,
}

func Role() *schema.Resource {
return &schema.Resource{
Expand All @@ -45,40 +41,112 @@ func Role() *schema.Resource {
}

func CreateRole(d *schema.ResourceData, meta interface{}) error {
return CreateResource("role", roleProperties, roleSchema, snowflake.NewRoleBuilder, ReadRole)(d, meta)
name := d.Get("name").(string)
db := meta.(*sql.DB)
builder := snowflake.NewRoleBuilder(db, name)
if v, ok := d.GetOk("comment"); ok {
builder.WithComment(v.(string))
}
if v, ok := d.GetOk("tag"); ok {
tags := getTags(v)
builder.WithTags(tags.toSnowflakeTagValues())
}
err := builder.Create()
if err != nil {
return err
}
d.SetId(name)
return ReadRole(d, meta)
}

func ReadRole(d *schema.ResourceData, meta interface{}) error {
db := meta.(*sql.DB)
id := d.Id()
// If the name is not set (such as during import) then use the id
name := d.Get("name").(string)
if name == "" {
name = id
}

row := snowflake.QueryRow(db, fmt.Sprintf("SHOW ROLES LIKE '%s'", id))
role, err := snowflake.ScanRole(row)
builder := snowflake.NewRoleBuilder(db, name)
role, err := builder.Show()
if errors.Is(err, sql.ErrNoRows) {
// If not found, mark resource to be removed from statefile during apply or refresh
log.Printf("[DEBUG] role (%s) not found", d.Id())
log.Printf("[WARN] role (%s) not found", name)
d.SetId("")
return nil
}
if err != nil {
} else if err != nil {
return err
}

if err := d.Set("name", role.Name.String); err != nil {
return err
}

if err := d.Set("comment", role.Comment.String); err != nil {
return err
}

return err
return nil
}

func UpdateRole(d *schema.ResourceData, meta interface{}) error {
return UpdateResource("role", roleProperties, roleSchema, snowflake.NewRoleBuilder, ReadRole)(d, meta)
db := meta.(*sql.DB)
name := d.Get("name").(string)
builder := snowflake.NewRoleBuilder(db, name)

if d.HasChange("name") {
o, n := d.GetChange("name")
builder.WithName(o.(string))
err := builder.Rename(n.(string))
if err != nil {
return err
}
builder.WithName(n.(string))
}

if d.HasChange("comment") {
o, n := d.GetChange("comment")
if n == nil || n.(string) == "" {
builder.WithComment(o.(string))
err := builder.UnsetComment()
if err != nil {
return err
}
} else {
err := builder.SetComment(n.(string))
if err != nil {
return err
}
}
}

if d.HasChange("tag") {
old, new := d.GetChange("tag")
removed, added, changed := getTags(old).diffs(getTags(new))
for _, tA := range removed {
err := builder.UnsetTag(tA.toSnowflakeTagValue())
if err != nil {
return err
}
}
for _, tA := range added {
err := builder.SetTag(tA.toSnowflakeTagValue())
if err != nil {
return err
}
}
for _, tA := range changed {
err := builder.ChangeTag(tA.toSnowflakeTagValue())
if err != nil {
return err
}
}
}

return nil
}

func DeleteRole(d *schema.ResourceData, meta interface{}) error {
return DeleteResource("role", snowflake.NewRoleBuilder)(d, meta)
db := meta.(*sql.DB)
name := d.Get("name").(string)
builder := snowflake.NewRoleBuilder(db, name)
err := builder.Drop()
return err
}
52 changes: 21 additions & 31 deletions pkg/resources/role_acceptance_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,62 +10,52 @@ import (
)

func TestAcc_Role(t *testing.T) {
prefix := "tst-terraform" + strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha))
prefix2 := "tst-terraform" + strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha))
name := "tst-terraform" + strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha))
name2 := "5tst-terraform" + strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha))

resource.ParallelTest(t, resource.TestCase{
Providers: providers(),
CheckDestroy: nil,
Steps: []resource.TestStep{
{
Config: rConfig(prefix),
Config: roleBasicConfig(name, "test comment"),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr("snowflake_role.w", "name", prefix),
resource.TestCheckResourceAttr("snowflake_role.w", "comment", "test comment"),
resource.TestCheckResourceAttr("snowflake_role.role", "name", name),
resource.TestCheckResourceAttr("snowflake_role.role", "comment", "test comment"),
),
},
// IMPORT
{
ResourceName: "snowflake_role.role",
ImportState: true,
ImportStateVerify: true,
},
// RENAME
{
Config: rConfig(prefix2),
Config: roleBasicConfig(name2, "test comment"),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr("snowflake_role.w", "name", prefix2),
resource.TestCheckResourceAttr("snowflake_role.w", "comment", "test comment"),
resource.TestCheckResourceAttr("snowflake_role.role", "name", name2),
resource.TestCheckResourceAttr("snowflake_role.role", "comment", "test comment"),
),
},
// CHANGE PROPERTIES
{
Config: rConfig2(prefix2),
Config: roleBasicConfig(name2, "test comment 2"),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr("snowflake_role.w", "name", prefix2),
resource.TestCheckResourceAttr("snowflake_role.w", "comment", "test comment 2"),
resource.TestCheckResourceAttr("snowflake_role.role", "name", name2),
resource.TestCheckResourceAttr("snowflake_role.role", "comment", "test comment 2"),
),
},
// IMPORT
{
ResourceName: "snowflake_role.w",
ImportState: true,
ImportStateVerify: true,
},
},
})
}

func rConfig(prefix string) string {
s := `
resource "snowflake_role" "w" {
name = "%s"
comment = "test comment"
}
`
return fmt.Sprintf(s, prefix)
}

func rConfig2(prefix string) string {
func roleBasicConfig(name, comment string) string {
s := `
resource "snowflake_role" "w" {
resource "snowflake_role" "role" {
name = "%s"
comment = "test comment 2"
comment = "%s"
}
`
return fmt.Sprintf(s, prefix)
return fmt.Sprintf(s, name, comment)
}
Loading

0 comments on commit d72dee8

Please sign in to comment.