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