From 0996d17ba151ee2849bc754832829b26434e7c05 Mon Sep 17 00:00:00 2001 From: Modular Magician Date: Thu, 15 Dec 2022 17:33:32 +0000 Subject: [PATCH] Implement Cloud SQL deletion protection (#6441) Signed-off-by: Modular Magician --- .changelog/6441.txt | 3 + google/resource_sql_database_instance.go | 75 ++++++++++--------- google/resource_sql_database_instance_test.go | 54 +++++++++++++ .../r/sql_database_instance.html.markdown | 4 + 4 files changed, 102 insertions(+), 34 deletions(-) create mode 100644 .changelog/6441.txt diff --git a/.changelog/6441.txt b/.changelog/6441.txt new file mode 100644 index 00000000000..87fd432acc8 --- /dev/null +++ b/.changelog/6441.txt @@ -0,0 +1,3 @@ +```release-note:enhancement +sql_database_instance: added [new deletion protection](https://cloud.google.com/sql/docs/mysql/deletion-protection) feature `deletion_protection_enabled` to guard against deletion from all surfaces. +``` diff --git a/google/resource_sql_database_instance.go b/google/resource_sql_database_instance.go index 152659ad9fb..9e59f1bc433 100644 --- a/google/resource_sql_database_instance.go +++ b/google/resource_sql_database_instance.go @@ -552,6 +552,11 @@ is set to true. Defaults to ZONAL.`, ValidateFunc: validation.StringInSlice([]string{"NOT_REQUIRED", "REQUIRED"}, false), Description: `Specifies if connections must use Cloud SQL connectors.`, }, + "deletion_protection_enabled": { + Type: schema.TypeBool, + Optional: true, + Description: `Configuration to protect against accidental instance deletion.`, + }, }, }, Description: `The settings to use for the database. The configuration is detailed below.`, @@ -1085,28 +1090,29 @@ func expandSqlDatabaseInstanceSettings(configured []interface{}) *sqladmin.Setti _settings := configured[0].(map[string]interface{}) settings := &sqladmin.Settings{ // Version is unset in Create but is set during update - SettingsVersion: int64(_settings["version"].(int)), - Tier: _settings["tier"].(string), - ForceSendFields: []string{"StorageAutoResize"}, - ActivationPolicy: _settings["activation_policy"].(string), - ActiveDirectoryConfig: expandActiveDirectoryConfig(_settings["active_directory_config"].([]interface{})), - DenyMaintenancePeriods: expandDenyMaintenancePeriod(_settings["deny_maintenance_period"].([]interface{})), - SqlServerAuditConfig: expandSqlServerAuditConfig(_settings["sql_server_audit_config"].([]interface{})), - TimeZone: _settings["time_zone"].(string), - AvailabilityType: _settings["availability_type"].(string), - ConnectorEnforcement: _settings["connector_enforcement"].(string), - Collation: _settings["collation"].(string), - DataDiskSizeGb: int64(_settings["disk_size"].(int)), - DataDiskType: _settings["disk_type"].(string), - PricingPlan: _settings["pricing_plan"].(string), - UserLabels: convertStringMap(_settings["user_labels"].(map[string]interface{})), - BackupConfiguration: expandBackupConfiguration(_settings["backup_configuration"].([]interface{})), - DatabaseFlags: expandDatabaseFlags(_settings["database_flags"].([]interface{})), - IpConfiguration: expandIpConfiguration(_settings["ip_configuration"].([]interface{})), - LocationPreference: expandLocationPreference(_settings["location_preference"].([]interface{})), - MaintenanceWindow: expandMaintenanceWindow(_settings["maintenance_window"].([]interface{})), - InsightsConfig: expandInsightsConfig(_settings["insights_config"].([]interface{})), - PasswordValidationPolicy: expandPasswordValidationPolicy(_settings["password_validation_policy"].([]interface{})), + SettingsVersion: int64(_settings["version"].(int)), + Tier: _settings["tier"].(string), + ForceSendFields: []string{"StorageAutoResize"}, + ActivationPolicy: _settings["activation_policy"].(string), + ActiveDirectoryConfig: expandActiveDirectoryConfig(_settings["active_directory_config"].([]interface{})), + DenyMaintenancePeriods: expandDenyMaintenancePeriod(_settings["deny_maintenance_period"].([]interface{})), + SqlServerAuditConfig: expandSqlServerAuditConfig(_settings["sql_server_audit_config"].([]interface{})), + TimeZone: _settings["time_zone"].(string), + AvailabilityType: _settings["availability_type"].(string), + ConnectorEnforcement: _settings["connector_enforcement"].(string), + Collation: _settings["collation"].(string), + DataDiskSizeGb: int64(_settings["disk_size"].(int)), + DataDiskType: _settings["disk_type"].(string), + PricingPlan: _settings["pricing_plan"].(string), + DeletionProtectionEnabled: _settings["deletion_protection_enabled"].(bool), + UserLabels: convertStringMap(_settings["user_labels"].(map[string]interface{})), + BackupConfiguration: expandBackupConfiguration(_settings["backup_configuration"].([]interface{})), + DatabaseFlags: expandDatabaseFlags(_settings["database_flags"].([]interface{})), + IpConfiguration: expandIpConfiguration(_settings["ip_configuration"].([]interface{})), + LocationPreference: expandLocationPreference(_settings["location_preference"].([]interface{})), + MaintenanceWindow: expandMaintenanceWindow(_settings["maintenance_window"].([]interface{})), + InsightsConfig: expandInsightsConfig(_settings["insights_config"].([]interface{})), + PasswordValidationPolicy: expandPasswordValidationPolicy(_settings["password_validation_policy"].([]interface{})), } resize := _settings["disk_autoresize"].(bool) @@ -1623,18 +1629,19 @@ func resourceSqlDatabaseInstanceImport(d *schema.ResourceData, meta interface{}) func flattenSettings(settings *sqladmin.Settings) []map[string]interface{} { data := map[string]interface{}{ - "version": settings.SettingsVersion, - "tier": settings.Tier, - "activation_policy": settings.ActivationPolicy, - "availability_type": settings.AvailabilityType, - "collation": settings.Collation, - "connector_enforcement": settings.ConnectorEnforcement, - "disk_type": settings.DataDiskType, - "disk_size": settings.DataDiskSizeGb, - "pricing_plan": settings.PricingPlan, - "user_labels": settings.UserLabels, - "password_validation_policy": settings.PasswordValidationPolicy, - "time_zone": settings.TimeZone, + "version": settings.SettingsVersion, + "tier": settings.Tier, + "activation_policy": settings.ActivationPolicy, + "availability_type": settings.AvailabilityType, + "collation": settings.Collation, + "connector_enforcement": settings.ConnectorEnforcement, + "disk_type": settings.DataDiskType, + "disk_size": settings.DataDiskSizeGb, + "pricing_plan": settings.PricingPlan, + "user_labels": settings.UserLabels, + "password_validation_policy": settings.PasswordValidationPolicy, + "time_zone": settings.TimeZone, + "deletion_protection_enabled": settings.DeletionProtectionEnabled, } if settings.ActiveDirectoryConfig != nil { diff --git a/google/resource_sql_database_instance_test.go b/google/resource_sql_database_instance_test.go index d396a9ece1b..b031191b256 100644 --- a/google/resource_sql_database_instance_test.go +++ b/google/resource_sql_database_instance_test.go @@ -314,6 +314,46 @@ func TestAccSqlDatabaseInstance_maintenanceVersion(t *testing.T) { }) } +func TestAccSqlDatabaseInstance_settings_deletionProtectionEnabled(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_settings_deletionProtectionEnabled, databaseName, "true"), + }, + { + ResourceName: "google_sql_database_instance.instance", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"deletion_protection"}, + }, + { + Config: fmt.Sprintf( + testGoogleSqlDatabaseInstance_settings_deletionProtectionEnabled, databaseName, "true"), + Destroy: true, + ExpectError: regexp.MustCompile(fmt.Sprintf("Error, failed to delete instance %s: googleapi: Error 400: The instance is protected. Please disable the deletion protection and try again. To disable deletion protection, update the instance settings with deletionProtectionEnabled set to false.", databaseName)), + }, + { + Config: fmt.Sprintf( + testGoogleSqlDatabaseInstance_settings_deletionProtectionEnabled, databaseName, "false"), + }, + { + ResourceName: "google_sql_database_instance.instance", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"deletion_protection"}, + }, + }, + }) +} + func TestAccSqlDatabaseInstance_settings_checkServiceNetworking(t *testing.T) { t.Parallel() @@ -1848,6 +1888,7 @@ resource "google_sql_database_instance" "instance" { } } ` + var testGoogleSqlDatabaseInstance_maintenanceVersionWithOldVersion = ` resource "google_sql_database_instance" "instance" { name = "%s" @@ -1861,6 +1902,19 @@ resource "google_sql_database_instance" "instance" { } ` +var testGoogleSqlDatabaseInstance_settings_deletionProtectionEnabled = ` +resource "google_sql_database_instance" "instance" { + name = "%s" + region = "us-central1" + database_version = "MYSQL_5_7" + deletion_protection = false + settings { + deletion_protection_enabled = %s + tier = "db-f1-micro" + } +} +` + var testGoogleSqlDatabaseInstance_settings_checkServiceNetworking = ` resource "google_compute_network" "servicenet" { name = "%s" diff --git a/website/docs/r/sql_database_instance.html.markdown b/website/docs/r/sql_database_instance.html.markdown index a211b393006..c648605fd55 100644 --- a/website/docs/r/sql_database_instance.html.markdown +++ b/website/docs/r/sql_database_instance.html.markdown @@ -210,6 +210,8 @@ includes an up-to-date reference of supported versions. * `deletion_protection` - (Optional) Whether or not to allow Terraform to destroy the instance. Unless this field is set to false in Terraform state, a `terraform destroy` or `terraform apply` command that deletes the instance will fail. Defaults to `true`. + + ~> **NOTE:** This flag only protects instances from deletion within Terraform. To protect your instances from accidental deletion across all surfaces (API, gcloud, Cloud Console and Terraform), use the API flag `settings.deletion_protection_enabled`. * `restore_backup_context` - (optional) The context needed to restore the database to a backup run. This field will cause Terraform to trigger the database to restore from the backup run indicated. The configuration is detailed below. @@ -240,6 +242,8 @@ The `settings` block supports: * `connector_enforcement` - (Optional) Specifies if connections must use Cloud SQL connectors. +* `deletion_protection_enabled` - (Optional) Enables protection of an instance from accidental deletion protection across all surfaces (API, gcloud, Cloud Console and Terraform). Defaults to `false`. + * `disk_autoresize` - (Optional) Enables auto-resizing of the storage size. Defaults to `true`. * `disk_autoresize_limit` - (Optional) The maximum size to which storage capacity can be automatically increased. The default value is 0, which specifies that there is no limit.