github.com/ndarilek/terraform@v0.3.8-0.20150320140257-d3135c1b2bac/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  						"device_name": &schema.Schema{
   269  							Type:     schema.TypeString,
   270  							Optional: true,
   271  							ForceNew: true,
   272  							Default:  "/dev/sda1",
   273  						},
   274  
   275  						"iops": &schema.Schema{
   276  							Type:     schema.TypeInt,
   277  							Optional: true,
   278  							Computed: true,
   279  							ForceNew: true,
   280  						},
   281  
   282  						"volume_size": &schema.Schema{
   283  							Type:     schema.TypeInt,
   284  							Optional: true,
   285  							Computed: true,
   286  							ForceNew: true,
   287  						},
   288  
   289  						"volume_type": &schema.Schema{
   290  							Type:     schema.TypeString,
   291  							Optional: true,
   292  							Computed: true,
   293  							ForceNew: true,
   294  						},
   295  					},
   296  				},
   297  				Set: func(v interface{}) int {
   298  					var buf bytes.Buffer
   299  					m := v.(map[string]interface{})
   300  					buf.WriteString(fmt.Sprintf("%t-", m["delete_on_termination"].(bool)))
   301  					buf.WriteString(fmt.Sprintf("%s-", m["device_name"].(string)))
   302  					// See the NOTE in "ebs_block_device" for why we skip iops here.
   303  					// buf.WriteString(fmt.Sprintf("%d-", m["iops"].(int)))
   304  					buf.WriteString(fmt.Sprintf("%d-", m["volume_size"].(int)))
   305  					buf.WriteString(fmt.Sprintf("%s-", m["volume_type"].(string)))
   306  					return hashcode.String(buf.String())
   307  				},
   308  			},
   309  		},
   310  	}
   311  }
   312  
   313  func resourceAwsInstanceCreate(d *schema.ResourceData, meta interface{}) error {
   314  	ec2conn := 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  	}
   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.RunInstancesRequest{
   345  		ImageID:            aws.String(d.Get("ami").(string)),
   346  		Placement:          placement,
   347  		InstanceType:       aws.String(d.Get("instance_type").(string)),
   348  		MaxCount:           aws.Integer(1),
   349  		MinCount:           aws.Integer(1),
   350  		UserData:           aws.String(userData),
   351  		EBSOptimized:       aws.Boolean(d.Get("ebs_optimized").(bool)),
   352  		IAMInstanceProfile: iam,
   353  	}
   354  
   355  	associatePublicIPAddress := false
   356  	if v := d.Get("associate_public_ip_address"); v != nil {
   357  		associatePublicIPAddress = v.(bool)
   358  	}
   359  
   360  	var groups []string
   361  	if v := d.Get("security_groups"); v != nil {
   362  		// Security group names.
   363  		// For a nondefault VPC, you must use security group IDs instead.
   364  		// See http://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_RunInstances.html
   365  		for _, v := range v.(*schema.Set).List() {
   366  			str := v.(string)
   367  			groups = append(groups, str)
   368  		}
   369  	}
   370  
   371  	if hasSubnet && associatePublicIPAddress {
   372  		// If we have a non-default VPC / Subnet specified, we can flag
   373  		// AssociatePublicIpAddress to get a Public IP assigned. By default these are not provided.
   374  		// You cannot specify both SubnetId and the NetworkInterface.0.* parameters though, otherwise
   375  		// you get: Network interfaces and an instance-level subnet ID may not be specified on the same request
   376  		// You also need to attach Security Groups to the NetworkInterface instead of the instance,
   377  		// to avoid: Network interfaces and an instance-level security groups may not be specified on
   378  		// the same request
   379  		ni := ec2.InstanceNetworkInterfaceSpecification{
   380  			AssociatePublicIPAddress: aws.Boolean(associatePublicIPAddress),
   381  			DeviceIndex:              aws.Integer(0),
   382  			SubnetID:                 aws.String(subnetID),
   383  		}
   384  
   385  		if v, ok := d.GetOk("private_ip"); ok {
   386  			ni.PrivateIPAddress = aws.String(v.(string))
   387  		}
   388  
   389  		if len(groups) > 0 {
   390  			ni.Groups = groups
   391  		}
   392  
   393  		runOpts.NetworkInterfaces = []ec2.InstanceNetworkInterfaceSpecification{ni}
   394  	} else {
   395  		if subnetID != "" {
   396  			runOpts.SubnetID = aws.String(subnetID)
   397  		}
   398  
   399  		if v, ok := d.GetOk("private_ip"); ok {
   400  			runOpts.PrivateIPAddress = aws.String(v.(string))
   401  		}
   402  		if runOpts.SubnetID != nil &&
   403  			*runOpts.SubnetID != "" {
   404  			runOpts.SecurityGroupIDs = groups
   405  		} else {
   406  			runOpts.SecurityGroups = groups
   407  		}
   408  	}
   409  
   410  	if v, ok := d.GetOk("key_name"); ok {
   411  		runOpts.KeyName = aws.String(v.(string))
   412  	}
   413  
   414  	blockDevices := make([]ec2.BlockDeviceMapping, 0)
   415  
   416  	if v, ok := d.GetOk("ebs_block_device"); ok {
   417  		vL := v.(*schema.Set).List()
   418  		for _, v := range vL {
   419  			bd := v.(map[string]interface{})
   420  			ebs := &ec2.EBSBlockDevice{
   421  				DeleteOnTermination: aws.Boolean(bd["delete_on_termination"].(bool)),
   422  			}
   423  
   424  			if v, ok := bd["snapshot_id"].(string); ok && v != "" {
   425  				ebs.SnapshotID = aws.String(v)
   426  			}
   427  
   428  			if v, ok := bd["volume_size"].(int); ok && v != 0 {
   429  				ebs.VolumeSize = aws.Integer(v)
   430  			}
   431  
   432  			if v, ok := bd["volume_type"].(string); ok && v != "" {
   433  				ebs.VolumeType = aws.String(v)
   434  			}
   435  
   436  			if v, ok := bd["iops"].(int); ok && v > 0 {
   437  				ebs.IOPS = aws.Integer(v)
   438  			}
   439  
   440  			blockDevices = append(blockDevices, ec2.BlockDeviceMapping{
   441  				DeviceName: aws.String(bd["device_name"].(string)),
   442  				EBS:        ebs,
   443  			})
   444  		}
   445  	}
   446  
   447  	if v, ok := d.GetOk("ephemeral_block_device"); ok {
   448  		vL := v.(*schema.Set).List()
   449  		for _, v := range vL {
   450  			bd := v.(map[string]interface{})
   451  			blockDevices = append(blockDevices, ec2.BlockDeviceMapping{
   452  				DeviceName:  aws.String(bd["device_name"].(string)),
   453  				VirtualName: aws.String(bd["virtual_name"].(string)),
   454  			})
   455  		}
   456  	}
   457  
   458  	if v, ok := d.GetOk("root_block_device"); ok {
   459  		vL := v.(*schema.Set).List()
   460  		if len(vL) > 1 {
   461  			return fmt.Errorf("Cannot specify more than one root_block_device.")
   462  		}
   463  		for _, v := range vL {
   464  			bd := v.(map[string]interface{})
   465  			ebs := &ec2.EBSBlockDevice{
   466  				DeleteOnTermination: aws.Boolean(bd["delete_on_termination"].(bool)),
   467  			}
   468  
   469  			if v, ok := bd["volume_size"].(int); ok && v != 0 {
   470  				ebs.VolumeSize = aws.Integer(v)
   471  			}
   472  
   473  			if v, ok := bd["volume_type"].(string); ok && v != "" {
   474  				ebs.VolumeType = aws.String(v)
   475  			}
   476  
   477  			if v, ok := bd["iops"].(int); ok && v > 0 {
   478  				ebs.IOPS = aws.Integer(v)
   479  			}
   480  
   481  			blockDevices = append(blockDevices, ec2.BlockDeviceMapping{
   482  				DeviceName: aws.String(bd["device_name"].(string)),
   483  				EBS:        ebs,
   484  			})
   485  		}
   486  	}
   487  
   488  	if len(blockDevices) > 0 {
   489  		runOpts.BlockDeviceMappings = blockDevices
   490  	}
   491  
   492  	// Create the instance
   493  	log.Printf("[DEBUG] Run configuration: %#v", runOpts)
   494  	runResp, err := ec2conn.RunInstances(runOpts)
   495  	if err != nil {
   496  		return fmt.Errorf("Error launching source instance: %s", err)
   497  	}
   498  
   499  	instance := &runResp.Instances[0]
   500  	log.Printf("[INFO] Instance ID: %s", *instance.InstanceID)
   501  
   502  	// Store the resulting ID so we can look this up later
   503  	d.SetId(*instance.InstanceID)
   504  
   505  	// Wait for the instance to become running so we can get some attributes
   506  	// that aren't available until later.
   507  	log.Printf(
   508  		"[DEBUG] Waiting for instance (%s) to become running",
   509  		*instance.InstanceID)
   510  
   511  	stateConf := &resource.StateChangeConf{
   512  		Pending:    []string{"pending"},
   513  		Target:     "running",
   514  		Refresh:    InstanceStateRefreshFunc(ec2conn, *instance.InstanceID),
   515  		Timeout:    10 * time.Minute,
   516  		Delay:      10 * time.Second,
   517  		MinTimeout: 3 * time.Second,
   518  	}
   519  
   520  	instanceRaw, err := stateConf.WaitForState()
   521  	if err != nil {
   522  		return fmt.Errorf(
   523  			"Error waiting for instance (%s) to become ready: %s",
   524  			*instance.InstanceID, err)
   525  	}
   526  
   527  	instance = instanceRaw.(*ec2.Instance)
   528  
   529  	// Initialize the connection info
   530  	if instance.PublicIPAddress != nil {
   531  		d.SetConnInfo(map[string]string{
   532  			"type": "ssh",
   533  			"host": *instance.PublicIPAddress,
   534  		})
   535  	}
   536  
   537  	// Set our attributes
   538  	if err := resourceAwsInstanceRead(d, meta); err != nil {
   539  		return err
   540  	}
   541  
   542  	// Update if we need to
   543  	return resourceAwsInstanceUpdate(d, meta)
   544  }
   545  
   546  func resourceAwsInstanceRead(d *schema.ResourceData, meta interface{}) error {
   547  	ec2conn := meta.(*AWSClient).ec2conn
   548  
   549  	resp, err := ec2conn.DescribeInstances(&ec2.DescribeInstancesRequest{
   550  		InstanceIDs: []string{d.Id()},
   551  	})
   552  	if err != nil {
   553  		// If the instance was not found, return nil so that we can show
   554  		// that the instance is gone.
   555  		if ec2err, ok := err.(aws.APIError); ok && ec2err.Code == "InvalidInstanceID.NotFound" {
   556  			d.SetId("")
   557  			return nil
   558  		}
   559  
   560  		// Some other error, report it
   561  		return err
   562  	}
   563  
   564  	// If nothing was found, then return no state
   565  	if len(resp.Reservations) == 0 {
   566  		d.SetId("")
   567  		return nil
   568  	}
   569  
   570  	instance := &resp.Reservations[0].Instances[0]
   571  
   572  	// If the instance is terminated, then it is gone
   573  	if *instance.State.Name == "terminated" {
   574  		d.SetId("")
   575  		return nil
   576  	}
   577  
   578  	if instance.Placement != nil {
   579  		d.Set("availability_zone", instance.Placement.AvailabilityZone)
   580  	}
   581  	if instance.Placement.Tenancy != nil {
   582  		d.Set("tenancy", instance.Placement.Tenancy)
   583  	}
   584  
   585  	d.Set("key_name", instance.KeyName)
   586  	d.Set("public_dns", instance.PublicDNSName)
   587  	d.Set("public_ip", instance.PublicIPAddress)
   588  	d.Set("private_dns", instance.PrivateDNSName)
   589  	d.Set("private_ip", instance.PrivateIPAddress)
   590  	if len(instance.NetworkInterfaces) > 0 {
   591  		d.Set("subnet_id", instance.NetworkInterfaces[0].SubnetID)
   592  	} else {
   593  		d.Set("subnet_id", instance.SubnetID)
   594  	}
   595  	d.Set("ebs_optimized", instance.EBSOptimized)
   596  	d.Set("tags", tagsToMap(instance.Tags))
   597  
   598  	// Determine whether we're referring to security groups with
   599  	// IDs or names. We use a heuristic to figure this out. By default,
   600  	// we use IDs if we're in a VPC. However, if we previously had an
   601  	// all-name list of security groups, we use names. Or, if we had any
   602  	// IDs, we use IDs.
   603  	useID := instance.SubnetID != nil && *instance.SubnetID != ""
   604  	if v := d.Get("security_groups"); v != nil {
   605  		match := false
   606  		for _, v := range v.(*schema.Set).List() {
   607  			if strings.HasPrefix(v.(string), "sg-") {
   608  				match = true
   609  				break
   610  			}
   611  		}
   612  
   613  		useID = match
   614  	}
   615  
   616  	// Build up the security groups
   617  	sgs := make([]string, len(instance.SecurityGroups))
   618  	for i, sg := range instance.SecurityGroups {
   619  		if useID {
   620  			sgs[i] = *sg.GroupID
   621  		} else {
   622  			sgs[i] = *sg.GroupName
   623  		}
   624  	}
   625  	d.Set("security_groups", sgs)
   626  
   627  	if err := readBlockDevices(d, instance, ec2conn); err != nil {
   628  		return err
   629  	}
   630  
   631  	return nil
   632  }
   633  
   634  func resourceAwsInstanceUpdate(d *schema.ResourceData, meta interface{}) error {
   635  	ec2conn := meta.(*AWSClient).ec2conn
   636  
   637  	// SourceDestCheck can only be set on VPC instances
   638  	if d.Get("subnet_id").(string) != "" {
   639  		log.Printf("[INFO] Modifying instance %s", d.Id())
   640  		err := ec2conn.ModifyInstanceAttribute(&ec2.ModifyInstanceAttributeRequest{
   641  			InstanceID: aws.String(d.Id()),
   642  			SourceDestCheck: &ec2.AttributeBooleanValue{
   643  				Value: aws.Boolean(d.Get("source_dest_check").(bool)),
   644  			},
   645  		})
   646  		if err != nil {
   647  			return err
   648  		}
   649  	}
   650  
   651  	// TODO(mitchellh): wait for the attributes we modified to
   652  	// persist the change...
   653  
   654  	if err := setTags(ec2conn, d); err != nil {
   655  		return err
   656  	} else {
   657  		d.SetPartial("tags")
   658  	}
   659  
   660  	return nil
   661  }
   662  
   663  func resourceAwsInstanceDelete(d *schema.ResourceData, meta interface{}) error {
   664  	ec2conn := meta.(*AWSClient).ec2conn
   665  
   666  	log.Printf("[INFO] Terminating instance: %s", d.Id())
   667  	req := &ec2.TerminateInstancesRequest{
   668  		InstanceIDs: []string{d.Id()},
   669  	}
   670  	if _, err := ec2conn.TerminateInstances(req); err != nil {
   671  		return fmt.Errorf("Error terminating instance: %s", err)
   672  	}
   673  
   674  	log.Printf(
   675  		"[DEBUG] Waiting for instance (%s) to become terminated",
   676  		d.Id())
   677  
   678  	stateConf := &resource.StateChangeConf{
   679  		Pending:    []string{"pending", "running", "shutting-down", "stopped", "stopping"},
   680  		Target:     "terminated",
   681  		Refresh:    InstanceStateRefreshFunc(ec2conn, d.Id()),
   682  		Timeout:    10 * time.Minute,
   683  		Delay:      10 * time.Second,
   684  		MinTimeout: 3 * time.Second,
   685  	}
   686  
   687  	_, err := stateConf.WaitForState()
   688  	if err != nil {
   689  		return fmt.Errorf(
   690  			"Error waiting for instance (%s) to terminate: %s",
   691  			d.Id(), err)
   692  	}
   693  
   694  	d.SetId("")
   695  	return nil
   696  }
   697  
   698  // InstanceStateRefreshFunc returns a resource.StateRefreshFunc that is used to watch
   699  // an EC2 instance.
   700  func InstanceStateRefreshFunc(conn *ec2.EC2, instanceID string) resource.StateRefreshFunc {
   701  	return func() (interface{}, string, error) {
   702  		resp, err := conn.DescribeInstances(&ec2.DescribeInstancesRequest{
   703  			InstanceIDs: []string{instanceID},
   704  		})
   705  		if err != nil {
   706  			if ec2err, ok := err.(aws.APIError); ok && ec2err.Code == "InvalidInstanceID.NotFound" {
   707  				// Set this to nil as if we didn't find anything.
   708  				resp = nil
   709  			} else {
   710  				log.Printf("Error on InstanceStateRefresh: %s", err)
   711  				return nil, "", err
   712  			}
   713  		}
   714  
   715  		if resp == nil || len(resp.Reservations) == 0 || len(resp.Reservations[0].Instances) == 0 {
   716  			// Sometimes AWS just has consistency issues and doesn't see
   717  			// our instance yet. Return an empty state.
   718  			return nil, "", nil
   719  		}
   720  
   721  		i := &resp.Reservations[0].Instances[0]
   722  		return i, *i.State.Name, nil
   723  	}
   724  }
   725  
   726  func readBlockDevices(d *schema.ResourceData, instance *ec2.Instance, ec2conn *ec2.EC2) error {
   727  	ibds, err := readBlockDevicesFromInstance(instance, ec2conn)
   728  	if err != nil {
   729  		return err
   730  	}
   731  
   732  	if err := d.Set("ebs_block_device", ibds["ebs"]); err != nil {
   733  		return err
   734  	}
   735  	if ibds["root"] != nil {
   736  		if err := d.Set("root_block_device", []interface{}{ibds["root"]}); err != nil {
   737  			return err
   738  		}
   739  	}
   740  
   741  	return nil
   742  }
   743  
   744  func readBlockDevicesFromInstance(instance *ec2.Instance, ec2conn *ec2.EC2) (map[string]interface{}, error) {
   745  	blockDevices := make(map[string]interface{})
   746  	blockDevices["ebs"] = make([]map[string]interface{}, 0)
   747  	blockDevices["root"] = nil
   748  
   749  	instanceBlockDevices := make(map[string]ec2.InstanceBlockDeviceMapping)
   750  	for _, bd := range instance.BlockDeviceMappings {
   751  		if bd.EBS != nil {
   752  			instanceBlockDevices[*(bd.EBS.VolumeID)] = bd
   753  		}
   754  	}
   755  
   756  	if len(instanceBlockDevices) == 0 {
   757  		return nil, nil
   758  	}
   759  
   760  	volIDs := make([]string, 0, len(instanceBlockDevices))
   761  	for volID := range instanceBlockDevices {
   762  		volIDs = append(volIDs, volID)
   763  	}
   764  
   765  	// Need to call DescribeVolumes to get volume_size and volume_type for each
   766  	// EBS block device
   767  	volResp, err := ec2conn.DescribeVolumes(&ec2.DescribeVolumesRequest{
   768  		VolumeIDs: volIDs,
   769  	})
   770  	if err != nil {
   771  		return nil, err
   772  	}
   773  
   774  	for _, vol := range volResp.Volumes {
   775  		instanceBd := instanceBlockDevices[*vol.VolumeID]
   776  		bd := make(map[string]interface{})
   777  
   778  		if instanceBd.EBS != nil && instanceBd.EBS.DeleteOnTermination != nil {
   779  			bd["delete_on_termination"] = *instanceBd.EBS.DeleteOnTermination
   780  		}
   781  		if instanceBd.DeviceName != nil {
   782  			bd["device_name"] = *instanceBd.DeviceName
   783  		}
   784  		if vol.Size != nil {
   785  			bd["volume_size"] = *vol.Size
   786  		}
   787  		if vol.VolumeType != nil {
   788  			bd["volume_type"] = *vol.VolumeType
   789  		}
   790  		if vol.IOPS != nil {
   791  			bd["iops"] = *vol.IOPS
   792  		}
   793  
   794  		if blockDeviceIsRoot(instanceBd, instance) {
   795  			blockDevices["root"] = bd
   796  		} else {
   797  			if vol.Encrypted != nil {
   798  				bd["encrypted"] = *vol.Encrypted
   799  			}
   800  			if vol.SnapshotID != nil {
   801  				bd["snapshot_id"] = *vol.SnapshotID
   802  			}
   803  
   804  			blockDevices["ebs"] = append(blockDevices["ebs"].([]map[string]interface{}), bd)
   805  		}
   806  	}
   807  
   808  	return blockDevices, nil
   809  }
   810  
   811  func blockDeviceIsRoot(bd ec2.InstanceBlockDeviceMapping, instance *ec2.Instance) bool {
   812  	return (bd.DeviceName != nil &&
   813  		instance.RootDeviceName != nil &&
   814  		*bd.DeviceName == *instance.RootDeviceName)
   815  }