github.com/tkak/terraform@v0.5.4-0.20150712180941-7f738dc27225/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  	// TODO(mitchellh): wait for the attributes we modified to
   583  	// persist the change...
   584  
   585  	d.Partial(false)
   586  
   587  	return resourceAwsInstanceRead(d, meta)
   588  }
   589  
   590  func resourceAwsInstanceDelete(d *schema.ResourceData, meta interface{}) error {
   591  	conn := meta.(*AWSClient).ec2conn
   592  
   593  	if err := awsTerminateInstance(conn, d.Id()); err != nil {
   594  		return err
   595  	}
   596  
   597  	d.SetId("")
   598  	return nil
   599  }
   600  
   601  // InstanceStateRefreshFunc returns a resource.StateRefreshFunc that is used to watch
   602  // an EC2 instance.
   603  func InstanceStateRefreshFunc(conn *ec2.EC2, instanceID string) resource.StateRefreshFunc {
   604  	return func() (interface{}, string, error) {
   605  		resp, err := conn.DescribeInstances(&ec2.DescribeInstancesInput{
   606  			InstanceIDs: []*string{aws.String(instanceID)},
   607  		})
   608  		if err != nil {
   609  			if ec2err, ok := err.(awserr.Error); ok && ec2err.Code() == "InvalidInstanceID.NotFound" {
   610  				// Set this to nil as if we didn't find anything.
   611  				resp = nil
   612  			} else {
   613  				log.Printf("Error on InstanceStateRefresh: %s", err)
   614  				return nil, "", err
   615  			}
   616  		}
   617  
   618  		if resp == nil || len(resp.Reservations) == 0 || len(resp.Reservations[0].Instances) == 0 {
   619  			// Sometimes AWS just has consistency issues and doesn't see
   620  			// our instance yet. Return an empty state.
   621  			return nil, "", nil
   622  		}
   623  
   624  		i := resp.Reservations[0].Instances[0]
   625  		return i, *i.State.Name, nil
   626  	}
   627  }
   628  
   629  func readBlockDevices(d *schema.ResourceData, instance *ec2.Instance, conn *ec2.EC2) error {
   630  	ibds, err := readBlockDevicesFromInstance(instance, conn)
   631  	if err != nil {
   632  		return err
   633  	}
   634  
   635  	if err := d.Set("ebs_block_device", ibds["ebs"]); err != nil {
   636  		return err
   637  	}
   638  	if ibds["root"] != nil {
   639  		if err := d.Set("root_block_device", []interface{}{ibds["root"]}); err != nil {
   640  			return err
   641  		}
   642  	}
   643  
   644  	return nil
   645  }
   646  
   647  func readBlockDevicesFromInstance(instance *ec2.Instance, conn *ec2.EC2) (map[string]interface{}, error) {
   648  	blockDevices := make(map[string]interface{})
   649  	blockDevices["ebs"] = make([]map[string]interface{}, 0)
   650  	blockDevices["root"] = nil
   651  
   652  	instanceBlockDevices := make(map[string]*ec2.InstanceBlockDeviceMapping)
   653  	for _, bd := range instance.BlockDeviceMappings {
   654  		if bd.EBS != nil {
   655  			instanceBlockDevices[*(bd.EBS.VolumeID)] = bd
   656  		}
   657  	}
   658  
   659  	if len(instanceBlockDevices) == 0 {
   660  		return nil, nil
   661  	}
   662  
   663  	volIDs := make([]*string, 0, len(instanceBlockDevices))
   664  	for volID := range instanceBlockDevices {
   665  		volIDs = append(volIDs, aws.String(volID))
   666  	}
   667  
   668  	// Need to call DescribeVolumes to get volume_size and volume_type for each
   669  	// EBS block device
   670  	volResp, err := conn.DescribeVolumes(&ec2.DescribeVolumesInput{
   671  		VolumeIDs: volIDs,
   672  	})
   673  	if err != nil {
   674  		return nil, err
   675  	}
   676  
   677  	for _, vol := range volResp.Volumes {
   678  		instanceBd := instanceBlockDevices[*vol.VolumeID]
   679  		bd := make(map[string]interface{})
   680  
   681  		if instanceBd.EBS != nil && instanceBd.EBS.DeleteOnTermination != nil {
   682  			bd["delete_on_termination"] = *instanceBd.EBS.DeleteOnTermination
   683  		}
   684  		if vol.Size != nil {
   685  			bd["volume_size"] = *vol.Size
   686  		}
   687  		if vol.VolumeType != nil {
   688  			bd["volume_type"] = *vol.VolumeType
   689  		}
   690  		if vol.IOPS != nil {
   691  			bd["iops"] = *vol.IOPS
   692  		}
   693  
   694  		if blockDeviceIsRoot(instanceBd, instance) {
   695  			blockDevices["root"] = bd
   696  		} else {
   697  			if instanceBd.DeviceName != nil {
   698  				bd["device_name"] = *instanceBd.DeviceName
   699  			}
   700  			if vol.Encrypted != nil {
   701  				bd["encrypted"] = *vol.Encrypted
   702  			}
   703  			if vol.SnapshotID != nil {
   704  				bd["snapshot_id"] = *vol.SnapshotID
   705  			}
   706  
   707  			blockDevices["ebs"] = append(blockDevices["ebs"].([]map[string]interface{}), bd)
   708  		}
   709  	}
   710  
   711  	return blockDevices, nil
   712  }
   713  
   714  func blockDeviceIsRoot(bd *ec2.InstanceBlockDeviceMapping, instance *ec2.Instance) bool {
   715  	return (bd.DeviceName != nil &&
   716  		instance.RootDeviceName != nil &&
   717  		*bd.DeviceName == *instance.RootDeviceName)
   718  }
   719  
   720  func fetchRootDeviceName(ami string, conn *ec2.EC2) (*string, error) {
   721  	if ami == "" {
   722  		return nil, fmt.Errorf("Cannot fetch root device name for blank AMI ID.")
   723  	}
   724  
   725  	log.Printf("[DEBUG] Describing AMI %q to get root block device name", ami)
   726  	res, err := conn.DescribeImages(&ec2.DescribeImagesInput{
   727  		ImageIDs: []*string{aws.String(ami)},
   728  	})
   729  	if err != nil {
   730  		return nil, err
   731  	}
   732  
   733  	// For a bad image, we just return nil so we don't block a refresh
   734  	if len(res.Images) == 0 {
   735  		return nil, nil
   736  	}
   737  
   738  	image := res.Images[0]
   739  	rootDeviceName := image.RootDeviceName
   740  
   741  	// Some AMIs have a RootDeviceName like "/dev/sda1" that does not appear as a
   742  	// DeviceName in the BlockDeviceMapping list (which will instead have
   743  	// something like "/dev/sda")
   744  	//
   745  	// While this seems like it breaks an invariant of AMIs, it ends up working
   746  	// on the AWS side, and AMIs like this are common enough that we need to
   747  	// special case it so Terraform does the right thing.
   748  	//
   749  	// Our heuristic is: if the RootDeviceName does not appear in the
   750  	// BlockDeviceMapping, assume that the DeviceName of the first
   751  	// BlockDeviceMapping entry serves as the root device.
   752  	rootDeviceNameInMapping := false
   753  	for _, bdm := range image.BlockDeviceMappings {
   754  		if bdm.DeviceName == image.RootDeviceName {
   755  			rootDeviceNameInMapping = true
   756  		}
   757  	}
   758  
   759  	if !rootDeviceNameInMapping && len(image.BlockDeviceMappings) > 0 {
   760  		rootDeviceName = image.BlockDeviceMappings[0].DeviceName
   761  	}
   762  
   763  	return rootDeviceName, nil
   764  }
   765  
   766  func readBlockDeviceMappingsFromConfig(
   767  	d *schema.ResourceData, conn *ec2.EC2) ([]*ec2.BlockDeviceMapping, error) {
   768  	blockDevices := make([]*ec2.BlockDeviceMapping, 0)
   769  
   770  	if v, ok := d.GetOk("ebs_block_device"); ok {
   771  		vL := v.(*schema.Set).List()
   772  		for _, v := range vL {
   773  			bd := v.(map[string]interface{})
   774  			ebs := &ec2.EBSBlockDevice{
   775  				DeleteOnTermination: aws.Boolean(bd["delete_on_termination"].(bool)),
   776  			}
   777  
   778  			if v, ok := bd["snapshot_id"].(string); ok && v != "" {
   779  				ebs.SnapshotID = aws.String(v)
   780  			}
   781  
   782  			if v, ok := bd["encrypted"].(bool); ok && v {
   783  				ebs.Encrypted = aws.Boolean(v)
   784  			}
   785  
   786  			if v, ok := bd["volume_size"].(int); ok && v != 0 {
   787  				ebs.VolumeSize = aws.Long(int64(v))
   788  			}
   789  
   790  			if v, ok := bd["volume_type"].(string); ok && v != "" {
   791  				ebs.VolumeType = aws.String(v)
   792  			}
   793  
   794  			if v, ok := bd["iops"].(int); ok && v > 0 {
   795  				ebs.IOPS = aws.Long(int64(v))
   796  			}
   797  
   798  			blockDevices = append(blockDevices, &ec2.BlockDeviceMapping{
   799  				DeviceName: aws.String(bd["device_name"].(string)),
   800  				EBS:        ebs,
   801  			})
   802  		}
   803  	}
   804  
   805  	if v, ok := d.GetOk("ephemeral_block_device"); ok {
   806  		vL := v.(*schema.Set).List()
   807  		for _, v := range vL {
   808  			bd := v.(map[string]interface{})
   809  			blockDevices = append(blockDevices, &ec2.BlockDeviceMapping{
   810  				DeviceName:  aws.String(bd["device_name"].(string)),
   811  				VirtualName: aws.String(bd["virtual_name"].(string)),
   812  			})
   813  		}
   814  	}
   815  
   816  	if v, ok := d.GetOk("root_block_device"); ok {
   817  		vL := v.(*schema.Set).List()
   818  		if len(vL) > 1 {
   819  			return nil, fmt.Errorf("Cannot specify more than one root_block_device.")
   820  		}
   821  		for _, v := range vL {
   822  			bd := v.(map[string]interface{})
   823  			ebs := &ec2.EBSBlockDevice{
   824  				DeleteOnTermination: aws.Boolean(bd["delete_on_termination"].(bool)),
   825  			}
   826  
   827  			if v, ok := bd["volume_size"].(int); ok && v != 0 {
   828  				ebs.VolumeSize = aws.Long(int64(v))
   829  			}
   830  
   831  			if v, ok := bd["volume_type"].(string); ok && v != "" {
   832  				ebs.VolumeType = aws.String(v)
   833  			}
   834  
   835  			if v, ok := bd["iops"].(int); ok && v > 0 {
   836  				ebs.IOPS = aws.Long(int64(v))
   837  			}
   838  
   839  			if dn, err := fetchRootDeviceName(d.Get("ami").(string), conn); err == nil {
   840  				if dn == nil {
   841  					return nil, fmt.Errorf(
   842  						"Expected 1 AMI for ID: %s, got none",
   843  						d.Get("ami").(string))
   844  				}
   845  
   846  				blockDevices = append(blockDevices, &ec2.BlockDeviceMapping{
   847  					DeviceName: dn,
   848  					EBS:        ebs,
   849  				})
   850  			} else {
   851  				return nil, err
   852  			}
   853  		}
   854  	}
   855  
   856  	return blockDevices, nil
   857  }
   858  
   859  type awsInstanceOpts struct {
   860  	BlockDeviceMappings   []*ec2.BlockDeviceMapping
   861  	DisableAPITermination *bool
   862  	EBSOptimized          *bool
   863  	Monitoring            *ec2.RunInstancesMonitoringEnabled
   864  	IAMInstanceProfile    *ec2.IAMInstanceProfileSpecification
   865  	ImageID               *string
   866  	InstanceType          *string
   867  	KeyName               *string
   868  	NetworkInterfaces     []*ec2.InstanceNetworkInterfaceSpecification
   869  	Placement             *ec2.Placement
   870  	PrivateIPAddress      *string
   871  	SecurityGroupIDs      []*string
   872  	SecurityGroups        []*string
   873  	SpotPlacement         *ec2.SpotPlacement
   874  	SubnetID              *string
   875  	UserData64            *string
   876  }
   877  
   878  func buildAwsInstanceOpts(
   879  	d *schema.ResourceData, meta interface{}) (*awsInstanceOpts, error) {
   880  	conn := meta.(*AWSClient).ec2conn
   881  
   882  	opts := &awsInstanceOpts{
   883  		DisableAPITermination: aws.Boolean(d.Get("disable_api_termination").(bool)),
   884  		EBSOptimized:          aws.Boolean(d.Get("ebs_optimized").(bool)),
   885  		ImageID:               aws.String(d.Get("ami").(string)),
   886  		InstanceType:          aws.String(d.Get("instance_type").(string)),
   887  	}
   888  
   889  	opts.Monitoring = &ec2.RunInstancesMonitoringEnabled{
   890  		Enabled: aws.Boolean(d.Get("monitoring").(bool)),
   891  	}
   892  
   893  	opts.IAMInstanceProfile = &ec2.IAMInstanceProfileSpecification{
   894  		Name: aws.String(d.Get("iam_instance_profile").(string)),
   895  	}
   896  
   897  	opts.UserData64 = aws.String(
   898  		base64.StdEncoding.EncodeToString([]byte(d.Get("user_data").(string))))
   899  
   900  	// check for non-default Subnet, and cast it to a String
   901  	subnet, hasSubnet := d.GetOk("subnet_id")
   902  	subnetID := subnet.(string)
   903  
   904  	// Placement is used for aws_instance; SpotPlacement is used for
   905  	// aws_spot_instance_request. They represent the same data. :-|
   906  	opts.Placement = &ec2.Placement{
   907  		AvailabilityZone: aws.String(d.Get("availability_zone").(string)),
   908  		GroupName:        aws.String(d.Get("placement_group").(string)),
   909  	}
   910  
   911  	opts.SpotPlacement = &ec2.SpotPlacement{
   912  		AvailabilityZone: aws.String(d.Get("availability_zone").(string)),
   913  		GroupName:        aws.String(d.Get("placement_group").(string)),
   914  	}
   915  
   916  	if v := d.Get("tenancy").(string); v != "" {
   917  		opts.Placement.Tenancy = aws.String(v)
   918  	}
   919  
   920  	associatePublicIPAddress := d.Get("associate_public_ip_address").(bool)
   921  
   922  	var groups []*string
   923  	if v := d.Get("security_groups"); v != nil {
   924  		// Security group names.
   925  		// For a nondefault VPC, you must use security group IDs instead.
   926  		// See http://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_RunInstances.html
   927  		sgs := v.(*schema.Set).List()
   928  		if len(sgs) > 0 && hasSubnet {
   929  			log.Printf("[WARN] Deprecated. Attempting to use 'security_groups' within a VPC instance. Use 'vpc_security_group_ids' instead.")
   930  		}
   931  		for _, v := range sgs {
   932  			str := v.(string)
   933  			groups = append(groups, aws.String(str))
   934  		}
   935  	}
   936  
   937  	if hasSubnet && associatePublicIPAddress {
   938  		// If we have a non-default VPC / Subnet specified, we can flag
   939  		// AssociatePublicIpAddress to get a Public IP assigned. By default these are not provided.
   940  		// You cannot specify both SubnetId and the NetworkInterface.0.* parameters though, otherwise
   941  		// you get: Network interfaces and an instance-level subnet ID may not be specified on the same request
   942  		// You also need to attach Security Groups to the NetworkInterface instead of the instance,
   943  		// to avoid: Network interfaces and an instance-level security groups may not be specified on
   944  		// the same request
   945  		ni := &ec2.InstanceNetworkInterfaceSpecification{
   946  			AssociatePublicIPAddress: aws.Boolean(associatePublicIPAddress),
   947  			DeviceIndex:              aws.Long(int64(0)),
   948  			SubnetID:                 aws.String(subnetID),
   949  			Groups:                   groups,
   950  		}
   951  
   952  		if v, ok := d.GetOk("private_ip"); ok {
   953  			ni.PrivateIPAddress = aws.String(v.(string))
   954  		}
   955  
   956  		if v := d.Get("vpc_security_group_ids").(*schema.Set); v.Len() > 0 {
   957  			for _, v := range v.List() {
   958  				ni.Groups = append(ni.Groups, aws.String(v.(string)))
   959  			}
   960  		}
   961  
   962  		opts.NetworkInterfaces = []*ec2.InstanceNetworkInterfaceSpecification{ni}
   963  	} else {
   964  		if subnetID != "" {
   965  			opts.SubnetID = aws.String(subnetID)
   966  		}
   967  
   968  		if v, ok := d.GetOk("private_ip"); ok {
   969  			opts.PrivateIPAddress = aws.String(v.(string))
   970  		}
   971  		if opts.SubnetID != nil &&
   972  			*opts.SubnetID != "" {
   973  			opts.SecurityGroupIDs = groups
   974  		} else {
   975  			opts.SecurityGroups = groups
   976  		}
   977  
   978  		if v := d.Get("vpc_security_group_ids").(*schema.Set); v.Len() > 0 {
   979  			for _, v := range v.List() {
   980  				opts.SecurityGroupIDs = append(opts.SecurityGroupIDs, aws.String(v.(string)))
   981  			}
   982  		}
   983  	}
   984  
   985  	if v, ok := d.GetOk("key_name"); ok {
   986  		opts.KeyName = aws.String(v.(string))
   987  	}
   988  
   989  	blockDevices, err := readBlockDeviceMappingsFromConfig(d, conn)
   990  	if err != nil {
   991  		return nil, err
   992  	}
   993  	if len(blockDevices) > 0 {
   994  		opts.BlockDeviceMappings = blockDevices
   995  	}
   996  
   997  	return opts, nil
   998  }
   999  
  1000  func awsTerminateInstance(conn *ec2.EC2, id string) error {
  1001  	log.Printf("[INFO] Terminating instance: %s", id)
  1002  	req := &ec2.TerminateInstancesInput{
  1003  		InstanceIDs: []*string{aws.String(id)},
  1004  	}
  1005  	if _, err := conn.TerminateInstances(req); err != nil {
  1006  		return fmt.Errorf("Error terminating instance: %s", err)
  1007  	}
  1008  
  1009  	log.Printf("[DEBUG] Waiting for instance (%s) to become terminated", id)
  1010  
  1011  	stateConf := &resource.StateChangeConf{
  1012  		Pending:    []string{"pending", "running", "shutting-down", "stopped", "stopping"},
  1013  		Target:     "terminated",
  1014  		Refresh:    InstanceStateRefreshFunc(conn, id),
  1015  		Timeout:    10 * time.Minute,
  1016  		Delay:      10 * time.Second,
  1017  		MinTimeout: 3 * time.Second,
  1018  	}
  1019  
  1020  	_, err := stateConf.WaitForState()
  1021  	if err != nil {
  1022  		return fmt.Errorf(
  1023  			"Error waiting for instance (%s) to terminate: %s", id, err)
  1024  	}
  1025  
  1026  	return nil
  1027  }