Skip to content

Commit

Permalink
test: validateStatements + getFunctionRoleName test cases added
Browse files Browse the repository at this point in the history
  • Loading branch information
Sergii Kovalev committed Nov 4, 2020
1 parent c1fcb0f commit 4c32855
Show file tree
Hide file tree
Showing 3 changed files with 121 additions and 81 deletions.
10 changes: 9 additions & 1 deletion .eslintrc.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
// @see https://eslint.org/docs/user-guide/configuring#configuring-rules

const OFF = 0;
const WARN = 1;
const ERROR = 2;
Expand All @@ -25,6 +26,14 @@ module.exports = {
rules: {
'@typescript-eslint/no-explicit-any': OFF,
'@typescript-eslint/explicit-module-boundary-types': OFF,
'@typescript-eslint/no-unused-vars': [ERROR, { argsIgnorePattern: '^_' }],
// eslint-disable-next-line max-len
// https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-unused-vars.md
'no-unused-vars': OFF,
'camelcase': ERROR,
'space-infix-ops': ERROR,
'keyword-spacing': ERROR,
'spaced-comment': ERROR,
'arrow-body-style': [ERROR, 'as-needed'],
'comma-dangle': [ERROR, 'always-multiline'],
'import/imports-first': ERROR,
Expand All @@ -36,7 +45,6 @@ module.exports = {
'max-len': [ERROR, 120],
'newline-per-chained-call': ERROR,
'no-confusing-arrow': ERROR,
'no-unused-vars': [ERROR, { argsIgnorePattern: '^_' }],
'no-use-before-define': ERROR,
'require-yield': ERROR,
'function-call-argument-newline': [ERROR, 'consistent'],
Expand Down
72 changes: 36 additions & 36 deletions src/lib/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ class ServerlessIamPerFunctionPlugin {
* @param {Serverless} serverless - serverless host object
* @param {Object} _options
*/
constructor(serverless: any) {
constructor(serverless: any, _options?: any) {
this.serverless = serverless;

if (this.serverless.service.provider.name !== 'aws') {
Expand Down Expand Up @@ -66,18 +66,18 @@ class ServerlessIamPerFunctionPlugin {
}

/**
* Utility function which throws an error. The msg will be formated with args using util.format.
* Utility function which throws an error. The msg will be formatted with args using util.format.
* Error message will be prefixed with ${PLUGIN_NAME}: ERROR:
* @param {string} msg
* @param {*[]} args
* @returns void
*/
throwError(msg: string, ...args: any[]) {
if(!_.isEmpty(args)) {
if (!_.isEmpty(args)) {
msg = util.format(msg, args);
}
const err_msg = `${PLUGIN_NAME}: ERROR: ${msg}`;
throw new this.serverless.classes.Error(err_msg);
const errMsg = `${PLUGIN_NAME}: ERROR: ${msg}`;
throw new this.serverless.classes.Error(errMsg);
}

/**
Expand All @@ -87,7 +87,7 @@ class ServerlessIamPerFunctionPlugin {
validateStatements(statements: any): void {
// Verify that iamRoleStatements (if present) is an array of { Effect: ...,
// Action: ..., Resource: ... } objects.
if(_.isEmpty(statements)) {
if (_.isEmpty(statements)) {
return;
}
let violationsFound;
Expand Down Expand Up @@ -121,12 +121,12 @@ class ServerlessIamPerFunctionPlugin {
}

/**
* @param {*[]} name_parts
* @param {*[]} nameParts
* @returns void
*/
getRoleNameLength(name_parts: any[]) {
let length=0; //calculate the expected length. Sum the length of each part
for (const part of name_parts) {
getRoleNameLength(nameParts: any[]) {
let length = 0; // calculate the expected length. Sum the length of each part
for (const part of nameParts) {
if (part.Ref) {
if (part.Ref === 'AWS::Region') {
length += this.serverless.service.provider.region.length;
Expand All @@ -137,7 +137,7 @@ class ServerlessIamPerFunctionPlugin {
length += part.length;
}
}
length += (name_parts.length - 1); //take into account the dashes between parts
length += (nameParts.length - 1); // take into account the dashes between parts
return length;
}

Expand All @@ -148,15 +148,15 @@ class ServerlessIamPerFunctionPlugin {
getFunctionRoleName(functionName: string) {
const roleName = this.serverless.providers.aws.naming.getRoleName();
const fnJoin = roleName['Fn::Join'];
if(!_.isArray(fnJoin) || fnJoin.length !== 2 || !_.isArray(fnJoin[1]) || fnJoin[1].length < 2) {
this.throwError('Global Role Name is not in exepcted format. Got name: ' + JSON.stringify(roleName));
if (!_.isArray(fnJoin) || fnJoin.length !== 2 || !_.isArray(fnJoin[1]) || fnJoin[1].length < 2) {
this.throwError('Global Role Name is not in expected format. Got name: ' + JSON.stringify(roleName));
}
fnJoin[1].splice(2, 0, functionName); //insert the function name
if(this.getRoleNameLength(fnJoin[1]) > 64 && fnJoin[1][fnJoin[1].length-1] === 'lambdaRole') {
fnJoin[1].splice(2, 0, functionName); // insert the function name
if (this.getRoleNameLength(fnJoin[1]) > 64 && fnJoin[1][fnJoin[1].length - 1] === 'lambdaRole') {
// Remove lambdaRole from name to give more space for function name.
fnJoin[1].pop();
}
if(this.getRoleNameLength(fnJoin[1]) > 64) { //aws limits to 64 chars the role name
if (this.getRoleNameLength(fnJoin[1]) > 64) { // aws limits to 64 chars the role name
this.throwError(`auto generated role name for function: ${functionName} is too long (over 64 chars).
Try setting a custom role name using the property: iamRoleStatementsName.`);
}
Expand All @@ -174,7 +174,7 @@ class ServerlessIamPerFunctionPlugin {
const functionResource = this.serverless.service.provider
.compiledCloudFormationTemplate.Resources[functionResourceName];

if(_.isEmpty(functionResource)
if (_.isEmpty(functionResource)
|| _.isEmpty(functionResource.Properties)
|| _.isEmpty(functionResource.Properties.Role)
|| !_.isArray(functionResource.Properties.Role['Fn::GetAtt'])
Expand Down Expand Up @@ -205,7 +205,7 @@ class ServerlessIamPerFunctionPlugin {
Resource: [],
};
for (const event of functionObject.events) {
if(event.sqs) {
if (event.sqs) {
const sqsArn = event.sqs.arn || event.sqs;
(sqsStatement.Resource as any[]).push(sqsArn);
}
Expand All @@ -220,7 +220,7 @@ class ServerlessIamPerFunctionPlugin {
*/
getStreamStatements(functionObject: any) {
const res: any[] = [];
if(_.isEmpty(functionObject.events)) { //no events
if (_.isEmpty(functionObject.events)) { // no events
return res;
}
const dynamodbStreamStatement: Statement = {
Expand All @@ -244,7 +244,7 @@ class ServerlessIamPerFunctionPlugin {
Resource: [],
};
for (const event of functionObject.events) {
if(event.stream) {
if (event.stream) {
const streamArn = event.stream.arn || event.stream;
const streamType = event.stream.type || streamArn.split(':')[2];
switch (streamType) {
Expand Down Expand Up @@ -277,24 +277,24 @@ class ServerlessIamPerFunctionPlugin {
*/
createRoleForFunction(functionName: string, functionToRoleMap: Map<string, string>) {
const functionObject = this.serverless.service.getFunction(functionName);
if(functionObject.iamRoleStatements === undefined) {
if (functionObject.iamRoleStatements === undefined) {
return;
}
if(functionObject.role) {
if (functionObject.role) {
this.throwError(
'Define function with both \'role\' and \'iamRoleStatements\' is not supported. Function name: '
+ functionName,
);
}
this.validateStatements(functionObject.iamRoleStatements);
//we use the configured role as a template
// we use the configured role as a template
const globalRoleName = this.serverless.providers.aws.naming.getRoleLogicalId();
const globalIamRole = this.serverless.service.provider.compiledCloudFormationTemplate.Resources[globalRoleName];
const functionIamRole = _.cloneDeep(globalIamRole);
//remove the statements
// remove the statements
const policyStatements: Statement[] = [];
functionIamRole.Properties.Policies[0].PolicyDocument.Statement = policyStatements;
//set log statements
// set log statements
policyStatements[0] = {
Effect: 'Allow',
Action: ['logs:CreateLogStream', 'logs:PutLogEvents'],
Expand All @@ -307,7 +307,7 @@ class ServerlessIamPerFunctionPlugin {
};
// remove managed policies
functionIamRole.Properties.ManagedPolicyArns = [];
//set vpc if needed
// set vpc if needed
if (!_.isEmpty(functionObject.vpc) || !_.isEmpty(this.serverless.service.provider.vpc)) {
functionIamRole.Properties.ManagedPolicyArns = [{
'Fn::Join': ['',
Expand All @@ -319,16 +319,16 @@ class ServerlessIamPerFunctionPlugin {
],
}];
}
for (const s of this.getStreamStatements(functionObject)) { //set stream statements (if needed)
for (const s of this.getStreamStatements(functionObject)) { // set stream statements (if needed)
policyStatements.push(s);
}
const sqsStatement = this.getSqsStatement(functionObject); //set sqs statement (if needed)
const sqsStatement = this.getSqsStatement(functionObject); // set sqs statement (if needed)
if (sqsStatement) {
policyStatements.push(sqsStatement);
}
// set sns publish for DLQ if needed
// currently only sns is supported: https://serverless.com/framework/docs/providers/aws/events/sns#dlq-with-sqs
if (!_.isEmpty(functionObject.onError)) { //
if (!_.isEmpty(functionObject.onError)) {
policyStatements.push({
Effect: 'Allow',
Action: [
Expand All @@ -341,13 +341,13 @@ class ServerlessIamPerFunctionPlugin {
const isInherit = functionObject.iamRoleStatementsInherit
|| (this.defaultInherit && functionObject.iamRoleStatementsInherit !== false);

if(isInherit && !_.isEmpty(this.serverless.service.provider.iamRoleStatements)) { //add global statements
if (isInherit && !_.isEmpty(this.serverless.service.provider.iamRoleStatements)) { // add global statements
for (const s of this.serverless.service.provider.iamRoleStatements) {
policyStatements.push(s);
}
}
//add iamRoleStatements
if(_.isArray(functionObject.iamRoleStatements)) {
// add iamRoleStatements
if (_.isArray(functionObject.iamRoleStatements)) {
for (const s of functionObject.iamRoleStatements) {
policyStatements.push(s);
}
Expand All @@ -369,14 +369,14 @@ class ServerlessIamPerFunctionPlugin {
*/
setEventSourceMappings(functionToRoleMap: Map<string, string>) {
for (const mapping of _.values(this.serverless.service.provider.compiledCloudFormationTemplate.Resources)) {
if(mapping.Type && mapping.Type === 'AWS::Lambda::EventSourceMapping') {
if (mapping.Type && mapping.Type === 'AWS::Lambda::EventSourceMapping') {
const functionNameFn = _.get(mapping, 'Properties.FunctionName.Fn::GetAtt');
if(!_.isArray(functionNameFn)) {
if (!_.isArray(functionNameFn)) {
continue;
}
const functionName = functionNameFn[0];
const roleName = functionToRoleMap.get(functionName);
if(roleName) {
if (roleName) {
mapping.DependsOn = roleName;
}
}
Expand All @@ -388,7 +388,7 @@ class ServerlessIamPerFunctionPlugin {
*/
createRolesPerFunction() {
const allFunctions = this.serverless.service.getAllFunctions();
if(_.isEmpty(allFunctions)) {
if (_.isEmpty(allFunctions)) {
return;
}
const functionToRoleMap: Map<string, string> = new Map();
Expand Down
Loading

0 comments on commit 4c32855

Please sign in to comment.