Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
  • Loading branch information
sfc-gh-jcieslak committed Feb 12, 2024
1 parent a5f969c commit 4b0151d
Show file tree
Hide file tree
Showing 9 changed files with 239 additions and 197 deletions.
85 changes: 32 additions & 53 deletions pkg/resources/user_password_policy_attachment.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,53 +18,34 @@ var userPasswordPolicyAttachmentSchema = map[string]*schema.Schema{
ForceNew: true,
Description: "User name of the user you want to attach the password policy to",
},
"password_policy_database": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
Description: "Database name where the password policy is stored",
},
"password_policy_schema": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
Description: "Schema name where the password policy is stored",
},
"password_policy_name": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
Description: "Non-qualified name of the password policy",
Description: "Fully qualified name of the password policy",
},
}

func UserPasswordPolicyAttachment() *schema.Resource {
return &schema.Resource{
Description: "Specifies the password policy to use for a certain user.",

Create: CreateUserPasswordPolicyAttachment,
Read: ReadUserPasswordPolicyAttachment,
Delete: DeleteUserPasswordPolicyAttachment,

Schema: userPasswordPolicyAttachmentSchema,

Create: CreateUserPasswordPolicyAttachment,
Read: ReadUserPasswordPolicyAttachment,
Delete: DeleteUserPasswordPolicyAttachment,
Schema: userPasswordPolicyAttachmentSchema,
Importer: &schema.ResourceImporter{
StateContext: schema.ImportStatePassthroughContext,
},
}
}

func CreateUserPasswordPolicyAttachment(d *schema.ResourceData, meta interface{}) error {
func CreateUserPasswordPolicyAttachment(d *schema.ResourceData, meta any) error {
db := meta.(*sql.DB)
client := sdk.NewClientFromDB(db)
ctx := context.Background()

userName := sdk.NewAccountObjectIdentifierFromFullyQualifiedName(d.Get("user_name").(string))
passwordPolicy := sdk.NewSchemaObjectIdentifier(
d.Get("password_policy_database").(string),
d.Get("password_policy_schema").(string),
d.Get("password_policy_name").(string),
)
passwordPolicy := sdk.NewSchemaObjectIdentifierFromFullyQualifiedName(d.Get("password_policy_name").(string))

err := client.Users.Alter(ctx, userName, &sdk.AlterUserOptions{
Set: &sdk.UserSet{
Expand All @@ -74,27 +55,25 @@ func CreateUserPasswordPolicyAttachment(d *schema.ResourceData, meta interface{}
if err != nil {
return err
}
d.SetId(fmt.Sprintf(`%s|%s`, helpers.EncodeSnowflakeID(passwordPolicy), helpers.EncodeSnowflakeID(userName)))

d.SetId(helpers.EncodeSnowflakeID(userName.FullyQualifiedName(), passwordPolicy.FullyQualifiedName()))

return ReadUserPasswordPolicyAttachment(d, meta)
}

func ReadUserPasswordPolicyAttachment(d *schema.ResourceData, meta interface{}) error {
parts := strings.Split(d.Id(), helpers.IDDelimiter)
if len(parts) != 4 {
// Note: this exception handling is particularly useful when importing
return fmt.Errorf("id should be in the format 'database|schema|password_policy|user_name', but I got '%s'", d.Id())
}
// Note: there is no alphanumeric id for an attachment, so we retrieve the password policies attached to a certain user.
userName := sdk.NewAccountObjectIdentifierFromFullyQualifiedName(parts[3])
func ReadUserPasswordPolicyAttachment(d *schema.ResourceData, meta any) error {
db := meta.(*sql.DB)
client := sdk.NewClientFromDB(db)
ctx := context.Background()
policyReferences, err := client.PolicyReferences.GetForEntity(ctx, &sdk.GetForEntityPolicyReferenceRequest{
// Note: I cannot insert both single and double quotes in the SDK, so for now I need to do this
RefEntityName: userName.FullyQualifiedName(),
RefEntityDomain: "user",
})

parts := strings.Split(d.Id(), helpers.IDDelimiter)
if len(parts) != 2 {
return fmt.Errorf("required id format 'user_name|password_policy_name', but got: '%s'", d.Id())
}

// Note: there is no alphanumeric id for an attachment, so we retrieve the password policies attached to a certain user.
userName := sdk.NewAccountObjectIdentifierFromFullyQualifiedName(parts[0])
policyReferences, err := client.PolicyReferences.GetForEntity(ctx, sdk.NewGetForEntityPolicyReferenceRequest(userName, sdk.PolicyEntityDomainUser))
if err != nil {
return err
}
Expand All @@ -109,23 +88,24 @@ func ReadUserPasswordPolicyAttachment(d *schema.ResourceData, meta interface{})
d.SetId("")
return nil
}
if err := d.Set("password_policy_database", sdk.NewAccountIdentifierFromFullyQualifiedName(policyReferences[0].PolicyDb).Name()); err != nil {
return err
}
if err := d.Set("password_policy_schema", sdk.NewAccountIdentifierFromFullyQualifiedName(policyReferences[0].PolicySchema).Name()); err != nil {
return err
}
if err := d.Set("password_policy_name", sdk.NewAccountIdentifierFromFullyQualifiedName(policyReferences[0].PolicyName).Name()); err != nil {

if err := d.Set("user_name", userName.Name()); err != nil {
return err
}
if err := d.Set("user_name", helpers.EncodeSnowflakeID(userName)); err != nil {
if err := d.Set(
"password_policy_name",
sdk.NewSchemaObjectIdentifier(
policyReferences[0].PolicyDb,
policyReferences[0].PolicySchema,
policyReferences[0].PolicyName,
).FullyQualifiedName()); err != nil {
return err
}

return err
}

// DeleteAccountPasswordPolicyAttachment implements schema.DeleteFunc.
func DeleteUserPasswordPolicyAttachment(d *schema.ResourceData, meta interface{}) error {
func DeleteUserPasswordPolicyAttachment(d *schema.ResourceData, meta any) error {
db := meta.(*sql.DB)
client := sdk.NewClientFromDB(db)
ctx := context.Background()
Expand All @@ -137,12 +117,11 @@ func DeleteUserPasswordPolicyAttachment(d *schema.ResourceData, meta interface{}
PasswordPolicy: sdk.Bool(true),
},
})

d.SetId("")

if err != nil {
return err
}

d.SetId("")

return nil
}
44 changes: 22 additions & 22 deletions pkg/resources/user_password_policy_attachment_acceptance_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,10 @@ import (
)

func TestAcc_UserPasswordPolicyAttachment(t *testing.T) {
prefix := "tst-terraform" + strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha))
prefix2 := "tst-terraform" + strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha))
userName := strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha))
NewUserName := strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha))
passwordPolicyName := strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha))
newPasswordPolicyName := strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha))

