Finding the URL and ARN of AWS CDK Lambdas
Published
I spent the last few months creating Node.js Lambda functions that integrate various services for an e-commerce site. This was my first time working with a composable architecture, and I enjoyed the event-driven nature of the programming.
We had a strategy worked out to auto-deployment our Lambdas, but regarding configuration, we faced a chicken and the egg situation: how could we automatically point downstream services to a Lambda if the ARN and API Gateway URL aren’t available until after it is deployed?
Problem Background and CDK Usage
This problem arose when using AWS’s CDK to define the cloud application stacks that included our Lambda functions and API Gateway REST APIs.
Having used the Serverless Framework in the past, CDK seemed like a pretty powerful alternative that let you to dynamically define resources in an otherwise static ecosystem (e.g. Serverless, or straight CloudFormation Templates).
Here is a code snippet defining a Node.js Lambda accessible via API Gateway:
const { Stack } = require('aws-cdk-lib');
const lambda = require('aws-cdk-lib/aws-lambda');
const apigateway = require("aws-cdk-lib/aws-apigateway");
const { LAMBDA_FUNCTION_NAME, API_GATEWAY_REST_API_NAME } = require('./constants');
class AwsCdkNodejsLambdaStack extends Stack {
constructor(scope, id, props) {
super(scope, id, props);
const getHikesLambdaFunction = new lambda.Function(this, LAMBDA_FUNCTION_NAME, {
runtime: lambda.Runtime.NODEJS_14_X, // execution environment
code: lambda.Code.fromAsset('handler'), // code loaded from "handler" directory
handler: 'index.main' // file is "index", function is "main"
});
const getHikesApi = new apigateway.RestApi(this, 'getHikesRestApiId', {
restApiName: API_GATEWAY_REST_API_NAME,
description: "This service returns a static list of hikes in JSON format."
});
const getHikesIntegration = new apigateway.LambdaIntegration(getHikesLambdaFunction, {
requestTemplates: { "application/json": '{ "statusCode": "200" }' }
});
getHikesApi.root.addMethod("GET", getHikesIntegration);
}
}
module.exports = { AwsCdkNodejsLambdaStack }
CDK provided a straightforward way to define and configure these resources, but did not provide the ability to retrieve the resource ARN or URL once it was deployed.
Solution with NPM Postdeploy Scripts
The problem was solved by introducing logic to query AWS for the deployed Lambda ARN and URLs in an NPM postdeploy
script.
In our deployment pipeline, NPM was used to execute tests and perform deployments. NPM provides support for pre and post scripts that can be automatically executed with other named scripts.
// package.json - non pertinent contents removed for brevity
{
"name": "aws-cdk-nodejs-lambda",
"version": "0.1.0",
"scripts": {
"deploy": "cdk deploy",
"postdeploy": "node ./postdeploy/getDeployedApiGatewayRestApiUrl.js && node ./postdeploy/getDeployedLambdaARN.js"
},
}
In the example above, calling npm run deploy
would automatically trigger the postdeploy
script once the original deploy
script had finished. This postdeploy
script is where we introduced the logic to query AWS for the deployed Lambda ARN and URLs.
Finding the ARN of a Deployed Lambda Function
The strategy shown below for determining a Lambda’s ARN once it is deployed is as follows:
- Retrieve all Lambdas available in a given AWS region.
- Loop through the Lambdas and find the one with the matching Function Name (defined as
LAMBDA_FUNCTION_NAME
in the CDK configuration above). - Return the ARN associated with that Lambda.
const AWS = require('aws-sdk');
/**
* Queries AWS to find a Lambda function's ARN based on the supplied stack name and function name
* used when creating the Lambda with the CDK.
*
* @param {string} lambdaFunctionName the Lambda function's Name (defined in lib/aws-cdk-nodejs-lambda-stack.js).
* @returns {string} Full AWS ARN for the Lambda function.
* @throws {Error} Will throw an error if the Lambda function cannot be found.
*/
const getLambdaFunctionArn = async function(lambdaFunctionName) {
const lambda = new AWS.Lambda({
apiVersion: '2015-03-31',
accessKeyId: AWS_ACCESS_KEY_ID,
secretAccessKey: AWS_SECRET_ACCESS_KEY,
region: AWS_REGION
});
const params = {
FunctionVersion: 'ALL'
};
const results = await lambda.listFunctions(params).promise();
for (let i = 0; i
We can see that line 22 retrieves all available Lambdas, line 24 starts the iteration over these Lambdas, and line 27 makes the name comparison. Lambda function names may include other information like the CDK App or Stack, so .includes()
is used for the name comparison.
Note that in this example Version 2 of the AWS SDK for JavaScript is used.
Determining an API Gateway REST API URL
A similar process is followed when determining the API Gateway URL for a CDK deployed Lambda:
- List all API Gateway REST APIs available in an AWS Region.
- Iterate over all REST APIs and match based on the name (specified as
API_GATEWAY_REST_API_NAME
in the CDK configuration above). - Assemble the execute-api URL for the matched REST API.
const AWS = require('aws-sdk');
/**
* Retrieves the URL for a REST API defined inside API Gateway.
*
* @param {string} restApiName REST API name used in the CDK Stack when defining the API.
* @returns {string} URL for the REST API.
* @throws {Error} Will throw an error if the REST API cannot be found.
*/
const getApiGatewayRestApiUrl = async function(restApiName) {
const apigateway = new AWS.APIGateway({
apiVersion: '2015-07-09',
accessKeyId: AWS_ACCESS_KEY_ID,
secretAccessKey: AWS_SECRET_ACCESS_KEY,
region: AWS_REGION
});
// find restApiId - uses older verion of AWS SDK
// https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/APIGateway.html
const restApiResults = await apigateway.getRestApis().promise();
const restApi = restApiResults.items.find(api => api.name === restApiName );
// get rest api resourceId
if (restApi && restApi.id) {
// return execute-api structured URL - no method but documented a few places
// - https://docs.aws.amazon.com/apigateway/latest/developerguide/getting-started.html
// - https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/APIGateway.html#getResources-property
return `https://${restApi.id}.execute-api.${AWS_REGION}.amazonaws.com/prod`
}
// throw an error if no API is found
throw new Error(`unable to find REST API named '${restApiName}' in API Gateway`);
}
In the snippet above, line 21 retrieves all API Gateway REST APIs, line 22 performs the name matching, and line 30 assembles the ugly execute-api URL we're all used to for API Gateway Lambdas (e.g. https://4ilrw8zgp3.execute-api.us-east-1.amazonaws.com/prod
). This logic may need to be adjusted if using a stage other than the default prod
.
Note that in this example Version 2 of the AWS SDK for JavaScript is used.
Example Project Posted to GitHub
A code repository has been assembled and posted to GitHub that contains an example Node.js Lambda that is accessible via API Gateway. The CDK configuration is included, as well as the postdeploy
scripts detailed in this post.
Comments
No responses yet