github.com/nicgrayson/terraform@v0.4.3-0.20150415203910-c4de50829380/builtin/providers/google/resource_compute_instance.go (about)

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