Skip to content

Commit

Permalink
Merge pull request #1627 from hashicorp/ephemeral-agent-token
Browse files Browse the repository at this point in the history
feat: add tfe_agent_token ephemeral resource
  • Loading branch information
uturunku1 authored Mar 4, 2025
2 parents 7156f0f + 91ecb24 commit 5fd2184
Show file tree
Hide file tree
Showing 6 changed files with 255 additions and 1 deletion.
1 change: 0 additions & 1 deletion GNUmakefile
Original file line number Diff line number Diff line change
Expand Up @@ -64,4 +64,3 @@ test-compile:
go test -c $(TEST) $(TESTARGS)

.PHONY: build test testacc vet fmt fmtcheck errcheck test-compile sweep

1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ require (
github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect
github.com/cloudflare/circl v1.5.0 // indirect
github.com/hashicorp/hc-install v0.9.1 // indirect
github.com/hashicorp/terraform-plugin-testing v1.11.0 // indirect
github.com/hashicorp/terraform-registry-address v0.2.4 // indirect
github.com/kr/text v0.2.0 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
Expand Down
120 changes: 120 additions & 0 deletions internal/provider/ephemeral_resource_agent_token.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0

package provider

import (
"context"
"fmt"
"log"

tfe "github.com/hashicorp/go-tfe"
"github.com/hashicorp/terraform-plugin-framework/ephemeral"
"github.com/hashicorp/terraform-plugin-framework/ephemeral/schema"
"github.com/hashicorp/terraform-plugin-framework/types"
)

var (
_ ephemeral.EphemeralResource = &AgentTokenEphemeralResource{}
)

func NewAgentTokenEphemeralResource() ephemeral.EphemeralResource {
return &AgentTokenEphemeralResource{}
}

type AgentTokenEphemeralResource struct {
config ConfiguredClient
}

type AgentTokenEphemeralResourceModel struct {
AgentPoolID types.String `tfsdk:"agent_pool_id"`
Description types.String `tfsdk:"description"`
Token types.String `tfsdk:"token"`
}

// defines a schema describing what data is available in the ephemeral resource's configuration and result data.
func (e *AgentTokenEphemeralResource) Schema(ctx context.Context, req ephemeral.SchemaRequest, resp *ephemeral.SchemaResponse) {
resp.Schema = schema.Schema{
Description: "This ephemeral resource can be used to retrieve an agent token without saving its value in state.",
Attributes: map[string]schema.Attribute{
"agent_pool_id": schema.StringAttribute{
Description: `ID of the agent. If omitted, agent must be defined in the provider config.`,
Required: true,
},
"description": schema.StringAttribute{
Description: `Description of the agent token.`,
Required: true,
},
"token": schema.StringAttribute{
Description: `The generated token.`,
Computed: true,
Sensitive: true,
},
},
}
}

// Configure adds the provider configured client to the data source.
func (e *AgentTokenEphemeralResource) Configure(_ context.Context, req ephemeral.ConfigureRequest, resp *ephemeral.ConfigureResponse) {
if req.ProviderData == nil {
return
}

client, ok := req.ProviderData.(ConfiguredClient)
if !ok {
resp.Diagnostics.AddError(
"Unexpected Ephemeral Resource Configure Type",
fmt.Sprintf("Expected tfe.ConfiguredClient, got %T. This is a bug in the tfe provider, so please report it on GitHub.", req.ProviderData),
)

return
}
e.config = client
}

func (e *AgentTokenEphemeralResource) Metadata(ctx context.Context, req ephemeral.MetadataRequest, resp *ephemeral.MetadataResponse) {
resp.TypeName = req.ProviderTypeName + "_agent_token" // tfe_agent_token
}

// The request contains the configuration supplied to Terraform for the ephemeral resource. The response contains the ephemeral result data. The data is defined by the schema of the ephemeral resource.
func (e *AgentTokenEphemeralResource) Open(ctx context.Context, req ephemeral.OpenRequest, resp *ephemeral.OpenResponse) {
var data AgentTokenEphemeralResourceModel

// Read Terraform config data into the model
resp.Diagnostics.Append(req.Config.Get(ctx, &data)...)
if resp.Diagnostics.HasError() {
return
}

agentPoolID := data.AgentPoolID.ValueString()
description := data.Description.ValueString()

options := tfe.AgentTokenCreateOptions{
Description: tfe.String(description),
}

log.Printf("[DEBUG] Create new agent token for agent pool ID: %s", agentPoolID)
log.Printf("[DEBUG] Create new agent token with description: %s", description)

result, err := e.config.Client.AgentTokens.Create(ctx, agentPoolID, options)

if err != nil {
resp.Diagnostics.AddError("Unable to create agent token", err.Error())
return
}

data = ephemeralResourceModelFromTFEagentToken(agentPoolID, result)

// Save to ephemeral result data
resp.Diagnostics.Append(resp.Result.Set(ctx, &data)...)
}

// ephemeralResourceModelFromTFEagentToken builds a agentTokenEphemeralResourceModel struct from a
// tfe.agentToken value.
func ephemeralResourceModelFromTFEagentToken(id string, v *tfe.AgentToken) AgentTokenEphemeralResourceModel {
return AgentTokenEphemeralResourceModel{
AgentPoolID: types.StringValue(id),
Description: types.StringValue(v.Description),
Token: types.StringValue(v.Token),
}
}
66 changes: 66 additions & 0 deletions internal/provider/ephemeral_resource_agent_token_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0

package provider

import (
"fmt"
"regexp"
"testing"

"github.com/hashicorp/terraform-plugin-go/tfprotov6"
"github.com/hashicorp/terraform-plugin-testing/echoprovider"
"github.com/hashicorp/terraform-plugin-testing/helper/resource"
"github.com/hashicorp/terraform-plugin-testing/knownvalue"
"github.com/hashicorp/terraform-plugin-testing/statecheck"
"github.com/hashicorp/terraform-plugin-testing/tfjsonpath"
"github.com/hashicorp/terraform-plugin-testing/tfversion"
)

func TestAccagentTokenEphemeralResource_basic(t *testing.T) {
tfeClient, err := getClientUsingEnv()
if err != nil {
t.Fatal(err)
}

org, orgCleanup := createBusinessOrganization(t, tfeClient)
t.Cleanup(orgCleanup)

resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
TerraformVersionChecks: []tfversion.TerraformVersionCheck{
tfversion.SkipBelow(tfversion.Version1_10_0),
},
ProtoV5ProviderFactories: testAccMuxedProviders,
ProtoV6ProviderFactories: map[string]func() (tfprotov6.ProviderServer, error){
"echo": echoprovider.NewProviderServer(),
},
Steps: []resource.TestStep{
{
Config: testAccAgentTokenEphemeralResourceConfig(org.Name),
ConfigStateChecks: []statecheck.StateCheck{
statecheck.ExpectKnownValue("echo.this", tfjsonpath.New("data"), knownvalue.StringRegexp(regexp.MustCompile(`^[a-zA-Z0-9]+\.atlasv1\.[a-zA-Z0-9]+$`))),
},
},
},
})
}

