github.com/turtlemonvh/terraform@v0.6.9-0.20151204001754-8e40b6b855e8/builtin/providers/google/resource_compute_instance.go (about)

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