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