Skip to content

Commit

Permalink
Merge e6a7f38 into c129c03
Browse files Browse the repository at this point in the history
  • Loading branch information
HyperBrain committed May 11, 2017
2 parents c129c03 + e6a7f38 commit 539ef32
Show file tree
Hide file tree
Showing 5 changed files with 108 additions and 95 deletions.
5 changes: 5 additions & 0 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ const BbPromise = require('bluebird')
, stackInformation = require('./lib/stackInformation')
, listAliases = require('./lib/listAliases')
, removeAlias = require('./lib/removeAlias')
, collectUserResources = require('./lib/collectUserResources')
, uploadAliasArtifacts = require('./lib/uploadAliasArtifacts');

class AwsAlias {
Expand Down Expand Up @@ -51,6 +52,7 @@ class AwsAlias {
_.assign(
this,
validate,
collectUserResources,
configureAliasStack,
createAliasStack,
updateAliasStack,
Expand Down Expand Up @@ -92,6 +94,9 @@ class AwsAlias {
'before:package:initialize': () => BbPromise.bind(this)
.then(this.validate),

'before:aws:package:finalize:mergeCustomProviderResources': () => BbPromise.bind(this)
.then(this.collectUserResources),

'before:deploy:deploy': () => BbPromise.bind(this)
.then(this.validate)
.then(this.configureAliasStack),
Expand Down
44 changes: 37 additions & 7 deletions lib/aliasRestructureStack.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,19 +22,49 @@ const cwEvents = require('./stackops/cwEvents');

module.exports = {

aliasInit: init,
aliasHandleFunctions: functions,
aliasHandleApiGateway: apiGateway,
aliasHandleUserResources: userResources,
aliasHandleLambdaRole: lambdaRole,
aliasHandleEvents: events,
aliasHandleCWEvents: cwEvents,
aliasInit(currentTemplate, aliasStackTemplates, currentAliasStackTemplate) {
return init.call(this, currentTemplate, aliasStackTemplates, currentAliasStackTemplate);
},

aliasHandleFunctions(currentTemplate, aliasStackTemplates, currentAliasStackTemplate) {
return functions.call(this, currentTemplate, aliasStackTemplates, currentAliasStackTemplate);
},

aliasHandleApiGateway(currentTemplate, aliasStackTemplates, currentAliasStackTemplate) {
return apiGateway.call(this, currentTemplate, aliasStackTemplates, currentAliasStackTemplate);
},

aliasHandleUserResources(currentTemplate, aliasStackTemplates, currentAliasStackTemplate) {
return userResources.call(this, currentTemplate, aliasStackTemplates, currentAliasStackTemplate);
},

aliasHandleLambdaRole(currentTemplate, aliasStackTemplates, currentAliasStackTemplate) {
return lambdaRole.call(this, currentTemplate, aliasStackTemplates, currentAliasStackTemplate);
},

aliasHandleEvents(currentTemplate, aliasStackTemplates, currentAliasStackTemplate) {
return events.call(this, currentTemplate, aliasStackTemplates, currentAliasStackTemplate);
},

aliasHandleCWEvents(currentTemplate, aliasStackTemplates, currentAliasStackTemplate) {
return cwEvents.call(this, currentTemplate, aliasStackTemplates, currentAliasStackTemplate);
},

aliasFinalize(currentTemplate, aliasStackTemplates, currentAliasStackTemplate) {
const stageStack = this._serverless.service.provider.compiledCloudFormationTemplate;
const aliasStack = this._serverless.service.provider.compiledCloudFormationAliasTemplate;

aliasStack.Outputs.AliasFlags.Value = JSON.stringify(aliasStack.Outputs.AliasFlags.Value);

// Check for missing dependencies and integrate them too
_.forEach(_.filter(stageStack.Resources, resource => !_.isEmpty(resource.DependsOn)), parent => {
_.forEach(parent.DependsOn, child => {
if (!_.has(stageStack.Resources, child) && _.has(currentTemplate.Resources, child)) {
stageStack.Resources[child] = currentTemplate.Resources[child];
}
});
});

return BbPromise.resolve([ currentTemplate, aliasStackTemplates, currentAliasStackTemplate ]);
},

Expand Down
18 changes: 18 additions & 0 deletions lib/collectUserResources.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
'use strict';
/**
* Persist the user resources.
* This is now necessary as the package command merges them already.
*/

const BbPromise = require('bluebird');
const _ = require('lodash');

module.exports = {
collectUserResources() {
this._serverless.service.provider.aliasUserResources =
_.cloneDeep(
_.get(this._serverless.service, 'resources', { Resources: {}, Outputs: {} }));

return BbPromise.resolve();
}
};
109 changes: 33 additions & 76 deletions lib/stackops/lambdaRole.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,94 +13,51 @@ module.exports = function(currentTemplate, aliasStackTemplates, currentAliasStac

const stageStack = this._serverless.service.provider.compiledCloudFormationTemplate;
const aliasStack = this._serverless.service.provider.compiledCloudFormationAliasTemplate;
let stageRolePolicies = _.get(stageStack, 'Resources.IamRoleLambdaExecution.Properties.Policies', []);
let currentRolePolicies = _.get(currentTemplate, 'Resources.IamRoleLambdaExecution.Properties.Policies', []);

// Older serverless versions (<1.7.0) do not use a inline policy.
if (_.isEmpty(currentRolePolicies) && _.has(currentTemplate, 'Resources.IamPolicyLambdaExecution')) {
this._serverless.cli.log('WARNING: Project created with SLS < 1.7.0. Using resources from policy.');
currentRolePolicies = [ _.get(currentTemplate, 'Resources.IamPolicyLambdaExecution.Properties') ];
}
if (_.isEmpty(stageRolePolicies) && _.has(stageStack, 'Resources.IamPolicyLambdaExecution')) {
stageRolePolicies = [ _.get(stageStack, 'Resources.IamPolicyLambdaExecution.Properties') ];
}

// There can be a service role defined. In this case there is no embedded IAM role.
if (_.has(this._serverless.service.provider, 'role')) {
// Use the role if any of the aliases reference it
if (!_.isEmpty(currentRolePolicies) &&
_.some(aliasStackTemplates, template => !template.Outputs.AliasFlags.Value.hasRole)) {
stageStack.Resources.IamRoleLambdaExecution = _.cloneDeep(currentTemplate.Resources.IamRoleLambdaExecution);
}

aliasStack.Outputs.AliasFlags.Value.hasRole = true;

return BbPromise.resolve([ currentTemplate, aliasStackTemplates, currentAliasStackTemplate ]);
}

// For now we only merge the first policy document and exit if SLS changes this behavior.
if (stageRolePolicies.length !== 1) {
return BbPromise.reject(new Error('Policy count should be 1! Please report this error to the alias plugin owner.'));
}
// Import all defined roles from the current template (without overwriting)
const currentRoles = _.assign({}, _.pickBy(currentTemplate.Resources, (resource, name) => resource.Type === 'AWS::IAM::Role' && /^IamRoleLambdaExecution/.test(name)));
_.defaults(stageStack.Resources, currentRoles);

const stageRolePolicyStatements = _.get(stageRolePolicies[0], 'PolicyDocument.Statement', []);
const currentRolePolicyStatements = _.get(currentRolePolicies[0], 'PolicyDocument.Statement', []);
// Remove old role for this alias
delete stageStack.Resources[`IamRoleLambdaExecution${this._alias}`];

_.forEach(currentRolePolicyStatements, statement => {
// Check if there is already a statement with the same actions and effect.
const sameStageStatement = _.find(stageRolePolicyStatements, value => value.Effect === statement.Effect &&
value.Action.length === statement.Action.length &&
_.every(value.Action, action => _.includes(statement.Action, action)));

if (sameStageStatement) {
// Merge the resources
sameStageStatement.Resource = _.uniqWith(_.concat(sameStageStatement.Resource, statement.Resource), (a,b) => _.isEqual(a,b));
} else {
// Add the different statement
stageRolePolicyStatements.push(statement);
}
});
return BbPromise.resolve([ currentTemplate, aliasStackTemplates, currentAliasStackTemplate ]);
}

// Remove all resource references of removed resources
const voidResourceRefs = utils.findReferences(stageRolePolicyStatements, this.removedResourceKeys);
const voidResourcePtrs = _.compact(_.map(voidResourceRefs, ref => {
const ptrs = /\[([0-9]+)\].Resource\[([0-9]+)\].*/.exec(ref);
if (ptrs && ptrs.length === 3) {
return { s: ptrs[1], r: ptrs[2] };
}
return null;
}));
_.forEach(voidResourcePtrs, ptr => {
const statement = stageRolePolicyStatements[ptr.s];
_.pullAt(statement.Resource, [ ptr.r ]);
if (_.isEmpty(statement.Resource)) {
_.pullAt(stageRolePolicyStatements, [ ptr.s ]);
}
const roleName = `IamRoleLambdaExecution${this._alias}`;
const role = stageStack.Resources.IamRoleLambdaExecution;

// Set role name
_.last(role.Properties.RoleName['Fn::Join']).push(this._alias);

stageStack.Resources[roleName] = stageStack.Resources.IamRoleLambdaExecution;
delete stageStack.Resources.IamRoleLambdaExecution;

// Replace references
const functions = _.filter(stageStack.Resources, ['Type', 'AWS::Lambda::Function']);
_.forEach(functions, func => {
func.Properties.Role = {
'Fn::GetAtt': [
roleName,
'Arn'
]
};
const dependencyIndex = _.indexOf(func.DependsOn, 'IamRoleLambdaExecution');
func.DependsOn[dependencyIndex] = roleName;
});

// Insert statement dependencies
const dependencies = _.reject((() => {
const result = [];
const stack = [ _.first(stageRolePolicyStatements) ];
while (!_.isEmpty(stack)) {
const statement = stack.pop();

_.forOwn(statement, (value, key) => {
if (key === 'Ref') {
result.push(value);
} else if (key === 'Fn::GetAtt') {
result.push(value[0]);
} else if (_.isObject(value)) {
stack.push(value);
}
});
}
return result;
})(), dependency => _.has(stageStack.Resources, dependency));
if (_.isEmpty(utils.findReferences(currentTemplate.Resources, 'IamRoleLambdaExecution')) && _.has(currentTemplate, 'Resources.IamRoleLambdaExecution')) {
delete currentTemplate.Resources.IamRoleLambdaExecution;
}

_.forEach(dependencies, dependency => {
stageStack.Resources[dependency] = currentTemplate.Resources[dependency];
});
// Import all defined roles from the current template (without overwriting)
const currentRoles = _.assign({}, _.pickBy(currentTemplate.Resources, (resource, name) => resource.Type === 'AWS::IAM::Role' && /^IamRoleLambdaExecution/.test(name)));
_.defaults(stageStack.Resources, currentRoles);

return BbPromise.resolve([ currentTemplate, aliasStackTemplates, currentAliasStackTemplate ]);
};
27 changes: 15 additions & 12 deletions lib/stackops/userResources.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ module.exports = function(currentTemplate, aliasStackTemplates, currentAliasStac

const stageStack = this._serverless.service.provider.compiledCloudFormationTemplate;
const aliasStack = this._serverless.service.provider.compiledCloudFormationAliasTemplate;
const userResources = _.get(this._serverless.service, 'resources', { Resources: {}, Outputs: {} });
const userResources = _.get(this._serverless.service, 'provider.aliasUserResources', { Resources: {}, Outputs: {} });

this.options.verbose && this._serverless.cli.log('Processing custom resources');

Expand All @@ -25,9 +25,6 @@ module.exports = function(currentTemplate, aliasStackTemplates, currentAliasStac
const resources = _.assign({}, _.pick(_.get(currentTemplate, 'Resources'), resourceRefs, {}));
const outputs = _.assign({}, _.pick(_.get(currentTemplate, 'Outputs'), outputRefs, {}));

// Check if there are IAM policy references for the alias resources and integrate them into
// the lambda policy.

_.assign(result.Resources, resources);
_.assign(result.Outputs, outputs);
return result;
Expand All @@ -43,20 +40,26 @@ module.exports = function(currentTemplate, aliasStackTemplates, currentAliasStac
// All used resources are copied from the current template

// Extract the user resources that are not overrides of existing Serverless resources
const stageUserResources = _.get(userResources, 'Resources', {});
const currentResources = _.get(userResources, 'Resources', {});
const currentOutputs = _.get(userResources, 'Outputs', {});

// Store a list of all removed resources
const allUsedResources = _.merge({}, aliasDependencies.Resources, stageUserResources, stageStack.Resources);
const deployedResourceKeys = _.keys(currentTemplate.Resources);
const allUsedResourceKeys = _.keys(allUsedResources);
this.removedResourceKeys = _.filter(deployedResourceKeys, key => !_.includes(allUsedResourceKeys, key));
const aliasResourceRefs = _.keys(aliasDependencies.Resources);
//const aliasOutputRefs = _.keys(aliasDependencies.Outputs);
const currentResourceRefs = _.keys(currentResources);
//const currentOutputRefs = _.keys(currentOutputs);
const oldResourceRefs = JSON.parse(_.get(currentAliasStackTemplate, 'Outputs.AliasResources.Value', "[]"));
//const oldOutputRefs = JSON.parse(_.get(currentAliasStackTemplate, 'Outputs.AliasOutputs.Value', "[]"));

// Removed resources are resources that are no longer referenced

const removedResourceRefs = _.difference(_.difference(oldResourceRefs, currentResourceRefs), aliasResourceRefs);
this.removedResourceKeys = removedResourceRefs;
this._options.verbose && this._serverless.cli.log(`Removing resources: ${this.removedResourceKeys}`);

// Add the alias resources as output to the alias stack
aliasStack.Outputs.AliasResources = {
Description: 'Custom resource references',
Value: JSON.stringify(_.keys(stageUserResources))
Value: JSON.stringify(_.keys(currentResources))
};

// Add the outputs as output to the alias stack
Expand All @@ -71,7 +74,7 @@ module.exports = function(currentTemplate, aliasStackTemplates, currentAliasStac
// immutable otherwise.

// Check if the resource is already used anywhere else with a different definition
_.forOwn(stageUserResources, (resource, name) => {
_.forOwn(currentResources, (resource, name) => {
if (_.has(aliasDependencies.Resources, name) && !_.isMatch(aliasDependencies.Resources[name], resource)) {

// If we deploy the master alias, allow reconfiguration of resources
Expand Down

0 comments on commit 539ef32

Please sign in to comment.