github.com/alexissmirnov/terraform@v0.4.3-0.20150423153700-1ef9731a2f14/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: func(v interface{}) int {
   111  					return hashcode.String(v.(string))
   112  				},
   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  			"iam_instance_profile": &schema.Schema{
   146  				Type:     schema.TypeString,
   147  				ForceNew: true,
   148  				Optional: true,
   149  			},
   150  
   151  			"tenancy": &schema.Schema{
   152  				Type:     schema.TypeString,
   153  				Optional: true,
   154  				Computed: true,
   155  				ForceNew: true,
   156  			},
   157  
   158  			"tags": tagsSchema(),
   159  
   160  			"block_device": &schema.Schema{
   161  				Type:     schema.TypeMap,
   162  				Optional: true,
   163  				Removed:  "Split out into three sub-types; see Changelog and Docs",
   164  			},
   165  
   166  			"ebs_block_device": &schema.Schema{
   167  				Type:     schema.TypeSet,
   168  				Optional: true,
   169  				Computed: true,
   170  				Elem: &schema.Resource{
   171  					Schema: map[string]*schema.Schema{
   172  						"delete_on_termination": &schema.Schema{
   173  							Type:     schema.TypeBool,
   174  							Optional: true,
   175  							Default:  true,
   176  							ForceNew: true,
   177  						},
   178  
   179  						"device_name": &schema.Schema{
   180  							Type:     schema.TypeString,
   181  							Required: true,
   182  							ForceNew: true,
   183  						},
   184  
   185  						"encrypted": &schema.Schema{
   186  							Type:     schema.TypeBool,
   187  							Optional: true,
   188  							Computed: true,
   189  							ForceNew: true,
   190  						},
   191  
   192  						"iops": &schema.Schema{
   193  							Type:     schema.TypeInt,
   194  							Optional: true,
   195  							Computed: true,
   196  							ForceNew: true,
   197  						},
   198  
   199  						"snapshot_id": &schema.Schema{
   200  							Type:     schema.TypeString,
   201  							Optional: true,
   202  							Computed: true,
   203  							ForceNew: true,
   204  						},
   205  
   206  						"volume_size": &schema.Schema{
   207  							Type:     schema.TypeInt,
   208  							Optional: true,
   209  							Computed: true,
   210  							ForceNew: true,
   211  						},
   212  
   213  						"volume_type": &schema.Schema{
   214  							Type:     schema.TypeString,
   215  							Optional: true,
   216  							Computed: true,
   217  							ForceNew: true,
   218  						},
   219  					},
   220  				},
   221  				Set: func(v interface{}) int {
   222  					var buf bytes.Buffer
   223  					m := v.(map[string]interface{})
   224  					buf.WriteString(fmt.Sprintf("%s-", m["device_name"].(string)))
   225  					buf.WriteString(fmt.Sprintf("%s-", m["snapshot_id"].(string)))
   226  					return hashcode.String(buf.String())
   227  				},
   228  			},
   229  
   230  			"ephemeral_block_device": &schema.Schema{
   231  				Type:     schema.TypeSet,
   232  				Optional: true,
   233  				Computed: true,
   234  				ForceNew: true,
   235  				Elem: &schema.Resource{
   236  					Schema: map[string]*schema.Schema{
   237  						"device_name": &schema.Schema{
   238  							Type:     schema.TypeString,
   239  							Required: true,
   240  						},
   241  
   242  						"virtual_name": &schema.Schema{
   243  							Type:     schema.TypeString,
   244  							Required: true,
   245  						},
   246  					},
   247  				},
   248  				Set: func(v interface{}) int {
   249  					var buf bytes.Buffer
   250  					m := v.(map[string]interface{})
   251  					buf.WriteString(fmt.Sprintf("%s-", m["device_name"].(string)))
   252  					buf.WriteString(fmt.Sprintf("%s-", m["virtual_name"].(string)))
   253  					return hashcode.String(buf.String())
   254  				},
   255  			},
   256  
   257  			"root_block_device": &schema.Schema{
   258  				// TODO: This is a set because we don't support singleton
   259  				//       sub-resources today. We'll enforce that the set only ever has
   260  				//       length zero or one below. When TF gains support for
   261  				//       sub-resources this can be converted.
   262  				Type:     schema.TypeSet,
   263  				Optional: true,
   264  				Computed: true,
   265  				Elem: &schema.Resource{
   266  					// "You can only modify the volume size, volume type, and Delete on
   267  					// Termination flag on the block device mapping entry for the root
   268  					// device volume." - bit.ly/ec2bdmap
   269  					Schema: map[string]*schema.Schema{
   270  						"delete_on_termination": &schema.Schema{
   271  							Type:     schema.TypeBool,
   272  							Optional: true,
   273  							Default:  true,
   274  							ForceNew: true,
   275  						},
   276  
   277  						"iops": &schema.Schema{
   278  							Type:     schema.TypeInt,
   279  							Optional: true,
   280  							Computed: true,
   281  							ForceNew: true,
   282  						},
   283  
   284  						"volume_size": &schema.Schema{
   285  							Type:     schema.TypeInt,
   286  							Optional: true,
   287  							Computed: true,
   288  							ForceNew: true,
   289  						},
   290  
   291  						"volume_type": &schema.Schema{
   292  							Type:     schema.TypeString,
   293  							Optional: true,
   294  							Computed: true,
   295  							ForceNew: true,
   296  						},
   297  					},
   298  				},
   299  				Set: func(v interface{}) int {
   300  					// there can be only one root device; no need to hash anything
   301  					return 0
   302  				},
   303  			},
   304  		},
   305  	}
   306  }
   307  
   308  func resourceAwsInstanceCreate(d *schema.ResourceData, meta interface{}) error {
   309  	conn := meta.(*AWSClient).ec2conn
   310  
   311  	// Figure out user data
   312  	userData := ""
   313  	if v := d.Get("user_data"); v != nil {
   314  		userData = base64.StdEncoding.EncodeToString([]byte(v.(string)))
   315  	}
   316  
   317  	// check for non-default Subnet, and cast it to a String
   318  	var hasSubnet bool
   319  	subnet, hasSubnet := d.GetOk("subnet_id")
   320  	subnetID := subnet.(string)
   321  
   322  	placement := &ec2.Placement{
   323  		AvailabilityZone: aws.String(d.Get("availability_zone").(string)),
   324  		GroupName:        aws.String(d.Get("placement_group").(string)),
   325  	}
   326  
   327  	if hasSubnet {
   328  		// Tenancy is only valid inside a VPC
   329  		// See http://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_Placement.html
   330  		if v := d.Get("tenancy").(string); v != "" {
   331  			placement.Tenancy = aws.String(v)
   332  		}
   333  	}
   334  
   335  	iam := &ec2.IAMInstanceProfileSpecification{
   336  		Name: aws.String(d.Get("iam_instance_profile").(string)),
   337  	}
   338  
   339  	// Build the creation struct
   340  	runOpts := &ec2.RunInstancesInput{
   341  		ImageID:            aws.String(d.Get("ami").(string)),
   342  		Placement:          placement,
   343  		InstanceType:       aws.String(d.Get("instance_type").(string)),
   344  		MaxCount:           aws.Long(int64(1)),
   345  		MinCount:           aws.Long(int64(1)),
   346  		UserData:           aws.String(userData),
   347  		EBSOptimized:       aws.Boolean(d.Get("ebs_optimized").(bool)),
   348  		IAMInstanceProfile: iam,
   349  	}
   350  
   351  	associatePublicIPAddress := false
   352  	if v := d.Get("associate_public_ip_address"); v != nil {
   353  		associatePublicIPAddress = v.(bool)
   354  	}
   355  
   356  	var groups []*string
   357  	if v := d.Get("security_groups"); v != nil {
   358  		// Security group names.
   359  		// For a nondefault VPC, you must use security group IDs instead.
   360  		// See http://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_RunInstances.html
   361  		if hasSubnet {
   362  			log.Printf("[WARN] Deprecated. Attempting to use 'security_groups' within a VPC instance. Use 'vpc_security_group_ids' instead.")
   363  		}
   364  		for _, v := range v.(*schema.Set).List() {
   365  			str := v.(string)
   366  			groups = append(groups, aws.String(str))
   367  		}
   368  	}
   369  
   370  	if hasSubnet && associatePublicIPAddress {
   371  		// If we have a non-default VPC / Subnet specified, we can flag
   372  		// AssociatePublicIpAddress to get a Public IP assigned. By default these are not provided.
   373  		// You cannot specify both SubnetId and the NetworkInterface.0.* parameters though, otherwise
   374  		// you get: Network interfaces and an instance-level subnet ID may not be specified on the same request
   375  		// You also need to attach Security Groups to the NetworkInterface instead of the instance,
   376  		// to avoid: Network interfaces and an instance-level security groups may not be specified on
   377  		// the same request
   378  		ni := &ec2.InstanceNetworkInterfaceSpecification{
   379  			AssociatePublicIPAddress: aws.Boolean(associatePublicIPAddress),
   380  			DeviceIndex:              aws.Long(int64(0)),
   381  			SubnetID:                 aws.String(subnetID),
   382  			Groups:                   groups,
   383  		}
   384  
   385  		if v, ok := d.GetOk("private_ip"); ok {
   386  			ni.PrivateIPAddress = aws.String(v.(string))
   387  		}
   388  
   389  		if v := d.Get("vpc_security_group_ids"); v != nil {
   390  			for _, v := range v.(*schema.Set).List() {
   391  				ni.Groups = append(ni.Groups, aws.String(v.(string)))
   392  			}
   393  		}
   394  
   395  		runOpts.NetworkInterfaces = []*ec2.InstanceNetworkInterfaceSpecification{ni}
   396  	} else {
   397  		if subnetID != "" {
   398  			runOpts.SubnetID = aws.String(subnetID)
   399  		}
   400  
   401  		if v, ok := d.GetOk("private_ip"); ok {
   402  			runOpts.PrivateIPAddress = aws.String(v.(string))
   403  		}
   404  		if runOpts.SubnetID != nil &&
   405  			*runOpts.SubnetID != "" {
   406  			runOpts.SecurityGroupIDs = groups
   407  		} else {
   408  			runOpts.SecurityGroups = groups
   409  		}
   410  
   411  		if v := d.Get("vpc_security_group_ids"); v != nil {
   412  			for _, v := range v.(*schema.Set).List() {
   413  				runOpts.SecurityGroupIDs = append(runOpts.SecurityGroupIDs, aws.String(v.(string)))
   414  			}
   415  		}
   416  	}
   417  
   418  	if v, ok := d.GetOk("key_name"); ok {
   419  		runOpts.KeyName = aws.String(v.(string))
   420  	}
   421  
   422  	blockDevices := make([]*ec2.BlockDeviceMapping, 0)
   423  
   424  	if v, ok := d.GetOk("ebs_block_device"); ok {
   425  		vL := v.(*schema.Set).List()
   426  		for _, v := range vL {
   427  			bd := v.(map[string]interface{})
   428  			ebs := &ec2.EBSBlockDevice{
   429  				DeleteOnTermination: aws.Boolean(bd["delete_on_termination"].(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 := false
   623  		for _, v := range v.(*schema.Set).List() {
   624  			if strings.HasPrefix(v.(string), "sg-") {
   625  				match = true
   626  				break
   627  			}
   628  		}
   629  
   630  		useID = match
   631  	}
   632  
   633  	// Build up the security groups
   634  	sgs := make([]string, 0, len(instance.SecurityGroups))
   635  	if useID {
   636  		for _, sg := range instance.SecurityGroups {
   637  			sgs = append(sgs, *sg.GroupID)
   638  		}
   639  		log.Printf("[DEBUG] Setting Security Group IDs: %#v", sgs)
   640  		if err := d.Set("vpc_security_group_ids", sgs); err != nil {
   641  			return err
   642  		}
   643  	} else {
   644  		for _, sg := range instance.SecurityGroups {
   645  			sgs = append(sgs, *sg.GroupName)
   646  		}
   647  		log.Printf("[DEBUG] Setting Security Group Names: %#v", sgs)
   648  		if err := d.Set("security_groups", sgs); err != nil {
   649  			return err
   650  		}
   651  	}
   652  
   653  	if err := readBlockDevices(d, instance, conn); err != nil {
   654  		return err
   655  	}
   656  
   657  	return nil
   658  }
   659  
   660  func resourceAwsInstanceUpdate(d *schema.ResourceData, meta interface{}) error {
   661  	conn := meta.(*AWSClient).ec2conn
   662  
   663  	d.Partial(true)
   664  
   665  	// SourceDestCheck can only be set on VPC instances
   666  	if d.Get("subnet_id").(string) != "" {
   667  		log.Printf("[INFO] Modifying instance %s", d.Id())
   668  		_, err := conn.ModifyInstanceAttribute(&ec2.ModifyInstanceAttributeInput{
   669  			InstanceID: aws.String(d.Id()),
   670  			SourceDestCheck: &ec2.AttributeBooleanValue{
   671  				Value: aws.Boolean(d.Get("source_dest_check").(bool)),
   672  			},
   673  		})
   674  		if err != nil {
   675  			return err
   676  		}
   677  	}
   678  
   679  	// TODO(mitchellh): wait for the attributes we modified to
   680  	// persist the change...
   681  
   682  	if err := setTagsSDK(conn, d); err != nil {
   683  		return err
   684  	} else {
   685  		d.SetPartial("tags")
   686  	}
   687  	d.Partial(false)
   688  
   689  	return resourceAwsInstanceRead(d, meta)
   690  }
   691  
   692  func resourceAwsInstanceDelete(d *schema.ResourceData, meta interface{}) error {
   693  	conn := meta.(*AWSClient).ec2conn
   694  
   695  	log.Printf("[INFO] Terminating instance: %s", d.Id())
   696  	req := &ec2.TerminateInstancesInput{
   697  		InstanceIDs: []*string{aws.String(d.Id())},
   698  	}
   699  	if _, err := conn.TerminateInstances(req); err != nil {
   700  		return fmt.Errorf("Error terminating instance: %s", err)
   701  	}
   702  
   703  	log.Printf(
   704  		"[DEBUG] Waiting for instance (%s) to become terminated",
   705  		d.Id())
   706  
   707  	stateConf := &resource.StateChangeConf{
   708  		Pending:    []string{"pending", "running", "shutting-down", "stopped", "stopping"},
   709  		Target:     "terminated",
   710  		Refresh:    InstanceStateRefreshFunc(conn, d.Id()),
   711  		Timeout:    10 * time.Minute,
   712  		Delay:      10 * time.Second,
   713  		MinTimeout: 3 * time.Second,
   714  	}
   715  
   716  	_, err := stateConf.WaitForState()
   717  	if err != nil {
   718  		return fmt.Errorf(
   719  			"Error waiting for instance (%s) to terminate: %s",
   720  			d.Id(), err)
   721  	}
   722  
   723  	d.SetId("")
   724  	return nil
   725  }
   726  
   727  // InstanceStateRefreshFunc returns a resource.StateRefreshFunc that is used to watch
   728  // an EC2 instance.
   729  func InstanceStateRefreshFunc(conn *ec2.EC2, instanceID string) resource.StateRefreshFunc {
   730  	return func() (interface{}, string, error) {
   731  		resp, err := conn.DescribeInstances(&ec2.DescribeInstancesInput{
   732  			InstanceIDs: []*string{aws.String(instanceID)},
   733  		})
   734  		if err != nil {
   735  			if ec2err, ok := err.(aws.APIError); ok && ec2err.Code == "InvalidInstanceID.NotFound" {
   736  				// Set this to nil as if we didn't find anything.
   737  				resp = nil
   738  			} else {
   739  				log.Printf("Error on InstanceStateRefresh: %s", err)
   740  				return nil, "", err
   741  			}
   742  		}
   743  
   744  		if resp == nil || len(resp.Reservations) == 0 || len(resp.Reservations[0].Instances) == 0 {
   745  			// Sometimes AWS just has consistency issues and doesn't see
   746  			// our instance yet. Return an empty state.
   747  			return nil, "", nil
   748  		}
   749  
   750  		i := resp.Reservations[0].Instances[0]
   751  		return i, *i.State.Name, nil
   752  	}
   753  }
   754  
   755  func readBlockDevices(d *schema.ResourceData, instance *ec2.Instance, conn *ec2.EC2) error {
   756  	ibds, err := readBlockDevicesFromInstance(instance, conn)
   757  	if err != nil {
   758  		return err
   759  	}
   760  
   761  	if err := d.Set("ebs_block_device", ibds["ebs"]); err != nil {
   762  		return err
   763  	}
   764  	if ibds["root"] != nil {
   765  		if err := d.Set("root_block_device", []interface{}{ibds["root"]}); err != nil {
   766  			return err
   767  		}
   768  	}
   769  
   770  	return nil
   771  }
   772  
   773  func readBlockDevicesFromInstance(instance *ec2.Instance, conn *ec2.EC2) (map[string]interface{}, error) {
   774  	blockDevices := make(map[string]interface{})
   775  	blockDevices["ebs"] = make([]map[string]interface{}, 0)
   776  	blockDevices["root"] = nil
   777  
   778  	instanceBlockDevices := make(map[string]*ec2.InstanceBlockDeviceMapping)
   779  	for _, bd := range instance.BlockDeviceMappings {
   780  		if bd.EBS != nil {
   781  			instanceBlockDevices[*(bd.EBS.VolumeID)] = bd
   782  		}
   783  	}
   784  
   785  	if len(instanceBlockDevices) == 0 {
   786  		return nil, nil
   787  	}
   788  
   789  	volIDs := make([]*string, 0, len(instanceBlockDevices))
   790  	for volID := range instanceBlockDevices {
   791  		volIDs = append(volIDs, aws.String(volID))
   792  	}
   793  
   794  	// Need to call DescribeVolumes to get volume_size and volume_type for each
   795  	// EBS block device
   796  	volResp, err := conn.DescribeVolumes(&ec2.DescribeVolumesInput{
   797  		VolumeIDs: volIDs,
   798  	})
   799  	if err != nil {
   800  		return nil, err
   801  	}
   802  
   803  	for _, vol := range volResp.Volumes {
   804  		instanceBd := instanceBlockDevices[*vol.VolumeID]
   805  		bd := make(map[string]interface{})
   806  
   807  		if instanceBd.EBS != nil && instanceBd.EBS.DeleteOnTermination != nil {
   808  			bd["delete_on_termination"] = *instanceBd.EBS.DeleteOnTermination
   809  		}
   810  		if vol.Size != nil {
   811  			bd["volume_size"] = *vol.Size
   812  		}
   813  		if vol.VolumeType != nil {
   814  			bd["volume_type"] = *vol.VolumeType
   815  		}
   816  		if vol.IOPS != nil {
   817  			bd["iops"] = *vol.IOPS
   818  		}
   819  
   820  		if blockDeviceIsRoot(instanceBd, instance) {
   821  			blockDevices["root"] = bd
   822  		} else {
   823  			if instanceBd.DeviceName != nil {
   824  				bd["device_name"] = *instanceBd.DeviceName
   825  			}
   826  			if vol.Encrypted != nil {
   827  				bd["encrypted"] = *vol.Encrypted
   828  			}
   829  			if vol.SnapshotID != nil {
   830  				bd["snapshot_id"] = *vol.SnapshotID
   831  			}
   832  
   833  			blockDevices["ebs"] = append(blockDevices["ebs"].([]map[string]interface{}), bd)
   834  		}
   835  	}
   836  
   837  	return blockDevices, nil
   838  }
   839  
   840  func blockDeviceIsRoot(bd *ec2.InstanceBlockDeviceMapping, instance *ec2.Instance) bool {
   841  	return (bd.DeviceName != nil &&
   842  		instance.RootDeviceName != nil &&
   843  		*bd.DeviceName == *instance.RootDeviceName)
   844  }
   845  
   846  func fetchRootDeviceName(ami string, conn *ec2.EC2) (*string, error) {
   847  	if ami == "" {
   848  		return nil, fmt.Errorf("Cannot fetch root device name for blank AMI ID.")
   849  	}
   850  
   851  	log.Printf("[DEBUG] Describing AMI %q to get root block device name", ami)
   852  	req := &ec2.DescribeImagesInput{ImageIDs: []*string{aws.String(ami)}}
   853  	if res, err := conn.DescribeImages(req); err == nil {
   854  		if len(res.Images) == 1 {
   855  			return res.Images[0].RootDeviceName, nil
   856  		} else {
   857  			return nil, fmt.Errorf("Expected 1 AMI for ID: %s, got: %#v", ami, res.Images)
   858  		}
   859  	} else {
   860  		return nil, err
   861  	}
   862  }