providers/google: support optionial uuid naming for Instance Template (#6604)

Auto-generating an Instance Template name (or just its suffix) allows the
create_before_destroy lifecycle option to function correctly on the
Instance Template resource. This in turn allows Instance Group Managers
to be updated without being destroyed.
This commit is contained in:
Evan Brown 2016-05-11 14:54:47 -07:00 committed by Paul Stack
parent 2620a78c49
commit 55742acf12
3 changed files with 209 additions and 10 deletions

View File

@ -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]
}

View File

@ -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
}

View File

@ -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