Skip to content

Commit

Permalink
Merge pull request #13 from Viostream/feature/warehouse-grant
Browse files Browse the repository at this point in the history
Feature/warehouse grant
  • Loading branch information
sjauld committed Jul 3, 2019
2 parents 1f2ef61 + 5443ee7 commit 8528953
Show file tree
Hide file tree
Showing 9 changed files with 271 additions and 11 deletions.
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,16 @@ You can see a number of examples [here](examples).
| scaling_policy | string | Specifies the policy for automatically starting and shutting down clusters in a multi-cluster warehouse running in Auto-scale mode. | true | false | true | <nil> |
| wait_for_provisioning | bool | Specifies whether the warehouse, after being resized, waits for all the servers to provision before executing any queued or new queries. | true | false | false | <nil> |
| warehouse_size | string | | true | false | true | <nil> |

### snowflake_warehouse_grant

#### properties

| NAME | TYPE | DESCRIPTION | OPTIONAL | REQUIRED | COMPUTED | DEFAULT |
|----------------|--------|---------------------------------------------------------|----------|-----------|----------|---------|
| privilege | string | The privilege to grant on the warehouse. | true | false | false | "USAGE" |
| roles | set | Grants privilege to these roles. | true | false | false | <nil> |
| warehouse_name | string | The name of the warehouse on which to grant privileges. | false | true | false | <nil> |
<!-- END -->

## Development
Expand Down
1 change: 1 addition & 0 deletions pkg/provider/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ func Provider() *schema.Provider {
"snowflake_view": resources.View(),
"snowflake_view_grant": resources.ViewGrant(),
"snowflake_warehouse": resources.Warehouse(),
"snowflake_warehouse_grant": resources.WarehouseGrant(),
},
DataSourcesMap: map[string]*schema.Resource{},
ConfigureFunc: ConfigureProvider,
Expand Down
5 changes: 4 additions & 1 deletion pkg/resources/database_grant.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,10 @@ import (
"github.com/chanzuckerberg/terraform-provider-snowflake/pkg/snowflake"
)

var validDatabasePrivileges = []string{"USAGE", "REFERENCE_USAGE"}
var validDatabasePrivileges = []string{
"ALL", "CREATE SCHEMA", "IMPORTED PRIVILEGES", "MODIFY", "MONITOR",
"OWNERSHIP", "REFERENCE_USAGE", "USAGE",
}

