Skip to content

Commit 3b753ce

Browse files
authored
Add update support for machine type, min cpu platform, and service accounts (#1005)
* Add update support for compute instance fields that require the machine to be stopped * add warnings in docs about stopping the instance before updating * add allow_stopping_for_update field
1 parent b708ff7 commit 3b753ce

File tree

4 files changed

+252
-14
lines changed

4 files changed

+252
-14
lines changed

google/field_helpers.go

+4
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,10 @@ func ParseAcceleratorFieldValue(accelerator string, d TerraformResourceData, con
5454
return parseZonalFieldValue("acceleratorTypes", accelerator, "project", "zone", d, config, false)
5555
}
5656

57+
func ParseMachineTypesFieldValue(machineType string, d TerraformResourceData, config *Config) (*ZonalFieldValue, error) {
58+
return parseZonalFieldValue("machineTypes", machineType, "project", "zone", d, config, false)
59+
}
60+
5761
// ------------------------------------------------------------
5862
// Base helpers used to create helpers for specific fields.
5963
// ------------------------------------------------------------

google/resource_compute_instance.go

+94-6
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ import (
77
"log"
88
"strings"
99

10+
"time"
11+
1012
"github.com/hashicorp/errwrap"
1113
"github.com/hashicorp/terraform/helper/customdiff"
1214
"github.com/hashicorp/terraform/helper/schema"
@@ -15,7 +17,6 @@ import (
1517
computeBeta "google.golang.org/api/compute/v0.beta"
1618
"google.golang.org/api/compute/v1"
1719
"google.golang.org/api/googleapi"
18-
"time"
1920
)
2021

2122
var InstanceBaseApiVersion = v1
@@ -240,7 +241,6 @@ func resourceComputeInstance() *schema.Resource {
240241
"machine_type": &schema.Schema{
241242
Type: schema.TypeString,
242243
Required: true,
243-
ForceNew: true,
244244
},
245245

246246
"name": &schema.Schema{
@@ -468,20 +468,17 @@ func resourceComputeInstance() *schema.Resource {
468468
Type: schema.TypeList,
469469
MaxItems: 1,
470470
Optional: true,
471-
ForceNew: true,
472471
Elem: &schema.Resource{
473472
Schema: map[string]*schema.Schema{
474473
"email": &schema.Schema{
475474
Type: schema.TypeString,
476-
ForceNew: true,
477475
Optional: true,
478476
Computed: true,
479477
},
480478

481479
"scopes": &schema.Schema{
482480
Type: schema.TypeSet,
483481
Required: true,
484-
ForceNew: true,
485482
Elem: &schema.Schema{
486483
Type: schema.TypeString,
487484
StateFunc: func(v interface{}) string {
@@ -524,7 +521,6 @@ func resourceComputeInstance() *schema.Resource {
524521
"min_cpu_platform": &schema.Schema{
525522
Type: schema.TypeString,
526523
Optional: true,
527-
ForceNew: true,
528524
},
529525

530526
"tags": &schema.Schema{
@@ -546,6 +542,11 @@ func resourceComputeInstance() *schema.Resource {
546542
Set: schema.HashString,
547543
},
548544

545+
"allow_stopping_for_update": &schema.Schema{
546+
Type: schema.TypeBool,
547+
Optional: true,
548+
},
549+
549550
"label_fingerprint": &schema.Schema{
550551
Type: schema.TypeString,
551552
Computed: true,
@@ -1188,6 +1189,93 @@ func resourceComputeInstanceUpdate(d *schema.ResourceData, meta interface{}) err
11881189
d.SetPartial("attached_disk")
11891190
}
11901191

1192+
// Attributes which can only be changed if the instance is stopped
1193+
if d.HasChange("machine_type") || d.HasChange("min_cpu_platform") || d.HasChange("service_account") {
1194+
if !d.Get("allow_stopping_for_update").(bool) {
1195+
return fmt.Errorf("Changing the machine_type, min_cpu_platform, or service_account on an instance requires stopping it. " +
1196+
"To acknowledge this, please set allow_stopping_for_update = true in your config.")
1197+
}
1198+
op, err := config.clientCompute.Instances.Stop(project, zone, instance.Name).Do()
1199+
if err != nil {
1200+
return errwrap.Wrapf("Error stopping instance: {{err}}", err)
1201+
}
1202+
1203+
opErr := computeOperationWaitTime(config.clientCompute, op, project, "stopping instance", int(d.Timeout(schema.TimeoutUpdate).Minutes()))
1204+
if opErr != nil {
1205+
return opErr
1206+
}
1207+
1208+
if d.HasChange("machine_type") {
1209+
mt, err := ParseMachineTypesFieldValue(d.Get("machine_type").(string), d, config)
1210+
if err != nil {
1211+
return err
1212+
}
1213+
req := &compute.InstancesSetMachineTypeRequest{
1214+
MachineType: mt.RelativeLink(),
1215+
}
1216+
op, err = config.clientCompute.Instances.SetMachineType(project, zone, instance.Name, req).Do()
1217+
if err != nil {
1218+
return err
1219+
}
1220+
opErr := computeOperationWaitTime(config.clientCompute, op, project, "updating machinetype", int(d.Timeout(schema.TimeoutUpdate).Minutes()))
1221+
if opErr != nil {
1222+
return opErr
1223+
}
1224+
d.SetPartial("machine_type")
1225+
}
1226+
1227+
if d.HasChange("min_cpu_platform") {
1228+
minCpuPlatform, ok := d.GetOk("min_cpu_platform")
1229+
// Even though you don't have to set minCpuPlatform on create, you do have to set it to an
1230+
// actual value on update. "Automatic" is the default. This will be read back from the API as empty,
1231+
// so we don't need to worry about diffs.
1232+
if !ok {
1233+
minCpuPlatform = "Automatic"
1234+
}
1235+
req := &compute.InstancesSetMinCpuPlatformRequest{
1236+
MinCpuPlatform: minCpuPlatform.(string),
1237+
}
1238+
op, err = config.clientCompute.Instances.SetMinCpuPlatform(project, zone, instance.Name, req).Do()
1239+
if err != nil {
1240+
return err
1241+
}
1242+
opErr := computeOperationWaitTime(config.clientCompute, op, project, "updating min cpu platform", int(d.Timeout(schema.TimeoutUpdate).Minutes()))
1243+
if opErr != nil {
1244+
return opErr
1245+
}
1246+
d.SetPartial("min_cpu_platform")
1247+
}
1248+
1249+
if d.HasChange("service_account") {
1250+
sa := d.Get("service_account").([]interface{})
1251+
req := &compute.InstancesSetServiceAccountRequest{ForceSendFields: []string{"email"}}
1252+
if len(sa) > 0 {
1253+
saMap := sa[0].(map[string]interface{})
1254+
req.Email = saMap["email"].(string)
1255+
req.Scopes = canonicalizeServiceScopes(convertStringSet(saMap["scopes"].(*schema.Set)))
1256+
}
1257+
op, err = config.clientCompute.Instances.SetServiceAccount(project, zone, instance.Name, req).Do()
1258+
if err != nil {
1259+
return err
1260+
}
1261+
opErr := computeOperationWaitTime(config.clientCompute, op, project, "updating service account", int(d.Timeout(schema.TimeoutUpdate).Minutes()))
1262+
if opErr != nil {
1263+
return opErr
1264+
}
1265+
d.SetPartial("service_account")
1266+
}
1267+
1268+
op, err = config.clientCompute.Instances.Start(project, zone, instance.Name).Do()
1269+
if err != nil {
1270+
return errwrap.Wrapf("Error starting instance: {{err}}", err)
1271+
}
1272+
1273+
opErr = computeOperationWaitTime(config.clientCompute, op, project, "starting instance", int(d.Timeout(schema.TimeoutUpdate).Minutes()))
1274+
if opErr != nil {
1275+
return opErr
1276+
}
1277+
}
1278+
11911279
// We made it, disable partial mode
11921280
d.Partial(false)
11931281

google/resource_compute_instance_test.go

+140
Original file line numberDiff line numberDiff line change
@@ -518,6 +518,60 @@ func TestAccComputeInstance_update(t *testing.T) {
518518
})
519519
}
520520

521+
func TestAccComputeInstance_stopInstanceToUpdate(t *testing.T) {
522+
t.Parallel()
523+
524+
var instance compute.Instance
525+
var instanceName = fmt.Sprintf("instance-test-%s", acctest.RandString(10))
526+
527+
resource.Test(t, resource.TestCase{
528+
PreCheck: func() { testAccPreCheck(t) },
529+
Providers: testAccProviders,
530+
CheckDestroy: testAccCheckComputeInstanceDestroy,
531+
Steps: []resource.TestStep{
532+
// Set fields that require stopping the instance
533+
resource.TestStep{
534+
Config: testAccComputeInstance_stopInstanceToUpdate(instanceName),
535+
Check: resource.ComposeTestCheckFunc(
536+
testAccCheckComputeInstanceExists(
537+
"google_compute_instance.foobar", &instance),
538+
),
539+
},
540+
resource.TestStep{
541+
ResourceName: "google_compute_instance.foobar",
542+
ImportState: true,
543+
ImportStateId: fmt.Sprintf("%s/%s/%s", getTestProjectFromEnv(), "us-central1-a", instanceName),
544+
},
545+
// Check that updating them works
546+
resource.TestStep{
547+
Config: testAccComputeInstance_stopInstanceToUpdate2(instanceName),
548+
Check: resource.ComposeTestCheckFunc(
549+
testAccCheckComputeInstanceExists(
550+
"google_compute_instance.foobar", &instance),
551+
),
552+
},
553+
resource.TestStep{
554+
ResourceName: "google_compute_instance.foobar",
555+
ImportState: true,
556+
ImportStateId: fmt.Sprintf("%s/%s/%s", getTestProjectFromEnv(), "us-central1-a", instanceName),
557+
},
558+
// Check that removing them works
559+
resource.TestStep{
560+
Config: testAccComputeInstance_stopInstanceToUpdate3(instanceName),
561+
Check: resource.ComposeTestCheckFunc(
562+
testAccCheckComputeInstanceExists(
563+
"google_compute_instance.foobar", &instance),
564+
),
565+
},
566+
resource.TestStep{
567+
ResourceName: "google_compute_instance.foobar",
568+
ImportState: true,
569+
ImportStateId: fmt.Sprintf("%s/%s/%s", getTestProjectFromEnv(), "us-central1-a", instanceName),
570+
},
571+
},
572+
})
573+
}
574+
521575
func TestAccComputeInstance_service_account(t *testing.T) {
522576
t.Parallel()
523577

@@ -2424,3 +2478,89 @@ resource "google_compute_instance" "foobar" {
24242478
}
24252479
}`, acctest.RandString(10), acctest.RandString(10), instance)
24262480
}
2481+
2482+
// Set fields that require stopping the instance: machine_type, min_cpu_platform, and service_account
2483+
func testAccComputeInstance_stopInstanceToUpdate(instance string) string {
2484+
return fmt.Sprintf(`
2485+
resource "google_compute_instance" "foobar" {
2486+
name = "%s"
2487+
machine_type = "n1-standard-1"
2488+
zone = "us-central1-a"
2489+
2490+
boot_disk {
2491+
initialize_params{
2492+
image = "debian-8-jessie-v20160803"
2493+
}
2494+
}
2495+
2496+
network_interface {
2497+
network = "default"
2498+
}
2499+
2500+
min_cpu_platform = "Intel Broadwell"
2501+
service_account {
2502+
scopes = [
2503+
"userinfo-email",
2504+
"compute-ro",
2505+
"storage-ro",
2506+
]
2507+
}
2508+
2509+
allow_stopping_for_update = true
2510+
}
2511+
`, instance)
2512+
}
2513+
2514+
// Update fields that require stopping the instance: machine_type, min_cpu_platform, and service_account
2515+
func testAccComputeInstance_stopInstanceToUpdate2(instance string) string {
2516+
return fmt.Sprintf(`
2517+
resource "google_compute_instance" "foobar" {
2518+
name = "%s"
2519+
machine_type = "n1-standard-2"
2520+
zone = "us-central1-a"
2521+
2522+
boot_disk {
2523+
initialize_params{
2524+
image = "debian-8-jessie-v20160803"
2525+
}
2526+
}
2527+
2528+
network_interface {
2529+
network = "default"
2530+
}
2531+
2532+
min_cpu_platform = "Intel Skylake"
2533+
service_account {
2534+
scopes = [
2535+
"userinfo-email",
2536+
"compute-ro",
2537+
]
2538+
}
2539+
2540+
allow_stopping_for_update = true
2541+
}
2542+
`, instance)
2543+
}
2544+
2545+
// Remove fields that require stopping the instance: min_cpu_platform and service_account (machine_type is Required)
2546+
func testAccComputeInstance_stopInstanceToUpdate3(instance string) string {
2547+
return fmt.Sprintf(`
2548+
resource "google_compute_instance" "foobar" {
2549+
name = "%s"
2550+
machine_type = "n1-standard-2"
2551+
zone = "us-central1-a"
2552+
2553+
boot_disk {
2554+
initialize_params{
2555+
image = "debian-8-jessie-v20160803"
2556+
}
2557+
}
2558+
2559+
network_interface {
2560+
network = "default"
2561+
}
2562+
2563+
allow_stopping_for_update = true
2564+
}
2565+
`, instance)
2566+
}

website/docs/r/compute_instance.html.markdown

+14-8
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,8 @@ The following arguments are supported:
6363

6464
* `machine_type` - (Required) The machine type to create. To create a custom
6565
machine type, value should be set as specified
66-
[here](https://cloud.google.com/compute/docs/reference/latest/instances#machineType)
66+
[here](https://cloud.google.com/compute/docs/reference/latest/instances#machineType).
67+
**Note**: [`allow_stopping_for_update`](#allow_stopping_for_update) must be set to true in order to update this field.
6768

6869
* `name` - (Required) A unique name for the resource, required by GCE.
6970
Changing this forces a new resource to be created.
@@ -75,6 +76,9 @@ The following arguments are supported:
7576

7677
- - -
7778

79+
* `allow_stopping_for_update` - (Optional) If true, allows Terraform to stop the instance to update its properties.
80+
If you try to update a property that requires stopping the instance without setting this field, the update will fail.
81+
7882
* `attached_disk` - (Optional) List of disks to attach to the instance. Structure is documented below.
7983

8084
* `can_ip_forward` - (Optional) Whether to allow sending and receiving of
@@ -86,6 +90,8 @@ The following arguments are supported:
8690

8791
* `description` - (Optional) A brief description of this resource.
8892

93+
* `guest_accelerator` - (Optional) List of the type and count of accelerator cards attached to the instance. Structure documented below.
94+
8995
* `labels` - (Optional) A set of key/value label pairs to assign to the instance.
9096

9197
* `metadata` - (Optional) Metadata key/value pairs to make available from
@@ -97,6 +103,10 @@ The following arguments are supported:
97103
startup-script metadata key on the created instance and thus the two
98104
mechanisms are not allowed to be used simultaneously.
99105

106+
* `min_cpu_platform` - (Optional) Specifies a minimum CPU platform for the VM instance. Applicable values are the friendly names of CPU platforms, such as
107+
`Intel Haswell` or `Intel Skylake`. See the complete list [here](https://cloud.google.com/compute/docs/instances/specify-min-cpu-platform).
108+
**Note**: [`allow_stopping_for_update`](#allow_stopping_for_update) must be set to true in order to update this field.
109+
100110
* `project` - (Optional) The project in which the resource belongs. If it
101111
is not provided, the provider project is used.
102112

@@ -108,6 +118,7 @@ The following arguments are supported:
108118

109119
* `service_account` - (Optional) Service account to attach to the instance.
110120
Structure is documented below.
121+
**Note**: [`allow_stopping_for_update`](#allow_stopping_for_update) must be set to true in order to update this field.
111122

112123
* `tags` - (Optional) A list of tags to attach to the instance.
113124

@@ -215,10 +226,12 @@ The `service_account` block supports:
215226

216227
* `email` - (Optional) The service account e-mail address. If not given, the
217228
default Google Compute Engine service account is used.
229+
**Note**: [`allow_stopping_for_update`](#allow_stopping_for_update) must be set to true in order to update this field.
218230

219231
* `scopes` - (Required) A list of service scopes. Both OAuth2 URLs and gcloud
220232
short names are supported. To allow full access to all Cloud APIs, use the
221233
`cloud-platform` scope. See a complete list of scopes [here](https://cloud.google.com/sdk/gcloud/reference/alpha/compute/instances/set-scopes#--scopes).
234+
**Note**: [`allow_stopping_for_update`](#allow_stopping_for_update) must be set to true in order to update this field.
222235

223236
The `scheduling` block supports:
224237

@@ -231,13 +244,6 @@ The `scheduling` block supports:
231244
* `automatic_restart` - (Optional) Specifies if the instance should be
232245
restarted if it was terminated by Compute Engine (not a user).
233246

234-
---
235-
236-
* `guest_accelerator` - (Optional) List of the type and count of accelerator cards attached to the instance. Structure documented below.
237-
238-
* `min_cpu_platform` - (Optional) Specifies a minimum CPU platform for the VM instance. Applicable values are the friendly names of CPU platforms, such as
239-
`Intel Haswell` or `Intel Skylake`. See the complete list [here](https://cloud.google.com/compute/docs/instances/specify-min-cpu-platform).
240-
241247
The `guest_accelerator` block supports:
242248

243249
* `type` (Required) - The accelerator type resource to expose to this instance. E.g. `nvidia-tesla-k80`.

0 commit comments

Comments
 (0)