UNPKG

hops-lambda

Version:

Quick and easy deployment of Hops applications to AWS Lambda

268 lines (249 loc) 7.24 kB
'use strict'; var inspect = require('util').inspect; var fs = require('fs'); var path = require('path'); var AWS = require('aws-sdk'); var createLambdaBundle = require('./create-lambda-bundle'); var progressWriter = require('./progress-writer'); var fsUtils = require('./fs-utils'); function formatParameters(params) { return Object.keys(params).map(function (key) { return { ParameterKey: key, ParameterValue: String(params[key]), }; }); } function createBucketIfNotExists(s3, bucketName) { return s3 .headBucket({ Bucket: bucketName }) .promise() .catch(function () { return s3 .createBucket({ Bucket: bucketName }) .promise() .then(function () { return s3.waitFor('bucketExists', { Bucket: bucketName }).promise(); }); }); } function uploadFile(s3, bucketName, file, logger) { var progress = progressWriter('uploading ' + path.basename(file), logger); return fsUtils.hashFileContents(file).then(function (hash) { var parsedPath = path.parse(file); return s3 .upload({ Bucket: bucketName, Key: path.format({ name: parsedPath.name + '-' + hash, ext: parsedPath.ext, }), Body: fs.createReadStream(file), }) .promise() .then(function (result) { progress(1, 1); return result; }); }); } function getStackOutput(cloudformation, stackName, logger) { return new Promise(function (resolve, reject) { function poll() { cloudformation .describeStacks({ StackName: stackName, }) .promise() .then(function (result) { var stack = result.Stacks[0]; if (logger) { logger.info(`stack status: ${result.Stacks[0].StackStatus}`); } if ( stack.StackStatus === 'CREATE_FAILED' || stack.StackStatus === 'ROLLBACK_FAILED' || stack.StackStatus === 'UPDATE_ROLLBACK_FAILED' || stack.StackStatus === 'ROLLBACK_COMPLETE' || stack.StackStatus === 'UPDATE_ROLLBACK_COMPLETE' ) { return reject(stack); } else if ( stack.StackStatus === 'CREATE_COMPLETE' || stack.StackStatus === 'UPDATE_COMPLETE' ) { return resolve(stack); } setTimeout(poll, 5000); }); } poll(); }); } function createOrUpdateStack( cloudformation, stackName, templateUrl, params, logger ) { return cloudformation .describeStacks({ StackName: stackName }) .promise() .then(function () { return 'UPDATE'; }) .catch(function () { return 'CREATE'; }) .then(function (changeSetType) { var changeSetName = stackName + '-changeset' + Date.now(); return cloudformation .createChangeSet({ ChangeSetName: changeSetName, ChangeSetType: changeSetType, StackName: stackName, TemplateURL: templateUrl, Capabilities: ['CAPABILITY_IAM'], Parameters: params, }) .promise() .then(function () { if (logger) { logger.info('Creating stack change set'); } return cloudformation .waitFor('changeSetCreateComplete', { StackName: stackName, ChangeSetName: changeSetName, $waiter: { delay: 5, }, }) .promise(); }) .then(function () { if (logger) { logger.info('Executing stack change set'); } return cloudformation .executeChangeSet({ ChangeSetName: changeSetName, StackName: stackName, }) .promise(); }) .then(function () { return getStackOutput(cloudformation, stackName, logger); }) .then(function (stack) { return stack.Outputs.reduce(function (result, output) { result[output.OutputKey] = output.OutputValue; return result; }, {}); }); }); } module.exports = function deploy( { hopsConfig, awsConfig }, options, parametersOverrides, logger ) { if (!fs.existsSync(hopsConfig.buildDir)) { if (logger) { logger.error( 'Could not find build directory. Please make sure that you ' + 'have executed "hops build" before trying to deploy your application.' ); } return process.exit(1); } AWS.config.update({ region: awsConfig.region }); AWS.config.apiVersions = { cloudformation: '2010-05-15', s3: '2006-03-01', }; var s3 = new AWS.S3(); var cloudformation = new AWS.CloudFormation(); return fsUtils .createTmpDirectory() .then(function (tmpDirectory) { var zippedBundleLocation = path.join(tmpDirectory, 'lambda.zip'); var progress = progressWriter('zipping'); function onProgress(event) { progress(event.fs.processedBytes, event.fs.totalBytes); } return Promise.all([ createLambdaBundle( hopsConfig.rootDir, zippedBundleLocation, awsConfig.include, awsConfig.exclude, onProgress ), createBucketIfNotExists(s3, awsConfig.bucketName), ]) .then(function () { return Promise.all([ uploadFile(s3, awsConfig.bucketName, zippedBundleLocation, logger), uploadFile( s3, awsConfig.bucketName, awsConfig.cloudformationTemplateFile, logger ), ]); }) .then(function (values) { var parameters = formatParameters( Object.assign( { Runtime: awsConfig.runtime, LambdaMemorySize: awsConfig.memorySize, StageName: awsConfig.stageName, BasePath: awsConfig.basePath, BucketName: awsConfig.bucketName, BundleName: values[0].Key, DomainName: awsConfig.domainName, CertificateArn: awsConfig.certificateArn, }, parametersOverrides || {} ) ); return createOrUpdateStack( cloudformation, awsConfig.stackName, values[1].Location, parameters, logger ); }); }) .then(function (outputs) { if (!logger) { return outputs; } logger.info('Your application has been deployed!'); if (outputs.DistributionDomainName) { logger.info( `Now you need to set your domain's A-Record or CNAME-Record to: ${outputs.DistributionDomainName}` ); } else { logger.info(`Visit ${outputs.Url} in your browser.`); } return outputs; }) .catch(function (error) { if (error.code) { if (logger) { logger.error(`AWS: (${error.code}) ${error.message}`); } } else { if (logger) { logger.error(inspect(error)); } } process.exitCode = 1; }); };