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: create snowflake_role_ownership_grant resource #917

Merged
merged 19 commits into from
Mar 21, 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/role_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_role_ownership_grant Resource - terraform-provider-snowflake"
subcategory: ""
description: |-

---

# snowflake_role_ownership_grant (Resource)





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

### Required

- **on_role_name** (String) The name of the role 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.


Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
terraform import snowflake_role_ownership_grant.example rolename
24 changes: 24 additions & 0 deletions examples/resources/snowflake_role_ownership_grants/resource.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
resource "snowflake_role" "role" {
name = "rking_test_role"
comment = "for testing"
}

resource "snowflake_role" "other_role" {
name = "rking_test_role2"
}

# ensure the Terraform user inherits ownership privileges for the rking_test_role role
# otherwise Terraform will fail to destroy the rking_test_role2 role due to insufficient privileges
resource "snowflake_role_grants" "grants" {
role_name = snowflake_role.role.name

roles = [
"ACCOUNTADMIN",
]
}

resource "snowflake_role_ownership_grant" "grant" {
on_role_name = snowflake_role.role.name
to_role_name = snowflake_role.other_role.name
current_grants = "COPY"
}
1 change: 1 addition & 0 deletions pkg/provider/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,7 @@ func getResources() map[string]*schema.Resource {
"snowflake_resource_monitor": resources.ResourceMonitor(),
"snowflake_role": resources.Role(),
"snowflake_role_grants": resources.RoleGrants(),
"snowflake_role_ownership_grant": resources.RoleOwnershipGrant(),
"snowflake_row_access_policy": resources.RowAccessPolicy(),
"snowflake_saml_integration": resources.SAMLIntegration(),
"snowflake_schema": resources.Schema(),
Expand Down
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 roleOwnershipGrant(t *testing.T, id string, params map[string]interface{}) *schema.ResourceData {
r := require.New(t)
d := schema.TestResourceDataRaw(t, resources.RoleOwnershipGrant().Schema, params)
r.NotNil(d)
d.SetId(id)
return d
}

func apiIntegration(t *testing.T, id string, params map[string]interface{}) *schema.ResourceData {
r := require.New(t)
d := schema.TestResourceDataRaw(t, resources.APIIntegration().Schema, params)
Expand Down
152 changes: 152 additions & 0 deletions pkg/resources/role_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 roleOwnershipGrantSchema = map[string]*schema.Schema{
"on_role_name": {
Type: schema.TypeString,
Elem: &schema.Schema{Type: schema.TypeString},
Required: true,
Description: "The name of the role 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 RoleOwnershipGrant() *schema.Resource {
return &schema.Resource{
Create: CreateRoleOwnershipGrant,
Read: ReadRoleOwnershipGrant,
Delete: DeleteRoleOwnershipGrant,
Update: UpdateRoleOwnershipGrant,
Schema: roleOwnershipGrantSchema,
Importer: &schema.ResourceImporter{
StateContext: schema.ImportStatePassthroughContext,
},
}
}

func CreateRoleOwnershipGrant(d *schema.ResourceData, meta interface{}) error {
db := meta.(*sql.DB)
onRoleName := d.Get("on_role_name").(string)
toRoleName := d.Get("to_role_name").(string)
currentGrants := d.Get("current_grants").(string)

g := snowflake.RoleOwnershipGrant(onRoleName, currentGrants)
err := snowflake.Exec(db, g.Role(toRoleName).Grant())
if err != nil {
return err
}

d.SetId(fmt.Sprintf(`%s|%s|%s`, onRoleName, toRoleName, currentGrants))

return ReadRoleOwnershipGrant(d, meta)
}

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

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

grant, err := snowflake.ScanRoleOwnershipGrant(row)
if 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())
d.SetId("")
return nil
}
if err != nil {
return err
}

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

grant.Name.String = strings.TrimPrefix(grant.Name.String, `"`)
grant.Name.String = strings.TrimSuffix(grant.Name.String, `"`)
err = d.Set("on_role_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 UpdateRoleOwnershipGrant(d *schema.ResourceData, meta interface{}) error {
db := meta.(*sql.DB)
onRoleName := d.Get("on_role_name").(string)
toRoleName := d.Get("to_role_name").(string)
currentGrants := d.Get("current_grants").(string)

d.SetId(fmt.Sprintf(`%s|%s|%s`, onRoleName, toRoleName, currentGrants))

g := snowflake.RoleOwnershipGrant(onRoleName, currentGrants)
err := snowflake.Exec(db, g.Role(toRoleName).Grant())
if err != nil {
return err
}

return ReadRoleOwnershipGrant(d, meta)
}

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

g := snowflake.RoleOwnershipGrant(onRoleName, 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/role_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 TestAccRoleOwnershipGrant_defaults(t *testing.T) {
onRoleName := acctest.RandStringFromCharSet(10, acctest.CharSetAlpha)
toRoleName := acctest.RandStringFromCharSet(10, acctest.CharSetAlpha)

resource.ParallelTest(t, resource.TestCase{
Providers: providers(),
Steps: []resource.TestStep{
{
Config: roleOwnershipGrantConfig(onRoleName, toRoleName),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr("snowflake_role_ownership_grant.grant", "on_role_name", onRoleName),
resource.TestCheckResourceAttr("snowflake_role_ownership_grant.grant", "to_role_name", toRoleName),
resource.TestCheckResourceAttr("snowflake_role_ownership_grant.grant", "current_grants", "COPY"),
),
},
},
})
}

func roleOwnershipGrantConfig(onRoleName, toRoleName string) string {
return fmt.Sprintf(`

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

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

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

roles = [
"ACCOUNTADMIN",
]
}

resource "snowflake_role_ownership_grant" "grant" {
on_role_name = snowflake_role.role.name

to_role_name = snowflake_role.other_role.name

current_grants = "COPY"
}
`, onRoleName, toRoleName)
}
84 changes: 84 additions & 0 deletions pkg/resources/role_ownership_grants_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
package resources_test

import (
"database/sql"
"testing"

sqlmock "github.com/DATA-DOG/go-sqlmock"
"github.com/chanzuckerberg/terraform-provider-snowflake/pkg/provider"
"github.com/chanzuckerberg/terraform-provider-snowflake/pkg/resources"
. "github.com/chanzuckerberg/terraform-provider-snowflake/pkg/testhelpers"
"github.com/stretchr/testify/require"
)

func TestRoleOwnershipGrant(t *testing.T) {
r := require.New(t)
err := resources.RoleOwnershipGrant().InternalValidate(provider.Provider().Schema, true)
r.NoError(err)
}

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

d := roleOwnershipGrant(t, "good_name", map[string]interface{}{
"on_role_name": "good_name",
"to_role_name": "other_good_name",
"current_grants": "COPY",
})

WithMockDb(t, func(db *sql.DB, mock sqlmock.Sqlmock) {
mock.ExpectExec(`GRANT OWNERSHIP ON ROLE "good_name" TO ROLE "other_good_name" COPY CURRENT GRANTS`).WillReturnResult(sqlmock.NewResult(1, 1))
expectReadRoleOwnershipGrant(mock)
err := resources.CreateRoleOwnershipGrant(d, db)
r.NoError(err)
})
}

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

