From a0aee19dfa003320b47ff3cdd334057956f788e7 Mon Sep 17 00:00:00 2001 From: Akkaash Goel Date: Mon, 19 Nov 2018 17:13:37 -0800 Subject: [PATCH 01/19] 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 02/19] 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 03/19] 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(); } }; From 1656684fa54984ec6c6b2e47583d959545b0d2bd Mon Sep 17 00:00:00 2001 From: Ronald Luitwieler Date: Wed, 1 May 2019 12:55:40 +0200 Subject: [PATCH 04/19] feat(bootstrap): allow specifying the toolkit staging bucket name --- packages/aws-cdk/README.md | 9 +++++---- packages/aws-cdk/aws-cdk | 1 + packages/aws-cdk/bin/cdk.ts | 12 ++++++++---- packages/aws-cdk/lib/api/bootstrap-environment.ts | 5 ++++- 4 files changed, 18 insertions(+), 9 deletions(-) create mode 120000 packages/aws-cdk/aws-cdk diff --git a/packages/aws-cdk/README.md b/packages/aws-cdk/README.md index 7fd8bff8b8057..2450bdc7aa2ed 100644 --- a/packages/aws-cdk/README.md +++ b/packages/aws-cdk/README.md @@ -164,11 +164,12 @@ configuration's order of precedence is: Some of the interesting keys that can be used in the JSON configuration files: ```js { - "app": "node bin/main.js", // Command to start the CDK app (--app='node bin/main.js') - "context": { // Context entries (--context=key=value) + "app": "node bin/main.js", // Command to start the CDK app (--app='node bin/main.js') + "context": { // Context entries (--context=key=value) "key": "value", }, - "toolkitStackName": "foo", // Customize 'bootstrap' stack name (--toolkit-stack-name=foo) - "versionReporting": false, // Opt-out of version reporting (--no-version-reporting) + "toolkitStackName": "foo", // Customize 'bootstrap' stack name (--toolkit-stack-name=foo) + "toolkitBucketName": "fooBucket", // Customize 'bootstrap' bucket name(--toolkit-bucket-name=fooBucket) + "versionReporting": false, // Opt-out of version reporting (--no-version-reporting) } ``` diff --git a/packages/aws-cdk/aws-cdk b/packages/aws-cdk/aws-cdk new file mode 120000 index 0000000000000..daec18417e708 --- /dev/null +++ b/packages/aws-cdk/aws-cdk @@ -0,0 +1 @@ +/Users/ldrhgp1/ronald/cdk/aws-cdk/node_modules/aws-cdk \ No newline at end of file diff --git a/packages/aws-cdk/bin/cdk.ts b/packages/aws-cdk/bin/cdk.ts index 91f0ebffe3919..e8341e3be8ceb 100644 --- a/packages/aws-cdk/bin/cdk.ts +++ b/packages/aws-cdk/bin/cdk.ts @@ -56,7 +56,8 @@ async function parseCommandLineArguments() { .option('interactive', { type: 'boolean', alias: 'i', desc: 'interactively watch and show template updates' }) .option('output', { type: 'string', alias: 'o', desc: 'write CloudFormation template for requested stacks to the given directory', requiresArg: true }) .option('numbered', { type: 'boolean', alias: 'n', desc: 'prefix filenames with numbers to indicate deployment ordering' })) - .command('bootstrap [ENVIRONMENTS..]', 'Deploys the CDK toolkit stack into an AWS environment') + .command('bootstrap [ENVIRONMENTS..]', 'Deploys the CDK toolkit stack into an AWS environment', yargs => yargs + .option('toolkit-bucket-name', { type: 'string', alias: 'b', desc: 'The name of the CDK toolkit bucket name', default: undefined })) .command('deploy [STACKS..]', 'Deploys the stack(s) named STACKS into your AWS account', yargs => yargs .option('build-exclude', { type: 'array', alias: 'E', nargs: 1, desc: 'do not rebuild asset with the given ID. Can be specified multiple times.', default: [] }) .option('exclusively', { type: 'boolean', alias: 'e', desc: 'only deploy requested stacks, don\'t include dependencies' }) @@ -189,7 +190,7 @@ async function initCommandLine() { }); case 'bootstrap': - return await cliBootstrap(args.ENVIRONMENTS, toolkitStackName, args.roleArn); + return await cliBootstrap(args.ENVIRONMENTS, toolkitStackName, args.roleArn, args.toolkitBucketName); case 'deploy': return await cli.deploy({ @@ -238,7 +239,7 @@ async function initCommandLine() { * all stacks are implicitly selected. * @param toolkitStackName the name to be used for the CDK Toolkit stack. */ - async function cliBootstrap(environmentGlobs: string[], toolkitStackName: string, roleArn: string | undefined): Promise { + async function cliBootstrap(environmentGlobs: string[], toolkitStackName: string, roleArn: string | undefined, toolkitBucketName: string | undefined): Promise { // Two modes of operation. // // If there is an '--app' argument, we select the environments from the app. Otherwise we just take the user @@ -247,11 +248,14 @@ async function initCommandLine() { const app = configuration.settings.get(['app']); const environments = app ? await globEnvironmentsFromStacks(appStacks, environmentGlobs) : environmentsFromDescriptors(environmentGlobs); + + // Bucket name can be passed using --toolkit-bucket-name or set in cdk.json + const bucketName = configuration.settings.get(['toolkitBucketName']) || toolkitBucketName; await Promise.all(environments.map(async (environment) => { success(' ⏳ Bootstrapping environment %s...', colors.blue(environment.name)); try { - const result = await bootstrapEnvironment(environment, aws, toolkitStackName, roleArn); + const result = await bootstrapEnvironment(environment, aws, toolkitStackName, roleArn, bucketName); const message = result.noOp ? ' ✅ Environment %s bootstrapped (no changes).' : ' ✅ Environment %s bootstrapped.'; success(message, colors.blue(environment.name)); diff --git a/packages/aws-cdk/lib/api/bootstrap-environment.ts b/packages/aws-cdk/lib/api/bootstrap-environment.ts index 122899919e97a..d2c751b36b6dc 100644 --- a/packages/aws-cdk/lib/api/bootstrap-environment.ts +++ b/packages/aws-cdk/lib/api/bootstrap-environment.ts @@ -7,7 +7,7 @@ import { SDK } from './util/sdk'; export const BUCKET_NAME_OUTPUT = 'BucketName'; export const BUCKET_DOMAIN_NAME_OUTPUT = 'BucketDomainName'; -export async function bootstrapEnvironment(environment: Environment, aws: SDK, toolkitStackName: string, roleArn: string | undefined): Promise { +export async function bootstrapEnvironment(environment: Environment, aws: SDK, toolkitStackName: string, roleArn: string | undefined, toolkitBucketName: string | undefined): Promise { const synthesizedStack: SynthesizedStack = { environment, metadata: {}, @@ -35,5 +35,8 @@ export async function bootstrapEnvironment(environment: Environment, aws: SDK, t }, name: toolkitStackName, }; + if (toolkitBucketName) { + synthesizedStack.template.Resources.StagingBucket.Properties.BucketName = toolkitBucketName; + } return await deployStack({ stack: synthesizedStack, sdk: aws, roleArn }); } From 75aab2aeb0aa95a3602f15a42fe05ccb839936bf Mon Sep 17 00:00:00 2001 From: Ronald Luitwieler Date: Fri, 10 May 2019 11:49:56 +0200 Subject: [PATCH 05/19] update with upstream, add lerna clean --- install.sh | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/install.sh b/install.sh index 054ad67eee528..a8b58bc91a6c1 100755 --- a/install.sh +++ b/install.sh @@ -16,5 +16,6 @@ npm ci --global-style export PATH=node_modules/.bin:$PATH echo "=============================================================================================" -echo "bootstrapping..." +echo "cleanup and start bootstrapping..." +lerna clean --yes lerna bootstrap --reject-cycles --ci From d11c8ca1a3de4e4f01ca42c81ba8a27acaaa330c Mon Sep 17 00:00:00 2001 From: Ronald Luitwieler Date: Sat, 11 May 2019 13:30:31 +0200 Subject: [PATCH 06/19] fix TS9999: JSII errors --- .../@aws-cdk/aws-apigateway/lib/api-key.ts | 12 ++++---- .../@aws-cdk/aws-apigateway/lib/usage-plan.ts | 30 +++++++++---------- 2 files changed, 21 insertions(+), 21 deletions(-) diff --git a/packages/@aws-cdk/aws-apigateway/lib/api-key.ts b/packages/@aws-cdk/aws-apigateway/lib/api-key.ts index 586e15bfb4303..e829536f4b47d 100644 --- a/packages/@aws-cdk/aws-apigateway/lib/api-key.ts +++ b/packages/@aws-cdk/aws-apigateway/lib/api-key.ts @@ -8,42 +8,42 @@ export interface ApiKeyProps { * A list of resources this api key is associated with. * @default none */ - resources?: IRestApiResource[]; + readonly resources?: IRestApiResource[]; /** * 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; + readonly customerId?: string; /** * 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; + readonly description?: string; /** * 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; + readonly enabled?: boolean; /** * 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; + readonly generateDistinctIdgenerateDistinctId?: boolean; /** * 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; + readonly name?: string; } /** diff --git a/packages/@aws-cdk/aws-apigateway/lib/usage-plan.ts b/packages/@aws-cdk/aws-apigateway/lib/usage-plan.ts index 14f8eab0e9880..b306ebde82d66 100644 --- a/packages/@aws-cdk/aws-apigateway/lib/usage-plan.ts +++ b/packages/@aws-cdk/aws-apigateway/lib/usage-plan.ts @@ -13,12 +13,12 @@ export interface ThrottleSettings { /** * The API request steady-state rate limit (average requests per second over an extended period of time) */ - rateLimit?: number; + readonly rateLimit?: number; /** * The maximum API request rate limit over a time ranging from one to a few seconds. */ - burstLimit?: number; + readonly burstLimit?: number; } /** @@ -37,25 +37,25 @@ export interface QuotaSettings { /** * The maximum number of requests that users can make within the specified time period. */ - limit?: number; + readonly limit?: number; /** * For the initial time period, the number of requests to subtract from the specified limit. */ - offset?: number; + readonly offset?: number; /** * The time period for which the maximum limit of requests applies. */ - period?: Period; + readonly period?: Period; } /** * Represents per-method throttling for a resource. */ export interface ThrottlingPerMethod { - method: Method, - throttle: ThrottleSettings + readonly method: Method, + readonly throttle: ThrottleSettings } /** @@ -69,36 +69,36 @@ export enum UsagePlanKeyType { * Represents the API stages that a usage plan applies to. */ export interface UsagePlanPerApiStage { - api?: IRestApiResource, - stage?: Stage, - throttle?: ThrottlingPerMethod[] + readonly api?: IRestApiResource, + readonly stage?: Stage, + readonly throttle?: ThrottlingPerMethod[] } export interface UsagePlanProps { /** * API Stages to be associated which the usage plan. */ - apiStages?: UsagePlanPerApiStage[], + readonly apiStages?: UsagePlanPerApiStage[], /** * Represents usage plan purpose. */ - description?: string, + readonly description?: string, /** * Number of requests clients can make in a given time period. */ - quota?: QuotaSettings + readonly quota?: QuotaSettings /** * Overall throttle settings for the API. */ - throttle?: ThrottleSettings, + readonly throttle?: ThrottleSettings, /** * Name for this usage plan. */ - name?: string, + readonly name?: string, } export class UsagePlan extends cdk.Construct { From 090da64e7a1cada7b3cd5aa8edcf332ac984588e Mon Sep 17 00:00:00 2001 From: Ronald Luitwieler Date: Thu, 16 May 2019 14:34:50 +0200 Subject: [PATCH 07/19] continued working on this PR.. --- packages/@aws-cdk/aws-apigateway/README.md | 51 ++++++++ .../@aws-cdk/aws-apigateway/lib/api-key.ts | 49 +++---- .../@aws-cdk/aws-apigateway/lib/usage-plan.ts | 121 ++++++++++-------- packages/@aws-cdk/aws-apigateway/lib/util.ts | 6 + .../test/integ.restapi.expected.json | 6 +- .../aws-apigateway/test/integ.restapi.ts | 6 +- .../aws-apigateway/test/test.api-key.ts | 2 +- .../aws-apigateway/test/test.usage-plan.ts | 4 +- 8 files changed, 155 insertions(+), 90 deletions(-) diff --git a/packages/@aws-cdk/aws-apigateway/README.md b/packages/@aws-cdk/aws-apigateway/README.md index a333707703efb..644f1ec2ae1ef 100644 --- a/packages/@aws-cdk/aws-apigateway/README.md +++ b/packages/@aws-cdk/aws-apigateway/README.md @@ -104,6 +104,57 @@ book.addMethod('GET', getBookIntegration, { }); ``` +The following example shows how to use an API Key with a usage plan: + +```ts +const hello = new lambda.Function(this, 'hello', { + runtime: lambda.Runtime.NodeJS810, + handler: 'hello.handler', + code: lambda.Code.asset('lambda') +}); + +const api = new apigateway.RestApi(this, 'hello-api', { }); +const integration = new apigateway.LambdaIntegration(hello); + +const v1 = api.root.addResource('v1'); +const echo = v1.addResource('echo'); +const echoMethod: apigateway.Method = echo.addMethod('GET', integration, { apiKeyRequired: true }); + +const 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, + stage: api.deploymentStage, + throttle: [ + { + method: echoMethod, + throttle: { + rateLimit: 10, + burstLimit: 2 + } + } + ] + } + ] +}); + +const key = new apigateway.ApiKey(this, 'ApiKey', { + resources: [api] +}); + +usagePlan.addApiKey(key); +``` + #### Default Integration and Method Options The `defaultIntegration` and `defaultMethodOptions` properties can be used to diff --git a/packages/@aws-cdk/aws-apigateway/lib/api-key.ts b/packages/@aws-cdk/aws-apigateway/lib/api-key.ts index e829536f4b47d..177ee5d597057 100644 --- a/packages/@aws-cdk/aws-apigateway/lib/api-key.ts +++ b/packages/@aws-cdk/aws-apigateway/lib/api-key.ts @@ -1,14 +1,14 @@ -import cdk = require('@aws-cdk/cdk'); -import { cloudformation } from './apigateway.generated'; -import { IRestApiResource } from "./resource"; +import { Construct, Resource } from '@aws-cdk/cdk'; +import { CfnApiKey } from './apigateway.generated'; +import { ResourceOptions } from "./resource"; import { RestApi } from './restapi'; -export interface ApiKeyProps { +export interface ApiKeyProps extends ResourceOptions { /** * A list of resources this api key is associated with. * @default none */ - readonly resources?: IRestApiResource[]; + readonly resources?: RestApi[]; /** * An AWS Marketplace customer identifier to use when integrating with the AWS SaaS Marketplace. @@ -36,12 +36,12 @@ export interface ApiKeyProps { * @link http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-apigateway-apikey.html#cfn-apigateway-apikey-generatedistinctid * @default false */ - readonly generateDistinctIdgenerateDistinctId?: boolean; + readonly generateDistinctId?: boolean; /** - * A name for the API key. + * A name for the API key. If you don't specify a name, AWS CloudFormation generates a unique physical ID and uses that ID for the API key name. * @link http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-apigateway-apikey.html#cfn-apigateway-apikey-name - * @default none + * @default automically generated name */ readonly name?: string; } @@ -52,38 +52,31 @@ export interface ApiKeyProps { * 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 { +export class ApiKey extends Resource { public readonly keyId: string; - constructor(parent: cdk.Construct, id: string, props: ApiKeyProps = {}) { - super(parent, id); - - 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); + constructor(scope: Construct, id: string, props: ApiKeyProps = { }) { + super(scope, id); - const resource = new cloudformation.ApiKeyResource(this, 'Resource', { - customerId, - description, - enabled, - generateDistinctId, - name, - stageKeys + const resource = new CfnApiKey(this, 'Resource', { + customerId: props.customerId, + description: props.description, + enabled: props.enabled || true, + generateDistinctId: props.generateDistinctId, + name: props.name, + stageKeys: this.renderStageKeys(props.resources) }); this.keyId = resource.ref; } - private renderStageKeys(resources: IRestApiResource[] | undefined): cloudformation.ApiKeyResource.StageKeyProperty[] | undefined { + private renderStageKeys(resources: RestApi[] | undefined): CfnApiKey.StageKeyProperty[] | undefined { if (!resources) { return undefined; } - return resources.map((resource: IRestApiResource) => { - const restApi: RestApi = resource.resourceApi; + return resources.map((resource: RestApi) => { + const restApi = resource; const restApiId = restApi.restApiId; const stageName = restApi.deploymentStage!.stageName.toString(); return { restApiId, stageName }; diff --git a/packages/@aws-cdk/aws-apigateway/lib/usage-plan.ts b/packages/@aws-cdk/aws-apigateway/lib/usage-plan.ts index b306ebde82d66..a0cf3517e2ac3 100644 --- a/packages/@aws-cdk/aws-apigateway/lib/usage-plan.ts +++ b/packages/@aws-cdk/aws-apigateway/lib/usage-plan.ts @@ -1,9 +1,11 @@ -import cdk = require('@aws-cdk/cdk'); +import { Token } from '@aws-cdk/cdk'; +import { Construct, Resource } from '@aws-cdk/cdk'; import { ApiKey } from './api-key'; -import { cloudformation } from './apigateway.generated'; +import { CfnUsagePlan, CfnUsagePlanKey } from './apigateway.generated'; import { Method } from './method'; -import { IRestApiResource } from './resource'; +import { RestApi } from './restapi'; import { Stage } from './stage'; +import { validateInteger } from './util' /** * Container for defining throttling parameters to API stages or methods. @@ -12,11 +14,15 @@ import { Stage } from './stage'; export interface ThrottleSettings { /** * The API request steady-state rate limit (average requests per second over an extended period of time) + * + * Type: Integer */ readonly rateLimit?: number; /** * The maximum API request rate limit over a time ranging from one to a few seconds. + * + * Type: Integer */ readonly burstLimit?: number; } @@ -36,11 +42,15 @@ export enum Period { export interface QuotaSettings { /** * The maximum number of requests that users can make within the specified time period. + * + * Type: Integer */ readonly limit?: number; /** * For the initial time period, the number of requests to subtract from the specified limit. + * + * Type: Integer */ readonly offset?: number; @@ -69,7 +79,7 @@ export enum UsagePlanKeyType { * Represents the API stages that a usage plan applies to. */ export interface UsagePlanPerApiStage { - readonly api?: IRestApiResource, + readonly api?: RestApi, readonly stage?: Stage, readonly throttle?: ThrottlingPerMethod[] } @@ -85,7 +95,7 @@ export interface UsagePlanProps { */ readonly description?: string, - /** + /** * Number of requests clients can make in a given time period. */ readonly quota?: QuotaSettings @@ -101,46 +111,45 @@ export interface UsagePlanProps { readonly name?: string, } -export class UsagePlan extends cdk.Construct { +export class UsagePlan extends Resource { public readonly usagePlanId: string; - constructor(parent: cdk.Construct, name: string, props?: UsagePlanProps) { - super(parent, name); - let resource: cloudformation.UsagePlanResource; - if (props !== undefined) { - const overallThrottle = this.renderThrottle(props.throttle); - const quota = this.renderQuota(props); - const apiStages = this.renderApiStages(props); + constructor(scope: Construct, name: string, props: UsagePlanProps = { }) { + super(scope, name); + let resource: CfnUsagePlan; - resource = new cloudformation.UsagePlanResource(this, 'Resource', { - apiStages, + if (props !== undefined) { + resource = new CfnUsagePlan(this, 'Resource', { + apiStages: this.renderApiStages(props), description: props.description, - quota, - throttle: overallThrottle, + quota: this.renderQuota(props), + throttle: this.renderThrottle(props.throttle), usagePlanName: props.name, }); } else { - resource = new cloudformation.UsagePlanResource(this, 'Resource'); + resource = new CfnUsagePlan(this, 'Resource'); } this.usagePlanId = resource.ref; } public addApiKey(apiKey: ApiKey): void { - new cloudformation.UsagePlanKeyResource(this, 'UsagePlanKeyResource', { + new CfnUsagePlanKey(this, 'UsagePlanKeyResource', { keyId: apiKey.keyId, keyType: UsagePlanKeyType.ApiKey, usagePlanId: this.usagePlanId }); } - private renderApiStages(props: UsagePlanProps): cloudformation.UsagePlanResource.ApiStageProperty[] | undefined { + private renderApiStages(props: UsagePlanProps): CfnUsagePlan.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); + + const apiStages: CfnUsagePlan.ApiStageProperty[] = []; + props.apiStages.forEach((apiStage: UsagePlanPerApiStage) => { + + const apiId = apiStage.api ? apiStage.api.restApiId : undefined; + const stage = apiStage.stage ? apiStage.stage.stageName.toString() : undefined; + const throttle = this.renderThrottlePerMethod(apiStage.throttle); apiStages.push({ apiId, stage, @@ -153,46 +162,50 @@ export class UsagePlan extends cdk.Construct { return undefined; } + private renderQuota(props: UsagePlanProps): CfnUsagePlan.QuotaSettingsProperty | undefined { + if (props.quota === undefined) { + return undefined; + } else { + const limit = props.quota ? props.quota.limit : undefined; + validateInteger(limit, 'Throttle quota limit') + return { + limit: limit, + offset: props.quota ? props.quota.offset : undefined, + period: props.quota ? props.quota.period : undefined + }; + } + } + + private renderThrottle(props: ThrottleSettings | undefined): CfnUsagePlan.ThrottleSettingsProperty | Token { + let ret: (CfnUsagePlan.ThrottleSettingsProperty | Token) = { }; + if (props !== undefined) { + const burstLimit = props.burstLimit + validateInteger(burstLimit, 'Throttle burst limit') + const rateLimit = props.rateLimit + validateInteger(rateLimit, 'Throttle rate limit') + + ret = { + burstLimit: burstLimit, + rateLimit: rateLimit + } + } + return ret; + } + private renderThrottlePerMethod(throttlePerMethod?: ThrottlingPerMethod[]): { - [key: string]: (cloudformation.UsagePlanResource.ThrottleSettingsProperty | cdk.Token) + [key: string]: (CfnUsagePlan.ThrottleSettingsProperty | Token) } { - const ret: { [key: string]: (cloudformation.UsagePlanResource.ThrottleSettingsProperty | cdk.Token) } = {}; + let ret: { [key: string]: (CfnUsagePlan.ThrottleSettingsProperty | 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}`; + const methodId = `${method.resource.path}/${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/lib/util.ts b/packages/@aws-cdk/aws-apigateway/lib/util.ts index 5217f1a3bcf49..b42557a60a7c1 100644 --- a/packages/@aws-cdk/aws-apigateway/lib/util.ts +++ b/packages/@aws-cdk/aws-apigateway/lib/util.ts @@ -67,3 +67,9 @@ export function parseAwsApiCall(path?: string, action?: string, actionParams?: { throw new Error(`Either "path" or "action" are required`); } + +export function validateInteger(property: number | undefined, messagePrefix: string) { + if (property && !Number.isInteger(property)) { + throw new Error(`${messagePrefix} should be an integer`); + } +} 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 197a31c2ae4c4..e36479ef7f437 100644 --- a/packages/@aws-cdk/aws-apigateway/test/integ.restapi.expected.json +++ b/packages/@aws-cdk/aws-apigateway/test/integ.restapi.expected.json @@ -6,7 +6,7 @@ "Name": "my-api" } }, - "myapiDeployment92F2CB498cbe233ce6b744150b3f2d80a9c3dd24": { + "myapiDeployment92F2CB4919460d935da8177bcfbc418506e514ff": { "Type": "AWS::ApiGateway::Deployment", "Properties": { "RestApiId": { @@ -37,7 +37,7 @@ "CacheClusterEnabled": true, "CacheClusterSize": "0.5", "DeploymentId": { - "Ref": "myapiDeployment92F2CB498cbe233ce6b744150b3f2d80a9c3dd24" + "Ref": "myapiDeployment92F2CB4919460d935da8177bcfbc418506e514ff" }, "Description": "beta stage", "MethodSettings": [ @@ -148,6 +148,7 @@ "RestApiId": { "Ref": "myapi4C7BF186" }, + "ApiKeyRequired": true, "AuthorizationType": "NONE", "Integration": { "IntegrationHttpMethod": "POST", @@ -670,6 +671,7 @@ "ApiKeyF9DDEE66": { "Type": "AWS::ApiGateway::ApiKey", "Properties": { + "Enabled": true, "StageKeys": [ { "RestApiId": { diff --git a/packages/@aws-cdk/aws-apigateway/test/integ.restapi.ts b/packages/@aws-cdk/aws-apigateway/test/integ.restapi.ts index af838b93951ea..80c4a36d9d476 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'); - const getToysMethod: apigateway.Method = toys.addMethod('GET', integration); + const getToysMethod: apigateway.Method = toys.addMethod('GET', integration, { apiKeyRequired: true }); toys.addMethod('POST'); toys.addMethod('PUT'); @@ -66,7 +66,7 @@ class Test extends cdk.Stack { }, apiStages: [ { - api: api.root, + api: api, stage: api.deploymentStage, throttle: [ { @@ -82,7 +82,7 @@ class Test extends cdk.Stack { }); const apiKey: apigateway.ApiKey = new apigateway.ApiKey(this, 'ApiKey', { - resources: [api.root] + resources: [api] }); usagePlan.addApiKey(apiKey); diff --git a/packages/@aws-cdk/aws-apigateway/test/test.api-key.ts b/packages/@aws-cdk/aws-apigateway/test/test.api-key.ts index ada5eff54f455..41806d0f2288a 100644 --- a/packages/@aws-cdk/aws-apigateway/test/test.api-key.ts +++ b/packages/@aws-cdk/aws-apigateway/test/test.api-key.ts @@ -27,7 +27,7 @@ export = { // WHEN new apigateway.ApiKey(stack, 'test-api-key', { customerId: 'test-customer', - resources: [api.root] + resources: [api] }); // THEN 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 8bc4c0b8e7253..07b1aed47bc7a 100644 --- a/packages/@aws-cdk/aws-apigateway/test/test.usage-plan.ts +++ b/packages/@aws-cdk/aws-apigateway/test/test.usage-plan.ts @@ -42,7 +42,7 @@ export = { description: usagePlanDescription, apiStages: [ { - api: api.root, + api: api, stage: api.deploymentStage, throttle: [ { @@ -56,8 +56,8 @@ export = { } ] }); - // THEN + // THEN expect(stack).to(haveResource(RESOURCE_TYPE, { UsagePlanName: usagePlanName, Description: usagePlanDescription, From 7cc5435803465dc83977d71be21abb873e71a15d Mon Sep 17 00:00:00 2001 From: Ronald Luitwieler Date: Fri, 17 May 2019 08:55:46 +0200 Subject: [PATCH 08/19] - specify keys in UsagePlanProps - deduce the api from the stage object --- .../@aws-cdk/aws-apigateway/lib/restapi.ts | 22 +++++++++++++++++++ packages/@aws-cdk/aws-apigateway/lib/stage.ts | 2 +- .../aws-apigateway/test/test.usage-plan.ts | 1 - 3 files changed, 23 insertions(+), 2 deletions(-) diff --git a/packages/@aws-cdk/aws-apigateway/lib/restapi.ts b/packages/@aws-cdk/aws-apigateway/lib/restapi.ts index 7c4afb3c96999..2b34458ddf973 100644 --- a/packages/@aws-cdk/aws-apigateway/lib/restapi.ts +++ b/packages/@aws-cdk/aws-apigateway/lib/restapi.ts @@ -6,6 +6,8 @@ import { Integration } from './integration'; import { Method, MethodOptions } from './method'; import { IResource, ResourceBase, ResourceOptions } from './resource'; import { Stage, StageOptions } from './stage'; +import { UsagePlan, UsagePlanProps } from './usage-plan'; +import { ApiKey } from './api-key'; export interface RestApiAttributes { /** @@ -267,6 +269,26 @@ export class RestApi extends Resource implements IRestApi { return this.deploymentStage.urlForPath(path); } + /** + * Adds a usage plan. + * + * @param id + * @param props + */ + public addUsagePlan(id: string, props: UsagePlanProps): UsagePlan { + return new UsagePlan(this, id, props) + } + + /** + * Add an ApiKey + * @param id + */ + public addApiKey(id: string): ApiKey { + return new ApiKey(this, id, { + resources: [this] + }); + } + /** * @returns The "execute-api" ARN. * @default "*" returns the execute API ARN for all methods/resources in diff --git a/packages/@aws-cdk/aws-apigateway/lib/stage.ts b/packages/@aws-cdk/aws-apigateway/lib/stage.ts index 8825bb866bbba..0cc53a22e64a8 100644 --- a/packages/@aws-cdk/aws-apigateway/lib/stage.ts +++ b/packages/@aws-cdk/aws-apigateway/lib/stage.ts @@ -138,7 +138,7 @@ export class Stage extends Resource { */ public readonly stageName: string; - private readonly restApi: IRestApi; + public readonly restApi: IRestApi; private enableCacheCluster?: boolean; constructor(scope: Construct, id: string, props: StageProps) { 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 07b1aed47bc7a..f8e89653bf311 100644 --- a/packages/@aws-cdk/aws-apigateway/test/test.usage-plan.ts +++ b/packages/@aws-cdk/aws-apigateway/test/test.usage-plan.ts @@ -42,7 +42,6 @@ export = { description: usagePlanDescription, apiStages: [ { - api: api, stage: api.deploymentStage, throttle: [ { From aa9a03a29f596906bf22d081eb186502394c07e4 Mon Sep 17 00:00:00 2001 From: Ronald Luitwieler Date: Fri, 17 May 2019 08:58:11 +0200 Subject: [PATCH 09/19] specify keys in UsagePlanProps --- packages/@aws-cdk/aws-apigateway/lib/usage-plan.ts | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/packages/@aws-cdk/aws-apigateway/lib/usage-plan.ts b/packages/@aws-cdk/aws-apigateway/lib/usage-plan.ts index a0cf3517e2ac3..7cd6bc2d7f4bb 100644 --- a/packages/@aws-cdk/aws-apigateway/lib/usage-plan.ts +++ b/packages/@aws-cdk/aws-apigateway/lib/usage-plan.ts @@ -108,7 +108,12 @@ export interface UsagePlanProps { /** * Name for this usage plan. */ - readonly name?: string, + readonly name?: string + + /** + * ApiKey to be associated with the usage plan. + */ + readonly apiKey?: ApiKey } export class UsagePlan extends Resource { @@ -131,6 +136,11 @@ export class UsagePlan extends Resource { } this.usagePlanId = resource.ref; + + // Add ApiKey when + if (props.apiKey) { + this.addApiKey(props.apiKey) + } } public addApiKey(apiKey: ApiKey): void { @@ -147,8 +157,8 @@ export class UsagePlan extends Resource { const apiStages: CfnUsagePlan.ApiStageProperty[] = []; props.apiStages.forEach((apiStage: UsagePlanPerApiStage) => { - const apiId = apiStage.api ? apiStage.api.restApiId : undefined; const stage = apiStage.stage ? apiStage.stage.stageName.toString() : undefined; + const apiId = apiStage.stage ? apiStage.stage.restApi.restApiId : undefined; const throttle = this.renderThrottlePerMethod(apiStage.throttle); apiStages.push({ apiId, From d1f8babe2f1ae57258c3b9b4d57dedad380b92d4 Mon Sep 17 00:00:00 2001 From: Ronald Luitwieler Date: Fri, 17 May 2019 14:38:19 +0200 Subject: [PATCH 10/19] refactoring --- packages/@aws-cdk/aws-apigateway/README.md | 49 ++++++----------- .../@aws-cdk/aws-apigateway/lib/usage-plan.ts | 54 +++++++++++-------- .../test/integ.restapi.expected.json | 14 ++--- .../aws-apigateway/test/integ.restapi.ts | 15 ++---- 4 files changed, 57 insertions(+), 75 deletions(-) diff --git a/packages/@aws-cdk/aws-apigateway/README.md b/packages/@aws-cdk/aws-apigateway/README.md index 644f1ec2ae1ef..870848f99c492 100644 --- a/packages/@aws-cdk/aws-apigateway/README.md +++ b/packages/@aws-cdk/aws-apigateway/README.md @@ -118,41 +118,26 @@ const integration = new apigateway.LambdaIntegration(hello); const v1 = api.root.addResource('v1'); const echo = v1.addResource('echo'); -const echoMethod: apigateway.Method = echo.addMethod('GET', integration, { apiKeyRequired: true }); - -const 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, - stage: api.deploymentStage, - throttle: [ - { - method: echoMethod, - throttle: { - rateLimit: 10, - burstLimit: 2 - } +const echoMethod = echo.addMethod('GET', integration, { apiKeyRequired: true }); +const key = api.addApiKey('ApiKey'); + +api.addUsagePlan('UsagePlan', { + name: 'Easy', + apiKey: key, + apiStages: [{ + stage: api.deploymentStage, + throttle: [ + { + method: echoMethod, + throttle: { + rateLimit: 10, + burstLimit: 2 } - ] - } - ] + } + ] + }] }); -const key = new apigateway.ApiKey(this, 'ApiKey', { - resources: [api] -}); - -usagePlan.addApiKey(key); ``` #### Default Integration and Method Options diff --git a/packages/@aws-cdk/aws-apigateway/lib/usage-plan.ts b/packages/@aws-cdk/aws-apigateway/lib/usage-plan.ts index 7cd6bc2d7f4bb..d9beceade9c61 100644 --- a/packages/@aws-cdk/aws-apigateway/lib/usage-plan.ts +++ b/packages/@aws-cdk/aws-apigateway/lib/usage-plan.ts @@ -125,7 +125,7 @@ export class UsagePlan extends Resource { if (props !== undefined) { resource = new CfnUsagePlan(this, 'Resource', { - apiStages: this.renderApiStages(props), + apiStages: this.renderApiStages(props.apiStages), description: props.description, quota: this.renderQuota(props), throttle: this.renderThrottle(props.throttle), @@ -143,6 +143,11 @@ export class UsagePlan extends Resource { } } + /** + * Adds and ApiKey. + * + * @param apiKey + */ public addApiKey(apiKey: ApiKey): void { new CfnUsagePlanKey(this, 'UsagePlanKeyResource', { keyId: apiKey.keyId, @@ -150,28 +155,33 @@ export class UsagePlan extends Resource { usagePlanId: this.usagePlanId }); } - - private renderApiStages(props: UsagePlanProps): CfnUsagePlan.ApiStageProperty[] | undefined { - if (props.apiStages && props.apiStages.length > 0) { - - const apiStages: CfnUsagePlan.ApiStageProperty[] = []; - props.apiStages.forEach((apiStage: UsagePlanPerApiStage) => { - - const stage = apiStage.stage ? apiStage.stage.stageName.toString() : undefined; - const apiId = apiStage.stage ? apiStage.stage.restApi.restApiId : undefined; - const throttle = this.renderThrottlePerMethod(apiStage.throttle); - apiStages.push({ - apiId, - stage, - throttle - }); + + /** + * + * @param props + */ + private renderApiStages(apiStages: UsagePlanPerApiStage[] | undefined): CfnUsagePlan.ApiStageProperty[] | undefined { + if (apiStages && apiStages.length > 0) { + const stages: CfnUsagePlan.ApiStageProperty[] = []; + apiStages.forEach((apiStage: UsagePlanPerApiStage) => { + stages.push(this.createStage(apiStage)); }); - return apiStages; + return stages; } - return undefined; } + private createStage(apiStage: UsagePlanPerApiStage): CfnUsagePlan.ApiStageProperty { + const stage = apiStage.stage ? apiStage.stage.stageName.toString() : undefined; + const apiId = apiStage.stage ? apiStage.stage.restApi.restApiId : undefined; + const throttle = this.renderThrottlePerMethod(apiStage.throttle); + return { + apiId, + stage, + throttle + }; + } + private renderQuota(props: UsagePlanProps): CfnUsagePlan.QuotaSettingsProperty | undefined { if (props.quota === undefined) { return undefined; @@ -186,8 +196,8 @@ export class UsagePlan extends Resource { } } - private renderThrottle(props: ThrottleSettings | undefined): CfnUsagePlan.ThrottleSettingsProperty | Token { - let ret: (CfnUsagePlan.ThrottleSettingsProperty | Token) = { }; + private renderThrottle(props: ThrottleSettings | undefined): CfnUsagePlan.ThrottleSettingsProperty | Token | undefined { + let ret: (CfnUsagePlan.ThrottleSettingsProperty | Token | undefined); if (props !== undefined) { const burstLimit = props.burstLimit validateInteger(burstLimit, 'Throttle burst limit') @@ -203,9 +213,9 @@ export class UsagePlan extends Resource { } private renderThrottlePerMethod(throttlePerMethod?: ThrottlingPerMethod[]): { - [key: string]: (CfnUsagePlan.ThrottleSettingsProperty | Token) + [key: string]: (CfnUsagePlan.ThrottleSettingsProperty | Token | undefined) } { - let ret: { [key: string]: (CfnUsagePlan.ThrottleSettingsProperty | Token) } = {}; + let ret: { [key: string]: (CfnUsagePlan.ThrottleSettingsProperty | Token | undefined ) } = {}; if (throttlePerMethod && throttlePerMethod.length > 0) { throttlePerMethod.forEach((value: ThrottlingPerMethod) => { 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 e36479ef7f437..78e5f8b9ec2c0 100644 --- a/packages/@aws-cdk/aws-apigateway/test/integ.restapi.expected.json +++ b/packages/@aws-cdk/aws-apigateway/test/integ.restapi.expected.json @@ -625,7 +625,7 @@ } } }, - "UsagePlanC18B28F1": { + "myapiUsagePlan56F9C4F2": { "Type": "AWS::ApiGateway::UsagePlan", "Properties": { "ApiStages": [ @@ -649,26 +649,22 @@ "Limit": 10000, "Period": "MONTH" }, - "Throttle": { - "BurstLimit": 5, - "RateLimit": 50 - }, "UsagePlanName": "Basic" } }, - "UsagePlanUsagePlanKeyResourceFB108041": { + "myapiUsagePlanUsagePlanKeyResource050D133F": { "Type": "AWS::ApiGateway::UsagePlanKey", "Properties": { "KeyId": { - "Ref": "ApiKeyF9DDEE66" + "Ref": "myapiApiKey43446CCF" }, "KeyType": "API_KEY", "UsagePlanId": { - "Ref": "UsagePlanC18B28F1" + "Ref": "myapiUsagePlan56F9C4F2" } } }, - "ApiKeyF9DDEE66": { + "myapiApiKey43446CCF": { "Type": "AWS::ApiGateway::ApiKey", "Properties": { "Enabled": true, diff --git a/packages/@aws-cdk/aws-apigateway/test/integ.restapi.ts b/packages/@aws-cdk/aws-apigateway/test/integ.restapi.ts index 80c4a36d9d476..1c3917369dd18 100644 --- a/packages/@aws-cdk/aws-apigateway/test/integ.restapi.ts +++ b/packages/@aws-cdk/aws-apigateway/test/integ.restapi.ts @@ -53,20 +53,17 @@ class Test extends cdk.Stack { }); } - const usagePlan: apigateway.UsagePlan = new apigateway.UsagePlan(this, 'UsagePlan', { + const key = api.addApiKey('ApiKey'); + api.addUsagePlan('UsagePlan', { name: 'Basic', + apiKey: key, description: 'Free tier monthly usage plan', quota: { limit: 10000, period: apigateway.Period.Month }, - throttle: { - rateLimit: 50, - burstLimit: 5 - }, apiStages: [ { - api: api, stage: api.deploymentStage, throttle: [ { @@ -80,12 +77,6 @@ class Test extends cdk.Stack { } ] }); - - const apiKey: apigateway.ApiKey = new apigateway.ApiKey(this, 'ApiKey', { - resources: [api] - }); - - usagePlan.addApiKey(apiKey); } } From 070578e965889e49cc73b6341074025766267315 Mon Sep 17 00:00:00 2001 From: Ronald Luitwieler Date: Fri, 17 May 2019 15:57:34 +0200 Subject: [PATCH 11/19] make TS happy --- packages/@aws-cdk/aws-apigateway/lib/usage-plan.ts | 14 +++++--------- .../test/integ.restapi.expected.json | 1 + 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/packages/@aws-cdk/aws-apigateway/lib/usage-plan.ts b/packages/@aws-cdk/aws-apigateway/lib/usage-plan.ts index d9beceade9c61..4e6c463319753 100644 --- a/packages/@aws-cdk/aws-apigateway/lib/usage-plan.ts +++ b/packages/@aws-cdk/aws-apigateway/lib/usage-plan.ts @@ -182,7 +182,7 @@ export class UsagePlan extends Resource { }; } - private renderQuota(props: UsagePlanProps): CfnUsagePlan.QuotaSettingsProperty | undefined { + private renderQuota(props: UsagePlanProps) { if (props.quota === undefined) { return undefined; } else { @@ -196,8 +196,8 @@ export class UsagePlan extends Resource { } } - private renderThrottle(props: ThrottleSettings | undefined): CfnUsagePlan.ThrottleSettingsProperty | Token | undefined { - let ret: (CfnUsagePlan.ThrottleSettingsProperty | Token | undefined); + private renderThrottle(props: ThrottleSettings | undefined): (CfnUsagePlan.ThrottleSettingsProperty | Token) { + let ret: CfnUsagePlan.ThrottleSettingsProperty | Token = {}; if (props !== undefined) { const burstLimit = props.burstLimit validateInteger(burstLimit, 'Throttle burst limit') @@ -212,11 +212,8 @@ export class UsagePlan extends Resource { return ret; } - private renderThrottlePerMethod(throttlePerMethod?: ThrottlingPerMethod[]): { - [key: string]: (CfnUsagePlan.ThrottleSettingsProperty | Token | undefined) - } { - let ret: { [key: string]: (CfnUsagePlan.ThrottleSettingsProperty | Token | undefined ) } = {}; - + private renderThrottlePerMethod(throttlePerMethod?: ThrottlingPerMethod[]) { + let ret: { [key: string]: (CfnUsagePlan.ThrottleSettingsProperty | Token) } = {}; if (throttlePerMethod && throttlePerMethod.length > 0) { throttlePerMethod.forEach((value: ThrottlingPerMethod) => { const method: Method = value.method; @@ -225,7 +222,6 @@ export class UsagePlan extends Resource { ret[methodId] = this.renderThrottle(value.throttle); }); } - return ret; } } 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 78e5f8b9ec2c0..5ebaf292a6ed1 100644 --- a/packages/@aws-cdk/aws-apigateway/test/integ.restapi.expected.json +++ b/packages/@aws-cdk/aws-apigateway/test/integ.restapi.expected.json @@ -645,6 +645,7 @@ } ], "Description": "Free tier monthly usage plan", + "Throttle": {}, "Quota": { "Limit": 10000, "Period": "MONTH" From 693147116b3b6abacee0df6c7e4b2202c407e34e Mon Sep 17 00:00:00 2001 From: Ronald Luitwieler Date: Sat, 18 May 2019 08:11:34 +0200 Subject: [PATCH 12/19] remove empty object --- packages/@aws-cdk/aws-apigateway/lib/usage-plan.ts | 4 ++-- .../@aws-cdk/aws-apigateway/test/integ.restapi.expected.json | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/packages/@aws-cdk/aws-apigateway/lib/usage-plan.ts b/packages/@aws-cdk/aws-apigateway/lib/usage-plan.ts index 4e6c463319753..0609979b080a1 100644 --- a/packages/@aws-cdk/aws-apigateway/lib/usage-plan.ts +++ b/packages/@aws-cdk/aws-apigateway/lib/usage-plan.ts @@ -197,7 +197,7 @@ export class UsagePlan extends Resource { } private renderThrottle(props: ThrottleSettings | undefined): (CfnUsagePlan.ThrottleSettingsProperty | Token) { - let ret: CfnUsagePlan.ThrottleSettingsProperty | Token = {}; + let ret: CfnUsagePlan.ThrottleSettingsProperty | Token; if (props !== undefined) { const burstLimit = props.burstLimit validateInteger(burstLimit, 'Throttle burst limit') @@ -209,7 +209,7 @@ export class UsagePlan extends Resource { rateLimit: rateLimit } } - return ret; + return ret!; } private renderThrottlePerMethod(throttlePerMethod?: ThrottlingPerMethod[]) { 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 5ebaf292a6ed1..78e5f8b9ec2c0 100644 --- a/packages/@aws-cdk/aws-apigateway/test/integ.restapi.expected.json +++ b/packages/@aws-cdk/aws-apigateway/test/integ.restapi.expected.json @@ -645,7 +645,6 @@ } ], "Description": "Free tier monthly usage plan", - "Throttle": {}, "Quota": { "Limit": 10000, "Period": "MONTH" From 93dfcd197958770b487fd4d7890f19d0d50dacbc Mon Sep 17 00:00:00 2001 From: Ronald Luitwieler Date: Sun, 19 May 2019 12:27:52 +0200 Subject: [PATCH 13/19] fix some linting issues --- .../@aws-cdk/aws-apigateway/lib/restapi.ts | 12 ++-- .../@aws-cdk/aws-apigateway/lib/usage-plan.ts | 57 ++++++++++--------- 2 files changed, 35 insertions(+), 34 deletions(-) diff --git a/packages/@aws-cdk/aws-apigateway/lib/restapi.ts b/packages/@aws-cdk/aws-apigateway/lib/restapi.ts index 2b34458ddf973..40f48bec992da 100644 --- a/packages/@aws-cdk/aws-apigateway/lib/restapi.ts +++ b/packages/@aws-cdk/aws-apigateway/lib/restapi.ts @@ -1,5 +1,6 @@ import iam = require('@aws-cdk/aws-iam'); import { CfnOutput, Construct, IResource as IResourceBase, Resource } from '@aws-cdk/cdk'; +import { ApiKey } from './api-key'; import { CfnAccount, CfnRestApi } from './apigateway.generated'; import { Deployment } from './deployment'; import { Integration } from './integration'; @@ -7,7 +8,6 @@ import { Method, MethodOptions } from './method'; import { IResource, ResourceBase, ResourceOptions } from './resource'; import { Stage, StageOptions } from './stage'; import { UsagePlan, UsagePlanProps } from './usage-plan'; -import { ApiKey } from './api-key'; export interface RestApiAttributes { /** @@ -271,17 +271,17 @@ export class RestApi extends Resource implements IRestApi { /** * Adds a usage plan. - * - * @param id - * @param props + * + * @param id + * @param props */ public addUsagePlan(id: string, props: UsagePlanProps): UsagePlan { - return new UsagePlan(this, id, props) + return new UsagePlan(this, id, props); } /** * Add an ApiKey - * @param id + * @param id */ public addApiKey(id: string): ApiKey { return new ApiKey(this, id, { diff --git a/packages/@aws-cdk/aws-apigateway/lib/usage-plan.ts b/packages/@aws-cdk/aws-apigateway/lib/usage-plan.ts index 0609979b080a1..52103377447c3 100644 --- a/packages/@aws-cdk/aws-apigateway/lib/usage-plan.ts +++ b/packages/@aws-cdk/aws-apigateway/lib/usage-plan.ts @@ -5,7 +5,7 @@ import { CfnUsagePlan, CfnUsagePlanKey } from './apigateway.generated'; import { Method } from './method'; import { RestApi } from './restapi'; import { Stage } from './stage'; -import { validateInteger } from './util' +import { validateInteger } from './util'; /** * Container for defining throttling parameters to API stages or methods. @@ -14,14 +14,14 @@ import { validateInteger } from './util' export interface ThrottleSettings { /** * The API request steady-state rate limit (average requests per second over an extended period of time) - * + * * Type: Integer */ readonly rateLimit?: number; /** * The maximum API request rate limit over a time ranging from one to a few seconds. - * + * * Type: Integer */ readonly burstLimit?: number; @@ -42,14 +42,14 @@ export enum Period { export interface QuotaSettings { /** * The maximum number of requests that users can make within the specified time period. - * + * * Type: Integer */ readonly limit?: number; /** * For the initial time period, the number of requests to subtract from the specified limit. - * + * * Type: Integer */ readonly offset?: number; @@ -95,7 +95,7 @@ export interface UsagePlanProps { */ readonly description?: string, - /** + /** * Number of requests clients can make in a given time period. */ readonly quota?: QuotaSettings @@ -111,7 +111,7 @@ export interface UsagePlanProps { readonly name?: string /** - * ApiKey to be associated with the usage plan. + * ApiKey to be associated with the usage plan. */ readonly apiKey?: ApiKey } @@ -137,16 +137,16 @@ export class UsagePlan extends Resource { this.usagePlanId = resource.ref; - // Add ApiKey when + // Add ApiKey when if (props.apiKey) { - this.addApiKey(props.apiKey) + this.addApiKey(props.apiKey); } } /** * Adds and ApiKey. - * - * @param apiKey + * + * @param apiKey */ public addApiKey(apiKey: ApiKey): void { new CfnUsagePlanKey(this, 'UsagePlanKeyResource', { @@ -155,10 +155,10 @@ export class UsagePlan extends Resource { usagePlanId: this.usagePlanId }); } - + /** - * - * @param props + * + * @param props */ private renderApiStages(apiStages: UsagePlanPerApiStage[] | undefined): CfnUsagePlan.ApiStageProperty[] | undefined { if (apiStages && apiStages.length > 0) { @@ -186,34 +186,35 @@ export class UsagePlan extends Resource { if (props.quota === undefined) { return undefined; } else { - const limit = props.quota ? props.quota.limit : undefined; - validateInteger(limit, 'Throttle quota limit') - return { - limit: limit, + const limit = props.quota ? props.quota.limit : undefined; + validateInteger(limit, 'Throttle quota limit'); + const ret = { + limit: limit ? limit : undefined, offset: props.quota ? props.quota.offset : undefined, period: props.quota ? props.quota.period : undefined - }; + }; + return ret; } } private renderThrottle(props: ThrottleSettings | undefined): (CfnUsagePlan.ThrottleSettingsProperty | Token) { let ret: CfnUsagePlan.ThrottleSettingsProperty | Token; if (props !== undefined) { - const burstLimit = props.burstLimit - validateInteger(burstLimit, 'Throttle burst limit') - const rateLimit = props.rateLimit - validateInteger(rateLimit, 'Throttle rate limit') - + const burstLimit = props.burstLimit; + validateInteger(burstLimit, 'Throttle burst limit'); + const rateLimit = props.rateLimit; + validateInteger(rateLimit, 'Throttle rate limit'); + ret = { - burstLimit: burstLimit, - rateLimit: rateLimit - } + burstLimit: burstLimit ? burstLimit : undefined, + rateLimit: rateLimit ? rateLimit : undefined + }; } return ret!; } private renderThrottlePerMethod(throttlePerMethod?: ThrottlingPerMethod[]) { - let ret: { [key: string]: (CfnUsagePlan.ThrottleSettingsProperty | Token) } = {}; + const ret: { [key: string]: (CfnUsagePlan.ThrottleSettingsProperty | Token) } = {}; if (throttlePerMethod && throttlePerMethod.length > 0) { throttlePerMethod.forEach((value: ThrottlingPerMethod) => { const method: Method = value.method; From c965274a4aff48bc1ce7f463271a806116287e9a Mon Sep 17 00:00:00 2001 From: Ronald Luitwieler Date: Sun, 19 May 2019 14:11:50 +0200 Subject: [PATCH 14/19] let build succeed --- .../@aws-cdk/aws-apigateway/lib/usage-plan.ts | 21 +++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/packages/@aws-cdk/aws-apigateway/lib/usage-plan.ts b/packages/@aws-cdk/aws-apigateway/lib/usage-plan.ts index 52103377447c3..b4de2c301b591 100644 --- a/packages/@aws-cdk/aws-apigateway/lib/usage-plan.ts +++ b/packages/@aws-cdk/aws-apigateway/lib/usage-plan.ts @@ -3,7 +3,7 @@ import { Construct, Resource } from '@aws-cdk/cdk'; import { ApiKey } from './api-key'; import { CfnUsagePlan, CfnUsagePlanKey } from './apigateway.generated'; import { Method } from './method'; -import { RestApi } from './restapi'; +import { IRestApi } from './restapi'; import { Stage } from './stage'; import { validateInteger } from './util'; @@ -64,6 +64,10 @@ export interface QuotaSettings { * Represents per-method throttling for a resource. */ export interface ThrottlingPerMethod { + /** + * + * [disable-awslint:ref-via-interface] + */ readonly method: Method, readonly throttle: ThrottleSettings } @@ -79,7 +83,13 @@ export enum UsagePlanKeyType { * Represents the API stages that a usage plan applies to. */ export interface UsagePlanPerApiStage { - readonly api?: RestApi, + + readonly api?: IRestApi, + + /** + * + * [disable-awslint:ref-via-interface] + */ readonly stage?: Stage, readonly throttle?: ThrottlingPerMethod[] } @@ -117,10 +127,13 @@ export interface UsagePlanProps { } export class UsagePlan extends Resource { + /** + * @attribute + */ public readonly usagePlanId: string; - constructor(scope: Construct, name: string, props: UsagePlanProps = { }) { - super(scope, name); + constructor(scope: Construct, id: string, props: UsagePlanProps = { }) { + super(scope, id); let resource: CfnUsagePlan; if (props !== undefined) { From fd5ff1e241fda187913e5b0d63b2302f9f8b67f9 Mon Sep 17 00:00:00 2001 From: Ronald Luitwieler Date: Mon, 20 May 2019 10:27:08 +0200 Subject: [PATCH 15/19] export ApiKey --- .../@aws-cdk/aws-apigateway/lib/api-key.ts | 36 +++++++++++++++++-- .../@aws-cdk/aws-apigateway/lib/restapi.ts | 4 +-- .../@aws-cdk/aws-apigateway/lib/usage-plan.ts | 6 ++-- 3 files changed, 39 insertions(+), 7 deletions(-) diff --git a/packages/@aws-cdk/aws-apigateway/lib/api-key.ts b/packages/@aws-cdk/aws-apigateway/lib/api-key.ts index 177ee5d597057..4972872319a4a 100644 --- a/packages/@aws-cdk/aws-apigateway/lib/api-key.ts +++ b/packages/@aws-cdk/aws-apigateway/lib/api-key.ts @@ -1,8 +1,30 @@ -import { Construct, Resource } from '@aws-cdk/cdk'; +import { CfnOutput, Construct, IResource as IResourceBase, Resource } from '@aws-cdk/cdk'; import { CfnApiKey } from './apigateway.generated'; import { ResourceOptions } from "./resource"; import { RestApi } from './restapi'; +export interface ApiKeyAttributes { + /** + * The API key ID. + * @attribute + */ + readonly keyId: string; +} + +export interface IApiKey extends IResourceBase { + /** + * The API key ID. + * @attribute + */ + readonly keyId: string; + + /** + * Exports a API key resource from this stack. + * @returns API key props that can be imported to another stack. + */ + export(): ApiKeyAttributes; +} + export interface ApiKeyProps extends ResourceOptions { /** * A list of resources this api key is associated with. @@ -52,7 +74,7 @@ export interface ApiKeyProps extends ResourceOptions { * An ApiKey can be distributed to API clients that are executing requests * for Method resources that require an Api Key. */ -export class ApiKey extends Resource { +export class ApiKey extends Resource implements IApiKey { public readonly keyId: string; constructor(scope: Construct, id: string, props: ApiKeyProps = { }) { @@ -70,6 +92,16 @@ export class ApiKey extends Resource { this.keyId = resource.ref; } + /** + * Exports a API key resource from this stack. + * @returns API key props that can be imported to another stack. + */ + public export(): ApiKeyAttributes { + return { + keyId: new CfnOutput(this, 'KeyId', { value: this.keyId }).makeImportValue().toString() + }; + } + private renderStageKeys(resources: RestApi[] | undefined): CfnApiKey.StageKeyProperty[] | undefined { if (!resources) { return undefined; diff --git a/packages/@aws-cdk/aws-apigateway/lib/restapi.ts b/packages/@aws-cdk/aws-apigateway/lib/restapi.ts index 40f48bec992da..4c3fb3afbd414 100644 --- a/packages/@aws-cdk/aws-apigateway/lib/restapi.ts +++ b/packages/@aws-cdk/aws-apigateway/lib/restapi.ts @@ -1,6 +1,6 @@ import iam = require('@aws-cdk/aws-iam'); import { CfnOutput, Construct, IResource as IResourceBase, Resource } from '@aws-cdk/cdk'; -import { ApiKey } from './api-key'; +import { ApiKey, IApiKey } from './api-key'; import { CfnAccount, CfnRestApi } from './apigateway.generated'; import { Deployment } from './deployment'; import { Integration } from './integration'; @@ -283,7 +283,7 @@ export class RestApi extends Resource implements IRestApi { * Add an ApiKey * @param id */ - public addApiKey(id: string): ApiKey { + public addApiKey(id: string): IApiKey { return new ApiKey(this, id, { resources: [this] }); diff --git a/packages/@aws-cdk/aws-apigateway/lib/usage-plan.ts b/packages/@aws-cdk/aws-apigateway/lib/usage-plan.ts index b4de2c301b591..7c3271ff2e842 100644 --- a/packages/@aws-cdk/aws-apigateway/lib/usage-plan.ts +++ b/packages/@aws-cdk/aws-apigateway/lib/usage-plan.ts @@ -1,6 +1,6 @@ import { Token } from '@aws-cdk/cdk'; import { Construct, Resource } from '@aws-cdk/cdk'; -import { ApiKey } from './api-key'; +import { IApiKey } from './api-key'; import { CfnUsagePlan, CfnUsagePlanKey } from './apigateway.generated'; import { Method } from './method'; import { IRestApi } from './restapi'; @@ -123,7 +123,7 @@ export interface UsagePlanProps { /** * ApiKey to be associated with the usage plan. */ - readonly apiKey?: ApiKey + readonly apiKey?: IApiKey } export class UsagePlan extends Resource { @@ -161,7 +161,7 @@ export class UsagePlan extends Resource { * * @param apiKey */ - public addApiKey(apiKey: ApiKey): void { + public addApiKey(apiKey: IApiKey): void { new CfnUsagePlanKey(this, 'UsagePlanKeyResource', { keyId: apiKey.keyId, keyType: UsagePlanKeyType.ApiKey, From 360f933078638bff39ba86c391e23ec6dad845e0 Mon Sep 17 00:00:00 2001 From: Ronald Luitwieler Date: Mon, 20 May 2019 15:50:44 +0200 Subject: [PATCH 16/19] Delete ln --- packages/aws-cdk/aws-cdk | 1 - 1 file changed, 1 deletion(-) delete mode 120000 packages/aws-cdk/aws-cdk diff --git a/packages/aws-cdk/aws-cdk b/packages/aws-cdk/aws-cdk deleted file mode 120000 index daec18417e708..0000000000000 --- a/packages/aws-cdk/aws-cdk +++ /dev/null @@ -1 +0,0 @@ -/Users/ldrhgp1/ronald/cdk/aws-cdk/node_modules/aws-cdk \ No newline at end of file From f65c6fda2059960b3f5d8a3deef041435d307158 Mon Sep 17 00:00:00 2001 From: Ronald Luitwieler Date: Tue, 21 May 2019 13:46:04 +0200 Subject: [PATCH 17/19] implemented addApiStage --- .../@aws-cdk/aws-apigateway/lib/usage-plan.ts | 67 +++++++++++++------ .../aws-apigateway/test/integ.restapi.ts | 24 +++---- 2 files changed, 57 insertions(+), 34 deletions(-) diff --git a/packages/@aws-cdk/aws-apigateway/lib/usage-plan.ts b/packages/@aws-cdk/aws-apigateway/lib/usage-plan.ts index 7c3271ff2e842..0f22312c28860 100644 --- a/packages/@aws-cdk/aws-apigateway/lib/usage-plan.ts +++ b/packages/@aws-cdk/aws-apigateway/lib/usage-plan.ts @@ -14,15 +14,13 @@ import { validateInteger } from './util'; export interface ThrottleSettings { /** * The API request steady-state rate limit (average requests per second over an extended period of time) - * - * Type: Integer + * @default none */ readonly rateLimit?: number; /** * The maximum API request rate limit over a time ranging from one to a few seconds. - * - * Type: Integer + * @default none */ readonly burstLimit?: number; } @@ -42,20 +40,19 @@ export enum Period { export interface QuotaSettings { /** * The maximum number of requests that users can make within the specified time period. - * - * Type: Integer + * @default none */ readonly limit?: number; /** * For the initial time period, the number of requests to subtract from the specified limit. - * - * Type: Integer + * @default none */ readonly offset?: number; /** * The time period for which the maximum limit of requests applies. + * @default none */ readonly period?: Period; } @@ -65,10 +62,16 @@ export interface QuotaSettings { */ export interface ThrottlingPerMethod { /** - * * [disable-awslint:ref-via-interface] + * The method for which you specify the throttling settings. + * @default none */ readonly method: Method, + + /** + * Specifies the overall request rate (average requests per second) and burst capacity. + * @default none + */ readonly throttle: ThrottleSettings } @@ -84,44 +87,58 @@ export enum UsagePlanKeyType { */ export interface UsagePlanPerApiStage { + /** + * @default none + */ readonly api?: IRestApi, /** * * [disable-awslint:ref-via-interface] + * @default none */ readonly stage?: Stage, + + /** + * @default none + */ readonly throttle?: ThrottlingPerMethod[] } export interface UsagePlanProps { /** * API Stages to be associated which the usage plan. + * @default none */ readonly apiStages?: UsagePlanPerApiStage[], /** * Represents usage plan purpose. + * @default none */ readonly description?: string, /** * Number of requests clients can make in a given time period. + * @default none */ readonly quota?: QuotaSettings /** * Overall throttle settings for the API. + * @default none */ readonly throttle?: ThrottleSettings, /** * Name for this usage plan. + * @default none */ readonly name?: string /** * ApiKey to be associated with the usage plan. + * @default none */ readonly apiKey?: IApiKey } @@ -132,21 +149,21 @@ export class UsagePlan extends Resource { */ public readonly usagePlanId: string; + private readonly apiStages = new Array(); + constructor(scope: Construct, id: string, props: UsagePlanProps = { }) { super(scope, id); let resource: CfnUsagePlan; - if (props !== undefined) { - resource = new CfnUsagePlan(this, 'Resource', { - apiStages: this.renderApiStages(props.apiStages), - description: props.description, - quota: this.renderQuota(props), - throttle: this.renderThrottle(props.throttle), - usagePlanName: props.name, - }); - } else { - resource = new CfnUsagePlan(this, 'Resource'); - } + resource = new CfnUsagePlan(this, 'Resource', { + apiStages: new Token(() => this.renderApiStages(this.apiStages)), + description: props.description, + quota: this.renderQuota(props), + throttle: this.renderThrottle(props.throttle), + usagePlanName: props.name, + }); + + this.apiStages.push(...(props.apiStages || [])); this.usagePlanId = resource.ref; @@ -157,7 +174,7 @@ export class UsagePlan extends Resource { } /** - * Adds and ApiKey. + * Adds an ApiKey. * * @param apiKey */ @@ -169,6 +186,14 @@ export class UsagePlan extends Resource { }); } + /** + * Adds an apiStage. + * @param apiStage + */ + public addApiStage(apiStage: UsagePlanPerApiStage) { + this.apiStages.push(apiStage); + } + /** * * @param props diff --git a/packages/@aws-cdk/aws-apigateway/test/integ.restapi.ts b/packages/@aws-cdk/aws-apigateway/test/integ.restapi.ts index b679edf0b1ee8..57562329ebc98 100644 --- a/packages/@aws-cdk/aws-apigateway/test/integ.restapi.ts +++ b/packages/@aws-cdk/aws-apigateway/test/integ.restapi.ts @@ -54,26 +54,24 @@ class Test extends cdk.Stack { } const key = api.addApiKey('ApiKey'); - api.addUsagePlan('UsagePlan', { + const plan = api.addUsagePlan('UsagePlan', { name: 'Basic', apiKey: key, description: 'Free tier monthly usage plan', quota: { limit: 10000, period: apigateway.Period.Month - }, - apiStages: [ + } + }); + plan.addApiStage({ + stage: api.deploymentStage, + throttle: [ { - stage: api.deploymentStage, - throttle: [ - { - method: getToysMethod, - throttle: { - rateLimit: 10, - burstLimit: 2 - } - } - ] + method: getToysMethod, + throttle: { + rateLimit: 10, + burstLimit: 2 + } } ] }); From 94e9bb58bcbca2aad8964095dca2ab997d5295d4 Mon Sep 17 00:00:00 2001 From: Ronald Luitwieler Date: Tue, 21 May 2019 13:47:12 +0200 Subject: [PATCH 18/19] Docstrings --- .../@aws-cdk/aws-apigateway/lib/api-key.ts | 29 ++++++++----------- .../@aws-cdk/aws-apigateway/lib/restapi.ts | 4 --- 2 files changed, 12 insertions(+), 21 deletions(-) diff --git a/packages/@aws-cdk/aws-apigateway/lib/api-key.ts b/packages/@aws-cdk/aws-apigateway/lib/api-key.ts index 4972872319a4a..1baf475f57f1b 100644 --- a/packages/@aws-cdk/aws-apigateway/lib/api-key.ts +++ b/packages/@aws-cdk/aws-apigateway/lib/api-key.ts @@ -1,8 +1,12 @@ -import { CfnOutput, Construct, IResource as IResourceBase, Resource } from '@aws-cdk/cdk'; +import { Construct, IResource as IResourceBase, Resource } from '@aws-cdk/cdk'; import { CfnApiKey } from './apigateway.generated'; import { ResourceOptions } from "./resource"; import { RestApi } from './restapi'; +/** + * API keys are alphanumeric string values that you distribute to + * app developer customers to grant access to your API + */ export interface ApiKeyAttributes { /** * The API key ID. @@ -11,20 +15,21 @@ export interface ApiKeyAttributes { readonly keyId: string; } +/** + * API keys are alphanumeric string values that you distribute to + * app developer customers to grant access to your API + */ export interface IApiKey extends IResourceBase { /** * The API key ID. * @attribute */ readonly keyId: string; - - /** - * Exports a API key resource from this stack. - * @returns API key props that can be imported to another stack. - */ - export(): ApiKeyAttributes; } +/** + * ApiKey Properties. + */ export interface ApiKeyProps extends ResourceOptions { /** * A list of resources this api key is associated with. @@ -92,16 +97,6 @@ export class ApiKey extends Resource implements IApiKey { this.keyId = resource.ref; } - /** - * Exports a API key resource from this stack. - * @returns API key props that can be imported to another stack. - */ - public export(): ApiKeyAttributes { - return { - keyId: new CfnOutput(this, 'KeyId', { value: this.keyId }).makeImportValue().toString() - }; - } - private renderStageKeys(resources: RestApi[] | undefined): CfnApiKey.StageKeyProperty[] | undefined { if (!resources) { return undefined; diff --git a/packages/@aws-cdk/aws-apigateway/lib/restapi.ts b/packages/@aws-cdk/aws-apigateway/lib/restapi.ts index d94cf143753f1..827f826dd5a27 100644 --- a/packages/@aws-cdk/aws-apigateway/lib/restapi.ts +++ b/packages/@aws-cdk/aws-apigateway/lib/restapi.ts @@ -242,9 +242,6 @@ export class RestApi extends Resource implements IRestApi { /** * Adds a usage plan. - * - * @param id - * @param props */ public addUsagePlan(id: string, props: UsagePlanProps): UsagePlan { return new UsagePlan(this, id, props); @@ -252,7 +249,6 @@ export class RestApi extends Resource implements IRestApi { /** * Add an ApiKey - * @param id */ public addApiKey(id: string): IApiKey { return new ApiKey(this, id, { From 6c69a29b81fb9ec6c012fdb900a115ff07354f6b Mon Sep 17 00:00:00 2001 From: Ronald Luitwieler Date: Tue, 21 May 2019 13:50:06 +0200 Subject: [PATCH 19/19] document --- packages/@aws-cdk/aws-apigateway/README.md | 28 +++++++++++----------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/packages/@aws-cdk/aws-apigateway/README.md b/packages/@aws-cdk/aws-apigateway/README.md index 870848f99c492..fe9324a3b4dbe 100644 --- a/packages/@aws-cdk/aws-apigateway/README.md +++ b/packages/@aws-cdk/aws-apigateway/README.md @@ -121,23 +121,23 @@ const echo = v1.addResource('echo'); const echoMethod = echo.addMethod('GET', integration, { apiKeyRequired: true }); const key = api.addApiKey('ApiKey'); -api.addUsagePlan('UsagePlan', { +const plan = api.addUsagePlan('UsagePlan', { name: 'Easy', - apiKey: key, - apiStages: [{ - stage: api.deploymentStage, - throttle: [ - { - method: echoMethod, - throttle: { - rateLimit: 10, - burstLimit: 2 - } - } - ] - }] + apiKey: key }); +plan.addApiStage({ + stage: api.deploymentStage, + throttle: [ + { + method: echoMethod, + throttle: { + rateLimit: 10, + burstLimit: 2 + } + } + ] +}); ``` #### Default Integration and Method Options