github.com/hobbeswalsh/terraform@v0.3.7-0.20150619183303-ad17cf55a0fa/builtin/providers/aws/resource_aws_spot_instance_request.go (about)

     1  package aws
     2  
     3  import (
     4  	"fmt"
     5  	"log"
     6  	"time"
     7  
     8  	"github.com/aws/aws-sdk-go/aws"
     9  	"github.com/aws/aws-sdk-go/aws/awserr"
    10  	"github.com/aws/aws-sdk-go/aws/awsutil"
    11  	"github.com/aws/aws-sdk-go/service/ec2"
    12  	"github.com/hashicorp/terraform/helper/resource"
    13  	"github.com/hashicorp/terraform/helper/schema"
    14  )
    15  
    16  func resourceAwsSpotInstanceRequest() *schema.Resource {
    17  	return &schema.Resource{
    18  		Create: resourceAwsSpotInstanceRequestCreate,
    19  		Read:   resourceAwsSpotInstanceRequestRead,
    20  		Delete: resourceAwsSpotInstanceRequestDelete,
    21  		Update: resourceAwsSpotInstanceRequestUpdate,
    22  
    23  		Schema: func() map[string]*schema.Schema {
    24  			// The Spot Instance Request Schema is based on the AWS Instance schema.
    25  			s := resourceAwsInstance().Schema
    26  
    27  			// Everything on a spot instance is ForceNew except tags
    28  			for k, v := range s {
    29  				if k == "tags" {
    30  					continue
    31  				}
    32  				v.ForceNew = true
    33  			}
    34  
    35  			s["spot_price"] = &schema.Schema{
    36  				Type:     schema.TypeString,
    37  				Required: true,
    38  				ForceNew: true,
    39  			}
    40  			s["wait_for_fulfillment"] = &schema.Schema{
    41  				Type:     schema.TypeBool,
    42  				Optional: true,
    43  				Default:  false,
    44  			}
    45  			s["spot_bid_status"] = &schema.Schema{
    46  				Type:     schema.TypeString,
    47  				Computed: true,
    48  			}
    49  			s["spot_request_state"] = &schema.Schema{
    50  				Type:     schema.TypeString,
    51  				Computed: true,
    52  			}
    53  			s["spot_instance_id"] = &schema.Schema{
    54  				Type:     schema.TypeString,
    55  				Computed: true,
    56  			}
    57  
    58  			return s
    59  		}(),
    60  	}
    61  }
    62  
    63  func resourceAwsSpotInstanceRequestCreate(d *schema.ResourceData, meta interface{}) error {
    64  	conn := meta.(*AWSClient).ec2conn
    65  
    66  	instanceOpts, err := buildAwsInstanceOpts(d, meta)
    67  	if err != nil {
    68  		return err
    69  	}
    70  
    71  	spotOpts := &ec2.RequestSpotInstancesInput{
    72  		SpotPrice: aws.String(d.Get("spot_price").(string)),
    73  
    74  		// We always set the type to "persistent", since the imperative-like
    75  		// behavior of "one-time" does not map well to TF's declarative domain.
    76  		Type: aws.String("persistent"),
    77  
    78  		// Though the AWS API supports creating spot instance requests for multiple
    79  		// instances, for TF purposes we fix this to one instance per request.
    80  		// Users can get equivalent behavior out of TF's "count" meta-parameter.
    81  		InstanceCount: aws.Long(1),
    82  
    83  		LaunchSpecification: &ec2.RequestSpotLaunchSpecification{
    84  			BlockDeviceMappings: instanceOpts.BlockDeviceMappings,
    85  			EBSOptimized:        instanceOpts.EBSOptimized,
    86  			IAMInstanceProfile:  instanceOpts.IAMInstanceProfile,
    87  			ImageID:             instanceOpts.ImageID,
    88  			InstanceType:        instanceOpts.InstanceType,
    89  			Placement:           instanceOpts.SpotPlacement,
    90  			SecurityGroupIDs:    instanceOpts.SecurityGroupIDs,
    91  			SecurityGroups:      instanceOpts.SecurityGroups,
    92  			UserData:            instanceOpts.UserData64,
    93  		},
    94  	}
    95  
    96  	// Make the spot instance request
    97  	resp, err := conn.RequestSpotInstances(spotOpts)
    98  	if err != nil {
    99  		return fmt.Errorf("Error requesting spot instances: %s", err)
   100  	}
   101  	if len(resp.SpotInstanceRequests) != 1 {
   102  		return fmt.Errorf(
   103  			"Expected response with length 1, got: %s", awsutil.StringValue(resp))
   104  	}
   105  
   106  	sir := *resp.SpotInstanceRequests[0]
   107  	d.SetId(*sir.SpotInstanceRequestID)
   108  
   109  	if d.Get("wait_for_fulfillment").(bool) {
   110  		spotStateConf := &resource.StateChangeConf{
   111  			// http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/spot-bid-status.html
   112  			Pending:    []string{"start", "pending-evaluation", "pending-fulfillment"},
   113  			Target:     "fulfilled",
   114  			Refresh:    SpotInstanceStateRefreshFunc(conn, sir),
   115  			Timeout:    10 * time.Minute,
   116  			Delay:      10 * time.Second,
   117  			MinTimeout: 3 * time.Second,
   118  		}
   119  
   120  		log.Printf("[DEBUG] waiting for spot bid to resolve... this may take several minutes.")
   121  		_, err = spotStateConf.WaitForState()
   122  
   123  		if err != nil {
   124  			return fmt.Errorf("Error while waiting for spot request (%s) to resolve: %s", awsutil.StringValue(sir), err)
   125  		}
   126  	}
   127  
   128  	return resourceAwsSpotInstanceRequestUpdate(d, meta)
   129  }
   130  
   131  // Update spot state, etc
   132  func resourceAwsSpotInstanceRequestRead(d *schema.ResourceData, meta interface{}) error {
   133  	conn := meta.(*AWSClient).ec2conn
   134  
   135  	req := &ec2.DescribeSpotInstanceRequestsInput{
   136  		SpotInstanceRequestIDs: []*string{aws.String(d.Id())},
   137  	}
   138  	resp, err := conn.DescribeSpotInstanceRequests(req)
   139  
   140  	if err != nil {
   141  		// If the spot request was not found, return nil so that we can show
   142  		// that it is gone.
   143  		if ec2err, ok := err.(awserr.Error); ok && ec2err.Code() == "InvalidSpotInstanceRequestID.NotFound" {
   144  			d.SetId("")
   145  			return nil
   146  		}
   147  
   148  		// Some other error, report it
   149  		return err
   150  	}
   151  
   152  	// If nothing was found, then return no state
   153  	if len(resp.SpotInstanceRequests) == 0 {
   154  		d.SetId("")
   155  		return nil
   156  	}
   157  
   158  	request := resp.SpotInstanceRequests[0]
   159  
   160  	// if the request is cancelled, then it is gone
   161  	if *request.State == "canceled" {
   162  		d.SetId("")
   163  		return nil
   164  	}
   165  
   166  	d.Set("spot_bid_status", *request.Status.Code)
   167  	d.Set("spot_instance_id", *request.InstanceID)
   168  	d.Set("spot_request_state", *request.State)
   169  	d.Set("tags", tagsToMap(request.Tags))
   170  
   171  	return nil
   172  }
   173  
   174  func resourceAwsSpotInstanceRequestUpdate(d *schema.ResourceData, meta interface{}) error {
   175  	conn := meta.(*AWSClient).ec2conn
   176  
   177  	d.Partial(true)
   178  	if err := setTags(conn, d); err != nil {
   179  		return err
   180  	} else {
   181  		d.SetPartial("tags")
   182  	}
   183  
   184  	d.Partial(false)
   185  
   186  	return resourceAwsInstanceRead(d, meta)
   187  }
   188  
   189  func resourceAwsSpotInstanceRequestDelete(d *schema.ResourceData, meta interface{}) error {
   190  	conn := meta.(*AWSClient).ec2conn
   191  
   192  	log.Printf("[INFO] Cancelling spot request: %s", d.Id())
   193  	_, err := conn.CancelSpotInstanceRequests(&ec2.CancelSpotInstanceRequestsInput{
   194  		SpotInstanceRequestIDs: []*string{aws.String(d.Id())},
   195  	})
   196  
   197  	if err != nil {
   198  		return fmt.Errorf("Error cancelling spot request (%s): %s", d.Id(), err)
   199  	}
   200  
   201  	if instanceId := d.Get("spot_instance_id").(string); instanceId != "" {
   202  		log.Printf("[INFO] Terminating instance: %s", instanceId)
   203  		if err := awsTerminateInstance(conn, instanceId); err != nil {
   204  			return fmt.Errorf("Error terminating spot instance: %s", err)
   205  		}
   206  	}
   207  
   208  	return nil
   209  }
   210  
   211  // SpotInstanceStateRefreshFunc returns a resource.StateRefreshFunc that is used to watch
   212  // an EC2 spot instance request
   213  func SpotInstanceStateRefreshFunc(
   214  	conn *ec2.EC2, sir ec2.SpotInstanceRequest) resource.StateRefreshFunc {
   215  
   216  	return func() (interface{}, string, error) {
   217  		resp, err := conn.DescribeSpotInstanceRequests(&ec2.DescribeSpotInstanceRequestsInput{
   218  			SpotInstanceRequestIDs: []*string{sir.SpotInstanceRequestID},
   219  		})
   220  
   221  		if err != nil {
   222  			if ec2err, ok := err.(awserr.Error); ok && ec2err.Code() == "InvalidSpotInstanceRequestID.NotFound" {
   223  				// Set this to nil as if we didn't find anything.
   224  				resp = nil
   225  			} else {
   226  				log.Printf("Error on StateRefresh: %s", err)
   227  				return nil, "", err
   228  			}
   229  		}
   230  
   231  		if resp == nil || len(resp.SpotInstanceRequests) == 0 {
   232  			// Sometimes AWS just has consistency issues and doesn't see
   233  			// our request yet. Return an empty state.
   234  			return nil, "", nil
   235  		}
   236  
   237  		req := resp.SpotInstanceRequests[0]
   238  		return req, *req.Status.Code, nil
   239  	}
   240  }