aws / aws-cdk

The AWS Cloud Development Kit is a framework for defining cloud infrastructure in code
https://aws.amazon.com/cdk
Apache License 2.0
11.55k stars 3.87k forks source link

API Gateway & Lambda: Unable to delete Lambda function from AWS API Gateway (Lambda still in use by API Gateway) #30658

Open tpotjj opened 3 months ago

tpotjj commented 3 months ago

Describe the bug

What I'm trying to do, is remove a Lambda Function from the API Gateway. I don't want to delete the whole Lambda Function itself (though that's not even possible when I try to).

I try to do this by simply commenting out the line of code that adds the Lambda Function to the API Gateway.

Here is my ApiGatewayStack:

import { Stack } from "aws-cdk-lib";
import { Construct } from "constructs";
import { StackPropsConfig } from "../config/stackPropsConfig";
import { Deployment, DomainName, EndpointType, LambdaIntegration, RestApi, Stage } from "aws-cdk-lib/aws-apigateway";
import { IHostedZone } from "aws-cdk-lib/aws-route53";
import { ICertificate } from "aws-cdk-lib/aws-certificatemanager";

interface ApiGatewayProps extends StackPropsConfig {
    hostedZone: IHostedZone;
    certificate: ICertificate;
    testLambda: LambdaIntegration;
    createPost: LambdaIntegration;
    getPost: LambdaIntegration;
}

export class ApiGatewayStack extends Stack {
    public readonly customDomain: DomainName;

    constructor(scope: Construct, id: string, props: ApiGatewayProps) {
        super(scope, id, props);

        const apiGateway = new RestApi(this, `butlai-api-gateway-${props.config.ENV}`, {
            deploy: false
        });

        this.customDomain = apiGateway.addDomainName(`butlai-api-gateway-custom-domain-${props.config.ENV}`, {
            domainName: props.config.ENV_URL,
            certificate: props.certificate,
            endpointType: EndpointType.EDGE,
        });

        // Explicitly create a deployment for v1
        const deploymentV1 = new Deployment(this, `butlai-api-deployment-v1-${props.config.ENV}-${Date.now()}`, {
            api: apiGateway,
            retainDeployments: true,
        });

        // Create a stage for v1
        new Stage(this, `butlai-api-stage-v1-${props.config.ENV}`, {
            deployment: deploymentV1,
            stageName: 'v1'
        });

        // Create resources and methods for v1
        const postsResourcesV1 = apiGateway.root.addResource("posts");
        postsResourcesV1.addMethod("POST", props.createPost);

        const postResourcesV1 = postsResourcesV1.addResource("{id}");
        postResourcesV1.addMethod("GET", props.getPost);
        postResourcesV1.addMethod("DELETE", props.testLambda);
    }
}

And this is my Launcher.ts file:

import { App } from "aws-cdk-lib"
import { NetworkStack } from "./stacks/NetworkStack";
import { AppConfig } from "./config/types";
import { loadConfig } from "./config/loadConfig";
import { DataStack } from "./stacks/DataStack";
import { LambdaStack } from "./stacks/LambdaStack";
import { ApiGatewayStack } from "./stacks/ApiGatewayStack";
import { CertificateStack } from "./stacks/CertificateStack";
import { Route53Stack } from "./stacks/Route53Stack";
import { ApiGatewayDomain } from "aws-cdk-lib/aws-route53-targets";
import { SecurityStack } from "./stacks/SecurityStack";

const app = new App();

const appConfig: AppConfig = loadConfig(app);

const route53Stack = new Route53Stack(app, `ButlaiRoute53Stack-${appConfig.ENV}`, {
    config: appConfig,
})

const certificateStack = new CertificateStack(app, `ButlaiCertificateStack-${appConfig.ENV}`, {
    config: appConfig,
    hostedZone: route53Stack.hostedZone
});

const networkStack = new NetworkStack(app, `ButlaiNetworkStack-${appConfig.ENV}`, {
    config: appConfig
});

const securityStack = new SecurityStack(app, `ButlaiSecurityStack-${appConfig.ENV}`, {
    vpc: networkStack.vpc,
});

const dataStack = new DataStack(app, `ButlaiDataStack-${appConfig.ENV}`, {
    config: appConfig,
    vpc: networkStack.vpc,
    databaseSecurityGroup: securityStack.databaseSecurityGroup,
});

const lambdaStack = new LambdaStack(app, `ButlaiLambdaStack-${appConfig.ENV}`, {
    config: appConfig,
    vpc: networkStack.vpc,
    databaseSecrets: dataStack.databaseSecrets,
    lambdaSecurityGroup: securityStack.lambdaSecurityGroup,
    databaseSecurityGroup: securityStack.databaseSecurityGroup,
});

const apiGatewayStack = new ApiGatewayStack(app, `ButlaiApiGatewayStack-${appConfig.ENV}`, {
    config: appConfig,
    hostedZone: route53Stack.hostedZone,
    certificate: certificateStack.certificate,
    testLambda: lambdaStack.testLambdaFunction,
    createPost: lambdaStack.createPostLambdaFunction,
    getPost: lambdaStack.getPostLambdaFunction,
});

And this is what I try to do:

// postResourcesV1.addMethod("DELETE", props.testLambda);

this results in the following error in the Console:

The CloudFormation Console for this stack tells me this: Export ButlaiLambdaStack-dev:ExportsOutputFnGetAtttestFunctionFOOBAR cannot be deleted as it is in use by ButlaiApiGatewayStack-dev

But there aren't any other references to that testLambda function elsewhere in my codebase. Next to that, I'm not trying to DELETE the whole Lambda function, I want to deploy a new version of the API without that Method.

Expected Behavior

In the next deployment (that I'm trying to deploy), the method that I want to delete from the API Gateway should be removed. The Lambda function itself will still exist in AWS, in case we want to revert to an older deployment version.

Current Behavior

The current behaviour is a ROLLBACK. Nothing else happens, the stack won't be updated, the method still exists and is callable.

Reproduction Steps

If you would copy and paste the code that I posted above (the ApiGatewayStack). And deploy it using: cdk deploy --all Then, comment out one of the added Lambda Functions an re-run: cdk deploy --all

Possible Solution

No response

Additional Information/Context

No response

CDK CLI Version

2.147.0 (build 3338fc0)

Framework Version

No response

Node.js Version

v22.2.0

OS

Mac OS 14.5 (23F79)

Language

TypeScript

Language Version

TypeScript (5.4.3)

Other information

No response

pahud commented 3 months ago

Export ButlaiLambdaStack-dev:ExportsOutputFnGetAtttestFunctionFOOBAR cannot be deleted as it is in use by ButlaiApiGatewayStack-dev

This error indicates the export ExportsOutputFnGetAtttestFunctionFOOBAR from ButlaiLambdaStack-dev is currently in used by ButlaiApiGatewayStack-dev stack and cloudformation was trying to "unexport" it, which is rejected, hence rollback.

If you just update this. Can you run npx cdk diff and see what's going to be changed? As I didn't see how you pass those props, my guess is props.testLambda might from cross stack reference, which auto built the output exports but I am not sure.

// postResourcesV1.addMethod("DELETE", props.testLambda);
tpotjj commented 3 months ago

@pahud Thanks for the response.

I've updated my question so that it contains the Launcher.ts file as well. As you can see, there is no cross-stack-reference between the ButlaiApiGatewayStack-dev and the ButlaiLambdaStack-dev. The only thing which I do, is passing the Lambda functions created in the Lambda stack to the ApiGateway stack.

I also ran npx cdk diff, the results are below.

Screenshot 2024-06-27 at 10 18 30