Skip to content

Commit ce5261a

Browse files
authored
Merge pull request #580 from distinction-dev/feat-eventbridge-scheduler
Create Schedules using Eventbridge Scheduler
2 parents 045b2e5 + 5514c54 commit ce5261a

File tree

3 files changed

+187
-10
lines changed

3 files changed

+187
-10
lines changed

Diff for: lib/deploy/events/schedule/compileScheduledEvents.js

+98-10
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
const _ = require('lodash');
44
const BbPromise = require('bluebird');
55

6+
const METHOD_SCHEDULER = 'scheduler';
7+
const METHOD_EVENT_BUS = 'eventBus';
68
module.exports = {
79
compileScheduledEvents() {
810
const service = this.serverless.service;
@@ -24,6 +26,8 @@ module.exports = {
2426
let InputPathsMap;
2527
let Name;
2628
let Description;
29+
let method;
30+
let timezone;
2731

2832
// TODO validate rate syntax
2933
if (typeof event.schedule === 'object') {
@@ -49,6 +53,8 @@ module.exports = {
4953
InputTemplate = InputTransformer && event.schedule.inputTransformer.inputTemplate;
5054
Name = event.schedule.name;
5155
Description = event.schedule.description;
56+
method = event.schedule.method || METHOD_EVENT_BUS;
57+
timezone = event.schedule.timezone;
5258

5359
if ([Input, InputPath, InputTransformer].filter(Boolean).length > 1) {
5460
const errorMessage = [
@@ -76,6 +82,29 @@ module.exports = {
7682
// escape quotes to favor JSON.parse
7783
InputTemplate = InputTemplate.replace(/\"/g, '\\"'); // eslint-disable-line
7884
}
85+
if (InputTransformer) {
86+
if (method === METHOD_SCHEDULER) {
87+
const errorMessage = [
88+
'Cannot setup "schedule" event: "inputTransformer" is not supported with "scheduler" mode',
89+
].join('');
90+
throw new this.serverless.classes
91+
.Error(errorMessage);
92+
}
93+
}
94+
if (InputPath && method === METHOD_SCHEDULER) {
95+
const errorMessage = [
96+
'Cannot setup "schedule" event: "inputPath" is not supported with "scheduler" mode',
97+
].join('');
98+
throw new this.serverless.classes
99+
.Error(errorMessage);
100+
}
101+
if (timezone && method !== METHOD_SCHEDULER) {
102+
const errorMessage = [
103+
'Cannot setup "schedule" event: "timezone" is only supported with "scheduler" mode',
104+
].join('');
105+
throw new this.serverless.classes
106+
.Error(errorMessage);
107+
}
79108
} else if (typeof event.schedule === 'string') {
80109
ScheduleExpression = event.schedule;
81110
State = 'ENABLED';
@@ -92,8 +121,9 @@ module.exports = {
92121

93122
const stateMachineLogicalId = this
94123
.getStateMachineLogicalId(stateMachineName, stateMachineObj);
95-
const scheduleLogicalId = this
96-
.getScheduleLogicalId(stateMachineName, scheduleNumberInFunction);
124+
const scheduleLogicalId = method !== METHOD_SCHEDULER ? this
125+
.getScheduleLogicalId(stateMachineName, scheduleNumberInFunction) : this
126+
.getSchedulerScheduleLogicalId(stateMachineName, scheduleNumberInFunction);
97127
const scheduleIamRoleLogicalId = this
98128
.getScheduleToStepFunctionsIamRoleLogicalId(stateMachineName);
99129
const scheduleId = this.getScheduleId(stateMachineName);
@@ -110,8 +140,69 @@ module.exports = {
110140
}
111141
`;
112142

113-
const scheduleTemplate = `
114-
{
143+
let scheduleTemplate;
144+
let iamRoleTemplate;
145+
// If condition for the event bridge schedular and it define
146+
// resource template and iamrole for the same
147+
if (method === METHOD_SCHEDULER) {
148+
scheduleTemplate = `{
149+
"Type": "AWS::Scheduler::Schedule",
150+
"Properties": {
151+
"ScheduleExpression": "${ScheduleExpression}",
152+
"State": "${State}",
153+
${timezone ? `"ScheduleExpressionTimezone": "${timezone}",` : ''}
154+
${Name ? `"Name": "${Name}",` : ''}
155+
${Description ? `"Description": "${Description}",` : ''}
156+
"Target": {
157+
"Arn": { "Ref": "${stateMachineLogicalId}" },
158+
"RoleArn": ${roleArn}
159+
},
160+
"FlexibleTimeWindow": {
161+
"Mode": "OFF"
162+
}
163+
}
164+
}`;
165+
166+
iamRoleTemplate = `{
167+
"Type": "AWS::IAM::Role",
168+
"Properties": {
169+
"AssumeRolePolicyDocument": {
170+
"Version": "2012-10-17",
171+
"Statement": [
172+
{
173+
"Effect": "Allow",
174+
"Principal": {
175+
"Service": "scheduler.amazonaws.com"
176+
},
177+
"Action": "sts:AssumeRole"
178+
}
179+
]
180+
},
181+
"Policies": [
182+
{
183+
"PolicyName": "${policyName}",
184+
"PolicyDocument": {
185+
"Version": "2012-10-17",
186+
"Statement": [
187+
{
188+
"Effect": "Allow",
189+
"Action": [
190+
"states:StartExecution"
191+
],
192+
"Resource": {
193+
"Ref": "${stateMachineLogicalId}"
194+
}
195+
}
196+
]
197+
}
198+
}
199+
]
200+
}
201+
}`;
202+
} else {
203+
// else condition for the event rule and
204+
// it define resource template and iamrole for the same
205+
scheduleTemplate = `{
115206
"Type": "AWS::Events::Rule",
116207
"Properties": {
117208
"ScheduleExpression": "${ScheduleExpression}",
@@ -130,11 +221,8 @@ module.exports = {
130221
"RoleArn": ${roleArn}
131222
}]
132223
}
133-
}
134-
`;
135-
136-
let iamRoleTemplate = `
137-
{
224+
}`;
225+
iamRoleTemplate = `{
138226
"Type": "AWS::IAM::Role",
139227
"Properties": {
140228
"AssumeRolePolicyDocument": {
@@ -169,8 +257,8 @@ module.exports = {
169257
}
170258
]
171259
}
260+
}`;
172261
}
173-
`;
174262
if (permissionsBoundary) {
175263
const jsonIamRole = JSON.parse(iamRoleTemplate);
176264
jsonIamRole.Properties.PermissionsBoundary = permissionsBoundary;

Diff for: lib/deploy/events/schedule/compileScheduledEvents.test.js

+83
Original file line numberDiff line numberDiff line change
@@ -447,4 +447,87 @@ describe('#httpValidate()', () => {
447447
.FirstScheduleToStepFunctionsRole
448448
.Properties.PermissionsBoundary).to.equal('arn:aws:iam::myAccount:policy/permission_boundary');
449449
});
450+
451+
it('should have type of AWS::Scheduler::Schedule if method is scheduler', () => {
452+
serverlessStepFunctions.serverless.service.stepFunctions = {
453+
stateMachines: {
454+
first: {
455+
events: [
456+
{
457+
schedule: {
458+
method: 'scheduler',
459+
rate: 'rate(10 minutes)',
460+
enabled: false,
461+
timezone: 'Asia/Mumbai',
462+
},
463+
},
464+
],
465+
},
466+
},
467+
};
468+
serverlessStepFunctions.compileScheduledEvents();
469+
expect(serverlessStepFunctions.serverless.service.provider.compiledCloudFormationTemplate.Resources.FirstStepFunctionsSchedulerSchedule1.Type).to.equal('AWS::Scheduler::Schedule');
470+
});
471+
472+
it('should have service as scheduler.amazonaws.com if method is scheduler', () => {
473+
serverlessStepFunctions.serverless.service.stepFunctions = {
474+
stateMachines: {
475+
first: {
476+
events: [
477+
{
478+
schedule: {
479+
method: 'scheduler',
480+
rate: 'rate(10 minutes)',
481+
enabled: false,
482+
timezone: 'Asia/Mumbai',
483+
},
484+
},
485+
],
486+
},
487+
},
488+
};
489+
serverlessStepFunctions.compileScheduledEvents();
490+
expect(serverlessStepFunctions.serverless.service.provider.compiledCloudFormationTemplate.Resources.FirstScheduleToStepFunctionsRole.Properties.AssumeRolePolicyDocument.Statement[0].Principal.Service).to.equal('scheduler.amazonaws.com');
491+
});
492+
493+
it('should define timezone when schedular and timezone given', () => {
494+
serverlessStepFunctions.serverless.service.stepFunctions = {
495+
stateMachines: {
496+
first: {
497+
events: [
498+
{
499+
schedule: {
500+
method: 'scheduler',
501+
rate: 'rate(10 minutes)',
502+
enabled: false,
503+
timezone: 'Asia/Mumbai',
504+
},
505+
},
506+
],
507+
},
508+
},
509+
};
510+
serverlessStepFunctions.compileScheduledEvents();
511+
512+
expect(serverlessStepFunctions.serverless.service.provider.compiledCloudFormationTemplate.Resources.FirstStepFunctionsSchedulerSchedule1.Properties.ScheduleExpressionTimezone).to.equal('Asia/Mumbai');
513+
});
514+
515+
it('should accept timezone only if method is scheduler', () => {
516+
serverlessStepFunctions.serverless.service.stepFunctions = {
517+
stateMachines: {
518+
first: {
519+
events: [
520+
{
521+
schedule: {
522+
rate: 'rate(10 minutes)',
523+
enabled: false,
524+
timezone: 'Asia/Mumbai',
525+
},
526+
},
527+
],
528+
},
529+
},
530+
};
531+
expect(() => serverlessStepFunctions.compileScheduledEvents()).to.throw(Error);
532+
});
450533
});

Diff for: lib/naming.js

+6
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,12 @@ module.exports = {
6363
.getNormalizedFunctionName(stateMachineName)}StepFunctionsEventsRuleSchedule${scheduleIndex}`;
6464
},
6565

66+
getSchedulerScheduleLogicalId(stateMachineName, scheduleIndex) {
67+
return `${this.provider.naming.getNormalizedFunctionName(
68+
stateMachineName,
69+
)}StepFunctionsSchedulerSchedule${scheduleIndex}`;
70+
},
71+
6672
getScheduleToStepFunctionsIamRoleLogicalId(stateMachineName) {
6773
return `${this.provider.naming.getNormalizedFunctionName(
6874
stateMachineName,

0 commit comments

Comments
 (0)