github.com/mkuzmin/terraform@v0.3.7-0.20161118171027-ec4c00ff92a9/builtin/providers/google/resource_compute_instance.go (about)

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