Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import (
func DataSourceSqlDatabaseInstanceLatestRecoveryTime() *schema.Resource {
return &schema.Resource{
Read: dataSourceSqlDatabaseInstanceLatestRecoveryTimeRead,

Schema: map[string]*schema.Schema{
"instance": {
Type: schema.TypeString,
Expand All @@ -29,6 +28,11 @@ func DataSourceSqlDatabaseInstanceLatestRecoveryTime() *schema.Resource {
Computed: true,
Description: `Timestamp, identifies the latest recovery time of the source instance.`,
},
"source_instance_deletion_time": {
Type: schema.TypeString,
Optional: true,
Description: `Timestamp, identifies when the source instance was deleted. If this instance is deleted, then you must set the timestamp.`,
},
},
}
}
Expand All @@ -39,23 +43,28 @@ func dataSourceSqlDatabaseInstanceLatestRecoveryTimeRead(d *schema.ResourceData,
if err != nil {
return err
}

fv, err := tpgresource.ParseProjectFieldValue("instances", d.Get("instance").(string), "project", d, config, false)
if err != nil {
return err
}
project := fv.Project
instance := fv.Name

latestRecoveryTime, err := config.NewSqlAdminClient(userAgent).Projects.Instances.GetLatestRecoveryTime(project, instance).Do()
deletionTime := d.Get("source_instance_deletion_time").(string)

latestRecoveryTimeCall := config.NewSqlAdminClient(userAgent).Projects.Instances.GetLatestRecoveryTime(project, instance)

if deletionTime != "" {
latestRecoveryTimeCall = latestRecoveryTimeCall.SourceInstanceDeletionTime(deletionTime)
}

latestRecoveryTime, err := latestRecoveryTimeCall.Do()
if err != nil {
return err
}

if err := d.Set("project", project); err != nil {
return fmt.Errorf("Error setting project: %s", err)
}

if err := d.Set("latest_recovery_time", latestRecoveryTime.LatestRecoveryTime); err != nil {
return fmt.Errorf("Error setting latest_recovery_time: %s", err)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package sql_test

import (
"regexp"
"testing"

"github.com/hashicorp/terraform-plugin-testing/helper/resource"
Expand All @@ -15,10 +16,19 @@ func TestAccDataSourceSqlDatabaseInstanceLatestRecoveryTime_basic(t *testing.T)
}
resourceName := "data.google_sql_database_instance_latest_recovery_time.default"

expectedError := regexp.MustCompile(`.*No backups found for instance.* and deletion time seconds: .*`)

if acctest.IsVcrEnabled() {
expectedError = regexp.MustCompile(`.*Error 400.*`)
}

acctest.VcrTest(t, resource.TestCase{
PreCheck: func() { acctest.AccTestPreCheck(t) },
ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t),
CheckDestroy: testAccSqlDatabaseInstanceDestroyProducer(t),
ExternalProviders: map[string]resource.ExternalProvider{
"time": {},
},
Steps: []resource.TestStep{
{
Config: testAccDataSourceSqlDatabaseInstanceLatestRecoveryTime_basic(context),
Expand All @@ -28,6 +38,11 @@ func TestAccDataSourceSqlDatabaseInstanceLatestRecoveryTime_basic(t *testing.T)
resource.TestCheckResourceAttrSet(resourceName, "latest_recovery_time"),
),
},
{
// On non-deleted instance should return error containing both instance name and deletion time
Config: testAccDataSourceSqlDatabaseInstanceLatestRecoveryTime_withDeletionTime(context),
ExpectError: expectedError,
},
},
})
}
Expand All @@ -52,8 +67,49 @@ resource "google_sql_database_instance" "main" {
deletion_protection = false
}

resource "time_sleep" "wait_for_instance" {
// Wait 30 seconds after the instance is created
depends_on = [google_sql_database_instance.main]
create_duration = "330s"
}

data "google_sql_database_instance_latest_recovery_time" "default" {
instance = google_sql_database_instance.main.name
depends_on = [time_sleep.wait_for_instance]
}
`, context)
}

func testAccDataSourceSqlDatabaseInstanceLatestRecoveryTime_withDeletionTime(context map[string]interface{}) string {
return acctest.Nprintf(`
resource "google_sql_database_instance" "main" {
name = "tf-test-instance-%{random_suffix}"
database_version = "POSTGRES_14"
region = "us-central1"

settings {
tier = "db-g1-small"
backup_configuration {
enabled = true
point_in_time_recovery_enabled = true
start_time = "20:55"
transaction_log_retention_days = "3"
}
}

deletion_protection = false
}

resource "time_sleep" "wait_for_instance" {
// Wait 30 seconds after the instance is created
depends_on = [google_sql_database_instance.main]
create_duration = "330s"
}

data "google_sql_database_instance_latest_recovery_time" "default" {
instance = resource.google_sql_database_instance.main.name
instance = google_sql_database_instance.main.name
source_instance_deletion_time = "2025-06-20T17:23:59.648821586Z"
depends_on = [time_sleep.wait_for_instance]
}
`, context)
}
Original file line number Diff line number Diff line change
Expand Up @@ -1251,6 +1251,12 @@ API (for read pools, effective_availability_type may differ from availability_ty
Optional: true,
Description: `The name of the allocated ip range for the private ip CloudSQL instance. For example: "google-managed-services-default". If set, the cloned instance ip will be created in the allocated range. The range name must comply with [RFC 1035](https://tools.ietf.org/html/rfc1035). Specifically, the name must be 1-63 characters long and match the regular expression [a-z]([-a-z0-9]*[a-z0-9])?.`,
},
"source_instance_deletion_time": {
Type: schema.TypeString,
Optional: true,
DiffSuppressFunc: tpgresource.TimestampDiffSuppress(time.RFC3339Nano),
Description: `The timestamp of when the source instance was deleted for a clone from a deleted instance.`,
},
},
},
},
Expand Down Expand Up @@ -1675,6 +1681,7 @@ func expandCloneContext(configured []interface{}) (*sqladmin.CloneContext, strin
PreferredZone: _cloneConfiguration["preferred_zone"].(string),
DatabaseNames: databaseNames,
AllocatedIpRange: _cloneConfiguration["allocated_ip_range"].(string),
SourceInstanceDeletionTime: _cloneConfiguration["source_instance_deletion_time"].(string),
}, _cloneConfiguration["source_instance_name"].(string)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1610,6 +1610,38 @@ func TestAccSqlDatabaseInstance_cloneWithDatabaseNames(t *testing.T) {
})
}

