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