github.com/lamielle/terraform@v0.3.2-0.20141121070651-81f008ba53d5/builtin/providers/aws/resource_aws_instance.go (about)

     1  package aws
     2  
     3  import (
     4  	"bytes"
     5  	"crypto/sha1"
     6  	"encoding/hex"
     7  	"fmt"
     8  	"log"
     9  	"strings"
    10  	"time"
    11  
    12  	"github.com/hashicorp/terraform/helper/hashcode"
    13  	"github.com/hashicorp/terraform/helper/resource"
    14  	"github.com/hashicorp/terraform/helper/schema"
    15  	"github.com/mitchellh/goamz/ec2"
    16  )
    17  
    18  func resourceAwsInstance() *schema.Resource {
    19  	return &schema.Resource{
    20  		Create: resourceAwsInstanceCreate,
    21  		Read:   resourceAwsInstanceRead,
    22  		Update: resourceAwsInstanceUpdate,
    23  		Delete: resourceAwsInstanceDelete,
    24  
    25  		Schema: map[string]*schema.Schema{
    26  			"ami": &schema.Schema{
    27  				Type:     schema.TypeString,
    28  				Required: true,
    29  				ForceNew: true,
    30  			},
    31  
    32  			"associate_public_ip_address": &schema.Schema{
    33  				Type:     schema.TypeBool,
    34  				Optional: true,
    35  				ForceNew: true,
    36  			},
    37  
    38  			"availability_zone": &schema.Schema{
    39  				Type:     schema.TypeString,
    40  				Optional: true,
    41  				Computed: true,
    42  				ForceNew: true,
    43  			},
    44  
    45  			"instance_type": &schema.Schema{
    46  				Type:     schema.TypeString,
    47  				Required: true,
    48  				ForceNew: true,
    49  			},
    50  
    51  			"key_name": &schema.Schema{
    52  				Type:     schema.TypeString,
    53  				Optional: true,
    54  				ForceNew: true,
    55  				Computed: true,
    56  			},
    57  
    58  			"subnet_id": &schema.Schema{
    59  				Type:     schema.TypeString,
    60  				Optional: true,
    61  				Computed: true,
    62  				ForceNew: true,
    63  			},
    64  
    65  			"private_ip": &schema.Schema{
    66  				Type:     schema.TypeString,
    67  				Optional: true,
    68  				ForceNew: true,
    69  				Computed: true,
    70  			},
    71  
    72  			"source_dest_check": &schema.Schema{
    73  				Type:     schema.TypeBool,
    74  				Optional: true,
    75  			},
    76  
    77  			"user_data": &schema.Schema{
    78  				Type:     schema.TypeString,
    79  				Optional: true,
    80  				ForceNew: true,
    81  				StateFunc: func(v interface{}) string {
    82  					switch v.(type) {
    83  					case string:
    84  						hash := sha1.Sum([]byte(v.(string)))
    85  						return hex.EncodeToString(hash[:])
    86  					default:
    87  						return ""
    88  					}
    89  				},
    90  			},
    91  
    92  			"security_groups": &schema.Schema{
    93  				Type:     schema.TypeSet,
    94  				Optional: true,
    95  				Computed: true,
    96  				ForceNew: true,
    97  				Elem:     &schema.Schema{Type: schema.TypeString},
    98  				Set: func(v interface{}) int {
    99  					return hashcode.String(v.(string))
   100  				},
   101  			},
   102  
   103  			"public_dns": &schema.Schema{
   104  				Type:     schema.TypeString,
   105  				Computed: true,
   106  			},
   107  
   108  			"public_ip": &schema.Schema{
   109  				Type:     schema.TypeString,
   110  				Computed: true,
   111  			},
   112  
   113  			"private_dns": &schema.Schema{
   114  				Type:     schema.TypeString,
   115  				Computed: true,
   116  			},
   117  
   118  			"ebs_optimized": &schema.Schema{
   119  				Type:     schema.TypeBool,
   120  				Optional: true,
   121  			},
   122  
   123  			"iam_instance_profile": &schema.Schema{
   124  				Type:     schema.TypeString,
   125  				ForceNew: true,
   126  				Optional: true,
   127  			},
   128  			"tags": tagsSchema(),
   129  
   130  			"block_device": &schema.Schema{
   131  				Type:     schema.TypeSet,
   132  				Optional: true,
   133  				Elem: &schema.Resource{
   134  					Schema: map[string]*schema.Schema{
   135  						"device_name": &schema.Schema{
   136  							Type:     schema.TypeString,
   137  							Required: true,
   138  							ForceNew: true,
   139  						},
   140  
   141  						"snapshot_id": &schema.Schema{
   142  							Type:     schema.TypeString,
   143  							Optional: true,
   144  							ForceNew: true,
   145  						},
   146  
   147  						"volume_type": &schema.Schema{
   148  							Type:     schema.TypeString,
   149  							Optional: true,
   150  							ForceNew: true,
   151  						},
   152  
   153  						"volume_size": &schema.Schema{
   154  							Type:     schema.TypeInt,
   155  							Optional: true,
   156  							ForceNew: true,
   157  						},
   158  
   159  						"delete_on_termination": &schema.Schema{
   160  							Type:     schema.TypeBool,
   161  							Optional: true,
   162  							Default:  true,
   163  							ForceNew: true,
   164  						},
   165  
   166  						"encrypted": &schema.Schema{
   167  							Type:     schema.TypeBool,
   168  							Optional: true,
   169  							ForceNew: true,
   170  						},
   171  					},
   172  				},
   173  				Set: resourceAwsInstanceBlockDevicesHash,
   174  			},
   175  		},
   176  	}
   177  }
   178  
   179  func resourceAwsInstanceCreate(d *schema.ResourceData, meta interface{}) error {
   180  	p := meta.(*ResourceProvider)
   181  	ec2conn := p.ec2conn
   182  
   183  	// Figure out user data
   184  	userData := ""
   185  	if v := d.Get("user_data"); v != nil {
   186  		userData = v.(string)
   187  	}
   188  
   189  	associatePublicIPAddress := false
   190  	if v := d.Get("associate_public_ip_address"); v != nil {
   191  		associatePublicIPAddress = v.(bool)
   192  	}
   193  
   194  	// Build the creation struct
   195  	runOpts := &ec2.RunInstances{
   196  		ImageId:                  d.Get("ami").(string),
   197  		AvailZone:                d.Get("availability_zone").(string),
   198  		InstanceType:             d.Get("instance_type").(string),
   199  		KeyName:                  d.Get("key_name").(string),
   200  		SubnetId:                 d.Get("subnet_id").(string),
   201  		PrivateIPAddress:         d.Get("private_ip").(string),
   202  		AssociatePublicIpAddress: associatePublicIPAddress,
   203  		UserData:                 []byte(userData),
   204  		EbsOptimized:             d.Get("ebs_optimized").(bool),
   205  		IamInstanceProfile:       d.Get("iam_instance_profile").(string),
   206  	}
   207  
   208  	if v := d.Get("security_groups"); v != nil {
   209  		for _, v := range v.(*schema.Set).List() {
   210  			str := v.(string)
   211  
   212  			var g ec2.SecurityGroup
   213  			if runOpts.SubnetId != "" {
   214  				g.Id = str
   215  			} else {
   216  				g.Name = str
   217  			}
   218  
   219  			runOpts.SecurityGroups = append(runOpts.SecurityGroups, g)
   220  		}
   221  	}
   222  
   223  	if v := d.Get("block_device"); v != nil {
   224  		vs := v.(*schema.Set).List()
   225  		if len(vs) > 0 {
   226  			runOpts.BlockDevices = make([]ec2.BlockDeviceMapping, len(vs))
   227  			for i, v := range vs {
   228  				bd := v.(map[string]interface{})
   229  				runOpts.BlockDevices[i].DeviceName = bd["device_name"].(string)
   230  				runOpts.BlockDevices[i].SnapshotId = bd["snapshot_id"].(string)
   231  				runOpts.BlockDevices[i].VolumeType = bd["volume_type"].(string)
   232  				runOpts.BlockDevices[i].VolumeSize = int64(bd["volume_size"].(int))
   233  				runOpts.BlockDevices[i].DeleteOnTermination = bd["delete_on_termination"].(bool)
   234  				runOpts.BlockDevices[i].Encrypted = bd["encrypted"].(bool)
   235  			}
   236  		}
   237  	}
   238  
   239  	// Create the instance
   240  	log.Printf("[DEBUG] Run configuration: %#v", runOpts)
   241  	runResp, err := ec2conn.RunInstances(runOpts)
   242  	if err != nil {
   243  		return fmt.Errorf("Error launching source instance: %s", err)
   244  	}
   245  
   246  	instance := &runResp.Instances[0]
   247  	log.Printf("[INFO] Instance ID: %s", instance.InstanceId)
   248  
   249  	// Store the resulting ID so we can look this up later
   250  	d.SetId(instance.InstanceId)
   251  
   252  	// Wait for the instance to become running so we can get some attributes
   253  	// that aren't available until later.
   254  	log.Printf(
   255  		"[DEBUG] Waiting for instance (%s) to become running",
   256  		instance.InstanceId)
   257  
   258  	stateConf := &resource.StateChangeConf{
   259  		Pending:    []string{"pending"},
   260  		Target:     "running",
   261  		Refresh:    InstanceStateRefreshFunc(ec2conn, instance.InstanceId),
   262  		Timeout:    10 * time.Minute,
   263  		Delay:      10 * time.Second,
   264  		MinTimeout: 3 * time.Second,
   265  	}
   266  
   267  	instanceRaw, err := stateConf.WaitForState()
   268  	if err != nil {
   269  		return fmt.Errorf(
   270  			"Error waiting for instance (%s) to become ready: %s",
   271  			instance.InstanceId, err)
   272  	}
   273  
   274  	instance = instanceRaw.(*ec2.Instance)
   275  
   276  	// Initialize the connection info
   277  	d.SetConnInfo(map[string]string{
   278  		"type": "ssh",
   279  		"host": instance.PublicIpAddress,
   280  	})
   281  
   282  	// Set our attributes
   283  	if err := resourceAwsInstanceRead(d, meta); err != nil {
   284  		return err
   285  	}
   286  
   287  	// Update if we need to
   288  	return resourceAwsInstanceUpdate(d, meta)
   289  }
   290  
   291  func resourceAwsInstanceUpdate(d *schema.ResourceData, meta interface{}) error {
   292  	p := meta.(*ResourceProvider)
   293  	ec2conn := p.ec2conn
   294  
   295  	modify := false
   296  	opts := new(ec2.ModifyInstance)
   297  
   298  	if v, ok := d.GetOk("source_dest_check"); ok {
   299  		opts.SourceDestCheck = v.(bool)
   300  		opts.SetSourceDestCheck = true
   301  		modify = true
   302  	}
   303  
   304  	if modify {
   305  		log.Printf("[INFO] Modifing instance %s: %#v", d.Id(), opts)
   306  		if _, err := ec2conn.ModifyInstance(d.Id(), opts); err != nil {
   307  			return err
   308  		}
   309  
   310  		// TODO(mitchellh): wait for the attributes we modified to
   311  		// persist the change...
   312  	}
   313  
   314  	if err := setTags(ec2conn, d); err != nil {
   315  		return err
   316  	} else {
   317  		d.SetPartial("tags")
   318  	}
   319  
   320  	return nil
   321  }
   322  
   323  func resourceAwsInstanceDelete(d *schema.ResourceData, meta interface{}) error {
   324  	p := meta.(*ResourceProvider)
   325  	ec2conn := p.ec2conn
   326  
   327  	log.Printf("[INFO] Terminating instance: %s", d.Id())
   328  	if _, err := ec2conn.TerminateInstances([]string{d.Id()}); err != nil {
   329  		return fmt.Errorf("Error terminating instance: %s", err)
   330  	}
   331  
   332  	log.Printf(
   333  		"[DEBUG] Waiting for instance (%s) to become terminated",
   334  		d.Id())
   335  
   336  	stateConf := &resource.StateChangeConf{
   337  		Pending:    []string{"pending", "running", "shutting-down", "stopped", "stopping"},
   338  		Target:     "terminated",
   339  		Refresh:    InstanceStateRefreshFunc(ec2conn, d.Id()),
   340  		Timeout:    10 * time.Minute,
   341  		Delay:      10 * time.Second,
   342  		MinTimeout: 3 * time.Second,
   343  	}
   344  
   345  	_, err := stateConf.WaitForState()
   346  	if err != nil {
   347  		return fmt.Errorf(
   348  			"Error waiting for instance (%s) to terminate: %s",
   349  			d.Id(), err)
   350  	}
   351  
   352  	d.SetId("")
   353  	return nil
   354  }
   355  
   356  func resourceAwsInstanceRead(d *schema.ResourceData, meta interface{}) error {
   357  	p := meta.(*ResourceProvider)
   358  	ec2conn := p.ec2conn
   359  
   360  	resp, err := ec2conn.Instances([]string{d.Id()}, ec2.NewFilter())
   361  	if err != nil {
   362  		// If the instance was not found, return nil so that we can show
   363  		// that the instance is gone.
   364  		if ec2err, ok := err.(*ec2.Error); ok && ec2err.Code == "InvalidInstanceID.NotFound" {
   365  			d.SetId("")
   366  			return nil
   367  		}
   368  
   369  		// Some other error, report it
   370  		return err
   371  	}
   372  
   373  	// If nothing was found, then return no state
   374  	if len(resp.Reservations) == 0 {
   375  		d.SetId("")
   376  		return nil
   377  	}
   378  
   379  	instance := &resp.Reservations[0].Instances[0]
   380  
   381  	// If the instance is terminated, then it is gone
   382  	if instance.State.Name == "terminated" {
   383  		d.SetId("")
   384  		return nil
   385  	}
   386  
   387  	d.Set("availability_zone", instance.AvailZone)
   388  	d.Set("key_name", instance.KeyName)
   389  	d.Set("public_dns", instance.DNSName)
   390  	d.Set("public_ip", instance.PublicIpAddress)
   391  	d.Set("private_dns", instance.PrivateDNSName)
   392  	d.Set("private_ip", instance.PrivateIpAddress)
   393  	d.Set("subnet_id", instance.SubnetId)
   394  	d.Set("ebs_optimized", instance.EbsOptimized)
   395  	d.Set("tags", tagsToMap(instance.Tags))
   396  
   397  	// Determine whether we're referring to security groups with
   398  	// IDs or names. We use a heuristic to figure this out. By default,
   399  	// we use IDs if we're in a VPC. However, if we previously had an
   400  	// all-name list of security groups, we use names. Or, if we had any
   401  	// IDs, we use IDs.
   402  	useID := instance.SubnetId != ""
   403  	if v := d.Get("security_groups"); v != nil {
   404  		match := false
   405  		for _, v := range v.(*schema.Set).List() {
   406  			if strings.HasPrefix(v.(string), "sg-") {
   407  				match = true
   408  				break
   409  			}
   410  		}
   411  
   412  		useID = match
   413  	}
   414  
   415  	// Build up the security groups
   416  	sgs := make([]string, len(instance.SecurityGroups))
   417  	for i, sg := range instance.SecurityGroups {
   418  		if useID {
   419  			sgs[i] = sg.Id
   420  		} else {
   421  			sgs[i] = sg.Name
   422  		}
   423  	}
   424  	d.Set("security_groups", sgs)
   425  
   426  	volIDs := make([]string, len(instance.BlockDevices))
   427  	bdByVolID := make(map[string]ec2.BlockDevice)
   428  	for i, bd := range instance.BlockDevices {
   429  		volIDs[i] = bd.VolumeId
   430  		bdByVolID[bd.VolumeId] = bd
   431  	}
   432  
   433  	volResp, err := ec2conn.Volumes(volIDs, ec2.NewFilter())
   434  	if err != nil {
   435  		return err
   436  	}
   437  
   438  	bds := make([]map[string]interface{}, len(instance.BlockDevices))
   439  	for i, vol := range volResp.Volumes {
   440  		bds[i] = make(map[string]interface{})
   441  		bds[i]["device_name"] = bdByVolID[vol.VolumeId].DeviceName
   442  		bds[i]["snapshot_id"] = vol.SnapshotId
   443  		bds[i]["volume_type"] = vol.VolumeType
   444  		bds[i]["volume_size"] = vol.Size
   445  		bds[i]["delete_on_termination"] = bdByVolID[vol.VolumeId].DeleteOnTermination
   446  		bds[i]["encrypted"] = vol.Encrypted
   447  	}
   448  	d.Set("block_device", bds)
   449  
   450  	return nil
   451  }
   452  
   453  // InstanceStateRefreshFunc returns a resource.StateRefreshFunc that is used to watch
   454  // an EC2 instance.
   455  func InstanceStateRefreshFunc(conn *ec2.EC2, instanceID string) resource.StateRefreshFunc {
   456  	return func() (interface{}, string, error) {
   457  		resp, err := conn.Instances([]string{instanceID}, ec2.NewFilter())
   458  		if err != nil {
   459  			if ec2err, ok := err.(*ec2.Error); ok && ec2err.Code == "InvalidInstanceID.NotFound" {
   460  				// Set this to nil as if we didn't find anything.
   461  				resp = nil
   462  			} else {
   463  				log.Printf("Error on InstanceStateRefresh: %s", err)
   464  				return nil, "", err
   465  			}
   466  		}
   467  
   468  		if resp == nil || len(resp.Reservations) == 0 || len(resp.Reservations[0].Instances) == 0 {
   469  			// Sometimes AWS just has consistency issues and doesn't see
   470  			// our instance yet. Return an empty state.
   471  			return nil, "", nil
   472  		}
   473  
   474  		i := &resp.Reservations[0].Instances[0]
   475  		return i, i.State.Name, nil
   476  	}
   477  }
   478  
   479  func resourceAwsInstanceBlockDevicesHash(v interface{}) int {
   480  	var buf bytes.Buffer
   481  	m := v.(map[string]interface{})
   482  	buf.WriteString(fmt.Sprintf("%s-", m["device_name"].(string)))
   483  	buf.WriteString(fmt.Sprintf("%s-", m["snapshot_id"].(string)))
   484  	buf.WriteString(fmt.Sprintf("%s-", m["volume_type"].(string)))
   485  	buf.WriteString(fmt.Sprintf("%d-", m["volume_size"].(int)))
   486  	buf.WriteString(fmt.Sprintf("%t-", m["delete_on_termination"].(bool)))
   487  	buf.WriteString(fmt.Sprintf("%t-", m["encrypted"].(bool)))
   488  	return hashcode.String(buf.String())
   489  }