Skip to content

Commit

Permalink
Merge branch 'functions-sdk' of https://github.com/Snowflake-Labs/ter…
Browse files Browse the repository at this point in the history
…raform-provider-snowflake into functions-sdk
  • Loading branch information
sfc-gh-swinkler committed Dec 8, 2023
2 parents 641b0b8 + 6f9c4a5 commit 3ad3e24
Show file tree
Hide file tree
Showing 15 changed files with 351 additions and 91 deletions.
2 changes: 2 additions & 0 deletions docs/resources/password_policy.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,13 @@ A password policy specifies the requirements that must be met to create and rese
### Optional

- `comment` (String) Adds a comment or overwrites an existing comment for the password policy.
- `history` (Number) Specifies the number of the most recent passwords that Snowflake stores. These stored passwords cannot be repeated when a user updates their password value. The current password value does not count towards the history. When you increase the history value, Snowflake saves the previous values. When you decrease the value, Snowflake saves the stored values up to that value that is set. For example, if the history value is 8 and you change the history value to 3, Snowflake stores the most recent 3 passwords and deletes the 5 older password values from the history. Default: 0 Max: 24
- `if_not_exists` (Boolean) Prevent overwriting a previous password policy with the same name.
- `lockout_time_mins` (Number) Specifies the number of minutes the user account will be locked after exhausting the designated number of password retries (i.e. PASSWORD_MAX_RETRIES). Supported range: 1 to 999, inclusive. Default: 15
- `max_age_days` (Number) Specifies the maximum number of days before the password must be changed. Supported range: 0 to 999, inclusive. A value of zero (i.e. 0) indicates that the password does not need to be changed. Snowflake does not recommend choosing this value for a default account-level password policy or for any user-level policy. Instead, choose a value that meets your internal security guidelines. Default: 90, which means the password must be changed every 90 days.
- `max_length` (Number) Specifies the maximum number of characters the password must contain. This number must be greater than or equal to the sum of PASSWORD_MIN_LENGTH, PASSWORD_MIN_UPPER_CASE_CHARS, and PASSWORD_MIN_LOWER_CASE_CHARS. Supported range: 8 to 256, inclusive. Default: 256
- `max_retries` (Number) Specifies the maximum number of attempts to enter a password before being locked out. Supported range: 1 to 10, inclusive. Default: 5
- `min_age_days` (Number) Specifies the number of days the user must wait before a recently changed password can be changed again. Supported range: 0 to 999, inclusive. Default: 0
- `min_length` (Number) Specifies the minimum number of characters the password must contain. Supported range: 8 to 256, inclusive. Default: 8
- `min_lower_case_chars` (Number) Specifies the minimum number of lowercase characters the password must contain. Supported range: 0 to 256, inclusive. Default: 1
- `min_numeric_chars` (Number) Specifies the minimum number of numeric characters the password must contain. Supported range: 0 to 256, inclusive. Default: 1
Expand Down
53 changes: 48 additions & 5 deletions pkg/resources/password_policy.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,13 @@ var passwordPolicySchema = map[string]*schema.Schema{
Description: "Specifies the minimum number of special characters the password must contain. Supported range: 0 to 256, inclusive. Default: 1",
ValidateFunc: validation.IntBetween(0, 256),
},
"min_age_days": {
Type: schema.TypeInt,
Optional: true,
Default: 0,
Description: "Specifies the number of days the user must wait before a recently changed password can be changed again. Supported range: 0 to 999, inclusive. Default: 0",
ValidateFunc: validation.IntBetween(0, 999),
},
"max_age_days": {
Type: schema.TypeInt,
Optional: true,
Expand All @@ -111,6 +118,13 @@ var passwordPolicySchema = map[string]*schema.Schema{
Description: "Specifies the number of minutes the user account will be locked after exhausting the designated number of password retries (i.e. PASSWORD_MAX_RETRIES). Supported range: 1 to 999, inclusive. Default: 15",
ValidateFunc: validation.IntBetween(1, 999),
},
"history": {
Type: schema.TypeInt,
Optional: true,
Default: 0,
Description: "Specifies the number of the most recent passwords that Snowflake stores. These stored passwords cannot be repeated when a user updates their password value. The current password value does not count towards the history. When you increase the history value, Snowflake saves the previous values. When you decrease the value, Snowflake saves the stored values up to that value that is set. For example, if the history value is 8 and you change the history value to 3, Snowflake stores the most recent 3 passwords and deletes the 5 older password values from the history. Default: 0 Max: 24",
ValidateFunc: validation.IntBetween(0, 24),
},
"comment": {
Type: schema.TypeString,
Optional: true,
Expand Down Expand Up @@ -157,9 +171,11 @@ func CreatePasswordPolicy(d *schema.ResourceData, meta interface{}) error {
PasswordMinLowerCaseChars: sdk.Int(d.Get("min_lower_case_chars").(int)),
PasswordMinNumericChars: sdk.Int(d.Get("min_numeric_chars").(int)),
PasswordMinSpecialChars: sdk.Int(d.Get("min_special_chars").(int)),
PasswordMinAgeDays: sdk.Int(d.Get("min_age_days").(int)),
PasswordMaxAgeDays: sdk.Int(d.Get("max_age_days").(int)),
PasswordMaxRetries: sdk.Int(d.Get("max_retries").(int)),
PasswordLockoutTimeMins: sdk.Int(d.Get("lockout_time_mins").(int)),
PasswordHistory: sdk.Int(d.Get("history").(int)),
}

if v, ok := d.GetOk("comment"); ok {
Expand Down Expand Up @@ -225,6 +241,9 @@ func ReadPasswordPolicy(d *schema.ResourceData, meta interface{}) error {
if err := setIntProperty(d, "min_special_chars", passwordPolicyDetails.PasswordMinSpecialChars); err != nil {
return err
}
if err := setIntProperty(d, "min_age_days", passwordPolicyDetails.PasswordMinAgeDays); err != nil {
return err
}
if err := setIntProperty(d, "max_age_days", passwordPolicyDetails.PasswordMaxAgeDays); err != nil {
return err
}
Expand All @@ -234,6 +253,9 @@ func ReadPasswordPolicy(d *schema.ResourceData, meta interface{}) error {
if err := setIntProperty(d, "lockout_time_mins", passwordPolicyDetails.PasswordLockoutTimeMins); err != nil {
return err
}
if err := setIntProperty(d, "history", passwordPolicyDetails.PasswordHistory); err != nil {
return err
}

return nil
}
Expand Down Expand Up @@ -315,6 +337,18 @@ func UpdatePasswordPolicy(d *schema.ResourceData, meta interface{}) error {
}
}

if d.HasChange("min_age_days") {
alterOptions := &sdk.AlterPasswordPolicyOptions{
Set: &sdk.PasswordPolicySet{
PasswordMinAgeDays: sdk.Int(d.Get("min_age_days").(int)),
},
}
err := client.PasswordPolicies.Alter(ctx, objectIdentifier, alterOptions)
if err != nil {
return err
}
}

if d.HasChange("max_age_days") {
alterOptions := &sdk.AlterPasswordPolicyOptions{
Set: &sdk.PasswordPolicySet{
Expand All @@ -338,6 +372,7 @@ func UpdatePasswordPolicy(d *schema.ResourceData, meta interface{}) error {
return err
}
}

if d.HasChange("lockout_time_mins") {
alterOptions := &sdk.AlterPasswordPolicyOptions{
Set: &sdk.PasswordPolicySet{
Expand All @@ -350,20 +385,28 @@ func UpdatePasswordPolicy(d *schema.ResourceData, meta interface{}) error {
}
}

if d.HasChange("history") {
alterOptions := &sdk.AlterPasswordPolicyOptions{
Set: &sdk.PasswordPolicySet{
PasswordHistory: sdk.Int(d.Get("history").(int)),
},
}
err := client.PasswordPolicies.Alter(ctx, objectIdentifier, alterOptions)
if err != nil {
return err
}
}

if d.HasChange("comment") {
alterOptions := &sdk.AlterPasswordPolicyOptions{}
if v, ok := d.GetOk("comment"); ok {
alterOptions.Set = &sdk.PasswordPolicySet{
Comment: sdk.String(v.(string)),
}
} else {
alterOptions.Set = &sdk.PasswordPolicySet{
Comment: sdk.String(""),
}
/* todo [SNOW-928909]: uncomment this once comments are working again
alterOptions.Unset = &sdk.PasswordPolicyUnset{
Comment: sdk.Bool(true),
}*/
}
}
err := client.PasswordPolicies.Alter(ctx, objectIdentifier, alterOptions)
if err != nil {
Expand Down
147 changes: 101 additions & 46 deletions pkg/resources/password_policy_acceptance_test.go
Original file line number Diff line number Diff line change
@@ -1,49 +1,96 @@
package resources_test

import (
"fmt"
"strings"
"testing"

acc "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/acceptance"
"github.com/hashicorp/terraform-plugin-testing/config"
"github.com/hashicorp/terraform-plugin-testing/helper/acctest"
"github.com/hashicorp/terraform-plugin-testing/helper/resource"
"github.com/hashicorp/terraform-plugin-testing/tfversion"
)

func TestAcc_PasswordPolicy(t *testing.T) {
accName := strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha))
m := func(minLength int, maxLength int, minUpperCaseChars int, minLowerCaseChars int, minNumericChars int, minSpecialChars int, minAgeDays int, maxAgeDays int, maxRetries int, lockoutTimeMins int, history int, comment string) map[string]config.Variable {
return map[string]config.Variable{
"name": config.StringVariable(accName),
"database": config.StringVariable(acc.TestDatabaseName),
"schema": config.StringVariable(acc.TestSchemaName),
"min_length": config.IntegerVariable(minLength),
"max_length": config.IntegerVariable(maxLength),
"min_upper_case_chars": config.IntegerVariable(minUpperCaseChars),
"min_lower_case_chars": config.IntegerVariable(minLowerCaseChars),
"min_numeric_chars": config.IntegerVariable(minNumericChars),
"min_special_chars": config.IntegerVariable(minSpecialChars),
"min_age_days": config.IntegerVariable(minAgeDays),
"max_age_days": config.IntegerVariable(maxAgeDays),
"max_retries": config.IntegerVariable(maxRetries),
"lockout_time_mins": config.IntegerVariable(lockoutTimeMins),
"history": config.IntegerVariable(history),
"comment": config.StringVariable(comment),
}
}
variables1 := m(10, 30, 2, 3, 4, 5, 6, 7, 8, 9, 10, "this is a test resource")
variables2 := m(20, 50, 1, 2, 3, 4, 5, 6, 7, 8, 9, "this is a test resource")
variables3 := m(20, 50, 1, 2, 3, 4, 5, 6, 7, 8, 9, "")

resource.ParallelTest(t, resource.TestCase{
Providers: acc.TestAccProviders(),
PreCheck: func() { acc.TestAccPreCheck(t) },
resource.Test(t, resource.TestCase{
ProtoV6ProviderFactories: acc.TestAccProtoV6ProviderFactories,
PreCheck: func() { acc.TestAccPreCheck(t) },
TerraformVersionChecks: []tfversion.TerraformVersionCheck{
tfversion.RequireAbove(tfversion.Version1_5_0),
},
CheckDestroy: nil,
Steps: []resource.TestStep{
{
Config: passwordPolicyConfig(accName, 10, 30, "this is a test resource", acc.TestDatabaseName, acc.TestSchemaName),
ConfigDirectory: config.TestNameDirectory(),
ConfigVariables: variables1,
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr("snowflake_password_policy.pa", "name", accName),
resource.TestCheckResourceAttr("snowflake_password_policy.pa", "min_length", "10"),
resource.TestCheckResourceAttr("snowflake_password_policy.pa", "max_length", "30"),
resource.TestCheckResourceAttr("snowflake_password_policy.pa", "min_upper_case_chars", "2"),
resource.TestCheckResourceAttr("snowflake_password_policy.pa", "min_lower_case_chars", "3"),
resource.TestCheckResourceAttr("snowflake_password_policy.pa", "min_numeric_chars", "4"),
resource.TestCheckResourceAttr("snowflake_password_policy.pa", "min_special_chars", "5"),
resource.TestCheckResourceAttr("snowflake_password_policy.pa", "min_age_days", "6"),
resource.TestCheckResourceAttr("snowflake_password_policy.pa", "max_age_days", "7"),
resource.TestCheckResourceAttr("snowflake_password_policy.pa", "max_retries", "8"),
resource.TestCheckResourceAttr("snowflake_password_policy.pa", "lockout_time_mins", "9"),
resource.TestCheckResourceAttr("snowflake_password_policy.pa", "history", "10"),
resource.TestCheckResourceAttr("snowflake_password_policy.pa", "comment", "this is a test resource"),
),
},
{
Config: passwordPolicyConfig(accName, 20, 50, "this is a test resource", acc.TestDatabaseName, acc.TestSchemaName),
ConfigDirectory: config.TestNameDirectory(),
ConfigVariables: variables2,
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr("snowflake_password_policy.pa", "min_length", "20"),
resource.TestCheckResourceAttr("snowflake_password_policy.pa", "max_length", "50"),
resource.TestCheckResourceAttr("snowflake_password_policy.pa", "min_upper_case_chars", "1"),
resource.TestCheckResourceAttr("snowflake_password_policy.pa", "min_lower_case_chars", "2"),
resource.TestCheckResourceAttr("snowflake_password_policy.pa", "min_numeric_chars", "3"),
resource.TestCheckResourceAttr("snowflake_password_policy.pa", "min_special_chars", "4"),
resource.TestCheckResourceAttr("snowflake_password_policy.pa", "min_age_days", "5"),
resource.TestCheckResourceAttr("snowflake_password_policy.pa", "max_age_days", "6"),
resource.TestCheckResourceAttr("snowflake_password_policy.pa", "max_retries", "7"),
resource.TestCheckResourceAttr("snowflake_password_policy.pa", "lockout_time_mins", "8"),
resource.TestCheckResourceAttr("snowflake_password_policy.pa", "history", "9"),
resource.TestCheckResourceAttr("snowflake_password_policy.pa", "comment", "this is a test resource"),
),
},
/*
todo [SNOW-928909]: fix once comments are working again for password policies
query CREATE PASSWORD POLICY IF NOT EXISTS "T_Kn1bY6?2kx"."}k*3DrsXP:w9TRK#4wtS"."9ec016f6-ce74-0c94-2bd5-dc46547dbeff" PASSWORD_MIN_LENGTH = 10 PASSWORD_MAX_LENGTH = 20 PASSWORD_MIN_UPPER_CASE_CHARS = 5 COMMENT = 'test comment' err 001420 (22023): SQL compilation error: invalid property 'COMMENT' for 'PASSWORD_POLICY'
{
Config: passwordPolicyConfig(accName, 20, 50, ""),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr("snowflake_password_policy.pa", "comment", ""),
),
},
*/
{
ConfigDirectory: config.TestNameDirectory(),
ConfigVariables: variables3,
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr("snowflake_password_policy.pa", "comment", ""),
),
},
{
ConfigDirectory: config.TestNameDirectory(),
ConfigVariables: variables3,
ResourceName: "snowflake_password_policy.pa",
ImportState: true,
ImportStateVerify: true,
Expand All @@ -52,63 +99,71 @@ func TestAcc_PasswordPolicy(t *testing.T) {
})
}

func passwordPolicyConfig(s string, minLength int, maxLength int, comment string, databaseName string, schemaName string) string {
return fmt.Sprintf(`
resource "snowflake_password_policy" "pa" {
name = "%v"
database = "%s"
schema = "%s"
min_length = %d
max_length = %d
or_replace = true
}
`, s, databaseName, schemaName, minLength, maxLength)
}

func TestAcc_PasswordPolicyMaxAgeDays(t *testing.T) {
accName := strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha))
m := func(maxAgeDays int) map[string]config.Variable {
return map[string]config.Variable{
"name": config.StringVariable(accName),
"database": config.StringVariable(acc.TestDatabaseName),
"schema": config.StringVariable(acc.TestSchemaName),
"max_age_days": config.IntegerVariable(maxAgeDays),
}
}

resource.ParallelTest(t, resource.TestCase{
Providers: acc.TestAccProviders(),
PreCheck: func() { acc.TestAccPreCheck(t) },
resource.Test(t, resource.TestCase{
ProtoV6ProviderFactories: acc.TestAccProtoV6ProviderFactories,
PreCheck: func() { acc.TestAccPreCheck(t) },
TerraformVersionChecks: []tfversion.TerraformVersionCheck{
tfversion.RequireAbove(tfversion.Version1_5_0),
},
CheckDestroy: nil,
Steps: []resource.TestStep{
// Creation sets zero properly
{
Config: passwordPolicyDefaultMaxageDaysConfig(accName, acc.TestDatabaseName, acc.TestSchemaName, 0),
ConfigDirectory: acc.ConfigurationDirectory("TestAcc_PasswordPolicy_withMaxAgeDays"),
ConfigVariables: m(0),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr("snowflake_password_policy.pa", "max_age_days", "0"),
),
},
{
Config: passwordPolicyDefaultMaxageDaysConfig(accName, acc.TestDatabaseName, acc.TestSchemaName, 10),
ConfigDirectory: acc.ConfigurationDirectory("TestAcc_PasswordPolicy_withMaxAgeDays"),
ConfigVariables: m(10),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr("snowflake_password_policy.pa", "max_age_days", "10"),
),
},
// Update sets zero properly
{
Config: passwordPolicyDefaultMaxageDaysConfig(accName, acc.TestDatabaseName, acc.TestSchemaName, 0),
ConfigDirectory: acc.ConfigurationDirectory("TestAcc_PasswordPolicy_withMaxAgeDays"),
ConfigVariables: m(0),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr("snowflake_password_policy.pa", "max_age_days", "0"),
),
},
// Unsets properly
{
ConfigDirectory: acc.ConfigurationDirectory("TestAcc_PasswordPolicy_noOptionals"),
ConfigVariables: map[string]config.Variable{
"name": config.StringVariable(accName),
"database": config.StringVariable(acc.TestDatabaseName),
"schema": config.StringVariable(acc.TestSchemaName),
},
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr("snowflake_password_policy.pa", "max_age_days", "90"),
),
},
{
ConfigDirectory: acc.ConfigurationDirectory("TestAcc_PasswordPolicy_noOptionals"),
ConfigVariables: map[string]config.Variable{
"name": config.StringVariable(accName),
"database": config.StringVariable(acc.TestDatabaseName),
"schema": config.StringVariable(acc.TestSchemaName),
},
ResourceName: "snowflake_password_policy.pa",
ImportState: true,
ImportStateVerify: true,
},
},
})
}

func passwordPolicyDefaultMaxageDaysConfig(s string, databaseName string, schemaName string, maxAgeDays int) string {
return fmt.Sprintf(`
resource "snowflake_password_policy" "pa" {
name = "%v"
database = "%s"
schema = "%s"
max_age_days = %d
}
`, s, databaseName, schemaName, maxAgeDays)
}
18 changes: 18 additions & 0 deletions pkg/resources/testdata/TestAcc_PasswordPolicy/test.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
resource "snowflake_password_policy" "pa" {
name = var.name
database = var.database
schema = var.schema
min_length = var.min_length
max_length = var.max_length
min_upper_case_chars = var.min_upper_case_chars
min_lower_case_chars = var.min_lower_case_chars
min_numeric_chars = var.min_numeric_chars
min_special_chars = var.min_special_chars
min_age_days = var.min_age_days
max_age_days = var.max_age_days
max_retries = var.max_retries
lockout_time_mins = var.lockout_time_mins
history = var.history
comment = var.comment
or_replace = true
}
Loading

0 comments on commit 3ad3e24

Please sign in to comment.