Skip to content

Cloudformation Template with Lambda. Need index.mjs to be auto deployed but automatically defaults to index.js, how to fix?

0

Has anyone encountered this issue when deploying lambda with cloudformation templates?

Lambda will automatically create index.js , not index.mjs

Here is my cloudformation template

AWSTemplateFormatVersion: '2010-09-09'
Description: CloudFormation template to create a Lambda function with API Gateway (stages dev and prod) with proxy integration, along with appropriate IAM policies for S3, CloudFormation, and Lambda.

Resources:
  LambdaExecutionRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal:
              Service: lambda.amazonaws.com
            Action: sts:AssumeRole
      Policies:
        - PolicyName: LambdaS3FullAccessPolicy
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Effect: Allow
                Action:
                  - s3:*
                Resource: "*"
        - PolicyName: LambdaCloudFormationFullAccessPolicy
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Effect: Allow
                Action:
                  - cloudformation:*
                Resource: "*"
        - PolicyName: LambdaLoggingPolicy
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Effect: Allow
                Action:
                  - logs:CreateLogGroup
                  - logs:CreateLogStream
                  - logs:PutLogEvents
                Resource: arn:aws:logs:*:*:*

  CloudFormationLambdaFunction:
    Type: AWS::Lambda::Function
    Properties:
      Handler: index.mjs  # Updated handler to index.mjs
      Role: !GetAtt LambdaExecutionRole.Arn
      Runtime: nodejs18.x
      Code:
        ZipFile: |
          import { CloudFormationClient, CreateStackCommand } from "@aws-sdk/client-cloudformation";

          const cloudformation = new CloudFormationClient();

          export const handler = async (event) => {
              console.log("Received event:", JSON.stringify(event, null, 2));

              let body;
              try {
                  body = JSON.parse(event.body);
              } catch (error) {
                  return {
                      statusCode: 400,
                      body: JSON.stringify({
                          message: 'Invalid JSON format',
                          error: error.message
                      }),
                  };
              }

              const { templateUrl, stackName, parameters = [] } = body;

              if (!stackName) {
                  return {
                      statusCode: 400,
                      body: JSON.stringify({
                          message: 'stackName is required',
                      }),
                  };
              }

              const params = {
                  StackName: stackName,
                  TemplateURL: templateUrl,
                  Parameters: Array.isArray(parameters) ? parameters.map(param => ({
                      ParameterKey: param.key,
                      ParameterValue: param.value
                  })) : [],
                  Capabilities: ['CAPABILITY_IAM', 'CAPABILITY_NAMED_IAM']
              };

              try {
                  const command = new CreateStackCommand(params);
                  const response = await cloudformation.send(command);
                  
                  return {
                      statusCode: 200,
                      body: JSON.stringify({
                          message: 'Stack creation initiated',
                          stackId: response.StackId
                      }),
                  };
              } catch (error) {
                  return {
                      statusCode: 500,
                      body: JSON.stringify({
                          message: 'Error launching CloudFormation stack',
                          error: error.message
                      }),
                  };
              }
          };
      Timeout: 15
      MemorySize: 128
      Architectures: ['x86_64']
      PackageType: Zip
      # Enable ES Modules in the Lambda function
      Environment:
        Variables:
          NODE_OPTIONS: "--experimental-json-modules"

  ApiGatewayRestApi:
    Type: AWS::ApiGateway::RestApi
    Properties:
      Name: CloudFormationApi
      Description: API Gateway for invoking the CloudFormation stack creation Lambda function

  ApiGatewayResource:
    Type: AWS::ApiGateway::Resource
    Properties:
      ParentId: !GetAtt ApiGatewayRestApi.RootResourceId
      PathPart: launchcf  # Updated PathPart
      RestApiId: !Ref ApiGatewayRestApi

  ApiGatewayMethod:
    Type: AWS::ApiGateway::Method
    Properties:
      AuthorizationType: NONE
      HttpMethod: POST
      ResourceId: !Ref ApiGatewayResource
      RestApiId: !Ref ApiGatewayRestApi
      Integration:
        IntegrationHttpMethod: POST
        Type: AWS_PROXY
        Uri: 
          Fn::Sub: arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${CloudFormationLambdaFunction.Arn}/invocations
      RequestParameters:
        method.request.header.Content-Type: true  # Ensure Content-Type is passed
      RequestModels:
        application/json: "Empty"  # Specify content type for request body
      MethodResponses:
        - StatusCode: 200
        - StatusCode: 400
        - StatusCode: 500

  ApiGatewayDeploymentDev:
    Type: AWS::ApiGateway::Deployment
    DependsOn: ApiGatewayMethod
    Properties:
      RestApiId: !Ref ApiGatewayRestApi
      StageName: dev
      Description: "Development Stage"

  ApiGatewayDeploymentProd:
    Type: AWS::ApiGateway::Deployment
    DependsOn: ApiGatewayMethod
    Properties:
      RestApiId: !Ref ApiGatewayRestApi
      StageName: prod
      Description: "Production Stage"

  LambdaApiPermission:
    Type: AWS::Lambda::Permission
    Properties:
      Action: lambda:InvokeFunction
      FunctionName: !Ref CloudFormationLambdaFunction
      Principal: apigateway.amazonaws.com
      SourceArn: 
        Fn::Sub: arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${ApiGatewayRestApi}/*/POST/launchcf  # Updated source

Outputs:
  DevApiUrl:
    Value: 
      Fn::Sub: https://${ApiGatewayRestApi}.execute-api.${AWS::Region}.amazonaws.com/dev/launchcf
    Description: "The URL of the POST endpoint for the dev stage"

  ProdApiUrl:
    Value: 
      Fn::Sub: https://${ApiGatewayRestApi}.execute-api.${AWS::Region}.amazonaws.com/prod/launchcf
    Description: "The URL of the POST endpoint for the prod stage"
asked a year ago445 views
2 Answers
0

Hello.

In the current situation with CloudFormation, it automatically defaults to index.js.
I think the only way to avoid this is to package the JavaScript code, upload it to S3, and reference S3 in CloudFormation's CodeUri, as mentioned in the documentation below.
https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/using-cfn-cli-package.html
https://docs.aws.amazon.com/lambda/latest/dg/nodejs-package.html

I found the same issue as you in the GitHub issue below, but it seems that the issue is still unaddressed as of October 2024.
https://github.com/aws-cloudformation/cloudformation-coverage-roadmap/issues/1832

EXPERT
answered a year ago
-2

The issue you're encountering is related to how AWS Lambda handles JavaScript files in Node.js environments. By default, Lambda looks for an index.js file as the entry point for Node.js functions, even when you specify index.mjs in your CloudFormation template.

To resolve this issue and ensure that your index.mjs file is used correctly, you need to make a few modifications to your CloudFormation template:

  1. Change the Handler property of your Lambda function to use index.handler instead of index.mjs.

  2. Add an Environment section to your Lambda function properties to enable ES modules.

  3. Ensure your package.json file (if you're using one) includes "type": "module" to indicate that your project uses ES modules.

Here's how you can modify the CloudFormationLambdaFunction resource in your template:

CloudFormationLambdaFunction:
Type: AWS::Lambda::Function
Properties:
Handler: index.handler
Role: !GetAtt LambdaExecutionRole.Arn
Runtime: nodejs18.x
Code:
ZipFile: |
// Your Lambda function code here
Timeout: 15
MemorySize: 128
Architectures: ['x86_64']
PackageType: Zip
Environment:
Variables:
NODE_OPTIONS: "--experimental-vm-modules"

By making these changes, you're telling Lambda to use the handler function exported from the index file (which can be either .js or .mjs), and you're enabling ES module support through the NODE_OPTIONS environment variable.

Remember to include a package.json file in your deployment package with "type": "module" if you're using external dependencies or if you want to ensure ES module syntax is used.

These modifications should allow your Lambda function to correctly use the index.mjs file as intended when deployed through CloudFormation.

Sources
AWS::Lambda::Function - AWS CloudFormation
AWS Lambda template - AWS CloudFormation

answered a year ago
  • the AI suggestion doesn't work

You are not logged in. Log in to post an answer.

A good answer clearly answers the question and provides constructive feedback and encourages professional growth in the question asker.