Skip to content

Commit

Permalink
Merge branch 'release/1.7.0'
Browse files Browse the repository at this point in the history
  • Loading branch information
Diana Ionita committed Apr 7, 2021
2 parents ad87b1f + e16b047 commit 7f82672
Show file tree
Hide file tree
Showing 13 changed files with 165 additions and 40 deletions.
10 changes: 8 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -265,7 +265,11 @@ functions:
Cache key parameters coming from multi-value query strings and multi-value headers are currently not supported.

## Configuring a shared API Gateway
This just means that no changes are applied to the root caching configuration of the API Gateway, however `ttlInSeconds`, `dataEncryption` and `perKeyInvalidation` are still applied to all functions, unless specifically overridden.
Setting `apiGatewayIsShared` to `true` means that no changes are applied to the root caching configuration of the API Gateway. However, `ttlInSeconds`, `dataEncryption` and `perKeyInvalidation` are still applied to all functions, unless specifically overridden.

If the shared API Gateway is in a different CloudFormation stack, you'll need to export its `RestApiId` and pass it to the plugin via the optional `restApiId` setting. If the gateway is part of the stack you are deploying, you don't need to do this; the plugin will find the `RestApiId` automatically.

If the shared gateway has a default base path that is not part of your endpoint configuration, you can specify it using the optional `basePath` setting.

