github.com/joshgarnett/terraform@v0.5.4-0.20160219181435-92dc20bb3594/builtin/providers/google/resource_compute_instance.go (about)

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