github.com/joshgarnett/terraform@v0.5.4-0.20160219181435-92dc20bb3594/builtin/providers/google/resource_compute_instance_template.go (about)

     1  package google
     2  
     3  import (
     4  	"fmt"
     5  	"log"
     6  
     7  	"github.com/hashicorp/terraform/helper/schema"
     8  	"google.golang.org/api/compute/v1"
     9  	"google.golang.org/api/googleapi"
    10  )
    11  
    12  func resourceComputeInstanceTemplate() *schema.Resource {
    13  	return &schema.Resource{
    14  		Create: resourceComputeInstanceTemplateCreate,
    15  		Read:   resourceComputeInstanceTemplateRead,
    16  		Delete: resourceComputeInstanceTemplateDelete,
    17  
    18  		Schema: map[string]*schema.Schema{
    19  			"name": &schema.Schema{
    20  				Type:     schema.TypeString,
    21  				Required: true,
    22  				ForceNew: true,
    23  			},
    24  
    25  			"description": &schema.Schema{
    26  				Type:     schema.TypeString,
    27  				Optional: true,
    28  				ForceNew: true,
    29  			},
    30  
    31  			"can_ip_forward": &schema.Schema{
    32  				Type:     schema.TypeBool,
    33  				Optional: true,
    34  				Default:  false,
    35  				ForceNew: true,
    36  			},
    37  
    38  			"instance_description": &schema.Schema{
    39  				Type:     schema.TypeString,
    40  				Optional: true,
    41  				ForceNew: true,
    42  			},
    43  
    44  			"machine_type": &schema.Schema{
    45  				Type:     schema.TypeString,
    46  				Required: true,
    47  				ForceNew: true,
    48  			},
    49  
    50  			"disk": &schema.Schema{
    51  				Type:     schema.TypeList,
    52  				Required: true,
    53  				ForceNew: true,
    54  				Elem: &schema.Resource{
    55  					Schema: map[string]*schema.Schema{
    56  						"auto_delete": &schema.Schema{
    57  							Type:     schema.TypeBool,
    58  							Optional: true,
    59  							Default:  true,
    60  							ForceNew: true,
    61  						},
    62  
    63  						"boot": &schema.Schema{
    64  							Type:     schema.TypeBool,
    65  							Optional: true,
    66  							ForceNew: true,
    67  						},
    68  
    69  						"device_name": &schema.Schema{
    70  							Type:     schema.TypeString,
    71  							Optional: true,
    72  							ForceNew: true,
    73  						},
    74  
    75  						"disk_name": &schema.Schema{
    76  							Type:     schema.TypeString,
    77  							Optional: true,
    78  							ForceNew: true,
    79  						},
    80  
    81  						"disk_size_gb": &schema.Schema{
    82  							Type:     schema.TypeInt,
    83  							Optional: true,
    84  							ForceNew: true,
    85  						},
    86  
    87  						"disk_type": &schema.Schema{
    88  							Type:     schema.TypeString,
    89  							Optional: true,
    90  							ForceNew: true,
    91  						},
    92  
    93  						"source_image": &schema.Schema{
    94  							Type:     schema.TypeString,
    95  							Optional: true,
    96  							ForceNew: true,
    97  						},
    98  
    99  						"interface": &schema.Schema{
   100  							Type:     schema.TypeString,
   101  							Optional: true,
   102  							ForceNew: true,
   103  						},
   104  
   105  						"mode": &schema.Schema{
   106  							Type:     schema.TypeString,
   107  							Optional: true,
   108  							ForceNew: true,
   109  						},
   110  
   111  						"source": &schema.Schema{
   112  							Type:     schema.TypeString,
   113  							Optional: true,
   114  							ForceNew: true,
   115  						},
   116  
   117  						"type": &schema.Schema{
   118  							Type:     schema.TypeString,
   119  							Optional: true,
   120  							ForceNew: true,
   121  						},
   122  					},
   123  				},
   124  			},
   125  
   126  			"metadata": &schema.Schema{
   127  				Type:     schema.TypeMap,
   128  				Optional: true,
   129  				ForceNew: true,
   130  			},
   131  
   132  			"network_interface": &schema.Schema{
   133  				Type:     schema.TypeList,
   134  				Optional: true,
   135  				ForceNew: true,
   136  				Elem: &schema.Resource{
   137  					Schema: map[string]*schema.Schema{
   138  						"network": &schema.Schema{
   139  							Type:     schema.TypeString,
   140  							Optional: true,
   141  							ForceNew: true,
   142  						},
   143  
   144  						"subnetwork": &schema.Schema{
   145  							Type:     schema.TypeString,
   146  							Optional: true,
   147  							ForceNew: true,
   148  						},
   149  
   150  						"access_config": &schema.Schema{
   151  							Type:     schema.TypeList,
   152  							Optional: true,
   153  							Elem: &schema.Resource{
   154  								Schema: map[string]*schema.Schema{
   155  									"nat_ip": &schema.Schema{
   156  										Type:     schema.TypeString,
   157  										Computed: true,
   158  										Optional: true,
   159  									},
   160  								},
   161  							},
   162  						},
   163  					},
   164  				},
   165  			},
   166  
   167  			"automatic_restart": &schema.Schema{
   168  				Type:       schema.TypeBool,
   169  				Optional:   true,
   170  				Default:    true,
   171  				ForceNew:   true,
   172  				Deprecated: "Please use `scheduling.automatic_restart` instead",
   173  			},
   174  
   175  			"on_host_maintenance": &schema.Schema{
   176  				Type:       schema.TypeString,
   177  				Optional:   true,
   178  				ForceNew:   true,
   179  				Deprecated: "Please use `scheduling.on_host_maintenance` instead",
   180  			},
   181  
   182  			"region": &schema.Schema{
   183  				Type:     schema.TypeString,
   184  				Optional: true,
   185  				ForceNew: true,
   186  			},
   187  
   188  			"scheduling": &schema.Schema{
   189  				Type:     schema.TypeList,
   190  				Optional: true,
   191  				ForceNew: true,
   192  				Elem: &schema.Resource{
   193  					Schema: map[string]*schema.Schema{
   194  						"preemptible": &schema.Schema{
   195  							Type:     schema.TypeBool,
   196  							Optional: true,
   197  							ForceNew: true,
   198  						},
   199  
   200  						"automatic_restart": &schema.Schema{
   201  							Type:     schema.TypeBool,
   202  							Optional: true,
   203  							Default:  true,
   204  							ForceNew: true,
   205  						},
   206  
   207  						"on_host_maintenance": &schema.Schema{
   208  							Type:     schema.TypeString,
   209  							Optional: true,
   210  							ForceNew: true,
   211  						},
   212  					},
   213  				},
   214  			},
   215  
   216  			"service_account": &schema.Schema{
   217  				Type:     schema.TypeList,
   218  				Optional: true,
   219  				ForceNew: true,
   220  				Elem: &schema.Resource{
   221  					Schema: map[string]*schema.Schema{
   222  						"email": &schema.Schema{
   223  							Type:     schema.TypeString,
   224  							Computed: true,
   225  							ForceNew: true,
   226  						},
   227  
   228  						"scopes": &schema.Schema{
   229  							Type:     schema.TypeList,
   230  							Required: true,
   231  							ForceNew: true,
   232  							Elem: &schema.Schema{
   233  								Type: schema.TypeString,
   234  								StateFunc: func(v interface{}) string {
   235  									return canonicalizeServiceScope(v.(string))
   236  								},
   237  							},
   238  						},
   239  					},
   240  				},
   241  			},
   242  
   243  			"tags": &schema.Schema{
   244  				Type:     schema.TypeSet,
   245  				Optional: true,
   246  				ForceNew: true,
   247  				Elem:     &schema.Schema{Type: schema.TypeString},
   248  				Set:      schema.HashString,
   249  			},
   250  
   251  			"metadata_fingerprint": &schema.Schema{
   252  				Type:     schema.TypeString,
   253  				Computed: true,
   254  			},
   255  
   256  			"tags_fingerprint": &schema.Schema{
   257  				Type:     schema.TypeString,
   258  				Computed: true,
   259  			},
   260  
   261  			"self_link": &schema.Schema{
   262  				Type:     schema.TypeString,
   263  				Computed: true,
   264  			},
   265  		},
   266  	}
   267  }
   268  
   269  func buildDisks(d *schema.ResourceData, meta interface{}) ([]*compute.AttachedDisk, error) {
   270  	config := meta.(*Config)
   271  
   272  	disksCount := d.Get("disk.#").(int)
   273  
   274  	disks := make([]*compute.AttachedDisk, 0, disksCount)
   275  	for i := 0; i < disksCount; i++ {
   276  		prefix := fmt.Sprintf("disk.%d", i)
   277  
   278  		// Build the disk
   279  		var disk compute.AttachedDisk
   280  		disk.Type = "PERSISTENT"
   281  		disk.Mode = "READ_WRITE"
   282  		disk.Interface = "SCSI"
   283  		disk.Boot = i == 0
   284  		disk.AutoDelete = d.Get(prefix + ".auto_delete").(bool)
   285  
   286  		if v, ok := d.GetOk(prefix + ".boot"); ok {
   287  			disk.Boot = v.(bool)
   288  		}
   289  
   290  		if v, ok := d.GetOk(prefix + ".device_name"); ok {
   291  			disk.DeviceName = v.(string)
   292  		}
   293  
   294  		if v, ok := d.GetOk(prefix + ".source"); ok {
   295  			disk.Source = v.(string)
   296  		} else {
   297  			disk.InitializeParams = &compute.AttachedDiskInitializeParams{}
   298  
   299  			if v, ok := d.GetOk(prefix + ".disk_name"); ok {
   300  				disk.InitializeParams.DiskName = v.(string)
   301  			}
   302  			if v, ok := d.GetOk(prefix + ".disk_size_gb"); ok {
   303  				disk.InitializeParams.DiskSizeGb = int64(v.(int))
   304  			}
   305  			disk.InitializeParams.DiskType = "pd-standard"
   306  			if v, ok := d.GetOk(prefix + ".disk_type"); ok {
   307  				disk.InitializeParams.DiskType = v.(string)
   308  			}
   309  
   310  			if v, ok := d.GetOk(prefix + ".source_image"); ok {
   311  				imageName := v.(string)
   312  				imageUrl, err := resolveImage(config, imageName)
   313  				if err != nil {
   314  					return nil, fmt.Errorf(
   315  						"Error resolving image name '%s': %s",
   316  						imageName, err)
   317  				}
   318  				disk.InitializeParams.SourceImage = imageUrl
   319  			}
   320  		}
   321  
   322  		if v, ok := d.GetOk(prefix + ".interface"); ok {
   323  			disk.Interface = v.(string)
   324  		}
   325  
   326  		if v, ok := d.GetOk(prefix + ".mode"); ok {
   327  			disk.Mode = v.(string)
   328  		}
   329  
   330  		if v, ok := d.GetOk(prefix + ".type"); ok {
   331  			disk.Type = v.(string)
   332  		}
   333  
   334  		disks = append(disks, &disk)
   335  	}
   336  
   337  	return disks, nil
   338  }
   339  
   340  func buildNetworks(d *schema.ResourceData, meta interface{}) (error, []*compute.NetworkInterface) {
   341  	// Build up the list of networks
   342  	config := meta.(*Config)
   343  
   344  	networksCount := d.Get("network_interface.#").(int)
   345  	networkInterfaces := make([]*compute.NetworkInterface, 0, networksCount)
   346  	for i := 0; i < networksCount; i++ {
   347  		prefix := fmt.Sprintf("network_interface.%d", i)
   348  
   349  		var networkName, subnetworkName string
   350  		if v, ok := d.GetOk(prefix + ".network"); ok {
   351  			networkName = v.(string)
   352  		}
   353  		if v, ok := d.GetOk(prefix + ".subnetwork"); ok {
   354  			subnetworkName = v.(string)
   355  		}
   356  
   357  		if networkName == "" && subnetworkName == "" {
   358  			return fmt.Errorf("network or subnetwork must be provided"), nil
   359  		}
   360  		if networkName != "" && subnetworkName != "" {
   361  			return fmt.Errorf("network or subnetwork must not both be provided"), nil
   362  		}
   363  
   364  		var networkLink, subnetworkLink string
   365  		if networkName != "" {
   366  			network, err := config.clientCompute.Networks.Get(
   367  				config.Project, networkName).Do()
   368  			if err != nil {
   369  				return fmt.Errorf(
   370  					"Error referencing network '%s': %s",
   371  					networkName, err), nil
   372  			}
   373  			networkLink = network.SelfLink
   374  		} else {
   375  			// lookup subnetwork link using region and subnetwork name
   376  			region := d.Get("region").(string)
   377  			if region == "" {
   378  				region = config.Region
   379  			}
   380  			subnetwork, err := config.clientCompute.Subnetworks.Get(
   381  				config.Project, region, subnetworkName).Do()
   382  			if err != nil {
   383  				return fmt.Errorf(
   384  					"Error referencing subnetwork '%s' in region '%s': %s",
   385  					subnetworkName, region, err), nil
   386  			}
   387  			subnetworkLink = subnetwork.SelfLink
   388  		}
   389  
   390  		// Build the networkInterface
   391  		var iface compute.NetworkInterface
   392  		iface.Network = networkLink
   393  		iface.Subnetwork = subnetworkLink
   394  
   395  		accessConfigsCount := d.Get(prefix + ".access_config.#").(int)
   396  		iface.AccessConfigs = make([]*compute.AccessConfig, accessConfigsCount)
   397  		for j := 0; j < accessConfigsCount; j++ {
   398  			acPrefix := fmt.Sprintf("%s.access_config.%d", prefix, j)
   399  			iface.AccessConfigs[j] = &compute.AccessConfig{
   400  				Type:  "ONE_TO_ONE_NAT",
   401  				NatIP: d.Get(acPrefix + ".nat_ip").(string),
   402  			}
   403  		}
   404  
   405  		networkInterfaces = append(networkInterfaces, &iface)
   406  	}
   407  	return nil, networkInterfaces
   408  }
   409  
   410  func resourceComputeInstanceTemplateCreate(d *schema.ResourceData, meta interface{}) error {
   411  	config := meta.(*Config)
   412  
   413  	instanceProperties := &compute.InstanceProperties{}
   414  
   415  	instanceProperties.CanIpForward = d.Get("can_ip_forward").(bool)
   416  	instanceProperties.Description = d.Get("instance_description").(string)
   417  	instanceProperties.MachineType = d.Get("machine_type").(string)
   418  	disks, err := buildDisks(d, meta)
   419  	if err != nil {
   420  		return err
   421  	}
   422  	instanceProperties.Disks = disks
   423  	metadata, err := resourceInstanceMetadata(d)
   424  	if err != nil {
   425  		return err
   426  	}
   427  	instanceProperties.Metadata = metadata
   428  	err, networks := buildNetworks(d, meta)
   429  	if err != nil {
   430  		return err
   431  	}
   432  	instanceProperties.NetworkInterfaces = networks
   433  
   434  	instanceProperties.Scheduling = &compute.Scheduling{}
   435  	instanceProperties.Scheduling.OnHostMaintenance = "MIGRATE"
   436  
   437  	if v, ok := d.GetOk("automatic_restart"); ok {
   438  		instanceProperties.Scheduling.AutomaticRestart = v.(bool)
   439  	}
   440  
   441  	if v, ok := d.GetOk("on_host_maintenance"); ok {
   442  		instanceProperties.Scheduling.OnHostMaintenance = v.(string)
   443  	}
   444  
   445  	if v, ok := d.GetOk("scheduling"); ok {
   446  		_schedulings := v.([]interface{})
   447  		if len(_schedulings) > 1 {
   448  			return fmt.Errorf("Error, at most one `scheduling` block can be defined")
   449  		}
   450  		_scheduling := _schedulings[0].(map[string]interface{})
   451  
   452  		if vp, okp := _scheduling["automatic_restart"]; okp {
   453  			instanceProperties.Scheduling.AutomaticRestart = vp.(bool)
   454  		}
   455  
   456  		if vp, okp := _scheduling["on_host_maintenance"]; okp {
   457  			instanceProperties.Scheduling.OnHostMaintenance = vp.(string)
   458  		}
   459  
   460  		if vp, okp := _scheduling["preemptible"]; okp {
   461  			instanceProperties.Scheduling.Preemptible = vp.(bool)
   462  		}
   463  	}
   464  
   465  	serviceAccountsCount := d.Get("service_account.#").(int)
   466  	serviceAccounts := make([]*compute.ServiceAccount, 0, serviceAccountsCount)
   467  	for i := 0; i < serviceAccountsCount; i++ {
   468  		prefix := fmt.Sprintf("service_account.%d", i)
   469  
   470  		scopesCount := d.Get(prefix + ".scopes.#").(int)
   471  		scopes := make([]string, 0, scopesCount)
   472  		for j := 0; j < scopesCount; j++ {
   473  			scope := d.Get(fmt.Sprintf(prefix+".scopes.%d", j)).(string)
   474  			scopes = append(scopes, canonicalizeServiceScope(scope))
   475  		}
   476  
   477  		serviceAccount := &compute.ServiceAccount{
   478  			Email:  "default",
   479  			Scopes: scopes,
   480  		}
   481  
   482  		serviceAccounts = append(serviceAccounts, serviceAccount)
   483  	}
   484  	instanceProperties.ServiceAccounts = serviceAccounts
   485  
   486  	instanceProperties.Tags = resourceInstanceTags(d)
   487  
   488  	instanceTemplate := compute.InstanceTemplate{
   489  		Description: d.Get("description").(string),
   490  		Properties:  instanceProperties,
   491  		Name:        d.Get("name").(string),
   492  	}
   493  
   494  	op, err := config.clientCompute.InstanceTemplates.Insert(
   495  		config.Project, &instanceTemplate).Do()
   496  	if err != nil {
   497  		return fmt.Errorf("Error creating instance: %s", err)
   498  	}
   499  
   500  	// Store the ID now
   501  	d.SetId(instanceTemplate.Name)
   502  
   503  	err = computeOperationWaitGlobal(config, op, "Creating Instance Template")
   504  	if err != nil {
   505  		return err
   506  	}
   507  
   508  	return resourceComputeInstanceTemplateRead(d, meta)
   509  }
   510  
   511  func resourceComputeInstanceTemplateRead(d *schema.ResourceData, meta interface{}) error {
   512  	config := meta.(*Config)
   513  
   514  	instanceTemplate, err := config.clientCompute.InstanceTemplates.Get(
   515  		config.Project, d.Id()).Do()
   516  	if err != nil {
   517  		if gerr, ok := err.(*googleapi.Error); ok && gerr.Code == 404 {
   518  			log.Printf("[WARN] Removing Instance Template %q because it's gone", d.Get("name").(string))
   519  			// The resource doesn't exist anymore
   520  			d.SetId("")
   521  
   522  			return nil
   523  		}
   524  
   525  		return fmt.Errorf("Error reading instance template: %s", err)
   526  	}
   527  
   528  	// Set the metadata fingerprint if there is one.
   529  	if instanceTemplate.Properties.Metadata != nil {
   530  		d.Set("metadata_fingerprint", instanceTemplate.Properties.Metadata.Fingerprint)
   531  	}
   532  
   533  	// Set the tags fingerprint if there is one.
   534  	if instanceTemplate.Properties.Tags != nil {
   535  		d.Set("tags_fingerprint", instanceTemplate.Properties.Tags.Fingerprint)
   536  	}
   537  	d.Set("self_link", instanceTemplate.SelfLink)
   538  
   539  	return nil
   540  }
   541  
   542  func resourceComputeInstanceTemplateDelete(d *schema.ResourceData, meta interface{}) error {
   543  	config := meta.(*Config)
   544  
   545  	op, err := config.clientCompute.InstanceTemplates.Delete(
   546  		config.Project, d.Id()).Do()
   547  	if err != nil {
   548  		return fmt.Errorf("Error deleting instance template: %s", err)
   549  	}
   550  
   551  	err = computeOperationWaitGlobal(config, op, "Deleting Instance Template")
   552  	if err != nil {
   553  		return err
   554  	}
   555  
   556  	d.SetId("")
   557  	return nil
   558  }