```yml
plugins:
Expand All @@ -275,6 +279,8 @@ custom:
apiGatewayCaching:
enabled: true
apiGatewayIsShared: true
restApiId: ${cf:api-gateway-${self:provider.stage}.RestApiId}
basePath: /animals
clusterSize: '0.5'
ttlInSeconds: 300
dataEncrypted: true
Expand All @@ -295,7 +301,7 @@ resources:
Properties:
ParentId:
Fn::GetAtt:
- ApiGatewayRestApi # the default Rest API logical ID
- ApiGatewayRestApi # the default REST API logical ID
- RootResourceId
PathPart: serverless # the endpoint in your API that is set as proxy
RestApiId:
Expand Down
8 changes: 4 additions & 4 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "serverless-api-gateway-caching",
"version": "1.6.1",
"version": "1.7.0",
"description": "A plugin for the serverless framework which helps with configuring caching for API Gateway endpoints.",
"main": "src/apiGatewayCachingPlugin.js",
"scripts": {
Expand Down
13 changes: 13 additions & 0 deletions src/ApiGatewayCachingSettings.js
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,17 @@ class ApiGatewayEndpointCachingSettings {
this.path = this.path.slice(0, -1);
}

let { basePath } = globalSettings;
if (basePath) {
if (!basePath.startsWith('/')) {
basePath = '/'.concat(basePath);
}
if (basePath.endsWith('/')) {
basePath = basePath.slice(0, -1);
}
this.path = basePath.concat(this.path);
}

if (!event.http.caching) {
this.cachingEnabled = false;
return;
Expand Down Expand Up @@ -95,6 +106,8 @@ class ApiGatewayCachingSettings {
const cachingSettings = serverless.service.custom.apiGatewayCaching;
this.cachingEnabled = cachingSettings.enabled;
this.apiGatewayIsShared = cachingSettings.apiGatewayIsShared;
this.restApiId = cachingSettings.restApiId;
this.basePath = cachingSettings.basePath;

if (options) {
this.stage = options.stage || serverless.service.provider.stage;
Expand Down
8 changes: 4 additions & 4 deletions src/apiGatewayCachingPlugin.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,9 @@ class ApiGatewayCachingPlugin {
}

updateCloudFormationTemplate() {
this.thereIsARestApi = restApiExists(this.serverless);
this.thereIsARestApi = restApiExists(this.serverless, this.settings);
if (!this.thereIsARestApi) {
this.serverless.cli.log(`[serverless-api-gateway-caching] No Rest API found. Caching settings will not be updated.`);
this.serverless.cli.log(`[serverless-api-gateway-caching] No REST API found. Caching settings will not be updated.`);
return;
}

Expand All @@ -41,9 +41,9 @@ class ApiGatewayCachingPlugin {
}

updateStage() {
this.thereIsARestApi = restApiExists(this.serverless);
this.thereIsARestApi = restApiExists(this.serverless, this.settings);
if (!this.thereIsARestApi) {
this.serverless.cli.log(`[serverless-api-gateway-caching] No Rest API found. Caching settings will not be updated.`);
this.serverless.cli.log(`[serverless-api-gateway-caching] No REST API found. Caching settings will not be updated.`);
return;
}

Expand Down
11 changes: 9 additions & 2 deletions src/restApiId.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,10 @@ const getConfiguredRestApiId = (serverless) => {
return get(serverless, 'service.provider.apiGateway.restApiId')
}

const restApiExists = (serverless) => {
const restApiExists = (serverless, settings) => {
if (get(settings, 'restApiId')) {
return true;
}
const configuredRestApiId = getConfiguredRestApiId(serverless);
if (configuredRestApiId) {
return true;
Expand All @@ -24,12 +27,16 @@ const outputRestApiIdTo = (serverless) => {
const autoGeneratedRestApiId = { Ref: 'ApiGatewayRestApi' };

serverless.service.provider.compiledCloudFormationTemplate.Outputs[REST_API_ID_KEY] = {
Description: 'Rest API Id',
Description: 'REST API ID',
Value: configuredRestApiId || autoGeneratedRestApiId,
};
};

const retrieveRestApiId = async (serverless, settings) => {
if (settings.restApiId) {
return settings.restApiId;
}

const stackName = serverless.providers.aws.naming.getStackName(settings.stage);

const cloudFormation = await serverless.providers.aws.request('CloudFormation', 'describeStacks', { StackName: stackName },
Expand Down
75 changes: 75 additions & 0 deletions test/configuring-a-default-base-path.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
const APP_ROOT = '..';
const given = require(`${APP_ROOT}/test/steps/given`);
const expect = require('chai').expect;
const ApiGatewayCachingSettings = require(`${APP_ROOT}/src/ApiGatewayCachingSettings`);

describe('Configuring a default base path', () => {
const serviceName = 'cat-api';
const endpointPath = '/cat/{pawId}';
const functionName = 'get-cat-by-paw-id';

describe('when a base path is specified', () => {
const scenarios = [
{
description: 'does not start with a forward slash',
basePath: 'animals'
},
{
description: 'starts with a forward slash',
basePath: '/animals'
},
{
description: 'has a trailing slash',
basePath: 'animals/'
}
];
let settings;

for (scenario of scenarios) {
describe(`and ${scenario.description}`, () => {
before(() => {
const endpoint = given
.a_serverless_function(functionName)
.withHttpEndpoint('get', endpointPath, { enabled: true });

const serverless = given
.a_serverless_instance(serviceName)
.withApiGatewayCachingConfig({ basePath: scenario.basePath })
.withFunction(endpoint);

settings = new ApiGatewayCachingSettings(serverless);
});

it('should be prepended to each endpoint and form a valid path', () => {
expect(path_of(functionName, settings)).to.equal(`/animals${endpointPath}`);
});
});
}
});

describe('when no base path is specified', () => {
before(() => {
const endpoint = given
.a_serverless_function(functionName)
.withHttpEndpoint('get', endpointPath, { enabled: true });

const serverless = given
.a_serverless_instance(serviceName)
.withApiGatewayCachingConfig()
.withFunction(endpoint);

settings = new ApiGatewayCachingSettings(serverless);
});

it('should just use the endpoint path', () => {
expect(path_of(functionName, settings)).to.equal(endpointPath);
});
});
});

const path_of = (functionName, settings) => {
return settings
.endpointSettings
.find(x => x.functionName === functionName)
.path;
}
2 changes: 1 addition & 1 deletion test/configuring-path-parameters.js
Original file line number Diff line number Diff line change
Expand Up @@ -634,7 +634,7 @@ describe('Configuring path parameter caching', () => {
expect(method.Properties.Integration.CacheNamespace).to.exist;
});
});
})
});
});

const when_configuring_path_parameters = (serverless) => {
Expand Down
42 changes: 32 additions & 10 deletions test/configuring-rest-api-id.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,33 +2,55 @@ const APP_ROOT = '..';
const given = require(`${APP_ROOT}/test/steps/given`);
const expect = require('chai').expect;
const { restApiExists } = require(`${APP_ROOT}/src/restApiId`);
const ApiGatewayCachingSettings = require(`${APP_ROOT}/src/ApiGatewayCachingSettings`);

describe('Finding the Rest API', () => {
describe('Finding the REST API', () => {
let result;
describe('when the Rest API Id has already been defined in serverless configuration', () => {

describe('when the REST API ID has been specified in the settings', () => {
before(() => {
let serverless = given.a_serverless_instance()
.withPredefinedRestApiId(given.a_rest_api_id());
let serverless = given
.a_serverless_instance()
.withApiGatewayCachingConfig({ restApiId: given.a_rest_api_id() });

let settings = new ApiGatewayCachingSettings(serverless);

result = restApiExists(serverless, settings);
});

it('should return that the REST API exists', () => {
expect(result).to.be.true;
});
});

describe('when the REST API ID has already been defined in serverless configuration', () => {
before(() => {
let serverless = given
.a_serverless_instance()
.withProviderRestApiId(given.a_rest_api_id());

result = restApiExists(serverless);
});

it('should return that the Rest API exists', () => {
it('should return that the REST API exists', () => {
expect(result).to.be.true;
});
});

describe('when the Rest API has not been defined in serverless configuration', () => {
describe('when the REST API has not been defined in serverless configuration', () => {
describe('and there are HTTP handler functions', () => {
before(() => {
let functionWithHttpEndpoint = given.a_serverless_function('get-cat-by-paw-id')
let functionWithHttpEndpoint = given
.a_serverless_function('get-cat-by-paw-id')
.withHttpEndpoint('get', '/cat/{pawId}');
serverless = given.a_serverless_instance()
serverless = given
.a_serverless_instance()
.withFunction(functionWithHttpEndpoint);

result = restApiExists(serverless);
});

it('should return that the Rest API does exist', () => {
it('should return that the REST API does exist', () => {
expect(result).to.be.true;
});
});
Expand All @@ -40,7 +62,7 @@ describe('Finding the Rest API', () => {
result = restApiExists(serverless);
});

it('should return that the Rest API does not exist', () => {
it('should return that the REST API does not exist', () => {
expect(result).to.be.false;
});
});
Expand Down
16 changes: 8 additions & 8 deletions test/creating-plugin.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,22 +8,22 @@ describe('Creating plugin', () => {
describe('When updating the CloudFormation template', () => {
let scenarios = [
{
description: 'there is no rest api',
description: 'there is no REST API',
thereIsARestApi: false,
expectedLogMessage: '[serverless-api-gateway-caching] No Rest API found. Caching settings will not be updated.',
expectedLogMessage: '[serverless-api-gateway-caching] No REST API found. Caching settings will not be updated.',
expectedToOutputRestApiId: false,
expectedToAddPathParametersCacheConfig: false
},
{
description: 'there is a rest api and caching is enabled',
description: 'there is a REST API and caching is enabled',
cachingEnabled: true,
thereIsARestApi: true,
expectedLogMessage: undefined,
expectedToOutputRestApiId: true,
expectedToAddPathParametersCacheConfig: true,
},
{
description: 'there is a rest api and caching is disabled',
description: 'there is a REST API and caching is disabled',
cachingEnabled: false,
thereIsARestApi: true,
expectedLogMessage: undefined,
Expand Down Expand Up @@ -55,7 +55,7 @@ describe('Creating plugin', () => {
expect(logCalledWith).to.equal(scenario.expectedLogMessage);
});

it(`is expected to output rest api id: ${scenario.expectedToOutputRestApiId}`, () => {
it(`is expected to output REST API ID: ${scenario.expectedToOutputRestApiId}`, () => {
expect(outputRestApiIdCalled).to.equal(scenario.expectedToOutputRestApiId);
});

Expand All @@ -69,14 +69,14 @@ describe('Creating plugin', () => {
describe('When updating the stage', () => {
let scenarios = [
{
description: 'there is no rest api',
description: 'there is no REST API',
thereIsARestApi: false,
expectedLogMessage: '[serverless-api-gateway-caching] No Rest API found. Caching settings will not be updated.',
expectedLogMessage: '[serverless-api-gateway-caching] No REST API found. Caching settings will not be updated.',
expectedToUpdateStageCache: false,
expectedToHaveSettings: false
},
{
description: 'there is a rest api',
description: 'there is a REST API',
thereIsARestApi: true,
expectedLogMessage: undefined,
expectedToUpdateStageCache: true,
Expand Down
6 changes: 4 additions & 2 deletions test/model/Serverless.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,12 @@ class Serverless {
return this;
}

withApiGatewayCachingConfig({ cachingEnabled = true, clusterSize = '0.5', ttlInSeconds = 45, perKeyInvalidation, dataEncrypted, apiGatewayIsShared } = {}) {
withApiGatewayCachingConfig({ cachingEnabled = true, clusterSize = '0.5', ttlInSeconds = 45, perKeyInvalidation, dataEncrypted, apiGatewayIsShared, restApiId, basePath } = {}) {
this.service.custom.apiGatewayCaching = {
enabled: cachingEnabled,
apiGatewayIsShared,
restApiId,
basePath,
clusterSize,
ttlInSeconds,
perKeyInvalidation,
Expand Down Expand Up @@ -70,7 +72,7 @@ class Serverless {
return this;
}

withPredefinedRestApiId(restApiId) {
withProviderRestApiId(restApiId) {
if (!this.service.provider.apiGateway) {
this.service.provider.apiGateway = {}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ describe('Updating stage cache settings for additional endpoints defined as Clou
apiGatewayRequest = requestsToAws.find(r => r.awsService == apiGatewayService && r.method == updateStageMethod);
});

it('should contain the Rest Api Id', () => {
it('should contain the REST API ID', () => {
expect(apiGatewayRequest.properties.restApiId).to.equal(restApiId);
});

Expand Down
Loading

0 comments on commit 7f82672

Please sign in to comment.