From e17c192dd51b3a8083e940d212dde7ec45b06b43 Mon Sep 17 00:00:00 2001 From: Artur Gadelshin <a.gadelshin@melsoft-games.com> Date: Tue, 29 Jan 2019 17:58:05 +0300 Subject: [PATCH] ability to change instance_state --- google/resource_compute_instance.go | 76 +++++++++++++++ google/resource_compute_instance_test.go | 115 +++++++++++++++++++++++ 2 files changed, 191 insertions(+) diff --git a/google/resource_compute_instance.go b/google/resource_compute_instance.go index 71de97cc086..4746b982075 100644 --- a/google/resource_compute_instance.go +++ b/google/resource_compute_instance.go @@ -11,6 +11,7 @@ import ( "github.com/hashicorp/errwrap" "github.com/hashicorp/terraform/helper/customdiff" + "github.com/hashicorp/terraform/helper/resource" "github.com/hashicorp/terraform/helper/schema" "github.com/hashicorp/terraform/helper/validation" "github.com/mitchellh/hashstructure" @@ -127,6 +128,13 @@ func resourceComputeInstance() *schema.Resource { Required: true, }, + "instance_state": { + Type: schema.TypeString, + Optional: true, + Default: "running", + ValidateFunc: validation.StringInSlice([]string{"stopped", "running"}, false), + }, + "name": { Type: schema.TypeString, Required: true, @@ -762,6 +770,15 @@ func resourceComputeInstanceRead(d *schema.ResourceData, meta interface{}) error d.Set("can_ip_forward", instance.CanIpForward) d.Set("machine_type", GetResourceNameFromSelfLink(instance.MachineType)) + // PROVISIONING, STAGING, RUNNING, STOPPING, STOPPED, + // // SUSPENDING, SUSPENDED, and TERMINATED + switch instance.Status { + case "PROVISIONING", "STAGING", "RUNNING": + d.Set("instance_state", "running") + default: + d.Set("instance_state", "stopped") + } + // Set the networks // Use the first external IP found for the default connection info. networkInterfaces, _, internalIP, externalIP, err := flattenNetworkInterfaces(d, config, instance.NetworkInterfaces) @@ -964,6 +981,55 @@ func resourceComputeInstanceUpdate(d *schema.ResourceData, meta interface{}) err d.SetPartial("tags") } + if d.HasChange("instance_state") { + if d.Get("instance_state") == "running" { + _, err := config.clientCompute.Instances.Start(project, + zone, d.Id()).Do() + if err != nil { + return fmt.Errorf( + "Error starting an instance (%s): %s", d.Id(), err) + } + + stateConf := &resource.StateChangeConf{ + Pending: []string{"PROVISIONING", "STAGING", "TERMINATED"}, + Target: []string{"RUNNING"}, + Refresh: instanceStateRefreshFunc(config.clientCompute, project, zone, d.Id()), + Timeout: 10 * time.Minute, + Delay: 10 * time.Second, + MinTimeout: 3 * time.Second, + } + + _, err = stateConf.WaitForState() + if err != nil { + return fmt.Errorf( + "Error waiting for instance (%s) to start: %s", d.Id(), err) + } + } else { + _, err := config.clientCompute.Instances.Stop(project, + zone, d.Id()).Do() + if err != nil { + return fmt.Errorf( + "Error stopping an instance (%s): %s", d.Id(), err) + } + + stateConf := &resource.StateChangeConf{ + Pending: []string{"PROVISIONING", "STAGING", "RUNNING", "STOPPING"}, + Target: []string{"TERMINATED"}, + Refresh: instanceStateRefreshFunc(config.clientCompute, project, zone, d.Id()), + Timeout: 10 * time.Minute, + Delay: 10 * time.Second, + MinTimeout: 3 * time.Second, + } + + _, err = stateConf.WaitForState() + if err != nil { + return fmt.Errorf( + "Error waiting for instance (%s) to stop: %s", d.Id(), err) + } + } + + } + if d.HasChange("labels") { labels := expandLabels(d) labelFingerprint := d.Get("label_fingerprint").(string) @@ -1335,6 +1401,16 @@ func resourceComputeInstanceUpdate(d *schema.ResourceData, meta interface{}) err return resourceComputeInstanceRead(d, meta) } +func instanceStateRefreshFunc(computeClient *compute.Service, project, zone, instance string) resource.StateRefreshFunc { + return func() (interface{}, string, error) { + i, err := computeClient.Instances.Get(project, zone, instance).Do() + if err != nil { + return nil, "", err + } + return i, i.Status, nil + } +} + func expandAttachedDisk(diskConfig map[string]interface{}, d *schema.ResourceData, meta interface{}) (*computeBeta.AttachedDisk, error) { config := meta.(*Config) diff --git a/google/resource_compute_instance_test.go b/google/resource_compute_instance_test.go index 0a23538b07a..0994567051e 100644 --- a/google/resource_compute_instance_test.go +++ b/google/resource_compute_instance_test.go @@ -1043,6 +1043,37 @@ func TestAccComputeInstance_secondaryAliasIpRange(t *testing.T) { }) } +func TestAccComputeInstanceState(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{ + { + Config: testAccComputeInstance_running(instanceName), + Check: resource.ComposeTestCheckFunc( + testAccCheckComputeInstanceExists( + "google_compute_instance.foobar", &instance), + testAccCheckComputeInstanceRunning(&instance), + ), + }, + { + Config: testAccComputeInstance_stopped(instanceName), + Check: resource.ComposeTestCheckFunc( + testAccCheckComputeInstanceExists( + "google_compute_instance.foobar", &instance), + testAccCheckComputeInstanceTerminated(&instance), + ), + }, + }, + }) +} + func testAccCheckComputeInstanceUpdateMachineType(n string) resource.TestCheckFunc { return func(s *terraform.State) error { rs, ok := s.RootModule().Resources[n] @@ -1133,6 +1164,24 @@ func testAccCheckComputeInstanceExistsInProject(n, p string, instance *compute.I } } +func testAccCheckComputeInstanceRunning(instance *compute.Instance) resource.TestCheckFunc { + return func(s *terraform.State) error { + if instance.Status != "RUNNING" { + return fmt.Errorf("Instance is not RUNNING, state: %s", instance.Status) + } + return nil + } +} + +func testAccCheckComputeInstanceTerminated(instance *compute.Instance) resource.TestCheckFunc { + return func(s *terraform.State) error { + if instance.Status != "TERMINATED" { + return fmt.Errorf("Instance is not TERMINATED, state: %s", instance.Status) + } + return nil + } +} + func testAccCheckComputeInstanceMetadata( instance *compute.Instance, k string, v string) resource.TestCheckFunc { @@ -1584,6 +1633,72 @@ resource "google_compute_instance" "foobar" { `, instance) } +func testAccComputeInstance_running(instance string) string { + return fmt.Sprintf(` +data "google_compute_image" "my_image" { + family = "debian-9" + project = "debian-cloud" +} + +resource "google_compute_instance" "foobar" { + name = "%s" + machine_type = "n1-standard-1" + zone = "us-central1-a" + can_ip_forward = false + tags = ["foo", "bar"] + + boot_disk { + initialize_params{ + image = "${data.google_compute_image.my_image.self_link}" + } + } + + network_interface { + network = "default" + } + + instance_state = "running" + + metadata = { + foo = "bar" + } +} +`, instance) +} + +func testAccComputeInstance_stopped(instance string) string { + return fmt.Sprintf(` +data "google_compute_image" "my_image" { + family = "debian-9" + project = "debian-cloud" +} + +resource "google_compute_instance" "foobar" { + name = "%s" + machine_type = "n1-standard-1" + zone = "us-central1-a" + can_ip_forward = false + tags = ["foo", "bar"] + + boot_disk { + initialize_params{ + image = "${data.google_compute_image.my_image.self_link}" + } + } + + network_interface { + network = "default" + } + + instance_state = "stopped" + + metadata = { + foo = "bar" + } +} +`, instance) +} + func testAccComputeInstance_basic3(instance string) string { return fmt.Sprintf(` data "google_compute_image" "my_image" {