Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add tfe_agent_token ephemeral resource #1627

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion GNUmakefile
Original file line number Diff line number Diff line change
Expand Up @@ -63,5 +63,8 @@ test-compile:
fi
go test -c $(TEST) $(TESTARGS)

.PHONY: build test testacc vet fmt fmtcheck errcheck test-compile sweep
generate:
go run ./generate -name=$(NAME)

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

34 changes: 34 additions & 0 deletions generate/config.hcl
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
ephemeral_resource "organization_token" {
upcase_name = "OrganizationToken"
description = "An organization token is a unique identifier that can be used to authenticate and authorize requests to the organization's API. Organization tokens are generated and managed by the organization's administrators."

field "organization" {
upcase_name = "Organization"
description = "Name of the organization. If omitted, organization must be defined in the provider config."
type = "String"
suppress_test_check = true
}

field "token" {
upcase_name = "Token"
description = "The generated token."
type = "String"
model_attr = "Token"
computed = true
}

field "force_generate" {
upcase_name = "ForceRegenerate"
description = "If set to true, a new token will be generated even if a token already exists. This will invalidate the existing token!"
type = "Bool"
suppress_test_check = true
}

field "expired_at" {
upcase_name = "ExpiredAt"
description = "The token's expiration date. The expiration date must be a date/time string in RFC3339 format (e.g., \"2024-12-31T23:59:59Z\"). If no expiration date is supplied, the expiration date will default to null and never expire."
type = "String"
model_attr = "ExpiredAt.String()"
suppress_test_check = true
}
}
103 changes: 103 additions & 0 deletions generate/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
package main

import (
"bytes"
"flag"
"fmt"
"os"
"text/template"

"github.com/hashicorp/hcl/v2/hclsimple"
)

func main() {
resourceName := flag.String("name", "", "")
flag.Parse()

if *resourceName == "" {
fmt.Fprintf(os.Stderr, "name is required\n")
os.Exit(1)
}

templates := map[string]string{
"ephemeral": "generate/templates/ephemeral/ephemeral.tmpl",
"ephemeraltest": "generate/templates/ephemeral/ephemeraltest.tmpl",
// "websitedoc": "generate/templates/ephemeral/websitedoc.tmpl",
}

var cfg GeneratorConfig
err := hclsimple.DecodeFile("generate/config.hcl", nil, &cfg)
if err != nil {
fmt.Fprintf(os.Stderr, "error reading config: %v\n", err)
os.Exit(1)
}

var model EphemeralResource
for _, resource := range cfg.EphemeralResources {
if resource.Name == *resourceName {
model = resource
}
}

if model.Name == "" {
fmt.Fprintf(os.Stderr, "resource %s not found in generator config\n", *resourceName)
os.Exit(1)
}

for name, path := range templates {
outputPath := "internal/provider/ephemeral_resource_" + model.Name

if name == "ephemeraltest" {
outputPath += "_test"
}

outputPath += ".go"

if name == "websitedoc" {
outputPath = "website/docs/e/{{.Name}}.md"
}

err := generateFromTemplate(model, path, outputPath)
if err != nil {
fmt.Fprintf(os.Stderr, "error generating %s: %v\n", name, err)
os.Exit(1)
}
}
}

func generateFromTemplate(model EphemeralResource, templatePath string, outputPath string) error {
tmpl, err := template.ParseFiles(templatePath)
if err != nil {
return err
}

var buf bytes.Buffer
err = tmpl.Execute(&buf, model)
if err != nil {
return err
}

return os.WriteFile(outputPath, buf.Bytes(), 0o600)
}

type GeneratorConfig struct {
EphemeralResources []EphemeralResource `hcl:"ephemeral_resource,block"`
}

type EphemeralResource struct {
Name string `hcl:"name,label"`
UpcaseName string `hcl:"upcase_name"`
Description string `hcl:"description"`
Fields []Field `hcl:"field,block"`
}

