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