Skip to content

Commit 4f35201

Browse files
Merge pull request #1 from horike37/master
merged changes from upstream
2 parents 6915022 + 1b2d788 commit 4f35201

File tree

8 files changed

+200
-32
lines changed

8 files changed

+200
-32
lines changed

.travis.yml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,10 @@ language: node_js
22

33
matrix:
44
include:
5-
- node_js: '4.4'
6-
- node_js: '5.11'
7-
- node_js: '6.2'
85
- node_js: '6.2'
6+
- node_js: '8.9'
7+
- node_js: '10.6'
8+
- node_js: '10.6'
99
env:
1010
- DISABLE_TESTS=true
1111
- LINTING=true
@@ -20,4 +20,4 @@ script:
2020
- if [[ ! -z "$DISABLE_TESTS" && ! -z "$LINTING" ]]; then npm run lint; fi
2121

2222
after_success:
23-
- cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js && rm -rf ./coverage
23+
- cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js && rm -rf ./coverage

README.md

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ stepFunctions:
4747
Type: Task
4848
Resource: arn:aws:lambda:#{AWS::Region}:#{AWS::AccountId}:function:${self:service}-${opt:stage}-hello
4949
End: true
50+
dependsOn: CustomIamRole
5051
hellostepfunc2:
5152
definition:
5253
StartAt: HelloWorld2
@@ -55,6 +56,10 @@ stepFunctions:
5556
Type: Task
5657
Resource: arn:aws:states:#{AWS::Region}:#{AWS::AccountId}:activity:myTask
5758
End: true
59+
dependsOn:
60+
- DynamoDBTable
61+
- KinesisStream
62+
- CUstomIamRole
5863
activities:
5964
- myTask
6065
- yourTask
@@ -110,6 +115,23 @@ plugins:
110115

111116
You can then `Ref: SendMessageStateMachine` in various parts of CloudFormation or serverless.yml
112117

118+
#### Depending on another logical id
119+
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.
120+
121+
This `dependsOn` field can be either a string, or an array of strings.
122+
123+
```yaml
124+
stepFunctions:
125+
stateMachines:
126+
myStateMachine:
127+
dependsOn: myDB
128+
129+
myOtherStateMachine:
130+
dependsOn:
131+
- myOtherDB
132+
- myStream
133+
```
134+
113135
#### Current Gotcha
114136
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.
115137

@@ -329,11 +351,11 @@ stepFunctions:
329351
events:
330352
- http:
331353
path: /users
332-
...
354+
...
333355
authorizer:
334356
# Provide both type and authorizerId
335357
type: COGNITO_USER_POOLS # TOKEN, CUSTOM or COGNITO_USER_POOLS, same as AWS Cloudformation documentation
336-
authorizerId:
358+
authorizerId:
337359
Ref: ApiGatewayAuthorizer # or hard-code Authorizer ID
338360
```
339361

@@ -581,7 +603,7 @@ stepFunctions:
581603
state:
582604
- pending
583605
definition:
584-
...
606+
...
585607
```
586608

