mirror of
https://github.com/serverless/serverless.git
synced 2026-01-25 15:07:39 +00:00
Merge branch 'master' into travis-keys
This commit is contained in:
commit
4ccf96dfdc
@ -332,10 +332,6 @@ Please note that those are the API keys names, not the actual values. Once you d
|
||||
|
||||
Clients connecting to this Rest API will then need to set any of these API keys values in the `x-api-key` header of their request. This is only necessary for functions where the `private` property is set to true.
|
||||
|
||||
## Lambda Integration
|
||||
|
||||
This method is more complicated and involves a lot more configuration of the `http` event syntax.
|
||||
|
||||
### Request Parameters
|
||||
|
||||
To pass optional and required parameters to your functions, so you can use them in API Gateway tests and SDK generation, marking them as `true` will make them required, `false` will make them optional.
|
||||
@ -348,7 +344,6 @@ functions:
|
||||
- http:
|
||||
path: posts/create
|
||||
method: post
|
||||
integration: lambda
|
||||
request:
|
||||
parameters:
|
||||
querystrings:
|
||||
@ -369,13 +364,16 @@ functions:
|
||||
- http:
|
||||
path: posts/{id}
|
||||
method: get
|
||||
integration: lambda
|
||||
request:
|
||||
parameters:
|
||||
paths:
|
||||
id: true
|
||||
```
|
||||
|
||||
## Lambda Integration
|
||||
|
||||
This method is more complicated and involves a lot more configuration of the `http` event syntax.
|
||||
|
||||
### Request templates
|
||||
|
||||
#### Default Request Templates
|
||||
|
||||
@ -73,3 +73,41 @@ functions:
|
||||
bucket: photos
|
||||
event: s3:ObjectRemoved:*
|
||||
```
|
||||
|
||||
## Custom bucket configuration
|
||||
|
||||
If you need to configure the bucket itself, you'll need to create the bucket and the Lambda Permission manually in
|
||||
the Resources section while paying attention to some of the logical IDs. This relies on the Serverless naming convention. See the [Serverless Resource Reference](../guide/resources.md#aws-cloudformation-resource-reference) for details. These are the logical IDs that require your attention:
|
||||
- The logical ID of the custom bucket in the Resources section needs to match the bucket name in the S3 event after the Serverless naming convention is applied to it.
|
||||
- The Lambda Permission's logical ID needs to match the Serverless naming convention for Lambda Permissions for S3 events.
|
||||
- The `FunctionName` in the Lambda Permission configuration needs to match the logical ID generated for the target Lambda function as determined by the Serverless naming convention.
|
||||
|
||||
The following example will work:
|
||||
|
||||
```yaml
|
||||
functions:
|
||||
resize:
|
||||
handler: resize.handler
|
||||
events:
|
||||
- s3: photos
|
||||
|
||||
resources:
|
||||
Resources:
|
||||
S3BucketPhotos:
|
||||
Type: AWS::S3::Bucket
|
||||
Properties:
|
||||
BucketName: my-custom-bucket-name
|
||||
# add additional custom bucket configuration here
|
||||
ResizeLambdaPermissionPhotosS3:
|
||||
Type: "AWS::Lambda::Permission"
|
||||
Properties:
|
||||
FunctionName:
|
||||
"Fn::GetAtt":
|
||||
- ResizeLambdaFunction
|
||||
- Arn
|
||||
Principal: "s3.amazonaws.com"
|
||||
Action: "lambda:InvokeFunction"
|
||||
SourceAccount:
|
||||
Ref: AWS::AccountId
|
||||
SourceArn: "arn:aws:s3:::my-custom-bucket-name"
|
||||
```
|
||||
|
||||
@ -330,7 +330,7 @@ functions:
|
||||
|
||||
The `onError` config currently only supports SNS topic arns due to a race condition when using SQS queue arns and updating the IAM role.
|
||||
|
||||
We're working on a fix so that SQS queue arns are be supported in the future.
|
||||
We're working on a fix so that SQS queue arns will be supported in the future.
|
||||
|
||||
## KMS Keys
|
||||
|
||||
|
||||
@ -316,6 +316,8 @@ class MyPlugin {
|
||||
module.exports = MyPlugin;
|
||||
```
|
||||
|
||||
**Note:** [Variable references](./variables.md#reference-properties-in-serverlessyml) in the `serverless` instance are not resolved before a Plugin's constructor is called, so if you need these, make sure to wait to access those from your [hooks](#hooks).
|
||||
|
||||
### Command Naming
|
||||
|
||||
Command names need to be unique. If we load two commands and both want to specify the same command (e.g. we have an integrated command `deploy` and an external command also wants to use `deploy`) the Serverless CLI will print an error and exit. If you want to have your own `deploy` command you need to name it something different like `myCompanyDeploy` so they don't clash with existing plugins.
|
||||
|
||||
@ -150,25 +150,43 @@ functions:
|
||||
In the above example, you're referencing the entire `myCustomFile.yml` file in the `custom` property. You need to pass the path relative to your service directory. You can also request specific properties in that file as shown in the `schedule` property. It's completely recursive and you can go as deep as you want.
|
||||
|
||||
## Reference Variables in Javascript Files
|
||||
To add dynamic data into your variables, reference javascript files by putting `${file(./myFile.js):someModule}` syntax in your `serverless.yml`. Here's an example:
|
||||
|
||||
You can reference JavaScript files to add dynamic data into your variables.
|
||||
|
||||
References can be either named or unnamed exports. To use the exported `someModule` in `myFile.js` you'd use the following code `${file(./myFile.js):someModule}`. For an unnamed export you'd write `${file(./myFile.js)}`.
|
||||
|
||||
Here are other examples:
|
||||
|
||||
```js
|
||||
// myCustomFile.js
|
||||
module.exports.hello = () => {
|
||||
// scheduleConfig.js
|
||||
module.exports.rate = () => {
|
||||
// Code that generates dynamic data
|
||||
return 'rate (10 minutes)';
|
||||
}
|
||||
```
|
||||
|
||||
```js
|
||||
// config.js
|
||||
module.exports = () => {
|
||||
return {
|
||||
property1: 'some value',
|
||||
property2: 'some other value'
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```yml
|
||||
# serverless.yml
|
||||
service: new-service
|
||||
provider: aws
|
||||
|
||||
custom: ${file(./config.js)}
|
||||
|
||||
functions:
|
||||
hello:
|
||||
handler: handler.hello
|
||||
events:
|
||||
- schedule: ${file(./myCustomFile.js):hello} # Reference a specific module
|
||||
- schedule: ${file(./scheduleConfig.js):rate} # Reference a specific module
|
||||
```
|
||||
|
||||
You can also return an object and reference a specific property. Just make sure you are returning a valid object and referencing a valid property:
|
||||
@ -274,7 +292,7 @@ service: new-service
|
||||
provider:
|
||||
name: aws
|
||||
runtime: nodejs6.10
|
||||
variableSyntax: "\\${{([\\s\\S]+?)}}" # notice the double quotes for yaml to ignore the escape characters!
|
||||
variableSyntax: "\\${{([ :a-zA-Z0-9._,\\-\\/\\(\\)]+?)}}" # notice the double quotes for yaml to ignore the escape characters!
|
||||
|
||||
custom:
|
||||
myStage: ${{opt:stage}}
|
||||
|
||||
@ -347,6 +347,8 @@ class MyPlugin {
|
||||
module.exports = MyPlugin;
|
||||
```
|
||||
|
||||
**Note:** [Variable references](./variables.md#reference-properties-in-serverlessyml) in the `serverless` instance are not resolved before a Plugin's constructor is called, so if you need these, make sure to wait to access those from your [hooks](#hooks).
|
||||
|
||||
### Command Naming
|
||||
|
||||
Command names need to be unique. If we load two commands and both want to specify
|
||||
|
||||
@ -56,27 +56,40 @@ way, you can easily change the schedule for all functions whenever you like.
|
||||
|
||||
## Reference Variables in JavaScript Files
|
||||
|
||||
To add dynamic data into your variables, reference JavaScript files by putting
|
||||
`${file(./myFile.js):someModule}` syntax in your `serverless.yml`. Here's an
|
||||
example:
|
||||
You can reference JavaScript files to add dynamic data into your variables.
|
||||
|
||||
References can be either named or unnamed exports. To use the exported `someModule` in `myFile.js` you'd use the following code `${file(./myFile.js):someModule}`. For an unnamed export you'd write `${file(./myFile.js)}`.
|
||||
|
||||
```js
|
||||
// myCustomFile.js
|
||||
module.exports.hello = () => {
|
||||
// scheduleConfig.js
|
||||
module.exports.cron = () => {
|
||||
// Code that generates dynamic data
|
||||
return 'cron(0 * * * *)';
|
||||
}
|
||||
```
|
||||
|
||||
```js
|
||||
// config.js
|
||||
module.exports = () => {
|
||||
return {
|
||||
property1: 'some value',
|
||||
property2: 'some other value'
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```yml
|
||||
# serverless.yml
|
||||
service: new-service
|
||||
provider: azure
|
||||
|
||||
custom: ${file(./config.js)}
|
||||
|
||||
functions:
|
||||
hello:
|
||||
handler: handler.hello
|
||||
events:
|
||||
- timer: ${file(./myCustomFile.js):hello} # Reference a specific module
|
||||
- timer: ${file(./scheduleConfig.js):cron} # Reference a specific module
|
||||
```
|
||||
|
||||
You can also return an object and reference a specific property. Just make sure
|
||||
|
||||
@ -315,6 +315,8 @@ class MyPlugin {
|
||||
module.exports = MyPlugin;
|
||||
```
|
||||
|
||||
**Note:** [Variable references](./variables.md#reference-properties-in-serverlessyml) in the `serverless` instance are not resolved before a Plugin's constructor is called, so if you need these, make sure to wait to access those from your [hooks](#hooks).
|
||||
|
||||
### Command Naming
|
||||
|
||||
Command names need to be unique. If we load two commands and both want to specify the same command (e.g. we have an integrated command `deploy` and an external command also wants to use `deploy`) the Serverless CLI will print an error and exit. If you want to have your own `deploy` command you need to name it something different like `myCompanyDeploy` so they don't clash with existing plugins.
|
||||
|
||||
@ -12,7 +12,7 @@ This guide is designed to help you get started as quick as possible.
|
||||
|
||||
## 1. Create a new service
|
||||
|
||||
1. Create a new service with the `google-nodejs` template
|
||||
1. Create a new service with the [`google-nodejs`](https://github.com/serverless/serverless/tree/master/lib/plugins/create/templates/google-nodejs) template
|
||||
|
||||
```bash
|
||||
serverless create --template google-nodejs --path my-service
|
||||
|
||||
@ -52,29 +52,43 @@ In the above example you're setting a global event resource for all functions by
|
||||
|
||||
## Reference Variables in JavaScript Files
|
||||
|
||||
To add dynamic data into your variables, reference javascript files by putting `${file(./myFile.js):someModule}` syntax in your `serverless.yml`. Here's an example:
|
||||
You can reference JavaScript files to add dynamic data into your variables.
|
||||
|
||||
References can be either named or unnamed exports. To use the exported `someModule` in `myFile.js` you'd use the following code `${file(./myFile.js):someModule}`. For an unnamed export you'd write `${file(./myFile.js)}`.
|
||||
|
||||
```javascript
|
||||
// myCustomFile.js
|
||||
module.exports.resource = () => {
|
||||
// resources.js
|
||||
module.exports.topic = () => {
|
||||
// Code that generates dynamic data
|
||||
return 'projects/*/topics/my-topic';
|
||||
}
|
||||
```
|
||||
|
||||
```js
|
||||
// config.js
|
||||
module.exports = () => {
|
||||
return {
|
||||
property1: 'some value',
|
||||
property2: 'some other value'
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```yml
|
||||
# serverless.yml
|
||||
service: new-service
|
||||
|
||||
provider: google
|
||||
|
||||
custom: ${file(./config.js)}
|
||||
|
||||
functions:
|
||||
first:
|
||||
handler: pubSub
|
||||
events:
|
||||
- event:
|
||||
eventType: providers/cloud.pubsub/eventTypes/topics.publish
|
||||
resource: ${file(./myCustomFile.js):resource} # Reference a specific module
|
||||
resource: ${file(./resources.js):topic} # Reference a specific module
|
||||
```
|
||||
|
||||
You can also return an object and reference a specific property. Just make sure you are returning a valid object and referencing a valid property:
|
||||
|
||||
@ -32,7 +32,7 @@ functions:
|
||||
crawl:
|
||||
handler: crawl
|
||||
events:
|
||||
- schedule: cron(* * * * * *) // run every minute
|
||||
- schedule: cron(* * * * *) // run every minute
|
||||
```
|
||||
|
||||
This automatically generates a new trigger (``${service}_crawl_schedule_trigger`)
|
||||
|
||||
@ -316,6 +316,8 @@ class MyPlugin {
|
||||
module.exports = MyPlugin;
|
||||
```
|
||||
|
||||
**Note:** [Variable references](./variables.md#reference-properties-in-serverlessyml) in the `serverless` instance are not resolved before a Plugin's constructor is called, so if you need these, make sure to wait to access those from your [hooks](#hooks).
|
||||
|
||||
### Command Naming
|
||||
|
||||
Command names need to be unique. If we load two commands and both want to specify the same command (e.g. we have an integrated command `deploy` and an external command also wants to use `deploy`) the Serverless CLI will print an error and exit. If you want to have your own `deploy` command you need to name it something different like `myCompanyDeploy` so they don't clash with existing plugins.
|
||||
|
||||
@ -106,25 +106,41 @@ functions:
|
||||
In the above example, you're referencing the entire `myCustomFile.yml` file in the `custom` property. You need to pass the path relative to your service directory. You can also request specific properties in that file as shown in the `schedule` property. It's completely recursive and you can go as deep as you want.
|
||||
|
||||
## Reference Variables in Javascript Files
|
||||
To add dynamic data into your variables, reference javascript files by putting `${file(./myFile.js):someModule}` syntax in your `serverless.yml`. Here's an example:
|
||||
|
||||
You can reference JavaScript files to add dynamic data into your variables.
|
||||
|
||||
References can be either named or unnamed exports. To use the exported `someModule` in `myFile.js` you'd use the following code `${file(./myFile.js):someModule}`. For an unnamed export you'd write `${file(./myFile.js)}`.
|
||||
|
||||
```js
|
||||
// myCustomFile.js
|
||||
module.exports.hello = () => {
|
||||
// scheduleConfig.js
|
||||
module.exports.cron = () => {
|
||||
// Code that generates dynamic data
|
||||
return 'cron(0 * * * *)';
|
||||
}
|
||||
```
|
||||
|
||||
```js
|
||||
// config.js
|
||||
module.exports = () => {
|
||||
return {
|
||||
property1: 'some value',
|
||||
property2: 'some other value'
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```yml
|
||||
# serverless.yml
|
||||
service: new-service
|
||||
provider: openwhisk
|
||||
|
||||
custom: ${file(./config.js)}
|
||||
|
||||
functions:
|
||||
hello:
|
||||
handler: handler.hello
|
||||
events:
|
||||
- schedule: ${file(./myCustomFile.js):hello} # Reference a specific module
|
||||
- schedule: ${file(./scheduleConfig.js):cron} # Reference a specific module
|
||||
```
|
||||
|
||||
You can also return an object and reference a specific property. Just make sure you are returning a valid object and referencing a valid property:
|
||||
|
||||
@ -16,7 +16,7 @@ class Service {
|
||||
this.provider = {
|
||||
stage: 'dev',
|
||||
region: 'us-east-1',
|
||||
variableSyntax: '\\${([ :a-zA-Z0-9._,\\-\\/\\(\\)]+?)}',
|
||||
variableSyntax: '\\${([ ~:a-zA-Z0-9._,\\-\\/\\(\\)]+?)}',
|
||||
};
|
||||
this.custom = {};
|
||||
this.plugins = [];
|
||||
|
||||
@ -31,7 +31,7 @@ describe('Service', () => {
|
||||
expect(serviceInstance.provider).to.deep.equal({
|
||||
stage: 'dev',
|
||||
region: 'us-east-1',
|
||||
variableSyntax: '\\${([ :a-zA-Z0-9._,\\-\\/\\(\\)]+?)}',
|
||||
variableSyntax: '\\${([ ~:a-zA-Z0-9._,\\-\\/\\(\\)]+?)}',
|
||||
});
|
||||
expect(serviceInstance.custom).to.deep.equal({});
|
||||
expect(serviceInstance.plugins).to.deep.equal([]);
|
||||
@ -131,7 +131,7 @@ describe('Service', () => {
|
||||
name: 'aws',
|
||||
stage: 'dev',
|
||||
region: 'us-east-1',
|
||||
variableSyntax: '\\${{([\\s\\S]+?)}}',
|
||||
variableSyntax: '\\${{([ :a-zA-Z0-9._,\\-\\/\\(\\)]+?)}}',
|
||||
},
|
||||
plugins: ['testPlugin'],
|
||||
functions: {
|
||||
@ -164,7 +164,9 @@ describe('Service', () => {
|
||||
.then(() => {
|
||||
expect(serviceInstance.service).to.be.equal('new-service');
|
||||
expect(serviceInstance.provider.name).to.deep.equal('aws');
|
||||
expect(serviceInstance.provider.variableSyntax).to.equal('\\${{([\\s\\S]+?)}}');
|
||||
expect(serviceInstance.provider.variableSyntax).to.equal(
|
||||
'\\${{([ :a-zA-Z0-9._,\\-\\/\\(\\)]+?)}}'
|
||||
);
|
||||
expect(serviceInstance.plugins).to.deep.equal(['testPlugin']);
|
||||
expect(serviceInstance.resources.aws).to.deep.equal({ resourcesProp: 'value' });
|
||||
expect(serviceInstance.resources.azure).to.deep.equal({});
|
||||
@ -186,7 +188,7 @@ describe('Service', () => {
|
||||
name: 'aws',
|
||||
stage: 'dev',
|
||||
region: 'us-east-1',
|
||||
variableSyntax: '\\${{([\\s\\S]+?)}}',
|
||||
variableSyntax: '\\${{([ :a-zA-Z0-9._,\\-\\/\\(\\)]+?)}}',
|
||||
},
|
||||
plugins: ['testPlugin'],
|
||||
functions: {
|
||||
@ -218,7 +220,9 @@ describe('Service', () => {
|
||||
.then(() => {
|
||||
expect(serviceInstance.service).to.be.equal('new-service');
|
||||
expect(serviceInstance.provider.name).to.deep.equal('aws');
|
||||
expect(serviceInstance.provider.variableSyntax).to.equal('\\${{([\\s\\S]+?)}}');
|
||||
expect(serviceInstance.provider.variableSyntax).to.equal(
|
||||
'\\${{([ :a-zA-Z0-9._,\\-\\/\\(\\)]+?)}}'
|
||||
);
|
||||
expect(serviceInstance.plugins).to.deep.equal(['testPlugin']);
|
||||
expect(serviceInstance.resources.aws).to.deep.equal({ resourcesProp: 'value' });
|
||||
expect(serviceInstance.resources.azure).to.deep.equal({});
|
||||
@ -240,7 +244,7 @@ describe('Service', () => {
|
||||
name: 'aws',
|
||||
stage: 'dev',
|
||||
region: 'us-east-1',
|
||||
variableSyntax: '\\${{([\\s\\S]+?)}}',
|
||||
variableSyntax: '\\${{([ :a-zA-Z0-9._,\\-\\/\\(\\)]+?)}}',
|
||||
},
|
||||
plugins: ['testPlugin'],
|
||||
functions: {
|
||||
@ -272,7 +276,9 @@ describe('Service', () => {
|
||||
.then(() => {
|
||||
expect(serviceInstance.service).to.be.equal('new-service');
|
||||
expect(serviceInstance.provider.name).to.deep.equal('aws');
|
||||
expect(serviceInstance.provider.variableSyntax).to.equal('\\${{([\\s\\S]+?)}}');
|
||||
expect(serviceInstance.provider.variableSyntax).to.equal(
|
||||
'\\${{([ :a-zA-Z0-9._,\\-\\/\\(\\)]+?)}}'
|
||||
);
|
||||
expect(serviceInstance.plugins).to.deep.equal(['testPlugin']);
|
||||
expect(serviceInstance.resources.aws).to.deep.equal({ resourcesProp: 'value' });
|
||||
expect(serviceInstance.resources.azure).to.deep.equal({});
|
||||
@ -293,7 +299,7 @@ describe('Service', () => {
|
||||
name: 'aws',
|
||||
stage: 'dev',
|
||||
region: 'us-east-1',
|
||||
variableSyntax: '\\${{([\\s\\S]+?)}}',
|
||||
variableSyntax: '\\${{([ :a-zA-Z0-9._,\\-\\/\\(\\)]+?)}}',
|
||||
},
|
||||
plugins: ['testPlugin'],
|
||||
functions: {
|
||||
@ -724,7 +730,7 @@ describe('Service', () => {
|
||||
name: 'aws',
|
||||
stage: 'dev',
|
||||
region: 'us-east-1',
|
||||
variableSyntax: '\\${{([\\s\\S]+?)}}',
|
||||
variableSyntax: '\\${{([ :a-zA-Z0-9._,\\-\\/\\(\\)]+?)}}',
|
||||
},
|
||||
plugins: ['testPlugin'],
|
||||
functions: {
|
||||
|
||||
@ -249,7 +249,7 @@ class Utils {
|
||||
}
|
||||
|
||||
let hasCustomVariableSyntaxDefined = false;
|
||||
const defaultVariableSyntax = '\\${([ :a-zA-Z0-9._,\\-\\/\\(\\)]+?)}';
|
||||
const defaultVariableSyntax = '\\${([ ~:a-zA-Z0-9._,\\-\\/\\(\\)]+?)}';
|
||||
|
||||
// check if the variableSyntax in the provider section is defined
|
||||
if (provider && provider.variableSyntax
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
|
||||
const path = require('path');
|
||||
const os = require('os');
|
||||
const expect = require('chai').expect;
|
||||
const chai = require('chai');
|
||||
const sinon = require('sinon');
|
||||
const Serverless = require('../../lib/Serverless');
|
||||
const testUtils = require('../../tests/utils');
|
||||
@ -10,6 +10,8 @@ const configUtils = require('../utils/config');
|
||||
const serverlessVersion = require('../../package.json').version;
|
||||
const segment = require('../utils/segment');
|
||||
const proxyquire = require('proxyquire');
|
||||
chai.use(require('chai-as-promised'));
|
||||
const expect = require('chai').expect;
|
||||
|
||||
describe('Utils', () => {
|
||||
let utils;
|
||||
@ -83,7 +85,7 @@ describe('Utils', () => {
|
||||
|
||||
serverless.utils.writeFileSync(tmpFilePath, { foo: 'bar' });
|
||||
|
||||
return serverless.yamlParser.parse(tmpFilePath).then((obj) => {
|
||||
return expect(serverless.yamlParser.parse(tmpFilePath)).to.be.fulfilled.then((obj) => {
|
||||
expect(obj.foo).to.equal('bar');
|
||||
});
|
||||
});
|
||||
@ -93,7 +95,7 @@ describe('Utils', () => {
|
||||
|
||||
serverless.utils.writeFileSync(tmpFilePath, { foo: 'bar' });
|
||||
|
||||
return serverless.yamlParser.parse(tmpFilePath).then((obj) => {
|
||||
return expect(serverless.yamlParser.parse(tmpFilePath)).to.be.fulfilled.then((obj) => {
|
||||
expect(obj.foo).to.equal('bar');
|
||||
});
|
||||
});
|
||||
@ -108,7 +110,8 @@ describe('Utils', () => {
|
||||
const tmpFilePath = testUtils.getTmpFilePath('anything.json');
|
||||
|
||||
// note: use return when testing promises otherwise you'll have unhandled rejection errors
|
||||
return serverless.utils.writeFile(tmpFilePath, { foo: 'bar' }).then(() => {
|
||||
return expect(serverless.utils.writeFile(tmpFilePath, { foo: 'bar' }))
|
||||
.to.be.fulfilled.then(() => {
|
||||
const obj = serverless.utils.readFileSync(tmpFilePath);
|
||||
|
||||
expect(obj.foo).to.equal('bar');
|
||||
@ -178,7 +181,7 @@ describe('Utils', () => {
|
||||
serverless.utils.writeFileSync(tmpFilePath, { foo: 'bar' });
|
||||
|
||||
// note: use return when testing promises otherwise you'll have unhandled rejection errors
|
||||
return serverless.utils.readFile(tmpFilePath).then((obj) => {
|
||||
return expect(serverless.utils.readFile(tmpFilePath)).to.be.fulfilled.then((obj) => {
|
||||
expect(obj.foo).to.equal('bar');
|
||||
});
|
||||
});
|
||||
@ -349,7 +352,7 @@ describe('Utils', () => {
|
||||
// help is a whitelisted option
|
||||
serverless.processedInput.options = options;
|
||||
|
||||
return utils.logStat(serverless).then(() => {
|
||||
return expect(utils.logStat(serverless)).to.be.fulfilled.then(() => {
|
||||
expect(trackStub.calledOnce).to.equal(true);
|
||||
expect(getConfigStub.calledOnce).to.equal(true);
|
||||
|
||||
@ -365,7 +368,7 @@ describe('Utils', () => {
|
||||
frameworkId: '1234wasd',
|
||||
});
|
||||
|
||||
return utils.logStat(serverless).then(() => {
|
||||
return expect(utils.logStat(serverless)).to.be.fulfilled.then(() => {
|
||||
expect(getConfigStub.calledOnce).to.equal(true);
|
||||
expect(isDockerContainerStub.calledOnce).to.equal(true);
|
||||
expect(trackStub.calledOnce).to.equal(true);
|
||||
@ -398,9 +401,12 @@ describe('Utils', () => {
|
||||
],
|
||||
},
|
||||
},
|
||||
package: {
|
||||
path: 'foo',
|
||||
},
|
||||
};
|
||||
|
||||
return utils.logStat(serverless).then(() => {
|
||||
return expect(utils.logStat(serverless)).to.be.fulfilled.then(() => {
|
||||
expect(getConfigStub.calledOnce).to.equal(true);
|
||||
expect(trackStub.calledOnce).to.equal(true);
|
||||
|
||||
@ -436,10 +442,13 @@ describe('Utils', () => {
|
||||
],
|
||||
},
|
||||
},
|
||||
package: {
|
||||
path: 'foo',
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
return utils.logStat(serverless).then(() => {
|
||||
return expect(utils.logStat(serverless)).to.be.fulfilled.then(() => {
|
||||
expect(getConfigStub.calledOnce).to.equal(true);
|
||||
expect(trackStub.calledOnce).to.equal(true);
|
||||
|
||||
@ -473,9 +482,12 @@ describe('Utils', () => {
|
||||
],
|
||||
},
|
||||
},
|
||||
package: {
|
||||
path: 'foo',
|
||||
},
|
||||
};
|
||||
|
||||
return utils.logStat(serverless).then(() => {
|
||||
return expect(utils.logStat(serverless)).to.be.fulfilled.then(() => {
|
||||
expect(getConfigStub.calledOnce).to.equal(true);
|
||||
expect(trackStub.calledOnce).to.equal(true);
|
||||
|
||||
@ -511,9 +523,12 @@ describe('Utils', () => {
|
||||
],
|
||||
},
|
||||
},
|
||||
package: {
|
||||
path: 'foo',
|
||||
},
|
||||
};
|
||||
|
||||
return utils.logStat(serverless).then(() => {
|
||||
return expect(utils.logStat(serverless)).to.be.fulfilled.then(() => {
|
||||
expect(getConfigStub.calledOnce).to.equal(true);
|
||||
expect(trackStub.calledOnce).to.equal(true);
|
||||
|
||||
@ -549,9 +564,12 @@ describe('Utils', () => {
|
||||
],
|
||||
},
|
||||
},
|
||||
package: {
|
||||
path: 'foo',
|
||||
},
|
||||
};
|
||||
|
||||
return utils.logStat(serverless).then(() => {
|
||||
return expect(utils.logStat(serverless)).to.be.fulfilled.then(() => {
|
||||
expect(getConfigStub.calledOnce).to.equal(true);
|
||||
expect(trackStub.calledOnce).to.equal(true);
|
||||
|
||||
@ -585,9 +603,12 @@ describe('Utils', () => {
|
||||
],
|
||||
},
|
||||
},
|
||||
package: {
|
||||
path: 'foo',
|
||||
},
|
||||
};
|
||||
|
||||
return utils.logStat(serverless).then(() => {
|
||||
return expect(utils.logStat(serverless)).to.be.fulfilled.then(() => {
|
||||
expect(getConfigStub.calledOnce).to.equal(true);
|
||||
expect(trackStub.calledOnce).to.equal(true);
|
||||
|
||||
@ -623,9 +644,12 @@ describe('Utils', () => {
|
||||
],
|
||||
},
|
||||
},
|
||||
package: {
|
||||
path: 'foo',
|
||||
},
|
||||
};
|
||||
|
||||
return utils.logStat(serverless).then(() => {
|
||||
return expect(utils.logStat(serverless)).to.be.fulfilled.then(() => {
|
||||
expect(getConfigStub.calledOnce).to.equal(true);
|
||||
expect(trackStub.calledOnce).to.equal(true);
|
||||
|
||||
@ -648,7 +672,7 @@ describe('Utils', () => {
|
||||
},
|
||||
};
|
||||
|
||||
return utils.logStat(serverless).then(() => {
|
||||
return expect(utils.logStat(serverless)).to.be.fulfilled.then(() => {
|
||||
expect(getConfigStub.calledOnce).to.equal(true);
|
||||
expect(trackStub.calledOnce).to.equal(true);
|
||||
|
||||
@ -711,7 +735,7 @@ describe('Utils', () => {
|
||||
package: {},
|
||||
};
|
||||
|
||||
return utils.logStat(serverless).then(() => {
|
||||
return expect(utils.logStat(serverless)).to.be.fulfilled.then(() => {
|
||||
expect(trackStub.calledOnce).to.equal(true);
|
||||
expect(getConfigStub.calledOnce).to.equal(true);
|
||||
|
||||
|
||||
@ -5,6 +5,7 @@ const path = require('path');
|
||||
const replaceall = require('replaceall');
|
||||
const logWarning = require('./Error').logWarning;
|
||||
const BbPromise = require('bluebird');
|
||||
const os = require('os');
|
||||
|
||||
class Variables {
|
||||
|
||||
@ -13,7 +14,7 @@ class Variables {
|
||||
this.service = this.serverless.service;
|
||||
|
||||
this.overwriteSyntax = RegExp(/,/g);
|
||||
this.fileRefSyntax = RegExp(/^file\(([a-zA-Z0-9._\-/]+?)\)/g);
|
||||
this.fileRefSyntax = RegExp(/^file\((~?[a-zA-Z0-9._\-/]+?)\)/g);
|
||||
this.envRefSyntax = RegExp(/^env:/g);
|
||||
this.optRefSyntax = RegExp(/^opt:/g);
|
||||
this.selfRefSyntax = RegExp(/^self:/g);
|
||||
@ -215,12 +216,14 @@ class Variables {
|
||||
getValueFromFile(variableString) {
|
||||
const matchedFileRefString = variableString.match(this.fileRefSyntax)[0];
|
||||
const referencedFileRelativePath = matchedFileRefString
|
||||
.replace(this.fileRefSyntax, (match, varName) => varName.trim());
|
||||
const referencedFileFullPath = path.join(this.serverless.config.servicePath,
|
||||
referencedFileRelativePath);
|
||||
.replace(this.fileRefSyntax, (match, varName) => varName.trim())
|
||||
.replace('~', os.homedir());
|
||||
|
||||
const referencedFileFullPath = (path.isAbsolute(referencedFileRelativePath) ?
|
||||
referencedFileRelativePath :
|
||||
path.join(this.serverless.config.servicePath, referencedFileRelativePath));
|
||||
let fileExtension = referencedFileRelativePath.split('.');
|
||||
fileExtension = fileExtension[fileExtension.length - 1];
|
||||
|
||||
// Validate file exists
|
||||
if (!this.serverless.utils.fileExistsSync(referencedFileFullPath)) {
|
||||
return BbPromise.resolve(undefined);
|
||||
@ -231,9 +234,25 @@ class Variables {
|
||||
// Process JS files
|
||||
if (fileExtension === 'js') {
|
||||
const jsFile = require(referencedFileFullPath); // eslint-disable-line global-require
|
||||
let jsModule = variableString.split(':')[1];
|
||||
jsModule = jsModule.split('.')[0];
|
||||
valueToPopulate = jsFile[jsModule]();
|
||||
const variableArray = variableString.split(':');
|
||||
let returnValueFunction;
|
||||
if (variableArray[1]) {
|
||||
let jsModule = variableArray[1];
|
||||
jsModule = jsModule.split('.')[0];
|
||||
returnValueFunction = jsFile[jsModule];
|
||||
} else {
|
||||
returnValueFunction = jsFile;
|
||||
}
|
||||
|
||||
if (typeof returnValueFunction !== 'function') {
|
||||
throw new this.serverless.classes
|
||||
.Error([
|
||||
'Invalid variable syntax when referencing',
|
||||
` file "${referencedFileRelativePath}".`,
|
||||
' Check if your javascript is exporting a function that returns a value.',
|
||||
].join(''));
|
||||
}
|
||||
valueToPopulate = returnValueFunction();
|
||||
|
||||
return BbPromise.resolve(valueToPopulate).then(valueToPopulateResolved => {
|
||||
let deepProperties = variableString.replace(matchedFileRefString, '');
|
||||
@ -274,7 +293,6 @@ class Variables {
|
||||
return this.getDeepValue(deepProperties, valueToPopulate);
|
||||
}
|
||||
}
|
||||
|
||||
return BbPromise.resolve(valueToPopulate);
|
||||
}
|
||||
|
||||
|
||||
@ -12,6 +12,7 @@ const testUtils = require('../../tests/utils');
|
||||
const slsError = require('./Error');
|
||||
const AwsProvider = require('../plugins/aws/provider/awsProvider');
|
||||
const BbPromise = require('bluebird');
|
||||
const os = require('os');
|
||||
|
||||
describe('Variables', () => {
|
||||
describe('#constructor()', () => {
|
||||
@ -32,7 +33,7 @@ describe('Variables', () => {
|
||||
it('should set variableSyntax', () => {
|
||||
const serverless = new Serverless();
|
||||
|
||||
serverless.service.provider.variableSyntax = '\\${{([\\s\\S]+?)}}';
|
||||
serverless.service.provider.variableSyntax = '\\${{([ :a-zA-Z0-9._,\\-\\/\\(\\)]+?)}}';
|
||||
|
||||
serverless.variables.loadVariableSyntax();
|
||||
expect(serverless.variables.variableSyntax).to.be.a('RegExp');
|
||||
@ -55,7 +56,7 @@ describe('Variables', () => {
|
||||
it('should use variableSyntax', () => {
|
||||
const serverless = new Serverless();
|
||||
|
||||
const variableSyntax = '\\${{([\\s\\S]+?)}}';
|
||||
const variableSyntax = '\\${{([ :a-zA-Z0-9._,\\-\\/\\(\\)]+?)}}';
|
||||
const fooValue = '${clientId()}';
|
||||
const barValue = 'test';
|
||||
|
||||
@ -535,6 +536,33 @@ describe('Variables', () => {
|
||||
});
|
||||
|
||||
describe('#getValueFromFile()', () => {
|
||||
it('should work for absolute paths with ~ ', () => {
|
||||
const serverless = new Serverless();
|
||||
const expectedFileName = `${os.homedir}/somedir/config.yml`;
|
||||
const configYml = {
|
||||
test: 1,
|
||||
test2: 'test2',
|
||||
testObj: {
|
||||
sub: 2,
|
||||
prob: 'prob',
|
||||
},
|
||||
};
|
||||
const fileExistsStub = sinon
|
||||
.stub(serverless.utils, 'fileExistsSync').returns(true);
|
||||
|
||||
const readFileSyncStub = sinon
|
||||
.stub(serverless.utils, 'readFileSync').returns(configYml);
|
||||
|
||||
return serverless.variables.getValueFromFile('file(~/somedir/config.yml)')
|
||||
.then(valueToPopulate => {
|
||||
expect(fileExistsStub.calledWithMatch(expectedFileName));
|
||||
expect(readFileSyncStub.calledWithMatch(expectedFileName));
|
||||
expect(valueToPopulate).to.deep.equal(configYml);
|
||||
readFileSyncStub.restore();
|
||||
fileExistsStub.restore();
|
||||
});
|
||||
});
|
||||
|
||||
it('should populate an entire variable file', () => {
|
||||
const serverless = new Serverless();
|
||||
const SUtils = new Utils();
|
||||
@ -639,6 +667,36 @@ describe('Variables', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('should populate an entire variable exported by a javascript file', () => {
|
||||
const serverless = new Serverless();
|
||||
const SUtils = new Utils();
|
||||
const tmpDirPath = testUtils.getTmpDirPath();
|
||||
const jsData = 'module.exports=function(){return { hello: "hello world" };};';
|
||||
|
||||
SUtils.writeFileSync(path.join(tmpDirPath, 'hello.js'), jsData);
|
||||
|
||||
serverless.config.update({ servicePath: tmpDirPath });
|
||||
|
||||
return serverless.variables.getValueFromFile('file(./hello.js)')
|
||||
.then(valueToPopulate => {
|
||||
expect(valueToPopulate.hello).to.equal('hello world');
|
||||
});
|
||||
});
|
||||
|
||||
it('should thow if property exported by a javascript file is not a function', () => {
|
||||
const serverless = new Serverless();
|
||||
const SUtils = new Utils();
|
||||
const tmpDirPath = testUtils.getTmpDirPath();
|
||||
const jsData = 'module.exports={ hello: "hello world" };';
|
||||
|
||||
SUtils.writeFileSync(path.join(tmpDirPath, 'hello.js'), jsData);
|
||||
|
||||
serverless.config.update({ servicePath: tmpDirPath });
|
||||
|
||||
expect(() => serverless.variables
|
||||
.getValueFromFile('file(./hello.js)')).to.throw(Error);
|
||||
});
|
||||
|
||||
it('should populate deep object from a javascript file', () => {
|
||||
const serverless = new Serverless();
|
||||
const SUtils = new Utils();
|
||||
|
||||
@ -33,8 +33,16 @@ module.exports = {
|
||||
this.serverless.service.package.individually) {
|
||||
// artifact file validation (multiple function artifacts)
|
||||
this.serverless.service.getAllFunctions().forEach(functionName => {
|
||||
const artifactFileName = this.provider.naming.getFunctionArtifactName(functionName);
|
||||
const artifactFilePath = path.join(this.packagePath, artifactFileName);
|
||||
let artifactFileName = this.provider.naming.getFunctionArtifactName(functionName);
|
||||
let artifactFilePath = path.join(this.packagePath, artifactFileName);
|
||||
|
||||
// check if an artifact is used in function package level
|
||||
const functionObject = this.serverless.service.getFunction(functionName);
|
||||
if (_.has(functionObject, ['package', 'artifact'])) {
|
||||
artifactFilePath = functionObject.package.artifact;
|
||||
artifactFileName = path.basename(artifactFilePath);
|
||||
}
|
||||
|
||||
if (!this.serverless.utils.fileExistsSync(artifactFilePath)) {
|
||||
throw new this.serverless.classes
|
||||
.Error(`No ${artifactFileName} file found in the package path you provided.`);
|
||||
@ -42,8 +50,14 @@ module.exports = {
|
||||
});
|
||||
} else if (!_.isEmpty(this.serverless.service.functions)) {
|
||||
// artifact file validation (single service artifact)
|
||||
const artifactFileName = this.provider.naming.getServiceArtifactName();
|
||||
const artifactFilePath = path.join(this.packagePath, artifactFileName);
|
||||
let artifactFilePath;
|
||||
let artifactFileName;
|
||||
if (this.serverless.service.package.artifact) {
|
||||
artifactFileName = artifactFilePath = this.serverless.service.package.artifact;
|
||||
} else {
|
||||
artifactFileName = this.provider.naming.getServiceArtifactName();
|
||||
artifactFilePath = path.join(this.packagePath, artifactFileName);
|
||||
}
|
||||
if (!this.serverless.utils.fileExistsSync(artifactFilePath)) {
|
||||
throw new this.serverless.classes
|
||||
.Error(`No ${artifactFileName} file found in the package path you provided.`);
|
||||
|
||||
@ -46,57 +46,106 @@ describe('extendedValidate', () => {
|
||||
});
|
||||
|
||||
describe('extendedValidate()', () => {
|
||||
it('should throw error if state file does not exist', () => {
|
||||
sinon.stub(awsDeploy.serverless.utils, 'fileExistsSync').returns(false);
|
||||
expect(() => awsDeploy.extendedValidate()).to.throw(Error);
|
||||
let fileExistsSyncStub;
|
||||
let readFileSyncStub;
|
||||
|
||||
beforeEach(() => {
|
||||
fileExistsSyncStub = sinon
|
||||
.stub(awsDeploy.serverless.utils, 'fileExistsSync');
|
||||
readFileSyncStub = sinon
|
||||
.stub(awsDeploy.serverless.utils, 'readFileSync');
|
||||
awsDeploy.serverless.service.package.individually = false;
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
awsDeploy.serverless.utils.fileExistsSync.restore();
|
||||
awsDeploy.serverless.utils.readFileSync.restore();
|
||||
});
|
||||
|
||||
it('should throw error if state file does not exist', () => {
|
||||
fileExistsSyncStub.returns(false);
|
||||
|
||||
expect(() => awsDeploy.extendedValidate()).to.throw(Error);
|
||||
});
|
||||
|
||||
it('should throw error if packaged individually but functions packages do not exist', () => {
|
||||
const fileExistsSyncStub = sinon.stub(awsDeploy.serverless.utils, 'fileExistsSync');
|
||||
fileExistsSyncStub.onCall(0).returns(true);
|
||||
fileExistsSyncStub.onCall(1).returns(false);
|
||||
sinon.stub(awsDeploy.serverless.utils, 'readFileSync').returns(stateFileMock);
|
||||
readFileSyncStub.returns(stateFileMock);
|
||||
|
||||
awsDeploy.serverless.service.package.individually = true;
|
||||
|
||||
expect(() => awsDeploy.extendedValidate()).to.throw(Error);
|
||||
awsDeploy.serverless.service.package.individually = false;
|
||||
awsDeploy.serverless.utils.fileExistsSync.restore();
|
||||
awsDeploy.serverless.utils.readFileSync.restore();
|
||||
});
|
||||
|
||||
it('should throw error if service package does not exist', () => {
|
||||
const fileExistsSyncStub = sinon.stub(awsDeploy.serverless.utils, 'fileExistsSync');
|
||||
fileExistsSyncStub.onCall(0).returns(true);
|
||||
fileExistsSyncStub.onCall(1).returns(false);
|
||||
sinon.stub(awsDeploy.serverless.utils, 'readFileSync').returns(stateFileMock);
|
||||
readFileSyncStub.returns(stateFileMock);
|
||||
|
||||
expect(() => awsDeploy.extendedValidate()).to.throw(Error);
|
||||
awsDeploy.serverless.utils.fileExistsSync.restore();
|
||||
awsDeploy.serverless.utils.readFileSync.restore();
|
||||
});
|
||||
|
||||
it('should not throw error if service has no functions and no service package available', () => { // eslint-disable-line max-len
|
||||
const functionsTmp = stateFileMock.service.functions;
|
||||
it('should not throw error if service has no functions and no service package', () => {
|
||||
stateFileMock.service.functions = {};
|
||||
sinon.stub(awsDeploy.serverless.utils, 'fileExistsSync').returns(true);
|
||||
sinon.stub(awsDeploy.serverless.utils, 'readFileSync').returns(stateFileMock);
|
||||
fileExistsSyncStub.returns(true);
|
||||
readFileSyncStub.returns(stateFileMock);
|
||||
|
||||
return awsDeploy.extendedValidate().then(() => {
|
||||
stateFileMock.service.functions = functionsTmp;
|
||||
awsDeploy.serverless.utils.fileExistsSync.restore();
|
||||
awsDeploy.serverless.utils.readFileSync.restore();
|
||||
expect(fileExistsSyncStub.calledOnce).to.equal(true);
|
||||
expect(readFileSyncStub.calledOnce).to.equal(true);
|
||||
});
|
||||
});
|
||||
|
||||
it('should not throw error if service has no functions and no function packages available', () => { // eslint-disable-line max-len
|
||||
const functionsTmp = stateFileMock.service.functions;
|
||||
it('should not throw error if service has no functions and no function packages', () => {
|
||||
stateFileMock.service.functions = {};
|
||||
awsDeploy.serverless.service.package.individually = true;
|
||||
sinon.stub(awsDeploy.serverless.utils, 'fileExistsSync').returns(true);
|
||||
sinon.stub(awsDeploy.serverless.utils, 'readFileSync').returns(stateFileMock);
|
||||
fileExistsSyncStub.returns(true);
|
||||
readFileSyncStub.returns(stateFileMock);
|
||||
|
||||
return awsDeploy.extendedValidate().then(() => {
|
||||
awsDeploy.serverless.service.package.individually = false;
|
||||
stateFileMock.service.functions = functionsTmp;
|
||||
awsDeploy.serverless.utils.fileExistsSync.restore();
|
||||
awsDeploy.serverless.utils.readFileSync.restore();
|
||||
expect(fileExistsSyncStub.calledOnce).to.equal(true);
|
||||
expect(readFileSyncStub.calledOnce).to.equal(true);
|
||||
});
|
||||
});
|
||||
|
||||
it('should use function package level artifact when provided', () => {
|
||||
stateFileMock.service.functions = {
|
||||
first: {
|
||||
package: {
|
||||
artifact: 'artifact.zip',
|
||||
},
|
||||
},
|
||||
};
|
||||
awsDeploy.serverless.service.package.individually = true;
|
||||
fileExistsSyncStub.returns(true);
|
||||
readFileSyncStub.returns(stateFileMock);
|
||||
|
||||
return awsDeploy.extendedValidate().then(() => {
|
||||
expect(fileExistsSyncStub.calledTwice).to.equal(true);
|
||||
expect(readFileSyncStub.calledOnce).to.equal(true);
|
||||
expect(fileExistsSyncStub).to.have.been.calledWithExactly('artifact.zip');
|
||||
});
|
||||
});
|
||||
|
||||
it('should throw error if specified package artifact does not exist', () => {
|
||||
// const fileExistsSyncStub = sinon.stub(awsDeploy.serverless.utils, 'fileExistsSync');
|
||||
fileExistsSyncStub.onCall(0).returns(true);
|
||||
fileExistsSyncStub.onCall(1).returns(false);
|
||||
readFileSyncStub.returns(stateFileMock);
|
||||
awsDeploy.serverless.service.package.artifact = 'some/file.zip';
|
||||
expect(() => awsDeploy.extendedValidate()).to.throw(Error);
|
||||
delete awsDeploy.serverless.service.package.artifact;
|
||||
});
|
||||
|
||||
it('should not throw error if specified package artifact exists', () => {
|
||||
// const fileExistsSyncStub = sinon.stub(awsDeploy.serverless.utils, 'fileExistsSync');
|
||||
fileExistsSyncStub.onCall(0).returns(true);
|
||||
fileExistsSyncStub.onCall(1).returns(true);
|
||||
readFileSyncStub.returns(stateFileMock);
|
||||
awsDeploy.serverless.service.package.artifact = 'some/file.zip';
|
||||
return awsDeploy.extendedValidate().then(() => {
|
||||
delete awsDeploy.serverless.service.package.artifact;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
'use strict';
|
||||
|
||||
const BbPromise = require('bluebird');
|
||||
const _ = require('lodash');
|
||||
const crypto = require('crypto');
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
@ -68,7 +69,15 @@ class AwsDeployFunction {
|
||||
deployFunction() {
|
||||
const artifactFileName = this.provider.naming
|
||||
.getFunctionArtifactName(this.options.function);
|
||||
const artifactFilePath = path.join(this.packagePath, artifactFileName);
|
||||
let artifactFilePath = this.serverless.service.package.artifact ||
|
||||
path.join(this.packagePath, artifactFileName);
|
||||
|
||||
// check if an artifact is used in function package level
|
||||
const functionObject = this.serverless.service.getFunction(this.options.function);
|
||||
if (_.has(functionObject, ['package', 'artifact'])) {
|
||||
artifactFilePath = functionObject.package.artifact;
|
||||
}
|
||||
|
||||
const data = fs.readFileSync(artifactFilePath);
|
||||
|
||||
const remoteHash = this.serverless.service.provider.remoteFunctionData.Configuration.CodeSha256;
|
||||
|
||||
@ -221,5 +221,42 @@ describe('AwsDeployFunction', () => {
|
||||
expect(readFileSyncStub.calledWithExactly(artifactFilePath)).to.equal(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when artifact is provided', () => {
|
||||
let getFunctionStub;
|
||||
const artifactZipFile = 'artifact.zip';
|
||||
|
||||
beforeEach(() => {
|
||||
getFunctionStub = sinon.stub(serverless.service, 'getFunction').returns({
|
||||
handler: true,
|
||||
package: {
|
||||
artifact: artifactZipFile,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
serverless.service.getFunction.restore();
|
||||
});
|
||||
|
||||
it('should read the provided artifact', () => awsDeployFunction.deployFunction().then(() => {
|
||||
const data = fs.readFileSync(artifactZipFile);
|
||||
|
||||
expect(readFileSyncStub).to.have.been.calledWithExactly(artifactZipFile);
|
||||
expect(statSyncStub).to.have.been.calledWithExactly(artifactZipFile);
|
||||
expect(getFunctionStub).to.have.been.calledWithExactly('first');
|
||||
expect(updateFunctionCodeStub.calledOnce).to.equal(true);
|
||||
expect(updateFunctionCodeStub.calledWithExactly(
|
||||
'Lambda',
|
||||
'updateFunctionCode',
|
||||
{
|
||||
FunctionName: 'first',
|
||||
ZipFile: data,
|
||||
},
|
||||
awsDeployFunction.options.stage,
|
||||
awsDeployFunction.options.region
|
||||
)).to.be.equal(true);
|
||||
}));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@ -84,8 +84,8 @@ class AwsInvokeLocal {
|
||||
LD_LIBRARY_PATH: '/usr/local/lib64/node-v4.3.x/lib:/lib64:/usr/lib64:/var/runtime:/var/runtime/lib:/var/task:/var/task/lib', // eslint-disable-line max-len
|
||||
LAMBDA_TASK_ROOT: '/var/task',
|
||||
LAMBDA_RUNTIME_DIR: '/var/runtime',
|
||||
AWS_REGION: this.options.region,
|
||||
AWS_DEFAULT_REGION: this.options.region,
|
||||
AWS_REGION: this.options.region || _.get(this.serverless, 'service.provider.region'),
|
||||
AWS_DEFAULT_REGION: this.options.region || _.get(this.serverless, 'service.provider.region'),
|
||||
AWS_LAMBDA_LOG_GROUP_NAME: this.provider.naming.getLogGroupName(lambdaName),
|
||||
AWS_LAMBDA_LOG_STREAM_NAME: '2016/12/02/[$LATEST]f77ff5e4026c45bda9a9ebcec6bc9cad',
|
||||
AWS_LAMBDA_FUNCTION_NAME: lambdaName,
|
||||
|
||||
@ -74,7 +74,9 @@ module.exports = {
|
||||
}
|
||||
// Keep track of first failed event
|
||||
if (eventStatus
|
||||
&& eventStatus.endsWith('FAILED') && stackLatestError === null) {
|
||||
&& (eventStatus.endsWith('FAILED')
|
||||
|| eventStatus === 'UPDATE_ROLLBACK_IN_PROGRESS')
|
||||
&& stackLatestError === null) {
|
||||
stackLatestError = event;
|
||||
}
|
||||
// Log stack events
|
||||
@ -102,6 +104,8 @@ module.exports = {
|
||||
|| (stackStatus
|
||||
&& stackStatus.endsWith('ROLLBACK_COMPLETE')
|
||||
&& this.options.verbose)) {
|
||||
// empty console.log for a prettier output
|
||||
if (!this.options.verbose) this.serverless.cli.consoleLog('');
|
||||
this.serverless.cli.log('Deployment failed!');
|
||||
let errorMessage = 'An error occurred while provisioning your stack: ';
|
||||
errorMessage += `${stackLatestError.LogicalResourceId} - `;
|
||||
|
||||
@ -646,5 +646,69 @@ describe('monitorStack', () => {
|
||||
awsPlugin.provider.request.restore();
|
||||
});
|
||||
});
|
||||
|
||||
it('should record an error and fail if status is UPDATE_ROLLBACK_IN_PROGRESS', () => {
|
||||
const describeStackEventsStub = sinon.stub(awsPlugin.provider, 'request');
|
||||
const cfDataMock = {
|
||||
StackId: 'new-service-dev',
|
||||
};
|
||||
const updateStartEvent = {
|
||||
StackEvents: [
|
||||
{
|
||||
EventId: '1a2b3c4d',
|
||||
LogicalResourceId: 'mocha',
|
||||
ResourceType: 'AWS::CloudFormation::Stack',
|
||||
Timestamp: new Date(),
|
||||
ResourceStatus: 'UPDATE_IN_PROGRESS',
|
||||
},
|
||||
],
|
||||
};
|
||||
const updateRollbackEvent = {
|
||||
StackEvents: [
|
||||
{
|
||||
EventId: '1i2j3k4l',
|
||||
LogicalResourceId: 'mocha',
|
||||
ResourceType: 'AWS::CloudFormation::Stack',
|
||||
Timestamp: new Date(),
|
||||
ResourceStatus: 'UPDATE_ROLLBACK_IN_PROGRESS',
|
||||
ResourceStatusReason: 'Export is in use',
|
||||
},
|
||||
],
|
||||
};
|
||||
const updateRollbackCompleteEvent = {
|
||||
StackEvents: [
|
||||
{
|
||||
EventId: '1m2n3o4p',
|
||||
LogicalResourceId: 'mocha',
|
||||
ResourceType: 'AWS::CloudFormation::Stack',
|
||||
Timestamp: new Date(),
|
||||
ResourceStatus: 'UPDATE_ROLLBACK_COMPLETE',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
describeStackEventsStub.onCall(0).resolves(updateStartEvent);
|
||||
describeStackEventsStub.onCall(1).resolves(updateRollbackEvent);
|
||||
describeStackEventsStub.onCall(2).resolves(updateRollbackCompleteEvent);
|
||||
|
||||
return awsPlugin.monitorStack('update', cfDataMock, 10).catch((e) => {
|
||||
let errorMessage = 'An error occurred while provisioning your stack: ';
|
||||
errorMessage += 'mocha - Export is in use.';
|
||||
expect(e.name).to.be.equal('ServerlessError');
|
||||
expect(e.message).to.be.equal(errorMessage);
|
||||
// callCount is 2 because Serverless will immediately exits and shows the error
|
||||
expect(describeStackEventsStub.callCount).to.be.equal(2);
|
||||
expect(describeStackEventsStub.calledWithExactly(
|
||||
'CloudFormation',
|
||||
'describeStackEvents',
|
||||
{
|
||||
StackName: cfDataMock.StackId,
|
||||
},
|
||||
awsPlugin.options.stage,
|
||||
awsPlugin.options.region
|
||||
)).to.be.equal(true);
|
||||
awsPlugin.provider.request.restore();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@ -96,15 +96,42 @@ module.exports = {
|
||||
}
|
||||
} else if (http.integration === 'AWS_PROXY') {
|
||||
// show a warning when request / response config is used with AWS_PROXY (LAMBDA-PROXY)
|
||||
if (http.request || http.response) {
|
||||
if (http.request) {
|
||||
const keys = Object.keys(http.request);
|
||||
if (!(keys.length === 1 && keys[0] === 'parameters')) {
|
||||
const requestWarningMessage = [
|
||||
'Warning! You\'re using the LAMBDA-PROXY in combination with a request',
|
||||
` configuration in your function "${functionName}". Only the`,
|
||||
' \'request.parameters\' configs are available in conjunction with',
|
||||
' LAMBDA-PROXY. Serverless will remove this configuration automatically',
|
||||
' before deployment.',
|
||||
].join('');
|
||||
this.serverless.cli.log(requestWarningMessage);
|
||||
for (const key of keys) {
|
||||
if (key !== 'parameters') {
|
||||
delete http.request[key];
|
||||
}
|
||||
}
|
||||
}
|
||||
if (Object.keys(http.request).length === 0) {
|
||||
// No keys left, delete the request object
|
||||
delete http.request;
|
||||
} else {
|
||||
http.request = this.getRequest(http);
|
||||
|
||||
if (http.request.parameters) {
|
||||
http.request.parameters = this.getRequestParameters(http.request);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (http.response) {
|
||||
const warningMessage = [
|
||||
'Warning! You\'re using the LAMBDA-PROXY in combination with request / response',
|
||||
'Warning! You\'re using the LAMBDA-PROXY in combination with response',
|
||||
` configuration in your function "${functionName}".`,
|
||||
' Serverless will remove this configuration automatically before deployment.',
|
||||
].join('');
|
||||
this.serverless.cli.log(warningMessage);
|
||||
|
||||
delete http.request;
|
||||
delete http.response;
|
||||
}
|
||||
} else if (http.integration === 'HTTP' || http.integration === 'HTTP_PROXY') {
|
||||
@ -246,7 +273,7 @@ module.exports = {
|
||||
|
||||
const integration = this.getIntegration(http);
|
||||
if (integration === 'AWS_PROXY'
|
||||
&& typeof arn === 'string' && arn.match(/^arn:aws:cognito-idp/) && authorizer.claims) {
|
||||
&& typeof arn === 'string' && arn.match(/^arn:aws:cognito-idp/) && authorizer.claims) {
|
||||
const errorMessage = [
|
||||
'Cognito claims can only be filtered when using the lambda integration type',
|
||||
];
|
||||
|
||||
@ -945,7 +945,7 @@ describe('#validate()', () => {
|
||||
expect(() => awsCompileApigEvents.validate()).to.throw(Error);
|
||||
});
|
||||
|
||||
it('should process request parameters', () => {
|
||||
it('should process request parameters for lambda integration', () => {
|
||||
awsCompileApigEvents.serverless.service.functions = {
|
||||
first: {
|
||||
events: [
|
||||
@ -988,6 +988,49 @@ describe('#validate()', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('should process request parameters for lambda-proxy integration', () => {
|
||||
awsCompileApigEvents.serverless.service.functions = {
|
||||
first: {
|
||||
events: [
|
||||
{
|
||||
http: {
|
||||
integration: 'lambda-proxy',
|
||||
path: 'foo/bar',
|
||||
method: 'GET',
|
||||
request: {
|
||||
parameters: {
|
||||
querystrings: {
|
||||
foo: true,
|
||||
bar: false,
|
||||
},
|
||||
paths: {
|
||||
foo: true,
|
||||
bar: false,
|
||||
},
|
||||
headers: {
|
||||
foo: true,
|
||||
bar: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
const validated = awsCompileApigEvents.validate();
|
||||
expect(validated.events).to.be.an('Array').with.length(1);
|
||||
expect(validated.events[0].http.request.parameters).to.deep.equal({
|
||||
'method.request.querystring.foo': true,
|
||||
'method.request.querystring.bar': false,
|
||||
'method.request.path.foo': true,
|
||||
'method.request.path.bar': false,
|
||||
'method.request.header.foo': true,
|
||||
'method.request.header.bar': false,
|
||||
});
|
||||
});
|
||||
|
||||
it('should throw an error if the provided response config is not an object', () => {
|
||||
awsCompileApigEvents.serverless.service.functions = {
|
||||
first: {
|
||||
@ -1262,11 +1305,51 @@ describe('#validate()', () => {
|
||||
|
||||
awsCompileApigEvents.validate();
|
||||
|
||||
expect(logStub.calledOnce).to.be.equal(true);
|
||||
expect(logStub.calledTwice).to.be.equal(true);
|
||||
expect(logStub.args[0][0].length).to.be.at.least(1);
|
||||
});
|
||||
|
||||
it('should remove request/response config with LAMBDA-PROXY', () => {
|
||||
it('should not show a warning message when using request.parameter with LAMBDA-PROXY', () => {
|
||||
awsCompileApigEvents.serverless.service.functions = {
|
||||
first: {
|
||||
events: [
|
||||
{
|
||||
http: {
|
||||
method: 'GET',
|
||||
path: 'users/list',
|
||||
integration: 'lambda-proxy',
|
||||
request: {
|
||||
parameters: {
|
||||
querystrings: {
|
||||
foo: true,
|
||||
bar: false,
|
||||
},
|
||||
paths: {
|
||||
foo: true,
|
||||
bar: false,
|
||||
},
|
||||
headers: {
|
||||
foo: true,
|
||||
bar: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
// initialize so we get the log method from the CLI in place
|
||||
serverless.init();
|
||||
|
||||
const logStub = sinon.stub(serverless.cli, 'log');
|
||||
|
||||
awsCompileApigEvents.validate();
|
||||
|
||||
expect(logStub.called).to.be.equal(false);
|
||||
});
|
||||
|
||||
it('should remove non-parameter request/response config with LAMBDA-PROXY', () => {
|
||||
awsCompileApigEvents.serverless.service.functions = {
|
||||
first: {
|
||||
events: [
|
||||
@ -1279,6 +1362,11 @@ describe('#validate()', () => {
|
||||
template: {
|
||||
'template/1': '{ "stage" : "$context.stage" }',
|
||||
},
|
||||
parameters: {
|
||||
paths: {
|
||||
foo: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
response: {},
|
||||
},
|
||||
@ -1294,8 +1382,10 @@ describe('#validate()', () => {
|
||||
|
||||
const validated = awsCompileApigEvents.validate();
|
||||
expect(validated.events).to.be.an('Array').with.length(1);
|
||||
expect(validated.events[0].http.request).to.equal(undefined);
|
||||
expect(validated.events[0].http.response).to.equal(undefined);
|
||||
expect(validated.events[0].http.request.parameters).to.deep.equal({
|
||||
'method.request.path.foo': true,
|
||||
});
|
||||
});
|
||||
|
||||
it('should throw an error when an invalid integration type was provided', () => {
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
'use strict';
|
||||
|
||||
const BbPromise = require('bluebird');
|
||||
const path = require('path');
|
||||
const _ = require('lodash');
|
||||
|
||||
module.exports = {
|
||||
@ -13,6 +14,7 @@ module.exports = {
|
||||
'serverless.yaml',
|
||||
'serverless.json',
|
||||
'.serverless/**',
|
||||
'.serverless_plugins/**',
|
||||
],
|
||||
|
||||
getIncludes(include) {
|
||||
@ -38,6 +40,9 @@ module.exports = {
|
||||
this.serverless.cli.log(`Packaging disabled for function: "${functionName}"`);
|
||||
return BbPromise.resolve();
|
||||
}
|
||||
if (functionObject.package.artifact) {
|
||||
return BbPromise.resolve();
|
||||
}
|
||||
if (functionObject.package.individually || this.serverless.service
|
||||
.package.individually) {
|
||||
return this.packageFunction(functionName);
|
||||
@ -47,7 +52,7 @@ module.exports = {
|
||||
});
|
||||
|
||||
return BbPromise.all(packagePromises).then(() => {
|
||||
if (shouldPackageService) {
|
||||
if (shouldPackageService && !this.serverless.service.package.artifact) {
|
||||
return this.packageAll();
|
||||
}
|
||||
return BbPromise.resolve();
|
||||
@ -74,12 +79,29 @@ module.exports = {
|
||||
const functionObject = this.serverless.service.getFunction(functionName);
|
||||
const funcPackageConfig = functionObject.package || {};
|
||||
|
||||
// use the artifact in function config if provided
|
||||
if (funcPackageConfig.artifact) {
|
||||
const filePath = path.join(this.serverless.config.servicePath, funcPackageConfig.artifact);
|
||||
functionObject.package.artifact = filePath;
|
||||
return BbPromise.resolve(filePath);
|
||||
}
|
||||
|
||||
// use the artifact in service config if provided
|
||||
if (this.serverless.service.package.artifact) {
|
||||
const filePath = path.join(this.serverless.config.servicePath,
|
||||
this.serverless.service.package.artifact);
|
||||
funcPackageConfig.artifact = filePath;
|
||||
return BbPromise.resolve(filePath);
|
||||
}
|
||||
|
||||
const exclude = this.getExcludes(funcPackageConfig.exclude);
|
||||
const include = this.getIncludes(funcPackageConfig.include);
|
||||
const zipFileName = `${functionName}.zip`;
|
||||
|
||||
return this.zipService(exclude, include, zipFileName).then(artifactPath => {
|
||||
functionObject.artifact = artifactPath;
|
||||
functionObject.package = {
|
||||
artifact: artifactPath,
|
||||
};
|
||||
return artifactPath;
|
||||
});
|
||||
},
|
||||
|
||||
@ -82,7 +82,8 @@ describe('#packageService()', () => {
|
||||
'.git/**', '.gitignore', '.DS_Store',
|
||||
'npm-debug.log', 'serverless.yml',
|
||||
'serverless.yaml', 'serverless.json',
|
||||
'.serverless/**', 'dir', 'file.js',
|
||||
'.serverless/**', '.serverless_plugins/**',
|
||||
'dir', 'file.js',
|
||||
]);
|
||||
});
|
||||
|
||||
@ -101,8 +102,8 @@ describe('#packageService()', () => {
|
||||
'.git/**', '.gitignore', '.DS_Store',
|
||||
'npm-debug.log', 'serverless.yml',
|
||||
'serverless.yaml', 'serverless.json',
|
||||
'.serverless/**', 'dir', 'file.js',
|
||||
'lib', 'other.js',
|
||||
'.serverless/**', '.serverless_plugins/**',
|
||||
'dir', 'file.js', 'lib', 'other.js',
|
||||
]);
|
||||
});
|
||||
});
|
||||
@ -161,13 +162,24 @@ describe('#packageService()', () => {
|
||||
));
|
||||
});
|
||||
|
||||
it('should not package service with only disabled functions', () => {
|
||||
it('should not package functions if package artifact specified', () => {
|
||||
serverless.service.package.artifact = 'some/file.zip';
|
||||
|
||||
const packageAllStub = sinon.stub(packagePlugin, 'packageAll').resolves();
|
||||
|
||||
return expect(packagePlugin.packageService()).to.be.fulfilled
|
||||
.then(() => expect(packageAllStub).to.not.be.called);
|
||||
});
|
||||
|
||||
it('should package functions individually if package artifact specified', () => {
|
||||
serverless.service.package.artifact = 'some/file.zip';
|
||||
serverless.service.package.individually = true;
|
||||
serverless.service.functions = {
|
||||
'test-one': {
|
||||
name: 'test-one',
|
||||
package: {
|
||||
disable: true,
|
||||
},
|
||||
},
|
||||
'test-two': {
|
||||
name: 'test-two',
|
||||
},
|
||||
};
|
||||
|
||||
@ -178,10 +190,36 @@ describe('#packageService()', () => {
|
||||
|
||||
return expect(packagePlugin.packageService()).to.be.fulfilled
|
||||
.then(() => BbPromise.join(
|
||||
expect(packageFunctionStub).to.not.be.calledOnce,
|
||||
expect(packageAllStub).to.not.be.calledOnce
|
||||
expect(packageFunctionStub).to.be.calledTwice,
|
||||
expect(packageAllStub).to.not.be.called
|
||||
));
|
||||
});
|
||||
|
||||
it('should package single functions individually if package artifact specified', () => {
|
||||
serverless.service.package.artifact = 'some/file.zip';
|
||||
serverless.service.functions = {
|
||||
'test-one': {
|
||||
name: 'test-one',
|
||||
package: {
|
||||
individually: true,
|
||||
},
|
||||
},
|
||||
'test-two': {
|
||||
name: 'test-two',
|
||||
},
|
||||
};
|
||||
|
||||
const packageFunctionStub = sinon
|
||||
.stub(packagePlugin, 'packageFunction').resolves((func) => func.name);
|
||||
const packageAllStub = sinon
|
||||
.stub(packagePlugin, 'packageAll').resolves((func) => func.name);
|
||||
|
||||
return expect(packagePlugin.packageService()).to.be.fulfilled
|
||||
.then(() => BbPromise.join(
|
||||
expect(packageFunctionStub).to.be.calledOnce,
|
||||
expect(packageAllStub).to.not.be.called
|
||||
));
|
||||
});
|
||||
});
|
||||
|
||||
describe('#packageAll()', () => {
|
||||
@ -273,5 +311,49 @@ describe('#packageService()', () => {
|
||||
),
|
||||
]));
|
||||
});
|
||||
|
||||
it('should return function artifact file path', () => {
|
||||
const servicePath = 'test';
|
||||
const funcName = 'test-func';
|
||||
|
||||
serverless.config.servicePath = servicePath;
|
||||
serverless.service.functions = {};
|
||||
serverless.service.functions[funcName] = {
|
||||
name: `test-proj-${funcName}`,
|
||||
package: {
|
||||
artifact: 'artifact.zip',
|
||||
},
|
||||
};
|
||||
|
||||
return expect(packagePlugin.packageFunction(funcName)).to.eventually
|
||||
.equal('test/artifact.zip')
|
||||
.then(() => BbPromise.all([
|
||||
expect(getExcludesStub).to.not.have.been.called,
|
||||
expect(getIncludesStub).to.not.have.been.called,
|
||||
expect(zipServiceStub).to.not.have.been.called,
|
||||
]));
|
||||
});
|
||||
|
||||
it('should return service artifact file path', () => {
|
||||
const servicePath = 'test';
|
||||
const funcName = 'test-func';
|
||||
|
||||
serverless.config.servicePath = servicePath;
|
||||
serverless.service.functions = {};
|
||||
serverless.service.package = {
|
||||
artifact: 'artifact.zip',
|
||||
};
|
||||
serverless.service.functions[funcName] = {
|
||||
name: `test-proj-${funcName}`,
|
||||
};
|
||||
|
||||
return expect(packagePlugin.packageFunction(funcName)).to.eventually
|
||||
.equal('test/artifact.zip')
|
||||
.then(() => BbPromise.all([
|
||||
expect(getExcludesStub).to.not.have.been.called,
|
||||
expect(getIncludesStub).to.not.have.been.called,
|
||||
expect(zipServiceStub).to.not.have.been.called,
|
||||
]));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
4508
package-lock.json
generated
4508
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
Loading…
x
Reference in New Issue
Block a user