Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(core): Schedule class #27105

Merged
merged 34 commits into from
Sep 20, 2023
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
2a37ef5
prelim scheduel in core
kaizencc Sep 7, 2023
1a7848e
fix schedule
kaizencc Sep 11, 2023
c1d2e2e
expose backup.Schedule, create BackupPlanRule.schedule, deprecated Ba…
kaizencc Sep 11, 2023
9597cd8
add timezone to at expression
kaizencc Sep 11, 2023
e72f216
add bind to core.Schedule, rename protected methods as protectedXxx c…
kaizencc Sep 11, 2023
89e2355
move schedule.ts to helpers-internal
kaizencc Sep 11, 2023
8645d07
move schedule back tto main
kaizencc Sep 11, 2023
72ec383
export
kaizencc Sep 11, 2023
f59f475
synthetics
kaizencc Sep 11, 2023
2c04d30
scheduler
kaizencc Sep 11, 2023
404ab4c
touchups
kaizencc Sep 11, 2023
eda29d3
Merge branch 'main' into conroy/schedulecore
kaizencc Sep 11, 2023
97e492e
add annotations and fix some tests
kaizencc Sep 11, 2023
863d720
Merge branch 'main' into conroy/schedulecore
kaizencc Sep 13, 2023
8399112
Update packages/aws-cdk-lib/aws-backup/lib/schedule.ts
kaizencc Sep 13, 2023
20cb339
rename to CoreSchedule
kaizencc Sep 13, 2023
223070e
backup synth check
kaizencc Sep 13, 2023
b1940c7
autoscaling timezone test
kaizencc Sep 13, 2023
380bf5f
finally fixed nuance in autoscaling.Schedule
kaizencc Sep 14, 2023
ab7a05a
Merge branch 'main' into conroy/schedulecore
kaizencc Sep 14, 2023
acd58a2
add documentation
kaizencc Sep 14, 2023
c64a3ca
fix bugs and tests in scheduler
kaizencc Sep 19, 2023
66d1df7
finalize schedule class
kaizencc Sep 19, 2023
70ac3bf
minor test fixes
kaizencc Sep 19, 2023
40f4078
test schedule in core
kaizencc Sep 19, 2023
240a016
Merge branch 'main' into conroy/schedulecore
kaizencc Sep 19, 2023
1e444c8
fix tests
kaizencc Sep 19, 2023
44b535e
lint
kaizencc Sep 19, 2023
b1f3603
Merge branch 'main' into conroy/schedulecore
kaizencc Sep 19, 2023
a530f35
snappies
kaizencc Sep 19, 2023
094c454
Merge branch 'main' into conroy/schedulecore
kaizencc Sep 19, 2023
3697f55
fix cronoptions
kaizencc Sep 19, 2023
77fd854
Update packages/@aws-cdk/aws-scheduler-alpha/test/schedule-expression…
kaizencc Sep 20, 2023
1efab47
Merge branch 'main' into conroy/schedulecore
mergify[bot] Sep 20, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 7 additions & 55 deletions packages/@aws-cdk/aws-scheduler-alpha/lib/schedule-expression.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import * as events from 'aws-cdk-lib/aws-events';
import { Duration, TimeZone } from 'aws-cdk-lib/core';
import { Duration, TimeZone, Schedule, CronOptions } from 'aws-cdk-lib/core';