func testAccAgentTokenEphemeralResourceConfig(orgName string) string {
return fmt.Sprintf(`
resource "tfe_agent_pool" "foobar" {
name = "agent-pool-test"
organization = "%s"
}
ephemeral "tfe_agent_token" "this" {
agent_pool_id = tfe_agent_pool.foobar.id
description = "agent-token-test"
}
provider "echo" {
data = ephemeral.tfe_agent_token.this.token
}
resource "echo" "this" {}
`, orgName)
}
9 changes: 9 additions & 0 deletions internal/provider/provider_next.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"os"

"github.com/hashicorp/terraform-plugin-framework/datasource"
"github.com/hashicorp/terraform-plugin-framework/ephemeral"
"github.com/hashicorp/terraform-plugin-framework/provider"
"github.com/hashicorp/terraform-plugin-framework/provider/schema"
"github.com/hashicorp/terraform-plugin-framework/resource"
Expand All @@ -22,6 +23,7 @@ type frameworkProvider struct{}

// Compile-time interface check
var _ provider.Provider = &frameworkProvider{}
var _ provider.ProviderWithEphemeralResources = &frameworkProvider{}

// FrameworkProviderConfig is a helper type for extracting the provider
// configuration from the provider block.
Expand Down Expand Up @@ -109,6 +111,7 @@ func (p *frameworkProvider) Configure(ctx context.Context, req provider.Configur

res.DataSourceData = configuredClient
res.ResourceData = configuredClient
res.EphemeralResourceData = configuredClient
}

func (p *frameworkProvider) DataSources(ctx context.Context) []func() datasource.DataSource {
Expand Down Expand Up @@ -143,3 +146,9 @@ func (p *frameworkProvider) Resources(ctx context.Context) []func() resource.Res
NewWorkspaceRunTaskResource,
}
}

func (p *frameworkProvider) EphemeralResources(ctx context.Context) []func() ephemeral.EphemeralResource {
return []func() ephemeral.EphemeralResource{
NewAgentTokenEphemeralResource,
}
}
59 changes: 59 additions & 0 deletions website/docs/ephemeral-resources/agent_token.html.markdown
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
---
layout: "tfe"
page_title: "Terraform Enterprise: tfe_agent_token"
description: |-
Generates an ephemeral agent token.
---

# tfe_agent_token

Generates a new agent token as an ephemeral value.

Each agent pool can have multiple tokens and they can be long-lived. For that reason, this ephemeral resource does not implement the Close method, which would tear the token down after the configuration is complete.

Agent token strings are sensitive and only returned on creation, so making those strings ephemeral values is beneficial to avoid state exposure.

If you need to use this value in the future, make sure to capture the token and save it in a secure location. Any resource with write-only values can accept ephemeral resource attributes.

## Example Usage

Basic usage:

```hcl
ephemeral "tfe_agent_token" "this" {
agent_pool_id = tfe_agent_pool.foobar.id
description = "my description"
}
```

## Argument Reference

The following arguments are supported:

* `agent_pool_id` - (Required) Id for the Agent Pool.
* `description` - (Required) A brief description about the Agent Pool.

## Example Usage

```hcl
resource "tfe_agent_pool" "foobar" {
name = "agent-pool-test"
organization = "my-org-name"
}
ephemeral "tfe_agent_token" "this" {
agent_pool_id = tfe_agent_pool.foobar.id
description = "my description"
}
output "my-agent-token" {
value = ephemeral.tfe_agent_token.this.token
description = "Token for tfe agent."
ephemeral = true
}
```

## Attributes Reference

* `token` - The generated token.

0 comments on commit 5fd2184

Please sign in to comment.