pulumi-migrate-from-discovered-stack
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseScope: this skill only applies to stacks Pulumi Cloud's Discovery feature has already scanned and exposed through the discovered-stacks API (, see below). It is not for migrating an arbitrary CloudFormation/ARM template or account that Discovery hasn't scanned yet — if no discovered stack exists for the source, this skill has nothing to read and does not apply.
GET .../discovered-stacks/{projectName}/{stackName}/resourcesDo not loadorcloudformation-to-pulumialongside this skill. Those two skills prescribe a template-first workflow (mechanical translation → import) and mandatepulumi-arm-to-pulumifor AWS. This skill is cloud-state-first (import from discovered state → optional refactor against the template later) and defaults toaws-nativeclassic /aws. The useful reference material from those two skills has been curated intoazure-nativeandcloudformation.mdin this folder.arm.md
适用范围:此技能仅适用于Pulumi Cloud的Discovery功能已扫描并通过discovered-stacks API暴露的栈(,详见下文)。它不适用于迁移任意CloudFormation/ARM模板或Discovery尚未扫描的账户——如果源端没有对应的已发现栈,此技能无数据可读取,不适用。
GET .../discovered-stacks/{projectName}/{stackName}/resources请勿将或cloudformation-to-pulumi与此技能一同加载。那两个技能采用模板优先的工作流(机械翻译 → 导入),且强制要求AWS使用pulumi-arm-to-pulumi。此技能采用云状态优先的工作流(从已发现状态导入 → 之后可按需对照模板重构),默认使用aws-native经典版 /aws。这两个技能中的有用参考资料已被整理到本文件夹的azure-native和cloudformation.md中。arm.md
Plan adjustment
计划调整
If you already generated a migration plan before loading this skill, review it against the workflow below and update it — the phases here supersede any earlier plan. Communicate the adjusted plan to the user before proceeding.
如果在加载此技能前你已生成迁移计划,请对照以下工作流进行审查并更新——此处的阶段将取代之前的任何计划。在继续操作前,需将调整后的计划告知用户。
Success criteria
成功标准
A migration is complete when:
- Complete resource coverage — every discovered resource is imported OR has an annotation explaining why not.
- Zero-diff — shows no changes. This proves the code matches the cloud state exactly.
pulumi preview - Progress tracked via the API — use and migration annotations so progress is visible in Pulumi Cloud, not just in agent memory.
compareTo - PR as the output — a pull request with the migrated code and a migration report.
迁移完成的标志:
- 完整资源覆盖——每个已发现的资源要么已被导入,要么有注释说明未导入的原因。
- 零差异——显示无任何变更。这证明代码与云状态完全匹配。
pulumi preview - 通过API跟踪进度——使用和迁移注释,使进度在Pulumi Cloud中可见,而不仅仅保存在Agent内存中。
compareTo - 输出为PR——包含迁移代码和迁移报告的拉取请求。
THE DISCOVERED-STACKS API
DISCOVERED-STACKS API
Fetching resources
获取资源
GET /api/preview/insights/{orgName}/discovered-stacks/{projectName}/{stackName}/resources?compareTo={targetProject}/{targetStack}Always include if the target Pulumi stack exists (it may already have state from a previous migration attempt). Returns a list of objects. The JSON paths below are exact — verify before consuming:
compareToDiscoveredResourceInfo- — top-level: logical name (CF Logical ID / ARM resource name). Use this as the Pulumi resource name.
name - — top-level: native cloud type (e.g.
originType,AWS::S3::Bucket)Microsoft.Storage/storageAccounts - — top-level: mapped Pulumi type token (e.g.
providerType).aws:s3/bucket:Bucketif unmapped.null - — physical cloud ID for
resource.inputs.providerId.pulumi import - — the URN to use as
resource.urnin annotation requests (copy verbatim).resourceUrn - — top-level: one of the statuses below.
migrationStatus - — top-level: user/agent annotation if one exists (see Annotations below).
annotation
The object also carries raw cloud-provider data:
resource.inputsFor CloudFormation — :
resource.inputs.cloudFormation- — the original CF physical ID
physicalResourceId - — the CF type (e.g.
resourceType)AWS::IAM::Role - — e.g.
resourceStatus,CREATE_COMPLETEDELETE_COMPLETE - —
driftStatus,NOT_CHECKED,IN_SYNCDRIFTED
For CDK-synthesized CF stacks, is also present — see for how to use it.
inputs.cdkPathcloudformation.md §5For ARM — . ARM resources are grouped by resource group, not by
deployment, so a resource with no deployment currently backing it (its deployment history
aged out of Azure's retention window, or it was created outside any tracked deployment) is
still returned — just as a placeholder, with empty and a
different shape:
resource.inputs.armresource.inputs.deploymentNamearm- Deployment-backed (non-empty) —
resource.inputs.deploymentNameis a deployment-operation object:arm/properties.targetResource.id/.resourceType,.resourceName(e.g.properties.provisioningState).Succeeded - Placeholder (empty) —
resource.inputs.deploymentNameis the raw Azure generic-resource object instead: top-levelarm,id,name,type,location. Notags— don't look for it.properties.targetResource
Either way, prefer the top-level for the import ID (see
above) rather than reaching into — it's already normalized across both shapes.
resource.inputs.providerIdarmGET /api/preview/insights/{orgName}/discovered-stacks/{projectName}/{stackName}/resources?compareTo={targetProject}/{targetStack}如果目标Pulumi栈已存在(可能已有之前迁移尝试的状态),请始终包含参数。返回对象列表。以下JSON路径为精确路径——使用前请验证:
compareToDiscoveredResourceInfo- ——顶层字段:逻辑名称(CloudFormation逻辑ID / ARM资源名称)。请将其用作Pulumi资源名称。
name - ——顶层字段:原生云类型(例如
originType、AWS::S3::Bucket)Microsoft.Storage/storageAccounts - ——顶层字段:映射后的Pulumi类型令牌(例如
providerType)。若未映射则为aws:s3/bucket:Bucket。null - ——用于
resource.inputs.providerId的物理云ID。pulumi import - ——在注释请求中用作
resource.urn的URN(需完全复制)。resourceUrn - ——顶层字段:以下状态之一。
migrationStatus - ——顶层字段:若存在用户/Agent注释则显示(详见下文注释部分)。
annotation
resource.inputs对于CloudFormation——:
resource.inputs.cloudFormation- ——原始CloudFormation物理ID
physicalResourceId - ——CloudFormation类型(例如
resourceType)AWS::IAM::Role - ——例如
resourceStatus、CREATE_COMPLETEDELETE_COMPLETE - ——
driftStatus、NOT_CHECKED、IN_SYNCDRIFTED
对于CDK合成的CloudFormation栈,还存在——请查看了解其用法。
inputs.cdkPathcloudformation.md §5对于ARM——。ARM资源按资源组分组,而非按部署分组,因此即使没有部署支持的资源(其部署历史已超出Azure的保留窗口,或在任何跟踪部署之外创建)仍会被返回——仅作为占位符,为空,且结构不同:
resource.inputs.armresource.inputs.deploymentNamearm- 部署支持的(非空)——
resource.inputs.deploymentName是部署操作对象:arm/properties.targetResource.id/.resourceType、.resourceName(例如properties.provisioningState)。Succeeded - 占位符(为空)——
resource.inputs.deploymentName是原始Azure通用资源对象:顶层arm、id、name、type、location。不存在tags——无需查找。properties.targetResource
无论哪种情况,优先使用顶层的作为导入ID(如上所述),而非从中提取——它已针对两种结构进行了标准化处理。
resource.inputs.providerIdarmMigration statuses
迁移状态
Statuses are PascalCase. First match wins:
- — the resource was found in the
MigratedPulumi stack. Already under Pulumi management; skip.compareTo - —
ReadyandproviderTypeare set and the scanner confirmed the resource exists. Import withproviderId(NEVER withoutpulumi import <providerType> <name> <providerId> --generate-code --out <file>.ts— see Phase 4).--generate-code --out - —
NotFoundandproviderTypeare set, but the scanner could not confirm the resource's current state. May be deleted, mapping may be imperfect, or scanner hit a gap. Verify before importing.providerId - — container or wrapper types (
NotApplicable,AWS::CloudFormation::Stack,Microsoft.Resources/deployments,Microsoft.Resources/resourceGroups) are not individually migratable. Skip silently.pulumi:providers:* - —
NoMatchisproviderType. No mapping found. Common examples:null- CF Custom Resources (e.g. ) — no direct Pulumi equivalent.
Custom::VpcRestrictDefaultSG - Inline policies — modeled as an inline property of
AWS::IAM::Policy. Once the parent Role is migrated, annotate the policy as migrated.aws:iam/role:Role
- CF Custom Resources (e.g.
- — exists in the
PulumiOnlystack with no discovered counterpart, or those created to migrate NoMatch resources. Surface to user.compareTo
Resources with should be treated as resolved per the override, even if the computed status disagrees.
annotation.statusOverride状态采用大驼峰命名法。优先匹配第一个符合条件的状态:
- ——资源在
Migrated指定的Pulumi栈中已存在。已纳入Pulumi管理;跳过。compareTo - ——
Ready和providerType已设置,且扫描器确认资源存在。使用providerId进行导入(必须包含pulumi import <providerType> <name> <providerId> --generate-code --out <file>.ts参数——详见第4阶段)。--generate-code --out - ——
NotFound和providerType已设置,但扫描器无法确认资源的当前状态。可能已被删除、映射不完善或扫描器存在遗漏。导入前请验证。providerId - ——容器或包装类型(
NotApplicable、AWS::CloudFormation::Stack、Microsoft.Resources/deployments、Microsoft.Resources/resourceGroups)无法单独迁移。直接跳过。pulumi:providers:* - ——
NoMatch为providerType。未找到映射。常见示例:null- CloudFormation自定义资源(例如)——无直接对应的Pulumi等效资源。
Custom::VpcRestrictDefaultSG - 内联策略——被建模为
AWS::IAM::Policy的内联属性。父Role迁移完成后,将策略注释为已迁移。aws:iam/role:Role
- CloudFormation自定义资源(例如
- ——存在于
PulumiOnly栈中,但无对应的已发现资源,或为迁移NoMatch资源而创建的资源。需告知用户。compareTo
带有的资源应按照覆盖状态处理,即使计算得出的状态与之不符。
annotation.statusOverrideAnnotations
注释
PUT /api/preview/insights/{orgName}/discovered-stacks/{projectName}/{stackName}/migration
DELETE /api/preview/insights/{orgName}/discovered-stacks/{projectName}/{stackName}/migration?resourceUrn={urn}PUT body:
json
{
"resourceUrn": "<copy verbatim from resource.urn in the list response>",
"note": "explanation of what happened",
"statusOverride": "Migrated" | "",
"linkedResourceUrn": "<optional: URN of the Pulumi resource paired 1:1 with this origin>"
}Use the Pulumi Cloud API for all annotation requests. At least one of or must be non-empty. Omit entirely (do not send ) when updating only the note. The DELETE endpoint clears both the note and any .
notestatusOverridestatusOverridenullstatusOverrideUse annotations to:
- Flag deleted resources as with a note explaining they no longer exist
Migrated - Flag inline/child resources covered by a parent's migration as with a note naming the parent — do not use
Migratedhere (it's a 1:many relationship and the UI only merges 1:1 pairs)linkedResourceUrn - Bridge a or
NotFoundorigin to itsNoMatchcounterpart after a corrected mapping (PulumiOnly+statusOverride=Migrated→ the PulumiOnly URN). This is the only valid use oflinkedResourceUrn.linkedResourceUrn - Leave notes explaining blockers or manual steps taken
- Track decisions for resources the automatic classifier can't resolve
The annotation endpoint is the shared place to track migration progress — always read existing annotations before acting on a resource, and respect overrides left by the user.
The field is user-authored context. Treat it as a high-priority instruction about that specific resource. Common uses: naming preferences, import ID hints, resources to skip, or special handling instructions. Notes reach the agent through two channels:
note-
Starting prompt — when the user kicks off a migration task, any note on a selected resource is appended to that resource's line:
- aws:s3/bucket:Bucket "my-bucket" (provider ID: my-bucket-prod) — note: use logical name "appBucket" in codeRead these before calling the API. -
API response — thefield on each
annotation.notewhen you callDiscoveredResourceInfo. TheGET .../resourcesobject is omitted entirely when no annotation has been set.annotation
PUT /api/preview/insights/{orgName}/discovered-stacks/{projectName}/{stackName}/migration
DELETE /api/preview/insights/{orgName}/discovered-stacks/{projectName}/{stackName}/migration?resourceUrn={urn}PUT请求体:
json
{
"resourceUrn": "<从列表响应的resource.urn中完全复制>",
"note": "操作说明",
"statusOverride": "Migrated" | "",
"linkedResourceUrn": "<可选:与此源资源一对一配对的Pulumi资源URN>"
}所有注释请求均使用Pulumi Cloud API。或至少有一个不能为空。仅更新注释时,请完全省略(不要发送)。DELETE端点会清除注释和任何。
notestatusOverridestatusOverridenullstatusOverride使用注释可实现以下目的:
- 将已删除的资源标记为,并在注释中说明其已不存在
Migrated - 将父资源迁移时涵盖的内联/子资源标记为,并在注释中指明父资源——此处请勿使用
Migrated(这是一对多关系,UI仅合并一对一配对)linkedResourceUrn - 在修正映射后,将或
NotFound源资源与其NoMatch对应资源关联(PulumiOnly+statusOverride=Migrated→ PulumiOnly的URN)。这是linkedResourceUrn的唯一有效用法。linkedResourceUrn - 留下注释说明阻塞问题或已执行的手动步骤
- 跟踪自动分类器无法解决的资源决策
注释端点是跟踪迁移进度的共享位置——操作资源前请始终读取现有注释,并尊重用户留下的覆盖设置。
note-
初始提示——当用户启动迁移任务时,所选资源的任何注释都会附加到该资源的行中:
- aws:s3/bucket:Bucket "my-bucket" (provider ID: my-bucket-prod) — note: use logical name "appBucket" in code调用API前请读取这些信息。 -
API响应——调用时,每个
GET .../resources的DiscoveredResourceInfo字段。未设置注释时,annotation.note对象会被完全省略。annotation
MIGRATION WORKFLOW
迁移工作流
Phase 0 — Preconditions and scoping
第0阶段——前置条件与范围界定
Before any tool call, gather and confirm all of the following. If anything is missing, ask — don't proceed with a guess.
Source (the discovered stack):
- Org name (e.g. )
pulumi_local - Discovered project name (e.g. )
AcmeCdkExampleStack - Discovered stack name (e.g. ) — this is the scanner-generated name, usually encoding account + region + CF/ARM stack name.
dev-sandbox-disc_us-west-2__Dev - Region (confirm even if the stack name suggests it).
Target (where the Pulumi code and state will live):
- Target git repo URL — ask the user. All work happens inside this repo from the start.
- Subfolder (optional) — ask if the user has a preference; default to repo root.
- Target project + stack names — ask the user, don't invent. If the stack already exists, use in Phase 1; if not, create it in Phase 2.
compareTo - Target language — TypeScript default.
Refactor preferences (for Phase 7):
- Does the user have the original source code (CDK repo, Bicep project, Terraform modules)? If yes, path or URL. This is the primary structural reference for Phase 7 — the refactored Pulumi code will draw on its component boundaries, file layout, and naming conventions as a guide, adapted to what the import model actually produced.
- Does the user have a preferred program layout for the Pulumi output? (e.g. "one file per service", "match my existing repo shape", "I don't care")
- Does the user want Phase 7 at all, or stop after zero-diff (Phase 6)?
Credentials:
- ESC environment for cloud credentials (ask if not given; never invent). ESC is preferred — see or
cloudformation.md §1.arm.md §1
Don't start Phase 1 until all of the above are confirmed. Summarize the plan back to the user and wait for approval.
在进行任何工具调用前,收集并确认所有以下信息。若有缺失,请询问用户——不要猜测。
源端(已发现的栈):
- 组织名称(例如)
pulumi_local - 已发现的项目名称(例如)
AcmeCdkExampleStack - 已发现的栈名称(例如)——这是扫描器生成的名称,通常包含账户+区域+CloudFormation/ARM栈名称。
dev-sandbox-disc_us-west-2__Dev - 区域(即使栈名称已暗示,也请确认)。
目标端(Pulumi代码和状态的存储位置):
- 目标Git仓库URL——询问用户。所有工作从一开始就在此仓库内进行。
- 子文件夹(可选)——询问用户是否有偏好;默认使用仓库根目录。
- 目标项目+栈名称——询问用户,不要自行创建。若栈已存在,在第1阶段使用;若不存在,在第2阶段创建。
compareTo - 目标语言——默认使用TypeScript。
重构偏好(用于第7阶段):
- 用户是否拥有原始源代码(CDK仓库、Bicep项目、Terraform模块)?若有,请提供路径或URL。这是第7阶段的主要结构参考——重构后的Pulumi代码将借鉴其组件边界、文件布局和命名约定,并根据导入模型的实际输出进行调整。
- 用户对Pulumi输出是否有偏好的程序布局?(例如“每个服务一个文件”、“匹配我现有仓库结构”、“无所谓”)
- 用户是否需要第7阶段,还是在零差异阶段(第6阶段)后停止?
凭据:
- 用于云凭据的ESC环境(若未提供则询问;请勿自行编造)。优先使用ESC——请查看或
cloudformation.md §1。arm.md §1
在确认所有上述信息前,请勿启动第1阶段。向用户总结计划并等待批准。
Phase 1 — Resource fetch and triage
第1阶段——资源获取与分类
Start here every time, even when resuming an existing migration. The API + any existing annotations are the source of truth for what's been done so far. If the target repo already has Pulumi code, read it — it tells you the conventions, existing resources, and how far a previous attempt got.
- Fetch discovered resources: .
GET .../discovered-stacks/{projectName}/{stackName}/resources- Target stack exists (resumed migration): append .
?compareTo=<targetProject>/<targetStack> - Target stack does not exist (greenfield): omit — the API returns 404 if the target stack isn't found. After Phase 2 creates the stack, subsequent calls can include it.
compareTo
- Target stack exists (resumed migration): append
- Save the response to disk — .
./.migration/resources-baseline.json - Run triage: — prints status counts (accounting for annotation overrides) and a per-resource table.
python3 <skill-base-dir>/scripts/triage.py .migration/resources-baseline.json - Present the plan to the user:
"Found N resources. A already Migrated, M Ready, K NotFound, J NoMatch, L non-migratable containers. I'll import Ready first, then verify NotFound, then triage NoMatch with you. Sound good?"
Get confirmation before writing any code.
每次都从此阶段开始,即使是恢复现有迁移。API+任何现有注释是当前已完成工作的唯一来源。若目标仓库已有Pulumi代码,请读取它——它会告诉你约定、现有资源以及之前的尝试进展到了哪一步。
- 获取已发现资源:。
GET .../discovered-stacks/{projectName}/{stackName}/resources- 目标栈已存在(恢复迁移):附加。
?compareTo=<targetProject>/<targetStack> - 目标栈不存在(全新项目):省略——若目标栈未找到,API会返回404。第2阶段创建栈后,后续调用可包含该参数。
compareTo
- 目标栈已存在(恢复迁移):附加
- 将响应保存到磁盘——。
./.migration/resources-baseline.json - 运行分类脚本:——打印状态计数(考虑注释覆盖)和按资源分类的表格。
python3 <skill-base-dir>/scripts/triage.py .migration/resources-baseline.json - 向用户展示计划:
"共发现N个资源。其中A个已迁移,M个已就绪,K个未找到,J个无匹配,L个不可迁移的容器。我将先导入已就绪的资源,然后验证未找到的资源,再与你一起分类无匹配的资源。这样可以吗?"
在编写任何代码前,请获得用户确认。
Phase 2 — Target repo and Pulumi stack setup
第2阶段——目标仓库与Pulumi栈设置
- Clone the target git repo (from Phase 0) and work inside it for all subsequent phases. If the repo already has code, read it to understand existing conventions and resources before adding new ones.
- If the target stack already exists (Phase 0 check), select it. Otherwise:
- Create the Pulumi project: .
pulumi new <language> --name <project> --stack <org>/<project>/<stack> --yes
- Create the Pulumi project:
- Set provider config: (or
pulumi config set aws:region <r>).azure-native:location - Link the ESC environment if provided.
No empty needed.
pulumi upConcrete commands for project + stack setup, region config, and provider install:orcloudformation.md §2.arm.md §2
- 克隆目标Git仓库(来自第0阶段),并在所有后续阶段中在此仓库内工作。若仓库已有代码,请先读取以了解现有约定和资源,再添加新内容。
- 若目标栈已存在(第0阶段已确认),选择该栈。否则:
- 创建Pulumi项目:。
pulumi new <language> --name <project> --stack <org>/<project>/<stack> --yes
- 创建Pulumi项目:
- 设置提供商配置:(或
pulumi config set aws:region <r>)。azure-native:location - 若提供了ESC环境,请链接它。
无需执行空的。
pulumi up项目+栈设置、区域配置和提供商安装的具体命令:或cloudformation.md §2。arm.md §2
Phase 3 — Build the import file
第3阶段——构建导入文件
Generate the import file: . This filters Ready/NotFound resources (excluding already-annotated ones) and maps API fields to the Pulumi import format ( ← providerType, ← name, ← resource.inputs.providerId).
python3 <skill-base-dir>/scripts/build_import.py .migration/resources-baseline.json .migration/import.jsontypenameid生成导入文件:。此脚本会过滤已就绪/未找到的资源(排除已注释的资源),并将API字段映射到Pulumi导入格式( ← providerType, ← name, ← resource.inputs.providerId)。
python3 <skill-base-dir>/scripts/build_import.py .migration/resources-baseline.json .migration/import.jsontypenameidPhase 4 — Import
第4阶段——导入
Using the from Phase 3.
import.jsonAlways use — without it, resources land in state with no code, breaking .
--generate-code --outpulumi previewpulumi import --file import.json --generate-code --out batch.<ext>使用第3阶段生成的。
import.json必须使用参数——若不使用,资源将仅进入状态而无代码,导致失效。
--generate-code --outpulumi previewpulumi import --file import.json --generate-code --out batch.<ext>then: append generated code into the main program file and delete the batch file
然后:将生成的代码追加到主程序文件中,并删除batch文件
**Per-batch loop: import → preview → commit → annotate.** Aim for ~20 resources per batch. For CDK stacks, batch by `cdkPath` top-level group; otherwise batch by resource type prefix.
1. **Import** the batch.
2. **`pulumi preview`** — zero diff required. Fix any diffs before moving on.
3. **Commit** the program changes to a branch.
4. **Annotate** each imported resource — `PUT .../migration` with note and **no `statusOverride`**. Annotations survive context resets and are visible in the UI.
`?compareTo` is a **live progress signal** — `pulumi import` writes state immediately, so `migrationStatus` flips to `Migrated` after each import. **Do not run `pulumi up`** (see Phase 6).
**Reserve `statusOverride`** for cases where the computed status will be wrong:
- `statusOverride=Migrated` (with `linkedResourceUrn`) — resource covered by another (inline IAM policy → parent Role, IGW attachment → IGW, etc.).
- `statusOverride=Migrated` (with a note, no `linkedResourceUrn`) — resource is deleted, dangling, or has no Pulumi equivalent. Flag it resolved so it drops out of the outstanding work.
**批量循环流程:导入 → 预览 → 提交 → 注释**。建议每批处理约20个资源。对于CDK栈,按`cdkPath`顶层分组批量处理;否则按资源类型前缀批量处理。
1. **导入**该批资源。
2. **执行`pulumi preview`**——要求零差异。继续操作前请修复所有差异。
3. **将程序变更提交到分支**。
4. **为每个已导入资源添加注释**——使用`PUT .../migration`,包含注释内容且**不设置`statusOverride`**。注释会在上下文重置后保留,并在UI中可见。
`?compareTo`是**实时进度信号**——`pulumi import`会立即写入状态,因此每次导入后`migrationStatus`会切换为`Migrated`。**请勿执行`pulumi up`**(详见第6阶段)。
**仅在计算状态会出错的情况下使用`statusOverride`**:
- `statusOverride=Migrated`(附带`linkedResourceUrn`)——资源被其他资源涵盖(内联IAM策略 → 父Role,IGW附件 → IGW等)。
- `statusOverride=Migrated`(附带注释,无`linkedResourceUrn`)——资源已删除、悬空或无Pulumi等效资源。将其标记为已解决,使其从待处理工作中移除。Phase 5 — NotFound and NoMatch triage
第5阶段——未找到与无匹配资源的分类
Default strategy: try first, annotate second. For both NotFound and corrected-mapping cases, attempting is the fastest way to learn what's actually wrong. The error messages are precise and actionable.
pulumi importNotFound ( set, state unconfirmed):
providerTypeCommon outcomes:
-
Resource is deleted.returns
pulumi import. AnnotatePreview failed: resource '<id>' does not existwith the literal error in the note. Don't retry. In practice, a substantial share of NotFound resources turn out to be deleted rather than a mapping error.statusOverride=Migrated -
Wrongmapping. Import fails with a type-validation error or schema mismatch. Several CF types have multiple valid Pulumi mappings (VPC gateway attachment / VPN vs IGW; S3 vs s3control; RDS instance vs cluster instance; SES v2 vs v1; etc.) — our scanner picks a primary that doesn't always match your resource. Look up the
providerTypeinoriginType, overridecloudformation.md §7in the import file, retry. Then handle the fingerprint side-effect (next bullet).providerType -
Wrong-mapping side-effect: PulumiOnly appears. When the agent imports with a corrected, fingerprint matching against the discovered resource fails. The discovered resource stays
providerType(orNotFound) and a newNoMatchentry appears. Annotate the original origin row asPulumiOnlywithstatusOverride=Migratedpointing to the PulumiOnly URN. This bridges them in the UI and keeps the bookkeeping clean.linkedResourceUrn -
Resource is alive and mapping is correct. Import succeeds. Status flips toautomatically.
Migrated
NoMatch ( is ):
providerTypenullCommon patterns:
-
Inline IAM policies.whose name matches a migrated Role's prefix is an inline policy already captured as
AWS::IAM::Policyon the Role's import. AnnotateinlinePolicieswith note:statusOverride=Migrated."inline policy of <RoleName>" -
AWS::SecretsManager::SecretTargetAttachment has no direct Pulumi mapping. The link between secret and target (RDS cluster, etc.) is implicit via the cluster's credentials config. Annotatewith a note explaining the implicit link.
statusOverride=Migrated -
CDK Custom Resources (). Typically a Lambda handler doing the actual work. Check
Custom::*for known handler → Pulumi replacement mappings. Don't annotatecloudformation.md §5without confirming with the user — surface what the handler does and let them decide.Migrated -
Other NoMatch types. Look up thein the cloud provider docs (CF resource type reference or ARM resource type reference) to understand what the resource is, then search the Pulumi registry for a matching provider type. If the mapping is ambiguous, surface to the user and ask.
originType
Pre-existing PulumiOnly entries. Beyond the corrected-mapping artifacts above, also covers resources already in the target stack that aren't part of this migration. Leave those as-is — no annotation needed.
PulumiOnlyFor cloud-specific lookup commands (verifying resources exist, finding import IDs, querying the cloud), provider-choice rules (classic vsaws,aws-nativevsazure-native), the Preview Resolution Workflow, and known import quirks, seeazureorcloudformation.md §3–§6.arm.md §3–§6
默认策略:先尝试,后注释。对于未找到和修正映射的情况,尝试是了解实际问题最快的方法。错误消息精确且可操作。
pulumi import未找到(已设置,状态未确认):
providerType常见结果:
-
资源已删除。返回
pulumi import。将其注释为Preview failed: resource '<id>' does not exist,并在注释中包含确切错误信息。请勿重试。实际上,相当一部分未找到的资源最终被证实是已删除而非映射错误。statusOverride=Migrated -
映射错误。导入失败并返回类型验证错误或架构不匹配。部分CloudFormation类型有多个有效的Pulumi映射(VPC网关附件/VPN vs IGW;S3 vs s3control;RDS实例 vs 集群实例;SES v2 vs v1等)——我们的扫描器选择的主映射并不总是匹配你的资源。在
providerType中查找cloudformation.md §7,在导入文件中覆盖originType,然后重试。之后处理指纹副作用(下一点)。providerType -
错误映射的副作用:出现PulumiOnly资源。当Agent使用修正后的导入时,与已发现资源的指纹匹配失败。已发现资源仍保持
providerType(或NotFound)状态,同时出现新的NoMatch条目。将原始源资源行注释为PulumiOnly,并将statusOverride=Migrated指向PulumiOnly的URN。这会在UI中关联它们,保持记账清晰。linkedResourceUrn -
资源存在且映射正确。导入成功。状态自动切换为。
Migrated
无匹配(为):
providerTypenull常见模式:
-
内联IAM策略。名称与已迁移Role前缀匹配的是内联策略,已在Role的导入中被捕获为
AWS::IAM::Policy。将其注释为inlinePolicies,注释内容为:statusOverride=Migrated。"<RoleName>的内联策略" -
AWS::SecretsManager::SecretTargetAttachment无直接Pulumi映射。密钥与目标(RDS集群等)之间的链接通过集群的凭据配置隐式实现。将其注释为,并在注释中解释隐式链接。
statusOverride=Migrated -
CDK自定义资源()。通常是执行实际工作的Lambda处理程序。查看
Custom::*了解已知的处理程序→Pulumi替换映射。未与用户确认前请勿注释为cloudformation.md §5——告知用户处理程序的功能并让他们决定。Migrated -
其他无匹配类型。在云提供商文档(CloudFormation资源类型参考或ARM资源类型参考)中查找以了解资源是什么,然后在Pulumi注册表中搜索匹配的提供商类型。若映射不明确,请告知用户并询问。
originType
预先存在的PulumiOnly条目。除上述修正映射产生的条目外,还涵盖目标栈中已存在但不属于本次迁移的资源。保留这些条目即可——无需添加注释。
PulumiOnly云特定的查找命令(验证资源存在、查找导入ID、查询云)、提供商选择规则(经典版 vsaws、aws-nativevsazure-native)、预览解决工作流以及已知的导入 quirks,请查看azure或cloudformation.md §3–§6。arm.md §3–§6
Phase 6 — Reconciliation & PR
第6阶段——调和与PR
-
Run— confirm there are NO changes. Any diff means the generated code doesn't match the imported state. Diffs come in three shapes:
pulumi preview- Removed () — a field the cloud has but your code doesn't set. Add it to the code with the real cloud value. Don't
-.ignoreChanges - Added () — a field your code sets that the provider didn't return. If computed/read-only →
+. If a provider default re-statement → remove from code.ignoreChanges - Changed () — value mismatch. Query the cloud, determine the correct value, update code. Never silence with
~.ignoreChanges
Expect 2–5 preview rounds for complex resources. Never runto resolve diffs — that modifies the cloud, not the code. Seepulumi uporcloudformation.md §6for cloud-specific diff patterns.arm.md §6 - Removed (
-
Do one finaland verify the expected distribution (this reflects the current backend state —
GET resources?compareTo=...is already up-to-date since everymigrationStatuswrites state):pulumi import- — all imported resources
Migrated - — Pulumi-only resources, including any corrected-mapping imports linked via annotation
PulumiOnly - remaining — every one should have an annotation
NoMatch - (containers) — silently skipped
NotApplicable
-
Proceed to Phase 7 (refactor offer) before opening the PR.
The migration is functionally complete when preview is clean and the API triage shows no unresolved resources. Do not run . The imported state is already synced to Pulumi Cloud via ; there's nothing for to do that serves the migration.
pulumi uppulumi importpulumi up-
执行——确认无任何变更。任何差异都意味着生成的代码与导入的状态不匹配。差异分为三种类型:
pulumi preview- 移除()——云中有但代码未设置的字段。将其添加到代码中并使用实际云值。请勿使用
-。ignoreChanges - 添加()——代码设置但提供商未返回的字段。若为计算/只读字段→使用
+。若为提供商默认值的重复声明→从代码中移除。ignoreChanges - 变更()——值不匹配。查询云,确定正确值,更新代码。请勿使用
~掩盖问题。ignoreChanges
复杂资源预计需要2–5轮预览。请勿执行来解决差异——这会修改云而非代码。云特定的差异模式请查看pulumi up或cloudformation.md §6。arm.md §6 - 移除(
-
最后执行一次,并验证预期的状态分布(这反映了当前后端状态——
GET resources?compareTo=...在每次migrationStatus写入状态后已更新):pulumi import- ——所有已导入资源
Migrated - ——仅Pulumi资源,包括任何通过注释关联的修正映射导入资源
PulumiOnly - 剩余——每个都应有注释
NoMatch - (容器)——已被静默跳过
NotApplicable
-
在打开PR前,进入第7阶段(提供重构选项)。
当预览无差异且API分类显示无未解决资源时,迁移在功能上已完成。请勿执行。导入的状态已通过同步到Pulumi Cloud;对迁移没有任何作用。
pulumi uppulumi importpulumi upPhase 7 — Refactor and maintainability review (before the PR)
第7阶段——重构与可维护性审查(PR前)
Zero-diff is achieved, but the imported code is "flat" — hardcoded values, no cross-resource references, all resources at the top level. Before opening the PR, offer the user a readability refactor.
7a — Offer and orient
If Phase 0 didn't capture explicit refactor preferences, or the user hasn't explicitly declined, ask now:
"Preview is clean and all resources are accounted for. Before I open the PR, I'd like to refactor the code for maintainability — replacing any hardcoding with cross-resource references, extracting config parameters, and grouping related resources. (If you gave me a source repo, I'll use it as the structural blueprint, matching the intended file layout and component names.) Want me to go ahead?"
If they decline, skip to Phase 8.
7b — Implement the refactor
Read for strategies, invariants, and template-reading references. Key priorities in order:
refactor.md- Take structural cues from the source repo first — if the user provided a source repo, read it now (clone or use the local path from Phase 0). Use its file layout, component/module boundaries, and naming conventions as a guide. Where a group of resources maps naturally to a class, module, or subdirectory in the source and that grouping still feels natural for the imported Pulumi program, mirror that structure; otherwise avoid forcing artificial groupings just to match the source mechanically.
- Replace literal ARN/ID references with cross-resource output references.
- Extract config parameters (region, account ID, environment tag).
- Consolidate into classes where the source repo or CDK paths suggest a natural grouping.
ComponentResource - Split into files only last — and only when a natural isle warrants it.
Run after every non-trivial change. Zero-diff must hold throughout. If preview shows a diff, revert that single change before trying anything else — see § The invariant for recovery steps.
pulumi previewrefactor.md7c — User walkthrough and maintainability sign-off
After the refactor, present the result to the user before opening the PR:
- Show a summary of what changed: files created/renamed, components introduced, literals replaced, config keys added.
- Walk through the top-level (or equivalent) line by line if it's under ~80 lines; otherwise describe the module breakdown.
index.ts - Highlight any judgment calls (e.g. "I grouped the IAM resources into to match the
iam.tsdirectory in your source repo — let me know if you'd prefer a different name").lib/iam/ - Ask explicitly: "Does this structure match how you'd expect to maintain this code?" Wait for the user's answer. If they request changes, make them (always preview after) and repeat the walkthrough until they're satisfied.
Only proceed to Phase 8 once the user confirms the structure is acceptable.
已实现零差异,但导入的代码是“扁平化”的——包含硬编码值、无跨资源引用、所有资源都在顶层。在打开PR前,向用户提供可读性重构选项。
7a——提供选项并说明
若第0阶段未捕获明确的重构偏好,或用户未明确拒绝,请现在询问:
"预览无差异,所有资源都已处理完毕。在打开PR前,我想对代码进行重构以提高可维护性——将硬编码值替换为跨资源引用,提取配置参数,并对相关资源进行分组。(若你提供了源仓库,我会将其作为结构蓝图,匹配预期的文件布局和组件名称。)需要我继续吗?"
若用户拒绝,跳至第8阶段。
7b——实施重构
查看了解策略、不变量和模板读取参考。优先顺序如下:
refactor.md- 首先从源仓库获取结构线索——若用户提供了源仓库,请立即读取(克隆或使用第0阶段提供的本地路径)。借鉴其文件布局、组件/模块边界和命名约定。若一组资源在源仓库中自然映射到类、模块或子目录,且该分组对导入的Pulumi程序仍合理,则镜像该结构;否则避免为了机械匹配源仓库而强制进行人工分组。
- 将字面ARN/ID引用替换为跨资源输出引用。
- 提取配置参数(区域、账户ID、环境标签)。
- 将资源整合到类中——若源仓库或CDK路径暗示存在自然分组。
ComponentResource - 最后拆分到文件中——仅在存在自然分组时才进行。
每次非 trivial 变更后都要执行。整个过程必须保持零差异。若预览显示差异,请先撤销该变更再尝试其他操作——恢复步骤请查看 § The invariant。
pulumi previewrefactor.md7c——用户演练与可维护性签字确认
重构完成后,在打开PR前向用户展示结果:
- 展示变更摘要:创建/重命名的文件、引入的组件、替换的字面量、添加的配置键。
- 若顶层(或等效文件)少于约80行,则逐行演练;否则描述模块划分。
index.ts - 突出显示任何判断性决策(例如“我将IAM资源分组到中,以匹配你源仓库中的
iam.ts目录——若你偏好其他名称,请告知我”)。lib/iam/ - 明确询问:**“此结构是否符合你对代码维护的预期?”**等待用户回答。若用户要求变更,请进行修改(每次修改后都要预览)并重复演练,直到用户满意。
只有在用户确认结构可接受后,才能进入第8阶段。
Phase 8 — PR and migration report
第8阶段——PR与迁移报告
Produce the migration report (see below) and open the PR. The PR includes whatever state the code is in — raw imported code (if the user skipped Phase 7) or the refactored version (if they opted in).
生成迁移报告(如下所示)并打开PR。PR包含代码的当前状态——原始导入代码(若用户跳过第7阶段)或重构后的版本(若用户选择进行重构)。
MIGRATION REPORT FORMAT
迁移报告格式
Include in the PR description:
- Overview — source discovered stack → target Pulumi stack, region, language.
- Triage summary — counts by status at start and end.
- Resource mapping table — name, origin type, provider type, status, notes.
- Gaps — unmapped resources and why, with annotations.
- Progress URL — link to the discovered-stacks comparison endpoint for ongoing tracking.
- Next steps — pending user decisions, optional refactoring.
PR描述中需包含:
- 概述——源已发现栈 → 目标Pulumi栈、区域、语言。
- 分类摘要——开始和结束时的状态计数。
- 资源映射表——名称、源类型、提供商类型、状态、注释。
- 差距——未映射的资源及原因,附带注释。
- 进度URL——指向discovered-stacks对比端点的链接,用于持续跟踪。
- 下一步——待用户决策的事项、可选重构。