UNPKG

14.2 kBJavaScriptView Raw
1/**
2 * Include utilities from @bowtie/sls
3 */
4const AWS = require('aws-sdk');
5const qs = require('qs');
6const fs = require('fs-extra')
7const path = require('path')
8const axios = require('axios')
9const { action, parser, builder, migrator, deployer, notifier, validator, models, controllers, oauth, utils } = require('./src')
10const { BaseController, BuildsController, DeploysController, DocumentsController, SubmissionsController } = controllers
11const { Build, Deploy } = models
12const { parseServiceConfig } = utils
13const handlers = {}
14
15const CORS_HEADERS = {
16 'Access-Control-Allow-Origin': '*', // Required for CORS support to work
17 'Access-Control-Allow-Credentials': true, // Required for cookies, authorization headers with HTTPS
18};
19
20// const s3 = new AWS.S3();
21
22/**
23 * Generate MVC-Style REST API routes using controllers:
24 */
25// baseController = new BaseController
26// buildsController = new BuildsController()
27// deploysController = new DeploysController()
28
29// TODO: Hookup actual build/deploy logic for create/update/destroy?
30const methods = [ 'index', 'show', 'create', 'update', 'destroy', 'logs', 'deploy', 'tags', 'stacks', 'download', 'audits' ]
31// const methods = [ 'index', 'show', 'logs' ]
32const activeControllers = {
33 builds: new BuildsController(),
34 deploys: new DeploysController(),
35 documents: new DocumentsController(),
36 submissions: new SubmissionsController()
37}
38
39Object.keys(activeControllers).forEach(model => {
40 const ctrl = activeControllers[model]
41
42 methods.forEach(method => {
43 if (typeof ctrl[method] === 'function') {
44 handlers[`${model}_${method}`] = ctrl[method].bind(ctrl)
45 }
46 })
47})
48
49/**
50 * Add info handler to report service info
51 */
52if (typeof BaseController.info === 'function') {
53 handlers.info = BaseController.info
54}
55
56// handlers.spec = async (event, context) => {
57// return {
58// statusCode: 200,
59// headers: BaseController.REQUIRED_RESPONSE_HEADERS,
60// body: fs.readFileSync('spec.yml').toString()
61// }
62// }
63
64handlers.oauthBitbucketAuthorize = (event, context, callback) => {
65 action.init(event).then(event => {
66 oauth.bitbucket.authorize(event, context, callback)
67 }).catch(callback)
68}
69
70/**
71 * Handle CloudFormation stack change events
72 * @param {object} event
73 * @param {object} context
74 * @param {function} callback
75 */
76handlers.stackChange = (event, context, callback) => {
77 action.init(event) // Initialize the action promise chain
78 .then(parser.stackChange) // Parse SNS Records into slack messages
79 .then(deployer.stackChange) // Handle stack change deployments
80 .then(notifier.stackChange) // Send parsed messages to slack
81 .then(migrator.stackChange) // Find & run DB migration on successfull stack change
82 .then(() => callback(null)) // Callback with null error successful
83 .catch(callback) // Callback with error when a promise in the chain is rejected
84}
85
86/**
87 * Handle Bitbucket webhook events (HTTP POST request on push)
88 * @param {object} event
89 * @param {object} context
90 * @param {function} callback
91 */
92handlers.bitbucketWebhook = (event, context, callback) => {
93 action.init(event) // Initialize the action promise chain
94 .then(validator.bitbucketWebhook) // Validate webhook source is bitbucket
95 .then(parser.parseBody) // Parse payload body (from JSON string => object)
96 .then(builder.prepareBuild) // Prepare next build (if applicable)
97 .then(builder.startBuild) // Start prepared build (if exists)
98 .then(builder.trackBuild) // Track started build (if started)
99 .then(parser.deployments) // Parse deployments (if any, defined in service yaml file)
100 .then(deployer.deployBuild) // Deploy build(s) (if any, determined by previous parse step)
101 .then(notifier.deploymentNotifyAirbrake) // Notify Airbrake of deployment (if any)
102 .then(e => {
103 // Successful webhook, return null error with response code 200
104 callback(null, {
105 statusCode: '200'
106 })
107 })
108 .catch(err => {
109 // Something failed, notify error using slack integration
110 notifier.actionFailureNotifySlack(err)
111 // Return callback with null error to avoid lambda reporting errors
112 .then(() => callback(null, { statusCode: '422' }))
113 .catch(callback)
114 })
115}
116
117/**
118 * Handle GitHub webhook events (HTTP POST request on push)
119 * @param {object} event
120 * @param {object} context
121 * @param {function} callback
122 */
123handlers.githubWebhook = (event, context, callback) => {
124 /**
125 * Use headers
126 * X-GitHub-Event = [ "push", "create", "pull_request" ]
127 */
128 action.init(event)
129 .then(validator.githubWebhook)
130 .then(parser.parseBody)
131 .then(builder.prepareBuild)
132 .then(builder.startBuild)
133 .then(builder.trackBuild)
134 .then(parser.deployments)
135 .then(deployer.deployBuild)
136 .then(notifier.deploymentNotifyAirbrake)
137 .then(e => {
138 // Successful webhook, return null error with response code 200
139 callback(null, {
140 statusCode: '200'
141 })
142 })
143 .catch(err => {
144 // Something failed, notify error using slack integration
145 notifier.actionFailureNotifySlack(err)
146 // Return callback with null error to avoid lambda reporting errors
147 .then(() => callback(null, { statusCode: '422' }))
148 .catch(callback)
149 })
150}
151
152/**
153 * Handle CodeBuild status change events
154 * @param {object} event
155 * @param {object} context
156 * @param {function} callback
157 */
158handlers.buildChange = (event, context, callback) => {
159 action.init(event) // Initialize the action promise chain
160 .then(parser.buildChange) // Parse build change message details
161 .then(notifier.buildChange) // Update build from image details
162 .then(notifier.buildChangeNotifyGithub) // Notify github of changes / commit statuses
163 .then(notifier.buildChangeNotifyBitbucket) // Notify bitbucket of changes / commit statuses
164 .then(notifier.buildChangeNotifySlack) // Notify slack of build changes
165 .then(parser.deployments) // Parse deployments (from successful builds)
166 .then(deployer.deployBuild) // Deploy build(s) (if any, determined by previous parse step)
167 .then(notifier.deploymentNotifyAirbrake) // Notify airbrake of deployments (if any & airbrake is configured)
168 .then(e => callback(null)) // Successful handler, return callback with null error
169 .catch(err => {
170 // Something failed, notify error using slack integration
171 notifier.actionFailureNotifySlack(err)
172 // Return callback with null error to avoid lambda reporting errors
173 .then(() => callback(null))
174 .catch(callback)
175 })
176}
177
178// handlers.s3Download = (event, context, callback) => {
179// action.init(event)
180// .then(event => {
181// const Expires = 5;
182// const Bucket = process.env.ASSET_BUCKET_NAME;
183// const { Key } = event.queryStringParameters;
184
185// AWS.config.update({region: 'us-east-1'});
186
187// const s3 = new AWS.S3({
188// signatureVersion: 'v4'
189// });
190
191// const headers= {
192// 'Location': s3.getSignedUrl('getObject', { Bucket, Key, Expires })
193// };
194
195// console.log('s3Download() headers', headers);
196
197// return callback(null, {
198// statusCode: '302',
199// headers
200// })
201// })
202// .catch(err => {
203// // Something failed, notify error using slack integration
204// notifier.actionFailureNotifySlack(err)
205// // Return callback with null error to avoid lambda reporting errors
206// .then(() => callback(null))
207// .catch(callback)
208// })
209// }
210
211handlers.s3Upload = (event, context, callback) => {
212 action.init(event)
213 .then(parser.parseBody)
214 .then(event => {
215 const queryParams = event.queryStringParameters || {};
216 const { ctx } = queryParams;
217 const useSecureBucket = (process.env.CTX_SECURE && ctx && ctx === process.env.CTX_SECURE);
218 const Bucket = useSecureBucket ? process.env.SECURE_BUCKET_NAME : process.env.ASSET_BUCKET_NAME;
219 const { Key, ContentType, Data } = event.parsed.body;
220
221 // const { Bucket = 'svig-testing-uploads', Key, ContentType } = event.queryStringParameters;
222
223 if (!Bucket || !Key || !ContentType) {
224 return callback(null, {
225 statusCode: '422',
226 headers: CORS_HEADERS,
227 body: JSON.stringify({ message: 'Invalid parameter(s)' })
228 })
229 }
230
231 // [HIGH] TODO: Validate incoming data with form submission(s)
232 // if (useSecureBucket && !Data) {
233 // return callback(null, {
234 // statusCode: '422',
235 // headers,
236 // body: JSON.stringify({ message: 'Invalid parameter(s)' })
237 // })
238 // }
239
240 // AWS.config.update({region: 'us-east-1'});
241
242 const s3 = new AWS.S3({
243 signatureVersion: 'v4'
244 });
245
246 // var params = {Bucket, Key};
247 // s3.getSignedUrl('putObject', params, function (err, url) {
248 // console.log('The URL is', url);
249 // });
250
251 s3.getSignedUrlPromise('putObject', { Bucket, Key, ContentType })
252 .then(signedUrl => {
253 let publicUrl;
254 console.log("Signed URL IS: ", signedUrl)
255
256 if (!useSecureBucket) {
257 publicUrl = `https://${Bucket}.s3.amazonaws.com/${Key}`;
258 console.log("Public URL IS: ", publicUrl)
259 }
260
261 callback(null, {
262 statusCode: '200',
263 headers: CORS_HEADERS,
264 body: JSON.stringify({ signedUrl, publicUrl, location: publicUrl })
265 })
266 })
267 .catch(err => {
268 console.error(err)
269 callback(null, {
270 statusCode: '400',
271 headers: CORS_HEADERS,
272 body: JSON.stringify({ message: err.message })
273 })
274 })
275 })
276 .catch(err => {
277 // Something failed, notify error using slack integration
278 notifier.actionFailureNotifySlack(err)
279 // Return callback with null error to avoid lambda reporting errors
280 .then(() => callback(null))
281 .catch(callback)
282 })
283}
284
285/**
286 * Verify recaptcha v3 token
287 * @param {ApiEvent} event - Request event
288 * @param {object} context - Handler execution context
289 */
290handlers.verifyRecaptcha = async (event, context) => {
291 if (event.queryStringParameters.token) {
292 const resp = await axios.post('https://www.google.com/recaptcha/api/siteverify', qs.stringify({
293 secret: process.env.RECAPTCHA_SECRET_KEY,
294 response: event.queryStringParameters.token
295 }));
296
297 return {
298 headers: CORS_HEADERS,
299 statusCode: '200',
300 body: JSON.stringify(resp.data)
301 };
302 } else {
303 return {
304 headers: CORS_HEADERS,
305 statusCode: '400',
306 message: 'Missing or invalid token'
307 };
308 }
309};
310
311// handlers.sendEmail = (event, context, callback) => {
312// action.init(event) // Initialize the action promise chain
313// .then(parser.parseBody) // Parse payload body (from JSON string => object)
314// .then(notifier.sendEmail) // Send email (using SES)
315// .then(response => {
316// // Successful webhook, return null error with response code 200
317// callback(null, {
318// statusCode: '200',
319// body: JSON.stringify(response)
320// })
321// })
322// .catch(err => {
323// // Something failed, notify error using slack integration
324// notifier.actionFailureNotifySlack(err)
325// // Return callback with null error to avoid lambda reporting errors
326// .then(() => callback(null, { statusCode: '422', body: JSON.stringify({ message: err.message || 'Something failed' }) }))
327// .catch(callback)
328// })
329// };
330
331// /**
332// * Show build logs
333// * [HIGH] TODO: Authenticate?
334// * @param {object} event
335// * @param {object} context
336// * @param {function} callback
337// */
338// handlers.buildLogs = (event, context, callback) => {
339// action.init(event) // Initialize the action promise chain
340// .then(builder.buildLogs) // Notify airbrake of deployments (if any & airbrake is configured)
341// .then(e => callback(null, e.response)) // Successful handler, return callback with null error
342// .catch(err => {
343// callback(null, {
344// statusCode: '422',
345// body: JSON.stringify({ message: err.message || err })
346// })
347// })
348// }
349
350// /**
351// * Handle Slack slash command events
352// */
353// handlers.slackCommand = (event, context, callback) => {
354// action.init(event) // Initialize the action promise chain
355// .then(parser.decodeBody) // Decode the urlencoded event.body into event.parsed.body
356// .then(validator.slackCommand) // Validate the request token from Slack
357// .then(parser.slackCommand) // Build command info from the parsed body
358// .then(manager.slackCommand) // Handle the command built in the previous step
359// .then((e) => callback(null, e.response)) // Callback with null error and constructed response if successful
360// .catch(callback); // Callback with error when a promise in the chain is rejected
361// }
362
363// /**
364// * Handle Slack response events
365// */
366// handlers.slackResponse = (event, context, callback) => {
367// action.init(event) // Initialize the action promise chain
368// .then(parser.decodeBody) // Decode the urlencoded event.body into event.parsed.body
369// .then(parser.parsePayload) // Parse payload as JSON from the decoded body
370// .then(validator.slackResponse) // Validate the request token from Slack
371// .then(parser.slackResponse) // Build response info from the parsed body
372// .then(manager.slackResponse) // Handle the response built in the previous step
373// .then((e) => callback(null, e.response)) // Callback with null error and constructed response if successful
374// .catch(callback); // Callback with error when a promise in the chain is rejected
375// }
376
377module.exports = handlers;