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