From 6d04fb91563c528173f76f428503637630ffe4c3 Mon Sep 17 00:00:00 2001 From: Yan Cui Date: Wed, 30 Jan 2019 17:05:05 +0000 Subject: [PATCH 1/3] - add support for a `dependsOn` option proposed by #158 --- README.md | 31 ++++++- .../stepFunctions/compileStateMachines.js | 21 ++++- .../compileStateMachines.test.js | 90 +++++++++++++++++-- 3 files changed, 130 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 591bf8b1..aa75b8e1 100644 --- a/README.md +++ b/README.md @@ -47,6 +47,8 @@ stepFunctions: Type: Task Resource: arn:aws:lambda:#{AWS::Region}:#{AWS::AccountId}:function:${self:service}-${opt:stage}-hello End: true + dependsOn: + Ref: DynamoDBTable hellostepfunc2: definition: StartAt: HelloWorld2 @@ -55,6 +57,10 @@ stepFunctions: Type: Task Resource: arn:aws:states:#{AWS::Region}:#{AWS::AccountId}:activity:myTask End: true + dependsOn: + - Ref: DynamoDBTable + - Ref: KinesisStream + - Ref: CUstomIamRole activities: - myTask - yourTask @@ -110,6 +116,23 @@ plugins: You can then `Ref: SendMessageStateMachine` in various parts of CloudFormation or serverless.yml +#### Depending on another logical id +If your state machine depends on another resource defined in your `serverless.yml` then you can add a `dependsOn` field to the state machine `definition`. This would add the `DependsOn`clause to the generated CloudFormation template. + +This `dependsOn` field can be either a string, or an array of strings. + +```yaml +stepFunctions: + stateMachines: + myStateMachine: + dependsOn: myDB + + myOtherStateMachine: + dependsOn: + - myOtherDB + - myStream +``` + #### Current Gotcha Please keep this gotcha in mind if you want to reference the `name` from the `resources` section. To generate Logical ID for CloudFormation, the plugin transforms the specified name in serverless.yml based on the following scheme. @@ -329,11 +352,11 @@ stepFunctions: events: - http: path: /users - ... + ... authorizer: # Provide both type and authorizerId type: COGNITO_USER_POOLS # TOKEN, CUSTOM or COGNITO_USER_POOLS, same as AWS Cloudformation documentation - authorizerId: + authorizerId: Ref: ApiGatewayAuthorizer # or hard-code Authorizer ID ``` @@ -581,7 +604,7 @@ stepFunctions: state: - pending definition: - ... + ... ``` ## Specifying a Name @@ -654,7 +677,7 @@ resources: Resources: StateMachineRole: Type: AWS::IAM::Role - Properties: + Properties: ... ``` diff --git a/lib/deploy/stepFunctions/compileStateMachines.js b/lib/deploy/stepFunctions/compileStateMachines.js index 77a20aa3..07c5e902 100644 --- a/lib/deploy/stepFunctions/compileStateMachines.js +++ b/lib/deploy/stepFunctions/compileStateMachines.js @@ -15,7 +15,7 @@ module.exports = { const stateMachineObj = this.getStateMachine(stateMachineName); let DefinitionString; let RoleArn; - let DependsOn; + let DependsOn = []; if (stateMachineObj.definition) { if (typeof stateMachineObj.definition === 'string') { @@ -63,7 +63,24 @@ module.exports = { 'Arn', ], }; - DependsOn = 'IamRoleStateMachineExecution'; + DependsOn.push('IamRoleStateMachineExecution'); + } + + if (stateMachineObj.dependsOn) { + const dependsOn = stateMachineObj.dependsOn; + + if (_.isArray(dependsOn) && _.every(dependsOn, _.isString)) { + DependsOn = _.concat(DependsOn, dependsOn); + } else if (_.isString(dependsOn)) { + DependsOn.push(dependsOn); + } else { + const errorMessage = [ + `dependsOn property in stateMachine "${stateMachineName}" is neither a string`, + ' nor an array of strings', + ].join(''); + throw new this.serverless.classes + .Error(errorMessage); + } } const stateMachineLogicalId = this.getStateMachineLogicalId(stateMachineName, diff --git a/lib/deploy/stepFunctions/compileStateMachines.test.js b/lib/deploy/stepFunctions/compileStateMachines.test.js index 3a23fd4e..8c5666d0 100644 --- a/lib/deploy/stepFunctions/compileStateMachines.test.js +++ b/lib/deploy/stepFunctions/compileStateMachines.test.js @@ -64,11 +64,11 @@ describe('#compileStateMachines', () => { expect(serverlessStepFunctions.serverless.service .provider.compiledCloudFormationTemplate.Resources .StateMachineBeta1.DependsOn - ).to.equal('IamRoleStateMachineExecution'); + ).to.deep.eq(['IamRoleStateMachineExecution']); expect(serverlessStepFunctions.serverless.service .provider.compiledCloudFormationTemplate.Resources .StateMachineBeta2.DependsOn - ).to.equal('IamRoleStateMachineExecution'); + ).to.deep.eq(['IamRoleStateMachineExecution']); expect(serverlessStepFunctions.serverless.service .provider.compiledCloudFormationTemplate.Outputs .StateMachineBeta1Arn.Value.Ref @@ -119,11 +119,11 @@ describe('#compileStateMachines', () => { expect(serverlessStepFunctions.serverless.service .provider.compiledCloudFormationTemplate.Resources .MyStateMachine1StepFunctionsStateMachine.DependsOn - ).to.equal('IamRoleStateMachineExecution'); + ).to.deep.eq(['IamRoleStateMachineExecution']); expect(serverlessStepFunctions.serverless.service .provider.compiledCloudFormationTemplate.Resources .MyStateMachine2StepFunctionsStateMachine.DependsOn - ).to.equal('IamRoleStateMachineExecution'); + ).to.deep.eq(['IamRoleStateMachineExecution']); expect(serverlessStepFunctions.serverless.service .provider.compiledCloudFormationTemplate.Outputs .MyStateMachine1StepFunctionsStateMachineArn.Value.Ref @@ -176,11 +176,11 @@ describe('#compileStateMachines', () => { expect(serverlessStepFunctions.serverless.service .provider.compiledCloudFormationTemplate.Resources .StateMachineBeta1.DependsOn - ).to.equal('IamRoleStateMachineExecution'); + ).to.deep.eq(['IamRoleStateMachineExecution']); expect(serverlessStepFunctions.serverless.service .provider.compiledCloudFormationTemplate.Resources .StateMachineBeta2.DependsOn - ).to.equal('IamRoleStateMachineExecution'); + ).to.deep.eq(['IamRoleStateMachineExecution']); }); it('should create corresponding resources when definition and role property are given', () => { @@ -405,4 +405,82 @@ describe('#compileStateMachines', () => { expect(actual).to.equal(JSON.stringify(definition, undefined, 2)); }); + + it('should add dependsOn resources', () => { + serverless.service.stepFunctions = { + stateMachines: { + myStateMachine1: { + definition: 'definition1', + name: 'stateMachineBeta1', + dependsOn: 'DynamoDBTable', + }, + myStateMachine2: { + definition: 'definition2', + name: 'stateMachineBeta2', + dependsOn: [ + 'DynamoDBTable', + 'KinesisStream', + ], + }, + }, + }; + + serverlessStepFunctions.compileStateMachines(); + expect(serverlessStepFunctions.serverless.service + .provider.compiledCloudFormationTemplate.Resources + .StateMachineBeta1.Type + ).to.equal('AWS::StepFunctions::StateMachine'); + expect(serverlessStepFunctions.serverless.service + .provider.compiledCloudFormationTemplate.Resources + .StateMachineBeta2.Type + ).to.equal('AWS::StepFunctions::StateMachine'); + expect(serverlessStepFunctions.serverless.service + .provider.compiledCloudFormationTemplate.Resources + .StateMachineBeta1.Properties.DefinitionString + ).to.equal('"definition1"'); + expect(serverlessStepFunctions.serverless.service + .provider.compiledCloudFormationTemplate.Resources + .StateMachineBeta2.Properties.DefinitionString + ).to.equal('"definition2"'); + expect(serverlessStepFunctions.serverless.service + .provider.compiledCloudFormationTemplate.Resources + .StateMachineBeta1.Properties.RoleArn['Fn::GetAtt'][0] + ).to.equal('IamRoleStateMachineExecution'); + expect(serverlessStepFunctions.serverless.service + .provider.compiledCloudFormationTemplate.Resources + .StateMachineBeta2.Properties.RoleArn['Fn::GetAtt'][0] + ).to.equal('IamRoleStateMachineExecution'); + expect(serverlessStepFunctions.serverless.service + .provider.compiledCloudFormationTemplate.Resources + .StateMachineBeta1.DependsOn + ).to.deep.eq(['IamRoleStateMachineExecution', 'DynamoDBTable']); + expect(serverlessStepFunctions.serverless.service + .provider.compiledCloudFormationTemplate.Resources + .StateMachineBeta2.DependsOn + ).to.deep.eq(['IamRoleStateMachineExecution', 'DynamoDBTable', 'KinesisStream']); + }); + + it('should throw error when dependsOn property is neither string nor [string]', () => { + serverless.service.stepFunctions = { + stateMachines: { + myStateMachine1: { + definition: 'definition1', + name: 'stateMachineBeta1', + dependsOn: { Ref: 'ss' }, + }, + }, + }; + expect(() => serverlessStepFunctions.compileStateMachines()).to.throw(Error); + + serverless.service.stepFunctions = { + stateMachines: { + myStateMachine1: { + definition: 'definition1', + name: 'stateMachineBeta1', + dependsOn: [{ Ref: 'ss' }], + }, + }, + }; + expect(() => serverlessStepFunctions.compileStateMachines()).to.throw(Error); + }); }); From 31efd440a67e92beb3c092cb15609ab91b58cc32 Mon Sep 17 00:00:00 2001 From: Yan Cui Date: Wed, 30 Jan 2019 17:11:16 +0000 Subject: [PATCH 2/3] - updated ReadMe --- README.md | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index aa75b8e1..03f2fdbc 100644 --- a/README.md +++ b/README.md @@ -47,8 +47,7 @@ stepFunctions: Type: Task Resource: arn:aws:lambda:#{AWS::Region}:#{AWS::AccountId}:function:${self:service}-${opt:stage}-hello End: true - dependsOn: - Ref: DynamoDBTable + dependsOn: CustomIamRole hellostepfunc2: definition: StartAt: HelloWorld2 @@ -58,9 +57,9 @@ stepFunctions: Resource: arn:aws:states:#{AWS::Region}:#{AWS::AccountId}:activity:myTask End: true dependsOn: - - Ref: DynamoDBTable - - Ref: KinesisStream - - Ref: CUstomIamRole + - DynamoDBTable + - KinesisStream + - CUstomIamRole activities: - myTask - yourTask From 50dfa5c52140c86bef24b62030832ec7fbf3c1db Mon Sep 17 00:00:00 2001 From: Yan Cui Date: Wed, 30 Jan 2019 20:19:21 +0000 Subject: [PATCH 3/3] - fixed node4/5 error --- lib/deploy/stepFunctions/compileIamRole.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/deploy/stepFunctions/compileIamRole.js b/lib/deploy/stepFunctions/compileIamRole.js index b330a67e..0ae8a1e9 100644 --- a/lib/deploy/stepFunctions/compileIamRole.js +++ b/lib/deploy/stepFunctions/compileIamRole.js @@ -130,7 +130,7 @@ function consolidatePermissionsByAction(permissions) { .mapValues(perms => { // find the unique resources let resources = _.uniqWith(_.flatMap(perms, p => p.resource), _.isEqual); - if (resources.includes('*')) { + if (_.includes(resources, '*')) { resources = '*'; }