Guide d'implémentation des Actions Terraform Provider
Vue d'ensemble
Les Actions Terraform permettent des opérations impératives pendant le cycle de vie de Terraform. Les Actions sont des fonctionnalités expérimentales permettant d'effectuer des opérations provider à des événements de cycle de vie spécifiques (avant/après création, mise à jour, suppression).
Références :
Structure des fichiers
Les Actions suivent la structure standard du paquet de service :
internal/service/<service>/
├── <action_name>_action.go # Implémentation de l'action
├── <action_name>_action_test.go # Tests de l'action
└── service_package_gen.go # Enregistrement de service auto-généré
Structure de la documentation :
website/docs/actions/
└── <service>_<action_name>.html.markdown # Documentation destinée aux utilisateurs
Entrée du changelog :
.changelog/
└── <pr_number_or_description>.txt # Entrée de note de version
Définition du schéma d'action
Les Actions utilisent le Terraform Plugin Framework avec un motif de schéma standard :
func (a *actionType) Schema(ctx context.Context, req action.SchemaRequest, resp *action.SchemaResponse) {
resp.Schema = schema.Schema{
Attributes: map[string]schema.Attribute{
// Paramètres de configuration requis
"resource_id": schema.StringAttribute{
Required: true,
Description: "ID de la ressource à exploiter",
},
// Paramètres optionnels avec valeurs par défaut
"timeout": schema.Int64Attribute{
Optional: true,
Description: "Délai d'expiration de l'opération en secondes",
Default: int64default.StaticInt64(1800),
Computed: true,
},
},
}
}
Problèmes de schéma courants
Accordez une attention particulière à la définition du schéma - les problèmes courants après un premier brouillon :
-
Incompatibilités de type
- Utiliser
types.Stringau lieu defwtypes.Stringdans les structs de modèle - Utiliser
types.StringTypeau lieu defwtypes.StringTypedans le schéma - Mélanger les types framework avec les types plugin-framework
- Utiliser
-
Types d'éléments de liste/map
// INCORRECT - ElementType manquant "items": schema.ListAttribute{ Optional: true, } // CORRECT "items": schema.ListAttribute{ Optional: true, ElementType: fwtypes.StringType, } -
Computed vs Optional
- Les attributs avec valeurs par défaut doivent être à la fois
Optional: trueetComputed: true - Ne marquez pas les entrées d'action comme
Computedsauf s'ils ont des valeurs par défaut
- Les attributs avec valeurs par défaut doivent être à la fois
-
Imports de validateurs
// Assurez-vous les imports corrects "github.com/hashicorp/terraform-plugin-framework-validators/int64validator" "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" -
Attribut région/provider
- Utilisez la gestion de région fournie par le framework quand elle est disponible
- Ne définissez pas manuellement la config spécifique au provider dans le schéma si le framework la gère
-
Attributs imbriqués
- Utilisez les types d'objets imbriqués appropriés pour les structures complexes
- Assurez-vous que les types imbriqués sont correctement définis
Liste de vérification de validation du schéma
Avant de soumettre, vérifiez :
- [ ] Tous les attributs ont des descriptions
- [ ] Les attributs liste/map ont ElementType défini
- [ ] Les validateurs sont importés et appliqués correctement
- [ ] Le struct de modèle utilise les types framework corrects
- [ ] Les attributs optionnels avec valeurs par défaut sont marqués Computed
- [ ] Le code compile sans erreurs de type
- [ ] Exécutez
go buildpour détecter les incompatibilités de type
Méthode Invoke de l'action
La méthode Invoke contient la logique de l'action :
func (a *actionType) Invoke(ctx context.Context, req action.InvokeRequest, resp *action.InvokeResponse) {
var data actionModel
resp.Diagnostics.Append(req.Config.Get(ctx, &data)...)
// Créer un client provider
conn := a.Meta().Client(ctx)
// Mises à jour de progression pour les opérations longues
resp.Progress.Set(ctx, "Démarrage de l'opération...")
// Implémenter la logique de l'action avec gestion d'erreur
// Utiliser le contexte pour la gestion du délai d'expiration
// Interroger la completion si opération asynchrone
resp.Progress.Set(ctx, "Opération terminée")
}
Exigences de mise en œuvre clés
1. Rapports de progression
- Utilisez
resp.SendProgress(action.InvokeProgressEvent{...})pour les mises à jour en temps réel - Fournissez des messages de progression significatifs lors d'opérations longues
- Mettez à jour la progression aux jalons clés
- Incluez le temps écoulé pour les opérations longues
2. Gestion des délais d'expiration
- Incluez toujours un paramètre de délai d'expiration configurable (par défaut : 1800s)
- Utilisez
context.WithTimeout()pour les appels API - Gérez les erreurs de délai d'expiration avec grâce
- Validez les plages de délai d'expiration (généralement 60-7200 secondes)
3. Gestion des erreurs
- Ajoutez des diagnostiques avec
resp.Diagnostics.AddError() - Fournissez des messages d'erreur clairs avec contexte
- Incluez les détails d'erreur API quand pertinent
- Mappez les types d'erreur du provider à des messages conviviaux
- Documentez tous les cas d'erreur possibles
Exemple de gestion d'erreur :
// Gérer les erreurs spécifiques
var notFound *types.ResourceNotFoundException
if errors.As(err, ¬Found) {
resp.Diagnostics.AddError(
"Ressource non trouvée",
fmt.Sprintf("La ressource %s n'a pas été trouvée", resourceID),
)
return
}
// Gestion d'erreur générique
resp.Diagnostics.AddError(
"Opération échouée",
fmt.Sprintf("Impossible de compléter l'opération pour %s : %s", resourceID, err),
)
4. Intégration du SDK Provider
- Utilisez les clients SDK du provider depuis
a.Meta().<Service>Client(ctx) - Gérez la pagination pour les opérations de liste
- Implémentez la logique de tentative pour les défaillances transitoires
- Utilisez les types d'erreur appropriés
5. Validation des paramètres
- Utilisez les validateurs framework pour la validation des entrées
- Validez l'existence des ressources avant les opérations
- Vérifiez les paramètres conflictuels
- Validez selon les exigences de nommage du provider
6. Interrogation et attente
Pour les opérations qui nécessitent une attente de completion :
result, err := wait.WaitForStatus(ctx,
func(ctx context.Context) (wait.FetchResult[*ResourceType], error) {
// Récupérer le statut courant
resource, err := findResource(ctx, conn, id)
if err != nil {
return wait.FetchResult[*ResourceType]{}, err
}
return wait.FetchResult[*ResourceType]{
Status: wait.Status(resource.Status),
Value: resource,
}, nil
},
wait.Options[*ResourceType]{
Timeout: timeout,
Interval: wait.FixedInterval(5 * time.Second),
SuccessStates: []wait.Status{"AVAILABLE", "COMPLETED"},
TransitionalStates: []wait.Status{"CREATING", "PENDING"},
ProgressInterval: 30 * time.Second,
ProgressSink: func(fr wait.FetchResult[any], meta wait.ProgressMeta) {
resp.SendProgress(action.InvokeProgressEvent{
Message: fmt.Sprintf("Statut : %s, Écoulé : %v", fr.Status, meta.Elapsed.Round(time.Second)),
})
},
},
)
Motifs d'action courants
Opérations par lot
- Traiter les éléments par lots configurables
- Rapporter la progression par lot
- Gérer les défaillances partielles avec grâce
- Supporter les paramètres de préfixe/filtre
Exécution de commande
- Soumettre une commande et obtenir un ID d'opération
- Interroger le statut de completion
- Récupérer et rapporter la sortie
- Gérer le délai d'expiration lors de l'interrogation
- Valider que les ressources existent avant l'exécution
Invocation de service
- Invoquer le service avec des paramètres
- Attendre la completion (si synchrone)
- Retourner la sortie/résultats
- Gérer les erreurs spécifiques au service
Changements d'état de ressource
- Valider l'état actuel
- Appliquer le changement d'état
- Interroger pour l'état cible
- Gérer les états transitoires
Soumission de travail asynchrone
- Soumettre un travail avec configuration
- Obtenir l'ID du travail
- Optionnellement attendre la completion
- Rapporter le statut du travail
Déclencheurs d'action
Les Actions sont invoquées via des blocs de cycle de vie action_trigger dans les configurations Terraform :
action "provider_service_action" "name" {
config {
parameter = value
}
}
resource "terraform_data" "trigger" {
lifecycle {
action_trigger {
events = [after_create]
actions = [action.provider_service_action.name]
}
}
}
Événements de déclenchement disponibles
Événements supportés dans Terraform 1.14.0 :
before_create- Avant la création de ressourceafter_create- Après la création de ressourcebefore_update- Avant la mise à jour de ressourceafter_update- Après la mise à jour de ressource
Non supportés dans Terraform 1.14.0 :
before_destroy- Non disponible (causera une erreur de validation)after_destroy- Non disponible (causera une erreur de validation)
Tests des actions
Tests d'acceptation
- Tester l'invocation d'action avec des paramètres valides
- Tester les scénarios de délai d'expiration
- Tester les conditions d'erreur
- Vérifier les changements d'état du provider
- Tester les rapports de progression
- Tester avec des paramètres personnalisés
- Tester l'invocation basée sur un déclencheur
Motif de test
func TestAccServiceAction_basic(t *testing.T) {
ctx := acctest.Context(t)
resource.ParallelTest(t, resource.TestCase{
PreCheck: func() { acctest.PreCheck(ctx, t) },
ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories,
TerraformVersionChecks: []tfversion.TerraformVersionCheck{
tfversion.SkipBelow(tfversion.Version1_14_0),
},
Steps: []resource.TestStep{
{
Config: testAccActionConfig_basic(),
Check: resource.ComposeTestCheckFunc(
testAccCheckResourceExists(ctx, "provider_resource.test"),
),
},
},
})
}
Nettoyage des tests avec fonctions Sweep
Ajoutez des fonctions sweep pour nettoyer les ressources de test :
func sweepResources(region string) error {
ctx := context.Background()
client := /* obtenir le client pour la région */
input := &service.ListInput{
// Filtrer les ressources de test
}
var sweeperErrs *multierror.Error
pages := service.NewListPaginator(client, input)
for pages.HasMorePages() {
page, err := pages.NextPage(ctx)
if err != nil {
sweeperErrs = multierror.Append(sweeperErrs, err)
continue
}
for _, item := range page.Items {
id := item.Id
// Ignorer les ressources non-test
if !strings.HasPrefix(id, "tf-acc-test") {
continue
}
_, err := client.Delete(ctx, &service.DeleteInput{
Id: id,
})
if err != nil {
sweeperErrs = multierror.Append(sweeperErrs, err)
}
}
}
return sweeperErrs.ErrorOrNil()
}
Meilleures pratiques de test
Prérequis spécifiques au service
- Vérifiez toujours les prérequis spécifiques au service qui doivent être satisfaits avant que les actions ne réussissent
- Documentez les prérequis dans la documentation d'action et les configurations de test
Correspondance de motif d'erreur
- Terraform encapsule les erreurs d'action avec contexte supplémentaire
- Utilisez des motifs regex flexibles :
regexache.MustCompile(\(?s)Titre d'erreur.*phrase clé`)`
Motifs de test non applicables aux actions
- Les actions se déclenchent sur des événements de cycle de vie, pas sur réapplication de config
- Tests avant/après destruction : Non supportés dans Terraform 1.14.0
Exécution des tests
Compilez le test pour vérifier les erreurs :
go test -c -o /dev/null ./internal/service/<service>
Exécutez des tests d'action spécifiques :
TF_ACC=1 go test ./internal/service/<service> -run TestAccServiceAction_ -v
Exécutez sweep pour nettoyer les ressources de test :
TF_ACC=1 go test ./internal/service/<service> -sweep=<region> -v
Normes de documentation
Chaque fichier de documentation d'action doit inclure :
-
Front Matter
--- subcategory: "Nom du service" layout: "provider" page_title: "Provider: provider_service_action" description: |- Description brève de ce que fait l'action. --- -
En-tête avec avertissements
- Avis de statut bêta/alpha sur l'état expérimental
- Avertissement sur les conséquences non intentionnelles potentielles
- Lien vers la documentation du provider
-
Exemple d'utilisation
- Exemple d'utilisation basique
- Utilisation avancée avec toutes les options
- Exemple basé sur un déclencheur avec
terraform_data - Exemples de cas d'utilisation réels
-
Référence d'argument
- Lister tous les arguments requis et optionnels
- Incluez les descriptions et valeurs par défaut
- Notez toutes les règles de validation
-
Linting de documentation
- Exécutez
terrafmt fmtavant la soumission - Vérifiez avec
terrafmt diff
- Exécutez
Format d'entrée du changelog
Créez une entrée de changelog dans le répertoire .changelog/ :
.changelog/<pr_number_or_description>.txt
Format du contenu :
action/provider_service_action : Description brève de l'action
Liste de vérification avant soumission
Avant de soumettre votre implémentation d'action :
- [ ] Le code compile :
go build -o /dev/null . - [ ] Les tests compilent :
go test -c -o /dev/null ./internal/service/<service> - [ ] Code formaté :
make fmt - [ ] Documentation formatée :
terrafmt fmt website/docs/actions/<action>.html.markdown - [ ] Entrée de changelog créée
- [ ] Le schéma utilise les types corrects
- [ ] Tous les attributs liste/map ont ElementType
- [ ] Mises à jour de progression implémentées pour les opérations longues
- [ ] Les messages d'erreur incluent le contexte et les identifiants de ressource
- [ ] La documentation inclut plusieurs exemples
- [ ] La documentation inclut les prérequis et les avertissements