packages/aws-cdk-lib/aws-scheduler/lib/schedule.ts (161 lines of code) (raw):

import { Construct } from 'constructs'; import { ScheduleExpression } from './schedule-expression'; import { IScheduleGroup } from './schedule-group'; import { CfnSchedule } from './scheduler.generated'; import { IScheduleTarget } from './target'; import * as cloudwatch from '../../aws-cloudwatch'; import * as kms from '../../aws-kms'; import { Arn, ArnFormat, Duration, IResource, Resource, Token } from '../../core'; import { addConstructMetadata } from '../../core/lib/metadata-resource'; /** * Interface representing a created or an imported `Schedule`. */ export interface ISchedule extends IResource { /** * The arn of the schedule. * @attribute */ readonly scheduleArn: string; /** * The name of the schedule. * @attribute */ readonly scheduleName: string; /** * The schedule group associated with this schedule. */ readonly scheduleGroup?: IScheduleGroup; } /** * 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`. */ export interface ScheduleProps { /** * The expression that defines when the schedule runs. Can be either a `at`, `rate` * or `cron` expression. */ readonly schedule: ScheduleExpression; /** * The schedule's target details. */ readonly target: IScheduleTarget; /** * The name of the schedule. * * Up to 64 letters (uppercase and lowercase), numbers, hyphens, underscores and dots are allowed. * * @default - A unique name will be generated */ readonly scheduleName?: string; /** * The description you specify for the schedule. * * @default - no value */ readonly description?: string; /** * The schedule's group. * * @default - By default a schedule will be associated with the `default` group. */ readonly scheduleGroup?: IScheduleGroup; /** * Indicates whether the schedule is enabled. * * @default true */ readonly enabled?: boolean; /** * The customer managed KMS key that EventBridge Scheduler will use to encrypt and decrypt your data. * * @default - All events in Scheduler are encrypted with a key that AWS owns and manages. */ 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. * * @default - no value */ readonly start?: Date; /** * The date, in UTC, before which the schedule can invoke its target. * EventBridge Scheduler ignores end for one-time schedules. * * @default - no value */ readonly end?: Date; } /** * An EventBridge Schedule */ export class Schedule extends Resource implements ISchedule { /** * Return the given named metric for all schedules. * * @default - sum over 5 minutes */ public static metricAll(metricName: string, props?: cloudwatch.MetricOptions): cloudwatch.Metric { return new cloudwatch.Metric({ namespace: 'AWS/Scheduler', metricName, statistic: 'sum', ...props, }); } /** * Metric for the number of invocations that were throttled across all schedules. * * @see https://docs.aws.amazon.com/scheduler/latest/UserGuide/scheduler-quotas.html * * @default - sum over 5 minutes */ public static metricAllThrottled(props?: cloudwatch.MetricOptions): cloudwatch.Metric { return this.metricAll('InvocationThrottleCount', props); } /** * Metric for all invocation attempts across all schedules. * * @default - sum over 5 minutes */ public static metricAllAttempts(props?: cloudwatch.MetricOptions): cloudwatch.Metric { return this.metricAll('InvocationAttemptCount', props); } /** * Emitted when the target returns an exception after EventBridge Scheduler calls the target API across all schedules. * * @default - sum over 5 minutes */ public static metricAllErrors(props?: cloudwatch.MetricOptions): cloudwatch.Metric { return this.metricAll('TargetErrorCount', props); } /** * Metric for invocation failures due to API throttling by the target across all schedules. * * @default - sum over 5 minutes */ public static metricAllTargetThrottled(props?: cloudwatch.MetricOptions): cloudwatch.Metric { return this.metricAll('TargetErrorThrottledCount', props); } /** * Metric for dropped invocations when EventBridge Scheduler stops attempting to invoke the target after a schedule's retry policy has been exhausted. * Metric is calculated for all schedules. * * @default - sum over 5 minutes */ public static metricAllDropped(props?: cloudwatch.MetricOptions): cloudwatch.Metric { return this.metricAll('InvocationDroppedCount', props); } /** * Metric for invocations delivered to the DLQ across all schedules. * * @default - sum over 5 minutes */ public static metricAllSentToDLQ(props?: cloudwatch.MetricOptions): cloudwatch.Metric { return this.metricAll('InvocationsSentToDeadLetterCount', props); } /** * Metric for failed invocations that also failed to deliver to DLQ across all schedules. * * @default - sum over 5 minutes */ public static metricAllFailedToBeSentToDLQ(errorCode?: string, props?: cloudwatch.MetricOptions): cloudwatch.Metric { if (errorCode) { return this.metricAll(`InvocationsFailedToBeSentToDeadLetterCount_${errorCode}`, props); } return this.metricAll('InvocationsFailedToBeSentToDeadLetterCount', props); } /** * Metric for delivery of failed invocations to DLQ when the payload of the event sent to the DLQ exceeds the maximum size allowed by Amazon SQS. * Metric is calculated for all schedules. * * @default - sum over 5 minutes */ public static metricAllSentToDLQTruncated(props?: cloudwatch.MetricOptions): cloudwatch.Metric { return this.metricAll('InvocationsSentToDeadLetterCount_Truncated_MessageSizeExceeded', props); } /** * Import an existing schedule using the ARN. */ public static fromScheduleArn(scope: Construct, id: string, scheduleArn: string): ISchedule { class Import extends Resource implements ISchedule { public readonly scheduleArn = scheduleArn; public readonly scheduleName = Arn.split(scheduleArn, ArnFormat.SLASH_RESOURCE_NAME).resourceName!.split('/')[1]; } return new Import(scope, id); } /** * The schedule group associated with this schedule. */ public readonly scheduleGroup?: IScheduleGroup; /** * The arn of the schedule. */ public readonly scheduleArn: string; /** * The name of the schedule. */ public readonly scheduleName: string; /** * The customer managed KMS key that EventBridge Scheduler will use to encrypt and decrypt your data. */ readonly key?: kms.IKey; /** * A `RetryPolicy` object that includes information about the retry policy settings. */ private readonly retryPolicy?: CfnSchedule.RetryPolicyProperty; constructor(scope: Construct, id: string, props: ScheduleProps) { super(scope, id, { physicalName: props.scheduleName, }); // Enhanced CDK Analytics Telemetry addConstructMetadata(this, props); this.scheduleGroup = props.scheduleGroup; const targetConfig = props.target.bind(this); this.key = props.key; if (this.key) { this.key.grantDecrypt(targetConfig.role); } this.retryPolicy = targetConfig.retryPolicy; const flexibleTimeWindow = props.timeWindow ?? TimeWindow.off(); this.validateTimeFrame(props.start, props.end); if (props.scheduleName && !Token.isUnresolved(props.scheduleName) && props.scheduleName.length > 64) { throw new Error(`scheduleName cannot be longer than 64 characters, got: ${props.scheduleName.length}`); } const resource = new CfnSchedule(this, 'Resource', { name: this.physicalName, flexibleTimeWindow: { mode: flexibleTimeWindow.mode, maximumWindowInMinutes: flexibleTimeWindow.maxWindow?.toMinutes(), }, scheduleExpression: props.schedule.expressionString, scheduleExpressionTimezone: props.schedule.timeZone?.timezoneName, groupName: this.scheduleGroup?.scheduleGroupName, state: (props.enabled ?? true) ? 'ENABLED' : 'DISABLED', kmsKeyArn: this.key?.keyArn, target: { arn: targetConfig.arn, roleArn: targetConfig.role.roleArn, input: targetConfig.input?.bind(this), deadLetterConfig: targetConfig.deadLetterConfig, retryPolicy: this.renderRetryPolicy(), ecsParameters: targetConfig.ecsParameters, kinesisParameters: targetConfig.kinesisParameters, eventBridgeParameters: targetConfig.eventBridgeParameters, sageMakerPipelineParameters: targetConfig.sageMakerPipelineParameters, sqsParameters: targetConfig.sqsParameters, }, startDate: props.start?.toISOString(), endDate: props.end?.toISOString(), description: props.description, }); this.scheduleName = this.getResourceNameAttribute(resource.ref); this.scheduleArn = this.getResourceArnAttribute(resource.attrArn, { service: 'scheduler', resource: 'schedule', resourceName: `${this.scheduleGroup?.scheduleGroupName ?? 'default'}/${this.physicalName}`, }); } private renderRetryPolicy(): CfnSchedule.RetryPolicyProperty | undefined { const policy = { ...this.retryPolicy, }; if (policy.maximumEventAgeInSeconds && (policy.maximumEventAgeInSeconds < 60 || policy.maximumEventAgeInSeconds > 86400)) { throw new Error(`maximumEventAgeInSeconds must be between 60 and 86400, got ${policy.maximumEventAgeInSeconds}`); } if (policy.maximumRetryAttempts && (policy.maximumRetryAttempts < 0 || policy.maximumRetryAttempts > 185)) { throw new Error(`maximumRetryAttempts must be between 0 and 185, got ${policy.maximumRetryAttempts}`); } const isEmptyPolicy = Object.values(policy).every(value => value === undefined); return !isEmptyPolicy ? policy : undefined; } private validateTimeFrame(start?: Date, end?: Date) { if (start && end && start >= end) { throw new Error(`start must precede end, got start: ${start.toISOString()}, end: ${end.toISOString()}`); } } }