terraform-provider-google/google/resource_compute_instance_from_template.go
The Magician 55c7a8b332 Hard remove broken Removed fields, fix 0.12 configs (#3437)
Signed-off-by: Modular Magician <magic-modules@google.com>
2019-04-15 18:14:01 -07:00

237 lines
7.4 KiB
Go

package google
import (
"fmt"
"log"
"github.com/hashicorp/terraform/helper/schema"
strcase "github.com/stoewer/go-strcase"
computeBeta "google.golang.org/api/compute/v0.beta"
compute "google.golang.org/api/compute/v1"
)
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"} {
// The user can set these fields as an override, but doesn't need to -
// the template values will be used if they're unset.
s[field].Required = false
s[field].Optional = true
}
// schema.SchemaConfigModeAttr allows these fields to be removed in Terraform 0.12.
// Passing field_name = [] in this mode differentiates between an intentionally empty
// block vs an ignored computed block.
nic := s["network_interface"].Elem.(*schema.Resource)
nic.Schema["alias_ip_range"].ConfigMode = schema.SchemaConfigModeAttr
nic.Schema["access_config"].ConfigMode = schema.SchemaConfigModeAttr
for _, field := range []string{"attached_disk", "guest_accelerator", "service_account", "scratch_disk"} {
s[field].ConfigMode = schema.SchemaConfigModeAttr
}
// 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{"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
}
tpl, err := ParseInstanceTemplateFieldValue(d.Get("source_instance_template").(string), d, config)
if err != nil {
return err
}
it, err := config.clientComputeBeta.InstanceTemplates.Get(project, tpl.Name).Do()
if err != nil {
return err
}
instance.Disks, err = adjustInstanceFromTemplateDisks(d, config, it, zone, project)
if err != nil {
return err
}
// Force send all top-level fields that have been set in case they're overridden to zero values.
// TODO: consider doing so for nested fields as well.
// Initialize ForceSendFields to empty so we don't get things that the instance resource
// always force-sends.
instance.ForceSendFields = []string{}
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))
}
}
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)
}
// Instances have disks spread across multiple schema properties. This function
// ensures that overriding one of these properties does not override the others.
func adjustInstanceFromTemplateDisks(d *schema.ResourceData, config *Config, it *computeBeta.InstanceTemplate, zone *compute.Zone, project string) ([]*computeBeta.AttachedDisk, error) {
disks := []*computeBeta.AttachedDisk{}
if _, hasBootDisk := d.GetOk("boot_disk"); hasBootDisk {
bootDisk, err := expandBootDisk(d, config, zone, project)
if err != nil {
return nil, err
}
disks = append(disks, bootDisk)
} else {
// boot disk was not overridden, so use the one from the instance template
for _, disk := range it.Properties.Disks {
if disk.Boot {
if dt := disk.InitializeParams.DiskType; dt != "" {
// Instances need a URL for the disk type, but instance templates
// only have the name (since they're global).
disk.InitializeParams.DiskType = fmt.Sprintf("zones/%s/diskTypes/%s", zone.Name, dt)
}
disks = append(disks, disk)
break
}
}
}
if _, hasScratchDisk := d.GetOk("scratch_disk"); hasScratchDisk {
scratchDisks, err := expandScratchDisks(d, config, zone, project)
if err != nil {
return nil, err
}
disks = append(disks, scratchDisks...)
} else {
// scratch disks were not overridden, so use the ones from the instance template
for _, disk := range it.Properties.Disks {
if disk.Type == "SCRATCH" {
disks = append(disks, disk)
}
}
}
attachedDisksCount := d.Get("attached_disk.#").(int)
if attachedDisksCount > 0 {
for i := 0; i < attachedDisksCount; i++ {
diskConfig := d.Get(fmt.Sprintf("attached_disk.%d", i)).(map[string]interface{})
disk, err := expandAttachedDisk(diskConfig, d, config)
if err != nil {
return nil, err
}
disks = append(disks, disk)
}
} else {
// attached disks were not overridden, so use the ones from the instance template
for _, disk := range it.Properties.Disks {
if !disk.Boot && disk.Type != "SCRATCH" {
if s := disk.Source; s != "" {
// Instances need a URL for the disk source, but instance templates
// only have the name (since they're global).
disk.Source = fmt.Sprintf("zones/%s/disks/%s", zone.Name, s)
}
disks = append(disks, disk)
}
}
}
return disks, nil
}