UNPKG

now-flow

Version:

Add deployment workflows to Zeit now

388 lines (334 loc) 9 kB
// @flow // theirs const ms = require('ms') const mri = require('mri') const { gray, bold } = require('chalk') const bytes = require('bytes') const uid = require('uid-promise') const retry = require('async-retry') const debug = require('debug')('now:aws:deploy') // ours const resolve = require('../../resolve') const ok = require('../../util/output/ok') const wait = require('../../util/output/wait') const info = require('../../util/output/info') const error = require('../../util/output/error') const link = require('../../util/output/link') const success = require('../../util/output/success') const param = require('../../util/output/param') const humanPath = require('../../util/humanize-path') const build = require('../../serverless/build') const getAWS = require('./get-aws') const describeProject = require('../../describe-project') const copyToClipboard = require('../../util/copy-to-clipboard') const NOW_DEFAULT_IAM_ROLE = 'now-default-role' const IAM_POLICY_DOCUMENT = { Version: '2012-10-17', Statement: [ { Sid: '', Effect: 'Allow', Principal: { Service: 'lambda.amazonaws.com' }, Action: 'sts:AssumeRole' } ] } /*eslint-disable */ const getProcess = () => process /*eslint-enable */ const deploy = async ({ authConfig, argv }) => { // Example now.json for awsConfig // { // timeout: String, // memory: Number, // region: String // } const awsConfig = (argv || {}).aws || {} const region = awsConfig.region || 'us-west-1' const _timeout = awsConfig.timeout && typeof(awsConfig.timeout) == 'number' ? awsConfig.timeout : 15 const memory = awsConfig.memory && typeof(awsConfig.memory) == 'number' ? awsConfig.memory : 512 const _argv = mri(argv, { boolean: ['help'], alias: { help: 'h' } }) // `now [provider] [deploy] [target]` const [cmdOrTarget = null, target_ = null] = _argv._.slice(2).slice(-2) let target if (cmdOrTarget === 'aws' || cmdOrTarget === 'deploy') { target = target_ === null ? getProcess().cwd() : target_ } else { if (target_) { console.error(error('Unexpected number of arguments for deploy command')) return 1 } else { target = cmdOrTarget === null ? getProcess().cwd() : cmdOrTarget } } const start = Date.now() const resolved = await resolve(target) if (resolved === null) { console.error(error(`Could not resolve deployment target ${param(target)}`)) return 1 } let desc try { desc = await describeProject(resolved) } catch (err) { if (err.code === 'AMBIGOUS_CONFIG') { console.error( error(`There is more than one source of \`now\` config: ${err.files}`) ) return 1 } else { throw err } } // initialize aws client const aws = getAWS(authConfig) console.log( info( `Deploying ${param(humanPath(resolved))} ${gray('(aws)')} ${gray( `(${region})` )}` ) ) const buildStart = Date.now() const stopBuildSpinner = wait('Building and bundling your app…') const zipFile = await build(resolved, desc) stopBuildSpinner() // lambda limits to 50mb if (zipFile.length > 50 * 1024 * 1024) { console.error(error('The build exceeds the 50mb AWS Lambda limit')) return 1 } console.log( ok( `Build generated a ${bold(bytes(zipFile.length))} zip ${gray( `[${ms(Date.now() - buildStart)}]` )}` ) ) const iam = new aws.IAM({ apiVersion: '2010-05-08' }) const gateway = new aws.APIGateway({ apiVersion: '2015-07-09', region }) const lambda = new aws.Lambda({ apiVersion: '2015-03-31', region }) let role try { role = await getRole(iam, { RoleName: NOW_DEFAULT_IAM_ROLE }) } catch (err) { if ('NoSuchEntity' === err.code) { const iamStart = Date.now() role = await createRole(iam, { AssumeRolePolicyDocument: JSON.stringify(IAM_POLICY_DOCUMENT), RoleName: NOW_DEFAULT_IAM_ROLE }) console.log( ok( `Initialized IAM role ${param(NOW_DEFAULT_IAM_ROLE)} ${gray( `[${ms(iamStart - Date.now())}]` )}` ) ) } else { throw err } } const deploymentId = 'now-' + desc.name + '-' + (await uid(10)) const resourcesStart = Date.now() const stopResourcesSpinner = wait('Creating API resources') debug('initializing lambda function') const λ = await retry( async bail => { try { return await createFunction(lambda, { Code: { ZipFile: zipFile }, Runtime: 'nodejs6.10', Description: desc.description, FunctionName: deploymentId, Handler: 'index.handler', Role: role.Role.Arn, Timeout: _timeout, MemorySize: memory }) } catch (err) { if (err.retryable || err.code === 'InvalidParameterValueException') { // created role is not ready debug('retrying creating function (%s)', err.message) throw err } bail(err) } }, { minTimeout: 2500, maxTimeout: 5000 } ) debug('initializing api gateway') const api = await createAPI(gateway, { name: deploymentId, description: desc.description }) debug('retrieving root resource id') const resources = await getResources(gateway, { restApiId: api.id }) const rootResourceId = resources.items[0].id debug('initializing gateway method for /') await putMethod(gateway, { restApiId: api.id, authorizationType: 'NONE', httpMethod: 'ANY', resourceId: rootResourceId }) debug('initializing gateway integration for /') await putIntegration(gateway, { restApiId: api.id, resourceId: rootResourceId, httpMethod: 'ANY', type: 'AWS_PROXY', integrationHttpMethod: 'POST', uri: `arn:aws:apigateway:${region}:lambda:path/2015-03-31/functions/${λ.FunctionArn}/invocations` }) debug('initializing gateway resource') const resource = await createResource(gateway, { restApiId: api.id, parentId: rootResourceId, pathPart: '{proxy+}' }) debug('initializing gateway method for {proxy+}') await putMethod(gateway, { restApiId: api.id, authorizationType: 'NONE', httpMethod: 'ANY', resourceId: resource.id }) debug('initializing gateway integration for {proxy+}') await putIntegration(gateway, { restApiId: api.id, resourceId: resource.id, httpMethod: 'ANY', type: 'AWS_PROXY', integrationHttpMethod: 'POST', uri: `arn:aws:apigateway:${region}:lambda:path/2015-03-31/functions/${λ.FunctionArn}/invocations` }) debug('creating deployment') await createDeployment(gateway, { restApiId: api.id, stageName: 'now' }) const [, accountId] = role.Role.Arn.match(/^arn:aws:iam::(\d+):/) await addPermission(lambda, { FunctionName: deploymentId, StatementId: deploymentId, Action: 'lambda:InvokeFunction', Principal: 'apigateway.amazonaws.com', SourceArn: `arn:aws:execute-api:${region}:${accountId}:${api.id}/now/ANY/*` }) stopResourcesSpinner() console.log( ok( `API resources created (id: ${param(deploymentId)}) ${gray( `[${ms(Date.now() - resourcesStart)}]` )}` ) ) const url = `https://${api.id}.execute-api.${region}.amazonaws.com/now` const copied = copyToClipboard(url, true) console.log( success( `${link(url)} ${copied ? gray('(in clipboard)') : ''} ${gray( `[${ms(Date.now() - start)}]` )}` ) ) return 0 } const getRole = (iam, params) => { return new Promise((res, reject) => { iam.getRole(params, (err, data) => { if (err) return reject(err) res(data) }) }) } const createRole = (iam, params) => { return new Promise((res, reject) => { iam.createRole(params, (err, data) => { if (err) return reject(err) res(data) }) }) } const createFunction = (lambda, params) => { return new Promise((res, reject) => { lambda.createFunction(params, (err, data) => { if (err) return reject(err) res(data) }) }) } const addPermission = (lambda, params) => { return new Promise((res, reject) => { lambda.addPermission(params, (err, data) => { if (err) return reject(err) res(data) }) }) } const createAPI = (gateway, params) => { return new Promise((res, reject) => { gateway.createRestApi(params, (err, data) => { if (err) return reject(err) res(data) }) }) } const getResources = (gateway, params) => { return new Promise((res, reject) => { gateway.getResources(params, (err, data) => { if (err) return reject(err) res(data) }) }) } const createResource = (gateway, params) => { return new Promise((res, reject) => { gateway.createResource(params, (err, data) => { if (err) return reject(err) res(data) }) }) } const putMethod = (gateway, params) => { return new Promise((res, reject) => { gateway.putMethod(params, (err, data) => { if (err) return reject(err) res(data) }) }) } const putIntegration = (gateway, params) => { return new Promise((res, reject) => { gateway.putIntegration(params, (err, data) => { if (err) return reject(err) res(data) }) }) } const createDeployment = (gateway, params) => { return new Promise((res, reject) => { gateway.createDeployment(params, (err, data) => { if (err) return reject(err) res(data) }) }) } module.exports = deploy