Skip to content

Commit b1449a1

Browse files
authored
feat(events,applicationautoscaling): schedule can be a token (#13064)
Fix for Issue: #9413 Testing: Unit tests added for Schedule.rate used in ApplicationAutoScaling and Events. * [X] Testing - Unit test added (prefer not to modify an existing test, otherwise, it's probably a breaking change) - __CLI change?:__ coordinate update of integration tests with team - __cdk-init template change?:__ coordinated update of integration tests with team * [X] Docs - __jsdocs__: All public APIs documented - __README__: README and/or documentation topic updated - __Design__: For significant features, design document added to `design` folder * [X] Title and Description - __Change type__: title prefixed with **fix**, **feat** and module name in parents, which will appear in changelog - __Title__: use lower-case and doesn't end with a period - __Breaking?__: last paragraph: "BREAKING CHANGE: <describe what changed + link for details>" - __Issues__: Indicate issues fixed via: "**Fixes #xxx**" or "**Closes #xxx**" * [ ] Sensitive Modules (requires 2 PR approvers) - IAM Policy Document (in @aws-cdk/aws-iam) - EC2 Security Groups and ACLs (in @aws-cdk/aws-ec2) - Grant APIs (only if not based on official documentation with a reference) ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
1 parent 781aa97 commit b1449a1

File tree

8 files changed

+160
-7
lines changed

8 files changed

+160
-7
lines changed

packages/@aws-cdk/aws-applicationautoscaling/lib/schedule.ts

+7
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,13 @@ export abstract class Schedule {
1717
* Construct a schedule from an interval and a time unit
1818
*/
1919
public static rate(duration: Duration): Schedule {
20+
const validDurationUnit = ['minute', 'minutes', 'hour', 'hours', 'day', 'days'];
21+
if (!validDurationUnit.includes(duration.unitLabel())) {
22+
throw new Error("Allowed unit for scheduling is: 'minute', 'minutes', 'hour', 'hours', 'day' or 'days'");
23+
}
24+
if (duration.isUnresolved()) {
25+
return new LiteralSchedule(`rate(${duration.formatTokenToNumber()})`);
26+
}
2027
if (duration.toSeconds() === 0) {
2128
throw new Error('Duration cannot be 0');
2229
}

packages/@aws-cdk/aws-applicationautoscaling/test/test.cron.ts

+24-3
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Duration } from '@aws-cdk/core';
1+
import { Duration, Stack, Lazy } from '@aws-cdk/core';
22
import { Test } from 'nodeunit';
33
import * as appscaling from '../lib';
44

@@ -15,8 +15,15 @@ export = {
1515

1616
'rate must be whole number of minutes'(test: Test) {
1717
test.throws(() => {
18-
appscaling.Schedule.rate(Duration.seconds(12345));
19-
}, /'12345 seconds' cannot be converted into a whole number of minutes/);
18+
appscaling.Schedule.rate(Duration.minutes(0.13456));
19+
}, /'0.13456 minutes' cannot be converted into a whole number of seconds/);
20+
test.done();
21+
},
22+
23+
'rate must not be in seconds'(test: Test) {
24+
test.throws(() => {
25+
appscaling.Schedule.rate(Duration.seconds(1));
26+
}, /Allowed unit for scheduling is: 'minute', 'minutes', 'hour', 'hours', 'day' or 'days'/);
2027
test.done();
2128
},
2229

@@ -26,4 +33,18 @@ export = {
2633
}, /Duration cannot be 0/);
2734
test.done();
2835
},
36+
37+
'rate can be token'(test: Test) {
38+
const stack = new Stack();
39+
const lazyDuration = Duration.minutes(Lazy.number({ produce: () => 5 }));
40+
const rate = appscaling.Schedule.rate(lazyDuration);
41+
test.equal('rate(5 minutes)', stack.resolve(rate).expressionString);
42+
test.done();
43+
},
44+
45+
'rate can be in allowed type hours'(test: Test) {
46+
test.equal('rate(1 hour)', appscaling.Schedule.rate(Duration.hours(1))
47+
.expressionString);
48+
test.done();
49+
},
2950
};