resource.Test(t, resource.TestCase{
ProtoV6ProviderFactories: acc.TestAccProtoV6ProviderFactories,
Expand All @@ -26,18 +28,21 @@ func TestAcc_UserPasswordPolicyAttachment(t *testing.T) {
Steps: []resource.TestStep{
// CREATE
{
Config: userPasswordPolicyAttachmentConfig("USER", acc.TestDatabaseName, acc.TestSchemaName, prefix),
Config: userPasswordPolicyAttachmentConfig(userName, acc.TestDatabaseName, acc.TestSchemaName, passwordPolicyName),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttrSet("snowflake_user_password_policy_attachment.ppa", "id"),
resource.TestCheckResourceAttr("snowflake_user_password_policy_attachment.ppa", "user_name", userName),
resource.TestCheckResourceAttr("snowflake_user_password_policy_attachment.ppa", "password_policy_name", sdk.NewSchemaObjectIdentifier(acc.TestDatabaseName, acc.TestSchemaName, passwordPolicyName).FullyQualifiedName()),
resource.TestCheckResourceAttr("snowflake_user_password_policy_attachment.ppa", "id", fmt.Sprintf("%s|%s", sdk.NewAccountObjectIdentifier(userName).FullyQualifiedName(), sdk.NewSchemaObjectIdentifier(acc.TestDatabaseName, acc.TestSchemaName, passwordPolicyName).FullyQualifiedName())),
),
Destroy: false,
},
// UPDATE
{
Config: userPasswordPolicyAttachmentConfig(fmt.Sprintf("USER_%s", prefix), acc.TestDatabaseName, acc.TestSchemaName, prefix2),
Config: userPasswordPolicyAttachmentConfig(NewUserName, acc.TestDatabaseName, acc.TestSchemaName, newPasswordPolicyName),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttrSet("snowflake_user_password_policy_attachment.ppa", "id"),
resource.TestCheckResourceAttr("snowflake_user_password_policy_attachment.ppa", "user_name", fmt.Sprintf("USER_%s", prefix)),
resource.TestCheckResourceAttr("snowflake_user_password_policy_attachment.ppa", "user_name", NewUserName),
resource.TestCheckResourceAttr("snowflake_user_password_policy_attachment.ppa", "password_policy_name", sdk.NewSchemaObjectIdentifier(acc.TestDatabaseName, acc.TestSchemaName, newPasswordPolicyName).FullyQualifiedName()),
resource.TestCheckResourceAttr("snowflake_user_password_policy_attachment.ppa", "id", fmt.Sprintf("%s|%s", sdk.NewAccountObjectIdentifier(NewUserName).FullyQualifiedName(), sdk.NewSchemaObjectIdentifier(acc.TestDatabaseName, acc.TestSchemaName, newPasswordPolicyName).FullyQualifiedName())),
),
},
// IMPORT
Expand All @@ -55,46 +60,41 @@ func testAccCheckUserPasswordPolicyAttachmentDestroy(s *terraform.State) error {
client := sdk.NewClientFromDB(db)
ctx := context.Background()
for _, rs := range s.RootModule().Resources {
// Note: I leverage the fact that the state during the test is specific to the test case, so there should only be there resources created in this test
if rs.Type != "snowflake_user_password_policy_attachment" {
continue
}
userName := sdk.NewAccountObjectIdentifierFromFullyQualifiedName(rs.Primary.Attributes["user_name"])
policyReferences, err := client.PolicyReferences.GetForEntity(ctx, &sdk.GetForEntityPolicyReferenceRequest{
RefEntityName: userName.FullyQualifiedName(),
RefEntityDomain: "user",
})
policyReferences, err := client.PolicyReferences.GetForEntity(ctx, sdk.NewGetForEntityPolicyReferenceRequest(
sdk.NewAccountObjectIdentifierFromFullyQualifiedName(rs.Primary.Attributes["user_name"]),
sdk.PolicyEntityDomainUser,
))
if err != nil {
if strings.Contains(err.Error(), "does not exist or not authorized") {
// Note: this can happen if the Policy Reference or the User have been deleted as well; in this case, just ignore the error
// Note: this can happen if the Policy Reference or the User has been deleted as well; in this case, ignore the error
continue
}
return err
}
if len(policyReferences) > 0 {
return fmt.Errorf("User Password Policy attachment %v still exists", policyReferences[0].PolicyName)
return fmt.Errorf("user password policy attachment %v still exists", policyReferences[0].PolicyName)
}
}
return nil
}

func userPasswordPolicyAttachmentConfig(userName, databaseName, schemaName, prefix string) string {
s := `
return fmt.Sprintf(`
resource "snowflake_user" "user" {
name = "%s"
}
resource "snowflake_password_policy" "pp" {
database = "%s"
schema = "%s"
name = "pp_%v"
name = "%s"
}
resource "snowflake_user_password_policy_attachment" "ppa" {
password_policy_database = snowflake_password_policy.pp.database
password_policy_schema = snowflake_password_policy.pp.schema
password_policy_name = snowflake_password_policy.pp.name
password_policy_name = "\"${snowflake_password_policy.pp.database}\".\"${snowflake_password_policy.pp.schema}\".\"${snowflake_password_policy.pp.name}\""
user_name = snowflake_user.user.name
}
`
return fmt.Sprintf(s, userName, databaseName, schemaName, prefix)
`, userName, databaseName, schemaName, prefix)
}
34 changes: 21 additions & 13 deletions pkg/sdk/policy_references.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,29 +5,36 @@ import (
"database/sql"
)

var _ convertibleRow[PolicyReference] = new(policyReferenceDBRow)

type PolicyReferences interface {
GetForEntity(ctx context.Context, request *GetForEntityPolicyReferenceRequest) ([]PolicyReference, error)
}

type getForEntityPolicyReferenceOptions struct {
select_ bool `ddl:"static" sql:"SELECT"`
asterisk bool `ddl:"static" sql:"*"`
from bool `ddl:"static" sql:"FROM"`
tableFunction *tableFunction `ddl:"keyword"`
}

type tableFunction struct {
table *bool `ddl:"keyword" sql:"TABLE"`
policyReferenceFunction *policyReferenceFunction `ddl:"list,parentheses,no_comma"`
selectEverythingFrom bool `ddl:"static" sql:"SELECT * FROM TABLE"`
parameters *policyReferenceParameters `ddl:"list,parentheses,no_comma"`
}

type policyReferenceFunction struct {
functionFullyQualifiedName *bool `ddl:"keyword" sql:"SNOWFLAKE.INFORMATION_SCHEMA.POLICY_REFERENCES"`
type policyReferenceParameters struct {
functionFullyQualifiedName bool `ddl:"static" sql:"SNOWFLAKE.INFORMATION_SCHEMA.POLICY_REFERENCES"`
arguments *policyReferenceFunctionArguments `ddl:"list,parentheses"`
}

type PolicyEntityDomain string

const (
PolicyEntityDomainAccount PolicyEntityDomain = "ACCOUNT"
PolicyEntityDomainIntegration PolicyEntityDomain = "INTEGRATION"
PolicyEntityDomainTable PolicyEntityDomain = "TABLE"
PolicyEntityDomainTag PolicyEntityDomain = "TAG"
PolicyEntityDomainUser PolicyEntityDomain = "USER"
PolicyEntityDomainView PolicyEntityDomain = "VIEW"
)

type policyReferenceFunctionArguments struct {
refEntityName []ObjectIdentifier `ddl:"parameter,single_quotes,arrow_equals" sql:"ref_entity_name"`
refEntityDomain *string `ddl:"parameter,single_quotes,arrow_equals" sql:"ref_entity_domain"`
refEntityName []ObjectIdentifier `ddl:"parameter,single_quotes,arrow_equals" sql:"REF_ENTITY_NAME"`
refEntityDomain *PolicyEntityDomain `ddl:"parameter,single_quotes,arrow_equals" sql:"REF_ENTITY_DOMAIN"`
}

type PolicyReference struct {
Expand All @@ -47,6 +54,7 @@ type PolicyReference struct {
PolicyStatus string
}

// TODO: Check types
type policyReferenceDBRow struct {
PolicyDb sql.NullString `db:"POLICY_DB"`
PolicySchema sql.NullString `db:"POLICY_SCHEMA"`
Expand Down
17 changes: 15 additions & 2 deletions pkg/sdk/policy_references_dto.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,20 @@ package sdk

var _ optionsProvider[getForEntityPolicyReferenceOptions] = new(GetForEntityPolicyReferenceRequest)

//go:generate go run ./dto-builder-generator/main.go

type GetForEntityPolicyReferenceRequest struct {
RefEntityName string
RefEntityDomain string
RefEntityName ObjectIdentifier // required
RefEntityDomain PolicyEntityDomain // required
}

func (request *GetForEntityPolicyReferenceRequest) toOpts() *getForEntityPolicyReferenceOptions {
return &getForEntityPolicyReferenceOptions{
parameters: &policyReferenceParameters{
arguments: &policyReferenceFunctionArguments{
refEntityName: []ObjectIdentifier{request.RefEntityName},
refEntityDomain: Pointer(request.RefEntityDomain),
},
},
}
}
15 changes: 15 additions & 0 deletions pkg/sdk/policy_references_dto_builders_gen.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

19 changes: 2 additions & 17 deletions pkg/sdk/policy_references_impl.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ package sdk

import "context"

var _ PolicyReferences = new(policyReference)

type policyReference struct {
client *Client
}
Expand All @@ -12,23 +14,6 @@ func (v *policyReference) GetForEntity(ctx context.Context, request *GetForEntit
if err != nil {
return nil, err
}

resultList := convertRows[policyReferenceDBRow, PolicyReference](dbRows)

return resultList, nil
}

func (request *GetForEntityPolicyReferenceRequest) toOpts() *getForEntityPolicyReferenceOptions {
return &getForEntityPolicyReferenceOptions{
tableFunction: &tableFunction{
table: Bool(true),
policyReferenceFunction: &policyReferenceFunction{
functionFullyQualifiedName: Bool(true),
arguments: &policyReferenceFunctionArguments{
refEntityName: []ObjectIdentifier{NewObjectIdentifierFromFullyQualifiedName(request.RefEntityName)},
refEntityDomain: String(request.RefEntityDomain),
},
},
},
}
}
Loading

0 comments on commit 4b0151d

Please sign in to comment.