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