diff --git a/bin/slss.js b/bin/slss.js
new file mode 100755
index 000000000000..a8dd90ae50c5
--- /dev/null
+++ b/bin/slss.js
@@ -0,0 +1,12 @@
+#!/usr/bin/env node
+
+// TODO (BREAKING): Remove this file with next major release
+
+'use strict';
+
+require('../lib/utils/logDeprecation')(
+ 'SLSS_CLI_ALIAS',
+ 'Support for "slss" command will be removed with v2.0.0. Use "sls" or "serverless" instead'
+);
+
+require('./serverless.js');
diff --git a/docs/deprecations.md b/docs/deprecations.md
index 6e5c0ef9848e..cb043f4ee36c 100644
--- a/docs/deprecations.md
+++ b/docs/deprecations.md
@@ -6,6 +6,12 @@ layout: Doc
# Serverless Framework Deprecations
+
+
+## `slss` alias
+
+Support for `slss` command will be removed with v2.0.0. Use `sls` or `serverless` instead.
+
## AWS Lambda Function Destinations `maximumEventAge` & `maximumRetryAttempts`
diff --git a/docs/providers/aws/events/apigateway.md b/docs/providers/aws/events/apigateway.md
index 77c99fa11b93..6680fc31ccae 100644
--- a/docs/providers/aws/events/apigateway.md
+++ b/docs/providers/aws/events/apigateway.md
@@ -994,6 +994,22 @@ functions:
You can then access the query string `https://example.com/dev/whatever?bar=123` by `event.foo` in the lambda function.
If you want to spread a string into multiple lines, you can use the `>` or `|` syntax, but the following strings have to be all indented with the same amount, [read more about `>` syntax](http://stackoverflow.com/questions/3790454/in-yaml-how-do-i-break-a-string-over-multiple-lines).
+In order to remove one of the default request templates you just need to pass it as null, as follows:
+
+```yml
+functions:
+ create:
+ handler: posts.create
+ events:
+ - http:
+ method: get
+ path: whatever
+ integration: lambda
+ request:
+ template:
+ application/x-www-form-urlencoded: null
+```
+
#### Pass Through Behavior
[API Gateway](https://serverless.com/amazon-api-gateway/) provides multiple ways to handle requests where the Content-Type header does not match any of the specified mapping templates. When this happens, the request payload will either be passed through the integration request _without transformation_ or rejected with a `415 - Unsupported Media Type`, depending on the configuration.
diff --git a/lib/plugins/aws/info/getStackInfo.js b/lib/plugins/aws/info/getStackInfo.js
index edee86479824..82341ca86be4 100644
--- a/lib/plugins/aws/info/getStackInfo.js
+++ b/lib/plugins/aws/info/getStackInfo.js
@@ -34,24 +34,22 @@ module.exports = {
if (httpApiId) {
sdkRequests.push(
(httpApiId['Fn::ImportValue']
- ? resolveCfImportValue(this.provider, httpApiId['Fn::ImportValue']).then(id => {
- if (!id) {
- throw new this.serverless.classes.Error(
- `Could not resolve provider.httpApi.id parameter. Configured value: ${JSON.stringify(
- httpApiId
- )}`
- );
- }
- return id;
- })
+ ? resolveCfImportValue(this.provider, httpApiId['Fn::ImportValue'])
: BbPromise.resolve(httpApiId)
)
.then(id => {
return this.provider.request('ApiGatewayV2', 'getApi', { ApiId: id });
})
- .then(result => {
- if (result) stackData.externalHttpApiEndpoint = result.ApiEndpoint;
- })
+ .then(
+ result => {
+ stackData.externalHttpApiEndpoint = result.ApiEndpoint;
+ },
+ error => {
+ throw new this.serverless.classes.Error(
+ `Could not resolve provider.httpApi.id parameter. ${error.message}`
+ );
+ }
+ )
);
}
diff --git a/lib/plugins/aws/info/getStackInfo.test.js b/lib/plugins/aws/info/getStackInfo.test.js
index f44d2431a91d..2cb545737858 100644
--- a/lib/plugins/aws/info/getStackInfo.test.js
+++ b/lib/plugins/aws/info/getStackInfo.test.js
@@ -195,15 +195,25 @@ describe('#getStackInfo()', () => {
id: 'http-api-id',
};
- const describeStacksResponse = null;
-
- describeStacksStub.resolves(describeStacksResponse);
+ describeStacksStub
+ .withArgs('CloudFormation', 'describeStacks', {
+ StackName: awsInfo.provider.naming.getStackName(),
+ })
+ .resolves(null);
+
+ describeStacksStub
+ .withArgs('ApiGatewayV2', 'getApi', {
+ ApiId: 'http-api-id',
+ })
+ .resolves({
+ ApiEndpoint: 'my-endpoint',
+ });
const expectedGatheredDataObj = {
info: {
functions: [],
layers: [],
- endpoints: [],
+ endpoints: ['httpApi: my-endpoint'],
service: 'my-service',
stage: 'dev',
region: 'us-east-1',
diff --git a/lib/plugins/aws/invokeLocal/index.js b/lib/plugins/aws/invokeLocal/index.js
index 6c501e5bf4ff..61a729775c29 100644
--- a/lib/plugins/aws/invokeLocal/index.js
+++ b/lib/plugins/aws/invokeLocal/index.js
@@ -19,6 +19,7 @@ const dirExists = require('../../../utils/fs/dirExists');
const fileExists = require('../../../utils/fs/fileExists');
const isStandalone = require('../../../utils/isStandaloneExecutable');
const getEnsureArtifact = require('../../../utils/getEnsureArtifact');
+const resolveCfImportValue = require('../utils/resolveCfImportValue');
const mkdirpAsync = BbPromise.promisify(mkdirp);
@@ -150,41 +151,59 @@ class AwsInvokeLocal {
}
loadEnvVars() {
- const lambdaName = this.options.functionObj.name;
- const memorySize =
- Number(this.options.functionObj.memorySize) ||
- Number(this.serverless.service.provider.memorySize) ||
- 1024;
-
- const lambdaDefaultEnvVars = {
- LANG: 'en_US.UTF-8',
- 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.provider.getRegion(),
- AWS_DEFAULT_REGION: this.provider.getRegion(),
- 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,
- AWS_LAMBDA_FUNCTION_MEMORY_SIZE: memorySize,
- AWS_LAMBDA_FUNCTION_VERSION: '$LATEST',
- NODE_PATH: '/var/runtime:/var/task:/var/runtime/node_modules',
- };
-
- const credentialEnvVars = this.getCredentialEnvVars();
+ return BbPromise.try(() => {
+ const lambdaName = this.options.functionObj.name;
+ const memorySize =
+ Number(this.options.functionObj.memorySize) ||
+ Number(this.serverless.service.provider.memorySize) ||
+ 1024;
+
+ const lambdaDefaultEnvVars = {
+ LANG: 'en_US.UTF-8',
+ 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.provider.getRegion(),
+ AWS_DEFAULT_REGION: this.provider.getRegion(),
+ 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,
+ AWS_LAMBDA_FUNCTION_MEMORY_SIZE: memorySize,
+ AWS_LAMBDA_FUNCTION_VERSION: '$LATEST',
+ NODE_PATH: '/var/runtime:/var/task:/var/runtime/node_modules',
+ };
- // profile override from config
- const profileOverride = this.provider.getProfile();
- if (profileOverride) {
- lambdaDefaultEnvVars.AWS_PROFILE = profileOverride;
- }
+ const credentialEnvVars = this.getCredentialEnvVars();
- const configuredEnvVars = this.getConfiguredEnvVars();
+ // profile override from config
+ const profileOverride = this.provider.getProfile();
+ if (profileOverride) {
+ lambdaDefaultEnvVars.AWS_PROFILE = profileOverride;
+ }
- _.merge(process.env, lambdaDefaultEnvVars, credentialEnvVars, configuredEnvVars);
+ const configuredEnvVars = this.getConfiguredEnvVars();
+
+ const promises = _.entries(configuredEnvVars).map(([name, value]) => {
+ return (value['Fn::ImportValue']
+ ? resolveCfImportValue(this.provider, value['Fn::ImportValue'])
+ : BbPromise.resolve(value)
+ ).then(
+ resolvedValue => {
+ configuredEnvVars[name] = resolvedValue;
+ },
+ error => {
+ throw new this.serverless.classes.Error(
+ `Could not resolve ${name} environment variable. ${error.message}`
+ );
+ }
+ );
+ });
- return BbPromise.resolve();
+ return BbPromise.all(promises).then(() => {
+ _.merge(process.env, lambdaDefaultEnvVars, credentialEnvVars, configuredEnvVars);
+ });
+ });
}
invokeLocal() {
diff --git a/lib/plugins/aws/package/compile/events/apiGateway/lib/method/index.test.js b/lib/plugins/aws/package/compile/events/apiGateway/lib/method/index.test.js
index acfde4b271ab..c1ab20a1c2e2 100644
--- a/lib/plugins/aws/package/compile/events/apiGateway/lib/method/index.test.js
+++ b/lib/plugins/aws/package/compile/events/apiGateway/lib/method/index.test.js
@@ -1304,6 +1304,37 @@ describe('#compileMethods()', () => {
});
});
+ it('should delete the default "application/x-www-form-urlencoded" template if it\'s overriden with null', () => {
+ awsCompileApigEvents.validated.events = [
+ {
+ functionName: 'Second',
+ http: {
+ method: 'get',
+ path: 'users/list',
+ integration: 'AWS',
+ request: {
+ template: {
+ 'application/x-www-form-urlencoded': null,
+ },
+ },
+ response: {
+ statusCodes: {
+ 200: {
+ pattern: '',
+ },
+ },
+ },
+ },
+ },
+ ];
+ return awsCompileApigEvents.compileMethods().then(() => {
+ expect(
+ awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources
+ .ApiGatewayMethodUsersListGet.Properties.Integration.RequestTemplates
+ ).to.not.have.key('application/x-www-form-urlencoded');
+ });
+ });
+
it('should use defined pass-through behavior', () => {
awsCompileApigEvents.validated.events = [
{
diff --git a/lib/plugins/aws/package/compile/events/apiGateway/lib/method/integration.js b/lib/plugins/aws/package/compile/events/apiGateway/lib/method/integration.js
index 3be224959108..140b18edd6bb 100644
--- a/lib/plugins/aws/package/compile/events/apiGateway/lib/method/integration.js
+++ b/lib/plugins/aws/package/compile/events/apiGateway/lib/method/integration.js
@@ -209,7 +209,13 @@ module.exports = {
// set custom request templates if provided
if (http.request && typeof http.request.template === 'object') {
- Object.assign(integrationRequestTemplates, http.request.template);
+ _.entries(http.request.template).forEach(([contentType, template]) => {
+ if (template === null) {
+ delete integrationRequestTemplates[contentType];
+ } else {
+ integrationRequestTemplates[contentType] = template;
+ }
+ });
}
return Object.keys(integrationRequestTemplates).length
diff --git a/lib/plugins/aws/package/compile/events/sns/index.js b/lib/plugins/aws/package/compile/events/sns/index.js
index 24994ec5e8cc..ac2f0318800f 100644
--- a/lib/plugins/aws/package/compile/events/sns/index.js
+++ b/lib/plugins/aws/package/compile/events/sns/index.js
@@ -11,38 +11,47 @@ class AwsCompileSNSEvents {
'package:compileEvents': this.compileSNSEvents.bind(this),
};
- // TODO: Complete schema, see https://github.com/serverless/serverless/issues/8035
this.serverless.configSchemaHandler.defineFunctionEvent('aws', 'sns', {
- anyOf: [{ type: 'string' }, { type: 'object' }],
+ oneOf: [
+ { type: 'string', maxLength: 256, pattern: '^[\\w-]+$' },
+ { $ref: '#/definitions/awsArnString' },
+ {
+ type: 'object',
+ properties: {
+ arn: { $ref: '#/definitions/awsArn' },
+ topicName: { type: 'string', maxLength: 256, pattern: '^[\\w-]+$' },
+ displayName: { type: 'string', minLength: 1 },
+ filterPolicy: { type: 'object' },
+ redrivePolicy: {
+ type: 'object',
+ properties: {
+ deadLetterTargetArn: { $ref: '#/definitions/awsArnString' },
+ deadLetterTargetRef: { type: 'string', minLength: 1 },
+ deadLetterTargetImport: {
+ type: 'object',
+ properties: {
+ arn: { $ref: '#/definitions/awsArnString' },
+ url: { type: 'string', minLength: 1 },
+ },
+ required: ['arn', 'url'],
+ additionalProperties: false,
+ },
+ },
+ oneOf: [
+ { required: ['deadLetterTargetArn'] },
+ { required: ['deadLetterTargetRef'] },
+ { required: ['deadLetterTargetImport'] },
+ ],
+ additionalProperties: false,
+ },
+ },
+ oneOf: [{ required: ['arn'] }, { required: ['topicName', 'displayName'] }],
+ additionalProperties: false,
+ },
+ ],
});
}
- invalidPropertyErrorMessage(functionName, property) {
- return [
- `Missing or invalid ${property} property for sns event`,
- ` in function "${functionName}"`,
- ' The correct syntax is: sns: topic-name-or-arn',
- ' OR an object with ',
- ' arn and topicName OR',
- ' topicName and displayName.',
- ' Please check the docs for more info.',
- ].join('');
- }
-
- isValidStackImport(variable) {
- if (Object.keys(variable).length !== 1) {
- return false;
- }
- if (
- variable['Fn::ImportValue'] &&
- (variable['Fn::ImportValue']['Fn::GetAtt'] || variable['Fn::ImportValue'].Ref)
- ) {
- return false;
- }
- const intrinsicFunctions = ['Fn::ImportValue', 'Ref', 'Fn::GetAtt', 'Fn::Sub', 'Fn::Join'];
- return intrinsicFunctions.some(func => variable[func]);
- }
-
compileSNSEvents() {
const template = this.serverless.service.provider.compiledCloudFormationTemplate;
@@ -60,72 +69,37 @@ class AwsCompileSNSEvents {
if (typeof event.sns === 'object') {
if (event.sns.arn) {
topicArn = event.sns.arn;
- if (typeof topicArn === 'object') {
- if (!this.isValidStackImport(topicArn)) {
- throw new this.serverless.classes.Error(
- this.invalidPropertyErrorMessage(functionName, 'arn')
- );
- }
- } else if (typeof topicArn === 'string') {
- if (topicArn.indexOf('arn:') === 0) {
- // NOTE: we need to process the topic ARN that way due to lacking
- // support of regex lookbehind in Node.js 6.
- // Once Node.js 6 support is dropped we can change this to:
- // `const splitArn = topicArn.split(/(? s.replace(/@@/g, '::'));
- topicName = splitArn[splitArn.length - 1];
- if (splitArn[3] !== this.options.region) {
- region = splitArn[3];
- }
- } else {
- throw new this.serverless.classes.Error(
- this.invalidPropertyErrorMessage(functionName, 'arn')
- );
+ if (typeof topicArn === 'string') {
+ // NOTE: we need to process the topic ARN that way due to lacking
+ // support of regex lookbehind in Node.js 6.
+ // Once Node.js 6 support is dropped we can change this to:
+ // `const splitArn = topicArn.split(/(? s.replace(/@@/g, '::'));
+ topicName = splitArn[splitArn.length - 1];
+ if (splitArn[3] !== this.options.region) {
+ region = splitArn[3];
}
- } else {
- throw new this.serverless.classes.Error(
- this.invalidPropertyErrorMessage(functionName, 'arn')
- );
}
topicName = event.sns.topicName || topicName;
- if (!topicName || typeof topicName !== 'string') {
+ // Only true when providing CFN intrinsic function (not string) to arn property without topicName property
+ if (!topicName) {
throw new this.serverless.classes.Error(
- this.invalidPropertyErrorMessage(functionName, 'topicName')
+ 'Missing "sns.topicName" setting (it needs to be provided if "sns.arn" is not provided as plain ARN string)'
);
}
} else {
- ['topicName', 'displayName'].forEach(property => {
- if (typeof event.sns[property] === 'string') {
- return;
- }
- throw new this.serverless.classes.Error(
- this.invalidPropertyErrorMessage(functionName, property)
- );
- });
displayName = event.sns.displayName;
topicName = event.sns.topicName;
}
- } else if (typeof event.sns === 'string') {
- if (event.sns.indexOf('arn:') === 0) {
- topicArn = event.sns;
- const splitArn = topicArn.split(':');
- topicName = splitArn[splitArn.length - 1];
- } else {
- topicName = event.sns;
- }
+ } else if (event.sns.indexOf('arn:') === 0) {
+ topicArn = event.sns;
+ const splitArn = topicArn.split(':');
+ topicName = splitArn[splitArn.length - 1];
} else {
- const errorMessage = [
- `SNS event of function ${functionName} is not an object nor a string`,
- ' The correct syntax is: sns: topic-name-or-arn',
- ' OR an object with ',
- ' arn and topicName OR',
- ' topicName and displayName.',
- ' Please check the docs for more info.',
- ].join('');
- throw new this.serverless.classes.Error(errorMessage);
+ topicName = event.sns;
}
if (event.sns.redrivePolicy) {
@@ -136,41 +110,20 @@ class AwsCompileSNSEvents {
} = event.sns.redrivePolicy;
let targetArn;
let targetUrl;
- if (!deadLetterTargetArn && !deadLetterTargetRef && !deadLetterTargetImport) {
- throw new this.serverless.classes.Error(
- 'redrivePolicy must be specified with deadLetterTargetArn, deadLetterTargetRef or deadLetterTargetImport'
- );
- }
if (deadLetterTargetArn) {
- if (
- typeof deadLetterTargetArn !== 'string' ||
- !deadLetterTargetArn.startsWith('arn:')
- ) {
- throw new this.serverless.classes.Error(
- 'Invalid deadLetterTargetArn specified, it must be an string starting with arn:'
- );
- } else {
- targetArn = deadLetterTargetArn;
- // arn:aws:sqs:us-east-1:11111111111:myDLQ
- const [deQueueName, deAccount, deRegion] = deadLetterTargetArn
- .split(':')
- .reverse();
- targetUrl = {
- 'Fn::Join': [
- '',
- `https://sqs.${deRegion}.`,
- { Ref: 'AWS::URLSuffix' },
- `/${deAccount}/${deQueueName}`,
- ],
- };
- }
+ targetArn = deadLetterTargetArn;
+ // arn:aws:sqs:us-east-1:11111111111:myDLQ
+ const [deQueueName, deAccount, deRegion] = deadLetterTargetArn.split(':').reverse();
+ targetUrl = {
+ 'Fn::Join': [
+ '',
+ `https://sqs.${deRegion}.`,
+ { Ref: 'AWS::URLSuffix' },
+ `/${deAccount}/${deQueueName}`,
+ ],
+ };
} else if (deadLetterTargetRef) {
- if (typeof deadLetterTargetRef !== 'string') {
- throw new this.serverless.classes.Error(
- 'Invalid deadLetterTargetRef specified, it must be a string'
- );
- }
targetArn = {
'Fn::GetAtt': [deadLetterTargetRef, 'Arn'],
};
@@ -178,15 +131,6 @@ class AwsCompileSNSEvents {
Ref: deadLetterTargetRef,
};
} else {
- if (
- typeof deadLetterTargetImport !== 'object' ||
- !deadLetterTargetImport.arn ||
- !deadLetterTargetImport.url
- ) {
- throw new this.serverless.classes.Error(
- 'Invalid deadLetterTargetImport specified, it must be an object containing arn and url fields'
- );
- }
targetArn = {
'Fn::ImportValue': deadLetterTargetImport.arn,
};
diff --git a/lib/plugins/aws/package/compile/events/sns/index.test.js b/lib/plugins/aws/package/compile/events/sns/index.test.js
index e17a6045c553..4e74f0aa514d 100644
--- a/lib/plugins/aws/package/compile/events/sns/index.test.js
+++ b/lib/plugins/aws/package/compile/events/sns/index.test.js
@@ -514,51 +514,6 @@ describe('AwsCompileSNSEvents', () => {
}).to.throw(Error);
});
- // eslint-disable-next-line max-len
- it('should throw an error when invalid imported arn object is given as object properties', () => {
- awsCompileSNSEvents.serverless.service.functions = {
- first: {
- events: [
- {
- sns: {
- topicName: 'bar',
- arn: {
- 'Fn::ImportValue': {
- Ref: 'BarTopic',
- },
- },
- },
- },
- ],
- },
- };
-
- expect(() => {
- awsCompileSNSEvents.compileSNSEvents();
- }).to.throw(Error);
-
- awsCompileSNSEvents.serverless.service.functions = {
- first: {
- events: [
- {
- sns: {
- topicName: 'bar',
- arn: {
- 'Fn::ImportValue': {
- 'Fn::GetAtt': ['BarTopic', 'Arn'],
- },
- },
- },
- },
- ],
- },
- };
-
- expect(() => {
- awsCompileSNSEvents.compileSNSEvents();
- }).to.throw(Error);
- });
-
it('should create SNS topic when arn, topicName, and filterPolicy are given as object', () => {
awsCompileSNSEvents.serverless.service.functions = {
first: {
diff --git a/lib/plugins/aws/provider/awsProvider.js b/lib/plugins/aws/provider/awsProvider.js
index d4cc6397ac5c..2181e799b1d8 100644
--- a/lib/plugins/aws/provider/awsProvider.js
+++ b/lib/plugins/aws/provider/awsProvider.js
@@ -141,11 +141,27 @@ class AwsProvider {
// TODO: Complete schema, see https://github.com/serverless/serverless/issues/8016
serverless.configSchemaHandler.defineProvider('aws', {
definitions: {
- awsArn: {
+ awsArnString: {
type: 'string',
pattern: '^arn:',
},
- awsCfImport: {
+ awsArn: {
+ oneOf: [
+ { $ref: '#/definitions/awsArnString' },
+ { $ref: '#/definitions/awsCfFunction' },
+ ],
+ },
+ awsCfFunction: {
+ oneOf: [
+ { $ref: '#/definitions/awsCfImport' },
+ { $ref: '#/definitions/awsCfJoin' },
+ { $ref: '#/definitions/awsCfGetAtt' },
+ { $ref: '#/definitions/awsCfRef' },
+ { $ref: '#/definitions/awsCfSub' },
+ ],
+ },
+ // currently used by lib/plugins/aws/utils/resolveCfImportValue.js for non nested import expressions
+ awsCfImportLocallyResolvable: {
type: 'object',
properties: {
'Fn::ImportValue': { type: 'string' },
@@ -153,13 +169,64 @@ class AwsProvider {
additionalProperties: false,
required: ['Fn::ImportValue'],
},
- awsCfRef: {
+ awsCfImport: {
+ type: 'object',
+ properties: {
+ 'Fn::ImportValue': {},
+ },
+ additionalProperties: false,
+ required: ['Fn::ImportValue'],
+ },
+ awsCfJoin: {
+ type: 'object',
+ properties: {
+ 'Fn::Join': {
+ type: 'array',
+ minItems: 2,
+ maxItems: 2,
+ items: [{ type: 'string', minLength: 1 }, { type: 'array' }],
+ additionalItems: false,
+ },
+ },
+ required: ['Fn::Join'],
+ additionalProperties: false,
+ },
+ awsCfGetAtt: {
type: 'object',
properties: {
- Ref: { type: 'string' },
+ 'Fn::GetAtt': {
+ type: 'array',
+ minItems: 2,
+ maxItems: 2,
+ items: { type: 'string', minLength: 1 },
+ },
},
+ required: ['Fn::GetAtt'],
additionalProperties: false,
+ },
+ awsCfRef: {
+ type: 'object',
+ properties: {
+ Ref: { type: 'string', minLength: 1 },
+ },
required: ['Ref'],
+ additionalProperties: false,
+ },
+ awsCfSub: {
+ oneOf: [
+ { type: 'string', minLength: 1 },
+ {
+ type: 'object',
+ properties: {
+ 'Fn::Sub': {
+ type: 'array',
+ minItems: 2,
+ },
+ },
+ required: ['Fn::Sub'],
+ additionalProperties: false,
+ },
+ ],
},
},
provider: {
@@ -208,7 +275,12 @@ class AwsProvider {
},
],
},
- id: { oneOf: [{ type: 'string' }, { $ref: '#/definitions/awsCfImport' }] },
+ id: {
+ oneOf: [
+ { type: 'string' },
+ { $ref: '#/definitions/awsCfImportLocallyResolvable' },
+ ],
+ },
name: { type: 'string' },
payload: { type: 'string' },
timeout: { type: 'number', minimum: 0.05, maximum: 30 },
diff --git a/lib/plugins/aws/utils/resolveCfImportValue.js b/lib/plugins/aws/utils/resolveCfImportValue.js
index c6250b755f6f..53facaa2337f 100644
--- a/lib/plugins/aws/utils/resolveCfImportValue.js
+++ b/lib/plugins/aws/utils/resolveCfImportValue.js
@@ -1,5 +1,7 @@
'use strict';
+const ServerlessError = require('../../../classes/Error').ServerlessError;
+
function resolveCfImportValue(provider, name, sdkParams = {}) {
return provider.request('CloudFormation', 'listExports', sdkParams).then(result => {
const targetExportMeta = result.Exports.find(exportMeta => exportMeta.Name === name);
@@ -7,7 +9,10 @@ function resolveCfImportValue(provider, name, sdkParams = {}) {
if (result.NextToken) {
return resolveCfImportValue(provider, name, { NextToken: result.NextToken });
}
- return null;
+
+ throw new ServerlessError(
+ `Could not resolve Fn::ImportValue with name ${name}. Are you sure this value is exported ?`
+ );
});
}
diff --git a/package.json b/package.json
index 0751360066d1..fd5c2701a7be 100644
--- a/package.json
+++ b/package.json
@@ -19,7 +19,7 @@
"main": "lib/Serverless.js",
"bin": {
"serverless": "./bin/serverless.js",
- "slss": "./bin/serverless.js",
+ "slss": "./bin/slss.js",
"sls": "./bin/serverless.js"
},
"dependencies": {