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