-
Notifications
You must be signed in to change notification settings - Fork 4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat(aws-apigateway): add support for UsagePlan, ApiKey, UsagePlanKey… #1221
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,92 @@ | ||
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. | ||
* @default none | ||
*/ | ||
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; | ||
|
||
/** | ||
* 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; | ||
|
||
/** | ||
* 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; | ||
|
||
/** | ||
* 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; | ||
|
||
/** | ||
* 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; | ||
} | ||
|
||
/** | ||
* 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; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. All these don't need |
||
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 resource = new cloudformation.ApiKeyResource(this, 'Resource', { | ||
customerId, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Don't know why this couldn't just be
? |
||
description, | ||
enabled, | ||
generateDistinctId, | ||
name, | ||
stageKeys | ||
}); | ||
|
||
this.keyId = resource.ref; | ||
} | ||
|
||
private renderStageKeys(resources: IRestApiResource[] | undefined): cloudformation.ApiKeyResource.StageKeyProperty[] | undefined { | ||
if (!resources) { | ||
return undefined; | ||
} | ||
|
||
return resources.map((resource: IRestApiResource) => { | ||
const restApi: RestApi = resource.resourceApi; | ||
const restApiId = restApi.restApiId; | ||
const stageName = restApi.deploymentStage!.stageName.toString(); | ||
return { restApiId, stageName }; | ||
}); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,198 @@ | ||
import cdk = require('@aws-cdk/cdk'); | ||
import { ApiKey } from './api-key'; | ||
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. | ||
* @link https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-request-throttling.html | ||
*/ | ||
export interface ThrottleSettings { | ||
/** | ||
* The API request steady-state rate limit (average requests per second over an extended period of time) | ||
*/ | ||
rateLimit?: number; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
|
||
/** | ||
* The maximum API request rate limit over a time ranging from one to a few seconds. | ||
*/ | ||
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 { | ||
/** | ||
* The maximum number of requests that users can make within the specified time period. | ||
*/ | ||
limit?: number; | ||
|
||
/** | ||
* For the initial time period, the number of requests to subtract from the specified limit. | ||
*/ | ||
offset?: number; | ||
|
||
/** | ||
* The time period for which the maximum limit of requests applies. | ||
*/ | ||
period?: Period; | ||
} | ||
|
||
/** | ||
* Represents per-method throttling for a resource. | ||
*/ | ||
export interface ThrottlingPerMethod { | ||
method: Method, | ||
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. | ||
*/ | ||
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) { | ||
akkaash marked this conversation as resolved.
Show resolved
Hide resolved
|
||
super(parent, name); | ||
let resource: cloudformation.UsagePlanResource; | ||
if (props !== undefined) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Interesting way of modeling defaults. Should this path also be taken if the user passes To be honest, this makes me a little uncomfortable. I'd rather you default |
||
const overallThrottle = this.renderThrottle(props.throttle); | ||
const quota = this.renderQuota(props); | ||
const apiStages = 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; | ||
} | ||
|
||
public addApiKey(apiKey: ApiKey): void { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Needs docs |
||
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[] = []; | ||
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; | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think you mean to say "automically generated name" instead of "no name", right?