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:
parent
2620a78c49
commit
55742acf12
|
@ -2,6 +2,8 @@ package google
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"google.golang.org/api/compute/v1"
|
"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 {
|
func testAccCheckInstanceGroupManagerDestroy(s *terraform.State) error {
|
||||||
config := testAccProvider.Meta().(*Config)
|
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 {
|
func testAccInstanceGroupManager_basic(template, target, igm1, igm2 string) string {
|
||||||
return fmt.Sprintf(`
|
return fmt.Sprintf(`
|
||||||
resource "google_compute_instance_template" "igm-basic" {
|
resource "google_compute_instance_template" "igm-basic" {
|
||||||
|
@ -380,3 +447,49 @@ func testAccInstanceGroupManager_update2(template1, target, template2, igm strin
|
||||||
}
|
}
|
||||||
}`, template1, target, template2, igm)
|
}`, 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]
|
||||||
|
}
|
||||||
|
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
|
|
||||||
|
"github.com/hashicorp/terraform/helper/resource"
|
||||||
"github.com/hashicorp/terraform/helper/schema"
|
"github.com/hashicorp/terraform/helper/schema"
|
||||||
"google.golang.org/api/compute/v1"
|
"google.golang.org/api/compute/v1"
|
||||||
"google.golang.org/api/googleapi"
|
"google.golang.org/api/googleapi"
|
||||||
|
@ -16,6 +17,38 @@ func resourceComputeInstanceTemplate() *schema.Resource {
|
||||||
Delete: resourceComputeInstanceTemplateDelete,
|
Delete: resourceComputeInstanceTemplateDelete,
|
||||||
|
|
||||||
Schema: map[string]*schema.Schema{
|
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{
|
"disk": &schema.Schema{
|
||||||
Type: schema.TypeList,
|
Type: schema.TypeList,
|
||||||
Required: true,
|
Required: true,
|
||||||
|
@ -98,12 +131,6 @@ func resourceComputeInstanceTemplate() *schema.Resource {
|
||||||
ForceNew: true,
|
ForceNew: true,
|
||||||
},
|
},
|
||||||
|
|
||||||
"name": &schema.Schema{
|
|
||||||
Type: schema.TypeString,
|
|
||||||
Required: true,
|
|
||||||
ForceNew: true,
|
|
||||||
},
|
|
||||||
|
|
||||||
"automatic_restart": &schema.Schema{
|
"automatic_restart": &schema.Schema{
|
||||||
Type: schema.TypeBool,
|
Type: schema.TypeBool,
|
||||||
Optional: true,
|
Optional: true,
|
||||||
|
@ -512,10 +539,18 @@ func resourceComputeInstanceTemplateCreate(d *schema.ResourceData, meta interfac
|
||||||
|
|
||||||
instanceProperties.Tags = resourceInstanceTags(d)
|
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{
|
instanceTemplate := compute.InstanceTemplate{
|
||||||
Description: d.Get("description").(string),
|
Description: d.Get("description").(string),
|
||||||
Properties: instanceProperties,
|
Properties: instanceProperties,
|
||||||
Name: d.Get("name").(string),
|
Name: itName,
|
||||||
}
|
}
|
||||||
|
|
||||||
op, err := config.clientCompute.InstanceTemplates.Insert(
|
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("tags_fingerprint", instanceTemplate.Properties.Tags.Fingerprint)
|
||||||
}
|
}
|
||||||
d.Set("self_link", instanceTemplate.SelfLink)
|
d.Set("self_link", instanceTemplate.SelfLink)
|
||||||
|
d.Set("name", instanceTemplate.Name)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
## Argument Reference
|
||||||
|
|
||||||
Note that changing any field for this resource forces a new resource to be created.
|
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.
|
* `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
|
* `can_ip_forward` - (Optional) Whether to allow sending and receiving of
|
||||||
packets with non-matching source or destination IPs. This defaults to false.
|
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.
|
* `self_link` - The URI of the created resource.
|
||||||
|
|
||||||
* `tags_fingerprint` - The unique fingerprint of the tags.
|
* `tags_fingerprint` - The unique fingerprint of the tags.
|
||||||
|
|
||||||
|
[1]: /docs/providers/google/r/compute_instance_group_manager.html
|
||||||
|
[2]: /docs/configuration/resources.html#lifecycle
|
||||||
|
|
Loading…
Reference in New Issue