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