587609
## Specifying a Name
@@ -654,7 +676,7 @@ resources:
654676
Resources:
655677
StateMachineRole:
656678
Type: AWS::IAM::Role
657-
Properties:
679+
Properties:
658680
...
659681
```
660682

lib/deploy/stepFunctions/compileIamRole.js

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,7 @@ function consolidatePermissionsByAction(permissions) {
130130
.mapValues(perms => {
131131
// find the unique resources
132132
let resources = _.uniqWith(_.flatMap(perms, p => p.resource), _.isEqual);
133-
if (resources.includes('*')) {
133+
if (_.includes(resources, '*')) {
134134
resources = '*';
135135
}
136136

@@ -198,6 +198,25 @@ function getIamPermissions(serverless, taskStates) {
198198
});
199199
}
200200

201+
function getIamStatements(iamPermissions) {
202+
// when the state machine doesn't define any Task states, and therefore doesn't need ANY
203+
// permission, then we should follow the behaviour of the AWS console and return a policy
204+
// that denies access to EVERYTHING
205+
if (_.isEmpty(iamPermissions)) {
206+
return [{
207+
Effect: 'Deny',
208+
Action: '*',
209+
Resource: '*',
210+
}];
211+
}
212+
213+
return iamPermissions.map(p => ({
214+
Effect: 'Allow',
215+
Action: p.action.split(','),
216+
Resource: p.resource,
217+
}));
218+
}
219+
201220
module.exports = {
202221
compileIamRole() {
203222
const customRolesProvided = [];
@@ -223,11 +242,7 @@ module.exports = {
223242
iamPermissions = consolidatePermissionsByAction(iamPermissions);
224243
iamPermissions = consolidatePermissionsByResource(iamPermissions);
225244

226-
const iamStatements = iamPermissions.map(p => ({
227-
Effect: 'Allow',
228-
Action: p.action.split(','),
229-
Resource: p.resource,
230-
}));
245+
const iamStatements = getIamStatements(iamPermissions);
231246

232247
const iamRoleJson =
233248
iamRoleStateMachineExecutionTemplate

lib/deploy/stepFunctions/compileIamRole.test.js

Lines changed: 44 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,14 @@ describe('#compileIamRole', () => {
2424
serverlessStepFunctions = new ServerlessStepFunctions(serverless, options);
2525
});
2626

27+
const expectDenyAllPolicy = (policy) => {
28+
const statements = policy.PolicyDocument.Statement;
29+
expect(statements).to.have.lengthOf(1);
30+
expect(statements[0].Effect).to.equal('Deny');
31+
expect(statements[0].Action).to.equal('*');
32+
expect(statements[0].Resource).to.equal('*');
33+
};
34+
2735
it('should do nothing when role property exists in all statmachine properties', () => {
2836
serverless.service.stepFunctions = {
2937
stateMachines: {
@@ -243,7 +251,7 @@ describe('#compileIamRole', () => {
243251
const policy = serverlessStepFunctions.serverless.service
244252
.provider.compiledCloudFormationTemplate.Resources.IamRoleStateMachineExecution
245253
.Properties.Policies[0];
246-
expect(policy.PolicyDocument.Statement).to.have.lengthOf(0);
254+
expectDenyAllPolicy(policy);
247255
});
248256

249257
it('should give sqs:SendMessage permission for only SQS referenced by state machine', () => {
@@ -362,7 +370,7 @@ describe('#compileIamRole', () => {
362370
const policy = serverlessStepFunctions.serverless.service
363371
.provider.compiledCloudFormationTemplate.Resources.IamRoleStateMachineExecution
364372
.Properties.Policies[0];
365-
expect(policy.PolicyDocument.Statement).to.have.lengthOf(0);
373+
expectDenyAllPolicy(policy);
366374
});
367375

368376
it('should not give sqs:SendMessage permission if QueueUrl is invalid', () => {
@@ -789,10 +797,10 @@ describe('#compileIamRole', () => {
789797
};
790798

791799
serverlessStepFunctions.compileIamRole();
792-
const statements = serverlessStepFunctions.serverless.service
800+
const policy = serverlessStepFunctions.serverless.service
793801
.provider.compiledCloudFormationTemplate.Resources.IamRoleStateMachineExecution
794-
.Properties.Policies[0].PolicyDocument.Statement;
795-
expect(statements).to.have.lengthOf(0);
802+
.Properties.Policies[0];
803+
expectDenyAllPolicy(policy);
796804
});
797805

798806
it('should not generate any permissions for Task states not yet supported', () => {
@@ -818,9 +826,37 @@ describe('#compileIamRole', () => {
818826
};
819827

820828
serverlessStepFunctions.compileIamRole();
821-
const statements = serverlessStepFunctions.serverless.service
829+
const policy = serverlessStepFunctions.serverless.service
822830
.provider.compiledCloudFormationTemplate.Resources.IamRoleStateMachineExecution
823-
.Properties.Policies[0].PolicyDocument.Statement;
824-
expect(statements).to.have.lengthOf(0);
831+
.Properties.Policies[0];
832+
expectDenyAllPolicy(policy);
833+
});
834+
835+
it('should generate a Deny all statement if state machine has no tasks', () => {
836+
const genStateMachine = (name) => ({
837+
name,
838+
definition: {
839+
StartAt: 'A',
840+
States: {
841+
A: {
842+
Type: 'Pass',
843+
End: true,
844+
},
845+
},
846+
},
847+
});
848+
849+
serverless.service.stepFunctions = {
850+
stateMachines: {
851+
myStateMachine1: genStateMachine('stateMachineBeta1'),
852+
myStateMachine2: genStateMachine('stateMachineBeta2'),
853+
},
854+
};
855+
856+
serverlessStepFunctions.compileIamRole();
857+
const policy = serverlessStepFunctions.serverless.service
858+
.provider.compiledCloudFormationTemplate.Resources.IamRoleStateMachineExecution
859+
.Properties.Policies[0];
860+
expectDenyAllPolicy(policy);
825861
});
826862
});

lib/deploy/stepFunctions/compileStateMachines.js

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ module.exports = {
1515
const stateMachineObj = this.getStateMachine(stateMachineName);
1616
let DefinitionString;
1717
let RoleArn;
18-
let DependsOn;
18+
let DependsOn = [];
1919

2020
if (stateMachineObj.definition) {
2121
if (typeof stateMachineObj.definition === 'string') {
@@ -63,7 +63,24 @@ module.exports = {
6363
'Arn',
6464
],
6565
};
66-
DependsOn = 'IamRoleStateMachineExecution';
66+
DependsOn.push('IamRoleStateMachineExecution');
67+
}
68+
69+
if (stateMachineObj.dependsOn) {
70+
const dependsOn = stateMachineObj.dependsOn;
71+
72+
if (_.isArray(dependsOn) && _.every(dependsOn, _.isString)) {
73+
DependsOn = _.concat(DependsOn, dependsOn);
74+
} else if (_.isString(dependsOn)) {
75+
DependsOn.push(dependsOn);
76+
} else {
77+
const errorMessage = [
78+
`dependsOn property in stateMachine "${stateMachineName}" is neither a string`,
79+
' nor an array of strings',
80+
].join('');
81+
throw new this.serverless.classes
82+
.Error(errorMessage);
83+
}
6784
}
6885

6986
const stateMachineLogicalId = this.getStateMachineLogicalId(stateMachineName,

lib/deploy/stepFunctions/compileStateMachines.test.js

Lines changed: 84 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -64,11 +64,11 @@ describe('#compileStateMachines', () => {
6464
expect(serverlessStepFunctions.serverless.service
6565
.provider.compiledCloudFormationTemplate.Resources
6666
.StateMachineBeta1.DependsOn
67-
).to.equal('IamRoleStateMachineExecution');
67+
).to.deep.eq(['IamRoleStateMachineExecution']);
6868
expect(serverlessStepFunctions.serverless.service
6969
.provider.compiledCloudFormationTemplate.Resources
7070
.StateMachineBeta2.DependsOn
71-
).to.equal('IamRoleStateMachineExecution');
71+
).to.deep.eq(['IamRoleStateMachineExecution']);
7272
expect(serverlessStepFunctions.serverless.service
7373
.provider.compiledCloudFormationTemplate.Outputs
7474
.StateMachineBeta1Arn.Value.Ref
@@ -119,11 +119,11 @@ describe('#compileStateMachines', () => {
119119
expect(serverlessStepFunctions.serverless.service
120120
.provider.compiledCloudFormationTemplate.Resources
121121
.MyStateMachine1StepFunctionsStateMachine.DependsOn
122-
).to.equal('IamRoleStateMachineExecution');
122+
).to.deep.eq(['IamRoleStateMachineExecution']);
123123
expect(serverlessStepFunctions.serverless.service
124124
.provider.compiledCloudFormationTemplate.Resources
125125
.MyStateMachine2StepFunctionsStateMachine.DependsOn
126-
).to.equal('IamRoleStateMachineExecution');
126+
).to.deep.eq(['IamRoleStateMachineExecution']);
127127
expect(serverlessStepFunctions.serverless.service
128128
.provider.compiledCloudFormationTemplate.Outputs
129129
.MyStateMachine1StepFunctionsStateMachineArn.Value.Ref
@@ -176,11 +176,11 @@ describe('#compileStateMachines', () => {
176176
expect(serverlessStepFunctions.serverless.service
177177
.provider.compiledCloudFormationTemplate.Resources
178178
.StateMachineBeta1.DependsOn
179-
).to.equal('IamRoleStateMachineExecution');
179+
).to.deep.eq(['IamRoleStateMachineExecution']);
180180
expect(serverlessStepFunctions.serverless.service
181181
.provider.compiledCloudFormationTemplate.Resources
182182
.StateMachineBeta2.DependsOn
183-
).to.equal('IamRoleStateMachineExecution');
183+
).to.deep.eq(['IamRoleStateMachineExecution']);
184184
});
185185

186186
it('should create corresponding resources when definition and role property are given', () => {
@@ -405,4 +405,82 @@ describe('#compileStateMachines', () => {
405405

406406
expect(actual).to.equal(JSON.stringify(definition, undefined, 2));
407407
});
408+
409+
it('should add dependsOn resources', () => {
410+
serverless.service.stepFunctions = {
411+
stateMachines: {
412+
myStateMachine1: {
413+
definition: 'definition1',
414+
name: 'stateMachineBeta1',
415+
dependsOn: 'DynamoDBTable',
416+
},
417+
myStateMachine2: {
418+
definition: 'definition2',
419+
name: 'stateMachineBeta2',
420+
dependsOn: [
421+
'DynamoDBTable',
422+
'KinesisStream',
423+
],
424+
},
425+
},
426+
};
427+
428+
serverlessStepFunctions.compileStateMachines();
429+
expect(serverlessStepFunctions.serverless.service
430+
.provider.compiledCloudFormationTemplate.Resources
431+
.StateMachineBeta1.Type
432+
).to.equal('AWS::StepFunctions::StateMachine');
433+
expect(serverlessStepFunctions.serverless.service
434+
.provider.compiledCloudFormationTemplate.Resources
435+
.StateMachineBeta2.Type
436+
).to.equal('AWS::StepFunctions::StateMachine');
437+
expect(serverlessStepFunctions.serverless.service
438+
.provider.compiledCloudFormationTemplate.Resources
439+
.StateMachineBeta1.Properties.DefinitionString
440+
).to.equal('"definition1"');
441+
expect(serverlessStepFunctions.serverless.service
442+
.provider.compiledCloudFormationTemplate.Resources
443+
.StateMachineBeta2.Properties.DefinitionString
444+
).to.equal('"definition2"');
445+
expect(serverlessStepFunctions.serverless.service
446+
.provider.compiledCloudFormationTemplate.Resources
447+
.StateMachineBeta1.Properties.RoleArn['Fn::GetAtt'][0]
448+
).to.equal('IamRoleStateMachineExecution');
449+
expect(serverlessStepFunctions.serverless.service
450+
.provider.compiledCloudFormationTemplate.Resources
451+
.StateMachineBeta2.Properties.RoleArn['Fn::GetAtt'][0]
452+
).to.equal('IamRoleStateMachineExecution');
453+
expect(serverlessStepFunctions.serverless.service
454+
.provider.compiledCloudFormationTemplate.Resources
455+
.StateMachineBeta1.DependsOn
456+
).to.deep.eq(['IamRoleStateMachineExecution', 'DynamoDBTable']);
457+
expect(serverlessStepFunctions.serverless.service
458+
.provider.compiledCloudFormationTemplate.Resources
459+
.StateMachineBeta2.DependsOn
460+
).to.deep.eq(['IamRoleStateMachineExecution', 'DynamoDBTable', 'KinesisStream']);
461+
});
462+
463+
it('should throw error when dependsOn property is neither string nor [string]', () => {
464+
serverless.service.stepFunctions = {
465+
stateMachines: {
466+
myStateMachine1: {
467+
definition: 'definition1',
468+
name: 'stateMachineBeta1',
469+
dependsOn: { Ref: 'ss' },
470+
},
471+
},
472+
};
473+
expect(() => serverlessStepFunctions.compileStateMachines()).to.throw(Error);
474+
475+
serverless.service.stepFunctions = {
476+
stateMachines: {
477+
myStateMachine1: {
478+
definition: 'definition1',
479+
name: 'stateMachineBeta1',
480+
dependsOn: [{ Ref: 'ss' }],
481+
},
482+
},
483+
};
484+
expect(() => serverlessStepFunctions.compileStateMachines()).to.throw(Error);
485+
});
408486
});

package-lock.json

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)