From 9203c1f6e851f336253dc7f1a86a4423192b2c27 Mon Sep 17 00:00:00 2001 From: Nobuo Sakiyama Date: Mon, 26 Nov 2018 21:49:25 +0900 Subject: [PATCH 1/4] fix the case of per-function custom Role --- lib/stackops/lambdaRole.js | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/lib/stackops/lambdaRole.js b/lib/stackops/lambdaRole.js index 6038a66..685d16a 100644 --- a/lib/stackops/lambdaRole.js +++ b/lib/stackops/lambdaRole.js @@ -43,14 +43,16 @@ module.exports = function(currentTemplate, aliasStackTemplates, currentAliasStac // Replace references const functions = _.filter(stageStack.Resources, ['Type', 'AWS::Lambda::Function']); _.forEach(functions, func => { - func.Properties.Role = { - 'Fn::GetAtt': [ - roleLogicalId, - 'Arn' - ] - }; - const dependencyIndex = _.indexOf(func.DependsOn, 'IamRoleLambdaExecution'); - func.DependsOn[dependencyIndex] = roleLogicalId; + if (_.isEqual(func.Properties.Role, { 'Fn::GetAtt': ['IamRoleLambdaExecution', 'Arn' ]})) { + func.Properties.Role = { + 'Fn::GetAtt': [ + roleLogicalId, + 'Arn' + ] + }; + const dependencyIndex = _.indexOf(func.DependsOn, 'IamRoleLambdaExecution'); + func.DependsOn[dependencyIndex] = roleLogicalId; + } }); if (_.has(currentTemplate, 'Resources.IamRoleLambdaExecution')) { From 77213a314122eaa34d2e0227e8d2377811f57a8b Mon Sep 17 00:00:00 2001 From: Sergii Kovalev Date: Tue, 23 Apr 2019 22:06:12 +0300 Subject: [PATCH 2/4] Custom function role test --- lib/stackops/lambdaRole.js | 4 +- test/data/sls-stack-3.json | 430 +++++++++++++++++++++++++++++++ test/stackops/lambdaRole.test.js | 23 ++ 3 files changed, 456 insertions(+), 1 deletion(-) create mode 100644 test/data/sls-stack-3.json diff --git a/lib/stackops/lambdaRole.js b/lib/stackops/lambdaRole.js index 685d16a..e78c9f1 100644 --- a/lib/stackops/lambdaRole.js +++ b/lib/stackops/lambdaRole.js @@ -35,7 +35,9 @@ module.exports = function(currentTemplate, aliasStackTemplates, currentAliasStac const role = stageStack.Resources.IamRoleLambdaExecution; // Set role name - _.last(role.Properties.RoleName['Fn::Join']).push(this._alias); + if (role.Properties.RoleName['Fn::Join']) { + _.last(role.Properties.RoleName['Fn::Join']).push(this._alias); + } stageStack.Resources[roleLogicalId] = stageStack.Resources.IamRoleLambdaExecution; delete stageStack.Resources.IamRoleLambdaExecution; diff --git a/test/data/sls-stack-3.json b/test/data/sls-stack-3.json new file mode 100644 index 0000000..1707035 --- /dev/null +++ b/test/data/sls-stack-3.json @@ -0,0 +1,430 @@ +{ + "AWSTemplateFormatVersion": "2010-09-09", + "Description": "The AWS CloudFormation template for this Serverless application", + "Resources": { + "ServerlessDeploymentBucket": { + "Type": "AWS::S3::Bucket" + }, + "Testfct1LogGroup": { + "Type": "AWS::Logs::LogGroup", + "Properties": { + "LogGroupName": "/aws/lambda/sls-test-project-dev-testfct1" + } + }, + "WarmUpPluginLogGroup": { + "Type": "AWS::Logs::LogGroup", + "Properties": { + "LogGroupName": "/aws/lambda/warmup-plugin-sls-test-project-dev" + } + }, + "IamRoleLambdaExecution": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Principal": { + "Service": [ + "lambda.amazonaws.com" + ] + }, + "Action": [ + "sts:AssumeRole" + ] + } + ] + }, + "Policies": [ + { + "PolicyName": { + "Fn::Join": [ + "-", + [ + "dev", + "sls-test-project", + "lambda" + ] + ] + }, + "PolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Action": [ + "logs:CreateLogStream" + ], + "Resource": [ + { + "Fn::Sub": "arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/lambda/sls-test-project-dev-testfct1:*" + }, + { + "Fn::Sub": "arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/lambda/warmup-plugin-sls-test-project-dev:*" + } + ] + }, + { + "Effect": "Allow", + "Action": [ + "logs:PutLogEvents" + ], + "Resource": [ + { + "Fn::Sub": "arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/lambda/sls-test-project-dev-testfct1:*:*" + }, + { + "Fn::Sub": "arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/lambda/warmup-plugin-sls-test-project-dev:*:*" + } + ] + }, + { + "Effect": "Allow", + "Action": [ + "dynamodb:*" + ], + "Resource": [ + { + "Fn::Join": [ + "/", + [ + { + "Fn::Join": [ + ":", + [ + "arn:aws:dynamodb", + { + "Ref": "AWS::Region" + }, + { + "Ref": "AWS::AccountId" + }, + "table" + ] + ] + }, + { + "Ref": "TestDynamoDbTable" + } + ] + ] + } + ] + } + ] + } + } + ], + "Path": "/", + "RoleName": "custom-role-name", + "ManagedPolicyArns": [ + "arn:aws:iam::aws:policy/service-role/AWSLambdaVPCAccessExecutionRole" + ] + } + }, + "Testfct1LambdaFunction": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "S3Bucket": { + "Ref": "ServerlessDeploymentBucket" + }, + "S3Key": "serverless/sls-test-project/dev/1494367071172-2017-05-09T21:57:51.172Z/sls-test-project.zip" + }, + "FunctionName": "sls-test-project-dev-testfct1", + "Handler": "handlers/testfct1/handler.handle", + "MemorySize": 512, + "Role": { + "Fn::GetAtt": [ + "IamRoleLambdaExecution", + "Arn" + ] + }, + "Runtime": "nodejs4.3", + "Timeout": 15, + "Description": "Echo function echoes alias", + "Environment": { + "Variables": { + "SERVERLESS_PROJECT_NAME": "sls-test-project", + "SERVERLESS_PROJECT": "sls-test-project", + "SERVERLESS_STAGE": "dev", + "SERVERLESS_REGION": "us-east-1", + "TEST_TABLE_NAME": { + "Ref": "TestDynamoDbTable" + } + } + }, + "VpcConfig": { + "SecurityGroupIds": [ + { + "Fn::ImportValue": "stashimi-dev-PrivateSG" + } + ], + "SubnetIds": [ + { + "Fn::ImportValue": "stashimi-dev-PrivateSubnet1" + }, + { + "Fn::ImportValue": "stashimi-dev-PrivateSubnet2" + } + ] + } + }, + "DependsOn": [ + "Testfct1LogGroup", + "IamRoleLambdaExecution" + ] + }, + "Testfct1LambdaVersionWh5jTkiTR67V05RPWQIlzPI25WiPbdHDYNgbtAMneU": { + "Type": "AWS::Lambda::Version", + "DeletionPolicy": "Retain", + "Properties": { + "FunctionName": { + "Ref": "Testfct1LambdaFunction" + }, + "CodeSha256": "Wh5jTkiTR67+V05RPWQIlzPI25WiPbdHDYNgbtAMneU=", + "Description": "Echo function echoes alias" + } + }, + "WarmUpPluginLambdaFunction": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "S3Bucket": { + "Ref": "ServerlessDeploymentBucket" + }, + "S3Key": "serverless/sls-test-project/dev/1494367071172-2017-05-09T21:57:51.172Z/sls-test-project.zip" + }, + "FunctionName": "warmup-plugin-sls-test-project-dev", + "Handler": "_warmup/index.warmUp", + "MemorySize": 128, + "Role": { + "Fn::GetAtt": [ + "IamRoleLambdaExecution", + "Arn" + ] + }, + "Runtime": "nodejs6.10", + "Timeout": 10, + "Description": "Serverless WarmUP Plugin", + "Environment": { + "Variables": { + "SERVERLESS_PROJECT_NAME": "sls-test-project", + "SERVERLESS_PROJECT": "sls-test-project", + "SERVERLESS_STAGE": "dev", + "SERVERLESS_REGION": "us-east-1", + "TEST_TABLE_NAME": { + "Ref": "TestDynamoDbTable" + } + } + }, + "VpcConfig": { + "SecurityGroupIds": [ + { + "Fn::ImportValue": "stashimi-dev-PrivateSG" + } + ], + "SubnetIds": [ + { + "Fn::ImportValue": "stashimi-dev-PrivateSubnet1" + }, + { + "Fn::ImportValue": "stashimi-dev-PrivateSubnet2" + } + ] + } + }, + "DependsOn": [ + "WarmUpPluginLogGroup", + "IamRoleLambdaExecution" + ] + }, + "WarmUpPluginLambdaVersionWh5jTkiTR67V05RPWQIlzPI25WiPbdHDYNgbtAMneU": { + "Type": "AWS::Lambda::Version", + "DeletionPolicy": "Retain", + "Properties": { + "FunctionName": { + "Ref": "WarmUpPluginLambdaFunction" + }, + "CodeSha256": "Wh5jTkiTR67+V05RPWQIlzPI25WiPbdHDYNgbtAMneU=", + "Description": "Serverless WarmUP Plugin" + } + }, + "WarmUpPluginEventsRuleSchedule1": { + "Type": "AWS::Events::Rule", + "Properties": { + "ScheduleExpression": "rate(5 minutes)", + "State": "ENABLED", + "Targets": [ + { + "Arn": { + "Fn::GetAtt": [ + "WarmUpPluginLambdaFunction", + "Arn" + ] + }, + "Id": "warmUpPluginSchedule" + } + ] + } + }, + "WarmUpPluginLambdaPermissionEventsRuleSchedule1": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "FunctionName": { + "Fn::GetAtt": [ + "WarmUpPluginLambdaFunction", + "Arn" + ] + }, + "Action": "lambda:InvokeFunction", + "Principal": "events.amazonaws.com", + "SourceArn": { + "Fn::GetAtt": [ + "WarmUpPluginEventsRuleSchedule1", + "Arn" + ] + } + } + }, + "ApiGatewayRestApi": { + "Type": "AWS::ApiGateway::RestApi", + "Properties": { + "Name": "dev-sls-test-project" + } + }, + "ApiGatewayResourceFunc1": { + "Type": "AWS::ApiGateway::Resource", + "Properties": { + "ParentId": { + "Fn::GetAtt": [ + "ApiGatewayRestApi", + "RootResourceId" + ] + }, + "PathPart": "func1", + "RestApiId": { + "Ref": "ApiGatewayRestApi" + } + } + }, + "ApiGatewayMethodFunc1Get": { + "Type": "AWS::ApiGateway::Method", + "Properties": { + "HttpMethod": "GET", + "RequestParameters": {}, + "ResourceId": { + "Ref": "ApiGatewayResourceFunc1" + }, + "RestApiId": { + "Ref": "ApiGatewayRestApi" + }, + "AuthorizationType": "NONE", + "Integration": { + "IntegrationHttpMethod": "POST", + "Type": "AWS_PROXY", + "Uri": { + "Fn::Join": [ + "", + [ + "arn:aws:apigateway:", + { + "Ref": "AWS::Region" + }, + ":lambda:path/2015-03-31/functions/", + { + "Fn::GetAtt": [ + "Testfct1LambdaFunction", + "Arn" + ] + }, + "/invocations" + ] + ] + } + }, + "MethodResponses": [] + } + }, + "ApiGatewayDeployment1494367071211": { + "Type": "AWS::ApiGateway::Deployment", + "Properties": { + "RestApiId": { + "Ref": "ApiGatewayRestApi" + }, + "StageName": "dev" + }, + "DependsOn": [ + "ApiGatewayMethodFunc1Get" + ] + }, + "Testfct1LambdaPermissionApiGateway": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "FunctionName": { + "Fn::GetAtt": [ + "Testfct1LambdaFunction", + "Arn" + ] + }, + "Action": "lambda:InvokeFunction", + "Principal": "apigateway.amazonaws.com", + "SourceArn": { + "Fn::Join": [ + "", + [ + "arn:aws:execute-api:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":", + { + "Ref": "ApiGatewayRestApi" + }, + "/*/*" + ] + ] + } + } + } + }, + "Outputs": { + "ServerlessDeploymentBucketName": { + "Value": { + "Ref": "ServerlessDeploymentBucket" + } + }, + "Testfct1LambdaFunctionQualifiedArn": { + "Description": "Current Lambda function version", + "Value": { + "Ref": "Testfct1LambdaVersionWh5jTkiTR67V05RPWQIlzPI25WiPbdHDYNgbtAMneU" + } + }, + "WarmUpPluginLambdaFunctionQualifiedArn": { + "Description": "Current Lambda function version", + "Value": { + "Ref": "WarmUpPluginLambdaVersionWh5jTkiTR67V05RPWQIlzPI25WiPbdHDYNgbtAMneU" + } + }, + "ServiceEndpoint": { + "Description": "URL of the service endpoint", + "Value": { + "Fn::Join": [ + "", + [ + "https://", + { + "Ref": "ApiGatewayRestApi" + }, + ".execute-api.us-east-1.amazonaws.com/dev" + ] + ] + } + } + } +} diff --git a/test/stackops/lambdaRole.test.js b/test/stackops/lambdaRole.test.js index 06e9033..9f6b90e 100644 --- a/test/stackops/lambdaRole.test.js +++ b/test/stackops/lambdaRole.test.js @@ -99,5 +99,28 @@ describe('lambdaRole', () => { .then(() => expect(stackTemplate).to.have.a.nested.property('Resources.IamRoleLambdaExecutiontestAlias')); }); + it('should retain custom stack roles', () => { + const aliasTemplates = [{ + Resources: {}, + Outputs: { + ServerlessAliasName: { + Description: 'The current alias', + Value: 'testAlias' + } + } + }]; + const currentTemplate = { + Resources: { + IamRoleLambdaExecution: {}, + IamRoleLambdaExecutiontestAlias: {} + }, + Outputs: {} + }; + + const customRoleStack = _.cloneDeep(require('../data/sls-stack-3.json')); + const stackTemplate = serverless.service.provider.compiledCloudFormationTemplate = customRoleStack; + return expect(awsAlias.aliasHandleLambdaRole(currentTemplate, aliasTemplates, {})).to.be.fulfilled + .then(() => expect(stackTemplate).to.have.a.nested.property('Resources.IamRoleLambdaExecutiontestAlias')); + }); }); }); From 940537171421de05a8e424572889506f6decc768 Mon Sep 17 00:00:00 2001 From: Sergii Kovalev Date: Tue, 23 Apr 2019 22:15:59 +0300 Subject: [PATCH 3/4] CI exception fix --- test/updateAliasStack.test.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/test/updateAliasStack.test.js b/test/updateAliasStack.test.js index 5142237..f32a7c6 100644 --- a/test/updateAliasStack.test.js +++ b/test/updateAliasStack.test.js @@ -261,8 +261,7 @@ describe('updateAliasStack', () => { it('should resolve in case no updates are performed', () => { providerRequestStub.returns(BbPromise.resolve("done")); - monitorStackStub.returns(BbPromise.reject(new Error('No updates are to be performed.'))); - + monitorStackStub.rejects(new Error('No updates are to be performed.')); return expect(awsAlias.updateAlias()).to.be.fulfilled .then(() => expect(providerRequestStub).to.have.been.calledOnce); }); From cdff2ce56387edf31e833c0d0f802dccd1afcc40 Mon Sep 17 00:00:00 2001 From: Sergii Kovalev Date: Fri, 10 May 2019 19:41:15 +0300 Subject: [PATCH 4/4] CR Changes: fix the case of per-function custom Role --- lib/stackops/lambdaRole.js | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/lib/stackops/lambdaRole.js b/lib/stackops/lambdaRole.js index e78c9f1..eff9c43 100644 --- a/lib/stackops/lambdaRole.js +++ b/lib/stackops/lambdaRole.js @@ -44,17 +44,21 @@ module.exports = function(currentTemplate, aliasStackTemplates, currentAliasStac // Replace references const functions = _.filter(stageStack.Resources, ['Type', 'AWS::Lambda::Function']); - _.forEach(functions, func => { - if (_.isEqual(func.Properties.Role, { 'Fn::GetAtt': ['IamRoleLambdaExecution', 'Arn' ]})) { - func.Properties.Role = { - 'Fn::GetAtt': [ - roleLogicalId, - 'Arn' - ] - }; - const dependencyIndex = _.indexOf(func.DependsOn, 'IamRoleLambdaExecution'); - func.DependsOn[dependencyIndex] = roleLogicalId; - } + + const functionsWithIamRoleReference = _.filter(functions, (func) => _.isEqual( + func.Properties.Role, + {'Fn::GetAtt': ['IamRoleLambdaExecution', 'Arn' ]} + )); + + _.forEach(functionsWithIamRoleReference, func => { + func.Properties.Role = { + 'Fn::GetAtt': [ + roleLogicalId, + 'Arn' + ] + }; + const dependencyIndex = _.indexOf(func.DependsOn, 'IamRoleLambdaExecution'); + func.DependsOn[dependencyIndex] = roleLogicalId; }); if (_.has(currentTemplate, 'Resources.IamRoleLambdaExecution')) {