In November 2022, AWS unveiled something pretty exciting: the EventBridge Scheduler. This nifty tool is a game-changer for anyone needing to set up events that happen just once or on a regular schedule. Itâs like having a super-smart assistant who never forgets to do your important tasksâwhether thatâs sending out reports every Friday or backing up your data.
Before the EventBridge Scheduler, we had to juggle a bunch of complex setups like cron jobs, delay step functions, and even using DynamoDBâs expiration feature. Too much to do for a simple thing
But now, itâs all about simplicity and clarity.
Want to see how easy it is? Letâs roll up our sleeves and dive into setting up both one-off and recurring tasks using the EventBridge Scheduler, all through the magic of AWS CDK and TypeScript. By the end of this post, youâll see just how effortless and powerful your scheduling can be. Get ready to make your projects run smoother than ever!
So here is basically what we are going to do
- Create lambda functions, which will be invoke by our scheduler
- Now to invoke these lambda function we need to give our schedule permission a. so we will create policy which gives permission to invoke the lambda function b. assign that permission to a IAM role c. then assign that role to our scheduler, so it has the permission
- Then create our scheduler which will basically run recurring events from cdk
- Create a event scheduler creator, which we will be doing using sdk, (because most of you might want to create these schedulers on regular basis on user request, so you will need to know how to create it using SDK based on user need, ex: users asks for remainders, or scheduled email services you can pass the time and payload to the scheduler )
Setting the Stage with Lambda Functions
To kick things off, youâll either need an existing CDK project or the zest to start a fresh one. Our goal here is to set up Lambda functions that will be triggered both as a one-off event and on a recurring schedule.
Hereâs how you can define simple Lambda functions in your project:
- One-off Function: This function logs âone offâ when executed.
`
// ./resources/one-off.ts
export const handler = (payload:string) => {
console.log(JSON.parse(payload))
// do something just once
}
- Recurring Function: This function logs ârecurringâ each time itâs invoked.
// ./resources/recurring.ts
export const handler = (payload:string) => {
console.log(JSON.parse(payload))
// do something multiple times in regular intervals
}
In the CDK stack file located in your lib
directory, instantiate these functions like so:
export class EventbridgeSchedulerStack extends Stack {
constructor(scope: Construct, id: string, props?: StackProps) {
super(scope, id, props);
const recurringLambda = new NodejsFunction(this, 'RecurringLambda', {
entry: './resources/recurring.ts',
handler: 'handler',
runtime: Runtime.NODEJS_16_X,
timeout: Duration.seconds(10),
});
const oneOffLambda = new NodejsFunction(this, 'OneOffLambda', {
entry: './resources/one-off.ts',
handler: 'handler',
runtime: Runtime.NODEJS_16_X,
timeout: Duration.seconds(10),
});
}}
The Art of IAM: Roles and Policies
Hey, so before we dive into building our scheduler, weâve gotta set up a new role. Itâs like giving it a backstage pass so it can call up our Lambda functions when needed. Just use the snippet below to create a role that hooks up with the scheduler.amazonaws.com
service principal. Super straightforward, right? Hereâs how you can do it:
const schedulerRole = new Role(this, 'schedulerRole', {
assumedBy: new ServicePrincipal('scheduler.amazonaws.com'),
});
Next up, we need to whip up a new policy for that role we just created. This is basically giving it the green light to kick off our Lambda functions. Youâll just need to include the ARNs of the functions in the policy.
const invokePolicy = new Policy(this, 'invokeLambdaPolicy', {
document: new PolicyDocument({
statements: [new PolicyStatement({
actions: ['lambda:InvokeFunction'],
resources: [recurringLambda.functionArn, oneOffLambda.functionArn], effect: Effect.ALLOW,
}),],
}),
});
just assign it to the scheduler role
schedulerRole.attachInlinePolicy(invokeLambdaPolicy);
EventBridge Scheduler CDK Construct
Heads up, things might change down the line, but as of now (April 2023), there isnât a fancy L2 construct for the EventBridge Scheduler in the AWS CDK. Thereâs some buzz about it on GitHub, but for the moment, weâre sticking with the basic L1 construct (CfnResource
) to get the job done through the CDK.
If you havenât bumped into the CfnResource
construct before, think of it like a straightforward way to sling some CloudFormation code within the CDK to roll out resources on AWS. Sure, using L2 constructs would be smoother, but just because we donât have one for EventBridge Scheduler doesnât mean we should pass on using the CDK to build out our setup.
// 3. Defining our one-off schedule
new CfnResource(this, 'oneOffSchedule', {
type: 'AWS::Scheduler::Schedule',
properties: {
Name: 'oneOffSchedule',
Description: 'Runs a schedule at a fixed time',
FlexibleTimeWindow: { Mode: 'OFF' },
ActionAfterCompletion: "DELETE",
ScheduleExpression: 'at(2023-04-21T07:20:00)',
Target: {
Arn: oneOffLambda.functionArn,
RoleArn: schedulerRole.roleArn,
},
},
});
// 4. Defining our recurring schedule
new CfnResource(this, 'recurringSchedule', {
type: 'AWS::Scheduler::Schedule',
properties: {
Name: 'recurringSchedule',
Description: 'Runs a schedule for every 5 minutes',
FlexibleTimeWindow: { Mode: 'OFF' },
ScheduleExpression: 'cron(*/5 * * * ? *)',
Target: {
Arn: recurringLambda.functionArn,
RoleArn: schedulerRole.roleArn,
},
},
});
Explanation of each property
- FlexibleTimeWindow -> Here you want give a time window, in which the scheduler can work, basically if you set it 15 mins, then after your timer is hit, it can invoke lambda anytime within next 15 mins, probably you want it to be immediately after the settime is hit in most cases, I set it to âOFFâ
- ScheduleExpression: The expression that defines when the schedule runs. The following formats are supported. [
at
 expression -Âat(yyyy-mm-ddThh:mm:ss)
rate
 expression -Ârate(value unit)
cron
 expression -Âcron(fields)
You can useÂat
 expressions to create one-time schedules that invoke a target once, at the time and in the time zone, that you specify. You can useÂrate
 andÂcron
 expressions to create recurring schedules. Rate-based schedules are useful when you want to invoke a target at regular intervals, such as every 15 minutes or every five days. Cron-based schedules are useful when you want to invoke a target periodically at a specific time, such as at 8:00 am (UTC+0) every 1st day of the month.
AÂ cron
 expression consists of six fields separated by white spaces: (minutes hours day_of_month month day_of_week year)
.
AÂ rate
 expression consists of a value as a positive integer, and a unit with the following options: minute
 | minutes
 | hour
 | hours
 | day
 | days
] from aws-sdk docs
EventBridge Scheduler CDK Construct from Sdk
okay to do this we will need arns of the role, and target lamda, here our target lamda is one-off function
lets just log the arn values after it is deployed, here is how you can do it
new cdk.CfnOutput(this, "SchedulerLambdaArn", {
value: oneOffLambda.functionArn,
exportName: "SchedulerLambdaArn",
});
new cdk.CfnOutput(this, "SchedulerRoleArn", {
value: schedulerRole.roleArn,
exportName: "SchedulerRoleArn",
});
just copy them to your env file and install
pnpm install @aws-sdk/client-scheduler
and then create a client
Small Disclaimer: I am using t3-env library so you will see env. instead of process.env.
export const scheduler = new Scheduler({
region: env.AWS_REGION,
credentials: {
accessKeyId: env.AWS_ACCESS_KEY_ID,
secretAccessKey: env.AWS_SECRET_ACCESS_KEY,
},
});
then give the input and invoke it
const schedularInput = {
FlexibleTimeWindow: {
Mode: "OFF",
},
ActionAfterCompletion: "DELETE",
Target: {
Arn: env.LAMBDA_HIT_ROUTE_ARN,
RoleArn: env.SCHEDULAR_IAM_ROLE_ARN,
Input: JSON.stringify(eventDetail), // pass the a json
},
ScheduleExpression: `at(${formatUTCDate(time)})`,
ScheduleExpressionTimezone: "UTC",
Name: `scheduleforpost${postid}`,
} as CreateScheduleCommandInput;
try {
const res = await scheduler.createSchedule(schedularInput);
return res;
} catch (error) {
console.error("Failed to schedule event:", error);
throw error;
}
here is a function that come in handy
function formatUTCDate(date: Date): string {
const pad = (num: number) => num.toString().padStart(2, "0");
const year = date.getUTCFullYear();
const month = pad(date.getUTCMonth() + 1); // JavaScript months are zero-indexed
const day = pad(date.getUTCDate());
const hours = pad(date.getUTCHours());
const minutes = pad(date.getUTCMinutes());
const seconds = pad(date.getUTCSeconds());
return `${year}-${month}-${day}T${hours}:${minutes}:${seconds}`;
}
Thank you!!!
references: https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/client/scheduler/command/CreateScheduleCommand/