Skip to content

Commit d7f1541

Browse files
compute: Add scheduling.termination_time field to compute_instance resources (#12791) (#21717)
[upstream:fc3eeaab049d020371a96176262f2495ee9f7121] Signed-off-by: Modular Magician <magic-modules@google.com>
1 parent 90cd2b1 commit d7f1541

13 files changed

+479
-1
lines changed

.changelog/12791.txt

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
```release-note:enhancement
2+
compute: added `scheduling.termination_time` field to `google_compute_instance` resource
3+
```
4+
```release-note:enhancement
5+
compute: added `scheduling.termination_time` field to `google_compute_instance_from_machine_image` resource
6+
```
7+
```release-note:enhancement
8+
compute: added `scheduling.termination_time` field to `google_compute_instance_from_template` resource
9+
```
10+
```release-note:enhancement
11+
compute: added `scheduling.termination_time` field to `google_compute_instance_template` resource
12+
```
13+
```release-note:enhancement
14+
compute: added `scheduling.termination_time` field to `google_compute_region_instance_template` resource
15+
```

google/services/compute/compute_instance_helpers.go

+25-1
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,9 @@ func expandScheduling(v interface{}) (*compute.Scheduling, error) {
172172
scheduling.LocalSsdRecoveryTimeout = transformedLocalSsdRecoveryTimeout
173173
scheduling.ForceSendFields = append(scheduling.ForceSendFields, "LocalSsdRecoveryTimeout")
174174
}
175+
if v, ok := original["termination_time"]; ok {
176+
scheduling.TerminationTime = v.(string)
177+
}
175178
return scheduling, nil
176179
}
177180

@@ -268,6 +271,7 @@ func flattenScheduling(resp *compute.Scheduling) []map[string]interface{} {
268271
"provisioning_model": resp.ProvisioningModel,
269272
"instance_termination_action": resp.InstanceTerminationAction,
270273
"availability_domain": resp.AvailabilityDomain,
274+
"termination_time": resp.TerminationTime,
271275
}
272276

