Skip to content

Commit

Permalink
Merge pull request #5851 from exoego/tagging-apigw
Browse files Browse the repository at this point in the history
Add tags to AWS APIGateway Stage
  • Loading branch information
pmuens committed Apr 18, 2019
2 parents 8ce9591 + e6b7647 commit 381aa72
Show file tree
Hide file tree
Showing 4 changed files with 192 additions and 55 deletions.
29 changes: 23 additions & 6 deletions docs/providers/aws/events/apigateway.md
Expand Up @@ -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:

Expand Down Expand Up @@ -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
Expand All @@ -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:
Expand Down Expand Up @@ -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`
Expand All @@ -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
```
Expand Up @@ -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();
Expand All @@ -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.',
Expand All @@ -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. ',
Expand Down
39 changes: 29 additions & 10 deletions lib/plugins/aws/package/compile/events/apiGateway/lib/stage.js
Expand Up @@ -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
// opt-in for other features as well
const tracing = this.serverless.service.provider.tracing;
const provider = this.serverless.service.provider;

// TracingEnabled
const tracing = provider.tracing;
const TracingEnabled = !_.isEmpty(tracing) && tracing.apiGateway;

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();
// 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
if (TracingEnabled || Tags.length > 0) {
_.merge(this.serverless.service.provider.compiledCloudFormationTemplate.Resources, {
[this.apiGatewayStageLogicalId]: {
Type: 'AWS::ApiGateway::Stage',
Expand All @@ -25,7 +43,8 @@ module.exports = {
},
RestApiId: this.provider.getApiGatewayRestApiId(),
StageName: this.provider.getStage(),
TracingEnabled: true,
TracingEnabled,
Tags,
},
},
});
Expand Down
171 changes: 138 additions & 33 deletions lib/plugins/aws/package/compile/events/apiGateway/lib/stage.test.js
Expand Up @@ -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
Expand All @@ -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,
},
});
});
});
});

describe('tags', () => {
it('should create a dedicated stage resource if provider.stackTags is configured', () => {
awsCompileApigEvents.serverless.service.provider.stackTags = {
foo: '1',
};

expect(resources[awsCompileApigEvents.apiGatewayDeploymentLogicalId]).to.deep.equal({
Properties: {},
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' },
],
},
});
});
});
});
Expand Down

0 comments on commit 381aa72

Please sign in to comment.