mirror of
https://github.com/serverless/serverless.git
synced 2026-01-18 14:58:43 +00:00
283 lines
8.4 KiB
JavaScript
283 lines
8.4 KiB
JavaScript
'use strict';
|
|
|
|
/**
|
|
* Action: Code Provision: Lambda: Nodejs
|
|
* - Collects and optimizes Lambda code in a temp folder
|
|
* - Don't attach "evt" to context, it will be overwritten in concurrent operations
|
|
*/
|
|
|
|
const SPlugin = require('../ServerlessPlugin'),
|
|
SError = require('../ServerlessError'),
|
|
SUtils = require('../utils/index'),
|
|
SCli = require('../utils/cli'),
|
|
BbPromise = require('bluebird'),
|
|
path = require('path'),
|
|
fs = require('fs'),
|
|
os = require('os');
|
|
|
|
// Promisify fs module
|
|
BbPromise.promisifyAll(fs);
|
|
|
|
class CodeProvisionLambdaNodejs extends SPlugin {
|
|
|
|
/**
|
|
* Constructor
|
|
*/
|
|
|
|
constructor(S, config) {
|
|
super(S, config);
|
|
}
|
|
|
|
/**
|
|
* Get Name
|
|
*/
|
|
|
|
static getName() {
|
|
return 'serverless.core.' + CodeProvisionLambdaNodejs.name;
|
|
}
|
|
|
|
/**
|
|
* Register Plugin Actions
|
|
*/
|
|
|
|
registerActions() {
|
|
|
|
this.S.addAction(this.codeProvisionLambdaNodejs.bind(this), {
|
|
handler: 'codeProvisionLambdaNodejs',
|
|
description: 'Deploys the code or endpoint of a function, or both'
|
|
});
|
|
|
|
return BbPromise.resolve();
|
|
}
|
|
|
|
/**
|
|
* Function Deploy
|
|
*/
|
|
|
|
codeProvisionLambdaNodejs(evt) {
|
|
let _this = this;
|
|
|
|
// Load AWS Service Instances
|
|
let awsConfig = {
|
|
region: evt.region.region,
|
|
accessKeyId: _this.S._awsAdminKeyId,
|
|
secretAccessKey: _this.S._awsAdminSecretKey,
|
|
};
|
|
_this.CloudFormation = require('../utils/aws/CloudFormation')(awsConfig);
|
|
_this.AwsMisc = require('../utils/aws/Misc');
|
|
|
|
// Flow
|
|
return _this._validateAndPrepare(evt)
|
|
.bind(_this)
|
|
.then(_this._generateLambdaCf)
|
|
.then(_this._provision)
|
|
.then(function() {
|
|
return evt;
|
|
})
|
|
.catch(function(e) {
|
|
console.log(e.stack)
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Validate And Prepare
|
|
*/
|
|
|
|
_validateAndPrepare(evt) {
|
|
return BbPromise.resolve(evt);
|
|
}
|
|
|
|
/**
|
|
* Generate lambda-cf.json file
|
|
* - Always put in entries for lambdas marked as queued
|
|
* - If no existing lambda CF, just generate entries for lambdas intended for deployment
|
|
* - If existing lambda CF, find all s-function.json's in current project, and put in ones that are already in
|
|
* - existing lambda-cf.json. Making sure to use the existing CF obj for the lambda to not trigger an update
|
|
* @returns {Promise} true if there was an existing stack, false if not
|
|
* @private
|
|
*/
|
|
|
|
_generateLambdaCf(evt) {
|
|
|
|
let _this = this;
|
|
evt.stackExists = true;
|
|
|
|
// Fetch Lambdas CF Stack
|
|
let params = {
|
|
StackName: _this.CloudFormation.sGetLambdasStackName(
|
|
evt.stage,
|
|
_this.S._projectJson.name) /* required */
|
|
};
|
|
|
|
return _this.CloudFormation.getTemplatePromised(params)
|
|
.then(function(data) {
|
|
return data.TemplateBody;
|
|
})
|
|
.error(e => {
|
|
|
|
// ValidationError if does not exist
|
|
if (e && ['ValidationError', 'ResourceNotFoundException'].indexOf(e.code) == -1) {
|
|
console.error(
|
|
'Error trying to fetch existing lambda cf stack for region',
|
|
evt.region,
|
|
'stage',
|
|
evt.stage,
|
|
e
|
|
);
|
|
throw new SError(e.message);
|
|
}
|
|
|
|
SUtils.sDebug('no existing lambda stack');
|
|
evt.stackExists = false;
|
|
|
|
return false;
|
|
})
|
|
.then(deployedCfTemplate => {
|
|
|
|
// Recreate new CloudFormation Template
|
|
let templatesPath = path.join(__dirname, '..', '..', 'templates'),
|
|
projectCfTemplate = SUtils.readAndParseJsonSync(
|
|
path.join(templatesPath, 'lambdas-cf.json')
|
|
);
|
|
|
|
delete projectCfTemplate.Resources.lTemplate;
|
|
|
|
projectCfTemplate.Description = _this.S._projectJson.name + ' lambda resources';
|
|
projectCfTemplate.Parameters.aaLambdaRoleArn.Default = evt.region.iamRoleArnLambda;
|
|
|
|
// Always add lambdas tagged for deployment
|
|
evt.functions.forEach(func => {
|
|
|
|
Object.keys(func.cloudFormation.lambda).forEach(resourceKey => {
|
|
|
|
// Get cloudformation.lambda resource (Functions, EventSourceMapping, AccessPolicyX)
|
|
let resourceJson = func.cloudFormation.lambda[resourceKey];
|
|
|
|
// If its not a "Function" CF resource, prefix it with the function name to prevent collisions
|
|
resourceKey = (resourceJson.Type == 'AWS::Lambda::Function') ? func.name : func.name + '-' + resourceKey;
|
|
|
|
// Add to project CF template
|
|
projectCfTemplate.Resources[resourceKey] = resourceJson;
|
|
|
|
SUtils.sDebug(`Adding Resource ${resourceKey}`);
|
|
|
|
});
|
|
});
|
|
|
|
// If existing lambdas CF template
|
|
if (deployedCfTemplate) {
|
|
|
|
SUtils.sDebug('Existing lambdas CloudFormation stack detected for: ', params.StackName);
|
|
|
|
deployedCfTemplate = JSON.parse(deployedCfTemplate);
|
|
|
|
// Find all lambdas in project, and copy ones that are in deployed template
|
|
return SUtils.getFunctions(_this.S._projectRootPath)
|
|
.then(allFunctions => {
|
|
|
|
let allCfResources = [];
|
|
|
|
// Loop through all functions
|
|
for (let i = 0; i < allFunctions.length; i++) {
|
|
|
|
// Add Lambda CF Resources
|
|
if (allFunctions[i].cloudFormation && allFunctions[i].cloudFormation.lambda) {
|
|
|
|
let lambda = allFunctions[i].cloudFormation.lambda;
|
|
|
|
// Loop through each CF resource for this Lambda and add them to the allCfResources array
|
|
for (let resource in lambda) {
|
|
|
|
if (!resource.Type) continue;
|
|
|
|
// Prefix Function Name
|
|
if (['AWS::Lambda::EventSourceMapping',
|
|
'AWS::Lambda::Permission'].indexOf(lambda[resource].Type) !== -1) {
|
|
allCfResources.push(allFunctions[i].name + resource);
|
|
} else {
|
|
allCfResources.push(resource);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Loop through deployed CF template resource keys
|
|
Object.keys(deployedCfTemplate.Resources).forEach(deployedResource => {
|
|
|
|
// If resource key does not exist in project Cf Template
|
|
if (!projectCfTemplate.Resources[deployedResource]
|
|
&& allCfResources.indexOf(deployedResource) != -1) {
|
|
SUtils.sDebug(`Adding existing lambda ${deployedResource}`);
|
|
projectCfTemplate.Resources[resource] = deployedCfTemplate.Resources[deployedResource];
|
|
}
|
|
});
|
|
|
|
return projectCfTemplate;
|
|
});
|
|
} else {
|
|
return projectCfTemplate;
|
|
}
|
|
})
|
|
.then(projectCfTemplate => {
|
|
|
|
let lambdasCfPath = path.join(
|
|
_this.S._projectRootPath,
|
|
'cloudformation',
|
|
'lambdas-cf.json'
|
|
);
|
|
|
|
SUtils.sDebug(`Writing to ${lambdasCfPath}`);
|
|
|
|
evt.projectCfTemplate = projectCfTemplate;
|
|
|
|
return SUtils.writeFile(
|
|
lambdasCfPath,
|
|
JSON.stringify(evt.projectCfTemplate, null, 2)
|
|
)
|
|
.then(() => {
|
|
return evt;
|
|
});
|
|
});
|
|
|
|
}
|
|
|
|
_provision(evt) {
|
|
|
|
let _this = this,
|
|
createOrUpdate,
|
|
cfDeferred;
|
|
|
|
|
|
SUtils.sDebug(`Lambda stack exists (${evt.stackExists}), deploying with ARN: ${evt.region.iamRoleArnLambda}`);
|
|
|
|
if (evt.stackExists) {
|
|
cfDeferred = _this.CloudFormation.sUpdateLambdasStack(
|
|
_this.S,
|
|
evt.stage,
|
|
evt.region.region);
|
|
createOrUpdate = 'update';
|
|
} else {
|
|
cfDeferred = _this.CloudFormation.sCreateLambdasStack(
|
|
_this.S,
|
|
evt.stage,
|
|
evt.region.region);
|
|
createOrUpdate = 'create';
|
|
}
|
|
|
|
SCli.log('Running CloudFormation lambda deploy...');
|
|
|
|
let spinner = SCli.spinner();
|
|
spinner.start();
|
|
|
|
return cfDeferred
|
|
.then(function(cfData) {
|
|
return _this.CloudFormation.sMonitorCf(cfData, createOrUpdate);
|
|
})
|
|
.then(function() {
|
|
spinner.stop(true);
|
|
});
|
|
}
|
|
}
|
|
|
|
module.exports = CodeProvisionLambdaNodejs;
|