Skip to content

Commit

Permalink
Support Self Service Maintenance feature (#6131) (#4725)
Browse files Browse the repository at this point in the history
Co-authored-by: Shuya Ma <87669292+shuyama1@users.noreply.github.com>
Signed-off-by: Modular Magician <magic-modules@google.com>

Signed-off-by: Modular Magician <magic-modules@google.com>
Co-authored-by: Shuya Ma <87669292+shuyama1@users.noreply.github.com>
  • Loading branch information
modular-magician and shuyama1 authored Sep 27, 2022
1 parent c6f835d commit 54b626a
Show file tree
Hide file tree
Showing 4 changed files with 153 additions and 2 deletions.
3 changes: 3 additions & 0 deletions .changelog/6131.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:enhancement
sql: added `maintenance_version` and `available_maintenance_versions` fields to `google_sql_database_instance` resource
```
63 changes: 61 additions & 2 deletions google-beta/resource_sql_database_instance.go
Original file line number Diff line number Diff line change
Expand Up @@ -515,7 +515,21 @@ is set to true. Defaults to ZONAL.`,
Computed: true,
Description: `The connection name of the instance to be used in connection strings. For example, when connecting with Cloud SQL Proxy.`,
},

"maintenance_version": {
Type: schema.TypeString,
Computed: true,
Optional: true,
Description: `Maintenance version.`,
DiffSuppressFunc: maintenanceVersionDiffSuppress,
},
"available_maintenance_versions": {
Type: schema.TypeList,
Computed: true,
Elem: &schema.Schema{
Type: schema.TypeString,
},
Description: `Available Maintenance versions.`,
},
"database_version": {
Type: schema.TypeString,
Required: true,
Expand All @@ -536,7 +550,6 @@ is set to true. Defaults to ZONAL.`,
Sensitive: true,
Description: `Initial root password. Required for MS SQL Server.`,
},

"ip_address": {
Type: schema.TypeList,
Computed: true,
Expand Down Expand Up @@ -871,6 +884,10 @@ func resourceSqlDatabaseInstanceCreate(d *schema.ResourceData, meta interface{})
instance.Settings = desiredSettings
}

if _, ok := d.GetOk("maintenance_version"); ok {
instance.MaintenanceVersion = d.Get("maintenance_version").(string)
}

instance.RootPassword = d.Get("root_password").(string)

// Modifying a replica during Create can cause problems if the master is
Expand Down Expand Up @@ -1280,6 +1297,12 @@ func resourceSqlDatabaseInstanceRead(d *schema.ResourceData, meta interface{}) e
if err := d.Set("connection_name", instance.ConnectionName); err != nil {
return fmt.Errorf("Error setting connection_name: %s", err)
}
if err := d.Set("maintenance_version", instance.MaintenanceVersion); err != nil {
return fmt.Errorf("Error setting maintenance_version: %s", err)
}
if err := d.Set("available_maintenance_versions", instance.AvailableMaintenanceVersions); err != nil {
return fmt.Errorf("Error setting available_maintenance_version: %s", err)
}
if err := d.Set("service_account_email_address", instance.ServiceAccountEmailAddress); err != nil {
return fmt.Errorf("Error setting service_account_email_address: %s", err)
}
Expand Down Expand Up @@ -1356,10 +1379,15 @@ func resourceSqlDatabaseInstanceUpdate(d *schema.ResourceData, meta interface{})
if err != nil {
return err
}
var maintenance_version string
if v, ok := d.GetOk("maintenance_version"); ok {
maintenance_version = v.(string)
}

desiredSetting := d.Get("settings")
var op *sqladmin.Operation
var instance *sqladmin.DatabaseInstance

// Check if the database version is being updated, because patching database version is an atomic operation and can not be
// performed with other fields, we first patch database version before updating the rest of the fields.
if v, ok := d.GetOk("database_version"); ok {
Expand All @@ -1381,6 +1409,27 @@ func resourceSqlDatabaseInstanceUpdate(d *schema.ResourceData, meta interface{})
}
}

// Check if the maintenance version is being updated, because patching maintenance version is an atomic operation and can not be
// performed with other fields, we first patch maintenance version before updating the rest of the fields.
if d.HasChange("maintenance_version") {
instance = &sqladmin.DatabaseInstance{MaintenanceVersion: maintenance_version}
err = retryTimeDuration(func() (rerr error) {
op, rerr = config.NewSqlAdminClient(userAgent).Instances.Patch(project, d.Get("name").(string), instance).Do()
return rerr
}, d.Timeout(schema.TimeoutUpdate), isSqlOperationInProgressError)
if err != nil {
return fmt.Errorf("Error, failed to patch instance settings for %s: %s", instance.Name, err)
}
err = sqlAdminOperationWaitTime(config, op, project, "Patch Instance", userAgent, d.Timeout(schema.TimeoutUpdate))
if err != nil {
return err
}
err = resourceSqlDatabaseInstanceRead(d, meta)
if err != nil {
return err
}
}

s := d.Get("settings")
instance = &sqladmin.DatabaseInstance{
Settings: expandSqlDatabaseInstanceSettings(desiredSetting.([]interface{})),
Expand Down Expand Up @@ -1426,6 +1475,16 @@ func resourceSqlDatabaseInstanceUpdate(d *schema.ResourceData, meta interface{})
return resourceSqlDatabaseInstanceRead(d, meta)
}

func maintenanceVersionDiffSuppress(_, old, new string, _ *schema.ResourceData) bool {
// Ignore the database version part and only compare the last part of the maintenance version which represents the release date of the version.
if len(old) > 14 && len(new) > 14 && old[len(old)-14:] >= new[len(new)-14:] {
log.Printf("[DEBUG] Maintenance version in configuration [%s] is older than current maintenance version [%s] on instance. Suppressing diff", new, old)
return true
} else {
return false
}
}

func resourceSqlDatabaseInstanceDelete(d *schema.ResourceData, meta interface{}) error {
config := meta.(*Config)
userAgent, err := generateUserAgentString(d, config.userAgent)
Expand Down
85 changes: 85 additions & 0 deletions google-beta/resource_sql_database_instance_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,39 @@ func init() {
})
}

func TestMaintenanceVersionDiffSuppress(t *testing.T) {
cases := map[string]struct {
Old, New string
ShouldSuppress bool
}{
"older configuration maintenance version than current version should suppress diff": {
Old: "MYSQL_8_0_26.R20220508.01_09",
New: "MYSQL_5_7_37.R20210508.01_03",
ShouldSuppress: true,
},
"older configuration maintenance version than current version should suppress diff with lexicographically smaller database version": {
Old: "MYSQL_5_8_10.R20220508.01_09",
New: "MYSQL_5_8_7.R20210508.01_03",
ShouldSuppress: true,
},
"newer configuration maintenance version than current version should not suppress diff": {
Old: "MYSQL_5_7_37.R20210508.01_03",
New: "MYSQL_8_0_26.R20220508.01_09",
ShouldSuppress: false,
},
}

for tn, tc := range cases {
tc := tc
t.Run(tn, func(t *testing.T) {
t.Parallel()
if maintenanceVersionDiffSuppress("version", tc.Old, tc.New, nil) != tc.ShouldSuppress {
t.Fatalf("%q => %q expect DiffSuppress to return %t", tc.Old, tc.New, tc.ShouldSuppress)
}
})
}
}

func testSweepDatabases(region string) error {
config, err := sharedConfigForRegion(region)
if err != nil {
Expand Down Expand Up @@ -367,6 +400,46 @@ func TestAccSqlDatabaseInstance_settings_deletionProtection(t *testing.T) {
})
}

func TestAccSqlDatabaseInstance_maintenanceVersion(t *testing.T) {
t.Parallel()

databaseName := "tf-test-" + randString(t, 10)

vcrTest(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccSqlDatabaseInstanceDestroyProducer(t),
Steps: []resource.TestStep{
{
Config: fmt.Sprintf(
testGoogleSqlDatabaseInstance_maintenanceVersionWithOldVersion, databaseName),
ExpectError: regexp.MustCompile(
`.*Maintenance version \(MYSQL_5_7_37.R20210508.01_03\) must not be set.*`),
},
{
Config: fmt.Sprintf(
testGoogleSqlDatabaseInstance_basic3, databaseName),
},
{
ResourceName: "google_sql_database_instance.instance",
ImportState: true,
ImportStateVerify: true,
ImportStateVerifyIgnore: []string{"deletion_protection"},
},
{
Config: fmt.Sprintf(
testGoogleSqlDatabaseInstance_maintenanceVersionWithOldVersion, databaseName),
},
{
ResourceName: "google_sql_database_instance.instance",
ImportState: true,
ImportStateVerify: true,
ImportStateVerifyIgnore: []string{"deletion_protection"},
},
},
})
}

func TestAccSqlDatabaseInstance_settings_checkServiceNetworking(t *testing.T) {
t.Parallel()

Expand Down Expand Up @@ -1779,6 +1852,18 @@ resource "google_sql_database_instance" "instance" {
}
}
`
var testGoogleSqlDatabaseInstance_maintenanceVersionWithOldVersion = `
resource "google_sql_database_instance" "instance" {
name = "%s"
region = "us-central1"
database_version = "MYSQL_5_7"
deletion_protection = false
maintenance_version = "MYSQL_5_7_37.R20210508.01_03"
settings {
tier = "db-f1-micro"
}
}
`

var testGoogleSqlDatabaseInstance_settings_checkServiceNetworking = `
resource "google_compute_network" "servicenet" {
Expand Down
4 changes: 4 additions & 0 deletions website/docs/r/sql_database_instance.html.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,8 @@ includes an up-to-date reference of supported versions.
created. This is done because after a name is used, it cannot be reused for
up to [one week](https://cloud.google.com/sql/docs/delete-instance).

* `maintenance_version` - (Optional) The current software version on the instance. This attribute can not be set during creation. Refer to `available_maintenance_versions` attribute to see what `maintenance_version` are available for upgrade. When this attribute gets updated, it will cause an instance restart. Setting a `maintenance_version` value that is older than the current one on the instance will be ignored.

* `master_instance_name` - (Optional) The name of the existing instance that will
act as the master in the replication setup. Note, this requires the master to
have `binary_log_enabled` set, as well as existing backups.
Expand Down Expand Up @@ -449,6 +451,8 @@ instance.
support accessing the [first address in the list in a terraform output](https://github.com/hashicorp/terraform-provider-google/issues/912)
when the resource is configured with a `count`.

* `available_maintenance_versions` - The list of all maintenance versions applicable on the instance.

* `public_ip_address` - The first public (`PRIMARY`) IPv4 address assigned. This is
a workaround for an [issue fixed in Terraform 0.12](https://github.com/hashicorp/terraform/issues/17048)
but also provides a convenient way to access an IP of a specific type without
Expand Down

0 comments on commit 54b626a

Please sign in to comment.