From 3925979c4d363cf0bada9c8b0301921379eeeadc Mon Sep 17 00:00:00 2001 From: The Magician Date: Mon, 4 Nov 2019 09:19:27 -0800 Subject: [PATCH] Move RIGM/IGM updating to use versions and update_policy (#4763) Signed-off-by: Modular Magician --- ...resource_compute_instance_group_manager.go | 337 +++++++-- ...rce_compute_instance_group_manager_test.go | 648 +++++++++++++++++- ...e_compute_region_instance_group_manager.go | 305 +++++++-- ...pute_region_instance_group_manager_test.go | 620 ++++++++++++++++- ...mpute_instance_group_manager.html.markdown | 26 +- ...egion_instance_group_manager.html.markdown | 23 +- 6 files changed, 1790 insertions(+), 169 deletions(-) diff --git a/google/resource_compute_instance_group_manager.go b/google/resource_compute_instance_group_manager.go index 1592db55897..10b653737cf 100644 --- a/google/resource_compute_instance_group_manager.go +++ b/google/resource_compute_instance_group_manager.go @@ -38,7 +38,10 @@ func resourceComputeInstanceGroupManager() *schema.Resource { "instance_template": { Type: schema.TypeString, - Required: true, + Optional: true, + Computed: true, + Deprecated: "This field will be replaced by `version.instance_template` in 3.0.0", + ConflictsWith: []string{"version"}, DiffSuppressFunc: compareSelfLinkRelativePaths, }, @@ -46,39 +49,33 @@ func resourceComputeInstanceGroupManager() *schema.Resource { Type: schema.TypeList, Optional: true, Computed: true, - Removed: "This field is in beta. Use it in the the google-beta provider instead. See https://terraform.io/docs/providers/google/provider_versions.html for more details.", Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "name": { Type: schema.TypeString, - Required: true, - Removed: "This field is in beta. Use it in the the google-beta provider instead. See https://terraform.io/docs/providers/google/provider_versions.html for more details.", + Optional: true, }, "instance_template": { Type: schema.TypeString, Required: true, - Removed: "This field is in beta. Use it in the the google-beta provider instead. See https://terraform.io/docs/providers/google/provider_versions.html for more details.", DiffSuppressFunc: compareSelfLinkRelativePaths, }, "target_size": { Type: schema.TypeList, Optional: true, - Removed: "This field is in beta. Use it in the the google-beta provider instead. See https://terraform.io/docs/providers/google/provider_versions.html for more details.", MaxItems: 1, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "fixed": { Type: schema.TypeInt, Optional: true, - Removed: "This field is in beta. Use it in the the google-beta provider instead. See https://terraform.io/docs/providers/google/provider_versions.html for more details.", }, "percent": { Type: schema.TypeInt, Optional: true, - Removed: "This field is in beta. Use it in the the google-beta provider instead. See https://terraform.io/docs/providers/google/provider_versions.html for more details.", ValidateFunc: validation.IntBetween(0, 100), }, }, @@ -148,10 +145,12 @@ func resourceComputeInstanceGroupManager() *schema.Resource { }, "update_strategy": { - Type: schema.TypeString, - Optional: true, - Default: "REPLACE", - ValidateFunc: validation.StringInSlice([]string{"RESTART", "NONE", "ROLLING_UPDATE", "REPLACE"}, false), + Type: schema.TypeString, + Optional: true, + Default: "REPLACE", + Deprecated: "This field will be replaced by `update_policy` in 3.0.0", + ConflictsWith: []string{"update_policy"}, + ValidateFunc: validation.StringInSlice([]string{"RESTART", "NONE", "ROLLING_UPDATE", "REPLACE"}, false), DiffSuppressFunc: func(key, old, new string, d *schema.ResourceData) bool { if old == "REPLACE" && new == "RESTART" { return true @@ -182,20 +181,17 @@ func resourceComputeInstanceGroupManager() *schema.Resource { Type: schema.TypeList, Optional: true, MaxItems: 1, - Removed: "This field is in beta. Use it in the the google-beta provider instead. See https://terraform.io/docs/providers/google/provider_versions.html for more details.", Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "health_check": { Type: schema.TypeString, Required: true, - Removed: "This field is in beta. Use it in the the google-beta provider instead. See https://terraform.io/docs/providers/google/provider_versions.html for more details.", DiffSuppressFunc: compareSelfLinkRelativePaths, }, "initial_delay_sec": { Type: schema.TypeInt, Required: true, - Removed: "This field is in beta. Use it in the the google-beta provider instead. See https://terraform.io/docs/providers/google/provider_versions.html for more details.", ValidateFunc: validation.IntBetween(0, 3600), }, }, @@ -203,9 +199,9 @@ func resourceComputeInstanceGroupManager() *schema.Resource { }, "rolling_update_policy": { - Removed: "This field is in beta. Use it in the the google-beta provider instead. See https://terraform.io/docs/providers/google/provider_versions.html for more details.", Computed: true, Type: schema.TypeList, + Removed: "This field has been replaced by update_policy.", Optional: true, MaxItems: 1, Elem: &schema.Resource{ @@ -213,14 +209,12 @@ func resourceComputeInstanceGroupManager() *schema.Resource { "minimal_action": { Type: schema.TypeString, Required: true, - Removed: "This field is in beta. Use it in the the google-beta provider instead. See https://terraform.io/docs/providers/google/provider_versions.html for more details.", ValidateFunc: validation.StringInSlice([]string{"RESTART", "REPLACE"}, false), }, "type": { Type: schema.TypeString, Required: true, - Removed: "This field is in beta. Use it in the the google-beta provider instead. See https://terraform.io/docs/providers/google/provider_versions.html for more details.", ValidateFunc: validation.StringInSlice([]string{"OPPORTUNISTIC", "PROACTIVE"}, false), }, @@ -228,32 +222,83 @@ func resourceComputeInstanceGroupManager() *schema.Resource { Type: schema.TypeInt, Optional: true, Computed: true, - Removed: "This field is in beta. Use it in the the google-beta provider instead. See https://terraform.io/docs/providers/google/provider_versions.html for more details.", }, "max_surge_percent": { Type: schema.TypeInt, Optional: true, - Removed: "This field is in beta. Use it in the the google-beta provider instead. See https://terraform.io/docs/providers/google/provider_versions.html for more details.", ValidateFunc: validation.IntBetween(0, 100), }, "max_unavailable_fixed": { Type: schema.TypeInt, Optional: true, - Removed: "This field is in beta. Use it in the the google-beta provider instead. See https://terraform.io/docs/providers/google/provider_versions.html for more details.", + Computed: true, }, "max_unavailable_percent": { Type: schema.TypeInt, Optional: true, - Removed: "This field is in beta. Use it in the the google-beta provider instead. See https://terraform.io/docs/providers/google/provider_versions.html for more details.", ValidateFunc: validation.IntBetween(0, 100), }, "min_ready_sec": { Type: schema.TypeInt, - Removed: "This field is in beta. Use it in the the google-beta provider instead. See https://terraform.io/docs/providers/google/provider_versions.html for more details.", + Optional: true, + ValidateFunc: validation.IntBetween(0, 3600), + }, + }, + }, + }, + "update_policy": { + Computed: true, + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "minimal_action": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringInSlice([]string{"RESTART", "REPLACE"}, false), + }, + + "type": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringInSlice([]string{"OPPORTUNISTIC", "PROACTIVE"}, false), + }, + + "max_surge_fixed": { + Type: schema.TypeInt, + Optional: true, + Computed: true, + ConflictsWith: []string{"update_policy.0.max_surge_percent"}, + }, + + "max_surge_percent": { + Type: schema.TypeInt, + Optional: true, + ConflictsWith: []string{"update_policy.0.max_surge_fixed"}, + ValidateFunc: validation.IntBetween(0, 100), + }, + + "max_unavailable_fixed": { + Type: schema.TypeInt, + Optional: true, + Computed: true, + ConflictsWith: []string{"update_policy.0.max_unavailable_percent"}, + }, + + "max_unavailable_percent": { + Type: schema.TypeInt, + Optional: true, + ConflictsWith: []string{"update_policy.0.max_unavailable_fixed"}, + ValidateFunc: validation.IntBetween(0, 100), + }, + + "min_ready_sec": { + Type: schema.TypeInt, Optional: true, ValidateFunc: validation.IntBetween(0, 3600), }, @@ -311,13 +356,16 @@ func resourceComputeInstanceGroupManagerCreate(d *schema.ResourceData, meta inte // Build the parameter manager := &computeBeta.InstanceGroupManager{ - Name: d.Get("name").(string), - Description: d.Get("description").(string), - BaseInstanceName: d.Get("base_instance_name").(string), - InstanceTemplate: d.Get("instance_template").(string), - TargetSize: int64(d.Get("target_size").(int)), - NamedPorts: getNamedPortsBeta(d.Get("named_port").(*schema.Set).List()), - TargetPools: convertStringSet(d.Get("target_pools").(*schema.Set)), + Name: d.Get("name").(string), + Description: d.Get("description").(string), + BaseInstanceName: d.Get("base_instance_name").(string), + InstanceTemplate: d.Get("instance_template").(string), + TargetSize: int64(d.Get("target_size").(int)), + NamedPorts: getNamedPortsBeta(d.Get("named_port").(*schema.Set).List()), + TargetPools: convertStringSet(d.Get("target_pools").(*schema.Set)), + AutoHealingPolicies: expandAutoHealingPolicies(d.Get("auto_healing_policies").([]interface{})), + Versions: expandVersions(d.Get("version").([]interface{})), + UpdatePolicy: expandUpdatePolicy(d.Get("update_policy").([]interface{})), // Force send TargetSize to allow a value of 0. ForceSendFields: []string{"TargetSize"}, } @@ -359,6 +407,31 @@ func flattenNamedPortsBeta(namedPorts []*computeBeta.NamedPort) []map[string]int } +func flattenVersions(versions []*computeBeta.InstanceGroupManagerVersion) []map[string]interface{} { + result := make([]map[string]interface{}, 0, len(versions)) + for _, version := range versions { + versionMap := make(map[string]interface{}) + versionMap["name"] = version.Name + versionMap["instance_template"] = ConvertSelfLinkToV1(version.InstanceTemplate) + versionMap["target_size"] = flattenFixedOrPercent(version.TargetSize) + result = append(result, versionMap) + } + + return result +} + +func flattenFixedOrPercent(fixedOrPercent *computeBeta.FixedOrPercent) []map[string]interface{} { + result := make(map[string]interface{}) + if value := fixedOrPercent.Percent; value > 0 { + result["percent"] = value + } else if value := fixedOrPercent.Fixed; value > 0 { + result["fixed"] = fixedOrPercent.Fixed + } else { + return []map[string]interface{}{} + } + return []map[string]interface{}{result} +} + func getManager(d *schema.ResourceData, meta interface{}) (*computeBeta.InstanceGroupManager, error) { config := meta.(*Config) if err := parseImportId([]string{"(?P[^/]+)/(?P[^/]+)/(?P[^/]+)", "(?P[^/]+)/(?P[^/]+)", "(?P[^/]+)"}, d, config); err != nil { @@ -429,10 +502,18 @@ func resourceComputeInstanceGroupManagerRead(d *schema.ResourceData, meta interf } d.Set("update_strategy", update_strategy.(string)) - // When we make a list Removed, we see a permadiff from `field_name.#: "" => ""`. Set to nil in Read so we see no diff. - d.Set("version", nil) d.Set("rolling_update_policy", nil) + if err = d.Set("auto_healing_policies", flattenAutoHealingPolicies(manager.AutoHealingPolicies)); err != nil { + return fmt.Errorf("Error setting auto_healing_policies in state: %s", err.Error()) + } + if err := d.Set("version", flattenVersions(manager.Versions)); err != nil { + return err + } + if err = d.Set("update_policy", flattenUpdatePolicy(manager.UpdatePolicy)); err != nil { + return fmt.Errorf("Error setting update_policy in state: %s", err.Error()) + } + if d.Get("wait_for_instances").(bool) { conf := resource.StateChangeConf{ Pending: []string{"creating", "error"}, @@ -497,40 +578,54 @@ func resourceComputeInstanceGroupManagerUpdate(d *schema.ResourceData, meta inte return err } - zone, _ := getZone(d, config) - name := d.Get("name").(string) + zone, err := getZone(d, config) + if err != nil { + return err + } - d.Partial(true) + updatedManager := &computeBeta.InstanceGroupManager{ + Fingerprint: d.Get("fingerprint").(string), + } + var change bool - // If target_pools changes then update if d.HasChange("target_pools") { - targetPools := convertStringSet(d.Get("target_pools").(*schema.Set)) + updatedManager.TargetPools = convertStringSet(d.Get("target_pools").(*schema.Set)) + change = true + } - // Build the parameter - setTargetPools := &computeBeta.InstanceGroupManagersSetTargetPoolsRequest{ - Fingerprint: d.Get("fingerprint").(string), - TargetPools: targetPools, - } + if d.HasChange("auto_healing_policies") { + updatedManager.AutoHealingPolicies = expandAutoHealingPolicies(d.Get("auto_healing_policies").([]interface{})) + updatedManager.ForceSendFields = append(updatedManager.ForceSendFields, "AutoHealingPolicies") + change = true + } - op, err := config.clientComputeBeta.InstanceGroupManagers.SetTargetPools( - project, zone, name, setTargetPools).Do() + if d.HasChange("version") { + updatedManager.Versions = expandVersions(d.Get("version").([]interface{})) + change = true + } + if d.HasChange("update_policy") { + updatedManager.UpdatePolicy = expandUpdatePolicy(d.Get("update_policy").([]interface{})) + change = true + } + + if change { + op, err := config.clientComputeBeta.InstanceGroupManagers.Patch(project, zone, d.Get("name").(string), updatedManager).Do() if err != nil { - return fmt.Errorf("Error updating InstanceGroupManager: %s", err) + return fmt.Errorf("Error updating managed group instances: %s", err) } - // Wait for the operation to complete timeoutInMinutes := int(d.Timeout(schema.TimeoutUpdate).Minutes()) - err = computeSharedOperationWaitTime(config.clientCompute, op, project, timeoutInMinutes, "Updating InstanceGroupManager") + err = computeSharedOperationWaitTime(config.clientCompute, op, project, timeoutInMinutes, "Updating managed group instances") if err != nil { return err } - - d.SetPartial("target_pools") } - // If named_port changes then update: + // named ports can't be updated through PATCH + // so we call the update method on the instance group, instead of the igm if d.HasChange("named_port") { + d.Partial(true) // Build the parameters for a "SetNamedPorts" request: namedPorts := getNamedPortsBeta(d.Get("named_port").(*schema.Set).List()) @@ -540,7 +635,7 @@ func resourceComputeInstanceGroupManagerUpdate(d *schema.ResourceData, meta inte // Make the request: op, err := config.clientComputeBeta.InstanceGroups.SetNamedPorts( - project, zone, name, setNamedPorts).Do() + project, zone, d.Get("name").(string), setNamedPorts).Do() if err != nil { return fmt.Errorf("Error updating InstanceGroupManager: %s", err) @@ -552,14 +647,16 @@ func resourceComputeInstanceGroupManagerUpdate(d *schema.ResourceData, meta inte if err != nil { return err } - d.SetPartial("named_port") } + // target_size should be updated through resize if d.HasChange("target_size") { + d.Partial(true) + targetSize := int64(d.Get("target_size").(int)) op, err := config.clientComputeBeta.InstanceGroupManagers.Resize( - project, zone, name, targetSize).Do() + project, zone, d.Get("name").(string), targetSize).Do() if err != nil { return fmt.Errorf("Error updating InstanceGroupManager: %s", err) @@ -571,12 +668,14 @@ func resourceComputeInstanceGroupManagerUpdate(d *schema.ResourceData, meta inte if err != nil { return err } - d.SetPartial("target_size") } // If instance_template changes then update if d.HasChange("instance_template") { + d.Partial(true) + + name := d.Get("name").(string) // Build the parameter setInstanceTemplate := &computeBeta.InstanceGroupManagersSetInstanceTemplateRequest{ InstanceTemplate: d.Get("instance_template").(string), @@ -667,6 +766,136 @@ func resourceComputeInstanceGroupManagerDelete(d *schema.ResourceData, meta inte return nil } +func expandAutoHealingPolicies(configured []interface{}) []*computeBeta.InstanceGroupManagerAutoHealingPolicy { + autoHealingPolicies := make([]*computeBeta.InstanceGroupManagerAutoHealingPolicy, 0, len(configured)) + for _, raw := range configured { + data := raw.(map[string]interface{}) + autoHealingPolicy := computeBeta.InstanceGroupManagerAutoHealingPolicy{ + HealthCheck: data["health_check"].(string), + InitialDelaySec: int64(data["initial_delay_sec"].(int)), + } + + autoHealingPolicies = append(autoHealingPolicies, &autoHealingPolicy) + } + return autoHealingPolicies +} + +func expandVersions(configured []interface{}) []*computeBeta.InstanceGroupManagerVersion { + versions := make([]*computeBeta.InstanceGroupManagerVersion, 0, len(configured)) + for _, raw := range configured { + data := raw.(map[string]interface{}) + + version := computeBeta.InstanceGroupManagerVersion{ + Name: data["name"].(string), + InstanceTemplate: data["instance_template"].(string), + TargetSize: expandFixedOrPercent(data["target_size"].([]interface{})), + } + + versions = append(versions, &version) + } + return versions +} + +func expandFixedOrPercent(configured []interface{}) *computeBeta.FixedOrPercent { + fixedOrPercent := &computeBeta.FixedOrPercent{} + + for _, raw := range configured { + data := raw.(map[string]interface{}) + if percent := data["percent"]; percent.(int) > 0 { + fixedOrPercent.Percent = int64(percent.(int)) + } else { + fixedOrPercent.Fixed = int64(data["fixed"].(int)) + fixedOrPercent.ForceSendFields = []string{"Fixed"} + } + } + return fixedOrPercent +} + +func expandUpdatePolicy(configured []interface{}) *computeBeta.InstanceGroupManagerUpdatePolicy { + updatePolicy := &computeBeta.InstanceGroupManagerUpdatePolicy{} + + for _, raw := range configured { + data := raw.(map[string]interface{}) + + updatePolicy.MinimalAction = data["minimal_action"].(string) + updatePolicy.Type = data["type"].(string) + + // percent and fixed values are conflicting + // when the percent values are set, the fixed values will be ignored + if v := data["max_surge_percent"]; v.(int) > 0 { + updatePolicy.MaxSurge = &computeBeta.FixedOrPercent{ + Percent: int64(v.(int)), + NullFields: []string{"Fixed"}, + } + } else { + updatePolicy.MaxSurge = &computeBeta.FixedOrPercent{ + Fixed: int64(data["max_surge_fixed"].(int)), + // allow setting this value to 0 + ForceSendFields: []string{"Fixed"}, + NullFields: []string{"Percent"}, + } + } + + if v := data["max_unavailable_percent"]; v.(int) > 0 { + updatePolicy.MaxUnavailable = &computeBeta.FixedOrPercent{ + Percent: int64(v.(int)), + NullFields: []string{"Fixed"}, + } + } else { + updatePolicy.MaxUnavailable = &computeBeta.FixedOrPercent{ + Fixed: int64(data["max_unavailable_fixed"].(int)), + // allow setting this value to 0 + ForceSendFields: []string{"Fixed"}, + NullFields: []string{"Percent"}, + } + } + + if v, ok := data["min_ready_sec"]; ok { + updatePolicy.MinReadySec = int64(v.(int)) + } + } + return updatePolicy +} + +func flattenAutoHealingPolicies(autoHealingPolicies []*computeBeta.InstanceGroupManagerAutoHealingPolicy) []map[string]interface{} { + autoHealingPoliciesSchema := make([]map[string]interface{}, 0, len(autoHealingPolicies)) + for _, autoHealingPolicy := range autoHealingPolicies { + data := map[string]interface{}{ + "health_check": autoHealingPolicy.HealthCheck, + "initial_delay_sec": autoHealingPolicy.InitialDelaySec, + } + + autoHealingPoliciesSchema = append(autoHealingPoliciesSchema, data) + } + return autoHealingPoliciesSchema +} + +func flattenUpdatePolicy(updatePolicy *computeBeta.InstanceGroupManagerUpdatePolicy) []map[string]interface{} { + results := []map[string]interface{}{} + if updatePolicy != nil { + up := map[string]interface{}{} + if updatePolicy.MaxSurge != nil { + up["max_surge_fixed"] = updatePolicy.MaxSurge.Fixed + up["max_surge_percent"] = updatePolicy.MaxSurge.Percent + } else { + up["max_surge_fixed"] = 0 + up["max_surge_percent"] = 0 + } + if updatePolicy.MaxUnavailable != nil { + up["max_unavailable_fixed"] = updatePolicy.MaxUnavailable.Fixed + up["max_unavailable_percent"] = updatePolicy.MaxUnavailable.Percent + } else { + up["max_unavailable_fixed"] = 0 + up["max_unavailable_percent"] = 0 + } + up["min_ready_sec"] = updatePolicy.MinReadySec + up["minimal_action"] = updatePolicy.MinimalAction + up["type"] = updatePolicy.Type + results = append(results, up) + } + return results +} + func resourceInstanceGroupManagerStateImporter(d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { d.Set("wait_for_instances", false) config := meta.(*Config) diff --git a/google/resource_compute_instance_group_manager_test.go b/google/resource_compute_instance_group_manager_test.go index 75a47ad0b83..63e96eb595a 100644 --- a/google/resource_compute_instance_group_manager_test.go +++ b/google/resource_compute_instance_group_manager_test.go @@ -150,6 +150,53 @@ func TestAccInstanceGroupManager_updateStrategy(t *testing.T) { }) } +func TestAccInstanceGroupManager_updatePolicy(t *testing.T) { + t.Parallel() + + igm := fmt.Sprintf("igm-test-%s", acctest.RandString(10)) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckInstanceGroupManagerDestroy, + Steps: []resource.TestStep{ + { + Config: testAccInstanceGroupManager_rollingUpdatePolicy(igm), + }, + { + ResourceName: "google_compute_instance_group_manager.igm-rolling-update-policy", + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccInstanceGroupManager_rollingUpdatePolicy2(igm), + }, + + { + ResourceName: "google_compute_instance_group_manager.igm-rolling-update-policy", + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccInstanceGroupManager_rollingUpdatePolicy3(igm), + }, + { + ResourceName: "google_compute_instance_group_manager.igm-rolling-update-policy", + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccInstanceGroupManager_rollingUpdatePolicy4(igm), + }, + { + ResourceName: "google_compute_instance_group_manager.igm-rolling-update-policy", + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + func TestAccInstanceGroupManager_separateRegions(t *testing.T) { t.Parallel() @@ -178,6 +225,90 @@ func TestAccInstanceGroupManager_separateRegions(t *testing.T) { }) } +func TestAccInstanceGroupManager_versions(t *testing.T) { + t.Parallel() + + primaryTemplate := fmt.Sprintf("igm-test-%s", acctest.RandString(10)) + canaryTemplate := fmt.Sprintf("igm-test-%s", acctest.RandString(10)) + igm := fmt.Sprintf("igm-test-%s", acctest.RandString(10)) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckInstanceGroupManagerDestroy, + Steps: []resource.TestStep{ + { + Config: testAccInstanceGroupManager_versions(primaryTemplate, canaryTemplate, igm), + }, + { + ResourceName: "google_compute_instance_group_manager.igm-basic", + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccInstanceGroupManager_autoHealingPolicies(t *testing.T) { + t.Parallel() + + template := fmt.Sprintf("igm-test-%s", acctest.RandString(10)) + target := fmt.Sprintf("igm-test-%s", acctest.RandString(10)) + igm := fmt.Sprintf("igm-test-%s", acctest.RandString(10)) + hck := fmt.Sprintf("igm-test-%s", acctest.RandString(10)) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckInstanceGroupManagerDestroy, + Steps: []resource.TestStep{ + { + Config: testAccInstanceGroupManager_autoHealingPolicies(template, target, igm, hck), + }, + { + ResourceName: "google_compute_instance_group_manager.igm-basic", + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccInstanceGroupManager_autoHealingPoliciesRemoved(template, target, igm, hck), + }, + { + ResourceName: "google_compute_instance_group_manager.igm-basic", + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccInstanceGroupManager_upgradeInstanceTemplate(t *testing.T) { + t.Parallel() + + igm := fmt.Sprintf("igm-test-%s", acctest.RandString(10)) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckInstanceGroupManagerDestroy, + Steps: []resource.TestStep{ + { + Config: testAccInstanceGroupManager_upgradeInstanceTemplate1(igm), + }, + { + ResourceName: "google_compute_instance_group_manager.igm-instance-template-upgrade", + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccInstanceGroupManager_upgradeInstanceTemplate2(igm), + PlanOnly: true, + ExpectNonEmptyPlan: false, + }, + }, + }) +} + func testAccCheckInstanceGroupManagerDestroy(s *terraform.State) error { config := testAccProvider.Meta().(*Config) @@ -232,7 +363,12 @@ func testAccInstanceGroupManager_basic(template, target, igm1, igm2 string) stri resource "google_compute_instance_group_manager" "igm-basic" { description = "Terraform test instance group manager" name = "%s" - instance_template = "${google_compute_instance_template.igm-basic.self_link}" + + version { + name = "prod" + instance_template = "${google_compute_instance_template.igm-basic.self_link}" + } + target_pools = ["${google_compute_target_pool.igm-basic.self_link}"] base_instance_name = "igm-basic" zone = "us-central1-c" @@ -242,7 +378,12 @@ func testAccInstanceGroupManager_basic(template, target, igm1, igm2 string) stri resource "google_compute_instance_group_manager" "igm-no-tp" { description = "Terraform test instance group manager" name = "%s" - instance_template = "${google_compute_instance_template.igm-basic.self_link}" + + version { + name = "prod" + instance_template = "${google_compute_instance_template.igm-basic.self_link}" + } + base_instance_name = "igm-no-tp" zone = "us-central1-c" target_size = 2 @@ -281,7 +422,12 @@ func testAccInstanceGroupManager_targetSizeZero(template, igm string) string { resource "google_compute_instance_group_manager" "igm-basic" { description = "Terraform test instance group manager" name = "%s" - instance_template = "${google_compute_instance_template.igm-basic.self_link}" + + version { + name = "prod" + instance_template = "${google_compute_instance_template.igm-basic.self_link}" + } + base_instance_name = "igm-basic" zone = "us-central1-c" } @@ -325,7 +471,12 @@ func testAccInstanceGroupManager_update(template, target, igm string) string { resource "google_compute_instance_group_manager" "igm-update" { description = "Terraform test instance group manager" name = "%s" - instance_template = "${google_compute_instance_template.igm-update.self_link}" + + version { + name = "prod" + instance_template = "${google_compute_instance_template.igm-update.self_link}" + } + target_pools = ["${google_compute_target_pool.igm-update.self_link}"] base_instance_name = "igm-update" zone = "us-central1-c" @@ -402,7 +553,12 @@ func testAccInstanceGroupManager_update2(template1, target1, target2, template2, resource "google_compute_instance_group_manager" "igm-update" { description = "Terraform test instance group manager" name = "%s" - instance_template = "${google_compute_instance_template.igm-update2.self_link}" + + version { + name = "prod" + instance_template = "${google_compute_instance_template.igm-update2.self_link}" + } + target_pools = [ "${google_compute_target_pool.igm-update.self_link}", "${google_compute_target_pool.igm-update2.self_link}", @@ -455,7 +611,12 @@ func testAccInstanceGroupManager_updateLifecycle(tag, igm string) string { resource "google_compute_instance_group_manager" "igm-update" { description = "Terraform test instance group manager" name = "%s" - instance_template = "${google_compute_instance_template.igm-update.self_link}" + + version { + name = "prod" + instance_template = "${google_compute_instance_template.igm-update.self_link}" + } + base_instance_name = "igm-update" zone = "us-central1-c" target_size = 2 @@ -512,6 +673,204 @@ func testAccInstanceGroupManager_updateStrategy(igm string) string { }`, igm) } +func testAccInstanceGroupManager_rollingUpdatePolicy(igm string) string { + return fmt.Sprintf(` +data "google_compute_image" "my_image" { + family = "debian-9" + project = "debian-cloud" +} + +resource "google_compute_instance_template" "igm-rolling-update-policy" { + machine_type = "n1-standard-1" + can_ip_forward = false + tags = ["terraform-testing"] + + disk { + source_image = "${data.google_compute_image.my_image.self_link}" + auto_delete = true + boot = true + } + + network_interface { + network = "default" + } + + service_account { + scopes = ["userinfo-email", "compute-ro", "storage-ro"] + } + + lifecycle { + create_before_destroy = true + } +} + +resource "google_compute_instance_group_manager" "igm-rolling-update-policy" { + description = "Terraform test instance group manager" + name = "%s" + version { + name = "prod" + instance_template = "${google_compute_instance_template.igm-rolling-update-policy.self_link}" + } + base_instance_name = "igm-rolling-update-policy" + zone = "us-central1-c" + target_size = 3 + update_policy { + type = "PROACTIVE" + minimal_action = "REPLACE" + max_surge_percent = 50 + max_unavailable_percent = 50 + min_ready_sec = 20 + } + named_port { + name = "customhttp" + port = 8080 + } +}`, igm) +} + +func testAccInstanceGroupManager_rollingUpdatePolicy2(igm string) string { + return fmt.Sprintf(` +data "google_compute_image" "my_image" { + family = "debian-9" + project = "debian-cloud" +} + +resource "google_compute_instance_template" "igm-rolling-update-policy" { + machine_type = "n1-standard-1" + can_ip_forward = false + tags = ["terraform-testing"] + + disk { + source_image = "${data.google_compute_image.my_image.self_link}" + auto_delete = true + boot = true + } + + network_interface { + network = "default" + } + + lifecycle { + create_before_destroy = true + } +} + +resource "google_compute_instance_group_manager" "igm-rolling-update-policy" { + description = "Terraform test instance group manager" + name = "%s" + version { + name = "prod2" + instance_template = "${google_compute_instance_template.igm-rolling-update-policy.self_link}" + } + base_instance_name = "igm-rolling-update-policy" + zone = "us-central1-c" + target_size = 3 + update_policy { + type = "PROACTIVE" + minimal_action = "REPLACE" + max_surge_fixed = 2 + max_unavailable_fixed = 2 + min_ready_sec = 20 + } + named_port { + name = "customhttp" + port = 8080 + } +}`, igm) +} + +func testAccInstanceGroupManager_rollingUpdatePolicy3(igm string) string { + return fmt.Sprintf(` +data "google_compute_image" "my_image" { + family = "debian-9" + project = "debian-cloud" +} +resource "google_compute_instance_template" "igm-rolling-update-policy" { + machine_type = "n1-standard-1" + can_ip_forward = false + tags = ["terraform-testing"] + disk { + source_image = "${data.google_compute_image.my_image.self_link}" + auto_delete = true + boot = true + } + network_interface { + network = "default" + } + lifecycle { + create_before_destroy = true + } +} +resource "google_compute_instance_group_manager" "igm-rolling-update-policy" { + description = "Terraform test instance group manager" + name = "%s" + version { + name = "prod2" + instance_template = "${google_compute_instance_template.igm-rolling-update-policy.self_link}" + } + base_instance_name = "igm-rolling-update-policy" + zone = "us-central1-c" + target_size = 3 + update_policy { + type = "PROACTIVE" + minimal_action = "REPLACE" + max_surge_fixed = 0 + max_unavailable_fixed = 2 + min_ready_sec = 20 + } + named_port { + name = "customhttp" + port = 8080 + } +}`, igm) +} + +func testAccInstanceGroupManager_rollingUpdatePolicy4(igm string) string { + return fmt.Sprintf(` +data "google_compute_image" "my_image" { + family = "debian-9" + project = "debian-cloud" +} +resource "google_compute_instance_template" "igm-rolling-update-policy" { + machine_type = "n1-standard-1" + can_ip_forward = false + tags = ["terraform-testing"] + disk { + source_image = "${data.google_compute_image.my_image.self_link}" + auto_delete = true + boot = true + } + network_interface { + network = "default" + } + lifecycle { + create_before_destroy = true + } +} +resource "google_compute_instance_group_manager" "igm-rolling-update-policy" { + description = "Terraform test instance group manager" + name = "%s" + version { + name = "prod2" + instance_template = "${google_compute_instance_template.igm-rolling-update-policy.self_link}" + } + base_instance_name = "igm-rolling-update-policy" + zone = "us-central1-c" + target_size = 3 + update_policy { + type = "PROACTIVE" + minimal_action = "REPLACE" + max_surge_fixed = 2 + max_unavailable_fixed = 0 + min_ready_sec = 20 + } + named_port { + name = "customhttp" + port = 8080 + } +}`, igm) +} + func testAccInstanceGroupManager_separateRegions(igm1, igm2 string) string { return fmt.Sprintf(` data "google_compute_image" "my_image" { @@ -542,7 +901,12 @@ func testAccInstanceGroupManager_separateRegions(igm1, igm2 string) string { resource "google_compute_instance_group_manager" "igm-basic" { description = "Terraform test instance group manager" name = "%s" - instance_template = "${google_compute_instance_template.igm-basic.self_link}" + + version { + instance_template = "${google_compute_instance_template.igm-basic.self_link}" + name = "prod" + } + base_instance_name = "igm-basic" zone = "us-central1-c" target_size = 2 @@ -551,10 +915,278 @@ func testAccInstanceGroupManager_separateRegions(igm1, igm2 string) string { resource "google_compute_instance_group_manager" "igm-basic-2" { description = "Terraform test instance group manager" name = "%s" - instance_template = "${google_compute_instance_template.igm-basic.self_link}" + + version { + name = "prod" + instance_template = "${google_compute_instance_template.igm-basic.self_link}" + } + base_instance_name = "igm-basic-2" zone = "us-west1-b" target_size = 2 } `, igm1, igm2) } + +func testAccInstanceGroupManager_autoHealingPolicies(template, target, igm, hck string) string { + return fmt.Sprintf(` +data "google_compute_image" "my_image" { + family = "debian-9" + project = "debian-cloud" +} + +resource "google_compute_instance_template" "igm-basic" { + name = "%s" + machine_type = "n1-standard-1" + can_ip_forward = false + tags = ["foo", "bar"] + disk { + source_image = "${data.google_compute_image.my_image.self_link}" + auto_delete = true + boot = true + } + network_interface { + network = "default" + } + + service_account { + scopes = ["userinfo-email", "compute-ro", "storage-ro"] + } +} + +resource "google_compute_target_pool" "igm-basic" { + description = "Resource created for Terraform acceptance testing" + name = "%s" + session_affinity = "CLIENT_IP_PROTO" +} + +resource "google_compute_instance_group_manager" "igm-basic" { + description = "Terraform test instance group manager" + name = "%s" + version { + instance_template = "${google_compute_instance_template.igm-basic.self_link}" + name = "prod" + } + target_pools = ["${google_compute_target_pool.igm-basic.self_link}"] + base_instance_name = "igm-basic" + zone = "us-central1-c" + target_size = 2 + auto_healing_policies { + health_check = "${google_compute_http_health_check.zero.self_link}" + initial_delay_sec = "10" + } +} + +resource "google_compute_http_health_check" "zero" { + name = "%s" + request_path = "/" + check_interval_sec = 1 + timeout_sec = 1 +} + `, template, target, igm, hck) +} + +func testAccInstanceGroupManager_autoHealingPoliciesRemoved(template, target, igm, hck string) string { + return fmt.Sprintf(` +data "google_compute_image" "my_image" { + family = "debian-9" + project = "debian-cloud" +} +resource "google_compute_instance_template" "igm-basic" { + name = "%s" + machine_type = "n1-standard-1" + can_ip_forward = false + tags = ["foo", "bar"] + disk { + source_image = "${data.google_compute_image.my_image.self_link}" + auto_delete = true + boot = true + } + network_interface { + network = "default" + } + + service_account { + scopes = ["userinfo-email", "compute-ro", "storage-ro"] + } +} +resource "google_compute_target_pool" "igm-basic" { + description = "Resource created for Terraform acceptance testing" + name = "%s" + session_affinity = "CLIENT_IP_PROTO" +} +resource "google_compute_instance_group_manager" "igm-basic" { + description = "Terraform test instance group manager" + name = "%s" + version { + instance_template = "${google_compute_instance_template.igm-basic.self_link}" + name = "prod" + } + target_pools = ["${google_compute_target_pool.igm-basic.self_link}"] + base_instance_name = "igm-basic" + zone = "us-central1-c" + target_size = 2 +} +resource "google_compute_http_health_check" "zero" { + name = "%s" + request_path = "/" + check_interval_sec = 1 + timeout_sec = 1 +} + `, template, target, igm, hck) +} + +func testAccInstanceGroupManager_versions(primaryTemplate string, canaryTemplate string, igm string) string { + return fmt.Sprintf(` +data "google_compute_image" "my_image" { + family = "debian-9" + project = "debian-cloud" +} + +resource "google_compute_instance_template" "igm-primary" { + name = "%s" + machine_type = "n1-standard-1" + can_ip_forward = false + tags = ["foo", "bar"] + disk { + source_image = "${data.google_compute_image.my_image.self_link}" + auto_delete = true + boot = true + } + network_interface { + network = "default" + } + + service_account { + scopes = ["userinfo-email", "compute-ro", "storage-ro"] + } +} + +resource "google_compute_instance_template" "igm-canary" { + name = "%s" + machine_type = "n1-standard-1" + can_ip_forward = false + tags = ["foo", "bar"] + disk { + source_image = "${data.google_compute_image.my_image.self_link}" + auto_delete = true + boot = true + } + network_interface { + network = "default" + } + + service_account { + scopes = ["userinfo-email", "compute-ro", "storage-ro"] + } +} + +resource "google_compute_instance_group_manager" "igm-basic" { + description = "Terraform test instance group manager" + name = "%s" + base_instance_name = "igm-basic" + zone = "us-central1-c" + target_size = 2 + + version { + name = "primary" + instance_template = "${google_compute_instance_template.igm-primary.self_link}" + } + + version { + name = "canary" + instance_template = "${google_compute_instance_template.igm-canary.self_link}" + target_size { + fixed = 1 + } + } +} + `, primaryTemplate, canaryTemplate, igm) +} +func testAccInstanceGroupManager_upgradeInstanceTemplate1(igm string) string { + return fmt.Sprintf(` +data "google_compute_image" "my_image" { + family = "debian-9" + project = "debian-cloud" +} + +resource "google_compute_instance_template" "igm-instance-template-upgrade" { + machine_type = "n1-standard-1" + can_ip_forward = false + tags = ["terraform-testing"] + + disk { + source_image = "${data.google_compute_image.my_image.self_link}" + auto_delete = true + boot = true + } + + network_interface { + network = "default" + } + + lifecycle { + create_before_destroy = true + } +} + +resource "google_compute_instance_group_manager" "igm-instance-template-upgrade" { + description = "Terraform test instance group manager" + name = "%s" + + instance_template = "${google_compute_instance_template.igm-instance-template-upgrade.self_link}" + + target_size = 3 + base_instance_name = "igm-instance-template-upgrade" + + named_port { + name = "customhttp" + port = 8080 + } +}`, igm) +} + +func testAccInstanceGroupManager_upgradeInstanceTemplate2(igm string) string { + return fmt.Sprintf(` +data "google_compute_image" "my_image" { + family = "debian-9" + project = "debian-cloud" +} + +resource "google_compute_instance_template" "igm-instance-template-upgrade" { + machine_type = "n1-standard-1" + can_ip_forward = false + tags = ["terraform-testing"] + + disk { + source_image = "${data.google_compute_image.my_image.self_link}" + auto_delete = true + boot = true + } + + network_interface { + network = "default" + } + + lifecycle { + create_before_destroy = true + } +} + +resource "google_compute_instance_group_manager" "igm-instance-template-upgrade" { + description = "Terraform test instance group manager" + name = "%s" + + version { + instance_template = "${google_compute_instance_template.igm-instance-template-upgrade.self_link}" + } + + target_size = 3 + base_instance_name = "igm-instance-template-upgrade" + + named_port { + name = "customhttp" + port = 8080 + } +}`, igm) +} diff --git a/google/resource_compute_region_instance_group_manager.go b/google/resource_compute_region_instance_group_manager.go index 85db3a08b2d..984ce9fb0c3 100644 --- a/google/resource_compute_region_instance_group_manager.go +++ b/google/resource_compute_region_instance_group_manager.go @@ -44,7 +44,10 @@ func resourceComputeRegionInstanceGroupManager() *schema.Resource { "instance_template": { Type: schema.TypeString, - Required: true, + Optional: true, + Computed: true, + Deprecated: "This field will be replaced by `version.instance_template` in 3.0.0", + ConflictsWith: []string{"version"}, DiffSuppressFunc: compareSelfLinkRelativePaths, }, @@ -52,39 +55,33 @@ func resourceComputeRegionInstanceGroupManager() *schema.Resource { Type: schema.TypeList, Optional: true, Computed: true, - Removed: "This field is in beta. Use it in the the google-beta provider instead. See https://terraform.io/docs/providers/google/provider_versions.html for more details.", Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "name": { Type: schema.TypeString, - Required: true, - Removed: "This field is in beta. Use it in the the google-beta provider instead. See https://terraform.io/docs/providers/google/provider_versions.html for more details.", + Optional: true, }, "instance_template": { Type: schema.TypeString, Required: true, - Removed: "This field is in beta. Use it in the the google-beta provider instead. See https://terraform.io/docs/providers/google/provider_versions.html for more details.", DiffSuppressFunc: compareSelfLinkRelativePaths, }, "target_size": { Type: schema.TypeList, Optional: true, - Removed: "This field is in beta. Use it in the the google-beta provider instead. See https://terraform.io/docs/providers/google/provider_versions.html for more details.", MaxItems: 1, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "fixed": { Type: schema.TypeInt, Optional: true, - Removed: "This field is in beta. Use it in the the google-beta provider instead. See https://terraform.io/docs/providers/google/provider_versions.html for more details.", }, "percent": { Type: schema.TypeInt, Optional: true, - Removed: "This field is in beta. Use it in the the google-beta provider instead. See https://terraform.io/docs/providers/google/provider_versions.html for more details.", ValidateFunc: validation.IntBetween(0, 100), }, }, @@ -153,10 +150,10 @@ func resourceComputeRegionInstanceGroupManager() *schema.Resource { }, "update_strategy": { - Type: schema.TypeString, - Deprecated: "This field is removed.", - Optional: true, - Computed: true, + Type: schema.TypeString, + Deprecated: "This field will be replaced by `update_policy` in 3.0.0", + Optional: true, + ConflictsWith: []string{"update_policy"}, }, "target_pools": { @@ -186,20 +183,17 @@ func resourceComputeRegionInstanceGroupManager() *schema.Resource { Type: schema.TypeList, Optional: true, MaxItems: 1, - Removed: "This field is in beta. Use it in the the google-beta provider instead. See https://terraform.io/docs/providers/google/provider_versions.html for more details.", Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "health_check": { Type: schema.TypeString, Required: true, - Removed: "This field is in beta. Use it in the the google-beta provider instead. See https://terraform.io/docs/providers/google/provider_versions.html for more details.", DiffSuppressFunc: compareSelfLinkRelativePaths, }, "initial_delay_sec": { Type: schema.TypeInt, Required: true, - Removed: "This field is in beta. Use it in the the google-beta provider instead. See https://terraform.io/docs/providers/google/provider_versions.html for more details.", ValidateFunc: validation.IntBetween(0, 3600), }, }, @@ -219,9 +213,9 @@ func resourceComputeRegionInstanceGroupManager() *schema.Resource { }, "rolling_update_policy": { - Removed: "This field is in beta. Use it in the the google-beta provider instead. See https://terraform.io/docs/providers/google/provider_versions.html for more details.", - Computed: true, Type: schema.TypeList, + Computed: true, + Removed: "This field has been replaced by update_policy.", Optional: true, MaxItems: 1, Elem: &schema.Resource{ @@ -229,14 +223,12 @@ func resourceComputeRegionInstanceGroupManager() *schema.Resource { "minimal_action": { Type: schema.TypeString, Required: true, - Removed: "This field is in beta. Use it in the the google-beta provider instead. See https://terraform.io/docs/providers/google/provider_versions.html for more details.", ValidateFunc: validation.StringInSlice([]string{"RESTART", "REPLACE"}, false), }, "type": { Type: schema.TypeString, Required: true, - Removed: "This field is in beta. Use it in the the google-beta provider instead. See https://terraform.io/docs/providers/google/provider_versions.html for more details.", ValidateFunc: validation.StringInSlice([]string{"OPPORTUNISTIC", "PROACTIVE"}, false), }, @@ -244,35 +236,99 @@ func resourceComputeRegionInstanceGroupManager() *schema.Resource { Type: schema.TypeInt, Optional: true, Computed: true, - Removed: "This field is in beta. Use it in the the google-beta provider instead. See https://terraform.io/docs/providers/google/provider_versions.html for more details.", }, "max_surge_percent": { Type: schema.TypeInt, Optional: true, - Removed: "This field is in beta. Use it in the the google-beta provider instead. See https://terraform.io/docs/providers/google/provider_versions.html for more details.", ValidateFunc: validation.IntBetween(0, 100), }, "max_unavailable_fixed": { Type: schema.TypeInt, Optional: true, - Removed: "This field is in beta. Use it in the the google-beta provider instead. See https://terraform.io/docs/providers/google/provider_versions.html for more details.", + Computed: true, }, "max_unavailable_percent": { Type: schema.TypeInt, Optional: true, - Removed: "This field is in beta. Use it in the the google-beta provider instead. See https://terraform.io/docs/providers/google/provider_versions.html for more details.", ValidateFunc: validation.IntBetween(0, 100), }, "min_ready_sec": { Type: schema.TypeInt, - Removed: "This field is in beta. Use it in the the google-beta provider instead. See https://terraform.io/docs/providers/google/provider_versions.html for more details.", Optional: true, ValidateFunc: validation.IntBetween(0, 3600), }, + "instance_redistribution_type": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validation.StringInSlice([]string{"PROACTIVE", "NONE", ""}, false), + DiffSuppressFunc: emptyOrDefaultStringSuppress("PROACTIVE"), + }, + }, + }, + }, + + "update_policy": { + Type: schema.TypeList, + Computed: true, + Optional: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "minimal_action": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringInSlice([]string{"RESTART", "REPLACE"}, false), + }, + + "type": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringInSlice([]string{"OPPORTUNISTIC", "PROACTIVE"}, false), + }, + + "max_surge_fixed": { + Type: schema.TypeInt, + Optional: true, + Computed: true, + ConflictsWith: []string{"update_policy.0.max_surge_percent"}, + }, + + "max_surge_percent": { + Type: schema.TypeInt, + Optional: true, + ConflictsWith: []string{"update_policy.0.max_surge_fixed"}, + ValidateFunc: validation.IntBetween(0, 100), + }, + + "max_unavailable_fixed": { + Type: schema.TypeInt, + Optional: true, + Computed: true, + ConflictsWith: []string{"update_policy.0.max_unavailable_percent"}, + }, + + "max_unavailable_percent": { + Type: schema.TypeInt, + Optional: true, + ConflictsWith: []string{"update_policy.0.max_unavailable_fixed"}, + ValidateFunc: validation.IntBetween(0, 100), + }, + + "min_ready_sec": { + Type: schema.TypeInt, + Optional: true, + ValidateFunc: validation.IntBetween(0, 3600), + }, + "instance_redistribution_type": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validation.StringInSlice([]string{"PROACTIVE", "NONE", ""}, false), + DiffSuppressFunc: emptyOrDefaultStringSuppress("PROACTIVE"), + }, }, }, }, @@ -294,14 +350,17 @@ func resourceComputeRegionInstanceGroupManagerCreate(d *schema.ResourceData, met } manager := &computeBeta.InstanceGroupManager{ - Name: d.Get("name").(string), - Description: d.Get("description").(string), - BaseInstanceName: d.Get("base_instance_name").(string), - InstanceTemplate: d.Get("instance_template").(string), - TargetSize: int64(d.Get("target_size").(int)), - NamedPorts: getNamedPortsBeta(d.Get("named_port").(*schema.Set).List()), - TargetPools: convertStringSet(d.Get("target_pools").(*schema.Set)), - DistributionPolicy: expandDistributionPolicy(d.Get("distribution_policy_zones").(*schema.Set)), + Name: d.Get("name").(string), + Description: d.Get("description").(string), + BaseInstanceName: d.Get("base_instance_name").(string), + InstanceTemplate: d.Get("instance_template").(string), + TargetSize: int64(d.Get("target_size").(int)), + NamedPorts: getNamedPortsBeta(d.Get("named_port").(*schema.Set).List()), + TargetPools: convertStringSet(d.Get("target_pools").(*schema.Set)), + AutoHealingPolicies: expandAutoHealingPolicies(d.Get("auto_healing_policies").([]interface{})), + Versions: expandVersions(d.Get("version").([]interface{})), + UpdatePolicy: expandRegionUpdatePolicy(d.Get("update_policy").([]interface{})), + DistributionPolicy: expandDistributionPolicy(d.Get("distribution_policy_zones").(*schema.Set)), // Force send TargetSize to allow size of 0. ForceSendFields: []string{"TargetSize"}, } @@ -395,7 +454,6 @@ func resourceComputeRegionInstanceGroupManagerRead(d *schema.ResourceData, meta d.Set("base_instance_name", manager.BaseInstanceName) d.Set("instance_template", ConvertSelfLinkToV1(manager.InstanceTemplate)) - d.Set("name", manager.Name) d.Set("region", GetResourceNameFromSelfLink(manager.Region)) d.Set("description", manager.Description) @@ -413,10 +471,19 @@ func resourceComputeRegionInstanceGroupManagerRead(d *schema.ResourceData, meta return err } d.Set("self_link", ConvertSelfLinkToV1(manager.SelfLink)) - // When we make a list Removed, we see a permadiff from `field_name.#: "" => ""`. Set to nil in Read so we see no diff. - d.Set("version", nil) + d.Set("rolling_update_policy", nil) + if err := d.Set("auto_healing_policies", flattenAutoHealingPolicies(manager.AutoHealingPolicies)); err != nil { + return fmt.Errorf("Error setting auto_healing_policies in state: %s", err.Error()) + } + if err := d.Set("version", flattenVersions(manager.Versions)); err != nil { + return err + } + if err := d.Set("update_policy", flattenRegionUpdatePolicy(manager.UpdatePolicy)); err != nil { + return fmt.Errorf("Error setting update_policy in state: %s", err.Error()) + } + if d.Get("wait_for_instances").(bool) { conf := resource.StateChangeConf{ Pending: []string{"creating", "error"}, @@ -446,65 +513,54 @@ func resourceComputeRegionInstanceGroupManagerUpdate(d *schema.ResourceData, met return err } - d.Partial(true) + updatedManager := &computeBeta.InstanceGroupManager{ + Fingerprint: d.Get("fingerprint").(string), + } + var change bool if d.HasChange("target_pools") { - targetPools := convertStringSet(d.Get("target_pools").(*schema.Set)) - - // Build the parameter - setTargetPools := &computeBeta.RegionInstanceGroupManagersSetTargetPoolsRequest{ - Fingerprint: d.Get("fingerprint").(string), - TargetPools: targetPools, - } - - op, err := config.clientComputeBeta.RegionInstanceGroupManagers.SetTargetPools( - project, region, d.Get("name").(string), setTargetPools).Do() - - if err != nil { - return fmt.Errorf("Error updating RegionInstanceGroupManager: %s", err) - } - - // Wait for the operation to complete - timeoutInMinutes := int(d.Timeout(schema.TimeoutUpdate).Minutes()) - err = computeSharedOperationWaitTime(config.clientCompute, op, project, timeoutInMinutes, "Updating RegionInstanceGroupManager") - if err != nil { - return err - } + updatedManager.TargetPools = convertStringSet(d.Get("target_pools").(*schema.Set)) + change = true + } - d.SetPartial("target_pools") + if d.HasChange("auto_healing_policies") { + updatedManager.AutoHealingPolicies = expandAutoHealingPolicies(d.Get("auto_healing_policies").([]interface{})) + updatedManager.ForceSendFields = append(updatedManager.ForceSendFields, "AutoHealingPolicies") + change = true } - if d.HasChange("instance_template") { - // Build the parameter - setInstanceTemplate := &computeBeta.RegionInstanceGroupManagersSetTemplateRequest{ - InstanceTemplate: d.Get("instance_template").(string), - } + if d.HasChange("version") { + updatedManager.Versions = expandVersions(d.Get("version").([]interface{})) + change = true + } - op, err := config.clientComputeBeta.RegionInstanceGroupManagers.SetInstanceTemplate( - project, region, d.Get("name").(string), setInstanceTemplate).Do() + if d.HasChange("update_policy") { + updatedManager.UpdatePolicy = expandRegionUpdatePolicy(d.Get("update_policy").([]interface{})) + change = true + } + if change { + op, err := config.clientComputeBeta.RegionInstanceGroupManagers.Patch(project, region, d.Get("name").(string), updatedManager).Do() if err != nil { - return fmt.Errorf("Error updating RegionInstanceGroupManager: %s", err) + return fmt.Errorf("Error updating region managed group instances: %s", err) } - // Wait for the operation to complete timeoutInMinutes := int(d.Timeout(schema.TimeoutUpdate).Minutes()) - err = computeSharedOperationWaitTime(config.clientCompute, op, project, timeoutInMinutes, "Updating InstanceGroupManager") + err = computeSharedOperationWaitTime(config.clientCompute, op, project, timeoutInMinutes, "Updating region managed group instances") if err != nil { return err } - - d.SetPartial("instance_template") } + // named ports can't be updated through PATCH + // so we call the update method on the region instance group, instead of the rigm if d.HasChange("named_port") { - // Build the parameters for a "SetNamedPorts" request: + d.Partial(true) namedPorts := getNamedPortsBeta(d.Get("named_port").(*schema.Set).List()) setNamedPorts := &computeBeta.RegionInstanceGroupsSetNamedPortsRequest{ NamedPorts: namedPorts, } - // Make the request: op, err := config.clientComputeBeta.RegionInstanceGroups.SetNamedPorts( project, region, d.Get("name").(string), setNamedPorts).Do() @@ -512,17 +568,17 @@ func resourceComputeRegionInstanceGroupManagerUpdate(d *schema.ResourceData, met return fmt.Errorf("Error updating RegionInstanceGroupManager: %s", err) } - // Wait for the operation to complete: timeoutInMinutes := int(d.Timeout(schema.TimeoutUpdate).Minutes()) err = computeSharedOperationWaitTime(config.clientCompute, op, project, timeoutInMinutes, "Updating RegionInstanceGroupManager") if err != nil { return err } - d.SetPartial("named_port") } + // target size should use resize if d.HasChange("target_size") { + d.Partial(true) targetSize := int64(d.Get("target_size").(int)) op, err := config.clientComputeBeta.RegionInstanceGroupManagers.Resize( project, region, d.Get("name").(string), targetSize).Do() @@ -531,16 +587,38 @@ func resourceComputeRegionInstanceGroupManagerUpdate(d *schema.ResourceData, met return fmt.Errorf("Error resizing RegionInstanceGroupManager: %s", err) } - // Wait for the operation to complete timeoutInMinutes := int(d.Timeout(schema.TimeoutUpdate).Minutes()) err = computeSharedOperationWaitTime(config.clientCompute, op, project, timeoutInMinutes, "Resizing RegionInstanceGroupManager") if err != nil { return err } - d.SetPartial("target_size") } + if d.HasChange("instance_template") { + d.Partial(true) + // Build the parameter + setInstanceTemplate := &computeBeta.RegionInstanceGroupManagersSetTemplateRequest{ + InstanceTemplate: d.Get("instance_template").(string), + } + + op, err := config.clientComputeBeta.RegionInstanceGroupManagers.SetInstanceTemplate( + project, region, d.Get("name").(string), setInstanceTemplate).Do() + + if err != nil { + return fmt.Errorf("Error updating RegionInstanceGroupManager: %s", err) + } + + // Wait for the operation to complete + timeoutInMinutes := int(d.Timeout(schema.TimeoutUpdate).Minutes()) + err = computeSharedOperationWaitTime(config.clientCompute, op, project, timeoutInMinutes, "Updating InstanceGroupManager") + if err != nil { + return err + } + + d.SetPartial("instance_template") + } + d.Partial(false) return resourceComputeRegionInstanceGroupManagerRead(d, meta) @@ -585,6 +663,81 @@ func resourceComputeRegionInstanceGroupManagerDelete(d *schema.ResourceData, met return nil } +func expandRegionUpdatePolicy(configured []interface{}) *computeBeta.InstanceGroupManagerUpdatePolicy { + updatePolicy := &computeBeta.InstanceGroupManagerUpdatePolicy{} + + for _, raw := range configured { + data := raw.(map[string]interface{}) + + updatePolicy.MinimalAction = data["minimal_action"].(string) + updatePolicy.Type = data["type"].(string) + updatePolicy.InstanceRedistributionType = data["instance_redistribution_type"].(string) + + // percent and fixed values are conflicting + // when the percent values are set, the fixed values will be ignored + if v := data["max_surge_percent"]; v.(int) > 0 { + updatePolicy.MaxSurge = &computeBeta.FixedOrPercent{ + Percent: int64(v.(int)), + NullFields: []string{"Fixed"}, + } + } else { + updatePolicy.MaxSurge = &computeBeta.FixedOrPercent{ + Fixed: int64(data["max_surge_fixed"].(int)), + // allow setting this value to 0 + ForceSendFields: []string{"Fixed"}, + NullFields: []string{"Percent"}, + } + } + + if v := data["max_unavailable_percent"]; v.(int) > 0 { + updatePolicy.MaxUnavailable = &computeBeta.FixedOrPercent{ + Percent: int64(v.(int)), + NullFields: []string{"Fixed"}, + } + } else { + updatePolicy.MaxUnavailable = &computeBeta.FixedOrPercent{ + Fixed: int64(data["max_unavailable_fixed"].(int)), + // allow setting this value to 0 + ForceSendFields: []string{"Fixed"}, + NullFields: []string{"Percent"}, + } + } + + if v, ok := data["min_ready_sec"]; ok { + updatePolicy.MinReadySec = int64(v.(int)) + } + } + return updatePolicy +} + +func flattenRegionUpdatePolicy(updatePolicy *computeBeta.InstanceGroupManagerUpdatePolicy) []map[string]interface{} { + results := []map[string]interface{}{} + if updatePolicy != nil { + up := map[string]interface{}{} + if updatePolicy.MaxSurge != nil { + up["max_surge_fixed"] = updatePolicy.MaxSurge.Fixed + up["max_surge_percent"] = updatePolicy.MaxSurge.Percent + } else { + up["max_surge_fixed"] = 0 + up["max_surge_percent"] = 0 + } + if updatePolicy.MaxUnavailable != nil { + up["max_unavailable_fixed"] = updatePolicy.MaxUnavailable.Fixed + up["max_unavailable_percent"] = updatePolicy.MaxUnavailable.Percent + } else { + up["max_unavailable_fixed"] = 0 + up["max_unavailable_percent"] = 0 + } + up["min_ready_sec"] = updatePolicy.MinReadySec + up["minimal_action"] = updatePolicy.MinimalAction + up["type"] = updatePolicy.Type + up["instance_redistribution_type"] = updatePolicy.InstanceRedistributionType + + results = append(results, up) + } + return results +} + func expandDistributionPolicy(configured *schema.Set) *computeBeta.DistributionPolicy { if configured.Len() == 0 { return nil diff --git a/google/resource_compute_region_instance_group_manager_test.go b/google/resource_compute_region_instance_group_manager_test.go index a94513b8030..51c398146a2 100644 --- a/google/resource_compute_region_instance_group_manager_test.go +++ b/google/resource_compute_region_instance_group_manager_test.go @@ -151,6 +151,41 @@ func TestAccRegionInstanceGroupManager_updateStrategy(t *testing.T) { }) } +func TestAccRegionInstanceGroupManager_rollingUpdatePolicy(t *testing.T) { + t.Parallel() + + igm := fmt.Sprintf("igm-test-%s", acctest.RandString(10)) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckInstanceGroupManagerDestroy, + Steps: []resource.TestStep{ + { + Config: testAccRegionInstanceGroupManager_rollingUpdatePolicy(igm), + }, + { + ResourceName: "google_compute_region_instance_group_manager.igm-rolling-update-policy", + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccRegionInstanceGroupManager_rollingUpdatePolicySetToDefault(igm), + PlanOnly: true, + ExpectNonEmptyPlan: false, + }, + { + Config: testAccRegionInstanceGroupManager_rollingUpdatePolicy2(igm), + }, + { + ResourceName: "google_compute_region_instance_group_manager.igm-rolling-update-policy", + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + func TestAccRegionInstanceGroupManager_separateRegions(t *testing.T) { t.Parallel() @@ -179,6 +214,63 @@ func TestAccRegionInstanceGroupManager_separateRegions(t *testing.T) { }) } +func TestAccRegionInstanceGroupManager_versions(t *testing.T) { + t.Parallel() + + primaryTemplate := fmt.Sprintf("igm-test-%s", acctest.RandString(10)) + canaryTemplate := fmt.Sprintf("igm-test-%s", acctest.RandString(10)) + igm := fmt.Sprintf("igm-test-%s", acctest.RandString(10)) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckRegionInstanceGroupManagerDestroy, + Steps: []resource.TestStep{ + { + Config: testAccRegionInstanceGroupManager_versions(primaryTemplate, canaryTemplate, igm), + }, + { + ResourceName: "google_compute_region_instance_group_manager.igm-basic", + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccRegionInstanceGroupManager_autoHealingPolicies(t *testing.T) { + t.Parallel() + + template := fmt.Sprintf("igm-test-%s", acctest.RandString(10)) + target := fmt.Sprintf("igm-test-%s", acctest.RandString(10)) + igm := fmt.Sprintf("igm-test-%s", acctest.RandString(10)) + hck := fmt.Sprintf("igm-test-%s", acctest.RandString(10)) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckRegionInstanceGroupManagerDestroy, + Steps: []resource.TestStep{ + { + Config: testAccRegionInstanceGroupManager_autoHealingPolicies(template, target, igm, hck), + }, + { + ResourceName: "google_compute_region_instance_group_manager.igm-basic", + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccRegionInstanceGroupManager_autoHealingPoliciesRemoved(template, target, igm, hck), + }, + { + ResourceName: "google_compute_region_instance_group_manager.igm-basic", + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + func TestAccRegionInstanceGroupManager_distributionPolicy(t *testing.T) { t.Parallel() @@ -203,6 +295,33 @@ func TestAccRegionInstanceGroupManager_distributionPolicy(t *testing.T) { }) } +func TestAccRegionInstanceGroupManager_upgradeInstanceTemplate(t *testing.T) { + t.Parallel() + + igm := fmt.Sprintf("igm-test-%s", acctest.RandString(10)) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckInstanceGroupManagerDestroy, + Steps: []resource.TestStep{ + { + Config: testAccRegionInstanceGroupManager_upgradeInstanceTemplate1(igm), + }, + { + ResourceName: "google_compute_region_instance_group_manager.igm-instance-template-upgrade", + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccRegionInstanceGroupManager_upgradeInstanceTemplate2(igm), + PlanOnly: true, + ExpectNonEmptyPlan: false, + }, + }, + }) +} + func testAccCheckRegionInstanceGroupManagerDestroy(s *terraform.State) error { config := testAccProvider.Meta().(*Config) @@ -267,7 +386,12 @@ func testAccRegionInstanceGroupManager_basic(template, target, igm1, igm2 string resource "google_compute_region_instance_group_manager" "igm-basic" { description = "Terraform test instance group manager" name = "%s" - instance_template = "${google_compute_instance_template.igm-basic.self_link}" + + version { + name = "primary" + instance_template = "${google_compute_instance_template.igm-basic.self_link}" + } + target_pools = ["${google_compute_target_pool.igm-basic.self_link}"] base_instance_name = "igm-basic" region = "us-central1" @@ -277,7 +401,12 @@ func testAccRegionInstanceGroupManager_basic(template, target, igm1, igm2 string resource "google_compute_region_instance_group_manager" "igm-no-tp" { description = "Terraform test instance group manager" name = "%s" - instance_template = "${google_compute_instance_template.igm-basic.self_link}" + + version { + name = "primary" + instance_template = "${google_compute_instance_template.igm-basic.self_link}" + } + base_instance_name = "igm-no-tp" region = "us-central1" target_size = 2 @@ -316,7 +445,12 @@ func testAccRegionInstanceGroupManager_targetSizeZero(template, igm string) stri resource "google_compute_region_instance_group_manager" "igm-basic" { description = "Terraform test instance group manager" name = "%s" - instance_template = "${google_compute_instance_template.igm-basic.self_link}" + + version { + name = "primary" + instance_template = "${google_compute_instance_template.igm-basic.self_link}" + } + base_instance_name = "igm-basic" region = "us-central1" } @@ -360,7 +494,12 @@ func testAccRegionInstanceGroupManager_update(template, target, igm string) stri resource "google_compute_region_instance_group_manager" "igm-update" { description = "Terraform test instance group manager" name = "%s" - instance_template = "${google_compute_instance_template.igm-update.self_link}" + + version { + name = "primary" + instance_template = "${google_compute_instance_template.igm-update.self_link}" + } + target_pools = ["${google_compute_target_pool.igm-update.self_link}"] base_instance_name = "igm-update" region = "us-central1" @@ -437,7 +576,12 @@ func testAccRegionInstanceGroupManager_update2(template1, target1, target2, temp resource "google_compute_region_instance_group_manager" "igm-update" { description = "Terraform test instance group manager" name = "%s" - instance_template = "${google_compute_instance_template.igm-update2.self_link}" + + version { + instance_template = "${google_compute_instance_template.igm-update2.self_link}" + name = "primary" + } + target_pools = [ "${google_compute_target_pool.igm-update.self_link}", "${google_compute_target_pool.igm-update2.self_link}", @@ -490,7 +634,12 @@ func testAccRegionInstanceGroupManager_updateLifecycle(tag, igm string) string { resource "google_compute_region_instance_group_manager" "igm-update" { description = "Terraform test instance group manager" name = "%s" - instance_template = "${google_compute_instance_template.igm-update.self_link}" + + version { + instance_template = "${google_compute_instance_template.igm-update.self_link}" + name = "primary" + } + base_instance_name = "igm-update" region = "us-central1" target_size = 2 @@ -531,7 +680,12 @@ func testAccRegionInstanceGroupManager_separateRegions(igm1, igm2 string) string resource "google_compute_region_instance_group_manager" "igm-basic" { description = "Terraform test instance group manager" name = "%s" - instance_template = "${google_compute_instance_template.igm-basic.self_link}" + + version { + instance_template = "${google_compute_instance_template.igm-basic.self_link}" + name = "primary" + } + base_instance_name = "igm-basic" region = "us-central1" target_size = 2 @@ -540,7 +694,12 @@ func testAccRegionInstanceGroupManager_separateRegions(igm1, igm2 string) string resource "google_compute_region_instance_group_manager" "igm-basic-2" { description = "Terraform test instance group manager" name = "%s" - instance_template = "${google_compute_instance_template.igm-basic.self_link}" + + version { + instance_template = "${google_compute_instance_template.igm-basic.self_link}" + name = "primary" + } + base_instance_name = "igm-basic-2" region = "us-west1" target_size = 2 @@ -548,6 +707,182 @@ func testAccRegionInstanceGroupManager_separateRegions(igm1, igm2 string) string `, igm1, igm2) } +func testAccRegionInstanceGroupManager_autoHealingPolicies(template, target, igm, hck string) string { + return fmt.Sprintf(` +data "google_compute_image" "my_image" { + family = "debian-9" + project = "debian-cloud" +} + +resource "google_compute_instance_template" "igm-basic" { + name = "%s" + machine_type = "n1-standard-1" + can_ip_forward = false + tags = ["foo", "bar"] + disk { + source_image = "${data.google_compute_image.my_image.self_link}" + auto_delete = true + boot = true + } + network_interface { + network = "default" + } + + service_account { + scopes = ["userinfo-email", "compute-ro", "storage-ro"] + } +} + +resource "google_compute_target_pool" "igm-basic" { + description = "Resource created for Terraform acceptance testing" + name = "%s" + session_affinity = "CLIENT_IP_PROTO" +} + +resource "google_compute_region_instance_group_manager" "igm-basic" { + description = "Terraform test instance group manager" + name = "%s" + version { + instance_template = "${google_compute_instance_template.igm-basic.self_link}" + name = "primary" + } + target_pools = ["${google_compute_target_pool.igm-basic.self_link}"] + base_instance_name = "igm-basic" + region = "us-central1" + target_size = 2 + auto_healing_policies { + health_check = "${google_compute_http_health_check.zero.self_link}" + initial_delay_sec = "10" + } +} + +resource "google_compute_http_health_check" "zero" { + name = "%s" + request_path = "/" + check_interval_sec = 1 + timeout_sec = 1 +} + `, template, target, igm, hck) +} + +func testAccRegionInstanceGroupManager_autoHealingPoliciesRemoved(template, target, igm, hck string) string { + return fmt.Sprintf(` +data "google_compute_image" "my_image" { + family = "debian-9" + project = "debian-cloud" +} +resource "google_compute_instance_template" "igm-basic" { + name = "%s" + machine_type = "n1-standard-1" + can_ip_forward = false + tags = ["foo", "bar"] + disk { + source_image = "${data.google_compute_image.my_image.self_link}" + auto_delete = true + boot = true + } + network_interface { + network = "default" + } + + service_account { + scopes = ["userinfo-email", "compute-ro", "storage-ro"] + } +} +resource "google_compute_target_pool" "igm-basic" { + description = "Resource created for Terraform acceptance testing" + name = "%s" + session_affinity = "CLIENT_IP_PROTO" +} +resource "google_compute_region_instance_group_manager" "igm-basic" { + description = "Terraform test instance group manager" + name = "%s" + version { + instance_template = "${google_compute_instance_template.igm-basic.self_link}" + name = "primary" + } + target_pools = ["${google_compute_target_pool.igm-basic.self_link}"] + base_instance_name = "igm-basic" + region = "us-central1" + target_size = 2 +} +resource "google_compute_http_health_check" "zero" { + name = "%s" + request_path = "/" + check_interval_sec = 1 + timeout_sec = 1 +} + `, template, target, igm, hck) +} + +func testAccRegionInstanceGroupManager_versions(primaryTemplate string, canaryTemplate string, igm string) string { + return fmt.Sprintf(` +data "google_compute_image" "my_image" { + family = "debian-9" + project = "debian-cloud" +} + +resource "google_compute_instance_template" "igm-primary" { + name = "%s" + machine_type = "n1-standard-1" + can_ip_forward = false + tags = ["foo", "bar"] + disk { + source_image = "${data.google_compute_image.my_image.self_link}" + auto_delete = true + boot = true + } + network_interface { + network = "default" + } + + service_account { + scopes = ["userinfo-email", "compute-ro", "storage-ro"] + } +} + +resource "google_compute_instance_template" "igm-canary" { + name = "%s" + machine_type = "n1-standard-1" + can_ip_forward = false + tags = ["foo", "bar"] + disk { + source_image = "${data.google_compute_image.my_image.self_link}" + auto_delete = true + boot = true + } + network_interface { + network = "default" + } + + service_account { + scopes = ["userinfo-email", "compute-ro", "storage-ro"] + } +} + +resource "google_compute_region_instance_group_manager" "igm-basic" { + description = "Terraform test region instance group manager" + name = "%s" + base_instance_name = "igm-basic" + region = "us-central1" + target_size = 2 + + version { + name = "primary" + instance_template = "${google_compute_instance_template.igm-primary.self_link}" + } + + version { + name = "canary" + instance_template = "${google_compute_instance_template.igm-canary.self_link}" + target_size { + fixed = 1 + } + } +} + `, primaryTemplate, canaryTemplate, igm) +} + func testAccRegionInstanceGroupManager_distributionPolicy(template, igm string, zones []string) string { return fmt.Sprintf(` data "google_compute_image" "my_image" { @@ -573,7 +908,12 @@ resource "google_compute_instance_template" "igm-basic" { resource "google_compute_region_instance_group_manager" "igm-basic" { description = "Terraform test instance group manager" name = "%s" - instance_template = "${google_compute_instance_template.igm-basic.self_link}" + + version { + instance_template = "${google_compute_instance_template.igm-basic.self_link}" + name = "primary" + } + base_instance_name = "igm-basic" region = "us-central1" target_size = 2 @@ -581,7 +921,6 @@ resource "google_compute_region_instance_group_manager" "igm-basic" { } `, template, igm, strings.Join(zones, "\",\"")) } - func testAccRegionInstanceGroupManager_updateStrategy(igm string) string { return fmt.Sprintf(` data "google_compute_image" "my_image" { @@ -626,3 +965,264 @@ resource "google_compute_region_instance_group_manager" "igm-update-strategy" { } }`, igm) } + +func testAccRegionInstanceGroupManager_rollingUpdatePolicy(igm string) string { + return fmt.Sprintf(` +data "google_compute_image" "my_image" { + family = "debian-9" + project = "debian-cloud" +} + +resource "google_compute_instance_template" "igm-rolling-update-policy" { + machine_type = "n1-standard-1" + can_ip_forward = false + tags = ["terraform-testing"] + + disk { + source_image = "${data.google_compute_image.my_image.self_link}" + auto_delete = true + boot = true + } + + network_interface { + network = "default" + } + + service_account { + scopes = ["userinfo-email", "compute-ro", "storage-ro"] + } + + lifecycle { + create_before_destroy = true + } +} + +resource "google_compute_region_instance_group_manager" "igm-rolling-update-policy" { + description = "Terraform test instance group manager" + name = "%s" + version { + instance_template = "${google_compute_instance_template.igm-rolling-update-policy.self_link}" + name = "primary" + } + base_instance_name = "igm-rolling-update-policy" + region = "us-central1" + target_size = 4 + distribution_policy_zones = ["us-central1-a", "us-central1-f"] + + update_policy { + type = "PROACTIVE" + minimal_action = "REPLACE" + max_surge_fixed = 2 + max_unavailable_fixed = 2 + min_ready_sec = 20 + } + + named_port { + name = "customhttp" + port = 8080 + } +}`, igm) +} + +func testAccRegionInstanceGroupManager_rollingUpdatePolicySetToDefault(igm string) string { + return fmt.Sprintf(` +data "google_compute_image" "my_image" { + family = "debian-9" + project = "debian-cloud" +} + +resource "google_compute_instance_template" "igm-rolling-update-policy" { + machine_type = "n1-standard-1" + can_ip_forward = false + tags = ["terraform-testing"] + + disk { + source_image = "${data.google_compute_image.my_image.self_link}" + auto_delete = true + boot = true + } + + network_interface { + network = "default" + } + + service_account { + scopes = ["userinfo-email", "compute-ro", "storage-ro"] + } + + lifecycle { + create_before_destroy = true + } +} + +resource "google_compute_region_instance_group_manager" "igm-rolling-update-policy" { + description = "Terraform test instance group manager" + name = "%s" + version { + instance_template = "${google_compute_instance_template.igm-rolling-update-policy.self_link}" + name = "primary" + } + base_instance_name = "igm-rolling-update-policy" + region = "us-central1" + target_size = 4 + distribution_policy_zones = ["us-central1-a", "us-central1-f"] + + update_policy { + type = "PROACTIVE" + instance_redistribution_type = "PROACTIVE" + minimal_action = "REPLACE" + max_surge_fixed = 2 + max_unavailable_fixed = 2 + min_ready_sec = 20 + } + + named_port { + name = "customhttp" + port = 8080 + } +}`, igm) +} + +func testAccRegionInstanceGroupManager_rollingUpdatePolicy2(igm string) string { + return fmt.Sprintf(` +data "google_compute_image" "my_image" { + family = "debian-9" + project = "debian-cloud" +} + +resource "google_compute_instance_template" "igm-rolling-update-policy" { + machine_type = "n1-standard-1" + can_ip_forward = false + tags = ["terraform-testing"] + + disk { + source_image = "${data.google_compute_image.my_image.self_link}" + auto_delete = true + boot = true + } + + network_interface { + network = "default" + } + + lifecycle { + create_before_destroy = true + } +} + +resource "google_compute_region_instance_group_manager" "igm-rolling-update-policy" { + description = "Terraform test instance group manager" + name = "%s" + version { + name = "primary" + instance_template = "${google_compute_instance_template.igm-rolling-update-policy.self_link}" + } + base_instance_name = "igm-rolling-update-policy" + region = "us-central1" + distribution_policy_zones = ["us-central1-a", "us-central1-f"] + target_size = 3 + update_policy { + type = "PROACTIVE" + instance_redistribution_type = "NONE" + minimal_action = "REPLACE" + max_surge_fixed = 2 + max_unavailable_fixed = 0 + min_ready_sec = 10 + } + named_port { + name = "customhttp" + port = 8080 + } +}`, igm) +} +func testAccRegionInstanceGroupManager_upgradeInstanceTemplate1(igm string) string { + return fmt.Sprintf(` +data "google_compute_image" "my_image" { + family = "debian-9" + project = "debian-cloud" +} + +resource "google_compute_instance_template" "igm-instance-template-upgrade" { + machine_type = "n1-standard-1" + can_ip_forward = false + tags = ["terraform-testing"] + + disk { + source_image = "${data.google_compute_image.my_image.self_link}" + auto_delete = true + boot = true + } + + network_interface { + network = "default" + } + + lifecycle { + create_before_destroy = true + } +} + +resource "google_compute_region_instance_group_manager" "igm-instance-template-upgrade" { + description = "Terraform test instance group manager" + name = "%s" + + instance_template = "${google_compute_instance_template.igm-instance-template-upgrade.self_link}" + + region = "us-central1" + distribution_policy_zones = ["us-central1-a", "us-central1-f"] + target_size = 3 + base_instance_name = "igm-instance-template-upgrade" + + named_port { + name = "customhttp" + port = 8080 + } +}`, igm) +} + +func testAccRegionInstanceGroupManager_upgradeInstanceTemplate2(igm string) string { + return fmt.Sprintf(` +data "google_compute_image" "my_image" { + family = "debian-9" + project = "debian-cloud" +} + +resource "google_compute_instance_template" "igm-instance-template-upgrade" { + machine_type = "n1-standard-1" + can_ip_forward = false + tags = ["terraform-testing"] + + disk { + source_image = "${data.google_compute_image.my_image.self_link}" + auto_delete = true + boot = true + } + + network_interface { + network = "default" + } + + lifecycle { + create_before_destroy = true + } +} + +resource "google_compute_region_instance_group_manager" "igm-instance-template-upgrade" { + description = "Terraform test instance group manager" + name = "%s" + + version { + instance_template = "${google_compute_instance_template.igm-instance-template-upgrade.self_link}" + } + + region = "us-central1" + distribution_policy_zones = ["us-central1-a", "us-central1-f"] + target_size = 3 + base_instance_name = "igm-instance-template-upgrade" + + named_port { + name = "customhttp" + port = 8080 + } +}`, igm) +} diff --git a/website/docs/r/compute_instance_group_manager.html.markdown b/website/docs/r/compute_instance_group_manager.html.markdown index 21b2771b528..6d4dcb1b3b0 100644 --- a/website/docs/r/compute_instance_group_manager.html.markdown +++ b/website/docs/r/compute_instance_group_manager.html.markdown @@ -35,10 +35,12 @@ resource "google_compute_instance_group_manager" "appserver" { name = "appserver-igm" base_instance_name = "app" - instance_template = "${google_compute_instance_template.appserver.self_link}" - update_strategy = "NONE" zone = "us-central1-a" + version { + instance_template = "${google_compute_instance_template.appserver.self_link}" + } + target_pools = ["${google_compute_target_pool.appserver.self_link}"] target_size = 2 @@ -91,13 +93,16 @@ The following arguments are supported: appending a hyphen and a random four-character string to the base instance name. -* `instance_template` - (Required, [GA](https://terraform.io/docs/providers/google/provider_versions.html)) The +* `instance_template` - (Deprecated) The full URL to an instance template from which all new instances - will be created. This field is only present in the `google` provider. + will be created. This field is replaced by `version.instance_template`. You must + specify at least one `version` block with an `instance_template`. -* `version` - (Required, [Beta](https://terraform.io/docs/providers/google/provider_versions.html)) Application versions managed by this instance group. Each +* `version` - (Optional) Application versions managed by this instance group. Each version deals with a specific instance template, allowing canary release scenarios. Structure is documented below. + Until `instance_template` is removed this field will be Optional to allow for a + graceful upgrade. In the Beta provider and as of 3.0.0 it will be Required. * `name` - (Required) The name of the instance group manager. Must be 1-63 characters long and comply with @@ -118,11 +123,8 @@ The following arguments are supported: * `project` - (Optional) The ID of the project in which the resource belongs. If it is not provided, the provider project is used. -* `update_strategy` - (Optional, Default `"REPLACE"`) If the `instance_template` - resource is modified, a value of `"NONE"` will prevent any of the managed - instances from being restarted by Terraform. A value of `"REPLACE"` will - restart all of the instances at once. This field is only present in the - `google` provider. +* `update_strategy` - (Deprecated) This field has been deprecated, use `update_policy` + instead. * `target_size` - (Optional) The target number of running instances for this managed instance group. This value should always be explicitly set unless this resource is attached to @@ -138,10 +140,10 @@ The following arguments are supported: --- -* `auto_healing_policies` - (Optional, [Beta](https://terraform.io/docs/providers/google/provider_versions.html)) The autohealing policies for this managed instance +* `auto_healing_policies` - (Optional) The autohealing policies for this managed instance group. You can specify only one value. Structure is documented below. For more information, see the [official documentation](https://cloud.google.com/compute/docs/instance-groups/creating-groups-of-managed-instances#monitoring_groups). -* `update_policy` - (Optional, [Beta](https://terraform.io/docs/providers/google/provider_versions.html)) The update policy for this managed instance group. Structure is documented below. For more information, see the [official documentation](https://cloud.google.com/compute/docs/instance-groups/updating-managed-instance-groups) and [API](https://cloud.google.com/compute/docs/reference/rest/beta/instanceGroupManagers/patch) +* `update_policy` - (Optional) The update policy for this managed instance group. Structure is documented below. For more information, see the [official documentation](https://cloud.google.com/compute/docs/instance-groups/updating-managed-instance-groups) and [API](https://cloud.google.com/compute/docs/reference/rest/beta/instanceGroupManagers/patch) - - - The `update_policy` block supports: diff --git a/website/docs/r/compute_region_instance_group_manager.html.markdown b/website/docs/r/compute_region_instance_group_manager.html.markdown index 908918f255c..0b54c0c3dfe 100644 --- a/website/docs/r/compute_region_instance_group_manager.html.markdown +++ b/website/docs/r/compute_region_instance_group_manager.html.markdown @@ -35,7 +35,10 @@ resource "google_compute_region_instance_group_manager" "appserver" { name = "appserver-igm" base_instance_name = "app" - instance_template = "${google_compute_instance_template.appserver.self_link}" + + version { + instance_template = "${google_compute_instance_template.appserver.self_link}" + } region = "us-central1" distribution_policy_zones = ["us-central1-a", "us-central1-f"] @@ -55,13 +58,12 @@ resource "google_compute_region_instance_group_manager" "appserver" { ``` -## Example Usage with multiple versions (`google-beta` provider) +## Example Usage with multiple versions ```hcl resource "google_compute_region_instance_group_manager" "appserver" { name = "appserver-igm" base_instance_name = "app" - update_strategy = "NONE" region = "us-central1" target_size = 5 @@ -90,13 +92,16 @@ The following arguments are supported: appending a hyphen and a random four-character string to the base instance name. -* `instance_template` - (Required, [GA](https://terraform.io/docs/providers/google/provider_versions.html)) The full URL to an instance template from - which all new instances will be created. This field is only present in the - `google` provider. +* `instance_template` - (Deprecated) The + full URL to an instance template from which all new instances + will be created. This field is replaced by `version.instance_template`. You must + specify at least one `version` block with an `instance_template`. -* `version` - (Required, [Beta](https://terraform.io/docs/providers/google/provider_versions.html)) Application versions managed by this instance group. Each +* `version` - (Optional) Application versions managed by this instance group. Each version deals with a specific instance template, allowing canary release scenarios. Structure is documented below. + Until `instance_template` is removed this field will be Optional to allow for a + graceful upgrade. In the Beta provider and as of 3.0.0 it will be Required. * `name` - (Required) The name of the instance group manager. Must be 1-63 characters long and comply with @@ -130,11 +135,11 @@ The following arguments are supported: --- -* `auto_healing_policies` - (Optional, [Beta](https://terraform.io/docs/providers/google/provider_versions.html)) The autohealing policies for this managed instance +* `auto_healing_policies` - (Optional) The autohealing policies for this managed instance group. You can specify only one value. Structure is documented below. For more information, see the [official documentation](https://cloud.google.com/compute/docs/instance-groups/creating-groups-of-managed-instances#monitoring_groups). -* `update_policy` - (Optional, [Beta](https://terraform.io/docs/providers/google/provider_versions.html)) The update policy for this managed instance group. Structure is documented below. For more information, see the [official documentation](https://cloud.google.com/compute/docs/instance-groups/updating-managed-instance-groups) and [API](https://cloud.google.com/compute/docs/reference/rest/beta/regionInstanceGroupManagers/patch) +* `update_policy` - (Optional) The update policy for this managed instance group. Structure is documented below. For more information, see the [official documentation](https://cloud.google.com/compute/docs/instance-groups/updating-managed-instance-groups) and [API](https://cloud.google.com/compute/docs/reference/rest/beta/regionInstanceGroupManagers/patch) * `distribution_policy_zones` - (Optional) The distribution policy for this managed instance