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

feat: snowflake_user_ownership_grant resource #969

Merged
merged 5 commits into from
Apr 11, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
28 changes: 28 additions & 0 deletions docs/resources/user_ownership_grant.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
---
# generated by https://github.com/hashicorp/terraform-plugin-docs
page_title: "snowflake_user_ownership_grant Resource - terraform-provider-snowflake"
subcategory: ""
description: |-

---

# snowflake_user_ownership_grant (Resource)





<!-- schema generated by tfplugindocs -->
## Schema

### Required

- **on_user_name** (String) The name of the user ownership is granted on.
- **to_role_name** (String) The name of the role to grant ownership. Please ensure that the role that terraform is using is granted access.

### Optional

- **current_grants** (String) Specifies whether to remove or transfer all existing outbound privileges on the object when ownership is transferred to a new role.
- **id** (String) The ID of this resource.


4 changes: 2 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ require (
github.com/stretchr/testify v1.7.0
github.com/youmark/pkcs8 v0.0.0-20201027041543-1326539a0a0a
golang.org/x/crypto v0.0.0-20220307211146-efcb8507fb70
golang.org/x/tools v0.1.9
golang.org/x/tools v0.1.10
)

require (
Expand Down Expand Up @@ -111,7 +111,7 @@ require (
github.com/vmihailenco/tagparser v0.1.2 // indirect
github.com/zclconf/go-cty v1.10.0 // indirect
go.opencensus.io v0.23.0 // indirect
golang.org/x/mod v0.5.1 // indirect
golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3 // indirect
golang.org/x/net v0.0.0-20220225172249-27dd8689420f // indirect
golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b // indirect
golang.org/x/sys v0.0.0-20220307203707-22a9840ba4d7 // indirect
Expand Down
8 changes: 4 additions & 4 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -642,8 +642,8 @@ golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.5.1 h1:OJxoQ/rynoF0dcCdI7cLPktw/hR2cueqYfjm43oqK38=
golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro=
golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3 h1:kQgndtyPBW/JIYERgdxfwMYh3AVStj88WQTlNDi2a+o=
golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY=
golang.org/x/net v0.0.0-20180530234432-1e491301e022/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180811021610-c39426892332/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
Expand Down Expand Up @@ -861,8 +861,8 @@ golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.9 h1:j9KsMiaP1c3B0OTQGth0/k+miLGTgLsAFUCrF2vLcF8=
golang.org/x/tools v0.1.9/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU=
golang.org/x/tools v0.1.10 h1:QjFRCZxdOhBJ/UNgnBZLbNV13DlbnK0quyivTnXJM20=
golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
Expand Down
1 change: 1 addition & 0 deletions pkg/provider/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,7 @@ func getResources() map[string]*schema.Resource {
"snowflake_tag": resources.Tag(),
"snowflake_task": resources.Task(),
"snowflake_user": resources.User(),
"snowflake_user_ownership_grant": resources.UserOwnershipGrant(),
"snowflake_user_public_keys": resources.UserPublicKeys(),
"snowflake_view": resources.View(),
"snowflake_warehouse": resources.Warehouse(),
Expand Down
2 changes: 1 addition & 1 deletion pkg/resources/grant_helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ func grantIDFromString(stringID string) (*grantID, error) {
} else if len(lines[0]) == 5 && lines[0][4] == "true" {
grantOption = true
}

schemaName := ""
objectName := ""
privilege := ""
Expand Down
2 changes: 0 additions & 2 deletions pkg/resources/grant_helpers_internal_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,6 @@ func TestGrantIDFromString(t *testing.T) {
_, err = grantIDFromString(id)
r.Equal(fmt.Errorf("1 to 6 fields allowed in ID"), err)


// 0 lines
id = ""
_, err = grantIDFromString(id)
Expand Down Expand Up @@ -169,4 +168,3 @@ func TestGrantIDFromStringRoleGrant(t *testing.T) {
r.Equal([]string{"role3", "role4"}, grant.Roles)
r.Equal(false, grant.GrantOption)
}

8 changes: 8 additions & 0 deletions pkg/resources/helpers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,14 @@ func roleGrants(t *testing.T, id string, params map[string]interface{}) *schema.
return d
}

func userOwnershipGrant(t *testing.T, id string, params map[string]interface{}) *schema.ResourceData {
r := require.New(t)
d := schema.TestResourceDataRaw(t, resources.UserOwnershipGrant().Schema, params)
r.NotNil(d)
d.SetId(id)
return d
}

func roleOwnershipGrant(t *testing.T, id string, params map[string]interface{}) *schema.ResourceData {
r := require.New(t)
d := schema.TestResourceDataRaw(t, resources.RoleOwnershipGrant().Schema, params)
Expand Down
152 changes: 152 additions & 0 deletions pkg/resources/user_ownership_grant.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
package resources

import (
"database/sql"
"fmt"
"log"
"strings"

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

var userOwnershipGrantSchema = map[string]*schema.Schema{
"on_user_name": {
Type: schema.TypeString,
Elem: &schema.Schema{Type: schema.TypeString},
Required: true,
Description: "The name of the user ownership is granted on.",
ValidateFunc: func(val interface{}, key string) ([]string, []error) {
return snowflake.ValidateIdentifier(val)
},
},
"to_role_name": {
Type: schema.TypeString,
Elem: &schema.Schema{Type: schema.TypeString},
Required: true,
Description: "The name of the role to grant ownership. Please ensure that the role that terraform is using is granted access.",
ValidateFunc: func(val interface{}, key string) ([]string, []error) {
return snowflake.ValidateIdentifier(val)
},
},
"current_grants": {
Type: schema.TypeString,
Elem: &schema.Schema{Type: schema.TypeString},
Optional: true,
Description: "Specifies whether to remove or transfer all existing outbound privileges on the object when ownership is transferred to a new role.",
Default: "COPY",
ValidateFunc: validation.StringInSlice([]string{
"COPY",
"REVOKE",
}, true),
},
}

func UserOwnershipGrant() *schema.Resource {
return &schema.Resource{
Create: CreateUserOwnershipGrant,
Read: ReadUserOwnershipGrant,
Delete: DeleteUserOwnershipGrant,
Update: UpdateUserOwnershipGrant,
Schema: userOwnershipGrantSchema,
Importer: &schema.ResourceImporter{
StateContext: schema.ImportStatePassthroughContext,
},
}
}

func CreateUserOwnershipGrant(d *schema.ResourceData, meta interface{}) error {
db := meta.(*sql.DB)
user := d.Get("on_user_name").(string)
role := d.Get("to_role_name").(string)
currentGrants := d.Get("current_grants").(string)

g := snowflake.UserOwnershipGrant(user, currentGrants)
err := snowflake.Exec(db, g.Role(role).Grant())
if err != nil {
return err
}

d.SetId(fmt.Sprintf(`%s|%s|%s`, user, role, currentGrants))

return ReadUserOwnershipGrant(d, meta)
}

func ReadUserOwnershipGrant(d *schema.ResourceData, meta interface{}) error {
db := meta.(*sql.DB)
log.Println(d.Id())
user := strings.Split(d.Id(), "|")[0]
currentGrants := strings.Split(d.Id(), "|")[2]

stmt := fmt.Sprintf("SHOW USERS LIKE '%s'", user)
row := snowflake.QueryRow(db, stmt)

grant, err := snowflake.ScanUserOwnershipGrant(row)
if err == sql.ErrNoRows {
// If not found, mark resource to be removed from statefile during apply or refresh
log.Printf("[DEBUG] user (%s) not found", d.Id())
d.SetId("")
return nil
}
if err != nil {
return err
}

if user != grant.Name.String {
return fmt.Errorf("no user found like '%s'", user)
}

grant.Name.String = strings.TrimPrefix(grant.Name.String, `"`)
grant.Name.String = strings.TrimSuffix(grant.Name.String, `"`)
err = d.Set("on_user_name", grant.Name.String)
if err != nil {
return err
}

grant.Owner.String = strings.TrimPrefix(grant.Owner.String, `"`)
grant.Owner.String = strings.TrimSuffix(grant.Owner.String, `"`)
err = d.Set("to_role_name", grant.Owner.String)
if err != nil {
return err
}

err = d.Set("current_grants", currentGrants)
if err != nil {
return err
}

return nil
}

func UpdateUserOwnershipGrant(d *schema.ResourceData, meta interface{}) error {
db := meta.(*sql.DB)
user := d.Get("on_user_name").(string)
role := d.Get("to_role_name").(string)
currentGrants := d.Get("current_grants").(string)

d.SetId(fmt.Sprintf(`%s|%s|%s`, user, role, currentGrants))

g := snowflake.UserOwnershipGrant(user, currentGrants)
err := snowflake.Exec(db, g.Role(role).Grant())
if err != nil {
return err
}

return ReadUserOwnershipGrant(d, meta)
}

func DeleteUserOwnershipGrant(d *schema.ResourceData, meta interface{}) error {
db := meta.(*sql.DB)
user := d.Get("on_user_name").(string)
currentGrants := d.Get("current_grants").(string)

g := snowflake.UserOwnershipGrant(user, currentGrants)
err := snowflake.Exec(db, g.Role("ACCOUNTADMIN").Revoke())
if err != nil {
return err
}

d.SetId("")
return nil
}
57 changes: 57 additions & 0 deletions pkg/resources/user_ownership_grant_acceptance_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package resources_test

import (
"fmt"
"testing"

"github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
)

func TestAccUserOwnershipGrant_defaults(t *testing.T) {
user := acctest.RandStringFromCharSet(10, acctest.CharSetAlpha)
role := acctest.RandStringFromCharSet(10, acctest.CharSetAlpha)

resource.ParallelTest(t, resource.TestCase{
Providers: providers(),
Steps: []resource.TestStep{
{
Config: userOwnershipGrantConfig(user, role),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr("snowflake_user_ownership_grant.grant", "on_user_name", user),
resource.TestCheckResourceAttr("snowflake_user_ownership_grant.grant", "to_role_name", role),
resource.TestCheckResourceAttr("snowflake_user_ownership_grant.grant", "current_grants", "COPY"),
),
},
},
})
}

func userOwnershipGrantConfig(user, role string) string {
return fmt.Sprintf(`

resource "snowflake_user" "user" {
name = "%v"
}

resource "snowflake_role" "role" {
name = "%v"
}

resource "snowflake_role_grants" "grants" {
role_name = snowflake_role.role.name

roles = [
"ACCOUNTADMIN",
]
}

resource "snowflake_user_ownership_grant" "grant" {
on_user_name = snowflake_user.user.name

to_role_name = snowflake_role.role.name

current_grants = "COPY"
}
`, user, role)
}
Loading