Merge branch 'master' into travis-keys

This commit is contained in:
Eslam A. Hefnawy 2017-07-16 18:56:35 +07:00
commit 4ccf96dfdc
31 changed files with 5151 additions and 206 deletions

View File

@ -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

View File

@ -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"
```

View File

@ -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

View File

@ -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.

View File

@ -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}}

View File

@ -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

View File

@ -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

View File

@ -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.

View File

@ -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

View File

@ -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:

View File

@ -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`)

View File

@ -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.

View File

@ -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:

View File

@ -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 = [];

View File

@ -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: {

View File

@ -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

View File

@ -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);

View File

@ -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);
}

View File

@ -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();

View File

@ -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.`);

View File

@ -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;
});
});
});

View File

@ -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;

View File

@ -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);
}));
});
});
});

View File

@ -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,

View File

@ -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} - `;

View File

@ -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();
});
});
});
});

View File

@ -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',
];

View File

@ -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', () => {

View File

@ -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;
});
},

View File

@ -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

File diff suppressed because it is too large Load Diff