This commit is contained in:
Rafal Wilinski 2017-10-20 00:20:09 +02:00
commit b6ca75876c
116 changed files with 2929 additions and 575 deletions

3
.gitignore vendored
View File

@ -49,3 +49,6 @@ tracking-config.json
# Misc
jest
# VIM
*.swp

View File

@ -23,13 +23,6 @@ matrix:
- SLS_IGNORE_WARNING=*
- secure: Ia2nYzOeYvTE6qOP7DBKX3BO7s/U7TXdsvB2nlc3kOPFi//IbTVD0/cLKCAE5XqTzrrliHINSVsFcJNSfjCwmDSRmgoIGrHj5CJkWpkI6FEPageo3mdqFQYEc8CZeAjsPBNaHe6Ewzg0Ev/sjTByLSJYVqokzDCF1QostSxx1Ss6SGt1zjxeP/Hp4yOJn52VAm9IHAKYn7Y62nMAFTaaTPUQHvW0mJj6m2Z8TWyPU+2Bx6mliO65gTPFGs+PdHGwHtmSF/4IcUO504x+HjDuwzW2itomLXZmIOFfGDcFYadKWzVMAfJzoRWOcVKF4jXdMoSCOviWpHGtK35E7K956MTXkroVoWCS7V0knQDovbRZj8c8td8mS4tdprUA+TzgZoHet2atWNtMuTh79rdmwoAO+IAWJegYj62Tdfy3ycESzY+KxSaV8kysG9sR3PRFoWjZerA7MhLZEzQMORXDGjJlgwLaZfYVqjlsGe5p5etFBUTd0WbFgSwOKLoA2U/fm7WzqItkjs3UWaHuvFVvwYixGxjEVmVczS6wa2cdGpHtVD9H7km4fPEzljHqQ26v0P5e8eylgqLF2IB6mL7UqGFrAtrMvAgN/M3gnq4dTs/wq1AJIOxEP7YW7kc0NAldk8vUz6t5GzCPNcuukxAku91Awnh0twxgUywatgJLZPY=
- secure: Dgaa5XIsA5Vbw/CYQLUAuVVsDX26C8+f1XYGwsbNmFQKbKvM8iy9lGrHlfrT3jftJkJH6re8tP1RjyZjjzLe25KPk4Tps7grNteCyiIIEDsC2aHhiXHD6zNHsItpxYusaFfyQinFWnK4CAYKWb9ZNIwHIDUIB4vq807QGAhYsnoj1Lg/ajWvtEKBwYjEzDz9OjB91lw7lpCnHtmKKw5A+TNIVGpDDZ/jRBqETsPaePtiXC9UTHZQyM3gFoeVXiJw9KSU/gjIx9REihCaWWPbnuQSeIONGGlVWY9V4DTZIsJr9/uwDcbioeXDD3G1ezGtNPPRSNTtq08QlUtE4mEtKea/+ObpllKZCeZGn6AJhMn+uqMIP95FFlqBB55YzRcLZY+Igi/qm/9LJ9RinAhxRVXiwzeQ+BdVA6jshAAzr+7wklux6lZAa0xGw9pgTv7MI4RP2LJ/LMP1ppFsnv9n/qt93Ax1VEwEu3xHZe3VTYL9tbXOPTZutf6fKjUrW7wSSuy637queESjYnnPKSb1vZcPxjSFlyh+GJvxu/3PurF9aqfiBdiorIBre+pQS4lakLtoft5nsbA+4iYUwrXR58qUPVUqQ7a0A0hedOWlp6g9ixLa6nugUP5aobJzR71T8l/IjqpnY2EEd/iINEb0XfUiZtB5zHaqFWejBtmWwCI=
- node_js: '6.2'
env:
- INTEGRATION_TEST=true
- INTEGRATION_TEST_SUITE=complex
- SLS_IGNORE_WARNING=*
- secure: Ia2nYzOeYvTE6qOP7DBKX3BO7s/U7TXdsvB2nlc3kOPFi//IbTVD0/cLKCAE5XqTzrrliHINSVsFcJNSfjCwmDSRmgoIGrHj5CJkWpkI6FEPageo3mdqFQYEc8CZeAjsPBNaHe6Ewzg0Ev/sjTByLSJYVqokzDCF1QostSxx1Ss6SGt1zjxeP/Hp4yOJn52VAm9IHAKYn7Y62nMAFTaaTPUQHvW0mJj6m2Z8TWyPU+2Bx6mliO65gTPFGs+PdHGwHtmSF/4IcUO504x+HjDuwzW2itomLXZmIOFfGDcFYadKWzVMAfJzoRWOcVKF4jXdMoSCOviWpHGtK35E7K956MTXkroVoWCS7V0knQDovbRZj8c8td8mS4tdprUA+TzgZoHet2atWNtMuTh79rdmwoAO+IAWJegYj62Tdfy3ycESzY+KxSaV8kysG9sR3PRFoWjZerA7MhLZEzQMORXDGjJlgwLaZfYVqjlsGe5p5etFBUTd0WbFgSwOKLoA2U/fm7WzqItkjs3UWaHuvFVvwYixGxjEVmVczS6wa2cdGpHtVD9H7km4fPEzljHqQ26v0P5e8eylgqLF2IB6mL7UqGFrAtrMvAgN/M3gnq4dTs/wq1AJIOxEP7YW7kc0NAldk8vUz6t5GzCPNcuukxAku91Awnh0twxgUywatgJLZPY=
- secure: Dgaa5XIsA5Vbw/CYQLUAuVVsDX26C8+f1XYGwsbNmFQKbKvM8iy9lGrHlfrT3jftJkJH6re8tP1RjyZjjzLe25KPk4Tps7grNteCyiIIEDsC2aHhiXHD6zNHsItpxYusaFfyQinFWnK4CAYKWb9ZNIwHIDUIB4vq807QGAhYsnoj1Lg/ajWvtEKBwYjEzDz9OjB91lw7lpCnHtmKKw5A+TNIVGpDDZ/jRBqETsPaePtiXC9UTHZQyM3gFoeVXiJw9KSU/gjIx9REihCaWWPbnuQSeIONGGlVWY9V4DTZIsJr9/uwDcbioeXDD3G1ezGtNPPRSNTtq08QlUtE4mEtKea/+ObpllKZCeZGn6AJhMn+uqMIP95FFlqBB55YzRcLZY+Igi/qm/9LJ9RinAhxRVXiwzeQ+BdVA6jshAAzr+7wklux6lZAa0xGw9pgTv7MI4RP2LJ/LMP1ppFsnv9n/qt93Ax1VEwEu3xHZe3VTYL9tbXOPTZutf6fKjUrW7wSSuy637queESjYnnPKSb1vZcPxjSFlyh+GJvxu/3PurF9aqfiBdiorIBre+pQS4lakLtoft5nsbA+4iYUwrXR58qUPVUqQ7a0A0hedOWlp6g9ixLa6nugUP5aobJzR71T8l/IjqpnY2EEd/iINEb0XfUiZtB5zHaqFWejBtmWwCI=
- node_js: '6.2'
env:
- DISABLE_TESTS=true
@ -44,7 +37,6 @@ script:
- if [[ -z "$INTEGRATION_TEST" && -z "$DISABLE_TESTS" ]]; then npm test; fi
- if [[ ! -z "$DISABLE_TESTS" && ! -z "$LINTING" && -z "$INTEGRATION_TEST" ]]; then npm run lint; fi
- if [[ ! -z "$INTEGRATION_TEST" && ! -z ${AWS_ACCESS_KEY_ID+x} && "$INTEGRATION_TEST_SUITE" == "simple" ]]; then npm run simple-integration-test; fi
- if [[ ! -z "$INTEGRATION_TEST" && ! -z ${AWS_ACCESS_KEY_ID+x} && "$INTEGRATION_TEST_SUITE" == "complex" && "$TRAVIS_BRANCH" == "master" && "$TRAVIS_PULL_REQUEST" == "false" ]]; then npm run complex-integration-test; fi
after_success:
- cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js && rm -rf ./coverage

View File

