SKILL·B672D7

provider-ephemeral-resources

hashicorp
更新于 Today
797
117
797
在 GitHub 上查看
设计designdata

关于

This skill guides developers in implementing Terraform provider ephemeral resources using the Plugin Framework, which securely manage short-lived credentials like tokens and certificates without persisting them in state. It covers the Open/Renew/Close lifecycle, schema design, and integration with write-only attributes and provider configuration. Use this when you need to expose sensitive, temporary values or wire credentials between providers while ensuring they never appear in plans or state files.

快速安装

Claude Code

推荐
主要方式
npx skills add hashicorp/agent-skills -a claude-code
插件命令备选方式
/plugin add https://github.com/hashicorp/agent-skills
Git 克隆备选方式
git clone https://github.com/hashicorp/agent-skills.git ~/.claude/skills/provider-ephemeral-resources

在 Claude Code 中复制并粘贴此命令以安装该技能

技能文档

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

SituationUse
Read-only lookup of non-sensitive dataData 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 the RenewAt returned by Open/Renew, for values that expire while Terraform is still running. Renew cannot return a new result — it can only extend/refresh what Open produced (e.g. re-lease the same credential); if the value itself changes on renewal, the API is not renewable in this sense and Open must 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: true anyway — it guards rendering in the ephemeral value's own lifecycle output.
  • No plan modifiers, no import, no id convention — 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 RenewAt with a safety margin before the real expiry; Terraform renews lazily, not on a precise timer.
  • If the upstream value cannot be revoked, skip Close rather 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)
  • Open implemented; Renew/Close only where the API supports them
  • Secret attributes Sensitive: true; value never logged or in diagnostics
  • Lease/handle passed via Private, not via the result
  • RenewAt set with margin for expiring credentials
  • Registered in EphemeralResources(); EphemeralResourceData set in provider Configure
  • Echo-provider acceptance tests, version-gated to Terraform >= 1.10
  • Docs page explains lifetime, renewal, and revocation behavior

GitHub 仓库

hashicorp/agent-skills
路径: plugins/terraform/skills/provider-ephemeral-resources
0
doormat-managed
FAQ

常见问题

什么是 provider-ephemeral-resources Skill?

provider-ephemeral-resources 是一个 Claude Skill,作者为 hashicorp。Skill 将 Claude 按需加载的说明和资源打包,让 Claude 无需额外提示即可执行与 provider-ephemeral-resources 相关的任务。

如何安装 provider-ephemeral-resources?

使用本页的安装命令:将 provider-ephemeral-resources 作为插件添加到 Claude Code,或将其仓库克隆到 skills 目录,然后重启 Claude 以加载该 Skill。

provider-ephemeral-resources 属于哪个分类?

provider-ephemeral-resources 属于设计分类。

provider-ephemeral-resources 可以免费使用吗?

可以。provider-ephemeral-resources 已收录在 AIMCP,可免费安装。

相关推荐技能

executing-plans
设计

该Skill用于当开发者提供完整实施计划时,以受控批次方式执行代码实现。它会先审阅计划并提出疑问,然后分批次执行任务(默认每批3个任务),并在批次间暂停等待审查。关键特性包括分批次执行、内置检查点和架构师审查机制,确保复杂系统实现的可控性。

查看技能
requesting-code-review
设计

该Skill可在完成任务、实现主要功能或合并代码前自动调度代码审查子代理,确保实现符合需求和计划。它支持通过指定git SHA范围进行精准的代码变更审查,帮助开发者在关键节点及时发现潜在问题。核心原则是"早审查、勤审查",适用于开发流程的各个关键阶段。

查看技能
connect-mcp-server
设计

这个Skill指导开发者如何将MCP服务器连接到Claude Code,支持HTTP、stdio和SSE三种传输协议。它涵盖了从安装配置到认证安全的完整流程,适用于集成GitHub、Notion、数据库等外部服务。当开发者需要添加集成、配置外部工具或提及MCP相关功能时,这个Skill能提供实用的操作指南。

查看技能
web-cli-teleport
设计

该Skill帮助开发者根据任务特性选择Claude Code的Web或CLI界面,并指导如何在两种环境间无缝迁移会话。它能分析任务复杂度、迭代需求等要素,推荐最优工作界面和工作流。关键特性包括会话状态管理、环境切换指导和上下文优化建议。

查看技能