mirror of
https://github.com/serverless/serverless.git
synced 2026-01-18 14:58:43 +00:00
Merge with master.
There was a merge issue with createStack
This commit is contained in:
commit
4d34dfde44
10
.editorconfig
Normal file
10
.editorconfig
Normal file
@ -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
|
||||
@ -1,3 +1,4 @@
|
||||
coverage
|
||||
node_modules
|
||||
tmp
|
||||
tmpdirs-serverless
|
||||
|
||||
20
.github/PULL_REQUEST_TEMPLATE.md
vendored
20
.github/PULL_REQUEST_TEMPLATE.md
vendored
@ -1,8 +1,7 @@
|
||||
<!--
|
||||
1. Please check out and follow our Contributing Guidelines: https://github.com/serverless/serverless/blob/master/CONTRIBUTING.md
|
||||
2. Fill out the whole template so we have a good overview on the issue
|
||||
3. Do not remove any section of the template. If something is not applicable leave it empty but leave it in the PR
|
||||
3. Please follow the template, otherwise we'll have to ask you to update it
|
||||
2. Do not remove any section of the template. If something is not applicable leave it empty but leave it in the PR
|
||||
3. Please follow the template, otherwise we'll have to ask you to update it and it will take longer until your PR is merged
|
||||
-->
|
||||
|
||||
## What did you implement:
|
||||
@ -23,8 +22,14 @@ If this is a nontrivial change please briefly describe your implementation so it
|
||||
|
||||
<!--
|
||||
Add any applicable config, commands, screenshots or other resources
|
||||
to make it easy for us to verify this works, e.g. an example serverless.yml
|
||||
or AWS CLI commands to trigger something.
|
||||
to make it easy for us to verify this works. The easier you make it for us
|
||||
to review a PR, the faster we can review and merge it.
|
||||
|
||||
Examples:
|
||||
* serverless.yml - Fully functioning to easily deploy changes
|
||||
* Screenshots - Showing the difference between your output and the master
|
||||
* AWS CLI commands - To list AWS resources and show that the correct config is in place
|
||||
* Other - Anything else that comes to mind to help us evaluate
|
||||
-->
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
8
.gitignore
vendored
8
.gitignore
vendored
@ -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
|
||||
|
||||
@ -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`
|
||||
|
||||
@ -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:
|
||||
|
||||
31
README.md
31
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!
|
||||
|
||||
## <a name="v1-plugins"></a>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)
|
||||
|
||||
## <a name="v1-services"></a>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)
|
||||
|
||||
## <a name="contributing"></a>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 <project-name>`
|
||||
* [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.
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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.
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -52,5 +52,3 @@ plugins:
|
||||
```
|
||||
|
||||
In this case `plugin1` is loaded before `plugin2`.
|
||||
|
||||
[Next step > Removing your service](removing-a-service.md)
|
||||
|
||||
@ -28,7 +28,7 @@ We're also using the term `normalizedName` or similar terms in this guide. This
|
||||
|Lambda::Permission | <ul><li>**Schedule**: {normalizedFunctionName}LambdaPermissionEventsRuleSchedule{index} </li><li>**S3**: {normalizedFunctionName}LambdaPermissionS3</li><li>**APIG**: {normalizedFunctionName}LambdaPermissionApiGateway</li><li>**SNS**: {normalizedFunctionName}LambdaPermission{normalizedTopicName}</li> | <ul><li>**Schedule**: HelloLambdaPermissionEventsRuleSchedule1 </li><li>**S3**: HelloLambdaPermissionS3</li><li>**APIG**: HelloLambdaPermissionApiGateway</li><li>**SNS**: HelloLambdaPermissionSometopic</li> |
|
||||
|Events::Rule | {normalizedFuntionName}EventsRuleSchedule{SequentialID} | HelloEventsRuleSchedule1 |
|
||||
|ApiGateway::RestApi | ApiGatewayRestApi | ApiGatewayRestApi |
|
||||
|ApiGateway::Resource | ApiGatewayResource{normalizedPath} | ApiGatewayResourceUsers |
|
||||
|ApiGateway::Resource | ApiGatewayResource{normalizedPath} | <ul><li>ApiGatewayResourceUsers</li><li>ApiGatewayResourceUsers**Var** for paths containing a variable</li><li>ApiGatewayResource**Dash** if the path is just a `-`</li></ul> |
|
||||
|ApiGateway::Method | ApiGatewayResource{normalizedPath}{normalizedMethod} | ApiGatewayResourceUsersGet |
|
||||
|ApiGateway::Authorizer | {normalizedFunctionName}ApiGatewayAuthorizer | HelloApiGatewayAuthorizer |
|
||||
|ApiGateway::Deployment | ApiGatewayDeployment{randomNumber} | ApiGatewayDeployment12356789 |
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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:
|
||||
|
||||
@ -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)
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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',
|
||||
|
||||
@ -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;
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@ -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);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@ -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\\].*' }
|
||||
);
|
||||
|
||||
@ -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 => {
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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]
|
||||
|
||||
@ -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');
|
||||
})
|
||||
);
|
||||
|
||||
|
||||
@ -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: {
|
||||
|
||||
@ -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
|
||||
```
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -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/' },
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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');
|
||||
|
||||
|
||||
@ -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
|
||||
*/
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
21
lib/plugins/create/templates/aws-scala-sbt/build.sbt
Normal file
21
lib/plugins/create/templates/aws-scala-sbt/build.sbt
Normal file
@ -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")
|
||||
5
lib/plugins/create/templates/aws-scala-sbt/event.json
Normal file
5
lib/plugins/create/templates/aws-scala-sbt/event.json
Normal file
@ -0,0 +1,5 @@
|
||||
{
|
||||
"key3": "value3",
|
||||
"key2": "value2",
|
||||
"key1": "value1"
|
||||
}
|
||||
@ -0,0 +1,3 @@
|
||||
resolvers += Resolver.sonatypeRepo("public")
|
||||
|
||||
addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.14.0")
|
||||
@ -0,0 +1 @@
|
||||
addSbtPlugin("com.github.gseitz" % "sbt-release" % "1.0.0")
|
||||
70
lib/plugins/create/templates/aws-scala-sbt/serverless.yml
Normal file
70
lib/plugins/create/templates/aws-scala-sbt/serverless.yml
Normal file
@ -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"
|
||||
@ -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)
|
||||
}
|
||||
|
||||
}
|
||||
@ -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("", "", "")
|
||||
}
|
||||
@ -0,0 +1,5 @@
|
||||
package hello
|
||||
|
||||
import scala.beans.BeanProperty
|
||||
|
||||
case class Response(@BeanProperty message: String, @BeanProperty request: Request)
|
||||
@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@ -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);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@ -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');
|
||||
}
|
||||
}
|
||||
|
||||
68
npm-shrinkwrap.json
generated
68
npm-shrinkwrap.json
generated
@ -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": {
|
||||
|
||||
@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
@ -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();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@ -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();
|
||||
|
||||
@ -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',
|
||||
|
||||
@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@ -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" ]
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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);
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user