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

Support for Cloudwatch Event InputTransformer #5912

Merged
merged 2 commits into from
Mar 13, 2019
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
13 changes: 13 additions & 0 deletions docs/providers/aws/events/cloudwatch-event.md
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -89,6 +89,19 @@ functions:
state: state:
- pending - pending
inputPath: '$.stageVariables' inputPath: '$.stageVariables'
- cloudwatchEvent:
event:
source:
- "aws.ec2"
detail-type:
- "EC2 Instance State-change Notification"
detail:
state:
- pending
inputTransformer:
inputPathsMap:
eventTime: '$.time'
inputTemplate: '{"time": <eventTime>, "key1": "value1"}'
``` ```


## Specifying a Description ## Specifying a Description
Expand Down
7 changes: 7 additions & 0 deletions docs/providers/aws/events/schedule.md
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -47,6 +47,13 @@ functions:
rate: cron(0 12 * * ? *) rate: cron(0 12 * * ? *)
enabled: false enabled: false
inputPath: '$.stageVariables' inputPath: '$.stageVariables'
- schedule:
rate: rate(2 hours)
enabled: true
inputTransformer:
inputPathsMap:
eventTime: '$.time'
inputTemplate: '{"time": <eventTime>, "key1": "value1"}'
``` ```


## Specify Name and Description ## Specify Name and Description
Expand Down
11 changes: 10 additions & 1 deletion docs/providers/aws/guide/serverless.yml.md
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -200,12 +200,17 @@ functions:
description: a description of my scheduled event's purpose description: a description of my scheduled event's purpose
rate: rate(10 minutes) rate: rate(10 minutes)
enabled: false enabled: false
# Note, you can use only one of input, inputPath, or inputTransformer
input: input:
key1: value1 key1: value1
key2: value2 key2: value2
stageParams: stageParams:
stage: dev stage: dev
inputPath: '$.stageVariables' inputPath: '$.stageVariables'
inputTransformer:
inputPathsMap:
eventTime: '$.time'
inputTemplate: '{"time": <eventTime>, "key1": "value1"}'
- sns: - sns:
topicName: aggregate topicName: aggregate
displayName: Data aggregation pipeline displayName: Data aggregation pipeline
Expand Down Expand Up @@ -238,13 +243,17 @@ functions:
detail: detail:
state: state:
- pending - pending
# Note: you can either use "input" or "inputPath" # Note, you can use only one of input, inputPath, or inputTransformer
input: input:
key1: value1 key1: value1
key2: value2 key2: value2
stageParams: stageParams:
stage: dev stage: dev
inputPath: '$.stageVariables' inputPath: '$.stageVariables'
inputTransformer:
inputPathsMap:
eventTime: '$.time'
inputTemplate: '{"time": <eventTime>, "key1": "value1"}'
- cloudwatchLog: - cloudwatchLog:
logGroup: '/aws/lambda/hello' logGroup: '/aws/lambda/hello'
filter: '{$.userIdentity.type = Root}' filter: '{$.userIdentity.type = Root}'
Expand Down
31 changes: 28 additions & 3 deletions lib/plugins/aws/package/compile/events/cloudWatchEvent/index.js
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ class AwsCompileCloudWatchEventEvents {
let State; let State;
let Input; let Input;
let InputPath; let InputPath;
let InputTransformer;
let Description; let Description;
let Name; let Name;


Expand All @@ -45,13 +46,15 @@ class AwsCompileCloudWatchEventEvents {
} }
Input = event.cloudwatchEvent.input; Input = event.cloudwatchEvent.input;
InputPath = event.cloudwatchEvent.inputPath; InputPath = event.cloudwatchEvent.inputPath;
InputTransformer = event.cloudwatchEvent.inputTransformer;
Description = event.cloudwatchEvent.description; Description = event.cloudwatchEvent.description;
Name = event.cloudwatchEvent.name; Name = event.cloudwatchEvent.name;