@ -10,9 +10,12 @@ const initializeErrorReporter = require('../lib/utils/sentry').initializeErrorRe
Error.stackTraceLimit = Infinity;
BbPromise.config({
longStackTraces: true,
});
if (process.env.SLS_DEBUG) {
// For performance reasons enabled only in SLS_DEBUG mode
BbPromise.config({
longStackTraces: true,
});
}
process.on('unhandledRejection', (e) => {
logError(e);

View File

@ -79,6 +79,10 @@ services:
image: python2.7
volumes:
- ./tmp/serverless-integration-test-spotinst-python:/app
spotinst-ruby:
image: ruby2.4.1
volumes:
- ./tmp/serverless-integration-test-spotinst-ruby:/app
webtasks-nodejs:
image: node:6.10.3
volumes:

View File

@ -122,6 +122,7 @@ Already using AWS or another cloud provider? Read on.
<li><a href="./providers/spotinst/guide">Guide</a></li>
<li><a href="./providers/spotinst/cli-reference">CLI Reference</a></li>
<li><a href="./providers/spotinst/events">Events</a></li>
<li><a href="./providers/spotinst/examples">Examples</a></li>
</ul>
</div>
</div>

View File

@ -72,6 +72,7 @@ If you have questions, join the [chat in gitter](https://gitter.im/serverless/se
<li><a href="./cli-reference/plugin-search.md">Plugin Search</a></li>
<li><a href="./cli-reference/plugin-install.md">Plugin Install</a></li>
<li><a href="./cli-reference/plugin-uninstall.md">Plugin Uninstall</a></li>
<li><a href="./cli-reference/print.md">Print</a></li>
<li><a href="./cli-reference/slstats.md">Serverless Stats</a></li>
</ul>
</div>
@ -91,6 +92,7 @@ If you have questions, join the [chat in gitter](https://gitter.im/serverless/se
<li><a href="./events/schedule.md">Schedule</a></li>
<li><a href="./events/sns.md">SNS</a></li>
<li><a href="./events/alexa-skill.md">Alexa Skill</a></li>
<li><a href="./events/alexa-smart-home.md">Alexa Smart Home</a></li>
<li><a href="./events/iot.md">IoT</a></li>
<li><a href="./events/cloudwatch-event.md">CloudWatch Event</a></li>
<li><a href="./events/cloudwatch-log.md">CloudWatch Log</a></li>

View File

@ -26,8 +26,15 @@ serverless create --template aws-nodejs
serverless create --template aws-nodejs --path myService
```
**Create service in new folder using a custom template:**
```bash
serverless create --template-url https://github.com/serverless/serverless/tree/master/lib/plugins/create/templates/aws-nodejs --path myService
```
## Options
- `--template` or `-t` The name of one of the available templates. **Required**.
- `--template` or `-t` The name of one of the available templates. **Required if --template-url is not present**.
- `--template-url` or `-u` The name of one of the available templates. **Required if --template is not present**.
- `--path` or `-p` The path where the service should be created.
- `--name` or `-n` the name of the service in `serverless.yml`.

View File

@ -0,0 +1,78 @@
<!--
title: Serverless Framework Commands - AWS Lambda - Print
menuText: Print
menuOrder: 21
description: Print your config with all variables resolved for debugging
layout: Doc
-->
<!-- DOCS-SITE-LINK:START automatically generated -->
### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/aws/cli-reference/print)
<!-- DOCS-SITE-LINK:END -->
# Print
Print your `serverless.yml` config file with all variables resolved.
If you're using [Serverless Variables](https://serverless.com/framework/docs/providers/aws/guide/variables/)
in your `serverless.yml`, it can be difficult to know if your syntax is correct
or if the variables are resolving as you expect.
With this command, it will print the fully-resolved config to your console.
```bash
serverless print
```
## Options
- None
## Examples:
Assuming you have the following config file:
```yml
service: my-service
custom:
bucketName: test
provider:
name: aws
runtime: nodejs6.10
stage: ${opt:stage, "dev"}
functions:
hello:
handler: handler.hello
resources:
Resources:
MyBucket:
Type: AWS::S3::Bucket
Properties:
BucketName: ${self:custom.bucketName}
```
Using `sls print` will resolve the variables in `provider.stage` and `BucketName`.
```bash
$ sls print
service: my-service
custom:
bucketName: test
provider:
name: aws
runtime: nodejs6.10
stage: dev # <-- Resolved
functions:
hello:
handler: handler.hello
resources:
Resources:
MyBucket:
Type: 'AWS::S3::Bucket'
Properties:
BucketName: test # <-- Resolved
```

View File

@ -19,6 +19,8 @@ Rollback the Serverless service to a specific deployment.
serverless rollback --timestamp timestamp
```
If `timestamp` is not specified, Framework will show your existing deployments.
## Options
- `--timestamp` or `-t` The deployment you want to rollback to.
- `--verbose` or `-v` Shows any Stack Output.

View File

@ -1,7 +1,7 @@
<!--
title: Serverless Framework Commands - AWS Lambda - Serverless Stats
menuText: serverless stats
menuOrder: 21
menuOrder: 22
description: Enables or disables Serverless Statistic logging within the Serverless Framework.
layout: Doc
-->

View File

@ -0,0 +1,46 @@
<!--
title: Serverless Framework - AWS Lambda Events - Alexa Smart Home
menuText: Alexa Smart Home
menuOrder: 11
description: Setting up AWS Alexa Smart Home Events with AWS Lambda via the Serverless Framework
layout: Doc
-->
<!-- DOCS-SITE-LINK:START automatically generated -->
### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/aws/events/alexa-smart-home)
<!-- DOCS-SITE-LINK:END -->
# Alexa Smart Home
## Event definition
This will enable your Lambda function to be called by an Alexa Smart Home Skill.
`amzn1.ask.skill.xx-xx-xx-xx` is an application ID for Alexa Smart Home. You need to sign up [Amazon Developer Console](https://developer.amazon.com/) and get your application ID.
After deploying, add your deployed Lambda function ARN to which this event is attached to the Service Endpoint under Configuration on Amazon Developer Console.
Please see [Steps to Create a Smart Home Skill](https://developer.amazon.com/public/solutions/alexa/alexa-skills-kit/docs/steps-to-create-a-smart-home-skill) for more info.
```yml
functions:
mySkill:
handler: mySkill.handler
events:
- alexaSmartHome: amzn1.ask.skill.xx-xx-xx-xx
```
## Enabling / Disabling
**Note:** `alexaSmartHome` events are enabled by default.
This will create and attach a alexaSmartHome event for the `mySkill` function which is disabled. If enabled it will call
the `mySkill` function by an Alexa Smart Home Skill.
```yaml
functions:
mySkill:
handler: mySkill.handler
events:
- alexaSmartHome:
appId: amzn1.ask.skill.xx-xx-xx-xx
enabled: false
```

View File

@ -277,6 +277,7 @@ functions:
resultTtlInSeconds: 0
identitySource: method.request.header.Authorization
identityValidationExpression: someRegex
type: token
authorizerFunc:
handler: handler.authorizerFunc
```
@ -313,6 +314,24 @@ functions:
identityValidationExpression: someRegex
```
You can also use the Request Type Authorizer by setting the `type` property. In this case, your `identitySource` could contain multiple entries for you policy cache. The default `type` is 'token'.
```yml
functions:
create:
handler: posts.create
events:
- http:
path: posts/create
method: post
authorizer:
arn: xxx:xxx:Lambda-Name
resultTtlInSeconds: 0
identitySource: method.request.header.Authorization, context.identity.sourceIp
identityValidationExpression: someRegex
type: request
```
You can also configure an existing Cognito User Pool as the authorizer, as shown
in the following example:

View File

@ -139,7 +139,7 @@ provider:
- Effect: "Allow"
Action:
- "s3:ListBucket"
# You can put CloudFormation syntax in here. No one will judge you.
# You can put CloudFormation syntax in here. No one will judge you.
# Remember, this all gets translated to CloudFormation.
Resource: { "Fn::Join" : ["", ["arn:aws:s3:::", { "Ref" : "ServerlessDeploymentBucket"} ] ] }
- Effect: "Allow"
@ -226,6 +226,11 @@ Then, when you run `serverless deploy`, VPC configuration will be deployed along
The Lambda function execution role must have permissions to create, describe and delete [Elastic Network Interfaces](http://docs.aws.amazon.com/AmazonVPC/latest/UserGuide/VPC_ElasticNetworkInterfaces.html) (ENI). When VPC configuration is provided the default AWS `AWSLambdaVPCAccessExecutionRole` will be associated with your Lambda execution role. In case custom roles are provided be sure to include the proper [ManagedPolicyArns](http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-role.html#cfn-iam-role-managepolicyarns). For more information please check [configuring a Lambda Function for Amazon VPC Access](http://docs.aws.amazon.com/lambda/latest/dg/vpc.html)
**VPC Lambda Internet Access**
By default, when a Lambda function is executed inside a VPC, it looses internet access and some resources inside AWS may become unavailable. In order for S3 resources and DynamoDB resources to be available for your Lambda function running inside the VPC, a VPC end point needs to be created. For more information please check [VPC Endpoint for Amazon S3](https://aws.amazon.com/blogs/aws/new-vpc-endpoint-for-amazon-s3/).
In order for other services such as Kinesis streams to be made available, a NAT Gateway needs to be configured inside the subnets that are being used to run the Lambda, for the VPC used to execute the Lambda. For more information, please check [Enable Outgoing Internet Access within VPC](https://medium.com/@philippholly/aws-lambda-enable-outgoing-internet-access-within-vpc-8dd250e11e12)
## Environment Variables
You can add environment variable configuration to a specific function in `serverless.yml` by adding an `environment` object property in the function configuration. This object should contain a key/value collection of strings:
@ -305,7 +310,7 @@ These versions are not cleaned up by serverless, so make sure you use a plugin o
## Dead Letter Queue (DLQ)
When AWS lambda functions fail, they are [retried](http://docs.aws.amazon.com/lambda/latest/dg/retries-on-errors.html). If the retries also fail, AWS has a feature to send information about the failed request to a SNS topic or SNS queue, called the [Dead Letter Queue](http://docs.aws.amazon.com/lambda/latest/dg/dlq.html), which you can use to track and diagnose and react to lambda failures.
When AWS lambda functions fail, they are [retried](http://docs.aws.amazon.com/lambda/latest/dg/retries-on-errors.html). If the retries also fail, AWS has a feature to send information about the failed request to a SNS topic or SQS queue, called the [Dead Letter Queue](http://docs.aws.amazon.com/lambda/latest/dg/dlq.html), which you can use to track and diagnose and react to lambda failures.
You can setup a dead letter queue for your serverless functions with the help of a SNS topic and the `onError` config parameter.

View File

@ -70,7 +70,7 @@ We're also using the term `normalizedName` or similar terms in this guide. This
|Lambda::Function | {normalizedFunctionName}LambdaFunction | HelloLambdaFunction |
|Lambda::Version | {normalizedFunctionName}LambdaVersion{sha256} | HelloLambdaVersionr3pgoTvv1xT4E4NiCL6JG02fl6vIyi7OS1aW0FwAI |
|Logs::LogGroup | {normalizedFunctionName}LogGroup | HelloLogGroup |
|Lambda::Permission | <ul><li>**Schedule**: {normalizedFunctionName}LambdaPermissionEventsRuleSchedule{index}</li><li>**CloudWatch Event**: {normalizedFunctionName}LambdaPermissionEventsRuleCloudWatchEvent{index}</li><li>**CloudWatch Log**: {normalizedFunctionName}LambdaPermissionLogsSubscriptionFilterCloudWatchLog{index}</li><li>**IoT**: {normalizedFunctionName}LambdaPermissionIotTopicRule{index} </li><li>**S3**: {normalizedFunctionName}LambdaPermission{normalizedBucketName}S3</li><li>**APIG**: {normalizedFunctionName}LambdaPermissionApiGateway</li><li>**SNS**: {normalizedFunctionName}LambdaPermission{normalizedTopicName}SNS</li><li>**Alexa Skill**: {normalizedFunctionName}LambdaPermissionAlexaSkill</li><li>**Cognito User Pool Trigger Source**: {normalizedFunctionName}LambdaPermissionCognitoUserPool{normalizedPoolId}TriggerSource{triggerSource}</li> </ul> | <ul><li>**Schedule**: HelloLambdaPermissionEventsRuleSchedule1</li><li>**CloudWatch Event**: HelloLambdaPermissionEventsRuleCloudWatchEvent1</li><li>**CloudWatch Log**: HelloLambdaPermissionLogsSubscriptionFilterCloudWatchLog1</li><li>**IoT**: HelloLambdaPermissionIotTopicRule1 </li><li>**S3**: HelloLambdaPermissionBucketS3</li><li>**APIG**: HelloLambdaPermissionApiGateway</li><li>**SNS**: HelloLambdaPermissionTopicSNS</li><li>**Alexa Skill**: HelloLambdaPermissionAlexaSkill</li><li>**Cognito User Pool Trigger Source**: HelloLambdaPermissionCognitoUserPoolMyPoolTriggerSourceCustomMessage</li> </ul>|
|Lambda::Permission | <ul><li>**Schedule**: {normalizedFunctionName}LambdaPermissionEventsRuleSchedule{index}</li><li>**CloudWatch Event**: {normalizedFunctionName}LambdaPermissionEventsRuleCloudWatchEvent{index}</li><li>**CloudWatch Log**: {normalizedFunctionName}LambdaPermissionLogsSubscriptionFilterCloudWatchLog{index}</li><li>**IoT**: {normalizedFunctionName}LambdaPermissionIotTopicRule{index} </li><li>**S3**: {normalizedFunctionName}LambdaPermission{normalizedBucketName}S3</li><li>**APIG**: {normalizedFunctionName}LambdaPermissionApiGateway</li><li>**SNS**: {normalizedFunctionName}LambdaPermission{normalizedTopicName}SNS</li><li>**Alexa Skill**: {normalizedFunctionName}LambdaPermissionAlexaSkill</li><li>**Alexa Smart Home**: {normalizedFunctionName}LambdaPermissionAlexaSmartHome{index}</li><li>**Cognito User Pool Trigger Source**: {normalizedFunctionName}LambdaPermissionCognitoUserPool{normalizedPoolId}TriggerSource{triggerSource}</li> </ul> | <ul><li>**Schedule**: HelloLambdaPermissionEventsRuleSchedule1</li><li>**CloudWatch Event**: HelloLambdaPermissionEventsRuleCloudWatchEvent1</li><li>**CloudWatch Log**: HelloLambdaPermissionLogsSubscriptionFilterCloudWatchLog1</li><li>**IoT**: HelloLambdaPermissionIotTopicRule1 </li><li>**S3**: HelloLambdaPermissionBucketS3</li><li>**APIG**: HelloLambdaPermissionApiGateway</li><li>**SNS**: HelloLambdaPermissionTopicSNS</li><li>**Alexa Skill**: HelloLambdaPermissionAlexaSkill</li><li>**Alexa Smart Home**: HelloLambdaPermissionAlexaSmartHome1</li><li>**Cognito User Pool Trigger Source**: HelloLambdaPermissionCognitoUserPoolMyPoolTriggerSourceCustomMessage</li> </ul>|
|Events::Rule | <ul><li>**Schedule**: {normalizedFuntionName}EventsRuleSchedule{SequentialID}</li><li>**CloudWatch Event**: {normalizedFuntionName}EventsRuleCloudWatchEvent{SequentialID}</li> </ul> | <ul><li>**Schedule**: HelloEventsRuleSchedule1</li><li>**CloudWatch Event**: HelloEventsRuleCloudWatchEvent1</li></ul> |
|AWS::Logs::SubscriptionFilter | {normalizedFuntionName}LogsSubscriptionFilterCloudWatchLog{SequentialID} | HelloLogsSubscriptionFilterCloudWatchLog1 |
|AWS::IoT::TopicRule | {normalizedFuntionName}IotTopicRule{SequentialID} | HelloIotTopicRule1 |
@ -111,6 +111,7 @@ functions:
resources:
Resources:
WriteDashPostLogGroup:
Type: AWS::Logs::LogGroup
Properties:
RetentionInDays: "30"
```

View File

@ -30,7 +30,7 @@ provider:
region: us-east-1 # Overwrite the default region used. Default is us-east-1
profile: production # The default profile to use with this service
memorySize: 512 # Overwrite the default memory size. Default is 1024
timeout: 10 # The default is 6 seconds
timeout: 10 # The default is 6 seconds. Note: API Gateway current maximum is 30 seconds
deploymentBucket:
name: com.serverless.${self:provider.region}.deploys # Deployment bucket name. Default is generated by the framework
serverSideEncryption: AES256 # when using server-side encryption
@ -55,13 +55,13 @@ provider:
key: value
iamRoleStatements: # IAM role statements so that services can be accessed in the AWS account
- Effect: 'Allow'
Action:
- 's3:ListBucket'
Resource:
Fn::Join:
- ''
- - 'arn:aws:s3:::'
- Ref: ServerlessDeploymentBucket
Action:
- 's3:ListBucket'
Resource:
Fn::Join:
- ''
- - 'arn:aws:s3:::'
- Ref: ServerlessDeploymentBucket
stackPolicy: # Optional CF stack policy. The example below allows updates to all resources except deleting/replacing EC2 instances (use with caution!)
- Effect: Allow
Principal: "*"
@ -150,6 +150,9 @@ functions:
startingPosition: LATEST
enabled: false
- alexaSkill
- alexaSmartHome:
appId: amzn1.ask.skill.xx-xx-xx-xx
enabled: true
- iot:
name: myIoTEvent
description: An IoT event

View File

@ -12,22 +12,63 @@ layout: Doc
# Variables
The Serverless framework provides a powerful variable system which allows you to add dynamic data into your `serverless.yml`. With Serverless Variables, you'll be able to do the following:
Variables allow users to dynamically replace config values in `serverless.yml` config.
- Reference & load variables from environment variables
- Reference & load variables from CLI options
- Reference & load variables from CloudFormation stack outputs
- Recursively reference properties of any type from the same `serverless.yml` file
- Recursively reference properties of any type from other YAML/JSON files
- Recursively reference properties exported from JS files, synchronously or asynchronously
- Recursively nest variable references within each other for ultimate flexibility
- Combine multiple variable references to overwrite each other
- Define your own variable syntax if it conflicts with CF syntax
- Reference & load variables from S3
- Reference & load variables from SSM
They are especially useful when providing secrets for your service to use and when you are working with multiple stages.
## Syntax
To use variables, you will need to reference values enclosed in `${}` brackets.
```yml
# serverless.yml file
yamlKeyXYZ: ${variableSource} # see list of current variable sources below
# this is an example of providing a default value as the second parameter
otherYamlKey: ${variableSource, defaultValue}
```
You can define your own variable syntax (regex) if it conflicts with CloudFormation's syntax.
**Note:** You can only use variables in `serverless.yml` property **values**, not property keys. So you can't use variables to generate dynamic logical IDs in the custom resources section for example.
## Current variable sources:
- [environment variables](https://serverless.com/framework/docs/providers/aws/guide/variables#referencing-environment-variables)
- [CLI options](https://serverless.com/framework/docs/providers/aws/guide/variables#referencing-cli-options)
- [other properties defined in `serverless.yml`](https://serverless.com/framework/docs/providers/aws/guide/variables#reference-properties-in-serverlessyml)
- [external YAML/JSON files](https://serverless.com/framework/docs/providers/aws/guide/variables#reference-variables-in-other-files)
- [variables from S3](https://serverless.com/framework/docs/providers/aws/guide/variables#referencing-s3-objects)
- [variables from AWS SSM Parameter Store](https://serverless.com/framework/docs/providers/aws/guide/variables#reference-variables-using-the-ssm-parameter-store)
- [CloudFormation stack outputs](https://serverless.com/framework/docs/providers/aws/guide/variables#reference-cloudformation-outputs)
- [properties exported from Javascript files (sync or async)](https://serverless.com/framework/docs/providers/aws/guide/variables#reference-variables-in-javascript-files)
## Recursively reference properties
You can also **Recursively reference properties** with the variable system. This means you can combine multiple values and variable sources for a lot of flexibility.
For example:
```yml
provider:
name: aws
stage: ${opt:stage, 'dev'}
environment:
MY_SECRET: ${file(./config.${self:provider.stage}.json):CREDS}
```
If `sls deploy --stage qa` is ran, the option `stage=qa` is used inside the `${file(./config.${self:provider.stage}.json):CREDS}` variable and it will resolve the `config.qa.json` file and use the `CREDS` key defined.
**How that works:**
1. stage is set to `qa` from the option supplied to the `sls deploy --stage qa` command
2. `${self:provider.stage}` resolves to `qa` and is used in `${file(./config.${self:provider.stage}.json):CREDS}`
3. `${file(./config.qa.json):CREDS}` is found & the `CREDS` value is read
4. `MY_SECRET` value is set
Likewise, if `sls deploy --stage prod` is ran the `config.prod.json` file would be found and used.
If no `--stage` flag is provided, the second parameter defined in `${opt:stage, 'dev'}` a.k.a `dev` will be used and result in `${file(./config.dev.json):CREDS}`.
## Reference Properties In serverless.yml
To self-reference properties in `serverless.yml`, use the `${self:someProperty}` syntax in your `serverless.yml`. `someProperty` can contain the empty string for a top-level self-reference or a dotted attribute reference to any depth of attribute, so you can go as shallow or deep in the object tree as you want.
@ -205,7 +246,7 @@ In your `serverless.yml`, depending on the type of your source file, either have
functions:
hello:
handler: handler.hello
events: ${file(./myCustomFile.yml):myevents
events: ${file(./myCustomFile.yml):myevents}
```
or for a JSON reference file use this sytax:
@ -214,7 +255,7 @@ or for a JSON reference file use this sytax:
functions:
hello:
handler: handler.hello
events: ${file(./myCustomFile.json):myevents
events: ${file(./myCustomFile.json):myevents}
```
## Reference Variables in Javascript Files

View File

@ -61,6 +61,7 @@ If you have questions, join the [chat in gitter](https://gitter.im/serverless/se
<li><a href="./cli-reference/plugin-search.md">Plugin Search</a></li>
<li><a href="./cli-reference/plugin-install.md">Plugin Install</a></li>
<li><a href="./cli-reference/plugin-uninstall.md">Plugin Uninstall</a></li>
<li><a href="./cli-reference/print.md">Print</a></li>
</ul>
</div>
</div>

View File

@ -28,7 +28,8 @@ serverless create --template azure-nodejs --path myService
```
## Options
- `--template` or `-t` The name of one of the available templates. **Required**.
- `--template` or `-t` The name of one of the available templates. **Required if --template-url is not present**.
- `--template-url` or `-u` The name of one of the available templates. **Required if --template is not present**.
- `--path` or `-p` The path where the service should be created.
- `--name` or `-n` the name of the service in `serverless.yml`.
@ -68,3 +69,9 @@ Serverless will use the already present directory.
Additionally Serverless will rename the service according to the path you
provide. In this example the service will be renamed to `my-new-service`.
### Create service in new folder using a custom template
```bash
serverless create --template-url https://github.com/serverless/serverless/tree/master/lib/plugins/create/templates/azure-nodejs --path myService
```

View File

@ -0,0 +1,69 @@
<!--
title: Serverless Framework Commands - Azure - Print
menuText: Print
menuOrder: 13
description: Print your config with all variables resolved for debugging
layout: Doc
-->
<!-- DOCS-SITE-LINK:START automatically generated -->
### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/azure/cli-reference/print)
<!-- DOCS-SITE-LINK:END -->
# Print
Print your `serverless.yml` config file with all variables resolved.
If you're using [Serverless Variables](https://serverless.com/framework/docs/providers/azure/guide/variables/)
in your `serverless.yml`, it can be difficult to know if your syntax is correct
or if the variables are resolving as you expect.
With this command, it will print the fully-resolved config to your console.
```bash
serverless print
```
## Options
- None
## Examples:
Assuming you have the following config file:
```yml
service: new-service
provider: azure
custom:
globalSchedule: cron(0 * * * *)
functions:
hello:
handler: handler.hello
events:
- timer: ${self:custom.globalSchedule}
world:
handler: handler.world
events:
- timer: ${self:custom.globalSchedule}
```
Using `sls print` will resolve the variables in the `timer` blocks.
```bash
service: new-service
provider: azure
custom:
globalSchedule: cron(0 * * * *)
functions:
hello:
handler: handler.hello
events:
- timer: cron(0 * * * *) # <-- Resolved
world:
handler: handler.world
events:
- timer: cron(0 * * * *) # <-- Resolved
```

View File

@ -33,7 +33,7 @@ functions:
events:
- timer:
x-azure-settings:
name: item #<string>, default - "myQueueItem", specifies which name it's available on `context.bindings`
name: timerObj #<string>, default - "myTimer", specifies which name it's available on `context.bindings`
schedule: 0 */5 * * * * #<string>, cron expression to run on
```

View File

@ -62,6 +62,7 @@ If you have questions, join the [chat in gitter](https://gitter.im/serverless/se
<li><a href="./cli-reference/plugin-search.md">Plugin Search</a></li>
<li><a href="./cli-reference/plugin-install.md">Plugin Install</a></li>
<li><a href="./cli-reference/plugin-uninstall.md">Plugin Uninstall</a></li>
<li><a href="./cli-reference/print.md">Print</a></li>
</ul>
</div>
</div>

View File

@ -28,7 +28,8 @@ serverless create --template google-nodejs --path my-service
## Options
- `--template` or `-t` The name of one of the available templates. **Required**.
- `--template` or `-t` The name of one of the available templates. **Required if --template-url is not present**.
- `--template-url` or `-u` The name of one of the available templates. **Required if --template is not present**.
- `--path` or `-p` The path where the service should be created.
- `--name` or `-n` the name of the service in `serverless.yml`.
@ -59,3 +60,9 @@ serverless create --template google-nodejs --path my-new-service
This example will generate scaffolding for a service with `google` as a provider and `nodejs` as runtime. The scaffolding will be generated in the `my-new-service` directory. This directory will be created if not present. Otherwise Serverless will use the already present directory.
Additionally Serverless will rename the service according to the path you provide. In this example the service will be renamed to `my-new-service`.
### Create service in new folder using a custom template
```bash
serverless create --template-url https://github.com/serverless/serverless/tree/master/lib/plugins/create/templates/google-nodejs --path myService
```

View File

@ -0,0 +1,80 @@
<!--
title: Serverless Framework Commands - Google Cloud Functions - Print
menuText: Print
menuOrder: 13
description: Print your config with all variables resolved for debugging
layout: Doc
-->
<!-- DOCS-SITE-LINK:START automatically generated -->
### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/google/cli-reference/print)
<!-- DOCS-SITE-LINK:END -->
# Print
Print your `serverless.yml` config file with all variables resolved.
If you're using [Serverless Variables](https://serverless.com/framework/docs/providers/google/guide/variables/)
in your `serverless.yml`, it can be difficult to know if your syntax is correct
or if the variables are resolving as you expect.
With this command, it will print the fully-resolved config to your console.
```bash
serverless print
```
## Options
- None
## Examples:
Assuming you have the following config file:
```yml
service: new-service
provider: google
custom:
resource: projects/*/topics/my-topic
functions:
first:
handler: firstPubSub
events:
- event:
eventType: providers/cloud.pubsub/eventTypes/topics.publish
resource: ${self:custom.resource}
second:
handler: secondPubSub
events:
- event:
eventType: providers/cloud.pubsub/eventTypes/topics.publish
resource: ${self:custom.resource}
```
Using `sls print` will resolve the variables in the `resource` blocks:
```bash
$ sls print
service: new-service
provider: google
custom:
resource: projects/*/topics/my-topic
functions:
first:
handler: firstPubSub
events:
- event:
eventType: providers/cloud.pubsub/eventTypes/topics.publish
resource: projects/*/topics/my-topic # <-- Resolved.
second:
handler: secondPubSub
events:
- event:
eventType: providers/cloud.pubsub/eventTypes/topics.publish
resource: projects/*/topics/my-topic # <-- Resolved.
```

View File

@ -28,7 +28,7 @@ If necessary, a more detailed guide on creating a Billing Account can be found <
A Google Cloud Project is required to use Google Cloud Functions. Here's how to create one:
1. Go to the <a href="https://console.cloud.google.com" target="_blank">Google Cloud Console Console</a>.
1. Go to the <a href="https://console.cloud.google.com" target="_blank">Google Cloud Console</a>.
2. There is a dropdown near the top left of the screen (near the search bar that lists your projects). Click it and select "Create Project".
3. Enter a Project name and select the Billing Account you created in the steps above (or any Billing Account with a valid credit card attached).
3. Click on "Create" to start the creation process.

View File

@ -68,6 +68,7 @@ If you have questions, join the [chat in gitter](https://gitter.im/serverless/se
<ul>
<li><a href="./events/http.md">HTTP Events</a></li>
<li><a href="./events/pubsub.md">PubSub Events</a></li>
<li><a href="./events/scheduler.md">Scheduled Events</a></li>
</ul>
</div>
</div>

View File

@ -35,7 +35,8 @@ serverless create --template kubeless-nodejs --path my-service
```
## Options
- `--template` or `-t` The name of one of the available templates. **Required**.
- `--template` or `-t` The name of one of the available templates. **Required if --template-url is not present**.
- `--template-url` or `-u` The name of one of the available templates. **Required if --template is not present**.
- `--path` or `-p` The path where the service should be created.
- `--name` or `-n` the name of the service in `serverless.yml`.
@ -71,4 +72,4 @@ serverless create --template kubeless-python --path my-new-service
This example will generate scaffolding for a service with `kubeless` as a provider and `python2.7` as runtime. The scaffolding will be generated in the `my-new-service` directory. This directory will be created if not present. Otherwise Serverless will use the already present directory.
Additionally Serverless will rename the service according to the path you provide. In this example the service will be renamed to `my-new-service`.
Additionally Serverless will rename the service according to the path you provide. In this example the service will be renamed to `my-new-service`.

View File

@ -20,5 +20,8 @@ serverless remove
It will remove the Kubeless Function objects from your Kubernetes cluster, the Kubernetes Deployments and the Kubernetes Services associated with the Serverless service.
## Options
- `--verbose` or `-v` Shows additional information during the removal.
## Provided lifecycle events
- `remove:remove`

View File

@ -84,10 +84,6 @@ If the events HTTP definitions contain a `path` attribute, when deploying this S
```
kubectl get ingress
NAME HOSTS ADDRESS PORTS AGE
ingress-create * 192.168.99.100 80 2m
ingress-delete * 192.168.99.100 80 2m
ingress-read-all * 192.168.99.100 80 2m
ingress-read-one * 192.168.99.100 80 2m
ingress-update * 192.168.99.100 80 2m
NAME HOSTS ADDRESS PORTS AGE
ingress-1506350705094 192.168.99.100.nip.io 80 28s
```

View File

@ -0,0 +1,34 @@
<!--
title: Serverless Framework - Kubeless Events - Schedule
menuText: Schedule
menuOrder: 3
description: Scheduled Events in Kubeless
layout: Doc
-->
<!-- DOCS-SITE-LINK:START automatically generated -->
### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/kubeless/events/schedule)
<!-- DOCS-SITE-LINK:END -->
# Kubeless Scheduled Events
Kubeless functions can be triggered following a certain schedule. The schedule can be specified events section of the `serverless.yml` following the Cron notation:
```
service: clock
provider:
name: kubeless
runtime: nodejs6
plugins:
- serverless-kubeless
functions:
clock:
handler: handler.printClock
events:
- schedule: "* * * * *"
```
When deploying this `serverless.yml` file, Kubeless will create a Kubernetes cron job that will trigger the function `printClock` every minute.

View File

@ -76,7 +76,7 @@ Kubeless will create a [Kubernetes Deployment](https://kubernetes.io/docs/concep
## Deploy Function
This deployment method updates a single function. It performs the platform API call to deploy your package without the other resources. It is much faster than redeploying your whole service each time.
This deployment method updates or deploys a single function. It performs the platform API call to deploy your package without the other resources. It is much faster than redeploying your whole service each time.
```bash
serverless deploy function --function myFunction

View File

@ -26,7 +26,7 @@ Go to the official [Node.js website](https://nodejs.org), download and follow th
**Note:** Serverless runs on Node v4 or higher.
You can verify that Node.js is installed successfully by runnning `node --version` in your terminal. You should see the corresponding Node version number printed out.
You can verify that Node.js is installed successfully by running `node --version` in your terminal. You should see the corresponding Node version number printed out.
## Installing the Serverless Framework

View File

@ -24,7 +24,7 @@ Here are the Serverless Framework's main concepts and how they pertain to Kubele
### Functions
A Function is an [Kubeless Function](http://kubeless.io/). It's an independent unit of deployment, like a microservice. It's merely code, deployed in the cloud, that is most often written to perform a single job such as:
A Function is a [Kubeless Function](http://kubeless.io/). It's an independent unit of deployment, like a microservice. It's merely code, deployed in the cloud, that is most often written to perform a single job such as:
* *Saving a user to the database*
* *Processing a file in a database*

View File

@ -67,6 +67,7 @@ If you have questions, join the [chat in gitter](https://gitter.im/serverless/se
<li><a href="./cli-reference/plugin-search.md">Plugin Search</a></li>
<li><a href="./cli-reference/plugin-install.md">Plugin Install</a></li>
<li><a href="./cli-reference/plugin-uninstall.md">Plugin Uninstall</a></li>
<li><a href="./cli-reference/print.md">Print</a></li>
<li><a href="./cli-reference/slstats.md">Serverless Stats</a></li>
</ul>
</div>

View File

@ -27,7 +27,8 @@ serverless create --template openwhisk-nodejs --path myService
```
## Options
- `--template` or `-t` The name of one of the available templates. **Required**.
- `--template` or `-t` The name of one of the available templates. **Required if --template-url is not present**.
- `--template-url` or `-u` The name of one of the available templates. **Required if --template is not present**.
- `--path` or `-p` The path where the service should be created.
- `--name` or `-n` the name of the service in `serverless.yml`.
@ -68,6 +69,12 @@ This example will generate scaffolding for a service with `openwhisk` as a provi
Additionally Serverless will rename the service according to the path you provide. In this example the service will be renamed to `my-new-service`.
### Create service in new folder using a custom template
```bash
serverless create --template-url https://github.com/serverless/serverless/tree/master/lib/plugins/create/templates/openwhisk-nodejs --path myService
```
### Creating a new plugin
```

View File

@ -0,0 +1,70 @@
<!--
title: Serverless Framework Commands - Apache OpenWhisk - Print
menuText: Print
menuOrder: 16
description: Print your config with all variables resolved for debugging
layout: Doc
-->
<!-- DOCS-SITE-LINK:START automatically generated -->
### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/openwhisk/cli-reference/print)
<!-- DOCS-SITE-LINK:END -->
# Print
Print your `serverless.yml` config file with all variables resolved.
If you're using [Serverless Variables](https://serverless.com/framework/docs/providers/openwhisk/guide/variables/)
in your `serverless.yml`, it can be difficult to know if your syntax is correct
or if the variables are resolving as you expect.
With this command, it will print the fully-resolved config to your console.
```bash
serverless print
```
## Options
- None
## Examples:
Assuming you have the following config file:
```yml
service: new-service
provider: openwhisk
custom:
globalSchedule: cron(0 * * * *)
functions:
hello:
handler: handler.hello
events:
- schedule: ${self:custom.globalSchedule}
world:
handler: handler.world
events:
- schedule: ${self:custom.globalSchedule}
```
Using `sls print` will resolve the variables in the `schedule` blocks.
```bash
$ sls print
service: new-service
provider: openwhisk
custom:
globalSchedule: cron(0 * * * *)
functions:
hello:
handler: handler.hello
events:
- schedule: cron(0 * * * *) # <-- Resolved
world:
handler: handler.world
events:
- schedule: cron(0 * * * *) # <-- Resolved
```

View File

@ -1,7 +1,7 @@
<!--
title: Serverless Framework Commands - Apache OpenWhisk - Serverless Stats
menuText: serverless stats
menuOrder: 16
menuOrder: 17
description: Enables or disables Serverless Statistic logging within the Serverless Framework.
layout: Doc
-->

View File

@ -111,8 +111,15 @@ functions:
- http:
path: posts/create
method: post
resp: json
```
HTTP event configuration supports the following parameters.
- `method` - HTTP method (mandatory).
- `path` - URI path for API gateway (mandatory).
- `resp` - controls [web action content type](https://github.com/apache/incubator-openwhisk/blob/master/docs/webactions.md#additional-features), values include: `json`, `html`, `http`, `svg`or `text` (optional, defaults to `json`).
### CORS Support
**Note:** All HTTP endpoints defined in this manner have cross-site requests

View File

@ -13,7 +13,7 @@ layout: Doc
1. Node.js `v6.5.0` or later.
2. Serverless CLI `v1.9.0` or later. You can run
`npm install -g serverless` to install it.
3. An IBM Bluemix account. If you don't already have one, you can sign up for an [account](https://aws.amazon.com/s/dm/optimization/server-side-test/free-tier/free_np/) and then follow the instructions for getting access to [OpenWhisk on Bluemix](https://console.ng.bluemix.net/openwhisk/).
3. An IBM Bluemix account. If you don't already have one, you can sign up for an [account](https://console.bluemix.net/registration/) and then follow the instructions for getting access to [OpenWhisk on Bluemix](https://console.ng.bluemix.net/openwhisk/).
4. **Set-up your [Provider Credentials](./credentials.md)**.
5. Install Framework & Dependencies
*Due to an [outstanding issue](https://github.com/serverless/serverless/issues/2895) with provider plugins, the [OpenWhisk provider](https://github.com/serverless/serverless-openwhisk) must be installed as a global module.*

View File

@ -14,28 +14,74 @@ Welcome to the Serverless Spotinst documentation!
If you have questions, join the [chat in gitter](https://gitter.im/serverless/serverless) or [post over on the forums](https://forum.serverless.com/)
## Getting Started
<div class="docsSections">
<div class="docsSection">
<div class="docsSectionHeader">
<a href="./guide/">
<img src="https://s3.amazonaws.com/spotinst-public/assets/serverless-docs/functions_guide.jpg" alt="Serverless Spotinst Guide" width="250" draggable="false"/>
</a>
</div>
<div>
<ul>
<li><a href="./guide/intro.md">Intro</a></li>
<li><a href="./guide/quick-start.md">Quick Start</a></li>
<li><a href="./guide/create-token.md">Create Token</a></li>
<li><a href="./guide/credentials.md">Credentials</a></li>
<li><a href="./guide/serverless.yml.md">Serverless.yml Reference</a></li>
</ul>
</div>
</div>
<a href="./guide">Get started here by reading the guide</a>
<div class="docsSection">
<div class="docsSectionHeader">
<a href="./cli-reference/">
<img src="https://s3.amazonaws.com/spotinst-public/assets/serverless-docs/functions_cli.jpg" alt="Serverless Spotinst CLI Reference" width="250" draggable="false"/>
</a>
</div>
<div>
<ul>
<li><a href="./cli-reference/config-credentials.md">Config Credentials</a></li>
<li><a href="./cli-reference/create.md">Create</a></li>
<li><a href="./cli-reference/deploy.md">Deploy</a></li>
<li><a href="./cli-reference/deploy-function.md">Deploy Function</a></li>
<li><a href="./cli-reference/invoke.md">Invoke</a></li>
<li><a href="./cli-reference/logs.md">Logs</a></li>
<li><a href="./cli-reference/info.md">Info</a></li>
<li><a href="./cli-reference/remove.md">Remove</a></li>
<li><a href="./cli-reference/plugin-list.md">Plugin List</a></li>
<li><a href="./cli-reference/plugin-search.md">Plugin Search</a></li>
<li><a href="./cli-reference/plugin-install.md">Plugin Install</a></li>
<li><a href="./cli-reference/plugin-uninstall.md">Plugin Uninstall</a></li>
</ul>
</div>
</div>
## CLI reference
<div class="docsSection">
<div class="docsSectionHeader">
<a href="./events/">
<img src="https://s3.amazonaws.com/spotinst-public/assets/serverless-docs/functions_+events.jpg" alt="Serverless Spotinst Events" width="250" draggable="false"/>
</a>
</div>
<div>
<ul>
<li><a href="./events/http.md">HTTP</a></li>
<li><a href="./events/schedule.md">Schedule</a></li>
</ul>
</div>
</div>
<ul>
<li><a href="./cli-reference/config-credentials.md">Config Credentials</a></li>
<li><a href="./cli-reference/create.md">Create</a></li>
<li><a href="./cli-reference/deploy.md">Deploy</a></li>
<li><a href="./cli-reference/invoke.md">Invoke</a></li>
<li><a href="./cli-reference/logs.md">Logs</a></li>
<li><a href="./cli-reference/info.md">Info</a></li>
<li><a href="./cli-reference/remove.md">Remove</a></li>
<li><a href="./cli-reference/plugin-list.md">Plugin List</a></li>
<li><a href="./cli-reference/plugin-search.md">Plugin Search</a></li>
<li><a href="./cli-reference/plugin-install.md">Plugin Install</a></li>
<li><a href="./cli-reference/plugin-uninstall.md">Plugin Uninstall</a></li>
</ul>
## Supported Events
<ul>
<li><a href="./events/http.md">http event</a></li>
<li><a href="./events/schedule.md">Schedule event</a></li>
</ul>
<div class="docsSection">
<div class="docsSectionHeader">
<a href="./examples/">
<img src="https://s3.amazonaws.com/spotinst-public/assets/serverless-docs/functions_examples.jpg" alt="Serverless Spotinst Examples" width="250" draggable="false"/>
</a>
</div>
<div>
<div>
<ul>
<li><a href="./examples/">Hello World</a></li>
</ul>
</div>
</div>
</div>
</div>

View File

@ -27,7 +27,8 @@ serverless create -t spotinst-nodejs -p myService
```
## Options
- `--template` or `-t` The name of one of the available templates. **Required**.
- `--template` or `-t` The name of one of the available templates. **Required if --template-url is not present**.
- `--template-url` or `-u` The name of one of the available templates. **Required if --template is not present**.
- `--path` or `-p` The path where the service should be created.
- `--name` or `-n` the name of the service in `serverless.yml`.

View File

@ -33,6 +33,10 @@ functions:
handler: handler.main
memory: 128
timeout: 30
access: private
# cron: # Setup scheduled trigger with cron expression
# active: true
# value: '* * * * *'
# extend the framework using plugins listed here:
# https://github.com/serverless/plugins

View File

@ -12,18 +12,8 @@ layout: Doc
# HTTP
Spotinst Functions can be triggered by an HTTP endpoint. To create HTTP endpoints as event sources for your Spotinst Functions, use the `http` event syntax.
Spotinst Functions are automatically given an HTTP endpoint when they are created. This means that you do not need to specify the event type when writing your function. After you deploy your function for the first time a unique URL is generated based on the application ID, environment where your application is launched, and the function ID. Here is a sample of how the URL is created
This setup specifies that the `first` function should be run when someone accesses the Functions API endpoint via a `GET` request. You can get the URL for the endpoint by running the `serverless info` command after deploying your service.
`https://{app id}{environment id}.spotinst.io/{function id}`
Here's an example:
```yml
# serverless.yml
functions:
first:
handler: http
events:
- http: path
```
For information on your application ID, environment ID and function ID please checkout your Spotinst Functions dashboard on the [Spotinst website](https://console.spotinst.com/#/dashboard)

View File

@ -12,14 +12,38 @@ layout: Doc
# Schedule
You can trigger the functions by using a scheduled event. This will execute the function according to the cron expressions you specify
You can trigger the functions by using a scheduled event. This will execute the function according to the cron expressions you specify.
You can either use the `rate` or `cron` syntax.
You can use `cron` syntax.
The following example is a function configuration in the serverless.yml file that are scheduled to trigger the function crawl every day at 6:30 PM.
```yml
functions:
crawl:
handler: crawl
events:
- schedule: cron(0 12 * * ? *)
handler: handler.crawl
cron: # Setup scheduled trigger with cron expression
active: true
value: '30 18 * * *'
```
## Active Status
You also have the option to set your functions active status as either true or false
**Note** `schedule` events active status are set to true by default
This example will create and attach a schedule event for the function `crawl` which is active status is set to `false`. If the status is changed to true the `crawl` function will be called every Monday at 6:00 PM.
```yml
functions:
crawl:
handler: handler.crawl
cron: # Setup scheduled trigger with cron expression
active: false
value: '* 18 * * 1'
```
**Note** When creating a `cron` trigger the `value` is the crontab expression. For help on crontab check out the [documentation](http://www.adminschoice.com/crontab-quick-reference)

View File

@ -0,0 +1,18 @@
<!--
title: Hello World Example
menuText: Hello World Example
description: Example of creating a Hello World function in Node.js and Python with the Serverless framework
layout: Doc
-->
<!-- DOCS-SITE-LINK:START automatically generated -->
### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/aws/examples/hello-world/)
<!-- DOCS-SITE-LINK:END -->
# Hello World Serverless Example
Pick your language of choice:
* [JavaScript](./node)
* [Python](./python)
* [Ruby](./ruby)

View File

@ -0,0 +1,48 @@
<!--
title: Hello World Javascript Example
menuText: Hello World JavaScript Example
description: Create a JavaScript Hello World function
layout: Doc
-->
<!-- DOCS-SITE-LINK:START automatically generated -->
### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/spotinst/)
<!-- DOCS-SITE-LINK:END -->
# Hello World JavaScript Example
Make sure `serverless` is installed.
## 1. Create a service
`serverless create --template spotinst-nodejs --path serviceName` `serviceName` is going to be a new directory there the python template will be loaded. Once the download is complete change into that directory. Next you will need to install the Spotinst Serverless Functions plugin by running `npm install` in the root directory
## 2. Deploy
`serverless deploy`
## 3. Invoke deployed function
`serverless invoke --function hello`
In your terminal window you should see the response
```bash
{
Deploy functions:
hello: created
Service Information
service: spotinst-python
functions:
hello
}
```
Congrats you have just deployed and ran your Hello World function!
## Short Hand Guide
`sls` is short hand for serverless cli commands
`-f` is short hand for `--function`
`-t` is short hand for `--template`
`-p` is short hang for `--path`

View File

@ -0,0 +1,49 @@
<!--
title: Hello World Python Example
menuText: Hello World Python Example
description: Create a Python Hello World function
layout: Doc
-->
<!-- DOCS-SITE-LINK:START automatically generated -->
### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/spotinst/)
<!-- DOCS-SITE-LINK:END -->
# Hello World Python Example
Make sure `serverless` is installed.
## 1. Create a service
`serverless create --template spotinst-python --path serviceName` `serviceName` is going to be a new directory there the python template will be loaded. Once the download is complete change into that directory. Next you will need to install the Spotinst Serverless Functions plugin by running `npm install` in the root directory
## 2. Deploy
`serverless deploy`
## 3. Invoke deployed function
`serverless invoke --function hello`
In your terminal window you should see the response
```bash
{
Deploy functions:
hello: created
Service Information
service: spotinst-python
functions:
hello
}
```
Congrats you have just deployed and ran your Hello World function!
## Short Hand Guide
`sls` is short hand for serverless cli commands
`-f` is short hand for `--function`
`-t` is short hand for `--template`
`-p` is short hang for `--path`

View File

@ -0,0 +1,49 @@
<!--
title: Hello World Ruby Example
menuText: Hello World Ruby Example
description: Create a Ruby Hello World function
layout: Doc
-->
<!-- DOCS-SITE-LINK:START automatically generated -->
### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/spotinst/)
<!-- DOCS-SITE-LINK:END -->
# Hello World Ruby Example
Make sure `serverless` is installed.
## 1. Create a service
`serverless create --template spotinst-ruby --path serviceName` `serviceName` is going to be a new directory there the python template will be loaded. Once the download is complete change into that directory. Next you will need to install the Spotinst Serverless Functions plugin by running `npm install` in the root directory
## 2. Deploy
`serverless deploy`
## 3. Invoke deployed function
`serverless invoke --function hello`
In your terminal window you should see the response
```bash
{
Deploy functions:
hello: created
Service Information
service: spotinst-ruby
functions:
hello
}
```
Congrats you have just deployed and ran your Hello World function!
## Short Hand Guide
`sls` is short hand for serverless cli commands
`-f` is short hand for `--function`
`-t` is short hand for `--template`
`-p` is short hang for `--path`

View File

@ -0,0 +1,72 @@
<!--
title: Serverless Framework - Spotinst Functions Guide - Create Token
menuText: Create Token
menuOrder: 3
description: How to set up the Serverless Framework with your Spotinst Token
layout: Doc
-->
<!-- DOCS-SITE-LINK:START automatically generated -->
### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/spotinst/guide/credentials)
<!-- DOCS-SITE-LINK:END -->
# Spotinst Functions - Create Token
The Serverless Framework needs access to your Spotinst account so that it can create and manage resources on your behalf. To do this you will need either a permanent or tempary token that is linked to your account
## Create a Permanent Token
You can generate a Permanent Token from the [Spotinst Console](https://console.spotinst.com/#/settings/tokens/permanent).
> `WARNING`: Do not share your personal access token or your application secret with anyone outside your organization. Please contact our support if youre concerned your token has been compromised.
## Temporary Access Token
You can also generate a the temporary access token, which is only valid for 2 hours (7200 seconds).
You can generate a temporary token from the [Spotinst Console](https://console.spotinst.com/#/settings/tokens/temporary). Or, using the below command:
```bash
$ curl -X POST -H "Content-Type: application/x-www-form-urlencoded" -d 'username=<USERNAME>&password=<PASSWORD>&grant_type=password&client_id=<CLIENT_ID>&client_secret=<CLIENT_SECRET>' https://oauth.spotinst.io/token
```
Replace the following parameters, more info can be found [here](https://console.spotinst.com/#/settings/tokens/temporary)
- `<USERNAME>`
- `<PASSWORD>`
- `<CLIENT_ID>`
- `<CLIENT_SECRET>`
The request will return two tokens:
```json
{
"request": {
"id": "a2285a3f-4950-4874-a931-1ee1cdf33012",
"url": "/token",
"method": "POST",
"timestamp": "2017-08-30T22:00:34.610Z"
},
"response": {
"status": {
"code": 200,
"message": "OK"
},
"kind": "spotinst:oauth2:token",
"items": [
{
"accessToken": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJzcG90aW5zdCIsInVpZCI6LTgsIm9pZCI6NjA2MDc5ODYxOTExLCJyb2xlIjoyLCJleHAiOjE1MDQxMzc2MzQsImlhdCI6MTUwNDEzMDQzNH0.xyax",
"tokenType": "bearer",
"expiresIn": 7199
}
],
"count": 1
}
}
```
* *accessToken* - Use this token when making calls to Spotinst API
* *refreshToken* - Use this token in order to refresh the temporary token. This will return a new token that is valid for additional 2 hours:
```bash
$ curl -X POST -H "Content-Type: application/x-www-form-urlencoded" -d 'refresh_token=<REFRESH_TOKEN>&grant_type=refresh_token&client_id=<CLIENT_ID>&client_secret=<CLIENT_SECRET>' https://api.spotinst.io/token
```

View File

@ -1,7 +1,7 @@
<!--
title: Serverless Framework - Spotinst Functions Guide - Credentials
menuText: Credentials
menuOrder: 3
menuOrder: 4
description: How to set up the Serverless Framework with your Spotinst Functions credentials
layout: Doc
-->
@ -12,60 +12,32 @@ layout: Doc
# Spotinst Functions - Credentials
The Serverless Framework needs access to your Spotinst account so that it can create and manage resources on your behalf.
The Serverless Framework needs access to your Spotinst account so that it can create and manage resources on your behalf. Please make sure you have created and saved your Permanent or Temporary Token before continuing.
## Create a Permanent Token
## Configure Credentials
You can generate a Permanent Token from the [Spotinst Console](https://console.spotinst.com/#/settings/tokens/permanent).
You will need to have your account ID number and your account token ready. Your account ID can be found on the Spotinst console and your token can be generated by following the [Create Token Guide](./create-token.md).
> `WARNING`: Do not share your personal access token or your application secret with anyone outside your organization. Please contact our support if youre concerned your token has been compromised.
In order to run the config credentials command from the terminal you will need to start a new Spotinst project and install the plugin. First you will need to run the create a new project using the Spotinst template. To do this run:
## Temporary Access Token
You can also generate a the temporary access token, which is only valid for 2 hours (7200 seconds).
You can generate a temporary token from the [Spotinst Console](https://console.spotinst.com/#/settings/tokens/temporary). Or, using the below command:
```bash
$ curl -X POST -H "Content-Type: application/x-www-form-urlencoded" -d 'username=<USERNAME>&password=<PASSWORD>&grant_type=password&client_id=<CLIENT_ID>&client_secret=<CLIENT_SECRET>' https://oauth.spotinst.io/token
```
serverless create -t spotinst-nodejs -p new-function
```
Replace the following parameters, more info can be found [here](https://console.spotinst.com/#/settings/tokens/temporary)
- `<USERNAME>`
- `<PASSWORD>`
- `<CLIENT_ID>`
- `<CLIENT_SECRET>`
Then navigate to the directory that was just created `new-function` and install the Spotinst Serverless Plugin by running the `npm install` command.
The request will return two tokens:
```json
{
"request": {
"id": "a2285a3f-4950-4874-a931-1ee1cdf33012",
"url": "/token",
"method": "POST",
"timestamp": "2017-08-30T22:00:34.610Z"
},
"response": {
"status": {
"code": 200,
"message": "OK"
},
"kind": "spotinst:oauth2:token",
"items": [
{
"accessToken": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJzcG90aW5zdCIsInVpZCI6LTgsIm9pZCI6NjA2MDc5ODYxOTExLCJyb2xlIjoyLCJleHAiOjE1MDQxMzc2MzQsImlhdCI6MTUwNDEzMDQzNH0.xyax",
"tokenType": "bearer",
"expiresIn": 7199
}
],
"count": 1
}
}
Once this has completed you will be able to configure your credentials by running
```
serverless config credentials -p spotinst -k {your account number} -t {your token}
```
* *accessToken* - Use this token when making calls to Spotinst API
* *refreshToken* - Use this token in order to refresh the temporary token. This will return a new token that is valid for additional 2 hours:
This will create a ~/.spotinst/credentials file the file should look like this when if done correctly:
```
default:
token: {your token}
account: {your account number}
```
```bash
$ curl -X POST -H "Content-Type: application/x-www-form-urlencoded" -d 'refresh_token=<REFRESH_TOKEN>&grant_type=refresh_token&client_id=<CLIENT_ID>&client_secret=<CLIENT_SECRET>' https://api.spotinst.io/token
```
After this is set up properly you will be able to deploy functions from your computer and monitor them on the Spotinst Console.

View File

@ -92,6 +92,10 @@ functions:
handler: handler.main
memory: 128
timeout: 30
# access: private
# cron: # Setup scheduled trigger with cron expression
# active: true
# value: '* * * * *'
```
When you deploy with the Framework by running `serverless deploy`, everything in `serverless.yml` is deployed at once.
@ -105,4 +109,4 @@ You can overwrite or extend the functionality of the Framework using **Plugins**
plugins:
- serverless-spotinst-functions
```
```

View File

@ -1,7 +1,7 @@
<!--
title: Serverless Framework - Spotinst Guide - Quick Start
menuText: Quick Start
menuOrder: 1
menuOrder: 2
description: Getting started with the Serverless Framework on AWS Lambda
layout: Doc
-->

View File

@ -0,0 +1,51 @@
<!--
title: Serverless Framework - Spotinst Guide - Serverless.yml Reference
menuText: Serverless.yml
menuOrder: 5
description: Serverless.yml reference
layout: Doc
-->
# Serverless.yml Reference
This is an outline of a `serverless.yml` file with descriptions of the properties for reference
```yml
# serverless.yml
# The service can be whatever you choose. You can have multiple functions
# under one service
service: your-service
# The provider is Spotinst and the Environment ID can be found on the
# Spotinst Console under Functions
provider:
name: spotinst
spotinst:
environment: #{Your Environment ID}
# Here is where you will list your functions for this service. Each Function is
# required to have a name, runtime, handler, memory and timeout. The runtime is
# the language that you want to run your function with, the handler tells which
# file and function to run, memory is the amount of memory needed to run your
# function, timeout is the time the function will take to run, if it goes over
# this time it will terminate itself. Access is default set to private so if you
# want to be able to run the function by HTTPS request this needs to be set to
# public. For information on cron functions read the post here.
functions:
function-name:
runtime: nodejs4.8
handler: handler.main
memory: 128
timeout: 30
# access: public
# cron:
# active: false
# value: '*/1 * * * *'
plugins:
- serverless-spotinst-functions
```

View File

@ -28,7 +28,8 @@ serverless create --template webtasks-nodejs --path my-service
## Options
- `--template` or `-t` The name of one of the available templates. **Required**.
- `--template` or `-t` The name of one of the available templates. **Required if --template-url is not present**.
- `--template-url` or `-u` The name of one of the available templates. **Required if --template is not present**.
- `--path` or `-p` The path where the service should be created.
- `--name` or `-n` the name of the service in `serverless.yml`.

View File

@ -14,7 +14,7 @@ layout: Doc
The Serverless Framework helps you develop and deploy serverless applications using Auth0 Webtasks. The Serverless CLI offers structure, automation and best practices out-of-the-box. And with Auth0 Webtasks it's simple and easy to deploy code in just seconds.
**Note:** A local profile is required to use Auth0 Webtasks with the Serverless Framework. Follow the steps in the [Quick Start](../quick-start.md) to get setup in less than a minute.
**Note:** A local profile is required to use Auth0 Webtasks with the Serverless Framework. Follow the steps in the [Quick Start](quick-start.md) to get setup in less than a minute.
## Core Concepts

View File

@ -90,17 +90,23 @@ class PluginManager {
this.addPlugin(Plugin);
} catch (error) {
// Rethrow the original error in case we're in debug mode.
if (process.env.SLS_DEBUG) {
throw error;
let errorMessage;
if (error && error.code === 'MODULE_NOT_FOUND' && error.message.endsWith(`'${plugin}'`)) {
// Plugin not installed
errorMessage = [
`Serverless plugin "${plugin}" not found.`,
' Make sure it\'s installed and listed in the "plugins" section',
' of your serverless config file.',
].join('');
} else {
// Plugin initialization error
// Rethrow the original error in case we're in debug mode.
if (process.env.SLS_DEBUG) {
throw error;
}
errorMessage =
`Serverless plugin "${plugin}" initialization errored: ${error.message}`;
}
const errorMessage = [
`Serverless plugin "${plugin}" not found.`,
' Make sure it\'s installed and listed in the "plugins" section',
' of your serverless config file.',
].join('');
if (!this.cliOptions.help) {
throw new this.serverless.classes.Error(errorMessage);
}

View File

@ -8,8 +8,10 @@ const fse = BbPromise.promisifyAll(require('fs-extra'));
const _ = require('lodash');
const fileExistsSync = require('../utils/fs/fileExistsSync');
const writeFileSync = require('../utils/fs/writeFileSync');
const copyDirContentsSync = require('../utils/fs/copyDirContentsSync');
const readFileSync = require('../utils/fs/readFileSync');
const walkDirSync = require('../utils/fs/walkDirSync');
const dirExistsSync = require('../utils/fs/dirExistsSync');
const isDockerContainer = require('is-docker');
const version = require('../../package.json').version;
const segment = require('../utils/segment');
@ -25,12 +27,7 @@ class Utils {
}
dirExistsSync(dirPath) {
try {
const stats = fse.statSync(dirPath);
return stats.isDirectory();
} catch (e) {
return false;
}
return dirExistsSync(dirPath);
}
fileExistsSync(filePath) {
@ -92,12 +89,7 @@ class Utils {
}
copyDirContentsSync(srcDir, destDir) {
const fullFilesPaths = this.walkDirSync(srcDir);
fullFilesPaths.forEach(fullFilePath => {
const relativeFilePath = fullFilePath.replace(srcDir, '');
fse.copySync(fullFilePath, path.join(destDir, relativeFilePath));
});
return copyDirContentsSync(srcDir, destDir);
}
generateShortId(length) {

View File

@ -12,6 +12,7 @@
"./login/login.js",
"./logout/logout.js",
"./metrics/metrics.js",
"./print/print.js",
"./remove/remove.js",
"./rollback/index.js",
"./slstats/slstats.js",
@ -40,6 +41,7 @@
"./aws/package/compile/events/sns/index.js",
"./aws/package/compile/events/stream/index.js",
"./aws/package/compile/events/alexaSkill/index.js",
"./aws/package/compile/events/alexaSmartHome/index.js",
"./aws/package/compile/events/iot/index.js",
"./aws/package/compile/events/cloudWatchEvent/index.js",
"./aws/package/compile/events/cloudWatchLog/index.js",

View File

@ -11,6 +11,7 @@ const testUtils = require('../../../../../tests/utils');
describe('createStack', () => {
let awsDeploy;
let sandbox;
const tmpDirPath = testUtils.getTmpDirPath();
const serverlessYmlPath = path.join(tmpDirPath, 'serverless.yml');
@ -38,13 +39,21 @@ describe('createStack', () => {
awsDeploy.serverless.cli = new serverless.classes.CLI();
});
beforeEach(() => {
sandbox = sinon.sandbox.create();
});
afterEach(() => {
sandbox.restore();
});
describe('#create()', () => {
it('should include custom stack tags', () => {
awsDeploy.serverless.service.provider.stackTags = { STAGE: 'overridden', tag1: 'value1' };
const createStackStub = sinon
const createStackStub = sandbox
.stub(awsDeploy.provider, 'request').resolves();
sinon.stub(awsDeploy, 'monitorStack').resolves();
sandbox.stub(awsDeploy, 'monitorStack').resolves();
return awsDeploy.create().then(() => {
expect(createStackStub.args[0][2].Tags)
@ -52,33 +61,29 @@ describe('createStack', () => {
{ Key: 'STAGE', Value: 'overridden' },
{ Key: 'tag1', Value: 'value1' },
]);
awsDeploy.provider.request.restore();
awsDeploy.monitorStack.restore();
});
});
it('should use CloudFormation service role ARN if it is specified', () => {
awsDeploy.serverless.service.provider.cfnRole = 'arn:aws:iam::123456789012:role/myrole';
const createStackStub = sinon
const createStackStub = sandbox
.stub(awsDeploy.provider, 'request').resolves();
sinon.stub(awsDeploy, 'monitorStack').resolves();
sandbox.stub(awsDeploy, 'monitorStack').resolves();
return awsDeploy.create().then(() => {
expect(createStackStub.args[0][2].RoleARN)
.to.equal('arn:aws:iam::123456789012:role/myrole');
awsDeploy.provider.request.restore();
awsDeploy.monitorStack.restore();
});
});
});
describe('#createStack()', () => {
it('should resolve if stack already created', () => {
const createStub = sinon
const createStub = sandbox
.stub(awsDeploy, 'create').resolves();
sinon.stub(awsDeploy.provider, 'request').resolves();
sandbox.stub(awsDeploy.provider, 'request').resolves();
return awsDeploy.createStack().then(() => {
expect(createStub.called).to.be.equal(false);
@ -87,7 +92,7 @@ describe('createStack', () => {
it('should set the createLater flag and resolve if deployment bucket is provided', () => {
awsDeploy.serverless.service.provider.deploymentBucket = 'serverless';
sinon.stub(awsDeploy.provider, 'request')
sandbox.stub(awsDeploy.provider, 'request')
.returns(BbPromise.reject({ message: 'does not exist' }));
return awsDeploy.createStack().then(() => {
@ -100,7 +105,7 @@ describe('createStack', () => {
message: 'Something went wrong.',
};
sinon.stub(awsDeploy.provider, 'request').rejects(errorMock);
sandbox.stub(awsDeploy.provider, 'request').rejects(errorMock);
const createStub = sinon
.stub(awsDeploy, 'create').resolves();
@ -117,7 +122,7 @@ describe('createStack', () => {
message: 'does not exist',
};
sinon.stub(awsDeploy.provider, 'request').rejects(errorMock);
sandbox.stub(awsDeploy.provider, 'request').rejects(errorMock);
const createStub = sinon
.stub(awsDeploy, 'create').resolves();

View File

@ -29,6 +29,29 @@ module.exports = {
this.serverless.service.package.artifact = path
.join(this.serverless.config.servicePath, '.serverless', state.package.artifact);
}
// Check function's attached to API Gateway timeout
if (!_.isEmpty(this.serverless.service.functions)) {
this.serverless.service.getAllFunctions().forEach(functionName => {
const functionObject = this.serverless.service.getFunction(functionName);
// Check if function timeout is greater than API Gateway timeout
if (functionObject.timeout > 30 && functionObject.events) {
functionObject.events.forEach(event => {
if (Object.keys(event)[0] === 'http') {
const warnMessage = [
`WARNING: Function ${functionName} has timeout of ${functionObject.timeout} `,
'seconds, however, it\'s attached to API Gateway so it\'s automatically ',
'limited to 30 seconds.',
].join('');
this.serverless.cli.log(warnMessage);
}
});
}
});
}
if (!_.isEmpty(this.serverless.service.functions) &&
this.serverless.service.package.individually) {
// artifact file validation (multiple function artifacts)

View File

@ -42,7 +42,9 @@ describe('extendedValidate', () => {
};
awsDeploy = new AwsDeploy(serverless, options);
awsDeploy.serverless.service.service = `service-${(new Date()).getTime().toString()}`;
awsDeploy.serverless.cli = new serverless.classes.CLI();
awsDeploy.serverless.cli = {
log: sinon.spy(),
};
});
describe('extendedValidate()', () => {
@ -148,5 +150,30 @@ describe('extendedValidate', () => {
delete awsDeploy.serverless.service.package.artifact;
});
});
it('should warn if function\'s timeout is greater than 30 and it\'s attached to APIGW', () => {
stateFileMock.service.functions = {
first: {
timeout: 31,
package: {
artifact: 'artifact.zip',
},
events: [{
http: {},
}],
},
};
awsDeploy.serverless.service.package.individually = true;
fileExistsSyncStub.returns(true);
readFileSyncStub.returns(stateFileMock);
return awsDeploy.extendedValidate().then(() => {
const msg = [
'WARNING: Function first has timeout of 31 seconds, however, it\'s ',
'attached to API Gateway so it\'s automatically limited to 30 seconds.',
].join('');
expect(awsDeploy.serverless.cli.log.firstCall.calledWithExactly(msg)).to.be.equal(true);
});
});
});
});

View File

@ -73,6 +73,47 @@ class AwsDeployFunction {
});
}
normalizeArnRole(role) {
if (typeof role === 'string') {
if (role.indexOf(':') === -1) {
const roleResource = this.serverless.service.resources.Resources[role];
if (roleResource.Type !== 'AWS::IAM::Role') {
throw new Error('Provided resource is not IAM Role.');
}
const roleProperties = roleResource.Properties;
const compiledFullRoleName = `${roleProperties.Path || '/'}${roleProperties.RoleName}`;
return this.provider.getAccountId().then((accountId) =>
`arn:aws:iam::${accountId}:role${compiledFullRoleName}`
);
}
return BbPromise.resolve(role);
}
return this.provider.request(
'IAM',
'getRole',
{
RoleName: role['Fn::GetAtt'][0],
},
this.options.stage, this.options.region
).then((data) => data.Arn);
}
callUpdateFunctionConfiguration(params) {
return this.provider.request(
'Lambda',
'updateFunctionConfiguration',
params,
this.options.stage, this.options.region
).then(() => {
this.serverless.cli.log(`Successfully updated function: ${this.options.function}`);
});
}
updateFunctionConfiguration() {
const functionObj = this.options.functionObj;
const serviceObj = this.serverless.service.serviceObject;
@ -97,12 +138,6 @@ class AwsDeployFunction {
params.MemorySize = providerObj.memorySize;
}
if ('role' in functionObj) {
params.Role = functionObj.role;
} else if ('role' in providerObj) {
params.Role = providerObj.role;
}
if ('timeout' in functionObj) {
params.Timeout = functionObj.timeout;
} else if ('timeout' in providerObj) {
@ -148,14 +183,21 @@ class AwsDeployFunction {
params.VpcConfig.SubnetIds = providerObj.vpc.subnetIds;
}
return this.provider.request(
'Lambda',
'updateFunctionConfiguration',
params,
this.options.stage, this.options.region
).then(() => {
this.serverless.cli.log(`Successfully updated function: ${this.options.function}`);
});
if ('role' in functionObj) {
return this.normalizeArnRole(functionObj.role).then(roleArn => {
params.Role = roleArn;
return this.callUpdateFunctionConfiguration(params);
});
} else if ('role' in providerObj) {
return this.normalizeArnRole(providerObj.role).then(roleArn => {
params.Role = roleArn;
return this.callUpdateFunctionConfiguration(params);
});
}
return this.callUpdateFunctionConfiguration(params);
}
deployFunction() {

View File

@ -116,8 +116,73 @@ describe('AwsDeployFunction', () => {
});
});
describe('#normalizeArnRole', () => {
let getAccountIdStub;
let getRoleStub;
beforeEach(() => {
getAccountIdStub = sinon
.stub(awsDeployFunction.provider, 'getAccountId')
.resolves('123456789012');
getRoleStub = sinon
.stub(awsDeployFunction.provider, 'request')
.resolves({ Arn: 'arn:aws:iam::123456789012:role/role_2' });
serverless.service.resources = {
Resources: {
MyCustomRole: {
Type: 'AWS::IAM::Role',
Properties: {
RoleName: 'role_123',
},
},
},
};
});
afterEach(() => {
awsDeployFunction.provider.getAccountId.restore();
awsDeployFunction.provider.request.restore();
serverless.service.resources = undefined;
});
it('should return unmodified ARN if ARN was provided', () => {
const arn = 'arn:aws:iam::123456789012:role/role';
return awsDeployFunction.normalizeArnRole(arn).then((result) => {
expect(getAccountIdStub.calledOnce).to.be.equal(false);
expect(result).to.be.equal(arn);
});
});
it('should return compiled ARN if role name was provided', () => {
const roleName = 'MyCustomRole';
return awsDeployFunction.normalizeArnRole(roleName).then((result) => {
expect(getAccountIdStub.calledOnce).to.be.equal(true);
expect(result).to.be.equal('arn:aws:iam::123456789012:role/role_123');
});
});
it('should return compiled ARN if object role was provided', () => {
const roleObj = {
'Fn::GetAtt': [
'role_2',
'Arn',
],
};
return awsDeployFunction.normalizeArnRole(roleObj).then((result) => {
expect(getRoleStub.calledOnce).to.be.equal(true);
expect(getAccountIdStub.calledOnce).to.be.equal(false);
expect(result).to.be.equal('arn:aws:iam::123456789012:role/role_2');
});
});
});
describe('#updateFunctionConfiguration', () => {
let updateFunctionConfigurationStub;
let normalizeArnRoleStub;
const options = {
stage: 'dev',
region: 'us-east-1',
@ -131,10 +196,15 @@ describe('AwsDeployFunction', () => {
updateFunctionConfigurationStub = sinon
.stub(awsDeployFunction.provider, 'request')
.resolves();
normalizeArnRoleStub = sinon
.stub(awsDeployFunction, 'normalizeArnRole')
.resolves('arn:aws:us-east-1:123456789012:role/role');
});
afterEach(() => {
awsDeployFunction.provider.request.restore();
awsDeployFunction.normalizeArnRole.restore();
awsDeployFunction.serverless.service.provider.timeout = undefined;
awsDeployFunction.serverless.service.provider.memorySize = undefined;
awsDeployFunction.serverless.service.provider.role = undefined;
@ -162,6 +232,8 @@ describe('AwsDeployFunction', () => {
awsDeployFunction.options = options;
return awsDeployFunction.updateFunctionConfiguration().then(() => {
expect(normalizeArnRoleStub.calledOnce).to.be.equal(true);
expect(normalizeArnRoleStub.calledWithExactly('arn:aws:iam::123456789012:role/Admin'));
expect(updateFunctionConfigurationStub.calledOnce).to.be.equal(true);
expect(updateFunctionConfigurationStub.calledWithExactly(
'Lambda',
@ -179,7 +251,7 @@ describe('AwsDeployFunction', () => {
FunctionName: 'first',
KMSKeyArn: 'arn:aws:kms:us-east-1:123456789012',
MemorySize: 128,
Role: 'arn:aws:iam::123456789012:role/Admin',
Role: 'arn:aws:us-east-1:123456789012:role/role',
Timeout: 3,
VpcConfig: {
SecurityGroupIds: ['1'],
@ -247,6 +319,8 @@ describe('AwsDeployFunction', () => {
awsDeployFunction.options = options;
return awsDeployFunction.updateFunctionConfiguration().then(() => {
expect(normalizeArnRoleStub.calledOnce).to.be.equal(true);
expect(normalizeArnRoleStub.calledWithExactly('role'));
expect(updateFunctionConfigurationStub.calledOnce).to.be.equal(true);
expect(updateFunctionConfigurationStub.calledWithExactly(
'Lambda',
@ -260,7 +334,7 @@ describe('AwsDeployFunction', () => {
},
Timeout: 12,
MemorySize: 512,
Role: 'role',
Role: 'arn:aws:us-east-1:123456789012:role/role',
},
awsDeployFunction.options.stage,
awsDeployFunction.options.region

View File

@ -58,6 +58,8 @@ if __name__ == '__main__':
handler = getattr(module, args.handler_name)
input = json.load(sys.stdin)
if sys.platform != 'win32':
sys.stdin = open('/dev/tty')
context = FakeLambdaContext(**input.get('context', {}))
result = handler(input['event'], context)
sys.stdout.write(json.dumps(result, indent=4))

View File

@ -285,6 +285,10 @@ module.exports = {
getLambdaAlexaSkillPermissionLogicalId(functionName) {
return `${this.getNormalizedFunctionName(functionName)}LambdaPermissionAlexaSkill`;
},
getLambdaAlexaSmartHomePermissionLogicalId(functionName, alexaSmartHomeIndex) {
return `${this.getNormalizedFunctionName(functionName)}LambdaPermissionAlexaSmartHome${
alexaSmartHomeIndex}`;
},
getLambdaCloudWatchLogPermissionLogicalId(functionName, logsIndex) {
return `${this.getNormalizedFunctionName(functionName)
}LambdaPermissionLogsSubscriptionFilterCloudWatchLog${logsIndex}`;

View File

@ -469,6 +469,14 @@ describe('#naming()', () => {
});
});
describe('#getLambdaAlexaSmartHomePermissionLogicalId()', () => {
it('should normalize the function name and append the standard suffix',
() => {
expect(sdk.naming.getLambdaAlexaSmartHomePermissionLogicalId('functionName', 0))
.to.equal('FunctionNameLambdaPermissionAlexaSmartHome0');
});
});
describe('#getLambdaSnsSubscriptionLogicalId()', () => {
it('should normalize the function name and append the standard suffix', () => {
expect(sdk.naming.getLambdaSnsSubscriptionLogicalId('functionName', 'topicName'))

View File

@ -0,0 +1,87 @@
'use strict';
const _ = require('lodash');
class AwsCompileAlexaSmartHomeEvents {
constructor(serverless) {
this.serverless = serverless;
this.provider = this.serverless.getProvider('aws');
this.hooks = {
'package:compileEvents': this.compileAlexaSmartHomeEvents.bind(this),
};
}
compileAlexaSmartHomeEvents() {
this.serverless.service.getAllFunctions().forEach((functionName) => {
const functionObj = this.serverless.service.getFunction(functionName);
let alexaSmartHomeNumberInFunction = 0;
if (functionObj.events) {
functionObj.events.forEach(event => {
if (event.alexaSmartHome) {
alexaSmartHomeNumberInFunction++;
let EventSourceToken;
let Action;
if (typeof event.alexaSmartHome === 'object') {
if (!event.alexaSmartHome.appId) {
const errorMessage = [
`Missing "appId" property for alexaSmartHome event in function ${functionName}`,
' The correct syntax is: appId: amzn1.ask.skill.xxxx-xxxx',
' OR an object with "appId" property.',
' Please check the docs for more info.',
].join('');
throw new this.serverless.classes
.Error(errorMessage);
}
EventSourceToken = event.alexaSmartHome.appId;
Action = event.alexaSmartHome.enabled !== false ?
'lambda:InvokeFunction' : 'lambda:DisableInvokeFunction';
} else if (typeof event.alexaSmartHome === 'string') {
EventSourceToken = event.alexaSmartHome;
Action = 'lambda:InvokeFunction';
} else {
const errorMessage = [
`Alexa Smart Home event of function "${functionName}" is not an object or string.`,
' The correct syntax is: alexaSmartHome.',
' Please check the docs for more info.',
].join('');
throw new this.serverless.classes.Error(errorMessage);
}
const lambdaLogicalId = this.provider.naming
.getLambdaLogicalId(functionName);
const permissionTemplate = {
Type: 'AWS::Lambda::Permission',
Properties: {
FunctionName: {
'Fn::GetAtt': [
lambdaLogicalId,
'Arn',
],
},
Action: Action.replace(/\\n|\\r/g, ''),
Principal: 'alexa-connectedhome.amazon.com',
EventSourceToken: EventSourceToken.replace(/\\n|\\r/g, ''),
},
};
const lambdaPermissionLogicalId = this.provider.naming
.getLambdaAlexaSmartHomePermissionLogicalId(functionName,
alexaSmartHomeNumberInFunction);
const permissionCloudFormationResource = {
[lambdaPermissionLogicalId]: permissionTemplate,
};
_.merge(this.serverless.service.provider.compiledCloudFormationTemplate.Resources,
permissionCloudFormationResource);
}
});
}
});
}
}
module.exports = AwsCompileAlexaSmartHomeEvents;

View File

@ -0,0 +1,221 @@
'use strict';
const expect = require('chai').expect;
const AwsProvider = require('../../../../provider/awsProvider');
const AwsCompileAlexaSmartHomeEvents = require('./index');
const Serverless = require('../../../../../../Serverless');
describe('AwsCompileAlexaSmartHomeEvents', () => {
let serverless;
let awsCompileAlexaSmartHomeEvents;
beforeEach(() => {
serverless = new Serverless();
serverless.service.provider.compiledCloudFormationTemplate = { Resources: {} };
serverless.setProvider('aws', new AwsProvider(serverless));
awsCompileAlexaSmartHomeEvents = new AwsCompileAlexaSmartHomeEvents(serverless);
awsCompileAlexaSmartHomeEvents.serverless.service.service = 'new-service';
});
describe('#constructor()', () => {
it('should set the provider variable to an instance of AwsProvider', () =>
expect(awsCompileAlexaSmartHomeEvents.provider).to.be.instanceof(AwsProvider));
});
describe('#compileAlexaSmartHomeEvents()', () => {
it('should throw an error if alexaSmartHome event type is not a string or an object', () => {
awsCompileAlexaSmartHomeEvents.serverless.service.functions = {
first: {
events: [
{
alexaSmartHome: 42,
},
],
},
};
expect(() => awsCompileAlexaSmartHomeEvents.compileAlexaSmartHomeEvents()).to.throw(Error);
});
it('should throw an error if the "appId" property is not given', () => {
awsCompileAlexaSmartHomeEvents.serverless.service.functions = {
first: {
events: [
{
alexaSmartHome: {
appId: null,
},
},
],
},
};
expect(() => awsCompileAlexaSmartHomeEvents.compileAlexaSmartHomeEvents()).to.throw(Error);
});
it('should create corresponding resources when alexaSmartHome events are given', () => {
awsCompileAlexaSmartHomeEvents.serverless.service.functions = {
first: {
events: [
{
alexaSmartHome: {
appId: 'amzn1.ask.skill.xx-xx-xx-xx',
enabled: false,
},
},
{
alexaSmartHome: {
appId: 'amzn1.ask.skill.yy-yy-yy-yy',
enabled: true,
},
},
{
alexaSmartHome: 'amzn1.ask.skill.zz-zz-zz-zz',
},
],
},
};
awsCompileAlexaSmartHomeEvents.compileAlexaSmartHomeEvents();
expect(awsCompileAlexaSmartHomeEvents.serverless.service
.provider.compiledCloudFormationTemplate.Resources
.FirstLambdaPermissionAlexaSmartHome1.Type
).to.equal('AWS::Lambda::Permission');
expect(awsCompileAlexaSmartHomeEvents.serverless.service
.provider.compiledCloudFormationTemplate.Resources
.FirstLambdaPermissionAlexaSmartHome2.Type
).to.equal('AWS::Lambda::Permission');
expect(awsCompileAlexaSmartHomeEvents.serverless.service
.provider.compiledCloudFormationTemplate.Resources
.FirstLambdaPermissionAlexaSmartHome3.Type
).to.equal('AWS::Lambda::Permission');
expect(awsCompileAlexaSmartHomeEvents.serverless.service
.provider.compiledCloudFormationTemplate.Resources
.FirstLambdaPermissionAlexaSmartHome1.Properties.FunctionName
).to.deep.equal({ 'Fn::GetAtt': ['FirstLambdaFunction', 'Arn'] });
expect(awsCompileAlexaSmartHomeEvents.serverless.service
.provider.compiledCloudFormationTemplate.Resources
.FirstLambdaPermissionAlexaSmartHome2.Properties.FunctionName
).to.deep.equal({ 'Fn::GetAtt': ['FirstLambdaFunction', 'Arn'] });
expect(awsCompileAlexaSmartHomeEvents.serverless.service
.provider.compiledCloudFormationTemplate.Resources
.FirstLambdaPermissionAlexaSmartHome3.Properties.FunctionName
).to.deep.equal({ 'Fn::GetAtt': ['FirstLambdaFunction', 'Arn'] });
expect(awsCompileAlexaSmartHomeEvents.serverless.service
.provider.compiledCloudFormationTemplate.Resources
.FirstLambdaPermissionAlexaSmartHome1.Properties.Action
).to.equal('lambda:DisableInvokeFunction');
expect(awsCompileAlexaSmartHomeEvents.serverless.service
.provider.compiledCloudFormationTemplate.Resources
.FirstLambdaPermissionAlexaSmartHome2.Properties.Action
).to.equal('lambda:InvokeFunction');
expect(awsCompileAlexaSmartHomeEvents.serverless.service
.provider.compiledCloudFormationTemplate.Resources
.FirstLambdaPermissionAlexaSmartHome3.Properties.Action
).to.equal('lambda:InvokeFunction');
expect(awsCompileAlexaSmartHomeEvents.serverless.service
.provider.compiledCloudFormationTemplate.Resources
.FirstLambdaPermissionAlexaSmartHome1.Properties.Principal
).to.equal('alexa-connectedhome.amazon.com');
expect(awsCompileAlexaSmartHomeEvents.serverless.service
.provider.compiledCloudFormationTemplate.Resources
.FirstLambdaPermissionAlexaSmartHome2.Properties.Principal
).to.equal('alexa-connectedhome.amazon.com');
expect(awsCompileAlexaSmartHomeEvents.serverless.service
.provider.compiledCloudFormationTemplate.Resources
.FirstLambdaPermissionAlexaSmartHome3.Properties.Principal
).to.equal('alexa-connectedhome.amazon.com');
expect(awsCompileAlexaSmartHomeEvents.serverless.service
.provider.compiledCloudFormationTemplate.Resources
.FirstLambdaPermissionAlexaSmartHome1.Properties.EventSourceToken
).to.equal('amzn1.ask.skill.xx-xx-xx-xx');
expect(awsCompileAlexaSmartHomeEvents.serverless.service
.provider.compiledCloudFormationTemplate.Resources
.FirstLambdaPermissionAlexaSmartHome2.Properties.EventSourceToken
).to.equal('amzn1.ask.skill.yy-yy-yy-yy');
expect(awsCompileAlexaSmartHomeEvents.serverless.service
.provider.compiledCloudFormationTemplate.Resources
.FirstLambdaPermissionAlexaSmartHome3.Properties.EventSourceToken
).to.equal('amzn1.ask.skill.zz-zz-zz-zz');
});
it('should respect enabled variable, defaulting to true', () => {
awsCompileAlexaSmartHomeEvents.serverless.service.functions = {
first: {
events: [
{
alexaSmartHome: {
appId: 'amzn1.ask.skill.xx-xx-xx-xx',
enabled: false,
},
},
{
alexaSmartHome: {
appId: 'amzn1.ask.skill.yy-yy-yy-yy',
enabled: true,
},
},
{
alexaSmartHome: {
appId: 'amzn1.ask.skill.jj-jj-jj-jj',
},
},
{
alexaSmartHome: 'amzn1.ask.skill.zz-zz-zz-zz',
},
],
},
};
awsCompileAlexaSmartHomeEvents.compileAlexaSmartHomeEvents();
expect(awsCompileAlexaSmartHomeEvents.serverless.service
.provider.compiledCloudFormationTemplate.Resources
.FirstLambdaPermissionAlexaSmartHome1.Properties.Action
).to.equal('lambda:DisableInvokeFunction');
expect(awsCompileAlexaSmartHomeEvents.serverless.service
.provider.compiledCloudFormationTemplate.Resources
.FirstLambdaPermissionAlexaSmartHome2.Properties.Action
).to.equal('lambda:InvokeFunction');
expect(awsCompileAlexaSmartHomeEvents.serverless.service
.provider.compiledCloudFormationTemplate.Resources
.FirstLambdaPermissionAlexaSmartHome3.Properties.Action
).to.equal('lambda:InvokeFunction');
expect(awsCompileAlexaSmartHomeEvents.serverless.service
.provider.compiledCloudFormationTemplate.Resources
.FirstLambdaPermissionAlexaSmartHome4.Properties.Action
).to.equal('lambda:InvokeFunction');
});
it('should not create corresponding resources when alexaSmartHome events are not given', () => {
awsCompileAlexaSmartHomeEvents.serverless.service.functions = {
first: {
events: [
'alexaSkill',
],
},
};
awsCompileAlexaSmartHomeEvents.compileAlexaSmartHomeEvents();
expect(
awsCompileAlexaSmartHomeEvents.serverless.service.provider.compiledCloudFormationTemplate
.Resources
).to.deep.equal({});
});
it('should not create corresponding resources when events are not given', () => {
awsCompileAlexaSmartHomeEvents.serverless.service.functions = {
first: {},
};
awsCompileAlexaSmartHomeEvents.compileAlexaSmartHomeEvents();
expect(
awsCompileAlexaSmartHomeEvents.serverless.service.provider.compiledCloudFormationTemplate
.Resources
).to.deep.equal({});
});
});
});

View File

@ -37,7 +37,7 @@ module.exports = {
'/invocations',
],
] };
authorizerProperties.Type = 'TOKEN';
authorizerProperties.Type = authorizer.type ? authorizer.type.toUpperCase() : 'TOKEN';
}
_.merge(this.serverless.service.provider.compiledCloudFormationTemplate.Resources, {

View File

@ -96,6 +96,53 @@ describe('#compileAuthorizers()', () => {
});
});
it('should apply optional provided type value to Authorizer Type', () => {
awsCompileApigEvents.validated.events = [{
http: {
path: 'users/create',
method: 'POST',
authorizer: {
name: 'authorizer',
arn: 'foo',
resultTtlInSeconds: 500,
identityValidationExpression: 'regex',
type: 'request',
},
},
}];
return awsCompileApigEvents.compileAuthorizers().then(() => {
const resource = awsCompileApigEvents.serverless.service.provider
.compiledCloudFormationTemplate.Resources.AuthorizerApiGatewayAuthorizer;
expect(resource.Type).to.equal('AWS::ApiGateway::Authorizer');
expect(resource.Properties.Type).to.equal('REQUEST');
});
});
it('should apply TOKEN as authorizer Type when not given a type value', () => {
awsCompileApigEvents.validated.events = [{
http: {
path: 'users/create',
method: 'POST',
authorizer: {
name: 'authorizer',
arn: 'foo',
resultTtlInSeconds: 500,
identityValidationExpression: 'regex',
},
},
}];
return awsCompileApigEvents.compileAuthorizers().then(() => {
const resource = awsCompileApigEvents.serverless.service.provider
.compiledCloudFormationTemplate.Resources.AuthorizerApiGatewayAuthorizer;
expect(resource.Type).to.equal('AWS::ApiGateway::Authorizer');
expect(resource.Properties.Type).to.equal('TOKEN');
});
});
it('should create a valid cognito user pool authorizer', () => {
awsCompileApigEvents.validated.events = [{
http: {

View File

@ -26,6 +26,8 @@ module.exports = {
if (event.http.private) {
template.Properties.ApiKeyRequired = true;
} else {
template.Properties.ApiKeyRequired = false;
}
const methodLogicalId = this.provider.naming

View File

@ -538,6 +538,24 @@ describe('#compileMethods()', () => {
});
});
it('should set api key as not required if private property is not specified', () => {
awsCompileApigEvents.validated.events = [
{
functionName: 'First',
http: {
path: 'users/create',
method: 'post',
},
},
];
return awsCompileApigEvents.compileMethods().then(() => {
expect(
awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate
.Resources.ApiGatewayMethodUsersCreatePost.Properties.ApiKeyRequired
).to.equal(false);
});
});
it('should set the correct lambdaUri', () => {
awsCompileApigEvents.validated.events = [
{

View File

@ -251,6 +251,10 @@ module.exports = {
throw new this.serverless.classes.Error('Please provide either an authorizer name or ARN');
}
if (!type) {
type = authorizer.type;
}
resultTtlInSeconds = Number.parseInt(authorizer.resultTtlInSeconds, 10);
resultTtlInSeconds = Number.isNaN(resultTtlInSeconds) ? 300 : resultTtlInSeconds;
claims = authorizer.claims || [];

View File

@ -406,6 +406,33 @@ describe('#validate()', () => {
expect(authorizer.identityValidationExpression).to.equal('foo');
});
it('should accept authorizer config with a type', () => {
awsCompileApigEvents.serverless.service.functions = {
foo: {},
first: {
events: [
{
http: {
method: 'GET',
path: 'foo/bar',
authorizer: {
name: 'foo',
type: 'request',
resultTtlInSeconds: 500,
identitySource: 'method.request.header.Custom',
identityValidationExpression: 'foo',
},
},
},
],
},
};
const validated = awsCompileApigEvents.validate();
const authorizer = validated.events[0].http.authorizer;
expect(authorizer.type).to.equal('request');
});
it('should accept authorizer config when resultTtlInSeconds is 0', () => {
awsCompileApigEvents.serverless.service.functions = {
foo: {},

View File

@ -167,11 +167,11 @@ describe('AwsProvider', () => {
return {
send(cb) {
if (first) {
first = false;
cb(error);
} else {
cb(undefined, {});
}
first = false;
},
};
}

View File

@ -25,10 +25,21 @@ class AwsRollback {
this.hooks = {
'before:rollback:initialize': () => BbPromise.bind(this)
.then(this.validate),
'rollback:rollback': () => BbPromise.bind(this)
.then(this.setBucketName)
.then(this.setStackToUpdate)
.then(this.updateStack),
'rollback:rollback': () => {
if (!this.options.timestamp) {
const command = this.serverless.pluginManager.spawn('deploy:list');
this.serverless.cli.log([
'Use a timestamp from the deploy list below to rollback to a specific version.',
'Run `sls rollback -t YourTimeStampHere`',
].join('\n'));
return command;
}
return BbPromise.bind(this)
.then(this.setBucketName)
.then(this.setStackToUpdate)
.then(this.updateStack);
},
};
}

View File

@ -10,9 +10,11 @@ const sinon = require('sinon');
describe('AwsRollback', () => {
let awsRollback;
let s3Key;
let spawnStub;
let serverless;
beforeEach(() => {
const serverless = new Serverless();
serverless = new Serverless();
const options = {
stage: 'dev',
region: 'us-east-1',
@ -20,11 +22,16 @@ describe('AwsRollback', () => {
};
serverless.setProvider('aws', new AwsProvider(serverless));
serverless.service.service = 'rollback';
spawnStub = sinon.stub(serverless.pluginManager, 'spawn');
awsRollback = new AwsRollback(serverless, options);
awsRollback.serverless.cli = new serverless.classes.CLI();
s3Key = `serverless/${serverless.service.service}/${options.stage}`;
});
afterEach(() => {
serverless.pluginManager.spawn.restore();
});
describe('#constructor()', () => {
it('should have hooks', () => expect(awsRollback.hooks).to.be.not.empty);
@ -58,6 +65,15 @@ describe('AwsRollback', () => {
.to.be.equal(true);
});
});
it('should run "deploy:list" if timestamp is not specified', () => {
const spawnDeployListStub = spawnStub.withArgs('deploy:list').resolves();
awsRollback.options.timestamp = undefined;
return awsRollback.hooks['rollback:rollback']().then(() => {
expect(spawnDeployListStub.calledOnce).to.be.equal(true);
});
});
});
describe('#setStackToUpdate()', () => {

View File

@ -4,7 +4,9 @@ const BbPromise = require('bluebird');
const path = require('path');
const fse = require('fs-extra');
const _ = require('lodash');
const userStats = require('../../utils/userStats');
const download = require('../../utils/downloadTemplateFromRepo');
// class wide constants
const validTemplates = [
@ -31,6 +33,7 @@ const validTemplates = [
'openwhisk-swift',
'spotinst-nodejs',
'spotinst-python',
'spotinst-ruby',
'webtasks-nodejs',
'plugin',
@ -56,9 +59,12 @@ class Create {
options: {
template: {
usage: `Template for the service. Available templates: ${humanReadableTemplateList}`,
required: true,
shortcut: 't',
},
'template-url': {
usage: 'Template URL for the service. Supports: GitHub, BitBucket',
shortcut: 'u',
},
path: {
usage: 'The path where the service should be created (e.g. --path my-service)',
shortcut: 'p',
@ -79,6 +85,42 @@ class Create {
create() {
this.serverless.cli.log('Generating boilerplate...');
if ('template' in this.options) {
this.createFromTemplate();
} else if ('template-url' in this.options) {
return download.downloadTemplateFromRepo(
this.options['template-url'],
this.options.name,
this.options.path
)
.then(dirName => {
const message = [
`Successfully installed "${dirName}" `,
`${this.options.name && this.options.name !== dirName ? `as "${dirName}"` : ''}`,
].join('');
this.serverless.cli.log(message);
userStats.track('service_created', {
template: this.options.template,
serviceName: this.options.name,
});
})
.catch(err => {
throw new this.serverless.classes.Error(err);
});
} else {
const errorMessage = [
'You must either pass a template name (--template) or a ',
'a URL (--template-url).',
].join('');
throw new this.serverless.classes.Error(errorMessage);
}
return BbPromise.resolve();
}
createFromTemplate() {
const notPlugin = this.options.template !== 'plugin';
if (validTemplates.indexOf(this.options.template) === -1) {
@ -180,8 +222,6 @@ class Create {
this.serverless.cli
.log('NOTE: Please update the "service" property in serverless.yml with your service name');
}
return BbPromise.resolve();
}
}

View File

@ -9,6 +9,7 @@ const Serverless = require('../../Serverless');
const sinon = require('sinon');
const testUtils = require('../../../tests/utils');
const walkDirSync = require('../../utils/fs/walkDirSync');
const download = require('./../../utils/downloadTemplateFromRepo');
describe('Create', () => {
let create;
@ -462,6 +463,19 @@ describe('Create', () => {
});
});
it('should generate scaffolding for "spotinst-ruby" template', () => {
process.chdir(tmpDir);
create.options.template = 'spotinst-ruby';
return create.create().then(() => {
const dirContent = fs.readdirSync(tmpDir);
expect(dirContent).to.include('package.json');
expect(dirContent).to.include('serverless.yml');
expect(dirContent).to.include('handler.rb');
expect(dirContent).to.include('.gitignore');
});
});
it('should generate scaffolding for "webtasks-nodejs" template', () => {
process.chdir(tmpDir);
create.options.template = 'webtasks-nodejs';
@ -606,5 +620,30 @@ describe('Create', () => {
expect(() => create.create()).to.throw(Error);
});
it('should reject if download fails', (done) => {
sinon.stub(download, 'downloadTemplateFromRepo');
create.options = {};
create.options['template-url'] = 'https://github.com/serverless/serverless';
download.downloadTemplateFromRepo.rejects(new Error('Wrong'));
create
.create()
.catch(() => download.downloadTemplateFromRepo.restore())
.then(() => done());
});
it('should resolve if download succeeds', () => {
sinon.stub(download, 'downloadTemplateFromRepo');
create.options = {};
create.options['template-url'] = 'https://github.com/serverless/serverless';
download.downloadTemplateFromRepo.resolves();
return create.create().catch(() => download.downloadTemplateFromRepo.restore());
});
});
});

View File

@ -68,6 +68,7 @@ functions:
# - sns: greeter-topic
# - stream: arn:aws:dynamodb:region:XXXXXX:table/foo/stream/1970-01-01T00:00:00.000
# - alexaSkill
# - alexaSmartHome: amzn1.ask.skill.xx-xx-xx-xx
# - iot:
# sql: "SELECT * FROM 'some_topic'"
# - cloudwatchEvent:

View File

@ -68,6 +68,7 @@ functions:
# - sns: greeter-topic
# - stream: arn:aws:dynamodb:region:XXXXXX:table/foo/stream/1970-01-01T00:00:00.000
# - alexaSkill
# - alexaSmartHome: amzn1.ask.skill.xx-xx-xx-xx
# - iot:
# sql: "SELECT * FROM 'some_topic'"
# - cloudwatchEvent:

View File

@ -65,6 +65,7 @@ functions:
# - sns: greeter-topic
# - stream: arn:aws:dynamodb:region:XXXXXX:table/foo/stream/1970-01-01T00:00:00.000
# - alexaSkill
# - alexaSmartHome: amzn1.ask.skill.xx-xx-xx-xx
# - iot:
# sql: "SELECT * FROM 'some_topic'"
# - cloudwatchEvent:

View File

@ -65,6 +65,7 @@ functions:
# - sns: greeter-topic
# - stream: arn:aws:dynamodb:region:XXXXXX:table/foo/stream/1970-01-01T00:00:00.000
# - alexaSkill
# - alexaSmartHome: amzn1.ask.skill.xx-xx-xx-xx
# - iot:
# sql: "SELECT * FROM 'some_topic'"
# - cloudwatchEvent:

View File

@ -65,6 +65,7 @@ functions:
# - sns: greeter-topic
# - stream: arn:aws:dynamodb:region:XXXXXX:table/foo/stream/1970-01-01T00:00:00.000
# - alexaSkill
# - alexaSmartHome: amzn1.ask.skill.xx-xx-xx-xx
# - iot:
# sql: "SELECT * FROM 'some_topic'"
# - cloudwatchEvent:

View File

@ -65,6 +65,7 @@ functions:
# - sns: greeter-topic
# - stream: arn:aws:dynamodb:region:XXXXXX:table/foo/stream/1970-01-01T00:00:00.000
# - alexaSkill
# - alexaSmartHome: amzn1.ask.skill.xx-xx-xx-xx
# - iot:
# sql: "SELECT * FROM 'some_topic'"
# - cloudwatchEvent:

View File

@ -61,6 +61,7 @@ functions:
# - sns: greeter-topic
# - stream: arn:aws:dynamodb:region:XXXXXX:table/foo/stream/1970-01-01T00:00:00.000
# - alexaSkill
# - alexaSmartHome: amzn1.ask.skill.xx-xx-xx-xx
# - iot:
# sql: "SELECT * FROM 'some_topic'"
# - cloudwatchEvent:
@ -91,4 +92,4 @@ functions:
# Outputs:
# NewOutput:
# Description: "Description for the output"
# Value: "Some output value"
# Value: "Some output value"

View File

@ -11,7 +11,7 @@
"babel-plugin-transform-runtime": "^6.23.0",
"babel-polyfill": "^6.23.0",
"babel-preset-env": "^1.6.0",
"serverless-webpack": "^2.2.0",
"serverless-webpack": "^3.1.1",
"webpack": "^3.3.0"
},
"author": "The serverless webpack authors (https://github.com/elastic-coders/serverless-webpack)",

View File

@ -10,14 +10,8 @@ provider:
runtime: nodejs6.10
functions:
# Example without LAMBDA-PROXY integration
# Invoking locally:
# sls webpack invoke -f first
first:
handler: first.hello
# Example with LAMBDA-PROXY integration
# Invoking locally:
# sls webpack invoke -f second
second:
handler: second.hello
events:

View File

@ -70,6 +70,7 @@ functions:
# - sns: greeter-topic
# - stream: arn:aws:dynamodb:region:XXXXXX:table/foo/stream/1970-01-01T00:00:00.000
# - alexaSkill
# - alexaSmartHome: amzn1.ask.skill.xx-xx-xx-xx
# - iot:
# sql: "SELECT * FROM 'some_topic'"
# - cloudwatchEvent:

View File

@ -70,6 +70,7 @@ functions:
# - sns: greeter-topic
# - stream: arn:aws:dynamodb:region:XXXXXX:table/foo/stream/1970-01-01T00:00:00.000
# - alexaSkill
# - alexaSmartHome: amzn1.ask.skill.xx-xx-xx-xx
# - iot:
# sql: "SELECT * FROM 'some_topic'"
# - cloudwatchEvent:

View File

@ -70,6 +70,7 @@ functions:
# - sns: greeter-topic
# - stream: arn:aws:dynamodb:region:XXXXXX:table/foo/stream/1970-01-01T00:00:00.000
# - alexaSkill
# - alexaSmartHome: amzn1.ask.skill.xx-xx-xx-xx
# - iot:
# sql: "SELECT * FROM 'some_topic'"
# - cloudwatchEvent:

View File

@ -67,6 +67,7 @@ functions:
# - sns: greeter-topic
# - stream: arn:aws:dynamodb:region:XXXXXX:table/foo/stream/1970-01-01T00:00:00.000
# - alexaSkill
# - alexaSmartHome: amzn1.ask.skill.xx-xx-xx-xx
# - iot:
# sql: "SELECT * FROM 'some_topic'"
# - cloudwatchEvent:

View File

@ -7,7 +7,7 @@
"serverless",
"spotinst"
],
"dependencies": {
"devDependencies": {
"serverless-spotinst-functions": "*"
}
}

View File

@ -20,10 +20,14 @@ provider:
functions:
hello:
runtime: nodejs4.8
runtime: nodejs8.3
handler: handler.main
memory: 128
timeout: 30
access: private
# cron: # Setup scheduled trigger with cron expression
# active: true
# value: '* * * * *'
# extend the framework using plugins listed here:
# https://github.com/serverless/plugins

View File

@ -7,7 +7,7 @@
"serverless",
"spotinst"
],
"dependencies": {
"devDependencies": {
"serverless-spotinst-functions": "*"
}
}

View File

@ -24,6 +24,10 @@ functions:
handler: handler.main
memory: 128
timeout: 30
access: private
# cron: # Setup scheduled trigger with cron expression
# active: true
# value: '* * * * *'
# extend the framework using plugins listed here:
# https://github.com/serverless/plugins

View File

@ -0,0 +1,6 @@
# package directories
node_modules
jspm_packages
# Serverless directories
.serverless

View File

@ -0,0 +1,13 @@
# Implement your function here.
# The function will get the request as parameter.
# The function should return an Hash
def main(args)
queryparams = args["query"]
body = args["body"]
{
:statusCode => 200,
:body => '{"hello":"from Ruby2.4.1 function"}'
}
end

View File

@ -0,0 +1,13 @@
{
"name": "spotionst-ruby",
"version": "1.0.0",
"description": "Spotinst Functions Ruby sample for serverless framework service.",
"main": "handler.js",
"keywords": [
"serverless",
"spotinst"
],
"devDependencies": {
"serverless-spotinst-functions": "*"
}
}

View File

@ -0,0 +1,35 @@
# 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: spotinst-ruby # NOTE: update this with your service name
provider:
name: spotinst
spotinst:
#environment: <env-XXXX> # NOTE: Remember to add the environment ID
functions:
hello:
runtime: ruby2.4.1
handler: handler.main
memory: 128
timeout: 30
access: private
# cron: # Setup scheduled trigger with cron expression
# active: true
# value: '* * * * *'
# extend the framework using plugins listed here:
# https://github.com/serverless/plugins
plugins:
- serverless-spotinst-functions

View File

@ -1,12 +1,10 @@
'use strict';
const BbPromise = require('bluebird');
const path = require('path');
const URL = require('url');
const download = require('download');
const fse = require('fs-extra');
const os = require('os');
const userStats = require('../../utils/userStats');
const downloadTemplateFromRepo = require('../../utils/downloadTemplateFromRepo')
.downloadTemplateFromRepo;
class Install {
constructor(serverless, options) {
@ -37,130 +35,23 @@ class Install {
'install:install': () => BbPromise.bind(this)
.then(this.install),
};
this.renameService = (name, servicePath) => {
const serviceFile = path.join(servicePath, 'serverless.yml');
const packageFile = path.join(servicePath, 'package.json');
if (!this.serverless.utils.fileExistsSync(serviceFile)) {
const errorMessage = [
'serverless.yml not found in',
` ${servicePath}`,
].join('');
throw new this.serverless.classes.Error(errorMessage);
}
const serverlessYml =
fse.readFileSync(serviceFile, 'utf-8')
.replace(/service\s*:.+/gi, (match) => {
const fractions = match.split('#');
fractions[0] = `service: ${name}`;
return fractions.join(' #');
});
fse.writeFileSync(serviceFile, serverlessYml);
if (this.serverless.utils.fileExistsSync(packageFile)) {
const json = this.serverless.utils.readFileSync(packageFile);
this.serverless.utils.writeFile(packageFile, Object.assign(json, { name }));
}
};
}
install() {
const url = URL.parse(this.options.url.replace(/\/$/, ''));
return downloadTemplateFromRepo(this.options.url, this.options.name)
.then(dirName => {
const message = [
`Successfully installed "${dirName}" `,
`${this.options.name && this.options.name !== dirName ? `as "${dirName}"` : ''}`,
].join('');
userStats.track('service_installed', {
data: { // will be updated with core analtyics lib
url: this.options.url,
},
});
// check if url parameter is a valid url
if (!url.host) {
throw new this.serverless.classes.Error('The URL you passed is not a valid URL');
}
const parts = url.pathname.split('/');
const parsedGitHubUrl = {
owner: parts[1],
repo: parts[2],
branch: parts[4] || 'master',
};
// validate if given url is a valid GitHub url
if (url.hostname !== 'github.com' || !parsedGitHubUrl.owner || !parsedGitHubUrl.repo) {
const errorMessage = [
'The URL must be a valid GitHub URL in the following format:',
' https://github.com/serverless/serverless',
].join('');
throw new this.serverless.classes.Error(errorMessage);
}
const downloadUrl = [
'https://github.com/',
parsedGitHubUrl.owner,
'/',
parsedGitHubUrl.repo,
'/archive/',
parsedGitHubUrl.branch,
'.zip',
].join('');
const endIndex = parts.length - 1;
let dirName;
let serviceName;
let downloadServicePath;
// check if it's a directory or the whole repository
if (parts.length > 4) {
serviceName = parts[endIndex];
dirName = this.options.name || parts[endIndex];
// download the repo into a temporary directory
downloadServicePath = path.join(os.tmpdir(), parsedGitHubUrl.repo);
} else {
serviceName = parsedGitHubUrl.repo;
dirName = this.options.name || parsedGitHubUrl.repo;
downloadServicePath = path.join(process.cwd(), dirName);
}
const servicePath = path.join(process.cwd(), dirName);
const renamed = dirName !== (parts.length > 4 ? parts[endIndex] : parsedGitHubUrl.repo);
if (this.serverless.utils.dirExistsSync(path.join(process.cwd(), dirName))) {
const errorMessage = `A folder named "${dirName}" already exists.`;
throw new this.serverless.classes.Error(errorMessage);
}
this.serverless.cli.log(`Downloading and installing "${serviceName}"...`);
const that = this;
// download service
return download(
downloadUrl,
downloadServicePath,
{ timeout: 30000, extract: true, strip: 1, mode: '755' }
).then(() => {
// if it's a directory inside of git
if (parts.length > 4) {
let directory = downloadServicePath;
for (let i = 5; i <= endIndex; i++) {
directory = path.join(directory, parts[i]);
}
that.serverless.utils
.copyDirContentsSync(directory, servicePath);
fse.removeSync(downloadServicePath);
}
}).then(() => {
if (!renamed) return BbPromise.resolve();
return this.renameService(dirName, servicePath);
}).then(() => {
let message = `Successfully installed "${serviceName}"`;
userStats.track('service_installed', {
data: { // will be updated with core analtyics lib
url: this.options.url,
},
this.serverless.cli.log(message);
});
if (renamed) message = `${message} as "${dirName}"`;
that.serverless.cli.log(message);
});
}
}

Some files were not shown because too many files have changed in this diff Show More