github.com/bengesoff/terraform@v0.3.1-0.20141018223233-b25a53629922/builtin/providers/aws/resource_aws_instance.go (about)

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