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