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