type Field struct {
Name string `hcl:"name,label"`
UpcaseName string `hcl:"upcase_name"`
Description string `hcl:"description"`
Type string `hcl:"type"`
Required bool `hcl:"required,optional"`
Computed bool `hcl:"computed,optional"`
ModelAttr string `hcl:"model_attr,optional"`
SuppressTestCheck bool `hcl:"suppress_test_check,optional"`
}
102 changes: 102 additions & 0 deletions generate/templates/ephemeral/ephemeral.tmpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0

package provider

import (
"context"
"fmt"

"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 = &{{.UpcaseName}}EphemeralResource{}
)

type {{.UpcaseName}}EphemeralResource struct {
config ConfiguredClient
}

type {{.UpcaseName}}EphemeralResourceModel struct {
{{- range $val := .Fields}}
{{$val.UpcaseName}} types.{{$val.Type}} `tfsdk:"{{$val.Name}}"`
{{- end}}
}

func (e *{{.UpcaseName}}EphemeralResource) Schema(ctx context.Context, req ephemeral.SchemaRequest, resp *ephemeral.SchemaResponse) {
resp.Schema = schema.Schema{
Attributes: map[string]schema.Attribute{
{{- range $val := .Fields}}
"{{$val.Name}}": schema.{{$val.Type}}Attribute{
Description: `{{$val.Description}}`,
{{- if $val.Required}}
Required: {{$val.Required}},
{{- end}}
{{- if $val.Computed}}
Computed: {{$val.Computed}},
{{- end}}
},
{{- end}}
},
}
}

// Configure adds the provider configured client to the data source.
func (e *{{.UpcaseName}}EphemeralResource) 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 *{{.UpcaseName}}EphemeralResource) Metadata(ctx context.Context, req ephemeral.MetadataRequest, resp *ephemeral.MetadataResponse) {
resp.TypeName = req.ProviderTypeName + "_{{.Name}}"
}

func (e *{{.UpcaseName}}EphemeralResource) Open(ctx context.Context, req ephemeral.OpenRequest, resp *ephemeral.OpenResponse) {
var data {{.UpcaseName}}EphemeralResourceModel

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

result, err := e.config.Client.{{.UpcaseName}}s.Read(ctx, data.Organization.String())
if err != nil {
resp.Diagnostics.AddError("Unable to read resource", err.Error())
return
}

data = ephemeralResourceModelFromTFE{{.UpcaseName}}(result)

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

// ephemeralResourceModelFromTFE{{.UpcaseName}} builds a {{.UpcaseName}}EphemeralResourceModel struct from a
// tfe.{{.UpcaseName}} value.
func ephemeralResourceModelFromTFE{{.UpcaseName}}(v *tfe.{{.UpcaseName}}) {{.UpcaseName}}EphemeralResourceModel {
return {{.UpcaseName}}EphemeralResourceModel{
{{- range $val := .Fields}}
{{- if $val.ModelAttr}}
{{$val.UpcaseName}}: types.{{$val.Type}}Value(v.{{$val.ModelAttr}}),
{{- end}}
{{- end}}
}
}
53 changes: 53 additions & 0 deletions generate/templates/ephemeral/ephemeraltest.tmpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0

package provider

import (
"fmt"
"testing"
"context"

"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
)

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

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

result, err := tfeClient.{{.UpcaseName}}s.Create(context.Background(), org.Name)
if err != nil {
t.Fatal(err)
}

resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
Steps: []resource.TestStep{
{
Config: testAcc{{.UpcaseName}}EphemeralResourceConfig(org.Name),
Check: resource.ComposeAggregateTestCheckFunc(
{{- range $val := .Fields}}
{{- if not $val.SuppressTestCheck}}
resource.TestCheckResourceAttr(
"ephemeral.{{$.Name}}.this", "{{.Name}}",
result.{{.UpcaseName}}),
{{- end}}
{{- end}}
),
},
},
})
}

func testAcc{{.UpcaseName}}EphemeralResourceConfig(orgName string) string {
return fmt.Sprintf(`
ephemeral "organization_token" "this" {
organization = "%s"
}`, orgName)
}
7 changes: 7 additions & 0 deletions generate/templates/ephemeral/websitedoc.tmpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package {{.Package}}

import "fmt"

func Handler() {
fmt.Println("Handler executed")
}
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
Loading