UNPKG

@bowtie/sls

Version:

Serverless helpers & utilities

283 lines (231 loc) 13.3 kB
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width,initial-scale=1"> <title>deployer.js - Documentation</title> <script src="scripts/prettify/prettify.js"></script> <script src="scripts/prettify/lang-css.js"></script> <!--[if lt IE 9]> <script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script> <![endif]--> <link type="text/css" rel="stylesheet" href="https://code.ionicframework.com/ionicons/2.0.1/css/ionicons.min.css"> <link type="text/css" rel="stylesheet" href="styles/prettify-tomorrow.css"> <link type="text/css" rel="stylesheet" href="styles/jsdoc-default.css"> </head> <body> <input type="checkbox" id="nav-trigger" class="nav-trigger" /> <label for="nav-trigger" class="navicon-button x"> <div class="navicon"></div> </label> <label for="nav-trigger" class="overlay"></label> <nav> <li class="nav-link nav-home-link"><a href="index.html">Home</a></li><li class="nav-heading"><a href="global.html">Globals</a></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="global.html#actionFailureNotifySlack">actionFailureNotifySlack</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="global.html#bitbucketWebhook">bitbucketWebhook</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="global.html#buildChange">buildChange</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="global.html#buildChangeNotifyBitbucket">buildChangeNotifyBitbucket</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="global.html#buildChangeNotifyGithub">buildChangeNotifyGithub</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="global.html#buildChangeNotifySlack">buildChangeNotifySlack</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="global.html#decodeBody">decodeBody</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="global.html#deployBuild">deployBuild</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="global.html#deployEcr">deployEcr</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="global.html#deploymentNotifyAirbrake">deploymentNotifyAirbrake</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="global.html#deployments">deployments</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="global.html#deployS3">deployS3</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="global.html#describeStack">describeStack</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="global.html#findClusterName">findClusterName</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="global.html#findClusterStack">findClusterStack</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="global.html#findMigrationTask">findMigrationTask</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="global.html#getStatusColor">getStatusColor</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="global.html#githubWebhook">githubWebhook</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="global.html#init">init</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="global.html#initMigration">initMigration</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="global.html#parseBody">parseBody</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="global.html#parsePayload">parsePayload</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="global.html#prepareBuild">prepareBuild</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="global.html#runMigration">runMigration</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="global.html#slackCommand">slackCommand</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="global.html#slackResponse">slackResponse</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="global.html#stackChange">stackChange</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="global.html#startBuild">startBuild</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="global.html#trackBuild">trackBuild</a></span></li> </nav> <div id="main"> <h1 class="page-title">deployer.js</h1> <section> <article> <pre class="prettyprint source linenums"><code>// Include the AWS SDK const AWS = require('aws-sdk') const async = require('async') const { Deploy } = require('./models') // Create instances for ECR &amp; CloudFormation const ECR = new AWS.ECR() const CloudFormation = new AWS.CloudFormation() /** * Handle a stack change event (as published from SNS topic) * @param {object} event */ module.exports.stackChange = (event) => { // Return a promise for the promise chain return new Promise( (resolve, reject) => { // Ensure the SNS records have been parsed into messages if (!event.parsed.messages || event.parsed.messages.length === 0) { reject(new Error('No messages to be sent.')) return } console.log('Messages:', JSON.stringify(event.parsed.messages, null, 2)) // Loop through all messages (async) async.each(event.parsed.messages, (msg, next) => { // Flag to determine whether this message is for parent stack or a stack resource const isStackMessage = (msg.ResourceType === 'AWS::CloudFormation::Stack' &amp;&amp; msg.LogicalResourceId === msg.StackName) if (isStackMessage) { console.log('Tracking deploy status from msg:', msg) console.log('Current event object', JSON.stringify(event, null, 2)) next() } else { console.log('Not tracking deploy status for msg:', msg) next() } }, err => { if (err) { // Reject on error reject(err) } else { // Resolve with event resolve(event) } }) } ) } /** * Deploy S3 * @param {object} event * @param {object} deployment * @param {function} done */ const deployS3 = (event, deployment, done) => { console.log('Deploy to S3:', deployment) if (event.parsed.build.found) { done(new Error(`Skip deploy, build exists. Rebuilding`)); } else { done(null, 'SUCCEEDED'); } } /** * Deploy ECR * @param {object} event * @param {object} deployment * @param {function} done */ const deployEcr = (event, deployment, done) => { // Construct ECR parameters to search for the specified tag const ecrParams = { // Use repo as specified in the parsed service parameters repositoryName: process.env.ECR_REPO_NAME, imageIds: [ { // Search for an image tag as specified in the parsed service parameters imageTag: deployment.tag } ] } // Check if repo &amp; tag exist ECR.batchGetImage(ecrParams, (err, data) => { // Reject on error if (err) { return done(err) } // Skip deployment if tag does not exist in ECR if (data.images.length === 0) { console.log('Unable to find ECR image with params:', JSON.stringify(ecrParams)) return done(null, 'FAILED') } // Ensure at least 1 image, and no failures // if (!data.images || data.images.length == 0 || data.failures.length > 0) { // return done(data) // } // Describe the stack (verifies it exists, and can build parameter list) CloudFormation.describeStacks({ // Get the stack name from the parsed service parameters StackName: deployment.stack }, (err, data) => { // Reject on error if (err) { console.warn(err) console.log('Unable to find CF Stack with name:', deployment.stack) return done(null, 'FAILED') } // This should be impossible, but ensure only a single stack is being returned if (data.Stacks.length > 1) { return done(new Error(`More than 1 stack matching name: ${deployment.stack}`)) } // Filter current stack parameters except for "Tag" const params = data.Stacks[0].Parameters.map(p => { return { ParameterKey: p.ParameterKey, UsePreviousValue: true } }).filter(p => p.ParameterKey !== 'Tag') // Add the "Tag" parameter with the tag as specified by the parsed service parameters params.push({ ParameterKey: 'Tag', ParameterValue: deployment.tag }) // Update the CloudFormation stack with the reconstructed parameters CloudFormation.updateStack({ // Get the stack name from the parsed service parameters StackName: deployment.stack, // Should include all previous parameters, and an updated "Tag" parameter Parameters: params, // Reuse the previous stack template UsePreviousTemplate: true, // Execute this stack update with the given role ARN // - This is passed into the environment by serverless (see "serverless.yml") RoleARN: process.env.UPDATE_ROLE_ARN, // A stack that creates/changes IAM roles/policies/etc MUST provide this capability flag Capabilities: [ 'CAPABILITY_IAM' ], // Send stack change notifications to the SNS topic ARN // - This is passed into the environment by serverless (see "serverless.yml") // - This SNS topic is automatically hooked up to the "stack-change" function to send messages to Slack NotificationARNs: [ process.env.NOTIFY_SNS_ARN ] }, (err) => { if (err) { return done(err) } done(null, 'IN_PROGRESS') }) }) }) } /** * Deploy build (from parsed deployments) * @param {object} event */ module.exports.deployBuild = (event) => { // Return a promise for the promise chain return new Promise( (resolve, reject) => { if (event.builds.prepared) { console.log('Event contains prepared build (new build). Skipping deployments ...') resolve(event) return } // Ensure the deployments have been parsed if (!event.parsed.deployments) { console.log('Event does not contain parsed deployment details.') resolve(event) return } // Debug logs for deployments being handled console.log('Parsed deployments', JSON.stringify(event.parsed.deployments, null, 2)) console.log('Current event object', JSON.stringify(event, null, 2)) // Loop through parsed deployments (async) and update CF stack(s) async.each(event.parsed.deployments, (deployment, next) => { const deployMethod = deployment.target === 's3' ? deployS3 : deployEcr deployMethod(event, deployment, (err, status) => { if (err) return next(err) const newDeploy = new Deploy(Object.assign({}, deployment, { service_name: process.env.SERVICE_NAME, deploy_status: status, deploy_timestamp: Date.now() })) newDeploy.saveNotify().then(data => { console.log('Saved deploy data:', data) next() }).catch(next) }) }, err => { if (err) { console.warn(err) } resolve(event) }) } ) } </code></pre> </article> </section> </div> <br class="clear"> <footer> Generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 3.6.4</a> on Mon Jun 22 2020 11:46:50 GMT-0600 (Mountain Daylight Time) using the Minami theme. </footer> <script>prettyPrint();</script> <script src="scripts/linenumber.js"></script> </body> </html>