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(scheduler-targets): add support for universal target #32341

Merged
merged 29 commits into from
Jan 13, 2025
Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
59def06
add new target
Nov 29, 2024
58dfca3
add test
sakurai-ryo Dec 1, 2024
0071ae2
export
sakurai-ryo Dec 1, 2024
4c49741
Merge branch 'main' of https://github.com/sakurai-ryo/aws-cdk into sc…
sakurai-ryo Dec 1, 2024
450a7a9
fix readme
sakurai-ryo Dec 1, 2024
2ec0567
Merge branch 'main' of https://github.com/sakurai-ryo/aws-cdk into sc…
sakurai-ryo Dec 25, 2024
7b4a4ec
fix type
sakurai-ryo Dec 25, 2024
319ae62
change construct name
sakurai-ryo Dec 25, 2024
3c1c2e3
rm iamResources and iamAction
sakurai-ryo Dec 25, 2024
209f6ea
fix typo
sakurai-ryo Dec 25, 2024
316aed7
rm unintended file
sakurai-ryo Dec 25, 2024
50c8858
rm link
sakurai-ryo Dec 25, 2024
2de7da1
fix comments
sakurai-ryo Dec 25, 2024
c5bb21a
update readme
sakurai-ryo Dec 25, 2024
7297027
update universal.ts
sakurai-ryo Dec 26, 2024
e0469c6
add props
sakurai-ryo Dec 26, 2024
120eacb
Merge branch 'scheduler-universal-target' of https://github.com/sakur…
sakurai-ryo Dec 26, 2024
e211940
fix indent
sakurai-ryo Dec 26, 2024
bec0414
update readme
sakurai-ryo Dec 26, 2024
3d88826
required iamResources
sakurai-ryo Dec 27, 2024
2fe40a6
update readme
sakurai-ryo Dec 27, 2024
ead143b
fix jsdoc
sakurai-ryo Jan 10, 2025
a2d684b
change props name
sakurai-ryo Jan 10, 2025
b0af30b
Merge branch 'main' of https://github.com/sakurai-ryo/aws-cdk into sc…
sakurai-ryo Jan 10, 2025
6e5591a
Merge branches 'scheduler-universal-target' and 'main' of https://git…
sakurai-ryo Jan 13, 2025
44e9f84
Merge branch 'main' of https://github.com/sakurai-ryo/aws-cdk into sc…
sakurai-ryo Jan 13, 2025
6bae7c3
add policyStatements prop
sakurai-ryo Jan 13, 2025
a6ba36e
fix build error
sakurai-ryo Jan 13, 2025
c49cc2b
Merge branch 'main' into scheduler-universal-target
mergify[bot] Jan 13, 2025
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
52 changes: 52 additions & 0 deletions packages/@aws-cdk/aws-scheduler-targets-alpha/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ The following targets are supported:
9. `targets.KinesisDataFirehosePutRecord`: [Put a record to a Kinesis Data Firehose](#put-a-record-to-a-kinesis-data-firehose)
10. `targets.CodePipelineStartPipelineExecution`: [Start a CodePipeline execution](#start-a-codepipeline-execution)
11. `targets.SageMakerStartPipelineExecution`: [Start a SageMaker pipeline execution](#start-a-sagemaker-pipeline-execution)
12. `targets.Universal`: [Invoke a wider set of AWS API](#invoke-a-wider-set-of-aws-api)

## Invoke a Lambda function

Expand Down Expand Up @@ -312,3 +313,54 @@ new Schedule(this, 'Schedule', {
}),
});
```

## Invoke a wider set of AWS API

Use the `Universal` target to invoke AWS API.

The code snippet below creates an event rule with AWS API as the target which is
called at midnight every day by EventBridge Scheduler.

```ts
new Schedule(this, 'Schedule', {
schedule: ScheduleExpression.cron({
minute: '0',
hour: '0',
}),
target: new targets.Universal({
service: 'rds',
action: 'stopDBCluster',
input: ScheduleTargetInput.fromObject({
DbClusterIdentifier: 'my-db',
}),
}),
});
```

The `service` must be in lowercase and the `action` must be in camelCase.

By default, IAM policies for the Scheduler are extracted from the API call.

You can also provide custom IAM policy statements to the Scheduler.

This is useful when you want to control the permissions of the Scheduler.
sakurai-ryo marked this conversation as resolved.
Show resolved Hide resolved

```ts
new Schedule(this, 'Schedule', {
schedule: ScheduleExpression.rate(Duration.minutes(60)),
target: new targets.Universal({
service: 'sqs',
action: 'sendMessage',
policyStatements: [
new iam.PolicyStatement({
actions: ['sqs:SendMessage'],
resources: ['arn:aws:sqs:us-east-1:123456789012:my_queue'],
}),
new iam.PolicyStatement({
actions: ['kms:Decrypt', 'kms:GenerateDataKey*'],
resources: ['arn:aws:kms:us-west-1:123456789012:key/0987dcba-09fe-87dc-65ba-ab0987654321'],
}),
],
}),
});
```
1 change: 1 addition & 0 deletions packages/@aws-cdk/aws-scheduler-targets-alpha/lib/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,4 @@ export * from './sns-publish';
export * from './sqs-send-message';
export * from './stepfunctions-start-execution';
export * from './target';
export * from './universal';
110 changes: 110 additions & 0 deletions packages/@aws-cdk/aws-scheduler-targets-alpha/lib/universal.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
import { IScheduleTarget } from '@aws-cdk/aws-scheduler-alpha';
import { Aws, Token } from 'aws-cdk-lib';
import { IRole, PolicyStatement } from 'aws-cdk-lib/aws-iam';
import { awsSdkToIamAction } from 'aws-cdk-lib/custom-resources/lib/helpers-internal';
import { ScheduleTargetBase, ScheduleTargetBaseProps } from './target';

/**
* AWS read-only API action name prefixes that are not supported by EventBridge Scheduler.
*
* @see https://docs.aws.amazon.com/scheduler/latest/UserGuide/managing-targets-universal.html
*/
const NOT_SUPPORTED_ACTION_PREFIX = [
'get',
'describe',
'list',
'poll',
'receive',
'search',
'scan',
'query',
'select',
'read',
'lookup',
'discover',
'validate',
'batchGet',
'batchDescribe',
'batchRead',
'transactGet',
'adminGet',
'adminList',
'testMigration',
'retrieve',
'testConnection',
'translateDocument',
'isAuthorized',
'invokeModel',
];

/**
* Properties for a Universal Target
*/
export interface UniversalProps extends ScheduleTargetBaseProps {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit:

Suggested change
export interface UniversalProps extends ScheduleTargetBaseProps {
export interface UniversalTargetProps extends ScheduleTargetBaseProps {

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed.a2d684b

/**
* The AWS service to call.
*
* This must be in lowercase.
*/
readonly service: string;

/**
* The API action to call.
*
* You cannot use read-only API actions such as common GET operations.
*
* Also, This must be in camelCase.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit:

Suggested change
* The API action to call.
*
* You cannot use read-only API actions such as common GET operations.
*
* Also, This must be in camelCase.
* The API action to call. Must be camelCase.
*
* You cannot use read-only API actions such as common GET operations. See https://docs.aws.amazon.com/scheduler/latest/UserGuide/managing-targets-universal.html#unsupported-api-actions

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, fixed.
ead143b

*/
readonly action: string;

/**
* The IAM policy statements to allow the API call.
*
* These policies will be added to the role used to invoke the API.
*
* @default - extract the permission from the API call
*/
readonly policyStatements?: PolicyStatement[];
}

/**
* Use a wider set of AWS API as a target for AWS EventBridge Scheduler.
*
* @see https://docs.aws.amazon.com/scheduler/latest/UserGuide/managing-targets-universal.html
*/
export class Universal extends ScheduleTargetBase implements IScheduleTarget {
constructor(
private readonly props: UniversalProps,
) {
const service = props.service;
const action = props.action;

if (!Token.isUnresolved(service) && service !== service.toLowerCase()) {
throw new Error(`API service must be lowercase, got: ${service}`);
}
if (!Token.isUnresolved(action) && !action.startsWith(action[0]?.toLowerCase())) {
throw new Error(`API action must be camelCase, got: ${action}`);
}
if (!Token.isUnresolved(action) && NOT_SUPPORTED_ACTION_PREFIX.some(prefix => action.startsWith(prefix))) {
throw new Error(`Read-only API action is not supported by EventBridge Scheduler: ${service}:${action}`);
}

const arn = `arn:${Aws.PARTITION}:scheduler:::aws-sdk:${service}:${action}`;
super(props, arn);
}

protected addTargetActionToRole(role: IRole): void {
// If policyStatements are not provided or are empty, add a policy statement extracted from the API call
if (!this.props.policyStatements?.length) {
role.addToPrincipalPolicy(new PolicyStatement({
actions: [awsSdkToIamAction(this.props.service, this.props.action)],
resources: ['*'],
sakurai-ryo marked this conversation as resolved.
Show resolved Hide resolved
}));
return;
}

for (const policyStatement of this.props.policyStatements) {
role.addToPrincipalPolicy(policyStatement);
}
}
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
{
"Resources": {
"Schedule83A77FD1": {
"Type": "AWS::Scheduler::Schedule",
"Properties": {
"FlexibleTimeWindow": {
"Mode": "OFF"
},
"ScheduleExpression": "rate(1 minute)",
"ScheduleExpressionTimezone": "Etc/UTC",
"State": "ENABLED",
"Target": {
"Arn": {
"Fn::Join": [
"",
[
"arn:",
{
"Ref": "AWS::Partition"
},
":scheduler:::aws-sdk:sqs:createQueue"
]
]
},
"Input": "{\"QueueName\":\"aws-scheduler-targets-create-queue\"}",
"RetryPolicy": {
"MaximumEventAgeInSeconds": 86400,
"MaximumRetryAttempts": 185
},
"RoleArn": {
"Fn::GetAtt": [
"SchedulerRoleForTarget5cddf726972933",
"Arn"
]
}
}
}
},
"SchedulerRoleForTarget5cddf726972933": {
"Type": "AWS::IAM::Role",
"Properties": {
"AssumeRolePolicyDocument": {
"Statement": [
{
"Action": "sts:AssumeRole",
"Condition": {
"StringEquals": {
"aws:SourceAccount": {
"Ref": "AWS::AccountId"
},
"aws:SourceArn": {
"Fn::Join": [
"",
[
"arn:",
{
"Ref": "AWS::Partition"
},
":scheduler:",
{
"Ref": "AWS::Region"
},
":",
{
"Ref": "AWS::AccountId"
},
":schedule-group/default"
]
]
}
}
},
"Effect": "Allow",
"Principal": {
"Service": "scheduler.amazonaws.com"
}
}
],
"Version": "2012-10-17"
}
}
},
"SchedulerRoleForTarget5cddf7DefaultPolicy3159C97B": {
"Type": "AWS::IAM::Policy",
"Properties": {
"PolicyDocument": {
"Statement": [
{
"Action": "sqs:CreateQueue",
"Effect": "Allow",
"Resource": "*"
}
],
"Version": "2012-10-17"
},
"PolicyName": "SchedulerRoleForTarget5cddf7DefaultPolicy3159C97B",
"Roles": [
{
"Ref": "SchedulerRoleForTarget5cddf726972933"
}
]
}
}
},
"Parameters": {
"BootstrapVersion": {
"Type": "AWS::SSM::Parameter::Value<String>",
"Default": "/cdk-bootstrap/hnb659fds/version",
"Description": "Version of the CDK Bootstrap resources in this environment, automatically retrieved from SSM Parameter Store. [cdk:skip]"
}
},
"Rules": {
"CheckBootstrapVersion": {
"Assertions": [
{
"Assert": {
"Fn::Not": [
{
"Fn::Contains": [
[
"1",
"2",
"3",
"4",
"5"
],
{
"Ref": "BootstrapVersion"
}
]
}
]
},
"AssertDescription": "CDK bootstrap stack version 6 required. Please run 'cdk bootstrap' with a recent version of the CDK CLI."
}
]
}
}
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading