github.com/sathiyas/terraform@v0.6.9-0.20151210233947-3330da00b997/builtin/providers/aws/resource_aws_instance.go (about)

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