Skip to content

Commit 6e1107b

Browse files
committed
adding source_instance_deletion_time
1 parent e9c7b3b commit 6e1107b

File tree

5 files changed

+203
-5
lines changed

5 files changed

+203
-5
lines changed

mmv1/third_party/terraform/services/sql/data_source_sql_database_instance_latest_recovery_time.go

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ import (
1111
func DataSourceSqlDatabaseInstanceLatestRecoveryTime() *schema.Resource {
1212
return &schema.Resource{
1313
Read: dataSourceSqlDatabaseInstanceLatestRecoveryTimeRead,
14-
1514
Schema: map[string]*schema.Schema{
1615
"instance": {
1716
Type: schema.TypeString,
@@ -29,6 +28,11 @@ func DataSourceSqlDatabaseInstanceLatestRecoveryTime() *schema.Resource {
2928
Computed: true,
3029
Description: `Timestamp, identifies the latest recovery time of the source instance.`,
3130
},
31+
"source_instance_deletion_time": {
32+
Type: schema.TypeString,
33+
Optional: true,
34+
Description: `Timestamp, identifies when the source instance was deleted. If this instance is deleted, then you must set the timestamp.`,
35+
},
3236
},
3337
}
3438
}
@@ -39,23 +43,28 @@ func dataSourceSqlDatabaseInstanceLatestRecoveryTimeRead(d *schema.ResourceData,
3943
if err != nil {
4044
return err
4145
}
42-
4346
fv, err := tpgresource.ParseProjectFieldValue("instances", d.Get("instance").(string), "project", d, config, false)
4447
if err != nil {
4548
return err
4649
}
4750
project := fv.Project
4851
instance := fv.Name
4952

50-
latestRecoveryTime, err := config.NewSqlAdminClient(userAgent).Projects.Instances.GetLatestRecoveryTime(project, instance).Do()
53+
deletionTime := d.Get("source_instance_deletion_time").(string)
54+
55+
latestRecoveryTimeCall := config.NewSqlAdminClient(userAgent).Projects.Instances.GetLatestRecoveryTime(project, instance)
56+
57+
if deletionTime != "" {
58+
latestRecoveryTimeCall = latestRecoveryTimeCall.SourceInstanceDeletionTime(deletionTime)
59+
}
60+
61+
latestRecoveryTime, err := latestRecoveryTimeCall.Do()
5162
if err != nil {
5263
return err
5364
}
54-
5565
if err := d.Set("project", project); err != nil {
5666
return fmt.Errorf("Error setting project: %s", err)
5767
}
58-
5968
if err := d.Set("latest_recovery_time", latestRecoveryTime.LatestRecoveryTime); err != nil {
6069
return fmt.Errorf("Error setting latest_recovery_time: %s", err)
6170
}

mmv1/third_party/terraform/services/sql/data_source_sql_database_instance_latest_recovery_time_test.go

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,3 +57,30 @@ data "google_sql_database_instance_latest_recovery_time" "default" {
5757
}
5858
`, context)
5959
}
60+
61+
func testAccDataSourceSqlDatabaseInstanceLatestRecoveryTime_withDeletionTime(context map[string]interface{}) string {
62+
return acctest.Nprintf(`
63+
resource "google_sql_database_instance" "main" {
64+
name = "tf-test-instance-%{random_suffix}"
65+
database_version = "POSTGRES_14"
66+
region = "us-central1"
67+
68+
settings {
69+
tier = "db-g1-small"
70+
backup_configuration {
71+
enabled = true
72+
point_in_time_recovery_enabled = true
73+
start_time = "20:55"
74+
transaction_log_retention_days = "3"
75+
}
76+
}
77+
78+
deletion_protection = false
79+
}
80+
81+
data "google_sql_database_instance_latest_recovery_time" "default" {
82+
instance = resource.google_sql_database_instance.main.name
83+
source_instance_deletion_time = "2025-06-20T17:23:59.648821586Z"
84+
}
85+
`, context)
86+
}

mmv1/third_party/terraform/services/sql/resource_sql_database_instance.go.tmpl

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1251,6 +1251,12 @@ API (for read pools, effective_availability_type may differ from availability_ty
12511251
Optional: true,
12521252
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])?.`,
12531253
},
1254+
"source_instance_deletion_time": {
1255+
Type: schema.TypeString,
1256+
Optional: true,
1257+
DiffSuppressFunc: tpgresource.TimestampDiffSuppress(time.RFC3339Nano),
1258+
Description: `The timestamp of when the source instance was deleted for a clone from a deleted instance.`,
1259+
},
12541260
},
12551261
},
12561262
},
@@ -1675,6 +1681,7 @@ func expandCloneContext(configured []interface{}) (*sqladmin.CloneContext, strin
16751681
PreferredZone: _cloneConfiguration["preferred_zone"].(string),
16761682
DatabaseNames: databaseNames,
16771683
AllocatedIpRange: _cloneConfiguration["allocated_ip_range"].(string),
1684+
SourceInstanceDeletionTime: _cloneConfiguration["source_instance_deletion_time"].(string),
16781685
}, _cloneConfiguration["source_instance_name"].(string)
16791686
}
16801687