func TestAccSqlDatabaseInstance_cloneWithSourceInstanceDeletionTimestamp(t *testing.T) {
// Sqladmin client
acctest.SkipIfVcr(t)
t.Parallel()

project := envvar.GetTestProjectFromEnv()

context := map[string]interface{}{
"random_suffix": acctest.RandString(t, 10),
"source_db_name": "source-instance-" + acctest.RandString(t, 6),
"clone_db_name": "clone-instance-" + acctest.RandString(t, 6),
"project" : project,
"original_db_name": acctest.BootstrapSharedSQLInstanceBackupRun(t),
}

acctest.VcrTest(t, resource.TestCase{
PreCheck: func() { acctest.AccTestPreCheck(t) },
ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t),
ExternalProviders: map[string]resource.ExternalProvider{
"time": {},
},
CheckDestroy: testAccSqlDatabaseInstanceDestroyProducer(t),
Steps: []resource.TestStep{
{
// On non-deleted instance should return error containing both instance name and deletion time
Config: testAccSqlDatabaseInstance_cloneWithSourceInstanceDeletionTimestamp(context),
ExpectError: regexp.MustCompile(`.*No backups found for instance.* and deletion time seconds: .*`),
},
},
})
}

func TestAccSqlDatabaseInstance_pointInTimeRestore(t *testing.T) {
// Skipped due to randomness
acctest.SkipIfVcr(t)
Expand Down Expand Up @@ -1683,6 +1715,7 @@ func TestAccSqlDatabaseInstance_pointInTimeRestoreWithSettings(t *testing.T) {
ImportState: true,
ImportStateVerify: true,
ImportStateVerifyIgnore: []string{"deletion_protection", "point_in_time_restore_context"},

},
},
})
Expand Down Expand Up @@ -7001,6 +7034,34 @@ data "google_sql_backup_run" "backup" {
`, context)
}

func testAccSqlDatabaseInstance_cloneWithSourceInstanceDeletionTimestamp(context map[string]interface{}) string {
return acctest.Nprintf(`
resource "google_sql_database_instance" "instance" {
name = "tf-test-%{random_suffix}"
database_version = "POSTGRES_11"
region = "us-central1"

clone {
source_instance_name = data.google_sql_backup_run.backup.instance
point_in_time = data.google_sql_backup_run.backup.start_time
source_instance_deletion_time = data.google_sql_backup_run.backup.start_time
}

deletion_protection = false

// Ignore changes, since the most recent backup may change during the test
lifecycle{
ignore_changes = [clone[0].point_in_time]
}
}

data "google_sql_backup_run" "backup" {
instance = "%{original_db_name}"
most_recent = true
}
`, context)
}

func testAccSqlDatabaseInstance_cloneWithDatabaseNames(context map[string]interface{}) string {
return acctest.Nprintf(`
resource "google_sql_database_instance" "instance" {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -625,6 +625,10 @@ The optional `point_in_time_restore_context` block supports:

* `allocated_ip_range` - (Optional) The name of the allocated ip range for the private ip CloudSQL instance. For example: "google-managed-services-default". If set, the cloned instance ip will be created in the allocated range. The range name must comply with [RFC 1035](https://tools.ietf.org/html/rfc1035). Specifically, the name must be 1-63 characters long and match the regular expression [a-z]([-a-z0-9]*[a-z0-9])?.

* `source_instance_deletion_time` - (Optional) The timestamp of when the source instance was deleted for a clone from a deleted instance.

A timestamp in RFC3339 UTC "Zulu" format, with nanosecond resolution and up to nine fractional digits. Examples: "2014-10-02T15:01:23Z" and "2014-10-02T15:01:23.045123456Z".

* `database_names` - (Optional) (SQL Server only, use with `point_in_time`) Clone only the specified databases from the source instance. Clone all databases if empty.

The optional `clone` block supports:
Expand All @@ -641,6 +645,10 @@ The optional `clone` block supports:

* `allocated_ip_range` - (Optional) The name of the allocated ip range for the private ip CloudSQL instance. For example: "google-managed-services-default". If set, the cloned instance ip will be created in the allocated range. The range name must comply with [RFC 1035](https://tools.ietf.org/html/rfc1035). Specifically, the name must be 1-63 characters long and match the regular expression [a-z]([-a-z0-9]*[a-z0-9])?.

* `source_instance_deletion_time` - (Optional) The timestamp of when the source instance was deleted for a clone from a deleted instance.

A timestamp in RFC3339 UTC "Zulu" format, with nanosecond resolution and up to nine fractional digits. Examples: "2014-10-02T15:01:23Z" and "2014-10-02T15:01:23.045123456Z".

The optional `restore_backup_context` block supports:
**NOTE:** Restoring from a backup is an imperative action and not recommended via Terraform. Adding or modifying this
block during resource creation/update will trigger the restore action after the resource is created/updated.
Expand Down
Loading