diff --git a/packages/@aws-cdk/aws-scheduler-alpha/README.md b/packages/@aws-cdk/aws-scheduler-alpha/README.md index f4840acc8362c..2685e57c6715e 100644 --- a/packages/@aws-cdk/aws-scheduler-alpha/README.md +++ b/packages/@aws-cdk/aws-scheduler-alpha/README.md @@ -23,7 +23,7 @@ of millions of tasks across many AWS services without provisioning or managing u 2. **Targets**: A target is an API operation that EventBridge Scheduler calls on your behalf every time your schedule runs. EventBridge Scheduler supports two types of targets: templated targets and universal targets. Templated targets invoke common API operations across a core groups of services. For example, EventBridge Scheduler supports templated targets for invoking AWS Lambda Function or starting execution of Step Function state -machine. For API operations that are not supported by templated targets you can use customizeable universal targets. Universal targets support calling +machine. For API operations that are not supported by templated targets you can use customizable universal targets. Universal targets support calling more than 6,000 API operations across over 270 AWS services. 3. **Schedule Group**: A schedule group is an Amazon EventBridge Scheduler resource that you use to organize your schedules. Your AWS account comes with a default scheduler group. A new schedule will always be added to a scheduling group. If you do not provide a scheduling group to add to, it @@ -157,7 +157,7 @@ new Schedule(this, 'Schedule', { The `@aws-cdk/aws-scheduler-targets-alpha` module includes classes that implement the `IScheduleTarget` interface for various AWS services. EventBridge Scheduler supports two types of targets: templated targets invoke common API -operations across a core groups of services, and customizeable universal targets that you can use to call more +operations across a core groups of services, and customizable universal targets that you can use to call more than 6,000 operations across over 270 services. A list of supported targets can be found at `@aws-cdk/aws-scheduler-targets-alpha`. ### Input @@ -241,6 +241,23 @@ const schedule = new Schedule(this, 'Schedule', { > Visit [Data protection in Amazon EventBridge Scheduler](https://docs.aws.amazon.com/scheduler/latest/UserGuide/data-protection.html) for more details. +## Configuring flexible time windows + +You can configure flexible time windows by specifying the `timeWindow` property. +Flexible time windows is disabled by default. + +```ts +declare const target: targets.LambdaInvoke; + +const schedule = new Schedule(this, 'Schedule', { + schedule: ScheduleExpression.rate(Duration.hours(12)), + target, + timeWindow: TimeWindow.flexible(Duration.hours(10)), +}); +``` + +> Visit [Configuring flexible time windows](https://docs.aws.amazon.com/scheduler/latest/UserGuide/managing-schedule-flexible-time-windows.html) for more details. + ## Error-handling You can configure how your schedule handles failures, when EventBridge Scheduler is unable to deliver an event diff --git a/packages/@aws-cdk/aws-scheduler-alpha/lib/schedule.ts b/packages/@aws-cdk/aws-scheduler-alpha/lib/schedule.ts index 8fefbe068a5b5..52e22ac5e0495 100644 --- a/packages/@aws-cdk/aws-scheduler-alpha/lib/schedule.ts +++ b/packages/@aws-cdk/aws-scheduler-alpha/lib/schedule.ts @@ -59,6 +59,47 @@ export interface ScheduleTargetProps { readonly retryAttempts?: number; } +/** + * A time window during which EventBridge Scheduler invokes the schedule. + */ +export class TimeWindow { + /** + * TimeWindow is disabled. + */ + public static off(): TimeWindow { + return new TimeWindow('OFF'); + } + + /** + * TimeWindow is enabled. + */ + public static flexible(maxWindow: Duration): TimeWindow { + if (maxWindow.toMinutes() < 1 || maxWindow.toMinutes() > 1440) { + throw new Error(`The provided duration must be between 1 minute and 1440 minutes, got ${maxWindow.toMinutes()}`); + } + return new TimeWindow('FLEXIBLE', maxWindow); + } + + /** + * Determines whether the schedule is invoked within a flexible time window. + */ + public readonly mode: 'OFF' | 'FLEXIBLE'; + + /** + * The maximum time window during which the schedule can be invoked. + * + * Must be between 1 to 1440 minutes. + * + * @default - no value + */ + public readonly maxWindow?: Duration; + + private constructor(mode: 'OFF' | 'FLEXIBLE', maxWindow?: Duration) { + this.mode = mode; + this.maxWindow = maxWindow; + } +} + /** * Construction properties for `Schedule`. */ @@ -104,6 +145,7 @@ export interface ScheduleProps { /** * Indicates whether the schedule is enabled. + * * @default true */ readonly enabled?: boolean; @@ -115,6 +157,15 @@ export interface ScheduleProps { */ readonly key?: kms.IKey; + /** + * A time window during which EventBridge Scheduler invokes the schedule. + * + * @see https://docs.aws.amazon.com/scheduler/latest/UserGuide/managing-schedule-flexible-time-windows.html + * + * @default TimeWindow.off() + */ + readonly timeWindow?: TimeWindow; + /** * The date, in UTC, after which the schedule can begin invoking its target. * EventBridge Scheduler ignores start for one-time schedules. @@ -270,11 +321,16 @@ export class Schedule extends Resource implements ISchedule { this.retryPolicy = targetConfig.retryPolicy; + const flexibleTimeWindow = props.timeWindow ?? TimeWindow.off(); + this.validateTimeFrame(props.start, props.end); const resource = new CfnSchedule(this, 'Resource', { name: this.physicalName, - flexibleTimeWindow: { mode: 'OFF' }, + flexibleTimeWindow: { + mode: flexibleTimeWindow.mode, + maximumWindowInMinutes: flexibleTimeWindow.maxWindow?.toMinutes(), + }, scheduleExpression: props.schedule.expressionString, scheduleExpressionTimezone: props.schedule.timeZone?.timezoneName, groupName: this.group?.groupName, diff --git a/packages/@aws-cdk/aws-scheduler-alpha/rosetta/default.ts-fixture b/packages/@aws-cdk/aws-scheduler-alpha/rosetta/default.ts-fixture index cbb0128852cb7..4c66c83a7dd44 100644 --- a/packages/@aws-cdk/aws-scheduler-alpha/rosetta/default.ts-fixture +++ b/packages/@aws-cdk/aws-scheduler-alpha/rosetta/default.ts-fixture @@ -8,7 +8,7 @@ import * as sqs from 'aws-cdk-lib/aws-sqs'; import * as cloudwatch from 'aws-cdk-lib/aws-cloudwatch'; import * as targets from '@aws-cdk/aws-scheduler-targets-alpha'; import { App, Stack, TimeZone, Duration } from 'aws-cdk-lib'; -import { ScheduleExpression, ScheduleTargetInput, ContextAttribute, Group, Schedule } from '@aws-cdk/aws-scheduler-alpha'; +import { ScheduleExpression, ScheduleTargetInput, ContextAttribute, Group, Schedule, TimeWindow } from '@aws-cdk/aws-scheduler-alpha'; class Fixture extends cdk.Stack { constructor(scope: Construct, id: string) { diff --git a/packages/@aws-cdk/aws-scheduler-alpha/test/integ.schedule.js.snapshot/aws-cdk-scheduler-schedule.assets.json b/packages/@aws-cdk/aws-scheduler-alpha/test/integ.schedule.js.snapshot/aws-cdk-scheduler-schedule.assets.json index 03dd7cb96ad07..eb0c8d4f91218 100644 --- a/packages/@aws-cdk/aws-scheduler-alpha/test/integ.schedule.js.snapshot/aws-cdk-scheduler-schedule.assets.json +++ b/packages/@aws-cdk/aws-scheduler-alpha/test/integ.schedule.js.snapshot/aws-cdk-scheduler-schedule.assets.json @@ -1,7 +1,7 @@ { "version": "35.0.0", "files": { - "77d06d03c78dc7776966b7c7ee414cc19be012ccfba5d7a9b1e425718920ab3e": { + "7c2f669a6fc34b993d394fcc754d832f1fb720665c9db5146c9856a595ddaac2": { "source": { "path": "aws-cdk-scheduler-schedule.template.json", "packaging": "file" @@ -9,7 +9,7 @@ "destinations": { "current_account-current_region": { "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", - "objectKey": "77d06d03c78dc7776966b7c7ee414cc19be012ccfba5d7a9b1e425718920ab3e.json", + "objectKey": "7c2f669a6fc34b993d394fcc754d832f1fb720665c9db5146c9856a595ddaac2.json", "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}" } } diff --git a/packages/@aws-cdk/aws-scheduler-alpha/test/integ.schedule.js.snapshot/aws-cdk-scheduler-schedule.template.json b/packages/@aws-cdk/aws-scheduler-alpha/test/integ.schedule.js.snapshot/aws-cdk-scheduler-schedule.template.json index 5cdf90fef2ef6..f3d2bc6233f52 100644 --- a/packages/@aws-cdk/aws-scheduler-alpha/test/integ.schedule.js.snapshot/aws-cdk-scheduler-schedule.template.json +++ b/packages/@aws-cdk/aws-scheduler-alpha/test/integ.schedule.js.snapshot/aws-cdk-scheduler-schedule.template.json @@ -349,6 +349,37 @@ } } }, + "UseFlexibleTimeWindowBF55D3ED": { + "Type": "AWS::Scheduler::Schedule", + "Properties": { + "FlexibleTimeWindow": { + "MaximumWindowInMinutes": 10, + "Mode": "FLEXIBLE" + }, + "ScheduleExpression": "rate(12 hours)", + "ScheduleExpressionTimezone": "Etc/UTC", + "State": "ENABLED", + "Target": { + "Arn": { + "Fn::GetAtt": [ + "Function76856677", + "Arn" + ] + }, + "Input": "\"Input Text\"", + "RetryPolicy": { + "MaximumEventAgeInSeconds": 180, + "MaximumRetryAttempts": 3 + }, + "RoleArn": { + "Fn::GetAtt": [ + "Role1ABCC5F0", + "Arn" + ] + } + } + } + }, "ScheduleWithTimeFrameC1C8BDCC": { "Type": "AWS::Scheduler::Schedule", "Properties": { diff --git a/packages/@aws-cdk/aws-scheduler-alpha/test/integ.schedule.js.snapshot/manifest.json b/packages/@aws-cdk/aws-scheduler-alpha/test/integ.schedule.js.snapshot/manifest.json index 4fbc5e0eb40aa..836a18c963b27 100644 --- a/packages/@aws-cdk/aws-scheduler-alpha/test/integ.schedule.js.snapshot/manifest.json +++ b/packages/@aws-cdk/aws-scheduler-alpha/test/integ.schedule.js.snapshot/manifest.json @@ -18,7 +18,7 @@ "validateOnSynth": false, "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-deploy-role-${AWS::AccountId}-${AWS::Region}", "cloudFormationExecutionRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-cfn-exec-role-${AWS::AccountId}-${AWS::Region}", - "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/77d06d03c78dc7776966b7c7ee414cc19be012ccfba5d7a9b1e425718920ab3e.json", + "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/7c2f669a6fc34b993d394fcc754d832f1fb720665c9db5146c9856a595ddaac2.json", "requiresBootstrapStackVersion": 6, "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version", "additionalDependencies": [ @@ -118,6 +118,12 @@ "data": "CustomerKmsSchedule12B1FEFE" } ], + "/aws-cdk-scheduler-schedule/UseFlexibleTimeWindow/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "UseFlexibleTimeWindowBF55D3ED" + } + ], "/aws-cdk-scheduler-schedule/ScheduleWithTimeFrame/Resource": [ { "type": "aws:cdk:logicalId", diff --git a/packages/@aws-cdk/aws-scheduler-alpha/test/integ.schedule.js.snapshot/tree.json b/packages/@aws-cdk/aws-scheduler-alpha/test/integ.schedule.js.snapshot/tree.json index d99f1d9237516..d903ee961eaa2 100644 --- a/packages/@aws-cdk/aws-scheduler-alpha/test/integ.schedule.js.snapshot/tree.json +++ b/packages/@aws-cdk/aws-scheduler-alpha/test/integ.schedule.js.snapshot/tree.json @@ -616,6 +616,55 @@ "version": "0.0.0" } }, + "UseFlexibleTimeWindow": { + "id": "UseFlexibleTimeWindow", + "path": "aws-cdk-scheduler-schedule/UseFlexibleTimeWindow", + "children": { + "Resource": { + "id": "Resource", + "path": "aws-cdk-scheduler-schedule/UseFlexibleTimeWindow/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::Scheduler::Schedule", + "aws:cdk:cloudformation:props": { + "flexibleTimeWindow": { + "mode": "FLEXIBLE", + "maximumWindowInMinutes": 10 + }, + "scheduleExpression": "rate(12 hours)", + "scheduleExpressionTimezone": "Etc/UTC", + "state": "ENABLED", + "target": { + "arn": { + "Fn::GetAtt": [ + "Function76856677", + "Arn" + ] + }, + "roleArn": { + "Fn::GetAtt": [ + "Role1ABCC5F0", + "Arn" + ] + }, + "input": "\"Input Text\"", + "retryPolicy": { + "maximumEventAgeInSeconds": 180, + "maximumRetryAttempts": 3 + } + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_scheduler.CfnSchedule", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-scheduler-alpha.Schedule", + "version": "0.0.0" + } + }, "ScheduleWithTimeFrame": { "id": "ScheduleWithTimeFrame", "path": "aws-cdk-scheduler-schedule/ScheduleWithTimeFrame", diff --git a/packages/@aws-cdk/aws-scheduler-alpha/test/integ.schedule.ts b/packages/@aws-cdk/aws-scheduler-alpha/test/integ.schedule.ts index 59a16c5d89fa2..86cd851dc8f45 100644 --- a/packages/@aws-cdk/aws-scheduler-alpha/test/integ.schedule.ts +++ b/packages/@aws-cdk/aws-scheduler-alpha/test/integ.schedule.ts @@ -90,6 +90,12 @@ new scheduler.Schedule(stack, 'CustomerKmsSchedule', { key, }); +new scheduler.Schedule(stack, 'UseFlexibleTimeWindow', { + schedule: expression, + target: target, + timeWindow: scheduler.TimeWindow.flexible(cdk.Duration.minutes(10)), +}); + const currentYear = new Date().getFullYear(); new scheduler.Schedule(stack, 'ScheduleWithTimeFrame', { schedule: expression, diff --git a/packages/@aws-cdk/aws-scheduler-alpha/test/schedule.test.ts b/packages/@aws-cdk/aws-scheduler-alpha/test/schedule.test.ts index b931769fc660b..a99fcf88aef3f 100644 --- a/packages/@aws-cdk/aws-scheduler-alpha/test/schedule.test.ts +++ b/packages/@aws-cdk/aws-scheduler-alpha/test/schedule.test.ts @@ -4,7 +4,7 @@ import { Template } from 'aws-cdk-lib/assertions'; import * as iam from 'aws-cdk-lib/aws-iam'; import * as kms from 'aws-cdk-lib/aws-kms'; import * as lambda from 'aws-cdk-lib/aws-lambda'; -import { IScheduleTarget, Schedule, ScheduleTargetConfig } from '../lib'; +import { IScheduleTarget, Schedule, ScheduleTargetConfig, TimeWindow } from '../lib'; import { ScheduleExpression } from '../lib/schedule-expression'; class SomeLambdaTarget implements IScheduleTarget { @@ -161,4 +161,58 @@ describe('Schedule', () => { }).toThrow(`start must precede end, got start: ${start}, end: ${end}`); }); }); + + describe('flexibleTimeWindow', () => { + test('flexibleTimeWindow mode is set to OFF by default', () => { + // WHEN + new Schedule(stack, 'TestSchedule', { + schedule: expr, + target: new SomeLambdaTarget(func, role), + }); + + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::Scheduler::Schedule', { + FlexibleTimeWindow: { + Mode: 'OFF', + }, + }); + }); + + test('flexibleTimeWindow mode can be set to FLEXIBLE', () => { + // WHEN + new Schedule(stack, 'TestSchedule', { + schedule: expr, + target: new SomeLambdaTarget(func, role), + timeWindow: TimeWindow.flexible(Duration.minutes(1440)), + }); + + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::Scheduler::Schedule', { + FlexibleTimeWindow: { + Mode: 'FLEXIBLE', + MaximumWindowInMinutes: 1440, + }, + }); + }); + + test('throw error when maximumWindowInMinutes is greater than 1440', () => { + expect(() => { + new Schedule(stack, 'TestSchedule', { + schedule: expr, + target: new SomeLambdaTarget(func, role), + timeWindow: TimeWindow.flexible(Duration.minutes(1441)), + }); + }).toThrow('The provided duration must be between 1 minute and 1440 minutes, got 1441'); + }); + + test('throw error when maximumWindowInMinutes is less than 1', () => { + expect(() => { + new Schedule(stack, 'TestSchedule', { + schedule: expr, + target: new SomeLambdaTarget(func, role), + timeWindow: TimeWindow.flexible(Duration.minutes(0)), + }); + }).toThrow('The provided duration must be between 1 minute and 1440 minutes, got 0'); + }); + }); });