Skip to content

Commit 6bcf499

Browse files
Merge pull request #285 from horike37/feature/express_workflow
Feature/express workflow
2 parents 655c4e8 + a634c73 commit 6bcf499

File tree

7 files changed

+185
-4
lines changed

7 files changed

+185
-4
lines changed

lib/deploy/stepFunctions/compileIamRole.js

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ const BbPromise = require('bluebird');
55
const path = require('path');
66
const { isIntrinsic, translateLocalFunctionNames, trimAliasFromLambdaArn } = require('../../utils/aws');
77

8-
98
function getTaskStates(states) {
109
return _.flatMap(states, (state) => {
1110
switch (state.Type) {
@@ -378,17 +377,29 @@ module.exports = {
378377
compileIamRole() {
379378
const customRolesProvided = [];
380379
let iamPermissions = [];
380+
let hasExpressWorkflow = false;
381381
this.getAllStateMachines().forEach((stateMachineName) => {
382382
const stateMachineObj = this.getStateMachine(stateMachineName);
383383
customRolesProvided.push('role' in stateMachineObj);
384384

385385
const taskStates = getTaskStates(stateMachineObj.definition.States);
386386
iamPermissions = iamPermissions.concat(getIamPermissions.bind(this)(taskStates));
387+
388+
if (stateMachineObj.type === 'EXPRESS') {
389+
hasExpressWorkflow = true;
390+
}
387391
});
388392
if (_.isEqual(_.uniq(customRolesProvided), [true])) {
389393
return BbPromise.resolve();
390394
}
391395

396+
if (hasExpressWorkflow) {
397+
iamPermissions.push({
398+
action: 'logs:CreateLogDelivery,logs:GetLogDelivery,logs:UpdateLogDelivery,logs:DeleteLogDelivery,logs:ListLogDeliveries,logs:PutResourcePolicy,logs:DescribeResourcePolicies,logs:DescribeLogGroups',
399+
resource: '*',
400+
});
401+
}
402+
392403
const iamRoleStateMachineExecutionTemplate = this.serverless.utils.readFileSync(
393404
path.join(__dirname,
394405
'..',

lib/deploy/stepFunctions/compileIamRole.test.js

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1726,4 +1726,51 @@ describe('#compileIamRole', () => {
17261726
];
17271727
expect(lambdaPermissions[0].Resource).to.deep.equal(lambdaArns);
17281728
});
1729+
1730+
it('should give CloudWatch Logs permissions for Express Workflow', () => {
1731+
serverless.service.stepFunctions = {
1732+
stateMachines: {
1733+
myStateMachine1: {
1734+
name: 'stateMachineBeta1',
1735+
type: 'EXPRESS',
1736+
loggingConfig: {
1737+
destinations: [
1738+
{
1739+
'Fn::GetAtt': ['MyLogGroup', 'Arn'],
1740+
},
1741+
],
1742+
},
1743+
definition: {
1744+
StartAt: 'A',
1745+
States: {
1746+
A: {
1747+
Type: 'Task',
1748+
Resource: 'arn:aws:lambda:#{AWS::Region}:#{AWS::AccountId}:function:hello',
1749+
End: true,
1750+
},
1751+
},
1752+
},
1753+
},
1754+
},
1755+
};
1756+
1757+
serverlessStepFunctions.compileIamRole();
1758+
const statements = serverlessStepFunctions.serverless.service
1759+
.provider.compiledCloudFormationTemplate.Resources.IamRoleStateMachineExecution
1760+
.Properties.Policies[0].PolicyDocument.Statement;
1761+
1762+
const logsPermissions = statements.filter(s => s.Action.includes('logs:CreateLogDelivery'));
1763+
expect(logsPermissions).to.have.lengthOf(1);
1764+
expect(logsPermissions[0].Resource).to.equal('*');
1765+
expect(logsPermissions[0].Action).to.deep.equal([
1766+
'logs:CreateLogDelivery',
1767+
'logs:GetLogDelivery',
1768+
'logs:UpdateLogDelivery',
1769+
'logs:DeleteLogDelivery',
1770+
'logs:ListLogDeliveries',
1771+
'logs:PutResourcePolicy',
1772+
'logs:DescribeResourcePolicies',
1773+
'logs:DescribeLogGroups',
1774+
]);
1775+
});
17291776
});

lib/deploy/stepFunctions/compileNotifications.js

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -305,12 +305,20 @@ function* compileResources(stateMachineLogicalId, stateMachineName, notification
305305
}
306306
}
307307

308-
function validateConfig(serverless, stateMachineName, notificationsObj) {
308+
function validateConfig(serverless, stateMachineName, stateMachineObj, notificationsObj) {
309309
// no notifications defined at all
310310
if (!_.isObject(notificationsObj)) {
311311
return false;
312312
}
313313

314+
if (stateMachineObj.type === 'EXPRESS') {
315+
serverless.cli.consoleLog(
316+
`State machine [${stateMachineName}] : notifications are not supported on Express Workflows. `
317+
+ 'Please see https://docs.aws.amazon.com/step-functions/latest/dg/concepts-standard-vs-express.html for difference between Step Functions Standard and Express Workflow.',
318+
);
319+
return false;
320+
}
321+
314322
const { error } = Joi.validate(
315323
notificationsObj, schema, { allowUnknown: false },
316324
);
@@ -335,7 +343,7 @@ module.exports = {
335343
const stateMachineName = stateMachineObj.name || name;
336344
const notificationsObj = stateMachineObj.notifications;
337345

338-
if (!validateConfig(this.serverless, stateMachineName, notificationsObj)) {
346+
if (!validateConfig(this.serverless, stateMachineName, stateMachineObj, notificationsObj)) {
339347
return [];
340348
}
341349

lib/deploy/stepFunctions/compileNotifications.test.js

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -433,4 +433,40 @@ describe('#compileNotifications', () => {
433433
expect(logMessage.startsWith('State machine [Beta1] : notifications config is malformed.'))
434434
.to.equal(true);
435435
});
436+
437+
it('should log the validation errors if state machine is an Express Workflow', () => {
438+
const genStateMachine = name => ({
439+
name,
440+
type: 'EXPRESS',
441+
definition: {
442+
StartAt: 'A',
443+
States: {
444+
A: {
445+
Type: 'Pass',
446+
End: true,
447+
},
448+
},
449+
},
450+
notifications: {
451+
ABORTED: [{ sns: 'SNS_TOPIC_ARN' }],
452+
},
453+
});
454+
455+
serverless.service.stepFunctions = {
456+
stateMachines: {
457+
beta1: genStateMachine('Beta1'),
458+
},
459+
};
460+
461+
serverlessStepFunctions.compileNotifications();
462+
const resources = serverlessStepFunctions.serverless.service
463+
.provider.compiledCloudFormationTemplate.Resources;
464+
expect(_.keys(resources)).to.have.lengthOf(0);
465+
466+
expect(consoleLogSpy.callCount).equal(1);
467+
const { args } = consoleLogSpy.lastCall;
468+
const [logMessage] = args;
469+
expect(logMessage.startsWith('State machine [Beta1] : notifications are not supported on Express Workflows.'))
470+
.to.equal(true);
471+
});
436472
});

lib/deploy/stepFunctions/compileStateMachines.js

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,9 +96,10 @@ module.exports = {
9696
let DefinitionString;
9797
let RoleArn;
9898
let DependsOn = [];
99+
let LoggingConfiguration;
99100
const Tags = toTags(this.serverless.service.provider.tags);
100101

101-
const { error } = Joi.validate(stateMachineObj, schema, { allowUnknown: false });
102+
const { error, value } = Joi.validate(stateMachineObj, schema, { allowUnknown: false });
102103
if (error) {
103104
const errorMessage = `State machine [${stateMachineName}] is malformed. `
104105
+ 'Please check the README for more info. '
@@ -181,6 +182,20 @@ module.exports = {
181182
_.forEach(stateMachineTags, tag => Tags.push(tag));
182183
}
183184

185+
if (value.type === 'EXPRESS' && value.loggingConfig) {
186+
const Destinations = (value.loggingConfig.destinations || [])
187+
.map(arn => ({
188+
CloudWatchLogsLogGroup: {
189+
LogGroupArn: arn,
190+
},
191+
}));
192+
LoggingConfiguration = {
193+
Level: value.loggingConfig.level,
194+
IncludeExecutionData: value.loggingConfig.includeExecutionData,
195+
Destinations,
196+
};
197+
}
198+
184199
const stateMachineLogicalId = this.getStateMachineLogicalId(stateMachineName,
185200
stateMachineObj);
186201
const stateMachineOutputLogicalId = this
@@ -191,6 +206,8 @@ module.exports = {
191206
DefinitionString,
192207
RoleArn,
193208
Tags,
209+
StateMachineType: stateMachineObj.type,
210+
LoggingConfiguration,
194211
},
195212
DependsOn,
196213
};

lib/deploy/stepFunctions/compileStateMachines.schema.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,13 +26,20 @@ const dependsOn = Joi.alternatives().try(
2626
Joi.array().items(Joi.string()),
2727
);
2828

29+
const loggingConfig = Joi.object().keys({
30+
level: Joi.string().valid('ALL', 'ERROR', 'FATAL', 'OFF').default('OFF'),
31+
includeExecutionData: Joi.boolean().default(false),
32+
destinations: Joi.array().items(arn),
33+
});
34+
2935
const id = Joi.string();
3036
const tags = Joi.object();
3137
const name = Joi.string();
3238
const events = Joi.array();
3339
const alarms = Joi.object();
3440
const notifications = Joi.object();
3541
const useExactVersion = Joi.boolean().default(false);
42+
const type = Joi.string().valid('STANDARD', 'EXPRESS').default('STANDARD');
3643

3744
const schema = Joi.object().keys({
3845
id,
@@ -45,6 +52,8 @@ const schema = Joi.object().keys({
4552
tags,
4653
alarms,
4754
notifications,
55+
type,
56+
loggingConfig,
4857
});
4958

5059
module.exports = schema;

lib/deploy/stepFunctions/compileStateMachines.test.js

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1175,4 +1175,57 @@ describe('#compileStateMachines', () => {
11751175
expect(definitionString).to.contain('${AWS::AccountId}');
11761176
expect(definitionString).to.not.contain('#{AWS::AccountId}');
11771177
});
1178+
1179+
it('should compile logging configuration for Express Workflows', () => {
1180+
serverless.service.stepFunctions = {
1181+
stateMachines: {
1182+
myStateMachine1: {
1183+
name: 'stateMachineBeta1',
1184+
type: 'EXPRESS',
1185+
loggingConfig: {
1186+
destinations: [
1187+
{
1188+
'Fn::GetAtt': ['MyLogGroup', 'Arn'],
1189+
},
1190+
],
1191+
},
1192+
definition: {
1193+
StartAt: 'A',
1194+
States: {
1195+
A: {
1196+
Type: 'Task',
1197+
Resource: 'arn:aws:lambda:#{AWS::Region}:#{AWS::AccountId}:function:hello',
1198+
End: true,
1199+
},
1200+
},
1201+
},
1202+
},
1203+
},
1204+
};
1205+
1206+
serverlessStepFunctions.compileStateMachines();
1207+
const actual = serverlessStepFunctions
1208+
.serverless
1209+
.service
1210+
.provider
1211+
.compiledCloudFormationTemplate
1212+
.Resources
1213+
.StateMachineBeta1
1214+
.Properties;
1215+
1216+
expect(actual.StateMachineType).to.equal('EXPRESS');
1217+
expect(actual).to.haveOwnProperty('LoggingConfiguration');
1218+
expect(actual.LoggingConfiguration.Level).to.equal('OFF'); // default value
1219+
expect(actual.LoggingConfiguration.IncludeExecutionData).to.equal(false); // default value
1220+
expect(actual.LoggingConfiguration.Destinations).to.have.length(1);
1221+
1222+
const loggingDestination = actual.LoggingConfiguration.Destinations[0];
1223+
expect(loggingDestination).to.deep.equal({
1224+
CloudWatchLogsLogGroup: {
1225+
LogGroupArn: {
1226+
'Fn::GetAtt': ['MyLogGroup', 'Arn'],
1227+
},
1228+
},
1229+
});
1230+
});
11781231
});

0 commit comments

Comments
 (0)