Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: cleanup grant logic #1522

Merged
merged 26 commits into from
Feb 17, 2023
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ require (
github.com/jmoiron/sqlx v1.3.5
github.com/luna-duclos/instrumentedsql v1.1.3
github.com/mitchellh/go-homedir v1.1.0
github.com/rs/xid v1.4.0
github.com/snowflakedb/gosnowflake v1.6.16
github.com/stretchr/testify v1.8.1
github.com/youmark/pkcs8 v0.0.0-20201027041543-1326539a0a0a
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -338,6 +338,8 @@ github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSg
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
github.com/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBOAvL+k=
github.com/rs/xid v1.4.0 h1:qd7wPTDkN6KQx2VmMBLrpHkiyQwgFXRnkOLacUiaSNY=
github.com/rs/xid v1.4.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
github.com/russross/blackfriday v1.6.0 h1:KqfZb0pUVN2lYqZUYRddxF4OR8ZMURnJIG5Y3VRLtww=
github.com/russross/blackfriday v1.6.0/go.mod h1:ti0ldHuxg49ri4ksnFxlkCfN+hvslNlmVHqNRXXJNAY=
github.com/ruudk/golang-pdf417 v0.0.0-20181029194003-1af4ab5afa58/go.mod h1:6lfFZQK844Gfx8o5WFuvpxWRwnSoipWe/p622j1v06w=
Expand Down
72 changes: 72 additions & 0 deletions pkg/helpers/imports.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package helpers

import (
"fmt"
"reflect"
"strconv"
"strings"

"github.com/rs/xid"
)

func RandomSnowflakeID() string {
guid := xid.New()
return fmt.Sprintf("snow-%s", guid.String())
}

func DecodeSnowflakeImportID(id string, v interface{}) (interface{}, error) {
attributes := make(map[string]string)
parts := strings.Split(id, "|")
for _, part := range parts {
if !strings.Contains(part, "=") {
return nil, fmt.Errorf("invalid import ID format: %s, attributes must be defined as key=value format", id)
}
key := strings.TrimSpace(strings.Split(part, "=")[0])
value := strings.TrimSpace(strings.Split(part, "=")[1])
attributes[key] = value
}
for k, v := range attributes {
fmt.Printf("[DEBUG] %s=%s\n", k, v)
}

// w is the interface{}
w := reflect.ValueOf(&v).Elem()

// Allocate a temporary variable with type of the struct.
// v.Elem() is the value contained in the interface.
tmp := reflect.New(w.Elem().Type()).Elem()

// Copy the struct value contained in interface to
// the temporary variable.
tmp.Set(w.Elem())

t := tmp.Type()
for i := 0; i < t.NumField(); i++ {
field := t.Field(i)
tag := field.Tag.Get("tf")
importValue := attributes[tag]
f := tmp.FieldByName(field.Name)
switch f.Kind() {
case reflect.String:
f.SetString(importValue)
case reflect.Int:
intVal, err := strconv.Atoi(importValue)
if err != nil {
return nil, err
}
f.SetInt(int64(intVal))
case reflect.Bool:
f.SetBool(importValue == "true")
case reflect.Slice:
p := strings.Split(importValue, ",")
for _, v := range p {
v := strings.Trim(v, "\"")
f.Set(reflect.Append(f, reflect.ValueOf(v)))
}
}
}
// Set the interface to the modified struct value.
w.Set(tmp)
fmt.Printf("[DEBUG] v=%+v\n", v)
return v, nil
}
70 changes: 36 additions & 34 deletions pkg/resources/account_grant.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package resources

import (
"context"

"github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/helpers"
"github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/snowflake"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
Expand Down Expand Up @@ -48,6 +51,7 @@ var accountGrantSchema = map[string]*schema.Schema{
Description: "The account privilege to grant. Valid privileges are those in [globalPrivileges](https://docs.snowflake.com/en/sql-reference/sql/grant-privilege.html)",
Default: privilegeMonitorUsage,
ValidateFunc: validation.StringInSlice(validAccountPrivileges.ToList(), true),
ForceNew: true,
},
"roles": {
Type: schema.TypeSet,
Expand Down Expand Up @@ -81,53 +85,54 @@ func AccountGrant() *TerraformGrantResource {

Schema: accountGrantSchema,
Importer: &schema.ResourceImporter{
StateContext: schema.ImportStatePassthroughContext,
StateContext: func(ctx context.Context, d *schema.ResourceData, m interface{}) ([]*schema.ResourceData, error) {
v, err := helpers.DecodeSnowflakeImportID(d.Id(), AccountGrantID{})
if err != nil {
return nil, err
}
id := v.(AccountGrantID)
err = d.Set("privilege", id.Privilege)
if err != nil {
return nil, err
}
err = d.Set("roles", id.Roles)
if err != nil {
return nil, err
}
err = d.Set("with_grant_option", id.WithGrantOption)
if err != nil {
return nil, err
}
d.SetId(helpers.RandomSnowflakeID())
return []*schema.ResourceData{d}, nil
},
},
},
ValidPrivs: validAccountPrivileges,
}
}

type AccountGrantID struct {
Privilege string `tf:"privilege"`
Roles []string `tf:"roles"`
WithGrantOption bool `tf:"with_grant_option"`
}

// CreateAccountGrant implements schema.CreateFunc.
func CreateAccountGrant(d *schema.ResourceData, meta interface{}) error {
priv := d.Get("privilege").(string)
grantOption := d.Get("with_grant_option").(bool)
roles := expandStringList(d.Get("roles").(*schema.Set).List())

builder := snowflake.AccountGrant()

if err := createGenericGrant(d, meta, builder); err != nil {
return err
}

grantID := &grantID{
ResourceName: "ACCOUNT",
Privilege: priv,
GrantOption: grantOption,
Roles: roles,
}
dataIDInput, err := grantID.String()
if err != nil {
return err
}
d.SetId(dataIDInput)
d.SetId(helpers.RandomSnowflakeID())

return ReadAccountGrant(d, meta)
}

// ReadAccountGrant implements schema.ReadFunc.
func ReadAccountGrant(d *schema.ResourceData, meta interface{}) error {
grantID, err := grantIDFromString(d.Id())
if err != nil {
return err
}
if err := d.Set("privilege", grantID.Privilege); err != nil {
return err
}
if err := d.Set("with_grant_option", grantID.GrantOption); err != nil {
return err
}

builder := snowflake.AccountGrant()

return readGenericGrant(d, meta, accountGrantSchema, builder, false, validAccountPrivileges)
Expand All @@ -150,20 +155,17 @@ func UpdateAccountGrant(d *schema.ResourceData, meta interface{}) error {

rolesToAdd, rolesToRevoke := changeDiff(d, "roles")

grantID, err := grantIDFromString(d.Id())
if err != nil {
return err
}

builder := snowflake.AccountGrant()
privilege := d.Get("privilege").(string)
withGrantOption := d.Get("with_grant_option").(bool)

// first revoke
if err := deleteGenericGrantRolesAndShares(meta, builder, grantID.Privilege, rolesToRevoke, nil); err != nil {
if err := deleteGenericGrantRolesAndShares(meta, builder, privilege, rolesToRevoke, nil); err != nil {
return err
}

// then add
if err := createGenericGrantRolesAndShares(meta, builder, grantID.Privilege, grantID.GrantOption, rolesToAdd, nil); err != nil {
if err := createGenericGrantRolesAndShares(meta, builder, privilege, withGrantOption, rolesToAdd, nil); err != nil {
return err
}

Expand Down
88 changes: 38 additions & 50 deletions pkg/resources/database_grant.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
package resources

import (
"context"
"fmt"

"github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/helpers"
"github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/snowflake"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
Expand Down Expand Up @@ -71,70 +73,58 @@ func DatabaseGrant() *TerraformGrantResource {

Schema: databaseGrantSchema,
Importer: &schema.ResourceImporter{
StateContext: schema.ImportStatePassthroughContext,
StateContext: func(ctx context.Context, d *schema.ResourceData, m interface{}) ([]*schema.ResourceData, error) {
v, err := helpers.DecodeSnowflakeImportID(d.Id(), DatabaseGrantID{})
if err != nil {
return nil, err
}
id := v.(DatabaseGrantID)
d.Set("database_name", id.DatabaseName)
sfc-gh-swinkler marked this conversation as resolved.
Show resolved Hide resolved
d.Set("privilege", id.Privilege)
sfc-gh-swinkler marked this conversation as resolved.
Show resolved Hide resolved
d.Set("roles", id.Roles)
sfc-gh-swinkler marked this conversation as resolved.
Show resolved Hide resolved
d.Set("shares", id.Shares)
d.Set("with_grant_option", id.WithGrantOption)
d.SetId(helpers.RandomSnowflakeID())
return []*schema.ResourceData{d}, nil
},
},
},
ValidPrivs: validDatabasePrivileges,
}
}

type DatabaseGrantID struct {
DatabaseName string `tf:"database_name"`
Privilege string `tf:"privilege"`
Roles []string `tf:"roles"`
Shares []string `tf:"shares"`
WithGrantOption bool `tf:"with_grant_option"`
}

// CreateDatabaseGrant implements schema.CreateFunc.
func CreateDatabaseGrant(d *schema.ResourceData, meta interface{}) error {
dbName := d.Get("database_name").(string)
builder := snowflake.DatabaseGrant(dbName)
priv := d.Get("privilege").(string)
grantOption := d.Get("with_grant_option").(bool)
roles := expandStringList(d.Get("roles").(*schema.Set).List())

databaseName := d.Get("database_name").(string)
builder := snowflake.DatabaseGrant(databaseName)
if err := createGenericGrant(d, meta, builder); err != nil {
return fmt.Errorf("error creating database grant err = %w", err)
}

grant := &grantID{
ResourceName: dbName,
Privilege: priv,
GrantOption: grantOption,
Roles: roles,
}
dataIDInput, err := grant.String()
if err != nil {
return fmt.Errorf("error creating database grant err = %w", err)
}
d.SetId(dataIDInput)
d.SetId(helpers.RandomSnowflakeID())

return ReadDatabaseGrant(d, meta)
}

// ReadDatabaseGrant implements schema.ReadFunc.
func ReadDatabaseGrant(d *schema.ResourceData, meta interface{}) error {
grantID, err := grantIDFromString(d.Id())
if err != nil {
return err
}
if err := d.Set("database_name", grantID.ResourceName); err != nil {
return err
}
if err := d.Set("privilege", grantID.Privilege); err != nil {
return err
}
if err := d.Set("with_grant_option", grantID.GrantOption); err != nil {
return err
}

// IMPORTED PRIVILEGES is not a real resource, so we can't actually verify
// that it is still there. Just exit for now
if grantID.Privilege == "IMPORTED PRIVILEGES" {
return nil
}

builder := snowflake.DatabaseGrant(grantID.ResourceName)
databaseName := d.Get("database_name").(string)
builder := snowflake.DatabaseGrant(databaseName)
return readGenericGrant(d, meta, databaseGrantSchema, builder, false, validDatabasePrivileges)
}

// DeleteDatabaseGrant implements schema.DeleteFunc.
func DeleteDatabaseGrant(d *schema.ResourceData, meta interface{}) error {
dbName := d.Get("database_name").(string)
builder := snowflake.DatabaseGrant(dbName)
databaseName := d.Get("database_name").(string)
builder := snowflake.DatabaseGrant(databaseName)

return deleteGenericGrant(d, meta, builder)
}
Expand All @@ -158,19 +148,17 @@ func UpdateDatabaseGrant(d *schema.ResourceData, meta interface{}) error {
sharesToAdd, sharesToRevoke = changeDiff(d, "shares")
}

grantID, err := grantIDFromString(d.Id())
if err != nil {
return err
}

databaseName := d.Get("database_name").(string)
privilege := d.Get("privilege").(string)
withGrantOption := d.Get("with_grant_option").(bool)
// create the builder
builder := snowflake.DatabaseGrant(grantID.ResourceName)
builder := snowflake.DatabaseGrant(databaseName)

// first revoke
if err := deleteGenericGrantRolesAndShares(
meta,
builder,
grantID.Privilege,
privilege,
rolesToRevoke,
sharesToRevoke,
); err != nil {
Expand All @@ -181,8 +169,8 @@ func UpdateDatabaseGrant(d *schema.ResourceData, meta interface{}) error {
if err := createGenericGrantRolesAndShares(
meta,
builder,
grantID.Privilege,
grantID.GrantOption,
privilege,
withGrantOption,
rolesToAdd,
sharesToAdd,
); err != nil {
Expand Down
Loading