Last updated: Jan 27, 2024
Reading timeยท4 min
Some values in our CDK code can't get resolved at synthesis time, but rather get resolved at deployment time. These values are called Tokens.
Tokens are encoded values, most commonly, attributes of resources we define in our CDK stack that will only get resolved at deployment time.
For example, let's define a simple
Lambda function and try to access its
functionName
property in our CDK code.
const myFunction = new NodejsFunction(this, id, { runtime: lambda.Runtime.NODEJS_18_X, handler: 'main', entry: path.join(__dirname, `/../src/path/index.js`), }); console.log('function name ๐', myFunction.functionName);
If we run npx aws-cdk deploy
we get a result like ${Token[Token.123]}
.
We can see that the value of the functionName
attribute is a token at
synthesis time.
Even if we explicitly set the functionName
property on the Lambda we
wouldn't be able to access the resolved value in our code.
Let's add a Dynamodb table to our code and
explicitly set the tableName
and functionName
properties on the resources.
const myTable = new dynamodb.Table(this, 'my-table', { // ๐ explicitly setting tableName tableName: 'my-table', partitionKey: {name: 'todoId', type: dynamodb.AttributeType.NUMBER}, billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, removalPolicy: cdk.RemovalPolicy.DESTROY, }); console.log('table name ๐', myTable.tableName); const myFunction = new NodejsFunction(this, id, { // ๐ explicitly setting functionName functionName: 'my-function', runtime: lambda.Runtime.NODEJS_18_X, handler: 'main', entry: path.join(__dirname, `/../src/path/index.js`), }); console.log('function name ๐', myFunction.functionName);
If we run npx aws-cdk deploy
, we can see that we still get the unresolved
token values.
Obviously, if we hardcode the table and function names, we have access to these values in our CDK code, but trying to access the attributes still returns a Token.
Since CDK gets compiled down to CloudFormation code, all these tokens are represented as CloudFormation Ref functions.
Let's update our code to remove the explicitly set resource names and pass the DynamoDB table name as an environment variable to the lambda and inspect our CloudFormation template.
const myTable = new dynamodb.Table(this, 'my-table', { partitionKey: {name: 'todoId', type: dynamodb.AttributeType.NUMBER}, billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, removalPolicy: cdk.RemovalPolicy.DESTROY, }); const myFunction = new NodejsFunction(this, id, { runtime: lambda.Runtime.NODEJS_18_X, handler: 'main', entry: path.join(__dirname, `/../src/path/index.js`), // ๐ passing in tableName as env variable environment: { tableName: myTable.tableName, }, });
Let's synth our CDK stack and look at the produced CloudFormation template.
npx aws-cdk synth --no-staging > template.yaml
The important parts of the CloudFormation template are the following.
Resources: mytable0324D45C: Type: AWS::DynamoDB::Table mycdkstack4E08F0DD: Type: AWS::Lambda::Function Properties: Environment: Variables: tableName: Ref: mytable0324D45C
Notice how the table name environment variable points to a reference that will eventually get replaced with the real value from CloudFormation.
When we deploy our stack, we can see that the value of the environment variable is resolved and accessible in our Lambda function.
A common source of confusion with Tokens in CDK is when we try to access the
region
, accountId
or availabilityZones
properties in an environment
agnostic stack and get Token values back.
It's a common requirement to conditionally update resource types based on the account or region we are deploying to.
const app = new cdk.App(); new MyCdkStack(app, 'my-cdk-stack', { stackName: `my-cdk-stack`, // ๐ not specifying the environment // env: { // region: process.env.CDK_DEFAULT_REGION, // account: process.env.CDK_DEFAULT_ACCOUNT, // }, });
If we now try to access the region
, accountId
or availabilityZones
in our
CDK code:
export class MyCdkStack extends cdk.Stack { constructor(scope: cdk.App, id: string, props?: cdk.StackProps) { super(scope, id, props); // ๐ recommended way to get access to the properties console.log('accountId: ', cdk.Stack.of(this).account); console.log('region: ', cdk.Stack.of(this).region); console.log('availability zones: ', cdk.Stack.of(this).availabilityZones); } }
We would simply get back unresolved Token values:
The way to solve this is to uncomment the commented lines in the snippet above and explicitly set the environment for our stack. I have written an article on this - Get region and AccountID in CDK
It's a common requirement to export resource attributes as Outputs so that they can be accessed from our frontend code.
For example, to provide our frontend code access to an API's URL, we would use Outputs and write the Outputs to a JSON file, which the frontend code can import and use.
Let's see how we can export our lambda and table names and when the values get resolved.
const myTable = new dynamodb.Table(this, 'my-table', { partitionKey: {name: 'todoId', type: dynamodb.AttributeType.NUMBER}, billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, removalPolicy: cdk.RemovalPolicy.DESTROY, }); const myFunction = new NodejsFunction(this, id, { runtime: lambda.Runtime.NODEJS_18_X, handler: 'main', entry: path.join(__dirname, `/../src/path/index.js`), environment: { tableName: myTable.tableName, }, }); // ๐ export values as Outputs new cdk.CfnOutput(this, 'tableName', {value: myTable.tableName}); new cdk.CfnOutput(this, 'lambdaName', {value: myFunction.functionName});
Let's deploy our stack and see if the values are solved in the file:
npx aws-cdk deploy --outputs-file ./cdk-exports.json
If we open the cdk-exports.json
file after the deployment, the result looks as follows.
{ "my-cdk-stack": { "tableName": "my-cdk-stack-mytable0324D45C-11VORISY29H3L", "lambdaName": "my-cdk-stack-mycdkstack4E08F0DD-KVZpFjrsLC3F" } }
The concept is the same, our CloudFormation template has specified references to these resources, which will eventually be replaced with the real values at deployment time.
Resources: mytable0324D45C: Type: AWS::DynamoDB::Table mycdkstack4E08F0DD: Type: AWS::Lambda::Function # ๐ Outputs reference the resources Outputs: tableName: Value: Ref: mytable0324D45C lambdaName: Value: Ref: mycdkstack4E08F0DD
Tokens are placeholder values that get replaced with the real value at deployment time by CloudFormation.
The most common source of confusion is trying to access the accountId
,
region
or availabilityZones
properties and getting Token values, because we
have not set the environment of the stack explicitly -
How to get accountId and region in AWS CDK.
You can learn more about the related topics by checking out the following tutorials: