mirror of
https://github.com/serverless/serverless.git
synced 2026-01-25 15:07:39 +00:00
351 lines
12 KiB
JavaScript
Executable File
351 lines
12 KiB
JavaScript
Executable File
/**
|
|
* JAWS Command Line Interface
|
|
*/
|
|
|
|
|
|
var aws = require('aws-sdk'),
|
|
exec = require('child_process').exec,
|
|
fs = require('fs'),
|
|
os = require('os'),
|
|
packageJson = require('./../package.json'),
|
|
async = require('async'),
|
|
zip = new require('node-zip')(),
|
|
wrench = require('wrench'),
|
|
moment = require('moment'),
|
|
yaml = require('js-yaml'),
|
|
Q = require('q'),
|
|
prettysize = require('prettysize'),
|
|
replace = require("replace"),
|
|
path = require('path');
|
|
|
|
var JAWS = function () {
|
|
this.jawsLibDir = this._findLibDir(process.cwd());
|
|
|
|
// Require Admin ENV Variables
|
|
require('dotenv').config({
|
|
path: this.jawsLibDir + '/../.adminenv'
|
|
});
|
|
|
|
this.version = packageJson.version;
|
|
this.configYml = yaml.safeLoad(fs.readFileSync(this.jawsLibDir + '/../jaws.yml', 'utf8')).jaws;
|
|
|
|
return this;
|
|
};
|
|
|
|
|
|
/**
|
|
* JAWS: Run Lambda Function Locally
|
|
*/
|
|
|
|
JAWS.prototype.run = function (functionRootDirPath, callback) {
|
|
// Check if in extension's root directory
|
|
if (!fs.existsSync(functionRootDirPath + '/index.js')) {
|
|
callback(new Error('****** JAWS: Error - You must be in the root directory of your Lambda function to run it locally.'));
|
|
}
|
|
|
|
var extension = require(functionRootDirPath + '/index.js');
|
|
var event = require(functionRootDirPath + '/event.json');
|
|
var metadata = require(functionRootDirPath + '/lambda.json');
|
|
|
|
console.log('****** JAWS: Running ' + metadata.FunctionName + ' Locally...');
|
|
|
|
// Run Handler
|
|
this._runHandler(extension.handler, event, callback);
|
|
};
|
|
|
|
|
|
JAWS.prototype._runHandler = function (handler, event, callback) {
|
|
var context = {
|
|
succeed: function (result) {
|
|
callback(null, result);
|
|
},
|
|
fail: function (error) {
|
|
console.error(error);
|
|
callback(new Error(error.message));
|
|
},
|
|
done: function (error, result) {
|
|
callback(error, result);
|
|
}
|
|
};
|
|
|
|
handler(event, context);
|
|
};
|
|
|
|
|
|
/**
|
|
* JAWS: Deploy Lambda Function
|
|
*/
|
|
|
|
JAWS.prototype.deploy = function (stage, functionDir) {
|
|
var deferred = Q.defer();
|
|
|
|
// Defaults
|
|
var _this = this;
|
|
var regions = this.configYml.deploy.regions;
|
|
|
|
// Get Lambda Config
|
|
if (!fs.existsSync(functionDir + '/lambda.json')) {
|
|
return Q.fcall(function () {
|
|
throw new Error('****** JAWS Error: lambda.json is missing in this folder');
|
|
});
|
|
}
|
|
var lambda_config = require(functionDir + '/lambda.json');
|
|
lambda_config.FunctionName = stage + "_" + lambda_config.FunctionName;
|
|
|
|
console.log('****** JAWS: Deploying ' + lambda_config.FunctionName);
|
|
|
|
var tmpCodeDir = os.tmpdir() + lambda_config.FunctionName + '-' + moment().unix(),
|
|
packageApiExcludesGlobal = _this.configYml.deploy.packageApiExcludes || [],
|
|
packageApiExcludesLambda = lambda_config.packageApiExcludes || [],
|
|
packageLibExcludesGlobal = _this.configYml.deploy.packageLibExcludes || [],
|
|
packageLibExcludesLambda = lambda_config.packageLibExcludes || [],
|
|
packageApiExcludes = packageApiExcludesGlobal.concat(packageApiExcludesLambda),
|
|
packageLibExclues = packageLibExcludesGlobal.concat(packageLibExcludesLambda);
|
|
|
|
console.log("\tCopying files to tmp dir " + tmpCodeDir);
|
|
|
|
// Copy code from api/model/action dir to temp location
|
|
wrench.copyDirSyncRecursive(functionDir, tmpCodeDir, {
|
|
forceDelete: true,
|
|
exclude: function (name, prefix) {
|
|
var relPath = prefix.replace(functionDir, '');
|
|
return _this._copyExclude(name, relPath, packageApiExcludes);
|
|
}
|
|
});
|
|
|
|
//Fixup rel links to shared lib dir
|
|
replace({
|
|
regex: RegExp("require\\(['\"](../)*(../../../lib)['\"]\\)", "g"),
|
|
replacement: "require('./lib')",
|
|
paths: [tmpCodeDir + '/index.js'],
|
|
recursive: true,
|
|
silent: true
|
|
});
|
|
|
|
// Copy lib dir
|
|
wrench.copyDirSyncRecursive(_this.jawsLibDir, tmpCodeDir + '/lib', {
|
|
forceDelete: true,
|
|
exclude: function (name, prefix) {
|
|
var relPath = prefix.replace(_this.jawsLibDir, '');
|
|
return _this._copyExclude(name, relPath, packageLibExclues);
|
|
}
|
|
});
|
|
|
|
// Copy down ENV file
|
|
var s3 = new aws.S3({
|
|
apiVersion: '2006-03-01',
|
|
accessKeyId: global.process.env.ADMIN_AWS_ACCESS_KEY_ID,
|
|
secretAccessKey: global.process.env.ADMIN_AWS_SECRET_ACCESS_KEY
|
|
}),
|
|
targetEnvFile = fs.createWriteStream(tmpCodeDir + '/lib/.env'),
|
|
s3EnvPath = _this.configYml.deploy.envS3Location,
|
|
firstSlash = s3EnvPath.indexOf('/'),
|
|
bucket = s3EnvPath.substring(0, firstSlash),
|
|
key = s3EnvPath.substring(firstSlash + 1) + stage;
|
|
|
|
console.log("\tDownolading ENV file s3://" + bucket + key);
|
|
s3.getObject({Bucket: bucket, Key: key})
|
|
.on("error", function (err) {
|
|
console.error("\tError getting ENV file s3://" + bucket + key);
|
|
deferred.reject(err);
|
|
})
|
|
.createReadStream().pipe(targetEnvFile);
|
|
|
|
targetEnvFile
|
|
.on('finish', function () {
|
|
console.log("\tCompressing files...");
|
|
|
|
_this._zip(lambda_config.FunctionName, tmpCodeDir, function (err, buffer) {
|
|
if (err) {
|
|
return deferred.reject(err);
|
|
}
|
|
|
|
async.map(regions, function (region, cb) {
|
|
var lambda = new aws.Lambda({
|
|
apiVersion: '2015-03-31',
|
|
region: region,
|
|
accessKeyId: global.process.env.ADMIN_AWS_ACCESS_KEY_ID,
|
|
secretAccessKey: global.process.env.ADMIN_AWS_SECRET_ACCESS_KEY
|
|
});
|
|
|
|
// Define Params for New Lambda Function
|
|
var params = {
|
|
FunctionName: lambda_config.FunctionName,
|
|
Handler: lambda_config.Handler ? lambda_config.Handler : 'index.handler',
|
|
Role: lambda_config.Role ? lambda_config.Role : _this.configYml.iamRoles[stage],
|
|
Runtime: lambda_config.Runtime,
|
|
Description: lambda_config.Description ? lambda_config.Description : 'A Lambda function that was created with the JAWS framework',
|
|
MemorySize: lambda_config.MemorySize,
|
|
Timeout: lambda_config.Timeout
|
|
};
|
|
|
|
// Check If Lambda Function Exists Already
|
|
lambda.getFunction({
|
|
FunctionName: lambda_config.FunctionName
|
|
}, function (err, data) {
|
|
|
|
if (err && err.code !== 'ResourceNotFoundException') {
|
|
return deferred.reject(err);
|
|
}
|
|
|
|
if (!data || !data.Code) {
|
|
|
|
|
|
/**
|
|
* Create New Lambda Function
|
|
*/
|
|
|
|
console.log("\tCreating in " + stage + " with params:");
|
|
console.log(params);
|
|
|
|
params.Code = {ZipFile: buffer};
|
|
lambda.createFunction(params, function (err, data) {
|
|
lambda_arn = data;
|
|
return cb(err, data);
|
|
});
|
|
} else {
|
|
//Update instead of delete so there is no downtime
|
|
|
|
/**
|
|
* Update existing (code and config)
|
|
*/
|
|
|
|
console.log("\tUpdating existing Lambda function in " + stage + " with params:");
|
|
console.log(params);
|
|
|
|
lambda.updateFunctionCode({
|
|
FunctionName: params.FunctionName,
|
|
ZipFile: buffer
|
|
}, function (err, data) {
|
|
if (err) {
|
|
return deferred.reject(err);
|
|
}
|
|
|
|
lambda.updateFunctionConfiguration({
|
|
FunctionName: params.FunctionName,
|
|
Description: params.Description,
|
|
Handler: params.Handler,
|
|
MemorySize: params.MemorySize,
|
|
Role: params.Role,
|
|
Timeout: params.Timeout
|
|
}, function (err, data) {
|
|
return cb(err, data);
|
|
});
|
|
});
|
|
}
|
|
});
|
|
|
|
}, function (err, results) {
|
|
|
|
if (err) {
|
|
return deferred.reject(err);
|
|
}
|
|
|
|
var functionArns = [];
|
|
for (var i in results) {
|
|
functionArns.push(results[i].FunctionArn);
|
|
}
|
|
|
|
deferred.resolve(functionArns);
|
|
});
|
|
});
|
|
});
|
|
|
|
return deferred.promise;
|
|
};
|
|
|
|
JAWS.prototype._zip = function (functionName, codeDirectory, callback) {
|
|
var options = {
|
|
type: 'nodebuffer',
|
|
compression: 'DEFLATE',
|
|
compressionOptions: {level: 9}
|
|
};
|
|
|
|
var files = wrench.readdirSyncRecursive(codeDirectory);
|
|
files.forEach(function (file) {
|
|
var filePath = [codeDirectory, file].join('/');
|
|
var isFile = fs.lstatSync(filePath).isFile();
|
|
if (isFile) {
|
|
var content = fs.readFileSync(filePath);
|
|
zip.file(file, content);
|
|
}
|
|
});
|
|
|
|
var data = zip.generate(options);
|
|
|
|
if (data.length > 52428800) {
|
|
return callback(new Error("Zip file is > the 50MB Lambda deploy limit (" + data.length + " bytes)"), null);
|
|
}
|
|
|
|
console.log("\tPackage is " + prettysize(data.length, false) + " (Max 50MB)");
|
|
|
|
return callback(null, data);
|
|
};
|
|
|
|
JAWS.prototype._copyExclude = function (fileName, relPath, tests) {
|
|
if (!tests.length) {
|
|
return false;
|
|
}
|
|
|
|
return tests.some(function (sRegex) {
|
|
var re = new RegExp(sRegex),
|
|
testFile = relPath + "/" + fileName,
|
|
testFile = ('/' == testFile.charAt(0)) ? testFile.substr(1) : testFile,
|
|
matches = re.exec(testFile);
|
|
|
|
return (matches && matches.length > 0);
|
|
});
|
|
};
|
|
|
|
JAWS.prototype._findLibDir = function (startDir) {
|
|
var packageName = 'jaws-lib',
|
|
foundPath = false,
|
|
previous = "/";
|
|
|
|
//Look ahead 1 folder cuz could be running from root for things like deploy all/multiple functions
|
|
if (fs.existsSync(startDir + '/lib/package.json')) {
|
|
var info = require(startDir + '/lib/package.json');
|
|
if (packageName == info.name) {
|
|
return path.dirname(startDir + '/lib/package.json');
|
|
}
|
|
}
|
|
|
|
for (var i = 0; i < 20; i++) {
|
|
previous = previous + '../';
|
|
var fullPath = startDir + previous + 'package.json';
|
|
if (fs.existsSync(fullPath)) {
|
|
var info = require(fullPath);
|
|
if (packageName == info.name) {
|
|
foundPath = path.dirname(fullPath);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!foundPath) {
|
|
throw new Error('***** JAWS Error: Can\'t find your jaws lib folder. Did you rename it or create folders over 20 levels deep in your api folder?');
|
|
}
|
|
|
|
return foundPath;
|
|
};
|
|
|
|
/**
|
|
* JAWS: Start "site" Server
|
|
*/
|
|
|
|
JAWS.prototype.server = function (serverDir) {
|
|
|
|
// Check if in server root folder
|
|
if (!fs.existsSync(serverDir + '/server.js')) return console.log('****** JAWS: Error - You must be in the "site" directory of your JAWS application to run this command and start the server.');
|
|
|
|
var child = exec('node server', function (error, stdout, stderr) {
|
|
if (error !== null) console.log('exec error: ' + error);
|
|
});
|
|
|
|
child.stdout.pipe(process.stdout);
|
|
child.stderr.pipe(process.stderr);
|
|
};
|
|
|
|
// Export
|
|
module.exports = new JAWS();
|