Skip to content

Commit 021e6d6

Browse files
authored
feat(scheduler-targets): add support for universal target (#32341)
### Issue # (if applicable) Closes #32328 ### Reason for this change EventBridge Scheduler has a mechanism called Universal Target that calls a wide range of AWS APIs. Supporting this mechanism in L2 Construct will make it easier to configure EventBridge Scheduler. https://docs.aws.amazon.com/scheduler/latest/UserGuide/managing-targets-universal.html ### Description of changes Added Universal construct targeting AWS APIs. Users can execute any AWS API by passing service and action to Props. According to the following documentation, the service must be lowercase, and the action must be camelCase, so that you can validate it. `arn:aws:scheduler:::aws-sdk:service:apiAction` https://docs.aws.amazon.com/scheduler/latest/UserGuide/managing-targets-universal.html#:~:text=schedule%20to%20target.-,Arn,-%E2%80%93%20The%20complete%20service ### Description of how you validated changes Added unit tests and integration tests. ### Checklist - [ ] My code adheres to the [CONTRIBUTING GUIDE](https://github.com/aws/aws-cdk/blob/main/CONTRIBUTING.md) and [DESIGN GUIDELINES](https://github.com/aws/aws-cdk/blob/main/docs/DESIGN_GUIDELINES.md) ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
1 parent f0e2f2a commit 021e6d6

File tree

14 files changed

+32958
-0
lines changed

14 files changed

+32958
-0
lines changed

packages/@aws-cdk/aws-scheduler-targets-alpha/README.md

+50
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ The following targets are supported:
3434
9. `targets.KinesisDataFirehosePutRecord`: [Put a record to a Kinesis Data Firehose](#put-a-record-to-a-kinesis-data-firehose)
3535
10. `targets.CodePipelineStartPipelineExecution`: [Start a CodePipeline execution](#start-a-codepipeline-execution)
3636
11. `targets.SageMakerStartPipelineExecution`: [Start a SageMaker pipeline execution](#start-a-sagemaker-pipeline-execution)
37+
12. `targets.Universal`: [Invoke a wider set of AWS API](#invoke-a-wider-set-of-aws-api)
3738

3839
## Invoke a Lambda function
3940

@@ -312,3 +313,52 @@ new Schedule(this, 'Schedule', {
312313
}),
313314
});
314315
```
316+
317+
## Invoke a wider set of AWS API
318+
319+
Use the `Universal` target to invoke AWS API.
320+
321+
The code snippet below creates an event rule with AWS API as the target which is
322+
called at midnight every day by EventBridge Scheduler.
323+
324+
```ts
325+
new Schedule(this, 'Schedule', {
326+
schedule: ScheduleExpression.cron({
327+
minute: '0',
328+
hour: '0',
329+
}),
330+
target: new targets.Universal({
331+
service: 'rds',
332+
action: 'stopDBCluster',
333+
input: ScheduleTargetInput.fromObject({
334+
DbClusterIdentifier: 'my-db',
335+
}),
336+
}),
337+
});
338+
```
339+
340+
The `service` must be in lowercase and the `action` must be in camelCase.
341+
342+
By default, an IAM policy for the Scheduler is extracted from the API call.
343+
344+
You can control the IAM policy for the Scheduler by specifying the `policyStatements` property.
345+
346+
```ts
347+
new Schedule(this, 'Schedule', {
348+
schedule: ScheduleExpression.rate(Duration.minutes(60)),
349+
target: new targets.Universal({
350+
service: 'sqs',
351+
action: 'sendMessage',
352+
policyStatements: [
353+
new iam.PolicyStatement({
354+
actions: ['sqs:SendMessage'],
355+
resources: ['arn:aws:sqs:us-east-1:123456789012:my_queue'],
356+
}),
357+
new iam.PolicyStatement({
358+
actions: ['kms:Decrypt', 'kms:GenerateDataKey*'],
359+
resources: ['arn:aws:kms:us-east-1:123456789012:key/0987dcba-09fe-87dc-65ba-ab0987654321'],
360+
}),
361+
],
362+
}),
363+
});
364+
```

packages/@aws-cdk/aws-scheduler-targets-alpha/lib/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,4 @@ export * from './sns-publish';
1010
export * from './sqs-send-message';
1111
export * from './stepfunctions-start-execution';
1212
export * from './target';
13+
export * from './universal';
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
import { IScheduleTarget } from '@aws-cdk/aws-scheduler-alpha';
2+
import { Aws, Token } from 'aws-cdk-lib';
3+
import { IRole, PolicyStatement } from 'aws-cdk-lib/aws-iam';
4+
import { awsSdkToIamAction } from 'aws-cdk-lib/custom-resources/lib/helpers-internal';
5+
import { ScheduleTargetBase, ScheduleTargetBaseProps } from './target';
6+
7+
/**
8+
* AWS read-only API action name prefixes that are not supported by EventBridge Scheduler.
9+
*
10+
* @see https://docs.aws.amazon.com/scheduler/latest/UserGuide/managing-targets-universal.html
11+
*/
12+
const NOT_SUPPORTED_ACTION_PREFIX = [
13+
'get',
14+
'describe',
15+
'list',
16+
'poll',
17+
'receive',
18+
'search',
19+
'scan',
20+
'query',
21+
'select',
22+
'read',
23+
'lookup',
24+
'discover',
25+
'validate',
26+
'batchGet',
27+
'batchDescribe',
28+
'batchRead',
29+
'transactGet',
30+
'adminGet',
31+
'adminList',
32+
'testMigration',
33+
'retrieve',
34+
'testConnection',
35+
'translateDocument',
36+
'isAuthorized',
37+
'invokeModel',
38+
];
39+
40+
/**
41+
* Properties for a Universal Target
42+
*/
43+
export interface UniversalTargetProps extends ScheduleTargetBaseProps {
44+
/**
45+
* The AWS service to call.
46+
*
47+
* This must be in lowercase.
48+
*/
49+
readonly service: string;
50+
51+
/**
52+
* The API action to call. Must be camelCase.
53+
*
54+
* You cannot use read-only API actions such as common GET operations.
55+
*
56+
* @see https://docs.aws.amazon.com/scheduler/latest/UserGuide/managing-targets-universal.html#unsupported-api-actions
57+
*/
58+
readonly action: string;
59+
60+
/**
61+
* The IAM policy statements needed to invoke the target. These statements are attached to the Scheduler's role.
62+
*
63+
* Note that the default may not be the correct actions as not all AWS services follows the same IAM action pattern, or there may be more actions needed to invoke the target.
64+
*
65+
* @default - Policy with `service:action` action only.
66+
*/
67+
readonly policyStatements?: PolicyStatement[];
68+
}
69+
70+
/**
71+
* Use a wider set of AWS API as a target for AWS EventBridge Scheduler.
72+
*
73+
* @see https://docs.aws.amazon.com/scheduler/latest/UserGuide/managing-targets-universal.html
74+
*/
75+
export class Universal extends ScheduleTargetBase implements IScheduleTarget {
76+
constructor(
77+
private readonly props: UniversalTargetProps,
78+
) {
79+
const service = props.service;
80+
const action = props.action;
81+
82+
if (!Token.isUnresolved(service) && service !== service.toLowerCase()) {
83+
throw new Error(`API service must be lowercase, got: ${service}`);
84+
}
85+
if (!Token.isUnresolved(action) && !action.startsWith(action[0]?.toLowerCase())) {
86+
throw new Error(`API action must be camelCase, got: ${action}`);
87+
}
88+
if (!Token.isUnresolved(action) && NOT_SUPPORTED_ACTION_PREFIX.some(prefix => action.startsWith(prefix))) {
89+
throw new Error(`Read-only API action is not supported by EventBridge Scheduler: ${service}:${action}`);
90+
}
91+
92+
const arn = `arn:${Aws.PARTITION}:scheduler:::aws-sdk:${service}:${action}`;
93+
super(props, arn);
94+
}
95+
96+
protected addTargetActionToRole(role: IRole): void {
97+
if (!this.props.policyStatements?.length) {
98+
role.addToPrincipalPolicy(new PolicyStatement({
99+
actions: [awsSdkToIamAction(this.props.service, this.props.action)],
100+
resources: ['*'],
101+
}));
102+
return;
103+
}
104+
105+
for (const statement of this.props.policyStatements) {
106+
role.addToPrincipalPolicy(statement);
107+
}
108+
}
109+
}

packages/@aws-cdk/aws-scheduler-targets-alpha/test/integ.universal.js.snapshot/AwsSchedulerTargetsUniversal.assets.json

+19
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
{
2+
"Resources": {
3+
"Schedule83A77FD1": {
4+
"Type": "AWS::Scheduler::Schedule",
5+
"Properties": {
6+
"FlexibleTimeWindow": {
7+
"Mode": "OFF"
8+
},
9+
"ScheduleExpression": "rate(1 minute)",
10+
"ScheduleExpressionTimezone": "Etc/UTC",
11+
"State": "ENABLED",
12+
"Target": {
13+
"Arn": {
14+
"Fn::Join": [
15+
"",
16+
[
17+
"arn:",
18+
{
19+
"Ref": "AWS::Partition"
20+
},
21+
":scheduler:::aws-sdk:sqs:createQueue"
22+
]
23+
]
24+
},
25+
"Input": "{\"QueueName\":\"aws-scheduler-targets-create-queue\"}",
26+
"RetryPolicy": {
27+
"MaximumEventAgeInSeconds": 86400,
28+
"MaximumRetryAttempts": 185
29+
},
30+
"RoleArn": {
31+
"Fn::GetAtt": [
32+
"SchedulerRoleForTarget5cddf726972933",
33+
"Arn"
34+
]
35+
}
36+
}
37+
}
38+
},
39+
"SchedulerRoleForTarget5cddf726972933": {
40+
"Type": "AWS::IAM::Role",
41+
"Properties": {
42+
"AssumeRolePolicyDocument": {
43+
"Statement": [
44+
{
45+
"Action": "sts:AssumeRole",
46+
"Condition": {
47+
"StringEquals": {
48+
"aws:SourceAccount": {
49+
"Ref": "AWS::AccountId"
50+
},
51+
"aws:SourceArn": {
52+
"Fn::Join": [
53+
"",
54+
[
55+
"arn:",
56+
{
57+
"Ref": "AWS::Partition"
58+
},
59+
":scheduler:",
60+
{
61+
"Ref": "AWS::Region"
62+
},
63+
":",
64+
{
65+
"Ref": "AWS::AccountId"
66+
},
67+
":schedule-group/default"
68+
]
69+
]
70+
}
71+
}
72+
},
73+
"Effect": "Allow",
74+
"Principal": {
75+
"Service": "scheduler.amazonaws.com"
76+
}
77+
}
78+
],
79+
"Version": "2012-10-17"
80+
}
81+
}
82+
},
83+
"SchedulerRoleForTarget5cddf7DefaultPolicy3159C97B": {
84+
"Type": "AWS::IAM::Policy",
85+
"Properties": {
86+
"PolicyDocument": {
87+
"Statement": [
88+
{
89+
"Action": "sqs:CreateQueue",
90+
"Effect": "Allow",
91+
"Resource": "*"
92+
}
93+
],
94+
"Version": "2012-10-17"
95+
},
96+
"PolicyName": "SchedulerRoleForTarget5cddf7DefaultPolicy3159C97B",
97+
"Roles": [
98+
{
99+
"Ref": "SchedulerRoleForTarget5cddf726972933"
100+
}
101+
]
102+
}
103+
}
104+
},
105+
"Parameters": {
106+
"BootstrapVersion": {
107+
"Type": "AWS::SSM::Parameter::Value<String>",
108+
"Default": "/cdk-bootstrap/hnb659fds/version",
109+
"Description": "Version of the CDK Bootstrap resources in this environment, automatically retrieved from SSM Parameter Store. [cdk:skip]"
110+
}
111+
},
112+
"Rules": {
113+
"CheckBootstrapVersion": {
114+
"Assertions": [
115+
{
116+
"Assert": {
117+
"Fn::Not": [
118+
{
119+
"Fn::Contains": [
120+
[
121+
"1",
122+
"2",
123+
"3",
124+
"4",
125+
"5"
126+
],
127+
{
128+
"Ref": "BootstrapVersion"
129+
}
130+
]
131+
}
132+
]
133+
},
134+
"AssertDescription": "CDK bootstrap stack version 6 required. Please run 'cdk bootstrap' with a recent version of the CDK CLI."
135+
}
136+
]
137+
}
138+
}
139+
}

packages/@aws-cdk/aws-scheduler-targets-alpha/test/integ.universal.js.snapshot/IntegTestUniversalDefaultTestDeployAssert42BBB3FD.assets.json

+32
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)