ProjectCreate: Create new meta scaffolding. Add CF to s-project.json template and stop using resources-cf.json

This commit is contained in:
Austen Collins 2015-12-27 17:08:38 -08:00
parent 80ab74991f
commit 3026632048
25 changed files with 347 additions and 196 deletions

View File

@ -33,6 +33,7 @@ class Serverless {
this._version = require('./../package.json').version;
this._projectRootPath = SUtils.getProjectPath(process.cwd());
this._project = false;
this._meta = false;
this.actions = {};
this.hooks = {};
this.commands = {};
@ -46,12 +47,12 @@ class Serverless {
this._loadPlugins(this._projectRootPath, this._project.plugins);
}
// If within project, add further queued data
// If within private, add further queued data
if (this._projectRootPath) {
// Get Project Information
this._project = SUtils.getProject(this._projectRootPath);
this._meta = SUtils.getProjectMeta(this._projectRootPath);
this._meta = SUtils.getMeta(this._projectRootPath);
// Load Admin ENV information
require('dotenv').config({
@ -63,7 +64,6 @@ class Serverless {
this._awsAdminKeyId = process.env.SERVERLESS_ADMIN_AWS_ACCESS_KEY_ID;
this._awsAdminSecretKey = process.env.SERVERLESS_ADMIN_AWS_SECRET_ACCESS_KEY;
}
console.log(this._meta.project.stages.development.regions);
}
/**
@ -268,7 +268,7 @@ class Serverless {
let PluginClass;
if (pluginMetadatum.path.indexOf('.') == 0) {
// Load non-npm plugin from the project plugins folder
// Load non-npm plugin from the private plugins folder
let pluginAbsPath = path.join(relDir, pluginMetadatum.path);
SUtils.sDebug('Attempting to load plugin from ' + pluginAbsPath);
PluginClass = require(pluginAbsPath);

View File

@ -228,7 +228,7 @@ class ServerlessPlugin {
cliPromptSelectStage(message, stage, addLocalStage) {
let _this = this,
stages = Object.keys(_this.S._meta.project.stages);
stages = Object.keys(_this.S._meta.private.stages);
// Resolve stage if provided
if (stage) return BbPromise.resolve(stage);
@ -236,7 +236,7 @@ class ServerlessPlugin {
// Skip if not interactive
if (!_this.S._interactive) return BbPromise.resolve();
// if project has 1 stage, skip prompt
// if private has 1 stage, skip prompt
if (stages.length === 1) {
return BbPromise.resolve(stages[0]);
}
@ -274,8 +274,8 @@ class ServerlessPlugin {
if (stage === 'local') return BbPromise.resolve('local');
// If stage has one region, skip prompt and return that instead
if (stage && Object.keys(_this.S._meta.project.stages[stage].regions).length === 1 && existing) {
return BbPromise.resolve(Object.keys(_this.S._meta.project.stages[stage].regions)[0]);
if (stage && Object.keys(_this.S._meta.private.stages[stage].regions).length === 1 && existing) {
return BbPromise.resolve(Object.keys(_this.S._meta.private.stages[stage].regions)[0]);
}
// Skip if not interactive or stage is local
@ -286,8 +286,8 @@ class ServerlessPlugin {
// if stage is provided, limit region list
if (stage){
// Make sure stage exists in project
if (!_this.S._meta.project.stages[stage]) {
// Make sure stage exists in private
if (!_this.S._meta.private.stages[stage]) {
return BbPromise.reject(new SError('Stage ' + stage + ' does not exist in your project', SError.errorCodes.UNKNOWN));
}
@ -296,18 +296,18 @@ class ServerlessPlugin {
// List only regions in stage
regionChoices = [];
Object.keys(_this.S._meta.project.stages[stage].regions).forEach(function(region) {
Object.keys(_this.S._meta.private.stages[stage].regions).forEach(function(region) {
regionChoices.push(region)
});
} else {
// Make sure there are regions left in stage
if (Object.keys(_this.S._meta.project.stages[stage].regions).length === 4) {
if (Object.keys(_this.S._meta.private.stages[stage].regions).length === 4) {
return BbPromise.reject(new SError('Stage ' + stage + ' already have all possible regions.', SError.errorCodes.UNKNOWN));
}
// List only regions NOT in stage
Object.keys(_this.S._meta.project.stages[stage].regions).forEach(function(regionInStage) {
Object.keys(_this.S._meta.private.stages[stage].regions).forEach(function(regionInStage) {
let index = regionChoices.indexOf(regionInStage.region);
regionChoices.splice(index, 1);
});

View File

@ -225,7 +225,7 @@ module.exports = function(SPlugin, serverlessPath) {
// If no region specified, deploy to all regions in stage
if (!evt.regions.length) {
evt.regions = Object.keys(this.S._meta.project.stages[evt.stage].regions);
evt.regions = Object.keys(this.S._meta.private.stages[evt.stage].regions);
}
// Delete region for neatness

View File

@ -157,7 +157,7 @@ usage: serverless env get`,
}
// validate stage: make sure stage exists
if (!_this.S._meta.project.stages[_this.evt.stage] && _this.evt.stage != 'local') {
if (!_this.S._meta.private.stages[_this.evt.stage] && _this.evt.stage != 'local') {
return BbPromise.reject(new SError('Stage ' + _this.evt.stage + ' does not exist in your project', SError.errorCodes.UNKNOWN));
}
@ -165,7 +165,7 @@ usage: serverless env get`,
if (_this.evt.stage != 'local' && _this.evt.region != 'all') {
// validate region: make sure region exists in stage
if (!_this.S._meta.project.stages[_this.evt.stage].regions[_this.evt.region]) {
if (!_this.S._meta.private.stages[_this.evt.stage].regions[_this.evt.region]) {
return BbPromise.reject(new SError('Region "' + _this.evt.region + '" does not exist in stage "' + _this.evt.stage + '"'));
}
}

View File

@ -144,7 +144,7 @@ Usage: serverless env list`,
}
// Validate stage: make sure stage exists
if (!_this.S._meta.project.stages[_this.evt.stage] && _this.evt.stage != 'local') {
if (!_this.S._meta.private.stages[_this.evt.stage] && _this.evt.stage != 'local') {
return BbPromise.reject(new SError('Stage ' + _this.evt.stage + ' does not exist in your project', SError.errorCodes.UNKNOWN));
}
@ -152,7 +152,7 @@ Usage: serverless env list`,
if (_this.evt.stage != 'local' && _this.evt.region != 'all') {
// Validate region: make sure region exists in stage
if (!_this.S._meta.project.stages[_this.evt.stage].regions[_this.evt.region]) {
if (!_this.S._meta.private.stages[_this.evt.stage].regions[_this.evt.region]) {
return BbPromise.reject(new SError('Region "' + _this.evt.region + '" does not exist in stage "' + _this.evt.stage + '"'));
}
}

View File

@ -168,7 +168,7 @@ usage: serverless env set`,
}
// Validate stage: make sure stage exists
if (!_this.S._meta.project.stages[_this.evt.stage] && _this.evt.stage != 'local') {
if (!_this.S._meta.private.stages[_this.evt.stage] && _this.evt.stage != 'local') {
return BbPromise.reject(new SError('Stage ' + _this.evt.stage + ' does not exist in your project', SError.errorCodes.UNKNOWN));
}
@ -176,7 +176,7 @@ usage: serverless env set`,
if (_this.evt.stage != 'local' && _this.evt.region != 'all') {
// validate region: make sure region exists in stage
if (!_this.S._meta.project.stages[_this.evt.stage].regions[region]) {
if (!_this.S._meta.private.stages[_this.evt.stage].regions[region]) {
return BbPromise.reject(new SError('Region "' + _this.evt.region + '" does not exist in stage "' + _this.evt.stage + '"'));
}
}

View File

@ -165,7 +165,7 @@ usage: serverless env unset`,
if (_this.evt.stage != 'local' && _this.evt.region != 'all') {
// Validate region: make sure region exists in stage
if (!_this.S._meta.project.stages[_this.evt.stage].regions[_this.evt.region]) {
if (!_this.S._meta.private.stages[_this.evt.stage].regions[_this.evt.region]) {
return BbPromise.reject(new SError('Region "' + _this.evt.region + '" does not exist in stage "' + _this.evt.stage + '"'));
}
}

View File

@ -226,7 +226,7 @@ module.exports = function(SPlugin, serverlessPath) {
// If no region specified, deploy to all regions in stage
if (!evt.regions.length) {
evt.regions = Object.keys(this.S._meta.project.stages);
evt.regions = Object.keys(this.S._meta.private.stages);
}
return evt;

View File

@ -7,7 +7,7 @@
* - Generates scaffolding for the new project in CWD
* - Creates a new project S3 bucket and puts env and CF files
* - Creates CF stack by default, unless noExeCf option is set to true
* - Generates the final s-project.json file
* - Generates project JSON files
*
* Event Properties:
* - name (String) a name for new project
@ -20,14 +20,15 @@
*/
module.exports = function(SPlugin, serverlessPath) {
const path = require('path'),
SError = require( path.join( serverlessPath, 'ServerlessError' ) ),
SCli = require( path.join( serverlessPath, 'utils/cli' ) ),
SUtils = require( path.join( serverlessPath, 'utils' ) ),
os = require('os'),
fs = require('fs'),
BbPromise = require('bluebird'),
awsMisc = require( path.join( serverlessPath, 'utils/aws/Misc' ) );
const path = require('path'),
SError = require( path.join( serverlessPath, 'ServerlessError' ) ),
SCli = require( path.join( serverlessPath, 'utils/cli' ) ),
SUtils = require( path.join( serverlessPath, 'utils' ) ),
os = require('os'),
fs = require('fs'),
BbPromise = require('bluebird'),
awsMisc = require( path.join( serverlessPath, 'utils/aws/Misc' ) );
BbPromise.promisifyAll(fs);
@ -100,7 +101,7 @@ module.exports = function(SPlugin, serverlessPath) {
let _this = this;
if(evt) {
if (evt) {
_this.evt = evt;
_this.S._interactive = false;
}
@ -108,15 +109,12 @@ module.exports = function(SPlugin, serverlessPath) {
// If CLI, parse arguments
if (_this.S.cli) {
_this.evt = JSON.parse(JSON.stringify(this.S.cli.options)); // Important: Clone objects, don't refer to them
if (_this.S.cli.options.nonInteractive) {
_this.S._interactive = false;
}
if (_this.S.cli.options.nonInteractive) _this.S._interactive = false;
}
// Add default runtime
if (!_this.evt.runtime) {
_this.evt.runtime = 'nodejs';
}
if (!_this.evt.runtime) _this.evt.runtime = 'nodejs';
// Always create "development" stage on ProjectCreate
_this.evt.stage = 'development';
@ -345,7 +343,7 @@ module.exports = function(SPlugin, serverlessPath) {
}
// Set Serverless Regional Bucket
this.evt.regionBucket = SUtils.generateRegionBucketName(this.evt.region, this.evt.domain);
this.evt.projectBucket = SUtils.generateProjectBucketName(this.evt.region, this.evt.domain);
return BbPromise.resolve();
}
@ -376,11 +374,18 @@ module.exports = function(SPlugin, serverlessPath) {
)
.then(function() {
// Create Folders
fs.mkdirSync(path.join(_this._projectRootPath, 'back', 'modules'));
fs.mkdirSync(path.join(_this._projectRootPath, 'meta'));
fs.mkdirSync(path.join(_this._projectRootPath, 'meta', 'private'));
fs.mkdirSync(path.join(_this._projectRootPath, 'meta', 'public'));
fs.mkdirSync(path.join(_this._projectRootPath, 'meta', 'private', 'variables'));
fs.mkdirSync(path.join(_this._projectRootPath, 'meta', 'public', 'variables'));
fs.mkdirSync(path.join(_this._projectRootPath, 'meta', 'private', 'resources'));
fs.mkdirSync(path.join(_this._projectRootPath, 'plugins'));
fs.mkdirSync(path.join(_this._projectRootPath, 'plugins', 'custom'));
return BbPromise.all([
fs.mkdirAsync(path.join(_this._projectRootPath, 'back', 'modules')),
fs.mkdirAsync(path.join(_this._projectRootPath, 'plugins')).then(function(){
return fs.mkdirAsync(path.join(_this._projectRootPath, 'plugins', 'custom'));
}),
SUtils.writeFile(path.join(_this._projectRootPath, 'admin.env'), adminEnv),
SUtils.writeFile(path.join(_this._projectRootPath, 'README.md'), readme),
SUtils.generateResourcesCf(
@ -401,8 +406,8 @@ module.exports = function(SPlugin, serverlessPath) {
*/
_createProjectBucket() {
SCli.log('Creating a project region bucket on S3: ' + this.evt.regionBucket + '...');
return this.S3.sCreateBucket(this.evt.regionBucket);
SCli.log('Creating a project region bucket on S3: ' + this.evt.projectBucket + '...');
return this.S3.sCreateBucket(this.evt.projectBucket);
}
/**
@ -416,7 +421,7 @@ module.exports = function(SPlugin, serverlessPath) {
SERVERLESS_PROJECT_NAME=${this.evt.name}`;
return this.S3.sPutEnvFile(
this.evt.regionBucket,
this.evt.projectBucket,
this.evt.name,
this.evt.stage,
envFileContents);
@ -429,7 +434,7 @@ module.exports = function(SPlugin, serverlessPath) {
_putCfFile() {
return this.CF.sPutCfFile(
this._projectRootPath,
this.evt.regionBucket,
this.evt.projectBucket,
this.evt.name,
this.evt.stage,
'resources');
@ -466,8 +471,7 @@ module.exports = function(SPlugin, serverlessPath) {
_this.evt.stage,
_this.evt.domain,
_this.evt.notificationEmail,
cfTemplateURL
)
cfTemplateURL)
.then(cfData => {
return _this.CF.sMonitorCf(cfData, 'create')
.then(cfStackData => {
@ -496,21 +500,38 @@ module.exports = function(SPlugin, serverlessPath) {
_this.evt.stageCfStack = cfStackData.StackName;
}
let prjJson = SUtils.readAndParseJsonSync(path.join(_this._templatesDir, 's-project.json'));
prjJson.stages[_this.evt.stage] = [{
region: _this.evt.region,
iamRoleArnLambda: _this.evt.iamRoleLambdaArn || '',
regionBucket: _this.evt.regionBucket
}];
prjJson.name = _this.evt.name;
prjJson.domain = _this.evt.domain;
// Create s-project.json
let prjJson = SUtils.readAndParseJsonSync(path.join(_this._templatesDir, 's-project.json'));
prjJson.name = _this.evt.name;
prjJson.description = 'A brand new Serverless project';
fs.writeFileSync(path.join(_this._projectRootPath, 's-project.json'),
JSON.stringify(prjJson, null, 2));
return prjJson;
// Save Meta
_this._meta = {
private: {
stages: {},
variables: {
domain: _this.evt.domain,
projectBucket: _this.evt.projectBucket
}
},
public: {
stages: {},
variables: {}
},
};
_this._meta.private.stages[_this.evt.stage] = {
regions: {},
variables: {}
};
_this._meta.private.stages[_this.evt.stage].regions[_this.evt.region] = {
variables: {
stackName: _this.evt.stageCfStack,
iamRoleLambdaArn: _this.evt.iamRoleLambdaArn
}
};
SUtils.saveMeta(_this._projectRootPath, _this._meta);
}
}

View File

@ -153,7 +153,7 @@ usage: serverless region create`,
}
// validate stage: make sure stage exists
if (!_this.S._meta.project.stages[_this.evt.stage]) {
if (!_this.S._meta.private.stages[_this.evt.stage]) {
return BbPromise.reject(new SError('Stage ' + _this.evt.stage + ' does not exist in your project', SError.errorCodes.UNKNOWN));
}
@ -192,7 +192,7 @@ usage: serverless region create`,
*/
_createRegionBucket() {
this.evt.regionBucket = SUtils.generateRegionBucketName(this.evt.region, this.S._meta.variables.domain);
this.evt.regionBucket = SUtils.generateProjectBucketName(this.evt.region, this.S._meta.variables.domain);
SCli.log('Creating a region bucket on S3: ' + this.evt.regionBucket + '...');
return this.S3.sCreateBucket(this.evt.regionBucket);
}
@ -286,7 +286,7 @@ SERVERLESS_PROJECT_NAME=${this.S._project.name}`;
_this.evt.stageCfStack = cfStackData.StackName;
}
_this.S._meta.project.stages[_this.evt.stage].regions[_this.evt.region] = {
_this.S._meta.private.stages[_this.evt.stage].regions[_this.evt.region] = {
regionBucket: _this.evt.regionBucket,
stackName: cfStackData.StackName,
iamRoleArnLambda: _this.evt.iamRoleArnLambda
@ -302,7 +302,7 @@ SERVERLESS_PROJECT_NAME=${this.S._project.name}`;
return SUtils.writeFile(
path.join(_this.S._projectRootPath, 'meta', 'variables', variableFile),
JSON.stringify(_this.S._meta.project.stages[_this.evt.stage].regions[_this.evt.region], null, 2)
JSON.stringify(_this.S._meta.private.stages[_this.evt.stage].regions[_this.evt.region], null, 2)
);
}
}

View File

@ -145,7 +145,7 @@ usage: serverless resources deploy`,
}
// validate stage: make sure stage exists
if (!_this.S._meta.project.stages[_this.evt.stage] && _this.evt.stage != 'local') {
if (!_this.S._meta.private.stages[_this.evt.stage] && _this.evt.stage != 'local') {
return BbPromise.reject(new SError('Stage ' + _this.evt.stage + ' does not exist in your project', SError.errorCodes.UNKNOWN));
}

View File

@ -179,7 +179,7 @@ usage: serverless stage create`,
}
// validate stage: Ensure stage doesn't already exist
if (this.S._meta.project.stages[this.evt.stage]) {
if (this.S._meta.private.stages[this.evt.stage]) {
return BbPromise.reject(new SError('Stage ' + this.evt.stage + ' already exists', SError.errorCodes.UNKNOWN));
}
@ -231,7 +231,7 @@ usage: serverless stage create`,
// Check if project name is in AllowedValues
if (cfTemplate.Parameters.ProjectName.AllowedValues.indexOf(this.S._project.name) == -1) {
cfTemplate.Parameters.ProjectName.AllowedValues.push(this.S._meta.project.name);
cfTemplate.Parameters.ProjectName.AllowedValues.push(this.S._project.name);
}
// Write it
@ -247,7 +247,7 @@ usage: serverless stage create`,
*/
_createRegionBucket() {
this.evt.regionBucket = SUtils.generateRegionBucketName(this.evt.region, this.S._project.domain);
this.evt.regionBucket = SUtils.generateProjectBucketName(this.evt.region, this.S._project.domain);
SCli.log('Creating a region bucket on S3: ' + this.evt.regionBucket + '...');
return this.S3.sCreateBucket(this.evt.regionBucket);
}
@ -296,7 +296,7 @@ SERVERLESS_PROJECT_NAME=${this.S._project.name}`;
let stackName = _this.CF.sGetResourcesStackName(_this.evt.stage, _this.S._project.name);
SCli.log(`Remember to run CloudFormation manually to create stack with name: ${stackName}`);
SCli.log('After creating CF stack, remember to put the IAM role outputs and regionBucket in your project s-project.json in the correct stage/region.');
SCli.log('After creating CF stack, remember to put the IAM role outputs and regionBucket in your private s-project.json in the correct stage/region.');
return BbPromise.resolve();
}

View File

@ -37,4 +37,5 @@ node_modules
#SERVERLESS STUFF
admin.env
.env
.env
meta/private

View File

@ -1,29 +0,0 @@
{
"AWSTemplateFormatVersion": "2010-09-09",
"Description": "The AWS CloudFormation template for this Serverless application's Lambda functions",
"Parameters": {
"LambdaRoleArn": {
"Type": "String",
"Default": ""
}
},
"Resources": {
"lTemplate": {
"Type": "AWS::Lambda::Function",
"Properties": {
"Code": {
"S3Bucket": "",
"S3Key": ""
},
"Description": "",
"Handler": "",
"MemorySize": 1024,
"Role": {
"Ref": "LambdaRoleArn"
},
"Runtime": "",
"Timeout": 6
}
}
}
}

View File

@ -70,23 +70,6 @@
"Path": "/"
}
},
"IamInstanceProfileLambda": {
"Type": "AWS::IAM::InstanceProfile",
"Properties": {
"Path": "/",
"Roles": [
{
"Ref": "IamRoleLambda"
}
]
}
},
"IamGroupLambda": {
"Type": "AWS::IAM::Group",
"Properties": {
"Path": "/"
}
},
"IamPolicyLambda": {
"Type": "AWS::IAM::Policy",
"Properties": {
@ -153,4 +136,4 @@
}
}
}
}
}

View File

@ -1,12 +1,150 @@
{
"name": "",
"version": "0.0.1",
"profile": "serverless-0",
"profile": "serverless-0.1",
"location": "https://github.com/...",
"author": "",
"description": "",
"domain": "",
"stages": {},
"custom": {},
"plugins": []
"modules": {},
"plugins": [],
"cloudFormation" : {
"AWSTemplateFormatVersion": "2010-09-09",
"Description": "The AWS CloudFormation template for this Serverless application's resources outside of Lambdas and Api Gateway",
"Parameters": {
"ProjectName": {
"Type": "String",
"AllowedValues": []
},
"ProjectDomain": {
"Type": "String",
"Default": "myapp.com"
},
"Stage": {
"Type": "String",
"AllowedValues": [
]
},
"DataModelStage": {
"Type": "String",
"AllowedValues": [
]
},
"NotificationEmail": {
"Type": "String",
"Default": "you@you.com"
},
"DynamoRWThroughput": {
"Type": "String",
"Default": "1"
}
},
"Metadata": {
"AWS::CloudFormation::Interface": {
"ParameterGroups": [
{
"Label": {"default": "Project Settings"},
"Parameters": ["ProjectName", "ProjectDomain", "Stage", "DataModelStage"]
},
{
"Label": {"default": "Monitoring"},
"Parameters": ["NotificationEmail"]
},
{
"Label": {"default": "Database Settings (DynamoDB)"},
"Parameters": ["DynamoRWThroughput"]
}
]
}
},
"Resources": {
"IamRoleLambda": {
"Type": "AWS::IAM::Role",
"Properties": {
"AssumeRolePolicyDocument": {
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Service": [
"lambda.amazonaws.com"
]
},
"Action": [
"sts:AssumeRole"
]
}
]
},
"Path": "/"
}
},
"IamPolicyLambda": {
"Type": "AWS::IAM::Policy",
"Properties": {
"PolicyName": {
"Fn::Join": [
"_-_",
[
{
"Ref": "Stage"
},
{
"Ref": "ProjectName"
},
"lambda"
]
]
},
"PolicyDocument": {
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"logs:CreateLogGroup",
"logs:CreateLogStream",
"logs:PutLogEvents"
],
"Resource": {
"Fn::Join": [
":",
[
"arn:aws:logs",
{
"Ref": "AWS::Region"
},
"*:*"
]
]
}
}
]
},
"Roles": [
{
"Ref": "IamRoleLambda"
}
],
"Groups": [
{
"Ref": "IamGroupLambda"
}
]
}
}
},
"Outputs": {
"IamRoleArnLambda": {
"Description": "ARN of the lambda IAM role",
"Value": {
"Fn::GetAtt": [
"IamRoleLambda",
"Arn"
]
}
}
}
}
}

View File

@ -126,12 +126,12 @@ module.exports = function(config) {
let S3 = require('./S3')(config);
if (['lambdas', 'resources'].indexOf(type) == -1) {
BbPromise.reject(new SError(`Type ${type} invalid. Must be lambdas or resources`, SError.errorCodes.UNKNOWN));
if (['resources'].indexOf(type) == -1) {
BbPromise.reject(new SError(`Type ${type} invalid. Must be resources only`, SError.errorCodes.UNKNOWN));
}
let d = new Date(),
cfPath = path.join(projRootPath, 'cloudformation', type + '-cf.json'),
cfPath = path.join(projRootPath, 'meta', 'private', 'resources', 's-' + type + '-cf.json'),
key = ['Serverless', projName, projStage, 'cloudformation/' + type].join('/') + '@' + d.getTime() + '.json',
params = {
Bucket: bucketName,

View File

@ -83,7 +83,7 @@ exports.getProject = function(projectRootPath) {
try {
let module = _this.readAndParseJsonSync(path.join(projectRootPath, 'back', 'modules', moduleList[i], 's-module.json'));
project.modules[module.name] = module;
project.modules[module.name] = module;
project.modules[module.name].pathModule = path.join('back', 'modules', moduleList[i], 's-module.json');
project.modules[module.name].functions = {};
@ -111,76 +111,110 @@ exports.getProject = function(projectRootPath) {
};
/**
* GetProjectMeta
* Get Meta
* - Get Project Meta Information
*/
exports.getProjectMeta = function(projectRootPath) {
exports.getMeta = function(projectRootPath) {
let _this = this,
projectMeta = {
project: {
private: {
stages: {},
variables: {}
},
public: {
variables: {}
}
};
// Create "meta" folder if does not exist
if (!_this.dirExistsSync(path.join(projectRootPath, 'meta'))) {
fs.mkdirSync(path.join(projectRootPath, 'meta'));
}
// Re-usable function to traverse public or private variable folders
let _getVariables = function(type) {
// Create "meta/variables" folder if does not exist
if (!_this.dirExistsSync(path.join(projectRootPath, 'meta', 'variables'))) {
fs.mkdirSync(path.join(projectRootPath, 'meta', 'variables'));
}
let variableFiles = fs.readdirSync(path.join(projectRootPath, 'meta', type, 'variables'));
for (let i = 0; i < variableFiles.length; i++) {
// Get Variables
let variableFiles = fs.readdirSync(path.join(projectRootPath, 'meta', 'variables'));
for (let i = 0; i < variableFiles.length; i++) {
let variableFile = _this.readAndParseJsonSync(path.join(projectRootPath, 'meta', type, 'variables', variableFiles[i]));
let variableFile = _this.readAndParseJsonSync(path.join(projectRootPath, 'meta', 'variables', variableFiles[i]));
// Parse file name to get stage/region
let file = variableFiles[i].replace('s-variables-', '').replace('.json', '');
// Parse file name to get stage/region
let file = variableFiles[i].replace('s-variables-', '').replace('.json', '');
if (file === 'common') {
if (file === 'common') {
// Set Common variables
projectMeta[type].variables = _this.readAndParseJsonSync(path.join(projectRootPath, 'meta', type, 'variables', variableFiles[i]));
// Set Common variables
projectMeta.project.variables = _this.readAndParseJsonSync(path.join(projectRootPath, 'meta', 'variables', variableFiles[i]));
} else {
} else {
// Set Stage/Region variables
file = file.split('-');
if (!projectMeta.project.stages[file[0]]) projectMeta.project.stages[file[0]] = {
regions: {},
variables: {}
};
if (file.length === 1) {
// Set Stage Variables
projectMeta.project.stages[file[0]].variables = _this.readAndParseJsonSync(path.join(projectRootPath, 'meta', 'variables', variableFiles[i]));
} else if (file.length === 2) {
// Set Stage-Region Variables
let region;
if (file[1] === 'useast1') region = 'us-east-1';
if (file[1] === 'uswest2') region = 'us-west-2';
if (file[1] === 'euwest1') region = 'eu-west-1';
if (file[1] === 'apnortheast1') region = 'ap-northeast-1';
if (!projectMeta.project.stages[file[0]].regions[region]) projectMeta.project.stages[file[0]].regions[region] = {
variables: _this.readAndParseJsonSync(path.join(projectRootPath, 'meta', 'variables', variableFiles[i]))
// Set Stage/Region variables
file = file.split('-');
if (!projectMeta[type].stages) projectMeta[type].stages = {};
if (!projectMeta[type].stages[file[0]]) projectMeta[type].stages[file[0]] = {
regions: {},
variables: {}
};
projectMeta.project.stages[file[0]].regions[region].variables.region = region;
if (file.length === 1) {
// Set Stage Variables
projectMeta[type].stages[file[0]].variables = _this.readAndParseJsonSync(path.join(projectRootPath, 'meta', type, 'variables', variableFiles[i]));
} else if (file.length === 2) {
// Set Stage-Region Variables
let region;
if (file[1] === 'useast1') region = 'us-east-1';
if (file[1] === 'uswest2') region = 'us-west-2';
if (file[1] === 'euwest1') region = 'eu-west-1';
if (file[1] === 'apnortheast1') region = 'ap-northeast-1';
if (!projectMeta[type].stages[file[0]].regions[region]) projectMeta[type].stages[file[0]].regions[region] = {
variables: _this.readAndParseJsonSync(path.join(projectRootPath, 'meta', type, 'variables', variableFiles[i]))
};
}
}
}
}
};
if (_this.dirExistsSync(path.join(projectRootPath, 'meta', 'public'))) _getVariables('public');
if (_this.dirExistsSync(path.join(projectRootPath, 'meta', 'private'))) _getVariables('private');
return projectMeta;
};
/**
* Save Meta
*/
exports.saveMeta = function(projectRootPath, projectMeta) {
// Re-usable function to save public or private variables
let _saveVariables = function(type) {
// Save Common Variables
fs.writeFileSync(path.join(projectRootPath, 'meta', type, 'variables', 's-variables-common.json'),
JSON.stringify(projectMeta[type].variables, null, 2));
for (let i = 0; i < Object.keys(projectMeta[type].stages).length; i++) {
let stage = projectMeta[type].stages[Object.keys(projectMeta[type].stages)[i]];
// Save Stage Variables
fs.writeFileSync(path.join(projectRootPath, 'meta', type, 'variables', 's-variables-' + Object.keys(projectMeta[type].stages)[i] + '.json'),
JSON.stringify(stage.variables, null, 2));
// Save Stage Region Variables
for (let j = 0; j < Object.keys(stage.regions).length; j++) {
fs.writeFileSync(path.join(projectRootPath, 'meta', type, 'variables', 's-variables-' + Object.keys(projectMeta[type].stages)[i] + '-' + Object.keys(stage.regions)[j].replace(/-/g, '') + '.json'),
JSON.stringify(stage.regions[Object.keys(stage.regions)[j]].variables, null, 2));
}
}
};
if (this.dirExistsSync(path.join(projectRootPath, 'meta', 'public'))) _saveVariables('public');
if (this.dirExistsSync(path.join(projectRootPath, 'meta', 'private'))) _saveVariables('private');
};
/**
* Execute (Command)
*/
@ -615,7 +649,7 @@ exports.generateShortId = function(maxLen) {
* Generate JawsBucket Name
*/
exports.generateRegionBucketName = function(region, projectDomain) {
exports.generateProjectBucketName = function(region, projectDomain) {
// Sanitize
region = region.trim().replace(/-/g, '').toLowerCase();
@ -685,6 +719,7 @@ exports.npmInstall = function(dir) {
*/
exports.generateResourcesCf = function(projRootPath, projName, projDomain, stage, region, notificationEmail) {
let cfTemplate = require('../templates/resources-cf');
cfTemplate.Parameters.ProjectName.Default = projName;
@ -694,13 +729,12 @@ exports.generateResourcesCf = function(projRootPath, projName, projDomain, stage
cfTemplate.Parameters.Stage.AllowedValues = [stage];
cfTemplate.Parameters.DataModelStage.AllowedValues = [stage];
cfTemplate.Parameters.NotificationEmail.Default = notificationEmail;
cfTemplate.Parameters.NotificationEmail.Default = notificationEmail;
cfTemplate.Description = projName + ' resources';
return this.writeFile(
path.join(projRootPath, 'cloudformation', 'resources-cf.json'),
JSON.stringify(cfTemplate, null, 2)
);
path.join(projRootPath, 'meta', 'private', 'resources', 's-resources-cf.json'),
JSON.stringify(cfTemplate, null, 2));
};
/**

View File

@ -1,5 +1,5 @@
{
"project": {
"private": {
"stages": {
"development": {
"regions": {
@ -11,5 +11,8 @@
}
},
"variables": {}
},
"public": {
"variables": {}
}
}

View File

@ -11,9 +11,9 @@ let fs = require('fs'),
SUtils = require('../lib/utils');
/**
* Create test project
* Create test private
* @param config see tests/config.js
* @param npmInstallDirs list of dirs relative to project root to execute npm install on
* @param npmInstallDirs list of dirs relative to private root to execute npm install on
* @returns {Promise} full path to proj temp dir that was just created
*/
@ -27,14 +27,14 @@ module.exports.createTestProject = function(config, npmInstallDirs) {
// Create Test Project
let tmpProjectPath = path.join(os.tmpdir(), projectName);
SUtils.sDebug('test_utils', 'Creating test project in ' + tmpProjectPath + '\n');
SUtils.sDebug('test_utils', 'Creating test private in ' + tmpProjectPath + '\n');
// Delete test folder if already exists
if (fs.existsSync(tmpProjectPath)) {
rimraf.sync(tmpProjectPath);
}
// Copy test project to temp directory
// Copy test private to temp directory
fs.mkdirSync(tmpProjectPath);
wrench.copyDirSyncRecursive(path.join(__dirname, './test-prj'), tmpProjectPath, {
forceDelete: true,
@ -42,12 +42,12 @@ module.exports.createTestProject = function(config, npmInstallDirs) {
let lambdasCF = SUtils.readAndParseJsonSync(__dirname + '/../lib/templates/lambdas-cf.json'),
resourcesCF = SUtils.readAndParseJsonSync(__dirname + '/../lib/templates/resources-cf.json'),
projectJSON = SUtils.readAndParseJsonSync(path.join(tmpProjectPath, 's-project.json'));
projectJSON = SUtils.readAndParseJsonSync(path.join(tmpProjectPath, 's-private.json'));
// Delete Lambda Template
delete lambdasCF.Resources.lTemplate;
// Add project name to AllowedValues
// Add private name to AllowedValues
resourcesCF.Parameters.ProjectName.AllowedValues.push(projectName);
// Add stages to AllowedValues
@ -69,10 +69,10 @@ module.exports.createTestProject = function(config, npmInstallDirs) {
projectJSON.stages[projectStage] = [{
region: projectRegion,
iamRoleArnLambda: projectLambdaIAMRole,
regionBucket: SUtils.generateRegionBucketName(projectRegion, projectDomain)
regionBucket: SUtils.generateProjectBucketName(projectRegion, projectDomain)
},];
fs.writeFileSync(path.join(tmpProjectPath, 's-project.json'), JSON.stringify(projectJSON, null, 2));
fs.writeFileSync(path.join(tmpProjectPath, 's-private.json'), JSON.stringify(projectJSON, null, 2));
// Write Admin.env file
let adminEnv = 'SERVERLESS_ADMIN_AWS_ACCESS_KEY_ID='
@ -81,7 +81,7 @@ module.exports.createTestProject = function(config, npmInstallDirs) {
+ process.env.TEST_SERVERLESS_AWS_SECRET_KEY + os.EOL;
fs.writeFileSync(path.join(tmpProjectPath, 'admin.env'), adminEnv);
//Need to run npm install on the test project, they recommend NOT doing this programatically
//Need to run npm install on the test private, they recommend NOT doing this programatically
//https://github.com/npm/npm#using-npm-programmatically
if (npmInstallDirs) {

View File

@ -2,7 +2,7 @@
/**
* Test: Function Create Action
* - Creates a new project in your system's temp directory
* - Creates a new private in your system's temp directory
* - Creates a new Function inside the "users" module
*/

View File

@ -2,8 +2,8 @@
/**
* Test: Module Create Action
* - Creates a new project in your system's temp directory
* - Creates a new Module inside test project
* - Creates a new private in your system's temp directory
* - Creates a new Module inside test private
*/
let Serverless = require('../../../lib/Serverless.js'),

View File

@ -2,7 +2,7 @@
/**
* Test: Module Install Action
* - Creates a new project in your system's temp directory
* - Creates a new private in your system's temp directory
* - Installs module-test Module from github using the ModuleInstall action
* - asserts that the Module was installed correctly
*/

View File

@ -2,8 +2,8 @@
/**
* Test: Project Create Action
* - Creates a new project in your system's temp directory
* - Deletes the CF stack created by the project
* - Creates a new private in your system's temp directory
* - Deletes the CF stack created by the private
*/
let Serverless = require('../../../lib/Serverless'),
@ -118,7 +118,7 @@ describe('Test action: Project Create', function() {
});
describe('Project Create', function() {
it('should create a new project in temp directory', function(done) {
it('should create a new private in temp directory', function(done) {
this.timeout(0);
@ -139,7 +139,7 @@ describe('Test action: Project Create', function() {
validateEvent(evt);
// Validate Project JSON
let projectJson = utils.readAndParseJsonSync(path.join(os.tmpdir(), name, 's-project.json'));
let projectJson = utils.readAndParseJsonSync(path.join(os.tmpdir(), name, 's-private.json'));
let region = false;
for (let i = 0; i < projectJson.stages.development.length; i++) {

View File

@ -2,8 +2,8 @@
/**
* Test: Resources Deploy Action
* - Creates a new project in your system's temp directory
* - Makes a tiny update to the project's CF template
* - Creates a new private in your system's temp directory
* - Makes a tiny update to the private's CF template
* - Deploy new CF template
* - Deploy/Rollback the original CF template for cleaning
*/