UNPKG

calculus-cli

Version:

AWS Lambda CLI to quickly generate Nodejs Lambda functions along with a CI/CD pipeline.

195 lines (162 loc) 7.72 kB
import AWS from 'aws-sdk'; import _ from 'lodash'; import fs from 'fs'; import os from 'os'; import chalk from 'chalk'; import interpolation from './interpolation'; const nrc = require('node-run-cmd'); import exec from 'executive'; const s3 = new AWS.S3({apiVersion: '2006-03-01'}); import TerraformDefaultAliases from '../templates/terraform/aliases'; const { task, of, fromPromised, waitAll } = require('folktale/concurrency/task'); const Result = require('folktale/result'); const uploadFunctionToS3 = (FunctionName: string, Bucket: string, Metadata: Object = {}) => { console.log(chalk.yellow(' - Uploading funciton .zip to S3')); return new Promise((resolve, reject) => { let params = { Bucket: Bucket, Key: `${FunctionName}.zip`, Body: fs.readFileSync(`${process.cwd()}/${FunctionName}.zip`), Metadata, ServerSideEncryption: 'AES256', }; s3.putObject(params, function (err, data) { if (err) return reject(err); else resolve(data); }); }); }; export function packageFunction(CalculusConfig: any): Promise<any> { console.log(chalk.yellow(' - Packaging function into .zip file')); let commands = [ {command: `rm -rf ${CalculusConfig.FunctionName}.zip`}, {command: `zip -r -j ${CalculusConfig.FunctionName}.zip dist/index.js`} ]; return nrc.run(commands, {cwd: `${process.cwd()}`}); } const pullTerraformScripts = (): Promise<any> => { let commands = [ {command: `git clone https://github.com/Lionshead-Consulting/calculus-terraform-template.git terraform`} ]; return nrc.run(commands, {cwd: `${process.cwd()}`}); }; async function getFunctionAliasValues(CalculusConfig: any, envs: Array<string> = ['dev', 'qa', 'preprod', 'prod']): Promise<any> { const lambda = new AWS.Lambda({ apiVersion: '2015-03-31', region: CalculusConfig.tfvars.region }); const getFunction = (FunctionName) => { let params = { FunctionName }; return new Promise(function (resolve, reject) { lambda.getFunction(params, function(err, data) { if (err) reject(err); else resolve(data); }); }); }; const getFunctionAlias = (FunctionName, alias) => { let params = { FunctionName, Name: alias }; return new Promise(function (resolve, reject) { lambda.getAlias(params, function(err, data) { if (err) reject(err); else resolve(data); }); }); }; let getFunctionT = fromPromised(getFunction); let getFunctionAliasT = fromPromised(getFunctionAlias); let functionExists = await getFunctionT(CalculusConfig.FunctionName).map(res => Result.Ok(res)).orElse(err => of(Result.Error(err))).run().promise(); let aliases = await waitAll([ getFunctionAliasT(CalculusConfig.FunctionName, 'dev'), getFunctionAliasT(CalculusConfig.FunctionName, 'qa'), getFunctionAliasT(CalculusConfig.FunctionName, 'preprod'), getFunctionAliasT(CalculusConfig.FunctionName, 'prod') ]) .map(res => Result.Ok( res.map((currVal) => { return { 'aws_lambda_alias': { [`${currVal.Name}_alias`]: { name: currVal.Name, description: currVal.Description, function_name: '${aws_lambda_function.calculus_generated_function.arn}', function_version: (process.env.NODE_ENV === currVal.Name) ? '${aws_lambda_function.calculus_generated_function.version}' : currVal.FunctionVersion } } }; }) )) .orElse(err => of(Result.Error(err))) .run() .promise(); return functionExists.chain(_ => aliases).getOrElse(TerraformDefaultAliases); } async function interpolateTerraformScripts(CalculusConfig: any): void { console.log(chalk.yellow(' - Configuring Terraform scripts')); // Interpolate Terraform template expressions within calculus.tf.json, vars.tfvars.json let calculusTF = fs.readFileSync(`${process.cwd()}/terraform/calculus.tf.json`, "utf8"); let nextCalculusTF = JSON.parse(interpolation(calculusTF, { FunctionName: CalculusConfig.FunctionName, Bucket: CalculusConfig.Bucket, Region: CalculusConfig.tfvars.region, NODE_ENV: process.env.NODE_ENV || 'development' })); // Based on the stage Env variable, we will update the correct stage alias with the following value, nextCalculusTF.resource.push(...(await getFunctionAliasValues(CalculusConfig))); let varsTF = fs.readFileSync(`${process.cwd()}/terraform/vars.tfvars`, "utf8"); let lambdaCalcConfig = JSON.parse(fs.readFileSync(`${process.cwd()}/lambda-calculus.config.json`, 'utf8')); let tfvars = Object.keys(lambdaCalcConfig.tfvars).filter((currVal) => { return _.indexOf(['dev', 'qa', 'preprod', 'prod'], currVal) < 0; }).reduce((acc, currVal) => { acc[currVal] = lambdaCalcConfig.tfvars[currVal]; return acc; }, {}); tfvars = _.merge({ NODE_ENV: process.env.NODE_ENV || 'dev' }, tfvars, lambdaCalcConfig.tfvars[process.env.NODE_ENV] || {}); for (let prop in tfvars) { if(prop === 'environment') continue; if(typeof tfvars[prop] === 'number') { varsTF += `${prop} = ${tfvars[prop]}${os.EOL}`; } else if(typeof tfvars[prop] === 'string') { varsTF += `${prop} = "${tfvars[prop]}"${os.EOL}`; } } fs.writeFileSync(`./terraform/vars.tfvars`, interpolation(varsTF, {})); let lambdaIdx = nextCalculusTF.resource.reduce((acc, currVal, currIdx) => { if(acc > -1) return acc; return (Object.keys(currVal)[0] === 'aws_lambda_function') ? currIdx : -1; }, -1); nextCalculusTF['resource'][lambdaIdx]['aws_lambda_function']['calculus_generated_function'].environment.variables = _.merge({}, nextCalculusTF['resource'][lambdaIdx]['aws_lambda_function']['calculus_generated_function'].environment.variables, (lambdaCalcConfig.tfvars[process.env.NODE_ENV] && lambdaCalcConfig.tfvars[process.env.NODE_ENV].environment) ? lambdaCalcConfig.tfvars[process.env.NODE_ENV].environment.variables : {}) fs.writeFileSync(`${process.cwd()}/terraform/calculus.tf.json`, JSON.stringify(nextCalculusTF)); }; const runTerraformScripts = (): Promise<any> => { console.log(chalk.yellow(' - Running Terraform scripts')); let commands = [ `cd ${process.cwd()}/terraform`, `terraform init`, `terraform plan -var-file=vars.tfvars -out=calculus-function-plan`, `terraform apply calculus-function-plan` ]; return exec(commands, {cwd: `${process.cwd()}/terraform`}); }; export async function deploy(): Promise<any> { const CalculusConfig = JSON.parse(fs.readFileSync(`${process.cwd()}/lambda-calculus.config.json`, 'utf-8')); try { // Create zip package of Lambda function await packageFunction(CalculusConfig); // Clone CalculusJS Terraform repo await pullTerraformScripts(); // Interpolate Terraform script with function specific metadata await interpolateTerraformScripts(CalculusConfig); // Run Terraform Script to provision AWS Lambda Function let result = await runTerraformScripts(); console.log(result); return Promise.resolve(); } catch (err) { console.log(err, 'catcherr'); } }