github.com/ggiamarchi/terraform@v0.3.7-0.20150607194748-ed2a66a46a71/builtin/providers/aws/resource_aws_instance.go (about)

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