diff --git a/builtin/providers/google/resource_compute_instance_group_manager_test.go b/builtin/providers/google/resource_compute_instance_group_manager_test.go index 299bff1acb..610793bc68 100644 --- a/builtin/providers/google/resource_compute_instance_group_manager_test.go +++ b/builtin/providers/google/resource_compute_instance_group_manager_test.go @@ -2,6 +2,8 @@ package google import ( "fmt" + "reflect" + "strings" "testing" "google.golang.org/api/compute/v1" @@ -79,6 +81,37 @@ func TestAccInstanceGroupManager_update(t *testing.T) { }) } +func TestAccInstanceGroupManager_updateLifecycle(t *testing.T) { + var manager compute.InstanceGroupManager + + tag1 := "tag1" + tag2 := "tag2" + 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{ + resource.TestStep{ + Config: testAccInstanceGroupManager_updateLifecycle(tag1, igm), + Check: resource.ComposeTestCheckFunc( + testAccCheckInstanceGroupManagerExists( + "google_compute_instance_group_manager.igm-update", &manager), + ), + }, + resource.TestStep{ + Config: testAccInstanceGroupManager_updateLifecycle(tag2, igm), + Check: resource.ComposeTestCheckFunc( + testAccCheckInstanceGroupManagerExists( + "google_compute_instance_group_manager.igm-update", &manager), + testAccCheckInstanceGroupManagerTemplateTags( + "google_compute_instance_group_manager.igm-update", []string{tag2}), + ), + }, + }, + }) +} func testAccCheckInstanceGroupManagerDestroy(s *terraform.State) error { config := testAccProvider.Meta().(*Config) @@ -201,6 +234,40 @@ func testAccCheckInstanceGroupManagerNamedPorts(n string, np map[string]int64, i } } +func testAccCheckInstanceGroupManagerTemplateTags(n string, tags []string) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[n] + if !ok { + return fmt.Errorf("Not found: %s", n) + } + + if rs.Primary.ID == "" { + return fmt.Errorf("No ID is set") + } + + config := testAccProvider.Meta().(*Config) + + manager, err := config.clientCompute.InstanceGroupManagers.Get( + config.Project, rs.Primary.Attributes["zone"], rs.Primary.ID).Do() + if err != nil { + return err + } + + // check that the instance template updated + instanceTemplate, err := config.clientCompute.InstanceTemplates.Get( + config.Project, resourceSplitter(manager.InstanceTemplate)).Do() + if err != nil { + return fmt.Errorf("Error reading instance template: %s", err) + } + + if !reflect.DeepEqual(instanceTemplate.Properties.Tags.Items, tags) { + return fmt.Errorf("instance template not updated") + } + + return nil + } +} + func testAccInstanceGroupManager_basic(template, target, igm1, igm2 string) string { return fmt.Sprintf(` resource "google_compute_instance_template" "igm-basic" { @@ -380,3 +447,49 @@ func testAccInstanceGroupManager_update2(template1, target, template2, igm strin } }`, template1, target, template2, igm) } + +func testAccInstanceGroupManager_updateLifecycle(tag, igm string) string { + return fmt.Sprintf(` + resource "google_compute_instance_template" "igm-update" { + machine_type = "n1-standard-1" + can_ip_forward = false + tags = ["%s"] + + disk { + source_image = "debian-cloud/debian-7-wheezy-v20160301" + 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-update" { + description = "Terraform test instance group manager" + name = "%s" + instance_template = "${google_compute_instance_template.igm-update.self_link}" + base_instance_name = "igm-update" + zone = "us-central1-c" + target_size = 2 + named_port { + name = "customhttp" + port = 8080 + } + }`, tag, igm) +} + +func resourceSplitter(resource string) string { + splits := strings.Split(resource, "/") + + return splits[len(splits)-1] +} diff --git a/builtin/providers/google/resource_compute_instance_template.go b/builtin/providers/google/resource_compute_instance_template.go index d836b97756..a4b2a35261 100644 --- a/builtin/providers/google/resource_compute_instance_template.go +++ b/builtin/providers/google/resource_compute_instance_template.go @@ -4,6 +4,7 @@ import ( "fmt" "log" + "github.com/hashicorp/terraform/helper/resource" "github.com/hashicorp/terraform/helper/schema" "google.golang.org/api/compute/v1" "google.golang.org/api/googleapi" @@ -16,6 +17,38 @@ func resourceComputeInstanceTemplate() *schema.Resource { Delete: resourceComputeInstanceTemplateDelete, Schema: map[string]*schema.Schema{ + "name": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Computed: true, + ForceNew: true, + ConflictsWith: []string{"name_prefix"}, + ValidateFunc: func(v interface{}, k string) (ws []string, errors []error) { + // https://cloud.google.com/compute/docs/reference/latest/instanceTemplates#resource + value := v.(string) + if len(value) > 63 { + errors = append(errors, fmt.Errorf( + "%q cannot be longer than 63 characters", k)) + } + return + }, + }, + + "name_prefix": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + ForceNew: true, + ValidateFunc: func(v interface{}, k string) (ws []string, errors []error) { + // https://cloud.google.com/compute/docs/reference/latest/instanceTemplates#resource + // uuid is 26 characters, limit the prefix to 37. + value := v.(string) + if len(value) > 37 { + errors = append(errors, fmt.Errorf( + "%q cannot be longer than 37 characters, name is limited to 63", k)) + } + return + }, + }, "disk": &schema.Schema{ Type: schema.TypeList, Required: true, @@ -98,12 +131,6 @@ func resourceComputeInstanceTemplate() *schema.Resource { ForceNew: true, }, - "name": &schema.Schema{ - Type: schema.TypeString, - Required: true, - ForceNew: true, - }, - "automatic_restart": &schema.Schema{ Type: schema.TypeBool, Optional: true, @@ -512,10 +539,18 @@ func resourceComputeInstanceTemplateCreate(d *schema.ResourceData, meta interfac instanceProperties.Tags = resourceInstanceTags(d) + var itName string + if v, ok := d.GetOk("name"); ok { + itName = v.(string) + } else if v, ok := d.GetOk("name_prefix"); ok { + itName = resource.PrefixedUniqueId(v.(string)) + } else { + itName = resource.UniqueId() + } instanceTemplate := compute.InstanceTemplate{ Description: d.Get("description").(string), Properties: instanceProperties, - Name: d.Get("name").(string), + Name: itName, } op, err := config.clientCompute.InstanceTemplates.Insert( @@ -567,7 +602,7 @@ func resourceComputeInstanceTemplateRead(d *schema.ResourceData, meta interface{ d.Set("tags_fingerprint", instanceTemplate.Properties.Tags.Fingerprint) } d.Set("self_link", instanceTemplate.SelfLink) - + d.Set("name", instanceTemplate.Name) return nil } diff --git a/website/source/docs/providers/google/r/compute_instance_template.html.markdown b/website/source/docs/providers/google/r/compute_instance_template.html.markdown index e6bc6e6b5a..be56da80e4 100644 --- a/website/source/docs/providers/google/r/compute_instance_template.html.markdown +++ b/website/source/docs/providers/google/r/compute_instance_template.html.markdown @@ -58,6 +58,51 @@ resource "google_compute_instance_template" "foobar" { } ``` +## Using with Instance Group Manager + +Instance Templates cannot be updated after creation with the Google +Cloud Platform API. In order to update an Instance Template, Terraform will +destroy the existing resource and create a replacement. In order to effectively +use an Instance Template resource with an [Instance Group Manager resource][1], +it's recommended to specify `create_before_destroy` in a [lifecycle][2] block. +Either omit the Instance Template `name` attribute, or specify a partial name +with `name_prefix`. Example: + +``` +resource "google_compute_instance_template" "instance_template" { + name_prefix = "instance-template-" + machine_type = "n1-standard-1" + region = "us-central1" + + // boot disk + disk { + ... + } + + // networking + network_interface { + ... + } + + lifecycle { + create_before_destroy = true + } +} + +resource "google_compute_instance_group_manager" "instance_group_manager" { + name = "instance-group-manager" + instance_template = "${google_compute_instance_template.instance_template.self_link}" + base_instance_name = "instance-group-manager" + zone = "us-central1-f" + target_size = "1" +} +``` + +With this setup Terraform generates a unique name for your Instance +Template and can then update the Instance Group manager without conflict before +destroying the previous Instance Template. + + ## Argument Reference Note that changing any field for this resource forces a new resource to be created. @@ -70,9 +115,12 @@ The following arguments are supported: * `machine_type` - (Required) The machine type to create. -* `name` - (Required) A unique name for the resource, required by GCE. - - - - +* `name` - (Optional) The name of the instance template. If you leave + this blank, Terraform will auto-generate a unique name. + +* `name_prefix` - (Optional) Creates a unique name beginning with the specified + prefix. Conflicts with `name`. * `can_ip_forward` - (Optional) Whether to allow sending and receiving of packets with non-matching source or destination IPs. This defaults to false. @@ -191,3 +239,6 @@ exported: * `self_link` - The URI of the created resource. * `tags_fingerprint` - The unique fingerprint of the tags. + +[1]: /docs/providers/google/r/compute_instance_group_manager.html +[2]: /docs/configuration/resources.html#lifecycle