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