From eb6bc92f102ec750c2a176919ea4fc53f4979bb3 Mon Sep 17 00:00:00 2001 From: Tyler Lynch Date: Thu, 9 Jul 2026 14:10:41 -0400 Subject: [PATCH] new-terraform-provider: add provider.go scaffold with Configure and auth guidance --- .../skills/new-terraform-provider/SKILL.md | 21 ++- .../new-terraform-provider/assets/provider.go | 139 ++++++++++++++++++ 2 files changed, 157 insertions(+), 3 deletions(-) create mode 100644 terraform/provider-development/skills/new-terraform-provider/assets/provider.go diff --git a/terraform/provider-development/skills/new-terraform-provider/SKILL.md b/terraform/provider-development/skills/new-terraform-provider/SKILL.md index 7bcf5a8..a054807 100644 --- a/terraform/provider-development/skills/new-terraform-provider/SKILL.md +++ b/terraform/provider-development/skills/new-terraform-provider/SKILL.md @@ -1,6 +1,11 @@ --- name: new-terraform-provider -description: Use this when scaffolding a new Terraform provider. +description: >- + Use this when scaffolding a new Terraform provider with the Plugin + Framework: workspace layout, go module setup, provider server main.go, + and a provider.go with schema and Configure. Also use when a user wants + to start building a provider for a new API or asks how to begin a + terraform-provider-* project. license: MPL-2.0 metadata: copyright: Copyright IBM Corp. 2026 @@ -15,11 +20,21 @@ To scaffold a new Terraform provider with Plugin Framework: 1. Create a new workspace root directory. The root directory name should be prefixed with "terraform-provider-". Perform all subsequent steps in this new workspace. -1. Initialize a new Go module.. +1. Initialize a new Go module. 1. Run `go get -u github.com/hashicorp/terraform-plugin-framework@latest`. 1. Write a main.go file that follows [the example](assets/main.go). -1. Remove TODO comments from `main.go` +1. Write an `internal/provider/provider.go` file that follows + [the example](assets/provider.go). Rename the `demo` provider, the + `DEMO_*` environment variables, and the authentication attributes to + match the target API. +1. Remove TODO comments from `main.go` and `provider.go`. 1. Run `go mod tidy` 1. Run `go build -o /dev/null` 1. Run `go test ./...` +The scaffold resolves credentials as explicit config with environment +variable fallback. To grow that into a full credential provider chain +(shared credentials files, profiles, platform identity, configure-time +validation), use the `provider-configuration` skill (if available). To add +the first resource or data source, use the `provider-resources` skill (if +available). diff --git a/terraform/provider-development/skills/new-terraform-provider/assets/provider.go b/terraform/provider-development/skills/new-terraform-provider/assets/provider.go new file mode 100644 index 0000000..5c550d8 --- /dev/null +++ b/terraform/provider-development/skills/new-terraform-provider/assets/provider.go @@ -0,0 +1,139 @@ +// Copyright IBM Corp. 2025, 2026 +// SPDX-License-Identifier: MPL-2.0 + +package provider + +import ( + "context" + "os" + + "github.com/hashicorp/terraform-plugin-framework/datasource" + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-framework/provider" + "github.com/hashicorp/terraform-plugin-framework/provider/schema" + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/types" +) + +var _ provider.Provider = &demoProvider{} + +// New returns the provider factory consumed by main.go. +func New(version string) func() provider.Provider { + return func() provider.Provider { + return &demoProvider{version: version} + } +} + +// TODO: Rename demoProvider (and the "demo" TypeName below) after your provider. +type demoProvider struct { + version string +} + +type demoProviderModel struct { + Endpoint types.String `tfsdk:"endpoint"` + APIKey types.String `tfsdk:"api_key"` +} + +func (p *demoProvider) Metadata(_ context.Context, _ provider.MetadataRequest, resp *provider.MetadataResponse) { + // TODO: Update this with your provider's type name. It is the prefix of + // every resource and data source type (e.g. "demo" -> demo_widget). + resp.TypeName = "demo" + resp.Version = p.version +} + +func (p *demoProvider) Schema(_ context.Context, _ provider.SchemaRequest, resp *provider.SchemaResponse) { + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + // Authentication attributes are Optional (never Required) so + // environment variables can supply them; secrets are Sensitive. + // TODO: Replace endpoint/api_key and the DEMO_* environment + // variables with your API's connection settings. + "endpoint": schema.StringAttribute{ + Optional: true, + MarkdownDescription: "API endpoint. May also be set via the `DEMO_ENDPOINT` environment variable.", + }, + "api_key": schema.StringAttribute{ + Optional: true, + Sensitive: true, + MarkdownDescription: "API key. May also be set via the `DEMO_API_KEY` environment variable.", + }, + }, + } +} + +func (p *demoProvider) Configure(ctx context.Context, req provider.ConfigureRequest, resp *provider.ConfigureResponse) { + var config demoProviderModel + resp.Diagnostics.Append(req.Config.Get(ctx, &config)...) + if resp.Diagnostics.HasError() { + return + } + + // Values wired to other resources' outputs are unknown during planning; + // treating them as empty would silently mis-authenticate. + if config.Endpoint.IsUnknown() { + resp.Diagnostics.AddAttributeError( + path.Root("endpoint"), + "Unknown endpoint", + "endpoint depends on a value known only after apply. Set a static value or use the DEMO_ENDPOINT environment variable.", + ) + } + if config.APIKey.IsUnknown() { + resp.Diagnostics.AddAttributeError( + path.Root("api_key"), + "Unknown API key", + "api_key depends on a value known only after apply. Set a static value or use the DEMO_API_KEY environment variable.", + ) + } + if resp.Diagnostics.HasError() { + return + } + + // Explicit configuration wins; environment variables are the fallback. + endpoint := config.Endpoint.ValueString() + if endpoint == "" { + endpoint = os.Getenv("DEMO_ENDPOINT") + } + apiKey := config.APIKey.ValueString() + if apiKey == "" { + apiKey = os.Getenv("DEMO_API_KEY") + } + + if endpoint == "" { + resp.Diagnostics.AddAttributeError( + path.Root("endpoint"), + "Missing endpoint", + "Set endpoint in the provider block or export DEMO_ENDPOINT.", + ) + } + if apiKey == "" { + resp.Diagnostics.AddAttributeError( + path.Root("api_key"), + "Missing API key", + "Set api_key in the provider block or export DEMO_API_KEY.", + ) + } + if resp.Diagnostics.HasError() { + return + } + + // TODO: Construct your API client here and hand it to resources and data + // sources. They receive it in their Configure methods via ProviderData. + // + // client := demoapi.NewClient(endpoint, apiKey) + // resp.ResourceData = client + // resp.DataSourceData = client + _ = endpoint + _ = apiKey +} + +func (p *demoProvider) Resources(_ context.Context) []func() resource.Resource { + return []func() resource.Resource{ + // TODO: Register resource constructors here, e.g. NewWidgetResource. + } +} + +func (p *demoProvider) DataSources(_ context.Context) []func() datasource.DataSource { + return []func() datasource.DataSource{ + // TODO: Register data source constructors here. + } +}