Skip to content

Commit

Permalink
Sql pvup support (#6472) (#12668)
Browse files Browse the repository at this point in the history
* Correcting miss-spelled word in the documentation

Correcting miss-spelled word

* Adding support for password validation user policy for Mysql.

* Correction to the testing logic and spacing correction.

* Adding flatten for new user level password policy.

* Adding destroy check to sql_user test.

* Fixing the test failure.

* Fixing some definitions that I got wrong.

* Update sql_database_instance_my_sql.tf.erb

* Fixing some more issues that caused tests to fail

* correcting typo

* Update resource_sql_user_test.go

* adding check for null

* Update resource_sql_user_test.go

Signed-off-by: Modular Magician <magic-modules@google.com>

Signed-off-by: Modular Magician <magic-modules@google.com>
  • Loading branch information
modular-magician committed Sep 28, 2022
1 parent 6a39e71 commit c5278cf
Show file tree
Hide file tree
Showing 4 changed files with 226 additions and 1 deletion.
4 changes: 4 additions & 0 deletions .changelog/6472.txt
@@ -0,0 +1,4 @@
```release-note:enhancement
sql: added `password_policy` field to `google_sql_user` resource

```
128 changes: 127 additions & 1 deletion google/resource_sql_user.go
Expand Up @@ -105,6 +105,54 @@ func resourceSqlUser() *schema.Resource {
},
},

"password_policy": {
Type: schema.TypeList,
Optional: true,
MaxItems: 1,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"allowed_failed_attempts": {
Type: schema.TypeInt,
Optional: true,
Description: `Number of failed attempts allowed before the user get locked.`,
},
"password_expiration_duration": {
Type: schema.TypeString,
Optional: true,
Description: `Password expiration duration with one week grace period.`,
},
"enable_failed_attempts_check": {
Type: schema.TypeBool,
Optional: true,
Description: `If true, the check that will lock user after too many failed login attempts will be enabled.`,
},
"enable_password_verification": {
Type: schema.TypeBool,
Optional: true,
Description: `If true, the user must specify the current password before changing the password. This flag is supported only for MySQL.`,
},
"status": {
Type: schema.TypeList,
Computed: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"locked": {
Type: schema.TypeBool,
Computed: true,
Description: `If true, user does not have login privileges.`,
},
"password_expiration_time": {
Type: schema.TypeString,
Computed: true,
Description: `Password expiration duration with one week grace period.`,
},
},
},
},
},
},
},

"project": {
Type: schema.TypeString,
Optional: true,
Expand Down Expand Up @@ -142,6 +190,30 @@ func expandSqlServerUserDetails(cfg interface{}) (*sqladmin.SqlServerUserDetails

}

func expandPasswordPolicy(cfg interface{}) *sqladmin.UserPasswordValidationPolicy {
if len(cfg.([]interface{})) == 0 || cfg.([]interface{})[0] == nil {
return nil
}
raw := cfg.([]interface{})[0].(map[string]interface{})

upvp := &sqladmin.UserPasswordValidationPolicy{}

if v, ok := raw["allowed_failed_attempts"]; ok {
upvp.AllowedFailedAttempts = int64(v.(int))
}
if v, ok := raw["password_expiration_duration"]; ok {
upvp.PasswordExpirationDuration = v.(string)
}
if v, ok := raw["enable_failed_attempts_check"]; ok {
upvp.EnableFailedAttemptsCheck = v.(bool)
}
if v, ok := raw["enable_password_verification"]; ok {
upvp.EnablePasswordVerification = v.(bool)
}

return upvp
}

func resourceSqlUserCreate(d *schema.ResourceData, meta interface{}) error {
config := meta.(*Config)
userAgent, err := generateUserAgentString(d, config.userAgent)
Expand Down Expand Up @@ -176,6 +248,11 @@ func resourceSqlUserCreate(d *schema.ResourceData, meta interface{}) error {
user.SqlserverUserDetails = ssud
}

if v, ok := d.GetOk("password_policy"); ok {
pp := expandPasswordPolicy(v)
user.PasswordPolicy = pp
}

mutexKV.Lock(instanceMutexKey(project, instance))
defer mutexKV.Unlock(instanceMutexKey(project, instance))
var op *sqladmin.Operation
Expand Down Expand Up @@ -282,18 +359,66 @@ func resourceSqlUserRead(d *schema.ResourceData, meta interface{}) error {
return fmt.Errorf("Error setting server_roles: %s", err)
}
}

if user.PasswordPolicy != nil {
passwordPolicy := flattenPasswordPolicy(user.PasswordPolicy)
if len(passwordPolicy.([]map[string]interface{})[0]) != 0 {
if err := d.Set("password_policy", passwordPolicy); err != nil {
return fmt.Errorf("Error setting password_policy: %s", err)
}
}
}
d.SetId(fmt.Sprintf("%s/%s/%s", user.Name, user.Host, user.Instance))
return nil
}

func flattenPasswordPolicy(passwordPolicy *sqladmin.UserPasswordValidationPolicy) interface{} {
data := map[string]interface{}{}
if passwordPolicy.AllowedFailedAttempts != 0 {
data["allowed_failed_attempts"] = passwordPolicy.AllowedFailedAttempts
}

if passwordPolicy.EnableFailedAttemptsCheck != false {
data["enable_failed_attempts_check"] = passwordPolicy.EnableFailedAttemptsCheck
}

if passwordPolicy.EnablePasswordVerification != false {
data["enable_password_verification"] = passwordPolicy.EnablePasswordVerification
}
if len(passwordPolicy.PasswordExpirationDuration) != 0 {
data["password_expiration_duration"] = passwordPolicy.PasswordExpirationDuration
}

if passwordPolicy.Status != nil {
status := flattenPasswordStatus(passwordPolicy.Status)
if len(status.([]map[string]interface{})[0]) != 0 {
data["status"] = flattenPasswordStatus(passwordPolicy.Status)
}
}

return []map[string]interface{}{data}
}

