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