var databaseGrantSchema = map[string]*schema.Schema{
"database_name": &schema.Schema{
Expand Down
29 changes: 22 additions & 7 deletions pkg/resources/grant_helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,14 @@ func createGenericGrant(data *schema.ResourceData, meta interface{}, builder *sn
db := meta.(*sql.DB)

priv := data.Get("privilege").(string)
roles := expandStringList(data.Get("roles").(*schema.Set).List())
shares := expandStringList(data.Get("shares").(*schema.Set).List())
var roles, shares []string
if _, ok := data.GetOk("roles"); ok {
roles = expandStringList(data.Get("roles").(*schema.Set).List())
}

if _, ok := data.GetOk("shares"); ok {
shares = expandStringList(data.Get("shares").(*schema.Set).List())
}

if len(roles)+len(shares) == 0 {
return fmt.Errorf("no roles or shares specified for this grant")
Expand Down Expand Up @@ -73,8 +79,7 @@ func readGenericGrant(data *schema.ResourceData, meta interface{}, builder *snow
}
priv := data.Get("privilege").(string)

var roles []string
var shares []string
var roles, shares []string

for _, grant := range grants {
if grant.Privilege != priv {
Expand Down Expand Up @@ -102,7 +107,10 @@ func readGenericGrant(data *schema.ResourceData, meta interface{}, builder *snow
}
err = data.Set("shares", shares)
if err != nil {
return err
// warehouses don't use shares - check for this error
if !strings.HasPrefix(err.Error(), "Invalid address to set") {
return err
}
}

return nil
Expand Down Expand Up @@ -135,8 +143,15 @@ func deleteGenericGrant(data *schema.ResourceData, meta interface{}, builder *sn
db := meta.(*sql.DB)

priv := data.Get("privilege").(string)
roles := expandStringList(data.Get("roles").(*schema.Set).List())
shares := expandStringList(data.Get("shares").(*schema.Set).List())

var roles, shares []string
if _, ok := data.GetOk("roles"); ok {
roles = expandStringList(data.Get("roles").(*schema.Set).List())
}

if _, ok := data.GetOk("shares"); ok {
shares = expandStringList(data.Get("shares").(*schema.Set).List())
}

for _, role := range roles {
err := DBExec(db, builder.Role(role).Revoke(priv))
Expand Down
101 changes: 101 additions & 0 deletions pkg/resources/warehouse_grant.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
package resources

import (
"fmt"

"github.com/hashicorp/terraform/helper/schema"
"github.com/hashicorp/terraform/helper/validation"

"github.com/chanzuckerberg/terraform-provider-snowflake/pkg/snowflake"
)

var validWarehousePrivileges = []string{
"ALL", "MODIFY", "MONITOR", "OPERATE", "OWNERSHIP", "USAGE",
}

var warehouseGrantSchema = map[string]*schema.Schema{
"warehouse_name": &schema.Schema{
Type: schema.TypeString,
Required: true,
Description: "The name of the warehouse on which to grant privileges.",
ForceNew: true,
},
"privilege": &schema.Schema{
Type: schema.TypeString,
Optional: true,
Description: "The privilege to grant on the warehouse.",
Default: "USAGE",
ValidateFunc: validation.StringInSlice(validWarehousePrivileges, true),
ForceNew: true,
},
"roles": &schema.Schema{
Type: schema.TypeSet,
Elem: &schema.Schema{Type: schema.TypeString},
Optional: true,
Description: "Grants privilege to these roles.",
ForceNew: true,
},
}

// WarehouseGrant returns a pointer to the resource representing a view grant
func WarehouseGrant() *schema.Resource {
return &schema.Resource{
Create: CreateWarehouseGrant,
Read: ReadWarehouseGrant,
Delete: DeleteWarehouseGrant,

Schema: warehouseGrantSchema,
Importer: &schema.ResourceImporter{
State: schema.ImportStatePassthrough,
},
}
}

// CreateWarehouseGrant implements schema.CreateFunc
func CreateWarehouseGrant(data *schema.ResourceData, meta interface{}) error {
w := data.Get("warehouse_name").(string)
priv := data.Get("privilege").(string)
builder := snowflake.WarehouseGrant(w)

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

// ID format is <warehouse_name>|||<privilege>
data.SetId(fmt.Sprintf("%v|||%v", w, priv))

return ReadWarehouseGrant(data, meta)
}

// ReadWarehouseGrant implements schema.ReadFunc
func ReadWarehouseGrant(data *schema.ResourceData, meta interface{}) error {
w, _, _, priv, err := splitGrantID(data.Id())
if err != nil {
return err
}
err = data.Set("warehouse_name", w)
if err != nil {
return err
}
err = data.Set("privilege", priv)
if err != nil {
return err
}

builder := snowflake.WarehouseGrant(w)

return readGenericGrant(data, meta, builder)
}

// DeleteWarehouseGrant implements schema.DeleteFunc
func DeleteWarehouseGrant(data *schema.ResourceData, meta interface{}) error {
w, _, _, _, err := splitGrantID(data.Id())
if err != nil {
return err
}

builder := snowflake.WarehouseGrant(w)

return deleteGenericGrant(data, meta, builder)
}
51 changes: 51 additions & 0 deletions pkg/resources/warehouse_grant_acceptance_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package resources_test

import (
"fmt"
"testing"

"github.com/hashicorp/terraform/helper/acctest"
"github.com/hashicorp/terraform/helper/resource"
)

func TestAccWarehouseGrant(t *testing.T) {
wName := acctest.RandStringFromCharSet(10, acctest.CharSetAlpha)
roleName := acctest.RandStringFromCharSet(10, acctest.CharSetAlpha)

resource.Test(t, resource.TestCase{
Providers: providers(),
Steps: []resource.TestStep{
{
Config: warehouseGrantConfig(wName, roleName),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr("snowflake_warehouse_grant.test", "warehouse_name", wName),
resource.TestCheckResourceAttr("snowflake_warehouse_grant.test", "privilege", "USAGE"),
),
},
// IMPORT
{
ResourceName: "snowflake_warehouse_grant.test",
ImportState: true,
ImportStateVerify: true,
},
},
})
}

func warehouseGrantConfig(n, role string) string {
return fmt.Sprintf(`
resource "snowflake_warehouse" "test" {
name = "%v"
}
resource "snowflake_role" "test" {
name = "%v"
}
resource "snowflake_warehouse_grant" "test" {
warehouse_name = snowflake_warehouse.test.name
roles = [snowflake_role.test.name]
}
`, n, role)
}
54 changes: 54 additions & 0 deletions pkg/resources/warehouse_grant_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package resources_test

import (
"database/sql"
"testing"
"time"

sqlmock "github.com/DATA-DOG/go-sqlmock"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"

"github.com/hashicorp/terraform/helper/schema"

"github.com/chanzuckerberg/terraform-provider-snowflake/pkg/provider"
"github.com/chanzuckerberg/terraform-provider-snowflake/pkg/resources"
. "github.com/chanzuckerberg/terraform-provider-snowflake/pkg/testhelpers"
)

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

func TestWarehouseGrantCreate(t *testing.T) {
a := assert.New(t)

in := map[string]interface{}{
"warehouse_name": "test-warehouse",
"privilege": "USAGE",
"roles": []string{"test-role-1", "test-role-2"},
}
d := schema.TestResourceDataRaw(t, resources.WarehouseGrant().Schema, in)
a.NotNil(d)

WithMockDb(t, func(db *sql.DB, mock sqlmock.Sqlmock) {
mock.ExpectExec(`^GRANT USAGE ON WAREHOUSE "test-warehouse" TO ROLE "test-role-1"$`).WillReturnResult(sqlmock.NewResult(1, 1))
mock.ExpectExec(`^GRANT USAGE ON WAREHOUSE "test-warehouse" TO ROLE "test-role-2"$`).WillReturnResult(sqlmock.NewResult(1, 1))
expectReadWarehouseGrant(mock)
err := resources.CreateWarehouseGrant(d, db)
a.NoError(err)
})
}

func expectReadWarehouseGrant(mock sqlmock.Sqlmock) {
rows := sqlmock.NewRows([]string{
"created_on", "privilege", "granted_on", "name", "granted_to", "grantee_name", "grant_option", "granted_by",
}).AddRow(
time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC), "USAGE", "WAREHOUSE", "test-warehouse", "ROLE", "test-role-1", false, "bob",
).AddRow(
time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC), "USAGE", "WAREHOUSE", "test-warehouse", "ROLE", "test-role-2", false, "bob",
)
mock.ExpectQuery(`^SHOW GRANTS ON WAREHOUSE "test-warehouse"$`).WillReturnRows(rows)
}
16 changes: 13 additions & 3 deletions pkg/snowflake/grant.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,10 @@ import (
type grantType string

const (
databaseType grantType = "DATABASE"
schemaType grantType = "SCHEMA"
viewType grantType = "VIEW"
databaseType grantType = "DATABASE"
schemaType grantType = "SCHEMA"
viewType grantType = "VIEW"
warehouseType grantType = "WAREHOUSE"
)

// GrantBuilder abstracts the creation of GrantExecutables
Expand Down Expand Up @@ -51,6 +52,15 @@ func ViewGrant(db, schema, view string) *GrantBuilder {
}
}

// WarehouseGrant returns a pointer to a GrantBuilder for a warehouse
func WarehouseGrant(w string) *GrantBuilder {
return &GrantBuilder{
name: w,
qualifiedName: fmt.Sprintf(`"%v"`, w),
grantType: warehouseType,
}
}

// Show returns the SQL that will show all privileges on the grant
func (gb *GrantBuilder) Show() string {
return fmt.Sprintf(`SHOW GRANTS ON %v %v`, gb.grantType, gb.qualifiedName)
Expand Down
15 changes: 15 additions & 0 deletions pkg/snowflake/grant_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,21 @@ func TestViewGrant(t *testing.T) {
a.Equal(`REVOKE USAGE ON VIEW "test_db"."PUBLIC"."testView" FROM SHARE "bob"`, s)
}

func TestWarehouseGrant(t *testing.T) {
a := assert.New(t)
wg := snowflake.WarehouseGrant("test_warehouse")
a.Equal(wg.Name(), "test_warehouse")

s := wg.Show()
a.Equal(`SHOW GRANTS ON WAREHOUSE "test_warehouse"`, s)

s = wg.Role("bob").Grant("USAGE")
a.Equal(`GRANT USAGE ON WAREHOUSE "test_warehouse" TO ROLE "bob"`, s)

s = wg.Role("bob").Revoke("USAGE")
a.Equal(`REVOKE USAGE ON WAREHOUSE "test_warehouse" FROM ROLE "bob"`, s)
}

func TestShowGrantsOf(t *testing.T) {
a := assert.New(t)
s := snowflake.ViewGrant("test_db", "PUBLIC", "testView").Role("testRole").Show()
Expand Down

0 comments on commit 8528953

Please sign in to comment.