Merge pull request #5851 from exoego/tagging-apigw

Add tags to AWS APIGateway Stage
This commit is contained in:
Philipp Muens 2019-04-18 14:24:37 +02:00 committed by GitHub
commit 381aa728cf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 193 additions and 56 deletions

View File

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

View File

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

View File

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

View File

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