Skip to content
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 #2564

Merged
merged 30 commits into from
May 28, 2019
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
a0aee19
feat(aws-apigateway): add support for UsagePlan, ApiKey, UsagePlanKey…
Nov 20, 2018
9c1bdf4
Update ts documentation for low-level cfn props
Nov 22, 2018
832d09d
replace UsagePlanKey with UsagPlan.addApiKey
Nov 27, 2018
1656684
feat(bootstrap): allow specifying the toolkit staging bucket name
May 1, 2019
6e47171
pulled upstream master
May 7, 2019
68c93e1
Merge branch 'master' of https://github.com/awslabs/aws-cdk
May 10, 2019
75aab2a
update with upstream, add lerna clean
May 10, 2019
dbd636f
Merge branch 'master' of https://github.com/awslabs/aws-cdk
May 11, 2019
13f1575
pulled in upstream
May 11, 2019
d11c8ca
fix TS9999: JSII errors
May 11, 2019
090da64
continued working on this PR..
May 16, 2019
7cc5435
- specify keys in UsagePlanProps
May 17, 2019
aa9a03a
specify keys in UsagePlanProps
May 17, 2019
d1f8bab
refactoring
May 17, 2019
4d656b0
Merge branch 'master' of https://github.com/awslabs/aws-cdk
May 17, 2019
4d066ef
Merge branch 'master' into orangewise/usage-723
May 17, 2019
070578e
make TS happy
May 17, 2019
6931471
remove empty object
May 18, 2019
93dfcd1
fix some linting issues
May 19, 2019
c965274
let build succeed
May 19, 2019
fd5ff1e
export ApiKey
May 20, 2019
b488719
pulled in upstream
May 20, 2019
c7b8871
merge upstream
May 20, 2019
360f933
Delete ln
orangewise May 20, 2019
057df82
Merge branch 'orangewise/usage-723' of https://github.com/cloudchefs/…
May 20, 2019
f65c6fd
implemented addApiStage
May 21, 2019
94e9bb5
Docstrings
May 21, 2019
6c69a29
document
May 21, 2019
431d731
pull upstream
May 24, 2019
663458d
Merge branch 'master' into orangewise/usage-723
May 24, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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
51 changes: 51 additions & 0 deletions packages/@aws-cdk/aws-apigateway/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 });
orangewise marked this conversation as resolved.
Show resolved Hide resolved

const usagePlan = new apigateway.UsagePlan(this, 'UsagePlan', {
orangewise marked this conversation as resolved.
Show resolved Hide resolved
name: 'Basic',
description: 'Free tier monthly usage plan',
quota: {
limit: 10000,
period: apigateway.Period.Month
},
throttle: {
rateLimit: 50,
burstLimit: 5
},
apiStages: [
orangewise marked this conversation as resolved.
Show resolved Hide resolved
{
api: api,
orangewise marked this conversation as resolved.
Show resolved Hide resolved
stage: api.deploymentStage,
throttle: [
orangewise marked this conversation as resolved.
Show resolved Hide resolved
{
method: echoMethod,
throttle: {
rateLimit: 10,
burstLimit: 2
}
}
]
}
]
});

const key = new apigateway.ApiKey(this, 'ApiKey', {
orangewise marked this conversation as resolved.
Show resolved Hide resolved
resources: [api]
});

