Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add update support for machine type, min cpu platform, and service accounts #1005

Merged
merged 3 commits into from
Jan 24, 2018
Merged
Show file tree
Hide file tree
Changes from all 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
4 changes: 4 additions & 0 deletions google/field_helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,10 @@ func ParseAcceleratorFieldValue(accelerator string, d TerraformResourceData, con
return parseZonalFieldValue("acceleratorTypes", accelerator, "project", "zone", d, config, false)
}

func ParseMachineTypesFieldValue(machineType string, d TerraformResourceData, config *Config) (*ZonalFieldValue, error) {
return parseZonalFieldValue("machineTypes", machineType, "project", "zone", d, config, false)
}

// ------------------------------------------------------------
// Base helpers used to create helpers for specific fields.
// ------------------------------------------------------------
Expand Down
100 changes: 94 additions & 6 deletions google/resource_compute_instance.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import (
"log"
"strings"

"time"

"github.com/hashicorp/errwrap"
"github.com/hashicorp/terraform/helper/customdiff"
"github.com/hashicorp/terraform/helper/schema"
Expand All @@ -15,7 +17,6 @@ import (
computeBeta "google.golang.org/api/compute/v0.beta"
"google.golang.org/api/compute/v1"
"google.golang.org/api/googleapi"
"time"
)

var InstanceBaseApiVersion = v1
Expand Down Expand Up @@ -240,7 +241,6 @@ func resourceComputeInstance() *schema.Resource {
"machine_type": &schema.Schema{
Type: schema.TypeString,
Required: true,
ForceNew: true,
},

"name": &schema.Schema{
Expand Down Expand Up @@ -468,20 +468,17 @@ func resourceComputeInstance() *schema.Resource {
Type: schema.TypeList,
MaxItems: 1,
Optional: true,
ForceNew: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"email": &schema.Schema{
Type: schema.TypeString,
ForceNew: true,
Optional: true,
Computed: true,
},

"scopes": &schema.Schema{
Type: schema.TypeSet,
Required: true,
ForceNew: true,
Elem: &schema.Schema{
Type: schema.TypeString,
StateFunc: func(v interface{}) string {
Expand Down Expand Up @@ -524,7 +521,6 @@ func resourceComputeInstance() *schema.Resource {
"min_cpu_platform": &schema.Schema{
Type: schema.TypeString,
Optional: true,
ForceNew: true,
},

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

"allow_stopping_for_update": &schema.Schema{
Type: schema.TypeBool,
Optional: true,
},

"label_fingerprint": &schema.Schema{
Type: schema.TypeString,
Computed: true,
Expand Down Expand Up @@ -1188,6 +1189,93 @@ func resourceComputeInstanceUpdate(d *schema.ResourceData, meta interface{}) err
d.SetPartial("attached_disk")
}

// Attributes which can only be changed if the instance is stopped
if d.HasChange("machine_type") || d.HasChange("min_cpu_platform") || d.HasChange("service_account") {
if !d.Get("allow_stopping_for_update").(bool) {
return fmt.Errorf("Changing the machine_type, min_cpu_platform, or service_account on an instance requires stopping it. " +
"To acknowledge this, please set allow_stopping_for_update = true in your config.")
}
op, err := config.clientCompute.Instances.Stop(project, zone, instance.Name).Do()
if err != nil {
return errwrap.Wrapf("Error stopping instance: {{err}}", err)
}

opErr := computeOperationWaitTime(config.clientCompute, op, project, "stopping instance", int(d.Timeout(schema.TimeoutUpdate).Minutes()))
if opErr != nil {
return opErr
}

if d.HasChange("machine_type") {
mt, err := ParseMachineTypesFieldValue(d.Get("machine_type").(string), d, config)
if err != nil {
return err
}
req := &compute.InstancesSetMachineTypeRequest{
MachineType: mt.RelativeLink(),
}
op, err = config.clientCompute.Instances.SetMachineType(project, zone, instance.Name, req).Do()
if err != nil {
return err
}
opErr := computeOperationWaitTime(config.clientCompute, op, project, "updating machinetype", int(d.Timeout(schema.TimeoutUpdate).Minutes()))
if opErr != nil {
return opErr
}
d.SetPartial("machine_type")
}

if d.HasChange("min_cpu_platform") {
minCpuPlatform, ok := d.GetOk("min_cpu_platform")
// Even though you don't have to set minCpuPlatform on create, you do have to set it to an
// actual value on update. "Automatic" is the default. This will be read back from the API as empty,
// so we don't need to worry about diffs.
if !ok {
minCpuPlatform = "Automatic"
}
req := &compute.InstancesSetMinCpuPlatformRequest{
MinCpuPlatform: minCpuPlatform.(string),
}
op, err = config.clientCompute.Instances.SetMinCpuPlatform(project, zone, instance.Name, req).Do()
if err != nil {
return err
}
opErr := computeOperationWaitTime(config.clientCompute, op, project, "updating min cpu platform", int(d.Timeout(schema.TimeoutUpdate).Minutes()))
if opErr != nil {
return opErr
}
d.SetPartial("min_cpu_platform")
}

if d.HasChange("service_account") {
sa := d.Get("service_account").([]interface{})
req := &compute.InstancesSetServiceAccountRequest{ForceSendFields: []string{"email"}}
if len(sa) > 0 {
saMap := sa[0].(map[string]interface{})
req.Email = saMap["email"].(string)
req.Scopes = canonicalizeServiceScopes(convertStringSet(saMap["scopes"].(*schema.Set)))
}
op, err = config.clientCompute.Instances.SetServiceAccount(project, zone, instance.Name, req).Do()
if err != nil {
return err
}
opErr := computeOperationWaitTime(config.clientCompute, op, project, "updating service account", int(d.Timeout(schema.TimeoutUpdate).Minutes()))
if opErr != nil {
return opErr
}
d.SetPartial("service_account")
}

op, err = config.clientCompute.Instances.Start(project, zone, instance.Name).Do()
if err != nil {
return errwrap.Wrapf("Error starting instance: {{err}}", err)
}

opErr = computeOperationWaitTime(config.clientCompute, op, project, "starting instance", int(d.Timeout(schema.TimeoutUpdate).Minutes()))
if opErr != nil {
return opErr
}
}

// We made it, disable partial mode
d.Partial(false)

Expand Down
140 changes: 140 additions & 0 deletions google/resource_compute_instance_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -518,6 +518,60 @@ func TestAccComputeInstance_update(t *testing.T) {
})
}

func TestAccComputeInstance_stopInstanceToUpdate(t *testing.T) {
t.Parallel()

var instance compute.Instance
var instanceName = fmt.Sprintf("instance-test-%s", acctest.RandString(10))

resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckComputeInstanceDestroy,
Steps: []resource.TestStep{
// Set fields that require stopping the instance
resource.TestStep{
Config: testAccComputeInstance_stopInstanceToUpdate(instanceName),
Check: resource.ComposeTestCheckFunc(
testAccCheckComputeInstanceExists(
"google_compute_instance.foobar", &instance),
),
},
resource.TestStep{
ResourceName: "google_compute_instance.foobar",
ImportState: true,
ImportStateId: fmt.Sprintf("%s/%s/%s", getTestProjectFromEnv(), "us-central1-a", instanceName),
},
// Check that updating them works
resource.TestStep{
Config: testAccComputeInstance_stopInstanceToUpdate2(instanceName),
Check: resource.ComposeTestCheckFunc(
testAccCheckComputeInstanceExists(
"google_compute_instance.foobar", &instance),
),
},
resource.TestStep{
ResourceName: "google_compute_instance.foobar",
ImportState: true,
ImportStateId: fmt.Sprintf("%s/%s/%s", getTestProjectFromEnv(), "us-central1-a", instanceName),
},
// Check that removing them works
resource.TestStep{
Config: testAccComputeInstance_stopInstanceToUpdate3(instanceName),
Check: resource.ComposeTestCheckFunc(
testAccCheckComputeInstanceExists(
"google_compute_instance.foobar", &instance),
),
},
resource.TestStep{
ResourceName: "google_compute_instance.foobar",
ImportState: true,
ImportStateId: fmt.Sprintf("%s/%s/%s", getTestProjectFromEnv(), "us-central1-a", instanceName),
},
},
})
}

func TestAccComputeInstance_service_account(t *testing.T) {
t.Parallel()

Expand Down Expand Up @@ -2424,3 +2478,89 @@ resource "google_compute_instance" "foobar" {
}
}`, acctest.RandString(10), acctest.RandString(10), instance)
}

// Set fields that require stopping the instance: machine_type, min_cpu_platform, and service_account
func testAccComputeInstance_stopInstanceToUpdate(instance string) string {
return fmt.Sprintf(`
resource "google_compute_instance" "foobar" {
name = "%s"
machine_type = "n1-standard-1"
zone = "us-central1-a"

boot_disk {
initialize_params{
image = "debian-8-jessie-v20160803"
}
}

network_interface {
network = "default"
}

min_cpu_platform = "Intel Broadwell"
service_account {
scopes = [
"userinfo-email",
"compute-ro",
"storage-ro",
]
}

allow_stopping_for_update = true
}
`, instance)
}

// Update fields that require stopping the instance: machine_type, min_cpu_platform, and service_account
func testAccComputeInstance_stopInstanceToUpdate2(instance string) string {
return fmt.Sprintf(`
resource "google_compute_instance" "foobar" {
name = "%s"
machine_type = "n1-standard-2"
zone = "us-central1-a"

boot_disk {
initialize_params{
image = "debian-8-jessie-v20160803"
}
}

network_interface {
network = "default"
}

min_cpu_platform = "Intel Skylake"
service_account {
scopes = [
"userinfo-email",
"compute-ro",
]
}

allow_stopping_for_update = true
}
`, instance)
}

// Remove fields that require stopping the instance: min_cpu_platform and service_account (machine_type is Required)
func testAccComputeInstance_stopInstanceToUpdate3(instance string) string {
return fmt.Sprintf(`
resource "google_compute_instance" "foobar" {
name = "%s"
machine_type = "n1-standard-2"
zone = "us-central1-a"

boot_disk {
initialize_params{
image = "debian-8-jessie-v20160803"
}
}

network_interface {
network = "default"
}

allow_stopping_for_update = true
}
`, instance)
}
22 changes: 14 additions & 8 deletions website/docs/r/compute_instance.html.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,8 @@ The following arguments are supported:

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

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

- - -

* `allow_stopping_for_update` - (Optional) If true, allows Terraform to stop the instance to update its properties.
If you try to update a property that requires stopping the instance without setting this field, the update will fail.

* `attached_disk` - (Optional) List of disks to attach to the instance. Structure is documented below.

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

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

* `guest_accelerator` - (Optional) List of the type and count of accelerator cards attached to the instance. Structure documented below.

* `labels` - (Optional) A set of key/value label pairs to assign to the instance.

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

* `min_cpu_platform` - (Optional) Specifies a minimum CPU platform for the VM instance. Applicable values are the friendly names of CPU platforms, such as
`Intel Haswell` or `Intel Skylake`. See the complete list [here](https://cloud.google.com/compute/docs/instances/specify-min-cpu-platform).
**Note**: [`allow_stopping_for_update`](#allow_stopping_for_update) must be set to true in order to update this field.

* `project` - (Optional) The project in which the resource belongs. If it
is not provided, the provider project is used.

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

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

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

Expand Down Expand Up @@ -215,10 +226,12 @@ The `service_account` block supports:

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

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

The `scheduling` block supports:

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

---

* `guest_accelerator` - (Optional) List of the type and count of accelerator cards attached to the instance. Structure documented below.

* `min_cpu_platform` - (Optional) Specifies a minimum CPU platform for the VM instance. Applicable values are the friendly names of CPU platforms, such as
`Intel Haswell` or `Intel Skylake`. See the complete list [here](https://cloud.google.com/compute/docs/instances/specify-min-cpu-platform).

The `guest_accelerator` block supports:

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