Skip to content

Commit

Permalink
Merge pull request serverless-operations#164 from theburningmonk/feat…
Browse files Browse the repository at this point in the history
…ure/support_dependsOn

add support for dependsOn option
  • Loading branch information
horike37 committed Jan 31, 2019
2 parents d986c56 + fdfe30c commit 14f8d4e
Show file tree
Hide file tree
Showing 4 changed files with 130 additions and 13 deletions.
30 changes: 26 additions & 4 deletions README.md
Expand Up @@ -47,6 +47,7 @@ stepFunctions:
Type: Task
Resource: arn:aws:lambda:#{AWS::Region}:#{AWS::AccountId}:function:${self:service}-${opt:stage}-hello
End: true
dependsOn: CustomIamRole
hellostepfunc2:
definition:
StartAt: HelloWorld2
Expand All @@ -55,6 +56,10 @@ stepFunctions:
Type: Task
Resource: arn:aws:states:#{AWS::Region}:#{AWS::AccountId}:activity:myTask
End: true
dependsOn:
- DynamoDBTable
- KinesisStream
- CUstomIamRole
activities:
- myTask
- yourTask
Expand Down Expand Up @@ -110,6 +115,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.

Expand Down Expand Up @@ -329,11 +351,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
```

Expand Down Expand Up @@ -581,7 +603,7 @@ stepFunctions:
state:
- pending
definition:
...
...
```

## Specifying a Name
Expand Down Expand Up @@ -654,7 +676,7 @@ resources:
Resources:
StateMachineRole:
Type: AWS::IAM::Role
Properties:
Properties:
...
```

Expand Down
2 changes: 1 addition & 1 deletion lib/deploy/stepFunctions/compileIamRole.js
Expand Up @@ -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 = '*';
}

Expand Down
21 changes: 19 additions & 2 deletions lib/deploy/stepFunctions/compileStateMachines.js
Expand Up @@ -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') {
Expand Down Expand Up @@ -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,
Expand Down
90 changes: 84 additions & 6 deletions lib/deploy/stepFunctions/compileStateMachines.test.js
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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', () => {
Expand Down Expand Up @@ -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);
});
});

0 comments on commit 14f8d4e

Please sign in to comment.