/**
* ScheduleExpression for EventBridge Schedule
Expand All @@ -9,23 +8,15 @@ import { Duration, TimeZone } from 'aws-cdk-lib/core';
*
* @see https://docs.aws.amazon.com/scheduler/latest/UserGuide/schedule-types.html
*/
export abstract class ScheduleExpression {
export abstract class ScheduleExpression extends Schedule {
/**
* Construct a one-time schedule from a date.
*
* @param date The date and time to use. The millisecond part will be ignored.
* @param timeZone The time zone to use for interpreting the date. Default: - UTC
*/
public static at(date: Date, timeZone?: TimeZone): ScheduleExpression {
try {
const literal = date.toISOString().split('.')[0];
return new LiteralScheduleExpression(`at(${literal})`, timeZone ?? TimeZone.ETC_UTC);
} catch (e) {
if (e instanceof RangeError) {
throw new Error('Invalid date');
}
throw e;
}
return super.protectedAt(date, timeZone);
}

/**
Expand All @@ -34,7 +25,7 @@ export abstract class ScheduleExpression {
* @param timeZone The time zone to use for interpreting the expression. Default: - UTC
*/
public static expression(expression: string, timeZone?: TimeZone): ScheduleExpression {
return new LiteralScheduleExpression(expression, timeZone ?? TimeZone.ETC_UTC);
return super.protectedExpression(expression, timeZone);
}

/**
Expand All @@ -43,53 +34,14 @@ export abstract class ScheduleExpression {
* Rates may be defined with any unit of time, but when converted into minutes, the duration must be a positive whole number of minutes.
*/
public static rate(duration: Duration): ScheduleExpression {
const schedule = events.Schedule.rate(duration);
return new LiteralScheduleExpression(schedule.expressionString);
return super.protectedRate(duration);
}

/**
* Create a recurring schedule from a set of cron fields and time zone.
*/
public static cron(options: CronOptionsWithTimezone): ScheduleExpression {
const { timeZone, ...cronOptions } = options;
const schedule = events.Schedule.cron(cronOptions);
return new LiteralScheduleExpression(schedule.expressionString, timeZone);
public static cron(options: CronOptions): ScheduleExpression {
return super.protectedCron(options);
}

/**
* Retrieve the expression for this schedule
*/
public abstract readonly expressionString: string;

/**
* Retrieve the expression for this schedule
*/
public abstract readonly timeZone?: TimeZone;

protected constructor() {}
}

/**
* Options to configure a cron expression
*
* All fields are strings so you can use complex expressions. Absence of
* a field implies '*' or '?', whichever one is appropriate.
*
* @see https://docs.aws.amazon.com/eventbridge/latest/userguide/scheduled-events.html#cron-expressions
*/
export interface CronOptionsWithTimezone extends events.CronOptions {
/**
* The timezone to run the schedule in
*
* @default - TimeZone.ETC_UTC
*/
readonly timeZone?: TimeZone;
}

const DEFAULT_TIMEZONE = TimeZone.ETC_UTC;

class LiteralScheduleExpression extends ScheduleExpression {
constructor(public readonly expressionString: string, public readonly timeZone: TimeZone = DEFAULT_TIMEZONE) {
super();
}
}
2 changes: 1 addition & 1 deletion packages/@aws-cdk/aws-scheduler-alpha/lib/target.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ export interface ScheduleTargetConfig {
readonly role: iam.IRole;

/**
* What input to pass to the tatget
* What input to pass to the target
*/
readonly input?: ScheduleTargetInput;

Expand Down
42 changes: 12 additions & 30 deletions packages/@aws-cdk/aws-synthetics-alpha/lib/schedule.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { Duration } from 'aws-cdk-lib/core';
import { Duration, Schedule as ScheduleExpression } from 'aws-cdk-lib/core';
import { Construct } from 'constructs';

/**
* Schedule for canary runs
*/
export class Schedule {
export class Schedule extends ScheduleExpression {

/**
* The canary will be executed once.
Expand Down Expand Up @@ -36,39 +37,24 @@ export class Schedule {
if (minutes === 0) {
return Schedule.once();
}
if (minutes === 1) {
return new Schedule('rate(1 minute)');
}
return new Schedule(`rate(${minutes} minutes)`);
return super.protectedRate(interval);
}

/**
* Create a schedule from a set of cron fields
*/
public static cron(options: CronOptions): Schedule {
if (options.weekDay !== undefined && options.day !== undefined) {
throw new Error('Cannot supply both \'day\' and \'weekDay\', use at most one');
}

const minute = fallback(options.minute, '*');
const hour = fallback(options.hour, '*');
const month = fallback(options.month, '*');

// Weekday defaults to '?' if not supplied. If it is supplied, day must become '?'
const day = fallback(options.day, options.weekDay !== undefined ? '?' : '*');
const weekDay = fallback(options.weekDay, '?');

// '*' is only allowed in the year field
const year = '*';
return super.protectedCron({
...options,
year: '*', // '*' is the only allowed value in the year field
});
}

return new Schedule(`cron(${minute} ${hour} ${day} ${month} ${weekDay} ${year})`);
private constructor(public readonly expressionString: string) {
super();
}

private constructor(
/**
* The Schedule expression
*/
public readonly expressionString: string) {}
public _bind(_scope: Construct) {}
}

/**
Expand Down Expand Up @@ -115,7 +101,3 @@ export interface CronOptions {
*/
readonly weekDay?: string;
}

function fallback(x: string | undefined, def: string): string {
return x ?? def;
}
6 changes: 3 additions & 3 deletions packages/aws-cdk-lib/aws-applicationautoscaling/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,7 @@ capacity.scaleOnSchedule('PrescaleInTheMorning', {

capacity.scaleOnSchedule('AllowDownscalingAtNight', {
schedule: appscaling.Schedule.cron({ hour: '20', minute: '0' }),
minCapacity: 1
minCapacity: 1,
});
```

Expand Down Expand Up @@ -220,12 +220,12 @@ const target = new appscaling.ScalableTarget(this, 'ScalableTarget', {
minCapacity: 10,
resourceId: `function:${handler.functionName}:${fnVer.version}`,
scalableDimension: 'lambda:function:ProvisionedConcurrency',
})
});

target.scaleToTrackMetric('PceTracking', {
targetValue: 0.9,
predefinedMetric: appscaling.PredefinedMetric.LAMBDA_PROVISIONED_CONCURRENCY_UTILIZATION,
})
});
```

### ElastiCache Redis shards scaling with target value
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,7 @@ export class ScalableTarget extends Resource implements IScalableTarget {
this.actions.push({
scheduledActionName: id,
schedule: action.schedule.expressionString,
timezone: action.schedule.timeZone?.timezoneName,
startTime: action.startTime,
endTime: action.endTime,
scalableTargetAction: {
Expand Down
113 changes: 10 additions & 103 deletions packages/aws-cdk-lib/aws-applicationautoscaling/lib/schedule.ts
Original file line number Diff line number Diff line change
@@ -1,87 +1,38 @@
import { Construct } from 'constructs';
import { Annotations, Duration } from '../../core';
import { Duration, TimeZone, CronOptions as CoreCronOptions, Schedule as ScheduleExpression } from '../../core';

/**
* Schedule for scheduled scaling actions
*/
export abstract class Schedule {
export abstract class Schedule extends ScheduleExpression {
/**
* Construct a schedule from a literal schedule expression
*
* @param expression The expression to use. Must be in a format that Application AutoScaling will recognize
*/
public static expression(expression: string): Schedule {
return new LiteralSchedule(expression);
public static expression(expression: string, timeZone?: TimeZone): Schedule {
return super.protectedExpression(expression, timeZone);
}

/**
* Construct a schedule from an interval and a time unit
*/
public static rate(duration: Duration): Schedule {
if (duration.isUnresolved()) {
const validDurationUnit = ['minute', 'minutes', 'hour', 'hours', 'day', 'days'];
if (!validDurationUnit.includes(duration.unitLabel())) {
throw new Error("Allowed units for scheduling are: 'minute', 'minutes', 'hour', 'hours', 'day' or 'days'");
}
return new LiteralSchedule(`rate(${duration.formatTokenToNumber()})`);
}
if (duration.toSeconds() === 0) {
throw new Error('Duration cannot be 0');
}

let rate = maybeRate(duration.toDays({ integral: false }), 'day');
if (rate === undefined) { rate = maybeRate(duration.toHours({ integral: false }), 'hour'); }
if (rate === undefined) { rate = makeRate(duration.toMinutes({ integral: true }), 'minute'); }
return new LiteralSchedule(rate);
return super.protectedRate(duration);
}

/**
* Construct a Schedule from a moment in time
*/
public static at(moment: Date): Schedule {
return new LiteralSchedule(`at(${formatISO(moment)})`);
public static at(moment: Date, timeZone?: TimeZone): Schedule {
return super.protectedAt(moment, timeZone);
}

/**
* Create a schedule from a set of cron fields
*/
public static cron(options: CronOptions): Schedule {
if (options.weekDay !== undefined && options.day !== undefined) {
throw new Error('Cannot supply both \'day\' and \'weekDay\', use at most one');
}

const minute = fallback(options.minute, '*');
const hour = fallback(options.hour, '*');
const month = fallback(options.month, '*');
const year = fallback(options.year, '*');

// Weekday defaults to '?' if not supplied. If it is supplied, day must become '?'
const day = fallback(options.day, options.weekDay !== undefined ? '?' : '*');
const weekDay = fallback(options.weekDay, '?');

return new class extends Schedule {
public readonly expressionString: string = `cron(${minute} ${hour} ${day} ${month} ${weekDay} ${year})`;
public _bind(scope: Construct) {
if (!options.minute) {
Annotations.of(scope).addWarningV2('@aws-cdk/aws-applicationautoscaling:defaultRunEveryMinute', 'cron: If you don\'t pass \'minute\', by default the event runs every minute. Pass \'minute: \'*\'\' if that\'s what you intend, or \'minute: 0\' to run once per hour instead.');
}
return new LiteralSchedule(this.expressionString);
}
};
public static cron(options: CoreCronOptions): Schedule {
return super.protectedCron(options);
}

/**
* Retrieve the expression for this schedule
*/
public abstract readonly expressionString: string;

protected constructor() {}

/**
*
* @internal
*/
public abstract _bind(scope: Construct): void;
}

/**
Expand All @@ -91,6 +42,7 @@ export abstract class Schedule {
* a field implies '*' or '?', whichever one is appropriate.
*
* @see https://docs.aws.amazon.com/AmazonCloudWatch/latest/events/ScheduledEvents.html#CronExpressions
* @deprecated use core.CronOptions instead
*/
export interface CronOptions {
/**
Expand Down Expand Up @@ -135,48 +87,3 @@ export interface CronOptions {
*/
readonly weekDay?: string;
}

class LiteralSchedule extends Schedule {
constructor(public readonly expressionString: string) {
super();
}

public _bind() {}
}

function fallback<T>(x: T | undefined, def: T): T {
return x === undefined ? def : x;
}

function formatISO(date?: Date) {
if (!date) { return undefined; }

return date.getUTCFullYear() +
'-' + pad(date.getUTCMonth() + 1) +
'-' + pad(date.getUTCDate()) +
'T' + pad(date.getUTCHours()) +
':' + pad(date.getUTCMinutes()) +
':' + pad(date.getUTCSeconds());

function pad(num: number) {
if (num < 10) {
return '0' + num;
}
return num;
}
}

/**
* Return the rate if the rate is whole number
*/
function maybeRate(interval: number, singular: string) {
if (interval === 0 || !Number.isInteger(interval)) { return undefined; }
return makeRate(interval, singular);
}

/**
* Return 'rate(${interval} ${singular}(s))` for the interval
*/
function makeRate(interval: number, singular: string) {
return interval === 1 ? `rate(1 ${singular})` : `rate(${interval} ${singular}s)`;
}
2 changes: 1 addition & 1 deletion packages/aws-cdk-lib/aws-autoscaling/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -288,7 +288,7 @@ autoScalingGroup.scaleOnSchedule('PrescaleInTheMorning', {

autoScalingGroup.scaleOnSchedule('AllowDownscalingAtNight', {
schedule: autoscaling.Schedule.cron({ hour: '20', minute: '0' }),
minCapacity: 1
minCapacity: 1,
});
```

Expand Down
Loading