Skip to content

Commit fcf8b7a

Browse files
authored
Add Event-driven lambda shortcut (#68)
* add shortcut for event-driven lambda * update api doc
1 parent 265e286 commit fcf8b7a

File tree

7 files changed

+883
-285
lines changed

7 files changed

+883
-285
lines changed

lib/shortcuts/api.md

Lines changed: 276 additions & 224 deletions
Large diffs are not rendered by default.

lib/shortcuts/event-lambda.js

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
'use strict';
2+
3+
const Lambda = require('./lambda');
4+
5+
/**
6+
* A Lambda function that executes on a schedule. Includes a LogGroup, a Role,
7+
* an Alarm on function errors, a CloudWatch Event Rule, and a Lambda permission.
8+
*
9+
* @param {Object} options configuration options for the scheduled Lambda
10+
* function and related resources. Extends [the `options` for a vanilla Lambda
11+
* function](#parameters) with the following additional attributes:
12+
* @param {String} options.EventPattern See [AWS documentation](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-events-rule.html#cfn-events-rule-eventpattern)
13+
* @param {String} [options.State='ENABLED'] See [AWS documentation](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-events-rule.html#cfn-events-rule-state)
14+
*
15+
* @example
16+
* const cf = require('@mapbox/cloudfriend');
17+
*
18+
* const myTemplate = { ... };
19+
*
20+
* const lambda = new cf.shortcuts.ScheduledLambda({
21+
* LogicalName: 'MyLambda',
22+
* Code: {
23+
* S3Bucket: 'my-code-bucket',
24+
* S3Key: 'path/to/code.zip'
25+
* },
26+
* ScheduleExpression: 'rate(1 hour)'
27+
* });
28+
*
29+
* module.exports = cf.merge(myTemplate, lambda);
30+
*/
31+
class EventLambda extends Lambda {
32+
constructor(options = {}) {
33+
super(options);
34+
35+
const {
36+
EventPattern,
37+
State = 'ENABLED'
38+
} = options;
39+
40+
const required = [EventPattern];
41+
if (required.some((variable) => !variable))
42+
throw new Error('You must provide an EventPattern');
43+
44+
this.Resources[`${this.LogicalName}Trigger`] = {
45+
Type: 'AWS::Events::Rule',
46+
Condition: this.Condition,
47+
Properties: {
48+
Name: this.FunctionName,
49+
Description: {
50+
'Fn::Sub': [
51+
'Event trigger for ${function} in ${AWS::StackName} stack',
52+
{ function: this.FunctionName }
53+
]
54+
},
55+
State,
56+
EventPattern,
57+
Targets: [
58+
{
59+
Id: this.FunctionName,
60+
Arn: {
61+
'Fn::GetAtt': [this.LogicalName, 'Arn']
62+
}
63+
}
64+
]
65+
}
66+
};
67+
68+
this.Resources[`${this.LogicalName}Permission`] = {
69+
Type: 'AWS::Lambda::Permission',
70+
Condition: this.Condition,
71+
Properties: {
72+
Action: 'lambda:InvokeFunction',
73+
FunctionName: {
74+
'Fn::GetAtt': [this.LogicalName, 'Arn']
75+
},
76+
Principal: {
77+
'Fn::Sub': 'events.${AWS::URLSuffix}'
78+
},
79+
SourceArn: {
80+
'Fn::GetAtt': [`${this.LogicalName}Trigger`, 'Arn']
81+
}
82+
}
83+
};
84+
}
85+
}
86+
87+
module.exports = EventLambda;

lib/shortcuts/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
module.exports = {
44
Lambda: require('./lambda'),
55
ScheduledLambda: require('./scheduled-lambda'),
6+
EventLambda: require('./event-lambda'),
67
QueueLambda: require('./queue-lambda'),
78
StreamLambda: require('./stream-lambda'),
89
Role: require('./role'),

lib/shortcuts/scheduled-lambda.js

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,11 @@
33
const Lambda = require('./lambda');
44

55
/**
6-
* A Lambda function that executes on a schedule. Includes a LogGroup, a Role,
7-
* an Alarm on function errors, a CloudWatch Event Rule, and a Lambda permission.
6+
* A Lambda function that executes on in response to a CloudWatch Event. Includes
7+
* a LogGroup, a Role, an Alarm on function errors, a CloudWatch Event Rule, and
8+
* a Lambda permission.
89
*
9-
* @param {Object} options configuration options for the scheduled Lambda
10+
* @param {Object} options configuration options for the event-driven Lambda
1011
* function and related resources. Extends [the `options` for a vanilla Lambda
1112
* function](#parameters) with the following additional attributes:
1213
* @param {String} options.ScheduleExpression See [AWS documentation](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-events-rule.html#cfn-events-rule-scheduleexpression)
@@ -17,18 +18,24 @@ const Lambda = require('./lambda');
1718
*
1819
* const myTemplate = { ... };
1920
*
20-
* const lambda = new cf.shortcuts.ScheduledLambda({
21+
* const lambda = new cf.shortcuts.EventLambda({
2122
* LogicalName: 'MyLambda',
2223
* Code: {
2324
* S3Bucket: 'my-code-bucket',
2425
* S3Key: 'path/to/code.zip'
2526
* },
26-
* ScheduleExpression: 'rate(1 hour)'
27+
* EventPattern: {
28+
* "source": [ "aws.ec2" ],
29+
* "detail-type": [ "EC2 Instance State-change Notification" ],
30+
* "detail": {
31+
* "state": [ "running" ]
32+
* }
33+
* }
2734
* });
2835
*
2936
* module.exports = cf.merge(myTemplate, lambda);
3037
*/
31-
class ScheduledLambda extends Lambda {
38+
class EventLambda extends Lambda {
3239
constructor(options = {}) {
3340
super(options);
3441

@@ -84,4 +91,4 @@ class ScheduledLambda extends Lambda {
8491
}
8592
}
8693

87-
module.exports = ScheduledLambda;
94+
module.exports = EventLambda;
Lines changed: 187 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,187 @@
1+
{
2+
"AWSTemplateFormatVersion": "2010-09-09",
3+
"Metadata": {},
4+
"Parameters": {},
5+
"Mappings": {},
6+
"Conditions": {},
7+
"Resources": {
8+
"MyLambdaLogs": {
9+
"Type": "AWS::Logs::LogGroup",
10+
"Properties": {
11+
"LogGroupName": {
12+
"Fn::Sub": [
13+
"/aws/lambda/${name}",
14+
{
15+
"name": {
16+
"Fn::Sub": "${AWS::StackName}-MyLambda"
17+
}
18+
}
19+
]
20+
},
21+
"RetentionInDays": 14
22+
}
23+
},
24+
"MyLambdaRole": {
25+
"Type": "AWS::IAM::Role",
26+
"Properties": {
27+
"AssumeRolePolicyDocument": {
28+
"Statement": [
29+
{
30+
"Effect": "Allow",
31+
"Action": "sts:AssumeRole",
32+
"Principal": {
33+
"Service": "lambda.amazonaws.com"
34+
}
35+
}
36+
]
37+
},
38+
"Policies": [
39+
{
40+
"PolicyName": "main",
41+
"PolicyDocument": {
42+
"Statement": [
43+
{
44+
"Effect": "Allow",
45+
"Action": "logs:*",
46+
"Resource": {
47+
"Fn::GetAtt": [
48+
"MyLambdaLogs",
49+
"Arn"
50+
]
51+
}
52+
}
53+
]
54+
}
55+
}
56+
]
57+
}
58+
},
59+
"MyLambda": {
60+
"Type": "AWS::Lambda::Function",
61+
"Properties": {
62+
"Code": {
63+
"S3Bucket": "my-code-bucket",
64+
"S3Key": "path/to/code.zip"
65+
},
66+
"Description": {
67+
"Fn::Sub": "MyLambda in the ${AWS::StackName} stack"
68+
},
69+
"FunctionName": {
70+
"Fn::Sub": "${AWS::StackName}-MyLambda"
71+
},
72+
"Handler": "index.handler",
73+
"MemorySize": 128,
74+
"Role": {
75+
"Fn::GetAtt": [
76+
"MyLambdaRole",
77+
"Arn"
78+
]
79+
},
80+
"Runtime": "nodejs8.10",
81+
"Timeout": 300
82+
}
83+
},
84+
"MyLambdaErrorAlarm": {
85+
"Type": "AWS::CloudWatch::Alarm",
86+
"Properties": {
87+
"AlarmName": {
88+
"Fn::Sub": "${AWS::StackName}-MyLambda-Errors-${AWS::Region}"
89+
},
90+
"AlarmDescription": {
91+
"Fn::Sub": [
92+
"Error alarm for ${name} lambda function in ${AWS::StackName} stack",
93+
{
94+
"name": {
95+
"Fn::Sub": "${AWS::StackName}-MyLambda"
96+
}
97+
}
98+
]
99+
},
100+
"AlarmActions": [],
101+
"Period": 60,
102+
"EvaluationPeriods": 1,
103+
"Statistic": "Sum",
104+
"Threshold": 0,
105+
"ComparisonOperator": "GreaterThanThreshold",
106+
"TreatMissingData": "notBreaching",
107+
"Namespace": "AWS/Lambda",
108+
"Dimensions": [
109+
{
110+
"Name": "FunctionName",
111+
"Value": {
112+
"Ref": "MyLambda"
113+
}
114+
}
115+
],
116+
"MetricName": "Errors"
117+
}
118+
},
119+
"MyLambdaTrigger": {
120+
"Type": "AWS::Events::Rule",
121+
"Properties": {
122+
"Name": {
123+
"Fn::Sub": "${AWS::StackName}-MyLambda"
124+
},
125+
"Description": {
126+
"Fn::Sub": [
127+
"Event trigger for ${function} in ${AWS::StackName} stack",
128+
{
129+
"function": {
130+
"Fn::Sub": "${AWS::StackName}-MyLambda"
131+
}
132+
}
133+
]
134+
},
135+
"State": "ENABLED",
136+
"EventPattern": {
137+
"source": [
138+
"aws.ec2"
139+
],
140+
"detail-type": [
141+
"EC2 Instance State-change Notification"
142+
],
143+
"detail": {
144+
"state": [
145+
"running"
146+
]
147+
}
148+
},
149+
"Targets": [
150+
{
151+
"Id": {
152+
"Fn::Sub": "${AWS::StackName}-MyLambda"
153+
},
154+
"Arn": {
155+
"Fn::GetAtt": [
156+
"MyLambda",
157+
"Arn"
158+
]
159+
}
160+
}
161+
]
162+
}
163+
},
164+
"MyLambdaPermission": {
165+
"Type": "AWS::Lambda::Permission",
166+
"Properties": {
167+
"Action": "lambda:InvokeFunction",
168+
"FunctionName": {
169+
"Fn::GetAtt": [
170+
"MyLambda",
171+
"Arn"
172+
]
173+
},
174+
"Principal": {
175+
"Fn::Sub": "events.${AWS::URLSuffix}"
176+
},
177+
"SourceArn": {
178+
"Fn::GetAtt": [
179+
"MyLambdaTrigger",
180+
"Arn"
181+
]
182+
}
183+
}
184+
}
185+
},
186+
"Outputs": {}
187+
}

0 commit comments

Comments
 (0)