provider-framework-migration

Par hashicorp · agent-skills

Migrer les ressources et sources de données d'un provider Terraform du Plugin SDKv2 vers le Plugin Framework : multiplexage des deux plugins dans un même provider (`terraform-plugin-mux`, `tf5to6server`), workflow de migration par ressource, correspondance de schémas SDKv2→Framework (`ForceNew`, `ValidateFunc`, `DiffSuppressFunc`, `Default`, `Timeouts`, blocs), pièges liés aux valeurs null vs zéro, et vérification de la compatibilité d'état. À utiliser lors de la conversion de ressources SDKv2 vers le Framework, de la mise en place d'un serveur provider multiplexé, pour décider si une ressource doit être migrée, ou pour déboguer des diffs de plan et des erreurs d'état apparus après une migration.

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

Migration de Plugin SDKv2 vers le Plugin Framework

Le Plugin Framework est obligatoire pour les nouvelles ressources et sources de données ; SDKv2 est en maintenance uniquement. La migration est par ressource et incrémentale : un fournisseur muxé servira les implémentations SDKv2 et Framework côte à côte, vous n'aurez donc jamais besoin d'une réécriture complète. Cette compétence couvre la configuration du mux, le flux de travail par ressource, et les pièges comportementaux qui transforment une traduction mécanique en changement cassant silencieux.

Référence (à charger si nécessaire) :

  • references/schema-mapping.md — la table de traduction complète SDKv2 → Framework avec des paires de code

Guide officiel : Framework migration.

Décider si la migration est nécessaire

La migration comporte des risques réels et peu d'avantages visibles pour l'utilisateur, donc triez d'abord :

  • Ne migrez pas les ressources complexes ou très utilisées sans un besoin moteur (une fonctionnalité réservée au Framework, un bug que SDKv2 ne peut pas corriger). Les deux SDK diffèrent comportementalement — surtout autour des valeurs null versus zéro — et ces différences apparaissent comme des changements cassants pour les utilisateurs existants. C'est la politique établie dans les grands fournisseurs comme terraform-provider-aws.
  • Les ressources simples migrent en toute sécurité : schémas plats, pas de DiffSuppressFunc, pas de CustomizeDiff, pas de StateFunc, pas de blocs imbriqués complexes.
  • Les nouvelles capacités ne nécessitent jamais de migrer l'ancien code — muxez et écrivez la nouvelle ressource dans le Framework à côté des anciennes.

Pour savoir dans quel mode se trouve un fournisseur, vérifiez go.mod : terraform-plugin-mux présent signifie qu'il servit déjà les deux ; seul terraform-plugin-sdk/v2 signifie SDKv2-only (la configuration du mux est votre première étape) ; seul terraform-plugin-framework signifie que la migration est terminée.

Étape 1 : Muxer le fournisseur

Combinez les deux serveurs plugin dans main.go. Servir la version de protocole 6 nécessite de mettre à niveau le serveur SDKv2 avec tf5to6server (le protocole 6 nécessite Terraform CLI >= 1.0 ; si vous devez supporter 0.12+, muxez au protocole 5 avec tf6to5server/tf5muxserver à la place — mais le fournisseur Framework ne peut alors pas utiliser les fonctionnalités réservées au protocole 6 comme les attributs imbriqués) :

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)
    }
}

Exigences du mux qui posent problème en pratique :

  • Les schémas de fournisseur doivent correspondre exactement entre les deux plugins — mêmes attributs au niveau du fournisseur, mêmes types, mêmes descriptions. Maintenez une source unique de vérité pour la configuration du fournisseur et refléter-la.
  • Chaque ressource et source de données ne peut exister que dans un seul des deux plugins. L'étape finale de la migration consiste à supprimer l'enregistrement SDKv2.
  • Si vous publiez au Registre avec le protocole 6, définissez "metadata": {"protocol_versions": ["6.0"]} dans terraform-registry-manifest.json.

Étape 2 : Établir une base avant de toucher quoi que ce soit