usagePlan.addApiKey(key);
orangewise marked this conversation as resolved.
Show resolved Hide resolved
```

#### Default Integration and Method Options

The `defaultIntegration` and `defaultMethodOptions` properties can be used to
Expand Down
85 changes: 85 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,85 @@
import { Construct, Resource } from '@aws-cdk/cdk';
import { CfnApiKey } from './apigateway.generated';
import { ResourceOptions } from "./resource";
import { RestApi } from './restapi';

export interface ApiKeyProps extends ResourceOptions {
orangewise marked this conversation as resolved.
Show resolved Hide resolved
/**
* A list of resources this api key is associated with.
* @default none
*/
readonly resources?: RestApi[];

/**
* 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
*/
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
*/
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
*/
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
*/
readonly generateDistinctId?: boolean;

/**
* 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 automically generated name
*/
readonly 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 Resource {
public readonly keyId: string;

constructor(scope: Construct, id: string, props: ApiKeyProps = { }) {
super(scope, id);

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: RestApi[] | undefined): CfnApiKey.StageKeyProperty[] | undefined {
if (!resources) {
return undefined;
}

return resources.map((resource: RestApi) => {
const restApi = resource;
const restApiId = restApi.restApiId;
const stageName = restApi.deploymentStage!.stageName.toString();
return { restApiId, stageName };
});
}
}
2 changes: 2 additions & 0 deletions packages/@aws-cdk/aws-apigateway/lib/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ export * from './deployment';
export * from './stage';
export * from './integrations';
export * from './lambda-api';
export * from './api-key';
export * from './usage-plan';
export * from './vpc-link';
export * from './methodresponse';
export * from './model';
Expand Down
211 changes: 211 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,211 @@
import { Token } from '@aws-cdk/cdk';
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 { Stage } from './stage';
import { validateInteger } from './util'

/**
* 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)
*
* Type: Integer
orangewise marked this conversation as resolved.
Show resolved Hide resolved
*/
readonly rateLimit?: number;

/**
* The maximum API request rate limit over a time ranging from one to a few seconds.
*
* Type: Integer
orangewise marked this conversation as resolved.
Show resolved Hide resolved
*/
readonly 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.
*
* Type: Integer
orangewise marked this conversation as resolved.
Show resolved Hide resolved
*/
readonly limit?: number;

/**
* For the initial time period, the number of requests to subtract from the specified limit.
*
* Type: Integer
orangewise marked this conversation as resolved.
Show resolved Hide resolved
*/
readonly offset?: number;

/**
* The time period for which the maximum limit of requests applies.
orangewise marked this conversation as resolved.
Show resolved Hide resolved
*/
readonly period?: Period;
}

/**
* Represents per-method throttling for a resource.
*/
export interface ThrottlingPerMethod {
readonly method: Method,
readonly throttle: ThrottleSettings
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Public docs please.

}

/**
* 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 {
readonly api?: RestApi,
readonly stage?: Stage,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@default

readonly throttle?: ThrottlingPerMethod[]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Public docs please.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@default

}

export interface UsagePlanProps {
/**
* API Stages to be associated which the usage plan.
*/
readonly apiStages?: UsagePlanPerApiStage[],
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@default


/**
* Represents usage plan purpose.
*/
readonly description?: string,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@default


/**
* Number of requests clients can make in a given time period.
*/
readonly quota?: QuotaSettings
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@default


/**
* Overall throttle settings for the API.
*/
readonly throttle?: ThrottleSettings,
orangewise marked this conversation as resolved.
Show resolved Hide resolved

/**
* Name for this usage plan.
*/
readonly name?: string,
}

export class UsagePlan extends Resource {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Public docstring

public readonly usagePlanId: string;

constructor(scope: Construct, name: string, props: UsagePlanProps = { }) {
super(scope, name);
let resource: CfnUsagePlan;

if (props !== undefined) {
orangewise marked this conversation as resolved.
Show resolved Hide resolved
resource = new CfnUsagePlan(this, 'Resource', {
apiStages: this.renderApiStages(props),
description: props.description,
quota: this.renderQuota(props),
throttle: this.renderThrottle(props.throttle),
usagePlanName: props.name,
});
} else {
resource = new CfnUsagePlan(this, 'Resource');
}

this.usagePlanId = resource.ref;
}

public addApiKey(apiKey: ApiKey): void {
new CfnUsagePlanKey(this, 'UsagePlanKeyResource', {
keyId: apiKey.keyId,
keyType: UsagePlanKeyType.ApiKey,
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 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,
throttle
});
});
return apiStages;
}

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]: (CfnUsagePlan.ThrottleSettingsProperty | 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.path}/${method.httpMethod}`;
ret[methodId] = this.renderThrottle(value.throttle);
});
}

return ret;
}
}
6 changes: 6 additions & 0 deletions packages/@aws-cdk/aws-apigateway/lib/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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`);
}
}
Loading