How do I incorporate a preexisting usage plan into an API stage using AWS CDK?

In the CDK documentation, it is mentioned that an external usage plan can be imported using the static function called fromUsagePlanId. However, this returns an IUsagePlan interface which does not contain the method addApiStage to attach my API and its stage.

Here is a snippet of my code:

import * as apigateway from 'aws-cdk-lib/aws-apigateway';

export class CdkApiGwTemplateStack extends cdk.Stack {
  constructor(scope: Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    const api = new apigateway.RestApi(this,`${domain.toLowerCase()}-${subDomain.toLowerCase()}`,{
      restApiName: `${domain.toLowerCase()}-${subDomain.toLowerCase()}`,
      description: apiDescription,
      binaryMediaTypes: binaryMediaTypes,
      deployOptions: {
        accessLogDestination: new LogGroupLogDestination(logGroup),
        loggingLevel: cloudwatchLoggingLevel.toUpperCase() as MethodLoggingLevel,
        stageName: environment.toLowerCase(),
        variables: variables,
      },
     });

    const key = api.addApiKey('ApiKey', {
      apiKeyName: apikeyName,
      description: apiKeyDescription,
    });

    const plan = apigateway.UsagePlan.fromUsagePlanId(this, 'getExternalUsagePlan', usagePlanId);

    plan.addApiKey(key);

I have tried searching for a CloudFormation level 1 construct to achieve this, but without success. Is there a way to use the addApiStage method of the UsagePlan constructor with the IUsagePlan interface, or any other idea on how to add my API to an existing Usage Plan?

Answer №1

After struggling to find a suitable ad-hoc solution, I decided to create my own custom construct. This custom construct utilizes a lambda resource provider to manage the lifecycle of resources. Special thanks to Jeffb4 for sharing this useful repository: https://github.com/stelligent/cloudformation-custom-resources/blob/master/lambda/python/customresource.py

Here's how you can use it:

usage_plan_id = "usage_id_here"
bind_usage_plan = BindUsagePlanConstruct(
    self, "BindUsagePlan", usage_plan_id, self.rest_api
)  # self.rest_api is of type apigateway.RestApi
bind_usage_plan.node.add_dependency(self.rest_api)

The construct itself looks like this:

from os import path
from aws_cdk import CustomResource, aws_lambda, RemovalPolicy, Duration, aws_iam
from aws_cdk import aws_logs
from aws_cdk.custom_resources import Provider
from constructs import Construct


class BindUsagePlanConstruct(Construct):
    def __init__(
        self, scope: Construct, _id: str, usage_plan_id: str, rest_api
    ) -> None:
        super().__init__(scope, _id)
        self._scope = scope._scope
        event_handler = self._create_lambda_function()
        provider = Provider(
            scope=self,
            id=f"{_id}Provider",
            on_event_handler=event_handler,
            log_retention=aws_logs.RetentionDays.ONE_DAY,
        )
        CustomResource(
            scope=self,
            id=f"{_id}UsagePlanBind",
            service_token=provider.service_token,
            removal_policy=RemovalPolicy.DESTROY,
            resource_type="Custom::UsagePlanBind",
            properties={
                "usage_plan_id": usage_plan_id,
                "api_gw_id": f"{rest_api.rest_api_id}",
                "api_gw_stage": f"{rest_api.deployment_stage.stage_name}",
            },
        )

    def _create_lambda_function(self):
        with open(
            path.join(path.dirname(__file__), "bind_usage_plan_lambda.py")
        ) as file:
            code = file.read()
        return aws_lambda.Function(
            self,
            f"{id}EventHandler",
            runtime=aws_lambda.Runtime.PYTHON_3_12,
            handler="index.on_event",
            code=aws_lambda.Code.from_inline(code),
            timeout=Duration.minutes(15),
            initial_policy=[
                aws_iam.PolicyStatement(
                    actions=[
                        "apigateway:GET",
                        "apigateway:POST",
                        "apigateway:DELETE",
                        "apigateway:PATCH",
                    ],
                    resources=[
                        f"arn:aws:apigateway:{self._scope.region}::/usageplans/*",
                        f"arn:aws:apigateway:{self._scope.region}::/usageplans/*/apiStages/*:prod",
                        f"arn:aws:apigateway:{self._scope.region}::/restapis/*",
                    ],
                )
            ],
        )

Additionally, here is the lambda function code:

import logging
from hashlib import sha224

LOGGER = logging.getLogger()
LOGGER.setLevel(logging.INFO)
import boto3

client = boto3.client("apigateway")

# Functionality for different request types - Create, Update, Delete

# Support functions for handling assignment and unassignment of usage plans

Answer №2

To achieve this goal, there are various methods you can utilize based on how the resources were initially created (whether through Click-ops or CloudFormation).

  • One option is to utilize the newly launched IaC Generator to automatically create CloudFormation and CDK from existing resources. For more information, visit this link and here.

  • Another alternative is to use CDK migrate to import CloudFormation resources into your CDK stacks. Refer to this guide for detailed instructions.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Do you find this unattractive? What are some ways to improve this unsightly JavaScript statement?

This code seems messy, how can I better structure this switch statement? function renderDataTypeIcon(dataType: string) { let iconName; switch (dataType) { case "STRING": //TODO - ENUM iconName = "text"; break; ...

Inject an asynchronous callback into the constructor of a class by leveraging TypeScript and Node with Passport integration

Currently, I am utilizing the passport-http authentication library. The issue at hand is that its documentation makes use of callbacks while my implementation involves TypeScript classes with async/await functionalities, thus causing uncertainty regarding ...

Angular is patiently awaiting the completion of the subscription

Currently, I am in the process of developing a test application using Angular. The challenge arises when I attempt to retrieve data through a Get request and then return a value based on that data. The code snippet below outlines the scenario: public getN ...

Triggering ngSubmit function when button is clicked inside an Ionic alert

My ionic app is up and running, utilizing a template driven form in Angular to gather user input. I'm using ngSubmit to pass this data to my ts.file. My challenge lies in triggering the ngSubmit function through a 'No and save data' button w ...

What prevents TypeScript from allowing an async function to return a combination of type T or Promise<T>?

During the development of my API in typescript, I encountered a situation where some controller actions can be synchronous while others cannot. To address this issue, I decided to specify a response type as follows: type ActionResult =IHttpActionResult | ...

Does Vetur have additional undefined types in the type inference of deconstructed props?

When reviewing the code below, Vetur concluded that x,y are of type number | undefined. The presence of undefined is leading to numerous warnings when using x,y further in the code. Is there a way to eliminate the undefined from the type inference? <s ...

One effective way to transfer state to a child component using function-based React

My goal is to pass an uploaded file to a child component in React. Both the parent and child components are function-based and utilize TypeScript and Material-UI. In the Parent component: import React from 'react'; import Child from './ ...

What is preventing me from retrieving a value from a member function or method within a TypeScript class instance?

I am facing an issue with the FileInfo class that implements the IFileInfo interface. This class has an instance member function ext and a function getExt(). Within my component, there is a private method named openTempFolder() which makes an HTTP call to ...

Execute an npm script using a gulp task

Is there a way to execute an npm script command within a gulp task? package.json "scripts": { "tsc": "tsc -w" } gulpfile.js gulp.task('compile:app', function(){ return gulp.src('src/**/*.ts') .pipe(/*execute npm run tsc*/ ...

1. "The power of three vows in the world

I encountered an issue while making three HTTP Post requests in my code. The first two requests are successful, and upon debugging the code, they return the correct values. However, the third request returns undefined. The reason behind making these three ...

Creating dynamic Angular child routes with variable initial segment

Recently, I've been working on a new project to set up a blogging system. The next step in my plan is to focus on the admin section, specifically editing posts. My idea for organizing the routes is as follows: /blog - Home page /blog/:slug - Access ...

Issue: The function (0, react__WEBPACK_IMPORTED_MODULE_1__.useActionState) is not recognized as a valid function or its output is not iterable

I found a great example of using useActionState at this source. Currently, I am implementing it in my project with Next.js and TypeScript. app/page.tsx: "use client"; import { useActionState } from "react"; import { createUser } from ...

Utilizing props in styled-components: A beginner's guide

I am trying to pass a URL to a component so that I can use it as the background image of the component. Additionally, I need to check if the URL is empty. Component: <BannerImg/> CSS (styled): `const BannerImg = styled.img` background-image: url( ...

Angular 2 form with ng2-bootstrap modal component reset functionality

I have implemented ng2-bs3-modal in my Angular 2 application. I am now looking for a way to clear all form fields when the close button is clicked. Can anyone suggest the best and easiest way to achieve this? html <button type="button" class="btn-u ...

Creating a digital collection using Vue, Typescript, and Webpack

A short while back, I made the decision to transform my Vue project into a library in order to make it easier to reuse the components across different projects. Following some guidelines, I successfully converted the project into a library. However, when ...

Retrieving the computed value created from an axios get request in Vue

I am attempting to retrieve the computed value in created for a GET request. I have declared the classroomBackground as a string in my data. data() { return { classroomBackground: '' as string, ...

ability to reach the sub-element dictionaries in typescript

class ProvinciaComponent extends CatalogoGenerico implements OnInit, AfterViewInit { page: Page = new Page({sort: {field: 'description', dir: 'asc'}}); dataSource: ProvinciaDataSource; columns = ['codprovi ...

Mapping arrays from objects using Next.js and Typescript

I am trying to create a mapping for the object below using { ...product.images[0] }, { ...product.type[0] }, and { ...product.productPackages[0] } in my code snippet. This is the object I need to map: export const customImage = [ { status: false, ...

What strategies can I use to address the issue of requiring a type before it has been defined?

Encountered an intriguing challenge. Here are the types I'm working with: export interface QuestionPrimative { question : string; id : string; name : string; formctrl? : string; formgrp? : string; lowEx ...

Develop a child interface within Typescript

I am unsure if the term sub interface is correct, but my goal is to develop an interface that inherits all properties from the super interface except for some optional ones. Despite referring to the TypeScript documentation for interfaces, I was unable to ...