Ressources Éphémères du Fournisseur Terraform
Les ressources éphémères (Terraform 1.10+) produisent des valeurs qui ne sont jamais persistées dans l'état ou le plan. Elles existent pour une seule raison : transmettre des secrets — tokens, mots de passe générés, certificats de courte durée, valeurs déchiffrées — aux parties de la configuration qui en ont besoin, sans les écrire sur le disque. Toute source de données qui retourne une valeur sensible est candidate pour être (ou aussi exister comme) une ressource éphémère.
Documentation officielle : Ephemeral Resources.
Quand en utiliser une
| Situation | Utiliser |
|---|---|
| Recherche en lecture seule de données non sensibles | Source de données |
| Valeur sensible et nécessaire uniquement au moment de l'apply (mot de passe DB pour un bloc provider, token pour un attribut en écriture seule) | Ressource éphémère |
| Valeur sensible que les ressources gérées en aval doivent stocker (par ex. comme attribut) | Ressource/source de données classique — mais utiliser des attributs en écriture seule si possible |
| Credential qui expire en cours d'opération (tokens style STS, leases courte durée) | Ressource éphémère avec Renew |
Les résultats éphémères peuvent être utilisés dans la configuration provider, les attributs en écriture seule, la configuration du provisioner et autres contextes éphémères — mais pas dans les attributs classiques, car ceux-ci persistent dans l'état.
Cycle de vie
Terraform appelle jusqu'à trois méthodes par opération :
Open(obligatoire) — récupérer ou créer la valeur ; s'exécute pendant le plan et/ou l'apply chaque fois que le résultat est nécessaire. Il n'y a pas d'état à rafraîchir et rien à importer.Renew(optionnel) — appelé quand l'horloge système dépasse leRenewAtretourné parOpen/Renew, pour les valeurs qui expirent pendant que Terraform s'exécute. Renew ne peut pas retourner un nouveau résultat — il peut seulement étendre/rafraîchir ce queOpena produit (par ex. relancer le même credential) ; si la valeur elle-même change au renouvellement, l'API n'est pas renouvelable dans ce sens etOpendoit retourner une valeur plus durable.Close(optionnel) — appelé quand Terraform a fini avec la valeur ; révoquer les leases ou supprimer les credentials temporaires ici.
Open peut transmettre des octets via resp.Private ; Renew et Close les reçoivent — utiliser cela pour les IDs de lease nécessaires au renouvellement/révocation.
Implémentation
var (
_ ephemeral.EphemeralResource = &tokenEphemeralResource{}
_ ephemeral.EphemeralResourceWithConfigure = &tokenEphemeralResource{}
_ ephemeral.EphemeralResourceWithRenew = &tokenEphemeralResource{}
_ ephemeral.EphemeralResourceWithClose = &tokenEphemeralResource{}
)
func NewTokenEphemeralResource() ephemeral.EphemeralResource {
return &tokenEphemeralResource{}
}
type tokenEphemeralResource struct {
client *examplecloud.Client
}
type tokenEphemeralResourceModel struct {
RoleName types.String `tfsdk:"role_name"`
Token types.String `tfsdk:"token"`
LeaseID types.String `tfsdk:"lease_id"`
}
func (r *tokenEphemeralResource) Metadata(_ context.Context, req ephemeral.MetadataRequest, resp *ephemeral.MetadataResponse) {
resp.TypeName = req.ProviderTypeName + "_token"
}
func (r *tokenEphemeralResource) Schema(_ context.Context, _ ephemeral.SchemaRequest, resp *ephemeral.SchemaResponse) {
resp.Schema = schema.Schema{
Attributes: map[string]schema.Attribute{
"role_name": schema.StringAttribute{
Required: true,
MarkdownDescription: "Role to obtain a token for.",
},
"token": schema.StringAttribute{
Computed: true,
Sensitive: true,
MarkdownDescription: "The issued token. Never persisted to state.",
},
"lease_id": schema.StringAttribute{
Computed: true,
MarkdownDescription: "Identifier of the token lease.",
},
},
}
}
func (r *tokenEphemeralResource) Open(ctx context.Context, req ephemeral.OpenRequest, resp *ephemeral.OpenResponse) {
var data tokenEphemeralResourceModel
resp.Diagnostics.Append(req.Config.Get(ctx, &data)...)
if resp.Diagnostics.HasError() {
return
}
lease, err := r.client.IssueToken(ctx, data.RoleName.ValueString())
if err != nil {
resp.Diagnostics.AddError(
"Error opening Token",
fmt.Sprintf("issuing token for role (%s): %s", data.RoleName.ValueString(), err),
)
return
}
data.Token = types.StringValue(lease.Token)
data.LeaseID = types.StringValue(lease.ID)
resp.RenewAt = lease.ExpiresAt.Add(-2 * time.Minute) // renew with margin
resp.Private.SetKey(ctx, "lease_id", []byte(lease.ID))
resp.Diagnostics.Append(resp.Result.Set(ctx, &data)...)
}
func (r *tokenEphemeralResource) Renew(ctx context.Context, req ephemeral.RenewRequest, resp *ephemeral.RenewResponse) {
leaseID, diags := req.Private.GetKey(ctx, "lease_id")
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
lease, err := r.client.RenewLease(ctx, string(leaseID))
if err != nil {
resp.Diagnostics.AddError("Error renewing Token", err.Error())
return
}
resp.RenewAt = lease.ExpiresAt.Add(-2 * time.Minute)
}
func (r *tokenEphemeralResource) Close(ctx context.Context, req ephemeral.CloseRequest, resp *ephemeral.CloseResponse) {
leaseID, diags := req.Private.GetKey(ctx, "lease_id")
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
if err := r.client.RevokeLease(ctx, string(leaseID)); err != nil {
resp.Diagnostics.AddError("Error closing Token", err.Error())
}
}
Configure suit le même pattern de transtypage ProviderData que les ressources (la skill provider-resources, si disponible, le montre) ; le client provient de resp.EphemeralResourceData défini dans Configure du provider.
Enregistrement
Le provider se déclare via provider.ProviderWithEphemeralResources :
var _ provider.ProviderWithEphemeralResources = &examplecloudProvider{}
func (p *examplecloudProvider) EphemeralResources(_ context.Context) []func() ephemeral.EphemeralResource {
return []func() ephemeral.EphemeralResource{
NewTokenEphemeralResource(),
}
}
Définir resp.EphemeralResourceData = client dans Configure du provider à côté de ResourceData/DataSourceData.
Règles de conception
- Ne jamais logger la valeur, ne jamais la mettre dans un diagnostic. L'idée est la non-persistance ; un message d'erreur contenant le token le défait.
- Marquer quand même l'attribut secret avec
Sensitive: true— cela protège le rendu dans la sortie du cycle de vie de la valeur éphémère elle-même. - Pas de plan modifiers, pas d'import, pas de convention
id— il n'y a pas d'état sur lequel ils agiraient. - Les entrées du schéma suivent les mêmes règles que les arguments de source de données ; exposer les identifiants de l'API (
role_name), pas d'identifiants inventés. - Définir
RenewAtavec une marge de sécurité avant l'expiration réelle ; Terraform renouvelle paresseusement, pas sur une minuterie précise. - Si la valeur en amont ne peut pas être révoquée, ignorer
Closeplutôt que d'implémenter un no-op qui suggère que la révocation se produit.
Tests
Les résultats éphémères ne rejoignent jamais l'état, donc les tests les affirment indirectement — le pattern standard fait échouer la valeur éphémère à travers le echoprovider dans une ressource classique que le test peut inspecter. Couverture minimale : un test basique open-and-use et des tests par attribut à côté des champs obligatoires. Utiliser la skill provider-test-patterns (si disponible) — sa référence de test éphémère couvre la configuration echoprovider, le version gating (tfversion.SkipBelow(tfversion.Version1_10_0)), et les patterns multi-étapes.
Documentation
La documentation du registre se trouve dans docs/ephemeral-resources/<name>.md, générée par tfplugindocs comme tout autre type de page. Utiliser la skill provider-docs (si disponible) pour le workflow ; documenter explicitement le comportement de renouvellement/révocation — les utilisateurs doivent savoir si fermer leur exécution Terraform révoque le credential.
Checklist
- [ ] Valeur qui ne doit vraiment pas persister (sinon une source de données est plus simple)
- [ ]
Openimplémenté ;Renew/Closeseulement où l'API les supporte - [ ] Attributs secret avec
Sensitive: true; valeur jamais loggée ou dans les diagnostics - [ ] Lease/handle transmis via
Private, pas via le résultat - [ ]
RenewAtdéfini avec marge pour les credentials expirant - [ ] Enregistré dans
EphemeralResources();EphemeralResourceDatadéfini dans provider Configure - [ ] Tests d'acceptance echo-provider, version-gatés à Terraform >= 1.10
- [ ] Page de docs expliquant la durée de vie, le renouvellement et le comportement de révocation