273277
if resp.AutomaticRestart != nil {
@@ -663,7 +667,9 @@ func schedulingHasChangeRequiringReboot(d *schema.ResourceData) bool {
663667
oScheduling := o.([]interface{})[0].(map[string]interface{})
664668
newScheduling := n.([]interface{})[0].(map[string]interface{})
665669

666-
return hasNodeAffinitiesChanged(oScheduling, newScheduling) || hasMaxRunDurationChanged(oScheduling, newScheduling)
670+
return (hasNodeAffinitiesChanged(oScheduling, newScheduling) ||
671+
hasMaxRunDurationChanged(oScheduling, newScheduling) ||
672+
hasTerminationTimeChanged(oScheduling, newScheduling))
667673
}
668674

669675
// Terraform doesn't correctly calculate changes on schema.Set, so we do it manually
@@ -708,6 +714,24 @@ func schedulingHasChangeWithoutReboot(d *schema.ResourceData) bool {
708714
return false
709715
}
710716

717+
func hasTerminationTimeChanged(oScheduling, nScheduling map[string]interface{}) bool {
718+
oTerminationTime := oScheduling["termination_time"].(string)
719+
nTerminationTime := nScheduling["termination_time"].(string)
720+
721+
if len(oTerminationTime) == 0 && len(nTerminationTime) == 0 {
722+
return false
723+
}
724+
if len(oTerminationTime) == 0 || len(nTerminationTime) == 0 {
725+
return true
726+
}
727+
728+
if oTerminationTime != nTerminationTime {
729+
return true
730+
}
731+
732+
return false
733+
}
734+
711735
func hasMaxRunDurationChanged(oScheduling, nScheduling map[string]interface{}) bool {
712736
oMrd := oScheduling["max_run_duration"].([]interface{})
713737
nMrd := nScheduling["max_run_duration"].([]interface{})
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
// Copyright (c) HashiCorp, Inc.
2+
// SPDX-License-Identifier: MPL-2.0
3+
package compute
4+
5+
import (
6+
"testing"
7+
)
8+
9+
func TestHasTerminationTimeChanged(t *testing.T) {
10+
t.Parallel()
11+
cases := map[string]struct {
12+
Old, New map[string]interface{}
13+
Expect bool
14+
}{
15+
"empty": {
16+
Old: map[string]interface{}{"termination_time": ""},
17+
New: map[string]interface{}{"termination_time": ""},
18+
Expect: false,
19+
},
20+
"new": {
21+
Old: map[string]interface{}{"termination_time": ""},
22+
New: map[string]interface{}{"termination_time": "2025-01-31T15:04:05Z"},
23+
Expect: true,
24+
},
25+
"changed": {
26+
Old: map[string]interface{}{"termination_time": "2025-01-30T15:04:05Z"},
27+
New: map[string]interface{}{"termination_time": "2025-01-31T15:04:05Z"},
28+
Expect: true,
29+
},
30+
"same": {
31+
Old: map[string]interface{}{"termination_time": "2025-01-30T15:04:05Z"},
32+
New: map[string]interface{}{"termination_time": "2025-01-30T15:04:05Z"},
33+
Expect: false,
34+
},
35+
}
36+
for tn, tc := range cases {
37+
if hasTerminationTimeChanged(tc.Old, tc.New) != tc.Expect {
38+
t.Errorf("%s: expected %t for whether termination time matched for old = %q, new = %q", tn, tc.Expect, tc.Old, tc.New)
39+
}
40+
}
41+
}

google/services/compute/resource_compute_instance.go

+10
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@ var (
9292
"scheduling.0.min_node_cpus",
9393
"scheduling.0.provisioning_model",
9494
"scheduling.0.instance_termination_action",
95+
"scheduling.0.termination_time",
9596
"scheduling.0.availability_domain",
9697
"scheduling.0.max_run_duration",
9798
"scheduling.0.on_instance_stop_action",
@@ -856,6 +857,15 @@ func ResourceComputeInstance() *schema.Resource {
856857
AtLeastOneOf: schedulingKeys,
857858
Description: `Specifies the action GCE should take when SPOT VM is preempted.`,
858859
},
860+
"termination_time": {
861+
Type: schema.TypeString,
862+
Optional: true,
863+
ForceNew: true,
864+
AtLeastOneOf: schedulingKeys,
865+
Description: `Specifies the timestamp, when the instance will be terminated,
866+
in RFC3339 text format. If specified, the instance termination action
867+
will be performed at the termination time.`,
868+
},
859869
"availability_domain": {
860870
Type: schema.TypeInt,
861871
Optional: true,

google/services/compute/resource_compute_instance_from_template_test.go

+82
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"fmt"
77
"regexp"
88
"testing"
9+
"time"
910

1011
"github.com/hashicorp/terraform-plugin-testing/helper/resource"
1112
"github.com/hashicorp/terraform-plugin-testing/terraform"
@@ -275,6 +276,32 @@ func TestAccComputeInstanceFromTemplate_overrideScheduling(t *testing.T) {
275276
})
276277
}
277278

279+
func TestAccComputeInstanceFromTemplate_TerminationTime(t *testing.T) {
280+
t.Parallel()
281+
282+
var instance compute.Instance
283+
instanceName := fmt.Sprintf("tf-test-%s", acctest.RandString(t, 10))
284+
templateName := fmt.Sprintf("tf-test-%s", acctest.RandString(t, 10))
285+
templateDisk := fmt.Sprintf("tf-test-%s", acctest.RandString(t, 10))
286+
resourceName := "google_compute_instance_from_template.inst"
287+
now := time.Now().UTC()
288+
terminationTime := time.Date(now.Year(), now.Month(), now.Day(), 23, 59, 59, 9999, now.Location()).Format(time.RFC3339)
289+
290+
acctest.VcrTest(t, resource.TestCase{
291+
PreCheck: func() { acctest.AccTestPreCheck(t) },
292+
ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t),
293+
CheckDestroy: testAccCheckComputeInstanceFromTemplateDestroyProducer(t),
294+
Steps: []resource.TestStep{
295+
{
296+
Config: testAccComputeInstanceFromTemplate_terminationTime(templateDisk, templateName, terminationTime, instanceName),
297+
Check: resource.ComposeTestCheckFunc(
298+
testAccCheckComputeInstanceExists(t, resourceName, &instance),
299+
),
300+
},
301+
},
302+
})
303+
}
304+
278305
func TestAccComputeInstanceFromTemplate_overrideMetadataDotStartupScript(t *testing.T) {
279306
var instance compute.Instance
280307
instanceName := fmt.Sprintf("tf-test-%s", acctest.RandString(t, 10))
@@ -978,6 +1005,61 @@ resource "google_compute_instance_from_template" "inst" {
9781005
`, templateDisk, template, instance)
9791006
}
9801007

1008+
func testAccComputeInstanceFromTemplate_terminationTime(templateDisk, template, termination_time, instance string) string {
1009+
return fmt.Sprintf(`
1010+
data "google_compute_image" "my_image" {
1011+
family = "debian-11"
1012+
project = "debian-cloud"
1013+
}
1014+
1015+
resource "google_compute_disk" "foobar" {
1016+
name = "%s"
1017+
image = data.google_compute_image.my_image.self_link
1018+
size = 10
1019+
type = "pd-ssd"
1020+
zone = "us-central1-a"
1021+
}
1022+
1023+
resource "google_compute_instance_template" "foobar" {
1024+
name = "%s"
1025+
machine_type = "e2-medium"
1026+
1027+
disk {
1028+
source = google_compute_disk.foobar.name
1029+
auto_delete = false
1030+
boot = true
1031+
}
1032+
1033+
network_interface {
1034+
network = "default"
1035+
}
1036+
1037+
metadata = {
1038+
foo = "bar"
1039+
}
1040+
1041+
scheduling {
1042+
instance_termination_action = "STOP"
1043+
termination_time = "%s"
1044+
}
1045+
1046+
can_ip_forward = true
1047+
}
1048+
1049+
resource "google_compute_instance_from_template" "inst" {
1050+
name = "%s"
1051+
zone = "us-central1-a"
1052+
1053+
source_instance_template = google_compute_instance_template.foobar.self_link
1054+
1055+
scheduling {
1056+
instance_termination_action = "STOP"
1057+
termination_time = "%s"
1058+
}
1059+
}
1060+
`, templateDisk, template, termination_time, instance, termination_time)
1061+
}
1062+
9811063
func testAccComputeInstanceFromTemplate_overrideMetadataDotStartupScript(instance, template string) string {
9821064
return fmt.Sprintf(`
9831065
data "google_compute_image" "my_image" {

google/services/compute/resource_compute_instance_template.go

+10
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ var (
3434
"scheduling.0.availability_domain",
3535
"scheduling.0.max_run_duration",
3636
"scheduling.0.on_instance_stop_action",
37+
"scheduling.0.termination_time",
3738
"scheduling.0.local_ssd_recovery_timeout",
3839
}
3940

@@ -739,6 +740,15 @@ be from 0 to 999,999,999 inclusive.`,
739740
},
740741
},
741742
},
743+
"termination_time": {
744+
Type: schema.TypeString,
745+
Optional: true,
746+
ForceNew: true,
747+
AtLeastOneOf: schedulingKeys,
748+
Description: `Specifies the timestamp, when the instance will be terminated,
749+
in RFC3339 text format. If specified, the instance termination action
750+
will be performed at the termination time.`,
751+
},
742752
"local_ssd_recovery_timeout": {
743753
Type: schema.TypeList,
744754
Optional: true,

google/services/compute/resource_compute_instance_template_test.go

+80
Original file line numberDiff line numberDiff line change
@@ -1319,6 +1319,37 @@ func TestAccComputeInstanceTemplate_maxRunDuration_onInstanceStopAction(t *testi
13191319
})
13201320
}
13211321

1322+
func TestAccComputeInstanceTemplate_instanceTerminationAction_terminationTime(t *testing.T) {
1323+
t.Parallel()
1324+
1325+
var instanceTemplate compute.InstanceTemplate
1326+
now := time.Now().UTC()
1327+
terminationTime := time.Date(now.Year(), now.Month(), now.Day(), 23, 59, 59, 9999, now.Location()).Format(time.RFC3339)
1328+
var instanceTerminationAction = "STOP"
1329+
1330+
acctest.VcrTest(t, resource.TestCase{
1331+
PreCheck: func() { acctest.AccTestPreCheck(t) },
1332+
ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t),
1333+
CheckDestroy: testAccCheckComputeInstanceTemplateDestroyProducer(t),
1334+
Steps: []resource.TestStep{
1335+
{
1336+
Config: testAccComputeInstanceTemplate_onInstanceStopAction_terminationTime(acctest.RandString(t, 10), terminationTime),
1337+
Check: resource.ComposeTestCheckFunc(
1338+
testAccCheckComputeInstanceTemplateExists(
1339+
t, "google_compute_instance_template.foobar", &instanceTemplate),
1340+
testAccCheckComputeInstanceTemplateInstanceTerminationAction(&instanceTemplate, instanceTerminationAction),
1341+
testAccCheckComputeInstanceTemplateInstanceTerminationTime(&instanceTemplate, terminationTime),
1342+
),
1343+
},
1344+
{
1345+
ResourceName: "google_compute_instance_template.foobar",
1346+
ImportState: true,
1347+
ImportStateVerify: true,
1348+
},
1349+
},
1350+
})
1351+
}
1352+
13221353
func TestAccComputeInstanceTemplate_spot_maxRunDuration(t *testing.T) {
13231354
t.Parallel()
13241355

@@ -1857,6 +1888,15 @@ func testAccCheckComputeInstanceTemplateInstanceTerminationAction(instanceTempla
18571888
}
18581889
}
18591890

1891+
func testAccCheckComputeInstanceTemplateInstanceTerminationTime(instanceTemplate *compute.InstanceTemplate, termination_time string) resource.TestCheckFunc {
1892+
return func(s *terraform.State) error {
1893+
if instanceTemplate.Properties.Scheduling.TerminationTime != termination_time {
1894+
return fmt.Errorf("Expected instance_termination_time %v, got %v", termination_time, instanceTemplate.Properties.Scheduling.TerminationTime)
1895+
}
1896+
return nil
1897+
}
1898+
}
1899+
18601900
func testAccCheckComputeInstanceTemplateMaxRunDuration(instanceTemplate *compute.InstanceTemplate, instance_max_run_duration_want compute.Duration) resource.TestCheckFunc {
18611901
return func(s *terraform.State) error {
18621902
if !reflect.DeepEqual(*instanceTemplate.Properties.Scheduling.MaxRunDuration, instance_max_run_duration_want) {
@@ -4071,6 +4111,46 @@ resource "google_compute_instance_template" "foobar" {
40714111
`, suffix)
40724112
}
40734113

4114+
func testAccComputeInstanceTemplate_onInstanceStopAction_terminationTime(suffix string, terminationTime string) string {
4115+
return fmt.Sprintf(`
4116+
data "google_compute_image" "my_image" {
4117+
family = "debian-11"
4118+
project = "debian-cloud"
4119+
}
4120+
4121+
resource "google_compute_instance_template" "foobar" {
4122+
name = "tf-test-instance-template-%s"
4123+
machine_type = "e2-medium"
4124+
can_ip_forward = false
4125+
tags = ["foo", "bar"]
4126+
4127+
disk {
4128+
source_image = data.google_compute_image.my_image.self_link
4129+
auto_delete = true
4130+
boot = true
4131+
}
4132+
4133+
network_interface {
4134+
network = "default"
4135+
}
4136+
4137+
scheduling {
4138+
automatic_restart = false
4139+
instance_termination_action = "STOP"
4140+
termination_time = "%s"
4141+
}
4142+
4143+
metadata = {
4144+
foo = "bar"
4145+
}
4146+
4147+
service_account {
4148+
scopes = ["userinfo-email", "compute-ro", "storage-ro"]
4149+
}
4150+
}
4151+
`, suffix, terminationTime)
4152+
}
4153+
40744154
func testAccComputeInstanceTemplate_localSsdRecoveryTimeout(suffix string) string {
40754155
return fmt.Sprintf(`
40764156
data "google_compute_image" "my_image" {

0 commit comments

Comments
 (0)