UNPKG

@mapbox/cloudfriend

Version:

Helper functions for assembling CloudFormation templates in JavaScript

137 lines (128 loc) 5.3 kB
'use strict'; const Lambda = require('./lambda'); /** * A Lambda function that runs in response to events in a DynamoDB or Kinesis * stream. Includes a Log Group, a Role, an Alarm on function errors, and an event * source mapping. * * @param {Object} options - Extends the options for [`Lambda`](#lambda) with the following additional attributes: * @param {String} options.EventSourceArn - See [AWS documentation](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-lambda-eventsourcemapping.html#cfn-lambda-eventsourcemapping-eventsourcearn). * @param {Number} [options.BatchSize=1] - See [AWS documentation](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-lambda-eventsourcemapping.html#cfn-lambda-eventsourcemapping-batchsize). * @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'); * * const myTemplate = { ... }; * * const lambda = new cf.shortcuts.StreamLambda({ * LogicalName: 'MyLambda', * Code: { * S3Bucket: 'my-code-bucket', * S3Key: 'path/to/code.zip' * }, * EventSourceArn: cf.getAtt('MyStream', 'Arn'), * }); * * // This lambda only gets invoked for 'INSERT' events for the DynamoDb event source * const lambdaWithFilterCriteria = new cf.shortcuts.StreamLambda({ * LogicalName: 'MyLambdaWithFilterCriteria', * Code: { * S3Bucket: 'my-code-bucket', * S3Key: 'path/to/code.zip' * }, * EventSourceArn: cf.getAtt('MyDynamoDbStream', 'Arn'), * FilterCriteria: { * Filters: [ * { * Pattern: JSON.stringify({ eventName: ['INSERT'] }), * } * ] * } * }); * * module.exports = cf.merge(myTemplate, lambda, lambdaWithFilterCriteria); */ class StreamLambda extends Lambda { constructor(options) { if (!options) throw new Error('Options required'); super(options); const { EventSourceArn, BatchSize = 1, MaximumBatchingWindowInSeconds, Enabled = true, StartingPosition = 'LATEST', FilterCriteria = undefined } = options; const required = [EventSourceArn]; if (required.some((variable) => !variable)) throw new Error('You must provide an EventSourceArn'); this.Resources[`${this.LogicalName}EventSource`] = { Type: 'AWS::Lambda::EventSourceMapping', Condition: this.Condition, Properties: { BatchSize, MaximumBatchingWindowInSeconds, Enabled, EventSourceArn, FunctionName: { Ref: this.LogicalName }, StartingPosition } }; if (FilterCriteria) { if (Object.prototype.toString.call(FilterCriteria) !== '[object Object]'){ 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'); } for (const filter of FilterCriteria.Filters){ 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; } const generatedRoleRef = this.Resources[`${this.LogicalName}Role`]; const streamStatement = { Effect: 'Allow', Action: [ 'dynamodb:GetRecords', 'dynamodb:GetShardIterator', 'dynamodb:DescribeStream', 'dynamodb:ListStreams', 'kinesis:GetRecords', 'kinesis:GetShardIterator', 'kinesis:DescribeStream', 'kinesis:ListStreams' ], Resource: [ EventSourceArn, { 'Fn::Sub': ['${arn}/*', { arn: EventSourceArn }] } ] }; if (generatedRoleRef && generatedRoleRef.Properties.Policies) { generatedRoleRef.Properties.Policies[0].PolicyDocument.Statement.push(streamStatement); } else if (generatedRoleRef) { generatedRoleRef.Properties.Policies = [ { PolicyName: 'StreamAccess', PolicyDocument: { Version: '2012-10-17', Statement: [streamStatement] } } ]; } } } module.exports = StreamLambda;