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