UNPKG

5.48 kBJavaScriptView Raw
1var AWS = require('aws-sdk');
2var utils = require('../lib/utils');
3var Response = require('../lib/response');
4
5module.exports = SpotFleet;
6
7/**
8 * Represents an Spot fleet
9 * @param {object} spotFleetRequestConfigData - see [the CloudFormation documentation](http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-spotfleetrequestconfigdata.html)
10 * @param {string} region - the region to request a spot fleet in
11 * @param {string} [requestId] - the ID of a preexisting spot fleet
12 */
13function SpotFleet(spotFleetRequestConfigData, region, overrideTargetCapacity, requestId) {
14 if (!spotFleetRequestConfigData)
15 throw new Error('Missing Parameter SpotFleetRequestConfigData');
16 if (!region)
17 throw new Error('Missing Parameter Region');
18
19 if (requestId) this.requestId = requestId;
20
21 // Need to number/booleanify the payload that CFN fucked with
22 this.spotFleetRequestConfigData = JSON.parse(JSON.stringify(spotFleetRequestConfigData), function(key, val) {
23 if (key === 'SpotPrice') return val;
24 if (key === 'WeightedCapacity') return parseFloat(val);
25 if (key === 'TargetCapacity') return Math.ceil(parseFloat(val));
26 if (val === '') return val;
27 if (val === 'true') return true;
28 if (val === 'false') return false;
29 if (!isNaN(val)) return parseInt(val, 10);
30 return val;
31 });
32
33 this.region = region;
34 this.overrideTargetCapacity = overrideTargetCapacity;
35 this.ec2 = new AWS.EC2({ region: region });
36}
37
38/**
39 * A Lambda function to create a SpotFleet whose instances are not cleaned up when the request is canceled
40 * @static
41 * @param {object} event - a Lambda invocation event sent from a custom CloudFormation resource
42 * @param {object} context - the Lambda invocation context
43 * @example
44 * // a custom CloudFormation resource that is backed by this Lambda function must
45 * // provide [SpotFleetRequestConfigData](http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-spotfleet-spotfleetrequestconfigdata.html),
46 * // and the region to request the spot fleet in.
47 * {
48 * "Type": "Custom::SpotFleet",
49 * "Properties": {
50 * "SpotFleetRequestConfigData": { ... }
51 * "Region": "us-east-1"
52 * }
53 * }
54 */
55SpotFleet.manage = function(event, context) {
56 if (!utils.validCloudFormationEvent(event))
57 return context.done(null, 'ERROR: Invalid CloudFormation event');
58
59 var response = new Response(event, context);
60
61 var requestType = event.RequestType.toLowerCase();
62 var spotFleet;
63 try {
64 spotFleet = new SpotFleet(
65 event.ResourceProperties.SpotFleetRequestConfigData,
66 event.ResourceProperties.Region,
67 event.ResourceProperties.OverrideTargetCapacity === 'true' ? true : false,
68 event.PhysicalResourceId
69 );
70 } catch (err) {
71 return response.send(err);
72 }
73
74 console.log('%s %s in %s', event.RequestType, event.LogicalResourceId, event.StackId);
75 console.log('%s override existing target capacity', event.ResourceProperties.OverrideTargetCapacity ? 'Will not' : 'Will');
76 spotFleet[requestType](function(err, requestId) {
77 if (requestId) response.setId(requestId);
78 response.send(err);
79 });
80};
81
82/**
83 * A create event triggers the creation of a spot fleet
84 * @param {function} callback - a function to handle the response
85 */
86SpotFleet.prototype.create = function(callback) {
87 var params = {
88 SpotFleetRequestConfig: this.spotFleetRequestConfigData,
89 DryRun: false
90 };
91
92 this.ec2.requestSpotFleet(params, function(err, data) {
93 if (err) return callback(err);
94 console.log('Created request %s', data.SpotFleetRequestId);
95 callback(null, data.SpotFleetRequestId);
96 });
97};
98
99/**
100 * An update event must create a new fleet and cancel the old one
101 * @param {function} callback - a function to handle the response
102 */
103SpotFleet.prototype.update = function(callback) {
104 var _this = this;
105
106 this.ec2.describeSpotFleetRequests({
107 SpotFleetRequestIds: [this.requestId]
108 }, function(err, data) {
109 if (err && err.code !== 'InvalidSpotFleetRequestId.NotFound') return callback(err);
110
111 if (!_this.overrideTargetCapacity && data && data.SpotFleetRequestConfigs.length) {
112 _this.spotFleetRequestConfigData.TargetCapacity = Math.max(
113 Number(_this.spotFleetRequestConfigData.TargetCapacity),
114 Number(data.SpotFleetRequestConfigs[0].SpotFleetRequestConfig.TargetCapacity)
115 );
116 }
117
118 _this.create(function(err, requestId) {
119 if (err) return callback(err);
120
121 _this.delete(function(err) {
122 if (err) return callback(err); // ? with new fleet request id ?
123
124 callback(null, requestId);
125 });
126 });
127 });
128};
129
130/**
131 * Cancel a SpotFleet without terminating its instances
132 * @param {function} callback - a function to handle the response
133 */
134SpotFleet.prototype.delete = function(callback) {
135 if (!this.requestId) return callback();
136 if (!/^sfr-[a-z0-9-]{36}$/.test(this.requestId)) return callback();
137
138 var requestId = this.requestId;
139 var ec2 = this.ec2;
140
141 ec2.describeSpotFleetRequests({
142 SpotFleetRequestIds: [requestId]
143 }, function(err) {
144 if (err && err.code === 'InvalidSpotFleetRequestId.NotFound') return callback();
145 if (err) return callback(err);
146
147 console.log('Cancel fleet request %s', requestId);
148 ec2.cancelSpotFleetRequests({
149 SpotFleetRequestIds: [requestId],
150 TerminateInstances: false,
151 DryRun: false
152 }, function(err) {
153 if (err) return callback(err);
154 callback();
155 });
156 });
157};