provider-framework-migration

Migra recursos de proveedor y fuentes de datos de Terraform del Plugin SDKv2 al Plugin Framework: combinando ambos plugins en un solo proveedor (terraform-plugin-mux,…

npx skills add https://github.com/hashicorp/agent-skills --skill provider-framework-migration

Migrating from Plugin SDKv2 to the Plugin Framework

The Plugin Framework is required for net-new resources and data sources; SDKv2 is maintenance-only. Migration is per-resource and incremental: a muxed provider serves SDKv2 and Framework implementations side by side, so you never need a big-bang rewrite. This skill covers the mux setup, the per-resource workflow, and the behavioral traps that turn a mechanical translation into a silent breaking change.

Reference (load when needed):

  • references/schema-mapping.md — the full SDKv2 → Framework translation table with code pairs

Official guide: Framework migration.

Decide Whether to Migrate at All

Migration has real risk and little user-visible payoff, so triage first:

  • Do not migrate complex or heavily-used resources without a driving need (a Framework-only feature, a bug that SDKv2 cannot fix). The two SDKs differ behaviorally — most importantly around null versus zero values — and those differences surface as breaking changes for existing users. This is the standing policy in large providers like terraform-provider-aws.
  • Simple resources migrate safely: flat schemas, no DiffSuppressFunc, no CustomizeDiff, no StateFunc, no complex nested blocks.
  • New capabilities never require migrating old code — mux and write the new resource in the Framework alongside the old ones.

To tell what mode a provider is in, check go.mod: terraform-plugin-mux present means it already serves both; only terraform-plugin-sdk/v2 means SDKv2-only (mux setup is your first step); only terraform-plugin-framework means the migration is done.

Step 1: Mux the Provider

Combine both plugin servers in main.go. Serving protocol version 6 requires upgrading the SDKv2 server with tf5to6server (protocol 6 needs Terraform CLI >= 1.0; if you must support 0.12+, mux at protocol 5 with tf6to5server/tf5muxserver instead — but the Framework provider then cannot use protocol-6-only features like nested attributes):

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 requirements that bite in practice:

  • Provider schemas must match exactly across both plugins — same provider-level attributes, same types, same descriptions. Keep one source of truth for the provider configuration and mirror it.
  • Each resource and data source may exist in only one of the two plugins. Migration's final step is deleting the SDKv2 registration.
  • If publishing to the Registry with protocol 6, set "metadata": {"protocol_versions": ["6.0"]} in terraform-registry-manifest.json.

Step 2: Baseline Before You Touch Anything

The migrated resource must be indistinguishable to users. Prove it with tests that exist before the migration:

  1. Ensure the resource has passing acceptance coverage: _basic with an import step (ImportStateVerify: true), _disappears, and per-attribute update tests. If coverage is missing, write it against the SDKv2 implementation first — these tests are the migration's acceptance criteria and must pass unchanged afterward.
  2. Note behaviors tests don't capture: attribute defaults, what happens when optional attributes are omitted (null vs ""/0/false is about to matter), and any DiffSuppressFunc/StateFunc normalization.

Step 3: Port the Resource

Translate schema and CRUD using the mapping table in references/schema-mapping.md. The rules that prevent breaking changes:

  • Blocks stay blocks. An SDKv2 Elem: &schema.Resource{...} written as block { ... } syntax in user configs must become a Framework Block (schema.ListNestedBlock/SetNestedBlock) — converting it to a nested attribute changes the HCL syntax users must write, which is a breaking change. Nested attributes are for new schema only.
  • Null is not zero. SDKv2 d.Get("name") returned "" for unset; the Framework model gives you types.String that distinguishes null, unknown, and "". Everywhere the old code checked == "" or relied on GetOk, decide explicitly what null means, and make sure you send the API the same thing SDKv2 sent (usually: omit the field when null).
  • Keep the id attribute. Net-new Framework resources may omit a redundant id, but a migrated resource must keep its exact schema — removing or renaming attributes breaks existing state and configs.
  • State must round-trip. The Framework reads the state SDKv2 wrote. If every attribute keeps its name and type, no state upgrade is needed. If the old schema stored a value the new types package normalizes differently, you need a StateUpgrader — treat that as a signal the resource may be in the do-not-migrate bucket.

Step 4: Move the Registration

Register the resource in the Framework provider's Resources() and delete it from the SDKv2 provider's ResourcesMap in the same commit — mux errors on duplicates.

Step 5: Verify

  1. The pre-existing acceptance tests pass without modification — especially ImportStateVerify, which diffs imported state against stored state and catches most null-vs-zero regressions.
  2. Add a state-compatibility step: apply a config with the last released (SDKv2) provider version, then plan with the migrated build — the plan must be empty. In terraform-plugin-testing this is a two-step test using ExternalProviders for the old version, then ProtoV6ProviderFactories with ConfigPlanChecks asserting an empty plan. The provider-test-patterns skill (if available) documents the pattern.
  3. terraform plan against a real pre-migration state file shows no diff.

Checklist

  • Resource is simple enough to migrate (no complex diff customization), or there's a driving need
  • Mux serves both plugins; provider-level schemas identical in both
  • Acceptance tests existed before migration and pass unchanged after
  • Blocks remained blocks; attribute names and types unchanged; id kept
  • Null/omitted semantics preserved (API receives what SDKv2 sent)
  • SDKv2 registration removed in the same change
  • Empty-plan verified against state written by the previous release
  • Changelog entry added, if the repo tracks release notes

Related Skills

Use the provider-resources skill (if available) for Framework CRUD, finder, and waiter patterns in the ported code, and provider-test-patterns for the regression and version-upgrade test patterns.

Más skills de hashicorp

provider-actions
hashicorp
Implementar acciones de proveedor de Terraform utilizando el Plugin Framework. Usar al desarrollar operaciones imperativas que se ejecutan en eventos de ciclo de vida (antes/después…
official
new-terraform-provider
hashicorp
Usa esto al crear la estructura de un nuevo proveedor de Terraform con el Plugin Framework: diseño del espacio de trabajo, configuración del módulo go, servidor del proveedor main.go y un provider.go…
official
terraform-test
hashicorp
Guía completa para escribir y ejecutar pruebas de Terraform. Úsala al crear archivos de prueba (.tftest.hcl), escribir escenarios de prueba con bloques run, validar…
official
terraform-test
hashicorp
Guía completa para escribir y ejecutar pruebas de Terraform con aserciones, simulación y validación de módulos. Escribe archivos de prueba usando la sintaxis .tftest.hcl con bloques run que se ejecutan en modo plan o apply, soportando ejecución secuencial y paralela con aislamiento de estado opcional. Afirma condiciones sobre atributos de recursos, salidas y fuentes de datos; usa expect_failures para validar que las entradas inválidas sean rechazadas correctamente. Simula proveedores (Terraform 1.7.0+) para simular el comportamiento de la infraestructura sin...
official
provider-actions
hashicorp
Implementa acciones imperativas del proveedor de Terraform en eventos del ciclo de vida de recursos utilizando el Plugin Framework. Soporta disparadores de ciclo de vida antes/después de crear y antes/después de actualizar (los eventos de destrucción no están disponibles en Terraform 1.14.0). Requiere una definición de esquema adecuada con tipos de framework correctos, ElementType para colecciones y validadores para la validación de entrada. Incluye informes de progreso, gestión de tiempos de espera y manejo integral de errores para operaciones de larga duración. Implementa polling y...
official
aws-ami-builder
hashicorp
Construye imágenes personalizadas de Amazon Machine con el builder amazon-ebs de Packer. Automatiza la creación de AMIs a partir de AMIs fuente usando plantillas HCL con provisionadores para personalización (scripts shell, cargas de archivos, gestión de configuración). Soporta distribución multi-región de AMIs mediante ami_regions y filtrado flexible de AMIs fuente por nombre, propietario y tipo de virtualización. Autentica mediante variables de entorno, archivo de credenciales de AWS o perfiles de instancia IAM; incluye comandos de validación y construcción para la plantilla...
official
new-terraform-provider
hashicorp
Crea un nuevo proveedor de Terraform utilizando el Plugin Framework. Genera un nuevo espacio de trabajo de módulo Go con la convención de nomenclatura estándar "terraform-provider-" e inicializa las dependencias requeridas. Proporciona un archivo main.go de plantilla siguiendo los patrones del Plugin Framework de HashiCorp, con marcadores TODO para personalización. Valida la configuración ejecutando comandos de compilación y prueba para asegurar que el proveedor compile y pase las verificaciones iniciales. Gestiona el espacio de trabajo confirmando la intención antes de crear uno nuevo...
official
azure-verified-modules
hashicorp
Requisitos de certificación y mejores prácticas para módulos de Terraform en Azure que buscan cumplimiento con AVM. Aplica restricciones de versión del proveedor (azurerm >= 4.0, < 5.0; azapi >= 2.0, < 3.0) y prohíbe referencias a módulos basados en git, favoreciendo fuentes fijas del registro de Terraform. Exige el uso de snake_case en minúsculas para todos los identificadores, tipos de variables precisos, atributos de salida discretos mediante el patrón de capa anticorrupción, y locales ordenados alfabéticamente. Requiere variables de activación de características para nuevos recursos agregados...
official