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