github.com/ezbercih/terraform@v0.1.1-0.20140729011846-3c33865e0839/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  	"strconv"
     9  	"strings"
    10  	"time"
    11  
    12  	"github.com/hashicorp/terraform/flatmap"
    13  	"github.com/hashicorp/terraform/helper/diff"
    14  	"github.com/hashicorp/terraform/helper/resource"
    15  	"github.com/hashicorp/terraform/terraform"
    16  	"github.com/mitchellh/goamz/ec2"
    17  )
    18  
    19  func resource_aws_instance_create(
    20  	s *terraform.ResourceState,
    21  	d *terraform.ResourceDiff,
    22  	meta interface{}) (*terraform.ResourceState, error) {
    23  	p := meta.(*ResourceProvider)
    24  	ec2conn := p.ec2conn
    25  
    26  	// Merge the diff into the state so that we have all the attributes
    27  	// properly.
    28  	rs := s.MergeDiff(d)
    29  	delete(rs.Attributes, "source_dest_check")
    30  
    31  	// Figure out user data
    32  	userData := ""
    33  	if attr, ok := d.Attributes["user_data"]; ok {
    34  		userData = attr.NewExtra.(string)
    35  	}
    36  
    37  	// Build the creation struct
    38  	runOpts := &ec2.RunInstances{
    39  		ImageId:      rs.Attributes["ami"],
    40  		InstanceType: rs.Attributes["instance_type"],
    41  		KeyName:      rs.Attributes["key_name"],
    42  		SubnetId:     rs.Attributes["subnet_id"],
    43  		UserData:     []byte(userData),
    44  	}
    45  	if raw := flatmap.Expand(rs.Attributes, "security_groups"); raw != nil {
    46  		if sgs, ok := raw.([]interface{}); ok {
    47  			for _, sg := range sgs {
    48  				str, ok := sg.(string)
    49  				if !ok {
    50  					continue
    51  				}
    52  
    53  				var g ec2.SecurityGroup
    54  				if runOpts.SubnetId != "" {
    55  					g.Id = str
    56  				} else {
    57  					g.Name = str
    58  				}
    59  
    60  				runOpts.SecurityGroups = append(runOpts.SecurityGroups, g)
    61  			}
    62  		}
    63  	}
    64  
    65  	// Create the instance
    66  	log.Printf("[DEBUG] Run configuration: %#v", runOpts)
    67  	runResp, err := ec2conn.RunInstances(runOpts)
    68  	if err != nil {
    69  		return nil, fmt.Errorf("Error launching source instance: %s", err)
    70  	}
    71  
    72  	instance := &runResp.Instances[0]
    73  	log.Printf("[INFO] Instance ID: %s", instance.InstanceId)
    74  
    75  	// Store the resulting ID so we can look this up later
    76  	rs.ID = instance.InstanceId
    77  
    78  	// Wait for the instance to become running so we can get some attributes
    79  	// that aren't available until later.
    80  	log.Printf(
    81  		"[DEBUG] Waiting for instance (%s) to become running",
    82  		instance.InstanceId)
    83  
    84  	stateConf := &resource.StateChangeConf{
    85  		Pending:    []string{"pending"},
    86  		Target:     "running",
    87  		Refresh:    InstanceStateRefreshFunc(ec2conn, instance.InstanceId),
    88  		Timeout:    10 * time.Minute,
    89  		Delay:      10 * time.Second,
    90  		MinTimeout: 3 * time.Second,
    91  	}
    92  
    93  	instanceRaw, err := stateConf.WaitForState()
    94  
    95  	if err != nil {
    96  		return rs, fmt.Errorf(
    97  			"Error waiting for instance (%s) to become ready: %s",
    98  			instance.InstanceId, err)
    99  	}
   100  
   101  	instance = instanceRaw.(*ec2.Instance)
   102  
   103  	// Initialize the connection info
   104  	rs.ConnInfo["type"] = "ssh"
   105  	rs.ConnInfo["host"] = instance.PublicIpAddress
   106  
   107  	// Set our attributes
   108  	rs, err = resource_aws_instance_update_state(rs, instance)
   109  	if err != nil {
   110  		return rs, err
   111  	}
   112  
   113  	// Update if we need to
   114  	return resource_aws_instance_update(rs, d, meta)
   115  }
   116  
   117  func resource_aws_instance_update(
   118  	s *terraform.ResourceState,
   119  	d *terraform.ResourceDiff,
   120  	meta interface{}) (*terraform.ResourceState, error) {
   121  	p := meta.(*ResourceProvider)
   122  	ec2conn := p.ec2conn
   123  	rs := s.MergeDiff(d)
   124  
   125  	modify := false
   126  	opts := new(ec2.ModifyInstance)
   127  
   128  	if attr, ok := d.Attributes["source_dest_check"]; ok {
   129  		modify = true
   130  		opts.SourceDestCheck = attr.New != "" && attr.New != "false"
   131  		opts.SetSourceDestCheck = true
   132  		rs.Attributes["source_dest_check"] = strconv.FormatBool(
   133  			opts.SourceDestCheck)
   134  	}
   135  
   136  	if modify {
   137  		log.Printf("[INFO] Modifing instance %s: %#v", s.ID, opts)
   138  		if _, err := ec2conn.ModifyInstance(s.ID, opts); err != nil {
   139  			return s, err
   140  		}
   141  
   142  		// TODO(mitchellh): wait for the attributes we modified to
   143  		// persist the change...
   144  	}
   145  
   146  	return rs, nil
   147  }
   148  
   149  func resource_aws_instance_destroy(
   150  	s *terraform.ResourceState,
   151  	meta interface{}) error {
   152  	p := meta.(*ResourceProvider)
   153  	ec2conn := p.ec2conn
   154  
   155  	log.Printf("[INFO] Terminating instance: %s", s.ID)
   156  	if _, err := ec2conn.TerminateInstances([]string{s.ID}); err != nil {
   157  		return fmt.Errorf("Error terminating instance: %s", err)
   158  	}
   159  
   160  	log.Printf(
   161  		"[DEBUG] Waiting for instance (%s) to become terminated",
   162  		s.ID)
   163  
   164  	stateConf := &resource.StateChangeConf{
   165  		Pending:    []string{"pending", "running", "shutting-down", "stopped", "stopping"},
   166  		Target:     "terminated",
   167  		Refresh:    InstanceStateRefreshFunc(ec2conn, s.ID),
   168  		Timeout:    10 * time.Minute,
   169  		Delay:      10 * time.Second,
   170  		MinTimeout: 3 * time.Second,
   171  	}
   172  
   173  	_, err := stateConf.WaitForState()
   174  
   175  	if err != nil {
   176  		return fmt.Errorf(
   177  			"Error waiting for instance (%s) to terminate: %s",
   178  			s.ID, err)
   179  	}
   180  
   181  	return nil
   182  }
   183  
   184  func resource_aws_instance_diff(
   185  	s *terraform.ResourceState,
   186  	c *terraform.ResourceConfig,
   187  	meta interface{}) (*terraform.ResourceDiff, error) {
   188  	b := &diff.ResourceBuilder{
   189  		Attrs: map[string]diff.AttrType{
   190  			"ami":               diff.AttrTypeCreate,
   191  			"availability_zone": diff.AttrTypeCreate,
   192  			"instance_type":     diff.AttrTypeCreate,
   193  			"key_name":          diff.AttrTypeCreate,
   194  			"security_groups":   diff.AttrTypeCreate,
   195  			"subnet_id":         diff.AttrTypeCreate,
   196  			"source_dest_check": diff.AttrTypeUpdate,
   197  			"user_data":         diff.AttrTypeCreate,
   198  		},
   199  
   200  		ComputedAttrs: []string{
   201  			"availability_zone",
   202  			"key_name",
   203  			"public_dns",
   204  			"public_ip",
   205  			"private_dns",
   206  			"private_ip",
   207  			"security_groups",
   208  			"subnet_id",
   209  		},
   210  
   211  		PreProcess: map[string]diff.PreProcessFunc{
   212  			"user_data": func(v string) string {
   213  				hash := sha1.Sum([]byte(v))
   214  				return hex.EncodeToString(hash[:])
   215  			},
   216  		},
   217  	}
   218  
   219  	return b.Diff(s, c)
   220  }
   221  
   222  func resource_aws_instance_refresh(
   223  	s *terraform.ResourceState,
   224  	meta interface{}) (*terraform.ResourceState, error) {
   225  	p := meta.(*ResourceProvider)
   226  	ec2conn := p.ec2conn
   227  
   228  	resp, err := ec2conn.Instances([]string{s.ID}, ec2.NewFilter())
   229  	if err != nil {
   230  		// If the instance was not found, return nil so that we can show
   231  		// that the instance is gone.
   232  		if ec2err, ok := err.(*ec2.Error); ok && ec2err.Code == "InvalidInstanceID.NotFound" {
   233  			return nil, nil
   234  		}
   235  
   236  		// Some other error, report it
   237  		return s, err
   238  	}
   239  
   240  	// If nothing was found, then return no state
   241  	if len(resp.Reservations) == 0 {
   242  		return nil, nil
   243  	}
   244  
   245  	instance := &resp.Reservations[0].Instances[0]
   246  
   247  	// If the instance is terminated, then it is gone
   248  	if instance.State.Name == "terminated" {
   249  		return nil, nil
   250  	}
   251  
   252  	return resource_aws_instance_update_state(s, instance)
   253  }
   254  
   255  func resource_aws_instance_update_state(
   256  	s *terraform.ResourceState,
   257  	instance *ec2.Instance) (*terraform.ResourceState, error) {
   258  	s.Attributes["availability_zone"] = instance.AvailZone
   259  	s.Attributes["key_name"] = instance.KeyName
   260  	s.Attributes["public_dns"] = instance.DNSName
   261  	s.Attributes["public_ip"] = instance.PublicIpAddress
   262  	s.Attributes["private_dns"] = instance.PrivateDNSName
   263  	s.Attributes["private_ip"] = instance.PrivateIpAddress
   264  	s.Attributes["subnet_id"] = instance.SubnetId
   265  	s.Dependencies = nil
   266  
   267  	// Extract the existing security groups
   268  	useID := false
   269  	if raw := flatmap.Expand(s.Attributes, "security_groups"); raw != nil {
   270  		if sgs, ok := raw.([]interface{}); ok {
   271  			for _, sg := range sgs {
   272  				str, ok := sg.(string)
   273  				if !ok {
   274  					continue
   275  				}
   276  
   277  				if strings.HasPrefix(str, "sg-") {
   278  					useID = true
   279  					break
   280  				}
   281  			}
   282  		}
   283  	}
   284  
   285  	// Build up the security groups
   286  	sgs := make([]string, len(instance.SecurityGroups))
   287  	for i, sg := range instance.SecurityGroups {
   288  		if instance.SubnetId != "" && useID {
   289  			sgs[i] = sg.Id
   290  		} else {
   291  			sgs[i] = sg.Name
   292  		}
   293  
   294  		s.Dependencies = append(s.Dependencies,
   295  			terraform.ResourceDependency{ID: sg.Id},
   296  		)
   297  	}
   298  	flatmap.Map(s.Attributes).Merge(flatmap.Flatten(map[string]interface{}{
   299  		"security_groups": sgs,
   300  	}))
   301  
   302  	if instance.SubnetId != "" {
   303  		s.Dependencies = append(s.Dependencies,
   304  			terraform.ResourceDependency{ID: instance.SubnetId},
   305  		)
   306  	}
   307  
   308  	return s, nil
   309  }
   310  
   311  // InstanceStateRefreshFunc returns a resource.StateRefreshFunc that is used to watch
   312  // an EC2 instance.
   313  func InstanceStateRefreshFunc(conn *ec2.EC2, instanceID string) resource.StateRefreshFunc {
   314  	return func() (interface{}, string, error) {
   315  		resp, err := conn.Instances([]string{instanceID}, ec2.NewFilter())
   316  		if err != nil {
   317  			if ec2err, ok := err.(*ec2.Error); ok && ec2err.Code == "InvalidInstanceID.NotFound" {
   318  				// Set this to nil as if we didn't find anything.
   319  				resp = nil
   320  			} else {
   321  				log.Printf("Error on InstanceStateRefresh: %s", err)
   322  				return nil, "", err
   323  			}
   324  		}
   325  
   326  		if resp == nil || len(resp.Reservations) == 0 || len(resp.Reservations[0].Instances) == 0 {
   327  			// Sometimes AWS just has consistency issues and doesn't see
   328  			// our instance yet. Return an empty state.
   329  			return nil, "", nil
   330  		}
   331  
   332  		i := &resp.Reservations[0].Instances[0]
   333  		return i, i.State.Name, nil
   334  	}
   335  }