github.com/ndarilek/terraform@v0.3.8-0.20150320140257-d3135c1b2bac/builtin/providers/google/resource_compute_instance.go (about)

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