From a0aee19dfa003320b47ff3cdd334057956f788e7 Mon Sep 17 00:00:00 2001 From: Akkaash Goel Date: Mon, 19 Nov 2018 17:13:37 -0800 Subject: [PATCH 1/3] feat(aws-apigateway): add support for UsagePlan, ApiKey, UsagePlanKey (#723) --- .../@aws-cdk/aws-apigateway/lib/api-key.ts | 83 +++++++++ packages/@aws-cdk/aws-apigateway/lib/index.ts | 3 + .../aws-apigateway/lib/usage-plan-key.ts | 37 ++++ .../@aws-cdk/aws-apigateway/lib/usage-plan.ts | 176 ++++++++++++++++++ .../test/integ.restapi.expected.json | 58 ++++++ .../aws-apigateway/test/integ.restapi.ts | 39 +++- .../aws-apigateway/test/test.api-key.ts | 46 +++++ .../test/test.usage-plan-key.ts | 33 ++++ .../aws-apigateway/test/test.usage-plan.ts | 111 +++++++++++ tools/cdk-integ-tools/bin/cdk-integ.ts | 2 +- 10 files changed, 586 insertions(+), 2 deletions(-) create mode 100644 packages/@aws-cdk/aws-apigateway/lib/api-key.ts create mode 100644 packages/@aws-cdk/aws-apigateway/lib/usage-plan-key.ts create mode 100644 packages/@aws-cdk/aws-apigateway/lib/usage-plan.ts create mode 100644 packages/@aws-cdk/aws-apigateway/test/test.api-key.ts create mode 100644 packages/@aws-cdk/aws-apigateway/test/test.usage-plan-key.ts create mode 100644 packages/@aws-cdk/aws-apigateway/test/test.usage-plan.ts diff --git a/packages/@aws-cdk/aws-apigateway/lib/api-key.ts b/packages/@aws-cdk/aws-apigateway/lib/api-key.ts new file mode 100644 index 0000000000000..b184a08ddd989 --- /dev/null +++ b/packages/@aws-cdk/aws-apigateway/lib/api-key.ts @@ -0,0 +1,83 @@ +import cdk = require('@aws-cdk/cdk'); +import { cloudformation } from './apigateway.generated'; +import { IRestApiResource } from "./resource"; +import { RestApi } from './restapi'; + +export interface ApiKeyProps { + /** + * A list of resources this api key is associated with. + */ + resources?: IRestApiResource[]; + /** + * AWS Marketplace customer identifier to distribute this key to. + */ + customerId?: string; + /** + * Purpose of the API Key + */ + description?: string; + /** + * Whether this API Key is enabled for use. + */ + enabled?: boolean; + /** + * Distinguish the key identifier from the key value + */ + generateDistinctId?: boolean; + /** + * Name of the key + */ + name?: string; +} + +/** + * Creates an API Gateway ApiKey. + * + * An ApiKey can be distributed to API clients that are executing requests + * for Method resources that require an Api Key. + */ +export class ApiKey extends cdk.Construct { + public readonly keyId: string; + constructor(parent: cdk.Construct, id: string, props?: ApiKeyProps) { + super(parent, id); + + const customerId = props ? props!.customerId : undefined; + const description = props ? props!.description : undefined; + const enabled = props ? props!.enabled : undefined; + const generateDistinctId = props ? props!.generateDistinctId : undefined; + const name = props ? props!.name : undefined; + const stageKeys = this.renderStageKeys(props ? props!.resources : undefined); + + const apiKeyResourceProps: cloudformation.ApiKeyResourceProps = { + customerId, + description, + enabled, + generateDistinctId, + name, + stageKeys + }; + + const resource: cloudformation.ApiKeyResource = new cloudformation.ApiKeyResource(this, 'Resource', apiKeyResourceProps); + + this.keyId = resource.ref; + } + + private renderStageKeys(resources: IRestApiResource[] | undefined): cloudformation.ApiKeyResource.StageKeyProperty[] | undefined { + if (!resources) { + return undefined; + } + + const stageKeys = new Array(); + resources.forEach((resource: IRestApiResource) => { + const restApi: RestApi = resource.resourceApi; + const restApiId = restApi.restApiId; + const stageName = restApi!.deploymentStage!.stageName.toString(); + stageKeys.push({ + restApiId, + stageName + }); + }); + + return stageKeys; + } +} diff --git a/packages/@aws-cdk/aws-apigateway/lib/index.ts b/packages/@aws-cdk/aws-apigateway/lib/index.ts index b36594885a3d4..d49013ab52dbc 100644 --- a/packages/@aws-cdk/aws-apigateway/lib/index.ts +++ b/packages/@aws-cdk/aws-apigateway/lib/index.ts @@ -7,6 +7,9 @@ export * from './deployment'; export * from './stage'; export * from './integrations'; export * from './lambda-api'; +export * from './api-key'; +export * from './usage-plan'; +export * from './usage-plan-key'; // AWS::ApiGateway CloudFormation Resources: export * from './apigateway.generated'; diff --git a/packages/@aws-cdk/aws-apigateway/lib/usage-plan-key.ts b/packages/@aws-cdk/aws-apigateway/lib/usage-plan-key.ts new file mode 100644 index 0000000000000..958c1049b0e86 --- /dev/null +++ b/packages/@aws-cdk/aws-apigateway/lib/usage-plan-key.ts @@ -0,0 +1,37 @@ +import cdk = require('@aws-cdk/cdk'); +import { ApiKey } from './api-key'; +import { cloudformation } from './apigateway.generated'; +import { UsagePlan } from './usage-plan'; + +export interface UsagePlanKeyProps { + /** + * Represents the clients to apply a Usage Plan + */ + apiKey: ApiKey, + /** + * Usage Plan to be associated. + */ + usagePlan: UsagePlan +} + +/** + * Type of Usage Plan Key. Currently the only supported type is 'API_KEY' + */ +export enum UsagePlanKeyType { + ApiKey = 'API_KEY' +} + +/** + * Associates client with an API Gateway Usage Plan. + */ +export class UsagePlanKey extends cdk.Construct { + constructor(parent: cdk.Construct, name: string, props: UsagePlanKeyProps) { + super(parent, name); + + new cloudformation.UsagePlanKeyResource(this, 'Resource', { + keyId: props.apiKey.keyId, + keyType: UsagePlanKeyType.ApiKey, + usagePlanId: props.usagePlan.usagePlanId + }); + } +} diff --git a/packages/@aws-cdk/aws-apigateway/lib/usage-plan.ts b/packages/@aws-cdk/aws-apigateway/lib/usage-plan.ts new file mode 100644 index 0000000000000..e68eb25810a62 --- /dev/null +++ b/packages/@aws-cdk/aws-apigateway/lib/usage-plan.ts @@ -0,0 +1,176 @@ +import cdk = require('@aws-cdk/cdk'); +import { cloudformation } from './apigateway.generated'; +import { Method } from './method'; +import { IRestApiResource } from './resource'; +import { Stage } from './stage'; + +/** + * Container for defining throttling parameters to API stages or methods. + * See link for more API Gateway's Request Throttling. + * + * @link https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-request-throttling.html + */ +export interface ThrottleSettings { + /** + * Represents the steady-state rate for the API stage or method. + */ + rateLimit?: number + /** + * Represents the burst size (i.e. maximum bucket size) for the API stage or method. + */ + burstLimit?: number, +} + +/** + * Time period for which quota settings apply. + */ +export enum Period { + Day = 'DAY', + Week = 'WEEK', + Month = 'MONTH' +} + +/** + * Specifies the maximum number of requests that clients can make to API Gateway APIs. + */ +export interface QuotaSettings { + /** + * Maximum number of requests that can be made in a given time period. + */ + limit?: number, + /** + * Number of requests to reduce from the limit for the first time period. + */ + offset?: number, + /** + * Time period to which the maximum limit applies. Valid values are DAY, WEEK or MONTH. + */ + period?: Period +} + +/** + * Represents per-method throttling for a resource. + */ +export interface ThrottlingPerMethod { + method: Method, + throttle: ThrottleSettings +} + +/** + * Represents the API stages that a usage plan applies to. + */ +export interface UsagePlanPerApiStage { + api?: IRestApiResource, + stage?: Stage, + throttle?: ThrottlingPerMethod[] +} + +export interface UsagePlanProps { + /** + * API Stages to be associated which the usage plan. + */ + apiStages?: UsagePlanPerApiStage[], + /** + * Represents usage plan purpose. + */ + description?: string, + /** + * Number of requests clients can make in a given time period. + */ + quota?: QuotaSettings + /** + * Overall throttle settings for the API. + */ + throttle?: ThrottleSettings, + /** + * Name for this usage plan. + */ + name?: string, +} + +export class UsagePlan extends cdk.Construct { + public readonly usagePlanId: string; + constructor(parent: cdk.Construct, name: string, props?: UsagePlanProps) { + super(parent, name); + let resource: cloudformation.UsagePlanResource; + if (props !== undefined) { + const overallThrottle: cloudformation.UsagePlanResource.ThrottleSettingsProperty = this.renderThrottle(props.throttle); + const quota: cloudformation.UsagePlanResource.QuotaSettingsProperty | undefined = this.renderQuota(props); + const apiStages: cloudformation.UsagePlanResource.ApiStageProperty[] | undefined = this.renderApiStages(props); + + resource = new cloudformation.UsagePlanResource(this, 'Resource', { + apiStages, + description: props.description, + quota, + throttle: overallThrottle, + usagePlanName: props.name, + }); + } else { + resource = new cloudformation.UsagePlanResource(this, 'Resource'); + } + + this.usagePlanId = resource.ref; + } + + private renderApiStages(props: UsagePlanProps): cloudformation.UsagePlanResource.ApiStageProperty[] | undefined { + if (props.apiStages && props.apiStages.length > 0) { + const apiStages: cloudformation.UsagePlanResource.ApiStageProperty[] = []; + props.apiStages.forEach((value: UsagePlanPerApiStage) => { + const apiId = value.api ? value.api.resourceApi.restApiId : undefined; + const stage = value.stage ? value.stage.stageName.toString() : undefined; + const throttle = this.renderThrottlePerMethod(value.throttle); + apiStages.push({ + apiId, + stage, + throttle + }); + }); + return apiStages; + } + + return undefined; + } + + private renderThrottlePerMethod(throttlePerMethod?: ThrottlingPerMethod[]): { + [key: string]: (cloudformation.UsagePlanResource.ThrottleSettingsProperty | cdk.Token) + } { + const ret: { [key: string]: (cloudformation.UsagePlanResource.ThrottleSettingsProperty | cdk.Token) } = {}; + + if (throttlePerMethod && throttlePerMethod.length > 0) { + throttlePerMethod.forEach((value: ThrottlingPerMethod) => { + const method: Method = value.method; + // this methodId is resource path and method for example /GET or /pets/GET + const methodId = `${method.resource.resourcePath}/${method.httpMethod}`; + ret[methodId] = this.renderThrottle(value.throttle); + }); + } + + return ret; + } + + private renderQuota(props: UsagePlanProps): cloudformation.UsagePlanResource.QuotaSettingsProperty | undefined { + if (props.quota === undefined) { + return undefined; + } + return { + limit: props.quota ? props.quota.limit : undefined, + offset: props.quota ? props.quota.offset : undefined, + period: props.quota ? props.quota.period : undefined + }; + } + + private renderThrottle(throttleSettings?: ThrottleSettings): cloudformation.UsagePlanResource.ThrottleSettingsProperty { + const throttle: cloudformation.UsagePlanResource.ThrottleSettingsProperty = {}; + if (throttleSettings !== undefined) { + const burstLimit: number|undefined = throttleSettings.burstLimit; + if (burstLimit) { + if (!Number.isInteger(burstLimit)) { + throw new Error('Throttle burst limit should be an integer'); + } + throttle.burstLimit = Number.isInteger(burstLimit) ? burstLimit : undefined; + } + throttle.rateLimit = throttleSettings.rateLimit; + } + return throttle; + } +} diff --git a/packages/@aws-cdk/aws-apigateway/test/integ.restapi.expected.json b/packages/@aws-cdk/aws-apigateway/test/integ.restapi.expected.json index 5d15ff5c7b253..1d40059b7767a 100644 --- a/packages/@aws-cdk/aws-apigateway/test/integ.restapi.expected.json +++ b/packages/@aws-cdk/aws-apigateway/test/integ.restapi.expected.json @@ -585,6 +585,64 @@ ] } } + }, + "UsagePlanC18B28F1": { + "Type": "AWS::ApiGateway::UsagePlan", + "Properties": { + "ApiStages": [ + { + "ApiId": { + "Ref": "myapi4C7BF186" + }, + "Stage": { + "Ref": "myapiDeploymentStagebeta96434BEB" + }, + "Throttle": { + "/v1/toys/GET": { + "BurstLimit": 2, + "RateLimit": 10 + } + } + } + ], + "Description": "Free tier monthly usage plan", + "Quota": { + "Limit": 10000, + "Period": "MONTH" + }, + "Throttle": { + "BurstLimit": 5, + "RateLimit": 50 + }, + "UsagePlanName": "Basic" + } + }, + "ApiKeyF9DDEE66": { + "Type": "AWS::ApiGateway::ApiKey", + "Properties": { + "StageKeys": [ + { + "RestApiId": { + "Ref": "myapi4C7BF186" + }, + "StageName": { + "Ref": "myapiDeploymentStagebeta96434BEB" + } + } + ] + } + }, + "UsagePlanKey803D3BF7": { + "Type": "AWS::ApiGateway::UsagePlanKey", + "Properties": { + "KeyId": { + "Ref": "ApiKeyF9DDEE66" + }, + "KeyType": "API_KEY", + "UsagePlanId": { + "Ref": "UsagePlanC18B28F1" + } + } } }, "Outputs": { diff --git a/packages/@aws-cdk/aws-apigateway/test/integ.restapi.ts b/packages/@aws-cdk/aws-apigateway/test/integ.restapi.ts index a492d0a2fb9f9..92e8229223b5a 100644 --- a/packages/@aws-cdk/aws-apigateway/test/integ.restapi.ts +++ b/packages/@aws-cdk/aws-apigateway/test/integ.restapi.ts @@ -33,7 +33,7 @@ class Test extends cdk.Stack { const integration = new apigateway.LambdaIntegration(handler); const toys = v1.addResource('toys'); - toys.addMethod('GET', integration); + const getToysMethod: apigateway.Method = toys.addMethod('GET', integration); toys.addMethod('POST'); toys.addMethod('PUT'); @@ -52,6 +52,43 @@ class Test extends cdk.Stack { body: JSON.stringify(event) }); } + + const usagePlan: apigateway.UsagePlan = new apigateway.UsagePlan(this, 'UsagePlan', { + name: 'Basic', + description: 'Free tier monthly usage plan', + quota: { + limit: 10000, + period: apigateway.Period.Month + }, + throttle: { + rateLimit: 50, + burstLimit: 5 + }, + apiStages: [ + { + api: api.root, + stage: api.deploymentStage, + throttle: [ + { + method: getToysMethod, + throttle: { + rateLimit: 10, + burstLimit: 2 + } + } + ] + } + ] + }); + + const apiKey: apigateway.ApiKey = new apigateway.ApiKey(this, 'ApiKey', { + resources: [api.root] + }); + + new apigateway.UsagePlanKey(this, 'UsagePlanKey', { + apiKey, + usagePlan + }); } } diff --git a/packages/@aws-cdk/aws-apigateway/test/test.api-key.ts b/packages/@aws-cdk/aws-apigateway/test/test.api-key.ts new file mode 100644 index 0000000000000..ada5eff54f455 --- /dev/null +++ b/packages/@aws-cdk/aws-apigateway/test/test.api-key.ts @@ -0,0 +1,46 @@ +import { expect, haveResource, ResourcePart } from '@aws-cdk/assert'; +import cdk = require('@aws-cdk/cdk'); +import { Test } from "nodeunit"; +import apigateway = require('../lib'); + +export = { + 'default setup'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + + // WHEN + new apigateway.ApiKey(stack, 'my-api-key'); + + // THEN + expect(stack).to(haveResource('AWS::ApiGateway::ApiKey', undefined, ResourcePart.CompleteDefinition)); + // should have an api key with no props defined. + + test.done(); + }, + + 'specify props for apiKey'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const api = new apigateway.RestApi(stack, 'test-api', { cloudWatchRole: false, deploy: true, deployOptions: { stageName: 'test' } }); + api.root.addMethod('GET'); // api must have atleast one method. + + // WHEN + new apigateway.ApiKey(stack, 'test-api-key', { + customerId: 'test-customer', + resources: [api.root] + }); + + // THEN + expect(stack).to(haveResource('AWS::ApiGateway::ApiKey', { + CustomerId: 'test-customer', + StageKeys: [ + { + RestApiId: { Ref: "testapiD6451F70" }, + StageName: { Ref: "testapiDeploymentStagetest5869DF71" } + } + ] + })); + + test.done(); + } +}; diff --git a/packages/@aws-cdk/aws-apigateway/test/test.usage-plan-key.ts b/packages/@aws-cdk/aws-apigateway/test/test.usage-plan-key.ts new file mode 100644 index 0000000000000..b1061cbcd6da3 --- /dev/null +++ b/packages/@aws-cdk/aws-apigateway/test/test.usage-plan-key.ts @@ -0,0 +1,33 @@ +import { expect, haveResource, ResourcePart } from '@aws-cdk/assert'; +import cdk = require('@aws-cdk/cdk'); +import { Test } from 'nodeunit'; +import apigateway = require('../lib'); + +const RESOURCE_TYPE = 'AWS::ApiGateway::UsagePlanKey'; + +export = { + 'default setup'(test: Test) { + const stack = new cdk.Stack(); + const usagePlan: apigateway.UsagePlan = new apigateway.UsagePlan(stack, 'my-usage-plan', { + name: 'Basic', + }); + const apiKey: apigateway.ApiKey = new apigateway.ApiKey(stack, 'my-api-key'); + + new apigateway.UsagePlanKey(stack, 'my-usage-plan-key', { + apiKey, + usagePlan + }); + + expect(stack).to(haveResource(RESOURCE_TYPE, { + KeyId: { + Ref: 'myapikey1B052F70' + }, + KeyType: 'API_KEY', + UsagePlanId: { + Ref: 'myusageplan23AA1E32' + } + }, ResourcePart.Properties)); + + test.done(); + } +}; diff --git a/packages/@aws-cdk/aws-apigateway/test/test.usage-plan.ts b/packages/@aws-cdk/aws-apigateway/test/test.usage-plan.ts new file mode 100644 index 0000000000000..a7600073a749b --- /dev/null +++ b/packages/@aws-cdk/aws-apigateway/test/test.usage-plan.ts @@ -0,0 +1,111 @@ +import { expect, haveResource, ResourcePart } from '@aws-cdk/assert'; +import cdk = require('@aws-cdk/cdk'); +import { Test } from "nodeunit"; +import apigateway = require('../lib'); + +const RESOURCE_TYPE = 'AWS::ApiGateway::UsagePlan'; +export = { + 'default setup'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const api = new apigateway.RestApi(stack, 'my-api', { cloudWatchRole: false, deploy: false }); + api.root.addMethod('GET'); // Need at least one method on the api + const usagePlanName = 'Pro'; + const usagePlanDescription = 'Pro Usage Plan with no throttling limits'; + + // WHEN + new apigateway.UsagePlan(stack, 'my-usage-plan', { + name: usagePlanName, + description: usagePlanDescription + }); + + // THEN + expect(stack).to(haveResource(RESOURCE_TYPE, { + UsagePlanName: usagePlanName, + Description: usagePlanDescription + }, ResourcePart.Properties)); + + test.done(); + }, + + 'usage plan with throttling limits'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const api = new apigateway.RestApi(stack, 'my-api', { cloudWatchRole: false, deploy: true, deployOptions: { stageName: 'test' } }); + const method: apigateway.Method = api.root.addMethod('GET'); // Need at least one method on the api + const usagePlanName = 'Basic'; + const usagePlanDescription = 'Basic Usage Plan with no throttling limits'; + + // WHEN + new apigateway.UsagePlan(stack, 'my-usage-plan', { + name: usagePlanName, + description: usagePlanDescription, + apiStages: [ + { + api: api.root, + stage: api.deploymentStage, + throttle: [ + { + method, + throttle: { + burstLimit: 20, + rateLimit: 10 + } + } + ] + } + ] + }); + // THEN + + expect(stack).to(haveResource(RESOURCE_TYPE, { + UsagePlanName: usagePlanName, + Description: usagePlanDescription, + ApiStages: [ + { + ApiId: { + Ref: 'myapi4C7BF186' + }, + Stage: { + Ref: 'myapiDeploymentStagetest4A4AB65E' + }, + Throttle: { + '//GET': { + BurstLimit: 20, + RateLimit: 10 + } + } + } + ] + }, ResourcePart.Properties)); + + test.done(); + + }, + + 'usage plan with quota limits'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + // const api = new apigateway.RestApi(stack, 'my-api', { cloudWatchRole: false, deploy: true, deployOptions: { stageName: 'test' } }); + // const method: apigateway.Method = api.root.addMethod('GET'); // Need at least one method on the api + + // WHEN + new apigateway.UsagePlan(stack, 'my-usage-plan', { + quota: { + limit: 10000, + period: apigateway.Period.Month + } + }); + + // THEN + expect(stack).to(haveResource(RESOURCE_TYPE, { + Quota: { + Limit: 10000, + Period: 'MONTH' + } + }, ResourcePart.Properties)); + + test.done(); + + } +}; diff --git a/tools/cdk-integ-tools/bin/cdk-integ.ts b/tools/cdk-integ-tools/bin/cdk-integ.ts index 1398c0e6f2bce..695ab23a618f4 100644 --- a/tools/cdk-integ-tools/bin/cdk-integ.ts +++ b/tools/cdk-integ-tools/bin/cdk-integ.ts @@ -8,7 +8,7 @@ import { IntegrationTests, STATIC_TEST_CONTEXT } from '../lib/integ-helpers'; async function main() { const argv = yargs .usage('Usage: cdk-integ [TEST...]') - .option('clean', { type: 'boolean', default: true, desc: 'Skipps stack clean up after test is completed (use --no-clean to negate)' }) + .option('clean', { type: 'boolean', default: true, desc: 'Skips stack clean up after test is completed (use --no-clean to negate)' }) .option('verbose', { type: 'boolean', default: false, alias: 'v', desc: 'Verbose logs' }) .argv; From 9c1bdf4fe50d5a76bbf14262bccdd51c6a52f1b0 Mon Sep 17 00:00:00 2001 From: Akkaash Goel Date: Thu, 22 Nov 2018 01:50:57 -0800 Subject: [PATCH 2/3] Update ts documentation for low-level cfn props --- .../@aws-cdk/aws-apigateway/lib/api-key.ts | 61 +++++++++++-------- .../aws-apigateway/lib/usage-plan-key.ts | 5 +- .../@aws-cdk/aws-apigateway/lib/usage-plan.ts | 36 ++++++----- 3 files changed, 59 insertions(+), 43 deletions(-) diff --git a/packages/@aws-cdk/aws-apigateway/lib/api-key.ts b/packages/@aws-cdk/aws-apigateway/lib/api-key.ts index b184a08ddd989..586e15bfb4303 100644 --- a/packages/@aws-cdk/aws-apigateway/lib/api-key.ts +++ b/packages/@aws-cdk/aws-apigateway/lib/api-key.ts @@ -6,58 +6,73 @@ import { RestApi } from './restapi'; export interface ApiKeyProps { /** * A list of resources this api key is associated with. + * @default none */ resources?: IRestApiResource[]; + /** - * AWS Marketplace customer identifier to distribute this key to. + * An AWS Marketplace customer identifier to use when integrating with the AWS SaaS Marketplace. + * @link http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-apigateway-apikey.html#cfn-apigateway-apikey-customerid + * @default none */ customerId?: string; + /** - * Purpose of the API Key + * A description of the purpose of the API key. + * @link http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-apigateway-apikey.html#cfn-apigateway-apikey-description + * @default none */ description?: string; + /** - * Whether this API Key is enabled for use. + * Indicates whether the API key can be used by clients. + * @link http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-apigateway-apikey.html#cfn-apigateway-apikey-enabled + * @default true */ enabled?: boolean; + /** - * Distinguish the key identifier from the key value + * Specifies whether the key identifier is distinct from the created API key value. + * @link http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-apigateway-apikey.html#cfn-apigateway-apikey-generatedistinctid + * @default false */ generateDistinctId?: boolean; + /** - * Name of the key + * A name for the API key. + * @link http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-apigateway-apikey.html#cfn-apigateway-apikey-name + * @default none */ name?: string; } /** - * Creates an API Gateway ApiKey. + * An API Gateway ApiKey. * * An ApiKey can be distributed to API clients that are executing requests * for Method resources that require an Api Key. */ export class ApiKey extends cdk.Construct { public readonly keyId: string; - constructor(parent: cdk.Construct, id: string, props?: ApiKeyProps) { + + constructor(parent: cdk.Construct, id: string, props: ApiKeyProps = {}) { super(parent, id); - const customerId = props ? props!.customerId : undefined; - const description = props ? props!.description : undefined; - const enabled = props ? props!.enabled : undefined; - const generateDistinctId = props ? props!.generateDistinctId : undefined; - const name = props ? props!.name : undefined; - const stageKeys = this.renderStageKeys(props ? props!.resources : undefined); + const customerId = props && props.customerId; + const description = props && props.description; + const enabled = props && props.enabled; + const generateDistinctId = props && props.generateDistinctId; + const name = props && props.name; + const stageKeys = this.renderStageKeys(props && props.resources); - const apiKeyResourceProps: cloudformation.ApiKeyResourceProps = { + const resource = new cloudformation.ApiKeyResource(this, 'Resource', { customerId, description, enabled, generateDistinctId, name, stageKeys - }; - - const resource: cloudformation.ApiKeyResource = new cloudformation.ApiKeyResource(this, 'Resource', apiKeyResourceProps); + }); this.keyId = resource.ref; } @@ -67,17 +82,11 @@ export class ApiKey extends cdk.Construct { return undefined; } - const stageKeys = new Array(); - resources.forEach((resource: IRestApiResource) => { + return resources.map((resource: IRestApiResource) => { const restApi: RestApi = resource.resourceApi; const restApiId = restApi.restApiId; - const stageName = restApi!.deploymentStage!.stageName.toString(); - stageKeys.push({ - restApiId, - stageName - }); + const stageName = restApi.deploymentStage!.stageName.toString(); + return { restApiId, stageName }; }); - - return stageKeys; } } diff --git a/packages/@aws-cdk/aws-apigateway/lib/usage-plan-key.ts b/packages/@aws-cdk/aws-apigateway/lib/usage-plan-key.ts index 958c1049b0e86..7af413f78f597 100644 --- a/packages/@aws-cdk/aws-apigateway/lib/usage-plan-key.ts +++ b/packages/@aws-cdk/aws-apigateway/lib/usage-plan-key.ts @@ -7,11 +7,12 @@ export interface UsagePlanKeyProps { /** * Represents the clients to apply a Usage Plan */ - apiKey: ApiKey, + apiKey: ApiKey; + /** * Usage Plan to be associated. */ - usagePlan: UsagePlan + usagePlan: UsagePlan; } /** diff --git a/packages/@aws-cdk/aws-apigateway/lib/usage-plan.ts b/packages/@aws-cdk/aws-apigateway/lib/usage-plan.ts index e68eb25810a62..a388e100989ba 100644 --- a/packages/@aws-cdk/aws-apigateway/lib/usage-plan.ts +++ b/packages/@aws-cdk/aws-apigateway/lib/usage-plan.ts @@ -6,19 +6,18 @@ import { Stage } from './stage'; /** * Container for defining throttling parameters to API stages or methods. - * See link for more API Gateway's Request Throttling. - * * @link https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-request-throttling.html */ export interface ThrottleSettings { /** - * Represents the steady-state rate for the API stage or method. + * The API request steady-state rate limit (average requests per second over an extended period of time) */ - rateLimit?: number + rateLimit?: number; + /** - * Represents the burst size (i.e. maximum bucket size) for the API stage or method. + * The maximum API request rate limit over a time ranging from one to a few seconds. */ - burstLimit?: number, + burstLimit?: number; } /** @@ -35,17 +34,19 @@ export enum Period { */ export interface QuotaSettings { /** - * Maximum number of requests that can be made in a given time period. + * The maximum number of requests that users can make within the specified time period. */ - limit?: number, + limit?: number; + /** - * Number of requests to reduce from the limit for the first time period. + * For the initial time period, the number of requests to subtract from the specified limit. */ - offset?: number, + offset?: number; + /** - * Time period to which the maximum limit applies. Valid values are DAY, WEEK or MONTH. + * The time period for which the maximum limit of requests applies. */ - period?: Period + period?: Period; } /** @@ -70,18 +71,22 @@ export interface UsagePlanProps { * API Stages to be associated which the usage plan. */ apiStages?: UsagePlanPerApiStage[], + /** * Represents usage plan purpose. */ description?: string, + /** * Number of requests clients can make in a given time period. */ quota?: QuotaSettings + /** * Overall throttle settings for the API. */ throttle?: ThrottleSettings, + /** * Name for this usage plan. */ @@ -90,13 +95,14 @@ export interface UsagePlanProps { export class UsagePlan extends cdk.Construct { public readonly usagePlanId: string; + constructor(parent: cdk.Construct, name: string, props?: UsagePlanProps) { super(parent, name); let resource: cloudformation.UsagePlanResource; if (props !== undefined) { - const overallThrottle: cloudformation.UsagePlanResource.ThrottleSettingsProperty = this.renderThrottle(props.throttle); - const quota: cloudformation.UsagePlanResource.QuotaSettingsProperty | undefined = this.renderQuota(props); - const apiStages: cloudformation.UsagePlanResource.ApiStageProperty[] | undefined = this.renderApiStages(props); + const overallThrottle = this.renderThrottle(props.throttle); + const quota = this.renderQuota(props); + const apiStages = this.renderApiStages(props); resource = new cloudformation.UsagePlanResource(this, 'Resource', { apiStages, From 832d09db85aa6adcc0ccee3d1d12a36c010ea2fa Mon Sep 17 00:00:00 2001 From: Akkaash Goel Date: Tue, 27 Nov 2018 10:43:08 -0500 Subject: [PATCH 3/3] replace UsagePlanKey with UsagPlan.addApiKey --- packages/@aws-cdk/aws-apigateway/lib/index.ts | 1 - .../aws-apigateway/lib/usage-plan-key.ts | 38 ------------------- .../@aws-cdk/aws-apigateway/lib/usage-plan.ts | 16 ++++++++ .../test/integ.restapi.expected.json | 24 ++++++------ .../aws-apigateway/test/integ.restapi.ts | 5 +-- .../test/test.usage-plan-key.ts | 33 ---------------- .../aws-apigateway/test/test.usage-plan.ts | 27 ++++++++++++- 7 files changed, 54 insertions(+), 90 deletions(-) delete mode 100644 packages/@aws-cdk/aws-apigateway/lib/usage-plan-key.ts delete mode 100644 packages/@aws-cdk/aws-apigateway/test/test.usage-plan-key.ts diff --git a/packages/@aws-cdk/aws-apigateway/lib/index.ts b/packages/@aws-cdk/aws-apigateway/lib/index.ts index d49013ab52dbc..56311a84aeb82 100644 --- a/packages/@aws-cdk/aws-apigateway/lib/index.ts +++ b/packages/@aws-cdk/aws-apigateway/lib/index.ts @@ -9,7 +9,6 @@ export * from './integrations'; export * from './lambda-api'; export * from './api-key'; export * from './usage-plan'; -export * from './usage-plan-key'; // AWS::ApiGateway CloudFormation Resources: export * from './apigateway.generated'; diff --git a/packages/@aws-cdk/aws-apigateway/lib/usage-plan-key.ts b/packages/@aws-cdk/aws-apigateway/lib/usage-plan-key.ts deleted file mode 100644 index 7af413f78f597..0000000000000 --- a/packages/@aws-cdk/aws-apigateway/lib/usage-plan-key.ts +++ /dev/null @@ -1,38 +0,0 @@ -import cdk = require('@aws-cdk/cdk'); -import { ApiKey } from './api-key'; -import { cloudformation } from './apigateway.generated'; -import { UsagePlan } from './usage-plan'; - -export interface UsagePlanKeyProps { - /** - * Represents the clients to apply a Usage Plan - */ - apiKey: ApiKey; - - /** - * Usage Plan to be associated. - */ - usagePlan: UsagePlan; -} - -/** - * Type of Usage Plan Key. Currently the only supported type is 'API_KEY' - */ -export enum UsagePlanKeyType { - ApiKey = 'API_KEY' -} - -/** - * Associates client with an API Gateway Usage Plan. - */ -export class UsagePlanKey extends cdk.Construct { - constructor(parent: cdk.Construct, name: string, props: UsagePlanKeyProps) { - super(parent, name); - - new cloudformation.UsagePlanKeyResource(this, 'Resource', { - keyId: props.apiKey.keyId, - keyType: UsagePlanKeyType.ApiKey, - usagePlanId: props.usagePlan.usagePlanId - }); - } -} diff --git a/packages/@aws-cdk/aws-apigateway/lib/usage-plan.ts b/packages/@aws-cdk/aws-apigateway/lib/usage-plan.ts index a388e100989ba..14f8eab0e9880 100644 --- a/packages/@aws-cdk/aws-apigateway/lib/usage-plan.ts +++ b/packages/@aws-cdk/aws-apigateway/lib/usage-plan.ts @@ -1,4 +1,5 @@ import cdk = require('@aws-cdk/cdk'); +import { ApiKey } from './api-key'; import { cloudformation } from './apigateway.generated'; import { Method } from './method'; import { IRestApiResource } from './resource'; @@ -57,6 +58,13 @@ export interface ThrottlingPerMethod { throttle: ThrottleSettings } +/** + * Type of Usage Plan Key. Currently the only supported type is 'API_KEY' + */ +export enum UsagePlanKeyType { + ApiKey = 'API_KEY' +} + /** * Represents the API stages that a usage plan applies to. */ @@ -118,6 +126,14 @@ export class UsagePlan extends cdk.Construct { this.usagePlanId = resource.ref; } + public addApiKey(apiKey: ApiKey): void { + new cloudformation.UsagePlanKeyResource(this, 'UsagePlanKeyResource', { + keyId: apiKey.keyId, + keyType: UsagePlanKeyType.ApiKey, + usagePlanId: this.usagePlanId + }); + } + private renderApiStages(props: UsagePlanProps): cloudformation.UsagePlanResource.ApiStageProperty[] | undefined { if (props.apiStages && props.apiStages.length > 0) { const apiStages: cloudformation.UsagePlanResource.ApiStageProperty[] = []; diff --git a/packages/@aws-cdk/aws-apigateway/test/integ.restapi.expected.json b/packages/@aws-cdk/aws-apigateway/test/integ.restapi.expected.json index 1d40059b7767a..f4e921fac4361 100644 --- a/packages/@aws-cdk/aws-apigateway/test/integ.restapi.expected.json +++ b/packages/@aws-cdk/aws-apigateway/test/integ.restapi.expected.json @@ -617,6 +617,18 @@ "UsagePlanName": "Basic" } }, + "UsagePlanUsagePlanKeyResourceFB108041": { + "Type": "AWS::ApiGateway::UsagePlanKey", + "Properties": { + "KeyId": { + "Ref": "ApiKeyF9DDEE66" + }, + "KeyType": "API_KEY", + "UsagePlanId": { + "Ref": "UsagePlanC18B28F1" + } + } + }, "ApiKeyF9DDEE66": { "Type": "AWS::ApiGateway::ApiKey", "Properties": { @@ -631,18 +643,6 @@ } ] } - }, - "UsagePlanKey803D3BF7": { - "Type": "AWS::ApiGateway::UsagePlanKey", - "Properties": { - "KeyId": { - "Ref": "ApiKeyF9DDEE66" - }, - "KeyType": "API_KEY", - "UsagePlanId": { - "Ref": "UsagePlanC18B28F1" - } - } } }, "Outputs": { diff --git a/packages/@aws-cdk/aws-apigateway/test/integ.restapi.ts b/packages/@aws-cdk/aws-apigateway/test/integ.restapi.ts index 92e8229223b5a..2f04622670a6d 100644 --- a/packages/@aws-cdk/aws-apigateway/test/integ.restapi.ts +++ b/packages/@aws-cdk/aws-apigateway/test/integ.restapi.ts @@ -85,10 +85,7 @@ class Test extends cdk.Stack { resources: [api.root] }); - new apigateway.UsagePlanKey(this, 'UsagePlanKey', { - apiKey, - usagePlan - }); + usagePlan.addApiKey(apiKey); } } diff --git a/packages/@aws-cdk/aws-apigateway/test/test.usage-plan-key.ts b/packages/@aws-cdk/aws-apigateway/test/test.usage-plan-key.ts deleted file mode 100644 index b1061cbcd6da3..0000000000000 --- a/packages/@aws-cdk/aws-apigateway/test/test.usage-plan-key.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { expect, haveResource, ResourcePart } from '@aws-cdk/assert'; -import cdk = require('@aws-cdk/cdk'); -import { Test } from 'nodeunit'; -import apigateway = require('../lib'); - -const RESOURCE_TYPE = 'AWS::ApiGateway::UsagePlanKey'; - -export = { - 'default setup'(test: Test) { - const stack = new cdk.Stack(); - const usagePlan: apigateway.UsagePlan = new apigateway.UsagePlan(stack, 'my-usage-plan', { - name: 'Basic', - }); - const apiKey: apigateway.ApiKey = new apigateway.ApiKey(stack, 'my-api-key'); - - new apigateway.UsagePlanKey(stack, 'my-usage-plan-key', { - apiKey, - usagePlan - }); - - expect(stack).to(haveResource(RESOURCE_TYPE, { - KeyId: { - Ref: 'myapikey1B052F70' - }, - KeyType: 'API_KEY', - UsagePlanId: { - Ref: 'myusageplan23AA1E32' - } - }, ResourcePart.Properties)); - - test.done(); - } -}; diff --git a/packages/@aws-cdk/aws-apigateway/test/test.usage-plan.ts b/packages/@aws-cdk/aws-apigateway/test/test.usage-plan.ts index a7600073a749b..8bc4c0b8e7253 100644 --- a/packages/@aws-cdk/aws-apigateway/test/test.usage-plan.ts +++ b/packages/@aws-cdk/aws-apigateway/test/test.usage-plan.ts @@ -86,8 +86,6 @@ export = { 'usage plan with quota limits'(test: Test) { // GIVEN const stack = new cdk.Stack(); - // const api = new apigateway.RestApi(stack, 'my-api', { cloudWatchRole: false, deploy: true, deployOptions: { stageName: 'test' } }); - // const method: apigateway.Method = api.root.addMethod('GET'); // Need at least one method on the api // WHEN new apigateway.UsagePlan(stack, 'my-usage-plan', { @@ -107,5 +105,30 @@ export = { test.done(); + }, + + 'UsagePlanKey'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const usagePlan: apigateway.UsagePlan = new apigateway.UsagePlan(stack, 'my-usage-plan', { + name: 'Basic', + }); + const apiKey: apigateway.ApiKey = new apigateway.ApiKey(stack, 'my-api-key'); + + // WHEN + usagePlan.addApiKey(apiKey); + + // THEN + expect(stack).to(haveResource('AWS::ApiGateway::UsagePlanKey', { + KeyId: { + Ref: 'myapikey1B052F70' + }, + KeyType: 'API_KEY', + UsagePlanId: { + Ref: 'myusageplan23AA1E32' + } + }, ResourcePart.Properties)); + + test.done(); } };