Home > Back-end >  aws cdk lambda is not bound when an api-gatewayv2 from a shared resource is used
aws cdk lambda is not bound when an api-gatewayv2 from a shared resource is used

Time:12-15

I'm playing with AWS CDK and I created the following scenario: 2 stacks with shared resources and then other stacks that use the shared resources.

This is the shared stack for the VPC:

import * as cdk from "@aws-cdk/core";
import * as ec2 from "@aws-cdk/aws-ec2";

export class VpcStack extends cdk.Stack {
  public readonly vpc: ec2.Vpc;
  constructor(scope: cdk.App, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    // assign a VPC to the class property SharedInfraStack
    this.vpc = new ec2.Vpc(this, 'my-vpc', {
      cidr: '10.0.0.0/16',
      natGateways: 1,
      maxAzs: 3,
      subnetConfiguration: [
        {
          name: 'private-subnet-1',
          subnetType: ec2.SubnetType.PRIVATE_WITH_NAT,
          cidrMask: 20,
        },
        {
          name: 'public-subnet-1',
          subnetType: ec2.SubnetType.PUBLIC,
          cidrMask: 20,
        },
        {
          name: 'isolated-subnet-1',
          subnetType: ec2.SubnetType.PRIVATE_ISOLATED,
          cidrMask: 20,
        },
      ],
    });
    
  }
}

And this for the API Gateway v2:

import {CorsHttpMethod, HttpApi } from '@aws-cdk/aws-apigatewayv2';
import * as cdk from '@aws-cdk/core';

export class ApiGatewayStack extends cdk.Stack {
  public apigw: HttpApi;

  constructor(scope: cdk.App, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    this.apigw = new HttpApi(this, 'my-http-api', {
      corsPreflight: {
        allowHeaders: [
          'Content-Type',
          'X-Amz-Date',
          'Authorization',
          'X-Api-Key',
        ],
        allowMethods: [
          CorsHttpMethod.OPTIONS,
          CorsHttpMethod.GET,
          CorsHttpMethod.POST,
          CorsHttpMethod.PUT,
          CorsHttpMethod.PATCH,
          CorsHttpMethod.DELETE,
        ],
        allowCredentials: true,
        allowOrigins: [
          'https://example.com:3000',
      ],
      },
    });

    new cdk.CfnOutput(this, 'apiUrl', {
      value: this.apigw.url!,
    });

  }
}

I also created an interface to use whenever I want to use the two shared resources in other stacks:

import * as cdk from '@aws-cdk/core';
import * as ec2 from "@aws-cdk/aws-ec2";
import { HttpApi } from '@aws-cdk/aws-apigatewayv2';

export interface FunctionProps extends cdk.StackProps {
    vpc: ec2.Vpc;
    apigw: HttpApi;
}

After that, I created a simple stack where a lambda function is defined and should use the VPC and APIGW provided in the props:

import {HttpMethod} from '@aws-cdk/aws-apigatewayv2';
import {LambdaProxyIntegration} from '@aws-cdk/aws-apigatewayv2-integrations';
import * as lambda from '@aws-cdk/aws-lambda';
import * as cdk from '@aws-cdk/core';
import * as ec2 from "@aws-cdk/aws-ec2";
import * as path from 'path';

import {FunctionProps} from './props';


export class UserStack extends cdk.Stack {
  constructor(scope: cdk.App, id: string, props?: FunctionProps) {
    super(scope, id, props);

    const whoAmILambda = new lambda.Function(this, 'who-am-i', {
        runtime: lambda.Runtime.NODEJS_14_X,
        handler: 'index.main',
        code: lambda.Code.fromAsset(path.join(__dirname, 'path/to/function')),
        vpc: props?.vpc,
        vpcSubnets: {
            subnetType: ec2.SubnetType.PRIVATE_ISOLATED,
        }
      });
      props?.apigw.addRoutes({
        path: '/whoami',
        methods: [HttpMethod.GET],
        integration: new LambdaProxyIntegration({
          handler: whoAmILambda,
        }),
      });
  }
}

The main.ts file is the following one:

import * as cdk from "@aws-cdk/core";
import { ApiGatewayStack } from "./src/api-gateway/api-gateway";
import { UserStack } from "./src/functions/user";
import { VpcStack } from "./src/vpc/vpc-stack";

const env = {
    account: process.env.ACCOUNT_NUMBER,
    region: process.env.AWS_REGION
}
const app = new cdk.App();

const vpcStack = new VpcStack(app, 'VpcStack', {env});
const apigwStack = new ApiGatewayStack(app, 'ApiGatewayStack', {env});

new UserStack(app, 'UserStack', {
    env,
    vpc: vpcStack.vpc,
    apigw: apigwStack.apigw,
})

I deployed the stacks in the following order:

cdk deploy VpcStack
cdk deploy ApiGatewayStack
cdk deploy UserStack

Everything works properly, the VPC is created and the APIGW is also created, the problem is in the lambda function.

The function has the expected configuration for the VPC but there is no trigger for the api-gateway. Looking at the resources in the API Gateway dashboard console nothing is created. But if I re-run the command cdk deploy ApiGatewayStack the resource whoami is created and I can use curl to make an HTTP GET request to retrieve the value generated in the lambda function.

The problem with this workaround is that whenever I want to add another stack that uses the APIGW I will have to run cdk deploy ApiGatewayStack multiple times increasing the deployment time. Is there anything I can do to use and create an HTTP endpoint inside the lambda stack to not rely on the cdk deploy ApiGatewayStack command to deploy new endpoints?

CodePudding user response:

The reason this is happening is that CloudFormation does not allow modifying imported resources (created outside of the stack). Since your gateway is created outside your lambda stack, it cannot modify it.

The way CDK works around this is by going back and changing the template for the ApiGatewayStack to apply the changes needed, even though the changes were implemented in another stack.

This is a limitation of CloudFormation. I would suggest not deploying your stacks separately to avoid this issue, deploying them all with cdk deploy --all. It should not take longer, because CloudFormation will only deploy the differences, it will not redeploy the whole ApiGatewayStack.

  • Related