La ressource migrée doit être indistinguible pour les utilisateurs. Prouvez-le avec des tests qui existent avant la migration :

  1. Assurez-vous que la ressource a une couverture d'acceptation qui passe : _basic avec une étape d'import (ImportStateVerify: true), _disappears, et des tests de mise à jour par attribut. Si la couverture manque, écrivez-la d'abord contre l'implémentation SDKv2 — ces tests sont les critères d'acceptation de la migration et doivent passer inchangés ensuite.
  2. Notez les comportements que les tests ne capturent pas : les valeurs par défaut des attributs, ce qui se passe quand les attributs optionnels sont omis (null vs ""/0/false va bientôt importer), et toute normalisation de DiffSuppressFunc/StateFunc.

Étape 3 : Porter la ressource

Traduisez le schéma et les opérations CRUD à l'aide de la table de mappage dans references/schema-mapping.md. Les règles qui préviennent les changements cassants :

  • Les blocs restent des blocs. Un Elem: &schema.Resource{...} SDKv2 écrit comme syntaxe block { ... } dans les configs utilisateur doit devenir un Block Framework (schema.ListNestedBlock/SetNestedBlock) — le convertir en un attribut imbriqué change la syntaxe HCL que les utilisateurs doivent écrire, ce qui est un changement cassant. Les attributs imbriqués sont réservés aux nouveaux schémas uniquement.
  • Null n'est pas zéro. SDKv2 d.Get("name") retournait "" pour non défini ; le modèle Framework vous donne types.String qui distingue null, unknown, et "". Partout où l'ancien code vérifiait == "" ou dépendait de GetOk, décidez explicitement ce que null signifie, et assurez-vous d'envoyer à l'API la même chose que SDKv2 envoyait (généralement : omettez le champ quand il est null).
  • Gardez l'attribut id. Les nouvelles ressources Framework peuvent omettre un id redondant, mais une ressource migrée doit garder son schéma exact — supprimer ou renommer des attributs casse l'état existant et les configs.
  • L'état doit faire un aller-retour. Le Framework lit l'état que SDKv2 a écrit. Si chaque attribut garde son nom et son type, aucune mise à niveau d'état n'est nécessaire. Si l'ancien schéma stockait une valeur que le nouveau package types normalise différemment, vous avez besoin d'un StateUpgrader — considérez cela comme un signal que la ressource peut être dans le bucket des ressources à ne pas migrer.

Étape 4 : Déplacer l'enregistrement

Enregistrez la ressource dans les Resources() du fournisseur Framework et supprimez-la de ResourcesMap du fournisseur SDKv2 dans le même commit — le mux produit une erreur sur les doublons.

Étape 5 : Vérifier

  1. Les tests d'acceptation préexistants passent sans modification — surtout ImportStateVerify, qui compare l'état importé avec l'état stocké et détecte la plupart des régressions null-vs-zéro.
  2. Ajoutez une étape de compatibilité d'état : appliquez une config avec la dernière version (SDKv2) publiée du fournisseur, puis planifiez avec la version migrée — le plan doit être vide. Dans terraform-plugin-testing c'est un test en deux étapes utilisant ExternalProviders pour l'ancienne version, puis ProtoV6ProviderFactories avec ConfigPlanChecks affirmant un plan vide. La compétence provider-test-patterns (si disponible) documente le pattern.
  3. terraform plan contre un véritable fichier d'état avant migration ne montre aucune différence.

Liste de contrôle

  • [ ] La ressource est assez simple pour migrer (pas de personnalisation de différence complexe), ou il y a un besoin moteur
  • [ ] Le mux servit les deux plugins ; les schémas au niveau du fournisseur sont identiques dans les deux
  • [ ] Les tests d'acceptation existaient avant la migration et passent inchangés après
  • [ ] Les blocs sont restés des blocs ; les noms et types d'attributs inchangés ; id conservé
  • [ ] La sémantique null/omise préservée (l'API reçoit ce que SDKv2 envoyait)
  • [ ] L'enregistrement SDKv2 supprimé dans le même changement
  • [ ] Plan vide vérifié contre l'état écrit par la version précédente
  • [ ] Entrée du journal des modifications ajoutée, si le dépôt suit les notes de version

Compétences liées

Utilisez la compétence provider-resources (si disponible) pour les patterns Framework CRUD, finder et waiter dans le code porté, et provider-test-patterns pour les patterns de test de régression et de mise à niveau de version.

Skills similaires