Skip to content

Commit

Permalink
feat(aws-apigateway): add support for UsagePlan, ApiKey, UsagePlanKey (
Browse files Browse the repository at this point in the history
  • Loading branch information
Akkaash Goel committed Nov 20, 2018
1 parent d3b8448 commit a0aee19
Show file tree
Hide file tree
Showing 10 changed files with 586 additions and 2 deletions.
83 changes: 83 additions & 0 deletions packages/@aws-cdk/aws-apigateway/lib/api-key.ts
Original file line number Diff line number Diff line change
@@ -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<cloudformation.ApiKeyResource.StageKeyProperty>();
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;
}
}
3 changes: 3 additions & 0 deletions packages/@aws-cdk/aws-apigateway/lib/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
37 changes: 37 additions & 0 deletions packages/@aws-cdk/aws-apigateway/lib/usage-plan-key.ts
Original file line number Diff line number Diff line change
@@ -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
});
}
}
176 changes: 176 additions & 0 deletions packages/@aws-cdk/aws-apigateway/lib/usage-plan.ts
Original file line number Diff line number Diff line change
@@ -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;
}
}
58 changes: 58 additions & 0 deletions packages/@aws-cdk/aws-apigateway/test/integ.restapi.expected.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": {
Expand Down
Loading

0 comments on commit a0aee19

Please sign in to comment.