Skip to content
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
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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).
Original file line number Diff line number Diff line change
@@ -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.
}
}