diff --git a/docs/README.md b/docs/README.md
index 07d3050a6..99dd565dd 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -79,6 +79,7 @@ The Serverless Framework allows you to deploy auto-scaling, pay-per-execution, e
S3
Schedule
SNS
+ Alexa
diff --git a/docs/providers/aws/events/alexa.md b/docs/providers/aws/events/alexa.md
new file mode 100644
index 000000000..0dcecba24
--- /dev/null
+++ b/docs/providers/aws/events/alexa.md
@@ -0,0 +1,25 @@
+
+
+
+### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/aws/events/alexa)
+
+
+# Alexa
+
+## Event definition
+
+This will enable your Lambda function to be called by an Alexa skill kit.
+
+```yaml
+functions:
+ mySkill:
+ handler: mySkill.handler
+ events:
+ - alexa: true
+```
diff --git a/docs/providers/aws/guide/resources.md b/docs/providers/aws/guide/resources.md
index f8d37aa09..b84aed570 100644
--- a/docs/providers/aws/guide/resources.md
+++ b/docs/providers/aws/guide/resources.md
@@ -71,7 +71,7 @@ We're also using the term `normalizedName` or similar terms in this guide. This
|Lambda::Function | {normalizedFunctionName}LambdaFunction | HelloLambdaFunction |
|Lambda::Version | {normalizedFunctionName}LambdaVersion{sha256} | HelloLambdaVersionr3pgoTvv1xT4E4NiCL6JG02fl6vIyi7OS1aW0FwAI |
|Logs::LogGroup | {normalizedFunctionName}LogGroup | HelloLogGroup |
-|Lambda::Permission | - **Schedule**: {normalizedFunctionName}LambdaPermissionEventsRuleSchedule{index}
- **S3**: {normalizedFunctionName}LambdaPermission{normalizedBucketName}S3
- **APIG**: {normalizedFunctionName}LambdaPermissionApiGateway
- **SNS**: {normalizedFunctionName}LambdaPermission{normalizedTopicName}SNS
| - **Schedule**: HelloLambdaPermissionEventsRuleSchedule1
- **S3**: HelloLambdaPermissionBucketS3
- **APIG**: HelloLambdaPermissionApiGateway
- **SNS**: HelloLambdaPermissionTopicSNS
|
+|Lambda::Permission | - **Schedule**: {normalizedFunctionName}LambdaPermissionEventsRuleSchedule{index}
- **S3**: {normalizedFunctionName}LambdaPermission{normalizedBucketName}S3
- **APIG**: {normalizedFunctionName}LambdaPermissionApiGateway
- **SNS**: {normalizedFunctionName}LambdaPermission{normalizedTopicName}SNS
- **Alexa**: {normalizedFunctionName}LambdaPermissionAlexa
| - **Schedule**: HelloLambdaPermissionEventsRuleSchedule1
- **S3**: HelloLambdaPermissionBucketS3
- **APIG**: HelloLambdaPermissionApiGateway
- **SNS**: HelloLambdaPermissionTopicSNS
- **Alexa**: HelloLambdaPermissionAlexa
|
|Events::Rule | {normalizedFuntionName}EventsRuleSchedule{SequentialID} | HelloEventsRuleSchedule1 |
|ApiGateway::RestApi | ApiGatewayRestApi | ApiGatewayRestApi |
|ApiGateway::Resource | ApiGatewayResource{normalizedPath} | ApiGatewayResourceUsers |
diff --git a/lib/plugins/Plugins.json b/lib/plugins/Plugins.json
index 0199a86c2..8d373d54a 100644
--- a/lib/plugins/Plugins.json
+++ b/lib/plugins/Plugins.json
@@ -27,6 +27,7 @@
"./aws/deploy/compile/events/apiGateway/index.js",
"./aws/deploy/compile/events/sns/index.js",
"./aws/deploy/compile/events/stream/index.js",
+ "./aws/deploy/compile/events/alexa/index.js",
"./aws/deployFunction/index.js",
"./aws/deployList/index.js",
"./aws/invokeLocal/index.js"
diff --git a/lib/plugins/aws/deploy/compile/events/alexa/index.js b/lib/plugins/aws/deploy/compile/events/alexa/index.js
new file mode 100644
index 000000000..7915b93cf
--- /dev/null
+++ b/lib/plugins/aws/deploy/compile/events/alexa/index.js
@@ -0,0 +1,64 @@
+'use strict';
+
+const _ = require('lodash');
+
+class AwsCompileAlexaEvents {
+ constructor(serverless) {
+ this.serverless = serverless;
+ this.provider = this.serverless.getProvider('aws');
+
+ this.hooks = {
+ 'deploy:compileEvents': this.compileAlexaEvents.bind(this),
+ };
+ }
+
+ compileAlexaEvents() {
+ this.serverless.service.getAllFunctions().forEach((functionName) => {
+ const functionObj = this.serverless.service.getFunction(functionName);
+
+ if (functionObj.events) {
+ functionObj.events.forEach(event => {
+ if (event.alexa) {
+ if (typeof event.alexa === 'boolean' && event.alexa) {
+ const lambdaLogicalId = this.provider.naming
+ .getLambdaLogicalId(functionName);
+
+ const permissionTemplate = {
+ Type: 'AWS::Lambda::Permission',
+ Properties: {
+ FunctionName: {
+ 'Fn::GetAtt': [
+ lambdaLogicalId,
+ 'Arn',
+ ],
+ },
+ Action: 'lambda:InvokeFunction',
+ Principal: 'alexa-appkit.amazon.com',
+ },
+ };
+
+ const lambdaPermissionLogicalId = this.provider.naming
+ .getLambdaAlexaPermissionLogicalId(functionName);
+
+ const permissionCloudForamtionResource = {
+ [lambdaPermissionLogicalId]: permissionTemplate,
+ };
+
+ _.merge(this.serverless.service.provider.compiledCloudFormationTemplate.Resources,
+ permissionCloudForamtionResource);
+ } else {
+ const errorMessage = [
+ `Alexa event of function "${functionName}" is not a boolean.`,
+ ' The correct syntax is: alexa: true.',
+ ' Please check the docs for more info.',
+ ].join('');
+ throw new this.serverless.classes.Error(errorMessage);
+ }
+ }
+ });
+ }
+ });
+ }
+}
+
+module.exports = AwsCompileAlexaEvents;
diff --git a/lib/plugins/aws/deploy/compile/events/alexa/index.test.js b/lib/plugins/aws/deploy/compile/events/alexa/index.test.js
new file mode 100644
index 000000000..9b22770fd
--- /dev/null
+++ b/lib/plugins/aws/deploy/compile/events/alexa/index.test.js
@@ -0,0 +1,105 @@
+'use strict';
+
+const expect = require('chai').expect;
+const AwsProvider = require('../../../../provider/awsProvider');
+const AwsCompileAlexaEvents = require('./index');
+const Serverless = require('../../../../../../Serverless');
+
+describe('AwsCompileAlexaEvents', () => {
+ let serverless;
+ let awsCompileAlexaEvents;
+
+ beforeEach(() => {
+ serverless = new Serverless();
+ serverless.service.provider.compiledCloudFormationTemplate = { Resources: {} };
+ serverless.setProvider('aws', new AwsProvider(serverless));
+ awsCompileAlexaEvents = new AwsCompileAlexaEvents(serverless);
+ });
+
+ describe('#constructor()', () => {
+ it('should set the provider variable to an instance of AwsProvider', () =>
+ expect(awsCompileAlexaEvents.provider).to.be.instanceof(AwsProvider));
+
+ it('should should hook into the "deploy:compileEvents" hook', () =>
+ expect(awsCompileAlexaEvents.hooks['deploy:compileEvents']).to.not.equal(undefined));
+ });
+
+ describe('#compileAlexaEvents()', () => {
+ it('should throw an error if alexa event type is not a boolean', () => {
+ awsCompileAlexaEvents.serverless.service.functions = {
+ first: {
+ events: [
+ {
+ alexa: 42,
+ },
+ ],
+ },
+ };
+
+ expect(() => awsCompileAlexaEvents.compileAlexaEvents()).to.throw(Error);
+ });
+
+ it('should create corresponding resources when event is given with value "true"', () => {
+ awsCompileAlexaEvents.serverless.service.functions = {
+ first: {
+ events: [
+ {
+ alexa: true,
+ },
+ ],
+ },
+ };
+
+ awsCompileAlexaEvents.compileAlexaEvents();
+
+ expect(awsCompileAlexaEvents.serverless.service
+ .provider.compiledCloudFormationTemplate.Resources
+ .FirstLambdaPermissionAlexa.Type
+ ).to.equal('AWS::Lambda::Permission');
+ expect(awsCompileAlexaEvents.serverless.service
+ .provider.compiledCloudFormationTemplate.Resources
+ .FirstLambdaPermissionAlexa.Properties.FunctionName
+ ).to.deep.equal({ 'Fn::GetAtt': ['FirstLambdaFunction', 'Arn'] });
+ expect(awsCompileAlexaEvents.serverless.service
+ .provider.compiledCloudFormationTemplate.Resources
+ .FirstLambdaPermissionAlexa.Properties.Action
+ ).to.equal('lambda:InvokeFunction');
+ expect(awsCompileAlexaEvents.serverless.service
+ .provider.compiledCloudFormationTemplate.Resources
+ .FirstLambdaPermissionAlexa.Properties.Principal
+ ).to.equal('alexa-appkit.amazon.com');
+ });
+
+ it('should not create corresponding resources when event is given with value "false"', () => {
+ awsCompileAlexaEvents.serverless.service.functions = {
+ first: {
+ events: [
+ {
+ alexa: false,
+ },
+ ],
+ },
+ };
+
+ awsCompileAlexaEvents.compileAlexaEvents();
+
+ expect(
+ awsCompileAlexaEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources
+ ).to.deep.equal({});
+ });
+
+ it('should not create corresponding resources when alexa event is not given', () => {
+ awsCompileAlexaEvents.serverless.service.functions = {
+ first: {
+ events: [],
+ },
+ };
+
+ awsCompileAlexaEvents.compileAlexaEvents();
+
+ expect(
+ awsCompileAlexaEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources
+ ).to.deep.equal({});
+ });
+ });
+});
diff --git a/lib/plugins/aws/lib/naming.js b/lib/plugins/aws/lib/naming.js
index afb5785b1..e8c060eca 100644
--- a/lib/plugins/aws/lib/naming.js
+++ b/lib/plugins/aws/lib/naming.js
@@ -227,4 +227,7 @@ module.exports = {
getLambdaApiGatewayPermissionLogicalId(functionName) {
return `${this.getNormalizedFunctionName(functionName)}LambdaPermissionApiGateway`;
},
+ getLambdaAlexaPermissionLogicalId(functionName) {
+ return `${this.getNormalizedFunctionName(functionName)}LambdaPermissionAlexa`;
+ },
};
diff --git a/lib/plugins/aws/lib/naming.test.js b/lib/plugins/aws/lib/naming.test.js
index dc06f850c..32a72c8c5 100644
--- a/lib/plugins/aws/lib/naming.test.js
+++ b/lib/plugins/aws/lib/naming.test.js
@@ -429,4 +429,12 @@ describe('#naming()', () => {
.to.equal('FunctionNameLambdaPermissionApiGateway');
});
});
+
+ describe('#getLambdaAlexaPermissionLogicalId()', () => {
+ it('should normalize the function name and append the standard suffix',
+ () => {
+ expect(sdk.naming.getLambdaAlexaPermissionLogicalId('functionName'))
+ .to.equal('FunctionNameLambdaPermissionAlexa');
+ });
+ });
});