2018-06-28 23:09:23 +00:00
|
|
|
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"} {
|
2018-09-20 23:49:48 +00:00
|
|
|
// The user can set these fields as an override, but doesn't need to -
|
|
|
|
// the template values will be used if they're unset.
|
2018-06-28 23:09:23 +00:00
|
|
|
s[field].Required = false
|
2018-09-20 23:49:48 +00:00
|
|
|
s[field].Optional = true
|
2018-06-28 23:09:23 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// 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)
|
|
|
|
}
|