diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 000000000..49dafa5e2
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,10 @@
+root = true ; top-most EditorConfig file
+
+; Unix-style newlines with a newline ending every file
+[*]
+charset = utf-8
+end_of_line = lf
+indent_size = 2
+indent_style = space
+insert_final_newline = true
+trim_trailing_whitespace = true
diff --git a/.eslintignore b/.eslintignore
index c14374115..412a51062 100644
--- a/.eslintignore
+++ b/.eslintignore
@@ -1,3 +1,4 @@
coverage
node_modules
tmp
+tmpdirs-serverless
diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md
index 92171cc87..4c321d3fd 100644
--- a/.github/PULL_REQUEST_TEMPLATE.md
+++ b/.github/PULL_REQUEST_TEMPLATE.md
@@ -1,8 +1,7 @@
## What did you implement:
@@ -23,8 +22,14 @@ If this is a nontrivial change please briefly describe your implementation so it
@@ -35,4 +40,7 @@ or AWS CLI commands to trigger something.
- [ ] Fix linting errors
- [ ] Make sure code coverage hasn't dropped
- [ ] Provide verification config/commands/resources
-- [ ] Leave a comment that this is ready for review once you've finished the implementation
+- [ ] Change ready for review message below
+
+
+***Is this ready for review?:*** NO
diff --git a/.gitignore b/.gitignore
index 9e1a89e4c..168ae1112 100755
--- a/.gitignore
+++ b/.gitignore
@@ -27,15 +27,17 @@ build/Release
# https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git
node_modules
-#IDE Stuff
+# IDE stuff
**/.idea
-#OS STUFF
+# OS stuff
.DS_Store
.tmp
-#SERVERLESS STUFF
+# Serverless stuff
admin.env
.env
tmp
.coveralls.yml
+tracking-id
+tmpdirs-serverless
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 08c2f8a9d..6f90de2d6 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -30,7 +30,7 @@ Please follow these Issue guidelines for opening Issues:
* Make sure your Issue is for a *feature request*, *bug report*, or *a discussion about a relevant topic*. For everything else, please use our [Discourse Forum](http://forum.serverless.com)
### Code Style
-We aim for clean, consistent code style. We're using ESlint to check for codestyle issues using the Airbnb preset. If ESlint issues are found our build will fail and we can't merge the PR.
+We aim for clean, consistent code style. We're using ESlint to check for codestyle issues using the Airbnb preset. If ESlint issues are found our build will fail and we can't merge the PR. To help reduce the effort of creating contributions with this style, an [.editorconfig file](http://editorconfig.org/) is provided that your editor may use to override any conflicting global defaults and automate a subset of the style settings. You may need to enable EditorConfig's use by changing a setting or installing a plugin. Using it is not compulsory.
Please follow these Code Style guidelines when writing your unit tests:
* In the root of our repo, use this command to check for styling issues: `npm run lint`
diff --git a/LICENSE.txt b/LICENSE.txt
index af47480d4..c63fb03f7 100644
--- a/LICENSE.txt
+++ b/LICENSE.txt
@@ -1,6 +1,6 @@
The MIT License (MIT)
-Copyright (c) 2015 Serverless, Inc. http://www.serverless.com
+Copyright (c) 2016 Serverless, Inc. http://www.serverless.com
The following license applies to all parts of this software except as
documented below:
diff --git a/README.md b/README.md
index b6e23beb2..175e63856 100755
--- a/README.md
+++ b/README.md
@@ -10,8 +10,6 @@ Serverless is an MIT open-source project, actively maintained by a full-time, ve
## Links
-
-
* [Guide to Serverless](./docs/01-guide/README.md)
* [Features](#features)
* [Documentation v.1](./docs/README.md) / [v.0](http://serverless.readme.io)
@@ -54,6 +52,31 @@ Check out our in-depth [Guide to Serverless](./docs/01-guide/README.md) for more
* An ecosystem of serverless services and plugins.
* A passionate and welcoming community!
+## Plugins (V1.0)
+
+Use these plugins to overwrite or extend the Framework's functionality...
+
+* [serverless-webpack](https://github.com/elastic-coders/serverless-webpack) - Bundle your lambdas with Webpack
+* [serverless-alexa-plugin](https://github.com/rajington/serverless-alexa-plugin) - Support Alexa Lambda events
+* [serverless-run-function](https://github.com/lithin/serverless-run-function-plugin) - Run functions locally
+* [serverless-plugin-write-env-vars](https://github.com/silvermine/serverless-plugin-write-env-vars)
+* [serverless-plugin-multiple-responses](https://github.com/silvermine/serverless-plugin-multiple-responses)
+* [serverless-build](https://github.com/nfour/serverless-build-plugin)
+* [serverless-scriptable](https://github.com/wei-xu-myob/serverless-scriptable-plugin)
+* [serverless-plugin-stage-variables](https://github.com/svdgraaf/serverless-plugin-stage-variables)
+
+## Services & Projects (V1.0)
+
+Pre-written functions you can use instantly and example implementations...
+
+* [serverless-examples](https://github.com/andymac4182/serverless_example)
+* [serverless-npm-registry](https://github.com/craftship/yith)
+* [serverless-pokego](https://github.com/jch254/pokego-serverless)
+* [serverless-pocket-app](https://github.com/s0enke/weekly2pocket)
+* [serverless-quotebot](https://github.com/pmuens/quotebot)
+* [serverless-slackbot](https://github.com/conveyal/trevorbot)
+* [serverless-garden-aid](https://github.com/garden-aid/web-bff)
+
## Contributing
We love our contributors! Please read our [Contributing Document](CONTRIBUTING.md) to learn how you can start working on the Framework yourself.
@@ -121,7 +144,7 @@ Below are projects and plugins relating to version 0.5 and below. Note that thes
You can read the v0.5.x documentation at [readme.io](https://serverless.readme.io/v0.5.0/docs).
-## v0.5.x Projects
+## Projects (v0.5.x)
Serverless Projects are shareable and installable. You can publish them to npm and install them via the Serverless Framework CLI by using `$ serverless project install `
* [serverless-graphql](https://github.com/serverless/serverless-graphql) - Official Serverless boilerplate to kick start your project
* [serverless-starter](https://github.com/serverless/serverless-starter) - A simple boilerplate for new projects (JavaScript) with a few architectural options
@@ -131,7 +154,7 @@ Serverless Projects are shareable and installable. You can publish them to npm
* [sc5-serverless-boilerplate](https://github.com/SC5/sc5-serverless-boilerplate) - A boilerplate for test driven development of REST endpoints
* [MoonMail] (https://github.com/microapps/MoonMail) - Build your own email marketing infrastructure using Lambda + SES
-## v0.5.x Plugins
+## Plugins (v0.5.x)
Serverless is composed of Plugins. A group of default Plugins ship with the Framework, and here are some others you can add to improve/help your workflow:
* [Meta Sync](https://github.com/serverless/serverless-meta-sync) - Securely sync your the variables in your project's `_meta/variables` across your team.
* [Offline](https://github.com/dherault/serverless-offline) - Emulate AWS Lambda and Api Gateway locally to speed up your development cycles.
diff --git a/docker-compose.yml b/docker-compose.yml
index 57fb343d4..8bc80df0e 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -1,7 +1,7 @@
version: '2'
services:
serverless-node:
- image: node:5.11.1
+ image: node:latest
working_dir: /app
volumes:
- .:/app
@@ -28,3 +28,7 @@ services:
image: qlik/gradle
volumes:
- ./tmp/serverless-integration-test-aws-java-gradle:/app
+ aws-scala-sbt:
+ image: hseeberger/scala-sbt
+ volumes:
+ - ./tmp/serverless-integration-test-aws-scala-sbt:/app
diff --git a/docs/01-guide/02-creating-services.md b/docs/01-guide/02-creating-services.md
index 9ea066723..eca5a3b16 100644
--- a/docs/01-guide/02-creating-services.md
+++ b/docs/01-guide/02-creating-services.md
@@ -12,10 +12,10 @@ You can create a service based on a specific template that specifies which provi
To create a service with a `nodejs` runtime running on `aws` just pass the `aws-nodejs` template to the create command:
```
-serverless create --template aws-nodejs
+serverless create --template aws-nodejs --name my-special-service
```
-This will create a service and generate `serverless.yml` and `handler.js` files in the current working directory.
+This will create a service and generate `serverless.yml`, `handler.js` and `event.json` files in the current working directory and set the name of the service to `my-special-service` in `serverless.yml`.
You can also check out the [create command docs](../03-cli-reference/01-create.md) for all the details and options.
diff --git a/docs/01-guide/05-event-sources.md b/docs/01-guide/05-event-sources.md
index 0c6800d3a..5d4922b7b 100644
--- a/docs/01-guide/05-event-sources.md
+++ b/docs/01-guide/05-event-sources.md
@@ -68,7 +68,7 @@ You've successfully executed the function through the HTTP endpoint!
Serverless provides more than just a HTTP event source. You can find the full list of all available event sources with
corresponding examples in the provider specific docs:
-* [AWS event documentation](../02-providers/aws/events/README.md).
+* [AWS event documentation](../02-providers/aws/events/).
## Conclusion
diff --git a/docs/01-guide/07-removing-services.md b/docs/01-guide/07-removing-services.md
index 8995ec38d..18aee6d95 100644
--- a/docs/01-guide/07-removing-services.md
+++ b/docs/01-guide/07-removing-services.md
@@ -21,8 +21,8 @@ We've just removed the whole service from our provider with a simple `serverless
## What's next?
-You can either dive deeper into our [Advanced Guides](./README.md#advanced-guides) or read through the provider specific documentation we provide:
+You can either dive deeper into our [Advanced Guides](./#advanced-guides) or read through the provider specific documentation we provide:
-* [AWS Documentation](../02-providers/aws/README.md)
+* [AWS Documentation](../02-providers/aws/)
Have fun with building your Serverless services and if you have feedback on the please let us know in [our Forum](forum.serverless.com) or [open an Issue in our Github repository](https://github.com/serverless/serverless/issues/new) for any bugs you might encounter or if you have an idea for a new feature.
diff --git a/docs/01-guide/09-installing-plugins.md b/docs/01-guide/09-installing-plugins.md
index d6ecd98be..f7372c91e 100644
--- a/docs/01-guide/09-installing-plugins.md
+++ b/docs/01-guide/09-installing-plugins.md
@@ -52,5 +52,3 @@ plugins:
```
In this case `plugin1` is loaded before `plugin2`.
-
-[Next step > Removing your service](removing-a-service.md)
diff --git a/docs/02-providers/aws/04-resource-names-reference.md b/docs/02-providers/aws/04-resource-names-reference.md
index c2ff1f148..efa6d6f05 100644
--- a/docs/02-providers/aws/04-resource-names-reference.md
+++ b/docs/02-providers/aws/04-resource-names-reference.md
@@ -28,7 +28,7 @@ We're also using the term `normalizedName` or similar terms in this guide. This
|Lambda::Permission | - **Schedule**: {normalizedFunctionName}LambdaPermissionEventsRuleSchedule{index}
- **S3**: {normalizedFunctionName}LambdaPermissionS3
- **APIG**: {normalizedFunctionName}LambdaPermissionApiGateway
- **SNS**: {normalizedFunctionName}LambdaPermission{normalizedTopicName}
| - **Schedule**: HelloLambdaPermissionEventsRuleSchedule1
- **S3**: HelloLambdaPermissionS3
- **APIG**: HelloLambdaPermissionApiGateway
- **SNS**: HelloLambdaPermissionSometopic
|
|Events::Rule | {normalizedFuntionName}EventsRuleSchedule{SequentialID} | HelloEventsRuleSchedule1 |
|ApiGateway::RestApi | ApiGatewayRestApi | ApiGatewayRestApi |
-|ApiGateway::Resource | ApiGatewayResource{normalizedPath} | ApiGatewayResourceUsers |
+|ApiGateway::Resource | ApiGatewayResource{normalizedPath} | - ApiGatewayResourceUsers
- ApiGatewayResourceUsers**Var** for paths containing a variable
- ApiGatewayResource**Dash** if the path is just a `-`
|
|ApiGateway::Method | ApiGatewayResource{normalizedPath}{normalizedMethod} | ApiGatewayResourceUsersGet |
|ApiGateway::Authorizer | {normalizedFunctionName}ApiGatewayAuthorizer | HelloApiGatewayAuthorizer |
|ApiGateway::Deployment | ApiGatewayDeployment{randomNumber} | ApiGatewayDeployment12356789 |
diff --git a/docs/02-providers/aws/README.md b/docs/02-providers/aws/README.md
index 43e9fa7e1..7472a0796 100644
--- a/docs/02-providers/aws/README.md
+++ b/docs/02-providers/aws/README.md
@@ -6,7 +6,7 @@ layout: Doc
# Serverless AWS Documentation
-Check out the [Getting started guide](../../01-guide/README.md) and the [CLI reference](../../03-cli-reference/README.md) for an introduction to Serverless.
+Check out the [Getting started guide](../../01-guide/) and the [CLI reference](../../03-cli-reference/) for an introduction to Serverless.
## Setup and configuration
diff --git a/docs/02-providers/aws/events/01-apigateway.md b/docs/02-providers/aws/events/01-apigateway.md
index 94c9818ba..d6912a598 100644
--- a/docs/02-providers/aws/events/01-apigateway.md
+++ b/docs/02-providers/aws/events/01-apigateway.md
@@ -71,12 +71,34 @@ functions:
path: whatever
request:
template:
- text/xhtml: { "stage" : "$context.stage" }
- application/json: { "httpMethod" : "$context.httpMethod" }
+ text/xhtml: '{ "stage" : "$context.stage" }'
+ application/json: '{ "httpMethod" : "$context.httpMethod" }'
```
**Note:** The templates are defined as plain text here. However you can also reference an external file with the help of the `${file(templatefile)}` syntax.
+**Note 2:** In .yml, strings containing `:`, `{`, `}`, `[`, `]`, `,`, `&`, `*`, `#`, `?`, `|`, `-`, `<`, `>`, `=`, `!`, `%`, `@`, `` ` `` must be quoted.
+
+If you want to map querystrings to the event object, you can use the `$input.params('hub.challenge')` syntax from API Gateway, as follows:
+
+```yml
+functions:
+ create:
+ handler: posts.create
+ events:
+ - http:
+ method: get
+ path: whatever
+ request:
+ template:
+ application/json: '{ "foo" : "$input.params(''bar'')" }'
+```
+
+**Note:** Notice when using single-quoted strings, any single quote `'` inside its contents must be doubled (`''`) to escape it.
+You can then access the query string `https://example.com/dev/whatever?bar=123` by `event.foo` in the lambda function.
+If you want to spread a string into multiple lines, you can use the `>` or `|` syntax, but the following strings have to be all indented with the same amount, [read more about `>` syntax](http://stackoverflow.com/questions/3790454/in-yaml-how-do-i-break-a-string-over-multiple-lines).
+
+
### Pass Through Behavior
API Gateway provides multiple ways to handle requests where the Content-Type header does not match any of the specified mapping templates. When this happens, the request payload will either be passed through the integration request *without transformation* or rejected with a `415 - Unsupported Media Type`, depending on the configuration.
@@ -356,14 +378,14 @@ resources:
- RootResourceId
PathPart: serverless # the endpoint in your API that is set as proxy
RestApiId:
- Ref: RestApiApigEvent
+ Ref: ApiGatewayRestApi
ProxyMethod:
- ResourceId:
- Ref: ProxyResource
- RestApiId:
- Ref: RestApiApigEvent
Type: AWS::ApiGateway::Method
Properties:
+ ResourceId:
+ Ref: ProxyResource
+ RestApiId:
+ Ref: ApiGatewayRestApi
HttpMethod: GET # the method of your proxy. Is it GET or POST or ... ?
MethodResponses:
- StatusCode: 200
diff --git a/docs/02-providers/aws/events/02-s3.md b/docs/02-providers/aws/events/02-s3.md
index 6f8352ed4..45b54e8f4 100644
--- a/docs/02-providers/aws/events/02-s3.md
+++ b/docs/02-providers/aws/events/02-s3.md
@@ -32,6 +32,23 @@ functions:
event: s3:ObjectRemoved:*
```
+## Setting filter rules
+
+This will create a bucket `photos`. The `users` function is called whenever an image with `.jpg` extension is uploaded to folder `uploads` in the bucket. Check out the [AWS documentation](http://docs.aws.amazon.com/AmazonS3/latest/dev/NotificationHowTo.html#notification-how-to-filtering) to learn more about all the different filter types that can be configured.
+
+```yml
+functions:
+ users:
+ handler: users.handler
+ rules:
+ - s3:
+ bucket: photos
+ event: s3:ObjectCreated:*
+ rules:
+ - prefix: uploads/
+ - suffix: .jpg
+```
+
## Triggering separate functions from the same bucket
You're able to repeat the S3 event configuration in the same or separate functions so one bucket can call these functions. One caveat though is that you can't repeat the same configuration in both functions, e.g. the event type has to be different.
diff --git a/docs/03-cli-reference/01-create.md b/docs/03-cli-reference/01-create.md
index 389d928aa..aa5b2410c 100644
--- a/docs/03-cli-reference/01-create.md
+++ b/docs/03-cli-reference/01-create.md
@@ -16,6 +16,7 @@ serverless create --template aws-nodejs
## Options
- `--template` or `-t` The name of your new service. **Required**.
- `--path` or `-p` The path where the service should be created.
+- `--name` or `-n` the name of the service in `serverless.yml`.
## Provided lifecycle events
- `create:create`
@@ -36,7 +37,7 @@ Most commonly used templates:
### Creating a new service
```
-serverless create --template aws-nodejs
+serverless create --template aws-nodejs --name my-special-service
```
This example will generate scaffolding for a service with `AWS` as a provider and `nodejs` as runtime. The scaffolding
diff --git a/docs/03-cli-reference/05-info.md b/docs/03-cli-reference/05-info.md
index 0975bdb07..e8e97af66 100644
--- a/docs/03-cli-reference/05-info.md
+++ b/docs/03-cli-reference/05-info.md
@@ -24,7 +24,8 @@ serverless info
### AWS
-On AWS the info plugin uses the `Outputs` section of the CloudFormation stack. Outputs will include Lambda function ARN's, a `ServiceEndpoint` for the API Gateway endpoint and user provided custom Outputs.
+On AWS the info plugin uses the `Outputs` section of the CloudFormation stack and the AWS SDK to gather the necessary information.
+See the example below for an example output.
**Example:**
@@ -35,6 +36,8 @@ Service Information
service: my-serverless-service
stage: dev
region: us-east-1
+api keys:
+ myKey: some123valid456api789key1011for1213api1415gateway
endpoints:
GET - https://dxaynpuzd4.execute-api.us-east-1.amazonaws.com/dev/users
functions:
diff --git a/docs/README.md b/docs/README.md
index 343a2532d..47a190cb3 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -8,15 +8,15 @@ layout: Doc
Welcome to the Serverless documentation.
-- [Quick Start Guide](./01-guide/README.md)
+- [Quick Start Guide](./01-guide/)
- [Core Concepts](#concepts)
-- [CLI Reference](./03-cli-reference/README.md)
+- [CLI Reference](./03-cli-reference/)
- [Providers](#providers)
-- [Extending Serverless](./04-extending-serverless/README.md)
+- [Extending Serverless](./04-extending-serverless/)
- [Contributing to Serverless](#contributing)
## Providers
-- [AWS Integration Docs](./02-providers/aws/README.md)
+- [AWS Integration Docs](./02-providers/aws/)
## Concepts
@@ -38,7 +38,7 @@ Serverless is used to build event driven architecture. Basically everything whic
Events could be HTTP requests, events fired from a cloud storage (like a S3 bucket), scheduled events, etc.
-- [AWS Events](./02-providers/aws/events/README.md)
+- [AWS Events](./02-providers/aws/events/)
### Resources
@@ -52,7 +52,7 @@ A *Serverless service* is a group of one or multiple functions and any resources
Here you can read how to develop your own Serverless plugins. We'll get into details on how to write custom plugins to extend the functionality of Serverless. Furthermore we'll look into the way how you can use your plugin knowledge to integrate your own provider into the Serverless framework.
-- [Building plugins](./04-extending-serverless/README.md)
+- [Building plugins](./04-extending-serverless/)
Connect with the community on [gitter](https://gitter.im/serverless/serverless) or in the [Forum](http://forum.serverless.com)
diff --git a/docs/usage-tracking.md b/docs/usage-tracking.md
index fd679ba28..b200679d9 100644
--- a/docs/usage-tracking.md
+++ b/docs/usage-tracking.md
@@ -14,16 +14,7 @@ of our users to improve Serverless in future releases. However you can always [d
Our main goal is anonymity while tracking usage behavior. All the data is anonymized and won't reveal who you are or what
the project you're working on is / looks like.
-Here's a list about all the information we track:
-- Entered command(s)
-- Operating system
-- Loaded plugins
-- Serverless version
-- Number of functions inside your service
-- Total number of events inside your service
-- Event types and how often they were defined
-- Provider of your service
-- If the command was executed inside a service
+Please take a look at the [`track()` method](../lib/classes/Utils.js) in the `Utils` class to see what (and how) we track.
## How tracking is implemented
diff --git a/lib/classes/Service.js b/lib/classes/Service.js
index 24b506303..7756e9a93 100644
--- a/lib/classes/Service.js
+++ b/lib/classes/Service.js
@@ -69,10 +69,11 @@ class Service {
};
}
- if (['aws', 'azure', 'google', 'ibm'].indexOf(serverlessFile.provider.name)) {
+ const providers = ['aws', 'azure', 'google', 'ibm'];
+ if (providers.indexOf(serverlessFile.provider.name) === -1) {
const errorMessage = [
`Provider "${serverlessFile.provider.name}" is not supported.`,
- ' Valid values for provider are: aws, azure, google, ibm.',
+ ` Valid values for provider are: ${providers.join(', ')}.`,
' Please provide one of those values to the "provider" property in serverless.yml.',
].join('');
throw new SError(errorMessage);
diff --git a/lib/classes/Utils.js b/lib/classes/Utils.js
index b03055cf5..23e97be8e 100644
--- a/lib/classes/Utils.js
+++ b/lib/classes/Utils.js
@@ -7,6 +7,7 @@ const BbPromise = require('bluebird');
const fse = BbPromise.promisifyAll(require('fs-extra'));
const _ = require('lodash');
const fetch = require('node-fetch');
+const uuid = require('uuid');
class Utils {
constructor(serverless) {
@@ -76,7 +77,7 @@ class Utils {
if (filePath.endsWith('.json')) {
contents = JSON.parse(contents);
} else if (filePath.endsWith('.yml') || filePath.endsWith('.yaml')) {
- contents = YAML.load(contents.toString());
+ contents = YAML.load(contents.toString(), { filename: filePath });
} else {
contents = contents.toString().trim();
}
@@ -144,47 +145,138 @@ class Utils {
track(serverless) {
const writeKey = 'XXXX'; // TODO: Replace me before release
- const loadedPlugins = serverless.pluginManager.plugins.map((plugin) => plugin.constructor.name);
- const events = [];
+ let userId = uuid.v1();
- if (serverless.service && serverless.service.functions) {
+ // create a new file with a uuid as the tracking id if not yet present
+ const trackingIdFilePath = path.join(serverless.config.serverlessPath, 'tracking-id');
+ if (!this.fileExistsSync(trackingIdFilePath)) {
+ fs.writeFileSync(trackingIdFilePath, userId);
+ } else {
+ userId = fs.readFileSync(trackingIdFilePath).toString();
+ }
+
+ // function related information retrieval
+ const numberOfFunctions = _.size(serverless.service.functions);
+
+ const memorySizeAndTimeoutPerFunction = [];
+ if (numberOfFunctions) {
+ _.forEach(serverless.service.functions, (func) => {
+ const memorySize = Number(func.memorySize)
+ || Number(this.serverless.service.provider.memorySize)
+ || 1024;
+ const timeout = Number(func.timeout)
+ || Number(this.serverless.service.provider.timeout)
+ || 6;
+
+ const memorySizeAndTimeoutObject = {
+ memorySize,
+ timeout,
+ };
+
+ memorySizeAndTimeoutPerFunction.push(memorySizeAndTimeoutObject);
+ });
+ }
+
+ // event related information retrieval
+ const numberOfEventsPerType = [];
+ const eventNamesPerFunction = [];
+ if (numberOfFunctions) {
_.forEach(serverless.service.functions, (func) => {
if (func.events) {
+ const funcEventsArray = [];
+
func.events.forEach((event) => {
const name = Object.keys(event)[0];
- const alreadyPresentEvent = _.find(events, { name });
+ funcEventsArray.push(name);
+
+ const alreadyPresentEvent = _.find(numberOfEventsPerType, { name });
if (alreadyPresentEvent) {
alreadyPresentEvent.count++;
} else {
- events.push({
+ numberOfEventsPerType.push({
name,
count: 1,
});
}
});
+
+ eventNamesPerFunction.push(funcEventsArray);
}
});
}
+ let hasCustomResourcesDefined = false;
+ // check if configuration in resources.Resources is defined
+ if ((serverless.service.resources &&
+ serverless.service.resources.Resources &&
+ Object.keys(serverless.service.resources.Resources).length)) {
+ hasCustomResourcesDefined = true;
+ }
+ // check if configuration in resources.Outputs is defined
+ if ((serverless.service.resources &&
+ serverless.service.resources.Outputs &&
+ Object.keys(serverless.service.resources.Outputs).length)) {
+ hasCustomResourcesDefined = true;
+ }
+
+ let hasCustomVariableSyntaxDefined = false;
+ const defaultVariableSyntax = '\\${([ :a-zA-Z0-9._,\\-\\/\\(\\)]+?)}';
+ // check if the variableSyntax in the defaults section is defined
+ if (serverless.service.defaults &&
+ serverless.service.defaults.variableSyntax &&
+ serverless.service.defaults.variableSyntax !== defaultVariableSyntax) {
+ hasCustomVariableSyntaxDefined = true;
+ }
+ // check if the variableSyntax in the provider section is defined
+ if (serverless.service.provider &&
+ serverless.service.provider.variableSyntax &&
+ serverless.service.provider.variableSyntax !== defaultVariableSyntax) {
+ hasCustomVariableSyntaxDefined = true;
+ }
+
const auth = `${writeKey}:`;
const data = {
- userId: 'anonymousUser',
+ userId,
event: 'Serverless framework usage',
properties: {
- commands: serverless.processedInput.commands,
- operatingSystem: process.platform,
- loadedPlugins,
- serverlessVersion: serverless.version,
- numberOfFunctions: _.size(serverless.service.functions),
- events,
- totalNumberOfEvents: events.length,
- provider: serverless.service.provider,
- isRunInsideService: (!!serverless.config.servicePath),
+ command: {
+ name: serverless.processedInput.commands.join(' '),
+ isRunInService: (!!serverless.config.servicePath),
+ },
+ service: {
+ numberOfCustomPlugins: _.size(serverless.service.plugins),
+ hasCustomResourcesDefined,
+ hasVariablesInCustomSectionDefined: (!!serverless.service.custom),
+ hasCustomVariableSyntaxDefined,
+ },
+ provider: {
+ name: serverless.service.provider.name,
+ runtime: serverless.service.provider.runtime,
+ stage: serverless.service.provider.stage,
+ region: serverless.service.provider.region,
+ },
+ functions: {
+ numberOfFunctions,
+ memorySizeAndTimeoutPerFunction,
+ },
+ events: {
+ numberOfEvents: numberOfEventsPerType.length,
+ numberOfEventsPerType,
+ eventNamesPerFunction,
+ },
+ general: {
+ userId,
+ timestamp: (new Date()).getTime(),
+ timezone: (new Date()).toString().match(/([A-Z]+[\+-][0-9]+.*)/)[1],
+ operatingSystem: process.platform,
+ serverlessVersion: serverless.version,
+ nodeJsVersion: process.version,
+ },
},
};
- fetch('https://api.segment.io/v1/track', {
+ return fetch('https://api.segment.io/v1/track', {
headers: {
Authorization: `Basic ${new Buffer(auth).toString('base64')}`,
'content-type': 'application/json',
diff --git a/lib/plugins/aws/deploy/compile/events/apiGateway/index.js b/lib/plugins/aws/deploy/compile/events/apiGateway/index.js
index b2b8e2188..ccf2db649 100644
--- a/lib/plugins/aws/deploy/compile/events/apiGateway/index.js
+++ b/lib/plugins/aws/deploy/compile/events/apiGateway/index.js
@@ -36,7 +36,8 @@ class AwsCompileApigEvents {
_.forEach(this.serverless.service.functions, functionObj => {
if (functionObj.events) {
functionObj.events.forEach(event => {
- if (event.http) noEndpoints = false;
+ // Allow events with empty http event to validate function
+ if ({}.hasOwnProperty.call(event, 'http')) noEndpoints = false;
});
}
});
diff --git a/lib/plugins/aws/deploy/compile/events/apiGateway/lib/apiKeys.js b/lib/plugins/aws/deploy/compile/events/apiGateway/lib/apiKeys.js
index 6e8dfa161..31dae6232 100644
--- a/lib/plugins/aws/deploy/compile/events/apiGateway/lib/apiKeys.js
+++ b/lib/plugins/aws/deploy/compile/events/apiGateway/lib/apiKeys.js
@@ -38,21 +38,6 @@ module.exports = {
_.merge(this.serverless.service.provider.compiledCloudFormationTemplate.Resources,
newApiKeyObject);
-
- // Add API Key to Outputs section
- const newOutput = {
- Description: apiKey,
- Value: {
- Ref: `ApiGatewayApiKey${apiKeyNumber}`,
- },
- };
-
- const newOutputObject = {
- [`ApiGatewayApiKey${apiKeyNumber}Value`]: newOutput,
- };
-
- _.merge(this.serverless.service.provider.compiledCloudFormationTemplate.Outputs,
- newOutputObject);
});
}
diff --git a/lib/plugins/aws/deploy/compile/events/apiGateway/lib/methods.js b/lib/plugins/aws/deploy/compile/events/apiGateway/lib/methods.js
index 521925868..e150d834e 100644
--- a/lib/plugins/aws/deploy/compile/events/apiGateway/lib/methods.js
+++ b/lib/plugins/aws/deploy/compile/events/apiGateway/lib/methods.js
@@ -333,7 +333,8 @@ module.exports = {
{ StatusCode: 422, SelectionPattern: '.*\\[422\\].*' },
{ StatusCode: 500,
SelectionPattern:
- '.*(Process\\s?exited\\s?before\\s?completing\\s?request|\\[500\\]).*' },
+ // eslint-disable-next-line max-len
+ '.*(Process\\s?exited\\s?before\\s?completing\\s?request|Task\\s?timed\\s?out\\s?|\\[500\\]).*' },
{ StatusCode: 502, SelectionPattern: '.*\\[502\\].*' },
{ StatusCode: 504, SelectionPattern: '.*\\[504\\].*' }
);
diff --git a/lib/plugins/aws/deploy/compile/events/apiGateway/lib/resources.js b/lib/plugins/aws/deploy/compile/events/apiGateway/lib/resources.js
index 7a55ed267..1892be7fb 100644
--- a/lib/plugins/aws/deploy/compile/events/apiGateway/lib/resources.js
+++ b/lib/plugins/aws/deploy/compile/events/apiGateway/lib/resources.js
@@ -43,7 +43,12 @@ module.exports = {
});
});
- const capitalizeAlphaNumericPath = (path) => _.capitalize(path.replace(/[^0-9A-Za-z]/g, ''));
+ const capitalizeAlphaNumericPath = (path) => _.upperFirst(
+ _.capitalize(path)
+ .replace(/-/g, 'Dash')
+ .replace(/\{(.*)\}/g, '$1Var')
+ .replace(/[^0-9A-Za-z]/g, '')
+ );
// ['users', 'users/create', 'users/create/something']
this.resourcePaths.forEach(path => {
diff --git a/lib/plugins/aws/deploy/compile/events/apiGateway/lib/validate.js b/lib/plugins/aws/deploy/compile/events/apiGateway/lib/validate.js
index 249fdc952..cfc608ec4 100644
--- a/lib/plugins/aws/deploy/compile/events/apiGateway/lib/validate.js
+++ b/lib/plugins/aws/deploy/compile/events/apiGateway/lib/validate.js
@@ -8,15 +8,26 @@ module.exports = {
// validate that path and method exists for each http event in service
_.forEach(this.serverless.service.functions, (functionObject, functionName) => {
functionObject.events.forEach(event => {
- if (event.http) {
+ if ({}.hasOwnProperty.call(event, 'http')) {
let method;
let path;
+ if (!event.http) {
+ const errorMessage = [
+ `Empty http event in function "${functionName}"`,
+ ' in serverless.yml.',
+ ' If you define an http event, make sure you pass a valid value for it,',
+ ' either as string syntax, or object syntax.',
+ ' Please check the docs for more options.',
+ ].join('');
+ throw new this.serverless.classes.Error(errorMessage);
+ }
+
if (typeof event.http === 'object') {
- method = event.http.method.toLowerCase();
+ method = event.http.method;
path = event.http.path;
} else if (typeof event.http === 'string') {
- method = event.http.split(' ')[0].toLowerCase();
+ method = event.http.split(' ')[0];
path = event.http.split(' ')[1];
}
@@ -43,11 +54,15 @@ module.exports = {
throw new this.serverless.classes
.Error(errorMessage);
}
+ method = method.toLowerCase();
- if (['get', 'post', 'put', 'patch', 'options', 'head', 'delete'].indexOf(method) === -1) {
+ const allowedMethods = [
+ 'get', 'post', 'put', 'patch', 'options', 'head', 'delete', 'any',
+ ];
+ if (allowedMethods.indexOf(method) === -1) {
const errorMessage = [
`Invalid APIG method "${method}" in function "${functionName}".`,
- ' AWS supported methods are: get, post, put, patch, options, head, delete.',
+ ` AWS supported methods are: ${allowedMethods.join(', ')}.`,
].join('');
throw new this.serverless.classes.Error(errorMessage);
}
diff --git a/lib/plugins/aws/deploy/compile/events/apiGateway/tests/apiKeys.js b/lib/plugins/aws/deploy/compile/events/apiGateway/tests/apiKeys.js
index fc721e7e4..94e7ce305 100644
--- a/lib/plugins/aws/deploy/compile/events/apiGateway/tests/apiKeys.js
+++ b/lib/plugins/aws/deploy/compile/events/apiGateway/tests/apiKeys.js
@@ -82,20 +82,6 @@ describe('#compileApiKeys()', () => {
})
);
- it('should add api keys cf output template', () => awsCompileApigEvents
- .compileApiKeys().then(() => {
- expect(
- awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate
- .Outputs.ApiGatewayApiKey1Value.Description
- ).to.equal('1234567890');
-
- expect(
- awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate
- .Outputs.ApiGatewayApiKey1Value.Value.Ref
- ).to.equal('ApiGatewayApiKey1');
- })
- );
-
it('throw error if apiKey property is not an array', () => {
awsCompileApigEvents.serverless.service.provider.apiKeys = 2;
expect(() => awsCompileApigEvents.compileApiKeys()).to.throw(Error);
diff --git a/lib/plugins/aws/deploy/compile/events/apiGateway/tests/methods.js b/lib/plugins/aws/deploy/compile/events/apiGateway/tests/methods.js
index 563eb5053..ab169a668 100644
--- a/lib/plugins/aws/deploy/compile/events/apiGateway/tests/methods.js
+++ b/lib/plugins/aws/deploy/compile/events/apiGateway/tests/methods.js
@@ -665,7 +665,9 @@ describe('#compileMethods()', () => {
awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate
.Resources.ApiGatewayMethodUsersListGet.Properties.Integration.IntegrationResponses[6]
).to.deep.equal({ StatusCode: 500,
- SelectionPattern: '.*(Process\\s?exited\\s?before\\s?completing\\s?request|\\[500\\]).*' });
+ SelectionPattern:
+ // eslint-disable-next-line max-len
+ '.*(Process\\s?exited\\s?before\\s?completing\\s?request|Task\\s?timed\\s?out\\s?|\\[500\\]).*' });
expect(
awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate
.Resources.ApiGatewayMethodUsersListGet.Properties.Integration.IntegrationResponses[7]
diff --git a/lib/plugins/aws/deploy/compile/events/apiGateway/tests/resources.js b/lib/plugins/aws/deploy/compile/events/apiGateway/tests/resources.js
index 450dfcf16..08ff6fe1c 100644
--- a/lib/plugins/aws/deploy/compile/events/apiGateway/tests/resources.js
+++ b/lib/plugins/aws/deploy/compile/events/apiGateway/tests/resources.js
@@ -21,6 +21,9 @@ describe('#compileResources()', () => {
method: 'POST',
},
},
+ {
+ http: 'GET bar/-',
+ },
{
http: 'GET bar/foo',
},
@@ -57,8 +60,17 @@ describe('#compileResources()', () => {
it('should construct the correct resourcePaths array', () => awsCompileApigEvents
.compileResources().then(() => {
- const expectedResourcePaths = ['foo/bar', 'foo', 'bar/foo', 'bar', 'bar/{id}',
- 'bar/{id}/foobar', 'bar/{foo_id}', 'bar/{foo_id}/foobar'];
+ const expectedResourcePaths = [
+ 'foo/bar',
+ 'foo',
+ 'bar/-',
+ 'bar',
+ 'bar/foo',
+ 'bar/{id}',
+ 'bar/{id}/foobar',
+ 'bar/{foo_id}',
+ 'bar/{foo_id}/foobar',
+ ];
expect(awsCompileApigEvents.resourcePaths).to.deep.equal(expectedResourcePaths);
})
);
@@ -66,12 +78,13 @@ describe('#compileResources()', () => {
it('should construct the correct resourceLogicalIds object', () => awsCompileApigEvents
.compileResources().then(() => {
const expectedResourceLogicalIds = {
+ 'bar/-': 'ApiGatewayResourceBarDash',
'foo/bar': 'ApiGatewayResourceFooBar',
foo: 'ApiGatewayResourceFoo',
- 'bar/{id}/foobar': 'ApiGatewayResourceBarIdFoobar',
- 'bar/{id}': 'ApiGatewayResourceBarId',
- 'bar/{foo_id}/foobar': 'ApiGatewayResourceBarFooidFoobar',
- 'bar/{foo_id}': 'ApiGatewayResourceBarFooid',
+ 'bar/{id}/foobar': 'ApiGatewayResourceBarIdVarFoobar',
+ 'bar/{id}': 'ApiGatewayResourceBarIdVar',
+ 'bar/{foo_id}/foobar': 'ApiGatewayResourceBarFooidVarFoobar',
+ 'bar/{foo_id}': 'ApiGatewayResourceBarFooidVar',
'bar/foo': 'ApiGatewayResourceBarFoo',
bar: 'ApiGatewayResourceBar',
};
@@ -94,14 +107,14 @@ describe('#compileResources()', () => {
.Resources.ApiGatewayResourceBar.Properties.ParentId['Fn::GetAtt'][1])
.to.equal('RootResourceId');
expect(awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate
- .Resources.ApiGatewayResourceBarId.Properties.ParentId.Ref)
+ .Resources.ApiGatewayResourceBarIdVar.Properties.ParentId.Ref)
.to.equal('ApiGatewayResourceBar');
expect(awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate
- .Resources.ApiGatewayResourceBarFooid.Properties.ParentId.Ref)
+ .Resources.ApiGatewayResourceBarFooidVar.Properties.ParentId.Ref)
.to.equal('ApiGatewayResourceBar');
expect(awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate
- .Resources.ApiGatewayResourceBarFooidFoobar.Properties.ParentId.Ref)
- .to.equal('ApiGatewayResourceBarFooid');
+ .Resources.ApiGatewayResourceBarFooidVarFoobar.Properties.ParentId.Ref)
+ .to.equal('ApiGatewayResourceBarFooidVar');
})
);
diff --git a/lib/plugins/aws/deploy/compile/events/apiGateway/tests/validate.js b/lib/plugins/aws/deploy/compile/events/apiGateway/tests/validate.js
index 179b590cf..a6df3229f 100644
--- a/lib/plugins/aws/deploy/compile/events/apiGateway/tests/validate.js
+++ b/lib/plugins/aws/deploy/compile/events/apiGateway/tests/validate.js
@@ -30,6 +30,19 @@ describe('#validate()', () => {
};
});
+ it('should reject an empty http event', () => {
+ awsCompileApigEvents.serverless.service.functions = {
+ first: {
+ events: [
+ {
+ http: null,
+ },
+ ],
+ },
+ };
+ expect(() => awsCompileApigEvents.validate()).to.throw(Error);
+ });
+
it('should validate the http events "path" property', () => {
awsCompileApigEvents.serverless.service.functions = {
first: {
diff --git a/lib/plugins/aws/deploy/compile/events/s3/README.md b/lib/plugins/aws/deploy/compile/events/s3/README.md
index c86575600..002e6cb1c 100644
--- a/lib/plugins/aws/deploy/compile/events/s3/README.md
+++ b/lib/plugins/aws/deploy/compile/events/s3/README.md
@@ -58,3 +58,19 @@ functions:
bucket: confidential-information
event: s3:ObjectRemoved:*
```
+
+We can also specify filter rules.
+
+```yml
+# serverless.yml
+functions:
+ mail:
+ handler: mail.removal
+ events:
+ - s3:
+ bucket: confidential-information
+ event: s3:ObjectRemoved:*
+ rules:
+ - prefix: inbox/
+ - suffix: .eml
+```
\ No newline at end of file
diff --git a/lib/plugins/aws/deploy/compile/events/s3/index.js b/lib/plugins/aws/deploy/compile/events/s3/index.js
index 4deb9ea71..fc849c786 100644
--- a/lib/plugins/aws/deploy/compile/events/s3/index.js
+++ b/lib/plugins/aws/deploy/compile/events/s3/index.js
@@ -23,6 +23,7 @@ class AwsCompileS3Events {
if (event.s3) {
let bucketName;
let notificationEvent = 's3:ObjectCreated:*';
+ let filter = {};
if (typeof event.s3 === 'object') {
if (!event.s3.bucket) {
@@ -38,6 +39,33 @@ class AwsCompileS3Events {
if (event.s3.event) {
notificationEvent = event.s3.event;
}
+ if (event.s3.rules) {
+ if (!_.isArray(event.s3.rules)) {
+ const errorMessage = [
+ `S3 filter rules of function ${functionName} is not an array`,
+ ' The correct syntax is: rules: [{ Name: Value }]',
+ ' Please check the docs for more info.',
+ ].join('');
+ throw new this.serverless.classes
+ .Error(errorMessage);
+ }
+ const rules = [];
+ event.s3.rules.forEach(rule => {
+ if (!_.isPlainObject(rule)) {
+ const errorMessage = [
+ `S3 filter rule ${rule} of function ${functionName} is not an object`,
+ ' The correct syntax is: { Name: Value }',
+ ' Please check the docs for more info.',
+ ].join('');
+ throw new this.serverless.classes
+ .Error(errorMessage);
+ }
+ const name = Object.keys(rule)[0];
+ const value = rule[name];
+ rules.push({ Name: name, Value: value });
+ });
+ filter = { Filter: { S3Key: { Rules: rules } } };
+ }
} else if (typeof event.s3 === 'string') {
bucketName = event.s3;
} else {
@@ -56,7 +84,7 @@ class AwsCompileS3Events {
// check if the bucket already defined
// in another S3 event in the service
if (bucketsLambdaConfigurations[bucketName]) {
- const newLambdaConfiguration = {
+ let newLambdaConfiguration = {
Event: notificationEvent,
Function: {
'Fn::GetAtt': [
@@ -66,6 +94,11 @@ class AwsCompileS3Events {
},
};
+ // Assign 'filter' if not empty
+ newLambdaConfiguration = _.assign(
+ newLambdaConfiguration,
+ filter
+ );
bucketsLambdaConfigurations[bucketName]
.push(newLambdaConfiguration);
} else {
@@ -80,6 +113,11 @@ class AwsCompileS3Events {
},
},
];
+ // Assign 'filter' if not empty
+ bucketsLambdaConfigurations[bucketName][0] = _.assign(
+ bucketsLambdaConfigurations[bucketName][0],
+ filter
+ );
}
s3EnabledFunctions.push(functionName);
}
diff --git a/lib/plugins/aws/deploy/compile/events/s3/tests/index.js b/lib/plugins/aws/deploy/compile/events/s3/tests/index.js
index 13e8ded85..9c504d946 100644
--- a/lib/plugins/aws/deploy/compile/events/s3/tests/index.js
+++ b/lib/plugins/aws/deploy/compile/events/s3/tests/index.js
@@ -51,6 +51,42 @@ describe('AwsCompileS3Events', () => {
expect(() => awsCompileS3Events.compileS3Events()).to.throw(Error);
});
+ it('should throw an error if the "rules" property is not an array', () => {
+ awsCompileS3Events.serverless.service.functions = {
+ first: {
+ events: [
+ {
+ s3: {
+ bucket: 'first-function-bucket',
+ event: 's3:ObjectCreated:Put',
+ rules: {},
+ },
+ },
+ ],
+ },
+ };
+
+ expect(() => awsCompileS3Events.compileS3Events()).to.throw(Error);
+ });
+
+ it('should throw an error if the "rules" property is invalid', () => {
+ awsCompileS3Events.serverless.service.functions = {
+ first: {
+ events: [
+ {
+ s3: {
+ bucket: 'first-function-bucket',
+ event: 's3:ObjectCreated:Put',
+ rules: [[]],
+ },
+ },
+ ],
+ },
+ };
+
+ expect(() => awsCompileS3Events.compileS3Events()).to.throw(Error);
+ });
+
it('should create corresponding resources when S3 events are given', () => {
awsCompileS3Events.serverless.service.functions = {
first: {
@@ -62,6 +98,9 @@ describe('AwsCompileS3Events', () => {
s3: {
bucket: 'first-function-bucket-two',
event: 's3:ObjectCreated:Put',
+ rules: [
+ { prefix: 'subfolder/' },
+ ],
},
},
],
@@ -79,6 +118,11 @@ describe('AwsCompileS3Events', () => {
expect(awsCompileS3Events.serverless.service.provider.compiledCloudFormationTemplate
.Resources.FirstLambdaPermissionS3.Type
).to.equal('AWS::Lambda::Permission');
+ expect(awsCompileS3Events.serverless.service.provider.compiledCloudFormationTemplate
+ .Resources.S3BucketFirstfunctionbuckettwo.Properties.NotificationConfiguration
+ .LambdaConfigurations[0].Filter).to.deep.equal({
+ S3Key: { Rules: [{ Name: 'prefix', Value: 'subfolder/' }] },
+ });
});
it('should create single bucket resource when the same bucket referenced repeatedly', () => {
@@ -92,6 +136,9 @@ describe('AwsCompileS3Events', () => {
s3: {
bucket: 'first-function-bucket-one',
event: 's3:ObjectCreated:Put',
+ rules: [
+ { prefix: 'subfolder/' },
+ ],
},
},
],
diff --git a/lib/plugins/aws/deploy/lib/createStack.js b/lib/plugins/aws/deploy/lib/createStack.js
index c6bc58c1b..5a8b29da8 100644
--- a/lib/plugins/aws/deploy/lib/createStack.js
+++ b/lib/plugins/aws/deploy/lib/createStack.js
@@ -10,7 +10,7 @@ module.exports = {
const params = {
StackName: stackName,
- OnFailure: 'DELETE',
+ OnFailure: 'ROLLBACK',
Capabilities: [
'CAPABILITY_IAM',
],
@@ -32,6 +32,16 @@ module.exports = {
createStack() {
const stackName = `${this.serverless.service.service}-${this.options.stage}`;
+ if (/^[^a-zA-Z].+|.*[^a-zA-Z0-9\-].*/.test(stackName) || stackName.length > 128) {
+ const errorMessage = [
+ `The stack service name "${stackName}" is not valid. `,
+ 'A service name should only contain alphanumeric',
+ ' (case sensitive) and hyphens. It should start',
+ ' with an alphabetic character and shouldn\'t',
+ ' exceed 128 characters.',
+ ].join('');
+ throw new this.serverless.classes.Error(errorMessage);
+ }
return BbPromise.bind(this)
// always write the template to disk, whether we are deploying or not
diff --git a/lib/plugins/aws/deploy/tests/uploadArtifacts.js b/lib/plugins/aws/deploy/tests/uploadArtifacts.js
index 94a25f613..2bd1f70a6 100644
--- a/lib/plugins/aws/deploy/tests/uploadArtifacts.js
+++ b/lib/plugins/aws/deploy/tests/uploadArtifacts.js
@@ -1,12 +1,12 @@
'use strict';
const sinon = require('sinon');
-const os = require('os');
const path = require('path');
const BbPromise = require('bluebird');
const expect = require('chai').expect;
const AwsDeploy = require('../index');
const Serverless = require('../../../../Serverless');
+const testUtils = require('../../../../../tests/utils');
describe('uploadArtifacts', () => {
let serverless;
@@ -61,7 +61,7 @@ describe('uploadArtifacts', () => {
});
it('should upload the .zip file to the S3 bucket', () => {
- const tmpDirPath = path.join(os.tmpdir(), (new Date()).getTime().toString());
+ const tmpDirPath = testUtils.getTmpDirPath();
const artifactFilePath = path.join(tmpDirPath, 'artifact.zip');
serverless.utils.writeFileSync(artifactFilePath, 'artifact.zip file content');
diff --git a/lib/plugins/aws/info/index.js b/lib/plugins/aws/info/index.js
index 26efcf3cc..dbbf6da54 100644
--- a/lib/plugins/aws/info/index.js
+++ b/lib/plugins/aws/info/index.js
@@ -51,12 +51,13 @@ class AwsInfo {
this.options.stage,
this.options.region)
.then((result) => {
+ let outputs;
+
if (result) {
- const outputs = result.Stacks[0].Outputs;
+ outputs = result.Stacks[0].Outputs;
// Functions
info.functions = [];
- info.apiKeys = [];
outputs.filter(x => x.OutputKey.match(/LambdaFunctionArn$/))
.forEach(x => {
const functionInfo = {};
@@ -71,21 +72,23 @@ class AwsInfo {
info.endpoint = x.OutputValue;
});
- // API Keys
- outputs.filter(x => x.OutputKey.match(/^ApiGatewayApiKey/))
- .forEach(x => {
- const apiKeyInfo = {};
- apiKeyInfo.name = x.Description;
- apiKeyInfo.value = x.OutputValue;
- info.apiKeys.push(apiKeyInfo);
- });
-
// Resources
info.resources = [];
+
+ // API Keys
+ info.apiKeys = [];
}
- return BbPromise.resolve(info);
+ // create a gatheredData object which can be passed around ("[call] by reference")
+ const gatheredData = {
+ outputs,
+ info,
+ };
+
+ return BbPromise.resolve(gatheredData);
})
+ .then((gatheredData) => this.getApiKeyValues(gatheredData))
+ .then((gatheredData) => BbPromise.resolve(gatheredData.info)) // resolve the info at the end
.catch((e) => {
let result;
@@ -102,6 +105,38 @@ class AwsInfo {
});
}
+ getApiKeyValues(gatheredData) {
+ const info = gatheredData.info;
+
+ // check if the user has set api keys
+ const apiKeyNames = this.serverless.service.provider.apiKeys || [];
+
+ if (apiKeyNames.length) {
+ return this.sdk.request('APIGateway',
+ 'getApiKeys',
+ { includeValues: true },
+ this.options.stage,
+ this.options.region
+ ).then((allApiKeys) => {
+ const items = allApiKeys.items;
+ if (items) {
+ // filter out the API keys only created for this stack
+ const filteredItems = items.filter((item) => _.includes(apiKeyNames, item.name));
+
+ // iterate over all apiKeys and push the API key info and update the info object
+ filteredItems.forEach((item) => {
+ const apiKeyInfo = {};
+ apiKeyInfo.name = item.name;
+ apiKeyInfo.value = item.value;
+ info.apiKeys.push(apiKeyInfo);
+ });
+ }
+ return BbPromise.resolve(gatheredData);
+ });
+ }
+ return BbPromise.resolve(gatheredData);
+ }
+
/**
* Display service information
*/
diff --git a/lib/plugins/aws/info/tests/index.js b/lib/plugins/aws/info/tests/index.js
index fc7844823..871cc261b 100644
--- a/lib/plugins/aws/info/tests/index.js
+++ b/lib/plugins/aws/info/tests/index.js
@@ -163,7 +163,7 @@ describe('AwsInfo', () => {
it('should gather with correct params', () => awsInfo.gather()
.then(() => {
- expect(describeStackStub.calledOnce).to.equal(true);
+ expect(describeStackStub.called).to.equal(true);
expect(describeStackStub.args[0][0]).to.equal('CloudFormation');
expect(describeStackStub.args[0][1]).to.equal('describeStacks');
expect(describeStackStub.args[0][2].StackName)
@@ -218,23 +218,6 @@ describe('AwsInfo', () => {
});
});
- it('should get api keys', () => {
- const expectedApiKeys = [
- {
- name: 'first',
- value: 'xxx',
- },
- {
- name: 'second',
- value: 'yyy',
- },
- ];
-
- return awsInfo.gather().then((info) => {
- expect(info.apiKeys).to.deep.equal(expectedApiKeys);
- });
- });
-
it("should provide only general info when stack doesn't exist (ValidationError)", () => {
awsInfo.sdk.request.restore();
@@ -267,6 +250,85 @@ describe('AwsInfo', () => {
});
});
+ describe('#getApiKeyValues()', () => {
+ it('should return the api keys in the info object', () => {
+ // TODO: implement a pattern for stub restoring to get rid of this
+ awsInfo.sdk.request.restore();
+
+ // set the API Keys for the service
+ awsInfo.serverless.service.provider.apiKeys = ['foo', 'bar'];
+
+ const gatheredData = {
+ outputs: [],
+ info: {
+ apiKeys: [],
+ },
+ };
+
+ const apiKeyItems = {
+ items: [
+ {
+ id: '4711',
+ name: 'SomeRandomIdInUsersAccount',
+ value: 'ShouldNotBeConsidered',
+ },
+ {
+ id: '1234',
+ name: 'foo',
+ value: 'valueForKeyFoo',
+ },
+ {
+ id: '5678',
+ name: 'bar',
+ value: 'valueForKeyBar',
+ },
+ ],
+ };
+
+ const gatheredDataAfterKeyLookup = {
+ info: {
+ apiKeys: [
+ { name: 'foo', value: 'valueForKeyFoo' },
+ { name: 'bar', value: 'valueForKeyBar' },
+ ],
+ },
+ };
+
+ const getApiKeysStub = sinon
+ .stub(awsInfo.sdk, 'request')
+ .returns(BbPromise.resolve(apiKeyItems));
+
+ return awsInfo.getApiKeyValues(gatheredData).then((result) => {
+ expect(getApiKeysStub.calledOnce).to.equal(true);
+ expect(result.info.apiKeys).to.deep.equal(gatheredDataAfterKeyLookup.info.apiKeys);
+
+ awsInfo.sdk.request.restore();
+ });
+ });
+
+ it('should resolve with the passed-in data if no API key retrieval is necessary', () => {
+ awsInfo.serverless.service.provider.apiKeys = null;
+
+ const gatheredData = {
+ outputs: [],
+ info: {
+ apiKeys: [],
+ },
+ };
+
+ const getApiKeysStub = sinon
+ .stub(awsInfo.sdk, 'request')
+ .returns(BbPromise.resolve());
+
+ return awsInfo.getApiKeyValues(gatheredData).then((result) => {
+ expect(getApiKeysStub.calledOnce).to.equal(false);
+ expect(result).to.deep.equal(gatheredData);
+
+ awsInfo.sdk.request.restore();
+ });
+ });
+ });
+
describe('#display()', () => {
it('should format information message correctly', () => {
serverless.cli = new CLI(serverless);
diff --git a/lib/plugins/aws/invoke/tests/index.js b/lib/plugins/aws/invoke/tests/index.js
index 87881b167..0229844c8 100644
--- a/lib/plugins/aws/invoke/tests/index.js
+++ b/lib/plugins/aws/invoke/tests/index.js
@@ -93,6 +93,23 @@ describe('AwsInvoke', () => {
});
});
+ it('it should parse a yaml file if file path is provided', () => {
+ serverless.config.servicePath = testUtils.getTmpDirPath();
+ const yamlContent = 'testProp: testValue';
+
+ serverless.utils.writeFileSync(path
+ .join(serverless.config.servicePath, 'data.yml'), yamlContent);
+ awsInvoke.options.path = 'data.yml';
+
+ return awsInvoke.extendedValidate().then(() => {
+ expect(awsInvoke.options.data).to.deep.equal({
+ testProp: 'testValue',
+ });
+ awsInvoke.options.path = false;
+ serverless.config.servicePath = true;
+ });
+ });
+
it('it should throw error if service path is not set', () => {
serverless.config.servicePath = false;
expect(() => awsInvoke.extendedValidate()).to.throw(Error);
diff --git a/lib/plugins/create/create.js b/lib/plugins/create/create.js
index 4d77a7c3c..d92a78f02 100644
--- a/lib/plugins/create/create.js
+++ b/lib/plugins/create/create.js
@@ -10,6 +10,7 @@ const validTemplates = [
'aws-python',
'aws-java-maven',
'aws-java-gradle',
+ 'aws-scala-sbt',
];
const humanReadableTemplateList = `${validTemplates.slice(0, -1)
@@ -36,6 +37,10 @@ class Create {
usage: 'The path where the service should be created (e.g. --path my-service)',
shortcut: 'p',
},
+ name: {
+ usage: 'Name for the service. Overwrites the default name of the created service.',
+ shortcut: 'n',
+ },
},
},
};
@@ -57,8 +62,9 @@ class Create {
throw new this.serverless.classes.Error(errorMessage);
}
- // store the path option for the service if given
+ // store the custom options for the service if given
const servicePath = this.options.path && this.options.path.length ? this.options.path : null;
+ const serviceName = this.options.name && this.options.name.length ? this.options.name : null;
// create (if not yet present) and chdir into the directory for the service
if (servicePath) {
@@ -78,8 +84,8 @@ class Create {
'plugins', 'create', 'templates', this.options.template), this.serverless.config.servicePath);
// rename the service if the user has provided a path via options
- if (servicePath) {
- const serviceName = servicePath.split(path.sep).pop();
+ if (servicePath || serviceName) {
+ const newServiceName = serviceName || servicePath.split(path.sep).pop();
const serverlessYmlFilePath = path
.join(this.serverless.config.servicePath, 'serverless.yml');
@@ -87,7 +93,7 @@ class Create {
.readFileSync(serverlessYmlFilePath).toString();
serverlessYmlFileContent = serverlessYmlFileContent
- .replace(/service: .+/, `service: ${serviceName}`);
+ .replace(/service: .+/, `service: ${newServiceName}`);
fse.writeFileSync(serverlessYmlFilePath, serverlessYmlFileContent);
}
diff --git a/lib/plugins/create/templates/aws-python/handler.py b/lib/plugins/create/templates/aws-python/handler.py
index d06142357..fc62ea2d0 100644
--- a/lib/plugins/create/templates/aws-python/handler.py
+++ b/lib/plugins/create/templates/aws-python/handler.py
@@ -1,2 +1,5 @@
def hello(event, context):
- return { "message": "Go Serverless v1.0! Your function executed successfully!", "event": event }
+ return {
+ "message": "Go Serverless v1.0! Your function executed successfully!",
+ "event": event
+ }
diff --git a/lib/plugins/create/templates/aws-scala-sbt/build.sbt b/lib/plugins/create/templates/aws-scala-sbt/build.sbt
new file mode 100644
index 000000000..17cb52aa7
--- /dev/null
+++ b/lib/plugins/create/templates/aws-scala-sbt/build.sbt
@@ -0,0 +1,21 @@
+import sbt.Keys._
+import sbt._
+import sbtrelease.Version
+
+name := "hello"
+
+resolvers += Resolver.sonatypeRepo("public")
+scalaVersion := "2.11.8"
+releaseNextVersion := { ver => Version(ver).map(_.bumpMinor.string).getOrElse("Error") }
+assemblyJarName in assembly := "hello.jar"
+
+libraryDependencies ++= Seq(
+ "com.amazonaws" % "aws-lambda-java-events" % "1.1.0",
+ "com.amazonaws" % "aws-lambda-java-core" % "1.1.0"
+)
+
+scalacOptions ++= Seq(
+ "-unchecked",
+ "-deprecation",
+ "-feature",
+ "-Xfatal-warnings")
diff --git a/lib/plugins/create/templates/aws-scala-sbt/event.json b/lib/plugins/create/templates/aws-scala-sbt/event.json
new file mode 100644
index 000000000..2ac50a459
--- /dev/null
+++ b/lib/plugins/create/templates/aws-scala-sbt/event.json
@@ -0,0 +1,5 @@
+{
+ "key3": "value3",
+ "key2": "value2",
+ "key1": "value1"
+}
diff --git a/lib/plugins/create/templates/aws-scala-sbt/project/assembly.sbt b/lib/plugins/create/templates/aws-scala-sbt/project/assembly.sbt
new file mode 100644
index 000000000..5b7f17a1c
--- /dev/null
+++ b/lib/plugins/create/templates/aws-scala-sbt/project/assembly.sbt
@@ -0,0 +1,3 @@
+resolvers += Resolver.sonatypeRepo("public")
+
+addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.14.0")
\ No newline at end of file
diff --git a/lib/plugins/create/templates/aws-scala-sbt/project/plugins.sbt b/lib/plugins/create/templates/aws-scala-sbt/project/plugins.sbt
new file mode 100644
index 000000000..dd405c41f
--- /dev/null
+++ b/lib/plugins/create/templates/aws-scala-sbt/project/plugins.sbt
@@ -0,0 +1 @@
+addSbtPlugin("com.github.gseitz" % "sbt-release" % "1.0.0")
\ No newline at end of file
diff --git a/lib/plugins/create/templates/aws-scala-sbt/serverless.yml b/lib/plugins/create/templates/aws-scala-sbt/serverless.yml
new file mode 100644
index 000000000..ca47838b1
--- /dev/null
+++ b/lib/plugins/create/templates/aws-scala-sbt/serverless.yml
@@ -0,0 +1,70 @@
+# Welcome to Serverless!
+#
+# This file is the main config file for your service.
+# It's very minimal at this point and uses default values.
+# You can always add more config options for more control.
+# We've included some commented out config examples here.
+# Just uncomment any of them to get that config option.
+#
+# For full config options, check the docs:
+# docs.serverless.com
+#
+# Happy Coding!
+
+service: aws-scala-sbt # NOTE: update this with your service name
+
+provider:
+ name: aws
+ runtime: java8
+
+# you can overwrite defaults here
+# stage: dev
+# region: us-east-1
+
+# you can add statements to the Lambda function's IAM Role here
+# iamRoleStatements:
+# - Effect: "Allow"
+# Action:
+# - "s3:ListBucket"
+# Resource: { "Fn::Join" : ["", ["arn:aws:s3:::", { "Ref" : "ServerlessDeploymentBucket" } ] ] }
+# - Effect: "Allow"
+# Action:
+# - "s3:PutObject"
+# Resource:
+# Fn::Join:
+# - ""
+# - - "arn:aws:s3:::"
+# - "Ref" : "ServerlessDeploymentBucket"
+
+# you can add packaging information here
+package:
+# include:
+# - include-me.java
+# exclude:
+# - exclude-me.java
+ artifact: target/scala-2.11/hello.jar
+
+functions:
+ hello:
+ handler: hello.Handler
+
+# you can add any of the following events
+# events:
+# - http:
+# path: users/create
+# method: get
+# - s3: ${env:bucket}
+# - schedule: rate(10 minutes)
+# - sns: greeter-topic
+
+# you can add CloudFormation resource templates here
+#resources:
+# Resources:
+# NewResource:
+# Type: AWS::S3::Bucket
+# Properties:
+# BucketName: my-new-bucket
+# Outputs:
+# NewOutput:
+# Description: "Description for the output"
+# Value: "Some output value"
diff --git a/lib/plugins/create/templates/aws-scala-sbt/src/main/scala/hello/Handler.scala b/lib/plugins/create/templates/aws-scala-sbt/src/main/scala/hello/Handler.scala
new file mode 100644
index 000000000..ac3ac0b0f
--- /dev/null
+++ b/lib/plugins/create/templates/aws-scala-sbt/src/main/scala/hello/Handler.scala
@@ -0,0 +1,11 @@
+package hello
+
+import com.amazonaws.services.lambda.runtime.{Context, RequestHandler}
+
+class Handler extends RequestHandler[Request, Response] {
+
+ def handleRequest(input: Request, context: Context): Response = {
+ return new Response("Go Serverless v1.0! Your function executed successfully!", input)
+ }
+
+}
diff --git a/lib/plugins/create/templates/aws-scala-sbt/src/main/scala/hello/Request.scala b/lib/plugins/create/templates/aws-scala-sbt/src/main/scala/hello/Request.scala
new file mode 100644
index 000000000..1724561e7
--- /dev/null
+++ b/lib/plugins/create/templates/aws-scala-sbt/src/main/scala/hello/Request.scala
@@ -0,0 +1,7 @@
+package hello
+
+import scala.beans.BeanProperty
+
+class Request(@BeanProperty var key1: String, @BeanProperty var key2: String, @BeanProperty var key3: String) {
+ def this() = this("", "", "")
+}
diff --git a/lib/plugins/create/templates/aws-scala-sbt/src/main/scala/hello/Response.scala b/lib/plugins/create/templates/aws-scala-sbt/src/main/scala/hello/Response.scala
new file mode 100644
index 000000000..dbed76047
--- /dev/null
+++ b/lib/plugins/create/templates/aws-scala-sbt/src/main/scala/hello/Response.scala
@@ -0,0 +1,5 @@
+package hello
+
+import scala.beans.BeanProperty
+
+case class Response(@BeanProperty message: String, @BeanProperty request: Request)
diff --git a/lib/plugins/create/tests/create.js b/lib/plugins/create/tests/create.js
index 908207067..6f6af52ee 100644
--- a/lib/plugins/create/tests/create.js
+++ b/lib/plugins/create/tests/create.js
@@ -48,6 +48,23 @@ describe('Create', () => {
expect(() => create.create()).to.throw(Error);
});
+ it('should overwrite the name for the service if user passed name', () => {
+ const cwd = process.cwd();
+ fse.mkdirsSync(tmpDir);
+ process.chdir(tmpDir);
+ create.options.template = 'aws-nodejs';
+ create.options.name = 'my_service';
+
+ return create.create().then(() =>
+ create.serverless.yamlParser.parse(
+ path.join(tmpDir, 'serverless.yml')
+ ).then((obj) => {
+ expect(obj.service).to.equal('my_service');
+ process.chdir(cwd);
+ })
+ );
+ });
+
it('should set servicePath based on cwd', () => {
const cwd = process.cwd();
fse.mkdirsSync(tmpDir);
@@ -157,6 +174,36 @@ describe('Create', () => {
});
});
+ it('should generate scaffolding for "aws-scala-sbt" template', () => {
+ const cwd = process.cwd();
+ fse.mkdirsSync(tmpDir);
+ process.chdir(tmpDir);
+ create.options.template = 'aws-scala-sbt';
+
+ return create.create().then(() => {
+ expect(create.serverless.utils.fileExistsSync(path.join(tmpDir, 'serverless.yml')))
+ .to.be.equal(true);
+ expect(create.serverless.utils.fileExistsSync(path.join(tmpDir, 'event.json')))
+ .to.be.equal(true);
+ expect(create.serverless.utils.fileExistsSync(path.join(tmpDir, 'build.sbt')))
+ .to.be.equal(true);
+ expect(create.serverless.utils.fileExistsSync(path.join(tmpDir, 'src', 'main', 'scala',
+ 'hello', 'Handler.scala'
+ )))
+ .to.be.equal(true);
+ expect(create.serverless.utils.fileExistsSync(path.join(tmpDir, 'src', 'main', 'scala',
+ 'hello', 'Request.scala'
+ )))
+ .to.be.equal(true);
+ expect(create.serverless.utils.fileExistsSync(path.join(tmpDir, 'src', 'main', 'scala',
+ 'hello', 'Response.scala'
+ )))
+ .to.be.equal(true);
+
+ process.chdir(cwd);
+ });
+ });
+
// this test should live here because of process.cwd() which might cause trouble when using
// nested dirs like its done here
it('should create a renamed service in the directory if using the "path" option', () => {
@@ -165,6 +212,7 @@ describe('Create', () => {
process.chdir(tmpDir);
create.options.path = 'my-new-service';
+ create.options.name = null;
// using the nodejs template (this test is completely be independent from the template)
create.options.template = 'aws-nodejs';
@@ -187,5 +235,36 @@ describe('Create', () => {
process.chdir(cwd);
});
});
+
+ it('should create a custom renamed service in the directory if using ' +
+ 'the "path" and "name" option', () => {
+ const cwd = process.cwd();
+ fse.mkdirsSync(tmpDir);
+ process.chdir(tmpDir);
+
+ create.options.path = 'my-new-service';
+ create.options.name = 'my-custom-new-service';
+
+ // using the nodejs template (this test is completely be independent from the template)
+ create.options.template = 'aws-nodejs';
+
+ return create.create().then(() => {
+ const serviceDir = path.join(tmpDir, create.options.path);
+
+ // check if files are created in the correct directory
+ expect(create.serverless.utils.fileExistsSync(
+ path.join(serviceDir, 'serverless.yml'))).to.be.equal(true);
+ expect(create.serverless.utils.fileExistsSync(
+ path.join(serviceDir, 'handler.js'))).to.be.equal(true);
+
+ // check if the service was renamed
+ const serverlessYmlfileContent = fse
+ .readFileSync(path.join(serviceDir, 'serverless.yml')).toString();
+
+ expect((/service: my-custom-new-service/).test(serverlessYmlfileContent)).to.equal(true);
+
+ process.chdir(cwd);
+ });
+ });
});
});
diff --git a/lib/plugins/package/tests/zipService.js b/lib/plugins/package/tests/zipService.js
index 6c8032a22..723bab8f2 100644
--- a/lib/plugins/package/tests/zipService.js
+++ b/lib/plugins/package/tests/zipService.js
@@ -2,6 +2,7 @@
const expect = require('chai').expect;
const fs = require('fs');
+const os = require('os');
const path = require('path');
const JsZip = require('jszip');
const _ = require('lodash');
@@ -140,13 +141,19 @@ describe('#zipService()', () => {
}).then(unzippedData => {
const unzippedFileData = unzippedData.files;
- // binary file is set with chmod of 777
- expect(unzippedFileData['bin/some-binary'].unixPermissions)
- .to.equal(Math.pow(2, 15) + 777);
+ if (os.platform() === 'win32') {
+ // chmod does not work right on windows. this is better than nothing?
+ expect(unzippedFileData['bin/some-binary'].unixPermissions)
+ .to.not.equal(unzippedFileData['bin/read-only'].unixPermissions);
+ } else {
+ // binary file is set with chmod of 777
+ expect(unzippedFileData['bin/some-binary'].unixPermissions)
+ .to.equal(Math.pow(2, 15) + 777);
- // read only file is set with chmod of 444
- expect(unzippedFileData['bin/read-only'].unixPermissions)
- .to.equal(Math.pow(2, 15) + 444);
+ // read only file is set with chmod of 444
+ expect(unzippedFileData['bin/read-only'].unixPermissions)
+ .to.equal(Math.pow(2, 15) + 444);
+ }
});
});
diff --git a/lib/plugins/tracking/tracking.js b/lib/plugins/tracking/tracking.js
index f1e0dbea7..10115bdaa 100644
--- a/lib/plugins/tracking/tracking.js
+++ b/lib/plugins/tracking/tracking.js
@@ -43,7 +43,7 @@ class Tracking {
}
if (this.options.disable && !this.options.enable) {
fs.writeFileSync(path.join(serverlessPath, trackingFileName),
- 'Keep this file to enable tracking');
+ 'Keep this file to disable tracking');
this.serverless.cli.log('Tracking successfully disabled');
}
}
diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json
index ebab1c289..04d1609e1 100644
--- a/npm-shrinkwrap.json
+++ b/npm-shrinkwrap.json
@@ -1,6 +1,6 @@
{
"name": "serverless",
- "version": "1.0.0-beta.2",
+ "version": "1.0.0-rc.2",
"dependencies": {
"abbrev": {
"version": "1.0.9",
@@ -114,6 +114,11 @@
"from": "async@>=1.5.2 <2.0.0",
"resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz"
},
+ "asynckit": {
+ "version": "0.4.0",
+ "from": "asynckit@>=0.4.0 <0.5.0",
+ "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz"
+ },
"aws-sdk": {
"version": "2.5.3",
"from": "aws-sdk@>=2.3.17 <3.0.0",
@@ -136,7 +141,7 @@
},
"bl": {
"version": "1.1.2",
- "from": "bl@>=1.0.0 <2.0.0",
+ "from": "bl@>=1.1.2 <1.2.0",
"resolved": "https://registry.npmjs.org/bl/-/bl-1.1.2.tgz",
"dependencies": {
"readable-stream": {
@@ -178,7 +183,7 @@
},
"builtin-modules": {
"version": "1.1.1",
- "from": "builtin-modules@>=1.0.0 <2.0.0",
+ "from": "builtin-modules@>=1.1.1 <2.0.0",
"resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz"
},
"caller-id": {
@@ -213,7 +218,7 @@
},
"chalk": {
"version": "1.1.3",
- "from": "chalk@>=1.1.1 <2.0.0",
+ "from": "chalk@>=1.1.0 <2.0.0",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz"
},
"circular-json": {
@@ -327,12 +332,12 @@
},
"debug": {
"version": "2.2.0",
- "from": "debug@>=2.0.0 <3.0.0",
+ "from": "debug@>=2.2.0 <3.0.0",
"resolved": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz"
},
"decamelize": {
"version": "1.2.0",
- "from": "decamelize@>=1.1.1 <2.0.0",
+ "from": "decamelize@>=1.0.0 <2.0.0",
"resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz"
},
"deep-eql": {
@@ -522,9 +527,9 @@
"resolved": "https://registry.npmjs.org/figures/-/figures-1.7.0.tgz"
},
"file-entry-cache": {
- "version": "1.3.1",
- "from": "file-entry-cache@>=1.3.1 <2.0.0",
- "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-1.3.1.tgz"
+ "version": "2.0.0",
+ "from": "file-entry-cache@>=2.0.0 <3.0.0",
+ "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-2.0.0.tgz"
},
"find-up": {
"version": "1.1.2",
@@ -558,7 +563,7 @@
},
"fs-extra": {
"version": "0.26.7",
- "from": "fs-extra@>=0.26.7 <0.27.0",
+ "from": "fs-extra@>=0.26.4 <0.27.0",
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-0.26.7.tgz"
},
"fs.realpath": {
@@ -637,7 +642,7 @@
},
"har-validator": {
"version": "2.0.6",
- "from": "har-validator@>=2.0.6 <2.1.0",
+ "from": "har-validator@>=2.0.2 <2.1.0",
"resolved": "https://registry.npmjs.org/har-validator/-/har-validator-2.0.6.tgz"
},
"has-ansi": {
@@ -652,7 +657,7 @@
},
"hawk": {
"version": "3.1.3",
- "from": "hawk@>=3.1.3 <3.2.0",
+ "from": "hawk@>=3.1.0 <3.2.0",
"resolved": "https://registry.npmjs.org/hawk/-/hawk-3.1.3.tgz"
},
"hoek": {
@@ -807,7 +812,7 @@
},
"js-yaml": {
"version": "3.6.1",
- "from": "js-yaml@>=3.6.1 <4.0.0",
+ "from": "js-yaml@>=3.5.5 <4.0.0",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.6.1.tgz"
},
"jsbn": {
@@ -890,11 +895,6 @@
"from": "lcid@>=1.0.0 <2.0.0",
"resolved": "https://registry.npmjs.org/lcid/-/lcid-1.0.0.tgz"
},
- "lcov-parse": {
- "version": "0.0.6",
- "from": "lcov-parse@0.0.6",
- "resolved": "https://registry.npmjs.org/lcov-parse/-/lcov-parse-0.0.6.tgz"
- },
"levn": {
"version": "0.3.0",
"from": "levn@>=0.3.0 <0.4.0",
@@ -985,11 +985,6 @@
"from": "lodash.keys@>=3.0.0 <4.0.0",
"resolved": "https://registry.npmjs.org/lodash.keys/-/lodash.keys-3.1.2.tgz"
},
- "log-driver": {
- "version": "1.2.4",
- "from": "log-driver@1.2.4",
- "resolved": "https://registry.npmjs.org/log-driver/-/log-driver-1.2.4.tgz"
- },
"lolex": {
"version": "1.3.2",
"from": "lolex@1.3.2",
@@ -1074,7 +1069,7 @@
},
"node-uuid": {
"version": "1.4.7",
- "from": "node-uuid@>=1.4.7 <1.5.0",
+ "from": "node-uuid@>=1.4.2 <2.0.0",
"resolved": "https://registry.npmjs.org/node-uuid/-/node-uuid-1.4.7.tgz"
},
"nopt": {
@@ -1099,12 +1094,12 @@
},
"oauth-sign": {
"version": "0.8.2",
- "from": "oauth-sign@>=0.8.1 <0.9.0",
+ "from": "oauth-sign@>=0.8.0 <0.9.0",
"resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.8.2.tgz"
},
"object-assign": {
"version": "4.1.0",
- "from": "object-assign@>=4.1.0 <5.0.0",
+ "from": "object-assign@>=4.0.1 <5.0.0",
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.0.tgz"
},
"once": {
@@ -1353,7 +1348,7 @@
},
"semver-regex": {
"version": "1.0.0",
- "from": "semver-regex@>=1.0.0 <2.0.0",
+ "from": "semver-regex@latest",
"resolved": "https://registry.npmjs.org/semver-regex/-/semver-regex-1.0.0.tgz"
},
"set-blocking": {
@@ -1425,7 +1420,7 @@
},
"stack-trace": {
"version": "0.0.9",
- "from": "stack-trace@>=0.0.7 <0.1.0",
+ "from": "stack-trace@>=0.0.0 <0.1.0",
"resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.9.tgz"
},
"string_decoder": {
@@ -1592,16 +1587,6 @@
"from": "uglify-to-browserify@>=1.0.0 <1.1.0",
"resolved": "https://registry.npmjs.org/uglify-to-browserify/-/uglify-to-browserify-1.0.2.tgz"
},
- "underscore": {
- "version": "1.7.0",
- "from": "underscore@>=1.7.0 <1.8.0",
- "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.7.0.tgz"
- },
- "underscore.string": {
- "version": "2.4.0",
- "from": "underscore.string@>=2.4.0 <2.5.0",
- "resolved": "https://registry.npmjs.org/underscore.string/-/underscore.string-2.4.0.tgz"
- },
"uri-js": {
"version": "2.1.1",
"from": "uri-js@>=2.1.1 <3.0.0",
@@ -1622,6 +1607,11 @@
"from": "util-deprecate@>=1.0.1 <1.1.0",
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz"
},
+ "uuid": {
+ "version": "2.0.2",
+ "from": "uuid@latest",
+ "resolved": "https://registry.npmjs.org/uuid/-/uuid-2.0.2.tgz"
+ },
"validate-npm-package-license": {
"version": "3.0.1",
"from": "validate-npm-package-license@>=3.0.1 <4.0.0",
@@ -1649,7 +1639,7 @@
},
"wordwrap": {
"version": "1.0.0",
- "from": "wordwrap@>=1.0.0 <1.1.0",
+ "from": "wordwrap@>=0.0.2",
"resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz"
},
"wrap-ansi": {
diff --git a/package.json b/package.json
index 79e1e71a1..bbd4eb23a 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "serverless",
- "version": "1.0.0-rc.1",
+ "version": "1.0.0-rc.2",
"engines": {
"node": ">=4.0"
},
@@ -39,7 +39,7 @@
"serverless-run-python-handler": "./bin/serverless-run-python-handler"
},
"scripts": {
- "test": "istanbul cover _mocha tests/all -- -R spec --recursive",
+ "test": "istanbul cover node_modules/mocha/bin/_mocha tests/all -- -R spec --recursive",
"lint": "eslint .",
"integration-test": "mocha tests/integration_test"
},
@@ -77,6 +77,7 @@
"replaceall": "^0.1.6",
"semver-regex": "^1.0.0",
"shelljs": "^0.6.0",
- "traverse": "^0.6.6"
+ "traverse": "^0.6.6",
+ "uuid": "^2.0.2"
}
}
diff --git a/tests/classes/CLI.js b/tests/classes/CLI.js
index 94932add9..6695353f7 100644
--- a/tests/classes/CLI.js
+++ b/tests/classes/CLI.js
@@ -6,13 +6,18 @@
const expect = require('chai').expect;
const CLI = require('../../lib/classes/CLI');
+const os = require('os');
+const fse = require('fs-extra');
+const exec = require('child_process').exec;
+const path = require('path');
const Serverless = require('../../lib/Serverless');
+const testUtils = require('../../tests/utils');
describe('CLI', () => {
let cli;
let serverless;
- beforeEach(() => {
+ beforeEach(function () { // eslint-disable-line prefer-arrow-callback
serverless = new Serverless({});
});
@@ -262,4 +267,55 @@ describe('CLI', () => {
expect(inputToBeProcessed).to.deep.equal(expectedObject);
});
});
+
+ describe('integration tests', () => {
+ before(function () {
+ const tmpDir = testUtils.getTmpDirPath();
+
+ this.cwd = process.cwd();
+
+ fse.mkdirSync(tmpDir);
+ process.chdir(tmpDir);
+
+ serverless = new Serverless();
+ serverless.init();
+
+ // Cannot rely on shebang in severless.js to invoke script using NodeJS on Windows.
+ const execPrefix = os.platform() === 'win32' ? 'node ' : '';
+
+ this.serverlessExec = execPrefix + path.join(serverless.config.serverlessPath,
+ '..', 'bin', 'serverless');
+ });
+
+ after(function () { // eslint-disable-line prefer-arrow-callback
+ process.chdir(this.cwd);
+ });
+
+ it('prints general --help to stdout', function (done) {
+ this.timeout(10000);
+ exec(`${this.serverlessExec} --help`, (err, stdout) => {
+ if (err) {
+ done(err);
+ return;
+ }
+
+ expect(stdout).to.contain('contextual help');
+ done();
+ });
+ });
+
+ it('prints command --help to stdout', function (done) {
+ this.timeout(10000);
+ exec(`${this.serverlessExec} deploy --help`, (err, stdout) => {
+ if (err) {
+ done(err);
+ return;
+ }
+
+ expect(stdout).to.contain('deploy');
+ expect(stdout).to.contain('--stage');
+ done();
+ });
+ });
+ });
});
diff --git a/tests/classes/PluginManager.js b/tests/classes/PluginManager.js
index 82c2dd830..d146f9331 100644
--- a/tests/classes/PluginManager.js
+++ b/tests/classes/PluginManager.js
@@ -10,6 +10,7 @@ const fse = require('fs-extra');
const execSync = require('child_process').execSync;
const mockRequire = require('mock-require');
const testUtils = require('../../tests/utils');
+const os = require('os');
describe('PluginManager', () => {
let pluginManager;
@@ -187,7 +188,7 @@ describe('PluginManager', () => {
}
}
- beforeEach(() => {
+ beforeEach(function () { // eslint-disable-line prefer-arrow-callback
serverless = new Serverless();
pluginManager = new PluginManager(serverless);
});
@@ -348,7 +349,7 @@ describe('PluginManager', () => {
});
describe('#loadAllPlugins()', () => {
- beforeEach(() => {
+ beforeEach(function () { // eslint-disable-line prefer-arrow-callback
mockRequire('ServicePluginMock1', ServicePluginMock1);
mockRequire('ServicePluginMock2', ServicePluginMock2);
});
@@ -395,7 +396,7 @@ describe('PluginManager', () => {
expect(pluginManager.plugins[2]).to.be.instanceof(ServicePluginMock2);
});
- afterEach(() => {
+ afterEach(function () { // eslint-disable-line prefer-arrow-callback
mockRequire.stop('ServicePluginMock1');
mockRequire.stop('ServicePluginMock2');
});
@@ -410,7 +411,7 @@ describe('PluginManager', () => {
});
describe('#loadServicePlugins()', () => {
- beforeEach(() => {
+ beforeEach(function () { // eslint-disable-line prefer-arrow-callback
mockRequire('ServicePluginMock1', ServicePluginMock1);
mockRequire('ServicePluginMock2', ServicePluginMock2);
});
@@ -426,7 +427,7 @@ describe('PluginManager', () => {
expect(pluginManager.plugins).to.contain(servicePluginMock2);
});
- afterEach(() => {
+ afterEach(function () { // eslint-disable-line prefer-arrow-callback
mockRequire.stop('ServicePluginMock1');
mockRequire.stop('ServicePluginMock2');
});
@@ -442,7 +443,7 @@ describe('PluginManager', () => {
});
describe('#getEvents()', () => {
- beforeEach(() => {
+ beforeEach(function () { // eslint-disable-line prefer-arrow-callback
const synchronousPluginMockInstance = new SynchronousPluginMock();
pluginManager.loadCommands(synchronousPluginMockInstance);
});
@@ -480,7 +481,7 @@ describe('PluginManager', () => {
});
describe('#getPlugins()', () => {
- beforeEach(() => {
+ beforeEach(function () { // eslint-disable-line prefer-arrow-callback
mockRequire('ServicePluginMock1', ServicePluginMock1);
mockRequire('ServicePluginMock2', ServicePluginMock2);
});
@@ -493,7 +494,7 @@ describe('PluginManager', () => {
expect(pluginManager.getPlugins()[1]).to.be.instanceof(ServicePluginMock2);
});
- afterEach(() => {
+ afterEach(function () { // eslint-disable-line prefer-arrow-callback
mockRequire.stop('ServicePluginMock1');
mockRequire.stop('ServicePluginMock2');
});
@@ -691,7 +692,7 @@ describe('PluginManager', () => {
});
describe('when using a synchronous hook function', () => {
- beforeEach(() => {
+ beforeEach(function () { // eslint-disable-line prefer-arrow-callback
pluginManager.addPlugin(SynchronousPluginMock);
});
@@ -715,7 +716,7 @@ describe('PluginManager', () => {
});
describe('when using a promise based hook function', () => {
- beforeEach(() => {
+ beforeEach(function () { // eslint-disable-line prefer-arrow-callback
pluginManager.addPlugin(PromisePluginMock);
});
@@ -739,7 +740,7 @@ describe('PluginManager', () => {
});
describe('when using provider specific plugins', () => {
- beforeEach(() => {
+ beforeEach(function () { // eslint-disable-line prefer-arrow-callback
pluginManager.setProvider('provider1');
pluginManager.addPlugin(Provider1PluginMock);
@@ -762,11 +763,15 @@ describe('PluginManager', () => {
});
});
- describe('Plugin/CLI integration', () => {
+ it('Plugin/CLI integration', function () {
+ this.timeout(10000);
const serverlessInstance = new Serverless();
serverlessInstance.init();
- const serverlessExec = path.join(serverlessInstance.config.serverlessPath,
- '..', 'bin', 'serverless');
+
+ // Cannot rely on shebang in severless.js to invoke script using NodeJS on Windows.
+ const execPrefix = os.platform() === 'win32' ? 'node ' : '';
+ const serverlessExec = execPrefix + path.join(serverlessInstance.config.serverlessPath,
+ '..', 'bin', 'serverless');
const tmpDir = testUtils.getTmpDirPath();
fse.mkdirSync(tmpDir);
const cwd = process.cwd();
diff --git a/tests/classes/Service.js b/tests/classes/Service.js
index 4a29720b2..520361f74 100644
--- a/tests/classes/Service.js
+++ b/tests/classes/Service.js
@@ -1,12 +1,12 @@
'use strict';
const path = require('path');
-const os = require('os');
const YAML = require('js-yaml');
const expect = require('chai').expect;
const Service = require('../../lib/classes/Service');
const Utils = require('../../lib/classes/Utils');
const Serverless = require('../../lib/Serverless');
+const testUtils = require('../../tests/utils');
describe('Service', () => {
describe('#constructor()', () => {
@@ -101,6 +101,11 @@ describe('Service', () => {
describe('#load()', () => {
let serviceInstance;
+ let tmpDirPath;
+
+ beforeEach(() => {
+ tmpDirPath = testUtils.getTmpDirPath();
+ });
it('should resolve if no servicePath is found', () => {
const serverless = new Serverless();
@@ -111,7 +116,6 @@ describe('Service', () => {
it('should load from filesystem', () => {
const SUtils = new Utils();
- const tmpDirPath = path.join(os.tmpdir(), (new Date()).getTime().toString());
const serverlessYml = {
service: 'new-service',
provider: 'aws',
@@ -164,7 +168,6 @@ describe('Service', () => {
it('should make sure function name contains the default stage', () => {
const SUtils = new Utils();
- const tmpDirPath = path.join(os.tmpdir(), (new Date()).getTime().toString());
const serverlessYml = {
service: 'new-service',
provider: 'aws',
@@ -204,9 +207,39 @@ describe('Service', () => {
});
});
+ it('should support Serverless file with a non-aws provider', () => {
+ const SUtils = new Utils();
+ const serverlessYaml = {
+ service: 'my-service',
+ provider: 'ibm',
+ functions: {
+ functionA: {
+ name: 'customFunctionName',
+ },
+ },
+ };
+
+ SUtils.writeFileSync(path.join(tmpDirPath, 'serverless.yaml'),
+ YAML.dump(serverlessYaml));
+
+ const serverless = new Serverless({ servicePath: tmpDirPath });
+ serviceInstance = new Service(serverless);
+
+ return serviceInstance.load().then(() => {
+ const expectedFunc = {
+ functionA: {
+ name: 'customFunctionName',
+ events: [],
+ },
+ };
+ expect(serviceInstance.service).to.be.equal('my-service');
+ expect(serviceInstance.provider.name).to.deep.equal('ibm');
+ expect(serviceInstance.functions).to.deep.equal(expectedFunc);
+ });
+ });
+
it('should support Serverless file with a .yaml extension', () => {
const SUtils = new Utils();
- const tmpDirPath = path.join(os.tmpdir(), (new Date()).getTime().toString());
const serverlessYaml = {
service: 'my-service',
provider: 'aws',
@@ -238,7 +271,6 @@ describe('Service', () => {
it('should support Serverless file with a .yml extension', () => {
const SUtils = new Utils();
- const tmpDirPath = path.join(os.tmpdir(), (new Date()).getTime().toString());
const serverlessYml = {
service: 'my-service',
provider: 'aws',
@@ -268,7 +300,6 @@ describe('Service', () => {
it('should throw error if service property is missing', () => {
const SUtils = new Utils();
- const tmpDirPath = path.join(os.tmpdir(), (new Date()).getTime().toString());
const serverlessYml = {
provider: 'aws',
functions: {},
@@ -290,7 +321,6 @@ describe('Service', () => {
it('should throw error if provider property is missing', () => {
const SUtils = new Utils();
- const tmpDirPath = path.join(os.tmpdir(), (new Date()).getTime().toString());
const serverlessYml = {
service: 'service-name',
functions: {},
@@ -312,7 +342,6 @@ describe('Service', () => {
it('should throw error if functions property is missing', () => {
const SUtils = new Utils();
- const tmpDirPath = path.join(os.tmpdir(), (new Date()).getTime().toString());
const serverlessYml = {
service: 'service-name',
provider: 'aws',
@@ -334,7 +363,6 @@ describe('Service', () => {
it("should throw error if a function's event is not an array", () => {
const SUtils = new Utils();
- const tmpDirPath = path.join(os.tmpdir(), (new Date()).getTime().toString());
const serverlessYml = {
service: 'service-name',
provider: 'aws',
@@ -361,7 +389,6 @@ describe('Service', () => {
it('should throw error if provider property is invalid', () => {
const SUtils = new Utils();
- const tmpDirPath = path.join(os.tmpdir(), (new Date()).getTime().toString());
const serverlessYml = {
service: 'service-name',
provider: 'invalid',
diff --git a/tests/classes/Utils.js b/tests/classes/Utils.js
index 7711df8a6..afa2b766b 100644
--- a/tests/classes/Utils.js
+++ b/tests/classes/Utils.js
@@ -3,6 +3,8 @@
const path = require('path');
const os = require('os');
const expect = require('chai').expect;
+const fse = require('fs-extra');
+const fs = require('fs');
const Serverless = require('../../lib/Serverless');
const testUtils = require('../../tests/utils');
@@ -107,6 +109,34 @@ describe('Utils', () => {
expect(obj.foo).to.equal('bar');
});
+
+ it('should read a filename extension .yml', () => {
+ const tmpFilePath = testUtils.getTmpFilePath('anything.yml');
+
+ serverless.utils.writeFileSync(tmpFilePath, { foo: 'bar' });
+ const obj = serverless.utils.readFileSync(tmpFilePath);
+
+ expect(obj.foo).to.equal('bar');
+ });
+
+ it('should read a filename extension .yaml', () => {
+ const tmpFilePath = testUtils.getTmpFilePath('anything.yaml');
+
+ serverless.utils.writeFileSync(tmpFilePath, { foo: 'bar' });
+ const obj = serverless.utils.readFileSync(tmpFilePath);
+
+ expect(obj.foo).to.equal('bar');
+ });
+
+ it('should throw YAMLException with filename if yml file is invalid format', () => {
+ const tmpFilePath = testUtils.getTmpFilePath('invalid.yml');
+
+ serverless.utils.writeFileSync(tmpFilePath, ': a');
+
+ expect(() => {
+ serverless.utils.readFileSync(tmpFilePath);
+ }).to.throw(new RegExp('YAMLException:.*invalid.yml'));
+ });
});
describe('#readFile()', () => {
@@ -228,4 +258,61 @@ describe('Utils', () => {
process.chdir(testDir);
});
});
+
+ describe('#track()', () => {
+ let serverlessPath;
+
+ beforeEach(() => {
+ serverless.init();
+
+ // create a new tmpDir for the serverlessPath
+ const tmpDirPath = testUtils.getTmpDirPath();
+ fse.mkdirsSync(tmpDirPath);
+
+ serverlessPath = tmpDirPath;
+ serverless.config.serverlessPath = tmpDirPath;
+
+ // add some mock data to the serverless service object
+ serverless.service.functions = {
+ foo: {
+ memorySize: 47,
+ timeout: 11,
+ events: [
+ {
+ http: 'GET foo',
+ },
+ ],
+ },
+ bar: {
+ events: [
+ {
+ http: 'GET foo',
+ s3: 'someBucketName',
+ },
+ ],
+ },
+ };
+ });
+
+ it('should create a new file with a tracking id if not found', () => {
+ const trackingIdFilePath = path.join(serverlessPath, 'tracking-id');
+
+ return serverless.utils.track(serverless).then(() => {
+ expect(fs.readFileSync(trackingIdFilePath).toString().length).to.be.above(1);
+ });
+ });
+
+ it('should re-use an existing file which contains the tracking id if found', () => {
+ const trackingIdFilePath = path.join(serverlessPath, 'tracking-id');
+ const trackingId = 'some-tracking-id';
+
+ // create a new file with a tracking id
+ fse.ensureFileSync(trackingIdFilePath);
+ fs.writeFileSync(trackingIdFilePath, trackingId);
+
+ return serverless.utils.track(serverless).then(() => {
+ expect(fs.readFileSync(trackingIdFilePath).toString()).to.be.equal(trackingId);
+ });
+ });
+ });
});
diff --git a/tests/templates/integration-test-template b/tests/templates/integration-test-template
index 32ba93833..0ea20ed5f 100755
--- a/tests/templates/integration-test-template
+++ b/tests/templates/integration-test-template
@@ -19,7 +19,7 @@ serverless create --template $template
echo "Overwriting Service Name"
sed -i.bak s/${template}/sls-test-$template-$RANDOM/g $template_folder/serverless.yml
-echo "Running Compose build for Teamplate"
+echo "Running Compose build for Template"
docker-compose build $template
if [ ! -z "$2" ]
diff --git a/tests/templates/test_all_templates b/tests/templates/test_all_templates
index 4ce56adf0..ce51a2638 100755
--- a/tests/templates/test_all_templates
+++ b/tests/templates/test_all_templates
@@ -10,5 +10,6 @@ function integration-test {
integration-test aws-java-gradle build
integration-test aws-java-maven mvn package
+integration-test aws-scala-sbt sbt assembly
integration-test aws-nodejs
integration-test aws-python
diff --git a/tests/utils/index.js b/tests/utils/index.js
index e1207f80e..2a9713b41 100644
--- a/tests/utils/index.js
+++ b/tests/utils/index.js
@@ -4,7 +4,8 @@ const os = require('os');
const path = require('path');
const crypto = require('crypto');
-const getTmpDirPath = () => path.join(os.tmpdir(), crypto.randomBytes(8).toString('hex'));
+const getTmpDirPath = () => path.join(os.tmpdir(),
+ 'tmpdirs-serverless', 'serverless', crypto.randomBytes(8).toString('hex'));
const getTmpFilePath = (fileName) => path.join(getTmpDirPath(), fileName);