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

MAPSAPI-2426: Allow stream lambdas to have FilterCriteria defined #147

Merged
merged 12 commits into from
Mar 27, 2024
4 changes: 4 additions & 0 deletions changelog.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Changelog

## 8.1.0

- Allow `FilterCriteria` property to be defined for Lambda shortcuts

## 8.0.0

- Updates `cf.shortcuts.ScheduledLambda` to use EventBridge Scheduler instead of EventBridge Rules to schedule lambda invocations. When using this version your template will have the following changes per scheduled lambda instance,
Expand Down
1 change: 1 addition & 0 deletions lib/shortcuts/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -782,6 +782,7 @@ source mapping.
| [options.MaximumBatchingWindowInSeconds] | <code>Number</code> | | See [AWS documentation](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-lambda-eventsourcemapping.html#cfn-lambda-eventsourcemapping-maximumbatchingwindowinseconds). |
| [options.Enabled] | <code>Boolean</code> | <code>true</code> | See [AWS documentation](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-lambda-eventsourcemapping.html#cfn-lambda-eventsourcemapping-enabled). |
| [options.StartingPosition] | <code>String</code> | <code>&#x27;LATEST&#x27;</code> | See [AWS documentation](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-lambda-eventsourcemapping.html#cfn-lambda-eventsourcemapping-startingposition). |
| [options.FilterCriteria] | <code>Object</code> | | See [AWS documentation](https://docs.aws.amazon.com/lambda/latest/dg/invocation-eventfiltering.html). |

**Example**
```js
Expand Down
35 changes: 33 additions & 2 deletions lib/shortcuts/stream-lambda.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ const Lambda = require('./lambda');
* @param {Number} [options.MaximumBatchingWindowInSeconds=undefined] - See [AWS documentation](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-lambda-eventsourcemapping.html#cfn-lambda-eventsourcemapping-maximumbatchingwindowinseconds).
* @param {Boolean} [options.Enabled=true] - See [AWS documentation](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-lambda-eventsourcemapping.html#cfn-lambda-eventsourcemapping-enabled).
* @param {String} [options.StartingPosition='LATEST'] - See [AWS documentation](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-lambda-eventsourcemapping.html#cfn-lambda-eventsourcemapping-startingposition).
* @param {Object} [options.FilterCriteria] - See [AWS documentation](https://docs.aws.amazon.com/lambda/latest/dg/invocation-eventfiltering.html).
*
* @example
* const cf = require('@mapbox/cloudfriend');
branyip marked this conversation as resolved.
Show resolved Hide resolved
Expand All @@ -25,7 +26,14 @@ const Lambda = require('./lambda');
* S3Bucket: 'my-code-bucket',
* S3Key: 'path/to/code.zip'
* },
* EventSourceArn: cf.getAtt('MyStream', 'Arn')
* EventSourceArn: cf.getAtt('MyStream', 'Arn'),
* FilterCriteria: {
* Filters: [
* {
* Pattern: JSON.stringify({ eventName: ['INSERT'] }),
* }
* ]
* }
branyip marked this conversation as resolved.
Show resolved Hide resolved
* });
*
* module.exports = cf.merge(myTemplate, lambda);
Expand All @@ -40,7 +48,8 @@ class StreamLambda extends Lambda {
BatchSize = 1,
MaximumBatchingWindowInSeconds,
Enabled = true,
StartingPosition = 'LATEST'
StartingPosition = 'LATEST',
FilterCriteria = undefined
} = options;

const required = [EventSourceArn];
Expand All @@ -59,6 +68,28 @@ class StreamLambda extends Lambda {
StartingPosition
}
};
if (FilterCriteria) {
if (Object.prototype.toString.call(FilterCriteria) !== '[object Object]'){
branyip marked this conversation as resolved.
Show resolved Hide resolved
throw new Error('`FilterCriteria` must be a JSON-like object');
}
if (!(FilterCriteria.Filters && Array.isArray(FilterCriteria.Filters))){
throw new Error('`FilterCriteria` must contain property `Filter` of type array');
}
if (FilterCriteria.Filters.length > 5){
throw new Error('`FilterCriteria.Filter` cannot contain more than 5 items, you may request a quota increase with AWS support if required.');
branyip marked this conversation as resolved.
Show resolved Hide resolved
}
for (const filter of FilterCriteria.Filters){
branyip marked this conversation as resolved.
Show resolved Hide resolved
if (!filter.Pattern){
throw new Error('An object in `FilterCriteria.Filter` was missing the required property `Pattern`');
}
try {
JSON.parse(filter.Pattern);
} catch (error) {
throw new Error('An object in `FilterCriteria.Filter` contains a `Pattern` property that is not a JSON parseable string');
}
}
this.Resources[`${this.LogicalName}EventSource`].Properties.FilterCriteria = FilterCriteria;
}
branyip marked this conversation as resolved.
Show resolved Hide resolved

const generatedRoleRef = this.Resources[`${this.LogicalName}Role`];
const streamStatement = {
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@mapbox/cloudfriend",
"version": "8.0.0",
"version": "8.1.0-dev.2",
branyip marked this conversation as resolved.
Show resolved Hide resolved
"description": "Helper functions for assembling CloudFormation templates in JavaScript",
"main": "index.js",
"engines": {
Expand Down
9 changes: 8 additions & 1 deletion test/fixtures/shortcuts/stream-lambda-no-defaults.json
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,13 @@
"Properties": {
"BatchSize": 10000,
"MaximumBatchingWindowInSeconds": 300,
"FilterCriteria": {
"Filters": [
{
"Pattern": "{\"eventName\":[\"INSERT\",\"MODIFY\"]}"
}
]
},
"Enabled": false,
"EventSourceArn": "arn:aws:kinesis:us-east-1:123456789012:stream/fake",
"FunctionName": {
Expand All @@ -177,4 +184,4 @@
}
},
"Outputs": {}
}
}
124 changes: 124 additions & 0 deletions test/shortcuts.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -475,6 +475,13 @@ test('[shortcuts] stream-lambda', (assert) => {
S3Key: 'path/to/code.zip'
},
EventSourceArn: 'arn:aws:kinesis:us-east-1:123456789012:stream/fake',
FilterCriteria: {
Filters: [
{
Pattern: JSON.stringify({ eventName: ['INSERT', 'MODIFY'] })
}
]
},
BatchSize: 10000,
MaximumBatchingWindowInSeconds: 300,
Enabled: false,
Expand All @@ -492,6 +499,123 @@ test('[shortcuts] stream-lambda', (assert) => {
assert.end();
});

test('[shortcuts] StreamLambda FilterCriteria', (assert) => {
assert.throws(
() => new cf.shortcuts.StreamLambda({
LogicalName: 'MyLambda',
Code: {
S3Bucket: 'my-code-bucket',
S3Key: 'path/to/code.zip'
},
EventSourceArn: 'arn:aws:kinesis:us-east-1:123456789012:stream/fake',
FilterCriteria: ['test']
}),
'`FilterCriteria` must be a JSON-like object',
);
assert.throws(
() => new cf.shortcuts.StreamLambda({
LogicalName: 'MyLambda',
Code: {
S3Bucket: 'my-code-bucket',
S3Key: 'path/to/code.zip'
},
EventSourceArn: 'arn:aws:kinesis:us-east-1:123456789012:stream/fake',
FilterCriteria: {}
}),
'`FilterCriteria` must contain property `Filter` of type array',
);
assert.throws(
() => new cf.shortcuts.StreamLambda({
LogicalName: 'MyLambda',
Code: {
S3Bucket: 'my-code-bucket',
S3Key: 'path/to/code.zip'
},
EventSourceArn: 'arn:aws:kinesis:us-east-1:123456789012:stream/fake',
FilterCriteria: {
Filter: 613
}
}),
'`FilterCriteria` must contain property `Filter` of type array',
);
assert.throws(
() => new cf.shortcuts.StreamLambda({
LogicalName: 'MyLambda',
Code: {
S3Bucket: 'my-code-bucket',
S3Key: 'path/to/code.zip'
},
EventSourceArn: 'arn:aws:kinesis:us-east-1:123456789012:stream/fake',
FilterCriteria: {
Filters: [
{
Pattern: JSON.stringify({ eventName: ['1'] })
},
{
Pattern: JSON.stringify({ eventName: ['2'] })
},
{
Pattern: JSON.stringify({ eventName: ['3'] })
},
{
Pattern: JSON.stringify({ eventName: ['4'] })
},
{
Pattern: JSON.stringify({ eventName: ['5'] })
},
{
Pattern: JSON.stringify({ eventName: ['6'] })
}
]
}
}),
'`FilterCriteria.Filter` cannot contain more than 5 items, you may request a quota increase with AWS support if required.',
);
assert.throws(
() => new cf.shortcuts.StreamLambda({
LogicalName: 'MyLambda',
Code: {
S3Bucket: 'my-code-bucket',
S3Key: 'path/to/code.zip'
},
EventSourceArn: 'arn:aws:kinesis:us-east-1:123456789012:stream/fake',
FilterCriteria: {
Filters: [
{
NotPattern: JSON.stringify({ eventName: ['INSERT', 'MODIFY'] })
},
{
Pattern: JSON.stringify({ eventName: ['INSERT', 'MODIFY'] })
}
]
}
}),
'An object in `FilterCriteria.Filter` was missing the required property `Pattern`',
);
assert.throws(
() => new cf.shortcuts.StreamLambda({
LogicalName: 'MyLambda',
Code: {
S3Bucket: 'my-code-bucket',
S3Key: 'path/to/code.zip'
},
EventSourceArn: 'arn:aws:kinesis:us-east-1:123456789012:stream/fake',
FilterCriteria: {
Filters: [
{
Pattern: '{"eventName":["INSERT","MODIFY"]}'
},
{
Pattern: { eventName: ['INSERT', 'MODIFY'] }
}
]
}
}),
'An object in `FilterCriteria.Filter` contains a `Pattern` property that is not a JSON parseable string',
);
assert.end();
});

test('[shortcuts] log-subscription-lambda', (assert) => {
assert.throws(
() => new cf.shortcuts.LogSubscriptionLambda(),
Expand Down
Loading