github.com/btobolaski/terraform@v0.6.4-0.20150928030114-0c3f2a915c02/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  			},
   204  
   205  			"service_account": &schema.Schema{
   206  				Type:     schema.TypeList,
   207  				Optional: true,
   208  				ForceNew: true,
   209  				Elem: &schema.Resource{
   210  					Schema: map[string]*schema.Schema{
   211  						"email": &schema.Schema{
   212  							Type:     schema.TypeString,
   213  							Computed: true,
   214  							ForceNew: true,
   215  						},
   216  
   217  						"scopes": &schema.Schema{
   218  							Type:     schema.TypeSet,
   219  							Required: true,
   220  							ForceNew: true,
   221  							Elem: &schema.Schema{
   222  								Type: schema.TypeString,
   223  								StateFunc: func(v interface{}) string {
   224  									return canonicalizeServiceScope(v.(string))
   225  								},
   226  							},
   227  							Set: stringScopeHashcode,
   228  						},
   229  					},
   230  				},
   231  			},
   232  
   233  			"tags": &schema.Schema{
   234  				Type:     schema.TypeSet,
   235  				Optional: true,
   236  				Elem:     &schema.Schema{Type: schema.TypeString},
   237  				Set:      stringHashcode,
   238  			},
   239  
   240  			"metadata_fingerprint": &schema.Schema{
   241  				Type:     schema.TypeString,
   242  				Computed: true,
   243  			},
   244  
   245  			"tags_fingerprint": &schema.Schema{
   246  				Type:     schema.TypeString,
   247  				Computed: true,
   248  			},
   249  
   250  			"self_link": &schema.Schema{
   251  				Type:     schema.TypeString,
   252  				Computed: true,
   253  			},
   254  		},
   255  	}
   256  }
   257  
   258  func getInstance(config *Config, d *schema.ResourceData) (*compute.Instance, error) {
   259  	instance, err := config.clientCompute.Instances.Get(
   260  		config.Project, d.Get("zone").(string), d.Id()).Do()
   261  	if err != nil {
   262  		if gerr, ok := err.(*googleapi.Error); ok && gerr.Code == 404 {
   263  			// The resource doesn't exist anymore
   264  			d.SetId("")
   265  
   266  			return nil, fmt.Errorf("Resource %s no longer exists", config.Project)
   267  		}
   268  
   269  		return nil, fmt.Errorf("Error reading instance: %s", err)
   270  	}
   271  
   272  	return instance, nil
   273  }
   274  
   275  func resourceComputeInstanceCreate(d *schema.ResourceData, meta interface{}) error {
   276  	config := meta.(*Config)
   277  
   278  	// Get the zone
   279  	log.Printf("[DEBUG] Loading zone: %s", d.Get("zone").(string))
   280  	zone, err := config.clientCompute.Zones.Get(
   281  		config.Project, d.Get("zone").(string)).Do()
   282  	if err != nil {
   283  		return fmt.Errorf(
   284  			"Error loading zone '%s': %s", d.Get("zone").(string), err)
   285  	}
   286  
   287  	// Get the machine type
   288  	log.Printf("[DEBUG] Loading machine type: %s", d.Get("machine_type").(string))
   289  	machineType, err := config.clientCompute.MachineTypes.Get(
   290  		config.Project, zone.Name, d.Get("machine_type").(string)).Do()
   291  	if err != nil {
   292  		return fmt.Errorf(
   293  			"Error loading machine type: %s",
   294  			err)
   295  	}
   296  
   297  	// Build up the list of disks
   298  	disksCount := d.Get("disk.#").(int)
   299  	disks := make([]*compute.AttachedDisk, 0, disksCount)
   300  	for i := 0; i < disksCount; i++ {
   301  		prefix := fmt.Sprintf("disk.%d", i)
   302  
   303  		// var sourceLink string
   304  
   305  		// Build the disk
   306  		var disk compute.AttachedDisk
   307  		disk.Type = "PERSISTENT"
   308  		disk.Mode = "READ_WRITE"
   309  		disk.Boot = i == 0
   310  		disk.AutoDelete = d.Get(prefix + ".auto_delete").(bool)
   311  
   312  		// Load up the disk for this disk if specified
   313  		if v, ok := d.GetOk(prefix + ".disk"); ok {
   314  			diskName := v.(string)
   315  			diskData, err := config.clientCompute.Disks.Get(
   316  				config.Project, zone.Name, diskName).Do()
   317  			if err != nil {
   318  				return fmt.Errorf(
   319  					"Error loading disk '%s': %s",
   320  					diskName, err)
   321  			}
   322  
   323  			disk.Source = diskData.SelfLink
   324  		} else {
   325  			// Create a new disk
   326  			disk.InitializeParams = &compute.AttachedDiskInitializeParams{}
   327  		}
   328  
   329  		if v, ok := d.GetOk(prefix + ".scratch"); ok {
   330  			if v.(bool) {
   331  				disk.Type = "SCRATCH"
   332  			}
   333  		}
   334  
   335  		// Load up the image for this disk if specified
   336  		if v, ok := d.GetOk(prefix + ".image"); ok {
   337  			imageName := v.(string)
   338  
   339  			imageUrl, err := resolveImage(config, imageName)
   340  			if err != nil {
   341  				return fmt.Errorf(
   342  					"Error resolving image name '%s': %s",
   343  					imageName, err)
   344  			}
   345  
   346  			disk.InitializeParams.SourceImage = imageUrl
   347  		}
   348  
   349  		if v, ok := d.GetOk(prefix + ".type"); ok {
   350  			diskTypeName := v.(string)
   351  			diskType, err := readDiskType(config, zone, diskTypeName)
   352  			if err != nil {
   353  				return fmt.Errorf(
   354  					"Error loading disk type '%s': %s",
   355  					diskTypeName, err)
   356  			}
   357  
   358  			disk.InitializeParams.DiskType = diskType.SelfLink
   359  		}
   360  
   361  		if v, ok := d.GetOk(prefix + ".size"); ok {
   362  			diskSizeGb := v.(int)
   363  			disk.InitializeParams.DiskSizeGb = int64(diskSizeGb)
   364  		}
   365  
   366  		if v, ok := d.GetOk(prefix + ".device_name"); ok {
   367  			disk.DeviceName = v.(string)
   368  		}
   369  
   370  		disks = append(disks, &disk)
   371  	}
   372  
   373  	networksCount := d.Get("network.#").(int)
   374  	networkInterfacesCount := d.Get("network_interface.#").(int)
   375  
   376  	if networksCount > 0 && networkInterfacesCount > 0 {
   377  		return fmt.Errorf("Error: cannot define both networks and network_interfaces.")
   378  	}
   379  	if networksCount == 0 && networkInterfacesCount == 0 {
   380  		return fmt.Errorf("Error: Must define at least one network_interface.")
   381  	}
   382  
   383  	var networkInterfaces []*compute.NetworkInterface
   384  
   385  	if networksCount > 0 {
   386  		// TODO: Delete this block when removing network { }
   387  		// Build up the list of networkInterfaces
   388  		networkInterfaces = make([]*compute.NetworkInterface, 0, networksCount)
   389  		for i := 0; i < networksCount; i++ {
   390  			prefix := fmt.Sprintf("network.%d", i)
   391  			// Load up the name of this network
   392  			networkName := d.Get(prefix + ".source").(string)
   393  			network, err := config.clientCompute.Networks.Get(
   394  				config.Project, networkName).Do()
   395  			if err != nil {
   396  				return fmt.Errorf(
   397  					"Error loading network '%s': %s",
   398  					networkName, err)
   399  			}
   400  
   401  			// Build the networkInterface
   402  			var iface compute.NetworkInterface
   403  			iface.AccessConfigs = []*compute.AccessConfig{
   404  				&compute.AccessConfig{
   405  					Type:  "ONE_TO_ONE_NAT",
   406  					NatIP: d.Get(prefix + ".address").(string),
   407  				},
   408  			}
   409  			iface.Network = network.SelfLink
   410  
   411  			networkInterfaces = append(networkInterfaces, &iface)
   412  		}
   413  	}
   414  
   415  	if networkInterfacesCount > 0 {
   416  		// Build up the list of networkInterfaces
   417  		networkInterfaces = make([]*compute.NetworkInterface, 0, networkInterfacesCount)
   418  		for i := 0; i < networkInterfacesCount; i++ {
   419  			prefix := fmt.Sprintf("network_interface.%d", i)
   420  			// Load up the name of this network_interfac
   421  			networkName := d.Get(prefix + ".network").(string)
   422  			network, err := config.clientCompute.Networks.Get(
   423  				config.Project, networkName).Do()
   424  			if err != nil {
   425  				return fmt.Errorf(
   426  					"Error referencing network '%s': %s",
   427  					networkName, err)
   428  			}
   429  
   430  			// Build the networkInterface
   431  			var iface compute.NetworkInterface
   432  			iface.Network = network.SelfLink
   433  
   434  			// Handle access_config structs
   435  			accessConfigsCount := d.Get(prefix + ".access_config.#").(int)
   436  			iface.AccessConfigs = make([]*compute.AccessConfig, accessConfigsCount)
   437  			for j := 0; j < accessConfigsCount; j++ {
   438  				acPrefix := fmt.Sprintf("%s.access_config.%d", prefix, j)
   439  				iface.AccessConfigs[j] = &compute.AccessConfig{
   440  					Type:  "ONE_TO_ONE_NAT",
   441  					NatIP: d.Get(acPrefix + ".nat_ip").(string),
   442  				}
   443  			}
   444  
   445  			networkInterfaces = append(networkInterfaces, &iface)
   446  		}
   447  	}
   448  
   449  	serviceAccountsCount := d.Get("service_account.#").(int)
   450  	serviceAccounts := make([]*compute.ServiceAccount, 0, serviceAccountsCount)
   451  	for i := 0; i < serviceAccountsCount; i++ {
   452  		prefix := fmt.Sprintf("service_account.%d", i)
   453  
   454  		scopesSet := d.Get(prefix + ".scopes").(*schema.Set)
   455  		scopes := make([]string, scopesSet.Len())
   456  		for i, v := range scopesSet.List() {
   457  			scopes[i] = canonicalizeServiceScope(v.(string))
   458  		}
   459  
   460  		serviceAccount := &compute.ServiceAccount{
   461  			Email:  "default",
   462  			Scopes: scopes,
   463  		}
   464  
   465  		serviceAccounts = append(serviceAccounts, serviceAccount)
   466  	}
   467  
   468  	metadata, err := resourceInstanceMetadata(d)
   469  	if err != nil {
   470  		return fmt.Errorf("Error creating metadata: %s", err)
   471  	}
   472  
   473  	// Create the instance information
   474  	instance := compute.Instance{
   475  		CanIpForward:      d.Get("can_ip_forward").(bool),
   476  		Description:       d.Get("description").(string),
   477  		Disks:             disks,
   478  		MachineType:       machineType.SelfLink,
   479  		Metadata:          metadata,
   480  		Name:              d.Get("name").(string),
   481  		NetworkInterfaces: networkInterfaces,
   482  		Tags:              resourceInstanceTags(d),
   483  		ServiceAccounts:   serviceAccounts,
   484  	}
   485  
   486  	log.Printf("[INFO] Requesting instance creation")
   487  	op, err := config.clientCompute.Instances.Insert(
   488  		config.Project, zone.Name, &instance).Do()
   489  	if err != nil {
   490  		return fmt.Errorf("Error creating instance: %s", err)
   491  	}
   492  
   493  	// Store the ID now
   494  	d.SetId(instance.Name)
   495  
   496  	// Wait for the operation to complete
   497  	waitErr := computeOperationWaitZone(config, op, zone.Name, "instance to create")
   498  	if waitErr != nil {
   499  		// The resource didn't actually create
   500  		d.SetId("")
   501  		return waitErr
   502  	}
   503  
   504  	return resourceComputeInstanceRead(d, meta)
   505  }
   506  
   507  func resourceComputeInstanceRead(d *schema.ResourceData, meta interface{}) error {
   508  	config := meta.(*Config)
   509  
   510  	instance, err := getInstance(config, d);
   511  	if err != nil {
   512  		return err
   513  	}
   514  
   515  	// Synch metadata 
   516  	md := instance.Metadata
   517  
   518  	if err = d.Set("metadata", MetadataFormatSchema(md)); err != nil {
   519  		return fmt.Errorf("Error setting metadata: %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 := getInstance(config, d);
   648  	if err != nil {
   649  		return err
   650  	}
   651  
   652  	// Enable partial mode for the resource since it is possible
   653  	d.Partial(true)
   654  
   655  	// If the Metadata has changed, then update that.
   656  	if d.HasChange("metadata") {
   657  		o, n := d.GetChange("metadata")
   658  
   659  		updateMD := func() error {
   660  			// Reload the instance in the case of a fingerprint mismatch
   661  			instance, err = getInstance(config, d);
   662  			if err != nil {
   663  				return err
   664  			}
   665  
   666  			md := instance.Metadata
   667  
   668  			MetadataUpdate(o.(map[string]interface{}), n.(map[string]interface{}), md)
   669  
   670  			if err != nil {
   671  				return fmt.Errorf("Error updating metadata: %s", err)
   672  			}
   673  			op, err := config.clientCompute.Instances.SetMetadata(
   674  				config.Project, zone, d.Id(), md).Do()
   675  			if err != nil {
   676  				return fmt.Errorf("Error updating metadata: %s", err)
   677  			}
   678  
   679  			opErr := computeOperationWaitZone(config, op, zone, "metadata to update")
   680  			if opErr != nil {
   681  				return opErr
   682  			}
   683  
   684  			d.SetPartial("metadata")
   685  			return nil
   686  		}
   687  
   688  		MetadataRetryWrapper(updateMD)
   689  	}
   690  
   691  	if d.HasChange("tags") {
   692  		tags := resourceInstanceTags(d)
   693  		op, err := config.clientCompute.Instances.SetTags(
   694  			config.Project, zone, d.Id(), tags).Do()
   695  		if err != nil {
   696  			return fmt.Errorf("Error updating tags: %s", err)
   697  		}
   698  
   699  		opErr := computeOperationWaitZone(config, op, zone, "tags to update")
   700  		if opErr != nil {
   701  			return opErr
   702  		}
   703  
   704  		d.SetPartial("tags")
   705  	}
   706  
   707  	networkInterfacesCount := d.Get("network_interface.#").(int)
   708  	if networkInterfacesCount > 0 {
   709  		// Sanity check
   710  		if networkInterfacesCount != len(instance.NetworkInterfaces) {
   711  			return fmt.Errorf("Instance had unexpected number of network interfaces: %d", len(instance.NetworkInterfaces))
   712  		}
   713  		for i := 0; i < networkInterfacesCount; i++ {
   714  			prefix := fmt.Sprintf("network_interface.%d", i)
   715  			instNetworkInterface := instance.NetworkInterfaces[i]
   716  			networkName := d.Get(prefix + ".name").(string)
   717  
   718  			// TODO: This sanity check is broken by #929, disabled for now (by forcing the equality)
   719  			networkName = instNetworkInterface.Name
   720  			// Sanity check
   721  			if networkName != instNetworkInterface.Name {
   722  				return fmt.Errorf("Instance networkInterface had unexpected name: %s", instNetworkInterface.Name)
   723  			}
   724  
   725  			if d.HasChange(prefix + ".access_config") {
   726  
   727  				// TODO: This code deletes then recreates accessConfigs.  This is bad because it may
   728  				// leave the machine inaccessible from either ip if the creation part fails (network
   729  				// timeout etc).  However right now there is a GCE limit of 1 accessConfig so it is
   730  				// the only way to do it.  In future this should be revised to only change what is
   731  				// necessary, and also add before removing.
   732  
   733  				// Delete any accessConfig that currently exists in instNetworkInterface
   734  				for _, ac := range instNetworkInterface.AccessConfigs {
   735  					op, err := config.clientCompute.Instances.DeleteAccessConfig(
   736  						config.Project, zone, d.Id(), ac.Name, networkName).Do()
   737  					if err != nil {
   738  						return fmt.Errorf("Error deleting old access_config: %s", err)
   739  					}
   740  					opErr := computeOperationWaitZone(config, op, zone, "old access_config to delete")
   741  					if opErr != nil {
   742  						return opErr
   743  					}
   744  				}
   745  
   746  				// Create new ones
   747  				accessConfigsCount := d.Get(prefix + ".access_config.#").(int)
   748  				for j := 0; j < accessConfigsCount; j++ {
   749  					acPrefix := fmt.Sprintf("%s.access_config.%d", prefix, j)
   750  					ac := &compute.AccessConfig{
   751  						Type:  "ONE_TO_ONE_NAT",
   752  						NatIP: d.Get(acPrefix + ".nat_ip").(string),
   753  					}
   754  					op, err := config.clientCompute.Instances.AddAccessConfig(
   755  						config.Project, zone, d.Id(), networkName, ac).Do()
   756  					if err != nil {
   757  						return fmt.Errorf("Error adding new access_config: %s", err)
   758  					}
   759  					opErr := computeOperationWaitZone(config, op, zone, "new access_config to add")
   760  					if opErr != nil {
   761  						return opErr
   762  					}
   763  				}
   764  			}
   765  		}
   766  	}
   767  
   768  	// We made it, disable partial mode
   769  	d.Partial(false)
   770  
   771  	return resourceComputeInstanceRead(d, meta)
   772  }
   773  
   774  func resourceComputeInstanceDelete(d *schema.ResourceData, meta interface{}) error {
   775  	config := meta.(*Config)
   776  
   777  	zone := d.Get("zone").(string)
   778  	log.Printf("[INFO] Requesting instance deletion: %s", d.Id())
   779  	op, err := config.clientCompute.Instances.Delete(config.Project, zone, d.Id()).Do()
   780  	if err != nil {
   781  		return fmt.Errorf("Error deleting instance: %s", err)
   782  	}
   783  
   784  	// Wait for the operation to complete
   785  	opErr := computeOperationWaitZone(config, op, zone, "instance to delete")
   786  	if opErr != nil {
   787  		return opErr
   788  	}
   789  
   790  	d.SetId("")
   791  	return nil
   792  }
   793  
   794  func resourceInstanceMetadata(d *schema.ResourceData) (*compute.Metadata, error) {
   795  	m := &compute.Metadata{}
   796  	mdMap := d.Get("metadata").(map[string]interface{})
   797  	_, mapScriptExists := mdMap["startup-script"]
   798  	dScript, dScriptExists := d.GetOk("metadata_startup_script")
   799  	if mapScriptExists && dScriptExists {
   800  		return nil, fmt.Errorf("Not allowed to have both metadata_startup_script and metadata.startup-script")
   801  	}
   802  	if dScriptExists {
   803  		mdMap["startup-script"] = dScript
   804  	}
   805  	if len(mdMap) > 0 {
   806  		m.Items = make([]*compute.MetadataItems, 0, len(mdMap))
   807  		for key, val := range mdMap {
   808  			v := val.(string)
   809  			m.Items = append(m.Items, &compute.MetadataItems{
   810  				Key:   key,
   811  				Value: &v,
   812  			})
   813  		}
   814  
   815  		// Set the fingerprint. If the metadata has never been set before
   816  		// then this will just be blank.
   817  		m.Fingerprint = d.Get("metadata_fingerprint").(string)
   818  	}
   819  
   820  	return m, nil
   821  }
   822  
   823  func resourceInstanceTags(d *schema.ResourceData) *compute.Tags {
   824  	// Calculate the tags
   825  	var tags *compute.Tags
   826  	if v := d.Get("tags"); v != nil {
   827  		vs := v.(*schema.Set)
   828  		tags = new(compute.Tags)
   829  		tags.Items = make([]string, vs.Len())
   830  		for i, v := range vs.List() {
   831  			tags.Items[i] = v.(string)
   832  		}
   833  
   834  		tags.Fingerprint = d.Get("tags_fingerprint").(string)
   835  	}
   836  
   837  	return tags
   838  }