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