mmv1/third_party/terraform/services/sql/resource_sql_database_instance_test.go.tmpl

Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1610,6 +1610,129 @@ func TestAccSqlDatabaseInstance_cloneWithDatabaseNames(t *testing.T) {
16101610
})
16111611
}
16121612

1613+
func testGoogleSqlDatabaseInstanceConfig_withPitrAndRetainBackups(instanceName string) string {
1614+
return fmt.Sprintf(`
1615+
resource "google_sql_database_instance" "instance" {
1616+
name = "%s"
1617+
region = "us-central1"
1618+
database_version = "MYSQL_5_7"
1619+
deletion_protection = false
1620+
1621+
settings {
1622+
tier = "db-n1-standard-1"
1623+
1624+
backup_configuration {
1625+
binary_log_enabled = "true"
1626+
enabled = "true"
1627+
start_time = "18:00"
1628+
}
1629+
1630+
retain_backups_on_delete = true
1631+
}
1632+
}
1633+
1634+
// Wait for the ten minutes (RPO is 5 minutes)
1635+
resource "time_sleep" "wait_for_binlog" {
1636+
depends_on = [google_sql_database.database]
1637+
1638+
create_duration = "600s"
1639+
1640+
context.point_in_time = timestamp()
1641+
}
1642+
`, instanceName)
1643+
1644+
}
1645+
1646+
func testAccSqlDatabaseInstance_emptyConfig() string {
1647+
return ""
1648+
}
1649+
1650+
func testAccSqlDatabaseInstance_captureDeletionTimeFromBackup(context map[string]interface{}) resource.TestCheckFunc {
1651+
return func(s *terraform.State) error {
1652+
sourceInstanceName := context["source_db_name"].(string)
1653+
1654+
// 1. Get the SQL Admin Client (assuming acctest provides one)
1655+
client, err := acctest.Client() // Placeholder: get provider client
1656+
if err != nil {
1657+
return fmt.Errorf("failed to get client: %w", err)
1658+
}
1659+
1660+
// 2. Call the Cloud SQL Admin API List method for the deleted instance
1661+
resp, err := client.SqlAdmin.BackupRuns.List(acctest.ProjectID(), sourceInstanceName).Do()
1662+
if err != nil {
1663+
return fmt.Errorf("failed to list backup runs for instance %s: %w", sourceInstanceName, err)
1664+
}
1665+
1666+
var deletionTime int64
1667+
1668+
// 3. Find the backup and extract the deletion timestamp
1669+
for _, backup := range resp.Items {
1670+
// Look for the specific manual backup created in the previous step
1671+
// This is a common field on a backup that was taken *before* instance deletion
1672+
if backup.InstanceDeletionTimestampMs > 0 {
1673+
deletionTime = backup.InstanceDeletionTimestampMs
1674+
break
1675+
}
1676+
}
1677+
1678+
if deletionTime == 0 {
1679+
return fmt.Errorf("could not find a backup with a valid instance_deletion_timestamp_ms for instance %s", sourceInstanceName)
1680+
}
1681+
1682+
// 4. Convert the milliseconds timestamp to the required RFC3339 format
1683+
// The API returns milliseconds since epoch, but the clone operation expects RFC3339 string.
1684+
t := time.Unix(0, deletionTime*int64(time.Millisecond))
1685+
deletionTimeString := t.Format(time.RFC3339Nano)
1686+
1687+
// 5. Update the context map
1688+
context["deletion_time"] = deletionTimeString
1689+
context["point_in_time"] = pointInTimeString
1690+
1691+
t.Logf("Captured instance deletion time from backup: %s (ms: %d)", deletionTimeString, deletionTime)
1692+
1693+
return nil
1694+
}
1695+
}
1696+
1697+
func TestAccSqlDatabaseInstance_cloneWithSourceInstanceDeletionTime(t *testing.T) {
1698+
// Sqladmin client
1699+
acctest.SkipIfVcr(t)
1700+
t.Parallel()
1701+
1702+
context := map[string]interface{}{
1703+
"random_suffix": acctest.RandString(t, 10),
1704+
"original_db_name": acctest.BootstrapSharedSQLInstanceBackupRun(t),
1705+
"source_db_name": "source-instance-" + acctest.RandString(t, 6),
1706+
"clone_db_name": "clone-instance-" + acctest.RandString(t, 6),
1707+
// This will be set by the test after Step 2
1708+
"deletion_time": "",
1709+
}
1710+
1711+
acctest.VcrTest(t, resource.TestCase{
1712+
PreCheck: func() { acctest.AccTestPreCheck(t) },
1713+
ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t),
1714+
CheckDestroy: testAccSqlDatabaseInstanceDestroyProducer(t),
1715+
Steps: []resource.TestStep{
1716+
{
1717+
Config: testGoogleSqlDatabaseInstanceConfig_withPitrAndRetainBackups(source_db_name),
1718+
},
1719+
{
1720+
Config: testAccSqlDatabaseInstance_emptyConfig(context),
1721+
},
1722+
{
1723+
Config: testAccSqlDatabaseInstance_captureDeletionTimeFromBackup(source_db_name)
1724+
},
1725+
{
1726+
Config: testAccSqlDatabaseInstance_cloneWithDeletionTime(context),
1727+
ResourceName: clone_db_name,
1728+
ImportState: true,
1729+
ImportStateVerify: true,
1730+
ImportStateVerifyIgnore: []string{"deletion_protection", "clone"},
1731+
},
1732+
},
1733+
})
1734+
}
1735+
16131736
func TestAccSqlDatabaseInstance_pointInTimeRestore(t *testing.T) {
16141737
// Skipped due to randomness
16151738
acctest.SkipIfVcr(t)
@@ -1683,6 +1806,7 @@ func TestAccSqlDatabaseInstance_pointInTimeRestoreWithSettings(t *testing.T) {
16831806
ImportState: true,
16841807
ImportStateVerify: true,
16851808
ImportStateVerifyIgnore: []string{"deletion_protection", "point_in_time_restore_context"},
1809+
16861810
},
16871811
},
16881812
})
@@ -7266,6 +7390,29 @@ resource "google_sql_database_instance" "instance" {
72667390
`, context)
72677391
}
72687392

7393+
func testAccSqlDatabaseInstance_cloneWithSourceInstanceDeletionTime(context map[string]interface{}) string {
7394+
return acctest.Nprintf(`
7395+
resource "google_sql_database_instance" "instance" {
7396+
name = "tf-test-%{random_suffix}"
7397+
database_version = "POSTGRES_11"
7398+
region = "us-central1"
7399+
7400+
clone {
7401+
source_instance_name = context.source_instance_name
7402+
point_in_time = context.point_in_time
7403+
source_instance_deletion_time = context.source_instance_deletion_time
7404+
}
7405+
7406+
deletion_protection = false
7407+
7408+
// Ignore changes, since the most recent backup may change during the test
7409+
lifecycle{
7410+
ignore_changes = [clone[0].point_in_time]
7411+
}
7412+
}
7413+
`, context)
7414+
}
7415+
72697416
func checkPromoteReplicaSkipConfigurations(resourceName string) func(*terraform.State) error {
72707417
return func(s *terraform.State) error {
72717418
resource, ok := s.RootModule().Resources[resourceName]

mmv1/third_party/terraform/website/docs/r/sql_database_instance.html.markdown

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -625,6 +625,10 @@ The optional `point_in_time_restore_context` block supports:
625625

626626
* `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])?.
627627

628+
* `source_instance_deletion_time` - (Optional) The timestamp of when the source instance was deleted for a clone from a deleted instance.
629+
630+
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".
631+
628632
* `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.
629633

630634
The optional `clone` block supports:
@@ -641,6 +645,10 @@ The optional `clone` block supports:
641645

642646
* `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])?.
643647

648+
* `source_instance_deletion_time` - (Optional) The timestamp of when the source instance was deleted for a clone from a deleted instance.
649+
650+
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".
651+
644652
The optional `restore_backup_context` block supports:
645653
**NOTE:** Restoring from a backup is an imperative action and not recommended via Terraform. Adding or modifying this
646654
block during resource creation/update will trigger the restore action after the resource is created/updated.

0 commit comments

Comments
 (0)