Provider Ephemeral Resources
HashiCorpOfficialPublished by HashiCorp
- Skill
- Tool integration
- Software development
- MPL-2.0
- No issues found
AutoGineer summary
Written by AutoGineer from the skill’s own files, not by the author
This skill provides a guide for implementing ephemeral resources within the Terraform Plugin Framework. Ephemeral resources are designed for values that must never be persisted to state or plan, such as temporary secrets, tokens, or short-lived certificates. The guide covers the Open/Renew/Close lifecycle, schema design, and registration requirements, ensuring developers can securely manage sensitive data that is only needed during the apply phase of a Terraform operation.
What it does
- Defines the Open/Renew/Close lifecycle for non-persistent resources.
- Explains how to register ephemeral resources within a Terraform provider.
- Provides patterns for passing lease identifiers via private data.
- Outlines best practices for sensitive data handling to prevent accidental logging.
- Details the implementation of renewal logic for expiring credentials.
- Guides the decision-making process between using data sources versus ephemeral resources.
When to use it
- When you need to handle sensitive values that should not persist in Terraform state.
- When working with credentials that require periodic renewal during a Terraform run.
- When you need to pass short-lived tokens into provider configurations or write-only attributes.
- When you need to ensure temporary resources are revoked or closed after use.
Advantages
- Prevents sensitive data from being written to disk or stored in state files.
- Provides a structured lifecycle for managing temporary credentials.
- Integrates natively with the Terraform Plugin Framework.
- Supports secure credential rotation through the Renew method.
Usage examples
- “How do I implement an ephemeral resource for a short-lived database token?”
- The agent provides a code template implementing the ephemeral.EphemeralResource interface, including the Open method to fetch the token and the Close method to revoke it.
- “What is the difference between a data source and an ephemeral resource?”
- The agent explains that data sources are for non-sensitive lookups that persist in state, while ephemeral resources are specifically for sensitive data that must never be persisted.
Install
Download the skill as a ZIP:
https://mp-staging.autogineer.com/agent-skills/hashicorp/provider-ephemeral-resources/downloadUnzip it into the cross-client skills folder of your project:
mkdir -p .agents/skills && unzip provider-ephemeral-resources.zip -d .agents/skills
Documentation by HashiCorp
Terraform Provider Ephemeral Resources
Ephemeral resources (Terraform 1.10+) produce values that are never persisted to state or plan. They exist for exactly one job: handing secrets — tokens, generated passwords, short-lived certificates, decrypted values — to the parts of a configuration that need them, without writing them to disk. Any data source that returns a sensitive value is a candidate to be (or to also exist as) an ephemeral resource.
Official docs: Ephemeral Resources.
When to Use One
| Situation | Use |
|---|---|
| Read-only lookup of non-sensitive data | Data source |
| Value is sensitive and only needed at apply time (DB password for a provider block, token for a write-only attribute) | Ephemeral resource |
| Sensitive value that downstream managed resources must store (e.g. as an attribute) | Regular resource/data source — but pair with write-only attributes where possible |
| Credential that expires mid-operation (STS-style tokens, short-TTL leases) | Ephemeral resource with Renew |
Ephemeral results can be used in provider configuration, write-only attributes, provisioner configuration, and other ephemeral contexts — but not in regular attributes, because those persist to state.
Lifecycle
Terraform calls up to three methods per operation:
Open(required) — fetch or create the value; runs during plan and/or apply whenever the result is needed. There is no state to refresh and nothing to import.Renew(optional) — called when the wall clock passes theRenewAtreturned byOpen/Renew, for values that expire while Terraform is still running. Renew cannot return a new result — it can only extend/refresh whatOpenproduced (e.g. re-lease the same credential); if the value itself changes on renewal, the API is not renewable in this sense andOpenmust return a longer-lived value.Close(optional) — called when Terraform is done with the value; revoke leases or delete temporary credentials here.
Open can pass bytes forward via resp.Private; Renew and Close
receive them — use this for lease IDs needed to renew/revoke.
Implementation
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 follows the same ProviderData-cast pattern as resources (the
provider-resources skill, if available, shows it); the client comes from
resp.EphemeralResourceData set in the provider's Configure.
Registration
The provider opts in via provider.ProviderWithEphemeralResources:
var _ provider.ProviderWithEphemeralResources = &examplecloudProvider{}
func (p *examplecloudProvider) EphemeralResources(_ context.Context) []func() ephemeral.EphemeralResource {
return []func() ephemeral.EphemeralResource{
NewTokenEphemeralResource(),
}
}Set resp.EphemeralResourceData = client in the provider's Configure
alongside ResourceData/DataSourceData.
Design Rules
Never log the value, never put it in a diagnostic. The whole point is non-persistence; an error message containing the token defeats it.
Mark the secret attribute
Sensitive: trueanyway — it guards rendering in the ephemeral value's own lifecycle output.No plan modifiers, no import, no
idconvention — there is no state for any of them to act on.Schema inputs follow the same rules as data source arguments; expose the API's identifiers (
role_name), not invented ones.Set
RenewAtwith a safety margin before the real expiry; Terraform renews lazily, not on a precise timer.If the upstream value cannot be revoked, skip
Closerather than implementing a no-op that suggests revocation happens.
Testing
Ephemeral results never reach state, so tests assert them indirectly — the
standard pattern echoes the ephemeral value through the echoprovider into
a regular resource the test can inspect. Minimum coverage: a basic
open-and-use test and per-attribute tests alongside required fields. Use
the provider-test-patterns skill (if available) — its ephemeral testing
reference covers the echoprovider setup, version gating
(tfversion.SkipBelow(tfversion.Version1_10_0)), and multi-step patterns.
Documentation
Registry docs live at docs/ephemeral-resources/<name>.md, generated by
tfplugindocs like every other page type. Use the provider-docs skill
(if available) for the workflow; document the renewal/revocation behavior
explicitly — users need to know whether closing their Terraform run revokes
the credential.
Checklist
[ ] Value genuinely must not persist (otherwise a data source is simpler)
[ ]
Openimplemented;Renew/Closeonly where the API supports them[ ] Secret attributes
Sensitive: true; value never logged or in diagnostics[ ] Lease/handle passed via
Private, not via the result[ ]
RenewAtset with margin for expiring credentials[ ] Registered in
EphemeralResources();EphemeralResourceDataset in provider Configure[ ] Echo-provider acceptance tests, version-gated to Terraform >= 1.10
[ ] Docs page explains lifetime, renewal, and revocation behavior
Files
- ATTRIBUTION.mdAdded by AutoGineer889 B
- LICENSELicence file added by AutoGineer16.3 KiB
- SKILL.md9.1 KiB
Requirements and compatibility
Works with: Claude Code, Claude apps, OpenAI Codex, GitHub Copilot, Cursor, Gemini CLI, Kiro, OpenCode, Goose, Amp, Roo Code, JetBrains Junie, Factory Droid, Devin (ex-Windsurf), Mistral Vibe, OpenHands, Letta
“Works with” means the tool reads the open Agent Skills format; a skill written for one tool may use features others lack.
Security
No issues found by our static scan· No issues found
A static scan is not a guarantee. Skills are third-party content: review a skill before you let an agent use it.
Attribution and licence
- Published by
- HashiCorp
- Publisher
- HashiCorp (Official)
- Source
- hashicorp/agent-skills · plugins/terraform/skills/provider-ephemeral-resources · commit 5e30fa5
- Licence
- Mozilla Public License 2.0 (redistribution allowed)
- LICENSE (added by AutoGineer from LICENSE)
- ATTRIBUTION.md (written by AutoGineer)
- Synced
AutoGineer is not affiliated with or endorsed by HashiCorp.
Download ZIP
The ZIP holds the skill’s files, its licence files and an ATTRIBUTION.md. Download provider-ephemeral-resources.zip