cloudformation-to-pulumi by pulumi/agent-skills
npx skills add https://github.com/pulumi/agent-skills --skill cloudformation-to-pulumi如果您在加载此技能之前已经生成了迁移计划,您必须:
迁移输出必须满足以下所有条件:
完整的资源覆盖
使用 CloudFormation 逻辑 ID 作为资源名称
cdk-importer 工具能够自动找到导入 ID。成功部署
pulumi preview(假设配置正确)。零差异导入验证(如果导入现有资源)
pulumi preview 必须显示没有更新、替换、创建或删除操作。广告位招租
在这里展示您的产品或服务
触达数万 AI 开发者,精准高效
最终迁移报告
如果用户没有提供 CloudFormation 模板,您必须使用堆栈名称从 AWS 获取它。
请严格按照以下顺序执行此工作流:
运行 AWS 命令需要通过 Pulumi ESC 加载的凭证。
有关 ESC 的详细信息: 使用技能 pulumi-esc。
您必须与用户确认 AWS 区域。
如果用户提供了模板文件:直接读取模板。
如果用户只提供了堆栈名称:从 AWS 获取模板:
aws cloudformation get-template \
--region <region> \
--stack-name <stack-name> \
--query 'TemplateBody' \
--output json > template.json
列出堆栈中的所有资源:
aws cloudformation list-stack-resources \
--region <region> \
--stack-name <stack-name> \
--output json
这提供了:
LogicalResourceId - 将其用作 Pulumi 资源名称PhysicalResourceId - 实际的 AWS 资源 IDResourceType - CloudFormation 资源类型从模板中提取:
重要: 没有用于 CloudFormation 的自动转换工具。您必须手动转换每个资源。
每个 Pulumi 资源必须使用 CloudFormation 逻辑 ID 作为其名称。
// CloudFormation:
// "MyAppBucketABC123": { "Type": "AWS::S3::Bucket", ... }
// Pulumi - 正确:
const myAppBucket = new aws.s3.Bucket("MyAppBucketABC123", { ... });
// Pulumi - 错误(不要这样做 - 导入将失败):
const myAppBucket = new aws.s3.Bucket("my-app-bucket", { ... });
此命名约定是必需的,因为 cdk-importer 工具通过名称匹配资源。
⚠️ 关键:默认始终使用 aws-native ⚠️
aws-native,除非有特定理由使用 aws。AWS::S3::Bucket → aws-native.s3.Bucket)。aws(经典)。这对于使用 cdk-importer 成功导入是强制性的。 cdk-importer 通过将 CloudFormation 资源与 Pulumi 资源进行匹配来工作,而 CloudFormation 与 aws-native 是 1:1 映射。使用经典的 aws 提供者将导致导入失败。
将 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 | 堆栈引用或配置 |
// CloudFormation: !Sub "arn:aws:s3:::${MyBucket}/*"
// Pulumi:
const bucketArn = pulumi.interpolate`arn:aws:s3:::${myBucket.bucket}/*`;
// CloudFormation: !GetAtt MyFunction.Arn
// Pulumi:
const functionArn = myFunction.arn;
将 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) {
// 创建仅用于生产的资源
}
将参数转换为 Pulumi 配置:
// CloudFormation:
// "Parameters": {
// "InstanceType": { "Type": "String", "Default": "t3.micro" }
// }
// Pulumi:
const config = new pulumi.Config();
const instanceType = config.get("instanceType") || "t3.micro";
将映射转换为 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;
CloudFormation 自定义资源(AWS::CloudFormation::CustomResource 或 Custom::*)需要特殊处理:
aws-native 输出通常包含 undefined。避免使用 ! 非空断言。始终使用 .apply() 安全地解包:
// 错误
functionName: lambdaFunction.functionName!,
// 正确
functionName: lambdaFunction.functionName.apply(name => name || ""),
转换后,导入现有资源由 Pulumi 管理。
在继续导入之前,验证您的代码:
aws-nativeaws(经典)提供者的情况都必须有正当理由因为您使用了 CloudFormation 逻辑 ID 作为资源名称,所以可以使用 cdk-importer 工具自动导入资源。
请按照 cfn-importer.md 获取详细的导入过程。
对于自动导入失败的资源:
pulumi import:pulumi import <pulumi-resource-type> <logical-id> <import-id>
导入后,运行 pulumi preview。必须满足:
如果有更改,请调查并更新程序,直到预览干净。
执行迁移时,始终生成:
| CloudFormation 逻辑 ID | CFN 类型 | Pulumi 类型 | 提供者 |
|---|---|---|---|
MyAppBucketABC123 | AWS::S3::Bucket | aws-native.s3.Bucket | aws-native |
MyLambdaFunction456 | AWS::Lambda::Function | aws-native.lambda.Function | aws-native |
从官方 Pulumi 文档获取内容:
每周安装次数
238
仓库
GitHub 星标数
30
首次出现
Jan 28, 2026
安全审计
安装于
opencode210
codex208
github-copilot207
gemini-cli202
amp201
kimi-cli200
If you have already generated a migration plan before loading this skill, you MUST:
The migration output MUST meet all of the following:
Complete Resource Coverage
CloudFormation Logical ID as Resource Name
cdk-importer tool to automatically find import IDs.Successful Deployment
pulumi preview (assuming proper config).Zero-Diff Import Validation (if importing existing resources)
pulumi preview must show NO updates, replaces, creates, or deletes.Final Migration Report
If the user has not provided a CloudFormation template, you MUST fetch it from AWS using the stack name.
Follow this workflow exactly and in this order:
Running AWS commands requires credentials loaded via Pulumi ESC.
For detailed ESC information: Use skill pulumi-esc.
You MUST confirm the AWS region with the user.
If user provided a template file : Read the template directly.
If user only provided a stack name : Fetch the template from AWS:
aws cloudformation get-template \
--region <region> \
--stack-name <stack-name> \
--query 'TemplateBody' \
--output json > template.json
List all resources in the stack:
aws cloudformation list-stack-resources \
--region <region> \
--stack-name <stack-name> \
--output json
This provides:
LogicalResourceId - Use this as the Pulumi resource namePhysicalResourceId - The actual AWS resource IDResourceType - The CloudFormation resource typeExtract from the template:
IMPORTANT: There is NO automated conversion tool for CloudFormation. You MUST convert each resource manually.
Every Pulumi resource MUST use the CloudFormation Logical ID as its name.
// 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", { ... });
This naming convention is REQUIRED because the cdk-importer tool matches resources by name.
⚠️ CRITICAL: ALWAYS USE aws-native BY DEFAULT ⚠️
aws-native for all resources unless there's a specific reason to use aws.AWS::S3::Bucket → aws-native.s3.Bucket).aws (classic) when aws-native doesn't support a required feature.This is MANDATORY for successful imports with cdk-importer. The cdk-importer works by matching CloudFormation resources to Pulumi resources, and CloudFormation maps 1:1 to aws-native. Using the classic aws provider will cause import failures.
Map CloudFormation intrinsic functions to Pulumi equivalents:
| CloudFormation | Pulumi Equivalent |
|---|---|
!Ref (resource) | Resource output (e.g., bucket.id) |
!Ref (parameter) | Pulumi config |
!GetAtt Resource.Attr | Resource property output |
!Sub "..." | pulumi.interpolate |
!Join [delim, [...]] |
// CloudFormation: !Sub "arn:aws:s3:::${MyBucket}/*"
// Pulumi:
const bucketArn = pulumi.interpolate`arn:aws:s3:::${myBucket.bucket}/*`;
// CloudFormation: !GetAtt MyFunction.Arn
// Pulumi:
const functionArn = myFunction.arn;
Convert CloudFormation conditions to TypeScript logic:
// 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
}
Convert parameters to Pulumi config:
// CloudFormation:
// "Parameters": {
// "InstanceType": { "Type": "String", "Default": "t3.micro" }
// }
// Pulumi:
const config = new pulumi.Config();
const instanceType = config.get("instanceType") || "t3.micro";
Convert mappings to TypeScript objects:
// 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;
CloudFormation Custom Resources (AWS::CloudFormation::CustomResource or Custom::*) require special handling:
aws-native outputs often include undefined. Avoid ! non-null assertions. Always safely unwrap with .apply():
// WRONG
functionName: lambdaFunction.functionName!,
// CORRECT
functionName: lambdaFunction.functionName.apply(name => name || ""),
After conversion, import existing resources to be managed by Pulumi.
Before proceeding with import, verify your code:
aws-nativeaws (classic) provider must be justifiedBecause you used CloudFormation Logical IDs as resource names, you can use the cdk-importer tool to automatically import resources.
Follow cfn-importer.md for detailed import procedures.
For resources that fail automatic import:
pulumi import:pulumi import <pulumi-resource-type> <logical-id> <import-id>
After import, run pulumi preview. There must be:
If there are changes, investigate and update the program until preview is clean.
When performing a migration, always produce:
| CloudFormation Logical ID | CFN Type | Pulumi Type | Provider |
|---|---|---|---|
MyAppBucketABC123 | AWS::S3::Bucket | aws-native.s3.Bucket | aws-native |
MyLambdaFunction456 | AWS::Lambda::Function | aws-native.lambda.Function | aws-native |
Fetch content from official Pulumi documentation:
Weekly Installs
238
Repository
GitHub Stars
30
First Seen
Jan 28, 2026
Security Audits
Gen Agent Trust HubWarnSocketPassSnykPass
Installed on
opencode210
codex208
github-copilot207
gemini-cli202
amp201
kimi-cli200
Azure Data Explorer (Kusto) 查询技能:KQL数据分析、日志遥测与时间序列处理
102,600 周安装
pulumi.interpolate or .apply() |
!If [cond, true, false] | Ternary operator |
!Equals [a, b] | === comparison |
!Select [idx, list] | Array indexing with .apply() |
!Split [delim, str] | .apply(v => v.split(...)) |
Fn::ImportValue | Stack references or config |