packages/@aws-cdk/aws-events/README.md

+1
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ onCommitRule.addTarget(new targets.SnsTopic(topic, {
8383
## Scheduling
8484

8585
You can configure a Rule to run on a schedule (cron or rate).
86+
Rate must be specified in minutes, hours or days.
8687

8788
The following example runs a task every day at 4am:
8889

packages/@aws-cdk/aws-events/lib/schedule.ts

+7
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,13 @@ export abstract class Schedule {
1717
* Construct a schedule from an interval and a time unit
1818
*/
1919
public static rate(duration: Duration): Schedule {
20+
const validDurationUnit = ['minute', 'minutes', 'hour', 'hours', 'day', 'days'];
21+
if (validDurationUnit.indexOf(duration.unitLabel()) === -1) {
22+
throw new Error('Allowed unit for scheduling is: \'minute\', \'minutes\', \'hour\', \'hours\', \'day\', \'days\'');
23+
}
24+
if (duration.isUnresolved()) {
25+
return new LiteralSchedule(`rate(${duration.formatTokenToNumber()})`);
26+
}
2027
if (duration.toSeconds() === 0) {
2128
throw new Error('Duration cannot be 0');
2229
}

packages/@aws-cdk/aws-events/test/test.rule.ts

+33
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,39 @@ export = {
4949
test.done();
5050
},
5151

52+
'get rate as token'(test: Test) {
53+
const app = new cdk.App();
54+
const stack = new cdk.Stack(app, 'MyScheduledStack');
55+
const lazyDuration = cdk.Duration.minutes(cdk.Lazy.number({ produce: () => 5 }));
56+
57+
new Rule(stack, 'MyScheduledRule', {
58+
ruleName: 'rateInMinutes',
59+
schedule: Schedule.rate(lazyDuration),
60+
});
61+
62+
// THEN
63+
expect(stack).to(haveResourceLike('AWS::Events::Rule', {
64+
'Name': 'rateInMinutes',
65+
'ScheduleExpression': 'rate(5 minutes)',
66+
}));
67+
68+
test.done();
69+
},
70+
71+
'Seconds is not an allowed value for Schedule rate'(test: Test) {
72+
const lazyDuration = cdk.Duration.seconds(cdk.Lazy.number({ produce: () => 5 }));
73+
test.throws(() => Schedule.rate(lazyDuration), /Allowed unit for scheduling is: 'minute', 'minutes', 'hour', 'hours', 'day', 'days'/);
74+
test.done();
75+
},
76+
77+
'Millis is not an allowed value for Schedule rate'(test: Test) {
78+
const lazyDuration = cdk.Duration.millis(cdk.Lazy.number({ produce: () => 5 }));
79+
80+
// THEN
81+
test.throws(() => Schedule.rate(lazyDuration), /Allowed unit for scheduling is: 'minute', 'minutes', 'hour', 'hours', 'day', 'days'/);
82+
test.done();
83+
},
84+
5285
'rule with physical name'(test: Test) {
5386
// GIVEN
5487
const stack = new cdk.Stack();

packages/@aws-cdk/aws-events/test/test.schedule.ts

+39-3
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Duration } from '@aws-cdk/core';
1+
import { Duration, Stack, Lazy } from '@aws-cdk/core';
22
import { Test } from 'nodeunit';
33
import * as events from '../lib';
44

@@ -33,8 +33,15 @@ export = {
3333

3434
'rate must be whole number of minutes'(test: Test) {
3535
test.throws(() => {
36-
events.Schedule.rate(Duration.seconds(12345));
37-
}, /'12345 seconds' cannot be converted into a whole number of minutes/);
36+
events.Schedule.rate(Duration.minutes(0.13456));
37+
}, /'0.13456 minutes' cannot be converted into a whole number of seconds/);
38+
test.done();
39+
},
40+
41+
'rate must be whole number'(test: Test) {
42+
test.throws(() => {
43+
events.Schedule.rate(Duration.minutes(1/8));
44+
}, /'0.125 minutes' cannot be converted into a whole number of seconds/);
3845
test.done();
3946
},
4047

@@ -44,4 +51,33 @@ export = {
4451
}, /Duration cannot be 0/);
4552
test.done();
4653
},
54+
55+
'rate can be from a token'(test: Test) {
56+
const stack = new Stack();
57+
const lazyDuration = Duration.minutes(Lazy.number({ produce: () => 5 }));
58+
const rate = events.Schedule.rate(lazyDuration);
59+
test.equal('rate(5 minutes)', stack.resolve(rate).expressionString);
60+
test.done();
61+
},
62+
63+
'rate can be in minutes'(test: Test) {
64+
test.equal('rate(10 minutes)',
65+
events.Schedule.rate(Duration.minutes(10))
66+
.expressionString);
67+
test.done();
68+
},
69+
70+
'rate can be in days'(test: Test) {
71+
test.equal('rate(10 days)',
72+
events.Schedule.rate(Duration.days(10))
73+
.expressionString);
74+
test.done();
75+
},
76+
77+
'rate can be in hours'(test: Test) {
78+
test.equal('rate(10 hours)',
79+
events.Schedule.rate(Duration.hours(10))
80+
.expressionString);
81+
test.done();
82+
},
4783
};

packages/@aws-cdk/core/lib/duration.ts

+23-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Token } from './token';
1+
import { Token, Tokenization } from './token';
22

