From c189782bb7d117e27979c21dfea60ba733e996df Mon Sep 17 00:00:00 2001 From: Artur Sawicki Date: Wed, 6 Dec 2023 14:49:49 +0100 Subject: [PATCH] feat: Add missing parameters to password policy resource (#2231) * Add missing options to password policy * Revert comments (they are working again) * Revert comments (they are working again) * Add missing params to resource * Check alter behavior * Regenerate docs * Use newer config type in tests * Format tf files * Fix after review * Fix after review * Add test for all params --- docs/resources/password_policy.md | 2 + pkg/resources/password_policy.go | 53 ++++++- .../password_policy_acceptance_test.go | 147 ++++++++++++------ .../testdata/TestAcc_PasswordPolicy/test.tf | 18 +++ .../TestAcc_PasswordPolicy/variables.tf | 59 +++++++ .../test.tf | 5 + .../variables.tf | 11 ++ .../test.tf | 6 + .../variables.tf | 15 ++ pkg/sdk/password_policy.go | 37 +++-- pkg/sdk/password_policy_test.go | 33 +++- pkg/sdk/tasks_gen.go | 4 +- pkg/sdk/tasks_gen_test.go | 18 +++ .../password_policy_integration_test.go | 30 ++-- pkg/sdk/testint/tasks_gen_integration_test.go | 4 +- 15 files changed, 351 insertions(+), 91 deletions(-) create mode 100644 pkg/resources/testdata/TestAcc_PasswordPolicy/test.tf create mode 100644 pkg/resources/testdata/TestAcc_PasswordPolicy/variables.tf create mode 100644 pkg/resources/testdata/TestAcc_PasswordPolicy_noOptionals/test.tf create mode 100644 pkg/resources/testdata/TestAcc_PasswordPolicy_noOptionals/variables.tf create mode 100644 pkg/resources/testdata/TestAcc_PasswordPolicy_withMaxAgeDays/test.tf create mode 100644 pkg/resources/testdata/TestAcc_PasswordPolicy_withMaxAgeDays/variables.tf diff --git a/docs/resources/password_policy.md b/docs/resources/password_policy.md index 370aa89549..d2fe738943 100644 --- a/docs/resources/password_policy.md +++ b/docs/resources/password_policy.md @@ -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 diff --git a/pkg/resources/password_policy.go b/pkg/resources/password_policy.go index 465d681de2..2f30635ae6 100644 --- a/pkg/resources/password_policy.go +++ b/pkg/resources/password_policy.go @@ -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, @@ -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, @@ -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 { @@ -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 } @@ -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 } @@ -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{ @@ -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{ @@ -350,6 +385,18 @@ 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 { @@ -357,13 +404,9 @@ func UpdatePasswordPolicy(d *schema.ResourceData, meta interface{}) error { 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 { diff --git a/pkg/resources/password_policy_acceptance_test.go b/pkg/resources/password_policy_acceptance_test.go index 4b98264c9a..e3b4e25a4e 100644 --- a/pkg/resources/password_policy_acceptance_test.go +++ b/pkg/resources/password_policy_acceptance_test.go @@ -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, @@ -52,48 +99,67 @@ 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, @@ -101,14 +167,3 @@ func TestAcc_PasswordPolicyMaxAgeDays(t *testing.T) { }, }) } - -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) -} diff --git a/pkg/resources/testdata/TestAcc_PasswordPolicy/test.tf b/pkg/resources/testdata/TestAcc_PasswordPolicy/test.tf new file mode 100644 index 0000000000..773fa53b0e --- /dev/null +++ b/pkg/resources/testdata/TestAcc_PasswordPolicy/test.tf @@ -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 +} \ No newline at end of file diff --git a/pkg/resources/testdata/TestAcc_PasswordPolicy/variables.tf b/pkg/resources/testdata/TestAcc_PasswordPolicy/variables.tf new file mode 100644 index 0000000000..5005be5eac --- /dev/null +++ b/pkg/resources/testdata/TestAcc_PasswordPolicy/variables.tf @@ -0,0 +1,59 @@ +variable "name" { + type = string +} + +variable "database" { + type = string +} + +variable "schema" { + type = string +} + +variable "min_length" { + type = number +} + +variable "max_length" { + type = number +} + +variable "min_upper_case_chars" { + type = number +} + +variable "min_lower_case_chars" { + type = number +} + +variable "min_numeric_chars" { + type = number +} + +variable "min_special_chars" { + type = number +} + +variable "min_age_days" { + type = number +} + +variable "max_age_days" { + type = number +} + +variable "max_retries" { + type = number +} + +variable "lockout_time_mins" { + type = number +} + +variable "history" { + type = number +} + +variable "comment" { + type = string +} diff --git a/pkg/resources/testdata/TestAcc_PasswordPolicy_noOptionals/test.tf b/pkg/resources/testdata/TestAcc_PasswordPolicy_noOptionals/test.tf new file mode 100644 index 0000000000..a5dc3e4ebc --- /dev/null +++ b/pkg/resources/testdata/TestAcc_PasswordPolicy_noOptionals/test.tf @@ -0,0 +1,5 @@ +resource "snowflake_password_policy" "pa" { + name = var.name + database = var.database + schema = var.schema +} \ No newline at end of file diff --git a/pkg/resources/testdata/TestAcc_PasswordPolicy_noOptionals/variables.tf b/pkg/resources/testdata/TestAcc_PasswordPolicy_noOptionals/variables.tf new file mode 100644 index 0000000000..31dd643cf2 --- /dev/null +++ b/pkg/resources/testdata/TestAcc_PasswordPolicy_noOptionals/variables.tf @@ -0,0 +1,11 @@ +variable "name" { + type = string +} + +variable "database" { + type = string +} + +variable "schema" { + type = string +} diff --git a/pkg/resources/testdata/TestAcc_PasswordPolicy_withMaxAgeDays/test.tf b/pkg/resources/testdata/TestAcc_PasswordPolicy_withMaxAgeDays/test.tf new file mode 100644 index 0000000000..0bbb0304d7 --- /dev/null +++ b/pkg/resources/testdata/TestAcc_PasswordPolicy_withMaxAgeDays/test.tf @@ -0,0 +1,6 @@ +resource "snowflake_password_policy" "pa" { + name = var.name + database = var.database + schema = var.schema + max_age_days = var.max_age_days +} \ No newline at end of file diff --git a/pkg/resources/testdata/TestAcc_PasswordPolicy_withMaxAgeDays/variables.tf b/pkg/resources/testdata/TestAcc_PasswordPolicy_withMaxAgeDays/variables.tf new file mode 100644 index 0000000000..e306c8f700 --- /dev/null +++ b/pkg/resources/testdata/TestAcc_PasswordPolicy_withMaxAgeDays/variables.tf @@ -0,0 +1,15 @@ +variable "name" { + type = string +} + +variable "database" { + type = string +} + +variable "schema" { + type = string +} + +variable "max_age_days" { + type = number +} diff --git a/pkg/sdk/password_policy.go b/pkg/sdk/password_policy.go index 9fea0bdd8c..a77dad0bec 100644 --- a/pkg/sdk/password_policy.go +++ b/pkg/sdk/password_policy.go @@ -45,9 +45,11 @@ type CreatePasswordPolicyOptions struct { PasswordMinLowerCaseChars *int `ddl:"parameter" sql:"PASSWORD_MIN_LOWER_CASE_CHARS"` PasswordMinNumericChars *int `ddl:"parameter" sql:"PASSWORD_MIN_NUMERIC_CHARS"` PasswordMinSpecialChars *int `ddl:"parameter" sql:"PASSWORD_MIN_SPECIAL_CHARS"` + PasswordMinAgeDays *int `ddl:"parameter" sql:"PASSWORD_MIN_AGE_DAYS"` PasswordMaxAgeDays *int `ddl:"parameter" sql:"PASSWORD_MAX_AGE_DAYS"` PasswordMaxRetries *int `ddl:"parameter" sql:"PASSWORD_MAX_RETRIES"` PasswordLockoutTimeMins *int `ddl:"parameter" sql:"PASSWORD_LOCKOUT_TIME_MINS"` + PasswordHistory *int `ddl:"parameter" sql:"PASSWORD_HISTORY"` Comment *string `ddl:"parameter,single_quotes" sql:"COMMENT"` } @@ -85,8 +87,8 @@ type AlterPasswordPolicyOptions struct { IfExists *bool `ddl:"keyword" sql:"IF EXISTS"` name SchemaObjectIdentifier `ddl:"identifier"` NewName *SchemaObjectIdentifier `ddl:"identifier" sql:"RENAME TO"` - Set *PasswordPolicySet `ddl:"keyword" sql:"SET"` - Unset *PasswordPolicyUnset `ddl:"keyword" sql:"UNSET"` + Set *PasswordPolicySet `ddl:"list,no_parentheses" sql:"SET"` + Unset *PasswordPolicyUnset `ddl:"list,no_parentheses" sql:"UNSET"` } func (opts *AlterPasswordPolicyOptions) validate() error { @@ -120,9 +122,11 @@ type PasswordPolicySet struct { PasswordMinLowerCaseChars *int `ddl:"parameter" sql:"PASSWORD_MIN_LOWER_CASE_CHARS"` PasswordMinNumericChars *int `ddl:"parameter" sql:"PASSWORD_MIN_NUMERIC_CHARS"` PasswordMinSpecialChars *int `ddl:"parameter" sql:"PASSWORD_MIN_SPECIAL_CHARS"` + PasswordMinAgeDays *int `ddl:"parameter" sql:"PASSWORD_MIN_AGE_DAYS"` PasswordMaxAgeDays *int `ddl:"parameter" sql:"PASSWORD_MAX_AGE_DAYS"` PasswordMaxRetries *int `ddl:"parameter" sql:"PASSWORD_MAX_RETRIES"` PasswordLockoutTimeMins *int `ddl:"parameter" sql:"PASSWORD_LOCKOUT_TIME_MINS"` + PasswordHistory *int `ddl:"parameter" sql:"PASSWORD_HISTORY"` Comment *string `ddl:"parameter,single_quotes" sql:"COMMENT"` } @@ -134,11 +138,13 @@ func (v *PasswordPolicySet) validate() error { v.PasswordMinLowerCaseChars, v.PasswordMinNumericChars, v.PasswordMinSpecialChars, + v.PasswordMinAgeDays, v.PasswordMaxAgeDays, v.PasswordMaxRetries, v.PasswordLockoutTimeMins, + v.PasswordHistory, v.Comment) { - return errors.New("must set at least one parameter") + return errAtLeastOneOf("PasswordPolicySet", "PasswordMinLength", "PasswordMaxLength", "PasswordMinUpperCaseChars", "PasswordMinLowerCaseChars", "PasswordMinNumericChars", "PasswordMinSpecialChars", "PasswordMinAgeDays", "PasswordMaxAgeDays", "PasswordMaxRetries", "PasswordLockoutTimeMins", "PasswordHistory", "Comment") } return nil } @@ -150,9 +156,11 @@ type PasswordPolicyUnset struct { PasswordMinLowerCaseChars *bool `ddl:"keyword" sql:"PASSWORD_MIN_LOWER_CASE_CHARS"` PasswordMinNumericChars *bool `ddl:"keyword" sql:"PASSWORD_MIN_NUMERIC_CHARS"` PasswordMinSpecialChars *bool `ddl:"keyword" sql:"PASSWORD_MIN_SPECIAL_CHARS"` + PasswordMinAgeDays *bool `ddl:"keyword" sql:"PASSWORD_MIN_AGE_DAYS"` PasswordMaxAgeDays *bool `ddl:"keyword" sql:"PASSWORD_MAX_AGE_DAYS"` PasswordMaxRetries *bool `ddl:"keyword" sql:"PASSWORD_MAX_RETRIES"` PasswordLockoutTimeMins *bool `ddl:"keyword" sql:"PASSWORD_LOCKOUT_TIME_MINS"` + PasswordHistory *bool `ddl:"keyword" sql:"PASSWORD_HISTORY"` Comment *bool `ddl:"keyword" sql:"COMMENT"` } @@ -164,24 +172,13 @@ func (v *PasswordPolicyUnset) validate() error { v.PasswordMinLowerCaseChars, v.PasswordMinNumericChars, v.PasswordMinSpecialChars, + v.PasswordMinAgeDays, v.PasswordMaxAgeDays, v.PasswordMaxRetries, v.PasswordLockoutTimeMins, + v.PasswordHistory, v.Comment) { - return errors.New("must unset at least one parameter") - } - if !exactlyOneValueSet( - v.PasswordMinLength, - v.PasswordMaxLength, - v.PasswordMinUpperCaseChars, - v.PasswordMinLowerCaseChars, - v.PasswordMinNumericChars, - v.PasswordMinSpecialChars, - v.PasswordMaxAgeDays, - v.PasswordMaxRetries, - v.PasswordLockoutTimeMins, - v.Comment) { - return errors.New("cannot unset more than one parameter in the same ALTER statement") + return errAtLeastOneOf("PasswordPolicyUnset", "PasswordMinLength", "PasswordMaxLength", "PasswordMinUpperCaseChars", "PasswordMinLowerCaseChars", "PasswordMinNumericChars", "PasswordMinSpecialChars", "PasswordMinAgeDays", "PasswordMaxAgeDays", "PasswordMaxRetries", "PasswordLockoutTimeMins", "PasswordHistory", "Comment") } return nil } @@ -367,9 +364,11 @@ type PasswordPolicyDetails struct { PasswordMinLowerCaseChars *IntProperty PasswordMinNumericChars *IntProperty PasswordMinSpecialChars *IntProperty + PasswordMinAgeDays *IntProperty PasswordMaxAgeDays *IntProperty PasswordMaxRetries *IntProperty PasswordLockoutTimeMins *IntProperty + PasswordHistory *IntProperty } func passwordPolicyDetailsFromRows(rows []propertyRow) *PasswordPolicyDetails { @@ -394,12 +393,16 @@ func passwordPolicyDetailsFromRows(rows []propertyRow) *PasswordPolicyDetails { v.PasswordMinNumericChars = row.toIntProperty() case "PASSWORD_MIN_SPECIAL_CHARS": v.PasswordMinSpecialChars = row.toIntProperty() + case "PASSWORD_MIN_AGE_DAYS": + v.PasswordMinAgeDays = row.toIntProperty() case "PASSWORD_MAX_AGE_DAYS": v.PasswordMaxAgeDays = row.toIntProperty() case "PASSWORD_MAX_RETRIES": v.PasswordMaxRetries = row.toIntProperty() case "PASSWORD_LOCKOUT_TIME_MINS": v.PasswordLockoutTimeMins = row.toIntProperty() + case "PASSWORD_HISTORY": + v.PasswordHistory = row.toIntProperty() } } return v diff --git a/pkg/sdk/password_policy_test.go b/pkg/sdk/password_policy_test.go index 05c15edcd3..ddac1d2f87 100644 --- a/pkg/sdk/password_policy_test.go +++ b/pkg/sdk/password_policy_test.go @@ -32,12 +32,14 @@ func TestPasswordPolicyCreate(t *testing.T) { PasswordMinLowerCaseChars: Int(1), PasswordMinNumericChars: Int(1), PasswordMinSpecialChars: Int(1), + PasswordMinAgeDays: Int(30), PasswordMaxAgeDays: Int(30), PasswordMaxRetries: Int(5), PasswordLockoutTimeMins: Int(30), + PasswordHistory: Int(15), Comment: String("test comment"), } - assertOptsValidAndSQLEquals(t, opts, `CREATE OR REPLACE PASSWORD POLICY IF NOT EXISTS %s PASSWORD_MIN_LENGTH = 10 PASSWORD_MAX_LENGTH = 20 PASSWORD_MIN_UPPER_CASE_CHARS = 1 PASSWORD_MIN_LOWER_CASE_CHARS = 1 PASSWORD_MIN_NUMERIC_CHARS = 1 PASSWORD_MIN_SPECIAL_CHARS = 1 PASSWORD_MAX_AGE_DAYS = 30 PASSWORD_MAX_RETRIES = 5 PASSWORD_LOCKOUT_TIME_MINS = 30 COMMENT = 'test comment'`, id.FullyQualifiedName()) + assertOptsValidAndSQLEquals(t, opts, `CREATE OR REPLACE PASSWORD POLICY IF NOT EXISTS %s PASSWORD_MIN_LENGTH = 10 PASSWORD_MAX_LENGTH = 20 PASSWORD_MIN_UPPER_CASE_CHARS = 1 PASSWORD_MIN_LOWER_CASE_CHARS = 1 PASSWORD_MIN_NUMERIC_CHARS = 1 PASSWORD_MIN_SPECIAL_CHARS = 1 PASSWORD_MIN_AGE_DAYS = 30 PASSWORD_MAX_AGE_DAYS = 30 PASSWORD_MAX_RETRIES = 5 PASSWORD_LOCKOUT_TIME_MINS = 30 PASSWORD_HISTORY = 15 COMMENT = 'test comment'`, id.FullyQualifiedName()) }) } @@ -56,6 +58,22 @@ func TestPasswordPolicyAlter(t *testing.T) { assertOptsInvalidJoinedErrors(t, opts, errExactlyOneOf("Set", "Unset", "NewName")) }) + t.Run("validation: no set", func(t *testing.T) { + opts := &AlterPasswordPolicyOptions{ + name: id, + Set: &PasswordPolicySet{}, + } + assertOptsInvalidJoinedErrors(t, opts, errAtLeastOneOf("PasswordPolicySet", "PasswordMinLength", "PasswordMaxLength", "PasswordMinUpperCaseChars", "PasswordMinLowerCaseChars", "PasswordMinNumericChars", "PasswordMinSpecialChars", "PasswordMinAgeDays", "PasswordMaxAgeDays", "PasswordMaxRetries", "PasswordLockoutTimeMins", "PasswordHistory", "Comment")) + }) + + t.Run("validation: no unset", func(t *testing.T) { + opts := &AlterPasswordPolicyOptions{ + name: id, + Unset: &PasswordPolicyUnset{}, + } + assertOptsInvalidJoinedErrors(t, opts, errAtLeastOneOf("PasswordPolicyUnset", "PasswordMinLength", "PasswordMaxLength", "PasswordMinUpperCaseChars", "PasswordMinLowerCaseChars", "PasswordMinNumericChars", "PasswordMinSpecialChars", "PasswordMinAgeDays", "PasswordMaxAgeDays", "PasswordMaxRetries", "PasswordLockoutTimeMins", "PasswordHistory", "Comment")) + }) + t.Run("with set", func(t *testing.T) { opts := &AlterPasswordPolicyOptions{ name: id, @@ -65,7 +83,7 @@ func TestPasswordPolicyAlter(t *testing.T) { PasswordMinUpperCaseChars: Int(1), }, } - assertOptsValidAndSQLEquals(t, opts, "ALTER PASSWORD POLICY %s SET PASSWORD_MIN_LENGTH = 10 PASSWORD_MAX_LENGTH = 20 PASSWORD_MIN_UPPER_CASE_CHARS = 1", id.FullyQualifiedName()) + assertOptsValidAndSQLEquals(t, opts, "ALTER PASSWORD POLICY %s SET PASSWORD_MIN_LENGTH = 10, PASSWORD_MAX_LENGTH = 20, PASSWORD_MIN_UPPER_CASE_CHARS = 1", id.FullyQualifiedName()) }) t.Run("with unset", func(t *testing.T) { @@ -78,6 +96,17 @@ func TestPasswordPolicyAlter(t *testing.T) { assertOptsValidAndSQLEquals(t, opts, "ALTER PASSWORD POLICY %s UNSET PASSWORD_MIN_LENGTH", id.FullyQualifiedName()) }) + t.Run("with multiple unset", func(t *testing.T) { + opts := &AlterPasswordPolicyOptions{ + name: id, + Unset: &PasswordPolicyUnset{ + PasswordMinLength: Bool(true), + PasswordMinAgeDays: Bool(true), + }, + } + assertOptsValidAndSQLEquals(t, opts, "ALTER PASSWORD POLICY %s UNSET PASSWORD_MIN_LENGTH, PASSWORD_MIN_AGE_DAYS", id.FullyQualifiedName()) + }) + t.Run("rename", func(t *testing.T) { newID := NewSchemaObjectIdentifier(id.databaseName, id.schemaName, random.UUID()) opts := &AlterPasswordPolicyOptions{ diff --git a/pkg/sdk/tasks_gen.go b/pkg/sdk/tasks_gen.go index c7e31aeb11..539fd22130 100644 --- a/pkg/sdk/tasks_gen.go +++ b/pkg/sdk/tasks_gen.go @@ -66,8 +66,8 @@ type AlterTaskOptions struct { Suspend *bool `ddl:"keyword" sql:"SUSPEND"` RemoveAfter []SchemaObjectIdentifier `ddl:"parameter,no_equals" sql:"REMOVE AFTER"` AddAfter []SchemaObjectIdentifier `ddl:"parameter,no_equals" sql:"ADD AFTER"` - Set *TaskSet `ddl:"keyword" sql:"SET"` - Unset *TaskUnset `ddl:"keyword" sql:"UNSET"` + Set *TaskSet `ddl:"list,no_parentheses" sql:"SET"` + Unset *TaskUnset `ddl:"list,no_parentheses" sql:"UNSET"` SetTags []TagAssociation `ddl:"keyword" sql:"SET TAG"` UnsetTags []ObjectIdentifier `ddl:"keyword" sql:"UNSET TAG"` ModifyAs *string `ddl:"parameter,no_quotes,no_equals" sql:"MODIFY AS"` diff --git a/pkg/sdk/tasks_gen_test.go b/pkg/sdk/tasks_gen_test.go index 7d0ac3b700..2172e58555 100644 --- a/pkg/sdk/tasks_gen_test.go +++ b/pkg/sdk/tasks_gen_test.go @@ -235,6 +235,15 @@ func TestTasks_Alter(t *testing.T) { assertOptsValidAndSQLEquals(t, opts, "ALTER TASK %s SET COMMENT = 'some comment'", id.FullyQualifiedName()) }) + t.Run("alter set: multiple", func(t *testing.T) { + opts := defaultOpts() + opts.Set = &TaskSet{ + UserTaskTimeoutMs: Int(2000), + Comment: String("some comment"), + } + assertOptsValidAndSQLEquals(t, opts, "ALTER TASK %s SET USER_TASK_TIMEOUT_MS = 2000, COMMENT = 'some comment'", id.FullyQualifiedName()) + }) + t.Run("alter set warehouse", func(t *testing.T) { warehouseId := RandomAccountObjectIdentifier() opts := defaultOpts() @@ -262,6 +271,15 @@ func TestTasks_Alter(t *testing.T) { assertOptsValidAndSQLEquals(t, opts, "ALTER TASK %s UNSET COMMENT", id.FullyQualifiedName()) }) + t.Run("alter unset: multiple", func(t *testing.T) { + opts := defaultOpts() + opts.Unset = &TaskUnset{ + UserTaskTimeoutMs: Bool(true), + Comment: Bool(true), + } + assertOptsValidAndSQLEquals(t, opts, "ALTER TASK %s UNSET USER_TASK_TIMEOUT_MS, COMMENT", id.FullyQualifiedName()) + }) + t.Run("alter set tags", func(t *testing.T) { opts := defaultOpts() opts.SetTags = []TagAssociation{ diff --git a/pkg/sdk/testint/password_policy_integration_test.go b/pkg/sdk/testint/password_policy_integration_test.go index de98ec4530..b410d7417d 100644 --- a/pkg/sdk/testint/password_policy_integration_test.go +++ b/pkg/sdk/testint/password_policy_integration_test.go @@ -93,27 +93,29 @@ func TestInt_PasswordPolicyCreate(t *testing.T) { PasswordMinLowerCaseChars: sdk.Int(1), PasswordMinNumericChars: sdk.Int(1), PasswordMinSpecialChars: sdk.Int(1), + PasswordMinAgeDays: sdk.Int(25), PasswordMaxAgeDays: sdk.Int(30), PasswordMaxRetries: sdk.Int(5), PasswordLockoutTimeMins: sdk.Int(30), - // todo [SNOW-928909]: uncomment this once comments are working again - // Comment: String("test comment"), + PasswordHistory: sdk.Int(15), + Comment: sdk.String("test comment"), }) require.NoError(t, err) passwordPolicyDetails, err := client.PasswordPolicies.Describe(ctx, id) require.NoError(t, err) assert.Equal(t, name, passwordPolicyDetails.Name.Value) - // todo [SNOW-928909]: uncomment this once comments are working again - // assert.Equal(t, "test comment", passwordPolicyDetails.Comment.Value) assert.Equal(t, 10, *passwordPolicyDetails.PasswordMinLength.Value) assert.Equal(t, 20, *passwordPolicyDetails.PasswordMaxLength.Value) assert.Equal(t, 1, *passwordPolicyDetails.PasswordMinUpperCaseChars.Value) assert.Equal(t, 1, *passwordPolicyDetails.PasswordMinLowerCaseChars.Value) assert.Equal(t, 1, *passwordPolicyDetails.PasswordMinNumericChars.Value) assert.Equal(t, 1, *passwordPolicyDetails.PasswordMinSpecialChars.Value) + assert.Equal(t, 25, *passwordPolicyDetails.PasswordMinAgeDays.Value) assert.Equal(t, 30, *passwordPolicyDetails.PasswordMaxAgeDays.Value) assert.Equal(t, 5, *passwordPolicyDetails.PasswordMaxRetries.Value) assert.Equal(t, 30, *passwordPolicyDetails.PasswordLockoutTimeMins.Value) + assert.Equal(t, 15, *passwordPolicyDetails.PasswordHistory.Value) + assert.Equal(t, "test comment", passwordPolicyDetails.Comment.Value) }) t.Run("test if_not_exists", func(t *testing.T) { @@ -125,15 +127,13 @@ func TestInt_PasswordPolicyCreate(t *testing.T) { PasswordMinLength: sdk.Int(10), PasswordMaxLength: sdk.Int(20), PasswordMinUpperCaseChars: sdk.Int(5), - // todo [SNOW-928909]: uncomment this once comments are working again - // Comment: String("test comment"), + Comment: sdk.String("test comment"), }) require.NoError(t, err) passwordPolicyDetails, err := client.PasswordPolicies.Describe(ctx, id) require.NoError(t, err) assert.Equal(t, name, passwordPolicyDetails.Name.Value) - // todo [SNOW-928909]: uncomment this once comments are working again - // assert.Equal(t, "test comment", passwordPolicyDetails.Comment.Value) + assert.Equal(t, "test comment", passwordPolicyDetails.Comment.Value) assert.Equal(t, 10, *passwordPolicyDetails.PasswordMinLength.Value) assert.Equal(t, 20, *passwordPolicyDetails.PasswordMaxLength.Value) assert.Equal(t, 5, *passwordPolicyDetails.PasswordMinUpperCaseChars.Value) @@ -231,8 +231,7 @@ func TestInt_PasswordPolicyAlter(t *testing.T) { createOptions := &sdk.CreatePasswordPolicyOptions{ PasswordMaxAgeDays: sdk.Int(20), PasswordMaxRetries: sdk.Int(10), - // todo [SNOW-928909]: uncomment this once comments are working again - // Comment: String("test comment") + Comment: sdk.String("test comment"), } passwordPolicy, passwordPolicyCleanup := createPasswordPolicyWithOptions(t, client, testDb(t), testSchema(t), createOptions) id := passwordPolicy.ID() @@ -247,8 +246,7 @@ func TestInt_PasswordPolicyAlter(t *testing.T) { alterOptions = &sdk.AlterPasswordPolicyOptions{ Unset: &sdk.PasswordPolicyUnset{ PasswordMaxAgeDays: sdk.Bool(true), - // todo [SNOW-928909]: uncomment this once comments are working again - // Comment: Bool("true") + Comment: sdk.Bool(true), }, } err = client.PasswordPolicies.Alter(ctx, id, alterOptions) @@ -264,8 +262,7 @@ func TestInt_PasswordPolicyAlter(t *testing.T) { createOptions := &sdk.CreatePasswordPolicyOptions{ PasswordMaxAgeDays: sdk.Int(20), PasswordMaxRetries: sdk.Int(10), - // todo [SNOW-928909]: uncomment this once comments are working again - // Comment: String("test comment") + Comment: sdk.String("test comment"), } passwordPolicy, passwordPolicyCleanup := createPasswordPolicyWithOptions(t, client, testDb(t), testSchema(t), createOptions) id := passwordPolicy.ID() @@ -274,12 +271,11 @@ func TestInt_PasswordPolicyAlter(t *testing.T) { Unset: &sdk.PasswordPolicyUnset{ PasswordMaxAgeDays: sdk.Bool(true), PasswordMaxRetries: sdk.Bool(true), - // todo [SNOW-928909]: uncomment this once comments are working again - // Comment: Bool("true") + Comment: sdk.Bool(true), }, } err := client.PasswordPolicies.Alter(ctx, id, alterOptions) - require.Error(t, err) + require.NoError(t, err) }) } diff --git a/pkg/sdk/testint/tasks_gen_integration_test.go b/pkg/sdk/testint/tasks_gen_integration_test.go index f68847fa47..5101171df3 100644 --- a/pkg/sdk/testint/tasks_gen_integration_test.go +++ b/pkg/sdk/testint/tasks_gen_integration_test.go @@ -389,7 +389,7 @@ func TestInt_Tasks(t *testing.T) { task := createTask(t) id := task.ID() - alterRequest := sdk.NewAlterTaskRequest(id).WithSet(sdk.NewTaskSetRequest().WithComment(sdk.String("new comment"))) + alterRequest := sdk.NewAlterTaskRequest(id).WithSet(sdk.NewTaskSetRequest().WithComment(sdk.String("new comment")).WithUserTaskTimeoutMs(sdk.Int(1000))) err := client.Tasks.Alter(ctx, alterRequest) require.NoError(t, err) @@ -398,7 +398,7 @@ func TestInt_Tasks(t *testing.T) { assert.Equal(t, "new comment", alteredTask.Comment) - alterRequest = sdk.NewAlterTaskRequest(id).WithUnset(sdk.NewTaskUnsetRequest().WithComment(sdk.Bool(true))) + alterRequest = sdk.NewAlterTaskRequest(id).WithUnset(sdk.NewTaskUnsetRequest().WithComment(sdk.Bool(true)).WithUserTaskTimeoutMs(sdk.Bool(true))) err = client.Tasks.Alter(ctx, alterRequest) require.NoError(t, err)