diff --git a/lib/deploy/events/apiGateway/iamRole.js b/lib/deploy/events/apiGateway/iamRole.js index 163a86f7..f3b4f8b8 100644 --- a/lib/deploy/events/apiGateway/iamRole.js +++ b/lib/deploy/events/apiGateway/iamRole.js @@ -60,6 +60,11 @@ module.exports = { }; + const rolePath = _.get(this.serverless.service, 'provider.iam.role.path'); + if (rolePath) { + iamRoleApiGatewayToStepFunctions.Properties.Path = rolePath; + } + const getApiToStepFunctionsIamRoleLogicalId = this.getApiToStepFunctionsIamRoleLogicalId(); const newIamRoleStateMachineExecutionObject = { [getApiToStepFunctionsIamRoleLogicalId]: iamRoleApiGatewayToStepFunctions, diff --git a/lib/deploy/events/apiGateway/iamRole.test.js b/lib/deploy/events/apiGateway/iamRole.test.js index 0eb31cbe..30b2e835 100644 --- a/lib/deploy/events/apiGateway/iamRole.test.js +++ b/lib/deploy/events/apiGateway/iamRole.test.js @@ -176,4 +176,25 @@ describe('#compileHttpIamRole()', () => { .to.deep.equal(['states:StartExecution']); }); }); + + it('should apply provider.iam.role.path to API Gateway IAM role', () => { + serverlessStepFunctions.pluginhttpValidated = { + events: [ + { + stateMachineName: 'first', + http: { + path: 'foo/bar1', + method: 'post', + }, + }, + ], + }; + serverless.service.provider.iam = { role: { path: '/teamA/' } }; + + return serverlessStepFunctions.compileHttpIamRole().then(() => { + const properties = serverlessStepFunctions.serverless.service.provider + .compiledCloudFormationTemplate.Resources.ApigatewayToStepFunctionsRole.Properties; + expect(properties.Path).to.equal('/teamA/'); + }); + }); }); diff --git a/lib/deploy/events/cloudWatchEvent/compileCloudWatchEventEvents.js b/lib/deploy/events/cloudWatchEvent/compileCloudWatchEventEvents.js index 342cfb95..a879536e 100644 --- a/lib/deploy/events/cloudWatchEvent/compileCloudWatchEventEvents.js +++ b/lib/deploy/events/cloudWatchEvent/compileCloudWatchEventEvents.js @@ -128,7 +128,7 @@ module.exports = { } `; - const iamRoleTemplate = ` + let iamRoleTemplate = ` { "Type": "AWS::IAM::Role", "Properties": { @@ -174,6 +174,13 @@ module.exports = { const objectsToMerge = [newCloudWatchEventRuleObject]; if (!IamRole) { + const rolePath = _.get(this.serverless.service, 'provider.iam.role.path'); + if (rolePath) { + const jsonIamRole = JSON.parse(iamRoleTemplate); + jsonIamRole.Properties.Path = rolePath; + iamRoleTemplate = JSON.stringify(jsonIamRole); + } + const newPermissionObject = { [cloudWatchIamRoleLogicalId]: JSON.parse(iamRoleTemplate), }; diff --git a/lib/deploy/events/cloudWatchEvent/compileCloudWatchEventEvents.test.js b/lib/deploy/events/cloudWatchEvent/compileCloudWatchEventEvents.test.js index 85df8b9c..43bde738 100644 --- a/lib/deploy/events/cloudWatchEvent/compileCloudWatchEventEvents.test.js +++ b/lib/deploy/events/cloudWatchEvent/compileCloudWatchEventEvents.test.js @@ -580,6 +580,35 @@ describe('awsCompileCloudWatchEventEvents', () => { .Properties.Targets[0].RoleArn).to.equal('{"Fn::GetAtt": ["StepFunctionsRole", "Arn"]}'); }); + it('should apply provider.iam.role.path to CloudWatch event IAM role', () => { + serverlessStepFunctions.serverless.service.stepFunctions = { + stateMachines: { + first: { + events: [ + { + cloudwatchEvent: { + event: { + source: ['aws.ec2'], + 'detail-type': ['EC2 Instance State-change Notification'], + detail: { state: ['pending'] }, + }, + enabled: false, + }, + }, + ], + }, + }, + }; + serverless.service.provider.iam = { role: { path: '/teamA/' } }; + + serverlessStepFunctions.compileCloudWatchEventEvents(); + + expect(serverlessStepFunctions.serverless.service + .provider.compiledCloudFormationTemplate.Resources + .FirstEventToStepFunctionsRole + .Properties.Path).to.equal('/teamA/'); + }); + it('should not create corresponding resources when events are not given', () => { serverlessStepFunctions.serverless.service.stepFunctions = { stateMachines: { diff --git a/lib/deploy/events/schedule/compileScheduledEvents.js b/lib/deploy/events/schedule/compileScheduledEvents.js index 2adf70c3..f4ae4757 100644 --- a/lib/deploy/events/schedule/compileScheduledEvents.js +++ b/lib/deploy/events/schedule/compileScheduledEvents.js @@ -266,6 +266,13 @@ module.exports = { iamRoleTemplate = JSON.stringify(jsonIamRole); } + const rolePath = _.get(service, 'provider.iam.role.path'); + if (rolePath) { + const jsonIamRole = JSON.parse(iamRoleTemplate); + jsonIamRole.Properties.Path = rolePath; + iamRoleTemplate = JSON.stringify(jsonIamRole); + } + const newScheduleObject = { [scheduleLogicalId]: JSON.parse(scheduleTemplate), }; diff --git a/lib/deploy/events/schedule/compileScheduledEvents.test.js b/lib/deploy/events/schedule/compileScheduledEvents.test.js index bf3e7d80..4ca7531b 100644 --- a/lib/deploy/events/schedule/compileScheduledEvents.test.js +++ b/lib/deploy/events/schedule/compileScheduledEvents.test.js @@ -448,6 +448,31 @@ describe('#httpValidate()', () => { .Properties.PermissionsBoundary).to.equal('arn:aws:iam::myAccount:policy/permission_boundary'); }); + it('should handle provider.iam.role.path', () => { + serverlessStepFunctions.serverless.service.stepFunctions = { + stateMachines: { + first: { + events: [ + { + schedule: { + rate: 'rate(10 minutes)', + enabled: false, + inputPath: '$.stageVariables', + }, + }, + ], + }, + }, + }; + serverless.service.provider.iam = { role: { path: '/teamA/' } }; + serverlessStepFunctions.compileScheduledEvents(); + + expect(serverlessStepFunctions.serverless.service + .provider.compiledCloudFormationTemplate.Resources + .FirstScheduleToStepFunctionsRole + .Properties.Path).to.equal('/teamA/'); + }); + it('should have type of AWS::Scheduler::Schedule if method is scheduler', () => { serverlessStepFunctions.serverless.service.stepFunctions = { stateMachines: { diff --git a/lib/deploy/stepFunctions/compileIamRole.js b/lib/deploy/stepFunctions/compileIamRole.js index bb50fcad..7612adfa 100644 --- a/lib/deploy/stepFunctions/compileIamRole.js +++ b/lib/deploy/stepFunctions/compileIamRole.js @@ -943,6 +943,13 @@ module.exports = { iamRoleJson = JSON.stringify(jsonIamRole); } + const rolePath = _.get(service, 'provider.iam.role.path'); + if (rolePath) { + const jsonIamRole = JSON.parse(iamRoleJson); + jsonIamRole.Properties.Path = rolePath; + iamRoleJson = JSON.stringify(jsonIamRole); + } + const stateMachineLogicalId = this.getStateMachineLogicalId( stateMachineId, stateMachineObj, diff --git a/lib/deploy/stepFunctions/compileIamRole.test.js b/lib/deploy/stepFunctions/compileIamRole.test.js index 5d81a143..f13f359f 100644 --- a/lib/deploy/stepFunctions/compileIamRole.test.js +++ b/lib/deploy/stepFunctions/compileIamRole.test.js @@ -4425,6 +4425,33 @@ describe('#compileIamRole', () => { expect(boundary).to.equal('arn:aws:iam::myAccount:policy/permission_boundary'); }); + it('should handle provider.iam.role.path', () => { + serverless.service.stepFunctions = { + stateMachines: { + myStateMachine1: { + id: 'StateMachine1', + definition: { + StartAt: 'A', + States: { + A: { + Type: 'Task', + Resource: + 'arn:aws:lambda:#{AWS::Region}:#{AWS::AccountId}:function:hello', + End: true, + }, + }, + }, + }, + }, + }; + serverless.service.provider.iam = { role: { path: '/teamA/' } }; + serverlessStepFunctions.compileIamRole(); + const rolePath = serverlessStepFunctions.serverless.service.provider + .compiledCloudFormationTemplate.Resources.StateMachine1Role.Properties + .Path; + expect(rolePath).to.equal('/teamA/'); + }); + it('should handle permissions listObjectsV2', () => { const myBucket = 'myBucket'; serverless.service.stepFunctions = { diff --git a/lib/deploy/stepFunctions/compileNotifications.js b/lib/deploy/stepFunctions/compileNotifications.js index f63da322..d8e2a518 100644 --- a/lib/deploy/stepFunctions/compileNotifications.js +++ b/lib/deploy/stepFunctions/compileNotifications.js @@ -201,7 +201,7 @@ function compilePermissionForTarget(status, targetObj) { }; } -function bootstrapIamRole() { +function bootstrapIamRole(rolePath) { const iamRole = { Type: 'AWS::IAM::Role', Properties: { @@ -215,6 +215,7 @@ function bootstrapIamRole() { }, }, Policies: [], + ...(rolePath && { Path: rolePath }), }, }; const addPolicy = (name, action, resource) => { @@ -234,8 +235,8 @@ function bootstrapIamRole() { return { iamRole, addPolicy }; } -function* compilePermissionResources(stateMachineLogicalId, iamRoleLogicalId, targets) { - const { iamRole, addPolicy } = bootstrapIamRole(); +function* compilePermissionResources(stateMachineLogicalId, iamRoleLogicalId, targets, rolePath) { + const { iamRole, addPolicy } = bootstrapIamRole(rolePath); for (let index = 0; index < targets.length; index++) { const { status, target } = targets[index]; @@ -263,12 +264,12 @@ function* compilePermissionResources(stateMachineLogicalId, iamRoleLogicalId, ta } } -function* compileResources(stateMachineLogicalId, stateMachineName, notificationsObj) { +function* compileResources(stateMachineLogicalId, stateMachineName, notificationsObj, rolePath) { const iamRoleLogicalId = `${stateMachineLogicalId}NotificationsIamRole`; const allTargets = _.flatMap(executionStatuses, status => _.get(notificationsObj, status, []).map(target => ({ status, target }))); const permissions = compilePermissionResources( - stateMachineLogicalId, iamRoleLogicalId, allTargets, + stateMachineLogicalId, iamRoleLogicalId, allTargets, rolePath, ); const permissionResources = Array.from(permissions); for (const { logicalId, resource } of permissionResources) { @@ -354,10 +355,12 @@ module.exports = { return []; } + const rolePath = _.get(this.serverless.service, 'provider.iam.role.path'); const resourcesIterator = compileResources( stateMachineLogicalId, stateMachineName, notificationsObj, + rolePath, ); return Array.from(resourcesIterator); diff --git a/lib/deploy/stepFunctions/compileNotifications.test.js b/lib/deploy/stepFunctions/compileNotifications.test.js index 8dfec843..1be8de1d 100644 --- a/lib/deploy/stepFunctions/compileNotifications.test.js +++ b/lib/deploy/stepFunctions/compileNotifications.test.js @@ -522,4 +522,19 @@ describe('#compileNotifications', () => { expect(logMessage.startsWith('State machine [Beta1] : notifications are not supported on Express Workflows.')) .to.equal(true); }); + + it('should apply provider.iam.role.path to notifications IAM role', () => { + serverless.service.stepFunctions = { + stateMachines: { + beta1: genStateMachineWithTargets('Beta1', [{ stepFunctions: 'STATE_MACHINE_ARN' }]), + }, + }; + serverless.service.provider.iam = { role: { path: '/teamA/' } }; + + serverlessStepFunctions.compileNotifications(); + const resources = serverlessStepFunctions.serverless.service + .provider.compiledCloudFormationTemplate.Resources; + + expect(resources.Beta1NotificationsIamRole.Properties.Path).to.equal('/teamA/'); + }); });