func flattenPasswordStatus(status *sqladmin.PasswordStatus) interface{} {
data := map[string]interface{}{}
if status.Locked != false {
data["locked"] = status.Locked
}
if len(status.PasswordExpirationTime) != 0 {
data["password_expiration_time"] = status.PasswordExpirationTime
}

return []map[string]interface{}{data}
}

func resourceSqlUserUpdate(d *schema.ResourceData, meta interface{}) error {
config := meta.(*Config)
userAgent, err := generateUserAgentString(d, config.userAgent)
if err != nil {
return err
}

if d.HasChange("password") {
if d.HasChange("password") || d.HasChange("password_policy") {
project, err := getProject(d, config)
if err != nil {
return err
Expand All @@ -317,6 +442,7 @@ func resourceSqlUserUpdate(d *schema.ResourceData, meta interface{}) error {
}
user.SqlserverUserDetails = ssud
}
user.PasswordPolicy = expandPasswordPolicy(d.Get("password_policy"))

mutexKV.Lock(instanceMutexKey(project, instance))
defer mutexKV.Unlock(instanceMutexKey(project, instance))
Expand Down
79 changes: 79 additions & 0 deletions google/resource_sql_user_test.go
Expand Up @@ -279,6 +279,43 @@ func testAccSqlUserDestroyProducer(t *testing.T) func(s *terraform.State) error
}
}

func TestAccSqlUser_mysqlPasswordPolicy(t *testing.T) {
// Multiple fine-grained resources
skipIfVcr(t)
t.Parallel()

instance := fmt.Sprintf("tf-test-i%d", randInt(t))
vcrTest(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccSqlUserDestroyProducer(t),
Steps: []resource.TestStep{
{
Config: testGoogleSqlUser_mysqlPasswordPolicy(instance, "password", false),
Check: resource.ComposeTestCheckFunc(
testAccCheckGoogleSqlUserExists(t, "google_sql_user.user1"),
testAccCheckGoogleSqlUserExists(t, "google_sql_user.user2"),
),
},
{
// Update password
Config: testGoogleSqlUser_mysqlPasswordPolicy(instance, "new_password", false),
Check: resource.ComposeTestCheckFunc(
testAccCheckGoogleSqlUserExists(t, "google_sql_user.user1"),
testAccCheckGoogleSqlUserExists(t, "google_sql_user.user2"),
),
},
{
ResourceName: "google_sql_user.user2",
ImportStateId: fmt.Sprintf("%s/%s/gmail.com/admin", getTestProjectFromEnv(), instance),
ImportState: true,
ImportStateVerify: true,
ImportStateVerifyIgnore: []string{"password"},
},
},
})
}

func testGoogleSqlUser_mysql(instance, password string, disabled bool) string {
return fmt.Sprintf(`
resource "google_sql_database_instance" "instance" {
Expand Down Expand Up @@ -312,6 +349,48 @@ resource "google_sql_user" "user2" {
`, instance, password, disabled)
}

func testGoogleSqlUser_mysqlPasswordPolicy(instance, password string, disabled bool) string {
return fmt.Sprintf(`
resource "google_sql_database_instance" "instance" {
name = "%s"
region = "us-central1"
database_version = "MYSQL_8_0"
deletion_protection = false
settings {
tier = "db-f1-micro"
}
}
resource "google_sql_user" "user1" {
name = "admin"
instance = google_sql_database_instance.instance.name
host = "google.com"
password = "%s"
sql_server_user_details {
disabled = "%t"
server_roles = [ "admin" ]
}
password_policy {
allowed_failed_attempts = 6
password_expiration_duration = "2592000s"
enable_failed_attempts_check = true
enable_password_verification = true
}
}
resource "google_sql_user" "user2" {
name = "admin"
instance = google_sql_database_instance.instance.name
host = "gmail.com"
password = "hunter2"
password_policy {
allowed_failed_attempts = 6
enable_failed_attempts_check = true
}
}
`, instance, password, disabled)
}

func testGoogleSqlUser_postgres(instance, password string) string {
return fmt.Sprintf(`
resource "google_sql_database_instance" "instance" {
Expand Down
16 changes: 16 additions & 0 deletions website/docs/r/sql_user.html.markdown
Expand Up @@ -100,6 +100,22 @@ The following arguments are supported:
* `project` - (Optional) The ID of the project in which the resource belongs. If it
is not provided, the provider project is used.

The optional `password_policy` block is only supported by Mysql. The `password_policy` block supports:

* `allowed_failed_attempts` - (Optional) Number of failed attempts allowed before the user get locked.

* `password_expiration_duration` - (Optional) Password expiration duration with one week grace period.

* `enable_failed_attempts_check` - (Optional) If true, the check that will lock user after too many failed login attempts will be enabled.

* `enable_password_verification` - (Optional) If true, the user must specify the current password before changing the password. This flag is supported only for MySQL.

The read only `password_policy.status` subblock supports:

* `locked` - (read only) If true, user does not have login privileges.

* `password_expiration_time` - (read only) Password expiration duration with one week grace period.

## Attributes Reference

Only the arguments listed above are exposed as attributes.
Expand Down

0 comments on commit c5278cf

Please sign in to comment.