Skip to content

Commit e1dc3b8

Browse files
feat: capture state machine schema explicitly
1 parent 2c9a17c commit e1dc3b8

File tree

3 files changed

+62
-53
lines changed

3 files changed

+62
-53
lines changed

lib/deploy/stepFunctions/compileStateMachines.js

Lines changed: 18 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
'use strict';
22

33
const _ = require('lodash');
4-
const BbPromise = require('bluebird');
4+
const Joi = require('@hapi/joi');
55
const Chance = require('chance');
6+
const BbPromise = require('bluebird');
7+
const schema = require('./compileStateMachines.schema');
68
const { isIntrinsic, translateLocalFunctionNames } = require('../../utils/aws');
79

810
const chance = new Chance();
@@ -14,22 +16,14 @@ function randomName() {
1416
});
1517
}
1618

17-
function toTags(obj, serverless) {
19+
function toTags(obj) {
1820
const tags = [];
1921

2022
if (!obj) {
2123
return tags;
2224
}
2325

24-
if (_.isPlainObject(obj)) {
25-
_.forEach(
26-
obj,
27-
(Value, Key) => tags.push({ Key, Value: Value.toString() }),
28-
);
29-
} else {
30-
throw new serverless.classes
31-
.Error('Unable to parse tags, it should be an object.');
32-
}
26+
_.forEach(obj, (Value, Key) => tags.push({ Key, Value: Value.toString() }));
3327

3428
return tags;
3529
}
@@ -75,7 +69,15 @@ module.exports = {
7569
let DefinitionString;
7670
let RoleArn;
7771
let DependsOn = [];
78-
const Tags = toTags(this.serverless.service.provider.tags, this.serverless);
72+
const Tags = toTags(this.serverless.service.provider.tags);
73+
74+
const { error } = Joi.validate(stateMachineObj, schema, { allowUnknown: false });
75+
if (error) {
76+
const errorMessage = `State machine [${stateMachineName}] is malformed. `
77+
+ 'Please check the README for more info. '
78+
+ `${error}`;
79+
throw new this.serverless.classes.Error(errorMessage);
80+
}
7981

8082
if (stateMachineObj.definition) {
8183
if (typeof stateMachineObj.definition === 'string') {
@@ -98,38 +100,10 @@ module.exports = {
98100
};
99101
}
100102
}
101-
} else {
102-
const errorMessage = [
103-
`Missing "definition" property in stateMachine ${stateMachineName}`,
104-
' Please check the README for more info.',
105-
].join('');
106-
throw new this.serverless.classes
107-
.Error(errorMessage);
108103
}
109104

110105
if (stateMachineObj.role) {
111-
if (typeof stateMachineObj.role === 'string') {
112-
if (stateMachineObj.role.startsWith('arn:aws')) {
113-
RoleArn = stateMachineObj.role;
114-
} else {
115-
const errorMessage = [
116-
`role property in stateMachine "${stateMachineName}" is not ARN`,
117-
' Please check the README for more info.',
118-
].join('');
119-
throw new this.serverless.classes
120-
.Error(errorMessage);
121-
}
122-
} else if (isIntrinsic(stateMachineObj.role)) {
123-
RoleArn = stateMachineObj.role;
124-
} else {
125-
const errorMessage = [
126-
`role property in stateMachine "${stateMachineName}" is neither a string`,
127-
' nor a CloudFormation intrinsic function',
128-
' Please check the README for more info.',
129-
].join('');
130-
throw new this.serverless.classes
131-
.Error(errorMessage);
132-
}
106+
RoleArn = stateMachineObj.role;
133107
} else {
134108
RoleArn = {
135109
'Fn::GetAtt': [
@@ -143,22 +117,15 @@ module.exports = {
143117
if (stateMachineObj.dependsOn) {
144118
const dependsOn = stateMachineObj.dependsOn;
145119

146-
if (_.isArray(dependsOn) && _.every(dependsOn, _.isString)) {
120+
if (_.isArray(dependsOn)) {
147121
DependsOn = _.concat(DependsOn, dependsOn);
148-
} else if (_.isString(dependsOn)) {
149-
DependsOn.push(dependsOn);
150122
} else {
151-
const errorMessage = [
152-
`dependsOn property in stateMachine "${stateMachineName}" is neither a string`,
153-
' nor an array of strings',
154-
].join('');
155-
throw new this.serverless.classes
156-
.Error(errorMessage);
123+
DependsOn.push(dependsOn);
157124
}
158125
}
159126

160127
if (stateMachineObj.tags) {
161-
const stateMachineTags = toTags(stateMachineObj.tags, this.serverless);
128+
const stateMachineTags = toTags(stateMachineObj.tags);
162129
_.forEach(stateMachineTags, tag => Tags.push(tag));
163130
}
164131

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
const Joi = require('@hapi/joi');
2+
3+
const arn = Joi.alternatives().try(
4+
Joi.string().regex(/^arn:aws/, 'ARN'),
5+
Joi.object().keys({
6+
Ref: Joi.string(),
7+
}),
8+
Joi.object().keys({
9+
'Fn::GetAtt': Joi.array().items(Joi.string()),
10+
}),
11+
);
12+
13+
const definition = Joi.alternatives().try(
14+
Joi.string(),
15+
Joi.object(),
16+
);
17+
18+
const dependsOn = Joi.alternatives().try(
19+
Joi.string(),
20+
Joi.array().items(Joi.string()),
21+
);
22+
23+
const id = Joi.string();
24+
const tags = Joi.object();
25+
const name = Joi.string();
26+
const events = Joi.array();
27+
const alarms = Joi.object();
28+
const notifications = Joi.object();
29+
30+
const schema = Joi.object().keys({
31+
id,
32+
events,
33+
name,
34+
role: arn,
35+
definition: definition.required(),
36+
dependsOn,
37+
tags,
38+
alarms,
39+
notifications,
40+
});
41+
42+
module.exports = schema;

lib/deploy/stepFunctions/compileStateMachines.test.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -204,7 +204,7 @@ describe('#compileStateMachines', () => {
204204
myStateMachine1: {
205205
name: 'stateMachineWithIntrinsicRole1',
206206
definition: 'definition1\n',
207-
role: { 'Fn::Attr': ['RoleID', 'Arn'] },
207+
role: { 'Fn::GetAtt': ['RoleID', 'Arn'] },
208208
},
209209
myStateMachine2: {
210210
name: 'stateMachineWithIntrinsicRole2',
@@ -216,7 +216,7 @@ describe('#compileStateMachines', () => {
216216
serverlessStepFunctions.compileStateMachines();
217217
expect(serverlessStepFunctions.serverless.service
218218
.provider.compiledCloudFormationTemplate.Resources
219-
.StateMachineWithIntrinsicRole1.Properties.RoleArn).to.deep.equal({ 'Fn::Attr': ['RoleID', 'Arn'] });
219+
.StateMachineWithIntrinsicRole1.Properties.RoleArn).to.deep.equal({ 'Fn::GetAtt': ['RoleID', 'Arn'] });
220220
expect(serverlessStepFunctions.serverless.service
221221
.provider.compiledCloudFormationTemplate.Resources
222222
.StateMachineWithIntrinsicRole2.Properties.RoleArn).to.deep.equal({ Ref: 'CloudformationId' });

0 commit comments

Comments
 (0)