Skip to content

Commit

Permalink
Add event bridge schema validation
Browse files Browse the repository at this point in the history
  • Loading branch information
fredericbarthelet committed Aug 21, 2020
1 parent b0938c7 commit a41b487
Show file tree
Hide file tree
Showing 2 changed files with 121 additions and 242 deletions.
232 changes: 121 additions & 111 deletions lib/plugins/aws/package/compile/events/eventBridge/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,52 @@ class AwsCompileEventBridgeEvents {
'package:compileEvents': this.compileEventBridgeEvents.bind(this),
};

// TODO: Complete schema, see https://github.com/serverless/serverless/issues/8029
this.serverless.configSchemaHandler.defineFunctionEvent('aws', 'eventBridge', {
type: 'object',
properties: {
eventBus: { type: 'string', minLength: 1 },
schedule: { pattern: '^(?:cron|rate)\\(.+\\)$' },
pattern: {
type: 'object',
properties: {
'version': {},
'id': {},
'detail-type': {},
'source': {},
'account': {},
'time': {},
'region': {},
'resources': {},
'detail': {},
},
additionalProperties: false,
},
input: { type: 'object' },
inputPath: { type: 'string', minLength: 1, maxLength: 256 },
inputTransformer: {
type: 'object',
properties: {
inputPathsMap: {
type: 'object',
additionalProperties: { type: 'string', minLength: 1 },
},
inputTemplate: { type: 'string', minLength: 1, maxLength: 8192 },
},
required: ['inputTemplate'],
additionalProperties: false,
},
},
allOf: [
{ anyOf: [{ required: ['pattern'] }, { required: ['schedule'] }] },
{
oneOf: [
{ required: [] },
{ required: ['input'] },
{ required: ['inputPath'] },
{ required: ['inputTransformer'] },
],
},
],
});
}

Expand All @@ -35,125 +78,92 @@ class AwsCompileEventBridgeEvents {
if (functionObj.events) {
functionObj.events.forEach((event, idx) => {
if (event.eventBridge) {
if (typeof event.eventBridge === 'object') {
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}`;
}
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 eventFunctionLogicalId = this.provider.naming.getLambdaLogicalId(functionName);
const customResourceFunctionLogicalId = this.provider.naming.getCustomResourceEventBridgeHandlerFunctionLogicalId();
const customEventBridgeResourceLogicalId = this.provider.naming.getCustomResourceEventBridgeResourceLogicalId(
functionName,
idx
);

if (InputTransformer) {
InputTransformer = _.mapKeys(
InputTransformer,
(value, key) => key[0].toLocaleUpperCase() + key.slice(1)
);
}

if (!Pattern && !Schedule) {
const errorMessage = [
'You need to configure the pattern or schedule property (or both) ',
'for eventBridge events.',
].join('');
throw new this.serverless.classes.Error(errorMessage);
}
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 inputOptions = [Input, InputPath, InputTransformer].filter(i => i);
if (inputOptions.length > 1) {
const errorMessage = [
'You can only set one of input, inputPath, or inputTransformer ',
'properties for eventBridge events.',
].join('');
throw new this.serverless.classes.Error(errorMessage);
}
_.merge(compiledCloudFormationTemplate.Resources, customEventBridge);

if (InputTransformer) {
const config = Object.assign({}, InputTransformer);
InputTransformer = {};
if (!config.inputTemplate) {
throw new this.serverless.classes.Error(
'The inputTemplate key is required when specifying an ' +
'inputTransformer for an eventBridge event'
);
}
InputTransformer.InputTemplate = config.inputTemplate;
if (config.inputPathsMap) {
InputTransformer.InputPathsMap = config.inputPathsMap;
}
if (EventBus) {
let eventBusName = EventBus;
if (EventBus.startsWith('arn')) {
eventBusName = EventBus.slice(EventBus.indexOf('/') + 1);
}

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,
},
},
},
};

_.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/*',
],
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;
}
],
};
iamRoleStatements.push({
Effect: 'Allow',
Resource: eventBusResources,
Action: ['events:CreateEventBus', 'events:DeleteEventBus'],
});
hasEventBusesIamRoleStatement = true;
}
} else {
const errorMessage = [
`Event Bridge event of function "${functionName}" is not an object`,
' Please check the docs for more info.',
].join('');
throw new this.serverless.classes.Error(errorMessage);
}
}
});
Expand Down

0 comments on commit a41b487

Please sign in to comment.