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