diff --git a/docs/deprecations.md b/docs/deprecations.md
index f7fb77f00..345a1018c 100644
--- a/docs/deprecations.md
+++ b/docs/deprecations.md
@@ -17,6 +17,16 @@ disabledDeprecations:
- '*' # To disable all deprecation messages
```
+
+
+## AWS EventBridge lambda event triggers
+
+Deprecation code: `AWS_EVENT_BRIDGE_CUSTOM_RESOURCE`
+
+Starting with v3.0.0 AWS EventBridge lambda event triggers and all associated EventBridge resources will be deployed using native CloudFormation resources instead of a custom resource that used a lambda to deploy them via the AWS SDK/API.
+
+Adapt to this behavior now by setting `provider.eventBridge.useCloudFormation: true`.
+
## New variables resolver
diff --git a/lib/plugins/aws/lib/naming.js b/lib/plugins/aws/lib/naming.js
index dac3b5ad4..a33d89b0f 100644
--- a/lib/plugins/aws/lib/naming.js
+++ b/lib/plugins/aws/lib/naming.js
@@ -127,7 +127,7 @@ module.exports = {
// Lambda
getNormalizedFunctionName(functionName) {
- return this.normalizeName(functionName.replace(/-/g, 'Dash').replace(/_/g, 'Underscore'));
+ return this.getNormalizedResourceName(functionName);
},
extractLambdaNameFromArn(functionArn) {
return functionArn.substring(functionArn.lastIndexOf(':') + 1);
@@ -567,6 +567,18 @@ module.exports = {
getCustomResourceEventBridgeResourceLogicalId(functionName, idx) {
return `${this.getNormalizedFunctionName(functionName)}CustomEventBridge${idx}`;
},
+ getNormalizedResourceName(resourceName) {
+ return this.normalizeName(resourceName.replace(/-/g, 'Dash').replace(/_/g, 'Underscore'));
+ },
+ getEventBridgeEventBusLogicalId(eventBusName) {
+ return `${this.getNormalizedResourceName(eventBusName)}EventBridgeEventBus`;
+ },
+ getEventBridgeRuleLogicalId(ruleName) {
+ return `${this.normalizeNameToAlphaNumericOnly(ruleName)}EventBridgeRule`;
+ },
+ getEventBridgeLambdaPermissionLogicalId(functionName, idx) {
+ return `${this.getNormalizedFunctionName(functionName)}EventBridgeLambdaPermission${idx}`;
+ },
// API Gateway Account Logs Write Role
getCustomResourceApiGatewayAccountCloudWatchRoleHandlerFunctionName() {
diff --git a/lib/plugins/aws/package/compile/events/eventBridge/index.js b/lib/plugins/aws/package/compile/events/eventBridge/index.js
index 6aa5e4cba..132a1cc73 100644
--- a/lib/plugins/aws/package/compile/events/eventBridge/index.js
+++ b/lib/plugins/aws/package/compile/events/eventBridge/index.js
@@ -1,9 +1,9 @@
'use strict';
const _ = require('lodash');
-const crypto = require('crypto');
const { addCustomResourceToService } = require('../../../../customResources');
const ServerlessError = require('../../../../../../serverless-error');
+const { makeAndHashRuleName, makeEventBusTargetId, makeRuleName } = require('./utils');
class AwsCompileEventBridgeEvents {
constructor(serverless, options) {
@@ -12,13 +12,28 @@ class AwsCompileEventBridgeEvents {
this.provider = this.serverless.getProvider('aws');
this.hooks = {
+ 'initialize': () => {
+ if (!_.get(this.serverless.service.provider, 'eventBridge.useCloudFormation')) {
+ const hasFunctionsWithEventBridgeTrigger = Object.values(
+ this.serverless.service.functions
+ ).some(({ events }) => events.some(({ eventBridge }) => eventBridge));
+ if (hasFunctionsWithEventBridgeTrigger) {
+ this.serverless._logDeprecation(
+ 'AWS_EVENT_BRIDGE_CUSTOM_RESOURCE',
+ 'AWS EventBridge resources are not being created using native CloudFormation, this is now possible and the use of custom resources is deprecated. Set `eventBridge.useCloudFormation: true` as a provider property to use this now.'
+ );
+ }
+ }
+ },
'package:compileEvents': this.compileEventBridgeEvents.bind(this),
};
this.serverless.configSchemaHandler.defineFunctionEvent('aws', 'eventBridge', {
type: 'object',
properties: {
- eventBus: { type: 'string', minLength: 1 },
+ eventBus: {
+ anyOf: [{ type: 'string', minLength: 1 }, { $ref: '#/definitions/awsArn' }],
+ },
schedule: { pattern: '^(?:cron|rate)\\(.+\\)$' },
pattern: {
type: 'object',
@@ -59,6 +74,8 @@ class AwsCompileEventBridgeEvents {
const { provider } = service;
const { compiledCloudFormationTemplate } = provider;
const iamRoleStatements = [];
+ const { eventBridge: options } = provider;
+ const shouldUseCloudFormation = options ? options.useCloudFormation : false;
let hasEventBusesIamRoleStatement = false;
let anyFuncUsesEventBridge = false;
@@ -71,30 +88,16 @@ class AwsCompileEventBridgeEvents {
if (event.eventBridge) {
idx++;
anyFuncUsesEventBridge = true;
-
const EventBus = event.eventBridge.eventBus;
const Schedule = event.eventBridge.schedule;
const Pattern = event.eventBridge.pattern;
const Input = event.eventBridge.input;
const InputPath = event.eventBridge.inputPath;
let InputTransformer = event.eventBridge.inputTransformer;
- const RuleNameSuffix = `rule-${idx}`;
- let RuleName = `${FunctionName}-${RuleNameSuffix}`;
- if (RuleName.length > 64) {
- // Rule names cannot be longer than 64.
- // Temporary solution until we have https://github.com/serverless/serverless/issues/6598
- RuleName = `${RuleName.slice(0, 31 - RuleNameSuffix.length)}${crypto
- .createHash('md5')
- .update(RuleName)
- .digest('hex')}-${RuleNameSuffix}`;
- }
-
- const eventFunctionLogicalId = this.provider.naming.getLambdaLogicalId(functionName);
- const customResourceFunctionLogicalId = this.provider.naming.getCustomResourceEventBridgeHandlerFunctionLogicalId();
- const customEventBridgeResourceLogicalId = this.provider.naming.getCustomResourceEventBridgeResourceLogicalId(
- functionName,
- idx
- );
+ const RuleName = makeAndHashRuleName({
+ functionName: FunctionName,
+ index: idx,
+ });
if ([Input, InputPath, InputTransformer].filter(Boolean).length > 1) {
throw new ServerlessError(
@@ -112,115 +115,330 @@ class AwsCompileEventBridgeEvents {
);
}
- const customEventBridge = {
- [customEventBridgeResourceLogicalId]: {
- Type: 'Custom::EventBridge',
- Version: 1.0,
- DependsOn: [eventFunctionLogicalId, customResourceFunctionLogicalId],
- Properties: {
- ServiceToken: {
- 'Fn::GetAtt': [customResourceFunctionLogicalId, 'Arn'],
- },
- FunctionName,
- EventBridgeConfig: {
- RuleName,
- EventBus,
- Schedule,
- Pattern,
- Input,
- InputPath,
- InputTransformer,
- },
- },
- },
- };
+ const eventBusName = EventBus;
+ // Custom resources will be deprecated in next major release
+ if (!shouldUseCloudFormation) {
+ const results = this.compileWithCustomResource({
+ eventBusName,
+ EventBus,
+ compiledCloudFormationTemplate,
+ functionName,
+ RuleName,
+ Input,
+ InputPath,
+ InputTransformer,
+ Pattern,
+ Schedule,
+ FunctionName,
+ idx,
+ hasEventBusesIamRoleStatement,
+ iamRoleStatements,
+ });
- _.merge(compiledCloudFormationTemplate.Resources, customEventBridge);
-
- if (EventBus) {
- let eventBusName = EventBus;
- if (EventBus.startsWith('arn')) {
- eventBusName = EventBus.slice(EventBus.indexOf('/') + 1);
- }
-
- if (!hasEventBusesIamRoleStatement && eventBusName !== 'default') {
- const eventBusResources = {
- 'Fn::Join': [
- ':',
- [
- 'arn',
- { Ref: 'AWS::Partition' },
- 'events',
- { Ref: 'AWS::Region' },
- { Ref: 'AWS::AccountId' },
- 'event-bus/*',
- ],
- ],
- };
- iamRoleStatements.push({
- Effect: 'Allow',
- Resource: eventBusResources,
- Action: ['events:CreateEventBus', 'events:DeleteEventBus'],
- });
- hasEventBusesIamRoleStatement = true;
- }
+ results.iamRoleStatements.forEach((statement) => iamRoleStatements.push(statement));
+ hasEventBusesIamRoleStatement = results.hasEventBusesIamRoleStatement;
+ } else {
+ this.compileWithCloudFormation({
+ eventBusName,
+ EventBus,
+ compiledCloudFormationTemplate,
+ functionName,
+ RuleName,
+ Input,
+ InputPath,
+ InputTransformer,
+ Pattern,
+ Schedule,
+ FunctionName,
+ idx,
+ hasEventBusesIamRoleStatement,
+ iamRoleStatements,
+ });
}
}
});
}
});
- if (anyFuncUsesEventBridge) {
- const ruleResources = {
- 'Fn::Join': [
- ':',
- [
- 'arn',
- { Ref: 'AWS::Partition' },
- 'events',
- { Ref: 'AWS::Region' },
- { Ref: 'AWS::AccountId' },
- 'rule/*',
- ],
- ],
- };
- iamRoleStatements.push({
- Effect: 'Allow',
- Resource: ruleResources,
- Action: [
- 'events:PutRule',
- 'events:RemoveTargets',
- 'events:PutTargets',
- 'events:DeleteRule',
- ],
- });
- const functionResources = {
- 'Fn::Join': [
- ':',
- [
- 'arn',
- { Ref: 'AWS::Partition' },
- 'lambda',
- { Ref: 'AWS::Region' },
- { Ref: 'AWS::AccountId' },
- 'function',
- '*',
- ],
- ],
- };
- iamRoleStatements.push({
- Effect: 'Allow',
- Resource: functionResources,
- Action: ['lambda:AddPermission', 'lambda:RemovePermission'],
- });
+ // These permissions are for the custom resource lambda
+ if (!shouldUseCloudFormation && anyFuncUsesEventBridge) {
+ return this._addCustomResourceToService({ iamRoleStatements });
}
+ return null;
+ }
+
+ compileWithCustomResource({
+ eventBusName,
+ EventBus,
+ compiledCloudFormationTemplate,
+ functionName,
+ RuleName,
+ Input,
+ InputPath,
+ InputTransformer,
+ Pattern,
+ Schedule,
+ FunctionName,
+ idx,
+ hasEventBusesIamRoleStatement,
+ }) {
+ if (_.isObject(eventBusName)) {
+ throw new ServerlessError(
+ 'Referencing event bus with CloudFormation intrinsic functions is not supported for EventBrigde integration backed by Custom Resources. Please use `provider.eventBridge.useCloudFormation` setting to use native CloudFormation support for EventBridge.',
+ 'ERROR_INVALID_REFERENCE_TO_EVENT_BUS_CUSTOM_RESOURCE'
+ );
+ }
+
+ const iamRoleStatements = [];
+
+ if (typeof eventBusName === 'string' && eventBusName.startsWith('arn')) {
+ eventBusName = EventBus.slice(EventBus.indexOf('/') + 1);
+ }
+
+ const eventFunctionLogicalId = this.provider.naming.getLambdaLogicalId(functionName);
+ const customResourceFunctionLogicalId = this.provider.naming.getCustomResourceEventBridgeHandlerFunctionLogicalId();
+ const customEventBridgeResourceLogicalId = this.provider.naming.getCustomResourceEventBridgeResourceLogicalId(
+ functionName,
+ idx
+ );
+
+ const customEventBridge = {
+ Type: 'Custom::EventBridge',
+ Version: 1.0,
+ DependsOn: [eventFunctionLogicalId, customResourceFunctionLogicalId],
+ Properties: {
+ ServiceToken: {
+ 'Fn::GetAtt': [customResourceFunctionLogicalId, 'Arn'],
+ },
+ FunctionName,
+ EventBridgeConfig: {
+ RuleName,
+ EventBus,
+ Schedule,
+ Pattern,
+ Input,
+ InputPath,
+ InputTransformer,
+ },
+ },
+ };
+
+ compiledCloudFormationTemplate.Resources[
+ customEventBridgeResourceLogicalId
+ ] = customEventBridge;
+
+ if (!hasEventBusesIamRoleStatement && eventBusName && eventBusName !== 'default') {
+ iamRoleStatements.push({
+ Effect: 'Allow',
+ Resource: {
+ 'Fn::Join': [
+ ':',
+ [
+ 'arn',
+ { Ref: 'AWS::Partition' },
+ 'events',
+ { Ref: 'AWS::Region' },
+ { Ref: 'AWS::AccountId' },
+ 'event-bus/*',
+ ],
+ ],
+ },
+ Action: ['events:CreateEventBus', 'events:DeleteEventBus'],
+ });
+ hasEventBusesIamRoleStatement = true;
+ }
+ return {
+ iamRoleStatements,
+ hasEventBusesIamRoleStatement,
+ };
+ }
+
+ compileWithCloudFormation({
+ eventBusName: _eventBusName,
+ EventBus,
+ compiledCloudFormationTemplate,
+ functionName,
+ RuleName,
+ Input,
+ InputPath,
+ InputTransformer,
+ Pattern,
+ Schedule,
+ FunctionName,
+ idx,
+ }) {
+ let eventBusResource;
+ let eventBusExists = false;
+ let eventBusName = _eventBusName;
+
+ // It suggests that the object already exists and is being imported
+ if (_.isObject(eventBusName)) {
+ eventBusExists = true;
+ }
+
+ // Does the resource already exist? ARN string - assume it is valid - CF will validate ultimately
+ if (typeof eventBusName === 'string' && eventBusName.startsWith('arn')) {
+ eventBusExists = true;
+ eventBusName = EventBus.slice(EventBus.indexOf('/') + 1);
+ }
+
+ const shouldCreateEventBus = !eventBusExists && eventBusName && eventBusName !== 'default';
+ if (shouldCreateEventBus) {
+ // Create EventBus Resource
+ eventBusResource = {
+ Type: 'AWS::Events::EventBus',
+ Properties: {
+ Name: eventBusName,
+ },
+ };
+
+ compiledCloudFormationTemplate.Resources[
+ this.provider.naming.getEventBridgeEventBusLogicalId(eventBusName)
+ ] = eventBusResource;
+ }
+
+ const targetBase = {
+ Arn: {
+ 'Fn::GetAtt': [this.provider.naming.getLambdaLogicalId(functionName), 'Arn'],
+ },
+ Id: makeEventBusTargetId(RuleName),
+ };
+
+ const target = this.addInputConfigToTarget({
+ target: targetBase,
+ Input,
+ InputPath,
+ InputTransformer,
+ });
+
+ // Create a rule
+ const eventRuleResource = {
+ Type: 'AWS::Events::Rule',
+ Properties: {
+ // default event bus is used when EventBusName is not set
+ EventBusName: eventBusName === 'default' ? undefined : eventBusName,
+ EventPattern: JSON.stringify(Pattern),
+ Name: RuleName,
+ ScheduleExpression: Schedule,
+ State: 'ENABLED',
+ Targets: [target],
+ },
+ };
+ // If this stack is creating the event bus the rule must depend on it to ensure stack can be removed
+ if (shouldCreateEventBus) {
+ eventRuleResource.DependsOn = this.provider.naming.getEventBridgeEventBusLogicalId(
+ eventBusName
+ );
+ }
+
+ const ruleNameLogicalIdStub = makeRuleName({
+ functionName: FunctionName,
+ index: idx,
+ });
+
+ compiledCloudFormationTemplate.Resources[
+ this.provider.naming.getEventBridgeRuleLogicalId(ruleNameLogicalIdStub)
+ ] = eventRuleResource;
+
+ const ruleNameArnPath = eventBusName ? [eventBusName, RuleName] : [RuleName];
+ const lambdaPermissionResource = {
+ Type: 'AWS::Lambda::Permission',
+ Properties: {
+ Action: 'lambda:InvokeFunction',
+ FunctionName: {
+ Ref: this.provider.naming.getLambdaLogicalId(functionName),
+ },
+ Principal: 'events.amazonaws.com',
+ SourceArn: {
+ 'Fn::Join': [
+ ':',
+ [
+ 'arn',
+ { Ref: 'AWS::Partition' },
+ 'events',
+ { Ref: 'AWS::Region' },
+ { Ref: 'AWS::AccountId' },
+ {
+ 'Fn::Join': ['/', ['rule', ...ruleNameArnPath]],
+ },
+ ],
+ ],
+ },
+ },
+ };
+
+ compiledCloudFormationTemplate.Resources[
+ this.provider.naming.getEventBridgeLambdaPermissionLogicalId(functionName, idx)
+ ] = lambdaPermissionResource;
+ }
+
+ _addCustomResourceToService({ iamRoleStatements: _iamRoleStatements }) {
+ const iamRoleStatements = _iamRoleStatements;
+ const ruleResources = {
+ 'Fn::Join': [
+ ':',
+ [
+ 'arn',
+ { Ref: 'AWS::Partition' },
+ 'events',
+ { Ref: 'AWS::Region' },
+ { Ref: 'AWS::AccountId' },
+ 'rule/*',
+ ],
+ ],
+ };
+ iamRoleStatements.push({
+ Effect: 'Allow',
+ Resource: ruleResources,
+ Action: ['events:PutRule', 'events:RemoveTargets', 'events:PutTargets', 'events:DeleteRule'],
+ });
+ const functionResources = {
+ 'Fn::Join': [
+ ':',
+ [
+ 'arn',
+ { Ref: 'AWS::Partition' },
+ 'lambda',
+ { Ref: 'AWS::Region' },
+ { Ref: 'AWS::AccountId' },
+ 'function',
+ '*',
+ ],
+ ],
+ };
+ iamRoleStatements.push({
+ Effect: 'Allow',
+ Resource: functionResources,
+ Action: ['lambda:AddPermission', 'lambda:RemovePermission'],
+ });
if (iamRoleStatements.length) {
return addCustomResourceToService(this.provider, 'eventBridge', iamRoleStatements);
}
-
return null;
}
+
+ addInputConfigToTarget({ target, Input, InputPath, InputTransformer }) {
+ if (Input) {
+ target = Object.assign(target, {
+ Input: JSON.stringify(Input),
+ });
+ return target;
+ }
+ if (InputPath) {
+ target = Object.assign(target, {
+ InputPath,
+ });
+ return target;
+ }
+ if (InputTransformer) {
+ target = Object.assign(target, {
+ InputTransformer,
+ });
+ return target;
+ }
+ return target;
+ }
}
module.exports = AwsCompileEventBridgeEvents;
diff --git a/lib/plugins/aws/package/compile/events/eventBridge/utils.js b/lib/plugins/aws/package/compile/events/eventBridge/utils.js
new file mode 100644
index 000000000..83007bccb
--- /dev/null
+++ b/lib/plugins/aws/package/compile/events/eventBridge/utils.js
@@ -0,0 +1,40 @@
+'use strict';
+
+const crypto = require('crypto');
+
+const makeAndHashRuleName = ({ functionName, index }) => {
+ const name = makeRuleName({ functionName, index });
+ if (name.length > 64) {
+ // Rule names cannot be longer than 64.
+ // Temporary solution until we have https://github.com/serverless/serverless/issues/6598
+ return hashName(name, makeRuleNameSuffix(index));
+ }
+ return name;
+};
+
+const makeRuleName = ({ functionName, index }) => `${functionName}-${makeRuleNameSuffix(index)}`;
+
+const makeRuleNameSuffix = (index) => `rule-${index}`;
+
+const makeEventBusTargetId = (ruleName) => {
+ const suffix = 'target';
+ let targetId = `${ruleName}-${suffix}`;
+ if (targetId.length > 64) {
+ // Target ids cannot be longer than 64.
+ targetId = hashName(targetId, suffix);
+ }
+ return targetId;
+};
+
+const hashName = (name, suffix) =>
+ `${name.slice(0, 31 - suffix.length)}${crypto
+ .createHash('md5')
+ .update(name)
+ .digest('hex')}-${suffix}`;
+
+module.exports = {
+ makeAndHashRuleName,
+ makeRuleName,
+ hashName,
+ makeEventBusTargetId,
+};
diff --git a/lib/plugins/aws/provider.js b/lib/plugins/aws/provider.js
index da44b0b7a..e699f5f1c 100644
--- a/lib/plugins/aws/provider.js
+++ b/lib/plugins/aws/provider.js
@@ -730,6 +730,13 @@ class AwsProvider {
anyOf: ['REGIONAL', 'EDGE', 'PRIVATE'].map(caseInsensitive),
},
environment: { $ref: '#/definitions/awsLambdaEnvironment' },
+ eventBridge: {
+ type: 'object',
+ properties: {
+ useCloudFormation: { const: true },
+ },
+ additionalProperties: false,
+ },
httpApi: {
type: 'object',
properties: {
diff --git a/test/fixtures/eventBridge/core.js b/test/fixtures/eventBridge/core.js
index f34605e6a..6bab92bc6 100644
--- a/test/fixtures/eventBridge/core.js
+++ b/test/fixtures/eventBridge/core.js
@@ -28,4 +28,9 @@ function eventBusArn(event, context, callback) {
return callback(null, event);
}
-module.exports = { eventBusDefault, eventBusDefaultArn, eventBusCustom, eventBusArn };
+module.exports = {
+ eventBusDefault,
+ eventBusDefaultArn,
+ eventBusCustom,
+ eventBusArn,
+};
diff --git a/test/integration/eventBridge.test.js b/test/integration/eventBridge.test.js
index b5de7c617..3aa6fea30 100644
--- a/test/integration/eventBridge.test.js
+++ b/test/integration/eventBridge.test.js
@@ -14,89 +14,132 @@ const {
const { deployService, removeService, getMarkers } = require('../utils/integration');
-describe('AWS - Event Bridge Integration Test', function () {
- this.timeout(1000 * 60 * 10); // Involves time-taking deploys
- let serviceName;
- let stackName;
- let servicePath;
- let namedEventBusName;
- let arnEventBusName;
- let arnEventBusArn;
- const eventSource = 'serverless.test';
- const stage = 'dev';
- const putEventEntries = [
- {
- Source: eventSource,
- DetailType: 'ServerlessDetailType',
- Detail: '{"Key1":"Value1"}',
- },
- ];
-
- before(async () => {
- const serviceData = await fixtures.setup('eventBridge');
- ({ servicePath } = serviceData);
- serviceName = serviceData.serviceConfig.service;
-
- namedEventBusName = `${serviceName}-named-event-bus`;
- arnEventBusName = `${serviceName}-arn-event-bus`;
-
- // get default event bus ARN
- const defaultEventBusArn = (await describeEventBus('default')).Arn;
-
- stackName = `${serviceName}-${stage}`;
- // create an external Event Bus
- // NOTE: deployment can only be done once the Event Bus is created
- arnEventBusArn = (await createEventBus(arnEventBusName)).EventBusArn;
- // update the YAML file with the arn
- await serviceData.updateConfig({
- functions: {
- eventBusDefaultArn: {
- events: [
- {
- eventBridge: {
- eventBus: defaultEventBusArn,
- pattern: { source: ['serverless.test'] },
- },
- },
- ],
- },
- eventBusArn: {
- events: [
- {
- eventBridge: {
- eventBus: arnEventBusArn,
- pattern: { source: ['serverless.test'] },
- },
- },
- ],
- },
+describe('AWS - Event Bridge Integration Test', () => {
+ describe('Using deprecated CustomResource deployment pattern', function () {
+ this.timeout(1000 * 60 * 100); // Involves time-taking deploys
+ let serviceName;
+ let stackName;
+ let servicePath;
+ let namedEventBusName;
+ let arnEventBusName;
+ let arnEventBusArn;
+ const eventSource = 'serverless.test';
+ const stage = 'dev';
+ const putEventEntries = [
+ {
+ Source: eventSource,
+ DetailType: 'ServerlessDetailType',
+ Detail: '{"Key1":"Value1"}',
},
+ ];
+
+ before(async () => {
+ const serviceData = await fixtures.setup('eventBridge');
+ ({ servicePath } = serviceData);
+ serviceName = serviceData.serviceConfig.service;
+
+ namedEventBusName = `${serviceName}-named-event-bus`;
+ arnEventBusName = `${serviceName}-arn-event-bus`;
+
+ // get default event bus ARN
+ const defaultEventBusArn = (await describeEventBus('default')).Arn;
+
+ stackName = `${serviceName}-${stage}`;
+ // create an external Event Bus
+ // NOTE: deployment can only be done once the Event Bus is created
+ arnEventBusArn = (await createEventBus(arnEventBusName)).EventBusArn;
+ // update the YAML file with the arn
+ await serviceData.updateConfig({
+ functions: {
+ eventBusDefaultArn: {
+ events: [
+ {
+ eventBridge: {
+ eventBus: defaultEventBusArn,
+ pattern: { source: [eventSource] },
+ },
+ },
+ ],
+ },
+ eventBusArn: {
+ events: [
+ {
+ eventBridge: {
+ eventBus: arnEventBusArn,
+ pattern: { source: [eventSource] },
+ },
+ },
+ ],
+ },
+ },
+ });
+ // deploy the service
+ return deployService(servicePath);
});
- // deploy the service
- return deployService(servicePath);
- });
- after(async () => {
- log.notice('Removing service...');
- await removeService(servicePath);
- log.notice(`Deleting Event Bus "${arnEventBusName}"...`);
- return deleteEventBus(arnEventBusName);
- });
+ after(async () => {
+ log.notice('Removing service...');
+ await removeService(servicePath);
+ log.notice(`Deleting Event Bus "${arnEventBusName}"...`);
+ return deleteEventBus(arnEventBusName);
+ });
- describe('Default Event Bus', () => {
- it('should invoke function when an event is sent to the event bus', () => {
- const functionName = 'eventBusDefault';
- const markers = getMarkers(functionName);
+ describe('Default Event Bus', () => {
+ it('should invoke function when an event is sent to the event bus', async () => {
+ const functionName = 'eventBusDefault';
+ const markers = getMarkers(functionName);
- return confirmCloudWatchLogs(
- `/aws/lambda/${stackName}-${functionName}`,
- () => putEvents('default', putEventEntries),
- {
- checkIsComplete: (events) =>
- events.find((event) => event.message.includes(markers.start)) &&
- events.find((event) => event.message.includes(markers.end)),
- }
- ).then((events) => {
+ const events = await confirmCloudWatchLogs(
+ `/aws/lambda/${stackName}-${functionName}`,
+ () => putEvents('default', putEventEntries),
+ {
+ checkIsComplete: (data) =>
+ data.find((event) => event.message.includes(markers.start)) &&
+ data.find((event) => event.message.includes(markers.end)),
+ }
+ );
+ const logs = events.map((event) => event.message).join('\n');
+ expect(logs).to.include(`"source":"${eventSource}"`);
+ expect(logs).to.include(`"detail-type":"${putEventEntries[0].DetailType}"`);
+ expect(logs).to.include(`"detail":${putEventEntries[0].Detail}`);
+ });
+ });
+
+ describe('Custom Event Bus', () => {
+ it('should invoke function when an event is sent to the event bus', async () => {
+ const functionName = 'eventBusCustom';
+ const markers = getMarkers(functionName);
+
+ const events = await confirmCloudWatchLogs(
+ `/aws/lambda/${stackName}-${functionName}`,
+ () => putEvents(namedEventBusName, putEventEntries),
+ {
+ checkIsComplete: (data) =>
+ data.find((event) => event.message.includes(markers.start)) &&
+ data.find((event) => event.message.includes(markers.end)),
+ }
+ );
+ const logs = events.map((event) => event.message).join('\n');
+ expect(logs).to.include(`"source":"${eventSource}"`);
+ expect(logs).to.include(`"detail-type":"${putEventEntries[0].DetailType}"`);
+ expect(logs).to.include(`"detail":${putEventEntries[0].Detail}`);
+ });
+ });
+
+ describe('Arn Event Bus', () => {
+ it('should invoke function when an event is sent to the event bus', async () => {
+ const functionName = 'eventBusArn';
+ const markers = getMarkers(functionName);
+
+ const events = await confirmCloudWatchLogs(
+ `/aws/lambda/${stackName}-${functionName}`,
+ () => putEvents(arnEventBusName, putEventEntries),
+ {
+ checkIsComplete: (data) =>
+ data.find((event) => event.message.includes(markers.start)) &&
+ data.find((event) => event.message.includes(markers.end)),
+ }
+ );
const logs = events.map((event) => event.message).join('\n');
expect(logs).to.include(`"source":"${eventSource}"`);
expect(logs).to.include(`"detail-type":"${putEventEntries[0].DetailType}"`);
@@ -105,42 +148,135 @@ describe('AWS - Event Bridge Integration Test', function () {
});
});
- describe('Custom Event Bus', () => {
- it('should invoke function when an event is sent to the event bus', () => {
- const functionName = 'eventBusCustom';
- const markers = getMarkers(functionName);
+ describe('Using native CloudFormation deployment pattern', function () {
+ this.timeout(1000 * 60 * 10); // Involves time-taking deploys
+ let serviceName;
+ let stackName;
+ let servicePath;
+ let namedEventBusName;
+ let arnEventBusName;
+ let arnEventBusArn;
+ const eventSource = 'serverless.test';
+ const stage = 'dev';
+ const putEventEntries = [
+ {
+ Source: eventSource,
+ DetailType: 'ServerlessDetailType',
+ Detail: '{"Key1":"Value1"}',
+ },
+ ];
- return confirmCloudWatchLogs(
- `/aws/lambda/${stackName}-${functionName}`,
- () => putEvents(namedEventBusName, putEventEntries),
- {
- checkIsComplete: (events) =>
- events.find((event) => event.message.includes(markers.start)) &&
- events.find((event) => event.message.includes(markers.end)),
- }
- ).then((events) => {
+ before(async () => {
+ const serviceData = await fixtures.setup('eventBridge');
+ ({ servicePath } = serviceData);
+ serviceName = serviceData.serviceConfig.service;
+
+ namedEventBusName = `${serviceName}-named-event-bus`;
+ arnEventBusName = `${serviceName}-arn-event-bus`;
+
+ // get default event bus ARN
+ const defaultEventBusArn = (await describeEventBus('default')).Arn;
+
+ stackName = `${serviceName}-${stage}`;
+ // create an external Event Bus
+ // NOTE: deployment can only be done once the Event Bus is created
+ arnEventBusArn = (await createEventBus(arnEventBusName)).EventBusArn;
+ await serviceData.updateConfig({
+ provider: {
+ eventBridge: {
+ useCloudFormation: true,
+ },
+ },
+ functions: {
+ eventBusDefaultArn: {
+ events: [
+ {
+ eventBridge: {
+ eventBus: defaultEventBusArn,
+ pattern: { source: [eventSource] },
+ },
+ },
+ ],
+ },
+ eventBusArn: {
+ events: [
+ {
+ eventBridge: {
+ eventBus: arnEventBusArn,
+ pattern: { source: [eventSource] },
+ },
+ },
+ ],
+ },
+ },
+ });
+ return deployService(servicePath);
+ });
+
+ after(async () => {
+ log.notice('Removing service...');
+ await removeService(servicePath);
+ log.notice(`Deleting Event Bus "${arnEventBusName}"...`);
+ return deleteEventBus(arnEventBusName);
+ });
+
+ describe('Default Event Bus', () => {
+ it('should invoke function when an event is sent to the event bus', async () => {
+ const functionName = 'eventBusDefault';
+ const markers = getMarkers(functionName);
+
+ const events = await confirmCloudWatchLogs(
+ `/aws/lambda/${stackName}-${functionName}`,
+ () => putEvents('default', putEventEntries),
+ {
+ checkIsComplete: (data) =>
+ data.find((event) => event.message.includes(markers.start)) &&
+ data.find((event) => event.message.includes(markers.end)),
+ }
+ );
const logs = events.map((event) => event.message).join('\n');
expect(logs).to.include(`"source":"${eventSource}"`);
expect(logs).to.include(`"detail-type":"${putEventEntries[0].DetailType}"`);
expect(logs).to.include(`"detail":${putEventEntries[0].Detail}`);
});
});
- });
- describe('Arn Event Bus', () => {
- it('should invoke function when an event is sent to the event bus', () => {
- const functionName = 'eventBusArn';
- const markers = getMarkers(functionName);
+ describe('Custom Event Bus', () => {
+ it('should invoke function when an event is sent to the event bus', async () => {
+ const functionName = 'eventBusCustom';
+ const markers = getMarkers(functionName);
+
+ const events = await confirmCloudWatchLogs(
+ `/aws/lambda/${stackName}-${functionName}`,
+ () => putEvents(namedEventBusName, putEventEntries),
+ {
+ checkIsComplete: (data) =>
+ data.find((event) => event.message.includes(markers.start)) &&
+ data.find((event) => event.message.includes(markers.end)),
+ }
+ );
+ const logs = events.map((event) => event.message).join('\n');
+ expect(logs).to.include(`"source":"${eventSource}"`);
+ expect(logs).to.include(`"detail-type":"${putEventEntries[0].DetailType}"`);
+ expect(logs).to.include(`"detail":${putEventEntries[0].Detail}`);
+ });
+ });
+
+ describe('Arn Event Bus', () => {
+ it('should invoke function when an event is sent to the event bus', async () => {
+ const functionName = 'eventBusArn';
+ const markers = getMarkers(functionName);
+
+ const events = await confirmCloudWatchLogs(
+ `/aws/lambda/${stackName}-${functionName}`,
+ () => putEvents(arnEventBusName, putEventEntries),
+ {
+ checkIsComplete: (data) =>
+ data.find((event) => event.message.includes(markers.start)) &&
+ data.find((event) => event.message.includes(markers.end)),
+ }
+ );
- return confirmCloudWatchLogs(
- `/aws/lambda/${stackName}-${functionName}`,
- () => putEvents(arnEventBusName, putEventEntries),
- {
- checkIsComplete: (events) =>
- events.find((event) => event.message.includes(markers.start)) &&
- events.find((event) => event.message.includes(markers.end)),
- }
- ).then((events) => {
const logs = events.map((event) => event.message).join('\n');
expect(logs).to.include(`"source":"${eventSource}"`);
expect(logs).to.include(`"detail-type":"${putEventEntries[0].DetailType}"`);
diff --git a/test/unit/lib/plugins/aws/lib/naming.test.js b/test/unit/lib/plugins/aws/lib/naming.test.js
index 62de01b85..caf102eee 100644
--- a/test/unit/lib/plugins/aws/lib/naming.test.js
+++ b/test/unit/lib/plugins/aws/lib/naming.test.js
@@ -983,4 +983,28 @@ describe('#naming()', () => {
);
});
});
+
+ describe('#getEventBridgeEventBusLogicalId()', () => {
+ it('should normalize the event bus name and append correct suffix', () => {
+ expect(sdk.naming.getEventBridgeEventBusLogicalId('ExampleEventBusName')).to.equal(
+ 'ExampleEventBusNameEventBridgeEventBus'
+ );
+ });
+ });
+
+ describe('#getEventBridgeRuleLogicalId()', () => {
+ it('should normalize the rule name and append correct suffix', () => {
+ expect(sdk.naming.getEventBridgeRuleLogicalId('exampleRuleName')).to.equal(
+ 'ExampleRuleNameEventBridgeRule'
+ );
+ });
+ });
+
+ describe('#getEventBridgeLambdaPermissionLogicalId()', () => {
+ it('should normalize the name and append correct suffix with index', () => {
+ expect(sdk.naming.getEventBridgeLambdaPermissionLogicalId('exampleFunction', 1)).to.equal(
+ 'ExampleFunctionEventBridgeLambdaPermission1'
+ );
+ });
+ });
});
diff --git a/test/unit/lib/plugins/aws/package/compile/events/eventBridge/index.test.js b/test/unit/lib/plugins/aws/package/compile/events/eventBridge/index.test.js
index 6d0bb7507..792c68e74 100644
--- a/test/unit/lib/plugins/aws/package/compile/events/eventBridge/index.test.js
+++ b/test/unit/lib/plugins/aws/package/compile/events/eventBridge/index.test.js
@@ -5,7 +5,10 @@
const chai = require('chai');
const runServerless = require('../../../../../../../../utils/run-serverless');
-const { expect } = chai;
+chai.use(require('chai-as-promised'));
+chai.use(require('sinon-chai'));
+
+const expect = chai.expect;
const NAME_OVER_64_CHARS = 'oneVeryLongAndVeryStrangeAndVeryComplicatedFunctionNameOver64Chars';
@@ -128,104 +131,362 @@ const serverlessConfigurationExtension = {
};
describe('EventBridgeEvents', () => {
- let cfResources;
- let naming;
+ describe('using custom resources deployment pattern', () => {
+ let cfResources;
+ let naming;
- before(() =>
- runServerless({
- fixture: 'function',
- configExt: serverlessConfigurationExtension,
- cliArgs: ['package'],
- }).then(({ cfTemplate, awsNaming }) => {
- ({ Resources: cfResources } = cfTemplate);
+ before(async () => {
+ const { cfTemplate, awsNaming } = await runServerless({
+ fixture: 'function',
+ configExt: serverlessConfigurationExtension,
+ cliArgs: ['package'],
+ });
+ cfResources = cfTemplate.Resources;
naming = awsNaming;
- })
- );
+ });
- /**
- *
- * @param {String} id
- */
- function getEventBridgeConfigById(resourceLogicalId) {
- const eventBridgeId = naming.getCustomResourceEventBridgeResourceLogicalId(
- resourceLogicalId,
- 1
- );
- return cfResources[eventBridgeId].Properties.EventBridgeConfig;
- }
+ function getEventBridgeConfigById(resourceLogicalId) {
+ const eventBridgeId = naming.getCustomResourceEventBridgeResourceLogicalId(
+ resourceLogicalId,
+ 1
+ );
+ return cfResources[eventBridgeId].Properties.EventBridgeConfig;
+ }
- it('should create the correct policy Statement', () => {
- const roleId = naming.getCustomResourcesRoleLogicalId('default', '12345');
+ it('should create the correct policy Statement', () => {
+ const roleId = naming.getCustomResourcesRoleLogicalId('default', '12345');
- const [firstStatement, secondStatement, thirdStatment] = cfResources[
- roleId
- ].Properties.Policies[0].PolicyDocument.Statement;
- expect(firstStatement.Effect).to.be.eq('Allow');
- expect(firstStatement.Resource['Fn::Join'][1]).to.deep.include('arn');
- expect(firstStatement.Resource['Fn::Join'][1]).to.deep.include('events');
- expect(firstStatement.Resource['Fn::Join'][1]).to.deep.include('event-bus/*');
- expect(firstStatement.Action).to.be.deep.eq(['events:CreateEventBus', 'events:DeleteEventBus']);
+ const [firstStatement, secondStatement, thirdStatment] = cfResources[
+ roleId
+ ].Properties.Policies[0].PolicyDocument.Statement;
+ expect(firstStatement.Effect).to.be.eq('Allow');
+ expect(firstStatement.Resource['Fn::Join'][1]).to.deep.include('arn');
+ expect(firstStatement.Resource['Fn::Join'][1]).to.deep.include('events');
+ expect(firstStatement.Resource['Fn::Join'][1]).to.deep.include('event-bus/*');
+ expect(firstStatement.Action).to.be.deep.eq([
+ 'events:CreateEventBus',
+ 'events:DeleteEventBus',
+ ]);
- expect(secondStatement.Effect).to.be.eq('Allow');
- expect(secondStatement.Resource['Fn::Join'][1]).to.deep.include('events');
- expect(secondStatement.Resource['Fn::Join'][1]).to.deep.include('rule/*');
- expect(secondStatement.Action).to.be.deep.eq([
- 'events:PutRule',
- 'events:RemoveTargets',
- 'events:PutTargets',
- 'events:DeleteRule',
- ]);
+ expect(secondStatement.Effect).to.be.eq('Allow');
+ expect(secondStatement.Resource['Fn::Join'][1]).to.deep.include('events');
+ expect(secondStatement.Resource['Fn::Join'][1]).to.deep.include('rule/*');
+ expect(secondStatement.Action).to.be.deep.eq([
+ 'events:PutRule',
+ 'events:RemoveTargets',
+ 'events:PutTargets',
+ 'events:DeleteRule',
+ ]);
- expect(thirdStatment.Effect).to.be.eq('Allow');
- expect(thirdStatment.Resource['Fn::Join'][1]).to.deep.include('function');
- expect(thirdStatment.Resource['Fn::Join'][1]).to.deep.include('lambda');
- expect(thirdStatment.Action).to.be.deep.eq(['lambda:AddPermission', 'lambda:RemovePermission']);
- });
- it('should create the necessary resource', () => {
- const eventBridgeConfig = getEventBridgeConfigById('default');
- expect(eventBridgeConfig.RuleName).to.include('dev-default-rule-1');
- });
+ expect(thirdStatment.Effect).to.be.eq('Allow');
+ expect(thirdStatment.Resource['Fn::Join'][1]).to.deep.include('function');
+ expect(thirdStatment.Resource['Fn::Join'][1]).to.deep.include('lambda');
+ expect(thirdStatment.Action).to.be.deep.eq([
+ 'lambda:AddPermission',
+ 'lambda:RemovePermission',
+ ]);
+ });
+ it('should create the necessary resource', () => {
+ const eventBridgeConfig = getEventBridgeConfigById('default');
+ expect(eventBridgeConfig.RuleName).to.include('dev-default-rule-1');
+ });
- it("should ensure rule name doesn't exceed 64 chars", () => {
- const eventBridgeConfig = getEventBridgeConfigById(NAME_OVER_64_CHARS);
- expect(eventBridgeConfig.RuleName.endsWith('rule-1')).to.be.true;
- expect(eventBridgeConfig.RuleName).lengthOf.lte(64);
- });
+ it("should ensure rule name doesn't exceed 64 chars", () => {
+ const eventBridgeConfig = getEventBridgeConfigById(NAME_OVER_64_CHARS);
+ expect(eventBridgeConfig.RuleName.endsWith('rule-1')).to.be.true;
+ expect(eventBridgeConfig.RuleName).lengthOf.lte(64);
+ });
- it('should support input configuration', () => {
- const eventBridgeConfig = getEventBridgeConfigById('configureInput');
- expect(eventBridgeConfig.Input.key1).be.eq('value1');
- expect(eventBridgeConfig.Input.key2).be.deep.eq({
- nested: 'value2',
+ it('should support input configuration', () => {
+ const eventBridgeConfig = getEventBridgeConfigById('configureInput');
+ expect(eventBridgeConfig.Input.key1).be.eq('value1');
+ expect(eventBridgeConfig.Input.key2).be.deep.eq({
+ nested: 'value2',
+ });
+ });
+
+ it('should support arn at eventBus', () => {
+ const eventBridgeConfig = getEventBridgeConfigById('configureInput');
+ expect(eventBridgeConfig.EventBus).be.eq(
+ 'arn:aws:events:us-east-1:12345:event-bus/some-event-bus'
+ );
+ });
+ it('should support inputPath configuration', () => {
+ const eventBridgeConfig = getEventBridgeConfigById('inputPathConfiguration');
+ expect(eventBridgeConfig.InputPath).be.eq('$.stageVariables');
+ });
+
+ it('should support inputTransformer configuration', () => {
+ const eventBridgeConfig = getEventBridgeConfigById('inputTransformer');
+ const {
+ InputTemplate,
+ InputPathsMap: { eventTime },
+ } = eventBridgeConfig.InputTransformer;
+ expect(InputTemplate).be.eq('{"time": , "key1": "value1"}');
+ expect(eventTime).be.eq('$.time');
+ });
+
+ it('should register created and delete event bus permissions for non default event bus', () => {
+ const roleId = naming.getCustomResourcesRoleLogicalId('customSaas', '12345');
+ const [firstStatement] = cfResources[roleId].Properties.Policies[0].PolicyDocument.Statement;
+ expect(firstStatement.Action[0]).to.be.eq('events:CreateEventBus');
+ expect(firstStatement.Action[1]).to.be.eq('events:DeleteEventBus');
+ expect(firstStatement.Effect).to.be.eq('Allow');
+ });
+
+ it('should fail when trying to reference event bus via CF intrinsic function', async () => {
+ await expect(
+ runServerless({
+ fixture: 'function',
+ configExt: {
+ functions: {
+ foo: {
+ events: [
+ {
+ eventBridge: {
+ eventBus: { Ref: 'ImportedEventBus' },
+ schedule: 'rate(10 minutes)',
+ },
+ },
+ ],
+ },
+ },
+ },
+ cliArgs: ['package'],
+ })
+ ).to.be.eventually.rejected.and.have.property(
+ 'code',
+ 'ERROR_INVALID_REFERENCE_TO_EVENT_BUS_CUSTOM_RESOURCE'
+ );
});
});
- it('should support arn at eventBus', () => {
- const eventBridgeConfig = getEventBridgeConfigById('configureInput');
- expect(eventBridgeConfig.EventBus).be.eq(
- 'arn:aws:events:us-east-1:12345:event-bus/some-event-bus'
- );
- });
- it('should support inputPath configuration', () => {
- const eventBridgeConfig = getEventBridgeConfigById('inputPathConfiguration');
- expect(eventBridgeConfig.InputPath).be.eq('$.stageVariables');
- });
+ describe('using native CloudFormation', () => {
+ describe('when event bus is created as a part of the stack', () => {
+ let cfResources;
+ let naming;
+ let eventBusLogicalId;
+ let ruleResource;
+ let ruleTarget;
+ let inputPathRuleTarget;
+ let inputTransformerRuleTarget;
+ const schedule = 'rate(10 minutes)';
+ const eventBusName = 'nondefault';
+ const pattern = {
+ source: ['aws.cloudformation'],
+ };
+ const input = {
+ key1: 'value1',
+ key2: {
+ nested: 'value2',
+ },
+ };
+ const inputPath = '$.stageVariables';
+ const inputTransformer = {
+ inputTemplate: '{"time": , "key1": "value1"}',
+ inputPathsMap: {
+ eventTime: '$.time',
+ },
+ };
- it('should support inputTransformer configuration', () => {
- const eventBridgeConfig = getEventBridgeConfigById('inputTransformer');
- const {
- InputTemplate,
- InputPathsMap: { eventTime },
- } = eventBridgeConfig.InputTransformer;
- expect(InputTemplate).be.eq('{"time": , "key1": "value1"}');
- expect(eventTime).be.eq('$.time');
- });
+ before(async () => {
+ const { cfTemplate, awsNaming } = await runServerless({
+ fixture: 'function',
+ configExt: {
+ provider: {
+ eventBridge: {
+ useCloudFormation: true,
+ },
+ },
+ functions: {
+ foo: {
+ events: [
+ {
+ eventBridge: {
+ eventBus: eventBusName,
+ schedule,
+ pattern,
+ input,
+ },
+ },
+ {
+ eventBridge: {
+ eventBus: eventBusName,
+ schedule,
+ pattern,
+ inputPath,
+ },
+ },
+ {
+ eventBridge: {
+ eventBus: eventBusName,
+ schedule,
+ pattern,
+ inputTransformer,
+ },
+ },
+ ],
+ },
+ },
+ },
+ cliArgs: ['package'],
+ });
+ cfResources = cfTemplate.Resources;
+ naming = awsNaming;
+ eventBusLogicalId = naming.getEventBridgeEventBusLogicalId(eventBusName);
+ ruleResource = Object.values(cfResources).find(
+ (resource) =>
+ resource.Type === 'AWS::Events::Rule' && resource.Properties.Name.endsWith('1')
+ );
+ ruleTarget = ruleResource.Properties.Targets[0];
+ const inputPathRuleResource = Object.values(cfResources).find(
+ (resource) =>
+ resource.Type === 'AWS::Events::Rule' && resource.Properties.Name.endsWith('2')
+ );
+ inputPathRuleTarget = inputPathRuleResource.Properties.Targets[0];
+ const inputTransformerRuleResource = Object.values(cfResources).find(
+ (resource) =>
+ resource.Type === 'AWS::Events::Rule' && resource.Properties.Name.endsWith('3')
+ );
+ inputTransformerRuleTarget = inputTransformerRuleResource.Properties.Targets[0];
+ });
- it('should register created and delete event bus permissions for non default event bus', () => {
- const roleId = naming.getCustomResourcesRoleLogicalId('customSaas', '12345');
- const [firstStatement] = cfResources[roleId].Properties.Policies[0].PolicyDocument.Statement;
- expect(firstStatement.Action[0]).to.be.eq('events:CreateEventBus');
- expect(firstStatement.Action[1]).to.be.eq('events:DeleteEventBus');
- expect(firstStatement.Effect).to.be.eq('Allow');
+ it('should create an EventBus resource', () => {
+ expect(cfResources[eventBusLogicalId].Properties).to.deep.equal({ Name: eventBusName });
+ });
+
+ it('should correctly set ScheduleExpression on a created rule', () => {
+ expect(ruleResource.Properties.ScheduleExpression).to.equal('rate(10 minutes)');
+ });
+
+ it('should correctly set EventPattern on a created rule', () => {
+ expect(ruleResource.Properties.EventPattern).to.deep.equal(JSON.stringify(pattern));
+ });
+
+ it('should correctly set Input on the target for the created rule', () => {
+ expect(ruleTarget.Input).to.deep.equal(JSON.stringify(input));
+ });
+
+ it('should correctly set InputPath on the target for the created rule', () => {
+ expect(inputPathRuleTarget.InputPath).to.deep.equal(inputPath);
+ });
+
+ it('should correctly set InputTransformer on the target for the created rule', () => {
+ expect(inputTransformerRuleTarget.InputTransformer.InputPathsMap).to.deep.equal(
+ inputTransformer.inputPathsMap
+ );
+ expect(inputTransformerRuleTarget.InputTransformer.InputTemplate).to.deep.equal(
+ inputTransformer.inputTemplate
+ );
+ });
+
+ it('should create a rule that depends on created EventBus', () => {
+ expect(ruleResource.DependsOn).to.equal(eventBusLogicalId);
+ });
+
+ it('should create a rule that references correct function in target', () => {
+ expect(ruleTarget.Arn['Fn::GetAtt'][0]).to.equal(naming.getLambdaLogicalId('foo'));
+ });
+
+ it('should create a lambda permission resource that correctly references event bus in SourceArn', () => {
+ const lambdaPermissionResource =
+ cfResources[naming.getEventBridgeLambdaPermissionLogicalId('foo', 1)];
+
+ expect(
+ lambdaPermissionResource.Properties.SourceArn['Fn::Join'][1][5]['Fn::Join'][1][1]
+ ).to.deep.equal(eventBusName);
+ });
+ });
+
+ describe('when it references already existing EventBus or uses default one', () => {
+ let cfResources;
+ let naming;
+
+ before(async () => {
+ const { cfTemplate, awsNaming } = await runServerless({
+ fixture: 'function',
+ cliArgs: ['package'],
+ configExt: {
+ provider: {
+ eventBridge: {
+ useCloudFormation: true,
+ },
+ },
+ functions: {
+ foo: {
+ events: [
+ {
+ eventBridge: {
+ schedule: 'rate(10 minutes)',
+ eventBus: 'arn:xxxxx',
+ },
+ },
+ {
+ eventBridge: {
+ schedule: 'rate(10 minutes)',
+ eventBus: { Ref: 'ImportedEventBus' },
+ },
+ },
+ {
+ eventBridge: {
+ schedule: 'rate(10 minutes)',
+ eventBus: 'default',
+ },
+ },
+ {
+ eventBridge: {
+ schedule: 'rate(10 minutes)',
+ },
+ },
+ ],
+ },
+ },
+ },
+ });
+ cfResources = cfTemplate.Resources;
+ naming = awsNaming;
+ });
+
+ it('should not create an EventBus if it is provided or default', async () => {
+ expect(Object.values(cfResources).some((value) => value.Type === 'AWS::Events::EventBus'))
+ .to.be.false;
+ });
+
+ it('should create a lambda permission resource that correctly references arn event bus in SourceArn', () => {
+ const lambdaPermissionResource =
+ cfResources[naming.getEventBridgeLambdaPermissionLogicalId('foo', 1)];
+
+ expect(
+ lambdaPermissionResource.Properties.SourceArn['Fn::Join'][1][5]['Fn::Join'][1][1]
+ ).to.deep.equal('arn:xxxxx');
+ });
+
+ it('should create a lambda permission resource that correctly references CF event bus in SourceArn', () => {
+ const lambdaPermissionResource =
+ cfResources[naming.getEventBridgeLambdaPermissionLogicalId('foo', 2)];
+
+ expect(
+ lambdaPermissionResource.Properties.SourceArn['Fn::Join'][1][5]['Fn::Join'][1][1]
+ ).to.deep.equal({ Ref: 'ImportedEventBus' });
+ });
+
+ it('should create a lambda permission resource that correctly references explicit default event bus in SourceArn', () => {
+ const lambdaPermissionResource =
+ cfResources[naming.getEventBridgeLambdaPermissionLogicalId('foo', 3)];
+
+ expect(
+ lambdaPermissionResource.Properties.SourceArn['Fn::Join'][1][5]['Fn::Join'][1][1]
+ ).to.equal('default');
+ });
+
+ it('should create a lambda permission resource that correctly references implicit default event bus in SourceArn', () => {
+ const lambdaPermissionResource =
+ cfResources[naming.getEventBridgeLambdaPermissionLogicalId('foo', 4)];
+
+ expect(
+ lambdaPermissionResource.Properties.SourceArn['Fn::Join'][1][5]['Fn::Join'][1]
+ ).not.to.include('default');
+ });
+ });
});
});