github.com/jsoriano/terraform@v0.6.7-0.20151026070445-8b70867fdd95/builtin/providers/google/resource_compute_instance.go (about)

     1  package google
     2  
     3  import (
     4  	"fmt"
     5  	"log"
     6  
     7  	"github.com/hashicorp/terraform/helper/hashcode"
     8  	"github.com/hashicorp/terraform/helper/schema"
     9  	"google.golang.org/api/compute/v1"
    10  	"google.golang.org/api/googleapi"
    11  )
    12  
    13  func stringHashcode(v interface{}) int {
    14  	return hashcode.String(v.(string))
    15  }
    16  
    17  func stringScopeHashcode(v interface{}) int {
    18  	v = canonicalizeServiceScope(v.(string))
    19  	return hashcode.String(v.(string))
    20  }
    21  
    22  func resourceComputeInstance() *schema.Resource {
    23  	return &schema.Resource{
    24  		Create: resourceComputeInstanceCreate,
    25  		Read:   resourceComputeInstanceRead,
    26  		Update: resourceComputeInstanceUpdate,
    27  		Delete: resourceComputeInstanceDelete,
    28  
    29  		SchemaVersion: 2,
    30  		MigrateState:  resourceComputeInstanceMigrateState,
    31  
    32  		Schema: map[string]*schema.Schema{
    33  			"name": &schema.Schema{
    34  				Type:     schema.TypeString,
    35  				Required: true,
    36  				ForceNew: true,
    37  			},
    38  
    39  			"description": &schema.Schema{
    40  				Type:     schema.TypeString,
    41  				Optional: true,
    42  				ForceNew: true,
    43  			},
    44  
    45  			"machine_type": &schema.Schema{
    46  				Type:     schema.TypeString,
    47  				Required: true,
    48  				ForceNew: true,
    49  			},
    50  
    51  			"zone": &schema.Schema{
    52  				Type:     schema.TypeString,
    53  				Required: true,
    54  				ForceNew: true,
    55  			},
    56  
    57  			"disk": &schema.Schema{
    58  				Type:     schema.TypeList,
    59  				Required: true,
    60  				ForceNew: true,
    61  				Elem: &schema.Resource{
    62  					Schema: map[string]*schema.Schema{
    63  						// TODO(mitchellh): one of image or disk is required
    64  
    65  						"disk": &schema.Schema{
    66  							Type:     schema.TypeString,
    67  							Optional: true,
    68  							ForceNew: true,
    69  						},
    70  
    71  						"image": &schema.Schema{
    72  							Type:     schema.TypeString,
    73  							Optional: true,
    74  							ForceNew: true,
    75  						},
    76  
    77  						"type": &schema.Schema{
    78  							Type:     schema.TypeString,
    79  							Optional: true,
    80  							ForceNew: true,
    81  						},
    82  
    83  						"scratch": &schema.Schema{
    84  							Type:     schema.TypeBool,
    85  							Optional: true,
    86  							ForceNew: true,
    87  						},
    88  
    89  						"auto_delete": &schema.Schema{
    90  							Type:     schema.TypeBool,
    91  							Optional: true,
    92  							Default:  true,
    93  							ForceNew: true,
    94  						},
    95  
    96  						"size": &schema.Schema{
    97  							Type:     schema.TypeInt,
    98  							Optional: true,
    99  							ForceNew: true,
   100  						},
   101  
   102  						"device_name": &schema.Schema{
   103  							Type:     schema.TypeString,
   104  							Optional: true,
   105  						},
   106  					},
   107  				},
   108  			},
   109  
   110  			"network_interface": &schema.Schema{
   111  				Type:     schema.TypeList,
   112  				Optional: true,
   113  				ForceNew: true,
   114  				Elem: &schema.Resource{
   115  					Schema: map[string]*schema.Schema{
   116  						"network": &schema.Schema{
   117  							Type:     schema.TypeString,
   118  							Required: true,
   119  							ForceNew: true,
   120  						},
   121  
   122  						"name": &schema.Schema{
   123  							Type:     schema.TypeString,
   124  							Computed: true,
   125  						},
   126  
   127  						"address": &schema.Schema{
   128  							Type:     schema.TypeString,
   129  							Computed: true,
   130  						},
   131  
   132  						"access_config": &schema.Schema{
   133  							Type:     schema.TypeList,
   134  							Optional: true,
   135  							Elem: &schema.Resource{
   136  								Schema: map[string]*schema.Schema{
   137  									"nat_ip": &schema.Schema{
   138  										Type:     schema.TypeString,
   139  										Computed: true,
   140  										Optional: true,
   141  									},
   142  								},
   143  							},
   144  						},
   145  					},
   146  				},
   147  			},
   148  
   149  			"network": &schema.Schema{
   150  				Type:       schema.TypeList,
   151  				Optional:   true,
   152  				ForceNew:   true,
   153  				Deprecated: "Please use network_interface",
   154  				Elem: &schema.Resource{
   155  					Schema: map[string]*schema.Schema{
   156  						"source": &schema.Schema{
   157  							Type:     schema.TypeString,
   158  							Required: true,
   159  							ForceNew: true,
   160  						},
   161  
   162  						"address": &schema.Schema{
   163  							Type:     schema.TypeString,
   164  							Optional: true,
   165  							ForceNew: true,
   166  						},
   167  
   168  						"name": &schema.Schema{
   169  							Type:     schema.TypeString,
   170  							Computed: true,
   171  						},
   172  
   173  						"internal_address": &schema.Schema{
   174  							Type:     schema.TypeString,
   175  							Computed: true,
   176  						},
   177  
   178  						"external_address": &schema.Schema{
   179  							Type:     schema.TypeString,
   180  							Computed: true,
   181  						},
   182  					},
   183  				},
   184  			},
   185  
   186  			"can_ip_forward": &schema.Schema{
   187  				Type:     schema.TypeBool,
   188  				Optional: true,
   189  				Default:  false,
   190  				ForceNew: true,
   191  			},
   192  
   193  			"metadata_startup_script": &schema.Schema{
   194  				Type:     schema.TypeString,
   195  				Optional: true,
   196  				ForceNew: true,
   197  			},
   198  
   199  			"metadata": &schema.Schema{
   200  				Type:         schema.TypeMap,
   201  				Optional:     true,
   202  				Elem:         schema.TypeString,
   203  				ValidateFunc: validateInstanceMetadata,
   204  			},
   205  
   206  			"service_account": &schema.Schema{
   207  				Type:     schema.TypeList,
   208  				Optional: true,
   209  				ForceNew: true,
   210  				Elem: &schema.Resource{
   211  					Schema: map[string]*schema.Schema{
   212  						"email": &schema.Schema{
   213  							Type:     schema.TypeString,
   214  							Computed: true,
   215  							ForceNew: true,
   216  						},
   217  
   218  						"scopes": &schema.Schema{
   219  							Type:     schema.TypeSet,
   220  							Required: true,
   221  							ForceNew: true,
   222  							Elem: &schema.Schema{
   223  								Type: schema.TypeString,
   224  								StateFunc: func(v interface{}) string {
   225  									return canonicalizeServiceScope(v.(string))
   226  								},
   227  							},
   228  							Set: stringScopeHashcode,
   229  						},
   230  					},
   231  				},
   232  			},
   233  
   234  			"tags": &schema.Schema{
   235  				Type:     schema.TypeSet,
   236  				Optional: true,
   237  				Elem:     &schema.Schema{Type: schema.TypeString},
   238  				Set:      stringHashcode,
   239  			},
   240  
   241  			"metadata_fingerprint": &schema.Schema{
   242  				Type:     schema.TypeString,
   243  				Computed: true,
   244  			},
   245  
   246  			"tags_fingerprint": &schema.Schema{
   247  				Type:     schema.TypeString,
   248  				Computed: true,
   249  			},
   250  
   251  			"self_link": &schema.Schema{
   252  				Type:     schema.TypeString,
   253  				Computed: true,
   254  			},
   255  		},
   256  	}
   257  }
   258  
   259  func getInstance(config *Config, d *schema.ResourceData) (*compute.Instance, error) {
   260  	instance, err := config.clientCompute.Instances.Get(
   261  		config.Project, d.Get("zone").(string), d.Id()).Do()
   262  	if err != nil {
   263  		if gerr, ok := err.(*googleapi.Error); ok && gerr.Code == 404 {
   264  			// The resource doesn't exist anymore
   265  			d.SetId("")
   266  
   267  			return nil, fmt.Errorf("Resource %s no longer exists", config.Project)
   268  		}
   269  
   270  		return nil, fmt.Errorf("Error reading instance: %s", err)
   271  	}
   272  
   273  	return instance, nil
   274  }
   275  
   276  func resourceComputeInstanceCreate(d *schema.ResourceData, meta interface{}) error {
   277  	config := meta.(*Config)
   278  
   279  	// Get the zone
   280  	log.Printf("[DEBUG] Loading zone: %s", d.Get("zone").(string))
   281  	zone, err := config.clientCompute.Zones.Get(
   282  		config.Project, d.Get("zone").(string)).Do()
   283  	if err != nil {
   284  		return fmt.Errorf(
   285  			"Error loading zone '%s': %s", d.Get("zone").(string), err)
   286  	}
   287  
   288  	// Get the machine type
   289  	log.Printf("[DEBUG] Loading machine type: %s", d.Get("machine_type").(string))
   290  	machineType, err := config.clientCompute.MachineTypes.Get(
   291  		config.Project, zone.Name, d.Get("machine_type").(string)).Do()
   292  	if err != nil {
   293  		return fmt.Errorf(
   294  			"Error loading machine type: %s",
   295  			err)
   296  	}
   297  
   298  	// Build up the list of disks
   299  	disksCount := d.Get("disk.#").(int)
   300  	disks := make([]*compute.AttachedDisk, 0, disksCount)
   301  	for i := 0; i < disksCount; i++ {
   302  		prefix := fmt.Sprintf("disk.%d", i)
   303  
   304  		// var sourceLink string
   305  
   306  		// Build the disk
   307  		var disk compute.AttachedDisk
   308  		disk.Type = "PERSISTENT"
   309  		disk.Mode = "READ_WRITE"
   310  		disk.Boot = i == 0
   311  		disk.AutoDelete = d.Get(prefix + ".auto_delete").(bool)
   312  
   313  		// Load up the disk for this disk if specified
   314  		if v, ok := d.GetOk(prefix + ".disk"); ok {
   315  			diskName := v.(string)
   316  			diskData, err := config.clientCompute.Disks.Get(
   317  				config.Project, zone.Name, diskName).Do()
   318  			if err != nil {
   319  				return fmt.Errorf(
   320  					"Error loading disk '%s': %s",
   321  					diskName, err)
   322  			}
   323  
   324  			disk.Source = diskData.SelfLink
   325  		} else {
   326  			// Create a new disk
   327  			disk.InitializeParams = &compute.AttachedDiskInitializeParams{}
   328  		}
   329  
   330  		if v, ok := d.GetOk(prefix + ".scratch"); ok {
   331  			if v.(bool) {
   332  				disk.Type = "SCRATCH"
   333  			}
   334  		}
   335  
   336  		// Load up the image for this disk if specified
   337  		if v, ok := d.GetOk(prefix + ".image"); ok {
   338  			imageName := v.(string)
   339  
   340  			imageUrl, err := resolveImage(config, imageName)
   341  			if err != nil {
   342  				return fmt.Errorf(
   343  					"Error resolving image name '%s': %s",
   344  					imageName, err)
   345  			}
   346  
   347  			disk.InitializeParams.SourceImage = imageUrl
   348  		}
   349  
   350  		if v, ok := d.GetOk(prefix + ".type"); ok {
   351  			diskTypeName := v.(string)
   352  			diskType, err := readDiskType(config, zone, diskTypeName)
   353  			if err != nil {
   354  				return fmt.Errorf(
   355  					"Error loading disk type '%s': %s",
   356  					diskTypeName, err)
   357  			}
   358  
   359  			disk.InitializeParams.DiskType = diskType.SelfLink
   360  		}
   361  
   362  		if v, ok := d.GetOk(prefix + ".size"); ok {
   363  			diskSizeGb := v.(int)
   364  			disk.InitializeParams.DiskSizeGb = int64(diskSizeGb)
   365  		}
   366  
   367  		if v, ok := d.GetOk(prefix + ".device_name"); ok {
   368  			disk.DeviceName = v.(string)
   369  		}
   370  
   371  		disks = append(disks, &disk)
   372  	}
   373  
   374  	networksCount := d.Get("network.#").(int)
   375  	networkInterfacesCount := d.Get("network_interface.#").(int)
   376  
   377  	if networksCount > 0 && networkInterfacesCount > 0 {
   378  		return fmt.Errorf("Error: cannot define both networks and network_interfaces.")
   379  	}
   380  	if networksCount == 0 && networkInterfacesCount == 0 {
   381  		return fmt.Errorf("Error: Must define at least one network_interface.")
   382  	}
   383  
   384  	var networkInterfaces []*compute.NetworkInterface
   385  
   386  	if networksCount > 0 {
   387  		// TODO: Delete this block when removing network { }
   388  		// Build up the list of networkInterfaces
   389  		networkInterfaces = make([]*compute.NetworkInterface, 0, networksCount)
   390  		for i := 0; i < networksCount; i++ {
   391  			prefix := fmt.Sprintf("network.%d", i)
   392  			// Load up the name of this network
   393  			networkName := d.Get(prefix + ".source").(string)
   394  			network, err := config.clientCompute.Networks.Get(
   395  				config.Project, networkName).Do()
   396  			if err != nil {
   397  				return fmt.Errorf(
   398  					"Error loading network '%s': %s",
   399  					networkName, err)
   400  			}
   401  
   402  			// Build the networkInterface
   403  			var iface compute.NetworkInterface
   404  			iface.AccessConfigs = []*compute.AccessConfig{
   405  				&compute.AccessConfig{
   406  					Type:  "ONE_TO_ONE_NAT",
   407  					NatIP: d.Get(prefix + ".address").(string),
   408  				},
   409  			}
   410  			iface.Network = network.SelfLink
   411  
   412  			networkInterfaces = append(networkInterfaces, &iface)
   413  		}
   414  	}
   415  
   416  	if networkInterfacesCount > 0 {
   417  		// Build up the list of networkInterfaces
   418  		networkInterfaces = make([]*compute.NetworkInterface, 0, networkInterfacesCount)
   419  		for i := 0; i < networkInterfacesCount; i++ {
   420  			prefix := fmt.Sprintf("network_interface.%d", i)
   421  			// Load up the name of this network_interfac
   422  			networkName := d.Get(prefix + ".network").(string)
   423  			network, err := config.clientCompute.Networks.Get(
   424  				config.Project, networkName).Do()
   425  			if err != nil {
   426  				return fmt.Errorf(
   427  					"Error referencing network '%s': %s",
   428  					networkName, err)
   429  			}
   430  
   431  			// Build the networkInterface
   432  			var iface compute.NetworkInterface
   433  			iface.Network = network.SelfLink
   434  
   435  			// Handle access_config structs
   436  			accessConfigsCount := d.Get(prefix + ".access_config.#").(int)
   437  			iface.AccessConfigs = make([]*compute.AccessConfig, accessConfigsCount)
   438  			for j := 0; j < accessConfigsCount; j++ {
   439  				acPrefix := fmt.Sprintf("%s.access_config.%d", prefix, j)
   440  				iface.AccessConfigs[j] = &compute.AccessConfig{
   441  					Type:  "ONE_TO_ONE_NAT",
   442  					NatIP: d.Get(acPrefix + ".nat_ip").(string),
   443  				}
   444  			}
   445  
   446  			networkInterfaces = append(networkInterfaces, &iface)
   447  		}
   448  	}
   449  
   450  	serviceAccountsCount := d.Get("service_account.#").(int)
   451  	serviceAccounts := make([]*compute.ServiceAccount, 0, serviceAccountsCount)
   452  	for i := 0; i < serviceAccountsCount; i++ {
   453  		prefix := fmt.Sprintf("service_account.%d", i)
   454  
   455  		scopesSet := d.Get(prefix + ".scopes").(*schema.Set)
   456  		scopes := make([]string, scopesSet.Len())
   457  		for i, v := range scopesSet.List() {
   458  			scopes[i] = canonicalizeServiceScope(v.(string))
   459  		}
   460  
   461  		serviceAccount := &compute.ServiceAccount{
   462  			Email:  "default",
   463  			Scopes: scopes,
   464  		}
   465  
   466  		serviceAccounts = append(serviceAccounts, serviceAccount)
   467  	}
   468  
   469  	metadata, err := resourceInstanceMetadata(d)
   470  	if err != nil {
   471  		return fmt.Errorf("Error creating metadata: %s", err)
   472  	}
   473  
   474  	// Create the instance information
   475  	instance := compute.Instance{
   476  		CanIpForward:      d.Get("can_ip_forward").(bool),
   477  		Description:       d.Get("description").(string),
   478  		Disks:             disks,
   479  		MachineType:       machineType.SelfLink,
   480  		Metadata:          metadata,
   481  		Name:              d.Get("name").(string),
   482  		NetworkInterfaces: networkInterfaces,
   483  		Tags:              resourceInstanceTags(d),
   484  		ServiceAccounts:   serviceAccounts,
   485  	}
   486  
   487  	log.Printf("[INFO] Requesting instance creation")
   488  	op, err := config.clientCompute.Instances.Insert(
   489  		config.Project, zone.Name, &instance).Do()
   490  	if err != nil {
   491  		return fmt.Errorf("Error creating instance: %s", err)
   492  	}
   493  
   494  	// Store the ID now
   495  	d.SetId(instance.Name)
   496  
   497  	// Wait for the operation to complete
   498  	waitErr := computeOperationWaitZone(config, op, zone.Name, "instance to create")
   499  	if waitErr != nil {
   500  		// The resource didn't actually create
   501  		d.SetId("")
   502  		return waitErr
   503  	}
   504  
   505  	return resourceComputeInstanceRead(d, meta)
   506  }
   507  
   508  func resourceComputeInstanceRead(d *schema.ResourceData, meta interface{}) error {
   509  	config := meta.(*Config)
   510  
   511  	instance, err := getInstance(config, d)
   512  	if err != nil {
   513  		return err
   514  	}
   515  
   516  	// Synch metadata
   517  	md := instance.Metadata
   518  
   519  	_md := MetadataFormatSchema(md)
   520  	delete(_md, "startup-script")
   521  
   522  	if script, scriptExists := d.GetOk("metadata_startup_script"); scriptExists {
   523  		d.Set("metadata_startup_script", script)
   524  	}
   525  
   526  	if err = d.Set("metadata", _md); err != nil {
   527  		return fmt.Errorf("Error setting metadata: %s", err)
   528  	}
   529  
   530  	d.Set("can_ip_forward", instance.CanIpForward)
   531  
   532  	// Set the service accounts
   533  	serviceAccounts := make([]map[string]interface{}, 0, 1)
   534  	for _, serviceAccount := range instance.ServiceAccounts {
   535  		scopes := make([]interface{}, len(serviceAccount.Scopes))
   536  		for i, scope := range serviceAccount.Scopes {
   537  			scopes[i] = scope
   538  		}
   539  		serviceAccounts = append(serviceAccounts, map[string]interface{}{
   540  			"email":  serviceAccount.Email,
   541  			"scopes": schema.NewSet(stringScopeHashcode, scopes),
   542  		})
   543  	}
   544  	d.Set("service_account", serviceAccounts)
   545  
   546  	networksCount := d.Get("network.#").(int)
   547  	networkInterfacesCount := d.Get("network_interface.#").(int)
   548  
   549  	if networksCount > 0 && networkInterfacesCount > 0 {
   550  		return fmt.Errorf("Error: cannot define both networks and network_interfaces.")
   551  	}
   552  	if networksCount == 0 && networkInterfacesCount == 0 {
   553  		return fmt.Errorf("Error: Must define at least one network_interface.")
   554  	}
   555  
   556  	// Set the networks
   557  	// Use the first external IP found for the default connection info.
   558  	externalIP := ""
   559  	internalIP := ""
   560  	networks := make([]map[string]interface{}, 0, 1)
   561  	if networksCount > 0 {
   562  		// TODO: Remove this when realizing deprecation of .network
   563  		for i, iface := range instance.NetworkInterfaces {
   564  			var natIP string
   565  			for _, config := range iface.AccessConfigs {
   566  				if config.Type == "ONE_TO_ONE_NAT" {
   567  					natIP = config.NatIP
   568  					break
   569  				}
   570  			}
   571  
   572  			if externalIP == "" && natIP != "" {
   573  				externalIP = natIP
   574  			}
   575  
   576  			network := make(map[string]interface{})
   577  			network["name"] = iface.Name
   578  			network["external_address"] = natIP
   579  			network["internal_address"] = iface.NetworkIP
   580  			network["source"] = d.Get(fmt.Sprintf("network.%d.source", i))
   581  			networks = append(networks, network)
   582  		}
   583  	}
   584  	d.Set("network", networks)
   585  
   586  	networkInterfaces := make([]map[string]interface{}, 0, 1)
   587  	if networkInterfacesCount > 0 {
   588  		for i, iface := range instance.NetworkInterfaces {
   589  			// The first non-empty ip is left in natIP
   590  			var natIP string
   591  			accessConfigs := make(
   592  				[]map[string]interface{}, 0, len(iface.AccessConfigs))
   593  			for _, config := range iface.AccessConfigs {
   594  				accessConfigs = append(accessConfigs, map[string]interface{}{
   595  					"nat_ip": config.NatIP,
   596  				})
   597  
   598  				if natIP == "" {
   599  					natIP = config.NatIP
   600  				}
   601  			}
   602  
   603  			if externalIP == "" {
   604  				externalIP = natIP
   605  			}
   606  
   607  			if internalIP == "" {
   608  				internalIP = iface.NetworkIP
   609  			}
   610  
   611  			networkInterfaces = append(networkInterfaces, map[string]interface{}{
   612  				"name":          iface.Name,
   613  				"address":       iface.NetworkIP,
   614  				"network":       d.Get(fmt.Sprintf("network_interface.%d.network", i)),
   615  				"access_config": accessConfigs,
   616  			})
   617  		}
   618  	}
   619  	d.Set("network_interface", networkInterfaces)
   620  
   621  	// Fall back on internal ip if there is no external ip.  This makes sense in the situation where
   622  	// terraform is being used on a cloud instance and can therefore access the instances it creates
   623  	// via their internal ips.
   624  	sshIP := externalIP
   625  	if sshIP == "" {
   626  		sshIP = internalIP
   627  	}
   628  
   629  	// Initialize the connection info
   630  	d.SetConnInfo(map[string]string{
   631  		"type": "ssh",
   632  		"host": sshIP,
   633  	})
   634  
   635  	// Set the metadata fingerprint if there is one.
   636  	if instance.Metadata != nil {
   637  		d.Set("metadata_fingerprint", instance.Metadata.Fingerprint)
   638  	}
   639  
   640  	// Set the tags fingerprint if there is one.
   641  	if instance.Tags != nil {
   642  		d.Set("tags_fingerprint", instance.Tags.Fingerprint)
   643  	}
   644  
   645  	d.Set("self_link", instance.SelfLink)
   646  	d.SetId(instance.Name)
   647  
   648  	return nil
   649  }
   650  
   651  func resourceComputeInstanceUpdate(d *schema.ResourceData, meta interface{}) error {
   652  	config := meta.(*Config)
   653  
   654  	zone := d.Get("zone").(string)
   655  
   656  	instance, err := getInstance(config, d)
   657  	if err != nil {
   658  		return err
   659  	}
   660  
   661  	// Enable partial mode for the resource since it is possible
   662  	d.Partial(true)
   663  
   664  	// If the Metadata has changed, then update that.
   665  	if d.HasChange("metadata") {
   666  		o, n := d.GetChange("metadata")
   667  		if script, scriptExists := d.GetOk("metadata_startup_script"); scriptExists {
   668  			if _, ok := n.(map[string]interface{})["startup-script"]; ok {
   669  				return fmt.Errorf("Only one of metadata.startup-script and metadata_startup_script may be defined")
   670  			}
   671  
   672  			n.(map[string]interface{})["startup-script"] = script
   673  		}
   674  
   675  		updateMD := func() error {
   676  			// Reload the instance in the case of a fingerprint mismatch
   677  			instance, err = getInstance(config, d)
   678  			if err != nil {
   679  				return err
   680  			}
   681  
   682  			md := instance.Metadata
   683  
   684  			MetadataUpdate(o.(map[string]interface{}), n.(map[string]interface{}), md)
   685  
   686  			if err != nil {
   687  				return fmt.Errorf("Error updating metadata: %s", err)
   688  			}
   689  			op, err := config.clientCompute.Instances.SetMetadata(
   690  				config.Project, zone, d.Id(), md).Do()
   691  			if err != nil {
   692  				return fmt.Errorf("Error updating metadata: %s", err)
   693  			}
   694  
   695  			opErr := computeOperationWaitZone(config, op, zone, "metadata to update")
   696  			if opErr != nil {
   697  				return opErr
   698  			}
   699  
   700  			d.SetPartial("metadata")
   701  			return nil
   702  		}
   703  
   704  		MetadataRetryWrapper(updateMD)
   705  	}
   706  
   707  	if d.HasChange("tags") {
   708  		tags := resourceInstanceTags(d)
   709  		op, err := config.clientCompute.Instances.SetTags(
   710  			config.Project, zone, d.Id(), tags).Do()
   711  		if err != nil {
   712  			return fmt.Errorf("Error updating tags: %s", err)
   713  		}
   714  
   715  		opErr := computeOperationWaitZone(config, op, zone, "tags to update")
   716  		if opErr != nil {
   717  			return opErr
   718  		}
   719  
   720  		d.SetPartial("tags")
   721  	}
   722  
   723  	networkInterfacesCount := d.Get("network_interface.#").(int)
   724  	if networkInterfacesCount > 0 {
   725  		// Sanity check
   726  		if networkInterfacesCount != len(instance.NetworkInterfaces) {
   727  			return fmt.Errorf("Instance had unexpected number of network interfaces: %d", len(instance.NetworkInterfaces))
   728  		}
   729  		for i := 0; i < networkInterfacesCount; i++ {
   730  			prefix := fmt.Sprintf("network_interface.%d", i)
   731  			instNetworkInterface := instance.NetworkInterfaces[i]
   732  			networkName := d.Get(prefix + ".name").(string)
   733  
   734  			// TODO: This sanity check is broken by #929, disabled for now (by forcing the equality)
   735  			networkName = instNetworkInterface.Name
   736  			// Sanity check
   737  			if networkName != instNetworkInterface.Name {
   738  				return fmt.Errorf("Instance networkInterface had unexpected name: %s", instNetworkInterface.Name)
   739  			}
   740  
   741  			if d.HasChange(prefix + ".access_config") {
   742  
   743  				// TODO: This code deletes then recreates accessConfigs.  This is bad because it may
   744  				// leave the machine inaccessible from either ip if the creation part fails (network
   745  				// timeout etc).  However right now there is a GCE limit of 1 accessConfig so it is
   746  				// the only way to do it.  In future this should be revised to only change what is
   747  				// necessary, and also add before removing.
   748  
   749  				// Delete any accessConfig that currently exists in instNetworkInterface
   750  				for _, ac := range instNetworkInterface.AccessConfigs {
   751  					op, err := config.clientCompute.Instances.DeleteAccessConfig(
   752  						config.Project, zone, d.Id(), ac.Name, networkName).Do()
   753  					if err != nil {
   754  						return fmt.Errorf("Error deleting old access_config: %s", err)
   755  					}
   756  					opErr := computeOperationWaitZone(config, op, zone, "old access_config to delete")
   757  					if opErr != nil {
   758  						return opErr
   759  					}
   760  				}
   761  
   762  				// Create new ones
   763  				accessConfigsCount := d.Get(prefix + ".access_config.#").(int)
   764  				for j := 0; j < accessConfigsCount; j++ {
   765  					acPrefix := fmt.Sprintf("%s.access_config.%d", prefix, j)
   766  					ac := &compute.AccessConfig{
   767  						Type:  "ONE_TO_ONE_NAT",
   768  						NatIP: d.Get(acPrefix + ".nat_ip").(string),
   769  					}
   770  					op, err := config.clientCompute.Instances.AddAccessConfig(
   771  						config.Project, zone, d.Id(), networkName, ac).Do()
   772  					if err != nil {
   773  						return fmt.Errorf("Error adding new access_config: %s", err)
   774  					}
   775  					opErr := computeOperationWaitZone(config, op, zone, "new access_config to add")
   776  					if opErr != nil {
   777  						return opErr
   778  					}
   779  				}
   780  			}
   781  		}
   782  	}
   783  
   784  	// We made it, disable partial mode
   785  	d.Partial(false)
   786  
   787  	return resourceComputeInstanceRead(d, meta)
   788  }
   789  
   790  func resourceComputeInstanceDelete(d *schema.ResourceData, meta interface{}) error {
   791  	config := meta.(*Config)
   792  
   793  	zone := d.Get("zone").(string)
   794  	log.Printf("[INFO] Requesting instance deletion: %s", d.Id())
   795  	op, err := config.clientCompute.Instances.Delete(config.Project, zone, d.Id()).Do()
   796  	if err != nil {
   797  		return fmt.Errorf("Error deleting instance: %s", err)
   798  	}
   799  
   800  	// Wait for the operation to complete
   801  	opErr := computeOperationWaitZone(config, op, zone, "instance to delete")
   802  	if opErr != nil {
   803  		return opErr
   804  	}
   805  
   806  	d.SetId("")
   807  	return nil
   808  }
   809  
   810  func resourceInstanceMetadata(d *schema.ResourceData) (*compute.Metadata, error) {
   811  	m := &compute.Metadata{}
   812  	mdMap := d.Get("metadata").(map[string]interface{})
   813  	if v, ok := d.GetOk("metadata_startup_script"); ok && v.(string) != "" {
   814  		mdMap["startup-script"] = v
   815  	}
   816  	if len(mdMap) > 0 {
   817  		m.Items = make([]*compute.MetadataItems, 0, len(mdMap))
   818  		for key, val := range mdMap {
   819  			v := val.(string)
   820  			m.Items = append(m.Items, &compute.MetadataItems{
   821  				Key:   key,
   822  				Value: &v,
   823  			})
   824  		}
   825  
   826  		// Set the fingerprint. If the metadata has never been set before
   827  		// then this will just be blank.
   828  		m.Fingerprint = d.Get("metadata_fingerprint").(string)
   829  	}
   830  
   831  	return m, nil
   832  }
   833  
   834  func resourceInstanceTags(d *schema.ResourceData) *compute.Tags {
   835  	// Calculate the tags
   836  	var tags *compute.Tags
   837  	if v := d.Get("tags"); v != nil {
   838  		vs := v.(*schema.Set)
   839  		tags = new(compute.Tags)
   840  		tags.Items = make([]string, vs.Len())
   841  		for i, v := range vs.List() {
   842  			tags.Items[i] = v.(string)
   843  		}
   844  
   845  		tags.Fingerprint = d.Get("tags_fingerprint").(string)
   846  	}
   847  
   848  	return tags
   849  }
   850  
   851  func validateInstanceMetadata(v interface{}, k string) (ws []string, es []error) {
   852  	mdMap := v.(map[string]interface{})
   853  	if _, ok := mdMap["startup-script"]; ok {
   854  		es = append(es, fmt.Errorf(
   855  			"Use metadata_startup_script instead of a startup-script key in %q.", k))
   856  	}
   857  	return
   858  }