UNPKG

3.67 kBJavaScriptView Raw
1var url = require('url');
2var https = require('https');
3var crypto = require('crypto');
4
5module.exports = Response;
6
7/**
8 * Represents a response to CloudFormation indicating whether or not the custom
9 * resource was created successfully.
10 * @param {object} event - the Lambda invocation event
11 * @param {object} context - the Lambda invocation context
12 * @example
13 * function MyCustomLambdaFunction(event, context) {
14 * var response = new Response(event, context);
15 *
16 * manageCustomResource(event.RequestType, event.RequestProperties, function(err, id) {
17 * if (err) return response.send(err);
18 * response.setId(id);
19 * response.send(null, { additional: 'properties' });
20 * });
21 * }
22 */
23function Response(event, context) {
24 var parsedUrl = url.parse(event.ResponseURL);
25
26 /**
27 * Data to be sent in the response body
28 * @memberof Response
29 * @instance
30 * @property {string} Status - either `FAILED` or `SUCCESS`
31 * @property {string} Reason - a description is required if status is set to `FAILED`
32 * @property {string} PhysicalResourceId - the physical id of the managed backend
33 * resource. This will be provided in an `Update` or `Delete` event, and must be
34 * generated during a `Create` event.
35 * @property {string} StackId - set by the invocation event and should not be adjusted
36 * @property {string} LogicalResourceId - set by the invocation event and should not be adjusted
37 * @property {string} RequestId - set by the invocation event and should not be adjusted
38 * @private
39 */
40 this.responseData = {
41 PhysicalResourceId: event.PhysicalResourceId || crypto.randomBytes(16).toString('hex'),
42 StackId: event.StackId,
43 LogicalResourceId: event.LogicalResourceId,
44 RequestId: event.RequestId
45 };
46
47 /**
48 * Request options used to send the response
49 * @memberof Response
50 * @instance
51 * @private
52 */
53 this.options = {
54 hostname: parsedUrl.hostname,
55 port: 443,
56 path: parsedUrl.path,
57 method: 'PUT',
58 headers: {
59 'content-type': '',
60 'content-length': 0
61 }
62 };
63
64 /**
65 * Function to finish the Lambda invocation
66 * @memberof Response
67 * @instance
68 * @private
69 */
70 this.done = context.done.bind(context);
71}
72
73/**
74 * Set the PhysicalResourceId to be used in the response to CloudFormation. You
75 * **must** provide this id when responding to a `Create` event.
76 * @param {string} id - the id to be provided
77 */
78Response.prototype.setId = function(id) {
79 this.responseData.PhysicalResourceId = id;
80};
81
82/**
83 * Send the response and finish the Lambda invocation
84 * @param {object} [err] - set to null if no error occurred, or provide an Error object
85 * @param {object} [data] - A hash of key-value pairs accessible via `Fn::GetAtt` calls on this custom resource
86 */
87Response.prototype.send = function(err, data) {
88 if (err) console.log(err);
89
90 this.responseData.Status = err ? 'FAILED' : 'SUCCESS';
91 this.responseData.Reason = err ? err.message || 'Unspecified failure' : '';
92 this.responseData.Data = data;
93
94 var body = JSON.stringify(this.responseData);
95 var options = this.options;
96 options.headers['content-length'] = body.length;
97 var done = this.done;
98
99 console.log('Response body: %j', this.responseData);
100 console.log('Response options: %j', this.options);
101
102 (function sendResponse(attempts) {
103 if (attempts > 5) return done(new Error('Failed to respond to CloudFormation'));
104
105 var req = https.request(options, function() {
106 done(null, err || body);
107 }).on('error', function(requestError) {
108 console.log(requestError);
109 attempts++;
110 sendResponse(attempts);
111 });
112
113 req.write(body);
114 req.end();
115 })(0);
116};