.agents/skills/sdk-to-pf-migration/SKILL.md
Guides migration of Terraform resources from Plugin SDK to Plugin Framework. Use when migrating SDK resources to PF, planning SDK-to-PF migrations, or when the user asks to migrate a resource to the Plugin Framework.
npx skillsauth add elastic/terraform-provider-elasticstack sdk-to-pf-migrationInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Migrate Terraform resources from terraform-plugin-sdk/v2 to terraform-plugin-framework while preserving behavior and avoiding breaking changes.
provider/factory.go combines SDK and PF via tf6muxserver. PF resources take precedence when both define the same type.internal/clients/elasticsearch (and analogous client packages) plus internal/models are used by both SDK and PF.Migrate client code to return PF diags — do not introduce a compatibility layer in the PF resource.
fwdiag.Diagnostics instead of SDK diag.Diagnostics. Use diagutil.CheckErrorFromFW() for HTTP errors, fwdiag.NewErrorDiagnostic() or diagutil.FrameworkDiagFromError() for other errors.resp.Diagnostics with no conversion.diagutil.SDKDiagsFromFramework() when calling the migrated client.If the resource has dedicated client functions, migrate them to return fwdiag.Diagnostics:
sdkdiag.Diagnostics to fwdiag.Diagnosticsdiagutil.CheckErrorFromFW() instead of diagutil.CheckError() for HTTP responsesfwdiag.NewErrorDiagnostic() / diagutil.FrameworkDiagFromError() for errorsUpdate any SDK callers to use diagutil.SDKDiagsFromFramework() when calling these functions.
(Example: ILM used PutIlm / GetIlm / DeleteIlm — same pattern applies to any named CRUD helpers.)
Create internal/<domain>/<resource>/ (path mirrors where the SDK resource lived). Typical layout:
| File | Purpose |
|------|---------|
| resource.go | Resource struct, Metadata, Configure, ImportState |
| schema.go | PF schema including the appropriate connection block for the backend |
| models.go | Plan/State model types |
| create.go | Create |
| read.go | Read |
| update.go | Update |
| delete.go | Delete |
| resource-description.md | Embedded description |
Schema: Use the same connection block helpers as other PF resources in this provider (e.g. providerschema.GetEsFWConnectionBlock(false) for Elasticsearch-backed resources). Replicate the SDK schema exactly; preserve attribute names, types, and validation.
Critical behaviors to preserve (audit the SDK implementation for each):
readonly / freeze / unfollow use enabled: false when absent in config.jsontypes.NormalizedType or the project’s equivalent for metadata-like blobs).total_shards_per_node: -1 when ES < 7.16).provider/plugin_framework.go — register NewResource in resources() following the existing registration pattern.provider/provider.go — remove the type from ResourcesMap.internal/<domain>/<resource>/acc_test.go with package <resource>_test.checkResourceDestroy from the old *_test.go.testdata/TestAccResourceX*/ into the new package.*_test.go.Add TestAccResourceXFromSDK to verify existing SDK-created state works after upgrade:
//go:embed testdata/TestAccResourceXFromSDK/create/resource.tf
var sdkCreateConfig string
func TestAccResourceXFromSDK(t *testing.T) {
name := sdkacctest.RandStringFromCharSet(10, sdkacctest.CharSetAlphaNum)
resource.Test(t, resource.TestCase{
PreCheck: func() { acctest.PreCheck(t) },
Steps: []resource.TestStep{
{
ExternalProviders: map[string]resource.ExternalProvider{
"elasticstack": {
Source: "elastic/elasticstack",
VersionConstraint: "0.14.3", // last SDK version for this resource
},
},
Config: sdkCreateConfig,
ConfigVariables: config.Variables{"name": config.StringVariable(name)},
Check: resource.ComposeTestCheckFunc(...),
},
{
ProtoV6ProviderFactories: acctest.Providers,
ConfigDirectory: acctest.NamedTestCaseDirectory("create"),
ConfigVariables: config.Variables{"name": config.StringVariable(name)},
Check: resource.ComposeTestCheckFunc(...),
},
},
})
}
Use the last provider version where the resource was still SDK-based for VersionConstraint.
Run schema-coverage analysis. Add tests for:
TestAccResourceX_importState if missing)Use these PF migrations as patterns (complexity varies):
internal/elasticsearch/security/user/ — includes TestAccResourceSecurityUserFromSDKinternal/elasticsearch/index/ilm/ — large schema, version gating, JSON normalizationinternal/elasticsearch/index/datastreamlifecycle/ — smaller surface areamake buildgo test ./internal/<domain>/<resource>/... -vgo test ./internal/<domain>/<resource>/... -v -count=1 -run TestAcc. Follow testing for environment requirements.elasticstack_elasticsearch_index_lifecycle).Avoid unless unavoidable. Preserve:
elasticstack_elasticsearch_index_lifecycle for ILM)testing
Analyzes a Terraform resource schema and compares it to attributes used in the acceptance test suite (configs + assertions). Produces a prioritized report of missing and poor coverage (set-only assertions, single-value coverage, missing unset/empty cases, missing update coverage). Use when the user asks about schema coverage, test coverage gaps, or improving Terraform acceptance tests for a resource.
testing
Analyzes an OpenSpec requirements spec for internal consistency, implementation compliance, and test opportunities; when a shell is available, run openspec validate first for structural checks. Use when reviewing specs, verifying implementation against requirements, or identifying test gaps.
testing
Verify implementation matches change artifacts. Use when the user wants to validate that implementation is complete, correct, and coherent before archiving.
data-ai
Sync delta specs from a change to main specs. Use when the user wants to update main specs with changes from a delta spec, without archiving the change.