github.com/hobbeswalsh/terraform@v0.3.7-0.20150619183303-ad17cf55a0fa/builtin/providers/aws/resource_aws_instance.go (about)

     1  package aws
     2  
     3  import (
     4  	"bytes"
     5  	"crypto/sha1"
     6  	"encoding/base64"
     7  	"encoding/hex"
     8  	"fmt"
     9  	"log"
    10  	"strings"
    11  	"time"
    12  
    13  	"github.com/aws/aws-sdk-go/aws"
    14  	"github.com/aws/aws-sdk-go/aws/awserr"
    15  	"github.com/aws/aws-sdk-go/aws/awsutil"
    16  	"github.com/aws/aws-sdk-go/service/ec2"
    17  	"github.com/hashicorp/terraform/helper/hashcode"
    18  	"github.com/hashicorp/terraform/helper/resource"
    19  	"github.com/hashicorp/terraform/helper/schema"
    20  )
    21  
    22  func resourceAwsInstance() *schema.Resource {
    23  	return &schema.Resource{
    24  		Create: resourceAwsInstanceCreate,
    25  		Read:   resourceAwsInstanceRead,
    26  		Update: resourceAwsInstanceUpdate,
    27  		Delete: resourceAwsInstanceDelete,
    28  
    29  		SchemaVersion: 1,
    30  		MigrateState:  resourceAwsInstanceMigrateState,
    31  
    32  		Schema: map[string]*schema.Schema{
    33  			"ami": &schema.Schema{
    34  				Type:     schema.TypeString,
    35  				Required: true,
    36  				ForceNew: true,
    37  			},
    38  
    39  			"associate_public_ip_address": &schema.Schema{
    40  				Type:     schema.TypeBool,
    41  				ForceNew: true,
    42  				Optional: true,
    43  			},
    44  
    45  			"availability_zone": &schema.Schema{
    46  				Type:     schema.TypeString,
    47  				Optional: true,
    48  				Computed: true,
    49  				ForceNew: true,
    50  			},
    51  
    52  			"placement_group": &schema.Schema{
    53  				Type:     schema.TypeString,
    54  				Optional: true,
    55  				Computed: true,
    56  				ForceNew: true,
    57  			},
    58  
    59  			"instance_type": &schema.Schema{
    60  				Type:     schema.TypeString,
    61  				Required: true,
    62  				ForceNew: true,
    63  			},
    64  
    65  			"key_name": &schema.Schema{
    66  				Type:     schema.TypeString,
    67  				Optional: true,
    68  				ForceNew: true,
    69  				Computed: true,
    70  			},
    71  
    72  			"subnet_id": &schema.Schema{
    73  				Type:     schema.TypeString,
    74  				Optional: true,
    75  				Computed: true,
    76  				ForceNew: true,
    77  			},
    78  
    79  			"private_ip": &schema.Schema{
    80  				Type:     schema.TypeString,
    81  				Optional: true,
    82  				ForceNew: true,
    83  				Computed: true,
    84  			},
    85  
    86  			"source_dest_check": &schema.Schema{
    87  				Type:     schema.TypeBool,
    88  				Optional: true,
    89  				Default:  true,
    90  			},
    91  
    92  			"user_data": &schema.Schema{
    93  				Type:     schema.TypeString,
    94  				Optional: true,
    95  				ForceNew: true,
    96  				StateFunc: func(v interface{}) string {
    97  					switch v.(type) {
    98  					case string:
    99  						hash := sha1.Sum([]byte(v.(string)))
   100  						return hex.EncodeToString(hash[:])
   101  					default:
   102  						return ""
   103  					}
   104  				},
   105  			},
   106  
   107  			"security_groups": &schema.Schema{
   108  				Type:     schema.TypeSet,
   109  				Optional: true,
   110  				Computed: true,
   111  				ForceNew: true,
   112  				Elem:     &schema.Schema{Type: schema.TypeString},
   113  				Set:      schema.HashString,
   114  			},
   115  
   116  			"vpc_security_group_ids": &schema.Schema{
   117  				Type:     schema.TypeSet,
   118  				Optional: true,
   119  				Computed: true,
   120  				Elem:     &schema.Schema{Type: schema.TypeString},
   121  				Set: func(v interface{}) int {
   122  					return hashcode.String(v.(string))
   123  				},
   124  			},
   125  
   126  			"public_dns": &schema.Schema{
   127  				Type:     schema.TypeString,
   128  				Computed: true,
   129  			},
   130  
   131  			"public_ip": &schema.Schema{
   132  				Type:     schema.TypeString,
   133  				Computed: true,
   134  			},
   135  
   136  			"private_dns": &schema.Schema{
   137  				Type:     schema.TypeString,
   138  				Computed: true,
   139  			},
   140  
   141  			"ebs_optimized": &schema.Schema{
   142  				Type:     schema.TypeBool,
   143  				Optional: true,
   144  			},
   145  
   146  			"disable_api_termination": &schema.Schema{
   147  				Type:     schema.TypeBool,
   148  				Optional: true,
   149  			},
   150  
   151  			"iam_instance_profile": &schema.Schema{
   152  				Type:     schema.TypeString,
   153  				ForceNew: true,
   154  				Optional: true,
   155  			},
   156  
   157  			"tenancy": &schema.Schema{
   158  				Type:     schema.TypeString,
   159  				Optional: true,
   160  				Computed: true,
   161  				ForceNew: true,
   162  			},
   163  
   164  			"tags": tagsSchema(),
   165  
   166  			"block_device": &schema.Schema{
   167  				Type:     schema.TypeMap,
   168  				Optional: true,
   169  				Removed:  "Split out into three sub-types; see Changelog and Docs",
   170  			},
   171  
   172  			"ebs_block_device": &schema.Schema{
   173  				Type:     schema.TypeSet,
   174  				Optional: true,
   175  				Computed: true,
   176  				Elem: &schema.Resource{
   177  					Schema: map[string]*schema.Schema{
   178  						"delete_on_termination": &schema.Schema{
   179  							Type:     schema.TypeBool,
   180  							Optional: true,
   181  							Default:  true,
   182  							ForceNew: true,
   183  						},
   184  
   185  						"device_name": &schema.Schema{
   186  							Type:     schema.TypeString,
   187  							Required: true,
   188  							ForceNew: true,
   189  						},
   190  
   191  						"encrypted": &schema.Schema{
   192  							Type:     schema.TypeBool,
   193  							Optional: true,
   194  							Computed: true,
   195  							ForceNew: true,
   196  						},
   197  
   198  						"iops": &schema.Schema{
   199  							Type:     schema.TypeInt,
   200  							Optional: true,
   201  							Computed: true,
   202  							ForceNew: true,
   203  						},
   204  
   205  						"snapshot_id": &schema.Schema{
   206  							Type:     schema.TypeString,
   207  							Optional: true,
   208  							Computed: true,
   209  							ForceNew: true,
   210  						},
   211  
   212  						"volume_size": &schema.Schema{
   213  							Type:     schema.TypeInt,
   214  							Optional: true,
   215  							Computed: true,
   216  							ForceNew: true,
   217  						},
   218  
   219  						"volume_type": &schema.Schema{
   220  							Type:     schema.TypeString,
   221  							Optional: true,
   222  							Computed: true,
   223  							ForceNew: true,
   224  						},
   225  					},
   226  				},
   227  				Set: func(v interface{}) int {
   228  					var buf bytes.Buffer
   229  					m := v.(map[string]interface{})
   230  					buf.WriteString(fmt.Sprintf("%s-", m["device_name"].(string)))
   231  					buf.WriteString(fmt.Sprintf("%s-", m["snapshot_id"].(string)))
   232  					return hashcode.String(buf.String())
   233  				},
   234  			},
   235  
   236  			"ephemeral_block_device": &schema.Schema{
   237  				Type:     schema.TypeSet,
   238  				Optional: true,
   239  				Computed: true,
   240  				ForceNew: true,
   241  				Elem: &schema.Resource{
   242  					Schema: map[string]*schema.Schema{
   243  						"device_name": &schema.Schema{
   244  							Type:     schema.TypeString,
   245  							Required: true,
   246  						},
   247  
   248  						"virtual_name": &schema.Schema{
   249  							Type:     schema.TypeString,
   250  							Required: true,
   251  						},
   252  					},
   253  				},
   254  				Set: func(v interface{}) int {
   255  					var buf bytes.Buffer
   256  					m := v.(map[string]interface{})
   257  					buf.WriteString(fmt.Sprintf("%s-", m["device_name"].(string)))
   258  					buf.WriteString(fmt.Sprintf("%s-", m["virtual_name"].(string)))
   259  					return hashcode.String(buf.String())
   260  				},
   261  			},
   262  
   263  			"root_block_device": &schema.Schema{
   264  				// TODO: This is a set because we don't support singleton
   265  				//       sub-resources today. We'll enforce that the set only ever has
   266  				//       length zero or one below. When TF gains support for
   267  				//       sub-resources this can be converted.
   268  				Type:     schema.TypeSet,
   269  				Optional: true,
   270  				Computed: true,
   271  				Elem: &schema.Resource{
   272  					// "You can only modify the volume size, volume type, and Delete on
   273  					// Termination flag on the block device mapping entry for the root
   274  					// device volume." - bit.ly/ec2bdmap
   275  					Schema: map[string]*schema.Schema{
   276  						"delete_on_termination": &schema.Schema{
   277  							Type:     schema.TypeBool,
   278  							Optional: true,
   279  							Default:  true,
   280  							ForceNew: true,
   281  						},
   282  
   283  						"iops": &schema.Schema{
   284  							Type:     schema.TypeInt,
   285  							Optional: true,
   286  							Computed: true,
   287  							ForceNew: true,
   288  						},
   289  
   290  						"volume_size": &schema.Schema{
   291  							Type:     schema.TypeInt,
   292  							Optional: true,
   293  							Computed: true,
   294  							ForceNew: true,
   295  						},
   296  
   297  						"volume_type": &schema.Schema{
   298  							Type:     schema.TypeString,
   299  							Optional: true,
   300  							Computed: true,
   301  							ForceNew: true,
   302  						},
   303  					},
   304  				},
   305  				Set: func(v interface{}) int {
   306  					// there can be only one root device; no need to hash anything
   307  					return 0
   308  				},
   309  			},
   310  		},
   311  	}
   312  }
   313  
   314  func resourceAwsInstanceCreate(d *schema.ResourceData, meta interface{}) error {
   315  	conn := meta.(*AWSClient).ec2conn
   316  
   317  	instanceOpts, err := buildAwsInstanceOpts(d, meta)
   318  	if err != nil {
   319  		return err
   320  	}
   321  
   322  	// Build the creation struct
   323  	runOpts := &ec2.RunInstancesInput{
   324  		BlockDeviceMappings:   instanceOpts.BlockDeviceMappings,
   325  		DisableAPITermination: instanceOpts.DisableAPITermination,
   326  		EBSOptimized:          instanceOpts.EBSOptimized,
   327  		IAMInstanceProfile:    instanceOpts.IAMInstanceProfile,
   328  		ImageID:               instanceOpts.ImageID,
   329  		InstanceType:          instanceOpts.InstanceType,
   330  		KeyName:               instanceOpts.KeyName,
   331  		MaxCount:              aws.Long(int64(1)),
   332  		MinCount:              aws.Long(int64(1)),
   333  		NetworkInterfaces:     instanceOpts.NetworkInterfaces,
   334  		Placement:             instanceOpts.Placement,
   335  		PrivateIPAddress:      instanceOpts.PrivateIPAddress,
   336  		SecurityGroupIDs:      instanceOpts.SecurityGroupIDs,
   337  		SecurityGroups:        instanceOpts.SecurityGroups,
   338  		SubnetID:              instanceOpts.SubnetID,
   339  		UserData:              instanceOpts.UserData64,
   340  	}
   341  
   342  	// Create the instance
   343  	log.Printf("[DEBUG] Run configuration: %s", awsutil.StringValue(runOpts))
   344  
   345  	var runResp *ec2.Reservation
   346  	for i := 0; i < 5; i++ {
   347  		runResp, err = conn.RunInstances(runOpts)
   348  		if awsErr, ok := err.(awserr.Error); ok {
   349  			// IAM profiles can take ~10 seconds to propagate in AWS:
   350  			//  http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/iam-roles-for-amazon-ec2.html#launch-instance-with-role-console
   351  			if awsErr.Code() == "InvalidParameterValue" && strings.Contains(awsErr.Message(), "Invalid IAM Instance Profile") {
   352  				log.Printf("[DEBUG] Invalid IAM Instance Profile referenced, retrying...")
   353  				time.Sleep(2 * time.Second)
   354  				continue
   355  			}
   356  		}
   357  		break
   358  	}
   359  	if err != nil {
   360  		return fmt.Errorf("Error launching source instance: %s", err)
   361  	}
   362  
   363  	instance := runResp.Instances[0]
   364  	log.Printf("[INFO] Instance ID: %s", *instance.InstanceID)
   365  
   366  	// Store the resulting ID so we can look this up later
   367  	d.SetId(*instance.InstanceID)
   368  
   369  	// Wait for the instance to become running so we can get some attributes
   370  	// that aren't available until later.
   371  	log.Printf(
   372  		"[DEBUG] Waiting for instance (%s) to become running",
   373  		*instance.InstanceID)
   374  
   375  	stateConf := &resource.StateChangeConf{
   376  		Pending:    []string{"pending"},
   377  		Target:     "running",
   378  		Refresh:    InstanceStateRefreshFunc(conn, *instance.InstanceID),
   379  		Timeout:    10 * time.Minute,
   380  		Delay:      10 * time.Second,
   381  		MinTimeout: 3 * time.Second,
   382  	}
   383  
   384  	instanceRaw, err := stateConf.WaitForState()
   385  	if err != nil {
   386  		return fmt.Errorf(
   387  			"Error waiting for instance (%s) to become ready: %s",
   388  			*instance.InstanceID, err)
   389  	}
   390  
   391  	instance = instanceRaw.(*ec2.Instance)
   392  
   393  	// Initialize the connection info
   394  	if instance.PublicIPAddress != nil {
   395  		d.SetConnInfo(map[string]string{
   396  			"type": "ssh",
   397  			"host": *instance.PublicIPAddress,
   398  		})
   399  	} else if instance.PrivateIPAddress != nil {
   400  		d.SetConnInfo(map[string]string{
   401  			"type": "ssh",
   402  			"host": *instance.PrivateIPAddress,
   403  		})
   404  	}
   405  
   406  	// Set our attributes
   407  	if err := resourceAwsInstanceRead(d, meta); err != nil {
   408  		return err
   409  	}
   410  
   411  	// Update if we need to
   412  	return resourceAwsInstanceUpdate(d, meta)
   413  }
   414  
   415  func resourceAwsInstanceRead(d *schema.ResourceData, meta interface{}) error {
   416  	conn := meta.(*AWSClient).ec2conn
   417  
   418  	resp, err := conn.DescribeInstances(&ec2.DescribeInstancesInput{
   419  		InstanceIDs: []*string{aws.String(d.Id())},
   420  	})
   421  	if err != nil {
   422  		// If the instance was not found, return nil so that we can show
   423  		// that the instance is gone.
   424  		if ec2err, ok := err.(awserr.Error); ok && ec2err.Code() == "InvalidInstanceID.NotFound" {
   425  			d.SetId("")
   426  			return nil
   427  		}
   428  
   429  		// Some other error, report it
   430  		return err
   431  	}
   432  
   433  	// If nothing was found, then return no state
   434  	if len(resp.Reservations) == 0 {
   435  		d.SetId("")
   436  		return nil
   437  	}
   438  
   439  	instance := resp.Reservations[0].Instances[0]
   440  
   441  	// If the instance is terminated, then it is gone
   442  	if *instance.State.Name == "terminated" {
   443  		d.SetId("")
   444  		return nil
   445  	}
   446  
   447  	if instance.Placement != nil {
   448  		d.Set("availability_zone", instance.Placement.AvailabilityZone)
   449  	}
   450  	if instance.Placement.Tenancy != nil {
   451  		d.Set("tenancy", instance.Placement.Tenancy)
   452  	}
   453  
   454  	d.Set("key_name", instance.KeyName)
   455  	d.Set("public_dns", instance.PublicDNSName)
   456  	d.Set("public_ip", instance.PublicIPAddress)
   457  	d.Set("private_dns", instance.PrivateDNSName)
   458  	d.Set("private_ip", instance.PrivateIPAddress)
   459  	if len(instance.NetworkInterfaces) > 0 {
   460  		d.Set("subnet_id", instance.NetworkInterfaces[0].SubnetID)
   461  	} else {
   462  		d.Set("subnet_id", instance.SubnetID)
   463  	}
   464  	d.Set("ebs_optimized", instance.EBSOptimized)
   465  	d.Set("tags", tagsToMap(instance.Tags))
   466  
   467  	// Determine whether we're referring to security groups with
   468  	// IDs or names. We use a heuristic to figure this out. By default,
   469  	// we use IDs if we're in a VPC. However, if we previously had an
   470  	// all-name list of security groups, we use names. Or, if we had any
   471  	// IDs, we use IDs.
   472  	useID := instance.SubnetID != nil && *instance.SubnetID != ""
   473  	if v := d.Get("security_groups"); v != nil {
   474  		match := useID
   475  		sgs := v.(*schema.Set).List()
   476  		if len(sgs) > 0 {
   477  			match = false
   478  			for _, v := range v.(*schema.Set).List() {
   479  				if strings.HasPrefix(v.(string), "sg-") {
   480  					match = true
   481  					break
   482  				}
   483  			}
   484  		}
   485  
   486  		useID = match
   487  	}
   488  
   489  	// Build up the security groups
   490  	sgs := make([]string, 0, len(instance.SecurityGroups))
   491  	if useID {
   492  		for _, sg := range instance.SecurityGroups {
   493  			sgs = append(sgs, *sg.GroupID)
   494  		}
   495  		log.Printf("[DEBUG] Setting Security Group IDs: %#v", sgs)
   496  		if err := d.Set("vpc_security_group_ids", sgs); err != nil {
   497  			return err
   498  		}
   499  	} else {
   500  		for _, sg := range instance.SecurityGroups {
   501  			sgs = append(sgs, *sg.GroupName)
   502  		}
   503  		log.Printf("[DEBUG] Setting Security Group Names: %#v", sgs)
   504  		if err := d.Set("security_groups", sgs); err != nil {
   505  			return err
   506  		}
   507  	}
   508  
   509  	if err := readBlockDevices(d, instance, conn); err != nil {
   510  		return err
   511  	}
   512  
   513  	return nil
   514  }
   515  
   516  func resourceAwsInstanceUpdate(d *schema.ResourceData, meta interface{}) error {
   517  	conn := meta.(*AWSClient).ec2conn
   518  
   519  	d.Partial(true)
   520  	if err := setTags(conn, d); err != nil {
   521  		return err
   522  	} else {
   523  		d.SetPartial("tags")
   524  	}
   525  
   526  	// SourceDestCheck can only be set on VPC instances
   527  	if d.Get("subnet_id").(string) != "" {
   528  		log.Printf("[INFO] Modifying instance %s", d.Id())
   529  		_, err := conn.ModifyInstanceAttribute(&ec2.ModifyInstanceAttributeInput{
   530  			InstanceID: aws.String(d.Id()),
   531  			SourceDestCheck: &ec2.AttributeBooleanValue{
   532  				Value: aws.Boolean(d.Get("source_dest_check").(bool)),
   533  			},
   534  		})
   535  		if err != nil {
   536  			return err
   537  		}
   538  	}
   539  
   540  	if d.HasChange("vpc_security_group_ids") {
   541  		var groups []*string
   542  		if v := d.Get("vpc_security_group_ids").(*schema.Set); v.Len() > 0 {
   543  			for _, v := range v.List() {
   544  				groups = append(groups, aws.String(v.(string)))
   545  			}
   546  		}
   547  		_, err := conn.ModifyInstanceAttribute(&ec2.ModifyInstanceAttributeInput{
   548  			InstanceID: aws.String(d.Id()),
   549  			Groups:     groups,
   550  		})
   551  		if err != nil {
   552  			return err
   553  		}
   554  	}
   555  
   556  	if d.HasChange("disable_api_termination") {
   557  		_, err := conn.ModifyInstanceAttribute(&ec2.ModifyInstanceAttributeInput{
   558  			InstanceID: aws.String(d.Id()),
   559  			DisableAPITermination: &ec2.AttributeBooleanValue{
   560  				Value: aws.Boolean(d.Get("disable_api_termination").(bool)),
   561  			},
   562  		})
   563  		if err != nil {
   564  			return err
   565  		}
   566  	}
   567  
   568  	// TODO(mitchellh): wait for the attributes we modified to
   569  	// persist the change...
   570  
   571  	d.Partial(false)
   572  
   573  	return resourceAwsInstanceRead(d, meta)
   574  }
   575  
   576  func resourceAwsInstanceDelete(d *schema.ResourceData, meta interface{}) error {
   577  	conn := meta.(*AWSClient).ec2conn
   578  
   579  	if err := awsTerminateInstance(conn, d.Id()); err != nil {
   580  		return err
   581  	}
   582  
   583  	d.SetId("")
   584  	return nil
   585  }
   586  
   587  // InstanceStateRefreshFunc returns a resource.StateRefreshFunc that is used to watch
   588  // an EC2 instance.
   589  func InstanceStateRefreshFunc(conn *ec2.EC2, instanceID string) resource.StateRefreshFunc {
   590  	return func() (interface{}, string, error) {
   591  		resp, err := conn.DescribeInstances(&ec2.DescribeInstancesInput{
   592  			InstanceIDs: []*string{aws.String(instanceID)},
   593  		})
   594  		if err != nil {
   595  			if ec2err, ok := err.(awserr.Error); ok && ec2err.Code() == "InvalidInstanceID.NotFound" {
   596  				// Set this to nil as if we didn't find anything.
   597  				resp = nil
   598  			} else {
   599  				log.Printf("Error on InstanceStateRefresh: %s", err)
   600  				return nil, "", err
   601  			}
   602  		}
   603  
   604  		if resp == nil || len(resp.Reservations) == 0 || len(resp.Reservations[0].Instances) == 0 {
   605  			// Sometimes AWS just has consistency issues and doesn't see
   606  			// our instance yet. Return an empty state.
   607  			return nil, "", nil
   608  		}
   609  
   610  		i := resp.Reservations[0].Instances[0]
   611  		return i, *i.State.Name, nil
   612  	}
   613  }
   614  
   615  func readBlockDevices(d *schema.ResourceData, instance *ec2.Instance, conn *ec2.EC2) error {
   616  	ibds, err := readBlockDevicesFromInstance(instance, conn)
   617  	if err != nil {
   618  		return err
   619  	}
   620  
   621  	if err := d.Set("ebs_block_device", ibds["ebs"]); err != nil {
   622  		return err
   623  	}
   624  	if ibds["root"] != nil {
   625  		if err := d.Set("root_block_device", []interface{}{ibds["root"]}); err != nil {
   626  			return err
   627  		}
   628  	}
   629  
   630  	return nil
   631  }
   632  
   633  func readBlockDevicesFromInstance(instance *ec2.Instance, conn *ec2.EC2) (map[string]interface{}, error) {
   634  	blockDevices := make(map[string]interface{})
   635  	blockDevices["ebs"] = make([]map[string]interface{}, 0)
   636  	blockDevices["root"] = nil
   637  
   638  	instanceBlockDevices := make(map[string]*ec2.InstanceBlockDeviceMapping)
   639  	for _, bd := range instance.BlockDeviceMappings {
   640  		if bd.EBS != nil {
   641  			instanceBlockDevices[*(bd.EBS.VolumeID)] = bd
   642  		}
   643  	}
   644  
   645  	if len(instanceBlockDevices) == 0 {
   646  		return nil, nil
   647  	}
   648  
   649  	volIDs := make([]*string, 0, len(instanceBlockDevices))
   650  	for volID := range instanceBlockDevices {
   651  		volIDs = append(volIDs, aws.String(volID))
   652  	}
   653  
   654  	// Need to call DescribeVolumes to get volume_size and volume_type for each
   655  	// EBS block device
   656  	volResp, err := conn.DescribeVolumes(&ec2.DescribeVolumesInput{
   657  		VolumeIDs: volIDs,
   658  	})
   659  	if err != nil {
   660  		return nil, err
   661  	}
   662  
   663  	for _, vol := range volResp.Volumes {
   664  		instanceBd := instanceBlockDevices[*vol.VolumeID]
   665  		bd := make(map[string]interface{})
   666  
   667  		if instanceBd.EBS != nil && instanceBd.EBS.DeleteOnTermination != nil {
   668  			bd["delete_on_termination"] = *instanceBd.EBS.DeleteOnTermination
   669  		}
   670  		if vol.Size != nil {
   671  			bd["volume_size"] = *vol.Size
   672  		}
   673  		if vol.VolumeType != nil {
   674  			bd["volume_type"] = *vol.VolumeType
   675  		}
   676  		if vol.IOPS != nil {
   677  			bd["iops"] = *vol.IOPS
   678  		}
   679  
   680  		if blockDeviceIsRoot(instanceBd, instance) {
   681  			blockDevices["root"] = bd
   682  		} else {
   683  			if instanceBd.DeviceName != nil {
   684  				bd["device_name"] = *instanceBd.DeviceName
   685  			}
   686  			if vol.Encrypted != nil {
   687  				bd["encrypted"] = *vol.Encrypted
   688  			}
   689  			if vol.SnapshotID != nil {
   690  				bd["snapshot_id"] = *vol.SnapshotID
   691  			}
   692  
   693  			blockDevices["ebs"] = append(blockDevices["ebs"].([]map[string]interface{}), bd)
   694  		}
   695  	}
   696  
   697  	return blockDevices, nil
   698  }
   699  
   700  func blockDeviceIsRoot(bd *ec2.InstanceBlockDeviceMapping, instance *ec2.Instance) bool {
   701  	return (bd.DeviceName != nil &&
   702  		instance.RootDeviceName != nil &&
   703  		*bd.DeviceName == *instance.RootDeviceName)
   704  }
   705  
   706  func fetchRootDeviceName(ami string, conn *ec2.EC2) (*string, error) {
   707  	if ami == "" {
   708  		return nil, fmt.Errorf("Cannot fetch root device name for blank AMI ID.")
   709  	}
   710  
   711  	log.Printf("[DEBUG] Describing AMI %q to get root block device name", ami)
   712  	req := &ec2.DescribeImagesInput{ImageIDs: []*string{aws.String(ami)}}
   713  	if res, err := conn.DescribeImages(req); err == nil {
   714  		if len(res.Images) == 1 {
   715  			return res.Images[0].RootDeviceName, nil
   716  		} else if len(res.Images) == 0 {
   717  			return nil, nil
   718  		} else {
   719  			return nil, fmt.Errorf("Expected 1 AMI for ID: %s, got: %#v", ami, res.Images)
   720  		}
   721  	} else {
   722  		return nil, err
   723  	}
   724  }
   725  
   726  func readBlockDeviceMappingsFromConfig(
   727  	d *schema.ResourceData, conn *ec2.EC2) ([]*ec2.BlockDeviceMapping, error) {
   728  	blockDevices := make([]*ec2.BlockDeviceMapping, 0)
   729  
   730  	if v, ok := d.GetOk("ebs_block_device"); ok {
   731  		vL := v.(*schema.Set).List()
   732  		for _, v := range vL {
   733  			bd := v.(map[string]interface{})
   734  			ebs := &ec2.EBSBlockDevice{
   735  				DeleteOnTermination: aws.Boolean(bd["delete_on_termination"].(bool)),
   736  			}
   737  
   738  			if v, ok := bd["snapshot_id"].(string); ok && v != "" {
   739  				ebs.SnapshotID = aws.String(v)
   740  			}
   741  
   742  			if v, ok := bd["encrypted"].(bool); ok && v {
   743  				ebs.Encrypted = aws.Boolean(v)
   744  			}
   745  
   746  			if v, ok := bd["volume_size"].(int); ok && v != 0 {
   747  				ebs.VolumeSize = aws.Long(int64(v))
   748  			}
   749  
   750  			if v, ok := bd["volume_type"].(string); ok && v != "" {
   751  				ebs.VolumeType = aws.String(v)
   752  			}
   753  
   754  			if v, ok := bd["iops"].(int); ok && v > 0 {
   755  				ebs.IOPS = aws.Long(int64(v))
   756  			}
   757  
   758  			blockDevices = append(blockDevices, &ec2.BlockDeviceMapping{
   759  				DeviceName: aws.String(bd["device_name"].(string)),
   760  				EBS:        ebs,
   761  			})
   762  		}
   763  	}
   764  
   765  	if v, ok := d.GetOk("ephemeral_block_device"); ok {
   766  		vL := v.(*schema.Set).List()
   767  		for _, v := range vL {
   768  			bd := v.(map[string]interface{})
   769  			blockDevices = append(blockDevices, &ec2.BlockDeviceMapping{
   770  				DeviceName:  aws.String(bd["device_name"].(string)),
   771  				VirtualName: aws.String(bd["virtual_name"].(string)),
   772  			})
   773  		}
   774  	}
   775  
   776  	if v, ok := d.GetOk("root_block_device"); ok {
   777  		vL := v.(*schema.Set).List()
   778  		if len(vL) > 1 {
   779  			return nil, fmt.Errorf("Cannot specify more than one root_block_device.")
   780  		}
   781  		for _, v := range vL {
   782  			bd := v.(map[string]interface{})
   783  			ebs := &ec2.EBSBlockDevice{
   784  				DeleteOnTermination: aws.Boolean(bd["delete_on_termination"].(bool)),
   785  			}
   786  
   787  			if v, ok := bd["volume_size"].(int); ok && v != 0 {
   788  				ebs.VolumeSize = aws.Long(int64(v))
   789  			}
   790  
   791  			if v, ok := bd["volume_type"].(string); ok && v != "" {
   792  				ebs.VolumeType = aws.String(v)
   793  			}
   794  
   795  			if v, ok := bd["iops"].(int); ok && v > 0 {
   796  				ebs.IOPS = aws.Long(int64(v))
   797  			}
   798  
   799  			if dn, err := fetchRootDeviceName(d.Get("ami").(string), conn); err == nil {
   800  				if dn == nil {
   801  					return nil, fmt.Errorf(
   802  						"Expected 1 AMI for ID: %s, got none",
   803  						d.Get("ami").(string))
   804  				}
   805  
   806  				blockDevices = append(blockDevices, &ec2.BlockDeviceMapping{
   807  					DeviceName: dn,
   808  					EBS:        ebs,
   809  				})
   810  			} else {
   811  				return nil, err
   812  			}
   813  		}
   814  	}
   815  
   816  	return blockDevices, nil
   817  }
   818  
   819  type awsInstanceOpts struct {
   820  	BlockDeviceMappings   []*ec2.BlockDeviceMapping
   821  	DisableAPITermination *bool
   822  	EBSOptimized          *bool
   823  	IAMInstanceProfile    *ec2.IAMInstanceProfileSpecification
   824  	ImageID               *string
   825  	InstanceType          *string
   826  	KeyName               *string
   827  	NetworkInterfaces     []*ec2.InstanceNetworkInterfaceSpecification
   828  	Placement             *ec2.Placement
   829  	PrivateIPAddress      *string
   830  	SecurityGroupIDs      []*string
   831  	SecurityGroups        []*string
   832  	SpotPlacement         *ec2.SpotPlacement
   833  	SubnetID              *string
   834  	UserData64            *string
   835  }
   836  
   837  func buildAwsInstanceOpts(
   838  	d *schema.ResourceData, meta interface{}) (*awsInstanceOpts, error) {
   839  	conn := meta.(*AWSClient).ec2conn
   840  
   841  	opts := &awsInstanceOpts{
   842  		DisableAPITermination: aws.Boolean(d.Get("disable_api_termination").(bool)),
   843  		EBSOptimized:          aws.Boolean(d.Get("ebs_optimized").(bool)),
   844  		ImageID:               aws.String(d.Get("ami").(string)),
   845  		InstanceType:          aws.String(d.Get("instance_type").(string)),
   846  	}
   847  
   848  	opts.IAMInstanceProfile = &ec2.IAMInstanceProfileSpecification{
   849  		Name: aws.String(d.Get("iam_instance_profile").(string)),
   850  	}
   851  
   852  	opts.UserData64 = aws.String(
   853  		base64.StdEncoding.EncodeToString([]byte(d.Get("user_data").(string))))
   854  
   855  	// check for non-default Subnet, and cast it to a String
   856  	subnet, hasSubnet := d.GetOk("subnet_id")
   857  	subnetID := subnet.(string)
   858  
   859  	// Placement is used for aws_instance; SpotPlacement is used for
   860  	// aws_spot_instance_request. They represent the same data. :-|
   861  	opts.Placement = &ec2.Placement{
   862  		AvailabilityZone: aws.String(d.Get("availability_zone").(string)),
   863  		GroupName:        aws.String(d.Get("placement_group").(string)),
   864  	}
   865  
   866  	opts.SpotPlacement = &ec2.SpotPlacement{
   867  		AvailabilityZone: aws.String(d.Get("availability_zone").(string)),
   868  		GroupName:        aws.String(d.Get("placement_group").(string)),
   869  	}
   870  
   871  	if v := d.Get("tenancy").(string); v != "" {
   872  		opts.Placement.Tenancy = aws.String(v)
   873  	}
   874  
   875  	associatePublicIPAddress := d.Get("associate_public_ip_address").(bool)
   876  
   877  	var groups []*string
   878  	if v := d.Get("security_groups"); v != nil {
   879  		// Security group names.
   880  		// For a nondefault VPC, you must use security group IDs instead.
   881  		// See http://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_RunInstances.html
   882  		sgs := v.(*schema.Set).List()
   883  		if len(sgs) > 0 && hasSubnet {
   884  			log.Printf("[WARN] Deprecated. Attempting to use 'security_groups' within a VPC instance. Use 'vpc_security_group_ids' instead.")
   885  		}
   886  		for _, v := range sgs {
   887  			str := v.(string)
   888  			groups = append(groups, aws.String(str))
   889  		}
   890  	}
   891  
   892  	if hasSubnet && associatePublicIPAddress {
   893  		// If we have a non-default VPC / Subnet specified, we can flag
   894  		// AssociatePublicIpAddress to get a Public IP assigned. By default these are not provided.
   895  		// You cannot specify both SubnetId and the NetworkInterface.0.* parameters though, otherwise
   896  		// you get: Network interfaces and an instance-level subnet ID may not be specified on the same request
   897  		// You also need to attach Security Groups to the NetworkInterface instead of the instance,
   898  		// to avoid: Network interfaces and an instance-level security groups may not be specified on
   899  		// the same request
   900  		ni := &ec2.InstanceNetworkInterfaceSpecification{
   901  			AssociatePublicIPAddress: aws.Boolean(associatePublicIPAddress),
   902  			DeviceIndex:              aws.Long(int64(0)),
   903  			SubnetID:                 aws.String(subnetID),
   904  			Groups:                   groups,
   905  		}
   906  
   907  		if v, ok := d.GetOk("private_ip"); ok {
   908  			ni.PrivateIPAddress = aws.String(v.(string))
   909  		}
   910  
   911  		if v := d.Get("vpc_security_group_ids").(*schema.Set); v.Len() > 0 {
   912  			for _, v := range v.List() {
   913  				ni.Groups = append(ni.Groups, aws.String(v.(string)))
   914  			}
   915  		}
   916  
   917  		opts.NetworkInterfaces = []*ec2.InstanceNetworkInterfaceSpecification{ni}
   918  	} else {
   919  		if subnetID != "" {
   920  			opts.SubnetID = aws.String(subnetID)
   921  		}
   922  
   923  		if v, ok := d.GetOk("private_ip"); ok {
   924  			opts.PrivateIPAddress = aws.String(v.(string))
   925  		}
   926  		if opts.SubnetID != nil &&
   927  			*opts.SubnetID != "" {
   928  			opts.SecurityGroupIDs = groups
   929  		} else {
   930  			opts.SecurityGroups = groups
   931  		}
   932  
   933  		if v := d.Get("vpc_security_group_ids").(*schema.Set); v.Len() > 0 {
   934  			for _, v := range v.List() {
   935  				opts.SecurityGroupIDs = append(opts.SecurityGroupIDs, aws.String(v.(string)))
   936  			}
   937  		}
   938  	}
   939  
   940  	if v, ok := d.GetOk("key_name"); ok {
   941  		opts.KeyName = aws.String(v.(string))
   942  	}
   943  
   944  	blockDevices, err := readBlockDeviceMappingsFromConfig(d, conn)
   945  	if err != nil {
   946  		return nil, err
   947  	}
   948  	if len(blockDevices) > 0 {
   949  		opts.BlockDeviceMappings = blockDevices
   950  	}
   951  
   952  	return opts, nil
   953  }
   954  
   955  func awsTerminateInstance(conn *ec2.EC2, id string) error {
   956  	log.Printf("[INFO] Terminating instance: %s", id)
   957  	req := &ec2.TerminateInstancesInput{
   958  		InstanceIDs: []*string{aws.String(id)},
   959  	}
   960  	if _, err := conn.TerminateInstances(req); err != nil {
   961  		return fmt.Errorf("Error terminating instance: %s", err)
   962  	}
   963  
   964  	log.Printf("[DEBUG] Waiting for instance (%s) to become terminated", id)
   965  
   966  	stateConf := &resource.StateChangeConf{
   967  		Pending:    []string{"pending", "running", "shutting-down", "stopped", "stopping"},
   968  		Target:     "terminated",
   969  		Refresh:    InstanceStateRefreshFunc(conn, id),
   970  		Timeout:    10 * time.Minute,
   971  		Delay:      10 * time.Second,
   972  		MinTimeout: 3 * time.Second,
   973  	}
   974  
   975  	_, err := stateConf.WaitForState()
   976  	if err != nil {
   977  		return fmt.Errorf(
   978  			"Error waiting for instance (%s) to terminate: %s", id, err)
   979  	}
   980  
   981  	return nil
   982  }