mirror of
https://github.com/serverless/serverless.git
synced 2026-01-18 14:58:43 +00:00
248 lines
8.3 KiB
JavaScript
248 lines
8.3 KiB
JavaScript
'use strict';
|
|
|
|
/**
|
|
* JAWS Command: install
|
|
* - Fetches an jaws-module from another github repo and installs it locally
|
|
*/
|
|
const ProjectCmd = require('./ProjectCmd.js'),
|
|
JawsError = require('../jaws-error'),
|
|
Promise = require('bluebird'),
|
|
JawsCLI = require('../utils/cli'),
|
|
utils = require('../utils'),
|
|
path = require('path'),
|
|
URL = require('url'),
|
|
Download = require('download');
|
|
|
|
let fs = require('fs'),
|
|
wrench = require('wrench'),
|
|
URL = require('url'),
|
|
temp = require('temp');
|
|
|
|
Promise.promisifyAll(fs);
|
|
Promise.promisifyAll(temp);
|
|
Promise.promisifyAll(wrench);
|
|
|
|
temp.track();
|
|
|
|
const CMD = class ModuleCreate extends ProjectCmd {
|
|
constructor(JAWS, url, saveCf, delExisting, dontInstallDep) {
|
|
super(JAWS);
|
|
this._url = url;
|
|
this._saveCf = saveCf;
|
|
this._dontInstallDep = dontInstallDep;
|
|
this._delExisting = (delExisting === true);
|
|
}
|
|
|
|
run() {
|
|
let _this = this;
|
|
|
|
return this._JAWS.validateProject()
|
|
.bind(_this)
|
|
.then(_this._downloadMod)
|
|
.then(_this._installFiles)
|
|
.then(module => {
|
|
if (_this._saveCf) {
|
|
return _this._saveCfTemplate(module.path).then(() => module);
|
|
} else {
|
|
return module;
|
|
}
|
|
})
|
|
.then(module => {
|
|
JawsCLI.log('Successfully installed ' + module.name);
|
|
|
|
let deferredDepInstalls = [];
|
|
|
|
if (utils.fileExistsSync(path.join(module.path, 'package.json'))) {
|
|
if (_this._dontInstallDep) {
|
|
JawsCLI.log('Make sure to run "npm install" from the module\'s dir');
|
|
} else {
|
|
JawsCLI.log('Installing node dependencies...');
|
|
deferredDepInstalls.push(utils.npmInstall(module.path));
|
|
}
|
|
}
|
|
|
|
return Promise.all(deferredDepInstalls);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Download and extract awsm
|
|
*
|
|
* @returns {Promise} tempDirPath
|
|
* @private
|
|
*/
|
|
_downloadMod() {
|
|
let _this = this,
|
|
spinner = JawsCLI.spinner(),
|
|
url = URL.parse(_this._url),
|
|
parts = url.pathname.split('/'),
|
|
repo = {
|
|
owner: parts[1],
|
|
repo: parts[2],
|
|
branch: 'master'
|
|
};
|
|
|
|
//TODO: support github tree URLS (branch): https://github.com/jaws-framework/JAWS/tree/cf-deploy
|
|
if (~repo.repo.indexOf('#')) {
|
|
url[2].split('#');
|
|
repo.repo = url[2].split('#')[0];
|
|
repo.branch = url[2].split('#')[1];
|
|
}
|
|
|
|
if (url.hostname !== 'github.com' || !repo.owner || !repo.repo) {
|
|
spinner.stop(true);
|
|
return Promise.reject(new JawsError(
|
|
'Must be a github url in this format: https://github.com/jaws-framework/JAWS',
|
|
JawsError.errorCodes.UNKNOWN
|
|
));
|
|
}
|
|
|
|
let downloadUrl = 'https://github.com/' + repo.owner + '/' + repo.repo + '/archive/' + repo.branch + '.zip';
|
|
|
|
return temp.mkdirAsync('awsm')
|
|
.then(tempDirPath => {
|
|
return new Promise(function(resolve, reject) {
|
|
utils.jawsDebug(`Downloading awsm to ${tempDirPath}`);
|
|
JawsCLI.log('Downloading aws-module ...');
|
|
spinner.start();
|
|
|
|
new Download({
|
|
timeout: 30000,
|
|
extract: true,
|
|
strip: 1,
|
|
mode: '755',
|
|
})
|
|
.get(downloadUrl)
|
|
.dest(tempDirPath)
|
|
.run(function(error) {
|
|
spinner.stop(true);
|
|
|
|
if (error) {
|
|
reject(new JawsError(`Module Download and installation failed: ${error}`, JawsError.errorCodes.UNKNOWN));
|
|
}
|
|
|
|
resolve(tempDirPath);
|
|
});
|
|
});
|
|
});
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param tempDirPath
|
|
* @returns {Promise} object {name: awsmJson.name, path: targetModPath}
|
|
* @private
|
|
*/
|
|
_installFiles(tempDirPath) {
|
|
let _this = this,
|
|
srcAwsmJsonPath = path.join(tempDirPath, 'awsm.json'),
|
|
awsModsPath = path.join(_this._JAWS._projectRootPath, 'aws_modules');
|
|
|
|
if (!utils.fileExistsSync(srcAwsmJsonPath)) {
|
|
return Promise.reject(new JawsError('Module missing awsm.json file in root of project', JawsError.errorCodes.UNKNOWN));
|
|
}
|
|
|
|
let awsmJson = utils.readAndParseJsonSync(srcAwsmJsonPath);
|
|
if (!awsmJson.name) {
|
|
return Promise.reject(new JawsError('awsm.json for module missing name attr', JawsError.errorCodes.UNKNOWN));
|
|
}
|
|
|
|
let targetModPath = path.join(awsModsPath, awsmJson.name);
|
|
if (!_this._delExisting && utils.dirExistsSync(targetModPath)) {
|
|
return Promise.reject(new JawsError('Module named ' + awsmJson.name + ' already exists in your project', JawsError.errorCodes.UNKNOWN));
|
|
}
|
|
|
|
if (
|
|
(!awsmJson.resources) || (!awsmJson.resources.cloudFormation) ||
|
|
(!awsmJson.resources.cloudFormation.LambdaIamPolicyDocumentStatements) ||
|
|
(!awsmJson.resources.cloudFormation.ApiGatewayIamPolicyDocumentStatements)
|
|
) {
|
|
return Promise.reject(new JawsError('Module does not have required cloudFormation attributes', JawsError.errorCodes.UNKNOWN));
|
|
}
|
|
|
|
//Things look good, copy over to proj
|
|
wrench.copyDirSyncRecursive(tempDirPath, targetModPath, {
|
|
forceDelete: _this._delExisting,
|
|
excludeHiddenUnix: false,
|
|
});
|
|
|
|
return Promise.reslove({name: awsmJson.name, path: targetModPath});
|
|
}
|
|
|
|
/**
|
|
* Save CloudFormation attrs
|
|
*
|
|
* @param modPath path to the newly installed module
|
|
* @returns {Promise}
|
|
*/
|
|
_saveCfTemplate(modPath) {
|
|
let awsmJson = utils.readAndParseJsonSync(path.join(modPath, 'awsm.json')),
|
|
projectCfPath = path.join(this._JAWS._meta.projectRootPath, 'cloudformation');
|
|
|
|
let cfExtensionPoints = awsmJson.resources.cloudFormation;
|
|
|
|
if (!utils.dirExistsSync(projectCfPath)) {
|
|
return Promise.reject(new JawsError('Your project has no cloudformation dir', JawsError.errorCodes.UNKNOWN));
|
|
}
|
|
|
|
//Update every resources-cf.json for every stage and region. Deep breath...
|
|
return new Promise(function(resolve, reject) {
|
|
resolve(wrench.readdirSyncRecursive(projectCfPath))
|
|
})
|
|
.then(function(files) {
|
|
files.forEach(file => {
|
|
file = path.join(projectCfPath, file);
|
|
if (utils.endsWith(file, 'resources-cf.json')) {
|
|
let regionStageResourcesCfJson = utils.readAndParseJsonSync(file);
|
|
|
|
if (cfExtensionPoints.LambdaIamPolicyDocumentStatements.length > 0) {
|
|
JawsCLI.log('Merging in Lambda IAM Policy statements from awsm');
|
|
}
|
|
cfExtensionPoints.LambdaIamPolicyDocumentStatements.forEach(function(policyStmt) {
|
|
regionStageResourcesCfJson.Resources.IamPolicyLambda.Properties.PolicyDocument.Statement.push(policyStmt);
|
|
});
|
|
|
|
if (cfExtensionPoints.ApiGatewayIamPolicyDocumentStatements.length > 0) {
|
|
JawsCLI.log('Merging in API Gateway IAM Policy statements from awsm');
|
|
}
|
|
cfExtensionPoints.ApiGatewayIamPolicyDocumentStatements.forEach(function(policyStmt) {
|
|
regionStageResourcesCfJson.Resources.IamPolicyApiGateway.Properties.PolicyDocument.Statement.push(policyStmt);
|
|
});
|
|
|
|
let cfResourceKeys = Object.keys(cfExtensionPoints.Resources);
|
|
|
|
if (cfResourceKeys.length > 0) {
|
|
JawsCLI.log('Merging in CF Resources from awsm');
|
|
}
|
|
cfResourceKeys.forEach(resourceKey => {
|
|
if (regionStageResourcesCfJson.Resources[resourceKey]) {
|
|
throw new JawsError(
|
|
`Resource key ${resourceKey} already defined in ${file}`,
|
|
JawsError.errorCodes.UNKNOWN
|
|
);
|
|
}
|
|
|
|
regionStageResourcesCfJson.Resources[resourceKey] = cfExtensionPoints.Resources[resourceKey];
|
|
});
|
|
|
|
utils.writeFile(file, JSON.stringify(regionStageResourcesCfJson, null, 2));
|
|
}
|
|
});
|
|
});
|
|
}
|
|
};
|
|
|
|
/**************************************
|
|
* EXPORTS
|
|
**************************************/
|
|
module.exports.install = function(JAWS, url, saveCf, dontInstallDep) {
|
|
let command = new CMD(JAWS, url, saveCf, false, dontInstallDep);
|
|
return command.run();
|
|
};
|
|
|
|
module.exports.update = function(JAWS, url, saveCf, dontInstallDep) {
|
|
let command = new CMD(JAWS, url, saveCf, true, dontInstallDep);
|
|
return command.run();
|
|
};
|
|
|
|
exports.ModuleInstall = CMD; |