UNPKG

@bowtie/sls

Version:

Serverless helpers & utilities

378 lines (341 loc) 14.2 kB
/** * Include utilities from @bowtie/sls */ const AWS = require('aws-sdk'); const qs = require('qs'); const fs = require('fs-extra') const path = require('path') const axios = require('axios') const { action, parser, builder, migrator, deployer, notifier, validator, models, controllers, oauth, utils } = require('./src') const { BaseController, BuildsController, DeploysController, DocumentsController, SubmissionsController } = controllers const { Build, Deploy } = models const { parseServiceConfig } = utils const handlers = {} const CORS_HEADERS = { 'Access-Control-Allow-Origin': '*', // Required for CORS support to work 'Access-Control-Allow-Credentials': true, // Required for cookies, authorization headers with HTTPS }; // const s3 = new AWS.S3(); /** * Generate MVC-Style REST API routes using controllers: */ // baseController = new BaseController // buildsController = new BuildsController() // deploysController = new DeploysController() // TODO: Hookup actual build/deploy logic for create/update/destroy? const methods = [ 'index', 'show', 'create', 'update', 'destroy', 'logs', 'deploy', 'tags', 'stacks', 'download', 'audits' ] // const methods = [ 'index', 'show', 'logs' ] const activeControllers = { builds: new BuildsController(), deploys: new DeploysController(), documents: new DocumentsController(), submissions: new SubmissionsController() } Object.keys(activeControllers).forEach(model => { const ctrl = activeControllers[model] methods.forEach(method => { if (typeof ctrl[method] === 'function') { handlers[`${model}_${method}`] = ctrl[method].bind(ctrl) } }) }) /** * Add info handler to report service info */ if (typeof BaseController.info === 'function') { handlers.info = BaseController.info } // handlers.spec = async (event, context) => { // return { // statusCode: 200, // headers: BaseController.REQUIRED_RESPONSE_HEADERS, // body: fs.readFileSync('spec.yml').toString() // } // } handlers.oauthBitbucketAuthorize = (event, context, callback) => { action.init(event).then(event => { oauth.bitbucket.authorize(event, context, callback) }).catch(callback) } /** * Handle CloudFormation stack change events * @param {object} event * @param {object} context * @param {function} callback */ handlers.stackChange = (event, context, callback) => { action.init(event) // Initialize the action promise chain .then(parser.stackChange) // Parse SNS Records into slack messages .then(deployer.stackChange) // Handle stack change deployments .then(notifier.stackChange) // Send parsed messages to slack .then(migrator.stackChange) // Find & run DB migration on successfull stack change .then(() => callback(null)) // Callback with null error successful .catch(callback) // Callback with error when a promise in the chain is rejected } /** * Handle Bitbucket webhook events (HTTP POST request on push) * @param {object} event * @param {object} context * @param {function} callback */ handlers.bitbucketWebhook = (event, context, callback) => { action.init(event) // Initialize the action promise chain .then(validator.bitbucketWebhook) // Validate webhook source is bitbucket .then(parser.parseBody) // Parse payload body (from JSON string => object) .then(builder.prepareBuild) // Prepare next build (if applicable) .then(builder.startBuild) // Start prepared build (if exists) .then(builder.trackBuild) // Track started build (if started) .then(parser.deployments) // Parse deployments (if any, defined in service yaml file) .then(deployer.deployBuild) // Deploy build(s) (if any, determined by previous parse step) .then(notifier.deploymentNotifyAirbrake) // Notify Airbrake of deployment (if any) .then(e => { // Successful webhook, return null error with response code 200 callback(null, { statusCode: '200' }) }) .catch(err => { // Something failed, notify error using slack integration notifier.actionFailureNotifySlack(err) // Return callback with null error to avoid lambda reporting errors .then(() => callback(null, { statusCode: '422' })) .catch(callback) }) } /** * Handle GitHub webhook events (HTTP POST request on push) * @param {object} event * @param {object} context * @param {function} callback */ handlers.githubWebhook = (event, context, callback) => { /** * Use headers * X-GitHub-Event = [ "push", "create", "pull_request" ] */ action.init(event) .then(validator.githubWebhook) .then(parser.parseBody) .then(builder.prepareBuild) .then(builder.startBuild) .then(builder.trackBuild) .then(parser.deployments) .then(deployer.deployBuild) .then(notifier.deploymentNotifyAirbrake) .then(e => { // Successful webhook, return null error with response code 200 callback(null, { statusCode: '200' }) }) .catch(err => { // Something failed, notify error using slack integration notifier.actionFailureNotifySlack(err) // Return callback with null error to avoid lambda reporting errors .then(() => callback(null, { statusCode: '422' })) .catch(callback) }) } /** * Handle CodeBuild status change events * @param {object} event * @param {object} context * @param {function} callback */ handlers.buildChange = (event, context, callback) => { action.init(event) // Initialize the action promise chain .then(parser.buildChange) // Parse build change message details .then(notifier.buildChange) // Update build from image details .then(notifier.buildChangeNotifyGithub) // Notify github of changes / commit statuses .then(notifier.buildChangeNotifyBitbucket) // Notify bitbucket of changes / commit statuses .then(notifier.buildChangeNotifySlack) // Notify slack of build changes .then(parser.deployments) // Parse deployments (from successful builds) .then(deployer.deployBuild) // Deploy build(s) (if any, determined by previous parse step) .then(notifier.deploymentNotifyAirbrake) // Notify airbrake of deployments (if any & airbrake is configured) .then(e => callback(null)) // Successful handler, return callback with null error .catch(err => { // Something failed, notify error using slack integration notifier.actionFailureNotifySlack(err) // Return callback with null error to avoid lambda reporting errors .then(() => callback(null)) .catch(callback) }) } // handlers.s3Download = (event, context, callback) => { // action.init(event) // .then(event => { // const Expires = 5; // const Bucket = process.env.ASSET_BUCKET_NAME; // const { Key } = event.queryStringParameters; // AWS.config.update({region: 'us-east-1'}); // const s3 = new AWS.S3({ // signatureVersion: 'v4' // }); // const headers= { // 'Location': s3.getSignedUrl('getObject', { Bucket, Key, Expires }) // }; // console.log('s3Download() headers', headers); // return callback(null, { // statusCode: '302', // headers // }) // }) // .catch(err => { // // Something failed, notify error using slack integration // notifier.actionFailureNotifySlack(err) // // Return callback with null error to avoid lambda reporting errors // .then(() => callback(null)) // .catch(callback) // }) // } handlers.s3Upload = (event, context, callback) => { action.init(event) .then(parser.parseBody) .then(event => { const queryParams = event.queryStringParameters || {}; const { ctx } = queryParams; const useSecureBucket = (process.env.CTX_SECURE && ctx && ctx === process.env.CTX_SECURE); const Bucket = useSecureBucket ? process.env.SECURE_BUCKET_NAME : process.env.ASSET_BUCKET_NAME; const { Key, ContentType, Data } = event.parsed.body; // const { Bucket = 'svig-testing-uploads', Key, ContentType } = event.queryStringParameters; if (!Bucket || !Key || !ContentType) { return callback(null, { statusCode: '422', headers: CORS_HEADERS, body: JSON.stringify({ message: 'Invalid parameter(s)' }) }) } // [HIGH] TODO: Validate incoming data with form submission(s) // if (useSecureBucket && !Data) { // return callback(null, { // statusCode: '422', // headers, // body: JSON.stringify({ message: 'Invalid parameter(s)' }) // }) // } // AWS.config.update({region: 'us-east-1'}); const s3 = new AWS.S3({ signatureVersion: 'v4' }); // var params = {Bucket, Key}; // s3.getSignedUrl('putObject', params, function (err, url) { // console.log('The URL is', url); // }); s3.getSignedUrlPromise('putObject', { Bucket, Key, ContentType }) .then(signedUrl => { let publicUrl; console.log("Signed URL IS: ", signedUrl) if (!useSecureBucket) { publicUrl = `https://${Bucket}.s3.amazonaws.com/${Key}`; console.log("Public URL IS: ", publicUrl) } callback(null, { statusCode: '200', headers: CORS_HEADERS, body: JSON.stringify({ signedUrl, publicUrl, location: publicUrl }) }) }) .catch(err => { console.error(err) callback(null, { statusCode: '400', headers: CORS_HEADERS, body: JSON.stringify({ message: err.message }) }) }) }) .catch(err => { // Something failed, notify error using slack integration notifier.actionFailureNotifySlack(err) // Return callback with null error to avoid lambda reporting errors .then(() => callback(null)) .catch(callback) }) } /** * Verify recaptcha v3 token * @param {ApiEvent} event - Request event * @param {object} context - Handler execution context */ handlers.verifyRecaptcha = async (event, context) => { if (event.queryStringParameters.token) { const resp = await axios.post('https://www.google.com/recaptcha/api/siteverify', qs.stringify({ secret: process.env.RECAPTCHA_SECRET_KEY, response: event.queryStringParameters.token })); return { headers: CORS_HEADERS, statusCode: '200', body: JSON.stringify(resp.data) }; } else { return { headers: CORS_HEADERS, statusCode: '400', message: 'Missing or invalid token' }; } }; // handlers.sendEmail = (event, context, callback) => { // action.init(event) // Initialize the action promise chain // .then(parser.parseBody) // Parse payload body (from JSON string => object) // .then(notifier.sendEmail) // Send email (using SES) // .then(response => { // // Successful webhook, return null error with response code 200 // callback(null, { // statusCode: '200', // body: JSON.stringify(response) // }) // }) // .catch(err => { // // Something failed, notify error using slack integration // notifier.actionFailureNotifySlack(err) // // Return callback with null error to avoid lambda reporting errors // .then(() => callback(null, { statusCode: '422', body: JSON.stringify({ message: err.message || 'Something failed' }) })) // .catch(callback) // }) // }; // /** // * Show build logs // * [HIGH] TODO: Authenticate? // * @param {object} event // * @param {object} context // * @param {function} callback // */ // handlers.buildLogs = (event, context, callback) => { // action.init(event) // Initialize the action promise chain // .then(builder.buildLogs) // Notify airbrake of deployments (if any & airbrake is configured) // .then(e => callback(null, e.response)) // Successful handler, return callback with null error // .catch(err => { // callback(null, { // statusCode: '422', // body: JSON.stringify({ message: err.message || err }) // }) // }) // } // /** // * Handle Slack slash command events // */ // handlers.slackCommand = (event, context, callback) => { // action.init(event) // Initialize the action promise chain // .then(parser.decodeBody) // Decode the urlencoded event.body into event.parsed.body // .then(validator.slackCommand) // Validate the request token from Slack // .then(parser.slackCommand) // Build command info from the parsed body // .then(manager.slackCommand) // Handle the command built in the previous step // .then((e) => callback(null, e.response)) // Callback with null error and constructed response if successful // .catch(callback); // Callback with error when a promise in the chain is rejected // } // /** // * Handle Slack response events // */ // handlers.slackResponse = (event, context, callback) => { // action.init(event) // Initialize the action promise chain // .then(parser.decodeBody) // Decode the urlencoded event.body into event.parsed.body // .then(parser.parsePayload) // Parse payload as JSON from the decoded body // .then(validator.slackResponse) // Validate the request token from Slack // .then(parser.slackResponse) // Build response info from the parsed body // .then(manager.slackResponse) // Handle the response built in the previous step // .then((e) => callback(null, e.response)) // Callback with null error and constructed response if successful // .catch(callback); // Callback with error when a promise in the chain is rejected // } module.exports = handlers;