Skip to content

Conversation

@maramure
Copy link
Contributor

@maramure maramure commented Jul 24, 2025

Issue # (if applicable)

Closes #28756.

Reason for this change

Couldn't create UsagePlans and ApiKeys for WebSocketApi (apigatewayv2) using L2 constructs, cause these 2 features were supported only by RestApi (apigatewayv1).

Description of changes

HttpApi(apigatewayv2) doesn't permit creating UsagePlans or ApiKeys in any way, so bringing these functionalities from apigatewayv1 to apigatewayv2 is possible only for WebSocketApi.

  • Added UsagePlan and ApiKey files in aws-apigatewayv2/lib/websocket folder
  • Implemented needed interfaces, classes and methods very similar to what was written for RestApi (apigatewayv1)
  • Included explicit dependency management between components for proper CloudFormation deployment order
  • Added new JSDoc in order to be able to build the project

Describe any new or updated permissions being added

N/A

Description of how you validated changes

  • Tested manually by deploying a stack containing and linking the new features
  • Wrote unit and integration tests

Checklist


By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license

@github-actions github-actions bot added effort/medium Medium work item – several days of effort feature-request A feature should be added or improved. p2 beginning-contributor [Pilot] contributed between 0-2 PRs to the CDK labels Jul 24, 2025
@aws-cdk-automation aws-cdk-automation requested a review from a team July 24, 2025 16:40
Copy link
Collaborator

@aws-cdk-automation aws-cdk-automation left a comment

Choose a reason for hiding this comment

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

(This review is outdated)

@aws-cdk-automation aws-cdk-automation dismissed their stale review July 28, 2025 17:14

✅ Updated pull request passes all PRLinter validations. Dismissing previous PRLinter review.

@maramure maramure marked this pull request as ready for review July 30, 2025 08:25
@aws-cdk-automation aws-cdk-automation added the pr/needs-community-review This PR needs a review from a Trusted Community Member or Core Team Member. label Jul 30, 2025

const webSocketApi = new WebSocketApi(stack, 'WebSocketApi');

