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