terraform-provider-google/google/resource_compute_instance_template.go
Dana Hoffman 7596164ad1
Deprecate name_prefix (#1035)
* deprecate name_prefix

* make name_prefix computed and add migration instructions
2018-04-02 10:28:59 -07:00

815 lines
23 KiB
Go

package google
import (
"fmt"
"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/helper/schema"
computeBeta "google.golang.org/api/compute/v0.beta"
"google.golang.org/api/compute/v1"
"google.golang.org/api/googleapi"
)
var InstanceTemplateBaseApiVersion = v1
var InstanceTemplateVersionedFeatures = []Feature{}
func resourceComputeInstanceTemplate() *schema.Resource {
return &schema.Resource{
Create: resourceComputeInstanceTemplateCreate,
Read: resourceComputeInstanceTemplateRead,
Delete: resourceComputeInstanceTemplateDelete,
Importer: &schema.ResourceImporter{
State: schema.ImportStatePassthrough,
},
SchemaVersion: 1,
MigrateState: resourceComputeInstanceTemplateMigrateState,
// A compute instance template is more or less a subset of a compute
// instance. Please attempt to maintain consistency with the
// resource_compute_instance schema when updating this one.
Schema: map[string]*schema.Schema{
"name": &schema.Schema{
Type: schema.TypeString,
Optional: true,
Computed: true,
ForceNew: true,
ConflictsWith: []string{"name_prefix"},
ValidateFunc: validateGCPName,
},
"name_prefix": &schema.Schema{
Type: schema.TypeString,
Optional: true,
Computed: 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
},
Deprecated: "Use the random provider instead. See migration instructions at " +
"https://github.com/terraform-providers/terraform-provider-google/issues/1054#issuecomment-377390209",
},
"disk": &schema.Schema{
Type: schema.TypeList,
Required: true,
ForceNew: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"auto_delete": &schema.Schema{
Type: schema.TypeBool,
Optional: true,
Default: true,
ForceNew: true,
},
"boot": &schema.Schema{
Type: schema.TypeBool,
Optional: true,
ForceNew: true,
Computed: true,
},
"device_name": &schema.Schema{
Type: schema.TypeString,
Optional: true,
Computed: true,
ForceNew: true,
},
"disk_name": &schema.Schema{
Type: schema.TypeString,
Optional: true,
ForceNew: true,
},
"disk_size_gb": &schema.Schema{
Type: schema.TypeInt,
Optional: true,
ForceNew: true,
},
"disk_type": &schema.Schema{
Type: schema.TypeString,
Optional: true,
ForceNew: true,
Computed: true,
},
"source_image": &schema.Schema{
Type: schema.TypeString,
Optional: true,
ForceNew: true,
},
"interface": &schema.Schema{
Type: schema.TypeString,
Optional: true,
ForceNew: true,
Computed: true,
},
"mode": &schema.Schema{
Type: schema.TypeString,
Optional: true,
ForceNew: true,
Computed: true,
},
"source": &schema.Schema{
Type: schema.TypeString,
Optional: true,
ForceNew: true,
},
"type": &schema.Schema{
Type: schema.TypeString,
Optional: true,
ForceNew: true,
Computed: true,
},
},
},
},
"machine_type": &schema.Schema{
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"automatic_restart": &schema.Schema{
Type: schema.TypeBool,
Optional: true,
ForceNew: true,
Removed: "Use 'scheduling.automatic_restart' instead.",
},
"can_ip_forward": &schema.Schema{
Type: schema.TypeBool,
Optional: true,
Default: false,
ForceNew: true,
},
"description": &schema.Schema{
Type: schema.TypeString,
Optional: true,
ForceNew: true,
},
"instance_description": &schema.Schema{
Type: schema.TypeString,
Optional: true,
ForceNew: true,
},
"metadata": &schema.Schema{
Type: schema.TypeMap,
Optional: true,
ForceNew: true,
},
"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,
Optional: true,
ForceNew: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"network": &schema.Schema{
Type: schema.TypeString,
Optional: true,
ForceNew: true,
Computed: true,
DiffSuppressFunc: compareSelfLinkOrResourceName,
},
"address": &schema.Schema{
Type: schema.TypeString,
Computed: true, // Computed because it is set if network_ip is set.
Optional: true,
ForceNew: true,
},
"network_ip": &schema.Schema{
Type: schema.TypeString,
Computed: true, // Computed because it is set if address is set.
Optional: true,
ForceNew: true,
Deprecated: "Please use address",
},
"subnetwork": &schema.Schema{
Type: schema.TypeString,
Optional: true,
ForceNew: true,
Computed: true,
DiffSuppressFunc: compareSelfLinkOrResourceName,
},
"subnetwork_project": &schema.Schema{
Type: schema.TypeString,
Optional: true,
ForceNew: true,
Computed: true,
},
"access_config": &schema.Schema{
Type: schema.TypeList,
Optional: true,
ForceNew: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"nat_ip": &schema.Schema{
Type: schema.TypeString,
Optional: true,
Computed: true,
},
// Instance templates will never have an
// 'assigned NAT IP', but we need this in
// the schema to allow us to share flatten
// code with an instance, which could.
"assigned_nat_ip": &schema.Schema{
Type: schema.TypeString,
Computed: true,
},
},
},
},
"alias_ip_range": &schema.Schema{
Type: schema.TypeList,
Optional: true,
ForceNew: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"ip_cidr_range": &schema.Schema{
Type: schema.TypeString,
Required: true,
ForceNew: true,
DiffSuppressFunc: ipCidrRangeDiffSuppress,
},
"subnetwork_range_name": &schema.Schema{
Type: schema.TypeString,
Optional: true,
ForceNew: true,
},
},
},
},
},
},
},
"on_host_maintenance": &schema.Schema{
Type: schema.TypeString,
Optional: true,
ForceNew: true,
Removed: "Use 'scheduling.on_host_maintenance' instead.",
},
"project": &schema.Schema{
Type: schema.TypeString,
Optional: true,
ForceNew: true,
Computed: true,
},
"region": &schema.Schema{
Type: schema.TypeString,
Optional: true,
ForceNew: true,
Computed: true,
},
"scheduling": &schema.Schema{
Type: schema.TypeList,
Optional: true,
Computed: true,
ForceNew: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"preemptible": &schema.Schema{
Type: schema.TypeBool,
Optional: true,
Default: false,
ForceNew: true,
},
"automatic_restart": &schema.Schema{
Type: schema.TypeBool,
Optional: true,
Default: true,
ForceNew: true,
},
"on_host_maintenance": &schema.Schema{
Type: schema.TypeString,
Optional: true,
Computed: true,
ForceNew: true,
},
},
},
},
"self_link": &schema.Schema{
Type: schema.TypeString,
Computed: true,
},
"service_account": &schema.Schema{
Type: schema.TypeList,
MaxItems: 1,
Optional: true,
ForceNew: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"email": &schema.Schema{
Type: schema.TypeString,
Optional: true,
Computed: true,
ForceNew: true,
},
"scopes": &schema.Schema{
Type: schema.TypeSet,
Required: true,
ForceNew: true,
Elem: &schema.Schema{
Type: schema.TypeString,
StateFunc: func(v interface{}) string {
return canonicalizeServiceScope(v.(string))
},
},
Set: stringScopeHashcode,
},
},
},
},
"guest_accelerator": &schema.Schema{
Type: schema.TypeList,
Optional: 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,
},
},
},
},
"min_cpu_platform": &schema.Schema{
Type: schema.TypeString,
Optional: true,
ForceNew: true,
},
"tags": &schema.Schema{
Type: schema.TypeSet,
Optional: true,
ForceNew: true,
Elem: &schema.Schema{Type: schema.TypeString},
Set: schema.HashString,
},
"tags_fingerprint": &schema.Schema{
Type: schema.TypeString,
Computed: true,
},
"labels": &schema.Schema{
Type: schema.TypeMap,
Optional: true,
ForceNew: true,
Elem: &schema.Schema{Type: schema.TypeString},
Set: schema.HashString,
},
},
}
}
func buildDisks(d *schema.ResourceData, config *Config) ([]*computeBeta.AttachedDisk, error) {
project, err := getProject(d, config)
if err != nil {
return nil, err
}
disksCount := d.Get("disk.#").(int)
disks := make([]*computeBeta.AttachedDisk, 0, disksCount)
for i := 0; i < disksCount; i++ {
prefix := fmt.Sprintf("disk.%d", i)
// Build the disk
var disk computeBeta.AttachedDisk
disk.Type = "PERSISTENT"
disk.Mode = "READ_WRITE"
disk.Interface = "SCSI"
disk.Boot = i == 0
disk.AutoDelete = d.Get(prefix + ".auto_delete").(bool)
if v, ok := d.GetOk(prefix + ".boot"); ok {
disk.Boot = v.(bool)
}
if v, ok := d.GetOk(prefix + ".device_name"); ok {
disk.DeviceName = v.(string)
}
if v, ok := d.GetOk(prefix + ".source"); ok {
disk.Source = v.(string)
} else {
disk.InitializeParams = &computeBeta.AttachedDiskInitializeParams{}
if v, ok := d.GetOk(prefix + ".disk_name"); ok {
disk.InitializeParams.DiskName = v.(string)
}
if v, ok := d.GetOk(prefix + ".disk_size_gb"); ok {
disk.InitializeParams.DiskSizeGb = int64(v.(int))
}
disk.InitializeParams.DiskType = "pd-standard"
if v, ok := d.GetOk(prefix + ".disk_type"); ok {
disk.InitializeParams.DiskType = v.(string)
}
if v, ok := d.GetOk(prefix + ".source_image"); ok {
imageName := v.(string)
imageUrl, err := resolveImage(config, project, imageName)
if err != nil {
return nil, fmt.Errorf(
"Error resolving image name '%s': %s",
imageName, err)
}
disk.InitializeParams.SourceImage = imageUrl
}
}
if v, ok := d.GetOk(prefix + ".interface"); ok {
disk.Interface = v.(string)
}
if v, ok := d.GetOk(prefix + ".mode"); ok {
disk.Mode = v.(string)
}
if v, ok := d.GetOk(prefix + ".type"); ok {
disk.Type = v.(string)
}
disks = append(disks, &disk)
}
return disks, nil
}
// We don't share this code with compute instances because instances want a
// partial URL, but instance templates want the bare accelerator name (despite
// the docs saying otherwise).
//
// Using a partial URL on an instance template results in:
// Invalid value for field 'resource.properties.guestAccelerators[0].acceleratorType':
// 'zones/us-east1-b/acceleratorTypes/nvidia-tesla-k80'.
// Accelerator type 'zones/us-east1-b/acceleratorTypes/nvidia-tesla-k80'
// must be a valid resource name (not an url).
func expandInstanceTemplateGuestAccelerators(d TerraformResourceData, config *Config) []*computeBeta.AcceleratorConfig {
configs, ok := d.GetOk("guest_accelerator")
if !ok {
return nil
}
accels := configs.([]interface{})
guestAccelerators := make([]*computeBeta.AcceleratorConfig, 0, len(accels))
for _, raw := range accels {
data := raw.(map[string]interface{})
if data["count"].(int) == 0 {
continue
}
guestAccelerators = append(guestAccelerators, &computeBeta.AcceleratorConfig{
AcceleratorCount: int64(data["count"].(int)),
// We can't use ParseAcceleratorFieldValue here because an instance
// template does not have a zone we can use.
AcceleratorType: data["type"].(string),
})
}
return guestAccelerators
}
func resourceComputeInstanceTemplateCreate(d *schema.ResourceData, meta interface{}) error {
config := meta.(*Config)
project, err := getProject(d, config)
if err != nil {
return err
}
instanceProperties := &computeBeta.InstanceProperties{
CanIpForward: d.Get("can_ip_forward").(bool),
Description: d.Get("instance_description").(string),
MachineType: d.Get("machine_type").(string),
MinCpuPlatform: d.Get("min_cpu_platform").(string),
}
disks, err := buildDisks(d, config)
if err != nil {
return err
}
instanceProperties.Disks = disks
metadata, err := resourceInstanceMetadata(d)
if err != nil {
return err
}
instanceProperties.Metadata = metadata
networks, err := expandNetworkInterfaces(d, config)
if err != nil {
return err
}
instanceProperties.NetworkInterfaces = networks
instanceProperties.Scheduling = &computeBeta.Scheduling{}
instanceProperties.Scheduling.OnHostMaintenance = "MIGRATE"
forceSendFieldsScheduling := make([]string, 0, 3)
var hasSendMaintenance bool
hasSendMaintenance = false
if v, ok := d.GetOk("scheduling"); ok {
_schedulings := v.([]interface{})
if len(_schedulings) > 1 {
return fmt.Errorf("Error, at most one `scheduling` block can be defined")
}
_scheduling := _schedulings[0].(map[string]interface{})
// "automatic_restart" has a default value and is always safe to dereference
automaticRestart := _scheduling["automatic_restart"].(bool)
instanceProperties.Scheduling.AutomaticRestart = googleapi.Bool(automaticRestart)
forceSendFieldsScheduling = append(forceSendFieldsScheduling, "AutomaticRestart")
if vp, okp := _scheduling["on_host_maintenance"]; okp {
instanceProperties.Scheduling.OnHostMaintenance = vp.(string)
forceSendFieldsScheduling = append(forceSendFieldsScheduling, "OnHostMaintenance")
hasSendMaintenance = true
}
if vp, okp := _scheduling["preemptible"]; okp {
instanceProperties.Scheduling.Preemptible = vp.(bool)
forceSendFieldsScheduling = append(forceSendFieldsScheduling, "Preemptible")
if vp.(bool) && !hasSendMaintenance {
instanceProperties.Scheduling.OnHostMaintenance = "TERMINATE"
forceSendFieldsScheduling = append(forceSendFieldsScheduling, "OnHostMaintenance")
}
}
}
instanceProperties.Scheduling.ForceSendFields = forceSendFieldsScheduling
instanceProperties.ServiceAccounts = expandServiceAccounts(d.Get("service_account").([]interface{}))
instanceProperties.GuestAccelerators = expandInstanceTemplateGuestAccelerators(d, config)
instanceProperties.Tags = resourceInstanceTags(d)
if _, ok := d.GetOk("labels"); ok {
instanceProperties.Labels = expandLabels(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 := &computeBeta.InstanceTemplate{
Description: d.Get("description").(string),
Properties: instanceProperties,
Name: itName,
}
var op interface{}
switch getComputeApiVersion(d, InstanceTemplateBaseApiVersion, InstanceGroupManagerVersionedFeatures) {
case v1:
instanceTemplateV1 := &compute.InstanceTemplate{}
if err := Convert(instanceTemplate, instanceTemplateV1); err != nil {
return err
}
op, err = config.clientCompute.InstanceTemplates.Insert(project, instanceTemplateV1).Do()
case v0beta:
op, err = config.clientComputeBeta.InstanceTemplates.Insert(project, instanceTemplate).Do()
}
if err != nil {
return fmt.Errorf("Error creating instance template: %s", err)
}
// Store the ID now
d.SetId(instanceTemplate.Name)
err = computeSharedOperationWait(config.clientCompute, op, project, "Creating Instance Template")
if err != nil {
return err
}
return resourceComputeInstanceTemplateRead(d, meta)
}
func flattenDisks(disks []*computeBeta.AttachedDisk, d *schema.ResourceData) []map[string]interface{} {
result := make([]map[string]interface{}, 0, len(disks))
for i, disk := range disks {
diskMap := make(map[string]interface{})
if disk.InitializeParams != nil {
var source_img = fmt.Sprintf("disk.%d.source_image", i)
if d.Get(source_img) == nil || d.Get(source_img) == "" {
diskMap["source_image"] = GetResourceNameFromSelfLink(disk.InitializeParams.SourceImage)
} else {
diskMap["source_image"] = d.Get(source_img)
}
diskMap["disk_type"] = disk.InitializeParams.DiskType
diskMap["disk_name"] = disk.InitializeParams.DiskName
diskMap["disk_size_gb"] = disk.InitializeParams.DiskSizeGb
}
diskMap["auto_delete"] = disk.AutoDelete
diskMap["boot"] = disk.Boot
diskMap["device_name"] = disk.DeviceName
diskMap["interface"] = disk.Interface
diskMap["source"] = disk.Source
diskMap["mode"] = disk.Mode
diskMap["type"] = disk.Type
result = append(result, diskMap)
}
return result
}
func resourceComputeInstanceTemplateRead(d *schema.ResourceData, meta interface{}) error {
config := meta.(*Config)
project, err := getProject(d, config)
if err != nil {
return err
}
instanceTemplate := &computeBeta.InstanceTemplate{}
switch getComputeApiVersion(d, InstanceBaseApiVersion, InstanceVersionedFeatures) {
case v1:
instanceTemplateV1, err := config.clientCompute.InstanceTemplates.Get(project, d.Id()).Do()
if err != nil {
return handleNotFoundError(err, d, fmt.Sprintf("Instance Template %q", d.Get("name").(string)))
}
if err := Convert(instanceTemplateV1, instanceTemplate); err != nil {
return err
}
case v0beta:
var err error
instanceTemplate, err = config.clientComputeBeta.InstanceTemplates.Get(project, d.Id()).Do()
if err != nil {
return handleNotFoundError(err, d, fmt.Sprintf("Instance Template %q", d.Get("name").(string)))
}
}
// Set the metadata fingerprint if there is one.
if instanceTemplate.Properties.Metadata != nil {
if err = d.Set("metadata_fingerprint", instanceTemplate.Properties.Metadata.Fingerprint); err != nil {
return fmt.Errorf("Error setting metadata_fingerprint: %s", err)
}
md := instanceTemplate.Properties.Metadata
_md := flattenMetadataBeta(md)
if script, scriptExists := d.GetOk("metadata_startup_script"); scriptExists {
if err = d.Set("metadata_startup_script", script); err != nil {
return fmt.Errorf("Error setting metadata_startup_script: %s", err)
}
delete(_md, "startup-script")
}
if err = d.Set("metadata", _md); err != nil {
return fmt.Errorf("Error setting metadata: %s", err)
}
}
// Set the tags fingerprint if there is one.
if instanceTemplate.Properties.Tags != nil {
if err = d.Set("tags_fingerprint", instanceTemplate.Properties.Tags.Fingerprint); err != nil {
return fmt.Errorf("Error setting tags_fingerprint: %s", err)
}
}
if instanceTemplate.Properties.Labels != nil {
d.Set("labels", instanceTemplate.Properties.Labels)
}
if err = d.Set("self_link", instanceTemplate.SelfLink); err != nil {
return fmt.Errorf("Error setting self_link: %s", err)
}
if err = d.Set("name", instanceTemplate.Name); err != nil {
return fmt.Errorf("Error setting name: %s", err)
}
if instanceTemplate.Properties.Disks != nil {
if err = d.Set("disk", flattenDisks(instanceTemplate.Properties.Disks, d)); err != nil {
return fmt.Errorf("Error setting disk: %s", err)
}
}
if err = d.Set("description", instanceTemplate.Description); err != nil {
return fmt.Errorf("Error setting description: %s", err)
}
if err = d.Set("machine_type", instanceTemplate.Properties.MachineType); err != nil {
return fmt.Errorf("Error setting machine_type: %s", err)
}
if err = d.Set("min_cpu_platform", instanceTemplate.Properties.MinCpuPlatform); err != nil {
return fmt.Errorf("Error setting min_cpu_platform: %s", err)
}
if err = d.Set("can_ip_forward", instanceTemplate.Properties.CanIpForward); err != nil {
return fmt.Errorf("Error setting can_ip_forward: %s", err)
}
if err = d.Set("instance_description", instanceTemplate.Properties.Description); err != nil {
return fmt.Errorf("Error setting instance_description: %s", err)
}
if err = d.Set("project", project); err != nil {
return fmt.Errorf("Error setting project: %s", err)
}
if instanceTemplate.Properties.NetworkInterfaces != nil {
networkInterfaces, region, _, _, err := flattenNetworkInterfaces(d, config, instanceTemplate.Properties.NetworkInterfaces)
if err != nil {
return err
}
if err = d.Set("network_interface", networkInterfaces); err != nil {
return fmt.Errorf("Error setting network_interface: %s", err)
}
// region is where to look up the subnetwork if there is one attached to the instance template
if region != "" {
if err = d.Set("region", region); err != nil {
return fmt.Errorf("Error setting region: %s", err)
}
}
}
if instanceTemplate.Properties.Scheduling != nil {
scheduling := flattenScheduling(instanceTemplate.Properties.Scheduling)
if err = d.Set("scheduling", scheduling); err != nil {
return fmt.Errorf("Error setting scheduling: %s", err)
}
}
if instanceTemplate.Properties.Tags != nil {
if err = d.Set("tags", instanceTemplate.Properties.Tags.Items); err != nil {
return fmt.Errorf("Error setting tags: %s", err)
}
}
if instanceTemplate.Properties.ServiceAccounts != nil {
if err = d.Set("service_account", flattenServiceAccounts(instanceTemplate.Properties.ServiceAccounts)); err != nil {
return fmt.Errorf("Error setting service_account: %s", err)
}
}
if instanceTemplate.Properties.GuestAccelerators != nil {
if err = d.Set("guest_accelerator", flattenGuestAccelerators(instanceTemplate.Properties.GuestAccelerators)); err != nil {
return fmt.Errorf("Error setting guest_accelerator: %s", err)
}
}
return nil
}
func resourceComputeInstanceTemplateDelete(d *schema.ResourceData, meta interface{}) error {
config := meta.(*Config)
project, err := getProject(d, config)
if err != nil {
return err
}
op, err := config.clientCompute.InstanceTemplates.Delete(
project, d.Id()).Do()
if err != nil {
return fmt.Errorf("Error deleting instance template: %s", err)
}
err = computeOperationWait(config.clientCompute, op, project, "Deleting Instance Template")
if err != nil {
return err
}
d.SetId("")
return nil
}