diff --git a/packages/@aws-cdk/aws-events-targets/README.md b/packages/@aws-cdk/aws-events-targets/README.md index 4b795ce5f19b5..d49621b0199cd 100644 --- a/packages/@aws-cdk/aws-events-targets/README.md +++ b/packages/@aws-cdk/aws-events-targets/README.md @@ -90,6 +90,39 @@ const rule = new events.Rule(this, 'rule', { rule.addTarget(new targets.CloudWatchLogGroup(logGroup)); ``` +## Trigger a CodeBuild project + +Use the `CodeBuildProject` target to trigger a CodeBuild project. + +The code snippet below creates a CodeCommit repository that triggers a CodeBuild project +on commit to the master branch. You can optionally attach a +[dead letter queue](https://docs.aws.amazon.com/eventbridge/latest/userguide/rule-dlq.html). + +```ts +import * as codebuild from '@aws-sdk/aws-codebuild'; +import * as codecommit from '@aws-sdk/aws-codecommit'; +import * as sqs from '@aws-sdk/aws-sqs'; +import * as targets from "@aws-cdk/aws-events-targets"; + +const repo = new codecommit.Repository(this, 'MyRepo', { + repositoryName: 'aws-cdk-codebuild-events', +}); + +const project = new codebuild.Project(this, 'MyProject', { + source: codebuild.Source.codeCommit({ repository: repo }), +}); + +const deadLetterQueue = new sqs.Queue(this, 'DeadLetterQueue'); + +// trigger a build when a commit is pushed to the repo +const onCommitRule = repo.onCommit('OnCommit', { + target: new targets.CodeBuildProject(project, { + deadLetterQueue: deadLetterQueue, + }), + branches: ['master'], +}); +``` + ## Trigger a State Machine Use the `SfnStateMachine` target to trigger a State Machine. diff --git a/packages/@aws-cdk/aws-events-targets/lib/codebuild.ts b/packages/@aws-cdk/aws-events-targets/lib/codebuild.ts index 55eac4cadd32f..d5b5e753b3867 100644 --- a/packages/@aws-cdk/aws-events-targets/lib/codebuild.ts +++ b/packages/@aws-cdk/aws-events-targets/lib/codebuild.ts @@ -1,7 +1,8 @@ import * as codebuild from '@aws-cdk/aws-codebuild'; import * as events from '@aws-cdk/aws-events'; import * as iam from '@aws-cdk/aws-iam'; -import { singletonEventRole } from './util'; +import * as sqs from '@aws-cdk/aws-sqs'; +import { addToDeadLetterQueueResourcePolicy, singletonEventRole } from './util'; /** * Customize the CodeBuild Event Target @@ -24,6 +25,18 @@ export interface CodeBuildProjectProps { * @default - the entire EventBridge event */ readonly event?: events.RuleTargetInput; + + /** + * The SQS queue to be used as deadLetterQueue. + * Check out the [considerations for using a dead-letter queue](https://docs.aws.amazon.com/eventbridge/latest/userguide/rule-dlq.html#dlq-considerations). + * + * The events not successfully delivered are automatically retried for a specified period of time, + * depending on the retry policy of the target. + * If an event is not delivered before all retry attempts are exhausted, it will be sent to the dead letter queue. + * + * @default - no dead-letter queue + */ + readonly deadLetterQueue?: sqs.IQueue; } /** @@ -39,9 +52,15 @@ export class CodeBuildProject implements events.IRuleTarget { * Allows using build projects as event rule targets. */ public bind(_rule: events.IRule, _id?: string): events.RuleTargetConfig { + + if (this.props.deadLetterQueue) { + addToDeadLetterQueueResourcePolicy(_rule, this.props.deadLetterQueue); + } + return { id: '', arn: this.project.projectArn, + deadLetterConfig: this.props.deadLetterQueue ? { arn: this.props.deadLetterQueue?.queueArn } : undefined, role: this.props.eventRole || singletonEventRole(this.project, [ new iam.PolicyStatement({ actions: ['codebuild:StartBuild'], diff --git a/packages/@aws-cdk/aws-events-targets/test/codebuild/codebuild.test.ts b/packages/@aws-cdk/aws-events-targets/test/codebuild/codebuild.test.ts index c7bb3d59b68e8..1eba641d7c8ce 100644 --- a/packages/@aws-cdk/aws-events-targets/test/codebuild/codebuild.test.ts +++ b/packages/@aws-cdk/aws-events-targets/test/codebuild/codebuild.test.ts @@ -2,6 +2,7 @@ import { expect, haveResource } from '@aws-cdk/assert'; import * as codebuild from '@aws-cdk/aws-codebuild'; import * as events from '@aws-cdk/aws-events'; import * as iam from '@aws-cdk/aws-iam'; +import * as sqs from '@aws-cdk/aws-sqs'; import { CfnElement, Stack } from '@aws-cdk/core'; import * as targets from '../../lib'; @@ -120,4 +121,84 @@ describe('CodeBuild event target', () => { ], })); }); + + test('use a Dead Letter Queue for the rule target', () => { + // GIVEN + const rule = new events.Rule(stack, 'Rule', { + schedule: events.Schedule.expression('rate(1 hour)'), + }); + + const queue = new sqs.Queue(stack, 'Queue'); + + // WHEN + const eventInput = { + buildspecOverride: 'buildspecs/hourly.yml', + }; + + rule.addTarget( + new targets.CodeBuildProject(project, { + event: events.RuleTargetInput.fromObject(eventInput), + deadLetterQueue: queue, + }), + ); + + // THEN + expect(stack).to(haveResource('AWS::Events::Rule', { + Targets: [ + { + Arn: projectArn, + Id: 'Target0', + DeadLetterConfig: { + Arn: { + 'Fn::GetAtt': [ + 'Queue4A7E3555', + 'Arn', + ], + }, + }, + Input: JSON.stringify(eventInput), + RoleArn: { + 'Fn::GetAtt': ['MyProjectEventsRole5B7D93F5', 'Arn'], + }, + }, + ], + })); + + expect(stack).to(haveResource('AWS::SQS::QueuePolicy', { + PolicyDocument: { + Statement: [ + { + Action: 'sqs:SendMessage', + Condition: { + ArnEquals: { + 'aws:SourceArn': { + 'Fn::GetAtt': [ + 'Rule4C995B7F', + 'Arn', + ], + }, + }, + }, + Effect: 'Allow', + Principal: { + Service: 'events.amazonaws.com', + }, + Resource: { + 'Fn::GetAtt': [ + 'Queue4A7E3555', + 'Arn', + ], + }, + Sid: 'AllowEventRuleRule', + }, + ], + Version: '2012-10-17', + }, + Queues: [ + { + Ref: 'Queue4A7E3555', + }, + ], + })); + }); }); diff --git a/packages/@aws-cdk/aws-events-targets/test/codebuild/integ.project-events.expected.json b/packages/@aws-cdk/aws-events-targets/test/codebuild/integ.project-events.expected.json index 007503de08383..aec41898a4622 100644 --- a/packages/@aws-cdk/aws-events-targets/test/codebuild/integ.project-events.expected.json +++ b/packages/@aws-cdk/aws-events-targets/test/codebuild/integ.project-events.expected.json @@ -43,6 +43,14 @@ "Arn" ] }, + "DeadLetterConfig": { + "Arn": { + "Fn::GetAtt": [ + "DeadLetterQueue9F481546", + "Arn" + ] + } + }, "Id": "Target0", "RoleArn": { "Fn::GetAtt": [ @@ -394,6 +402,50 @@ } } }, + "DeadLetterQueue9F481546": { + "Type": "AWS::SQS::Queue", + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" + }, + "DeadLetterQueuePolicyB1FB890C": { + "Type": "AWS::SQS::QueuePolicy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": "sqs:SendMessage", + "Condition": { + "ArnEquals": { + "aws:SourceArn": { + "Fn::GetAtt": [ + "MyRepoOnCommit0E80B304", + "Arn" + ] + } + } + }, + "Effect": "Allow", + "Principal": { + "Service": "events.amazonaws.com" + }, + "Resource": { + "Fn::GetAtt": [ + "DeadLetterQueue9F481546", + "Arn" + ] + }, + "Sid": "AllowEventRuleawscdkcodebuildeventsMyRepoOnCommit0ED1137A" + } + ], + "Version": "2012-10-17" + }, + "Queues": [ + { + "Ref": "DeadLetterQueue9F481546" + } + ] + } + }, "MyTopic86869434": { "Type": "AWS::SNS::Topic" }, diff --git a/packages/@aws-cdk/aws-events-targets/test/codebuild/integ.project-events.ts b/packages/@aws-cdk/aws-events-targets/test/codebuild/integ.project-events.ts index 6d17e0e01c688..7974a4188f969 100644 --- a/packages/@aws-cdk/aws-events-targets/test/codebuild/integ.project-events.ts +++ b/packages/@aws-cdk/aws-events-targets/test/codebuild/integ.project-events.ts @@ -20,6 +20,7 @@ const project = new codebuild.Project(stack, 'MyProject', { }); const queue = new sqs.Queue(stack, 'MyQueue'); +const deadLetterQueue = new sqs.Queue(stack, 'DeadLetterQueue'); const topic = new sns.Topic(stack, 'MyTopic'); topic.addSubscription(new subs.SqsSubscription(queue)); @@ -39,7 +40,9 @@ project.onPhaseChange('PhaseChange', { // trigger a build when a commit is pushed to the repo const onCommitRule = repo.onCommit('OnCommit', { - target: new targets.CodeBuildProject(project), + target: new targets.CodeBuildProject(project, { + deadLetterQueue: deadLetterQueue, + }), branches: ['master'], });