Skip to content

Commit

Permalink
fix: use "DESCRIBE USER" in ReadUser, UserExists (#769)
Browse files Browse the repository at this point in the history
  • Loading branch information
mcrlc authored Nov 29, 2021
1 parent 14b17b0 commit 36a4f2e
Show file tree
Hide file tree
Showing 6 changed files with 115 additions and 43 deletions.
16 changes: 11 additions & 5 deletions pkg/resources/user.go
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ func UserExists(data *schema.ResourceData, meta interface{}) (bool, error) {
db := meta.(*sql.DB)
id := data.Id()

stmt := snowflake.User(id).Show()
stmt := snowflake.User(id).Describe()
rows, err := db.Query(stmt)
if err != nil {
return false, err
Expand All @@ -173,10 +173,10 @@ func ReadUser(d *schema.ResourceData, meta interface{}) error {
db := meta.(*sql.DB)
id := d.Id()

stmt := snowflake.User(id).Show()
row := snowflake.QueryRow(db, stmt)

u, err := snowflake.ScanUser(row)
// We use User.Describe instead of User.Show because the "SHOW USERS ..." command
// requires the "MANAGE GRANTS" global privilege
stmt := snowflake.User(id).Describe()
rows, err := snowflake.Query(db, stmt)
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())
Expand All @@ -187,10 +187,16 @@ func ReadUser(d *schema.ResourceData, meta interface{}) error {
return err
}

u, err := snowflake.ScanUserDescription(rows)
if err != nil {
return err
}

err = d.Set("name", u.Name.String)
if err != nil {
return err
}

err = d.Set("comment", u.Comment.String)
if err != nil {
return err
Expand Down
5 changes: 2 additions & 3 deletions pkg/resources/user_public_keys.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,9 +61,8 @@ func UserPublicKeys() *schema.Resource {

func checkUserExists(db *sql.DB, name string) (bool, error) {
// First check if user exists
stmt := snowflake.User(name).Show()
row := snowflake.QueryRow(db, stmt)
_, err := snowflake.ScanUser(row)
stmt := snowflake.User(name).Describe()
_, err := snowflake.Query(db, stmt)
if err == sql.ErrNoRows {
log.Printf("[DEBUG] user (%s) not found", name)
return false, nil
Expand Down
19 changes: 1 addition & 18 deletions pkg/resources/user_public_keys_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package resources_test

import (
"database/sql"
"database/sql/driver"
"testing"

sqlmock "github.com/DATA-DOG/go-sqlmock"
Expand All @@ -19,18 +18,6 @@ func TestUserPublicKeys(t *testing.T) {
r.NoError(err)
}

func rowsFromMap(in map[string]string) *sqlmock.Rows {
cols := []string{}
vals := []driver.Value{}
for col, val := range in {
cols = append(cols, col)
vals = append(vals, val)
}
rows := sqlmock.NewRows(cols)
rows.AddRow(vals...)
return rows
}

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

Expand All @@ -42,14 +29,10 @@ func TestUserPublicKeysCreate(t *testing.T) {
d := schema.TestResourceDataRaw(t, resources.UserPublicKeys().Schema, in)
r.NotNil(d)

rows := map[string]string{
"name": "good_name",
}

WithMockDb(t, func(db *sql.DB, mock sqlmock.Sqlmock) {
mock.ExpectExec(`ALTER USER "good_name" SET rsa_public_key = 'asdf'`).WillReturnResult(sqlmock.NewResult(1, 1))
mock.ExpectExec(`ALTER USER "good_name" SET rsa_public_key_2 = 'asdf2'`).WillReturnResult(sqlmock.NewResult(1, 1))
mock.ExpectQuery(`SHOW USERS LIKE 'good_name'`).WillReturnRows(rowsFromMap(rows))
expectReadUser(mock, "good_name")
err := resources.CreateUserPublicKeys(d, db)
r.NoError(err)

Expand Down
68 changes: 51 additions & 17 deletions pkg/resources/user_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package resources_test

import (
"database/sql"
"fmt"
"testing"

sqlmock "github.com/DATA-DOG/go-sqlmock"
Expand Down Expand Up @@ -43,30 +44,63 @@ func TestUserCreate(t *testing.T) {
r.NotNil(d)

WithMockDb(t, func(db *sql.DB, mock sqlmock.Sqlmock) {
mock.ExpectExec(`^CREATE USER "good_name" COMMENT='great comment' DEFAULT_NAMESPACE='mynamespace' DEFAULT_ROLE='bestrole' DEFAULT_WAREHOUSE='mywarehouse' DISPLAY_NAME='Display Name' EMAIL='[email protected]' FIRST_NAME='Marcin' LAST_NAME='Zukowski' LOGIN_NAME='gname' PASSWORD='awesomepassword' RSA_PUBLIC_KEY='asdf' RSA_PUBLIC_KEY_2='asdf2' DISABLED=true MUST_CHANGE_PASSWORD=true$`).WillReturnResult(sqlmock.NewResult(1, 1))
expectReadUser(mock)
name := "good_name"
q := fmt.Sprintf(`^CREATE USER "%s" COMMENT='great comment' DEFAULT_NAMESPACE='mynamespace' DEFAULT_ROLE='bestrole' DEFAULT_WAREHOUSE='mywarehouse' DISPLAY_NAME='Display Name' EMAIL='[email protected]' FIRST_NAME='Marcin' LAST_NAME='Zukowski' LOGIN_NAME='gname' PASSWORD='awesomepassword' RSA_PUBLIC_KEY='asdf' RSA_PUBLIC_KEY_2='asdf2' DISABLED=true MUST_CHANGE_PASSWORD=true$`, name)
mock.ExpectExec(q).WillReturnResult(sqlmock.NewResult(1, 1))
expectReadUser(mock, name)
err := resources.CreateUser(d, db)
r.NoError(err)
})
}

func expectReadUser(mock sqlmock.Sqlmock) {
rows := sqlmock.NewRows([]string{
"name", "created_on", "login_name", "display_name", "first_name", "last_name", "email", "mins_to_unlock",
"days_to_expiry", "comment", "disabled", "must_change_password", "snowflake_lock", "default_warehouse",
"default_namespace", "default_role", "ext_authn_duo", "ext_authn_uid", "mins_to_bypass_mfa", "owner",
"last_success_login", "expires_at_time", "locked_until_time", "has_password", "has_rsa_public_key"},
).AddRow("good_name", "created_on", "myloginname", "display_name", "first_name", "last_name", "email", "mins_to_unlock", "days_to_expiry", "mock comment", false, true, "snowflake_lock", "default_warehouse", "default_namespace", "default_role", "ext_authn_duo", "ext_authn_uid", "mins_to_bypass_mfa", "owner", "last_success_login", "expires_at_time", "locked_until_time", "has_password", false)
mock.ExpectQuery(`^SHOW USERS LIKE 'good_name'$`).WillReturnRows(rows)
func expectReadUser(mock sqlmock.Sqlmock, name string) {
rowsmap := map[string]string{
"NAME": name,
"CREATED_ON": "created_on",
"LOGIN_NAME": "myloginname",
"DISPLAY_NAME": "display_name",
"FIRST_NAME": "first_name",
"LAST_NAME": "last_name",
"EMAIL": "email",
"MINS_TO_UNLOCK": "mins_to_unlock",
"DAYS_TO_EXPIRY": "days_to_expiry",
"COMMENT": "mock comment",
"DISABLED": "false",
"MUST_CHANGE_PASSWORD": "true",
"SNOWFLAKE_LOCK": "snowflake_lock",
"DEFAULT_WAREHOUSE": "default_warehouse",
"DEFAULT_NAMESPACE": "default_namespace",
"DEFAULT_ROLE": "default_role",
"EXT_AUTHN_DUO": "ext_authn_duo",
"EXT_AUTHN_UID": "ext_authn_uid",
"MINS_TO_BYPASS_MFA": "mins_to_bypass_mfa",
"OWNER": "owner",
"LAST_SUCCESS_LOGIN": "last_success_login",
"EXPIRES_AT_TIME": "expires_at_time",
"LOCKED_UNTIL_TIME": "locked_until_time",
"HAS_PASSWORD": "has_password",
"HAS_RSA_PUBLIC_KEY": "false",
}

rows := sqlmock.NewRows(
[]string{"property", "value", "default", "description"},
)

for k, v := range rowsmap {
rows.AddRow(k, v, "", "")
}

q := fmt.Sprintf(`^DESCRIBE USER "%s"$`, name)
mock.ExpectQuery(q).WillReturnRows(rows)
}

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

d := user(t, "good_name", map[string]interface{}{"name": "good_name"})
name := "good_name"
d := user(t, name, map[string]interface{}{"name": name})

WithMockDb(t, func(db *sql.DB, mock sqlmock.Sqlmock) {
expectReadUser(mock)
expectReadUser(mock, name)
err := resources.ReadUser(d, db)
r.NoError(err)
r.Equal("mock comment", d.Get("comment").(string))
Expand All @@ -75,7 +109,7 @@ func TestUserRead(t *testing.T) {

// Test when resource is not found, checking if state will be empty
r.NotEmpty(d.State())
q := snowflake.User(d.Id()).Show()
q := snowflake.User(d.Id()).Describe()
mock.ExpectQuery(q).WillReturnError(sql.ErrNoRows)
err2 := resources.ReadUser(d, db)
r.Empty(d.State())
Expand All @@ -85,11 +119,11 @@ func TestUserRead(t *testing.T) {

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

d := user(t, "good_name", map[string]interface{}{"name": "good_name"})
name := "good_name"
d := user(t, name, map[string]interface{}{"name": name})

WithMockDb(t, func(db *sql.DB, mock sqlmock.Sqlmock) {
expectReadUser(mock)
expectReadUser(mock, name)
b, err := resources.UserExists(d, db)
r.NoError(err)
r.True(b)
Expand Down
47 changes: 47 additions & 0 deletions pkg/snowflake/user.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,53 @@ func ScanUser(row *sqlx.Row) (*user, error) {
return r, err
}

func ScanUserDescription(rows *sqlx.Rows) (*user, error) {
r := &user{}
var err error

for rows.Next() {
userProp := &DescribeUserProp{}
err = rows.StructScan(userProp)

// The "DESCRIBE USER ..." command returns the string "null" for null values
if userProp.Value.String == "null" {
userProp.Value.Valid = false
userProp.Value.String = ""
}

switch propery := userProp.Property; propery {
case "COMMENT":
r.Comment = userProp.Value
case "DEFAULT_NAMESPACE":
r.DefaultNamespace = userProp.Value
case "DEFAULT_ROLE":
r.DefaultRole = userProp.Value
case "DEFAULT_WAREHOUSE":
r.DefaultWarehouse = userProp.Value
case "DISABLED":
r.Disabled = userProp.Value.String == "true"
case "DISPLAY_NAME":
r.DisplayName = userProp.Value
case "EMAIL":
r.Email = userProp.Value
case "FIRST_NAME":
r.FirstName = userProp.Value
case "RSA_PUBLIC_KEY_FP":
r.HasRsaPublicKey = userProp.Value.Valid
case "LAST_NAME":
r.LastName = userProp.Value
case "LOGIN_NAME":
r.LoginName = userProp.Value
case "NAME":
r.Name = userProp.Value
}
}

err = rows.Err()

return r, err
}

type DescribeUserProp struct {
Property string `db:"property"`
Value sql.NullString `db:"value"`
Expand Down
3 changes: 3 additions & 0 deletions pkg/snowflake/user_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ func TestUser(t *testing.T) {
q := u.Show()
r.Equal("SHOW USERS LIKE 'user1'", q)

q = u.Describe()
r.Equal(`DESCRIBE USER "user1"`, q)

q = u.Drop()
r.Equal(`DROP USER "user1"`, q)

Expand Down

0 comments on commit 36a4f2e

Please sign in to comment.