diff --git a/python/eventbridge-mesh/README.md b/python/eventbridge-mesh/README.md new file mode 100644 index 0000000000..e355d2fd74 --- /dev/null +++ b/python/eventbridge-mesh/README.md @@ -0,0 +1,78 @@ +# CDK Python Sample: Event Bridge mesh with CDK + +![Language-Support: Stable](https://img.shields.io/badge/language--support-stable-success.svg?style=for-the-badge) + +## Description: +A CDK way to set up a Event Bridge Mesh(Cross-Account), where you relay the messages from one Event Bridge in a producer account to another Event Bridge in a consumer account + +## Backgroud: +This is a CDK application that implements cross-account event routing using Amazon EventBridge. It's designed for enterprise scenarios where: + +- Teams work in separate AWS accounts (producer and consumer) +- Consumer teams need autonomy to manage their event processing +- Event routing changes shouldn't require coordination with producer teams + +## Solution: + +### Single consumer +![architecture](./images/single-consumer.png) + +### Multiple consumers +![architecture](./images/multi-consumers.png) + + +## Instructions + +### CDK bootstrapping +To deploy the stacks in different accounts, you need to bootstrap the CDKToolkit with trust flag. Assuming you will run `cdk deploy` command from account ID: `123456789012`, and deploys producer stack to account ID: `111111111111`, and consumer stack to account ID: `222222222222` + +1. In account: `111111111111`, run the below command: +``` +cdk bootstrap aws://111111111111/us-east-1 \ + --trust 123456789012 \ + --cloudformation-execution-policies arn:aws:iam::aws:policy/AdministratorAccess +``` + +2. In account: `222222222222`, run the below command: +``` +cdk bootstrap aws://222222222222/us-east-1 \ + --trust 123456789012 \ + --cloudformation-execution-policies arn:aws:iam::aws:policy/AdministratorAccess +``` + +3. (Optional: Only do this step, when you deploys multiple consumers solution) In account: `333333333333`, run the below command: +``` +cdk bootstrap aws://333333333333/us-east-1 \ + --trust 123456789012 \ + --cloudformation-execution-policies arn:aws:iam::aws:policy/AdministratorAccess +``` + +### Deploy Single consumer solution: +1. Run: `cd single-consumer` +2. Change the values of `producerAccountId` and `consumerAccountId` in `cdk.json` +3. Install and configure the CDK: https://docs.aws.amazon.com/CDK/latest/userguide/install_config.html +4. Make sure you use a role or user has proper permission in account ID: `123456789012`, and run below commands: +```shell +npm run build + +cdk ls + +cdk synth + +cdk deploy --all +``` + +### Deploy Multiple consumers solution: +1. Run: `cd multiple-consumer` +2. Change the values of `producerAccountId`, `consumer1AccountId`, and `consumer2AccountId` in `cdk.json` +3. Install and configure the CDK: https://docs.aws.amazon.com/CDK/latest/userguide/install_config.html +4. Make sure you use a role or user has proper permission in account ID: `123456789012`, and run below commands: +```shell +npm run build + +cdk ls + +cdk synth + +cdk deploy --all +``` diff --git a/python/eventbridge-mesh/images/multi-consumers.png b/python/eventbridge-mesh/images/multi-consumers.png new file mode 100644 index 0000000000..8c4ad38478 Binary files /dev/null and b/python/eventbridge-mesh/images/multi-consumers.png differ diff --git a/python/eventbridge-mesh/images/single-consumer.png b/python/eventbridge-mesh/images/single-consumer.png new file mode 100644 index 0000000000..a3daa8b2f7 Binary files /dev/null and b/python/eventbridge-mesh/images/single-consumer.png differ diff --git a/python/eventbridge-mesh/multiple-consumers/app.py b/python/eventbridge-mesh/multiple-consumers/app.py new file mode 100644 index 0000000000..7c520401e2 --- /dev/null +++ b/python/eventbridge-mesh/multiple-consumers/app.py @@ -0,0 +1,128 @@ +#!/usr/bin/env python3 +from aws_cdk import ( + aws_events as events, + aws_events_targets as targets, + aws_logs as logs, + aws_iam as iam, + App, Stack, Environment +) +from constructs import Construct + +class ProducerStack(Stack): + def __init__(self, scope: Construct, id: str, *, + app_name: str, + consumer_accounts: list[str], + **kwargs) -> None: + super().__init__(scope, id, **kwargs) + + # Create the EventBus + producer_event_bus = events.EventBus( + self, f"{app_name}-producer-event-bus" + ) + + # Create rules for each consumer account + for index, consumer_account_id in enumerate(consumer_accounts): + # Create rule to forward events to consumer account + rule = events.Rule( + self, + f"{app_name}-forward-to-consumer-{index + 1}-rule", + event_bus=producer_event_bus, + event_pattern=events.EventPattern( + source=['com.myapp.events'] + ) + ) + + # Add target to forward to consumer account's event bus + consumer_bus = events.EventBus.from_event_bus_arn( + self, + f"{app_name}-consumer-{index + 1}-event-bus", + f"arn:aws:events:{Stack.of(self).region}:{consumer_account_id}:event-bus/default" + ) + rule.add_target(targets.EventBus(consumer_bus)) + + +class ConsumerStack(Stack): + def __init__(self, scope: Construct, id: str, *, + app_name: str, + consumer_name: str, + producer_account_id: str, + **kwargs) -> None: + super().__init__(scope, id, **kwargs) + + # Create or reference the consumer event bus + consumer_event_bus = events.EventBus( + self, f"{app_name}-{consumer_name}-event-bus" + ) + + # Add policy to allow producer account to put events + consumer_event_bus.add_to_resource_policy(iam.PolicyStatement( + sid="allowProducerAccount", + effect=iam.Effect.ALLOW, + principals=[iam.AccountPrincipal(producer_account_id)], + actions=["events:PutEvents"], + resources=[consumer_event_bus.event_bus_arn] + )) + + # Create consumer rules + consumer_rule = events.Rule( + self, + f"{app_name}-{consumer_name}-rule", + event_bus=consumer_event_bus, + event_pattern=events.EventPattern( + source=['com.myapp.events'], + detail_type=['specific-event-type'] + ) + ) + + # Add target (e.g., CloudWatch) + log_group = logs.LogGroup( + self, f"{app_name}-{consumer_name}-logs" + ) + consumer_rule.add_target(targets.CloudWatchLogGroup(log_group)) + + +app = App() + +# Get context values +app_name = app.node.try_get_context("appName") +region = app.node.try_get_context("region") +producer_account_id = app.node.try_get_context("producerAccountId") +consumer1_account_id = app.node.try_get_context("consumer1AccountId") +consumer2_account_id = app.node.try_get_context("consumer2AccountId") + +# Create producer stack +producer_stack = ProducerStack( + app, f"{app_name}-producer-stack", + app_name=app_name, + consumer_accounts=[consumer1_account_id, consumer2_account_id], + env=Environment( + account=producer_account_id, + region=region + ) +) + +# Create consumer 1 stack +consumer1_stack = ConsumerStack( + app, f"{app_name}-consumer1-stack", + app_name=app_name, + consumer_name="consumer1", + producer_account_id=producer_account_id, + env=Environment( + account=consumer1_account_id, + region=region + ) +) + +# Create consumer 2 stack +consumer2_stack = ConsumerStack( + app, f"{app_name}-consumer2-stack", + app_name=app_name, + consumer_name="consumer2", + producer_account_id=producer_account_id, + env=Environment( + account=consumer2_account_id, + region=region + ) +) + +app.synth() diff --git a/python/eventbridge-mesh/multiple-consumers/cdk.json b/python/eventbridge-mesh/multiple-consumers/cdk.json new file mode 100644 index 0000000000..f354a5dabd --- /dev/null +++ b/python/eventbridge-mesh/multiple-consumers/cdk.json @@ -0,0 +1,10 @@ +{ + "app": "python3 app.py", + "context": { + "appName": "eventbridge-mesh", + "region": "us-east-1", + "producerAccountId": "111111111111", + "consumer1AccountId": "222222222222", + "consumer2AccountId": "333333333333" + } +} \ No newline at end of file diff --git a/python/eventbridge-mesh/multiple-consumers/requirements.txt b/python/eventbridge-mesh/multiple-consumers/requirements.txt new file mode 100644 index 0000000000..23452e33ae --- /dev/null +++ b/python/eventbridge-mesh/multiple-consumers/requirements.txt @@ -0,0 +1,2 @@ +aws-cdk-lib==2.186.0 +constructs>=10.0.0,<11.0.0 \ No newline at end of file diff --git a/python/eventbridge-mesh/single-consumer/app.py b/python/eventbridge-mesh/single-consumer/app.py new file mode 100644 index 0000000000..b5b6c338f4 --- /dev/null +++ b/python/eventbridge-mesh/single-consumer/app.py @@ -0,0 +1,111 @@ +#!/usr/bin/env python3 +from aws_cdk import ( + aws_events as events, + aws_events_targets as targets, + aws_logs as logs, + aws_iam as iam, + App, Stack, Environment +) +from constructs import Construct + +class ProducerStack(Stack): + def __init__(self, scope: Construct, id: str, *, + app_name: str, + consumer_account_id: str, + **kwargs) -> None: + super().__init__(scope, id, **kwargs) + + # Create the EventBus + producer_event_bus = events.EventBus( + self, f"{app_name}-producer-event-bus" + ) + + # Create rule to forward events to consumer account + rule = events.Rule( + self, f"{app_name}-forward-to-consumer-rule", + event_bus=producer_event_bus, + event_pattern=events.EventPattern( + source=['com.myapp.events'] + ) + ) + + # Add target to forward to consumer account's event bus + consumer_bus = events.EventBus.from_event_bus_arn( + self, + 'ConsumerEventBus', + f"arn:aws:events:{Stack.of(self).region}:{consumer_account_id}:event-bus/default" + ) + rule.add_target(targets.EventBus(consumer_bus)) + + # Optional: Add CloudWatch target for monitoring + log_group = logs.LogGroup(self, f"{app_name}-producer-logs") + rule.add_target(targets.CloudWatchLogGroup(log_group)) + + +class ConsumerStack(Stack): + def __init__(self, scope: Construct, id: str, *, + app_name: str, + producer_account_id: str, + **kwargs) -> None: + super().__init__(scope, id, **kwargs) + + # Create or reference the consumer event bus + consumer_event_bus = events.EventBus( + self, f"{app_name}-consumer-event-bus" + ) + + # Add policy to allow producer account to put events + consumer_event_bus.add_to_resource_policy(iam.PolicyStatement( + sid="allowProducerAccount", + effect=iam.Effect.ALLOW, + principals=[iam.AccountPrincipal(producer_account_id)], + actions=["events:PutEvents"], + resources=[consumer_event_bus.event_bus_arn] + )) + + # Create consumer rules + consumer_rule = events.Rule( + self, f"{app_name}-consumer-rule", + event_bus=consumer_event_bus, + event_pattern=events.EventPattern( + source=['com.myapp.events'], + detail_type=['specific-event-type'] + ) + ) + + # Add target (e.g., CloudWatch) + log_group = logs.LogGroup(self, f"{app_name}-consumer-logs") + consumer_rule.add_target(targets.CloudWatchLogGroup(log_group)) + + +app = App() + +# Get context values +app_name = app.node.try_get_context("appName") +region = app.node.try_get_context("region") +producer_account_id = app.node.try_get_context("producerAccountId") +consumer_account_id = app.node.try_get_context("consumer1AccountId") + +# Create producer stack +producer_stack = ProducerStack( + app, f"{app_name}-producer-stack", + app_name=app_name, + consumer_account_id=consumer_account_id, + env=Environment( + account=producer_account_id, + region=region + ) +) + +# Create consumer stack +consumer_stack = ConsumerStack( + app, f"{app_name}-consumer-stack", + app_name=app_name, + producer_account_id=producer_account_id, + env=Environment( + account=consumer_account_id, + region=region + ) +) + +app.synth() diff --git a/python/eventbridge-mesh/single-consumer/cdk.json b/python/eventbridge-mesh/single-consumer/cdk.json new file mode 100644 index 0000000000..1d604b114d --- /dev/null +++ b/python/eventbridge-mesh/single-consumer/cdk.json @@ -0,0 +1,10 @@ +{ + "app": "python3 app.py", + "context": { + "appName": "eventbridge-mesh", + "region": "us-east-1", + "producerAccountId": "111111111111", + "consumer1AccountId": "222222222222" + } + +} \ No newline at end of file diff --git a/python/eventbridge-mesh/single-consumer/requirements.txt b/python/eventbridge-mesh/single-consumer/requirements.txt new file mode 100644 index 0000000000..23452e33ae --- /dev/null +++ b/python/eventbridge-mesh/single-consumer/requirements.txt @@ -0,0 +1,2 @@ +aws-cdk-lib==2.186.0 +constructs>=10.0.0,<11.0.0 \ No newline at end of file diff --git a/typescript/eventbridge-mesh/.gitignore b/typescript/eventbridge-mesh/.gitignore new file mode 100644 index 0000000000..977f943d1d --- /dev/null +++ b/typescript/eventbridge-mesh/.gitignore @@ -0,0 +1,11 @@ +*.js +!jest.config.js +*.d.ts +node_modules + +# CDK asset staging directory +.cdk.staging +cdk.out + +package-lock.json + diff --git a/typescript/eventbridge-mesh/.npmignore b/typescript/eventbridge-mesh/.npmignore new file mode 100644 index 0000000000..c1d6d45dcf --- /dev/null +++ b/typescript/eventbridge-mesh/.npmignore @@ -0,0 +1,6 @@ +*.ts +!*.d.ts + +# CDK asset staging directory +.cdk.staging +cdk.out diff --git a/typescript/eventbridge-mesh/README.md b/typescript/eventbridge-mesh/README.md new file mode 100644 index 0000000000..db8c2f3279 --- /dev/null +++ b/typescript/eventbridge-mesh/README.md @@ -0,0 +1,78 @@ +# CDK Typescript Sample: Event Bridge mesh with CDK + +![Language-Support: Stable](https://img.shields.io/badge/language--support-stable-success.svg?style=for-the-badge) + +## Description: +A CDK way to set up a Event Bridge Mesh(Cross-Account), where you relay the messages from one Event Bridge in a producer account to another Event Bridge in a consumer account + +## Backgroud: +This is a CDK application that implements cross-account event routing using Amazon EventBridge. It's designed for enterprise scenarios where: + +- Teams work in separate AWS accounts (producer and consumer) +- Consumer teams need autonomy to manage their event processing +- Event routing changes shouldn't require coordination with producer teams + +## Solution: + +### Single consumer +![architecture](./images/single-consumer.png) + +### Multiple consumers +![architecture](./images/multi-consumers.png) + + +## Instructions + +### CDK bootstrapping +To deploy the stacks in different accounts, you need to bootstrap the CDKToolkit with trust flag. Assuming you will run `cdk deploy` command from account ID: `123456789012`, and deploys producer stack to account ID: `111111111111`, and consumer stack to account ID: `222222222222` + +1. In account: `111111111111`, run the below command: +``` +cdk bootstrap aws://111111111111/us-east-1 \ + --trust 123456789012 \ + --cloudformation-execution-policies arn:aws:iam::aws:policy/AdministratorAccess +``` + +2. In account: `222222222222`, run the below command: +``` +cdk bootstrap aws://222222222222/us-east-1 \ + --trust 123456789012 \ + --cloudformation-execution-policies arn:aws:iam::aws:policy/AdministratorAccess +``` + +3. (Optional: Only do this step, when you deploys multiple consumers solution) In account: `333333333333`, run the below command: +``` +cdk bootstrap aws://333333333333/us-east-1 \ + --trust 123456789012 \ + --cloudformation-execution-policies arn:aws:iam::aws:policy/AdministratorAccess +``` + +### Deploy Single consumer solution: +1. Run: `cd single-consumer` +2. Change the values of `producerAccountId` and `consumerAccountId` in `cdk.json` +3. Install and configure the CDK: https://docs.aws.amazon.com/CDK/latest/userguide/install_config.html +4. Make sure you use a role or user has proper permission in account ID: `123456789012`, and run below commands: +```shell +npm run build + +cdk ls + +cdk synth + +cdk deploy --all +``` + +### Deploy Multiple consumers solution: +1. Run: `cd multiple-consumer` +2. Change the values of `producerAccountId`, `consumer1AccountId`, and `consumer2AccountId` in `cdk.json` +3. Install and configure the CDK: https://docs.aws.amazon.com/CDK/latest/userguide/install_config.html +4. Make sure you use a role or user has proper permission in account ID: `123456789012`, and run below commands: +```shell +npm run build + +cdk ls + +cdk synth + +cdk deploy --all +``` diff --git a/typescript/eventbridge-mesh/images/multi-consumers.png b/typescript/eventbridge-mesh/images/multi-consumers.png new file mode 100644 index 0000000000..8c4ad38478 Binary files /dev/null and b/typescript/eventbridge-mesh/images/multi-consumers.png differ diff --git a/typescript/eventbridge-mesh/images/single-consumer.png b/typescript/eventbridge-mesh/images/single-consumer.png new file mode 100644 index 0000000000..a3daa8b2f7 Binary files /dev/null and b/typescript/eventbridge-mesh/images/single-consumer.png differ diff --git a/typescript/eventbridge-mesh/multiple-consumers/cdk.json b/typescript/eventbridge-mesh/multiple-consumers/cdk.json new file mode 100644 index 0000000000..13b73af643 --- /dev/null +++ b/typescript/eventbridge-mesh/multiple-consumers/cdk.json @@ -0,0 +1,10 @@ +{ + "app": "npx ts-node --prefer-ts-exts index.ts", + "context": { + "appName": "eventbridge-mesh", + "region": "us-east-1", + "producerAccountId": "111111111111", + "consumer1AccountId": "222222222222", + "consumer2AccountId": "333333333333" + } +} diff --git a/typescript/eventbridge-mesh/multiple-consumers/index.ts b/typescript/eventbridge-mesh/multiple-consumers/index.ts new file mode 100644 index 0000000000..dffe62f799 --- /dev/null +++ b/typescript/eventbridge-mesh/multiple-consumers/index.ts @@ -0,0 +1,122 @@ +import * as cdk from 'aws-cdk-lib'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import { EventBus, Rule } from 'aws-cdk-lib/aws-events'; +import { LogGroup } from 'aws-cdk-lib/aws-logs'; +import { CloudWatchLogGroup } from 'aws-cdk-lib/aws-events-targets'; +import { AccountPrincipal, Effect, PolicyStatement } from 'aws-cdk-lib/aws-iam'; +import { Construct } from 'constructs'; + +export interface producerStackProps extends cdk.StackProps { + readonly appName: string; + readonly consumerAccounts: string[]; +} + +export interface consumerStackProps extends cdk.StackProps { + readonly appName: string; + readonly consumerName: string; + readonly producerAccountId: string; +} + +export class producerStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: producerStackProps) { + super(scope, id, props); + + // Create the EventBus + const producerEventBus = new EventBus(this, `${props.appName}-producer-event-bus`); + + props.consumerAccounts.forEach((consumerAccountId, index) => { + // Create rule to forward events to consumer account + const rule = new Rule(this, `${props.appName}-forward-to-consumer-${index + 1}-rule`, { + eventBus: producerEventBus, + eventPattern: { + // Define your event pattern here + source: ['com.myapp.events'], + }, + }); + + // Add target to forward to consumer account's event bus + rule.addTarget(new targets.EventBus( + EventBus.fromEventBusArn( + this, + `${props.appName}-consumer-${index + 1}-event-bus`, + `arn:aws:events:${cdk.Stack.of(this).region}:${consumerAccountId}:event-bus/default` + ) + )); + }); + } +} + +export class consumerStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: consumerStackProps) { + super(scope, id, props); + + // Create or reference the consumer event bus + const consumerEventBus = new EventBus(this, `${props.appName}-${props.consumerName}-event-bus`); + + // Add policy to allow producer account to put events + consumerEventBus.addToResourcePolicy(new PolicyStatement({ + sid: 'allowProducerAccount', + effect: Effect.ALLOW, + principals: [new AccountPrincipal(props.producerAccountId)], + actions: ['events:PutEvents'], + resources: [consumerEventBus.eventBusArn] + })); + + // Create consumer rules + const consumerRule = new Rule(this, `${props.appName}-${props.consumerName}-rule`, { + eventBus: consumerEventBus, + eventPattern: { + // Define more specific filtering here + source: ['com.myapp.events'], + detail: { + type: ['specific-event-type'] + } + } + }); + + // Add target (e.g., CloudWatch) + consumerRule.addTarget(new CloudWatchLogGroup( + new LogGroup(this, `${props.appName}-${props.consumerName}-logs`) + )); + } +} + +const app = new cdk.App(); +const appName = app.node.tryGetContext('appName'); +const region = app.node.tryGetContext('region'); +const producerAccountId = app.node.tryGetContext('producerAccountId'); +const consumer1AccountId = app.node.tryGetContext('consumer1AccountId'); +const consumer2AccountId = app.node.tryGetContext('consumer2AccountId'); + +new producerStack(app, `${appName}-producer-stack`, { + env: { + account: producerAccountId, + region: region, + }, + appName, + consumerAccounts: [consumer1AccountId, consumer2AccountId], +}); + +// Deploy consumer 1 stack +new consumerStack(app, `${appName}-consumer1-stack`, { + env: { + account: consumer1AccountId, + region: region, + }, + appName, + producerAccountId, + consumerName: 'consumer1' +}); + +// Deploy consumer 2 stack +new consumerStack(app, `${appName}-consumer2-stack`, { + env: { + account: consumer2AccountId, + region: region, + }, + appName, + producerAccountId, + consumerName: 'consumer2' +}); + +app.synth(); diff --git a/typescript/eventbridge-mesh/multiple-consumers/package.json b/typescript/eventbridge-mesh/multiple-consumers/package.json new file mode 100644 index 0000000000..7f5cf9707b --- /dev/null +++ b/typescript/eventbridge-mesh/multiple-consumers/package.json @@ -0,0 +1,22 @@ +{ + "name": "eventbridge-mesh", + "version": "0.1.0", + "scripts": { + "build": "tsc", + "watch": "tsc -w", + "cdk": "cdk" + }, + "devDependencies": { + "@types/jest": "^29.5.14", + "@types/node": "22.7.9", + "jest": "^29.7.0", + "ts-jest": "^29.2.5", + "aws-cdk": "2.1007.0", + "ts-node": "^10.9.2", + "typescript": "~5.6.3" + }, + "dependencies": { + "aws-cdk-lib": "2.186.0", + "constructs": "^10.0.0" + } +} diff --git a/typescript/eventbridge-mesh/multiple-consumers/tsconfig.json b/typescript/eventbridge-mesh/multiple-consumers/tsconfig.json new file mode 100644 index 0000000000..aaa7dc510f --- /dev/null +++ b/typescript/eventbridge-mesh/multiple-consumers/tsconfig.json @@ -0,0 +1,31 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": [ + "es2020", + "dom" + ], + "declaration": true, + "strict": true, + "noImplicitAny": true, + "strictNullChecks": true, + "noImplicitThis": true, + "alwaysStrict": true, + "noUnusedLocals": false, + "noUnusedParameters": false, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": false, + "inlineSourceMap": true, + "inlineSources": true, + "experimentalDecorators": true, + "strictPropertyInitialization": false, + "typeRoots": [ + "./node_modules/@types" + ] + }, + "exclude": [ + "node_modules", + "cdk.out" + ] +} diff --git a/typescript/eventbridge-mesh/single-consumer/cdk.json b/typescript/eventbridge-mesh/single-consumer/cdk.json new file mode 100644 index 0000000000..f593a9b66e --- /dev/null +++ b/typescript/eventbridge-mesh/single-consumer/cdk.json @@ -0,0 +1,9 @@ +{ + "app": "npx ts-node --prefer-ts-exts index.ts", + "context": { + "appName": "eventbridge-mesh", + "region": "us-east-1", + "producerAccountId": "111111111111", + "consumerAccountId": "222222222222" + } +} diff --git a/typescript/eventbridge-mesh/single-consumer/index.ts b/typescript/eventbridge-mesh/single-consumer/index.ts new file mode 100644 index 0000000000..ff0f967113 --- /dev/null +++ b/typescript/eventbridge-mesh/single-consumer/index.ts @@ -0,0 +1,110 @@ +import * as cdk from 'aws-cdk-lib'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import { EventBus, Rule } from 'aws-cdk-lib/aws-events'; +import { LogGroup } from 'aws-cdk-lib/aws-logs'; +import { CloudWatchLogGroup } from 'aws-cdk-lib/aws-events-targets'; +import { AccountPrincipal, Effect, PolicyStatement } from 'aws-cdk-lib/aws-iam'; +import { Construct } from 'constructs'; + +export interface producerStackProps extends cdk.StackProps { + readonly appName: string; + readonly consumerAccountId: string; +} + +export interface consumerStackProps extends cdk.StackProps { + readonly appName: string; + readonly producerAccountId: string; +} + +export class producerStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: producerStackProps) { + super(scope, id, props); + + // Create the EventBus + const producerEventBus = new EventBus(this, `${props.appName}-producer-event-bus`); + + // Create rule to forward events to consumer account + const rule = new Rule(this, `${props.appName}-forward-to-consumer-rule`, { + eventBus: producerEventBus, + eventPattern: { + // Define your event pattern here + source: ['com.myapp.events'], + }, + }); + + // Add target to forward to consumer account's event bus + rule.addTarget(new targets.EventBus( + EventBus.fromEventBusArn( + this, + 'ConsumerEventBus', + `arn:aws:events:${cdk.Stack.of(this).region}:${props.consumerAccountId}:event-bus/default` + ) + )); + + // Optional: Add CloudWatch target for monitoring + rule.addTarget(new targets.CloudWatchLogGroup( + new LogGroup(this, `${props.appName}-producer-logs`) + )); + } + } + + export class consumerStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: consumerStackProps) { + super(scope, id, props); + + // Create or reference the consumer event bus + const consumerEventBus = new EventBus(this, `${props.appName}-consumer-event-bus`); + + // Add policy to allow producer account to put events + consumerEventBus.addToResourcePolicy(new PolicyStatement({ + sid: 'allowProducerAccount', + effect: Effect.ALLOW, + principals: [new AccountPrincipal(props.producerAccountId)], + actions: ['events:PutEvents'], + resources: [consumerEventBus.eventBusArn] + })); + + // Create consumer rules + const consumerRule = new Rule(this, `${props.appName}-consumer-rule`, { + eventBus: consumerEventBus, + eventPattern: { + // Define more specific filtering here + source: ['com.myapp.events'], + detail: { + type: ['specific-event-type'] + } + } + }); + + // Add target (e.g., CloudWatch) + consumerRule.addTarget(new CloudWatchLogGroup( + new LogGroup(this, `${props.appName}-consumer-logs`) + )); + } + } + + const app = new cdk.App(); + const appName = app.node.tryGetContext('appName'); + const region = app.node.tryGetContext('region'); + const producerAccountId = app.node.tryGetContext('producerAccountId'); + const consumerAccountId = app.node.tryGetContext('consumer1AccountId'); + + new producerStack(app, `${appName}-producer-stack`, { + env: { + account: producerAccountId, + region: region, + }, + appName, + consumerAccountId, + }); + + new consumerStack(app, `${appName}-consumer-stack`, { + env: { + account: consumerAccountId, + region: region, + }, + appName, + producerAccountId, + }); + + app.synth(); diff --git a/typescript/eventbridge-mesh/single-consumer/package.json b/typescript/eventbridge-mesh/single-consumer/package.json new file mode 100644 index 0000000000..7f5cf9707b --- /dev/null +++ b/typescript/eventbridge-mesh/single-consumer/package.json @@ -0,0 +1,22 @@ +{ + "name": "eventbridge-mesh", + "version": "0.1.0", + "scripts": { + "build": "tsc", + "watch": "tsc -w", + "cdk": "cdk" + }, + "devDependencies": { + "@types/jest": "^29.5.14", + "@types/node": "22.7.9", + "jest": "^29.7.0", + "ts-jest": "^29.2.5", + "aws-cdk": "2.1007.0", + "ts-node": "^10.9.2", + "typescript": "~5.6.3" + }, + "dependencies": { + "aws-cdk-lib": "2.186.0", + "constructs": "^10.0.0" + } +} diff --git a/typescript/eventbridge-mesh/single-consumer/tsconfig.json b/typescript/eventbridge-mesh/single-consumer/tsconfig.json new file mode 100644 index 0000000000..aaa7dc510f --- /dev/null +++ b/typescript/eventbridge-mesh/single-consumer/tsconfig.json @@ -0,0 +1,31 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": [ + "es2020", + "dom" + ], + "declaration": true, + "strict": true, + "noImplicitAny": true, + "strictNullChecks": true, + "noImplicitThis": true, + "alwaysStrict": true, + "noUnusedLocals": false, + "noUnusedParameters": false, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": false, + "inlineSourceMap": true, + "inlineSources": true, + "experimentalDecorators": true, + "strictPropertyInitialization": false, + "typeRoots": [ + "./node_modules/@types" + ] + }, + "exclude": [ + "node_modules", + "cdk.out" + ] +}