diff --git a/lib/plugins/aws/deploy/lib/configureStack.js b/lib/plugins/aws/deploy/lib/configureStack.js index f4e2b8aaa..48d024c0f 100644 --- a/lib/plugins/aws/deploy/lib/configureStack.js +++ b/lib/plugins/aws/deploy/lib/configureStack.js @@ -15,99 +15,6 @@ module.exports = { 'core-cloudformation-template.json') ); - if (typeof this.serverless.service.provider.iamRoleARN !== 'string') { - // merge in the iamRoleLambdaTemplate - const iamRoleLambdaExecutionTemplate = this.serverless.utils.readFileSync( - path.join(this.serverless.config.serverlessPath, - 'plugins', - 'aws', - 'deploy', - 'lib', - 'iam-role-lambda-execution-template.json') - ); - - _.merge(this.serverless.service.provider.compiledCloudFormationTemplate.Resources, - iamRoleLambdaExecutionTemplate); - - // merge in the iamPolicyLambdaTemplate - const iamPolicyLambdaExecutionTemplate = this.serverless.utils.readFileSync( - path.join(this.serverless.config.serverlessPath, - 'plugins', - 'aws', - 'deploy', - 'lib', - 'iam-policy-lambda-execution-template.json') - ); - - // set the necessary variables for the IamPolicyLambda - iamPolicyLambdaExecutionTemplate - .IamPolicyLambdaExecution - .Properties - .PolicyName = `${this.options.stage}-${this.serverless.service.service}-lambda`; - - _.merge(this.serverless.service.provider.compiledCloudFormationTemplate.Resources, - iamPolicyLambdaExecutionTemplate); - - this.serverless.service.getAllFunctions().forEach((functionName) => { - const functionObject = this.serverless.service.getFunction(functionName); - const normalizedFunctionName = functionName[0].toUpperCase() + functionName.substr(1); - const logGroupTemplate = ` - { - "${normalizedFunctionName}LogGroup": { - "Type" : "AWS::Logs::LogGroup", - "Properties" : { - "LogGroupName" : "/aws/lambda/${functionObject.name}" - } - } - } - `; - const newLogGroup = JSON.parse(logGroupTemplate); - _.merge(this.serverless.service.provider.compiledCloudFormationTemplate.Resources, - newLogGroup); - - this.serverless.service.provider.compiledCloudFormationTemplate - .Resources - .IamPolicyLambdaExecution - .Properties - .PolicyDocument - .Statement[0] - .Resource - .push({ 'Fn::GetAtt': [`${normalizedFunctionName}LogGroup`, 'Arn'] }); - - this.serverless.service.provider.compiledCloudFormationTemplate - .Resources - .IamPolicyLambdaExecution - .Properties - .PolicyDocument - .Statement[1] - .Resource - .push({ - 'Fn::Join': [ - ':', - [ - { 'Fn::GetAtt': [`${normalizedFunctionName}LogGroup`, 'Arn'] }, - '*', - ], - ], - }); - }); - - // add custom iam role statements - if (this.serverless.service.provider.iamRoleStatements && - this.serverless.service.provider.iamRoleStatements instanceof Array) { - this.serverless.service.provider.compiledCloudFormationTemplate - .Resources - .IamPolicyLambdaExecution - .Properties - .PolicyDocument - .Statement = this.serverless.service.provider.compiledCloudFormationTemplate - .Resources - .IamPolicyLambdaExecution - .Properties - .PolicyDocument - .Statement.concat(this.serverless.service.provider.iamRoleStatements); - } - } const bucketName = this.serverless.service.provider.deploymentBucket; if (bucketName) { diff --git a/lib/plugins/aws/deploy/lib/mergeIamTemplates.js b/lib/plugins/aws/deploy/lib/mergeIamTemplates.js index e14067a31..8a7c42e48 100644 --- a/lib/plugins/aws/deploy/lib/mergeIamTemplates.js +++ b/lib/plugins/aws/deploy/lib/mergeIamTemplates.js @@ -36,16 +36,53 @@ module.exports = { .Properties .PolicyName = `${this.options.stage}-${this.serverless.service.service}-lambda`; - iamPolicyLambdaExecutionTemplate - .IamPolicyLambdaExecution - .Properties - .PolicyDocument - .Statement[0] - .Resource = `arn:aws:logs:${this.options.region}:*:*`; - _.merge(this.serverless.service.provider.compiledCloudFormationTemplate.Resources, iamPolicyLambdaExecutionTemplate); + this.serverless.service.getAllFunctions().forEach((functionName) => { + const functionObject = this.serverless.service.getFunction(functionName); + const normalizedFunctionName = functionName[0].toUpperCase() + functionName.substr(1); + const logGroupTemplate = ` + { + "${normalizedFunctionName}LogGroup": { + "Type" : "AWS::Logs::LogGroup", + "Properties" : { + "LogGroupName" : "/aws/lambda/${functionObject.name}" + } + } + } + `; + const newLogGroup = JSON.parse(logGroupTemplate); + _.merge(this.serverless.service.provider.compiledCloudFormationTemplate.Resources, + newLogGroup); + + this.serverless.service.provider.compiledCloudFormationTemplate + .Resources + .IamPolicyLambdaExecution + .Properties + .PolicyDocument + .Statement[0] + .Resource + .push({ 'Fn::GetAtt': [`${normalizedFunctionName}LogGroup`, 'Arn'] }); + + this.serverless.service.provider.compiledCloudFormationTemplate + .Resources + .IamPolicyLambdaExecution + .Properties + .PolicyDocument + .Statement[1] + .Resource + .push({ + 'Fn::Join': [ + ':', + [ + { 'Fn::GetAtt': [`${normalizedFunctionName}LogGroup`, 'Arn'] }, + '*', + ], + ], + }); + }); + // add custom iam role statements if (this.serverless.service.provider.iamRoleStatements && diff --git a/lib/plugins/aws/deploy/tests/configureStack.js b/lib/plugins/aws/deploy/tests/configureStack.js index d348262ce..be7b5e2fc 100644 --- a/lib/plugins/aws/deploy/tests/configureStack.js +++ b/lib/plugins/aws/deploy/tests/configureStack.js @@ -77,201 +77,6 @@ describe('#configureStack', () => { .then(() => {}); }); - it('should merge the IamRoleLambdaExecution template into the CloudFormation template', () => { - const IamRoleLambdaExecutionTemplate = awsPlugin.serverless.utils.readFileSync( - path.join( - __dirname, - '..', - 'lib', - 'iam-role-lambda-execution-template.json' - ) - ); - - return awsPlugin.configureStack() - .then(() => { - expect(awsPlugin.serverless.service.provider.compiledCloudFormationTemplate - .Resources.IamRoleLambdaExecution - ).to.deep.equal(IamRoleLambdaExecutionTemplate.IamRoleLambdaExecution); - }); - }); - - it('should merge IamPolicyLambdaExecution template into the CloudFormation template', () => - awsPlugin.configureStack() - .then(() => { - // we check for the type here because a deep equality check will error out due to - // the updates which are made after the merge (they are tested in a separate test) - expect(awsPlugin.serverless.service.provider.compiledCloudFormationTemplate - .Resources.IamPolicyLambdaExecution.Type - ).to.deep.equal('AWS::IAM::Policy'); - }) - ); - - it('should update the necessary variables for the IamPolicyLambdaExecution', () => - awsPlugin.configureStack() - .then(() => { - expect(awsPlugin.serverless.service.provider.compiledCloudFormationTemplate - .Resources - .IamPolicyLambdaExecution - .Properties - .PolicyName - ).to.equal( - `${ - awsPlugin.options.stage - }-${ - awsPlugin.serverless.service.service - }-lambda` - ); - }) - ); - - it('should add a CloudWatch LogGroup resource', () => { - const normalizedName = `${functionName[0].toUpperCase()}${functionName.substr(1)}LogGroup`; - awsPlugin.configureStack(); - - expect(awsPlugin.serverless.service.provider.compiledCloudFormationTemplate - .Resources[normalizedName] - ).to.deep.equal( - { - Type: 'AWS::Logs::LogGroup', - Properties: { - LogGroupName: `/aws/lambda/${functionName}`, - }, - } - ); - }); - - it('should update IamPolicyLambdaExecution with a logging resource for the function', () => { - const service = awsPlugin.serverless.service; // avoid 100 char lines below - service.functions = { - func0: { - handler: 'func.function.handler', - name: 'func0', - }, - func1: { - handler: 'func.function.handler', - name: 'func1', - }, - }; - const f = service.functions; // avoid 100 char lines below - const normalizedNames = [ - `${f.func0.name[0].toUpperCase()}${f.func0.name.substr(1)}LogGroup`, - `${f.func1.name[0].toUpperCase()}${f.func1.name.substr(1)}LogGroup`, - ]; - awsPlugin.configureStack(); - - expect(awsPlugin.serverless.service.provider.compiledCloudFormationTemplate - .Resources[normalizedNames[0]] - ).to.deep.equal( - { - Type: 'AWS::Logs::LogGroup', - Properties: { - LogGroupName: `/aws/lambda/${service.functions.func0.name}`, - }, - } - ); - expect(awsPlugin.serverless.service.provider.compiledCloudFormationTemplate - .Resources[normalizedNames[1]] - ).to.deep.equal( - { - Type: 'AWS::Logs::LogGroup', - Properties: { - LogGroupName: `/aws/lambda/${service.functions.func1.name}`, - }, - } - ); - }); - - it('should update IamPolicyLambdaExecution with a logging resource for the function', () => { - const normalizedName = `${functionName[0].toUpperCase()}${functionName.substr(1)}LogGroup`; - awsPlugin.configureStack(); - - expect(awsPlugin.serverless.service.provider.compiledCloudFormationTemplate - .Resources - .IamPolicyLambdaExecution - .Properties - .PolicyDocument - .Statement[0] - .Resource - ).to.deep.equal([{ 'Fn::GetAtt': [normalizedName, 'Arn'] }]); - expect(awsPlugin.serverless.service.provider.compiledCloudFormationTemplate - .Resources - .IamPolicyLambdaExecution - .Properties - .PolicyDocument - .Statement[1] - .Resource - ).to.deep.equal([{ 'Fn::Join': [':', [{ 'Fn::GetAtt': [normalizedName, 'Arn'] }, '*']] }]); - }); - - it('should update IamPolicyLambdaExecution with each function\'s logging resources', () => { - const service = awsPlugin.serverless.service; // avoid 100 char lines below - service.functions = { - func0: { - handler: 'func.function.handler', - name: 'func0', - }, - func1: { - handler: 'func.function.handler', - name: 'func1', - }, - }; - const f = service.functions; // avoid 100 char lines below - const normalizedNames = [ - `${f.func0.name[0].toUpperCase()}${f.func0.name.substr(1)}LogGroup`, - `${f.func1.name[0].toUpperCase()}${f.func1.name.substr(1)}LogGroup`, - ]; - awsPlugin.configureStack(); - - expect(awsPlugin.serverless.service.provider.compiledCloudFormationTemplate - .Resources - .IamPolicyLambdaExecution - .Properties - .PolicyDocument - .Statement[0] - .Resource - ).to.deep.equal( - [ - { 'Fn::GetAtt': [normalizedNames[0], 'Arn'] }, - { 'Fn::GetAtt': [normalizedNames[1], 'Arn'] }, - ] - ); - expect(awsPlugin.serverless.service.provider.compiledCloudFormationTemplate - .Resources - .IamPolicyLambdaExecution - .Properties - .PolicyDocument - .Statement[1] - .Resource - ).to.deep.equal( - [ - { 'Fn::Join': [':', [{ 'Fn::GetAtt': [normalizedNames[0], 'Arn'] }, '*']] }, - { 'Fn::Join': [':', [{ 'Fn::GetAtt': [normalizedNames[1], 'Arn'] }, '*']] }, - ] - ); - }); - - it('should add custom IAM policy statements', () => { - awsPlugin.serverless.service.provider.name = 'aws'; - awsPlugin.serverless.service.provider.iamRoleStatements = [ - { - Effect: 'Allow', - Action: [ - 'something:SomethingElse', - ], - Resource: 'some:aws:arn:xxx:*:*', - }, - ]; - - - return awsPlugin.configureStack() - .then(() => { - expect(awsPlugin.serverless.service.provider.compiledCloudFormationTemplate - .Resources.IamPolicyLambdaExecution.Properties.PolicyDocument.Statement[2] - ).to.deep.equal(awsPlugin.serverless.service.provider.iamRoleStatements[0]); - }); - }); - - it('should use a custom bucket if specified', () => { const bucketName = 'com.serverless.deploys'; diff --git a/lib/plugins/aws/deploy/tests/mergeIamTemplates.js b/lib/plugins/aws/deploy/tests/mergeIamTemplates.js index 821114dc6..9a902d902 100644 --- a/lib/plugins/aws/deploy/tests/mergeIamTemplates.js +++ b/lib/plugins/aws/deploy/tests/mergeIamTemplates.js @@ -9,6 +9,7 @@ const AwsDeploy = require('../'); describe('#mergeIamTemplates()', () => { let awsDeploy; let serverless; + const functionName = 'test'; beforeEach(() => { serverless = new Serverless(); @@ -21,6 +22,14 @@ describe('#mergeIamTemplates()', () => { awsDeploy.serverless.service.provider.compiledCloudFormationTemplate = { Resources: {}, }; + awsDeploy.serverless.service.service = 'new-service'; + awsDeploy.serverless.service.functions = { + [functionName]: { + name: 'test', + artifact: 'test.zip', + handler: 'handler.hello', + }, + }; }); @@ -42,8 +51,8 @@ describe('#mergeIamTemplates()', () => { }); }); - it('should merge IamPolicyLambdaExecution template into the CloudFormation template', () => - awsDeploy.mergeIamTemplates() + it('should merge IamPolicyLambdaExecution template into the CloudFormation template', + () => awsDeploy.mergeIamTemplates() .then(() => { // we check for the type here because a deep equality check will error out due to // the updates which are made after the merge (they are tested in a separate test) @@ -53,8 +62,8 @@ describe('#mergeIamTemplates()', () => { }) ); - it('should update the necessary variables for the IamPolicyLambdaExecution', () => - awsDeploy.mergeIamTemplates() + it('should update the necessary variables for the IamPolicyLambdaExecution', + () => awsDeploy.mergeIamTemplates() .then(() => { expect(awsDeploy.serverless.service.provider.compiledCloudFormationTemplate .Resources @@ -68,15 +77,6 @@ describe('#mergeIamTemplates()', () => { awsDeploy.serverless.service.service }-lambda` ); - - expect(awsDeploy.serverless.service.provider.compiledCloudFormationTemplate - .Resources - .IamPolicyLambdaExecution - .Properties - .PolicyDocument - .Statement[0] - .Resource - ).to.equal(`arn:aws:logs:${awsDeploy.options.region}:*:*`); }) ); @@ -96,7 +96,7 @@ describe('#mergeIamTemplates()', () => { return awsDeploy.mergeIamTemplates() .then(() => { expect(awsDeploy.serverless.service.provider.compiledCloudFormationTemplate - .Resources.IamPolicyLambdaExecution.Properties.PolicyDocument.Statement[1] + .Resources.IamPolicyLambdaExecution.Properties.PolicyDocument.Statement[2] ).to.deep.equal(awsDeploy.serverless.service.provider.iamRoleStatements[0]); }); }); @@ -121,4 +121,130 @@ describe('#mergeIamTemplates()', () => { .Resources.IamRoleLambdaExecution ).to.not.exist); }); + + it('should add a CloudWatch LogGroup resource', () => { + const normalizedName = `${functionName[0].toUpperCase()}${functionName.substr(1)}LogGroup`; + return awsDeploy.mergeIamTemplates().then(() => { + expect(awsDeploy.serverless.service.provider.compiledCloudFormationTemplate + .Resources[normalizedName] + ).to.deep.equal( + { + Type: 'AWS::Logs::LogGroup', + Properties: { + LogGroupName: `/aws/lambda/${functionName}`, + }, + } + ); + }); + }); + + it('should update IamPolicyLambdaExecution with a logging resource for the function', () => { + const service = awsDeploy.serverless.service; // avoid 100 char lines below + service.functions = { + func0: { + handler: 'func.function.handler', + name: 'func0', + }, + func1: { + handler: 'func.function.handler', + name: 'func1', + }, + }; + const f = service.functions; // avoid 100 char lines below + const normalizedNames = [ + `${f.func0.name[0].toUpperCase()}${f.func0.name.substr(1)}LogGroup`, + `${f.func1.name[0].toUpperCase()}${f.func1.name.substr(1)}LogGroup`, + ]; + return awsDeploy.mergeIamTemplates().then(() => { + expect(awsDeploy.serverless.service.provider.compiledCloudFormationTemplate + .Resources[normalizedNames[0]] + ).to.deep.equal( + { + Type: 'AWS::Logs::LogGroup', + Properties: { + LogGroupName: `/aws/lambda/${service.functions.func0.name}`, + }, + } + ); + expect(awsDeploy.serverless.service.provider.compiledCloudFormationTemplate + .Resources[normalizedNames[1]] + ).to.deep.equal( + { + Type: 'AWS::Logs::LogGroup', + Properties: { + LogGroupName: `/aws/lambda/${service.functions.func1.name}`, + }, + } + ); + }); + }); + + it('should update IamPolicyLambdaExecution with a logging resource for the function', () => { + const normalizedName = `${functionName[0].toUpperCase()}${functionName.substr(1)}LogGroup`; + return awsDeploy.mergeIamTemplates().then(() => { + expect(awsDeploy.serverless.service.provider.compiledCloudFormationTemplate + .Resources + .IamPolicyLambdaExecution + .Properties + .PolicyDocument + .Statement[0] + .Resource + ).to.deep.equal([{ 'Fn::GetAtt': [normalizedName, 'Arn'] }]); + expect(awsDeploy.serverless.service.provider.compiledCloudFormationTemplate + .Resources + .IamPolicyLambdaExecution + .Properties + .PolicyDocument + .Statement[1] + .Resource + ).to.deep.equal([{ 'Fn::Join': [':', [{ 'Fn::GetAtt': [normalizedName, 'Arn'] }, '*']] }]); + }); + }); + + it('should update IamPolicyLambdaExecution with each function\'s logging resources', () => { + const service = awsDeploy.serverless.service; // avoid 100 char lines below + service.functions = { + func0: { + handler: 'func.function.handler', + name: 'func0', + }, + func1: { + handler: 'func.function.handler', + name: 'func1', + }, + }; + const f = service.functions; // avoid 100 char lines below + const normalizedNames = [ + `${f.func0.name[0].toUpperCase()}${f.func0.name.substr(1)}LogGroup`, + `${f.func1.name[0].toUpperCase()}${f.func1.name.substr(1)}LogGroup`, + ]; + return awsDeploy.mergeIamTemplates().then(() => { + expect(awsDeploy.serverless.service.provider.compiledCloudFormationTemplate + .Resources + .IamPolicyLambdaExecution + .Properties + .PolicyDocument + .Statement[0] + .Resource + ).to.deep.equal( + [ + { 'Fn::GetAtt': [normalizedNames[0], 'Arn'] }, + { 'Fn::GetAtt': [normalizedNames[1], 'Arn'] }, + ] + ); + expect(awsDeploy.serverless.service.provider.compiledCloudFormationTemplate + .Resources + .IamPolicyLambdaExecution + .Properties + .PolicyDocument + .Statement[1] + .Resource + ).to.deep.equal( + [ + { 'Fn::Join': [':', [{ 'Fn::GetAtt': [normalizedNames[0], 'Arn'] }, '*']] }, + { 'Fn::Join': [':', [{ 'Fn::GetAtt': [normalizedNames[1], 'Arn'] }, '*']] }, + ] + ); + }); + }); });