assets/main.go
// Copyright IBM Corp. 2025, 2026
// SPDX-License-Identifier: MPL-2.0
package main
import (
"context"
"flag"
"log"
"example.org/terraform-provider-demo/internal/provider"
"github.com/hashicorp/terraform-plugin-framework/providerserver"
)
var (
// these will be set by the goreleaser configuration
// to appropriate values for the compiled binary.
version string = "dev"
// goreleaser can pass other information to the main package, such as the specific commit
// https://goreleaser.com/cookbooks/using-main.version/
)
func main() {
var debug bool
flag.BoolVar(&debug, "debug", false, "set to true to run the provider with support for debuggers like delve")
flag.Parse()
opts := providerserver.ServeOpts{
// TODO: Update this string with the published name of your provider.
// Also update the tfplugindocs generate command to either remove the
// -provider-name flag or set its value to the updated provider name.
Address: "registry.terraform.io/example/demo",
Debug: debug,
}
err := providerserver.Serve(context.Background(), provider.New(version), opts)
if err != nil {
log.Fatal(err.Error())
}
}
assets/provider.go
// 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.
}
}
SKILL.md
---
name: 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:
lifecycle-status: active
copyright: Copyright IBM Corp. 2026
version: "0.0.1"
---
To scaffold a new Terraform provider with Plugin Framework:
1. If I am already in a Terraform provider workspace, then confirm that I want
to create a new workspace. If I do not want to create a new workspace, then
skip all remaining steps.
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. 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. 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).