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