33
/**
44
* Represents a length of time.
@@ -252,6 +252,28 @@ export class Duration {
252252
}
253253
return ret;
254254
}
255+
256+
/**
257+
* Checks if duration is a token or a resolvable object
258+
*/
259+
public isUnresolved() {
260+
return Token.isUnresolved(this.amount);
261+
}
262+
263+
/**
264+
* Returns unit of the duration
265+
*/
266+
public unitLabel() {
267+
return this.unit.label;
268+
}
269+
270+
/**
271+
* Returns stringified number of duration
272+
*/
273+
public formatTokenToNumber(): string {
274+
const number = Tokenization.stringifyNumber(this.amount);
275+
return `${number} ${this.unit.label}`;
276+
}
255277
}
256278

257279
/**

packages/@aws-cdk/core/test/duration.test.ts

+26
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,32 @@ nodeunitShim({
168168

169169
test.done();
170170
},
171+
172+
'get unit label from duration'(test: Test) {
173+
test.equal(Duration.minutes(Lazy.number({ produce: () => 10 })).unitLabel(), 'minutes');
174+
test.equal(Duration.minutes(62).unitLabel(), 'minutes');
175+
test.equal(Duration.seconds(10).unitLabel(), 'seconds');
176+
test.equal(Duration.millis(1).unitLabel(), 'millis');
177+
test.equal(Duration.hours(1000).unitLabel(), 'hours');
178+
test.equal(Duration.days(2).unitLabel(), 'days');
179+
test.done();
180+
},
181+
182+
'format number token to number'(test: Test) {
183+
const stack = new Stack();
184+
const lazyDuration = Duration.minutes(Lazy.number({ produce: () => 10 }));
185+
test.equal(stack.resolve(lazyDuration.formatTokenToNumber()), '10 minutes');
186+
test.equal(Duration.hours(10).formatTokenToNumber(), '10 hours');
187+
test.equal(Duration.days(5).formatTokenToNumber(), '5 days');
188+
test.done();
189+
},
190+
191+
'duration is unresolved'(test: Test) {
192+
const lazyDuration = Duration.minutes(Lazy.number({ produce: () => 10 }));
193+
test.equal(lazyDuration.isUnresolved(), true);
194+
test.equal(Duration.hours(10).isUnresolved(), false);
195+
test.done();
196+
},
171197
});
172198

173199
function floatEqual(test: Test, actual: number, expected: number) {

0 commit comments

Comments
 (0)