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