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