关于 provider-framework-migration
介绍将 Terraform Provider 资源从 Plugin SDKv2 逐步迁移到 Plugin Framework 的技能,重点是并行服务两种实现,并保持 Schema、状态和行为兼容。
主要功能
- 01SDKv2 与 Framework mux
- 02按资源迁移步骤
- 03Schema 映射指南
- 04处理 null 与零值差异
- 05状态兼容性测试
适用场景
- 01逐步迁移现有资源
- 02保持迁移期间的配置兼容
- 03验证旧版本写入的状态
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ hashicorp/ agent-skills --skill provider-framework-migration安装后在 Claude Code 中输入/provider-framework-migration确认已加载。
附带文件references/schema-mapping.md
详细介绍
从 Plugin SDKv2 迁移到 Plugin Framework
对于全新资源和数据源,必须使用 Plugin Framework;SDKv2 仅用于维护。迁移按资源逐个进行,并采取渐进方式:通过 mux 的服务可让 SDKv2 和 Framework 实现并行运行,因此无需一次性全面重写。本指南涵盖 mux 设置、逐个资源的迁移流程,以及可能把机械式转换变成静默破坏性变更的行为陷阱。
参考资料(按需加载):
references/schema-mapping.md— SDKv2 → Framework 的完整转换表,附带代码示例
官方指南:Framework 迁移。
判断是否需要迁移
迁移存在实际风险,但对用户可见的收益很少,因此应先进行评估:
- 除非有迫切需求,否则不要迁移复杂或使用广泛的资源(例如,只有 Framework 才支持的功能,或 SDKv2 无法修复的错误)。两个 SDK 在行为上有所不同,尤其是 null 值与零值的处理方式;这些差异会对现有用户造成破坏性变更。这是 terraform-provider-aws 等大型 Provider 一贯遵循的策略。
- 简单资源可以安全迁移:扁平 Schema,不使用
DiffSuppressFunc、CustomizeDiff、StateFunc,也没有复杂的嵌套块。 - 新功能从不要求迁移旧代码——可以使用 mux,并在 Framework 中与旧资源并行编写新资源。
要判断 Provider 当前处于哪种模式,请检查 go.mod:存在 terraform-plugin-mux 表示它已同时提供两种实现;只有 terraform-plugin-sdk/v2 表示仅使用 SDKv2(mux 设置是第一步);只有 terraform-plugin-framework 则表示迁移已完成。
第 1 步:为 Provider 设置 mux
在 main.go 中组合两个插件服务器。要提供协议版本 6,必须使用 tf5to6server 升级 SDKv2 服务器(协议 6 要求 Terraform CLI >= 1.0;如果必须支持 0.12+,则改用 tf6to5server/tf5muxserver,通过协议 5 设置 mux——但 Framework Provider 无法使用嵌套属性等仅限协议 6 的功能):
package main
import (
"context"
"flag"
"log"
"github.com/hashicorp/terraform-plugin-framework/providerserver"
"github.com/hashicorp/terraform-plugin-go/tfprotov6"
"github.com/hashicorp/terraform-plugin-go/tfprotov6/tf6server"
"github.com/hashicorp/terraform-plugin-mux/tf5to6server"
"github.com/hashicorp/terraform-plugin-mux/tf6muxserver"
"example.org/terraform-provider-examplecloud/internal/provider"
sdkprovider "example.org/terraform-provider-examplecloud/internal/sdkprovider"
)
func main() {
var debug bool
flag.BoolVar(&debug, "debug", false, "run with support for debuggers")
flag.Parse()
ctx := context.Background()
upgradedSDKServer, err := tf5to6server.UpgradeServer(
ctx,
sdkprovider.Provider().GRPCProvider,
)
if err != nil {
log.Fatal(err)
}
providers := []func() tfprotov6.ProviderServer{
providerserver.NewProtocol6(provider.New(version)()),
func() tfprotov6.ProviderServer { return upgradedSDKServer },
}
muxServer, err := tf6muxserver.NewMuxServer(ctx, providers...)
if err != nil {
log.Fatal(err)
}
var serveOpts []tf6server.ServeOpt
if debug {
serveOpts = append(serveOpts, tf6server.WithManagedDebug())
}
err = tf6server.Serve("registry.terraform.io/example/examplecloud",
muxServer.ProviderServer, serveOpts...)
if err != nil {
log.Fatal(err)
}
}
实际操作中容易忽视的 mux 要求:
- 两个插件的 Provider Schema 必须完全一致——Provider 级别的属性、类型和描述都必须相同。Provider 配置应只有一个真实来源,另一份保持镜像同步。
- 每个资源和数据源只能存在于两个插件中的一个。迁移的最后一步是删除 SDKv2 注册项。
- 如果使用协议 6 发布到 Registry,请在
terraform-registry-manifest.json中设置"metadata": {"protocol_versions": ["6.0"]}。
第 2 步:修改前建立基准
迁移后的资源对用户而言必须毫无差异。使用迁移前已有的测试来证明这一点:
- 确保该资源有通过的验收测试覆盖:
_basic测试带有导入步骤(ImportStateVerify: true)、_disappears测试,以及针对每个属性的更新测试。如果覆盖不足,请先针对 SDKv2 实现补齐——这些测试是迁移的验收标准,迁移后必须保持原样并通过。 - 记录测试未覆盖的行为:属性默认值、省略可选属性时的处理方式(null 与
""/0/false的区别即将变得重要),以及任何DiffSuppressFunc/StateFunc归一化处理。
第 3 步:迁移资源
使用 references/schema-mapping.md 中的映射表转换架构和 CRUD。以下规则可避免破坏性变更:
- 块必须保留为块。 在用户配置中以
block { ... }语法编写的 SDKv2Elem: &schema.Resource{...},必须转换为 Framework 块(schema.ListNestedBlock/SetNestedBlock)——将其转换为嵌套属性会改变用户必须编写的 HCL 语法,属于破坏性变更。嵌套属性仅适用于新架构。 - 空值不等于零值。 SDKv2 的
d.Get("name")在未设置时会返回"";Framework 模型提供的types.String能区分 null、unknown 和""。对于旧代码中所有检查== ""或依赖GetOk的地方,都要明确决定 null 的含义,并确保发送给 API 的内容与 SDKv2 相同(通常是:null 时省略该字段)。 - 保留
id属性。 新增的 Framework 资源可以省略冗余的id,但迁移后的资源必须保留完全相同的架构——删除或重命名属性会破坏现有状态和配置。 - 状态必须能够往返。 Framework 会读取 SDKv2 写入的状态。如果每个属性都保留其名称和类型,就不需要状态升级。如果旧架构存储的值被新类型包以不同方式规范化,就需要
StateUpgrader——这表明该资源可能属于不应迁移的类别。
第 4 步:迁移注册
在 Framework provider 的 Resources() 中注册该资源,并在同一提交中将其从 SDKv2 provider 的 ResourcesMap 中删除——重复注册会导致 mux 报错。
第 5 步:验证
- 现有验收测试应当无需修改即可通过——尤其是
ImportStateVerify,它会对比导入的状态与存储的状态,能够发现大多数 null 与零值之间的回归问题。 - 添加状态兼容性步骤:使用最后一个已发布的(SDKv2)provider 版本应用配置,然后使用迁移后的构建版本运行 plan——计划必须为空。在
terraform-plugin-testing中,这是一个两步测试:先通过ExternalProviders使用旧版本,然后通过ProtoV6ProviderFactories和ConfigPlanChecks断言计划为空。如果有provider-test-patterns技能,可参考其中的模式。 - 针对真实的迁移前状态文件运行
terraform plan,确认没有差异。
检查清单
- 资源足够简单,可以迁移(没有复杂的差异自定义),或确有迁移需求
- Mux 同时提供两个插件;两者的 provider 级架构相同
- 验收测试在迁移前已存在,且迁移后无需修改即可通过
- 块仍为块;属性名称和类型未变;保留了
id - 保留了 null/省略语义(API 收到的内容与 SDKv2 相同)
- 在同一变更中移除了 SDKv2 注册
- 已针对上一个版本写入的状态验证计划为空
- 如果仓库会跟踪发行说明,已添加变更日志条目
相关技能
迁移代码中的 Framework CRUD、查找器和等待器模式,可使用 provider-resources 技能(如果有);回归测试和版本升级测试模式,可使用 provider-test-patterns 技能。