if (Input && InputPath) { const inputOptions = [Input, InputPath, InputTransformer].filter(i => i);
if (inputOptions.length > 1) {
const errorMessage = [ const errorMessage = [
'You can\'t set both input & inputPath properties at the', 'You can only set one of input, inputPath, or inputTransformer ',
'same time for cloudwatch events.', 'properties at the same time for cloudwatch events. ',
'Please check the AWS docs for more info', 'Please check the AWS docs for more info',
].join(''); ].join('');
throw new this.serverless.classes.Error(errorMessage); throw new this.serverless.classes.Error(errorMessage);
Expand All @@ -64,6 +67,9 @@ class AwsCompileCloudWatchEventEvents {
// escape quotes to favor JSON.parse // escape quotes to favor JSON.parse
Input = Input.replace(/\"/g, '\\"'); // eslint-disable-line Input = Input.replace(/\"/g, '\\"'); // eslint-disable-line
} }
if (InputTransformer) {
InputTransformer = this.formatInputTransformer(InputTransformer);
}
} else { } else {
const errorMessage = [ const errorMessage = [
`CloudWatch event of function "${functionName}" is not an object`, `CloudWatch event of function "${functionName}" is not an object`,
Expand Down Expand Up @@ -93,6 +99,7 @@ class AwsCompileCloudWatchEventEvents {
"Targets": [{ "Targets": [{
${Input ? `"Input": "${Input.replace(/\\n|\\r/g, '')}",` : ''} ${Input ? `"Input": "${Input.replace(/\\n|\\r/g, '')}",` : ''}
${InputPath ? `"InputPath": "${InputPath.replace(/\r?\n/g, '')}",` : ''} ${InputPath ? `"InputPath": "${InputPath.replace(/\r?\n/g, '')}",` : ''}
${InputTransformer ? `"InputTransformer": ${InputTransformer},` : ''}
"Arn": { "Fn::GetAtt": ["${lambdaLogicalId}", "Arn"] }, "Arn": { "Fn::GetAtt": ["${lambdaLogicalId}", "Arn"] },
"Id": "${cloudWatchId}" "Id": "${cloudWatchId}"
}] }]
Expand Down Expand Up @@ -128,6 +135,24 @@ class AwsCompileCloudWatchEventEvents {
} }
}); });
} }

formatInputTransformer(inputTransformer) {
if (!inputTransformer.inputTemplate) {
throw new this.serverless.classes.Error(
'The inputTemplate key is required when specifying an ' +
'inputTransformer for a cloudwatchEvent event'
);
}
const cfmOutput = {
// InputTemplate is required
InputTemplate: inputTransformer.inputTemplate,
};
// InputPathsMap is optional
if (inputTransformer.inputPathsMap) {
cfmOutput.InputPathsMap = inputTransformer.inputPathsMap;
}
return JSON.stringify(cfmOutput);
}
} }


module.exports = AwsCompileCloudWatchEventEvents; module.exports = AwsCompileCloudWatchEventEvents;
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -218,6 +218,42 @@ describe('awsCompileCloudWatchEventEvents', () => {
).to.equal('{"key":"value"}'); ).to.equal('{"key":"value"}');
}); });


it('should respect inputTransformer variable', () => {
awsCompileCloudWatchEventEvents.serverless.service.functions = {
first: {
events: [
{
cloudwatchEvent: {
event: {
source: ['aws.ec2'],
'detail-type': ['EC2 Instance State-change Notification'],
detail: { state: ['pending'] },
},
enabled: false,
inputTransformer: {
inputPathsMap: {
eventTime: '$.time',
},
inputTemplate: '{"time": <eventTime>, "key1": "value1"}',
},
},
},
],
},
};

awsCompileCloudWatchEventEvents.compileCloudWatchEventEvents();

expect(awsCompileCloudWatchEventEvents.serverless.service
.provider.compiledCloudFormationTemplate.Resources.FirstEventsRuleCloudWatchEvent1
.Properties.Targets[0].InputTransformer
).to.eql({
InputTemplate: '{"time": <eventTime>, "key1": "value1"}',
InputPathsMap: { eventTime: '$.time' },
});
});


