这是 AWS CDK v2 开发者指南。旧版 CDK v1 于 2022 年 6 月 1 日进入维护阶段,并于 2023 年 6 月 1 日终止支持。
本文属于机器翻译版本。若本译文内容与英语原文存在差异,则一律以英文原文为准。
在中 AWS Cloud Development Kit (AWS CDK),令牌是定义构造或合成堆栈时未知值的占位符。创建实际基础设施时,将在部署时完全解析这些值。在开发 AWS CDK 应用程序时,您将使用令牌来管理整个应用程序中的这些值。
令牌示例
以下是 CDK 堆栈的示例,该堆栈定义了 Amazon Simple Storage Service(Amazon S3)存储桶的构造。由于我们尚不清楚存储桶的名称,bucketName
的值将存储为令牌:
import * as cdk from 'aws-cdk-lib';
import { Construct } from 'constructs';
import * as s3 from 'aws-cdk-lib/aws-s3';
export class CdkDemoAppStack extends cdk.Stack {
constructor(scope: Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);
// Define an S3 bucket
const myBucket = new s3.Bucket(this, 'myBucket');
// Store value of the S3 bucket name
const myBucketName = myBucket.bucketName;
// Print the current value for the S3 bucket name at synthesis
console.log("myBucketName: " + bucketName);
}
}
当我们运行 cdk synth
来合成堆栈时,myBucketName
的值将以 ${Token[TOKEN.
的令牌格式显示。这种令牌格式是对令牌进行 AWS CDK 编码的结果。在此示例中,令牌被编码为字符串:1234
]}
$
cdk synth --quiet
myBucketName: ${Token[TOKEN.21
]}
由于在合成时尚不清楚存储桶名称的值,令牌呈现为 myBucket
。我们的 AWS CloudFormation 模板使用<unique-hash>
Ref
内部函数来引用其值,该值将在部署时知道:
Resources:
myBucket5AF9C99B
:
# ...
Outputs:
bucketNameOutput:
Description: The name of the S3 bucket
Value:
Ref: myBucket5AF9C99B
有关如何生成唯一哈希的更多信息,请参阅 IDs 在您的 AWS CloudFormation 模板中逻辑生成。
传递令牌
令牌可以进行传递,就像其所代表的实际值一样。以下是将存储桶名称的令牌传递给 AWS Lambda 函数构造的示例:
import * as cdk from 'aws-cdk-lib';
import { Construct } from 'constructs';
import * as s3 from 'aws-cdk-lib/aws-s3';
import * as lambda from 'aws-cdk-lib/aws-lambda';
export class CdkDemoAppStack extends cdk.Stack {
constructor(scope: Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);
// Define an S3 bucket
const myBucket = new s3.Bucket(this, 'myBucket');
// ...
// Define a Lambda function
const myFunction = new lambda.Function(this, "myFunction", {
runtime: lambda.Runtime.NODEJS_20_X,
handler: "index.handler",
code: lambda.Code.fromInline(`
exports.handler = async function(event) {
return {
statusCode: 200,
body: JSON.stringify('Hello World!'),
};
};
`),
functionName: myBucketName + "Function", // Pass token for the S3 bucket name
environment: {
BUCKET_NAME: myBucketName, // Pass token for the S3 bucket name
}
});
}
}
当我们合成模板时,Ref
和 Fn::Join
内置函数用于指定值,这些值将在部署时为已知:
Resources:
myBucket5AF9C99B
:
Type: AWS::S3::Bucket
# ...
myFunction884E1557
:
Type: AWS::Lambda::Function
Properties:
# ...
Environment:
Variables:
BUCKET_NAME:
Ref: myBucket5AF9C99B
FunctionName:
Fn::Join:
- ""
- - Ref: myBucket5AF9C99B
- Function
# ...
令牌编码的工作原理
令牌是实现 IResolvable
接口的对象,该接口包含单个 resolve
方法。在合成过程中, AWS CDK 调用此方法以生成 CloudFormation 模板中标记的最终值。
注意
很少会直接使用 IResolvable
接口。您很可能只会看到字符串编码版本的令牌。
令牌编码类型
令牌参与合成过程以生成任何类型的任意值。其他函数通常只接受基本类型的参数,例如 string
或 number
。要在这些情况下使用令牌,可以使用 cdk.Token
类上的静态方法将其编码为三种类型之一。
-
Token.asString
,用于生成字符串编码(或在令牌对象上调用.toString()
)。 -
Token.asList
,用于生成列表编码。 -
Token.asNumber
,用于生成数字编码。
这些类型都采用了任意值(可以是 IResolvable
),然后将其编码为指定类型的原始值。
重要
由于之前的任何一种类型都可能是编码的令牌,在解析或尝试读取其内容时要小心。例如,如果您尝试解析字符串以从中提取值,而该字符串是编码的令牌,则解析将失败。同样,如果您尝试查询数组的长度或使用数字执行数学运算,则必须首先验证其不是编码的令牌。
如何在应用程序中检查令牌
要检查值中是否有未解析的令牌,请调用 Token.isUnresolved
(Python:is_unresolved
)方法。以下是检查 Amazon S3 存储桶名称的值是否为令牌的示例。如果不是令牌,则我们会验证存储桶名称的长度:
// ...
export class CdkDemoAppStack extends cdk.Stack {
constructor(scope: Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);
// Define an S3 bucket
const myBucket = new s3.Bucket(this, 'myBucket');
// ...
// Check if bucket name is a token. If not, check if length is less than 10 characters
if (cdk.Token.isUnresolved(myBucketName)) {
console.log("Token identified.");
} else if (!cdk.Token.isUnresolved(myBucketName) && myBucketName.length > 10) {
throw new Error('Maximum length for name is 10 characters.');
};
// ...
当我们运行 cdk synth
时,myBucketName
会被标识为令牌:
$
cdk synth --quiet
Token identified.
注意
您可以使用令牌编码来转义类型系统。例如,您可以对在合成时生成数字值的令牌进行字符串编码。如果您使用这些函数,则您有责任确保模板在合成后解析为可用状态。
使用字符串编码的令牌
字符串编码的令牌如下所示。
${TOKEN[Bucket.Name.1234]}
这些令牌可以像常规字符串一样进行传递,并且可以连接起来,如以下示例所示。
const functionName = bucket.bucketName + 'Function';
如果您的语言支持,您也可以使用字符串插值,如以下示例所示。
const functionName = `${bucket.bucketName}Function`;
避免以其他方式操作字符串。例如,取一个字符串的子字符串可能会破坏该字符串令牌。
使用列表编码的令牌
列表编码的令牌如下所示:
["#{TOKEN[Stack.NotificationArns.1234]}"]
处理这些列表的唯一安全方法是将其直接传递给其他构造。不能连接字符串列表形式的令牌,也不能从令牌中获取元素。操作它们的唯一安全方法是使用 Fn.select 之类的 AWS CloudFormation 内置函数。
使用数字编码的令牌
数字编码的令牌是一组微小的负浮点数,如下所示。
-1.8881545897087626e+289
与使用列表令牌一样,您无法修改数字值,因为这样做可能会破坏数字令牌。
以下是包含编码为数字的令牌的构造示例:
import { Stack, Duration, StackProps } from 'aws-cdk-lib';
import { Construct } from 'constructs';
import * as rds from 'aws-cdk-lib/aws-rds';
import * as ec2 from 'aws-cdk-lib/aws-ec2';
export class CdkDemoAppStack extends Stack {
constructor(scope: Construct, id: string, props?: StackProps) {
super(scope, id, props);
// Define a new VPC
const vpc = new ec2.Vpc(this, 'MyVpc', {
maxAzs: 3, // Maximum number of availability zones to use
});
// Define an RDS database cluster
const dbCluster = new rds.DatabaseCluster(this, 'MyRDSCluster', {
engine: rds.DatabaseClusterEngine.AURORA,
instanceProps: {
vpc,
},
});
// Get the port token (this is a token encoded as a number)
const portToken = dbCluster.clusterEndpoint.port;
// Print the value for our token at synthesis
console.log("portToken: " + portToken);
}
}
当我们运行 cdk synth
时,portToken
的值显示为数字编码的令牌:
$
cdk synth --quiet
portToken: -1.8881545897087968e+289
传递数字编码的令牌
当您将数字编码的令牌传递给其他构造时,可能有必要先将其转换为字符串。例如,如果您想要将数字编码字符串的值用作连接字符串的一部分,则对其进行转换有助于提高可读性。
在以下示例中,portToken
是一个数字编码的令牌,我们希望将其作为 connectionString
的一部分传递给 Lambda 函数:
import { Stack, Duration, CfnOutput, StackProps } from 'aws-cdk-lib';
// ...
import * as lambda from 'aws-cdk-lib/aws-lambda';
export class CdkDemoAppStack extends Stack {
constructor(scope: Construct, id: string, props?: StackProps) {
super(scope, id, props);
// Define a new VPC
// ...
// Define an RDS database cluster
// ...
// Get the port token (this is a token encoded as a number)
const portToken = dbCluster.clusterEndpoint.port;
// ...
// Example connection string with the port token as a number
const connectionString = `jdbc:mysql://mydb.cluster.amazonaws.com:${portToken}/mydatabase`;
// Use the connection string as an environment variable in a Lambda function
const myFunction = new lambda.Function(this, 'MyLambdaFunction', {
runtime: lambda.Runtime.NODEJS_20_X,
handler: 'index.handler',
code: lambda.Code.fromInline(`
exports.handler = async function(event) {
return {
statusCode: 200,
body: JSON.stringify('Hello World!'),
};
};
`),
environment: {
DATABASE_CONNECTION_STRING: connectionString, // Using the port token as part of the string
},
});
// Output the value of our connection string at synthesis
console.log("connectionString: " + connectionString);
// Output the connection string
new CfnOutput(this, 'ConnectionString', {
value: connectionString,
});
}
}
如果我们将此值传递给 connectionString
,则因为是数字编码的字符串,我们运行 cdk synth
时的输出值可能会令人困惑:
$
cdk synth --quiet
connectionString: jdbc:mysql://mydb.cluster.amazonaws.com:-1.888154589708796e+289/mydatabase
要将数字编码的令牌转换为字符串,请使用 cdk.Tokenization.stringifyNumber(
。在以下示例中,在定义连接字符串之前,我们将数字编码的令牌转换为了字符串:token
)
import { Stack, Duration, Tokenization, CfnOutput, StackProps } from 'aws-cdk-lib';
// ...
export class CdkDemoAppStack extends Stack {
constructor(scope: Construct, id: string, props?: StackProps) {
super(scope, id, props);
// Define a new VPC
// ...
// Define an RDS database cluster
// ...
// Get the port token (this is a token encoded as a number)
const portToken = dbCluster.clusterEndpoint.port;
// ...
// Convert the encoded number to an encoded string for use in the connection string
const portAsString = Tokenization.stringifyNumber(portToken);
// Example connection string with the port token as a string
const connectionString = `jdbc:mysql://mydb.cluster.amazonaws.com:${portAsString}/mydatabase`;
// Use the connection string as an environment variable in a Lambda function
const myFunction = new lambda.Function(this, 'MyLambdaFunction', {
// ...
environment: {
DATABASE_CONNECTION_STRING: connectionString, // Using the port token as part of the string
},
});
// Output the value of our connection string at synthesis
console.log("connectionString: " + connectionString);
// Output the connection string
new CfnOutput(this, 'ConnectionString', {
value: connectionString,
});
}
}
当我们运行 cdk synth
时,连接字符串的值会以更清晰、更明确的格式表示:
$
cdk synth --quiet
connectionString: jdbc:mysql://mydb.cluster.amazonaws.com:${Token[TOKEN.242]}/mydatabase
惰性值
除了表示部署时间值(例如 AWS CloudFormation 参数)外,令牌还通常用于表示合成时的延迟值。这些值将在合成完成之前确定为最终值,但不会在构造值时确定。使用令牌将文字字符串或数字值传递给另一个构造,而合成时的实际值可能取决于尚未进行的某些计算。
您可以使用 Lazy
类上的静态方法(例如 Lazy.string
和 Lazy.number
)来构造表示合成时惰性值的令牌。这些方法接受一个对象,该对象的 produce
属性是一个接受上下文参数并在调用时返回最终值的函数。
以下示例创建了一个自动扩缩组,其容量是在创建后确定的。
let actualValue: number;
new AutoScalingGroup(this, 'Group', {
desiredCapacity: Lazy.numberValue({
produce(context) {
return actualValue;
}
})
});
// At some later point
actualValue = 10;
转换为 JSON
有时您想要生成一个包含任意数据的 JSON 字符串,但您可能不知道这些数据是否包含令牌。要正确地对任何数据结构进行 JSON 编码(无论其是否包含令牌),请使用方法 stack.toJsonString
,如以下示例所示。
const stack = Stack.of(this);
const str = stack.toJsonString({
value: bucket.bucketName
});