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" {