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): eventBridge putEvents target #27629

Merged
merged 4 commits into from
Nov 29, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
28 changes: 28 additions & 0 deletions packages/@aws-cdk/aws-scheduler-targets-alpha/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ The following targets are supported:
3. `targets.CodeBuildStartBuild`: [Start a CodeBuild job](#start-a-codebuild-job)
4. `targets.SqsSendMessage`: [Send a Message to an Amazon SQS Queue](#send-a-message-to-sqs-queue)
5. `targets.SnsPublish`: [Publish messages to an Amazon SNS topic](#publish-messages-to-an-amazon-sns-topic)
6. `targets.EventBridgePutEvents`: [Put Events on EventBridge](#send-events-to-an-eventbridge-event-bus)

## Invoke a Lambda function

Expand Down Expand Up @@ -178,3 +179,30 @@ new Schedule(this, 'Schedule', {
target,
});
```

## Send events to an EventBridge event bus

Use the `EventBridgePutEvents` target to send events to an EventBridge event bus.

The code snippet below creates an event rule with an EventBridge event bus as a target
called every hour by Event Bridge Scheduler with a custom event payload.

```ts
import * as events from 'aws-cdk-lib/aws-events';

const eventBus = new events.EventBus(this, 'EventBus', {
eventBusName: 'DomainEvents',
});

const eventEntry: targets.EventBridgePutEventsEntry = {
eventBus,
source: 'PetService',
detail: ScheduleTargetInput.fromObject({ Name: 'Fluffy' }),
detailType: '🐶',
};

new Schedule(this, 'Schedule', {
schedule: ScheduleExpression.rate(Duration.hours(1)),
target: new targets.EventBridgePutEvents(eventEntry, {}),
});
```
Copy link
Contributor

Choose a reason for hiding this comment

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

theres a section at the top of this readme that mentions what targets are available, can you put this and the stepfunctions one there please

Copy link
Contributor Author

@WtfJoke WtfJoke Oct 28, 2023

Choose a reason for hiding this comment

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

StepFunction was already there. I've added eventbridge. Thanks for pointing this out

Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import { IScheduleTarget, ISchedule, ScheduleTargetInput, ScheduleTargetConfig } from '@aws-cdk/aws-scheduler-alpha';
import { Names } from 'aws-cdk-lib';
import * as events from 'aws-cdk-lib/aws-events';
import { IRole } from 'aws-cdk-lib/aws-iam';
import { ScheduleTargetBase, ScheduleTargetBaseProps } from './target';
import { sameEnvDimension } from './util';

/**
* An entry to be sent to EventBridge
*
* @see https://docs.aws.amazon.com/eventbridge/latest/APIReference/API_PutEventsRequestEntry.html
*/
export interface EventBridgePutEventsEntry {
/**
* The event body
*
* Can either be provided as an object or as a JSON-serialized string
* @example
kaizencc marked this conversation as resolved.
Show resolved Hide resolved
*
* ScheduleTargetInput.fromText('{"instance-id": "i-1234567890abcdef0", "state": "terminated"}');
* ScheduleTargetInput.fromObject({ Message: 'Hello from a friendly event :)' });
*/
readonly detail: ScheduleTargetInput;

/**
* Used along with the source field to help identify the fields and values expected in the detail field
*
* For example, events by CloudTrail have detail type "AWS API Call via CloudTrail"
* @see https://docs.aws.amazon.com/eventbridge/latest/userguide/eb-events.html
*/
readonly detailType: string;
Copy link
Contributor

Choose a reason for hiding this comment

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

I think we can type this better than a string. From the docs you've provided, the only thing that I see as a possible input here is "AWS API Call via CloudTrail". Lol. So here is what I propose:

readonly detailType: DetailType;

////


export class DetailType {
  public static API_CALL_VIA_CLOUDTRAIL() {
    return new DetailType('AWS API Call via CloudTrail');
  }

  public constructor (public readonly type: string) {}
}

This way, we can

a) give users an easy API to hit rather than getting the specific string correct (DetailType.API_CALL_VIA_CLOUDTRAIL),
b) provide space for future additions if we know of other valid detail types as static methods,
c) users can still specify whatever they want via new DetailType('my special type')

Copy link
Contributor Author

@WtfJoke WtfJoke Nov 28, 2023

Choose a reason for hiding this comment

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

Will do.

There seems to be more, but not documented which detail-type they emit.
I found only one additional example in the docs here "detail-type": "EC2 Instance State-change Notification"

Cant speak for others but our team uses only own detail types (e.g your point c).

For context:
I followed the existing EventBridgePutEvents implementation of the step function tasks package, which uses string as type (most likekly the type is identical).

Copy link
Contributor Author

@WtfJoke WtfJoke Nov 28, 2023

Choose a reason for hiding this comment

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

Introduced class DetailType as per request :)

Copy link
Contributor

Choose a reason for hiding this comment

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

Andddd I am now wavering on this suggestion. I still think it could be a good idea, but might be overengineered. I didn't see the nuance at first that most detail-types are user-owned, and then for events from AWS there maybe detail types with specific strings.

I think we should go back to using string here.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I just dropped my last commit, which effectively reverts the change.


/**
* The event bus the entry will be sent to.
*
*/
readonly eventBus: events.IEventBus;

/**
* The service or application that caused this event to be generated
*
* Example value: `com.example.service`
*
* @see https://docs.aws.amazon.com/eventbridge/latest/userguide/eb-events.html
*/
readonly source: string;
}

/**
* Send an event to an AWS EventBridge by AWS EventBridge Scheduler.
*/
export class EventBridgePutEvents extends ScheduleTargetBase implements IScheduleTarget {
constructor(
private readonly entry: EventBridgePutEventsEntry,
private readonly props: ScheduleTargetBaseProps,
) {
super(props, entry.eventBus.eventBusArn);
if (this.props.input) {
throw new Error('ScheduleTargetBaseProps.input is not supported for EventBridgePutEvents. Please use entry.detail instead.');
}
kaizencc marked this conversation as resolved.
Show resolved Hide resolved
}

protected addTargetActionToRole(schedule: ISchedule, role: IRole): void {
const eventBus = this.entry.eventBus;
const eventBusEnv = eventBus.env;
if (!sameEnvDimension(eventBusEnv.region, schedule.env.region)) {
throw new Error(`Cannot assign eventBus in region ${eventBusEnv.region} to the schedule ${Names.nodeUniqueId(schedule.node)} in region ${schedule.env.region}. Both the schedule and the eventBus must be in the same region.`);
}

if (!sameEnvDimension(eventBusEnv.account, schedule.env.account)) {
throw new Error(`Cannot assign eventBus in account ${eventBusEnv.account} to the schedule ${Names.nodeUniqueId(schedule.node)} in account ${schedule.env.region}. Both the schedule and the eventBus must be in the same account.`);
}

if (this.props.role && !sameEnvDimension(this.props.role.env.account, eventBusEnv.account)) {
throw new Error(`Cannot grant permission to execution role in account ${this.props.role.env.account} to invoke target ${Names.nodeUniqueId(eventBus.node)} in account ${eventBusEnv.account}. Both the target and the execution role must be in the same account.`);
}

eventBus.grantPutEventsTo(role);
}

protected bindBaseTargetConfig(_schedule: ISchedule): ScheduleTargetConfig {
WtfJoke marked this conversation as resolved.
Show resolved Hide resolved
return {
...super.bindBaseTargetConfig(_schedule),
input: this.entry.detail,
eventBridgeParameters: {
detailType: this.entry.detailType,
source: this.entry.source,
},
};
}
}
5 changes: 3 additions & 2 deletions packages/@aws-cdk/aws-scheduler-targets-alpha/lib/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
export * from './codebuild-start-build';
export * from './event-bridge-put-events';
export * from './target';
export * from './lambda-invoke';
export * from './sns-publish';
export * from './sqs-send-message';
export * from './stepfunctions-start-execution';
export * from './target';
export * from './sqs-send-message';
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ export abstract class ScheduleTargetBase {
/**
* Create a return a Schedule Target Configuration for the given schedule
* @param schedule
* @returnn
* @returns a Schedule Target Configuration
*/
bind(schedule: ISchedule): ScheduleTargetConfig {
return this.bindBaseTargetConfig(schedule);
Expand Down
Loading