From 6850a0b8064f0eb8c2a65fed9d7b172bfa30043a Mon Sep 17 00:00:00 2001 From: Elad Ben-Israel Date: Tue, 23 Apr 2019 00:59:56 +0300 Subject: [PATCH] feat(events-targets): LambdaFunction (#2350) The `LambdaFunction` class can be used to bind an AWS Lambda function as an event rule target. Related #1663 BREAKING CHANGE: `lambda.Function` no longer implements `IEventRuleTarget`. Instead, use `@aws-cdk/aws-events-targets.LambdaFunction`. --- .../@aws-cdk/aws-events-targets/lib/index.ts | 3 +- .../@aws-cdk/aws-events-targets/lib/lambda.ts | 36 +++++++++++ .../test/lambda}/integ.events.expected.json | 0 .../test/lambda}/integ.events.ts | 7 ++- .../test/lambda/lambda.test.ts | 62 +++++++++++++++++++ packages/@aws-cdk/aws-events/README.md | 1 + packages/@aws-cdk/aws-lambda/README.md | 10 +++ .../@aws-cdk/aws-lambda/lib/function-base.ts | 23 +------ .../@aws-cdk/aws-lambda/test/test.lambda.ts | 54 +--------------- 9 files changed, 117 insertions(+), 79 deletions(-) create mode 100644 packages/@aws-cdk/aws-events-targets/lib/lambda.ts rename packages/@aws-cdk/{aws-lambda/test => aws-events-targets/test/lambda}/integ.events.expected.json (100%) rename packages/@aws-cdk/{aws-lambda/test => aws-events-targets/test/lambda}/integ.events.ts (79%) create mode 100644 packages/@aws-cdk/aws-events-targets/test/lambda/lambda.test.ts diff --git a/packages/@aws-cdk/aws-events-targets/lib/index.ts b/packages/@aws-cdk/aws-events-targets/lib/index.ts index 0b9181cb29233..ee1e2d31b587e 100644 --- a/packages/@aws-cdk/aws-events-targets/lib/index.ts +++ b/packages/@aws-cdk/aws-events-targets/lib/index.ts @@ -1,2 +1,3 @@ export * from './sns'; -export * from './codebuild'; \ No newline at end of file +export * from './codebuild'; +export * from './lambda'; diff --git a/packages/@aws-cdk/aws-events-targets/lib/lambda.ts b/packages/@aws-cdk/aws-events-targets/lib/lambda.ts new file mode 100644 index 0000000000000..4384dd82a12a8 --- /dev/null +++ b/packages/@aws-cdk/aws-events-targets/lib/lambda.ts @@ -0,0 +1,36 @@ +import events = require('@aws-cdk/aws-events'); +import iam = require('@aws-cdk/aws-iam'); +import lambda = require('@aws-cdk/aws-lambda'); + +/** + * Use an AWS Lambda function as an event rule target. + */ +export class LambdaFunction implements events.IEventRuleTarget { + + /** + * @param handler The lambda function + */ + constructor(private readonly handler: lambda.IFunction) { + + } + + /** + * Returns a RuleTarget that can be used to trigger this Lambda as a + * result from a CloudWatch event. + */ + public asEventRuleTarget(ruleArn: string, ruleId: string): events.EventRuleTargetProps { + const permissionId = `AllowEventRule${ruleId}`; + if (!this.handler.node.tryFindChild(permissionId)) { + this.handler.addPermission(permissionId, { + action: 'lambda:InvokeFunction', + principal: new iam.ServicePrincipal('events.amazonaws.com'), + sourceArn: ruleArn + }); + } + + return { + id: this.handler.node.id, + arn: this.handler.functionArn, + }; + } +} diff --git a/packages/@aws-cdk/aws-lambda/test/integ.events.expected.json b/packages/@aws-cdk/aws-events-targets/test/lambda/integ.events.expected.json similarity index 100% rename from packages/@aws-cdk/aws-lambda/test/integ.events.expected.json rename to packages/@aws-cdk/aws-events-targets/test/lambda/integ.events.expected.json diff --git a/packages/@aws-cdk/aws-lambda/test/integ.events.ts b/packages/@aws-cdk/aws-events-targets/test/lambda/integ.events.ts similarity index 79% rename from packages/@aws-cdk/aws-lambda/test/integ.events.ts rename to packages/@aws-cdk/aws-events-targets/test/lambda/integ.events.ts index 318f21e414edc..c815efec9a00f 100644 --- a/packages/@aws-cdk/aws-lambda/test/integ.events.ts +++ b/packages/@aws-cdk/aws-events-targets/test/lambda/integ.events.ts @@ -1,6 +1,7 @@ import events = require('@aws-cdk/aws-events'); +import lambda = require('@aws-cdk/aws-lambda'); import cdk = require('@aws-cdk/cdk'); -import lambda = require('../lib'); +import targets = require('../../lib'); const app = new cdk.App(); @@ -13,10 +14,10 @@ const fn = new lambda.Function(stack, 'MyFunc', { }); const timer = new events.EventRule(stack, 'Timer', { scheduleExpression: 'rate(1 minute)' }); -timer.addTarget(fn); +timer.addTarget(new targets.LambdaFunction(fn)); const timer2 = new events.EventRule(stack, 'Timer2', { scheduleExpression: 'rate(2 minutes)' }); -timer2.addTarget(fn); +timer2.addTarget(new targets.LambdaFunction(fn)); app.run(); diff --git a/packages/@aws-cdk/aws-events-targets/test/lambda/lambda.test.ts b/packages/@aws-cdk/aws-events-targets/test/lambda/lambda.test.ts new file mode 100644 index 0000000000000..e611681c5d648 --- /dev/null +++ b/packages/@aws-cdk/aws-events-targets/test/lambda/lambda.test.ts @@ -0,0 +1,62 @@ +import { countResources, expect, haveResource } from '@aws-cdk/assert'; +import events = require('@aws-cdk/aws-events'); +import lambda = require('@aws-cdk/aws-lambda'); +import cdk = require('@aws-cdk/cdk'); +import targets = require('../../lib'); + +test('use lambda as an event rule target', () => { + // GIVEN + const stack = new cdk.Stack(); + const fn = newTestLambda(stack); + const rule1 = new events.EventRule(stack, 'Rule', { scheduleExpression: 'rate(1 minute)' }); + const rule2 = new events.EventRule(stack, 'Rule2', { scheduleExpression: 'rate(5 minutes)' }); + + // WHEN + rule1.addTarget(new targets.LambdaFunction(fn)); + rule2.addTarget(new targets.LambdaFunction(fn)); + + // THEN + const lambdaId = "MyLambdaCCE802FB"; + + expect(stack).to(haveResource('AWS::Lambda::Permission', { + Action: "lambda:InvokeFunction", + FunctionName: { + "Fn::GetAtt": [ + lambdaId, + "Arn" + ] + }, + Principal: "events.amazonaws.com", + SourceArn: { "Fn::GetAtt": ["Rule4C995B7F", "Arn"] } + })); + + expect(stack).to(haveResource('AWS::Lambda::Permission', { + Action: "lambda:InvokeFunction", + FunctionName: { + "Fn::GetAtt": [ + lambdaId, + "Arn" + ] + }, + Principal: "events.amazonaws.com", + SourceArn: { "Fn::GetAtt": ["Rule270732244", "Arn"] } + })); + + expect(stack).to(countResources('AWS::Events::Rule', 2)); + expect(stack).to(haveResource('AWS::Events::Rule', { + Targets: [ + { + Arn: { "Fn::GetAtt": [lambdaId, "Arn"] }, + Id: "MyLambda" + } + ] + })); +}); + +function newTestLambda(scope: cdk.Construct) { + return new lambda.Function(scope, 'MyLambda', { + code: new lambda.InlineCode('foo'), + handler: 'bar', + runtime: lambda.Runtime.Python27 + }); +} diff --git a/packages/@aws-cdk/aws-events/README.md b/packages/@aws-cdk/aws-events/README.md index d94bc17d54901..e89bfdce98166 100644 --- a/packages/@aws-cdk/aws-events/README.md +++ b/packages/@aws-cdk/aws-events/README.md @@ -72,4 +72,5 @@ The following targets are supported: * `targets.SnsTopic`: publish into an SNS topic when an event rule is triggered. * `targets.CodeBuildProject`: start a CodeBuild project when an event rule is triggered. +* `targets.LambdaFunction`: invoke an AWS Lambda function when an event rule is triggered. diff --git a/packages/@aws-cdk/aws-lambda/README.md b/packages/@aws-cdk/aws-lambda/README.md index f81166476bc88..9cb461684656d 100644 --- a/packages/@aws-cdk/aws-lambda/README.md +++ b/packages/@aws-cdk/aws-lambda/README.md @@ -40,6 +40,16 @@ granting permissions to other AWS accounts or organizations. [Example of Lambda Layer usage](test/integ.layer-version.lit.ts) +## Event Rule Target + +You can use an AWS Lambda function as a target for an Amazon CloudWatch event +rule: + +```ts +import targets = require('@aws-cdk/aws-events-targets'); +rule.addTarget(new targets.LambdaFunction(myFunction)); +``` + ### Event Sources AWS Lambda supports a [variety of event sources](https://docs.aws.amazon.com/lambda/latest/dg/invoking-lambda-function.html). diff --git a/packages/@aws-cdk/aws-lambda/lib/function-base.ts b/packages/@aws-cdk/aws-lambda/lib/function-base.ts index 6b4e6d18d2ced..07fcc0ea7f64c 100644 --- a/packages/@aws-cdk/aws-lambda/lib/function-base.ts +++ b/packages/@aws-cdk/aws-lambda/lib/function-base.ts @@ -1,6 +1,5 @@ import cloudwatch = require('@aws-cdk/aws-cloudwatch'); import ec2 = require('@aws-cdk/aws-ec2'); -import events = require('@aws-cdk/aws-events'); import iam = require('@aws-cdk/aws-iam'); import logs = require('@aws-cdk/aws-logs'); import s3n = require('@aws-cdk/aws-s3-notifications'); @@ -11,7 +10,7 @@ import { IEventSource } from './event-source'; import { CfnPermission } from './lambda.generated'; import { Permission } from './permission'; -export interface IFunction extends IResource, events.IEventRuleTarget, logs.ILogSubscriptionDestination, +export interface IFunction extends IResource, logs.ILogSubscriptionDestination, s3n.IBucketNotificationDestination, ec2.IConnectable, stepfunctions.IStepFunctionsTaskResource, iam.IGrantable { /** @@ -215,26 +214,6 @@ export abstract class FunctionBase extends Resource implements IFunction { return !!this._connections; } - /** - * Returns a RuleTarget that can be used to trigger this Lambda as a - * result from a CloudWatch event. - */ - public asEventRuleTarget(ruleArn: string, ruleId: string): events.EventRuleTargetProps { - const permissionId = `AllowEventRule${ruleId}`; - if (!this.node.tryFindChild(permissionId)) { - this.addPermission(permissionId, { - action: 'lambda:InvokeFunction', - principal: new iam.ServicePrincipal('events.amazonaws.com'), - sourceArn: ruleArn - }); - } - - return { - id: this.node.id, - arn: this.functionArn, - }; - } - /** * Grant the given identity permissions to invoke this Lambda */ diff --git a/packages/@aws-cdk/aws-lambda/test/test.lambda.ts b/packages/@aws-cdk/aws-lambda/test/test.lambda.ts index 23b588681b04f..d717376462991 100644 --- a/packages/@aws-cdk/aws-lambda/test/test.lambda.ts +++ b/packages/@aws-cdk/aws-lambda/test/test.lambda.ts @@ -1,5 +1,4 @@ -import { countResources, expect, haveResource, MatchStyle, ResourcePart } from '@aws-cdk/assert'; -import events = require('@aws-cdk/aws-events'); +import { expect, haveResource, MatchStyle, ResourcePart } from '@aws-cdk/assert'; import iam = require('@aws-cdk/aws-iam'); import logs = require('@aws-cdk/aws-logs'); import sqs = require('@aws-cdk/aws-sqs'); @@ -257,57 +256,6 @@ export = { }, }, - 'Lambda can serve as EventRule target, permission gets added'(test: Test) { - // GIVEN - const stack = new cdk.Stack(); - const fn = newTestLambda(stack); - const rule1 = new events.EventRule(stack, 'Rule', { scheduleExpression: 'rate(1 minute)' }); - const rule2 = new events.EventRule(stack, 'Rule2', { scheduleExpression: 'rate(5 minutes)' }); - - // WHEN - rule1.addTarget(fn); - rule2.addTarget(fn); - - // THEN - const lambdaId = "MyLambdaCCE802FB"; - - expect(stack).to(haveResource('AWS::Lambda::Permission', { - "Action": "lambda:InvokeFunction", - "FunctionName": { - "Fn::GetAtt": [ - lambdaId, - "Arn" - ] - }, - "Principal": "events.amazonaws.com", - "SourceArn": { "Fn::GetAtt": [ "Rule4C995B7F", "Arn" ] } - })); - - expect(stack).to(haveResource('AWS::Lambda::Permission', { - "Action": "lambda:InvokeFunction", - "FunctionName": { - "Fn::GetAtt": [ - lambdaId, - "Arn" - ] - }, - "Principal": "events.amazonaws.com", - "SourceArn": { "Fn::GetAtt": [ "Rule270732244", "Arn" ] } - })); - - expect(stack).to(countResources('AWS::Events::Rule', 2)); - expect(stack).to(haveResource('AWS::Events::Rule', { - "Targets": [ - { - "Arn": { "Fn::GetAtt": [ lambdaId, "Arn" ] }, - "Id": "MyLambda" - } - ] - })); - - test.done(); - }, - 'Lambda code can be read from a local directory via an asset'(test: Test) { // GIVEN const stack = new cdk.Stack();