d := roleOwnershipGrant(t, "good_name|other_good_name|COPY", map[string]interface{}{
"on_role_name": "good_name",
"to_role_name": "other_good_name",
"current_grants": "COPY",
})

WithMockDb(t, func(db *sql.DB, mock sqlmock.Sqlmock) {
expectReadRoleOwnershipGrant(mock)
err := resources.ReadRoleOwnershipGrant(d, db)
r.NoError(err)
})
}

func expectReadRoleOwnershipGrant(mock sqlmock.Sqlmock) {
rows := sqlmock.NewRows([]string{
"created_on",
"name",
"is_default",
"is_current",
"is_inherited",
"assigned_to_users",
"granted_to_roles",
"granted_roles",
"owner",
"comment",
}).AddRow("_", "good_name", "", "", "", "", "", "", "other_good_name", "")
mock.ExpectQuery(`SHOW ROLES LIKE 'good_name'`).WillReturnRows(rows)
}

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

d := roleOwnershipGrant(t, "good_name|other_good_name|COPY", map[string]interface{}{
"on_role_name": "good_name",
"to_role_name": "other_good_name",
"current_grants": "COPY",
})

WithMockDb(t, func(db *sql.DB, mock sqlmock.Sqlmock) {

mock.ExpectExec(`GRANT OWNERSHIP ON ROLE "good_name" TO ROLE "ACCOUNTADMIN" COPY CURRENT GRANTS`).WillReturnResult(sqlmock.NewResult(1, 1))
err := resources.DeleteRoleOwnershipGrant(d, db)
r.NoError(err)
})
}
Loading