Merge pull request #1301 from serverless/schedule

Adde Schedule Event Source support
This commit is contained in:
Eslam λ Hefnawy 2016-06-10 17:05:34 +02:00 committed by GitHub
commit 08d385b966
8 changed files with 161 additions and 5 deletions

View File

@ -27,6 +27,8 @@ Serverless provides (such as service creation, deployment, removal, function inv
- AWS plugins
- [awsCompileFunctions](/docs/plugins/aws/awsCompileFunctions.md) - Compiles the functions to CloudFormation resources
- [awsCompileS3Events](/docs/plugins/aws/awsCompileS3Events.md) - Compiles the S3 events to CloudFormation resources
- [awsCompileScheduledEvents](/docs/plugins/aws/awsCompileScheduledEvents.md) - Compiles the Scheduled events to
CloudFormation resources
- [awsDeploy](/docs/plugins/aws/awsDeploy.md) - Deploys the Serverless service to AWS
- [awsInvoke](/docs/plugins/aws/awsInvoke.md) - Invokes a AWS lambda function
- [awsRemove](/docs/plugins/aws/awsRemove.md) - Removes the service with all it's resources from AWS

View File

@ -0,0 +1,13 @@
# awsCompileScheduledEvents
This plugins compiles the function schedule event to to a CloudFormation resource.
## How it works
`awsCompileScheduledEvents` hooks into the [`deploy:compileEvents`](/docs/plugins/core/deploy.md) hook.
It loops over all functions which are defined in `serverless.yaml`. For each function that has a schedule event defined, a CloudWatch schedule event rule will be created with a status of "enabled" and targeting the lambda function the event is defined within.
Furthermore a lambda permission for the current function is created which makes is possible to invoke the function at the specified schedule.
Those two resources are then merged into the `serverless.service.resources.aws.Resources` section.

View File

@ -6,6 +6,7 @@
"./remove/remove.js",
"./awsCompileFunctions/awsCompileFunctions.js",
"./awsCompileS3Events/awsCompileS3Events.js",
"./awsCompileScheduledEvents/awsCompileScheduledEvents.js",
"./awsDeploy/awsDeploy.js",
"./awsInvoke/awsInvoke.js",
"./awsRemove/awsRemove.js",

View File

@ -0,0 +1,67 @@
'use strict';
const merge = require('lodash').merge;
class AwsCompileScheduledEvents {
constructor(serverless) {
this.serverless = serverless;
this.hooks = {
'deploy:compileEvents': this.compileScheduledEvents.bind(this),
};
}
compileScheduledEvents() {
if (!this.serverless.service.resources.aws.Resources) {
throw new this.serverless.Error(
'This plugin needs access to Resources section of the AWS CloudFormation template');
}
this.serverless.service.getAllFunctions().forEach((functionName) => {
const functionObj = this.serverless.service.getFunction(functionName);
// checking all three levels in the obj tree
// to avoid "can't read property of undefined" error
if (functionObj.events && functionObj.events.aws && functionObj.events.aws.schedule) {
const scheduleTemplate = `
{
"Type": "AWS::Events::Rule",
"Properties": {
"ScheduleExpression": "${functionObj.events.aws.schedule}",
"State": "ENABLED",
"Targets": [{
"Arn": { "Fn::GetAtt": ["${functionName}", "Arn"] },
"Id": "${functionName}ScheduleEvent"
}]
}
}
`;
const permissionTemplate = `
{
"Type": "AWS::Lambda::Permission",
"Properties": {
"FunctionName": { "Fn::GetAtt": ["${functionName}", "Arn"] },
"Action": "lambda:InvokeFunction",
"Principal": "events.amazonaws.com",
"SourceArn": { "Fn::GetAtt": ["${functionName}ScheduleEvent", "Arn"] }
}
}
`;
const newScheduleObject = {
[`${functionName}ScheduleEvent`]: JSON.parse(scheduleTemplate),
};
const newPermissionObject = {
[`${functionName}ScheduleEventPermission`]: JSON.parse(permissionTemplate),
};
merge(this.serverless.service.resources.aws.Resources,
newScheduleObject, newPermissionObject);
}
});
}
}
module.exports = AwsCompileScheduledEvents;

View File

@ -0,0 +1,72 @@
'use strict';
const expect = require('chai').expect;
const AwsCompileScheduledEvents = require('../awsCompileScheduledEvents');
const Serverless = require('../../../Serverless');
describe('awsCompileScheduledEvents', () => {
let serverless;
let awsCompileScheduledEvents;
beforeEach(() => {
serverless = new Serverless();
serverless.init();
serverless.service.resources = { aws: { Resources: {} } };
awsCompileScheduledEvents = new AwsCompileScheduledEvents(serverless);
awsCompileScheduledEvents.serverless.service.service = 'new-service';
});
describe('#compileScheduledEvents()', () => {
it('should throw an error if the aws resource is not available', () => {
awsCompileScheduledEvents.serverless.service.resources.aws.Resources = false;
expect(() => awsCompileScheduledEvents.compileScheduledEvents()).to.throw(Error);
});
it('should compile scheduled events into CF resources', () => {
awsCompileScheduledEvents.serverless.service.functions = {
hello: {
events: {
aws: {
schedule: 'rate(10 minutes)',
},
},
},
};
const scheduleResrouce = `
{
"Type": "AWS::Events::Rule",
"Properties": {
"ScheduleExpression": "rate(10 minutes)",
"State": "ENABLED",
"Targets": [{
"Arn": { "Fn::GetAtt": ["hello", "Arn"] },
"Id": "helloScheduleEvent"
}]
}
}
`;
const permissionResource = `
{
"Type": "AWS::Lambda::Permission",
"Properties": {
"FunctionName": { "Fn::GetAtt": ["hello", "Arn"] },
"Action": "lambda:InvokeFunction",
"Principal": "events.amazonaws.com",
"SourceArn": { "Fn::GetAtt": ["helloScheduleEvent", "Arn"] }
}
}
`;
awsCompileScheduledEvents.compileScheduledEvents();
expect(awsCompileScheduledEvents.serverless.service
.resources.aws.Resources.helloScheduleEvent)
.to.deep.equal(JSON.parse(scheduleResrouce));
expect(awsCompileScheduledEvents.serverless.service
.resources.aws.Resources.helloScheduleEventPermission)
.to.deep.equal(JSON.parse(permissionResource));
});
});
});

View File

@ -32,7 +32,7 @@ functions: # if this gets too big, you can always use JSON-REF
# - first-bucket
http_endpoint:
post: users/create
scheduled: 5 * * * *
# schedule: rate(10 minutes)
azure:
http_endpoint:
direction: in

View File

@ -22,3 +22,4 @@ require('../lib/plugins/awsRemove/tests/all');
require('../lib/plugins/awsInvoke/tests/awsInvoke');
require('../lib/plugins/awsCompileFunctions/tests/awsCompileFunctions');
require('../lib/plugins/awsCompileS3Events/tests/awsCompileS3Events');
require('../lib/plugins/awsCompileScheduledEvents/tests/awsCompileScheduledEvents');

View File

@ -31,7 +31,7 @@ describe('Service Lifecyle Integration Test', () => {
stageName
} --region ${
regionName
}`);
}`, { stdio: 'inherit' });
process.chdir(path.join(tmpDir, serviceName));
expect(serverless.utils
@ -50,7 +50,7 @@ describe('Service Lifecyle Integration Test', () => {
stageName
} --region ${
regionName
}`);
}`, { stdio: 'inherit' });
return CF.describeStacksPromised({ StackName: `${serviceName}-${stageName}` })
.then(d => expect(d.Stacks[0].StackStatus).to.be.equal('UPDATE_COMPLETE'));
@ -83,7 +83,7 @@ describe('Service Lifecyle Integration Test', () => {
stageName
} --region ${
regionName
}`);
}`, { stdio: 'inherit' });
});
it('should invoke updated function from aws', function () {
@ -103,7 +103,7 @@ describe('Service Lifecyle Integration Test', () => {
stageName
} --region ${
regionName
}`);
}`, { stdio: 'inherit' });
return CF.describeStacksPromised({ StackName: `${serviceName}-${stageName}` })
.then(d => expect(d.Stacks[0].StackStatus).to.be.equal('DELETE_COMPLETE'))