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

Add event bridge schema validation #8114

Merged
merged 1 commit into from
Aug 21, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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