add new compute_instance_from_template resource (#1652)

This was done as its own resource as suggested in slack, since we don't have the option of making all fields Computed in google_compute_instance. There's precedent in the aws provider for this sort of thing (see ami_copy, ami_from_instance).

When I started working on this I assumed I could do it in the compute_instance resource and so I went ahead and reordered the schema to make it easier to work with in the future. Now it's not quite relevant, but I left it in as its own commit that can be looked at separately from the other changes.

Fixes #1582.
This commit is contained in:
Dana Hoffman 2018-06-28 16:09:23 -07:00 committed by GitHub
parent cf4451177e
commit 7e04cee958
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 833 additions and 270 deletions

View File

@ -62,6 +62,10 @@ func ParseInstanceGroupFieldValue(instanceGroup string, d TerraformResourceData,
return parseZonalFieldValue("instanceGroups", instanceGroup, "project", "zone", d, config, false)
}
func ParseInstanceTemplateFieldValue(instanceTemplate string, d TerraformResourceData, config *Config) (*GlobalFieldValue, error) {
return parseGlobalFieldValue("instanceTemplates", instanceTemplate, "project", d, config, false)
}
func ParseSecurityPolicyFieldValue(securityPolicy string, d TerraformResourceData, config *Config) (*GlobalFieldValue, error) {
return parseGlobalFieldValue("securityPolicies", securityPolicy, "project", d, config, true)
}

View File

@ -120,6 +120,7 @@ func Provider() terraform.ResourceProvider {
"google_compute_health_check": resourceComputeHealthCheck(),
"google_compute_image": resourceComputeImage(),
"google_compute_instance": resourceComputeInstance(),
"google_compute_instance_from_template": resourceComputeInstanceFromTemplate(),
"google_compute_instance_group": resourceComputeInstanceGroup(),
"google_compute_instance_group_manager": resourceComputeInstanceGroupManager(),
"google_compute_instance_template": resourceComputeInstanceTemplate(),

View File

@ -122,126 +122,6 @@ func resourceComputeInstance() *schema.Resource {
},
},
"scratch_disk": &schema.Schema{
Type: schema.TypeList,
Optional: true,
ForceNew: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"interface": &schema.Schema{
Type: schema.TypeString,
Optional: true,
Default: "SCSI",
ValidateFunc: validation.StringInSlice([]string{"SCSI", "NVME"}, false),
},
},
},
},
"disk": &schema.Schema{
Type: schema.TypeList,
Optional: true,
ForceNew: true,
Removed: "Use boot_disk, scratch_disk, and attached_disk instead",
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
// TODO(mitchellh): one of image or disk is required
"disk": &schema.Schema{
Type: schema.TypeString,
Optional: true,
ForceNew: true,
},
"image": &schema.Schema{
Type: schema.TypeString,
Optional: true,
ForceNew: true,
},
"type": &schema.Schema{
Type: schema.TypeString,
Optional: true,
ForceNew: true,
},
"scratch": &schema.Schema{
Type: schema.TypeBool,
Optional: true,
ForceNew: true,
},
"auto_delete": &schema.Schema{
Type: schema.TypeBool,
Optional: true,
Default: true,
ForceNew: true,
},
"size": &schema.Schema{
Type: schema.TypeInt,
Optional: true,
ForceNew: true,
},
"device_name": &schema.Schema{
Type: schema.TypeString,
Optional: true,
},
"disk_encryption_key_raw": &schema.Schema{
Type: schema.TypeString,
Optional: true,
ForceNew: true,
Sensitive: true,
},
"disk_encryption_key_sha256": &schema.Schema{
Type: schema.TypeString,
Computed: true,
},
},
},
},
"attached_disk": &schema.Schema{
Type: schema.TypeList,
Optional: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"source": &schema.Schema{
Type: schema.TypeString,
Required: true,
DiffSuppressFunc: linkDiffSuppress,
},
"device_name": &schema.Schema{
Type: schema.TypeString,
Optional: true,
Computed: true,
},
"mode": &schema.Schema{
Type: schema.TypeString,
Optional: true,
Default: "READ_WRITE",
ValidateFunc: validation.StringInSlice([]string{"READ_WRITE", "READ_ONLY"}, false),
},
"disk_encryption_key_raw": &schema.Schema{
Type: schema.TypeString,
Optional: true,
Sensitive: true,
},
"disk_encryption_key_sha256": &schema.Schema{
Type: schema.TypeString,
Computed: true,
},
},
},
},
"machine_type": &schema.Schema{
Type: schema.TypeString,
Required: true,
@ -253,48 +133,6 @@ func resourceComputeInstance() *schema.Resource {
ForceNew: true,
},
"instance_id": &schema.Schema{
Type: schema.TypeString,
Computed: true,
},
"zone": &schema.Schema{
Type: schema.TypeString,
Optional: true,
Computed: true,
ForceNew: true,
},
"can_ip_forward": &schema.Schema{
Type: schema.TypeBool,
Optional: true,
Default: false,
ForceNew: true,
},
"description": &schema.Schema{
Type: schema.TypeString,
Optional: true,
ForceNew: true,
},
"metadata": &schema.Schema{
Type: schema.TypeMap,
Optional: true,
Elem: schema.TypeString,
},
"metadata_startup_script": &schema.Schema{
Type: schema.TypeString,
Optional: true,
ForceNew: true,
},
"metadata_fingerprint": &schema.Schema{
Type: schema.TypeString,
Computed: true,
},
"network_interface": &schema.Schema{
Type: schema.TypeList,
Required: true,
@ -400,6 +238,187 @@ func resourceComputeInstance() *schema.Resource {
},
},
"allow_stopping_for_update": &schema.Schema{
Type: schema.TypeBool,
Optional: true,
},
"attached_disk": &schema.Schema{
Type: schema.TypeList,
Optional: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"source": &schema.Schema{
Type: schema.TypeString,
Required: true,
DiffSuppressFunc: linkDiffSuppress,
},
"device_name": &schema.Schema{
Type: schema.TypeString,
Optional: true,
Computed: true,
},
"mode": &schema.Schema{
Type: schema.TypeString,
Optional: true,
Default: "READ_WRITE",
ValidateFunc: validation.StringInSlice([]string{"READ_WRITE", "READ_ONLY"}, false),
},
"disk_encryption_key_raw": &schema.Schema{
Type: schema.TypeString,
Optional: true,
Sensitive: true,
},
"disk_encryption_key_sha256": &schema.Schema{
Type: schema.TypeString,
Computed: true,
},
},
},
},
"can_ip_forward": &schema.Schema{
Type: schema.TypeBool,
Optional: true,
Default: false,
ForceNew: true,
},
"create_timeout": &schema.Schema{
Type: schema.TypeInt,
Optional: true,
Default: 4,
Deprecated: "Use timeouts block instead.",
},
"description": &schema.Schema{
Type: schema.TypeString,
Optional: true,
ForceNew: true,
},
"deletion_protection": &schema.Schema{
Type: schema.TypeBool,
Optional: true,
Default: false,
},
"disk": &schema.Schema{
Type: schema.TypeList,
Optional: true,
ForceNew: true,
Removed: "Use boot_disk, scratch_disk, and attached_disk instead",
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
// TODO(mitchellh): one of image or disk is required
"disk": &schema.Schema{
Type: schema.TypeString,
Optional: true,
ForceNew: true,
},
"image": &schema.Schema{
Type: schema.TypeString,
Optional: true,
ForceNew: true,
},
"type": &schema.Schema{
Type: schema.TypeString,
Optional: true,
ForceNew: true,
},
"scratch": &schema.Schema{
Type: schema.TypeBool,
Optional: true,
ForceNew: true,
},
"auto_delete": &schema.Schema{
Type: schema.TypeBool,
Optional: true,
Default: true,
ForceNew: true,
},
"size": &schema.Schema{
Type: schema.TypeInt,
Optional: true,
ForceNew: true,
},
"device_name": &schema.Schema{
Type: schema.TypeString,
Optional: true,
},
"disk_encryption_key_raw": &schema.Schema{
Type: schema.TypeString,
Optional: true,
ForceNew: true,
Sensitive: true,
},
"disk_encryption_key_sha256": &schema.Schema{
Type: schema.TypeString,
Computed: true,
},
},
},
},
"guest_accelerator": &schema.Schema{
Type: schema.TypeList,
Optional: true,
Computed: true,
ForceNew: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"count": &schema.Schema{
Type: schema.TypeInt,
Required: true,
ForceNew: true,
},
"type": &schema.Schema{
Type: schema.TypeString,
Required: true,
ForceNew: true,
DiffSuppressFunc: linkDiffSuppress,
},
},
},
},
"labels": &schema.Schema{
Type: schema.TypeMap,
Optional: true,
Elem: &schema.Schema{Type: schema.TypeString},
Set: schema.HashString,
},
"metadata": &schema.Schema{
Type: schema.TypeMap,
Optional: true,
Elem: schema.TypeString,
},
"metadata_startup_script": &schema.Schema{
Type: schema.TypeString,
Optional: true,
ForceNew: true,
},
"min_cpu_platform": &schema.Schema{
Type: schema.TypeString,
Optional: true,
},
"network": &schema.Schema{
Type: schema.TypeList,
Optional: true,
@ -444,11 +463,6 @@ func resourceComputeInstance() *schema.Resource {
ForceNew: true,
},
"self_link": &schema.Schema{
Type: schema.TypeString,
Computed: true,
},
"scheduling": &schema.Schema{
Type: schema.TypeList,
MaxItems: 1,
@ -478,6 +492,22 @@ func resourceComputeInstance() *schema.Resource {
},
},
"scratch_disk": &schema.Schema{
Type: schema.TypeList,
Optional: true,
ForceNew: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"interface": &schema.Schema{
Type: schema.TypeString,
Optional: true,
Default: "SCSI",
ValidateFunc: validation.StringInSlice([]string{"SCSI", "NVME"}, false),
},
},
},
},
"service_account": &schema.Schema{
Type: schema.TypeList,
MaxItems: 1,
@ -505,38 +535,6 @@ func resourceComputeInstance() *schema.Resource {
},
},
"guest_accelerator": &schema.Schema{
Type: schema.TypeList,
Optional: true,
Computed: true,
ForceNew: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"count": &schema.Schema{
Type: schema.TypeInt,
Required: true,
ForceNew: true,
},
"type": &schema.Schema{
Type: schema.TypeString,
Required: true,
ForceNew: true,
DiffSuppressFunc: linkDiffSuppress,
},
},
},
},
"cpu_platform": &schema.Schema{
Type: schema.TypeString,
Computed: true,
},
"min_cpu_platform": &schema.Schema{
Type: schema.TypeString,
Optional: true,
},
"tags": &schema.Schema{
Type: schema.TypeSet,
Optional: true,
@ -544,27 +542,21 @@ func resourceComputeInstance() *schema.Resource {
Set: schema.HashString,
},
"tags_fingerprint": &schema.Schema{
"zone": &schema.Schema{
Type: schema.TypeString,
Optional: true,
Computed: true,
ForceNew: true,
},
"cpu_platform": &schema.Schema{
Type: schema.TypeString,
Computed: true,
},
"labels": &schema.Schema{
Type: schema.TypeMap,
Optional: true,
Elem: &schema.Schema{Type: schema.TypeString},
Set: schema.HashString,
},
"allow_stopping_for_update": &schema.Schema{
Type: schema.TypeBool,
Optional: true,
},
"deletion_protection": &schema.Schema{
Type: schema.TypeBool,
Optional: true,
Default: false,
"instance_id": &schema.Schema{
Type: schema.TypeString,
Computed: true,
},
"label_fingerprint": &schema.Schema{
@ -572,11 +564,19 @@ func resourceComputeInstance() *schema.Resource {
Computed: true,
},
"create_timeout": &schema.Schema{
Type: schema.TypeInt,
Optional: true,
Default: 4,
Deprecated: "Use timeouts block instead.",
"metadata_fingerprint": &schema.Schema{
Type: schema.TypeString,
Computed: true,
},
"self_link": &schema.Schema{
Type: schema.TypeString,
Computed: true,
},
"tags_fingerprint": &schema.Schema{
Type: schema.TypeString,
Computed: true,
},
},
CustomizeDiff: customdiff.All(
@ -620,50 +620,36 @@ func getDisk(diskUri string, d *schema.ResourceData, config *Config) (*compute.D
return disk, err
}
func resourceComputeInstanceCreate(d *schema.ResourceData, meta interface{}) error {
config := meta.(*Config)
project, err := getProject(d, config)
if err != nil {
return err
}
// Get the zone
z, err := getZone(d, config)
if err != nil {
return err
}
log.Printf("[DEBUG] Loading zone: %s", z)
zone, err := config.clientCompute.Zones.Get(
project, z).Do()
if err != nil {
return fmt.Errorf(
"Error loading zone '%s': %s", z, err)
}
func expandComputeInstance(project string, zone *compute.Zone, d *schema.ResourceData, config *Config) (*computeBeta.Instance, error) {
// Get the machine type
log.Printf("[DEBUG] Loading machine type: %s", d.Get("machine_type").(string))
machineType, err := config.clientCompute.MachineTypes.Get(
project, zone.Name, d.Get("machine_type").(string)).Do()
if err != nil {
return fmt.Errorf(
"Error loading machine type: %s",
err)
var machineTypeUrl string
if mt, ok := d.GetOk("machine_type"); ok {
log.Printf("[DEBUG] Loading machine type: %s", mt.(string))
machineType, err := config.clientCompute.MachineTypes.Get(
project, zone.Name, mt.(string)).Do()
if err != nil {
return nil, fmt.Errorf(
"Error loading machine type: %s",
err)
}
machineTypeUrl = machineType.SelfLink
}
// Build up the list of disks
disks := []*computeBeta.AttachedDisk{}
bootDisk, err := expandBootDisk(d, config, zone, project)
if err != nil {
return err
if _, hasBootDisk := d.GetOk("boot_disk"); hasBootDisk {
bootDisk, err := expandBootDisk(d, config, zone, project)
if err != nil {
return nil, err
}
disks = append(disks, bootDisk)
}
disks = append(disks, bootDisk)
if _, hasScratchDisk := d.GetOk("scratch_disk"); hasScratchDisk {
scratchDisks, err := expandScratchDisks(d, config, zone, project)
if err != nil {
return err
return nil, err
}
disks = append(disks, scratchDisks...)
}
@ -674,7 +660,7 @@ func resourceComputeInstanceCreate(d *schema.ResourceData, meta interface{}) err
diskConfig := d.Get(fmt.Sprintf("attached_disk.%d", i)).(map[string]interface{})
disk, err := expandAttachedDisk(diskConfig, d, config)
if err != nil {
return err
return nil, err
}
disks = append(disks, disk)
@ -696,34 +682,27 @@ func resourceComputeInstanceCreate(d *schema.ResourceData, meta interface{}) err
}
scheduling.ForceSendFields = []string{"AutomaticRestart", "Preemptible"}
// Read create timeout
// Until "create_timeout" is removed, use that timeout if set.
createTimeout := int(d.Timeout(schema.TimeoutCreate).Minutes())
if v, ok := d.GetOk("create_timeout"); ok && v != 4 {
createTimeout = v.(int)
}
metadata, err := resourceInstanceMetadata(d)
if err != nil {
return fmt.Errorf("Error creating metadata: %s", err)
return nil, fmt.Errorf("Error creating metadata: %s", err)
}
networkInterfaces, err := expandNetworkInterfaces(d, config)
if err != nil {
return fmt.Errorf("Error creating network interfaces: %s", err)
return nil, fmt.Errorf("Error creating network interfaces: %s", err)
}
accels, err := expandInstanceGuestAccelerators(d, config)
if err != nil {
return fmt.Errorf("Error creating guest accelerators: %s", err)
return nil, fmt.Errorf("Error creating guest accelerators: %s", err)
}
// Create the instance information
instance := &computeBeta.Instance{
return &computeBeta.Instance{
CanIpForward: d.Get("can_ip_forward").(bool),
Description: d.Get("description").(string),
Disks: disks,
MachineType: machineType.SelfLink,
MachineType: machineTypeUrl,
Metadata: metadata,
Name: d.Get("name").(string),
NetworkInterfaces: networkInterfaces,
@ -734,6 +713,40 @@ func resourceComputeInstanceCreate(d *schema.ResourceData, meta interface{}) err
MinCpuPlatform: d.Get("min_cpu_platform").(string),
Scheduling: scheduling,
DeletionProtection: d.Get("deletion_protection").(bool),
ForceSendFields: []string{"CanIpForward", "DeletionProtection"},
}, nil
}
func resourceComputeInstanceCreate(d *schema.ResourceData, meta interface{}) error {
config := meta.(*Config)
project, err := getProject(d, config)
if err != nil {
return err
}
// Get the zone
z, err := getZone(d, config)
if err != nil {
return err
}
log.Printf("[DEBUG] Loading zone: %s", z)
zone, err := config.clientCompute.Zones.Get(
project, z).Do()
if err != nil {
return fmt.Errorf("Error loading zone '%s': %s", z, err)
}
instance, err := expandComputeInstance(project, zone, d, config)
if err != nil {
return err
}
// Read create timeout
// Until "create_timeout" is removed, use that timeout if set.
createTimeout := int(d.Timeout(schema.TimeoutCreate).Minutes())
if v, ok := d.GetOk("create_timeout"); ok && v != 4 {
createTimeout = v.(int)
}
log.Printf("[INFO] Requesting instance creation")
@ -804,7 +817,9 @@ func resourceComputeInstanceRead(d *schema.ResourceData, meta interface{}) error
if err != nil {
return err
}
d.Set("network_interface", networkInterfaces)
if err := d.Set("network_interface", networkInterfaces); err != nil {
return err
}
// Fall back on internal ip if there is no external ip. This makes sense in the situation where
// terraform is being used on a cloud instance and can therefore access the instances it creates
@ -831,8 +846,8 @@ func resourceComputeInstanceRead(d *schema.ResourceData, meta interface{}) error
d.Set("tags", convertStringArrToInterface(instance.Tags.Items))
}
if len(instance.Labels) > 0 {
d.Set("labels", instance.Labels)
if err := d.Set("labels", instance.Labels); err != nil {
return err
}
if instance.LabelFingerprint != "" {

View File

@ -0,0 +1,139 @@
package google
import (
"fmt"
"log"
"github.com/hashicorp/terraform/helper/schema"
strcase "github.com/stoewer/go-strcase"
)
func resourceComputeInstanceFromTemplate() *schema.Resource {
return &schema.Resource{
Create: resourceComputeInstanceFromTemplateCreate,
Read: resourceComputeInstanceRead,
Update: resourceComputeInstanceUpdate,
Delete: resourceComputeInstanceDelete,
// Import doesn't really make sense, because you could just import
// as a google_compute_instance.
Timeouts: resourceComputeInstance().Timeouts,
Schema: computeInstanceFromTemplateSchema(),
CustomizeDiff: resourceComputeInstance().CustomizeDiff,
}
}
func computeInstanceFromTemplateSchema() map[string]*schema.Schema {
s := resourceComputeInstance().Schema
for _, field := range []string{"boot_disk", "machine_type", "network_interface"} {
s[field].Required = false
}
// Remove deprecated/removed fields that are never d.Set. We can't
// programatically remove all of them, because some of them still have d.Set
// calls.
for _, field := range []string{"create_timeout", "disk", "network"} {
delete(s, field)
}
recurseOnSchema(s, func(field *schema.Schema) {
// We don't want to accidentally use default values to override the instance
// template, so remove defaults.
field.Default = nil
// Make non-required fields computed since they'll be set by the template.
// Leave deprecated and removed fields alone because we don't set them.
if !field.Required && !(field.Deprecated != "" || field.Removed != "") {
field.Computed = true
}
})
s["source_instance_template"] = &schema.Schema{
Type: schema.TypeString,
Required: true,
ForceNew: true,
}
return s
}
func recurseOnSchema(s map[string]*schema.Schema, f func(*schema.Schema)) {
for _, field := range s {
f(field)
if e := field.Elem; e != nil {
if r, ok := e.(*schema.Resource); ok {
recurseOnSchema(r.Schema, f)
}
}
}
}
func resourceComputeInstanceFromTemplateCreate(d *schema.ResourceData, meta interface{}) error {
config := meta.(*Config)
project, err := getProject(d, config)
if err != nil {
return err
}
// Get the zone
z, err := getZone(d, config)
if err != nil {
return err
}
log.Printf("[DEBUG] Loading zone: %s", z)
zone, err := config.clientCompute.Zones.Get(project, z).Do()
if err != nil {
return fmt.Errorf("Error loading zone '%s': %s", z, err)
}
instance, err := expandComputeInstance(project, zone, d, config)
if err != nil {
return err
}
// Force send all top-level fields in case they're overridden to zero values.
// TODO: consider doing so for nested fields as well.
for f, s := range computeInstanceFromTemplateSchema() {
// It seems that GetOkExists always returns true for sets.
// TODO: confirm this and file issue against Terraform core.
// In the meantime, don't force send sets.
if s.Type == schema.TypeSet {
continue
}
if _, exists := d.GetOkExists(f); exists {
// Assume for now that all fields are exact snake_case versions of the API fields.
// This won't necessarily always be true, but it serves as a good approximation and
// can be adjusted later as we discover issues.
instance.ForceSendFields = append(instance.ForceSendFields, strcase.UpperCamelCase(f))
}
}
tpl, err := ParseInstanceTemplateFieldValue(d.Get("source_instance_template").(string), d, config)
if err != nil {
return err
}
log.Printf("[INFO] Requesting instance creation")
op, err := config.clientComputeBeta.Instances.Insert(project, zone.Name, instance).SourceInstanceTemplate(tpl.RelativeLink()).Do()
if err != nil {
return fmt.Errorf("Error creating instance: %s", err)
}
// Store the ID now
d.SetId(instance.Name)
// Wait for the operation to complete
waitErr := computeSharedOperationWaitTime(config.clientCompute, op, project, int(d.Timeout(schema.TimeoutCreate).Minutes()), "instance to create")
if waitErr != nil {
// The resource didn't actually create
d.SetId("")
return waitErr
}
return resourceComputeInstanceRead(d, meta)
}

View File

@ -0,0 +1,109 @@
package google
import (
"fmt"
"testing"
"github.com/hashicorp/terraform/helper/acctest"
"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/terraform"
compute "google.golang.org/api/compute/v1"
)
func TestAccComputeInstanceFromTemplate_basic(t *testing.T) {
t.Parallel()
var instance compute.Instance
instanceName := fmt.Sprintf("terraform-test-%s", acctest.RandString(10))
templateName := fmt.Sprintf("terraform-test-%s", acctest.RandString(10))
resourceName := "google_compute_instance_from_template.foobar"
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckComputeInstanceFromTemplateDestroy,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccComputeInstanceFromTemplate_basic(instanceName, templateName),
Check: resource.ComposeTestCheckFunc(
testAccCheckComputeInstanceExists(resourceName, &instance),
// Check that fields were set based on the template
resource.TestCheckResourceAttr(resourceName, "machine_type", "n1-standard-1"),
resource.TestCheckResourceAttr(resourceName, "attached_disk.#", "1"),
),
},
},
})
}
func testAccCheckComputeInstanceFromTemplateDestroy(s *terraform.State) error {
config := testAccProvider.Meta().(*Config)
for _, rs := range s.RootModule().Resources {
if rs.Type != "google_compute_instance_from_template" {
continue
}
_, err := config.clientCompute.Instances.Get(
config.Project, rs.Primary.Attributes["zone"], rs.Primary.ID).Do()
if err == nil {
return fmt.Errorf("Instance still exists")
}
}
return nil
}
func testAccComputeInstanceFromTemplate_basic(instance, template string) string {
return fmt.Sprintf(`
resource "google_compute_disk" "foobar" {
name = "%s"
image = "debian-8-jessie-v20160803"
size = 10
type = "pd-ssd"
zone = "us-central1-a"
}
resource "google_compute_instance_template" "foobar" {
name = "%s"
machine_type = "n1-standard-1"
disk {
source_image = "debian-cloud/debian-8"
auto_delete = true
disk_size_gb = 100
boot = true
}
disk {
source = "${google_compute_disk.foobar.name}"
auto_delete = false
boot = false
}
network_interface {
network = "default"
}
metadata {
foo = "bar"
}
can_ip_forward = true
}
resource "google_compute_instance_from_template" "foobar" {
name = "%s"
zone = "us-central1-a"
source_instance_template = "${google_compute_instance_template.foobar.self_link}"
// Overrides
can_ip_forward = false
labels {
my_key = "my_value"
}
}
`, template, template, instance)
}

21
vendor/github.com/stoewer/go-strcase/LICENSE generated vendored Normal file
View File

@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2017, Adrian Stoewer <adrian.stoewer@rz.ifi.lmu.de>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

30
vendor/github.com/stoewer/go-strcase/README.md generated vendored Normal file
View File

@ -0,0 +1,30 @@
[![Build Status](https://travis-ci.org/stoewer/go-strcase.svg?branch=master)](https://travis-ci.org/stoewer/go-strcase)
[![Coverage Status](https://coveralls.io/repos/github/stoewer/go-strcase/badge.svg?branch=master)](https://coveralls.io/github/stoewer/go-strcase?branch=master)
[![GoDoc](https://godoc.org/github.com/stoewer/go-strcase?status.svg)](https://godoc.org/github.com/stoewer/go-strcase)
---
# Go strcase
The package `strcase` converts between different kinds of naming formats such as camel case
(`CamelCase`), snake case (`snake_case`) or kebab case (`kebab-case`).
The package is designed to work only with strings consisting of standard ASCII letters.
Unicode is currently not supported.
## Versioning and stability
Although the master branch is supposed to remain always backward compatible, the repository
contains version tags in order to support vendoring tools such as `glide`.
The tag names follow semantic versioning conventions and have the following format `v1.0.0`.
## Install and use
```sh
go get -u github.com/stoewer/go-strcase
```
```go
import "github.com/stoewer/go-strcase"
var snake = strcase.SnakeCase("CamelCase")
```

37
vendor/github.com/stoewer/go-strcase/camel.go generated vendored Normal file
View File

@ -0,0 +1,37 @@
// Copyright (c) 2017, A. Stoewer <adrian.stoewer@rz.ifi.lmu.de>
// All rights reserved.
package strcase
import (
"strings"
)
// UpperCamelCase converts a string into camel case starting with a upper case letter.
func UpperCamelCase(s string) string {
return camelCase(s, true)
}
// LowerCamelCase converts a string into camel case starting with a lower case letter.
func LowerCamelCase(s string) string {
return camelCase(s, false)
}
func camelCase(s string, upper bool) string {
s = strings.TrimSpace(s)
buffer := make([]rune, 0, len(s))
var prev rune
for _, curr := range s {
if !isDelimiter(curr) {
if isDelimiter(prev) || (upper && prev == 0) {
buffer = append(buffer, toUpper(curr))
} else {
buffer = append(buffer, toLower(curr))
}
}
prev = curr
}
return string(buffer)
}

8
vendor/github.com/stoewer/go-strcase/doc.go generated vendored Normal file
View File

@ -0,0 +1,8 @@
// Copyright (c) 2017, A. Stoewer <adrian.stoewer@rz.ifi.lmu.de>
// All rights reserved.
// Package strcase converts between different kinds of naming formats such as camel case
// (CamelCase), snake case (snake_case) or kebab case (`kebab-case`). The package is designed
// to work only with strings consisting of standard ASCII letters. Unicode is currently not
// supported.
package strcase

44
vendor/github.com/stoewer/go-strcase/helper.go generated vendored Normal file
View File

@ -0,0 +1,44 @@
// Copyright (c) 2017, A. Stoewer <adrian.stoewer@rz.ifi.lmu.de>
// All rights reserved.
package strcase
// isLower checks if a character is lower case. More precisely it evaluates if it is
// in the range of ASCII character 'a' to 'z'.
func isLower(ch rune) bool {
return ch >= 'a' && ch <= 'z'
}
// toLower converts a character in the range of ASCII characters 'A' to 'Z' to its lower
// case counterpart. Other characters remain the same.
func toLower(ch rune) rune {
if ch >= 'A' && ch <= 'Z' {
return ch + 32
}
return ch
}
// isLower checks if a character is upper case. More precisely it evaluates if it is
// in the range of ASCII characters 'A' to 'Z'.
func isUpper(ch rune) bool {
return ch >= 'A' && ch <= 'Z'
}
// toLower converts a character in the range of ASCII characters 'a' to 'z' to its lower
// case counterpart. Other characters remain the same.
func toUpper(ch rune) rune {
if ch >= 'a' && ch <= 'z' {
return ch - 32
}
return ch
}
// isSpace checks if a character is some kind of whitespace.
func isSpace(ch rune) bool {
return ch == ' ' || ch == '\t' || ch == '\n' || ch == '\r'
}
// isDelimiter checks if a character is some kind of whitespace or '_' or '-'.
func isDelimiter(ch rune) bool {
return ch == '-' || ch == '_' || isSpace(ch)
}

9
vendor/github.com/stoewer/go-strcase/kebab.go generated vendored Normal file
View File

@ -0,0 +1,9 @@
// Copyright (c) 2017, A. Stoewer <adrian.stoewer@rz.ifi.lmu.de>
// All rights reserved.
package strcase
// KebabCase converts a string into kebab case.
func KebabCase(s string) string {
return lowerDelimiterCase(s, '-')
}

48
vendor/github.com/stoewer/go-strcase/snake.go generated vendored Normal file
View File

@ -0,0 +1,48 @@
// Copyright (c) 2017, A. Stoewer <adrian.stoewer@rz.ifi.lmu.de>
// All rights reserved.
package strcase
import (
"strings"
)
// SnakeCase converts a string into snake case.
func SnakeCase(s string) string {
return lowerDelimiterCase(s, '_')
}
// lowerDelimiterCase converts a string into snake_case or kebab-case depending on
// the delimiter passed in as second argument.
func lowerDelimiterCase(s string, delimiter rune) string {
s = strings.TrimSpace(s)
buffer := make([]rune, 0, len(s)+3)
var prev rune
var curr rune
for _, next := range s {
if isDelimiter(curr) {
if !isDelimiter(prev) {
buffer = append(buffer, delimiter)
}
} else if isUpper(curr) {
if isLower(prev) || (isUpper(prev) && isLower(next)) {
buffer = append(buffer, delimiter)
}
buffer = append(buffer, toLower(curr))
} else if curr != 0 {
buffer = append(buffer, curr)
}
prev = curr
curr = next
}
if len(s) > 0 {
if isUpper(curr) && isLower(prev) && prev != 0 {
buffer = append(buffer, delimiter)
}
buffer = append(buffer, toLower(curr))
}
return string(buffer)
}

6
vendor/vendor.json vendored
View File

@ -974,6 +974,12 @@
"revision": "b061729afc07e77a8aa4fad0a2fd840958f1942a",
"revisionTime": "2016-09-27T10:08:44Z"
},
{
"checksumSHA1": "t/Hcc8jNXkH58QfnotLNtpLh+qc=",
"path": "github.com/stoewer/go-strcase",
"revision": "c8136b55823dc6af966d084a06056c5575f6400f",
"revisionTime": "2017-04-24T18:08:47Z"
},
{
"checksumSHA1": "qgMa75aMGbkFY0jIqqqgVnCUoNA=",
"path": "github.com/ulikunitz/xz",

View File

@ -0,0 +1,88 @@
---
layout: "google"
page_title: "Google: google_compute_instance_from_template"
sidebar_current: "docs-google-compute-instance-from-template"
description: |-
Manages a VM instance resource within GCE.
---
# google\_compute\_instance\_from\_template
Manages a VM instance resource within GCE. For more information see
[the official documentation](https://cloud.google.com/compute/docs/instances)
and
[API](https://cloud.google.com/compute/docs/reference/latest/instances).
This resource is specifically to create a compute instance from a given
`source_instance_template`. To create an instance without a template, use the
`google_compute_instance` resource.
## Example Usage
```hcl
resource "google_compute_instance_template" "tpl" {
name = "template"
machine_type = "n1-standard-1"
disk {
source_image = "debian-cloud/debian-8"
auto_delete = true
disk_size_gb = 100
boot = true
}
network_interface {
network = "default"
}
metadata {
foo = "bar"
}
can_ip_forward = true
}
resource "google_compute_instance_from_template" "tpl" {
name = "instance-from-template"
zone = "us-central1-a"
source_instance_template = "${google_compute_instance_template.tpl.self_link}"
// Override fields from instance template
can_ip_forward = false
labels {
my_key = "my_value"
}
}
```
## Argument Reference
The following arguments are supported:
* `name` - (Required) A unique name for the resource, required by GCE.
Changing this forces a new resource to be created.
* `source_instance_template` - (Required) Name or self link of an instance
template to create the instance based on.
- - -
* `zone` - (Optional) The zone that the machine should be created in. If not
set, the provider zone is used.
In addition to these, all arguments from `google_compute_instance` are supported
as a way to override the properties in the template. All exported attributes
from `google_compute_instance` are likewise exported here.
## Timeouts
This resource provides the following
[Timeouts](/docs/configuration/resources.html#timeouts) configuration options:
- `create` - Default is 6 minutes.
- `update` - Default is 6 minutes.
- `delete` - Default is 6 minutes.

View File

@ -299,6 +299,10 @@
<a href="/docs/providers/google/r/compute_instance.html">google_compute_instance</a>
</li>
<li<%= sidebar_current("docs-google-compute-instance-from-template") %>>
<a href="/docs/providers/google/r/compute_instance_from_template.html">google_compute_instance_from_template</a>
</li>
<li<%= sidebar_current("docs-google-compute-instance-group-x") %>>
<a href="/docs/providers/google/r/compute_instance_group.html">google_compute_instance_group</a>
</li>