mirror of
https://github.com/serverless/serverless.git
synced 2026-01-18 14:58:43 +00:00
feat(AWS EventBridge): Support deadLetterQueue and retryPolicy
Co-authored-by: JP Bochi <jpbochi@pm.me>
This commit is contained in:
parent
95d3024ef5
commit
130fb3838f
@ -186,3 +186,47 @@ functions:
|
||||
eventTime: '$.time'
|
||||
inputTemplate: '{"time": <eventTime>, "key1": "value1"}'
|
||||
```
|
||||
|
||||
## Adding a DLQ to an event rule
|
||||
|
||||
DeadLetterConfig is not available for custom resources, only for native CloudFormation.
|
||||
|
||||
```yml
|
||||
functions:
|
||||
myFunction:
|
||||
handler: index.handler
|
||||
events:
|
||||
- eventBridge:
|
||||
eventBus: custom-saas-events
|
||||
pattern:
|
||||
source:
|
||||
- saas.external
|
||||
deadLetterConfig:
|
||||
targetArn:
|
||||
Fn::GetAtt:
|
||||
- QueueName
|
||||
- Arn
|
||||
```
|
||||
|
||||
## Adding a retry policy to an event rule
|
||||
|
||||
RetryPolicy is not available for custom resources, only for native CloudFormation.
|
||||
|
||||
```yml
|
||||
functions:
|
||||
myFunction:
|
||||
handler: index.handler
|
||||
events:
|
||||
- eventBridge:
|
||||
eventBus: custom-saas-events
|
||||
pattern:
|
||||
source:
|
||||
- saas.external
|
||||
deadLetterConfig:
|
||||
Fn::GetAtt:
|
||||
- QueueName
|
||||
- Arn
|
||||
retryPolicy:
|
||||
maximumEventAge: 3600
|
||||
maximumRetryAttempts: 3
|
||||
```
|
||||
|
||||
@ -87,6 +87,30 @@ class AwsCompileEventBridgeEvents {
|
||||
required: ['inputTemplate'],
|
||||
additionalProperties: false,
|
||||
},
|
||||
retryPolicy: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
maximumEventAge: {
|
||||
type: 'integer',
|
||||
minimum: 60,
|
||||
maximum: 86400,
|
||||
},
|
||||
maximumRetryAttempts: {
|
||||
type: 'integer',
|
||||
minimum: 0,
|
||||
maximum: 185,
|
||||
},
|
||||
},
|
||||
},
|
||||
deadLetterConfig: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
targetArn: {
|
||||
$ref: '#/definitions/awsArn',
|
||||
},
|
||||
},
|
||||
additionalProperties: false,
|
||||
},
|
||||
},
|
||||
anyOf: [{ required: ['pattern'] }, { required: ['schedule'] }],
|
||||
});
|
||||
@ -117,6 +141,9 @@ class AwsCompileEventBridgeEvents {
|
||||
const Input = event.eventBridge.input;
|
||||
const InputPath = event.eventBridge.inputPath;
|
||||
let InputTransformer = event.eventBridge.inputTransformer;
|
||||
let RetryPolicy = event.eventBridge.retryPolicy;
|
||||
let DeadLetterConfig = event.eventBridge.deadLetterConfig;
|
||||
|
||||
const RuleName = makeAndHashRuleName({
|
||||
functionName: FunctionName,
|
||||
index: idx,
|
||||
@ -144,6 +171,32 @@ class AwsCompileEventBridgeEvents {
|
||||
);
|
||||
}
|
||||
|
||||
if (RetryPolicy) {
|
||||
if (!shouldUseCloudFormation) {
|
||||
throw new ServerlessError(
|
||||
'Configuring RetryPolicy 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_RETRY_POLICY_TO_EVENT_BUS_CUSTOM_RESOURCE'
|
||||
);
|
||||
}
|
||||
|
||||
RetryPolicy = {
|
||||
MaximumEventAge: RetryPolicy.maximumEventAge,
|
||||
MaximumRetryAttempts: RetryPolicy.maximumRetryAttempts,
|
||||
};
|
||||
}
|
||||
|
||||
if (DeadLetterConfig) {
|
||||
if (!shouldUseCloudFormation) {
|
||||
throw new ServerlessError(
|
||||
'Configuring DeadLetterConfig 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_DEAD_LETTER_CONFIG_TO_EVENT_BUS_CUSTOM_RESOURCE'
|
||||
);
|
||||
}
|
||||
DeadLetterConfig = {
|
||||
TargetArn: DeadLetterConfig.targetArn,
|
||||
};
|
||||
}
|
||||
|
||||
const eventBusName = EventBus;
|
||||
// Custom resources will be deprecated in next major release
|
||||
if (!shouldUseCloudFormation) {
|
||||
@ -184,6 +237,8 @@ class AwsCompileEventBridgeEvents {
|
||||
idx,
|
||||
hasEventBusesIamRoleStatement,
|
||||
iamRoleStatements,
|
||||
RetryPolicy,
|
||||
DeadLetterConfig,
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -297,6 +352,8 @@ class AwsCompileEventBridgeEvents {
|
||||
Pattern,
|
||||
Schedule,
|
||||
FunctionName,
|
||||
RetryPolicy,
|
||||
DeadLetterConfig,
|
||||
idx,
|
||||
}) {
|
||||
let eventBusResource;
|
||||
@ -336,11 +393,13 @@ class AwsCompileEventBridgeEvents {
|
||||
Id: makeEventBusTargetId(RuleName),
|
||||
};
|
||||
|
||||
const target = this.addInputConfigToTarget({
|
||||
const target = this.configureTarget({
|
||||
target: targetBase,
|
||||
Input,
|
||||
InputPath,
|
||||
InputTransformer,
|
||||
RetryPolicy,
|
||||
DeadLetterConfig,
|
||||
});
|
||||
|
||||
// Create a rule
|
||||
@ -449,7 +508,7 @@ class AwsCompileEventBridgeEvents {
|
||||
return null;
|
||||
}
|
||||
|
||||
addInputConfigToTarget({ target, Input, InputPath, InputTransformer }) {
|
||||
configureTarget({ target, Input, InputPath, InputTransformer, RetryPolicy, DeadLetterConfig }) {
|
||||
if (Input) {
|
||||
target = Object.assign(target, {
|
||||
Input: JSON.stringify(Input),
|
||||
@ -468,6 +527,21 @@ class AwsCompileEventBridgeEvents {
|
||||
});
|
||||
return target;
|
||||
}
|
||||
|
||||
if (RetryPolicy) {
|
||||
target = Object.assign(target, {
|
||||
RetryPolicy,
|
||||
});
|
||||
return target;
|
||||
}
|
||||
|
||||
if (DeadLetterConfig) {
|
||||
target = Object.assign(target, {
|
||||
DeadLetterConfig,
|
||||
});
|
||||
return target;
|
||||
}
|
||||
|
||||
return target;
|
||||
}
|
||||
}
|
||||
|
||||
@ -275,6 +275,71 @@ describe('EventBridgeEvents', () => {
|
||||
expect(firstStatement.Effect).to.be.eq('Allow');
|
||||
});
|
||||
|
||||
it('should fail when trying to set RetryPolicy', async () => {
|
||||
await expect(
|
||||
runServerless({
|
||||
fixture: 'function',
|
||||
configExt: {
|
||||
disabledDeprecations: ['AWS_EVENT_BRIDGE_CUSTOM_RESOURCE'],
|
||||
functions: {
|
||||
foo: {
|
||||
events: [
|
||||
{
|
||||
eventBridge: {
|
||||
retryPolicy: {
|
||||
maximumEventAge: 4200,
|
||||
maximumRetryAttempts: 180,
|
||||
},
|
||||
pattern: {
|
||||
source: ['aws.something'],
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
command: 'package',
|
||||
})
|
||||
).to.be.eventually.rejected.and.have.property(
|
||||
'code',
|
||||
'ERROR_INVALID_RETRY_POLICY_TO_EVENT_BUS_CUSTOM_RESOURCE'
|
||||
);
|
||||
});
|
||||
|
||||
it('should fail when trying to set DeadLetterConfig', async () => {
|
||||
await expect(
|
||||
runServerless({
|
||||
fixture: 'function',
|
||||
configExt: {
|
||||
disabledDeprecations: ['AWS_EVENT_BRIDGE_CUSTOM_RESOURCE'],
|
||||
functions: {
|
||||
foo: {
|
||||
events: [
|
||||
{
|
||||
eventBridge: {
|
||||
deadLetterConfig: {
|
||||
targetArn: {
|
||||
'Fn::GetAtt': ['not-supported', 'Arn'],
|
||||
},
|
||||
},
|
||||
pattern: {
|
||||
source: ['aws.something'],
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
command: 'package',
|
||||
})
|
||||
).to.be.eventually.rejected.and.have.property(
|
||||
'code',
|
||||
'ERROR_INVALID_DEAD_LETTER_CONFIG_TO_EVENT_BUS_CUSTOM_RESOURCE'
|
||||
);
|
||||
});
|
||||
|
||||
it('should fail when trying to reference event bus via CF intrinsic function', async () => {
|
||||
await expect(
|
||||
runServerless({
|
||||
@ -310,10 +375,6 @@ describe('EventBridgeEvents', () => {
|
||||
let eventBusLogicalId;
|
||||
let ruleResource;
|
||||
let ruleTarget;
|
||||
let inputPathRuleTarget;
|
||||
let inputTransformerRuleTarget;
|
||||
let disabledRuleResource;
|
||||
let enabledRuleResource;
|
||||
const schedule = 'rate(10 minutes)';
|
||||
const eventBusName = 'nondefault';
|
||||
const pattern = {
|
||||
@ -332,6 +393,22 @@ describe('EventBridgeEvents', () => {
|
||||
eventTime: '$.time',
|
||||
},
|
||||
};
|
||||
const retryPolicy = {
|
||||
maximumEventAge: 7200,
|
||||
maximumRetryAttempts: 9,
|
||||
};
|
||||
|
||||
const deadLetterConfig = {
|
||||
targetArn: {
|
||||
'Fn::GetAtt': ['test', 'Arn'],
|
||||
},
|
||||
};
|
||||
|
||||
const getRuleResourceEndingWith = (resources, ending) =>
|
||||
Object.values(resources).find(
|
||||
(resource) =>
|
||||
resource.Type === 'AWS::Events::Rule' && resource.Properties.Name.endsWith(ending)
|
||||
);
|
||||
|
||||
before(async () => {
|
||||
const { cfTemplate, awsNaming } = await runServerless({
|
||||
@ -385,6 +462,22 @@ describe('EventBridgeEvents', () => {
|
||||
pattern,
|
||||
},
|
||||
},
|
||||
{
|
||||
eventBridge: {
|
||||
eventBus: eventBusName,
|
||||
schedule,
|
||||
pattern,
|
||||
retryPolicy,
|
||||
},
|
||||
},
|
||||
{
|
||||
eventBridge: {
|
||||
eventBus: eventBusName,
|
||||
schedule,
|
||||
pattern,
|
||||
deadLetterConfig,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
@ -394,31 +487,8 @@ describe('EventBridgeEvents', () => {
|
||||
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')
|
||||
);
|
||||
ruleResource = getRuleResourceEndingWith(cfResources, '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];
|
||||
|
||||
disabledRuleResource = Object.values(cfResources).find(
|
||||
(resource) =>
|
||||
resource.Type === 'AWS::Events::Rule' && resource.Properties.Name.endsWith('4')
|
||||
);
|
||||
|
||||
enabledRuleResource = Object.values(cfResources).find(
|
||||
(resource) =>
|
||||
resource.Type === 'AWS::Events::Rule' && resource.Properties.Name.endsWith('5')
|
||||
);
|
||||
});
|
||||
|
||||
it('should create an EventBus resource', () => {
|
||||
@ -434,10 +504,12 @@ describe('EventBridgeEvents', () => {
|
||||
});
|
||||
|
||||
it('should correctly set State when disabled on a created rule', () => {
|
||||
const disabledRuleResource = getRuleResourceEndingWith(cfResources, '4');
|
||||
expect(disabledRuleResource.Properties.State).to.equal('DISABLED');
|
||||
});
|
||||
|
||||
it('should correctly set State when enabled on a created rule', () => {
|
||||
const enabledRuleResource = getRuleResourceEndingWith(cfResources, '5');
|
||||
expect(enabledRuleResource.Properties.State).to.equal('ENABLED');
|
||||
});
|
||||
|
||||
@ -450,10 +522,14 @@ describe('EventBridgeEvents', () => {
|
||||
});
|
||||
|
||||
it('should correctly set InputPath on the target for the created rule', () => {
|
||||
const inputPathRuleResource = getRuleResourceEndingWith(cfResources, '2');
|
||||
const inputPathRuleTarget = inputPathRuleResource.Properties.Targets[0];
|
||||
expect(inputPathRuleTarget.InputPath).to.deep.equal(inputPath);
|
||||
});
|
||||
|
||||
it('should correctly set InputTransformer on the target for the created rule', () => {
|
||||
const inputTransformerRuleResource = getRuleResourceEndingWith(cfResources, '3');
|
||||
const inputTransformerRuleTarget = inputTransformerRuleResource.Properties.Targets[0];
|
||||
expect(inputTransformerRuleTarget.InputTransformer.InputPathsMap).to.deep.equal(
|
||||
inputTransformer.inputPathsMap
|
||||
);
|
||||
@ -462,6 +538,21 @@ describe('EventBridgeEvents', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('should support retryPolicy configuration', () => {
|
||||
const retryPolicyRuleTarget = getRuleResourceEndingWith(cfResources, '6').Properties
|
||||
.Targets[0];
|
||||
expect(retryPolicyRuleTarget.RetryPolicy).to.deep.equal({
|
||||
MaximumEventAge: 7200,
|
||||
MaximumRetryAttempts: 9,
|
||||
});
|
||||
});
|
||||
|
||||
it('should support deadLetterConfig configuration', () => {
|
||||
const deadLetterConfigRuleTarget = getRuleResourceEndingWith(cfResources, '7').Properties
|
||||
.Targets[0];
|
||||
expect(deadLetterConfigRuleTarget.DeadLetterConfig).to.have.property('TargetArn');
|
||||
});
|
||||
|
||||
it('should create a rule that depends on created EventBus', () => {
|
||||
expect(ruleResource.DependsOn).to.equal(eventBusLogicalId);
|
||||
});
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user