it('should respect description variable', () => { it('should respect description variable', () => {
awsCompileCloudWatchEventEvents.serverless.service.functions = { awsCompileCloudWatchEventEvents.serverless.service.functions = {
first: { first: {
Expand Down Expand Up @@ -328,6 +364,62 @@ describe('awsCompileCloudWatchEventEvents', () => {
expect(() => awsCompileCloudWatchEventEvents.compileCloudWatchEventEvents()).to.throw(Error); expect(() => awsCompileCloudWatchEventEvents.compileCloudWatchEventEvents()).to.throw(Error);
}); });


it('should throw an error when both Input and InputTransformer are set', () => {
awsCompileCloudWatchEventEvents.serverless.service.functions = {
first: {
events: [
{
cloudwatchEvent: {
event: {
source: ['aws.ec2'],
'detail-type': ['EC2 Instance State-change Notification'],
detail: { state: ['pending'] },
},
enabled: false,
input: {
key: 'value',
},
inputTransformer: {
inputPathsMap: {
eventTime: '$.time',
},
inputTemplate: '{"time": <eventTime>, "key1": "value1"}',
},
},
},
],
},
};

expect(() => awsCompileCloudWatchEventEvents.compileCloudWatchEventEvents()).to.throw(Error);
});

it('should throw an error when inputTransformer does not have inputTemplate', () => {
awsCompileCloudWatchEventEvents.serverless.service.functions = {
first: {
events: [
{
cloudwatchEvent: {
event: {
source: ['aws.ec2'],
'detail-type': ['EC2 Instance State-change Notification'],
detail: { state: ['pending'] },
},
enabled: false,
inputTransformer: {
inputPathsMap: {
eventTime: '$.time',
},
},
},
},
],
},
};

expect(() => awsCompileCloudWatchEventEvents.compileCloudWatchEventEvents()).to.throw(Error);
});

it('should respect variables if multi-line variables is given', () => { it('should respect variables if multi-line variables is given', () => {
awsCompileCloudWatchEventEvents.serverless.service.functions = { awsCompileCloudWatchEventEvents.serverless.service.functions = {
first: { first: {
Expand Down
31 changes: 28 additions & 3 deletions lib/plugins/aws/package/compile/events/schedule/index.js
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ class AwsCompileScheduledEvents {
let State; let State;
let Input; let Input;
let InputPath; let InputPath;
let InputTransformer;
let Name; let Name;
let Description; let Description;


Expand All @@ -56,13 +57,15 @@ class AwsCompileScheduledEvents {
} }
Input = event.schedule.input; Input = event.schedule.input;
InputPath = event.schedule.inputPath; InputPath = event.schedule.inputPath;
InputTransformer = event.schedule.inputTransformer;
Name = event.schedule.name; Name = event.schedule.name;
Description = event.schedule.description; Description = event.schedule.description;


if (Input && InputPath) { const inputOptions = [Input, InputPath, InputTransformer].filter(i => i);
if (inputOptions.length > 1) {
const errorMessage = [ const errorMessage = [
'You can\'t set both input & inputPath properties at the', 'You can only set one of input, inputPath, or inputTransformer ',
'same time for schedule events.', 'properties at the same time for schedule events. ',
'Please check the AWS docs for more info', 'Please check the AWS docs for more info',
].join(''); ].join('');
throw new this.serverless.classes throw new this.serverless.classes
Expand Down Expand Up @@ -90,6 +93,9 @@ class AwsCompileScheduledEvents {
// escape quotes to favor JSON.parse // escape quotes to favor JSON.parse
Input = Input.replace(/\"/g, '\\"'); // eslint-disable-line Input = Input.replace(/\"/g, '\\"'); // eslint-disable-line
} }
if (InputTransformer) {
InputTransformer = this.formatInputTransformer(InputTransformer);
}
} else if (this.validateScheduleSyntax(event.schedule)) { } else if (this.validateScheduleSyntax(event.schedule)) {
ScheduleExpression = event.schedule; ScheduleExpression = event.schedule;
State = 'ENABLED'; State = 'ENABLED';
Expand Down Expand Up @@ -118,6 +124,7 @@ class AwsCompileScheduledEvents {
"Targets": [{ "Targets": [{
${Input ? `"Input": "${Input}",` : ''} ${Input ? `"Input": "${Input}",` : ''}
${InputPath ? `"InputPath": "${InputPath}",` : ''} ${InputPath ? `"InputPath": "${InputPath}",` : ''}
${InputTransformer ? `"InputTransformer": ${InputTransformer},` : ''}
"Arn": { "Fn::GetAtt": ["${lambdaLogicalId}", "Arn"] }, "Arn": { "Fn::GetAtt": ["${lambdaLogicalId}", "Arn"] },
"Id": "${scheduleId}" "Id": "${scheduleId}"
}] }]
Expand Down Expand Up @@ -158,6 +165,24 @@ class AwsCompileScheduledEvents {
return typeof input === 'string' && return typeof input === 'string' &&
(rateSyntaxPattern.test(input) || cronSyntaxPattern.test(input)); (rateSyntaxPattern.test(input) || cronSyntaxPattern.test(input));
} }

formatInputTransformer(inputTransformer) {
if (!inputTransformer.inputTemplate) {
throw new this.serverless.classes.Error(
'The inputTemplate key is required when specifying an ' +
'inputTransformer for a schedule event'
);
}
const cfmOutput = {
// InputTemplate is required
InputTemplate: inputTransformer.inputTemplate,
};
// InputPathsMap is optional
if (inputTransformer.inputPathsMap) {
cfmOutput.InputPathsMap = inputTransformer.inputPathsMap;
}
return JSON.stringify(cfmOutput);
}
} }


module.exports = AwsCompileScheduledEvents; module.exports = AwsCompileScheduledEvents;