github.com/armen/terraform@v0.5.2-0.20150529052519-caa8117a08f1/builtin/providers/google/resource_compute_instance.go (about)

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