Skip to content

Commit

Permalink
fix: Allow multiple resources of the same object grant (#824)
Browse files Browse the repository at this point in the history
  • Loading branch information
daniepett authored Feb 2, 2022
1 parent be6c454 commit 7ac4d54
Show file tree
Hide file tree
Showing 41 changed files with 113 additions and 49 deletions.
2 changes: 2 additions & 0 deletions pkg/resources/account_grant.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ func AccountGrant() *TerraformGrantResource {
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()

Expand All @@ -86,6 +87,7 @@ func CreateAccountGrant(d *schema.ResourceData, meta interface{}) error {
ResourceName: "ACCOUNT",
Privilege: priv,
GrantOption: grantOption,
Roles: roles,
}
dataIDInput, err := grantID.String()
if err != nil {
Expand Down
8 changes: 4 additions & 4 deletions pkg/resources/account_grant_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ func TestAccountGrantCreate(t *testing.T) { //lintignore:AT003
func TestAccountGrantRead(t *testing.T) {
r := require.New(t)

d := accountGrant(t, "ACCOUNT|||MANAGE GRANTS|true", map[string]interface{}{
d := accountGrant(t, "ACCOUNT|||MANAGE GRANTS||true", map[string]interface{}{
"privilege": "MANAGE GRANTS",
"roles": []interface{}{"test-role-1", "test-role-2"},
"with_grant_option": true,
Expand All @@ -63,7 +63,7 @@ func TestAccountGrantRead(t *testing.T) {
func TestMonitorExecution(t *testing.T) {
r := require.New(t)

d := accountGrant(t, "ACCOUNT|||MONITOR EXECUTION|true", map[string]interface{}{
d := accountGrant(t, "ACCOUNT|||MONITOR EXECUTION||true", map[string]interface{}{
"privilege": "MONITOR EXECUTION",
"roles": []interface{}{"test-role-1", "test-role-2"},
"with_grant_option": true,
Expand All @@ -81,7 +81,7 @@ func TestMonitorExecution(t *testing.T) {
func TestExecuteTask(t *testing.T) {
r := require.New(t)

d := accountGrant(t, "ACCOUNT|||EXECUTE TASK|false", map[string]interface{}{
d := accountGrant(t, "ACCOUNT|||EXECUTE TASK||false", map[string]interface{}{
"privilege": "EXECUTE TASK",
"roles": []interface{}{"test-role-1", "test-role-2"},
"with_grant_option": false,
Expand Down Expand Up @@ -110,7 +110,7 @@ func expectReadAccountGrant(mock sqlmock.Sqlmock) {
func TestApplyMaskingPolicy(t *testing.T) {
r := require.New(t)

d := accountGrant(t, "ACCOUNT|||APPLY MASKING POLICY|true", map[string]interface{}{
d := accountGrant(t, "ACCOUNT|||APPLY MASKING POLICY||true", map[string]interface{}{
"privilege": "APPLY MASKING POLICY",
"roles": []interface{}{"test-role-1", "test-role-2"},
"with_grant_option": true,
Expand Down
3 changes: 3 additions & 0 deletions pkg/resources/database_grant.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ var databaseGrantSchema = map[string]*schema.Schema{
Type: schema.TypeSet,
Elem: &schema.Schema{Type: schema.TypeString},
Optional: true,
ForceNew: true,
Description: "Grants privilege to these roles.",
},
"shares": {
Expand Down Expand Up @@ -77,6 +78,7 @@ func CreateDatabaseGrant(d *schema.ResourceData, meta interface{}) error {
builder := snowflake.DatabaseGrant(dbName)
priv := d.Get("privilege").(string)
grantOption := d.Get("with_grant_option").(bool)
roles := expandStringList(d.Get("roles").(*schema.Set).List())

err := createGenericGrant(d, meta, builder)
if err != nil {
Expand All @@ -87,6 +89,7 @@ func CreateDatabaseGrant(d *schema.ResourceData, meta interface{}) error {
ResourceName: dbName,
Privilege: priv,
GrantOption: grantOption,
Roles: roles,
}
dataIDInput, err := grant.String()
if err != nil {
Expand Down
2 changes: 1 addition & 1 deletion pkg/resources/database_grant_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ func TestDatabaseGrantCreate(t *testing.T) {
func TestDatabaseGrantRead(t *testing.T) {
r := require.New(t)

d := databaseGrant(t, "test-database|||USAGE|false", map[string]interface{}{
d := databaseGrant(t, "test-database|||USAGE||false", map[string]interface{}{
"database_name": "test-database",
"privilege": "USAGE",
"roles": []interface{}{},
Expand Down
2 changes: 2 additions & 0 deletions pkg/resources/external_table_grant.go
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ func CreateExternalTableGrant(d *schema.ResourceData, meta interface{}) error {
priv := d.Get("privilege").(string)
futureExternalTables := d.Get("on_future").(bool)
grantOption := d.Get("with_grant_option").(bool)
roles := expandStringList(d.Get("roles").(*schema.Set).List())

if (externalTableName == "") && !futureExternalTables {
return errors.New("external_table_name must be set unless on_future is true.")
Expand All @@ -124,6 +125,7 @@ func CreateExternalTableGrant(d *schema.ResourceData, meta interface{}) error {
ObjectName: externalTableName,
Privilege: priv,
GrantOption: grantOption,
Roles: roles,
}
dataIDInput, err := grant.String()
if err != nil {
Expand Down
2 changes: 1 addition & 1 deletion pkg/resources/external_table_grant_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ func TestExternalTableGrantCreate(t *testing.T) {
func TestExternalTableGrantRead(t *testing.T) {
r := require.New(t)

d := externalTableGrant(t, "test-db|PUBLIC|test-external-table|SELECT|false", map[string]interface{}{
d := externalTableGrant(t, "test-db|PUBLIC|test-external-table|SELECT||false", map[string]interface{}{
"external_table_name": "test-external-table",
"schema_name": "PUBLIC",
"database_name": "test-db",
Expand Down
2 changes: 2 additions & 0 deletions pkg/resources/file_format_grant.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ func CreateFileFormatGrant(d *schema.ResourceData, meta interface{}) error {
priv := d.Get("privilege").(string)
futureFileFormats := d.Get("on_future").(bool)
grantOption := d.Get("with_grant_option").(bool)
roles := expandStringList(d.Get("roles").(*schema.Set).List())

if (fileFormatName == "") && !futureFileFormats {
return errors.New("file_format_name must be set unless on_future is true.")
Expand All @@ -116,6 +117,7 @@ func CreateFileFormatGrant(d *schema.ResourceData, meta interface{}) error {
ObjectName: fileFormatName,
Privilege: priv,
GrantOption: grantOption,
Roles: roles,
}
dataIDInput, err := grant.String()
if err != nil {
Expand Down
2 changes: 1 addition & 1 deletion pkg/resources/file_format_grant_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ func TestFileFormatGrantCreate(t *testing.T) {
func TestFileFormatGrantRead(t *testing.T) {
r := require.New(t)

d := fileFormatGrant(t, "test-db|PUBLIC|test-file-format|USAGE|false", map[string]interface{}{
d := fileFormatGrant(t, "test-db|PUBLIC|test-file-format|USAGE||false", map[string]interface{}{
"file_format_name": "test-file-format",
"schema_name": "PUBLIC",
"database_name": "test-db",
Expand Down
2 changes: 2 additions & 0 deletions pkg/resources/function_grant.go
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,7 @@ func CreateFunctionGrant(d *schema.ResourceData, meta interface{}) error {
futureFunctions := d.Get("on_future").(bool)
grantOption := d.Get("with_grant_option").(bool)
arguments = d.Get("arguments").([]interface{})
roles := expandStringList(d.Get("roles").(*schema.Set).List())

if (functionName == "") && !futureFunctions {
return errors.New("function_name must be set unless on_future is true.")
Expand Down Expand Up @@ -169,6 +170,7 @@ func CreateFunctionGrant(d *schema.ResourceData, meta interface{}) error {
ObjectName: functionSignature,
Privilege: priv,
GrantOption: grantOption,
Roles: roles,
}
dataIDInput, err := grant.String()
if err != nil {
Expand Down
2 changes: 1 addition & 1 deletion pkg/resources/function_grant_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ func TestFunctionGrantCreate(t *testing.T) {
func TestFunctionGrantRead(t *testing.T) {
r := require.New(t)

d := functionGrant(t, "test-db|PUBLIC|test-function(A ARRAY, B STRING):STRING|USAGE|false", map[string]interface{}{
d := functionGrant(t, "test-db|PUBLIC|test-function(A ARRAY, B STRING):STRING|USAGE||false", map[string]interface{}{
"function_name": "test-function",
"arguments": []interface{}{map[string]interface{}{
"name": "a",
Expand Down
23 changes: 14 additions & 9 deletions pkg/resources/grant_helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,17 +81,19 @@ type grantID struct {
SchemaName string
ObjectName string
Privilege string
Roles []string
GrantOption bool
}

// String() takes in a grantID object and returns a pipe-delimited string:
// resourceName|schemaName|ObjectName|Privilege|GrantOption
// resourceName|schemaName|ObjectName|Privilege|Roles|GrantOption
func (gi *grantID) String() (string, error) {
var buf bytes.Buffer
csvWriter := csv.NewWriter(&buf)
csvWriter.Comma = grantIDDelimiter
grantOption := fmt.Sprintf("%v", gi.GrantOption)
dataIdentifiers := [][]string{{gi.ResourceName, gi.SchemaName, gi.ObjectName, gi.Privilege, grantOption}}
roles := strings.Join(gi.Roles, ",")
dataIdentifiers := [][]string{{gi.ResourceName, gi.SchemaName, gi.ObjectName, gi.Privilege, roles, grantOption}}
err := csvWriter.WriteAll(dataIdentifiers)
if err != nil {
return "", err
Expand All @@ -100,7 +102,7 @@ func (gi *grantID) String() (string, error) {
return strGrantID, nil
}

// grantIDFromString() takes in a pipe-delimited string: resourceName|schemaName|ObjectName|Privilege
// grantIDFromString() takes in a pipe-delimited string: resourceName|schemaName|ObjectName|Privilege|Roles
// and returns a grantID object
func grantIDFromString(stringID string) (*grantID, error) {
reader := csv.NewReader(strings.NewReader(stringID))
Expand All @@ -113,12 +115,12 @@ func grantIDFromString(stringID string) (*grantID, error) {
if len(lines) != 1 {
return nil, fmt.Errorf("1 line per grant")
}
if len(lines[0]) != 4 && len(lines[0]) != 5 {
return nil, fmt.Errorf("4 or 5 fields allowed")
if len(lines[0]) != 5 && len(lines[0]) != 6 {
return nil, fmt.Errorf("5 or 6 fields allowed")
}

grantOption := false
if len(lines[0]) == 5 && lines[0][4] == "true" {
if len(lines[0]) == 6 && lines[0][5] == "true" {
grantOption = true
}

Expand All @@ -127,6 +129,7 @@ func grantIDFromString(stringID string) (*grantID, error) {
SchemaName: lines[0][1],
ObjectName: lines[0][2],
Privilege: lines[0][3],
Roles: strings.Split(lines[0][4], ","),
GrantOption: grantOption,
}
return grantResult, nil
Expand Down Expand Up @@ -176,7 +179,7 @@ func createGenericGrant(d *schema.ResourceData, meta interface{}, builder snowfl
func readGenericGrant(
d *schema.ResourceData,
meta interface{},
schema map[string]*schema.Schema,
grantSchema map[string]*schema.Schema,
builder snowflake.GrantBuilder,
futureObjects bool,
validPrivileges PrivilegeSet) error {
Expand Down Expand Up @@ -243,11 +246,13 @@ func readGenericGrant(
}
}

existingRoles := d.Get("roles").(*schema.Set)
var roles, shares []string
// Now see which roles have our privilege
for roleName, privileges := range rolePrivileges {
// Where priv is not all so it should match exactly
if privileges.hasString(priv) {
// Match to currently assigned roles or let everything through if no specific role grants
if privileges.hasString(priv) && (existingRoles.Contains(roleName) || existingRoles.Len() == 0) {
roles = append(roles, roleName)
}
}
Expand All @@ -269,7 +274,7 @@ func readGenericGrant(
return err
}

_, sharesOk := schema["shares"]
_, sharesOk := grantSchema["shares"]
if sharesOk && !futureObjects {
err = d.Set("shares", shares)
if err != nil {
Expand Down
20 changes: 11 additions & 9 deletions pkg/resources/grant_helpers_internal_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import (
func TestGrantIDFromString(t *testing.T) {
r := require.New(t)
// Vanilla without GrantOption
id := "database_name|schema|view_name|privilege"
id := "database_name|schema|view_name|privilege|test1,test2"
grant, err := grantIDFromString(id)
r.NoError(err)

Expand All @@ -21,7 +21,7 @@ func TestGrantIDFromString(t *testing.T) {
r.Equal(false, grant.GrantOption)

// Vanilla with GrantOption
id = "database_name|schema|view_name|privilege|true"
id = "database_name|schema|view_name|privilege|test1,test2|true"
grant, err = grantIDFromString(id)
r.NoError(err)

Expand All @@ -44,17 +44,17 @@ func TestGrantIDFromString(t *testing.T) {
// Bad ID -- not enough fields
id = "database|name-privilege"
_, err = grantIDFromString(id)
r.Equal(fmt.Errorf("4 or 5 fields allowed"), err)
r.Equal(fmt.Errorf("5 or 6 fields allowed"), err)

// Bad ID -- privilege in wrong area
id = "database||name-privilege"
_, err = grantIDFromString(id)
r.Equal(fmt.Errorf("4 or 5 fields allowed"), err)
r.Equal(fmt.Errorf("5 or 6 fields allowed"), err)

// too many fields
id = "database_name|schema|view_name|privilege|false|2"
id = "database_name|schema|view_name|privilege|false|2|too-many"
_, err = grantIDFromString(id)
r.Equal(fmt.Errorf("4 or 5 fields allowed"), err)
r.Equal(fmt.Errorf("5 or 6 fields allowed"), err)

// 0 lines
id = ""
Expand All @@ -70,31 +70,32 @@ func TestGrantIDFromString(t *testing.T) {

func TestGrantStruct(t *testing.T) {
r := require.New(t)

// Vanilla
grant := &grantID{
ResourceName: "database_name",
SchemaName: "schema",
ObjectName: "view_name",
Privilege: "priv",
Roles: []string{"test1", "test2"},
GrantOption: true,
}
gID, err := grant.String()
r.NoError(err)
r.Equal("database_name|schema|view_name|priv|true", gID)
r.Equal("database_name|schema|view_name|priv|test1,test2|true", gID)

// Empty grant
grant = &grantID{}
gID, err = grant.String()
r.NoError(err)
r.Equal("||||false", gID)
r.Equal("|||||false", gID)

// Grant with extra delimiters
grant = &grantID{
ResourceName: "database|name",
SchemaName: "schema|name",
ObjectName: "view|name",
Privilege: "priv",
Roles: []string{"test3", "test4"},
GrantOption: false,
}
gID, err = grant.String()
Expand All @@ -105,5 +106,6 @@ func TestGrantStruct(t *testing.T) {
r.Equal("schema|name", newGrant.SchemaName)
r.Equal("view|name", newGrant.ObjectName)
r.Equal("priv", newGrant.Privilege)
r.Equal([]string{"test3", "test4"}, newGrant.Roles)
r.Equal(false, newGrant.GrantOption)
}
3 changes: 3 additions & 0 deletions pkg/resources/integration_grant.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,8 @@ func CreateIntegrationGrant(d *schema.ResourceData, meta interface{}) error {
w := d.Get("integration_name").(string)
priv := d.Get("privilege").(string)
grantOption := d.Get("with_grant_option").(bool)
roles := expandStringList(d.Get("roles").(*schema.Set).List())

builder := snowflake.IntegrationGrant(w)

err := createGenericGrant(d, meta, builder)
Expand All @@ -74,6 +76,7 @@ func CreateIntegrationGrant(d *schema.ResourceData, meta interface{}) error {
ResourceName: w,
Privilege: priv,
GrantOption: grantOption,
Roles: roles,
}
dataIDInput, err := grant.String()
if err != nil {
Expand Down
2 changes: 1 addition & 1 deletion pkg/resources/integration_grant_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ func TestIntegrationGrantCreate(t *testing.T) {
func TestIntegrationGrantRead(t *testing.T) {
r := require.New(t)

d := integrationGrant(t, "test-integration|||IMPORTED PRIVILIGES|false", map[string]interface{}{
d := integrationGrant(t, "test-integration|||IMPORTED PRIVILIGES||false", map[string]interface{}{
"integration_name": "test-integration",
"privilege": "IMPORTED PRIVILIGES",
"roles": []interface{}{"test-role-1", "test-role-2"},
Expand Down
2 changes: 2 additions & 0 deletions pkg/resources/masking_policy_grant.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ func CreateMaskingPolicyGrant(d *schema.ResourceData, meta interface{}) error {
schemaName := d.Get("schema_name").(string)
priv := d.Get("privilege").(string)
grantOption := d.Get("with_grant_option").(bool)
roles := expandStringList(d.Get("roles").(*schema.Set).List())

builder := snowflake.MaskingPolicyGrant(dbName, schemaName, maskingPolicyName)

Expand All @@ -95,6 +96,7 @@ func CreateMaskingPolicyGrant(d *schema.ResourceData, meta interface{}) error {
ObjectName: maskingPolicyName,
Privilege: priv,
GrantOption: grantOption,
Roles: roles,
}
dataIDInput, err := grant.String()
if err != nil {
Expand Down
2 changes: 1 addition & 1 deletion pkg/resources/masking_policy_grant_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ func TestMaskingPolicyGrantCreate(t *testing.T) {
func TestMaskingPolicyGrantRead(t *testing.T) {
r := require.New(t)

d := maskingPolicyGrant(t, "test-db|PUBLIC|test-masking-policy|APPLY|false", map[string]interface{}{
d := maskingPolicyGrant(t, "test-db|PUBLIC|test-masking-policy|APPLY||false", map[string]interface{}{
"masking_policy_name": "test-masking-policy",
"schema_name": "PUBLIC",
"database_name": "test-db",
Expand Down
2 changes: 2 additions & 0 deletions pkg/resources/materialized_view_grant.go
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ func CreateMaterializedViewGrant(d *schema.ResourceData, meta interface{}) error
priv := d.Get("privilege").(string)
futureMaterializedViews := d.Get("on_future").(bool)
grantOption := d.Get("with_grant_option").(bool)
roles := expandStringList(d.Get("roles").(*schema.Set).List())

if (schemaName == "") && !futureMaterializedViews {
return errors.New("schema_name must be set unless on_future is true.")
Expand Down Expand Up @@ -134,6 +135,7 @@ func CreateMaterializedViewGrant(d *schema.ResourceData, meta interface{}) error
ObjectName: materializedViewName,
Privilege: priv,
GrantOption: grantOption,
Roles: roles,
}
dataIDInput, err := grant.String()
if err != nil {
Expand Down
Loading

0 comments on commit 7ac4d54

Please sign in to comment.