关于 cloudformation-to-pulumi
用于将 AWS CloudFormation 模板手动转换为 Pulumi 程序并导入现有资源的技能。重点是保留逻辑 ID、默认使用 aws-native,并通过无差异预览验证导入结果。
主要功能
- 01CloudFormation 模板分析
- 02逐项手动转换资源
- 03逻辑 ID 命名要求
- 04aws-native Provider 指引
- 05自定义资源处理
- 06导入与无差异验证
适用场景
- 01将 CloudFormation 堆栈转换为 Pulumi
- 02准备将现有堆栈资源导入 Pulumi
- 03映射模板参数和内置函数
- 04检查导入基础设施是否有意外变更
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ pulumi/ agent-skills --skill cloudformation-to-pulumi安装后在 Claude Code 中输入/cloudformation-to-pulumi确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
cfn-importer.mdcloudformation-id-lookup.mduse_cases.yamlagents/openai.yaml详细介绍
重要:必须调整计划
如果你在加载此技能之前已经生成迁移计划,则必须:
- 根据此技能中的工作流和要求检查现有计划
- 找出任何差距、遗漏步骤或错误假设
- 更新并修订计划,使其符合此技能的指导
- 在继续操作之前,将调整后的计划告知用户
关键成功要求
迁移输出必须满足以下所有要求:
-
完整覆盖资源
- 每个 CloudFormation 资源都必须在 Pulumi 程序中有所体现,或在最终报告中明确说明理由。
-
将 CloudFormation 逻辑 ID 用作资源名称
- **关键:**每个 Pulumi 资源都必须使用 CloudFormation 逻辑 ID 作为其资源名称。
- 这样
cdk-importer工具才能自动查找导入 ID。 - 不要重命名资源。更改逻辑 ID 会导致自动导入失败。
-
成功部署
- 生成的 Pulumi 程序必须在结构上有效,并能够成功运行
pulumi preview(假设配置正确)。
- 生成的 Pulumi 程序必须在结构上有效,并能够成功运行
-
导入验证无差异(如果导入现有资源)
- 导入后,
pulumi preview必须不显示任何更新、替换、创建或删除操作。
- 导入后,
-
最终迁移报告
- 始终输出适用于 Pull Request 的正式迁移报告。
缺少信息时
如果用户未提供 CloudFormation 模板,则必须使用堆栈名称从 AWS 获取模板。
迁移工作流
必须严格按照以下顺序遵循此工作流:
1. 信息收集
1.1 验证 AWS 凭证(ESC)
运行 AWS 命令需要通过 Pulumi ESC 加载凭证。
- 如果用户已提供 ESC 环境,则使用该环境。
- 如果未指定 ESC 环境,继续操作前先询问用户要使用哪个 ESC 环境。
**有关 ESC 的详细信息:**使用技能 pulumi-esc。
必须向用户确认 AWS 区域。
1.2 获取 CloudFormation 模板
**如果用户提供了模板文件:**直接读取模板。
**如果用户只提供了堆栈名称:**从 AWS 获取模板:
aws cloudformation get-template \
--region <region> \
--stack-name <stack-name> \
--query 'TemplateBody' \
--output json > template.json
1.3 构建资源清单
列出堆栈中的所有资源:
aws cloudformation list-stack-resources \
--region <region> \
--stack-name <stack-name> \
--output json
这将提供:
LogicalResourceId- 将其用作 Pulumi 资源名称PhysicalResourceId- 实际的 AWS 资源 IDResourceType- CloudFormation 资源类型
1.4 分析模板结构
从模板中提取:
- 参数及其默认值
- 映射
- 条件
- 输出
- 资源依赖关系(Ref、GetAtt、DependsOn)
2. 代码转换(CloudFormation → Pulumi)
**重要:**CloudFormation 没有自动转换工具。必须手动转换每个资源。
2.1 资源命名约定(关键)
每个 Pulumi 资源都必须使用 CloudFormation 逻辑 ID 作为其名称。
// CloudFormation:
// "MyAppBucketABC123": { "Type": "AWS::S3::Bucket", ... }
// Pulumi - CORRECT:
const myAppBucket = new aws.s3.Bucket("MyAppBucketABC123", { ... });
// Pulumi - WRONG (DO NOT do this - import will fail):
const myAppBucket = new aws.s3.Bucket("my-app-bucket", { ... });
此命名约定是必需的,因为 cdk-importer 工具会根据名称匹配资源。
2.2 Provider 策略
⚠️ 关键:始终默认使用 aws-native ⚠️
- 除非有特定原因必须使用
aws,否则所有资源都使用aws-native。 - CloudFormation 类型可直接映射到 aws-native(例如,
AWS::S3::Bucket→aws-native.s3.Bucket)。 - 只有在 aws-native 不支持所需功能时,才使用
aws(经典版)。
这是使用 cdk-importer 成功导入的强制要求。 cdk-importer 通过匹配 CloudFormation 资源和 Pulumi 资源来工作,而 CloudFormation 与 aws-native 是一一对应的。使用经典 aws Provider 会导致导入失败。
2.3 CloudFormation 内置函数
将 CloudFormation 内置函数映射为 Pulumi 等效项:
| CloudFormation | Pulumi 等效项 |
|---|---|
!Ref(资源) | 资源输出(例如,bucket.id) |
!Ref(参数) | Pulumi 配置 |
!GetAtt Resource.Attr | 资源属性输出 |
!Sub "..." | pulumi.interpolate |
!Join [delim, [...]] | pulumi.interpolate 或 .apply() |
!If [cond, true, false] | 三元运算符 |
!Equals [a, b] | === 比较 |
!Select [idx, list] | 使用 .apply() 进行数组索引 |
!Split [delim, str] | .apply(v => v.split(...)) |
Fn::ImportValue | 堆栈引用或配置 |
示例:!Sub
// CloudFormation: !Sub "arn:aws:s3:::${MyBucket}/*"
// Pulumi:
const bucketArn = pulumi.interpolate`arn:aws:s3:::${myBucket.bucket}/*`;
示例:!GetAtt
// CloudFormation: !GetAtt MyFunction.Arn
// Pulumi:
const functionArn = myFunction.arn;
2.4 CloudFormation 条件
将 CloudFormation 条件转换为 TypeScript 逻辑:
// CloudFormation:
// "Conditions": {
// "CreateProdResources": { "Fn::Equals": [{ "Ref": "Environment" }, "prod"] }
// }
// Pulumi:
const config = new pulumi.Config();
const environment = config.require("environment");
const createProdResources = environment === "prod";
if (createProdResources) {
// Create production-only resources
}
2.5 CloudFormation 参数
将参数转换为 Pulumi 配置:
// CloudFormation:
// "Parameters": {
// "InstanceType": { "Type": "String", "Default": "t3.micro" }
// }
// Pulumi:
const config = new pulumi.Config();
const instanceType = config.get("instanceType") || "t3.micro";
2.6 CloudFormation 映射
将映射转换为 TypeScript 对象:
// CloudFormation:
// "Mappings": {
// "RegionMap": {
// "us-east-1": { "AMI": "ami-12345" },
// "us-west-2": { "AMI": "ami-67890" }
// }
// }
// Pulumi:
const regionMap: Record<string, { ami: string }> = {
"us-east-1": { ami: "ami-12345" },
"us-west-2": { ami: "ami-67890" },
};
const ami = regionMap[aws.config.region!].ami;
2.7 自定义资源
CloudFormation 自定义资源(AWS::CloudFormation::CustomResource 或 Custom::*)需要特殊处理:
- 识别用途:阅读 Lambda 函数代码,了解其功能
- 寻找原生替代方案:检查 Pulumi 是否提供具备相同功能的原生资源
- 如果没有替代方案:在迁移报告中注明需要手动实现
2.8 TypeScript 输出处理
aws-native 输出通常包含 undefined。避免使用 ! 非空断言。始终使用 .apply() 安全地解包:
// WRONG
functionName: lambdaFunction.functionName!,
// CORRECT
functionName: lambdaFunction.functionName.apply(name => name || ""),
3. 资源导入
转换完成后,导入现有资源,以便由 Pulumi 管理。
3.0 导入前验证(必需)
导入前,请验证代码:
- 检查 Provider 的使用情况:扫描代码,确保所有资源都使用
aws-native - 记录例外情况:必须说明使用
aws(经典)Provider 的理由 - 验证资源名称:确认所有资源都使用 CloudFormation 逻辑 ID 作为名称
3.1 使用 cdk-importer 自动导入
由于你使用了 CloudFormation 逻辑 ID 作为资源名称,因此可以使用 cdk-importer 工具自动导入资源。
有关详细的导入步骤,请参阅 cfn-importer.md。
3.2 手动导入失败的资源
对于自动导入失败的资源:
- 按照 cloudformation-id-lookup.md 查找导入 ID 格式
- 使用
pulumi import:
pulumi import <pulumi-resource-type> <logical-id> <import-id>
3.3 导入后运行预览
导入后,运行 pulumi preview。结果必须满足:
- 不得有更新
- 不得有替换
- 不得有创建
- 不得有删除
如果存在变更,请进行调查并更新程序,直到预览结果无变更。
输出格式(必需)
执行迁移时,始终生成以下内容:
- 概述(高层次描述)
- 迁移计划摘要
- Pulumi 代码输出(TypeScript;按文件整理)
- 资源映射表:
| CloudFormation 逻辑 ID | CFN 类型 | Pulumi 类型 | Provider |
|---|---|---|---|
MyAppBucketABC123 | AWS::S3::Bucket | aws-native.s3.Bucket | aws-native |
MyLambdaFunction456 | AWS::Lambda::Function | aws-native.lambda.Function | aws-native |
- 自定义资源摘要(如有)
- 最终迁移报告(可直接用于 PR)
- 后续步骤(导入说明)
详细文档
从 Pulumi 官方文档获取内容: