github.com/econnell/terraform@v0.5.4-0.20150722160631-78eb236786a4/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  			Monitoring:          instanceOpts.Monitoring,
    87  			IAMInstanceProfile:  instanceOpts.IAMInstanceProfile,
    88  			ImageID:             instanceOpts.ImageID,
    89  			InstanceType:        instanceOpts.InstanceType,
    90  			Placement:           instanceOpts.SpotPlacement,
    91  			SecurityGroupIDs:    instanceOpts.SecurityGroupIDs,
    92  			SecurityGroups:      instanceOpts.SecurityGroups,
    93  			UserData:            instanceOpts.UserData64,
    94  		},
    95  	}
    96  
    97  	// Make the spot instance request
    98  	log.Printf("[DEBUG] Requesting spot bid opts: %s", awsutil.StringValue(spotOpts))
    99  	resp, err := conn.RequestSpotInstances(spotOpts)
   100  	if err != nil {
   101  		return fmt.Errorf("Error requesting spot instances: %s", err)
   102  	}
   103  	if len(resp.SpotInstanceRequests) != 1 {
   104  		return fmt.Errorf(
   105  			"Expected response with length 1, got: %s", awsutil.StringValue(resp))
   106  	}
   107  
   108  	sir := *resp.SpotInstanceRequests[0]
   109  	d.SetId(*sir.SpotInstanceRequestID)
   110  
   111  	if d.Get("wait_for_fulfillment").(bool) {
   112  		spotStateConf := &resource.StateChangeConf{
   113  			// http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/spot-bid-status.html
   114  			Pending:    []string{"start", "pending-evaluation", "pending-fulfillment"},
   115  			Target:     "fulfilled",
   116  			Refresh:    SpotInstanceStateRefreshFunc(conn, sir),
   117  			Timeout:    10 * time.Minute,
   118  			Delay:      10 * time.Second,
   119  			MinTimeout: 3 * time.Second,
   120  		}
   121  
   122  		log.Printf("[DEBUG] waiting for spot bid to resolve... this may take several minutes.")
   123  		_, err = spotStateConf.WaitForState()
   124  
   125  		if err != nil {
   126  			return fmt.Errorf("Error while waiting for spot request (%s) to resolve: %s", awsutil.StringValue(sir), err)
   127  		}
   128  	}
   129  
   130  	return resourceAwsSpotInstanceRequestUpdate(d, meta)
   131  }
   132  
   133  // Update spot state, etc
   134  func resourceAwsSpotInstanceRequestRead(d *schema.ResourceData, meta interface{}) error {
   135  	conn := meta.(*AWSClient).ec2conn
   136  
   137  	req := &ec2.DescribeSpotInstanceRequestsInput{
   138  		SpotInstanceRequestIDs: []*string{aws.String(d.Id())},
   139  	}
   140  	resp, err := conn.DescribeSpotInstanceRequests(req)
   141  
   142  	if err != nil {
   143  		// If the spot request was not found, return nil so that we can show
   144  		// that it is gone.
   145  		if ec2err, ok := err.(awserr.Error); ok && ec2err.Code() == "InvalidSpotInstanceRequestID.NotFound" {
   146  			d.SetId("")
   147  			return nil
   148  		}
   149  
   150  		// Some other error, report it
   151  		return err
   152  	}
   153  
   154  	// If nothing was found, then return no state
   155  	if len(resp.SpotInstanceRequests) == 0 {
   156  		d.SetId("")
   157  		return nil
   158  	}
   159  
   160  	request := resp.SpotInstanceRequests[0]
   161  
   162  	// if the request is cancelled, then it is gone
   163  	if *request.State == "cancelled" {
   164  		d.SetId("")
   165  		return nil
   166  	}
   167  
   168  	d.Set("spot_bid_status", *request.Status.Code)
   169  	// Instance ID is not set if the request is still pending
   170  	if request.InstanceID != nil {
   171  		d.Set("spot_instance_id", *request.InstanceID)
   172  	}
   173  	d.Set("spot_request_state", *request.State)
   174  	d.Set("tags", tagsToMap(request.Tags))
   175  
   176  	return nil
   177  }
   178  
   179  func resourceAwsSpotInstanceRequestUpdate(d *schema.ResourceData, meta interface{}) error {
   180  	conn := meta.(*AWSClient).ec2conn
   181  
   182  	d.Partial(true)
   183  	if err := setTags(conn, d); err != nil {
   184  		return err
   185  	} else {
   186  		d.SetPartial("tags")
   187  	}
   188  
   189  	d.Partial(false)
   190  
   191  	return resourceAwsSpotInstanceRequestRead(d, meta)
   192  }
   193  
   194  func resourceAwsSpotInstanceRequestDelete(d *schema.ResourceData, meta interface{}) error {
   195  	conn := meta.(*AWSClient).ec2conn
   196  
   197  	log.Printf("[INFO] Cancelling spot request: %s", d.Id())
   198  	_, err := conn.CancelSpotInstanceRequests(&ec2.CancelSpotInstanceRequestsInput{
   199  		SpotInstanceRequestIDs: []*string{aws.String(d.Id())},
   200  	})
   201  
   202  	if err != nil {
   203  		return fmt.Errorf("Error cancelling spot request (%s): %s", d.Id(), err)
   204  	}
   205  
   206  	if instanceId := d.Get("spot_instance_id").(string); instanceId != "" {
   207  		log.Printf("[INFO] Terminating instance: %s", instanceId)
   208  		if err := awsTerminateInstance(conn, instanceId); err != nil {
   209  			return fmt.Errorf("Error terminating spot instance: %s", err)
   210  		}
   211  	}
   212  
   213  	return nil
   214  }
   215  
   216  // SpotInstanceStateRefreshFunc returns a resource.StateRefreshFunc that is used to watch
   217  // an EC2 spot instance request
   218  func SpotInstanceStateRefreshFunc(
   219  	conn *ec2.EC2, sir ec2.SpotInstanceRequest) resource.StateRefreshFunc {
   220  
   221  	return func() (interface{}, string, error) {
   222  		resp, err := conn.DescribeSpotInstanceRequests(&ec2.DescribeSpotInstanceRequestsInput{
   223  			SpotInstanceRequestIDs: []*string{sir.SpotInstanceRequestID},
   224  		})
   225  
   226  		if err != nil {
   227  			if ec2err, ok := err.(awserr.Error); ok && ec2err.Code() == "InvalidSpotInstanceRequestID.NotFound" {
   228  				// Set this to nil as if we didn't find anything.
   229  				resp = nil
   230  			} else {
   231  				log.Printf("Error on StateRefresh: %s", err)
   232  				return nil, "", err
   233  			}
   234  		}
   235  
   236  		if resp == nil || len(resp.SpotInstanceRequests) == 0 {
   237  			// Sometimes AWS just has consistency issues and doesn't see
   238  			// our request yet. Return an empty state.
   239  			return nil, "", nil
   240  		}
   241  
   242  		req := resp.SpotInstanceRequests[0]
   243  		return req, *req.Status.Code, nil
   244  	}
   245  }