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