webSocketApi.addRoute('$connect', {
Copy link
Contributor

Choose a reason for hiding this comment

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

What's the reasons for adding routes('$connect', 'sendMessage')?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

They were written just to test if addRoute method works. But because I want to test only the usage plan and the routes don't have an impact on this, I deleted them from the integ test.

const webSocketStage = new WebSocketStage(stack, 'WebSocketStage', {
webSocketApi,
stageName: 'dev',
throttle: {
Copy link
Contributor

Choose a reason for hiding this comment

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

since we're testing UsagePlan, can we remove all the optional props in other constructor. That will make the code easier to read/understand

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, I kept only the mandatory properties for both WebSocketStages.


const apiKey = new ApiKey(stack, 'ApiKey');

const usagePlan = new UsagePlan(stack, 'UsagePlan', {
Copy link
Contributor

Choose a reason for hiding this comment

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

can we add all the optional parameter? since we want to cover as much branches as possible

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, I added both the mandatory and optional parameters for ApiKey and UsagePlan, because these are the 2 new added features.

class Import extends ApiKeyBase {
public keyId = apiKeyId;
public keyArn = Stack.of(this).formatArn({
service: 'apigateway',
Copy link
Contributor

@gasolima gasolima Jul 30, 2025

Choose a reason for hiding this comment

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

Looking at other constructors in apigwv2, i see service: 'execute-api',. Why are we going with apigateway here? also same comment on line 181

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Looking here
I saw that 'execute-api' is used only for the endpoints, while the other resources use 'apigateway' no matter if they are apigateway v1 or v2.

public keyId = apiKeyId;
public keyArn = Stack.of(this).formatArn({
service: 'apigateway',
account: '',
Copy link
Contributor

Choose a reason for hiding this comment

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

why are we going with empty string account? instead of removing the account prop

Copy link
Contributor Author

Choose a reason for hiding this comment

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

For a resource's ARN we need the account field empty (as I have seen here). If we would delete the account property, the account would be written by default.

* @param options options that control the behaviour of this method
*/
public addApiKey(apiKey: IApiKey, options?: AddApiKeyOptions): void {
let id: string;
Copy link
Contributor

Choose a reason for hiding this comment

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

What's the point of having let here? also since it's used one time isn't it better to have

const resource = new CfnUsagePlanKey(this, `${prefix}:${Names.nodeUniqueId(apiKey.node)}`, {`

Copy link
Contributor Author

Choose a reason for hiding this comment

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

let is used only if that variable could take more values depending on some conditions. Actually you are right, we don't need let in this case because to the id we assign only one string, no matter what. I modified that part as you suggested.

addConstructMetadata(this, props);
let resource: CfnUsagePlan;

resource = new CfnUsagePlan(this, 'Resource', {
Copy link
Contributor

Choose a reason for hiding this comment

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

can we have

const resource = new CfnUsagePlan(this, 'Resource', {

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, actually this is the good way of writing. Same explanation as in the previous comment.

}
}

private renderThrottle(props: ThrottleSettings | undefined): (CfnUsagePlan.ThrottleSettingsProperty | Token) {
Copy link
Contributor

Choose a reason for hiding this comment

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

This function never returns Token, right? can we remove it?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, this function never return Token, but it is possible to return undefined. I replaced Token with undefined and I adjusted the code so that if the props are undefined, the function would also return undefined.

validateInteger(limit, 'Throttle quota limit');
const ret = {
limit: limit ? limit : undefined,
offset: props.quota ? props.quota.offset : undefined,
Copy link
Contributor

Choose a reason for hiding this comment

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

this check not needed, since it's checked in the if L283
the same comment on L291

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, they are not necessary. I removed the checks for limit, offset and period.
For example, this is enough: limit: props.quota.limit, because if the limit is undefined it will return undefined which is the desired outcome.

* @default none
* @deprecated use `addApiKey()`
*/
readonly apiKey?: IApiKey;
Copy link
Contributor

Choose a reason for hiding this comment

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

Why are we adding deprecated field?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I have kept the deprecated field apiKey only because it is kept in apigatewayv1 too. Now, I removed it from the WebSocket's UsagePlan because is not needed.

Why the apiKey property was not useful:

  • It only allowed adding a single API key to the UsagePlan
  • Internally, it just called the addApiKey() method anyway
  • The addApiKey() method provides the same functionality with more flexibility

The better approach:

  • Use only the addApiKey() method directly, which allows adding multiple keys
  • Keys can be added after UsagePlan creation, providing more control
  • This eliminates the deprecated property while maintaining full functionality

}

private createStage(apiStage: UsagePlanPerApiStage): CfnUsagePlan.ApiStageProperty {
const stage = apiStage.stage ? apiStage.stage.stageName.toString() : undefined;
Copy link
Contributor

Choose a reason for hiding this comment

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

suggestion: isn't it better to wrap this function with one condition

  private createStage(apiStage: UsagePlanPerApiStage): CfnUsagePlan.ApiStageProperty {
    if(apiStage.stage){
       return {
         stage: apiStage.stage.stageName.toString(),
         apiId: apiStage.stage.api.apiId
       }
    }
  } 

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, is much clear in this way. Also, I added the fact that if apiStage.stage is undefined, the method would explicitly return undefined.

}

private renderApiStages(apiStages: UsagePlanPerApiStage[] | undefined): CfnUsagePlan.ApiStageProperty[] | undefined {
if (apiStages && apiStages.length > 0) {
Copy link
Contributor

Choose a reason for hiding this comment

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

this condition && apiStages.length > 0 is useless, can we remove it?

Copy link
Contributor Author

@maramure maramure Aug 4, 2025

Choose a reason for hiding this comment

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

It is somehow useful for maintaining the CDK standards.

Without the apiStages.length > 0 condition:

  • When a user creates a UsagePlan with an empty apiStages array (apiStages: [])
  • The CloudFormation Template explicitly shows: "ApiStages": []

With the apiStages.length > 0 condition:

  • Empty arrays are treated as undefined
  • The ApiStages property is omitted entirely from the CloudFormation Template
  • This follows CDK's standard practice of omitting optional properties when they have no meaningful values

// WHEN
new RateLimitedApiKey(stack, 'test-api-key', {
customerId: 'test-customer',
quota: {
Copy link
Contributor

Choose a reason for hiding this comment

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

can we pass also throttle and apiStages? to check that we're propagating them correctly

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, this would be a better approach. I added throttle and apiStages too.

rateLimit: rateLimit,
};
}
return ret!;
Copy link
Contributor

Choose a reason for hiding this comment

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

non null assertion operator should be avoided unless it's needed

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, I modified the code such that all the cases are covered (props are undefined or not) and return ret! is not needed anymore.

@maramure maramure force-pushed the usageplan branch 4 times, most recently from 63229eb to 596eb12 Compare August 4, 2025 17:02
@aws-cdk-automation
Copy link
Collaborator

AWS CodeBuild CI Report

  • CodeBuild project: AutoBuildv2Project1C6BFA3F-wQm2hXv2jqQv
  • Commit ID: 596eb12
  • Result: SUCCEEDED
  • Build Logs (available for 30 days)

Powered by github-codebuild-logs, available on the AWS Serverless Application Repository

/**
* Uniquely identifies this class.
*/
public static readonly PROPERTY_INJECTION_ID: string = 'aws-cdk-lib.aws-apigatewayv2.ApiKey';
Copy link
Contributor

Choose a reason for hiding this comment

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

The PROPERTY_INJECTION_ID should be more specific to avoid conflicts with other similar constructs. Consider changing to:'aws-cdk-lib.aws-apigatewayv2.websocket.ApiKey or aws-cdk-lib.aws-apigatewayv2.WebSocketApiKey. Applicable for other constructs also.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, this implementation follows CDK patterns. I used aws-cdk-lib.aws-apigatewayv2.websocket.ApiKey because it aligns with existing CDK conventions to having clear names for the properties. The alternative approach would have required renaming classes, which would have been an unnecessary refactoring effort.


/**
* The API key ARN.
*/
Copy link
Contributor

Choose a reason for hiding this comment

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

Based on the Design guidelines, we should add the @attribute tag for this resource property.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thank you for the resource you attached. I didn't know this information. I added @attribute tag now.

/**
* 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
Copy link
Contributor

Choose a reason for hiding this comment

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

Typo: automically should be automatically.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Corrected it


usagePlan.addApiKey(apiKey);
usagePlan.addApiStage({ api: webSocketApi, stage: webSocketStage2 });

Copy link
Contributor

Choose a reason for hiding this comment

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

Consider adding a RateLimitedApiKey test case as well to the integration test.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good idea. I added the creation of a new WebsocketStage and a RateLimitedApiKey. It is not needed to do a UsagePlan too, because it is automatically created when a RateLimitedApiKey is created.

kumsmrit
kumsmrit previously approved these changes Aug 18, 2025
@mergify
Copy link
Contributor

mergify bot commented Aug 18, 2025

Thank you for contributing! Your pull request will be updated from main and then merged automatically (do not update manually, and be sure to allow changes to be pushed to your fork).

@mergify
Copy link
Contributor

mergify bot commented Aug 18, 2025

This pull request has been removed from the queue for the following reason: pull request branch update failed.

The pull request can't be updated.

You should update or rebase your pull request manually. If you do, this pull request will automatically be requeued once the queue conditions match again.
If you think this was a flaky issue, you can requeue the pull request, without updating it, by posting a @mergifyio requeue comment.

@mergify mergify bot dismissed kumsmrit’s stale review August 19, 2025 08:01

Pull request has been modified.

@mergify
Copy link
Contributor

mergify bot commented Aug 19, 2025

Thank you for contributing! Your pull request will be updated from main and then merged automatically (do not update manually, and be sure to allow changes to be pushed to your fork).

@mergify
Copy link
Contributor

mergify bot commented Aug 19, 2025

Thank you for contributing! Your pull request will be updated from main and then merged automatically (do not update manually, and be sure to allow changes to be pushed to your fork).

@mergify mergify bot merged commit f7faffe into aws:main Aug 19, 2025
18 checks passed
@github-actions
Copy link
Contributor

Comments on closed issues and PRs are hard for our team to see.
If you need help, please open a new issue that references this one.

@github-actions github-actions bot locked as resolved and limited conversation to collaborators Aug 19, 2025
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

beginning-contributor [Pilot] contributed between 0-2 PRs to the CDK effort/medium Medium work item – several days of effort feature-request A feature should be added or improved. p2 pr/needs-community-review This PR needs a review from a Trusted Community Member or Core Team Member.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[aws_cdk.aws_apigatewayv2]: [Add Usage Plan support via the L2 constructs for the aws_apigatewayv2 resources (eg. WebSocketApi)]

4 participants