Build a function-only provider with the Terraform Plugin Framework
Terraform provider developers can include functions in their providers. Users can call provider-defined functions and use the return value in their Terraform projects. Most Terraform providers include resources and data sources, but there are some use cases where you may want to create a provider that only contains functions.
In this tutorial, you will create a custom Terraform provider using the Terraform Plugin Framework and implement a function to parse RFC 3339 timestamps. You will also implement testing, logging, and documentation generation for your provider.
To create your function-only provider, you will:
- Set up your development environment.
Clone theterraform-provider-scaffolding-framework
repository and add additional files needed for testing. This repository contains a scaffold for a generic Terraform provider. - Implement the provider.
Prepare the provider code for all future implementation details. - Install and verify the provider locally.
Configure your environment to manually test your provider during development. - Implement the function.
Write the function code, and verify that it works as expected. - Test the function.
Write tests for the function and run them. - Generate documentation.
Generate the documentation for your provider and function.
Prerequisites
To follow this tutorial, you will need:
- Go 1.21+ installed and configured.
- Terraform v1.8+ installed locally.
Set up your development environment
Clone the Terraform Provider Scaffolding Framework repository.
$ git clone https://github.com/hashicorp/terraform-provider-scaffolding-framework
We recommend that you use this scaffolding repository as a starting point for any new providers you create. It contains a template for a new provider that you will extend and customize.
Rename the directory to terraform-provider-exampletime
.
$ mv terraform-provider-scaffolding-framework terraform-provider-exampletime
Change into the cloned repository.
$ cd terraform-provider-exampletime
Rename the go.mod
module.
$ go mod edit -module terraform-provider-exampletime
Then, install all the provider's dependencies.
$ go mod tidy
Open the main.go
file in the terraform-provider-exampletime
repository's root
directory. This file implements the provider server which interfaces with Terraform.
Replace the existing code in main.go
with the following.
main.go
// Copyright (c) HashiCorp, Inc.// SPDX-License-Identifier: MPL-2.0 package main import ( "context" "flag" "log" "github.com/hashicorp/terraform-plugin-framework/providerserver" "terraform-provider-exampletime/internal/provider") // Run "go generate" to format example terraform files and generate the docs for the registry/website // If you do not have terraform installed, you can remove the formatting command, but its suggested to// ensure the documentation is formatted properly.//go:generate terraform fmt -recursive ./examples/ // Run the docs generation tool, check its repository for more information on how it works and how docs// can be customized.//go:generate go run github.com/hashicorp/terraform-plugin-docs/cmd/tfplugindocs generate -provider-name exampletime 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{ // NOTE: This is not a typical Terraform Registry provider address, such // as registry.terraform.io/hashicorp/exampletime. This specific // provider address is used in this tutorial in conjunction with a // specific Terraform CLI configuration for manual development testing // of this provider. Address: "hashicorp.com/edu/exampletime", Debug: debug, } err := providerserver.Serve(context.Background(), provider.New(version), opts) if err != nil { log.Fatal(err.Error()) }}
The main
function servers your provider so that Terraform can communicate with
it over RPC. This example uses a specially formatted provider address so that
you can test and develop your provider locally.
Implement provider type
Providers use an implementation of the provider.Provider
interface type as the
starting point for all implementation details. To implement your function-only
provider, ensure that your provider satisfies both the Provider
and
ProviderWithFunctions
interfaces.
develop your provider in the internal/provider
directory. The directory from
the scaffolding repository contains an example resource, data source, and
function that you can use as a starting point when you develop your own
providers. Remove these files for this tutorial.
$ rm internal/provider/example_*
Open the internal/provider/provider.go
file and replace the existing code with
the following.
internal/provider/provider.go
package provider import ( "context" "github.com/hashicorp/terraform-plugin-framework/datasource" "github.com/hashicorp/terraform-plugin-framework/function" "github.com/hashicorp/terraform-plugin-framework/provider" "github.com/hashicorp/terraform-plugin-framework/provider/schema" "github.com/hashicorp/terraform-plugin-framework/resource") // Ensure the implementation satisfies the expected interfaces.var ( _ provider.Provider = &ExampleTimeProvider{} _ provider.ProviderWithFunctions = &ExampleTimeProvider{}) // New is a helper function to simplify provider server and testing implementation.func New(version string) func() provider.Provider { return func() provider.Provider { return &ExampleTimeProvider{ version: version, } }} // ExampleTimeProvider is the provider implementation.type ExampleTimeProvider struct { // version is set to the provider version on release, "dev" when the // provider is built and ran locally, and "test" when running acceptance // testing. version string} // Metadata returns the provider type name.func (p *ExampleTimeProvider) Metadata(_ context.Context, _ provider.MetadataRequest, resp *provider.MetadataResponse) { resp.TypeName = "exampletime" resp.Version = p.version} // Schema defines the provider-level schema for configuration data.func (p *ExampleTimeProvider) Schema(_ context.Context, _ provider.SchemaRequest, resp *provider.SchemaResponse) { resp.Schema = schema.Schema{}} // Configure prepares an API client for data sources and resources.func (p *ExampleTimeProvider) Configure(ctx context.Context, req provider.ConfigureRequest, resp *provider.ConfigureResponse) {} // DataSources defines the data sources implemented in the provider.func (p *ExampleTimeProvider) DataSources(_ context.Context) []func() datasource.DataSource { return nil} // Resources defines the resources implemented in the provider.func (p *ExampleTimeProvider) Resources(_ context.Context) []func() resource.Resource { return nil} // Functions defines the functions implemented in the provider.func (p *ExampleTimeProvider) Functions(_ context.Context) []func() function.Function { return nil}
The Provider
interface requires:
- A
Metadata
method that sets the provider name and version. - A
Schema
method that defines the attributes used to configure the provider. - A
Configure
method that configures the provider and API client. - A
DataSources
method that returns a list of the data sources implemented by the provider. - A
Resources
method that returns a list of the resources implemented by the provider.
Unlike providers that include resources and data sources, function-only
providers should avoid logic which is network-based, so they usually do not need
to implement any code in the Configure and Schema methods. In addition,
function-only providers will return nil
from the DataSources
and Resources
methods.
The ProviderWithFunctions
interface adds a required method:
- A
Functions
method that returns the list of functions implemented by the provider.
Later in this tutorial, you will update the Functions
method to return your
function.
The Provider
interface requires:
- A
Metadata
method that sets the provider name and version. - A
Schema
method that defines the attributes used to configure the provider. - A
Configure
method that configures the provider and API client. - A
DataSources
method that returns a list of the data sources implemented by the provider. - A
Resources
method that returns a list of the resources implemented by the provider.
Unlike providers that include resources and data sources, function-only
providers should not call upstream APIs, so they usually do not need to
implement any code in the Configure and Schema methods. In addition,
function-only providers will return nil
from the DataSources
and Resources
methods.
The ProviderWithFunctions
interface adds a required method:
- A
Functions
method that returns the list of functions implemented by the provider.
Later in this tutorial, you will update the Functions
method to return your
function.
Configure Terraform for local provider installation
Terraform installs providers and verifies their versions and checksums when you
run terraform init
. Terraform downloads your providers from either the
provider registry or a local registry. However, while building your provider, you
will want to test Terraform configuration against a local development build of
the provider. Development builds do not have an associated version number
or an official set of checksums listed in a provider registry.
You can reference a local build of a provider by setting a dev_overrides
block in a configuration file called .terraformrc
. This block overrides all
other configured installation methods. When you run terraform
commands,
Terraform will search for the .terraformrc
file in your home directory and
apply these configuration settings.
First, find the GOBIN
path where Go installs your binaries. Your path may vary depending on your Go environment variables.
$ go env GOBIN/Users/<Username>/go/bin
If the GOBIN
go environment variable is not set, use the default path,
/Users/<Username>/go/bin
.
Create a new file called .terraformrc
in your home directory (~
), then add the dev_overrides
block below. Change the <PATH>
to the value returned from the go env GOBIN
command above.
~/.terraformrc
provider_installation { dev_overrides { "hashicorp.com/edu/exampletime" = "<PATH>" } # For all other providers, install them directly from their origin provider # registries as normal. If you omit this, Terraform will _only_ use # the dev_overrides block, and so no other providers will be available. direct {}}
Locally install provider and verify with Terraform
Your Terraform CLI is now ready to use the locally installed provider in the
GOBIN
path. Use the go install
command from the example repository's root
directory to compile the provider into a binary and install it in your GOBIN
path.
$ go install .
Create an examples/functions/rfc3339_parse
directory and navigate to it. You will create example configuration that references your provider in this directory.
$ mkdir -p examples/functions/rfc3339_parse && cd "$_"
Create a function.tf
file with the following.
examples/functions/rfc3339_parse/function.tf
terraform { required_providers { exampletime = { source = "hashicorp.com/edu/exampletime" } }} provider "exampletime" {} output "timestamp" { value = provider::exampletime::rfc3339_parse("2023-07-25T23:43:16Z")}
This configuration refers to a "rfc3339_parse" function that your provider does
not yet support. You will implement this function in the next section.
You can invoke provider-defined functions with the syntax
provider::<PROVIDER_NAME>::<FUNCTION_NAME>(<ARGUMENTS>)
.
Running a Terraform plan now will report the provider override, as well as an error about the missing function. Despite the error, this step verifies that Terraform successfully referenced the locally installed provider.
Run a Terraform plan and review the warning and error.
$ terraform plan╷│ Warning: Provider development overrides are in effect│ │ The following provider development overrides are set in the CLI configuration:│ - hashicorp.com/edu/exampletime in /Users/<YOU>/go/bin│ │ The behavior may therefore not match any released version of the provider and applying changes may cause the state to become incompatible with published│ releases.╵╷│ Error: Unknown provider function│ │ on main.tf line 12, in output "timestamp":│ 12: value = provider::exampletime::rfc3339_parse()│ │ There is no function named "provider::exampletime::rfc3339_parse". Ensure that provider name "exampletime" is declared in this module's│ required_providers block, and that this provider offers a function named "rfc3339_parse".╵
Navigate to the terraform-provider-exampletime
directory.
$ cd ../../..
Implement the function
Provider functions are types that implement the function.Function
interface
from the plugin framework.
The Function interface requires:
- A
Metadata
method that sets the function name. Unlike resources and data sources, function names do not start with the provider name. - A
Definition
method that defines the parameters, return value, and documentation for the function. - A
Run
method that executes the function code.
Create a new internal/provider/function_rfc3339_parse.go
file.
internal/provider/function_rfc3339_parse.go
// Copyright (c) HashiCorp, Inc.// SPDX-License-Identifier: MPL-2.0 package provider import ( "context" "fmt" "time" "github.com/hashicorp/terraform-plugin-framework/attr" "github.com/hashicorp/terraform-plugin-framework/function" "github.com/hashicorp/terraform-plugin-framework/types" "github.com/hashicorp/terraform-plugin-log/tflog") var rfc3339ReturnAttrTypes = map[string]attr.Type{ "year": types.Int64Type, "year_day": types.Int64Type, "day": types.Int64Type, "month": types.Int64Type, "month_name": types.StringType, "weekday": types.Int64Type, "weekday_name": types.StringType, "hour": types.Int64Type, "minute": types.Int64Type, "second": types.Int64Type, "unix": types.Int64Type, "iso_year": types.Int64Type, "iso_week": types.Int64Type,} var _ function.Function = &RFC3339ParseFunction{} type RFC3339ParseFunction struct{} func NewRFC3339ParseFunction() function.Function { return &RFC3339ParseFunction{}} func (f *RFC3339ParseFunction) Metadata(ctx context.Context, req function.MetadataRequest, resp *function.MetadataResponse) { resp.Name = "rfc3339_parse"} func (f *RFC3339ParseFunction) Definition(ctx context.Context, req function.DefinitionRequest, resp *function.DefinitionResponse) { resp.Definition = function.Definition{ Summary: "Parse an RFC3339 timestamp string into an object", Description: "Given an RFC3339 timestamp string, will parse and return an object representation of that date and time.", Parameters: []function.Parameter{ function.StringParameter{ Name: "timestamp", Description: "RFC3339 timestamp string to parse", }, }, Return: function.ObjectReturn{ AttributeTypes: rfc3339ReturnAttrTypes, }, }} func (f *RFC3339ParseFunction) Run(ctx context.Context, req function.RunRequest, resp *function.RunResponse) { var timestamp string resp.Error = req.Arguments.Get(ctx, ×tamp) if resp.Error != nil { return } rfc3339, err := time.Parse(time.RFC3339, timestamp) if err != nil { // Intentionally not including the Go parse error in the return diagnostic, as the message is based on a Go-specific // reference time that may be unfamiliar to practitioners tflog.Error(ctx, fmt.Sprintf("failed to parse RFC3339 timestamp, underlying time.Time error: %s", err.Error())) resp.Error = function.NewArgumentFuncError(0, fmt.Sprintf("Error parsing RFC3339 timestamp: %q is not a valid RFC3339 timestamp", timestamp)) return } isoYear, isoWeek := rfc3339.ISOWeek() rfc3339Obj, diags := types.ObjectValue( rfc3339ReturnAttrTypes, map[string]attr.Value{ "year": types.Int64Value(int64(rfc3339.Year())), "year_day": types.Int64Value(int64(rfc3339.YearDay())), "day": types.Int64Value(int64(rfc3339.Day())), "month": types.Int64Value(int64(rfc3339.Month())), "month_name": types.StringValue(rfc3339.Month().String()), "weekday": types.Int64Value(int64(rfc3339.Weekday())), "weekday_name": types.StringValue(rfc3339.Weekday().String()), "hour": types.Int64Value(int64(rfc3339.Hour())), "minute": types.Int64Value(int64(rfc3339.Minute())), "second": types.Int64Value(int64(rfc3339.Second())), "unix": types.Int64Value(rfc3339.Unix()), "iso_year": types.Int64Value(int64(isoYear)), "iso_week": types.Int64Value(int64(isoWeek)), }, ) resp.Error = function.FuncErrorFromDiags(ctx, diags) if resp.Error != nil { return } resp.Error = resp.Result.Set(ctx, &rfc3339Obj)}
Note
This function is based on code from HashiCorp's time provider. You can review the time provider's code for an example of a provider that implements resources, data sources, and functions.
Add the function to your provider. Open the internal/provider/provider.go
file
and replace the Functions
method with the following.
internal/provider/provider.go
// Functions defines the functions implemented in the provider.func (p *ExampleTimeProvider) Functions(_ context.Context) []func() function.Function { return []func() function.Function{ NewRFC3339ParseFunction, } }
Build and install the updated provider.
$ go install .
Verify the function
Navigate to the examples/functions/rfc3339_parse
directory.
$ cd examples/functions/rfc3339_parse
Apply the configuration to ensure that the rfc3339
function parses the
formatted time string into a timestamp object.
$ terraform apply -auto-approve╷│ Warning: Provider development overrides are in effect│ │ The following provider development overrides are set in the CLI configuration:│ - hashicorp.com/edu/exampletime in /Users/rnorwood/go/bin│ │ The behavior may therefore not match any released version of the provider and applying changes may cause the state to become incompatible with published│ releases.╵ Changes to Outputs: + timestamp = { + day = 25 + hour = 23 + iso_week = 30 + iso_year = 2023 + minute = 43 + month = 7 + month_name = "July" + second = 16 + unix = 1690328596 + weekday = 2 + weekday_name = "Tuesday" + year = 2023 + year_day = 206 } You can apply this plan to save these new output values to the Terraform state, without changing any real infrastructure. Apply complete! Resources: 0 added, 0 changed, 0 destroyed. Outputs: timestamp = { "day" = 25 "hour" = 23 "iso_week" = 30 "iso_year" = 2023 "minute" = 43 "month" = 7 "month_name" = "July" "second" = 16 "unix" = 1690328596 "weekday" = 2 "weekday_name" = "Tuesday" "year" = 2023 "year_day" = 206}
Navigate to the terraform-provider-exampletime
directory.
$ cd ../../..
Test the function
Create acceptance tests to verify that your functions work as expected.
Configure your tests to use the exampletime
provider. Open the
internal/provider/provider_test.go
file and replace the
testAccProtoV6ProviderFactories
variable definition with the following.
internal/provider/provider_test.go
var testAccProtoV6ProviderFactories = map[string]func() (tfprotov6.ProviderServer, error){ "exampletime": providerserver.NewProtocol6WithError(New("test")()),}
Add tests for your function by creating a new
internal/provider/function_rfc3339_parse_test.go
file with the following.
internal/provider/function_rfc3339_parse_test.go
// Copyright (c) HashiCorp, Inc.// SPDX-License-Identifier: MPL-2.0 package provider import ( "regexp" "testing" "github.com/hashicorp/terraform-plugin-testing/helper/resource" "github.com/hashicorp/terraform-plugin-testing/knownvalue" "github.com/hashicorp/terraform-plugin-testing/plancheck" "github.com/hashicorp/terraform-plugin-testing/tfversion") func TestRFC3339Parse_UTC(t *testing.T) { resource.UnitTest(t, resource.TestCase{ TerraformVersionChecks: []tfversion.TerraformVersionCheck{ tfversion.SkipBelow(tfversion.Version1_8_0), }, ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, Steps: []resource.TestStep{ { Config: ` output "test" { value = provider::exampletime::rfc3339_parse("2023-07-25T23:43:16Z") } `, ConfigPlanChecks: resource.ConfigPlanChecks{ PreApply: []plancheck.PlanCheck{ plancheck.ExpectKnownOutputValue("test", knownvalue.ObjectExact( map[string]knownvalue.Check{ "day": knownvalue.Int64Exact(25), "hour": knownvalue.Int64Exact(23), "iso_week": knownvalue.Int64Exact(30), "iso_year": knownvalue.Int64Exact(2023), "minute": knownvalue.Int64Exact(43), "month": knownvalue.Int64Exact(7), "month_name": knownvalue.StringExact("July"), "second": knownvalue.Int64Exact(16), "unix": knownvalue.Int64Exact(1690328596), "weekday": knownvalue.Int64Exact(2), "weekday_name": knownvalue.StringExact("Tuesday"), "year": knownvalue.Int64Exact(2023), "year_day": knownvalue.Int64Exact(206), }, )), }, }, }, { Config: ` output "test" { value = provider::exampletime::rfc3339_parse("2023-07-25T23:43:16-00:00") } `, ConfigPlanChecks: resource.ConfigPlanChecks{ PreApply: []plancheck.PlanCheck{ plancheck.ExpectEmptyPlan(), }, }, }, { Config: ` output "test" { value = provider::exampletime::rfc3339_parse("2023-07-25T23:43:16+00:00") } `, ConfigPlanChecks: resource.ConfigPlanChecks{ PreApply: []plancheck.PlanCheck{ plancheck.ExpectEmptyPlan(), }, }, }, }, })} func TestRFC3339Parse_offset(t *testing.T) { resource.UnitTest(t, resource.TestCase{ TerraformVersionChecks: []tfversion.TerraformVersionCheck{ tfversion.SkipBelow(tfversion.Version1_8_0), }, ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, Steps: []resource.TestStep{ { Config: ` output "test" { value = provider::exampletime::rfc3339_parse("1996-12-19T16:39:57-08:00") } `, ConfigPlanChecks: resource.ConfigPlanChecks{ PreApply: []plancheck.PlanCheck{ plancheck.ExpectKnownOutputValue("test", knownvalue.ObjectExact( map[string]knownvalue.Check{ "day": knownvalue.Int64Exact(19), "hour": knownvalue.Int64Exact(16), "iso_week": knownvalue.Int64Exact(51), "iso_year": knownvalue.Int64Exact(1996), "minute": knownvalue.Int64Exact(39), "month": knownvalue.Int64Exact(12), "month_name": knownvalue.StringExact("December"), "second": knownvalue.Int64Exact(57), "unix": knownvalue.Int64Exact(851042397), "weekday": knownvalue.Int64Exact(4), "weekday_name": knownvalue.StringExact("Thursday"), "year": knownvalue.Int64Exact(1996), "year_day": knownvalue.Int64Exact(354), }, )), }, }, }, }, })} func TestRFC3339Parse_invalid(t *testing.T) { resource.UnitTest(t, resource.TestCase{ TerraformVersionChecks: []tfversion.TerraformVersionCheck{ tfversion.SkipBelow(tfversion.Version1_8_0), }, ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, Steps: []resource.TestStep{ { Config: ` output "test" { value = provider::exampletime::rfc3339_parse("abcdef") } `, ExpectError: regexp.MustCompile(`"abcdef" is not a valid RFC3339 timestamp.`), }, }, })}
These tests ensure the function works as expected, including raising an error for invalid input. Like other unit tests, your Terraform provider tests should cover both normal and error cases.
Verify function testing functionality
Now that you implemented tests for the rfc3339_parse
function, run them.
Navigate to the internal/provider
directory.
$ cd internal/provider
Run Go testing with the TF_ACC
environment variable set. The test framework
will report that your function's test passed.
$ TF_ACC=1 go test -count=1 -v=== RUN TestRFC3339Parse_UTC--- PASS: TestRFC3339Parse_UTC (0.96s)=== RUN TestRFC3339Parse_offset--- PASS: TestRFC3339Parse_offset (0.34s)=== RUN TestRFC3339Parse_invalid--- PASS: TestRFC3339Parse_invalid (0.13s)PASSok terraform-provider-exampletime/internal/provider 1.852s
Navigate to the terraform-provider-exampletime
directory.
$ cd ../..
Generate function documentation
The tfplugindocs
tool will generate documentation for functions based on the
function definition. Open the function_rfc3339_parse.go
file and review the
Definition
method.
internal/provider/function_rfc3339_parse.go
func (f *RFC3339ParseFunction) Definition(ctx context.Context, req function.DefinitionRequest, resp *function.DefinitionResponse) { resp.Definition = function.Definition{ Summary: "Parse an RFC3339 timestamp string into an object", Description: "Given an RFC3339 timestamp string, will parse and return an object representation of that date and time.", Parameters: []function.Parameter{ function.StringParameter{ Name: "timestamp", Description: "RFC3339 timestamp string to parse", }, }, Return: function.ObjectReturn{ AttributeTypes: rfc3339ReturnAttrTypes, }, }}
Add configuration examples
The tfplugindocs
tool will automatically include Terraform configuration
examples from files with the following naming conventions:
- Provider:
examples/provider/provider.tf
- Resources:
examples/resources/TYPE/resource.tf
- Data Sources:
examples/data-sources/TYPE/data-source.tf
- Functions:
examples/functions/TYPE/function.tf
Replace TYPE
with the name of the resource, data source, or function. For
example: examples/functions/rfc3339_parse/function.tf
.
The scaffolding framework includes examples that you can refer to when you create your own provider. Remove these examples for this tutorial.
$ rm -r examples/data-sources/scaffolding_example && rm -r examples/resources/scaffolding_example
Replace the examples/provider/provider.tf
file with the following.
examples/provider/provider.tf
provider "exampletime" { }
If your provider requires any arguments, include an example of their usage in this file.
Now that you have implemented the documentation generation functionality for
your provider, run the go generate ./...
command to generate the
documentation.
$ go generate ./...rendering website for provider "exampletime" (as "exampletime")exporting schema from Terraformcompiling provider "exampletime"using Terraform CLI binary from PATH if available, otherwise downloading latest Terraform CLI binaryrunning terraform initgetting provider schemagenerating missing templatesgenerating missing resource contentgenerating missing data source contentgenerating missing function contentgenerating new template for function "rfc3339_parse"generating missing provider contentgenerating new template for "exampletime"rendering static websitecleaning rendered website dirremoving directory: "data-sources"removing directory: "functions"removing file: "index.md"removing directory: "resources"rendering templated website to static markdownrendering "functions/rfc3339_parse.md.tmpl"rendering "index.md.tmpl"
View the docs/functions/rfc3339_parse.md
file to verify that it contains the
documentation for your function.
docs/functions/rfc3339_parse.md
---# generated by https://github.com/hashicorp/terraform-plugin-docspage_title: "rfc3339_parse function - exampletime"subcategory: ""description: |- Parse an RFC3339 timestamp string into an object--- # function: rfc3339_parse Given an RFC3339 timestamp string, will parse and return an object representation of that date and time. ##...
Next steps
Congratulations! You have created a function-only provider. While most Terraform providers will include resources and data sources, you can create and distribute function-only providers that do not rely on remote APIs.
- To learn more about the Terraform Plugin Framework, refer to the Terraform Plugin Framework documentation.
- For a full capability comparison between the SDKv2 and the Plugin Framework, refer to the Which SDK Should I Use? documentation.
- To create a provider than includes resources and data sources, follow the Custom Framework Providers tutorial collection.
- Submit any Terraform Plugin Framework bug reports or feature requests to the development team in the Terraform Plugin Framework Github repository.
- Submit any Terraform Plugin Framework questions in the Terraform Plugin Framework Discuss forum.