diff --git a/docs/providers/aws/events/apigateway.md b/docs/providers/aws/events/apigateway.md index 63ebd2902..0a50158a4 100644 --- a/docs/providers/aws/events/apigateway.md +++ b/docs/providers/aws/events/apigateway.md @@ -280,7 +280,7 @@ functions: maxAge: 86400 ``` -If you are using CloudFront or another CDN for your API Gateway, you may want to setup a `Cache-Control` header to allow for OPTIONS request to be cached to avoid the additional hop. +If you are using CloudFront or another CDN for your API Gateway, you may want to setup a `Cache-Control` header to allow for OPTIONS request to be cached to avoid the additional hop. To enable the `Cache-Control` header on preflight response, set the `cacheControl` property in the `cors` object: @@ -1282,7 +1282,7 @@ functions: events: - http: path: /users - ... + ... authorizer: # Provide both type and authorizerId type: COGNITO_USER_POOLS # TOKEN or REQUEST or COGNITO_USER_POOLS, same as AWS Cloudformation documentation @@ -1294,7 +1294,7 @@ functions: events: - http: path: /users/{userId} - ... + ... # Provide both type and authorizerId type: COGNITO_USER_POOLS # TOKEN or REQUEST or COGNITO_USER_POOLS, same as AWS Cloudformation documentation authorizerId: @@ -1349,11 +1349,13 @@ provider: minimumCompressionSize: 1024 ``` -## AWS X-Ray Tracing +## Stage specific setups -**IMPORTANT:** Due to CloudFormation limitations it's not possible to enable AWS X-Ray Tracing on existing deployments. Please remove your old API Gateway and re-deploy it with enabled tracing if you want to use AWS X-Ray Tracing for API Gateway. Once tracing is enabled you can re-deploy your service anytime without issues. +**IMPORTANT:** Due to CloudFormation limitations it's not possible to enable API Gateway stage settings on existing deployments. Please remove your old API Gateway and re-deploy with your new stage configuration. Once done, subsequent deployments should work without any issues. -Disabling tracing might result in unexpected behavior. We recommend to remove and re-deploy your service if you want to disable tracing. +Disabling settings might result in unexpected behavior. We recommend to remove and re-deploy your service without such stage settings. + +### AWS X-Ray Tracing API Gateway supports a form of out of the box distributed tracing via [AWS X-Ray](https://aws.amazon.com/xray/) though enabling [active tracing](https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-xray.html). To enable this feature for your serverless application's API Gateway add the following to your `serverless.yml` @@ -1366,3 +1368,18 @@ provider: tracing: apiGateway: true ``` + +### Tags / Stack Tags + +API Gateway stages will be tagged with the `tags` and `stackTags` values defined at the `provider` level: + +```yml +# serverless.yml + +provider: + name: aws + stackTags: + stackTagKey: stackTagValue + tags: + tagKey: tagValue +``` diff --git a/lib/plugins/aws/package/compile/events/apiGateway/lib/checkForBreakingChanges.js b/lib/plugins/aws/package/compile/events/apiGateway/lib/checkForBreakingChanges.js index a22877860..7593525b9 100644 --- a/lib/plugins/aws/package/compile/events/apiGateway/lib/checkForBreakingChanges.js +++ b/lib/plugins/aws/package/compile/events/apiGateway/lib/checkForBreakingChanges.js @@ -2,10 +2,6 @@ const BbPromise = require('bluebird'); -// NOTE: the checks here are X-Ray specific. However the error messages can be updated -// to reflect the general problem which occurrs when upgrading / downgrading the -// Stage resource / Deplyment resource - module.exports = { checkForBreakingChanges() { const StackName = this.provider.naming.getStackName(); @@ -27,7 +23,7 @@ module.exports = { // the old state still uses the stage defined on the AWS::ApiGateway::Deployment resource if (oldResources[oldDeploymentLogicalId] && oldResources[oldDeploymentLogicalId].Properties.StageName && newResources[stageLogicalId]) { // eslint-disable-line max-len const msg = [ - 'NOTE: Enabling API Gateway X-Ray Tracing for existing ', + 'NOTE: Enabling API Gateway stage settings for existing ', 'deployments requires a remove and re-deploy of your API Gateway. ', '\n\n ', 'Please refer to our documentation for more information.', @@ -40,7 +36,7 @@ module.exports = { if (oldResources[stageLogicalId] && newResources[newDeploymentLogicalId] && newResources[newDeploymentLogicalId].Properties.StageName) { // eslint-disable-line if (!this.options.force) { const msg = [ - 'NOTE: Disabling API Gateway X-Ray Tracing for existing ', + 'NOTE: Disabling API Gateway stage settings for existing ', 'deployments might result in unexpected behavior.', '\n ', 'We recommend to remove and re-deploy your API Gateway. ', diff --git a/lib/plugins/aws/package/compile/events/apiGateway/lib/stage.js b/lib/plugins/aws/package/compile/events/apiGateway/lib/stage.js index f74532708..79a2228ba 100644 --- a/lib/plugins/aws/package/compile/events/apiGateway/lib/stage.js +++ b/lib/plugins/aws/package/compile/events/apiGateway/lib/stage.js @@ -5,17 +5,35 @@ const BbPromise = require('bluebird'); module.exports = { compileStage() { - // NOTE: right now we're only using a dedicated Stage resource if AWS X-Ray - // tracing is enabled. We'll change this in the future so that users can + const provider = this.serverless.service.provider; + + // TracingEnabled + const tracing = provider.tracing; + const TracingEnabled = !_.isEmpty(tracing) && tracing.apiGateway; + + // Tags + const tagsMerged = [provider.stackTags, provider.tags].reduce((lastTags, newTags) => { + if (_.isPlainObject(newTags)) { + return _.extend(lastTags, newTags); + } + return lastTags; + }, {}); + const Tags = _.entriesIn(tagsMerged).map(pair => ({ + Key: pair[0], + Value: pair[1], + })); + + // NOTE: the DeploymentId is random, therefore we rely on prior usage here + const deploymentId = this.apiGatewayDeploymentLogicalId; + this.apiGatewayStageLogicalId = this.provider.naming + .getStageLogicalId(); + + // NOTE: right now we're only using a dedicated Stage resource + // - if AWS X-Ray tracing is enabled + // - if Tags are provided + // We'll change this in the future so that users can // opt-in for other features as well - const tracing = this.serverless.service.provider.tracing; - - if (!_.isEmpty(tracing) && tracing.apiGateway) { - // NOTE: the DeploymentId is random, therefore we rely on prior usage here - const deploymentId = this.apiGatewayDeploymentLogicalId; - this.apiGatewayStageLogicalId = this.provider.naming - .getStageLogicalId(); - + if (TracingEnabled || Tags.length > 0) { _.merge(this.serverless.service.provider.compiledCloudFormationTemplate.Resources, { [this.apiGatewayStageLogicalId]: { Type: 'AWS::ApiGateway::Stage', @@ -25,7 +43,8 @@ module.exports = { }, RestApiId: this.provider.getApiGatewayRestApiId(), StageName: this.provider.getStage(), - TracingEnabled: true, + TracingEnabled, + Tags, }, }, }); diff --git a/lib/plugins/aws/package/compile/events/apiGateway/lib/stage.test.js b/lib/plugins/aws/package/compile/events/apiGateway/lib/stage.test.js index ea69cd03d..d2e33d1df 100644 --- a/lib/plugins/aws/package/compile/events/apiGateway/lib/stage.test.js +++ b/lib/plugins/aws/package/compile/events/apiGateway/lib/stage.test.js @@ -31,10 +31,6 @@ describe('#compileStage()', () => { stage = awsCompileApigEvents.provider.getStage(); stageLogicalId = awsCompileApigEvents.provider.naming .getStageLogicalId(); - // setting up AWS X-Ray tracing - awsCompileApigEvents.serverless.service.provider.tracing = { - apiGateway: true, - }; // mocking the result of a Deployment resource since we remove the stage name // when using the Stage resource awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate @@ -45,45 +41,154 @@ describe('#compileStage()', () => { }; }); - it('should create a dedicated stage resource if tracing is configured', () => awsCompileApigEvents - .compileStage().then(() => { - const resources = awsCompileApigEvents.serverless.service.provider - .compiledCloudFormationTemplate.Resources; + describe('tracing', () => { + beforeEach(() => { + // setting up AWS X-Ray tracing + awsCompileApigEvents.serverless.service.provider.tracing = { + apiGateway: true, + }; + }); - expect(resources[stageLogicalId]).to.deep.equal({ - Type: 'AWS::ApiGateway::Stage', - Properties: { - RestApiId: { - Ref: awsCompileApigEvents.apiGatewayRestApiLogicalId, + it('should create a dedicated stage resource if tracing is configured', () => + awsCompileApigEvents.compileStage().then(() => { + const resources = awsCompileApigEvents.serverless.service.provider + .compiledCloudFormationTemplate.Resources; + + expect(resources[stageLogicalId]).to.deep.equal({ + Type: 'AWS::ApiGateway::Stage', + Properties: { + RestApiId: { + Ref: awsCompileApigEvents.apiGatewayRestApiLogicalId, + }, + DeploymentId: { + Ref: awsCompileApigEvents.apiGatewayDeploymentLogicalId, + }, + StageName: 'dev', + Tags: [], + TracingEnabled: true, }, - DeploymentId: { - Ref: awsCompileApigEvents.apiGatewayDeploymentLogicalId, + }); + + expect(resources[awsCompileApigEvents.apiGatewayDeploymentLogicalId]).to.deep.equal({ + Properties: {}, + }); + }) + ); + + it('should NOT create a dedicated stage resource if tracing is not enabled', () => { + awsCompileApigEvents.serverless.service.provider.tracing = {}; + + return awsCompileApigEvents.compileStage().then(() => { + const resources = awsCompileApigEvents.serverless.service.provider + .compiledCloudFormationTemplate.Resources; + + // eslint-disable-next-line + expect(resources[stageLogicalId]).not.to.exist; + + expect(resources[awsCompileApigEvents.apiGatewayDeploymentLogicalId]).to.deep.equal({ + Properties: { + StageName: stage, }, - StageName: 'dev', - TracingEnabled: true, - }, + }); }); + }); + }); - expect(resources[awsCompileApigEvents.apiGatewayDeploymentLogicalId]).to.deep.equal({ - Properties: {}, + describe('tags', () => { + it('should create a dedicated stage resource if provider.stackTags is configured', () => { + awsCompileApigEvents.serverless.service.provider.stackTags = { + foo: '1', + }; + + awsCompileApigEvents.compileStage().then(() => { + const resources = awsCompileApigEvents.serverless.service.provider + .compiledCloudFormationTemplate.Resources; + expect(resources[awsCompileApigEvents.apiGatewayDeploymentLogicalId]).to.deep.equal({ + Properties: {}, + }); + + expect(resources[stageLogicalId]).to.deep.equal({ + Type: 'AWS::ApiGateway::Stage', + Properties: { + RestApiId: { + Ref: awsCompileApigEvents.apiGatewayRestApiLogicalId, + }, + DeploymentId: { + Ref: awsCompileApigEvents.apiGatewayDeploymentLogicalId, + }, + StageName: stage, + TracingEnabled: false, + Tags: [ + { Key: 'foo', Value: '1' }, + ], + }, + }); }); - }) - ); + }); - it('should NOT create a dedicated stage resource if tracing is not enabled', () => { - awsCompileApigEvents.serverless.service.provider.tracing = {}; + it('should create a dedicated stage resource if provider.tags is configured', () => { + awsCompileApigEvents.serverless.service.provider.tags = { + foo: '1', + }; - return awsCompileApigEvents.compileStage().then(() => { - const resources = awsCompileApigEvents.serverless.service.provider - .compiledCloudFormationTemplate.Resources; + awsCompileApigEvents.compileStage().then(() => { + const resources = awsCompileApigEvents.serverless.service.provider + .compiledCloudFormationTemplate.Resources; + expect(resources[awsCompileApigEvents.apiGatewayDeploymentLogicalId]).to.deep.equal({ + Properties: {}, + }); - // eslint-disable-next-line - expect(resources[stageLogicalId]).not.to.exist; + expect(resources[stageLogicalId]).to.deep.equal({ + Type: 'AWS::ApiGateway::Stage', + Properties: { + RestApiId: { + Ref: awsCompileApigEvents.apiGatewayRestApiLogicalId, + }, + DeploymentId: { + Ref: awsCompileApigEvents.apiGatewayDeploymentLogicalId, + }, + StageName: stage, + TracingEnabled: false, + Tags: [ + { Key: 'foo', Value: '1' }, + ], + }, + }); + }); + }); - expect(resources[awsCompileApigEvents.apiGatewayDeploymentLogicalId]).to.deep.equal({ - Properties: { - StageName: stage, - }, + it('should override provider.stackTags by provider.tags', () => { + awsCompileApigEvents.serverless.service.provider.stackTags = { + foo: 'from-stackTags', + bar: 'from-stackTags', + }; + awsCompileApigEvents.serverless.service.provider.tags = { + foo: 'from-tags', + buz: 'from-tags', + }; + + awsCompileApigEvents.compileStage().then(() => { + const resources = awsCompileApigEvents.serverless.service.provider + .compiledCloudFormationTemplate.Resources; + + expect(resources[stageLogicalId]).to.deep.equal({ + Type: 'AWS::ApiGateway::Stage', + Properties: { + RestApiId: { + Ref: awsCompileApigEvents.apiGatewayRestApiLogicalId, + }, + DeploymentId: { + Ref: awsCompileApigEvents.apiGatewayDeploymentLogicalId, + }, + StageName: stage, + TracingEnabled: false, + Tags: [ + { Key: 'foo', Value: 'from-tags' }, + { Key: 'bar', Value: 'from-stackTags' }, + { Key: 'buz', Value: 'from-tags' }, + ], + }, + }); }); }); });