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