package google import ( "fmt" "time" "code.google.com/p/google-api-go-client/compute/v1" "code.google.com/p/google-api-go-client/googleapi" "github.com/hashicorp/terraform/helper/hashcode" "github.com/hashicorp/terraform/helper/schema" ) func resourceComputeInstanceTemplate() *schema.Resource { return &schema.Resource{ Create: resourceComputeInstanceTemplateCreate, Read: resourceComputeInstanceTemplateRead, Delete: resourceComputeInstanceTemplateDelete, Schema: map[string]*schema.Schema{ "name": &schema.Schema{ Type: schema.TypeString, Required: true, ForceNew: true, }, "description": &schema.Schema{ Type: schema.TypeString, Optional: true, ForceNew: true, }, "can_ip_forward": &schema.Schema{ Type: schema.TypeBool, Optional: true, Default: false, ForceNew: true, }, "instance_description": &schema.Schema{ Type: schema.TypeString, Optional: true, ForceNew: true, }, "machine_type": &schema.Schema{ Type: schema.TypeString, Required: true, ForceNew: true, }, // TODO: Constraint either source or other disk params "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, ForceNew: true, }, "boot": &schema.Schema{ Type: schema.TypeBool, Optional: true, ForceNew: true, }, "device_name": &schema.Schema{ Type: schema.TypeString, Optional: 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, }, "source_image": &schema.Schema{ Type: schema.TypeString, Optional: true, ForceNew: true, }, "interface": &schema.Schema{ Type: schema.TypeString, Optional: true, ForceNew: true, }, "mode": &schema.Schema{ Type: schema.TypeString, Optional: true, ForceNew: true, }, "source": &schema.Schema{ Type: schema.TypeString, Optional: true, ForceNew: true, }, "type": &schema.Schema{ Type: schema.TypeString, Optional: true, ForceNew: true, }, }, }, }, "metadata": &schema.Schema{ Type: schema.TypeList, Optional: true, ForceNew: true, Elem: &schema.Schema{ Type: schema.TypeMap, }, }, "network": &schema.Schema{ Type: schema.TypeList, Required: true, ForceNew: true, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "source": &schema.Schema{ Type: schema.TypeString, ForceNew: true, Required: true, }, "address": &schema.Schema{ Type: schema.TypeString, ForceNew: true, Optional: true, }, }, }, }, "automatic_restart": &schema.Schema{ Type: schema.TypeBool, Optional: true, Default: true, ForceNew: true, }, "on_host_maintenance": &schema.Schema{ Type: schema.TypeString, Optional: true, ForceNew: true, }, "service_account": &schema.Schema{ Type: schema.TypeList, Optional: true, ForceNew: true, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "email": &schema.Schema{ Type: schema.TypeString, Computed: true, ForceNew: true, }, "scopes": &schema.Schema{ Type: schema.TypeList, Required: true, ForceNew: true, Elem: &schema.Schema{ Type: schema.TypeString, StateFunc: func(v interface{}) string { return canonicalizeServiceScope(v.(string)) }, }, }, }, }, }, "tags": &schema.Schema{ Type: schema.TypeSet, Optional: true, ForceNew: true, Elem: &schema.Schema{Type: schema.TypeString}, Set: func(v interface{}) int { return hashcode.String(v.(string)) }, }, "metadata_fingerprint": &schema.Schema{ Type: schema.TypeString, Computed: true, }, "tags_fingerprint": &schema.Schema{ Type: schema.TypeString, Computed: true, }, "self_link": &schema.Schema{ Type: schema.TypeString, Computed: true, }, }, } } func buildDisks(d *schema.ResourceData, meta interface{}) []*compute.AttachedDisk { disksCount := d.Get("disk.#").(int) disks := make([]*compute.AttachedDisk, 0, disksCount) for i := 0; i < disksCount; i++ { prefix := fmt.Sprintf("disk.%d", i) // Build the disk var disk compute.AttachedDisk disk.Type = "PERSISTENT" disk.Mode = "READ_WRITE" disk.Interface = "SCSI" disk.Boot = i == 0 disk.AutoDelete = true if v, ok := d.GetOk(prefix + ".auto_delete"); ok { disk.AutoDelete = v.(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 = &compute.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 = v.(int64) } 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 { disk.InitializeParams.SourceImage = v.(string) } } 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 } func buildNetworks(d *schema.ResourceData, meta interface{}) (error, []*compute.NetworkInterface) { // Build up the list of networks networksCount := d.Get("network.#").(int) networks := make([]*compute.NetworkInterface, 0, networksCount) for i := 0; i < networksCount; i++ { prefix := fmt.Sprintf("network.%d", i) source := "global/networks/default" if v, ok := d.GetOk(prefix + ".source"); ok { if v.(string) != "default" { source = v.(string) } } // Build the interface var iface compute.NetworkInterface iface.AccessConfigs = []*compute.AccessConfig{ &compute.AccessConfig{ Type: "ONE_TO_ONE_NAT", NatIP: d.Get(prefix + ".address").(string), }, } iface.Network = source networks = append(networks, &iface) } return nil, networks } func resourceComputeInstanceTemplateCreate(d *schema.ResourceData, meta interface{}) error { config := meta.(*Config) instanceProperties := &compute.InstanceProperties{} instanceProperties.CanIpForward = d.Get("can_ip_forward").(bool) instanceProperties.Description = d.Get("instance_description").(string) instanceProperties.MachineType = d.Get("machine_type").(string) instanceProperties.Disks = buildDisks(d, meta) instanceProperties.Metadata = resourceInstanceMetadata(d) err, networks := buildNetworks(d, meta) if err != nil { return err } instanceProperties.NetworkInterfaces = networks instanceProperties.Scheduling = &compute.Scheduling{ AutomaticRestart: d.Get("automatic_restart").(bool), } instanceProperties.Scheduling.OnHostMaintenance = "MIGRATE" if v, ok := d.GetOk("on_host_maintenance"); ok { instanceProperties.Scheduling.OnHostMaintenance = v.(string) } serviceAccountsCount := d.Get("service_account.#").(int) serviceAccounts := make([]*compute.ServiceAccount, 0, serviceAccountsCount) for i := 0; i < serviceAccountsCount; i++ { prefix := fmt.Sprintf("service_account.%d", i) scopesCount := d.Get(prefix + ".scopes.#").(int) scopes := make([]string, 0, scopesCount) for j := 0; j < scopesCount; j++ { scope := d.Get(fmt.Sprintf(prefix+".scopes.%d", j)).(string) scopes = append(scopes, canonicalizeServiceScope(scope)) } serviceAccount := &compute.ServiceAccount{ Email: "default", Scopes: scopes, } serviceAccounts = append(serviceAccounts, serviceAccount) } instanceProperties.ServiceAccounts = serviceAccounts instanceProperties.Tags = resourceInstanceTags(d) instanceTemplate := compute.InstanceTemplate{ Description: d.Get("description").(string), Properties: instanceProperties, Name: d.Get("name").(string), } op, err := config.clientCompute.InstanceTemplates.Insert( config.Project, &instanceTemplate).Do() if err != nil { return fmt.Errorf("Error creating instance: %s", err) } // Store the ID now d.SetId(instanceTemplate.Name) // Wait for the operation to complete w := &OperationWaiter{ Service: config.clientCompute, Op: op, Project: config.Project, Type: OperationWaitGlobal, } state := w.Conf() state.Delay = 10 * time.Second state.Timeout = 10 * time.Minute state.MinTimeout = 2 * time.Second opRaw, err := state.WaitForState() if err != nil { return fmt.Errorf("Error waiting for instance template to create: %s", err) } op = opRaw.(*compute.Operation) if op.Error != nil { // The resource didn't actually create d.SetId("") // Return the error return OperationError(*op.Error) } return resourceComputeInstanceTemplateRead(d, meta) } func resourceComputeInstanceTemplateRead(d *schema.ResourceData, meta interface{}) error { config := meta.(*Config) instanceTemplate, err := config.clientCompute.InstanceTemplates.Get( config.Project, d.Id()).Do() if err != nil { if gerr, ok := err.(*googleapi.Error); ok && gerr.Code == 404 { // The resource doesn't exist anymore d.SetId("") return nil } return fmt.Errorf("Error reading instance template: %s", err) } // Set the metadata fingerprint if there is one. if instanceTemplate.Properties.Metadata != nil { d.Set("metadata_fingerprint", instanceTemplate.Properties.Metadata.Fingerprint) } // Set the tags fingerprint if there is one. if instanceTemplate.Properties.Tags != nil { d.Set("tags_fingerprint", instanceTemplate.Properties.Tags.Fingerprint) } d.Set("self_link", instanceTemplate.SelfLink) return nil } func resourceComputeInstanceTemplateDelete(d *schema.ResourceData, meta interface{}) error { config := meta.(*Config) op, err := config.clientCompute.InstanceTemplates.Delete( config.Project, d.Id()).Do() if err != nil { return fmt.Errorf("Error deleting instance template: %s", err) } // Wait for the operation to complete w := &OperationWaiter{ Service: config.clientCompute, Op: op, Project: config.Project, Type: OperationWaitGlobal, } state := w.Conf() state.Delay = 5 * time.Second state.Timeout = 5 * time.Minute state.MinTimeout = 2 * time.Second opRaw, err := state.WaitForState() if err != nil { return fmt.Errorf("Error waiting for instance template to delete: %s", err) } op = opRaw.(*compute.Operation) if op.Error != nil { // Return the error return OperationError(*op.Error) } d.SetId("") return nil }