diff --git a/content/terraform-plugin-framework/v1.15.x/data/plugin-framework-nav-data.json b/content/terraform-plugin-framework/v1.15.x/data/plugin-framework-nav-data.json new file mode 100644 index 000000000..b41e7901f --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/data/plugin-framework-nav-data.json @@ -0,0 +1,648 @@ +[ + { + "heading": "Framework" + }, + { + "title": "Overview", + "path": "" + }, + { + "title": "Getting Started", + "routes": [ + { + "title": "Provider Code Walkthrough", + "path": "getting-started/code-walkthrough" + }, + { + "title": "Tutorials", + "href": "https://developer.hashicorp.com/terraform/tutorials/providers-plugin-framework?utm_source=WEBSITE&utm_medium=WEB_IO&utm_offer=ARTICLE_PAGE&utm_content=DOCS" + }, + { + "title": "Clone Template Repository", + "href": "https://github.com/hashicorp/terraform-provider-scaffolding-framework" + } + ] + }, + { + "title": "Provider Servers", + "path": "provider-servers" + }, + { + "title": "Providers", + "routes": [ + { + "title": "Overview", + "path": "providers" + }, + { + "title": "Validate Configuration", + "path": "providers/validate-configuration" + } + ] + }, + { + "title": "Resources", + "routes": [ + { + "title": "Overview", + "path": "resources" + }, + { + "title": "Create", + "path": "resources/create" + }, + { + "title": "Read", + "path": "resources/read" + }, + { + "title": "Update", + "path": "resources/update" + }, + { + "title": "Delete", + "path": "resources/delete" + }, + { + "title": "Configure Clients", + "path": "resources/configure" + }, + { + "title": "Validate Configuration", + "path": "resources/validate-configuration" + }, + { + "title": "Modify Plan", + "path": "resources/plan-modification" + }, + { + "title": "Default Attribute Values", + "path": "resources/default" + }, + { + "title": "Import State", + "path": "resources/import" + }, + { + "title": "Move State", + "path": "resources/state-move" + }, + { + "title": "Upgrade State", + "path": "resources/state-upgrade" + }, + { + "title": "Manage Private State", + "path": "resources/private-state" + }, + { + "title": "Timeouts", + "path": "resources/timeouts" + }, + { + "title": "Write-only Arguments", + "path": "resources/write-only-arguments" + } + ] + }, + { + "title": "Data Sources", + "routes": [ + { + "title": "Overview", + "path": "data-sources" + }, + { + "title": "Configure Clients", + "path": "data-sources/configure" + }, + { + "title": "Validate Configuration", + "path": "data-sources/validate-configuration" + }, + { + "title": "Timeouts", + "path": "data-sources/timeouts" + } + ] + }, + { + "title": "Functions", + "routes": [ + { + "title": "Overview", + "path": "functions" + }, + { + "title": "Concepts", + "path": "functions/concepts" + }, + { + "title": "Implementation", + "path": "functions/implementation" + }, + { + "title": "Parameters", + "routes": [ + { + "title": "Overview", + "path": "functions/parameters" + }, + { + "title": "Bool", + "path": "functions/parameters/bool" + }, + { + "title": "Dynamic", + "path": "functions/parameters/dynamic" + }, + { + "title": "Float32", + "path": "functions/parameters/float32" + }, + { + "title": "Float64", + "path": "functions/parameters/float64" + }, + { + "title": "Int32", + "path": "functions/parameters/int32" + }, + { + "title": "Int64", + "path": "functions/parameters/int64" + }, + { + "title": "List", + "path": "functions/parameters/list" + }, + { + "title": "Map", + "path": "functions/parameters/map" + }, + { + "title": "Number", + "path": "functions/parameters/number" + }, + { + "title": "Object", + "path": "functions/parameters/object" + }, + { + "title": "Set", + "path": "functions/parameters/set" + }, + { + "title": "String", + "path": "functions/parameters/string" + } + ] + }, + { + "title": "Returns", + "routes": [ + { + "title": "Overview", + "path": "functions/returns" + }, + { + "title": "Bool", + "path": "functions/returns/bool" + }, + { + "title": "Dynamic", + "path": "functions/returns/dynamic" + }, + { + "title": "Float32", + "path": "functions/returns/float32" + }, + { + "title": "Float64", + "path": "functions/returns/float64" + }, + { + "title": "Int32", + "path": "functions/returns/int32" + }, + { + "title": "Int64", + "path": "functions/returns/int64" + }, + { + "title": "List", + "path": "functions/returns/list" + }, + { + "title": "Map", + "path": "functions/returns/map" + }, + { + "title": "Number", + "path": "functions/returns/number" + }, + { + "title": "Object", + "path": "functions/returns/object" + }, + { + "title": "Set", + "path": "functions/returns/set" + }, + { + "title": "String", + "path": "functions/returns/string" + } + ] + }, + { + "title": "Errors", + "path": "functions/errors" + }, + { + "title": "Testing", + "path": "functions/testing" + }, + { + "title": "Documentation", + "path": "functions/documentation" + } + ] + }, + { + "title": "Ephemeral Resources", + "routes": [ + { + "title": "Overview", + "path": "ephemeral-resources" + }, + { + "title": "Open", + "path": "ephemeral-resources/open" + }, + { + "title": "Configure Clients", + "path": "ephemeral-resources/configure" + }, + { + "title": "Validate Configuration", + "path": "ephemeral-resources/validate-configuration" + }, + { + "title": "Renew", + "path": "ephemeral-resources/renew" + }, + { + "title": "Close", + "path": "ephemeral-resources/close" + } + ] + }, + { + "title": "Handling Data", + "routes": [ + { + "title": "Terraform Concepts", + "path": "handling-data/terraform-concepts" + }, + { + "title": "Schemas", + "path": "handling-data/schemas" + }, + { + "title": "Attributes", + "routes": [ + { + "title": "Overview", + "path": "handling-data/attributes" + }, + { + "title": "Bool", + "path": "handling-data/attributes/bool" + }, + { + "title": "Dynamic", + "path": "handling-data/attributes/dynamic" + }, + { + "title": "Float32", + "path": "handling-data/attributes/float32" + }, + { + "title": "Float64", + "path": "handling-data/attributes/float64" + }, + { + "title": "Int32", + "path": "handling-data/attributes/int32" + }, + { + "title": "Int64", + "path": "handling-data/attributes/int64" + }, + { + "title": "List", + "path": "handling-data/attributes/list" + }, + { + "title": "List Nested", + "path": "handling-data/attributes/list-nested" + }, + { + "title": "Map", + "path": "handling-data/attributes/map" + }, + { + "title": "Map Nested", + "path": "handling-data/attributes/map-nested" + }, + { + "title": "Number", + "path": "handling-data/attributes/number" + }, + { + "title": "Object", + "path": "handling-data/attributes/object" + }, + { + "title": "Set", + "path": "handling-data/attributes/set" + }, + { + "title": "Set Nested", + "path": "handling-data/attributes/set-nested" + }, + { + "title": "Single Nested", + "path": "handling-data/attributes/single-nested" + }, + { + "title": "String", + "path": "handling-data/attributes/string" + } + ] + }, + { + "title": "Blocks", + "routes": [ + { + "title": "Overview", + "path": "handling-data/blocks" + }, + { + "title": "List Nested", + "path": "handling-data/blocks/list-nested" + }, + { + "title": "Set Nested", + "path": "handling-data/blocks/set-nested" + }, + { + "title": "Single Nested", + "path": "handling-data/blocks/single-nested" + } + ] + }, + { + "title": "Types", + "routes": [ + { + "title": "Overview", + "path": "handling-data/types" + }, + { + "title": "Bool", + "path": "handling-data/types/bool" + }, + { + "title": "Dynamic", + "path": "handling-data/types/dynamic" + }, + { + "title": "Float32", + "path": "handling-data/types/float32" + }, + { + "title": "Float64", + "path": "handling-data/types/float64" + }, + { + "title": "Int32", + "path": "handling-data/types/int32" + }, + { + "title": "Int64", + "path": "handling-data/types/int64" + }, + { + "title": "List", + "path": "handling-data/types/list" + }, + { + "title": "Map", + "path": "handling-data/types/map" + }, + { + "title": "Number", + "path": "handling-data/types/number" + }, + { + "title": "Object", + "path": "handling-data/types/object" + }, + { + "title": "Set", + "path": "handling-data/types/set" + }, + { + "title": "String", + "path": "handling-data/types/string" + }, + { + "title": "Tuple", + "path": "handling-data/types/tuple" + }, + { + "title": "Custom Types", + "path": "handling-data/types/custom" + } + ] + }, + { + "title": "Paths", + "path": "handling-data/paths" + }, + { + "title": "Path Expressions", + "path": "handling-data/path-expressions" + }, + { + "title": "Accessing Terraform Data", + "path": "handling-data/accessing-values" + }, + { + "title": "Writing Data", + "path": "handling-data/writing-state" + }, + { + "title": "Dynamic Data", + "path": "handling-data/dynamic-data" + } + ] + }, + { + "title": "Returning Errors and Warnings", + "path": "diagnostics" + }, + { + "title": "Validation", + "path": "validation" + }, + { + "title": "Acceptance Tests", + "path": "acctests" + }, + { + "title": "Debugging", + "path": "debugging" + }, + { + "title": "Deprecations, Removals, and Renames", + "path": "deprecations" + }, + { + "title": "Migrating from SDK", + "routes": [ + { + "title": "Overview", + "path": "migrating" + }, + { + "title": "Benefits", + "path": "migrating/benefits" + }, + { + "title": "Muxing", + "path": "migrating/mux" + }, + { + "title": "Testing", + "path": "migrating/testing" + }, + { + "title": "Schema", + "routes": [ + { + "title": "Overview", + "path": "migrating/schema" + } + ] + }, + { + "title": "Providers", + "routes": [ + { + "title": "Overview", + "path": "migrating/providers" + } + ] + }, + { + "title": "Resources", + "routes": [ + { + "title": "Overview", + "path": "migrating/resources" + }, + { + "title": "CRUD Functions", + "path": "migrating/resources/crud" + }, + { + "title": "Import", + "path": "migrating/resources/import" + }, + { + "title": "Plan Modification", + "path": "migrating/resources/plan-modification" + }, + { + "title": "State Upgraders", + "path": "migrating/resources/state-upgrade" + }, + { + "title": "Timeouts", + "path": "migrating/resources/timeouts" + } + ] + }, + { + "title": "Data Sources", + "routes": [ + { + "title": "Overview", + "path": "migrating/data-sources" + }, + { + "title": "Timeouts", + "path": "migrating/data-sources/timeouts" + } + ] + }, + { + "title": "Attributes & Blocks", + "routes": [ + { + "title": "Attribute Schema", + "path": "migrating/attributes-blocks/attribute-schema" + }, + { + "title": "Attribute Types", + "path": "migrating/attributes-blocks/types" + }, + { + "title": "Attribute Fields", + "path": "migrating/attributes-blocks/fields" + }, + { + "title": "Default Values", + "path": "migrating/attributes-blocks/default-values" + }, + { + "title": "Force New", + "path": "migrating/attributes-blocks/force-new" + }, + { + "title": "Validators - Predefined", + "path": "migrating/attributes-blocks/validators-predefined" + }, + { + "title": "Validators - Custom", + "path": "migrating/attributes-blocks/validators-custom" + }, + { + "title": "Blocks", + "path": "migrating/attributes-blocks/blocks" + }, + { + "title": "Blocks with Computed Fields", + "path": "migrating/attributes-blocks/blocks-computed" + } + ] + } + ] + }, + { + "title": "Internals", + "routes": [ + { + "title": "Overview", + "path": "internals" + }, + { + "title": "RPCs and Framework Functionality", + "path": "internals/rpcs" + } + ] + } +] diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/acctests.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/acctests.mdx new file mode 100644 index 000000000..e23cd6fbb --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/acctests.mdx @@ -0,0 +1,84 @@ +--- +page_title: Acceptance tests +description: >- + Learn how to write acceptance tests for providers built on the framework. + Acceptance tests help ensure your provider works as expected by imitating + Terraform operations. +--- + +# Acceptance tests + +Implement provider resource and data source acceptance tests with the [terraform-plugin-testing module](/terraform/plugin/testing). These tests are designed to execute Terraform commands against real Terraform configurations, simulating practitioner experiences with creating, refreshing, updating, and deleting infrastructure. + +This page only describes requirements of using the testing module with a framework provider. Refer to the [testing module documentation](/terraform/plugin/testing) for additional information on all available functionality in tests. + +## Requirements + +The testing module must know how to reference your provider code before Terraform commands and configurations can succeed. This is achieved by pointing the testing module at a [provider server](/terraform/plugin/framework/provider-servers) which wraps your [provider](/terraform/plugin/framework/providers). + +Use one of the [`resource.TestCase` type](/terraform/plugin/testing/acceptance-tests/testcase) [`ProtoV6ProviderFactories` field](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/helper/resource#TestCase.ProtoV6ProviderFactories) for [protocol version 6](/terraform/plugin/terraform-plugin-protocol#protocol-version-6) or [`ProtoV5ProviderFactories` field](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/helper/resource#TestCase.ProtoV5ProviderFactories) for [protocol version 5](/terraform/plugin/terraform-plugin-protocol#protocol-version-5). It is only necessary to test with the single protocol version matching the production provider server, typically defined in the `main.go` file of the provider codebase. + +### Protocol Version 6 + +Use the [`providerserver.NewProtocol6WithError`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/providerserver#NewProtocol6WithError) helper function to implement the provider server in the [`ProtoV6ProviderFactories` field](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/helper/resource#TestCase.ProtoV6ProviderFactories). + +```go +resource.Test(t, resource.TestCase{ + ProtoV6ProviderFactories: map[string]func() (tfprotov6.ProviderServer, error) { + // newProvider is an example function that returns a provider.Provider + "examplecloud": providerserver.NewProtocol6WithError(newProvider()), + }, + Steps: []resource.TestStep{/* ... */}, +}) +``` + +### Protocol Version 5 + +Use the [`providerserver.NewProtocol5WithError`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/providerserver#NewProtocol5WithError) helper function to implement the provider server in the [`ProtoV5ProviderFactories` field](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/helper/resource#TestCase.ProtoV5ProviderFactories). + +```go +resource.Test(t, resource.TestCase{ + ProtoV5ProviderFactories: map[string]func() (tfprotov5.ProviderServer, error) { + // newProvider is an example function that returns a provider.Provider + "examplecloud": providerserver.NewProtocol5WithError(newProvider()), + }, + Steps: []resource.TestStep{/* ... */}, +}) +``` + +## Troubleshooting + +### No id found in attributes + + + +terraform-plugin-testing version 1.5.0 and later no longer require managed resources and data resources to implement the `id` attribute. + + + +In SDKv2, resources and data sources automatically included an implicit, root level `id` attribute. In the framework, the `id` attribute is not implicitly added. + +When testing resources and data sources without the `id` attribute, the acceptance testing framework will return errors such as: + +```text +testing_new_config.go:111: no "id" found in attributes +testing_new.go:53: no "id" found in attributes +``` + +To avoid this, add a root level `id` attribute to resource and data source schemas. Ensure the attribute value is appropriately [written to state](/terraform/plugin/framework/writing-state). Conventionally, `id` is a computed attribute that contains the identifier for the resource. + +For example, in the `Schema` method implementation of a [`datasource.DataSource`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/datasource#DataSource) or [`resource.Resource`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource#Resource): + +```go +func (r *ThingResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + // ... potentially other schema configuration ... + Attributes: map[string]schema.Attribute{ + // ... potentially other schema attributes ... + "id": schema.StringAttribute{ + Computed: true, + }, + }, + } +} +``` diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/data-sources/configure.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/data-sources/configure.mdx new file mode 100644 index 000000000..11dda382b --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/data-sources/configure.mdx @@ -0,0 +1,104 @@ +--- +page_title: Configure data sources +description: >- + Learn how to configure data sources with provider data or clients in the + Terraform plugin framework. +--- + +# Configure data sources + +[Data sources](/terraform/plugin/framework/data-sources) may require provider-level data or remote system clients to operate correctly. The framework supports the ability to configure this data and/or clients once within the provider, then pass that information to data sources by adding the `Configure` method. + +## Prepare Provider Configure Method + +Implement the [`provider.ConfigureResponse.DataSourceData` field](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/provider#ConfigureResponse.DataSourceData) in the [`Provider` interface `Configure` method](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/provider#Provider.Configure). This value can be set to any type, whether an existing client or vendor SDK type, a provider-defined custom type, or the provider implementation itself. It is recommended to use pointer types so that data sources can determine if this value was configured before attempting to use it. + +During execution of the [`terraform plan`](/terraform/cli/commands/plan) and [`terraform apply`](/terraform/cli/commands/apply) commands, Terraform calls the [`ConfigureProvider`](/terraform/plugin/framework/internals/rpcs#configureprovider-rpc) RPC, in which the framework calls the [`provider.Provider` interface `Configure` method](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/provider#Provider.Configure). + +In this example, the Go standard library [`net/http.Client`](https://pkg.go.dev/net/http#Client) is configured in the provider, and made available for data sources: + +```go +// With the provider.Provider implementation +func (p *ExampleCloudProvider) Configure(ctx context.Context, req provider.ConfigureRequest, resp *provider.ConfigureResponse) { + resp.DataSourceData = &http.Client{/* ... */} +} +``` + +In this example, the code defines an `ExampleClient` type that is made available for data sources: + +```go +type ExampleClient struct { + /* ... */ +} + +// With the provider.Provider implementation +func (p *ExampleCloudProvider) Configure(ctx context.Context, req provider.ConfigureRequest, resp *provider.ConfigureResponse) { + resp.DataSourceData = &ExampleClient{/* ... */} +} +``` + +In this example, the `ExampleCloudProvider` type itself is made available for data sources: + +```go +// With the provider.Provider implementation +type ExampleCloudProvider struct { + /* ... */ +} + +func (p *ExampleCloudProvider) Configure(ctx context.Context, req provider.ConfigureRequest, resp *provider.ConfigureResponse) { + resp.DataSourceData = p +} +``` + +## Define Data Source Configure Method + +Implement the [`datasource.DataSourceWithConfigure` interface](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/datasource#DataSourceWithConfigure) which receives the provider configured data from the [`Provider` interface `Configure` method](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/provider#Provider.Configure) and saves it into the [`datasource.DataSource` interface](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/datasource#DataSource) implementation. + +The [`datasource.DataSourceWithConfigure` interface `Configure` method](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/datasource#DataSourceWithConfigure.Configure) is called during execution of the [`terraform validate`](/terraform/cli/commands/validate), [`terraform plan`](/terraform/cli/commands/plan) and [`terraform apply`](/terraform/cli/commands/apply) commands when the [`ValidateDataResourceConfig`](/terraform/plugin/framework/internals/rpcs#validatedataresourceconfig-rpc) RPC is sent. Additionally, the [`datasource.DataSourceWithConfigure` interface `Configure` method](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/datasource#DataSourceWithConfigure.Configure) is called during execution of the [`terraform plan`](/terraform/cli/commands/plan) and [`terraform apply`](/terraform/cli/commands/apply) commands when the [`ReadDataSource`](/terraform/plugin/framework/internals/rpcs#readdatasource-rpc) RPC is sent. + +-> Note that Terraform calling the [`ValidateDataResourceConfig`](/terraform/plugin/framework/internals/rpcs#validatedataresourceconfig-rpc) RPC would not call the [`ConfigureProvider`](/terraform/plugin/framework/internals/rpcs#configureprovider-rpc) RPC first, so implementations need to account for that situation. Configuration validation in Terraform occurs without provider configuration ("offline"). + +In this example, the provider configured the Go standard library [`net/http.Client`](https://pkg.go.dev/net/http#Client) which the data source uses during `Read`: + +```go +// With the datasource.DataSource implementation +type ThingDataSource struct { + client *http.Client +} + +func (d *ThingDataSource) Configure(ctx context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) { + // Always perform a nil check when handling ProviderData because Terraform + // sets that data after it calls the ConfigureProvider RPC. + if req.ProviderData == nil { + return + } + + client, ok := req.ProviderData.(*http.Client) + + if !ok { + resp.Diagnostics.AddError( + "Unexpected Data Source Configure Type", + fmt.Sprintf("Expected *http.Client, got: %T. Please report this issue to the provider developers.", req.ProviderData), + ) + + return + } + + d.client = client +} + +func (d *ThingDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { + // Prevent panic if the provider has not been configured. + if d.client == nil { + resp.Diagnostics.AddError( + "Unconfigured HTTP Client", + "Expected configured HTTP client. Please report this issue to the provider developers.", + ) + + return + } + + httpResp, err := d.client.Get("https://example.com") + /* ... */ +} +``` diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/data-sources/index.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/data-sources/index.mdx new file mode 100644 index 000000000..64dfb2ecc --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/data-sources/index.mdx @@ -0,0 +1,147 @@ +--- +page_title: Data sources +description: >- + Data sources allow Terraform to reference external data. Learn how the + framework can help you implement data sources. +--- + +# Data sources + +[Data sources](/terraform/language/data-sources) are an abstraction that allow Terraform to reference external data. Unlike [managed resources](/terraform/language/resources), Terraform does not manage the lifecycle of the resource or data. Data sources are intended to have no side-effects. + +This page describes the basic implementation details required for supporting a data source within the provider. Further documentation is available for deeper data source concepts: + +- [Configure](/terraform/plugin/framework/data-sources/configure) data sources with provider-level data types or clients. +- [Validate](/terraform/plugin/framework/data-sources/validate-configuration) practitioner configuration against acceptable values. +- [Timeouts](/terraform/plugin/framework/data-sources/timeouts) in practitioner configuration for use in a data source read function. + +## Define Data Source Type + +Implement the [`datasource.DataSource` interface](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/datasource#DataSource). Each of the methods is described in more detail below. + +In this example, a data source named `examplecloud_thing` with hardcoded behavior is defined: + +```go +// Ensure the implementation satisfies the desired interfaces. +var _ datasource.DataSource = &ThingDataSource{} + +type ThingDataSource struct {} + +type ThingDataSourceModel struct { + ExampleAttribute types.String `tfsdk:"example_attribute"` + ID types.String `tfsdk:"id"` +} + +func (d *ThingDataSource) Metadata(ctx context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) { + resp.TypeName = "examplecloud_thing" +} + +func (d *ThingDataSource) Schema(ctx context.Context, req datasource.SchemaRequest, resp *datasource.SchemaResponse) { + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "example_attribute": schema.StringAttribute{ + Required: true, + }, + "id": schema.StringAttribute{ + Computed: true, + }, + }, + } +} + +func (d *ThingDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { + var data ThingDataSourceModel + + // Read Terraform configuration data into the model + resp.Diagnostics.Append(req.Config.Get(ctx, &data)...) + + // Typically data sources will make external calls, however this example + // hardcodes setting the id attribute to a specific value for brevity. + data.ID = types.StringValue("example-id") + + // Save data into Terraform state + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} +``` + +### Metadata Method + +The [`datasource.DataSource` interface `Metadata` method](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/datasource#DataSource.Metadata) defines the data source name as it would appear in Terraform configurations. This name should include the provider type prefix, an underscore, then the data source specific name. For example, a provider named `examplecloud` and a data source that reads "thing" resources would be named `examplecloud_thing`. Ensure the [Add Data Source To Provider](#add-data-source-to-provider) documentation is followed so the data source becomes part of the provider implementation, and therefore available to practitioners. + +In this example, the data source name in an `examplecloud` provider that reads "thing" resources is hardcoded to `examplecloud_thing`: + +```go +// With the datasource.DataSource implementation +func (d *ThingDataSource) Metadata(ctx context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) { + resp.TypeName = "examplecloud_thing" +} +``` + +To simplify data source implementations, the [`provider.MetadataResponse.TypeName` field](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/provider#MetadataResponse.TypeName) from the [`provider.Provider` interface `Metadata` method](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/provider#Provider.Metadata) can set the provider name so it is available in the [`datasource.MetadataRequest.ProviderTypeName` field](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/datasource#MetadataRequest.ProviderTypeName). + +In this example, the provider defines the `examplecloud` name for itself, and the data source is named `examplecloud_thing`: + +```go +// With the provider.Provider implementation +func (p *ExampleCloudProvider) Metadata(ctx context.Context, req provider.MetadataRequest, resp *provider.MetadataResponse) { + resp.TypeName = "examplecloud" +} + +// With the datasource.DataSource implementation +func (d *ThingDataSource) Metadata(ctx context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_thing" +} +``` + +### Schema Method + +The [`datasource.DataSource` interface `Schema` method](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/datasource#DataSource.Schema) defines a [schema](/terraform/plugin/framework/schemas) describing what data is available in the data source's configuration and state. + +### Read Method + +The [`datasource.DataSource` interface `Read` method](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/datasource#DataSource.Read) defines how the data source updates Terraform's state to reflect the retrieved data. There is no plan or prior state to work with in `Read` requests, only configuration. + +During the [`terraform apply`](/terraform/cli/commands/apply), [`terraform plan`](/terraform/cli/commands/plan), and [`terraform refresh`](/terraform/cli/commands/refresh) commands, Terraform calls the provider [`ReadDataSource`](/terraform/plugin/framework/internals/rpcs#readdatasource-rpc) RPC, in which the framework calls the [`datasource.DataSource` interface `Read` method](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/datasource#DataSource.Read). + +Implement the `Read` method by: + +1. [Accessing configuration data](/terraform/plugin/framework/accessing-values) from the [`datasource.ReadRequest.Config` field](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/datasource#ReadRequest.Config). +1. Retriving any additional data, such as remote system information. +1. [Writing state data](/terraform/plugin/framework/writing-state) into the [`datasource.ReadResponse.State` field](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/datasource#ReadResponse.State). + +If the logic needs to return [warning or error diagnostics](/terraform/plugin/framework/diagnostics), they can added into the [`datasource.ReadResponse.Diagnostics` field](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/datasource#ReadResponse.Diagnostics). + +## Add Data Source to Provider + +Data sources become available to practitioners when they are included in the [provider](/terraform/plugin/framework/providers) implementation via the [`provider.ProviderWithDataSources` interface `DataSources` method](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/provider#ProviderWithDataSources.DataSources). + +In this example, the `ThingDataSource` type, which implements the `datasource.DataSource` interface, is added to the provider implementation: + +```go +// With the provider.Provider implementation +func (p *ExampleCloudProvider) DataSources(_ context.Context) []func() datasource.DataSource { + return []func() datasource.DataSource{ + func() datasource.DataSource { + return &ThingDataSource{}, + }, + } +} +``` + +To simplify provider implementations, a named function can be created with the data source implementation. + +In this example, the `ThingDataSource` code includes an additional `NewThingDataSource` function, which simplifies the provider implementation: + +```go +// With the provider.Provider implementation +func (p *ExampleCloudProvider) DataSources(_ context.Context) []func() datasource.DataSource { + return []func() datasource.DataSource{ + NewThingDataSource, + } +} + +// With the datasource.DataSource implementation +func NewThingDataSource() datasource.DataSource { + return &ThingDataSource{} +} +``` diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/data-sources/timeouts.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/data-sources/timeouts.mdx new file mode 100644 index 000000000..7868dd402 --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/data-sources/timeouts.mdx @@ -0,0 +1,132 @@ +--- +page_title: Timeouts +description: >- + Learn how to implement timeouts with the Terraform plugin framework. +--- + +# Timeouts + +The reality of cloud infrastructure is that it typically takes time to perform operations such as booting operating systems, discovering services, and replicating state across network edges. As the provider developer you should take known delays in data source APIs into account in the `Read` function of the data source. Terraform supports configurable timeouts to assist in these situations. + +The Framework can be used in conjunction with the [terraform-plugin-framework-timeouts](https://github.com/hashicorp/terraform-plugin-framework-timeouts) module in order to allow defining timeouts in configuration and have them be available in the `Read` function. + +## Specifying Timeouts in Configuration + +Timeouts can be defined using either nested blocks or nested attributes. + +If you are writing a new provider using [terraform-plugin-framework](https://github.com/hashicorp/terraform-plugin-framework) +then we recommend using nested attributes. + +If you are [migrating a provider from SDKv2 to the Framework](/terraform/plugin/framework/migrating) and +you are already using timeouts you can either continue to use block syntax, or switch to using nested attributes. +However, switching to using nested attributes will require that practitioners that are using your provider update their +Terraform configuration. + +#### Block + +If your configuration is using a nested block to define timeouts, such as the following: + +```hcl +resource "timeouts_example" "example" { + /* ... */ + + timeouts { + read = "60m" + } +} +``` + +Import the [timeouts module](https://github.com/hashicorp/terraform-plugin-framework-timeouts). + +```go +import ( + /* ... */ + "github.com/hashicorp/terraform-plugin-framework-timeouts/datasource/timeouts" +) +```` + +You can use this module to mutate the `schema.Schema` as follows: + +```go +func (d *ThingDataSource) Schema(ctx context.Context, req datasource.SchemaRequest, resp *datasource.SchemaResponse) { + resp.Schema = schema.Schema{ + /* ... */ + + Blocks: map[string]schema.Block{ + "timeouts": timeouts.Block(ctx), + }, +``` + +#### Attribute + +If your configuration is using nested attributes to define timeouts, such as the following: + +```hcl +resource "timeouts_example" "example" { + /* ... */ + + timeouts = { + read = "60m" + } +} +``` + +You can use this module to mutate the `schema.Schema` as follows: + +```go +func (d *ThingDataSource) Schema(ctx context.Context, req datasource.SchemaRequest, resp *datasource.SchemaResponse) { + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + /* ... */ + "timeouts": timeouts.Attributes(ctx), + }, +``` + +## Updating Models + +Given a `Read` method which fetches the entire configuration: + +```go +func (e *exampleDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { + var data exampleDataSourceData + + diags := req.Config.Get(ctx, &data) + resp.Diagnostics.Append(diags...) +``` + +Modify the `exampleDataSourceData` model to include a field for timeouts using a [`timeouts.Value`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework-timeouts/datasource/timeouts#Value) type. + +```go +type exampleDataSourceData struct { + /* ... */ + Timeouts timeouts.Value `tfsdk:"timeouts"` +``` + +## Accessing Timeout in Read Method + +Call the [`timeouts.Read()` function](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework-timeouts/datasource/timeouts#Value.Read). + +```go +func (e *exampleDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { + var data exampleDataSourceData + + diags := req.Config.Get(ctx, &data) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + readTimeout, diags := data.Timeouts.Read(ctx, 20*time.Minute) + + resp.Diagnostics.Append(diags...) + + if resp.Diagnostics.HasError() { + return + } + + ctx, cancel := context.WithTimeout(ctx, readTimeout) + defer cancel() + + /* ... */ +} +``` diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/data-sources/validate-configuration.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/data-sources/validate-configuration.mdx new file mode 100644 index 000000000..0a0a55dc6 --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/data-sources/validate-configuration.mdx @@ -0,0 +1,86 @@ +--- +page_title: Validate data source configurations +description: >- + Learn how to validate data source configurations with the Terraform plugin + framework. +--- + +# Validate data source configurations + +[Data sources](/terraform/plugin/framework/data-sources) support validating an entire practitioner configuration in either declarative or imperative logic. Feedback, such as required syntax or acceptable combinations of values, is returned via [diagnostics](/terraform/plugin/framework/diagnostics). + +This page describes implementation details for validating entire data source configurations, typically referencing multiple attributes. Further documentation is available for other configuration validation concepts: + +- [Single attribute validation](/terraform/plugin/framework/validation#attribute-validation) is a schema-based mechanism for implementing attribute-specific validation logic. +- [Type validation](/terraform/plugin/framework/validation#type-validation) is a schema-based mechanism for implementing reusable validation logic for any attribute using the type. + +-> Configuration validation in Terraform occurs without provider configuration ("offline"), so therefore the data source `Configure` method will not have been called. To implement validation with a configured API client, use logic within the `Read` method, which occurs during Terraform's planning phase when possible. + +## ConfigValidators Method + +The [`datasource.DataSourceWithConfigValidators` interface](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/datasource#DataSourceWithConfigValidators) follows a similar pattern to attribute validation and allows for a more declarative approach. This enables consistent validation logic across multiple data sources. Each validator intended for this interface must implement the [`datasource.ConfigValidator` interface](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/datasource#ConfigValidator). + +During execution of the [`terraform validate`](/terraform/cli/commands/validate), [`terraform plan`](/terraform/cli/commands/plan) and [`terraform apply`](/terraform/cli/commands/apply) commands, Terraform calls the provider [`ValidateDataResourceConfig`](/terraform/plugin/framework/internals/rpcs#validatedataresourceconfig-rpc) RPC, in which the framework calls the `ConfigValidators` method on data sources that implement the [`datasource.DataSourceWithConfigValidators` interface](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/datasource#DataSourceWithConfigValidators). + +The [`terraform-plugin-framework-validators` Go module](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework-validators) has a collection of common use case data source configuration validators in the [`datasourcevalidator` package](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework-validators/datasourcevalidator). These use [path expressions](/terraform/plugin/framework/path-expressions) for matching attributes. + +This example will raise an error if a practitioner attempts to configure both `attribute_one` and `attribute_two`: + +```go +// Other methods to implement the datasource.DataSource interface are omitted for brevity +type ThingDataSource struct {} + +func (d ThingDataSource) ConfigValidators(ctx context.Context) []datasource.ConfigValidator { + return []datasource.ConfigValidator{ + datasourcevalidator.Conflicting( + path.MatchRoot("attribute_one"), + path.MatchRoot("attribute_two"), + ), + } +} +``` + +## ValidateConfig Method + +The [`datasource.DataSourceWithValidateConfig` interface](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/datasource#DataSourceWithValidateConfig) is more imperative in design and is useful for validating unique functionality across multiple attributes that typically applies to a single data source. + +During execution of the [`terraform validate`](/terraform/cli/commands/validate), [`terraform plan`](/terraform/cli/commands/plan) and [`terraform apply`](/terraform/cli/commands/apply) commands, Terraform calls the provider [`ValidateDataResourceConfig`](/terraform/plugin/framework/internals/rpcs#validatedataresourceconfig-rpc) RPC, in which the framework calls the `ValidateConfig` method on providers that implement the [`datasource.DatasourceWithValidateConfig` interface](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/datasource#DataSourceWithValidateConfig). + +This example will raise a warning if a practitioner attempts to configure `attribute_one`, but not `attribute_two`: + +```go +// Other methods to implement the datasource.DataSource interface are omitted for brevity +type ThingDataSource struct {} + +type ThingDataSourceModel struct { + AttributeOne types.String `tfsdk:"attribute_one"` + AttributeTwo types.String `tfsdk:"attribute_two"` +} + +func (d ThingDataSource) ValidateConfig(ctx context.Context, req datasource.ValidateConfigRequest, resp *datasource.ValidateConfigResponse) { + var data ThingDataSourceModel + + resp.Diagnostics.Append(req.Config.Get(ctx, &data)...) + + if resp.Diagnostics.HasError() { + return + } + + // If attribute_one is not configured, return without warning. + if data.AttributeOne.IsNull() || data.AttributeOne.IsUnknown() { + return + } + + // If attribute_two is not null, return without warning. + if !data.AttributeTwo.IsNull() { + return + } + + resp.Diagnostics.AddAttributeWarning( + path.Root("attribute_two"), + "Missing Attribute Configuration", + "Expected attribute_two to be configured with attribute_one. "+ + "The data source may return unexpected results.", + ) +} +``` diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/debugging.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/debugging.mdx new file mode 100644 index 000000000..ddae1484b --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/debugging.mdx @@ -0,0 +1,35 @@ +--- +page_title: Debugging framework providers +description: >- + Learn how to implement debugger support in framework Terraform providers. +--- + +# Debugging framework Providers + +This page contains implementation details for inspecting runtime information of a Terraform provider developed with Framework via a debugger tool by adjusting the [provider server](/terraform/plugin/framework/provider-servers) implementation. Review the top level [Debugging](/terraform/plugin/debugging) page for information pertaining to the overall Terraform provider debugging process and other inspection options, such as log-based debugging. + +## Code Implementation + +Update the `main` function for the project to conditionally enable the [`providerserver/ServeOpts.Debug` field](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/providerserver#ServeOpts.Debug). Conventionally, a `-debug` flag is used to control the `Debug` value. + +This example uses a `-debug` flag to enable debugging, otherwise starting the provider normally on protocol version 6: + +```go +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{ + Address: "registry.terraform.io/example-namespace/example", + Debug: debug, + } + + err := providerserver.Serve(context.Background(), provider.New(), opts) + + if err != nil { + log.Fatal(err.Error()) + } +} +``` diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/deprecations.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/deprecations.mdx new file mode 100644 index 000000000..c86ce0ae0 --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/deprecations.mdx @@ -0,0 +1,1286 @@ +--- +page_title: Deprecations, removals, and renames +description: + Use the following recommendations to handle deprecations, removals, and + renames in framework providers. +--- + +# Deprecations, removals, and renames + +Terraform is trusted for managing many facets of infrastructure across many organizations. Part of that trust is due to consistent versioning guidelines and setting expectations for various levels of upgrades. Ensuring backwards compatibility for all patch and minor releases, potentially in concert with any upcoming major changes, is recommended and supported by the Terraform development framework. This allows operators to iteratively update their Terraform configurations rather than require massive refactoring. + +This guide is designed to walk through various scenarios where existing Terraform functionality requires future removal, while maintaining backwards compatibility. Further information about the versioning terminology (e.g. `MAJOR`.`MINOR`.`PATCH`) in this guide can be found in [the versioning guidelines documentation](/terraform/plugin/best-practices/versioning). + +~> **NOTE:** Removals should only ever occur in `MAJOR` version upgrades. + +~> **NOTE:** This documentation references usage of the `DeprecationMessage` field, please see the [schema documentation](/terraform/plugin/framework/handling-data/schemas#deprecationmessage-1) for more detailed guidance on how to structure warning messages and when those warnings will be raised to practitioners. + +## Table of Contents + +- [Provider Attribute Removal](#provider-attribute-removal) +- [Provider Attribute Rename](#provider-attribute-rename) + - [Renaming a Required Attribute](#renaming-a-required-attribute) + - [Renaming an Optional Attribute](#renaming-an-optional-attribute) + - [Renaming a Computed Attribute](#renaming-a-computed-attribute) +- [Provider Data Source or Resource Removal](#provider-data-source-or-resource-removal) +- [Provider Data Source or Resource Rename](#provider-data-source-or-resource-rename) + +## Provider Attribute Removal + +The recommended process for removing an attribute from a data source or resource in a provider is as follows: + +1. Add a [`DeprecationMessage`](/terraform/plugin/framework/handling-data/schemas#deprecationmessage-1) in the attribute schema definition. Set this field to a practitioner actionable message such as `"Remove this attribute's configuration as it's no longer in use and the attribute will be removed in the next major version of the provider."` +2. Ensure the changelog has an entry noting the deprecation. +3. Release a `MINOR` version with the deprecation. +4. In the next `MAJOR` version, remove all code associated with the attribute including the schema definition. +5. Ensure the changelog has an entry noting the removal. +6. Release the `MAJOR` version. + +## Provider Attribute Rename + +When renaming an attribute from one name to another, it is important to keep backwards compatibility with both existing Terraform configurations and the [Terraform state](/terraform/language/state) while operators migrate. To accomplish this, there will be some duplicated logic to support both attributes until the next `MAJOR` release. Once both attributes are appropriately handled, the process for deprecating and removing the old attribute is the same as noted in the [Provider Attribute Removal section](#provider-attribute-removal). + +The procedure for renaming an attribute depends on what type of attribute it is: + +- [Renaming a Required Attribute](#renaming-a-required-attribute) +- [Renaming an Optional Attribute](#renaming-an-optional-attribute) +- [Renaming a Computed Attribute](#renaming-a-computed-attribute) + +### Renaming a Required Attribute + +~> **NOTE:** If the schema definition does not contain `Optional` or `Required`, see the [Renaming a Computed Attribute section](#renaming-a-computed-attribute) instead. If the schema definition contains `Optional` instead of `Required`, see the [Renaming an Optional Attribute section](#renaming-an-optional-attribute). + +-> [Required attributes](/terraform/plugin/framework/handling-data/schemas#required) are also referred to as required "arguments" throughout the Terraform documentation. + +In general, the procedure here does two things: + +- Prevents the operator from needing to define two attributes with the same value. +- Allows the operator to migrate the configuration to the new attribute at the same time requiring that any other references only work with the new attribute. This is to prevent a situation with Terraform showing a difference when the existing attribute is configured, but the new attribute is saved into the Terraform state. For example, in `terraform plan` output format: + +``` +existing_attribute: "" => "value" +new_attribute: "value" => "" +``` + +The recommended process is as follows: + +1. Replace `Required: true` with `Optional: true` in the existing attribute schema definition. +1. Replace `Required` with `Optional` in the existing attribute documentation. +1. Duplicate the schema definition of the existing attribute, renaming one of them with the new attribute name. +1. Duplicate the documentation of the existing attribute, renaming one of them with the new attribute name. +1. Add a [`DeprecationMessage`](/terraform/plugin/framework/handling-data/schemas#deprecationmessage-1) to the schema definition of the existing (now the "old") attribute, noting to use the new attribute in the message. +1. Add `**Deprecated**` to the documentation of the existing (now the "old") attribute, noting to use the new attribute. +1. Add a note to the documentation that either the existing (now the "old") attribute or new attribute must be configured. +1. Add the type-specific [validator](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework-validators) `{type}validator.ExactlyOneOf` to the schema definition of the new attribute, with a path expression matching the old attribute. This will ensure at least one of the attributes is configured, but present an error to the operator if both are configured at the same time. For example, an attribute of type string would use the [`stringvalidator.ExactlyOneOf`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator#ExactlyOneOf) validator. +1. Add conditional logic in the `Create` and `Update` functions of the data source or resource to handle both attributes. Generally, this involves using [`{type}.IsNull()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types/basetypes). +1. Follow the rest of the procedures in the [Provider Attribute Removal section](#provider-attribute-removal). When the old attribute is removed, update the schema definition and documentation of the new attribute back to `Required`, and remove the `{type}validator.ExactlyOneOf` validator. + +#### Example Renaming of a Required Attribute + +Given this sample resource: + +```go +package provider + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/types" +) + +var _ resource.Resource = (*exampleWidgetResource)(nil) + +type exampleWidgetResource struct{} + +func NewWidgetResource() resource.Resource { + return &exampleWidgetResource{} +} + +func (e *exampleWidgetResource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_widget" +} + +func (e *exampleWidgetResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + // ... other configuration ... + + Attributes: map[string]schema.Attribute{ + // ... other attributes ... + + "existing_attribute": schema.StringAttribute{ + Required: true, + }, + }, + } +} + +type exampleWidgetResourceData struct { + // ... other attributes ... + + ExistingAttribute types.String `tfsdk:"existing_attribute"` +} + +func (e *exampleWidgetResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + var data exampleWidgetResourceData + + resp.Diagnostics.Append(req.Config.Get(ctx, &data)...) + if resp.Diagnostics.HasError() { + return + } + + // add attribute to provider create API call + + // ... other logic ... + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} + +func (e *exampleWidgetResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + // ... other logic ... +} + +func (e *exampleWidgetResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + var data exampleWidgetResourceData + + resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...) + if resp.Diagnostics.HasError() { + return + } + + // add attribute to provider update API call + + // ... other logic ... + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} + +func (e *exampleWidgetResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + // ... other logic ... +} +``` + +In order to support renaming `existing_attribute` to `new_attribute`, this sample can be written as the following to support both attributes simultaneously until the `existing_attribute` is removed: + +```go +package provider + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" + "github.com/hashicorp/terraform-plugin-framework/types" +) + +var _ resource.Resource = (*exampleWidgetResource)(nil) + +type exampleWidgetResource struct{} + +func NewWidgetResource() resource.Resource { + return &exampleWidgetResource{} +} + +func (e *exampleWidgetResource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_widget" +} + +func (e *exampleWidgetResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + // ... other configuration ... + + Attributes: map[string]schema.Attribute{ + // ... other attributes ... + + "existing_attribute": schema.StringAttribute{ + Optional: true, + DeprecationMessage: "use new_attribute instead", + }, + "new_attribute": schema.StringAttribute{ + Optional: true, + Validators: []validator.String{ + stringvalidator.ExactlyOneOf(path.Expressions{ + path.MatchRoot("existing_attribute"), + }...), + }, + }, + }, + } +} + +type exampleWidgetResourceData struct { + // ... other attributes ... + + ExistingAttribute types.String `tfsdk:"existing_attribute"` + NewAttribute types.String `tfsdk:"new_attribute"` +} + +func (e *exampleWidgetResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + var data exampleWidgetResourceData + + resp.Diagnostics.Append(req.Config.Get(ctx, &data)...) + if resp.Diagnostics.HasError() { + return + } + + if !data.NewAttribute.IsNull() { + // add NewAttribute to provider create API call + } else { + // add ExistingAttribute to provider create API call + } + + // ... other logic ... + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} + +func (e *exampleWidgetResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + // ... other logic ... +} + +func (e *exampleWidgetResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + var data exampleWidgetResourceData + + resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...) + if resp.Diagnostics.HasError() { + return + } + + if !data.NewAttribute.IsNull() { + // add NewAttribute to provider create API call + } else { + // add ExistingAttribute to provider create API call + } + + // ... other logic ... + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} + +func (e *exampleWidgetResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + // ... other logic ... +} +``` + +When the `existing_attribute` is ready for removal, then this can be written as: + +```go +package provider + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/types" +) + +var _ resource.Resource = (*exampleWidgetResource)(nil) + +type exampleWidgetResource struct{} + +func NewWidgetResource() resource.Resource { + return &exampleWidgetResource{} +} + +func (e *exampleWidgetResource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_widget" +} + +func (e *exampleWidgetResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + // ... other configuration ... + + Attributes: map[string]schema.Attribute{ + // ... other attributes ... + + "new_attribute": schema.StringAttribute{ + Required: true, + }, + }, + } +} + +type exampleWidgetResourceData struct { + // ... other attributes ... + + NewAttribute types.String `tfsdk:"new_attribute"` +} + +func (e *exampleWidgetResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + var data exampleWidgetResourceData + + resp.Diagnostics.Append(req.Config.Get(ctx, &data)...) + if resp.Diagnostics.HasError() { + return + } + + // add NewAttribute to provider create API call + + // ... other logic ... + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} + +func (e *exampleWidgetResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + // ... other logic ... +} + +func (e *exampleWidgetResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + var data exampleWidgetResourceData + + resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...) + if resp.Diagnostics.HasError() { + return + } + + // add NewAttribute to provider create API call + + // ... other logic ... + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} + +func (e *exampleWidgetResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + // ... other logic ... +} +``` + +### Renaming an Optional Attribute + +~> **NOTE:** If the schema definition does not contain `Optional` or `Required`, see the [Renaming a Computed Attribute section](#renaming-a-computed-attribute) instead. If the schema definition contains `Required` instead of `Optional`, see the [Renaming a Required Attribute section](#renaming-a-required-attribute). + +-> [Optional attributes](/terraform/plugin/framework/handling-data/schemas#optional) are also referred to as optional "arguments" throughout the Terraform documentation. + +In general, the procedure here allows the operator to migrate the configuration to the new attribute at the same time requiring that any other references only work with the new attribute. This is to prevent a situation with Terraform showing a difference when the existing attribute is configured, but the new attribute is saved into the Terraform state. For example, in `terraform plan` output format: + +```text +existing_attribute: "" => "value" +new_attribute: "value" => "" +``` + +The recommended process is as follows: + +1. Duplicate the schema definition of the existing attribute, renaming one of them with the new attribute name. +1. Duplicate the documentation of the existing attribute, renaming one of them with the new attribute name. +1. Add a [`DeprecationMessage`](/terraform/plugin/framework/handling-data/schemas#deprecationmessage-1) to the schema definition of the existing (now the "old") attribute, noting to use the new attribute in the message. +1. Add `**Deprecated**` to the documentation of the existing (now the "old") attribute, noting to use the new attribute. +1. Add the type-specific [validator](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework-validators) `{type}validator.ExactlyOneOf` to the schema definition of the new attribute, with a path expression matching the old attribute. This will ensure at least one of the attributes is configured, but present an error to the operator if both are configured at the same time. For example, an attribute of type string would use the [`stringvalidator.ExactlyOneOf`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator#ExactlyOneOf) validator. +1. Add conditional logic in the `Create` and `Update` functions of the data source or resource to handle both attributes. Generally, this involves using [`{type}.IsNull()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types/basetypes). +1. Follow the rest of the procedures in the [Provider Attribute Removal section](#provider-attribute-removal). When the old attribute is removed, remove the `{type}validator.ExactlyOneOf` validator. + +#### Example Renaming of an Optional Attribute + +Given this sample resource: + +```go +package provider + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/types" +) + +var _ resource.Resource = (*exampleWidgetResource)(nil) + +type exampleWidgetResource struct{} + +func NewWidgetResource() resource.Resource { + return &exampleWidgetResource{} +} + +func (e *exampleWidgetResource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_widget" +} + +func (e *exampleWidgetResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + // ... other configuration ... + + Attributes: map[string]schema.Attribute{ + // ... other attributes ... + + "existing_attribute": schema.StringAttribute{ + Optional: true, + }, + }, + } +} + +type exampleWidgetResourceData struct { + // ... other attributes ... + + ExistingAttribute types.String `tfsdk:"existing_attribute"` +} + +func (e *exampleWidgetResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + var data exampleWidgetResourceData + + resp.Diagnostics.Append(req.Config.Get(ctx, &data)...) + if resp.Diagnostics.HasError() { + return + } + + // add attribute to provider create API call + + // ... other logic ... + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} + +func (e *exampleWidgetResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + // ... other logic ... +} + +func (e *exampleWidgetResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + var data exampleWidgetResourceData + + resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...) + if resp.Diagnostics.HasError() { + return + } + + // add attribute to provider update API call + + // ... other logic ... + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} + +func (e *exampleWidgetResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + // ... other logic ... +} +``` + +In order to support renaming `existing_attribute` to `new_attribute`, this sample can be written as the following to support both attributes simultaneously until the `existing_attribute` is removed: + +```go +package provider + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" + "github.com/hashicorp/terraform-plugin-framework/types" +) + +var _ resource.Resource = (*exampleWidgetResource)(nil) + +type exampleWidgetResource struct{} + +func NewWidgetResource() resource.Resource { + return &exampleWidgetResource{} +} + +func (e *exampleWidgetResource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_widget" +} + +func (e *exampleWidgetResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + // ... other configuration ... + + Attributes: map[string]schema.Attribute{ + // ... other attributes ... + + "existing_attribute": schema.StringAttribute{ + Optional: true, + DeprecationMessage: "use new_attribute instead", + }, + + "new_attribute": schema.StringAttribute{ + Optional: true, + Validators: []validator.String{ + stringvalidator.ExactlyOneOf(path.Expressions{ + path.MatchRoot("existing_attribute"), + }...), + }, + }, + }, + } +} + +type exampleWidgetResourceData struct { + // ... other attributes ... + + ExistingAttribute types.String `tfsdk:"existing_attribute"` + NewAttribute types.String `tfsdk:"new_attribute"` +} + +func (e *exampleWidgetResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + var data exampleWidgetResourceData + + resp.Diagnostics.Append(req.Config.Get(ctx, &data)...) + if resp.Diagnostics.HasError() { + return + } + + if !data.NewAttribute.IsNull() { + // add NewAttribute to provider create API call + } else { + // add ExistingAttribute to provider create API call + } + + // ... other logic ... + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} + +func (e *exampleWidgetResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + // ... other logic ... +} + +func (e *exampleWidgetResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + var data exampleWidgetResourceData + + resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...) + if resp.Diagnostics.HasError() { + return + } + + if !data.NewAttribute.IsNull() { + // add NewAttribute to provider create API call + } else { + // add ExistingAttribute to provider create API call + } + + // ... other logic ... + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} + +func (e *exampleWidgetResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + // ... other logic ... +} +``` + +When the `existing_attribute` is ready for removal, then this can be written as: + +```go +package provider + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/types" +) + +var _ resource.Resource = (*exampleWidgetResource)(nil) + +type exampleWidgetResource struct{} + +func NewWidgetResource() resource.Resource { + return &exampleWidgetResource{} +} + +func (e *exampleWidgetResource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_widget" +} + +func (e *exampleWidgetResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + // ... other configuration ... + + Attributes: map[string]schema.Attribute{ + // ... other attributes ... + + "new_attribute": schema.StringAttribute{ + Optional: true, + }, + }, + } +} + +type exampleWidgetResourceData struct { + // ... other attributes ... + + NewAttribute types.String `tfsdk:"new_attribute"` +} + +func (e *exampleWidgetResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + var data exampleWidgetResourceData + + resp.Diagnostics.Append(req.Config.Get(ctx, &data)...) + if resp.Diagnostics.HasError() { + return + } + + // add NewAttribute to provider create API call + + // ... other logic ... + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} + +func (e *exampleWidgetResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + // ... other logic ... +} + +func (e *exampleWidgetResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + var data exampleWidgetResourceData + + resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...) + if resp.Diagnostics.HasError() { + return + } + + // add NewAttribute to provider create API call + + // ... other logic ... + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} + +func (e *exampleWidgetResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + // ... other logic ... +} +``` + +### Renaming a Computed Attribute + +~> **NOTE:** If the schema definition contains `Optional` see the [Renaming an Optional Attribute section](#renaming-an-optional-attribute) instead. If the schema definition contains `Required` see the [Renaming a Required Attribute section](#renaming-a-required-attribute) instead. + +The recommended process is as follows: + +1. Duplicate the schema definition of the existing attribute, renaming one of them with the new attribute name. +1. Duplicate the documentation of the existing attribute, renaming one of them with the new attribute name. +1. Add a [`DeprecationMessage`](/terraform/plugin/framework/handling-data/schemas#deprecationmessage-1) to the schema definition of the existing (now the "old") attribute, noting to use the new attribute in the message. +1. Add `**Deprecated**` to the documentation of the existing (now the "old") attribute, noting to use the new attribute. +1. Set both attributes in the Terraform state in the `Create`, `Update`, and `Read` functions of the resource (`Read` only for data source). +1. Follow the rest of the procedures in the [Provider Attribute Removal section](#provider-attribute-removal). + +#### Example Renaming of a Computed Attribute + +Given this sample resource: + +```go +package provider + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/types" +) + +var _ resource.Resource = (*exampleWidgetResource)(nil) + +type exampleWidgetResource struct{} + +func NewWidgetResource() resource.Resource { + return &exampleWidgetResource{} +} + +func (e *exampleWidgetResource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_widget" +} + +func (e *exampleWidgetResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + // ... other configuration ... + + Attributes: map[string]schema.Attribute{ + // ... other attributes ... + + "existing_attribute": schema.StringAttribute{ + Computed: true, + }, + }, + } +} + +type exampleWidgetResourceData struct { + // ... other attributes ... + + ExistingAttribute types.String `tfsdk:"existing_attribute"` +} + +func (e *exampleWidgetResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + var data exampleWidgetResourceData + + resp.Diagnostics.Append(req.Config.Get(ctx, &data)...) + if resp.Diagnostics.HasError() { + return + } + + // ... other logic ... + data.ExistingAttribute = // set to computed value + + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} + +func (e *exampleWidgetResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + var data exampleWidgetResourceData + + resp.Diagnostics.Append(req.State.Get(ctx, &data)...) + if resp.Diagnostics.HasError() { + return + } + + // ... other logic ... + data.ExistingAttribute = // set to computed value + + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} + +func (e *exampleWidgetResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + var data exampleWidgetResourceData + + resp.Diagnostics.Append(req.Config.Get(ctx, &data)...) + if resp.Diagnostics.HasError() { + return + } + + // ... other logic ... + data.ExistingAttribute = // set to computed value + + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} + +func (e *exampleWidgetResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + // ... other logic ... +} +``` + +In order to support renaming `existing_attribute` to `new_attribute`, this sample can be written as the following to support both attributes simultaneously until the `existing_attribute` is removed: + +```go +package provider + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/types" +) + +var _ resource.Resource = (*exampleWidgetResource)(nil) + +type exampleWidgetResource struct{} + +func NewWidgetResource() resource.Resource { + return &exampleWidgetResource{} +} + +func (e *exampleWidgetResource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_widget" +} + +func (e *exampleWidgetResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + // ... other configuration ... + + Attributes: map[string]schema.Attribute{ + // ... other attributes ... + + "existing_attribute": schema.StringAttribute{ + Computed: true, + DeprecationMessage: "use new_attribute instead", + }, + + "new_attribute": schema.StringAttribute{ + Computed: true, + }, + }, + } +} + +type exampleWidgetResourceData struct { + // ... other attributes ... + + ExistingAttribute types.String `tfsdk:"existing_attribute"` + NewAttribute types.String `tfsdk:"new_attribute"` +} + +func (e *exampleWidgetResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + var data exampleWidgetResourceData + + resp.Diagnostics.Append(req.Config.Get(ctx, &data)...) + if resp.Diagnostics.HasError() { + return + } + + // ... other logic ... + data.ExistingAttribute = // set to computed value + data.NewAttribute = // set to computed value + + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} + +func (e *exampleWidgetResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + var data exampleWidgetResourceData + + resp.Diagnostics.Append(req.State.Get(ctx, &data)...) + if resp.Diagnostics.HasError() { + return + } + + // ... other logic ... + data.ExistingAttribute = // set to computed value + data.NewAttribute = // set to computed value + + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} + +func (e *exampleWidgetResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + var data exampleWidgetResourceData + + resp.Diagnostics.Append(req.Config.Get(ctx, &data)...) + if resp.Diagnostics.HasError() { + return + } + + // ... other logic ... + data.ExistingAttribute = // set to computed value + data.NewAttribute = // set to computed value + + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} + +func (e *exampleWidgetResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + // ... other logic ... +} +``` + +When the `existing_attribute` is ready for removal, then this can be written as: + +```go +package provider + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/types" +) + +var _ resource.Resource = (*exampleWidgetResource)(nil) + +type exampleWidgetResource struct{} + +func NewWidgetResource() resource.Resource { + return &exampleWidgetResource{} +} + +func (e *exampleWidgetResource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_widget" +} + +func (e *exampleWidgetResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + // ... other configuration ... + + Attributes: map[string]schema.Attribute{ + // ... other attributes ... + + "new_attribute": schema.StringAttribute{ + Computed: true, + }, + }, + } +} + +type exampleWidgetResourceData struct { + // ... other attributes ... + + NewAttribute types.String `tfsdk:"new_attribute"` +} + +func (e *exampleWidgetResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + var data exampleWidgetResourceData + + resp.Diagnostics.Append(req.Config.Get(ctx, &data)...) + if resp.Diagnostics.HasError() { + return + } + + // ... other logic ... + data.NewAttribute = // set to computed value + + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} + +func (e *exampleWidgetResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + var data exampleWidgetResourceData + + resp.Diagnostics.Append(req.State.Get(ctx, &data)...) + if resp.Diagnostics.HasError() { + return + } + + // ... other logic ... + data.NewAttribute = // set to computed value + + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} + +func (e *exampleWidgetResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + var data exampleWidgetResourceData + + resp.Diagnostics.Append(req.Config.Get(ctx, &data)...) + if resp.Diagnostics.HasError() { + return + } + + // ... other logic ... + data.NewAttribute = // set to computed value + + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} + +func (e *exampleWidgetResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + // ... other logic ... +} +``` + +## Provider Data Source or Resource Removal + +The recommended process for removing a data source or resource from a provider is as follows: + +1. Add a [`DeprecationMessage`](/terraform/plugin/framework/handling-data/schemas#deprecationmessage) in the data source or resource schema definition. After an operator upgrades to this version, they will be shown a warning with the message provided when using the deprecated data source or resource, but the Terraform run will still complete. +1. Ensure the changelog has an entry noting the deprecation. +1. Release a `MINOR` version with the deprecation. +1. In the next `MAJOR` version, remove all code associated with the deprecated data source or resource except for the schema and replace the `Create`, `Read`, `Update`, and `Delete` functions to always return an error diagnostic. Remove the documentation sidebar link and update the resource or data source documentation page to include information about the removal and any potential migration information. After an operator upgrades to this version, they will be shown an error about the missing data source or resource. +1. Ensure the changelog has an entry noting the removal. +1. Release the `MAJOR` version. +1. In the next `MAJOR` version, remove all code associated with the removed data source or resource. Remove the resource or data source documentation page. +1. Release the `MAJOR` version. + +### Example Resource Removal + +Given this sample provider and resource: + +```go +// ... provider implementation ... + +func (p *exampleProvider) Resources(ctx context.Context) []func() resource.Resource { + return []func() resource.Resource{ + //... other resources ... + NewWidgetResource, + } +} +``` + +```go +func NewWidgetResource() resource.Resource { + return &exampleWidgetResource{} +} + +func (e *exampleWidgetResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + // ... other configuration ... + + Attributes: map[string]schema.Attribute{ + // ... other attributes ... + }, + } +} + +// ... resource implementation ... +``` + +In order to deprecate `example_widget`, this sample can be written as: + +```go +// ... provider implementation ... + +func (p *exampleProvider) Resources(ctx context.Context) []func() resource.Resource { + return []func() resource.Resource{ + //... other resources ... + NewWidgetResource, + } +} +``` + +```go +func NewWidgetResource() resource.Resource { + return &exampleWidgetResource{} +} + +func (e *exampleWidgetResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + // ... other configuration ... + + Attributes: map[string]schema.Attribute{ + // ... other attributes ... + }, + DeprecationMessage: "use example_thing resource instead", + } +} + +// ... resource implementation ... +``` + +To soft remove `example_widget` with a friendly error message, this sample can be written as: + +```go +// ... provider implementation ... + +func (p *exampleProvider) Resources(ctx context.Context) []func() resource.Resource { + return []func() resource.Resource{ + //... other resources ... + NewWidgetResource, + } +} +``` + +```go +func NewWidgetResource() resource.Resource { + return &exampleWidgetResource{} +} + +func (e *exampleWidgetResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + // ... other configuration ... + + Attributes: map[string]schema.Attribute{ + // ... other attributes ... + }, + DeprecationMessage: "use example_thing resource instead", + } +} + +func (e *exampleWidgetResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + resp.Diagnostics.Append( + diag.NewErrorDiagnostic("example_widget resource deprecated", "use example_thing resource instead"), + ) +} + +func (e *exampleWidgetResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + resp.Diagnostics.Append( + diag.NewErrorDiagnostic("example_widget resource deprecated", "use example_thing resource instead"), + ) +} + +func (e *exampleWidgetResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + resp.Diagnostics.Append( + diag.NewErrorDiagnostic("example_widget resource deprecated", "use example_thing resource instead"), + ) +} + +func (e *exampleWidgetResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + resp.Diagnostics.Append( + diag.NewErrorDiagnostic("example_widget resource deprecated", "use example_thing resource instead"), + ) +} + +``` + +To remove `example_widget`: +```go +// ... provider implementation ... + +func (p *exampleProvider) Resources(ctx context.Context) []func() resource.Resource { + return []func() resource.Resource{ + //... other resources ... + } +} +``` + +## Provider Data Source or Resource Rename + +When renaming a resource from one name to another, it is important to keep backwards compatibility with both existing Terraform configurations and the Terraform state while operators migrate. To accomplish this, there will be some duplicated logic to support both resources until the next `MAJOR` release. Once both resources are appropriately handled, the process for deprecating and removing the old resource is the same as noted in the [Provider Data Source or Resource Removal section](#provider-data-source-or-resource-removal). + +The recommended process is as follows: + +1. Duplicate the code of the existing resource, renaming (and potentially modifying) functions as necessary. +1. Duplicate the documentation of the existing resource, renaming (and potentially modifying) as necessary. +1. Add a [`DeprecationMessage`](/terraform/plugin/framework/handling-data/schemas#deprecationmessage) to the schema definition of the existing (now the "old") resource, noting to use the new resource in the message. +1. Add `~> This resource is deprecated and will be removed in the next major version` to the documentation of the existing (now the "old") resource, noting to use the new resource. +1. Add the new resource to the provider [`Resources`](/terraform/plugin/framework/providers#resources) function +1. Follow the rest of the procedures in the [Provider Data Source or Resource Removal section](#provider-data-source-or-resource-removal). + +### Example Resource Renaming + +Given this sample provider and resource: + +```go +// ... provider implementation ... + +func (p *exampleProvider) Resources(ctx context.Context) []func() resource.Resource { + return []func() resource.Resource{ + //... other resources ... + NewExistingWidgetResource, + } +} +``` + +```go +func NewExistingWidgetResource() resource.Resource { + return &exampleExistingWidgetResource{} +} + +func (e *exampleExistingWidgetResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + // ... other configuration ... + + Attributes: map[string]schema.Attribute{ + // ... other attributes ... + }, + } +} + +// ... resource implementation ... +``` + +In order to support renaming `example_existing_widget` to `example_new_widget`, this sample can be written as the following to support both resources simultaneously until the `example_existing_widget` resource is removed: + +```go +// ... provider implementation ... + +func (p *exampleProvider) Resources(ctx context.Context) []func() resource.Resource { + return []func() resource.Resource{ + //... other resources ... + NewExistingWidgetResource, + NewWidgetResource, + } +} +``` + +```go +func NewExistingWidgetResource() resource.Resource { + return &exampleExistingWidgetResource{} +} + +func (e *exampleExistingWidgetResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + // ... other configuration ... + + Attributes: map[string]schema.Attribute{ + // ... other attributes ... + }, + DeprecationMessage: "use example_new_widget resource instead", + } +} + +// ... resource implementation ... +``` + +```go +func NewWidgetResource() resource.Resource { + return &exampleWidgetResource{} +} + +func (e *exampleWidgetResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + // ... other configuration ... + + Attributes: map[string]schema.Attribute{ + // ... other attributes ... + }, + } +} + +// ... resource implementation ... +``` + +To soft remove `example_existing_widget` with a friendly error message: + + +```go +// ... provider implementation ... + +func (p *exampleProvider) Resources(ctx context.Context) []func() resource.Resource { + return []func() resource.Resource{ + //... other resources ... + NewExistingWidgetResource, + NewWidgetResource, + } +} +``` + +```go +func NewExistingWidgetResource() resource.Resource { + return &exampleExistingWidgetResource{} +} + +func (e *exampleExistingWidgetResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + // ... other configuration ... + + Attributes: map[string]schema.Attribute{ + // ... other attributes ... + }, + DeprecationMessage: "use example_new_widget resource instead", + } +} + +func (e *exampleExistingWidgetResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + resp.Diagnostics.Append( + diag.NewErrorDiagnostic("example_existing_widget resource deprecated", "use example_new_widget resource instead"), + ) +} + +func (e *exampleExistingWidgetResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + resp.Diagnostics.Append( + diag.NewErrorDiagnostic("example_existing_widget resource deprecated", "use example_new_widget resource instead"), + ) +} + +func (e *exampleExistingWidgetResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + resp.Diagnostics.Append( + diag.NewErrorDiagnostic("example_existing_widget resource deprecated", "use example_new_widget resource instead"), + ) +} + +func (e *exampleExistingWidgetResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + resp.Diagnostics.Append( + diag.NewErrorDiagnostic("example_existing_widget resource deprecated", "use example_new_widget resource instead"), + ) +} + +// ... resource implementation ... +``` + +```go +func NewWidgetResource() resource.Resource { + return &exampleWidgetResource{} +} + +func (e *exampleWidgetResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + // ... other configuration ... + + Attributes: map[string]schema.Attribute{ + // ... other attributes ... + }, + } +} + +// ... resource implementation ... +``` + +To remove `example_existing_widget`: + +```go +// ... provider implementation ... + +func (p *exampleProvider) Resources(ctx context.Context) []func() resource.Resource { + return []func() resource.Resource{ + //... other resources ... + NewWidgetResource, + } +} +``` + +```go +func NewWidgetResource() resource.Resource { + return &exampleWidgetResource{} +} + +func (e *exampleWidgetResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + // ... other configuration ... + + Attributes: map[string]schema.Attribute{ + // ... other attributes ... + }, + } +} + +// ... resource implementation ... +``` diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/diagnostics.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/diagnostics.mdx new file mode 100644 index 000000000..7208d4845 --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/diagnostics.mdx @@ -0,0 +1,378 @@ +--- +page_title: Errors and warnings +description: >- + Learn how to return errors and warnings from the Terraform plugin framework. +--- + +# Returning errors and warnings + +Providers use `Diagnostics` to surface errors and warnings to practitioners, +such as contextual messages returned from Terraform CLI at the end of +command output: + +```console +$ terraform plan +# ... other plan output ... +╷ +│ Error: Summary +│ +│ on example.tf line #: +│ #: source configuration line +│ +│ Details +╵ +``` + +In the framework, you may encounter them in response structs or as returns from +functions or methods: + +```go +func (m myResource) Create(ctx context.Context, + req resource.CreateRequest, resp *resource.CreateResponse) +``` + +This is the most common form for Diagnostics: a slice that has one or more +errors appended to it. This approach allows your provider to inform +practitioners about all relevant errors and warnings at the same time, allowing +practitioners to fix their configuration or environment more quickly. You +should only append to Diagnostics slices and never replace or remove +information from them. + +The next section will detail the concepts and typical behaviors of +diagnostics, while the final section will outline the typical methods for +working with diagnostics, using functionality from the available +[`diag` package](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/diag). + +## Diagnostic Concepts + +### Severity + +`Severity` specifies whether the diagnostic is an error or a warning. Neither Terraform, nor the framework, supports other severity levels. Use [logging](/terraform/plugin/log/writing) for debugging or informational purposes. + +- An **error** will be displayed to the practitioner and halt Terraform's + execution, not continuing to apply changes to later resources in the graph. + We recommend using errors to inform practitioners about a situation the + provider could not recover from. +- A **warning** will be displayed to the practitioner, but will not halt + further execution, and is considered informative only. We recommend using + warnings to inform practitioners about suboptimal situations that the + practitioner should resolve to ensure stable functioning (e.g., deprecations) + or to inform practitioners about possible unexpected behaviors. + +### Summary + +`Summary` is a short, practitioner-oriented description of the problem. Good +summaries are general—they don't contain specific details about +values—and concise. For example, "Error creating resource", "Invalid +value for foo", or "Field foo is deprecated". + +### Detail + +`Detail` is a longer, more specific practitioner-oriented description of +precisely what went wrong. Good details are specific—they tell the +practitioner exactly what they need to fix and how. For example, "The API +is currently unavailable, please try the request again.", "foo can only contain +letters, numbers, and digits.", or "foo has been deprecated in favor of bar. +Please update your configuration to use bar instead. foo will be removed in a +future release.". + +### Attribute + +`Attribute` identifies the specific part of a configuration that caused the +error or warning. Only diagnostics that pertain to a whole attribute or a +specific attribute value will include this information. + +### Argument + +`Argument` identifies the specific function argument position that caused the +error or warning. Only diagnostics that pertain to a function argument will +include this information. + +## How Errors Affect State + +**Returning an error diagnostic does not stop the state from being updated**. +Terraform will still persist the returned state even when an error diagnostic +is returned with it. This is to allow Terraform to persist the values that have +already been modified when a resource modification requires multiple API +requests or an API request fails after an earlier one succeeded. + +When returning error diagnostics, we recommend resetting the state in the +response to the prior state available in the configuration. + +## diag Package + +The framework provides the `diag` package for interacting with diagnostics. +While the [Go documentation](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/diag) +contains the complete functionality, this section will highlight the most +common methods. + +### Working With Existing Diagnostics + +#### Append + +When receiving `diag.Diagnostics` from a function or method, such as +`Config.Get()` or `State.Set()`, these should typically be appended to the +response diagnostics for the method. This can be accomplished with the +[`Append(in ...diag.Diagnostics)` method](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/diag#Diagnostics.Append). + +For example: + +```go +func (m myResource) Create(ctx context.Context, + req resource.CreateRequest, resp *resource.CreateResponse) { + // ... prior logic ... + diags := req.Config.Get(ctx, &resourceData) + resp.Diagnostics.Append(diags...) + // ... further logic ... +} +``` + +This method automatically ignores `nil` or empty slice diagnostics and +deduplicates where possible. + +#### HasError + +The most typical form of diagnostics checking is ensuring that execution should +not stop due to encountering an error, potentially causing further confusing +errors or crashes. The [`HasError()` method](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/diag#Diagnostics.HasError) +will check each of the diagnostics for error severity and return true if found. + +For example: + +```go +func (m myResource) Create(ctx context.Context, + req resource.CreateRequest, resp *resource.CreateResponse) { + // ... prior logic ... + diags := req.Config.Get(ctx, &resourceData) + resp.Diagnostics.Append(diags...) + + if resp.Diagnostics.HasError() { + return + } + // ... further logic ... +} +``` + +In this example, you will note that we opted to check `resp.Diagnostics` +instead of `diags`. Technically checking either is correct, however, checking +the response diagnostics can help ensure that any response will include the +expected diagnostics. + +### Creating Diagnostics + +When working with logic outside the framework, such as interacting with the +vendor or `net/http` library to make the actual calls to manage infrastructure +or creating custom plan modifiers and validators, it will be necessary to +create diagnostics. The `diag` package provides helper methods and allows +custom abstractions as described below. + +To craft the summary of a diagnostic, it is recommended to use a concise title +or single sentence that immediately can allow the practitioner to determine +the error cause and when it occurred. + +To craft the details portion of diagnostics, it is recommended to provide +practitioners (and potentially you as the maintainer) as much contextual, +troubleshooting, and next action information as possible. These details can +use newlines for easier readability where necessary. + +For example, with the top line as a summary and below as details: + +```text +API Error Reading Resource +``` + +```text +An unexpected error was encountered while reading the resource. + +Please check that the credentials being used are active and have sufficient +permissions to perform the Example API call against the resource. + +Region: example +ID: example123 +API Response: 403 Access Denied +``` + +#### AddError and AddWarning + +When creating diagnostics that affect an entire data source, provider, or +resource, and where a `diag.Diagnostics` is already available such as within +a response type, the [`AddError(summary string, detail string)` method](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/diag#Diagnostics.AddError) +and [`AddWarning(summary string, detail string)` method](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/diag#Diagnostics.AddWarning) +can append a new error or warning diagnostic. + +For example: + +```go +func (m myResource) Create(ctx context.Context, + req resource.CreateRequest, resp *resource.CreateResponse) { + // ... prior logic ... + resp, err := http.Post("https://example.com") + + if err != nil { + resp.Diagnostics.AddError( + "API Error Creating Resource", + fmt.Sprintf("... details ... %s", err) + ) + return + } + // ... further logic ... +} +``` + +#### AddAttributeError and AddAttributeWarning + +When creating diagnostics that affect only a single attribute, which is +typical of attribute-level plan modifiers and validators, the +[`AddAttributeError(path path.Path, summary string, detail string)` method](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/diag#Diagnostics.AddAttributeError) +and [`AddAttributeWarning(path path.Path, summary string, detail string)` method](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/diag#Diagnostics.AddAttributeWarning) +can append a new error or warning diagnostic pointing specifically at the +attribute path. This provides additional context to practitioners, such as +showing the specific line(s) and value(s) of configuration where possible. + +For example: + +```go +func (s exampleType) Validate(ctx context.Context, in tftypes.Value, path path.Path) diag.Diagnostics { + var diags diag.Diagnostics + + if !in.Type().Is(tftypes.Set{}) { + err := fmt.Errorf() + diags.AddAttributeError( + path, + "Example Type Validation Error", + "An unexpected error was encountered trying to validate an attribute value. "+ + "This is always an error in the provider. "+ + "Please report the following to the provider developer:\n\n"+ + fmt.Sprintf("Expected Set value, received %T with value: %v", in, in), + ) + return diags + } + // ... further logic ... +``` + +### Consistent Diagnostic Creation + +Create a helper function in your provider code using the diagnostic creation functions available in the [`diag` package](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/diag) to generate consistent diagnostics for types of errors/warnings. It is also possible to use [custom diagnostics types](#custom-diagnostics-types) to accomplish this same goal. + +The [`diag` package](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/diag) provides these functions to create various diagnostics: + +| Function | Description | +|---|---| +| [`diag.NewArgumentErrorDiagnostic()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/diag#NewArgumentErrorDiagnostic) | Create a new error diagnostic with a function argument position. | +| [`diag.NewArgumentWarningDiagnostic()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/diag#NewArgumentWarningDiagnostic) | Create a new warning diagnostic with a function argument position. | +| [`diag.NewAttributeErrorDiagnostic()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/diag#NewAttributeErrorDiagnostic) | Create a new error diagnostic with a [path](/terraform/plugin/framework/handling-data/paths). | +| [`diag.NewAttributeWarningDiagnostic()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/diag#NewAttributeWarningDiagnostic) | Create a new warning diagnostic with a [path](/terraform/plugin/framework/handling-data/paths). | +| [`diag.NewErrorDiagnostic()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/diag#NewErrorDiagnostic) | Create a new error diagnostic without a [path](/terraform/plugin/framework/handling-data/paths). | +| [`diag.NewWarningDiagnostic()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/diag#NewWarningDiagnostic) | Create a new warning diagnostic without a [path](/terraform/plugin/framework/handling-data/paths). | + +In this example, the provider code is setup to always convert `error` returns from the API SDK to a consistent error diagnostic. + +```go +func APIErrorDiagnostic(err error) diag.Diagnostic { + return diag.NewErrorDiagnostic( + "Unexpected API Error", + "While calling the API, an unexpected error was returned in the response. "+ + "Please contact support if you are unsure how to resolve the error.\n\n"+ + "Error: "+err.Error(), + ) +} +``` + +This enables calling code in the provider, such as: + +```go +func (r ThingResource) Read(ctx context.Context, req resource.ReadRequest, resp resource.ReadResponse) { + // ... other logic ... + + apiResp, err := examplesdk.Read(/* ... */) // example API SDK call that may return an error + + if err != nil { + resp.Diagnostics.Append(APIErrorDiagnostic(err)) + + return + } + + // ... further logic ... +} +``` + +## Custom Diagnostics Types + +Advanced provider developers may want to store additional data in diagnostics for other logic or create custom diagnostics that include specialized logic. + +The [`diag.Diagnostic` interface](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/diag#Diagnostic) that can be implemented with these methods: + +```go +type Diagnostic interface { + Severity() Severity + Summary() string + Detail() string + Equal(Diagnostic) bool +} +``` + +To include attribute path information, the [`diag.DiagnosticWithPath` interface](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/diag#DiagnosticWithPath) can be implemented with the additional `Path()` method: + +```go +type DiagnosticWithPath interface { + Diagnostic + Path() path.Path +} +``` + +To include function argument information, the [`diag.DiagnosticWithFunctionArgument` interface](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/diag#DiagnosticWithFunctionArgument) can be implemented with the additional `FunctionArgument()` method: + +```go +type DiagnosticWithFunctionArgument interface { + Diagnostic + FunctionArgument() int +} +``` + +In this example, a custom diagnostic type stores an underlying `error` that caused the diagnostic: + +```go +// UnderlyingErrorDiagnostic is an error diagnostic +// which also stores the underlying error. +type UnderlyingErrorDiagnostic struct { + Detail string + Summary string + UnderlyingError error +} + +func (d UnderlyingErrorDiagnostic) Detail() string { + return d.Detail +} + +func (d UnderlyingErrorDiagnostic) Equal(o SpecialDiagnostic) bool { + if d.Detail() != o.Detail() { + return false + } + + if d.Summary() != o.Summary() { + return false + } + + if d.UnderlyingError == nil { + return o.UnderlyingError == nil + } + + if o.UnderlyingError == nil { + return false + } + + if d.UnderlyingError.Error() != o.UnderlyingError.Error() { + return false + } + + return true +} + +func (d UnderlyingErrorDiagnostic) Severity() diag.Severity { + return diag.SeverityError +} + +func (d UnderlyingErrorDiagnostic) Summary() string { + return d.Summary +} +``` diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/ephemeral-resources/close.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/ephemeral-resources/close.mdx new file mode 100644 index 000000000..dade8f2bd --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/ephemeral-resources/close.mdx @@ -0,0 +1,94 @@ +--- +page_title: Closing ephemeral resources +description: >- + Learn how to close ephemeral resource in the Terraform plugin framework. +--- + +# Closing Ephemeral Resources + +Close is an optional part of the Terraform lifecycle for an ephemeral resource, which is different from the [managed resource lifecycle](https://github.com/hashicorp/terraform/blob/main/docs/resource-instance-change-lifecycle.md). During any Terraform operation (like [`terraform plan`](/terraform/cli/commands/plan) or [`terraform apply`](/terraform/cli/commands/apply)), when an ephemeral resource's data is needed, Terraform initially retrieves that data with the [`Open`](/terraform/plugin/framework/ephemeral-resources/open) lifecycle handler. Once the ephemeral resource data is no longer needed, Terraform calls the provider `CloseEphemeralResource` RPC, in which the framework calls the [`ephemeral.EphemeralResourceWithClose` interface `Close` method](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/ephemeral#EphemeralResourceWithClose). The request contains any `Private` data set in the latest `Open` or `Renew` call. + +`Close` is an optional lifecycle implementation for an ephemeral resource, other lifecycle implementations include: + +- [Open](/terraform/plugin/framework/ephemeral-resources/open) an ephemeral resource by receiving Terraform configuration, retrieving a remote object, and returning ephemeral result data to Terraform. +- [Renew](/terraform/plugin/framework/ephemeral-resources/renew) an expired remote object at a specified time. + +## Define Close Method + +The [`ephemeral.EphemeralResourceWithClose` interface](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/ephemeral#EphemeralResourceWithClose) on the [`ephemeral.EphemeralResource` interface](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/ephemeral#EphemeralResource) implementation will enable close support for an ephemeral resource. + +Implement the `Close` method by: + +1. [Accessing private data](/terraform/plugin/framework/resources/private-state#reading-private-state-data) from [`ephemeral.CloseRequest.Private` field](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/ephemeral#CloseRequest.Private) needed to close the remote object. +1. Performing logic or external calls to close the remote object. + +If the logic needs to return [warning or error diagnostics](/terraform/plugin/framework/diagnostics), they can be added into the [`ephemeral.CloseResponse.Diagnostics` field](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/ephemeral#CloseResponse.Diagnostics). + +In this example, an ephemeral resource named `examplecloud_thing` with hardcoded behavior is defined. `Private` data needed to execute `Close` is passed from the `Open` response: + +```go +var _ ephemeral.EphemeralResourceWithClose = (*ThingEphemeralResource)(nil) + +// ThingEphemeralResource defines the ephemeral resource implementation, which also implements Close. +type ThingEphemeralResource struct{} + +type ThingEphemeralResourceModel struct { + Name types.String `tfsdk:"name"` + Token types.String `tfsdk:"token"` +} + +type ThingPrivateData struct { + Name string `json:"name"` +} + +func (e *ThingEphemeralResource) Schema(ctx context.Context, req ephemeral.SchemaRequest, resp *ephemeral.SchemaResponse) { + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "name": schema.StringAttribute{ + Description: "Name of the thing to retrieve a token for.", + Required: true, + }, + "token": schema.StringAttribute{ + Description: "Token for the thing.", + Computed: true, + }, + }, + } +} + +func (e *ThingEphemeralResource) Open(ctx context.Context, req ephemeral.OpenRequest, resp *ephemeral.OpenResponse) { + var data ThingEphemeralResourceModel + + // Read Terraform config data into the model + resp.Diagnostics.Append(req.Config.Get(ctx, &data)...) + if resp.Diagnostics.HasError() { + return + } + + // Typically ephemeral resources will make external calls and reference returned data, + // however this example hardcodes the setting of result and private data for brevity. + data.Token = types.StringValue("token-123") + + // When closing, pass along this data (error handling omitted for brevity). + privateData, _ := json.Marshal(ThingPrivateData{Name: data.Name.ValueString()}) + resp.Private.SetKey(ctx, "thing_data", privateData) + + // Save data into ephemeral result data + resp.Diagnostics.Append(resp.Result.Set(ctx, &data)...) +} + +func (e *ThingEphemeralResource) Close(ctx context.Context, req ephemeral.CloseRequest, resp *ephemeral.CloseResponse) { + privateBytes, diags := req.Private.GetKey(ctx, "thing_data") + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + // Unmarshal private data (error handling omitted for brevity). + var privateData ThingPrivateData + json.Unmarshal(privateBytes, &privateData) + + // Perform external call to close/clean up "thing" data +} + +``` diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/ephemeral-resources/configure.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/ephemeral-resources/configure.mdx new file mode 100644 index 000000000..fef8e4a12 --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/ephemeral-resources/configure.mdx @@ -0,0 +1,104 @@ +--- +page_title: Configuring ephemeral resources +description: >- + Learn how to configure ephemeral resources with provider data or clients in + the Terraform plugin framework. +--- + +# Configuring ephemeral resources + +[Ephemeral Resources](/terraform/plugin/framework/ephemeral-resources) may require provider-level data or remote system clients to operate correctly. The framework supports the ability to configure this data and/or clients once within the provider, then pass that information to ephemeral resources by adding the `Configure` method. + +## Prepare Provider Configure Method + +Implement the [`provider.ConfigureResponse.EphemeralResourceData` field](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/provider#ConfigureResponse.EphemeralResourceData) in the [`Provider` interface `Configure` method](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/provider#Provider.Configure). This value can be set to any type, whether an existing client or vendor SDK type, a provider-defined custom type, or the provider implementation itself. It is recommended to use pointer types so that ephemeral resources can determine if this value was configured before attempting to use it. + +During execution of the [`terraform plan`](/terraform/cli/commands/plan) and [`terraform apply`](/terraform/cli/commands/apply) commands, Terraform calls the [`ConfigureProvider`](/terraform/plugin/framework/internals/rpcs#configureprovider-rpc) RPC, in which the framework calls the [`provider.Provider` interface `Configure` method](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/provider#Provider.Configure). + +In this example, the Go standard library [`net/http.Client`](https://pkg.go.dev/net/http#Client) is configured in the provider, and made available for ephemeral resources: + +```go +// With the provider.Provider implementation +func (p *ExampleCloudProvider) Configure(ctx context.Context, req provider.ConfigureRequest, resp *provider.ConfigureResponse) { + resp.EphemeralResourceData = &http.Client{/* ... */} +} +``` + +In this example, the code defines an `ExampleClient` type that is made available for ephemeral resources: + +```go +type ExampleClient struct { + /* ... */ +} + +// With the provider.Provider implementation +func (p *ExampleCloudProvider) Configure(ctx context.Context, req provider.ConfigureRequest, resp *provider.ConfigureResponse) { + resp.EphemeralResourceData = &ExampleClient{/* ... */} +} +``` + +In this example, the `ExampleCloudProvider` type itself is made available for ephemeral resources: + +```go +// With the provider.Provider implementation +type ExampleCloudProvider struct { + /* ... */ +} + +func (p *ExampleCloudProvider) Configure(ctx context.Context, req provider.ConfigureRequest, resp *provider.ConfigureResponse) { + resp.EphemeralResourceData = p +} +``` + +## Define Ephemeral Resource Configure Method + +Implement the [`ephemeral.EphemeralResourceWithConfigure` interface](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/ephemeral#EphemeralResourceWithConfigure) which receives the provider configured data from the [`Provider` interface `Configure` method](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/provider#Provider.Configure) and saves it into the [`ephemeral.EphemeralResource` interface](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/ephemeral#EphemeralResource) implementation. + +The [`ephemeral.EphemeralResourceWithConfigure` interface `Configure` method](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/ephemeral#EphemeralResourceWithConfigure.Configure) is called during execution of the [`terraform validate`](/terraform/cli/commands/validate), [`terraform plan`](/terraform/cli/commands/plan) and [`terraform apply`](/terraform/cli/commands/apply) commands when the `ValidateEphemeralResourceConfig` RPC is sent. Additionally, the [`ephemeral.EphemeralResourceWithConfigure` interface `Configure` method](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/ephemeral#EphemeralResourceWithConfigure.Configure) is called during execution of the [`terraform plan`](/terraform/cli/commands/plan) and [`terraform apply`](/terraform/cli/commands/apply) commands when the `OpenEphemeralResource` RPC is sent. + +-> Note that Terraform calling the `ValidateEphemeralResourceConfig` RPC would not call the [`ConfigureProvider`](/terraform/plugin/framework/internals/rpcs#configureprovider-rpc) RPC first, so implementations need to account for that situation. Configuration validation in Terraform occurs without provider configuration ("offline"). + +In this example, the provider configured the Go standard library [`net/http.Client`](https://pkg.go.dev/net/http#Client) which the ephemeral resource uses during `Open`: + +```go +// With the ephemeral.EphemeralResource implementation +type ThingEphemeralResource struct { + client *http.Client +} + +func (d *ThingEphemeralResource) Configure(ctx context.Context, req ephemeral.ConfigureRequest, resp *ephemeral.ConfigureResponse) { + // Always perform a nil check when handling ProviderData because Terraform + // sets that data after it calls the ConfigureProvider RPC. + if req.ProviderData == nil { + return + } + + client, ok := req.ProviderData.(*http.Client) + + if !ok { + resp.Diagnostics.AddError( + "Unexpected Ephemeral Resource Configure Type", + fmt.Sprintf("Expected *http.Client, got: %T. Please report this issue to the provider developers.", req.ProviderData), + ) + + return + } + + d.client = client +} + +func (d *ThingEphemeralResource) Open(ctx context.Context, req ephemeral.OpenRequest, resp *ephemeral.OpenResponse) { + // Prevent panic if the provider has not been configured. + if d.client == nil { + resp.Diagnostics.AddError( + "Unconfigured HTTP Client", + "Expected configured HTTP client. Please report this issue to the provider developers.", + ) + + return + } + + httpResp, err := d.client.Get("https://example.com") + /* ... */ +} +``` diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/ephemeral-resources/index.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/ephemeral-resources/index.mdx new file mode 100644 index 000000000..194d65d5f --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/ephemeral-resources/index.mdx @@ -0,0 +1,101 @@ +--- +page_title: Ephemeral resources +description: >- + Ephemeral resources allow Terraform to reference external data, while + guaranteeing that this data will not be persisted in plan or state. Learn how + to implement ephemeral resources in the Terraform plugin framework. +--- + +# Ephemeral resources + + + +Ephemeral resource support is in technical preview and offered without compatibility promises until Terraform 1.10 is generally available. + + + +[Ephemeral resources](/terraform/language/v1.10.x/resources/ephemeral) are an abstraction that allows Terraform to reference external data. Unlike [data sources](/terraform/language/data-sources), Terraform guarantees that ephemeral resource data will not be persisted in plan or state artifacts. The data produced by an ephemeral resource can only be referenced in [specific ephemeral contexts](/terraform/language/v1.10.x/resources/ephemeral#referencing-ephemeral-resources) or Terraform will throw an error. + +This page describes the basic implementation details required for supporting an ephemeral resource within the provider. Ephemeral resources, as a part of their lifecycle, must implement: + +- [Open](/terraform/plugin/framework/ephemeral-resources/open) an ephemeral resource by receiving Terraform configuration, retrieving a remote object, and returning ephemeral result data to Terraform. + +Further documentation is available for deeper ephemeral resource concepts: + +- [Configure](/terraform/plugin/framework/ephemeral-resources/configure) an ephemeral resource with provider-level data types or clients. +- [Validate](/terraform/plugin/framework/ephemeral-resources/validate-configuration) practitioner configuration against acceptable values. +- [Renew](/terraform/plugin/framework/ephemeral-resources/renew) an expired remote object at a specified time. +- [Close](/terraform/plugin/framework/ephemeral-resources/close) a remote object when Terraform no longer needs the data. + +## Define Ephemeral Resource Type + +Implement the [`ephemeral.EphemeralResource` interface](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/ephemeral#EphemeralResource). Ensure the [Add Ephemeral Resource To Provider](#add-ephemeral-resource-to-provider) documentation is followed so the ephemeral resource becomes part of the provider implementation, and therefore available to practitioners. + +### Metadata Method + +The [`ephemeral.EphemeralResource` interface `Metadata` method](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/ephemeral#EphemeralResource.Metadata) defines the ephemeral resource name as it would appear in Terraform configurations. This name should include the provider type prefix, an underscore, then the ephemeral resource specific name. For example, a provider named `examplecloud` and an ephemeral resource that reads "thing" ephemeral data would be named `examplecloud_thing`. + +In this example, the ephemeral resource name in an `examplecloud` provider that reads "thing" ephemeral resource data is hardcoded to `examplecloud_thing`: + +```go +// With the ephemeral.EphemeralResource implementation +func (r *ThingEphemeralResource) Metadata(ctx context.Context, req ephemeral.MetadataRequest, resp *ephemeral.MetadataResponse) { + resp.TypeName = "examplecloud_thing" +} +``` + +To simplify ephemeral resource implementations, the [`provider.MetadataResponse.TypeName` field](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/provider#MetadataResponse.TypeName) from the [`provider.Provider` interface `Metadata` method](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/provider#Provider.Metadata) can set the provider name so it is available in the [`ephemeral.MetadataRequest.ProviderTypeName` field](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/ephemeral#MetadataRequest.ProviderTypeName). + +In this example, the provider defines the `examplecloud` name for itself, and the ephemeral resource is named `examplecloud_thing`: + +```go +// With the provider.Provider implementation +func (p *ExampleCloudProvider) Metadata(ctx context.Context, req provider.MetadataRequest, resp *provider.MetadataResponse) { + resp.TypeName = "examplecloud" +} + +// With the ephemeral.EphemeralResource implementation +func (d *ThingEphemeralResource) Metadata(ctx context.Context, req ephemeral.MetadataRequest, resp *ephemeral.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_thing" +} +``` + +### Schema Method + +The [`ephemeral.EphemeralResource` interface `Schema` method](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/ephemeral#EphemeralResource.Schema) defines a [schema](/terraform/plugin/framework/handling-data/schemas) describing what data is available in the ephemeral resource's configuration and result data. + +## Add Ephemeral Resource to Provider + +Ephemeral resources become available to practitioners when they are included in the [provider](/terraform/plugin/framework/providers) implementation via the optional [`provider.ProviderWithEphemeralResources` interface `EphemeralResources` method](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/provider#ProviderWithEphemeralResources.EphemeralResource). + +In this example, the `ThingEphemeralResource` type, which implements the `ephemeral.EphemeralResource` interface, is added to the provider implementation: + +```go +var _ provider.ProviderWithEphemeralResources = (*ExampleCloudProvider)(nil) + +func (p *ExampleCloudProvider) EphemeralResources(_ context.Context) []func() ephemeral.EphemeralResource { + return []func() ephemeral.EphemeralResource{ + func() ephemeral.EphemeralResource { + return &ThingResource{}, + }, + } +} +``` + +To simplify provider implementations, a named function can be created with the ephemeral resource implementation. + +In this example, the `ThingEphemeralResource` code includes an additional `NewThingEphemeralResource` function, which simplifies the provider implementation: + +```go +// With the provider.ProviderWithEphemeralResources implementation +func (p *ExampleCloudProvider) EphemeralResources(_ context.Context) []func() ephemeral.EphemeralResource { + return []func() ephemeral.EphemeralResource{ + NewThingEphemeralResource, + } +} + +// With the ephemeral.EphemeralResource implementation +func NewThingEphemeralResource() ephemeral.EphemeralResource { + return &ThingEphemeralResource{} +} +``` diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/ephemeral-resources/open.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/ephemeral-resources/open.mdx new file mode 100644 index 000000000..3f7ff646a --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/ephemeral-resources/open.mdx @@ -0,0 +1,76 @@ +--- +page_title: Opening ephemeral resources +description: >- + Learn how to open ephemeral resource in the Terraform plugin framework. +--- + +# Opening ephemeral resources + +Open is part of the Terraform lifecycle for an ephemeral resource, which is different from the [managed resource lifecycle](https://github.com/hashicorp/terraform/blob/main/docs/resource-instance-change-lifecycle.md). During any Terraform operation (like [`terraform plan`](/terraform/cli/commands/plan) or [`terraform apply`](/terraform/cli/commands/apply)), when an ephemeral resource's data is needed, Terraform calls the provider `OpenEphemeralResource` RPC, in which the framework calls the [`ephemeral.EphemeralResource` interface `Open` method](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/ephemeral#EphemeralResource.Open). The request contains the configuration supplied to Terraform for the ephemeral resource. The response contains the ephemeral result data. The data is defined by the [schema](/terraform/plugin/framework/handling-data/schemas) of the ephemeral resource. + +`Open` is the only required lifecycle implementation for an ephemeral resource, optional lifecycle implementations include: + +- [Renew](/terraform/plugin/framework/ephemeral-resources/renew) an expired remote object at a specified time. +- [Close](/terraform/plugin/framework/ephemeral-resources/close) a remote object when Terraform no longer needs the data. + +## Define Open Method + +Implement the `Open` method by: + +1. [Accessing the `Config` data](/terraform/plugin/framework/handling-data/accessing-values) from the [`ephemeral.OpenRequest` type](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/ephemeral#OpenRequest). +1. Performing logic or external calls to read the result data for the ephemeral resource. +1. Determining if a remote object needs to be renewed, setting the [`ephemeral.OpenResponse.RenewAt` field](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/ephemeral#OpenResponse.RenewAt) to indicate to Terraform when to call the provider [`Renew`](/terraform/plugin/framework/ephemeral-resources/renew) method. +1. [Writing private data](/terraform/plugin/framework/resources/private-state#saving-private-state-data) needed to `Renew` or `Close` the ephemeral resource to the [`ephemeral.OpenResponse.Private` field](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/ephemeral#OpenResponse.Private). +1. [Writing result data](/terraform/plugin/framework/writing-state) into the [`ephemeral.OpenResponse.Result` field](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/ephemeral#OpenResponse.Result). + +If the logic needs to return [warning or error diagnostics](/terraform/plugin/framework/diagnostics), they can be added into the [`ephemeral.OpenResponse.Diagnostics` field](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/ephemeral#OpenResponse.Diagnostics). + +In this example, an ephemeral resource named `examplecloud_thing` with hardcoded behavior is defined: + +```go +// ThingEphemeralResource defines the ephemeral resource implementation. +// Some ephemeral.EphemeralResource interface methods are omitted for brevity. +type ThingEphemeralResource struct {} + +type ThingEphemeralResourceModel struct { + Name types.String `tfsdk:"name"` + Token types.String `tfsdk:"token"` +} + +func (e *ThingEphemeralResource) Schema(ctx context.Context, req ephemeral.SchemaRequest, resp *ephemeral.SchemaResponse) { + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "name": schema.StringAttribute{ + Description: "Name of the thing to retrieve a token for.", + Required: true, + }, + "token": schema.StringAttribute{ + Description: "Token for the thing.", + Computed: true, + }, + }, + } +} + +func (e *ThingEphemeralResource) Open(ctx context.Context, req ephemeral.OpenRequest, resp *ephemeral.OpenResponse) { + var data ThingEphemeralResourceModel + + // Read Terraform config data into the model + resp.Diagnostics.Append(req.Config.Get(ctx, &data)...) + if resp.Diagnostics.HasError() { + return + } + + // Typically ephemeral resources will make external calls, however this example + // hardcodes setting the token attribute to a specific value for brevity. + data.Token = types.StringValue("token-123") + + // Save data into ephemeral result data + resp.Diagnostics.Append(resp.Result.Set(ctx, &data)...) +} +``` + +## Caveats + +* An error is returned if the `Result` data contains unknown values. Set all attributes to either null or known values in the response. +* An error is returned unless every non-computed known value in the request config is saved exactly as-is into the result data. Only null values marked as computed can be modified. diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/ephemeral-resources/renew.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/ephemeral-resources/renew.mdx new file mode 100644 index 000000000..dadd729f7 --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/ephemeral-resources/renew.mdx @@ -0,0 +1,113 @@ +--- +page_title: Renewing ephemeral resources +description: >- + Learn how to renew ephemeral resource in the Terraform plugin framework. +--- + +# Renewing ephemeral resources + +Renew is an optional part of the Terraform lifecycle for an ephemeral resource, which is different from the [managed resource lifecycle](https://github.com/hashicorp/terraform/blob/main/docs/resource-instance-change-lifecycle.md). During any Terraform operation (like [`terraform plan`](/terraform/cli/commands/plan) or [`terraform apply`](/terraform/cli/commands/apply)), when an ephemeral resource's data is needed, Terraform initially retrieves that data with the [`Open`](/terraform/plugin/framework/ephemeral-resources/open) lifecycle handler. During `Open`, ephemeral resources can opt to include a timestamp in the `RenewAt` response field to indicate to Terraform when a provider must renew an ephemeral resource. If an ephemeral resource's data is still in-use and the `RenewAt` timestamp has passed, Terraform calls the provider `RenewEphemeralResource` RPC, in which the framework calls the [`ephemeral.EphemeralResourceWithRenew` interface `Renew` method](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/ephemeral#EphemeralResourceWithRenew). The request contains any `Private` data set in the latest `Open` or `Renew` call. The response contains `Private` data and an optional `RenewAt` field for further renew executions. + + + +`Renew` cannot return new result data for the ephemeral resource instance, so this logic is only appropriate for remote objects like HashiCorp Vault leases, which can be renewed without changing their data. + + + +`Renew` is an optional lifecycle implementation for an ephemeral resource, other lifecycle implementations include: + +- [Open](/terraform/plugin/framework/ephemeral-resources/open) an ephemeral resource by receiving Terraform configuration, retrieving a remote object, and returning ephemeral result data to Terraform. +- [Close](/terraform/plugin/framework/ephemeral-resources/close) a remote object when Terraform no longer needs the data. + +## Define Renew Method + +The [`ephemeral.EphemeralResourceWithRenew` interface](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/ephemeral#EphemeralResourceWithRenew) on the [`ephemeral.EphemeralResource` interface](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/ephemeral#EphemeralResource) implementation will enable renew support for an ephemeral resource. + +Implement the `Renew` method by: + +1. [Accessing private data](/terraform/plugin/framework/resources/private-state#reading-private-state-data) from [`ephemeral.RenewRequest.Private` field](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/ephemeral#RenewRequest.Private) needed to renew the remote object. +1. Performing logic or external calls to renew the remote object. +1. Determining if a remote object needs to be renewed again, setting the [`ephemeral.RenewResponse.RenewAt` field](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/ephemeral#RenewResponse.RenewAt) to indicate to Terraform when to call the provider [`Renew`](/terraform/plugin/framework/ephemeral-resources/renew) method. +1. [Writing private data](/terraform/plugin/framework/resources/private-state#saving-private-state-data) needed to `Renew` or `Close` the ephemeral resource to the [`ephemeral.RenewResponse.Private` field](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/ephemeral#RenewResponse.Private). + +If the logic needs to return [warning or error diagnostics](/terraform/plugin/framework/diagnostics), they can be added into the [`ephemeral.RenewResponse.Diagnostics` field](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/ephemeral#RenewResponse.Diagnostics). + +In this example, an ephemeral resource named `examplecloud_thing` with hardcoded behavior is defined. It indicates a renewal should occur 5 minutes from when either the `Open` or `Renew` method is executed: + +```go +var _ ephemeral.EphemeralResourceWithRenew = (*ThingEphemeralResource)(nil) + +// ThingEphemeralResource defines the ephemeral resource implementation, which also implements Renew. +type ThingEphemeralResource struct{} + +type ThingEphemeralResourceModel struct { + Name types.String `tfsdk:"name"` + Token types.String `tfsdk:"token"` +} + +type ThingPrivateData struct { + Name string `json:"name"` +} + +func (e *ThingEphemeralResource) Schema(ctx context.Context, req ephemeral.SchemaRequest, resp *ephemeral.SchemaResponse) { + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "name": schema.StringAttribute{ + Description: "Name of the thing to retrieve a token for.", + Required: true, + }, + "token": schema.StringAttribute{ + Description: "Token for the thing.", + Computed: true, + }, + }, + } +} + +func (e *ThingEphemeralResource) Open(ctx context.Context, req ephemeral.OpenRequest, resp *ephemeral.OpenResponse) { + var data ThingEphemeralResourceModel + + // Read Terraform config data into the model + resp.Diagnostics.Append(req.Config.Get(ctx, &data)...) + if resp.Diagnostics.HasError() { + return + } + + // Typically ephemeral resources will make external calls and reference returned data, + // however this example hardcodes the setting of result and private data for brevity. + data.Token = types.StringValue("token-123") + + // Renew 5 minutes from now + resp.RenewAt = time.Now().Add(5 * time.Minute) + + // When renewing, pass along this data (error handling omitted for brevity). + privateData, _ := json.Marshal(ThingPrivateData{Name: data.Name.ValueString()}) + resp.Private.SetKey(ctx, "thing_data", privateData) + + // Save data into ephemeral result data + resp.Diagnostics.Append(resp.Result.Set(ctx, &data)...) +} + +func (e *ThingEphemeralResource) Renew(ctx context.Context, req ephemeral.RenewRequest, resp *ephemeral.RenewResponse) { + privateBytes, _ := req.Private.GetKey(ctx, "thing_data") + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + // Unmarshal private data (error handling omitted for brevity). + var privateData ThingPrivateData + json.Unmarshal(privateBytes, &privateData) + + // Perform external call to renew "thing" data + + // Renew again in 5 minutes + resp.RenewAt = time.Now().Add(5 * time.Minute) + + // If needed, you can also set new `Private` data on the response. +} +``` + +## Recommendations + +* When setting the `RenewAt` response field, add extra time (usually no more than a few minutes) before an ephemeral resource expires to account for latency. diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/ephemeral-resources/validate-configuration.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/ephemeral-resources/validate-configuration.mdx new file mode 100644 index 000000000..242dd4cfc --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/ephemeral-resources/validate-configuration.mdx @@ -0,0 +1,86 @@ +--- +page_title: Validate ephemeral resource configurations +description: >- + Learn how to validate ephemeral resource configurations with the Terraform + plugin framework. +--- + +# Validate ephemeral resource configurations + +[Ephemeral resources](/terraform/plugin/framework/ephemeral-resources) support validating an entire practitioner configuration in either declarative or imperative logic. Feedback, such as required syntax or acceptable combinations of values, is returned via [diagnostics](/terraform/plugin/framework/diagnostics). + +This page describes implementation details for validating entire ephemeral resource configurations, typically referencing multiple attributes. Further documentation is available for other configuration validation concepts: + +- [Single attribute validation](/terraform/plugin/framework/validation#attribute-validation) is a schema-based mechanism for implementing attribute-specific validation logic. +- [Type validation](/terraform/plugin/framework/validation#type-validation) is a schema-based mechanism for implementing reusable validation logic for any attribute using the type. + +-> Configuration validation in Terraform occurs without provider configuration ("offline"), therefore the ephemeral resource `Configure` method will not have been called. To implement validation with a configured API client, use logic within the `Open` method, which occurs during Terraform's planning phase when possible. + +## ConfigValidators Method + +The [`ephemeral.EphemeralResourceWithConfigValidators` interface](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/ephemeral#EphemeralResourceWithConfigValidators) follows a similar pattern to attribute validation and allows for a more declarative approach. This enables consistent validation logic across multiple ephemeral resources. Each validator intended for this interface must implement the [`ephemeral.ConfigValidator` interface](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/ephemeral#ConfigValidator). + +During execution of the [`terraform validate`](/terraform/cli/commands/validate), [`terraform plan`](/terraform/cli/commands/plan) and [`terraform apply`](/terraform/cli/commands/apply) commands, Terraform calls the provider `ValidateEphemeralResourceConfig` RPC, in which the framework calls the `ConfigValidators` method on ephemeral resources that implement the [`ephemeral.EphemeralResourceWithConfigValidators` interface](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/ephemeral#EphemeralResourceWithConfigValidators). + +The [`terraform-plugin-framework-validators` Go module](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework-validators) has a collection of common use case ephemeral resource configuration validators in the [`ephemeralvalidator` package](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework-validators/ephemeralvalidator). These use [path expressions](/terraform/plugin/framework/path-expressions) for matching attributes. + +This example will raise an error if a practitioner attempts to configure both `attribute_one` and `attribute_two`: + +```go +// Other methods to implement the ephemeral.EphemeralResource interface are omitted for brevity +type ThingEphemeralResource struct {} + +func (d ThingEphemeralResource) ConfigValidators(ctx context.Context) []ephemeral.ConfigValidator { + return []ephemeral.ConfigValidator{ + ephemeralvalidator.Conflicting( + path.MatchRoot("attribute_one"), + path.MatchRoot("attribute_two"), + ), + } +} +``` + +## ValidateConfig Method + +The [`ephemeral.EphemeralResourceWithValidateConfig` interface](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/ephemeral#EphemeralResourceWithValidateConfig) is more imperative in design and is useful for validating unique functionality across multiple attributes that typically applies to a single ephemeral resource. + +During execution of the [`terraform validate`](/terraform/cli/commands/validate), [`terraform plan`](/terraform/cli/commands/plan) and [`terraform apply`](/terraform/cli/commands/apply) commands, Terraform calls the provider `ValidateEphemeralResourceConfig` RPC, in which the framework calls the `ValidateConfig` method on providers that implement the [`ephemeral.EphemeralResourceWithValidateConfig` interface](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/ephemeral#EphemeralResourceWithValidateConfig). + +This example will raise a warning if a practitioner attempts to configure `attribute_one`, but not `attribute_two`: + +```go +// Other methods to implement the ephemeral.EphemeralResource interface are omitted for brevity +type ThingEphemeralResource struct {} + +type ThingEphemeralResourceModel struct { + AttributeOne types.String `tfsdk:"attribute_one"` + AttributeTwo types.String `tfsdk:"attribute_two"` +} + +func (d ThingEphemeralResource) ValidateConfig(ctx context.Context, req ephemeral.ValidateConfigRequest, resp *ephemeral.ValidateConfigResponse) { + var data ThingEphemeralResourceModel + + resp.Diagnostics.Append(req.Config.Get(ctx, &data)...) + + if resp.Diagnostics.HasError() { + return + } + + // If attribute_one is not configured, return without warning. + if data.AttributeOne.IsNull() || data.AttributeOne.IsUnknown() { + return + } + + // If attribute_two is not null, return without warning. + if !data.AttributeTwo.IsNull() { + return + } + + resp.Diagnostics.AddAttributeWarning( + path.Root("attribute_two"), + "Missing Attribute Configuration", + "Expected attribute_two to be configured with attribute_one. "+ + "The ephemeral resource may return unexpected results.", + ) +} +``` diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/functions/concepts.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/functions/concepts.mdx new file mode 100644 index 000000000..23835d778 --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/functions/concepts.mdx @@ -0,0 +1,68 @@ +--- +page_title: Provider-defined functions +description: >- + Learn how provider-defined functions enable Terraform providers to define + functions for practitions to use in their Terraform configurations. +--- + +# Provider-defined functions + +This page describes Terraform concepts relating to provider-defined functions within framework-based provider code. Provider-defined functions are supported in Terraform 1.8 and later. The [What is Terraform](/terraform/intro), [Terraform language](/terraform/language), and [Plugin Development](/terraform/plugin) documentation covers more general concepts behind Terraform's workflow, its configuration, and how it interacts with providers. + +## Purpose + +The purpose of provider-defined functions is to encapsulate offline, computational logic beyond Terraform's built-in functions to simplify practitioner configurations. Terraform expects that provider-defined functions are implemented without side-effects and as pure functions where given the same input data that they always return the same output. Refer to [HashiCorp Provider Design Principles](/terraform/plugin/best-practices/hashicorp-provider-design-principles) for additional best practice details. + +Example use cases include: + +* Transforming existing data, such as merging complex data structures using a specific algorithm or converting between encodings. +* Parsing combined data into individual, referenceable components, such as taking an Amazon Resource Name (ARN) and returning an object of region, account identifier, etc. attributes. +* Building combined data from individual components, such as returning an Amazon Resource Name (ARN) based on given region, account identifier, etc. data. +* Static data lookups when there is no remote system query available, such as returning a data value typically necessary for a practitioner configuration. + +Differences from other provider-defined concepts include: + +* [Data Sources](/terraform/plugin/framework/data-sources): Intended to perform online or provider configuration dependent data lookup, which participate in Terraform's operational graph. +* [Resources](/terraform/plugin/framework/resources): Intended to manage the full lifecycle (create, update, destroy) of a remote system component, which participate in Terraform's operational graph. + +## Terminology + +There are two main components of provider-defined functions: + +* **Definition**: Defines the expected input and output data along with documentation descriptions. +* **Call**: When a practioner configuration causes a function's logic to be run. + +Within a function definition the components are: + +* **Parameters**: An ordered list of definitions for input data. + * **Variadic Parameter**: An optional, final parameter which accepts zero, one, or multiple parts of input data. +* **Return**: The definition for output data. + +Similar to many programming languages, when the function is called, the terminology for the data is slightly different than the terminology for the definition. + +* **Arguments**: Positionally ordered data based on the definitions of the parameters. +* **Result**: Data based on the definition of the return. + +## Implementation Overview + +For each provider listed as a [required provider](/terraform/language/providers/requirements), Terraform will query the provider for its function definitions. If a configuration attempts to call a provider-defined function without listing the provider as required, Terraform will return an error. + +Terraform will typically call functions before other provider concepts are evaluated. This includes before provider configuration being evaluated, which the framework enforces by not exposing provider configuration data to function implementations. + +### Naming + +Terraform requires that function names must be valid [identifiers](/terraform/language/syntax/configuration#identifiers). + +### Argument Handling + +Terraform will statically validate that the number and types of arguments in a configuration match the definitions of parameters, otherwise returning an error. + +If a null value is given as an argument, without individual parameter definition opt-in, Terraform will return an error. If an unknown value is given as an argument, without individual parameter definition opt-in, Terraform will skip calling the provider logic entirely and set the function result to an unknown value matching the return type. + +### Result Handling + +Terraform will statically validate that the return type is appropriately used in consuming configuration, otherwise returning an error. + +Function logic must always set the result to the return type, otherwise Terraform will return an error. + +Function logic can only set the result to an unknown value if there is a parameter that opted into unknown value handling and an unknown value argument was received for one of those parameters. diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/functions/documentation.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/functions/documentation.mdx new file mode 100644 index 000000000..fd731ae0f --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/functions/documentation.mdx @@ -0,0 +1,122 @@ +--- +page_title: Documenting functions +description: >- + Learn how to document provider-defined functions with the Terraform plugin + framework. +--- + +# Documenting functions + +When a function is [implemented](/terraform/plugin/framework/functions/implementation), ensure the function is discoverable by practitioners with usage information. + +There are two main components for function documentation: + +* [Implementation-Based Documentation](#implementation-based-documentation): Exposes function documentation to Terraform and downstream tooling, such as practitioner configuration editor integrations. +* [Registry-Based Documentation](#registry-based-documentation): Exposes function documentation to the [Terraform Registry](https://registry.terraform.io) when the [provider is published](/terraform/registry/providers/publishing), making it displayed and discoverable on the web. + +## Implementation-Based Documentation + +Add documentation directly inside the [function definition](/terraform/plugin/framework/functions/implementation#definition-method). All implementation-based documentation is passed to Terraform, which downstream tooling such as pracitioner configuration editor integrations will automatically display. + +### Definition + +The [`function.Definition` type](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/function#Definition) implements the following fields: + +| Field Name | Description | +|---|---| +| `Summary` | A short description of the function and its return, preferably a single sentence. | +| `Description` | Longer documentation about the function, its return, and pertinent implementation details in plaintext format. | +| `MarkdownDescription` | Longer documentation about the function, its return, and pertinent implementation details in Markdown format. | + +If there are no description formatting differences, set only one of `Description` or `MarkdownDescription`. When Terraform has not sent a preference for the description formatting, the framework will return `MarkdownDescription` if both are defined. + +In this example, the function definition sets summary and description documentation: + +```go +func (f *CidrContainsIpFunction) Definition(ctx context.Context, req function.DefinitionRequest, resp *function.DefinitionResponse) { + resp.Definition = function.Definition{ + // ... other fields ... + Summary: "Check if a network CIDR contains an IP", + Description: "Returns a boolean whether a RFC4632 CIDR contains an IP address", + } +} +``` + +### Parameters + +Each [parameter type](/terraform/plugin/framework/functions/parameters), whether in the definition `Parameters` or `VariadicParameter` field, implements the following fields: + +| Field Name | Description | +|-----------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------| +| `Name` | **Required**: Single word or abbreviation of parameter for function signature generation. If name is not provided, a runtime error will be generated. | +| `Description` | Documentation about the parameter and its expected values in plaintext format. | +| `MarkdownDescription` | Documentation about the parameter and its expected values in Markdown format. | + +The name must be unique in the context of the [function definition](/terraform/plugin/framework/functions/implementation#definition-method). It is used for documentation purposes and displayed in error diagnostics presented to practitioners. The name should delineate the purpose of the parameter, especially to disambiguate between multiple parameters, such as the words `cidr` and `ip` in a generated function signature like `cidr_contains_ip(cidr string, ip string) bool`. + +If there are no description formatting differences, set only one of `Description` or `MarkdownDescription`. When Terraform has not sent a preference for the description formatting, the framework will return `MarkdownDescription` if both are defined. + +In this example, the function parameters set name and description documentation: + +```go +func (f *CidrContainsIpFunction) Definition(ctx context.Context, req function.DefinitionRequest, resp *function.DefinitionResponse) { + resp.Definition = function.Definition{ + // ... other fields ... + Parameters: []function.Parameter{ + function.StringParameter{ + Name: "cidr", + Description: "RFC4632 CIDR to check whether it contains the given IP address", + }, + function.StringParameter{ + Name: "ip", + Description: "IP address to check whether its contained in the RFC4632 CIDR", + }, + }, + } +} +``` + +## Registry-Based Documentation + +Add Markdown documentation files in conventional provider codebase locations before [publishing](/terraform/registry/providers/publishing) to the [Terraform Registry](https://registry.terraform.io). The documentation is displayed and discoverable on the web. These files can be manually created or automatically generated using tooling such as [`terraform-plugin-docs`](https://github.com/hashicorp/terraform-plugin-docs). + +The [Registry provider documentation](/terraform/registry/providers/docs) covers the overall requirements, conventional file layout details, and how to enable additional features such as sub-categories for the navigation sidebar. Function documentation for most providers is expected under the `docs/functions/` directory with a file named after the function and with the extension `.md`. Older providers using the legacy file layout use `website/docs/functions/` and `.html.md`. + +Functions are conventionally documented with the following: + +* Description +* Example Usage +* Signature +* Arguments + +In this example, a `docs/functions/contains_ip.md` file (either manually or automatically created) will be displayed in the Terraform Registry after provider publishing: + +``````plain +--- +page_title: contains_ip Function - terraform-provider-cidr +description: |- + Returns a boolean whether a RFC4632 CIDR contains an IP address. +--- + +# Function: contains_ip + +Returns a boolean whether a RFC4632 CIDR contains an IP address. + +## Example Usage + +```terraform +# result: true +provider::cidr::contains_ip("10.0.0.0/8", "10.0.0.1") +``` + +## Signature + +```text +contains_ip(cidr string, ip string) bool +``` + +## Arguments + +1. `cidr` (String) RFC4632 CIDR to check whether it contains the given IP address. +2. `ip` (String) IP address to check whether its contained in the RFC4632 CIDR. +`````` diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/functions/errors.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/functions/errors.mdx new file mode 100644 index 000000000..64dc74931 --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/functions/errors.mdx @@ -0,0 +1,162 @@ +--- +page_title: Returning errors from functions +description: >- + Learn how to return errors from provider-defined functions with the Terraform + plugin framework. +--- + +# Returning errors from function + +Providers use [`FuncError`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/function#FuncError) to +surface a practitioner-facing error generated during execution of provider-defined functions. These errors are +returned from Terraform CLI at the end of command output: + +```console +$ terraform plan +# ... other plan output ... +╷ +│ Error: Error in function call +│ +│ on example.tf line #: +│ #: source configuration line +│ +│ Call to function "{FUNCTION}" failed: {TEXT}. +``` + +```console +$ terraform plan +# ... other plan output ... +╷ +│ Error: Invalid function argument +│ +│ on example.tf line #: +│ #: source configuration line +│ +│ Invalid value for "{PARAMETER_NAME}" parameter: {TEXT}. +``` + +In the framework, you may encounter them in response structs or as returns from +provider-defined function execution.: + +```go +func (f *ExampleFunction) Run(ctx context.Context, req function.RunRequest, resp *function.RunResponse) { +``` + +This is the most common form for `FuncError`: a single error whose text +is the concatenated error text from one or more errors. This approach allows +your provider to inform practitioners about all relevant errors at the same +time, allowing practitioners to fix their configuration or environment more +quickly. You should only concatenate a `FuncError` and never replace or +remove information it. + +The next section will detail the concepts and typical behaviors of +function error, while the final section will outline the typical methods for +working with function error, using functionality from the available +[`function` package](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/function). + +## Function Error Concepts + +### Text + +`Text` is a practitioner-oriented description of the problem. This should +contain sufficient detail to provide both general and more specific information +regarding the issue. For example "Error executing function: foo can only contain +letters, numbers, and digits." + +### FunctionArgument + +`FunctionArgument` is a zero-based, int64 value that identifies the specific +function argument position that caused the error. Only errors that pertain +to a function argument will include this information. + +### Working With Existing Function Errors + +#### ConcatFuncErrors + +When receiving `function.FuncError` from a function or method, such as +`Run()`, these should typically be concatenated with the +response function error for the method. This can be accomplished with the +[`ConcatFuncErrors(in ...*FuncError)` method](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/function#ConcatFuncErrors). + +For example: + +```go +func (f *ExampleFunction) Run(ctx context.Context, req function.RunRequest, resp *function.RunResponse) { + var boolArg bool + var stringArg string + + resp.Error = function.ConcatFuncErrors(resp.Error, req.Arguments.Get(ctx, &boolArg, &stringArg)) + + // ... other logic ... +} +``` + +This method automatically ignores `nil` function errors. + +### Creating Function Errors + +To craft the message of a function error, it is recommended to use sufficient +detail to convey both the cause of the error and as much contextual, +troubleshooting, and next action information as possible. These details can +use newlines for easier readability where necessary. + +#### NewFuncError + +When creating function errors where a `function.FunctionError` is already available, +such as within a response type, the [`ConcatFuncErrors(in ...*FuncError)` function](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/function#NewFuncError.AddError) +can be used with the [`NewFuncError(text string)` function](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/function#NewFuncError) to concatenate a new +function error. + +For example: + +```go +func (f *ExampleFunction) Run(ctx context.Context, req function.RunRequest, resp *function.RunResponse) { + // ... prior logic ... + + val, err := // operation that may return an error + + if err != nil { + resp.Error = ConcatFuncErrors(resp.Error, function.NewFuncError("Error performing operation: " + err.Error())) + return + } + + // ... further logic ... +} +``` + +#### NewArgumentFuncError + +When creating function errors that affect only a single function argument, the [`NewArgumentFuncError(functionArgument int, msg string)` function](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/function#NewArgumentFuncError) +can be used in conjunction with the [`ConcatFuncErrors(in ...*FuncError)` function](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/function#NewArgumentFuncError). This provides additional context to practitioners, such as showing the specific line(s) and value(s) of configuration where possible. + +For example: + +```go +func (f ExampleFunction) Run(ctx context.Context, req function.RunRequest, resp *function.RunResponse) { + // ... other logic ... + + // Add function error associated with first function argument position + resp.Error = function.ConcatFuncErrors(resp.Error, function.NewArgumentFuncError(0, "Example Error Summary: Example Error Detail")) + + // ... other logic ... +} +``` + +#### FuncErrorFromDiags + +A function error is created from diagnostics by using the [`FuncErrorFromDiags(context.Context, diag.Diagnostics)` function](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/function#FuncErrorFromDiags). The function error will contain the concatenated summary and details of error-level +diagnostics. + +~> **Note**: The [`FuncErrorFromDiags(context.Context, diag.Diagnostics)` function](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/function#FuncErrorFromDiags) does not include warning-level diagnostics in the function error. Warning-level diagnostics are logged instead. + +For example: + +```go +func (f *ExampleFunction) Run(ctx context.Context, req function.RunRequest, resp *function.RunResponse) { + // ... other logic ... + + _, diags := // operation that may return diagnostics + + resp.Error = function.ConcatFuncErrors(resp.Error, function.FuncErrorFromDiags(ctx, diags)) +} +``` diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/functions/implementation.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/functions/implementation.mdx new file mode 100644 index 000000000..c811870ee --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/functions/implementation.mdx @@ -0,0 +1,335 @@ +--- +page_title: Implement provider-defined functions +description: >- + Learn how to implement provider-defined functions with the Terraform + plugin framework. +--- + +# Implement provider-defined functions + +The framework supports implementing functions based on Terraform's [concepts for provider-defined functions](/terraform/plugin/framework/functions/concepts). It is recommended to understand those concepts before implementing a function since the terminology is used throughout this page and there are details that simplify function handling as compared to other provider concepts. Provider-defined functions are supported in Terraform 1.8 and later. + +The main code components of a function implementation are: + +* [Defining the function](#define-function-type) including its name, expected data types, descriptions, and logic. +* [Adding the function to the provider](#add-function-to-provider) so it is accessible by Terraform and practitioners. + +Once the code is implemented, it is always recommended to also add: + +* [Testing](/terraform/plugin/framework/functions/testing) to ensure expected function behaviors. +* [Documentation](/terraform/plugin/framework/functions/documentation) to ensure the function is discoverable by practitioners with usage information. + +## Define Function Type + +Implement the [`function.Function` interface](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/function#Function). Each of the methods is described in more detail below. + +In this example, a function named `echo` is defined, which takes a string argument and returns that value as the result: + +```go +import ( + "github.com/hashicorp/terraform-plugin-framework/function" +) + +// Ensure the implementation satisfies the desired interfaces. +var _ function.Function = &EchoFunction{} + +type EchoFunction struct {} + +func (f *EchoFunction) Metadata(ctx context.Context, req function.MetadataRequest, resp *function.MetadataResponse) { + resp.Name = "echo" +} + +func (f *EchoFunction) Definition(ctx context.Context, req function.DefinitionRequest, resp *function.DefinitionResponse) { + resp.Definition = function.Definition{ + Summary: "Echo a string", + Description: "Given a string value, returns the same value.", + + Parameters: []function.Parameter{ + function.StringParameter{ + Name: "input", + Description: "Value to echo", + }, + }, + Return: function.StringReturn{}, + } +} + +func (f *EchoFunction) Run(ctx context.Context, req function.RunRequest, resp *function.RunResponse) { + var input string + + // Read Terraform argument data into the variable + resp.Error = function.ConcatFuncErrors(resp.Error, req.Arguments.Get(ctx, &input)) + + // Set the result to the same data + resp.Error = function.ConcatFuncErrors(resp.Error, resp.Result.Set(ctx, input)) +} +``` + +### Metadata Method + +The [`function.Function` interface `Metadata` method](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/function#Function.Metadata) defines the function name as it would appear in Terraform configurations. Unlike resources and data sources, this name should **NOT** include the provider name as the configuration language syntax for calling functions will separately include the provider name. Refer to [naming](/terraform/plugin/best-practices/naming) for additional best practice details. + +In this example, the function name is set to `example`: + +```go +// With the function.Function implementation +func (f *ExampleFunction) Metadata(ctx context.Context, req function.MetadataRequest, resp *function.MetadataResponse) { + resp.Name = "example" +} +``` + +### Definition Method + +The [`function.Function` interface `Definition` method](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/function#Function.Definition) defines the parameters, return, and various descriptions for documentation of the function. + +In this example, the function definition includes one string parameter, a string return, and descriptions for documentation: + +```go +func (f *ExampleFunction) Definition(ctx context.Context, req function.DefinitionRequest, resp *function.DefinitionResponse) { + resp.Definition = function.Definition{ + Summary: "Echo a string", + Description: "Given a string value, returns the same value.", + + Parameters: []function.Parameter{ + function.StringParameter{ + Description: "Value to echo", + Name: "input", + }, + }, + Return: function.StringReturn{}, + } +} +``` + +#### Return + +The `Return` field must be defined as all functions must return a result. This influences how the [Run method](#run-method) must set the result data. Refer to the [returns](/terraform/plugin/framework/functions/returns) documentation for details about all available types and how to handle data with each type. + +#### Parameters + +There may be zero or more parameters, which are defined with the `Parameters` field. They are ordered, which influences how practitioners call the function in their configurations and how the [Run method](#run-method) must read the argument data. Refer to the [parameters](/terraform/plugin/framework/functions/parameters) documentation for details about all available types and how to handle data with each type. + +An optional `VariadicParameter` field enables a final variadic parameter which accepts zero, one, or more values of the same type. It may be optionally combined with `Parameters`, meaning it represents the any argument data after the final parameter. When reading argument data, a `VariadicParameter` is represented as a tuple, with each element matching the parameter type; the tuple has zero or more elements to match the given arguments. + +By default, Terraform will not pass null or unknown values to the provider logic when a function is called. Within each parameter, use the `AllowNullValue` and/or `AllowUnknownValues` fields to explicitly allow those kinds of values. Enabling `AllowNullValue` requires using a pointer type or [framework type](/terraform/plugin/framework/handling-data/types) when reading argument data. Enabling `AllowUnknownValues` requires using a [framework type](/terraform/plugin/framework/handling-data/types) when reading argument data. + +#### Documentation + +The [function documentation](/terraform/plugin/framework/functions/documentation) page describes how to implement documentation so it is available to Terraform, downstream tooling such as practitioner configuration editor integrations, and in the [Terraform Registry](https://registry.terraform.io). + +#### Deprecation + +If a function is being deprecated, such as for future removal, the `DeprecationMessage` field should be set. The message should be actionable for practitioners, such as telling them what to do with their configuration instead of calling this function. + +### Run Method + +The [`function.Function` interface `Run` method](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/function#Function.Run) defines the logic that is invoked when Terraform calls the function. Only argument data is provided when a function is called. Refer to [HashiCorp Provider Design Principles](/terraform/plugin/best-practices/hashicorp-provider-design-principles) for additional best practice details. + +Implement the `Run` method by: + +1. Creating variables for argument data, based on the parameter definitions. Refer to the [parameters](/terraform/plugin/framework/functions/parameters) documentation for details about all available parameter types and how to handle data with each type. +1. Reading argument data from the [`function.RunRequest.Arguments` field](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/function#RunRequest.Arguments). +1. Performing any computational logic. +1. Setting the result value, based on the return definition, into the [`function.RunResponse.Result` field](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/function#RunResponse.Result). Refer to the [returns](/terraform/plugin/framework/functions/returns) documentation for details about all available return types and how to handle data with each type. + +If the logic needs to return a [function error](/terraform/plugin/framework/functions/errors), it can be added into the [`function.RunResponse.Error` field](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/function#RunResponse.Error). + +### Reading Argument Data + +The framework supports two methodologies for reading argument data from the [`function.RunRequest.Arguments` field](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/function#RunRequest.Arguments), which is of the [`function.ArgumentsData` type](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/function#ArgumentsData). + +The first option is using the [`(function.ArgumentsData).Get()` method](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/function#ArgumentsData.Get) to read all arguments at once. The framework will return an error if the number and types of target variables does not match the argument data. + +In this example, the parameters are defined as a boolean and string which are read into Go built-in `bool` and `string` variables since they do not opt into null or unknown value handling: + +```go +func (f *ExampleFunction) Definition(ctx context.Context, req function.DefinitionRequest, resp *function.DefinitionResponse) { + resp.Definition = function.Definition{ + // ... other fields ... + Parameters: []function.Parameter{ + function.BoolParameter{ + Name: "bool_param", + // ... other fields ... + }, + function.StringParameter{ + Name: "string_param", + // ... other fields ... + }, + }, + } +} + +func (f *ExampleFunction) Run(ctx context.Context, req function.RunRequest, resp *function.RunResponse) { + var boolArg bool + var stringArg string + + resp.Error = function.ConcatFuncErrors(resp.Error, req.Arguments.Get(ctx, &boolArg, &stringArg)) + + // ... other logic ... +} +``` + +The second option is using [`(function.ArgumentsData).GetArgument()` method](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/function#ArgumentsData.GetArgument) to read individual arguments. The framework will return an error if the argument position does not exist or if the type of the target variable does not match the argument data. + +In this example, the parameters are defined as a boolean and string and the first argument is read into a Go built-in `bool` variable since it does not opt into null or unknown value handling: + +```go +func (f *ExampleFunction) Definition(ctx context.Context, req function.DefinitionRequest, resp *function.DefinitionResponse) { + resp.Definition = function.Definition{ + // ... other fields ... + Parameters: []function.Parameter{ + function.BoolParameter{ + Name: "bool_param", + // ... other fields ... + }, + function.StringParameter{ + Name: "string_param", + // ... other fields ... + }, + }, + } +} + +func (f *ExampleFunction) Run(ctx context.Context, req function.RunRequest, resp *function.RunResponse) { + var boolArg bool + + resp.Error = function.ConcatFuncErrors(resp.Error, req.Arguments.GetArgument(ctx, 0, &boolArg)) + + // ... other logic ... +} +``` + +#### Reading Variadic Parameter Argument Data + +The optional `VariadicParameter` field in a function definition enables a final variadic parameter which accepts zero, one, or more values of the same type. It may be optionally combined with `Parameters`, meaning it represents the argument data after the final parameter. When reading argument data, a `VariadicParameter` is represented as a tuple, with each element matching the parameter type; the tuple has zero or more elements to match the given arguments. + +Use either the [framework tuple type](/terraform/plugin/framework/handling-data/types/tuple) or a Go slice of an appropriate type to match the variadic parameter `[]T`. + +In this example, there is a boolean parameter and string variadic parameter, where the variadic parameter argument data is always fetched as a slice of `string`: + +```go +func (f *ExampleFunction) Definition(ctx context.Context, req function.DefinitionRequest, resp *function.DefinitionResponse) { + resp.Definition = function.Definition{ + // ... other fields ... + Parameters: []function.Parameter{ + function.BoolParameter{ + Name: "bool_param", + // ... other fields ... + }, + }, + VariadicParameter: function.StringParameter{ + Name: "variadic_param", + // ... other fields ... + }, + } +} + +func (f *ExampleFunction) Run(ctx context.Context, req function.RunRequest, resp *function.RunResponse) { + var boolArg bool + var stringVarg []string + + resp.Error = function.ConcatFuncErrors(resp.Error, req.Arguments.Get(ctx, &boolArg, &stringVarg)) + + // ... other logic ... +} +``` + +If it is necessary to return a [function error](/terraform/plugin/framework/functions/errors) for a specific variadic argument, note that Terraform treats each zero-based argument position individually unlike how the framework exposes the argument data. Add the number of non-variadic parameters (if any) to the variadic argument tuple element index to ensure the error is aligned to the correct argument in the configuration. + +In this example with two parameters and one variadic parameter, an error is returned for variadic arguments: + +```go +func (f *ExampleFunction) Definition(ctx context.Context, req function.DefinitionRequest, resp *function.DefinitionResponse) { + resp.Definition = function.Definition{ + // ... other fields ... + Parameters: []function.Parameter{ + function.BoolParameter{ + Name: "bool_param", + // ... other fields ... + }, + function.Int64Parameter{ + Name: "int64_param", + // ... other fields ... + }, + }, + VariadicParameter: function.StringParameter{ + Name: "variadic_param", + // ... other fields ... + }, + } +} + +func (f *ExampleFunction) Run(ctx context.Context, req function.RunRequest, resp *function.RunResponse) { + var boolArg bool + var int64Arg int64 + var stringVarg []string + + resp.Error = function.ConcatFuncErrors(resp.Error, req.Arguments.Get(ctx, &boolArg, &int64arg, &stringVarg)) + + for index, element := range stringVarg { + // Added by 2 to match the definition including two parameters. + resp.Error = function.ConcatFuncErrors(resp.Error, function.NewArgumentFuncError(2+index, "example summary: example detail")) + } + + // ... other logic ... +} +``` + +### Setting Result Data + +The framework supports setting a result value into the [`function.RunResponse.Result` field](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/function#RunResponse.Result), which is of the [`function.ResultData` type](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/function#ResultData). The result value must match the return type, otherwise the framework or Terraform will return an error. + +In this example, the return is defined as a string and a string value is set: + +```go +func (f *ExampleFunction) Definition(ctx context.Context, req function.DefinitionRequest, resp *function.DefinitionResponse) { + resp.Definition = function.Definition{ + // ... other fields ... + Return: function.StringReturn{}, + } +} + +func (f *ExampleFunction) Run(ctx context.Context, req function.RunRequest, resp *function.RunResponse) { + // ... other logic ... + + // Value based on the return type. Returns can also use the framework type system. + result := "hardcoded example" + + resp.Error = function.ConcatFuncErrors(resp.Error, resp.Result.Set(ctx, result)) +} +``` + +## Add Function to Provider + +Functions become available to practitioners when they are included in the [provider](/terraform/plugin/framework/providers) implementation via the [`provider.ProviderWithFunctions` interface `Functions` method](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/provider#ProviderWithFunctions.Functions). + +In this example, the `EchoFunction` type, which implements the `function.Function` interface, is added to the provider implementation: + +```go +// With the provider.Provider implementation +func (p *ExampleCloudProvider) Functions(_ context.Context) []func() function.Function { + return []func() function.Function{ + func() function.Function { + return &EchoFunction{}, + }, + } +} +``` + +To simplify provider implementations, a named function can be created with the function implementation. + +In this example, the `EchoFunction` code includes an additional `NewEchoFunction` function, which simplifies the provider implementation: + +```go +// With the provider.Provider implementation +func (p *ExampleCloudProvider) Functions(_ context.Context) []func() function.Function { + return []func() function.Function{ + NewEchoFunction, + } +} + +// With the function.Function implementation +func NewEchoFunction() function.Function { + return &EchoFunction{} +} +``` diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/functions/index.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/functions/index.mdx new file mode 100644 index 000000000..193c4ea1f --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/functions/index.mdx @@ -0,0 +1,34 @@ +--- +page_title: Provider-defined functions overview +description: >- + Provider-defined functions expose logic beyond Terraform's built-in functions + that practitioners can use to simplify Terraform configurations. Learn how the + plugin framework can help you implement provider-defined functions. +--- + + +# Provider-defined functions + +Functions are an abstraction that allow providers to expose computational logic beyond Terraform's [built-in functions](/terraform/language/functions) and simplify practitioner configurations. Provider-defined functions are supported in Terraform 1.8 and later. + + + +It is possible to add functions to an existing provider. If you do not have an existing provider, you will need to create your own provider to contain the functions. Please see [Getting Started - Code Walkthrough](/terraform/plugin/framework/getting-started/code-walkthrough) to learn how to create your first provider. + + + +## Concepts + +Learn about Terraform's [concepts](/terraform/plugin/framework/functions/concepts) for provider-defined functions, such as intended purpose, example use cases, and terminology. The framework's implementation details, such as naming, are based on these concepts. + +## Implementation + +Learn about how to [implement code](/terraform/plugin/framework/functions/implementation) for a provider-defined function in the framework. + +## Testing + +Learn about how to ensure a provider-defined function implementation works as expected via [unit testing and acceptance testing](/terraform/plugin/framework/functions/testing). + +## Documentation + +Learn about how to [document](/terraform/plugin/framework/functions/documentation) a provider-defined function implementation so practitioners can discover and use the function. diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/functions/parameters/bool.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/functions/parameters/bool.mdx new file mode 100644 index 000000000..3bf687562 --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/functions/parameters/bool.mdx @@ -0,0 +1,95 @@ +--- +page_title: Boolean function parameters +description: >- + Learn how to use the boolean function parameter type with the Terraform + plugin framework. +--- + +# Boolean function parameters + +Bool function parameters expect a boolean true or false value from a practitioner configuration. Values are accessible in function logic by the Go built-in `bool` type, Go built-in `*bool` type, or the [framework bool type](/terraform/plugin/framework/handling-data/types/bool). + +In this Terraform configuration example, a bool parameter is set to the value `true`: + +```hcl +provider::example::example(true) +``` + +## Function Definition + +Use the [`function.BoolParameter` type](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/function#BoolParameter) in the [function definition](/terraform/plugin/framework/functions/implementation#definition-method) to accept a bool value. + +In this example, a function definition includes a first position bool parameter: + +```go +func (f ExampleFunction) Definition(ctx context.Context, req function.DefinitionRequest, resp *function.DefinitionResponse) { + resp.Definition = function.Definition{ + // ... other Definition fields ... + Parameters: []function.Parameter{ + function.BoolParameter{ + Name: "example", + // ... potentially other BoolParameter fields ... + }, + }, + } +} +``` + +If the bool value should be the element type of a [collection parameter type](/terraform/plugin/framework/functions/parameters#collection-parameter-types), set the `ElementType` field according to the [framework bool type](/terraform/plugin/framework/handling-data/types/bool). Refer to the collection parameter type documentation for additional details. + +If the bool value should be a value type of an [object parameter type](/terraform/plugin/framework/functions/parameters#object-parameter-type), set the `AttributeTypes` map value according to the [framework bool type](/terraform/plugin/framework/handling-data/types/bool). Refer to the object parameter type documentation for additional details. + +### Allow Null Values + +By default, Terraform will not pass null values to the function logic. Use the `AllowNullValue` field to explicitly allow null values, if there is a meaningful distinction that should occur in function logic. Enabling `AllowNullValue` requires using a Go pointer type or [framework bool type](/terraform/plugin/framework/handling-data/types/bool) when reading argument data. + +### Allow Unknown Values + +By default, Terraform will not pass unknown values to the function logic. Use the `AllowUnknownValues` field to explicitly allow unknown values, if there is a meaningful distinction that should occur in function logic. Enabling `AllowUnknownValues` requires using a [framework bool type](/terraform/plugin/framework/handling-data/types/bool) when reading argument data. + +### Custom Types + +You may want to build your own data value and type implementations to allow your provider to combine validation and other behaviors into a reusable bundle. This helps avoid duplication and ensures consistency. These implementations use the `CustomType` field in the parameter type. + +Refer to [Custom Types](/terraform/plugin/framework/handling-data/types/custom) for further details on creating provider-defined types and values. + +### Documentation + +Refer to [function documentation](/terraform/plugin/framework/functions/documentation) for information about the `Name`, `Description`, and `MarkdownDescription` fields available. + +## Reading Argument Data + +The [function implementation](/terraform/plugin/framework/functions/implementation) documentation covers the general methods for reading function argument data in function logic. + +When retrieving the argument value for this parameter: + +* If `CustomType` is set, use its associated value type. +* If `AllowUnknownValues` is enabled, you must use the [framework bool type](/terraform/plugin/framework/handling-data/types/bool). +* If `AllowNullValue` is enabled, you must use the Go built-in `*bool` type or [framework bool type](/terraform/plugin/framework/handling-data/types/bool). +* Otherwise, use the Go built-in `bool` type, Go built-in `*bool` type, or [framework bool type](/terraform/plugin/framework/handling-data/types/bool). + +In this example, a function defines a single bool parameter and accesses its argument value: + +```go +func (f ExampleFunction) Definition(ctx context.Context, req function.DefinitionRequest, resp *function.DefinitionResponse) { + resp.Definition = function.Definition{ + // ... other Definition fields ... + Parameters: []function.Parameter{ + function.BoolParameter{ + Name: "bool_param", + }, + }, + } +} + +func (f ExampleFunction) Run(ctx context.Context, req function.RunRequest, resp *function.RunResponse) { + var boolArg bool + // var boolArg *bool // e.g. with AllowNullValue, where Go nil equals Terraform null + // var boolArg types.Bool // e.g. with AllowUnknownValues or AllowNullValue + + resp.Error = function.ConcatFuncErrors(resp.Error, req.Arguments.Get(ctx, &boolArg)) + + // boolArg is now populated + // ... other logic ... +} +``` diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/functions/parameters/dynamic.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/functions/parameters/dynamic.mdx new file mode 100644 index 000000000..e088e5c55 --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/functions/parameters/dynamic.mdx @@ -0,0 +1,177 @@ +--- +page_title: Dynamic function parameters +description: >- + Learn how to use dynamic fynction paramters with the Terraform plugin + framework. +--- + +# Dynamic function parameters + + + +Static types should always be preferred over dynamic types, when possible. + +Developers creating a function with a dynamic parameter will need to have extensive knowledge of the [Terraform type system](/terraform/language/expressions/types), as no type conversion will be performed to incoming argument data. + +Refer to [Dynamic Data - Considerations](/terraform/plugin/framework/handling-data/dynamic-data#considerations) for more information. + + + +Dynamic function parameters can receive **any** value type from a practitioner configuration. Values are accessible in function logic by the [framework dynamic type](/terraform/plugin/framework/handling-data/types/dynamic). + +In this Terraform configuration example, a dynamic parameter is set to the boolean value `true`: + +```hcl +provider::example::example(true) +``` + +In this example, the same dynamic parameter is set to a tuple (not a list) of string values `one` and `two`: + +```hcl +provider::example::example(["one", "two"]) +``` + +In this example, the same dynamic parameter is set to an object type with mapped values of `attr1` to `"value1"` and `attr2` to `123`: + +```hcl +provider::example::example({ + attr1 = "value1", + attr2 = 123, +}) +``` + +## Function Definition + +Use the [`function.DynamicParameter` type](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/function#DynamicParameter) in the [function definition](/terraform/plugin/framework/functions/implementation#definition-method) to accept a dynamic value. + +In this example, a function definition includes a first position dynamic parameter: + +```go +func (f ExampleFunction) Definition(ctx context.Context, req function.DefinitionRequest, resp *function.DefinitionResponse) { + resp.Definition = function.Definition{ + // ... other Definition fields ... + Parameters: []function.Parameter{ + function.DynamicParameter{ + Name: "dynamic_param", + // ... potentially other DynamicParameter fields ... + }, + }, + } +} +``` + +Dynamic values are not supported as the element type of a [collection type](/terraform/plugin/framework/handling-data/types#collection-types) or within [collection parameter types](/terraform/plugin/framework/functions/parameters#collection-parameter-types). + +If the dynamic value should be a value type of an [object parameter type](/terraform/plugin/framework/functions/parameters#object-parameter-type), set the `AttributeTypes` map value according to the [framework dynamic type](/terraform/plugin/framework/handling-data/types/dynamic). Refer to the object parameter type documentation for additional details. + +### Allow Null Values + + + +A known dynamic value with an underlying value that contains nulls (such as a list with null element values) will always be sent to the function logic, regardless of the `AllowNullValue` setting. Data handling must always account for this situation. + + + +By default, Terraform will not pass null values to the function logic. Use the `AllowNullValue` field to explicitly allow null values, if there is a meaningful distinction that should occur in function logic. + +### Allow Unknown Values + +By default, Terraform will not pass unknown values to the function logic. Use the `AllowUnknownValues` field to explicitly allow unknown values, if there is a meaningful distinction that should occur in function logic. + +### Custom Types + +You may want to build your own data value and type implementations to allow your provider to combine validation and other behaviors into a reusable bundle. This helps avoid duplication and ensures consistency. These implementations use the `CustomType` field in the parameter type. + +Refer to [Custom Types](/terraform/plugin/framework/handling-data/types/custom) for further details on creating provider-defined types and values. + +### Documentation + +Refer to [function documentation](/terraform/plugin/framework/functions/documentation) for information about the `Name`, `Description`, and `MarkdownDescription` fields available. + +## Reading Argument Data + +The [function implementation](/terraform/plugin/framework/functions/implementation) documentation covers the general methods for reading function argument data in function logic. + +When retrieving the argument value for this parameter: + +* If `CustomType` is set, use its associated value type. +* Otherwise, you must use the [framework dynamic type](/terraform/plugin/framework/handling-data/types/dynamic). + +In this example, a function defines a single dynamic parameter and accesses its argument value: + +```go +func (f ExampleFunction) Definition(ctx context.Context, req function.DefinitionRequest, resp *function.DefinitionResponse) { + resp.Definition = function.Definition{ + // ... other Definition fields ... + Parameters: []function.Parameter{ + function.DynamicParameter{ + Name: "dynamic_param", + // ... potentially other DynamicParameter fields ... + }, + }, + } +} + +func (f ExampleFunction) Run(ctx context.Context, req function.RunRequest, resp *function.RunResponse) { + var dynamicArg types.Dynamic + + resp.Error = function.ConcatFuncErrors(resp.Error, req.Arguments.Get(ctx, &dynamicArg)) + + // dynamicArg is now populated + // ... other logic ... +} +``` + +For more detail on working with dynamic values, see the [framework dynamic type](/terraform/plugin/framework/handling-data/types/dynamic) documentation. + +## Using Dynamic as a Variadic Parameter + +Utilizing `function.DynamicParameter` in the [`VariadicParameter`](/terraform/plugin/framework/functions/implementation#reading-variadic-parameter-argument-data) field will allow zero, one, or more values of **potentially different** types. + +To handle this scenario of multiple values with different types, utilize [`types.Tuple`](/terraform/plugin/framework/handling-data/types/tuple) or [`[]types.Dynamic`](/terraform/plugin/framework/handling-data/types/dynamic) when reading a dynamic variadic argument. + +```go +func (f *ExampleFunction) Definition(ctx context.Context, req function.DefinitionRequest, resp *function.DefinitionResponse) { + resp.Definition = function.Definition{ + // ... other Definition fields ... + VariadicParameter: function.DynamicParameter{ + Name: "variadic_param", + }, + } +} + +func (f *ExampleFunction) Run(ctx context.Context, req function.RunRequest, resp *function.RunResponse) { + var dynValues []types.Dynamic + + resp.Error = function.ConcatFuncErrors(resp.Error, req.Arguments.Get(ctx, &dynValues)) + if resp.Error != nil { + return + } + + for _, dynValue := range dynValues { + if dynValue.IsNull() || dynValue.IsUnknown() { + continue + } + // ... do something with argument value, i.e. dynValue.UnderlyingValue() ... + } + + // ... other logic ... +} + +``` + +In these Terraform configuration examples, the function variadic argument will receive the following value types: + +```hcl +# []types.Dynamic{} +provider::example::example() + +# []types.Dynamic{types.String} +provider::example::example("hello world") + +# []types.Dynamic{types.Bool, types.Number} +provider::example::example(true, 1) + +# []types.Dynamic{types.String, types.Tuple[types.String, types.String], types.List[types.String]} +provider::example::example("hello", ["one", "two"], tolist(["one", "two"])) +``` diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/functions/parameters/float32.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/functions/parameters/float32.mdx new file mode 100644 index 000000000..423df0cb2 --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/functions/parameters/float32.mdx @@ -0,0 +1,102 @@ +--- +page_title: Float32 function parameters +description: >- + Learn how to use the 32-bit floating point function parameter type with the + Terraform plugin framework. +--- + +# Float32 Function Parameter + + + +Use [Int32 Parameter](/terraform/plugin/framework/functions/parameters/int32) for 32-bit integer numbers. Use [Number Parameter](/terraform/plugin/framework/functions/parameters/number) for arbitrary precision numbers. + + + +Float32 function parameters expect a 32-bit floating point number value from a practitioner configuration. Values are accessible in function logic by the Go built-in `float32` type, Go built-in `*float32` type, or the [framework float32 type](/terraform/plugin/framework/handling-data/types/float32). + +In this Terraform configuration example, a float32 parameter is set to the value `1.23`: + +```hcl +provider::example::example(1.23) +``` + +## Function Definition + +Use the [`function.Float32Parameter` type](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/function#Float32Parameter) in the [function definition](/terraform/plugin/framework/functions/implementation#definition-method) to accept a float32 value. + +In this example, a function definition includes a first position float32 parameter: + +```go +func (f ExampleFunction) Definition(ctx context.Context, req function.DefinitionRequest, resp *function.DefinitionResponse) { + resp.Definition = function.Definition{ + // ... other Definition fields ... + Parameters: []function.Parameter{ + function.Float32Parameter{ + Name: "float32_param", + // ... potentially other Float32Parameter fields ... + }, + }, + } +} +``` + +If the float32 value should be the element type of a [collection parameter type](/terraform/plugin/framework/functions/parameters#collection-parameter-types), set the `ElementType` field according to the [framework float32 type](/terraform/plugin/framework/handling-data/types/float32). Refer to the collection parameter type documentation for additional details. + +If the float32 value should be a value type of an [object parameter type](/terraform/plugin/framework/functions/parameters#object-parameter-type), set the `AttributeTypes` map value according to the [framework float32 type](/terraform/plugin/framework/handling-data/types/float32). Refer to the object parameter type documentation for additional details. + +### Allow Null Values + +By default, Terraform will not pass null values to the function logic. Use the `AllowNullValue` field to explicitly allow null values, if there is a meaningful distinction that should occur in function logic. Enabling `AllowNullValue` requires using a Go pointer type or [framework float32 type](/terraform/plugin/framework/handling-data/types/float32) when reading argument data. + +### Allow Unknown Values + +By default, Terraform will not pass unknown values to the function logic. Use the `AllowUnknownValues` field to explicitly allow unknown values, if there is a meaningful distinction that should occur in function logic. Enabling `AllowUnknownValues` requires using a [framework float32 type](/terraform/plugin/framework/handling-data/types/float32) when reading argument data. + +### Custom Types + +You may want to build your own data value and type implementations to allow your provider to combine validation and other behaviors into a reusable bundle. This helps avoid duplication and ensures consistency. These implementations use the `CustomType` field in the parameter type. + +Refer to [Custom Types](/terraform/plugin/framework/handling-data/types/custom) for further details on creating provider-defined types and values. + +### Documentation + +Refer to [function documentation](/terraform/plugin/framework/functions/documentation) for information about the `Name`, `Description`, and `MarkdownDescription` fields available. + +## Reading Argument Data + +The [function implementation](/terraform/plugin/framework/functions/implementation) documentation covers the general methods for reading function argument data in function logic. + +When retrieving the argument value for this parameter: + +* If `CustomType` is set, use its associated value type. +* If `AllowUnknownValues` is enabled, you must use the [framework float32 type](/terraform/plugin/framework/handling-data/types/float32). +* If `AllowNullValue` is enabled, you must use the Go built-in `*float32` type or [framework float32 type](/terraform/plugin/framework/handling-data/types/float32). +* Otherwise, use the Go built-in `float32` type, Go built-in `*float32` type, or [framework float32 type](/terraform/plugin/framework/handling-data/types/float32). + +In this example, a function defines a single float32 parameter and accesses its argument value: + +```go +func (f ExampleFunction) Definition(ctx context.Context, req function.DefinitionRequest, resp *function.DefinitionResponse) { + resp.Definition = function.Definition{ + // ... other Definition fields ... + Parameters: []function.Parameter{ + function.Float32Parameter{ + Name: "float32_param", + // ... potentially other Float32Parameter fields ... + }, + }, + } +} + +func (f ExampleFunction) Run(ctx context.Context, req function.RunRequest, resp *function.RunResponse) { + var float32Arg float32 + // var float32Arg *float32 // e.g. with AllowNullValue, where Go nil equals Terraform null + // var float32Arg types.Float32 // e.g. with AllowUnknownValues or AllowNullValue + + resp.Error = function.ConcatFuncErrors(resp.Error, req.Arguments.Get(ctx, &float32Arg)) + + // float32Arg is now populated + // ... other logic ... +} +``` diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/functions/parameters/float64.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/functions/parameters/float64.mdx new file mode 100644 index 000000000..7dbe213ac --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/functions/parameters/float64.mdx @@ -0,0 +1,102 @@ +--- +page_title: Float64 function parameters +description: >- + Learn how to use the 64-bit floating point function parameter type with the + Terraform plugin framework. +--- + +# Float64 function parameters + + + +Use [Int64 Parameter](/terraform/plugin/framework/functions/parameters/int64) for 64-bit integer numbers. Use [Number Parameter](/terraform/plugin/framework/functions/parameters/number) for arbitrary precision numbers. + + + +Float64 function parameters expect a 64-bit floating point number value from a practitioner configuration. Values are accessible in function logic by the Go built-in `float64` type, Go built-in `*float64` type, or the [framework float64 type](/terraform/plugin/framework/handling-data/types/float64). + +In this Terraform configuration example, a float64 parameter is set to the value `1.23`: + +```hcl +provider::example::example(1.23) +``` + +## Function Definition + +Use the [`function.Float64Parameter` type](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/function#Float64Parameter) in the [function definition](/terraform/plugin/framework/functions/implementation#definition-method) to accept a float64 value. + +In this example, a function definition includes a first position float64 parameter: + +```go +func (f ExampleFunction) Definition(ctx context.Context, req function.DefinitionRequest, resp *function.DefinitionResponse) { + resp.Definition = function.Definition{ + // ... other Definition fields ... + Parameters: []function.Parameter{ + function.Float64Parameter{ + Name: "float64_param", + // ... potentially other Float64Parameter fields ... + }, + }, + } +} +``` + +If the float64 value should be the element type of a [collection parameter type](/terraform/plugin/framework/functions/parameters#collection-parameter-types), set the `ElementType` field according to the [framework float64 type](/terraform/plugin/framework/handling-data/types/float64). Refer to the collection parameter type documentation for additional details. + +If the float64 value should be a value type of an [object parameter type](/terraform/plugin/framework/functions/parameters#object-parameter-type), set the `AttributeTypes` map value according to the [framework float64 type](/terraform/plugin/framework/handling-data/types/float64). Refer to the object parameter type documentation for additional details. + +### Allow Null Values + +By default, Terraform will not pass null values to the function logic. Use the `AllowNullValue` field to explicitly allow null values, if there is a meaningful distinction that should occur in function logic. Enabling `AllowNullValue` requires using a Go pointer type or [framework float64 type](/terraform/plugin/framework/handling-data/types/float64) when reading argument data. + +### Allow Unknown Values + +By default, Terraform will not pass unknown values to the function logic. Use the `AllowUnknownValues` field to explicitly allow unknown values, if there is a meaningful distinction that should occur in function logic. Enabling `AllowUnknownValues` requires using a [framework float64 type](/terraform/plugin/framework/handling-data/types/float64) when reading argument data. + +### Custom Types + +You may want to build your own data value and type implementations to allow your provider to combine validation and other behaviors into a reusable bundle. This helps avoid duplication and ensures consistency. These implementations use the `CustomType` field in the parameter type. + +Refer to [Custom Types](/terraform/plugin/framework/handling-data/types/custom) for further details on creating provider-defined types and values. + +### Documentation + +Refer to [function documentation](/terraform/plugin/framework/functions/documentation) for information about the `Name`, `Description`, and `MarkdownDescription` fields available. + +## Reading Argument Data + +The [function implementation](/terraform/plugin/framework/functions/implementation) documentation covers the general methods for reading function argument data in function logic. + +When retrieving the argument value for this parameter: + +* If `CustomType` is set, use its associated value type. +* If `AllowUnknownValues` is enabled, you must use the [framework float64 type](/terraform/plugin/framework/handling-data/types/float64). +* If `AllowNullValue` is enabled, you must use the Go built-in `*float64` type or [framework float64 type](/terraform/plugin/framework/handling-data/types/float64). +* Otherwise, use the Go built-in `float64` type, Go built-in `*float64` type, or [framework float64 type](/terraform/plugin/framework/handling-data/types/float64). + +In this example, a function defines a single float64 parameter and accesses its argument value: + +```go +func (f ExampleFunction) Definition(ctx context.Context, req function.DefinitionRequest, resp *function.DefinitionResponse) { + resp.Definition = function.Definition{ + // ... other Definition fields ... + Parameters: []function.Parameter{ + function.Float64Parameter{ + Name: "float64_param", + // ... potentially other Float64Parameter fields ... + }, + }, + } +} + +func (f ExampleFunction) Run(ctx context.Context, req function.RunRequest, resp *function.RunResponse) { + var float64Arg float64 + // var float64Arg *float64 // e.g. with AllowNullValue, where Go nil equals Terraform null + // var float64Arg types.Float64 // e.g. with AllowUnknownValues or AllowNullValue + + resp.Error = function.ConcatFuncErrors(resp.Error, req.Arguments.Get(ctx, &float64Arg)) + + // float64Arg is now populated + // ... other logic ... +} +``` diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/functions/parameters/index.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/functions/parameters/index.mdx new file mode 100644 index 000000000..76b4e6ed2 --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/functions/parameters/index.mdx @@ -0,0 +1,130 @@ +--- +page_title: Function parameters +description: >- + The Terraform plugin framework includes multiple built-in function parameter + types and supports dynamic parameters. Parameters are positional data + arguments in a function definition. +--- + +# Function parameters + +Parameters in [function definitions](/terraform/plugin/framework/functions/implementation#definition-method) describes how data values are passed to the function logic. Every parameter type has an associated [value type](/terraform/plugin/framework/handling-data/types), although this data handling is simplified for function implementations over other provider concepts, such as resource implementations. + +## Available Parameter Types + +Function definitions support the following parameter types: + +- [Primitive](#primitive-parameter-types): Parameter that accepts a single value, such as a boolean, number, or string. +- [Collection](#collection-parameter-types): Parameter that accepts multiple values of a single element type, such as a list, map, or set. +- [Object](#object-parameter-type): Parameter that accepts a structure of explicit attribute names. +- [Dynamic](#dynamic-parameter-type): Parameter that accepts any value type. + +### Primitive Parameter Types + +Parameter types that accepts a single data value, such as a boolean, number, or string. + +| Parameter Type | Use Case | +|----------------|----------| +| [Bool](/terraform/plugin/framework/functions/parameters/bool) | Boolean true or false | +| [Float32](/terraform/plugin/framework/functions/parameters/float32) | 32-bit floating point number | +| [Float64](/terraform/plugin/framework/functions/parameters/float64) | 64-bit floating point number | +| [Int32](/terraform/plugin/framework/functions/parameters/int32) | 32-bit integer number | +| [Int64](/terraform/plugin/framework/functions/parameters/int64) | 64-bit integer number | +| [Number](/terraform/plugin/framework/functions/parameters/number) | Arbitrary precision (generally over 64-bit, up to 512-bit) number | +| [String](/terraform/plugin/framework/functions/parameters/string) | Collection of UTF-8 encoded characters | + +### Collection Parameter Types + +Parameter types that accepts multiple values of a single element type, such as a list, map, or set. + +| Parameter Type | Use Case | +|----------------|----------| +| [List](/terraform/plugin/framework/functions/parameters/list) | Ordered collection of single element type | +| [Map](/terraform/plugin/framework/functions/parameters/map) | Mapping of arbitrary string keys to values of single element type | +| [Set](/terraform/plugin/framework/functions/parameters/set) | Unordered, unique collection of single element type | + +### Object Parameter Type + +Parameter type that accepts a structure of explicit attribute names. + +| Parameter Type | Use Case | +|----------------|----------| +| [Object](/terraform/plugin/framework/functions/parameters/object) | Single structure mapping explicit attribute names | + +### Dynamic Parameter Type + + + +Dynamic value handling is an advanced use case. Prefer static parameter types when possible unless absolutely necessary for your use case. + + + +Parameter that accepts any value type, determined by Terraform at runtime. + +| Parameter Type | Use Case | +|----------------|----------| +| [Dynamic](/terraform/plugin/framework/functions/parameters/dynamic) | Accept any value type of data, determined at runtime. | + +## Parameter Naming + +All parameter types have a `Name` field that is **required**. + +### Missing Parameter Names + +Attempting to use unnamed parameters will generate runtime errors of the following form: + +```text +│ Error: Failed to load plugin schemas +│ +│ Error while loading schemas for plugin components: Failed to obtain provider schema: Could not load the schema for provider registry.terraform.io/cloud_provider/cloud_resource: failed to +│ retrieve schema from provider "registry.terraform.io/cloud_provider/cloud_resource": Invalid Function Definition: When validating the function definition, an implementation issue was +│ found. This is always an issue with the provider and should be reported to the provider developers. +│ +│ Function "example_function" - Parameter at position 0 does not have a name. +``` + +### Parameter Errors + +Parameter names are used in runtime errors to highlight which parameter is causing the issue. For example, using a value that is incompatible with the parameter type will generate an error message such as the following: + +```text +│ Error: Invalid function argument +│ +│ on resource.tf line 10, in resource "example_resource" "example": +│ 10: configurable_attribute = provider::example::example_function("string") +│ ├──────────────── +│ │ while calling provider::example::example_function(bool_param) +│ +│ Invalid value for "bool_param" parameter: a bool is required. +``` + +## Parameter Validation + +Validation handling for provider-defined function parameters can be enabled by using [custom types](/terraform/plugin/framework/handling-data/types/custom#validation). + +Implement the [`function.ValidateableParameter` interface](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/function#ValidateableParameter) on the custom value type to define and enable validation handling for a provider-defined function parameter, which will automatically raise an error when a value is determined to be invalid. + +```go +// Implementation of the function.ValidateableParameter interface +func (v CustomStringValue) ValidateParameter(ctx context.Context, req function.ValidateParameterRequest, resp *function.ValidateParameterResponse) { + if v.IsNull() || v.IsUnknown() { + return + } + + _, err := time.Parse(time.RFC3339, v.ValueString()) + + if err != nil { + resp.Error = function.NewArgumentFuncError( + req.Position, + "Invalid RFC 3339 String Value: "+ + "An unexpected error occurred while converting a string value that was expected to be RFC 3339 format. "+ + "The RFC 3339 string format is YYYY-MM-DDTHH:MM:SSZ, such as 2006-01-02T15:04:05Z or 2006-01-02T15:04:05+07:00.\n\n"+ + fmt.Sprintf("Position: %d", req.Position)+"\n"+ + "Given Value: "+v.ValueString()+"\n"+ + "Error: "+err.Error(), + ) + } +} +``` + +Refer to [Custom Types](/terraform/plugin/framework/handling-data/types/custom) for further details on creating provider-defined types and values diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/functions/parameters/int32.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/functions/parameters/int32.mdx new file mode 100644 index 000000000..de87e82b1 --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/functions/parameters/int32.mdx @@ -0,0 +1,102 @@ +--- +page_title: Int32 function parameters +description: >- + Learn how to use the 32-bit integer function parameter type with the + Terraform plugin framework. +--- + +# Int32 function parameters + + + +Use [Float32 Parameter](/terraform/plugin/framework/functions/parameters/float32) for 32-bit floating point numbers. Use [Number Parameter](/terraform/plugin/framework/functions/parameters/number) for arbitrary precision numbers. + + + +Int32 function parameters expect a 32-bit integer number value from a practitioner configuration. Values are accessible in function logic by the Go built-in `int32` type, Go built-in `*int32` type, or the [framework int32 type](/terraform/plugin/framework/handling-data/types/int32). + +In this Terraform configuration example, a int32 parameter is set to the value `123`: + +```hcl +provider::example::example(123) +``` + +## Function Definition + +Use the [`function.Int32Parameter` type](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/function#Int32Parameter) in the [function definition](/terraform/plugin/framework/functions/implementation#definition-method) to accept a int32 value. + +In this example, a function definition includes a first position int32 parameter: + +```go +func (f ExampleFunction) Definition(ctx context.Context, req function.DefinitionRequest, resp *function.DefinitionResponse) { + resp.Definition = function.Definition{ + // ... other Definition fields ... + Parameters: []function.Parameter{ + function.Int32Parameter{ + Name: "int32_param", + // ... potentially other Int32Parameter fields ... + }, + }, + } +} +``` + +If the int32 value should be the element type of a [collection parameter type](/terraform/plugin/framework/functions/parameters#collection-parameter-types), set the `ElementType` field according to the [framework int32 type](/terraform/plugin/framework/handling-data/types/int32). Refer to the collection parameter type documentation for additional details. + +If the int32 value should be a value type of an [object parameter type](/terraform/plugin/framework/functions/parameters#object-parameter-type), set the `AttributeTypes` map value according to the [framework int32 type](/terraform/plugin/framework/handling-data/types/int32). Refer to the object parameter type documentation for additional details. + +### Allow Null Values + +By default, Terraform will not pass null values to the function logic. Use the `AllowNullValue` field to explicitly allow null values, if there is a meaningful distinction that should occur in function logic. Enabling `AllowNullValue` requires using a Go pointer type or [framework int32 type](/terraform/plugin/framework/handling-data/types/int32) when reading argument data. + +### Allow Unknown Values + +By default, Terraform will not pass unknown values to the function logic. Use the `AllowUnknownValues` field to explicitly allow unknown values, if there is a meaningful distinction that should occur in function logic. Enabling `AllowUnknownValues` requires using a [framework int32 type](/terraform/plugin/framework/handling-data/types/int32) when reading argument data. + +### Custom Types + +You may want to build your own data value and type implementations to allow your provider to combine validation and other behaviors into a reusable bundle. This helps avoid duplication and ensures consistency. These implementations use the `CustomType` field in the parameter type. + +Refer to [Custom Types](/terraform/plugin/framework/handling-data/types/custom) for further details on creating provider-defined types and values. + +### Documentation + +Refer to [function documentation](/terraform/plugin/framework/functions/documentation) for information about the `Name`, `Description`, and `MarkdownDescription` fields available. + +## Reading Argument Data + +The [function implementation](/terraform/plugin/framework/functions/implementation) documentation covers the general methods for reading function argument data in function logic. + +When retrieving the argument value for this parameter: + +* If `CustomType` is set, use its associated value type. +* If `AllowUnknownValues` is enabled, you must use the [framework int32 type](/terraform/plugin/framework/handling-data/types/int32). +* If `AllowNullValue` is enabled, you must use the Go built-in `*int32` type or [framework int32 type](/terraform/plugin/framework/handling-data/types/int32). +* Otherwise, use the Go built-in `int32` type, Go built-in `*int32` type, or [framework int32 type](/terraform/plugin/framework/handling-data/types/int32). + +In this example, a function defines a single int32 parameter and accesses its argument value: + +```go +func (f ExampleFunction) Definition(ctx context.Context, req function.DefinitionRequest, resp *function.DefinitionResponse) { + resp.Definition = function.Definition{ + // ... other Definition fields ... + Parameters: []function.Parameter{ + function.Int32Parameter{ + Name: "int32_param", + // ... potentially other Int32Parameter fields ... + }, + }, + } +} + +func (f ExampleFunction) Run(ctx context.Context, req function.RunRequest, resp *function.RunResponse) { + var int32Arg int32 + // var int32Arg *int32 // e.g. with AllowNullValue, where Go nil equals Terraform null + // var int32Arg types.Int32 // e.g. with AllowUnknownValues or AllowNullValue + + resp.Error = function.ConcatFuncErrors(resp.Error, req.Arguments.Get(ctx, &int32Arg)) + + // int32Arg is now populated + // ... other logic ... +} +``` diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/functions/parameters/int64.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/functions/parameters/int64.mdx new file mode 100644 index 000000000..5e8411516 --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/functions/parameters/int64.mdx @@ -0,0 +1,102 @@ +--- +page_title: Int64 function parameters +description: >- + Learn how to use the 64-bit integer function parameter type with the + Terraform plugin framework. +--- + +# Int64 function parameters + + + +Use [Float64 Parameter](/terraform/plugin/framework/functions/parameters/float64) for 64-bit floating point numbers. Use [Number Parameter](/terraform/plugin/framework/functions/parameters/number) for arbitrary precision numbers. + + + +Int64 function parameters expect a 64-bit integer number value from a practitioner configuration. Values are accessible in function logic by the Go built-in `int64` type, Go built-in `*int64` type, or the [framework int64 type](/terraform/plugin/framework/handling-data/types/int64). + +In this Terraform configuration example, a int64 parameter is set to the value `123`: + +```hcl +provider::example::example(123) +``` + +## Function Definition + +Use the [`function.Int64Parameter` type](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/function#Int64Parameter) in the [function definition](/terraform/plugin/framework/functions/implementation#definition-method) to accept a int64 value. + +In this example, a function definition includes a first position int64 parameter: + +```go +func (f ExampleFunction) Definition(ctx context.Context, req function.DefinitionRequest, resp *function.DefinitionResponse) { + resp.Definition = function.Definition{ + // ... other Definition fields ... + Parameters: []function.Parameter{ + function.Int64Parameter{ + Name: "int64_param", + // ... potentially other Int64Parameter fields ... + }, + }, + } +} +``` + +If the int64 value should be the element type of a [collection parameter type](/terraform/plugin/framework/functions/parameters#collection-parameter-types), set the `ElementType` field according to the [framework int64 type](/terraform/plugin/framework/handling-data/types/int64). Refer to the collection parameter type documentation for additional details. + +If the int64 value should be a value type of an [object parameter type](/terraform/plugin/framework/functions/parameters#object-parameter-type), set the `AttributeTypes` map value according to the [framework int64 type](/terraform/plugin/framework/handling-data/types/int64). Refer to the object parameter type documentation for additional details. + +### Allow Null Values + +By default, Terraform will not pass null values to the function logic. Use the `AllowNullValue` field to explicitly allow null values, if there is a meaningful distinction that should occur in function logic. Enabling `AllowNullValue` requires using a Go pointer type or [framework int64 type](/terraform/plugin/framework/handling-data/types/int64) when reading argument data. + +### Allow Unknown Values + +By default, Terraform will not pass unknown values to the function logic. Use the `AllowUnknownValues` field to explicitly allow unknown values, if there is a meaningful distinction that should occur in function logic. Enabling `AllowUnknownValues` requires using a [framework int64 type](/terraform/plugin/framework/handling-data/types/int64) when reading argument data. + +### Custom Types + +You may want to build your own data value and type implementations to allow your provider to combine validation and other behaviors into a reusable bundle. This helps avoid duplication and ensures consistency. These implementations use the `CustomType` field in the parameter type. + +Refer to [Custom Types](/terraform/plugin/framework/handling-data/types/custom) for further details on creating provider-defined types and values. + +### Documentation + +Refer to [function documentation](/terraform/plugin/framework/functions/documentation) for information about the `Name`, `Description`, and `MarkdownDescription` fields available. + +## Reading Argument Data + +The [function implementation](/terraform/plugin/framework/functions/implementation) documentation covers the general methods for reading function argument data in function logic. + +When retrieving the argument value for this parameter: + +* If `CustomType` is set, use its associated value type. +* If `AllowUnknownValues` is enabled, you must use the [framework int64 type](/terraform/plugin/framework/handling-data/types/int64). +* If `AllowNullValue` is enabled, you must use the Go built-in `*int64` type or [framework int64 type](/terraform/plugin/framework/handling-data/types/int64). +* Otherwise, use the Go built-in `int64` type, Go built-in `*int64` type, or [framework int64 type](/terraform/plugin/framework/handling-data/types/int64). + +In this example, a function defines a single int64 parameter and accesses its argument value: + +```go +func (f ExampleFunction) Definition(ctx context.Context, req function.DefinitionRequest, resp *function.DefinitionResponse) { + resp.Definition = function.Definition{ + // ... other Definition fields ... + Parameters: []function.Parameter{ + function.Int64Parameter{ + Name: "int64_param", + // ... potentially other Int64Parameter fields ... + }, + }, + } +} + +func (f ExampleFunction) Run(ctx context.Context, req function.RunRequest, resp *function.RunResponse) { + var int64Arg int64 + // var int64Arg *int64 // e.g. with AllowNullValue, where Go nil equals Terraform null + // var int64Arg types.Int64 // e.g. with AllowUnknownValues or AllowNullValue + + resp.Error = function.ConcatFuncErrors(resp.Error, req.Arguments.Get(ctx, &int64Arg)) + + // int64Arg is now populated + // ... other logic ... +} +``` diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/functions/parameters/list.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/functions/parameters/list.mdx new file mode 100644 index 000000000..edbca773a --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/functions/parameters/list.mdx @@ -0,0 +1,103 @@ +--- +page_title: List function parameters +description: >- + Learn how to use the list function parameter type with the + Terraform plugin framework. +--- + +# List function parameters + +List function parameters expect an ordered collection of single element type value from a practitioner configuration. Values are accessible in function logic by a Go slice of an appropriate pointer type to match the element type `[]*T` or the [framework list type](/terraform/plugin/framework/handling-data/types/list). + +In this Terraform configuration example, a list of string parameter is set to the ordered collection values `one` and `two`: + +```hcl +provider::example::example(["one", "two"]) +``` + +## Function Definition + +Use the [`function.ListParameter` type](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/function#ListParameter) in the [function definition](/terraform/plugin/framework/functions/implementation#definition-method) to accept a list value. + +The `ElementType` field must be defined, which represents the single [framework value type](/terraform/plugin/framework/handling-data/types) of every element of the list. An element type may itself contain further collection or object types, if necessary. + +In this example, a function definition includes a first position list of string parameter: + +```go +func (f ExampleFunction) Definition(ctx context.Context, req function.DefinitionRequest, resp *function.DefinitionResponse) { + resp.Definition = function.Definition{ + // ... other Definition fields ... + Parameters: []function.Parameter{ + function.ListParameter{ + ElementType: types.StringType, + Name: "list_param", + // ... potentially other ListParameter fields ... + }, + }, + } +} +``` + +If the list value should be the element type of another [collection parameter type](/terraform/plugin/framework/functions/parameters#collection-parameter-types), set the `ElementType` field according to the [framework list type](/terraform/plugin/framework/handling-data/types/list). Refer to the collection parameter type documentation for additional details. + +If the list value should be a value type of an [object parameter type](/terraform/plugin/framework/functions/parameters#object-parameter-type), set the `AttributeTypes` map value according to the [framework list type](/terraform/plugin/framework/handling-data/types/list). Refer to the object parameter type documentation for additional details. + +### Allow Null Values + + + +A known list value with null element values will always be sent to the function logic, regardless of the `AllowNullValue` setting. Data handling must always account for this situation. + + + +By default, Terraform will not pass null values to the function logic. Use the `AllowNullValue` field to explicitly allow null values, if there is a meaningful distinction that should occur in function logic. Enabling `AllowNullValue` requires no changes when reading argument data. + +### Allow Unknown Values + +By default, Terraform will not pass unknown values to the function logic. Use the `AllowUnknownValues` field to explicitly allow unknown values, if there is a meaningful distinction that should occur in function logic. Enabling `AllowUnknownValues` requires using a [framework list type](/terraform/plugin/framework/handling-data/types/list) when reading argument data. + +### Custom Types + +You may want to build your own data value and type implementations to allow your provider to combine validation and other behaviors into a reusable bundle. This helps avoid duplication and ensures consistency. These implementations use the `CustomType` field in the parameter type. + +Refer to [Custom Types](/terraform/plugin/framework/handling-data/types/custom) for further details on creating provider-defined types and values. + +### Documentation + +Refer to [function documentation](/terraform/plugin/framework/functions/documentation) for information about the `Name`, `Description`, and `MarkdownDescription` fields available. + +## Reading Argument Data + +The [function implementation](/terraform/plugin/framework/functions/implementation) documentation covers the general methods for reading function argument data in function logic. + +When retrieving the argument value for this parameter: + +* If `CustomType` is set, use its associated value type. +* If `AllowUnknownValues` is enabled, you must use the [framework list type](/terraform/plugin/framework/handling-data/types/list). +* Otherwise, use the Go slice of an appropriate pointer type to match the element type `[]*T` or [framework list type](/terraform/plugin/framework/handling-data/types/list). + +In this example, a function defines a single list of string parameter and accesses its argument value: + +```go +func (f ExampleFunction) Definition(ctx context.Context, req function.DefinitionRequest, resp *function.DefinitionResponse) { + resp.Definition = function.Definition{ + // ... other Definition fields ... + Parameters: []function.Parameter{ + function.ListParameter{ + ElementType: types.StringType, + Name: "list_param", + }, + }, + } +} + +func (f ExampleFunction) Run(ctx context.Context, req function.RunRequest, resp *function.RunResponse) { + var listArg []*string // Go nil equals Terraform null + // var listArg types.List // e.g. with AllowUnknownValues + + resp.Error = function.ConcatFuncErrors(resp.Error, req.Arguments.Get(ctx, &listArg)) + + // listArg is now populated + // ... other logic ... +} +``` diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/functions/parameters/map.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/functions/parameters/map.mdx new file mode 100644 index 000000000..49d35d893 --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/functions/parameters/map.mdx @@ -0,0 +1,107 @@ +--- +page_title: Map function parameters +description: >- + Learn how to use the map function parameter type with the + Terraform plugin framework. +--- + +# List function parameters + +Map function parameters expect a mapping of arbitrary string keys to values of single element type from a practitioner configuration. Values are accessible in function logic by a Go map of string keys to values of an appropriate pointer type to match the element type `map[string]*T` or the [framework map type](/terraform/plugin/framework/handling-data/types/map). + +In this Terraform configuration example, a map of string parameter is set to the mapped values of `"key1"` to `"value1"` and `"key2"` to `"value2"`: + +```hcl +provider::example::example({ + "key1" = "value1", + "key2" = "value2", +}) +``` + +## Function Definition + +Use the [`function.MapParameter` type](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/function#MapParameter) in the [function definition](/terraform/plugin/framework/functions/implementation#definition-method) to accept a map value. + +The `ElementType` field must be defined, which represents the single [framework value type](/terraform/plugin/framework/handling-data/types) of every element of the map. An element type may itself contain further collection or object types, if necessary. + +In this example, a function definition includes a first position map of string parameter: + +```go +func (f ExampleFunction) Definition(ctx context.Context, req function.DefinitionRequest, resp *function.DefinitionResponse) { + resp.Definition = function.Definition{ + // ... other Definition fields ... + Parameters: []function.Parameter{ + function.MapParameter{ + ElementType: types.StringType, + Name: "map_param", + // ... potentially other MapParameter fields ... + }, + }, + } +} +``` + +If the map value should be the element type of another [collection parameter type](/terraform/plugin/framework/functions/parameters#collection-parameter-types), set the `ElementType` field according to the [framework map type](/terraform/plugin/framework/handling-data/types/map). Refer to the collection parameter type documentation for additional details. + +If the map value should be a value type of an [object parameter type](/terraform/plugin/framework/functions/parameters#object-parameter-type), set the `AttributeTypes` map value according to the [framework map type](/terraform/plugin/framework/handling-data/types/map). Refer to the object parameter type documentation for additional details. + +### Allow Null Values + + + +A known map value with null element values will always be sent to the function logic, regardless of the `AllowNullValue` setting. Data handling must always account for this situation. + + + +By default, Terraform will not pass null values to the function logic. Use the `AllowNullValue` field to explicitly allow null values, if there is a meaningful distinction that should occur in function logic. Enabling `AllowNullValue` requires no changes when reading argument data. + +### Allow Unknown Values + +By default, Terraform will not pass unknown values to the function logic. Use the `AllowUnknownValues` field to explicitly allow unknown values, if there is a meaningful distinction that should occur in function logic. Enabling `AllowUnknownValues` requires using a [framework map type](/terraform/plugin/framework/handling-data/types/map) when reading argument data. + +### Custom Types + +You may want to build your own data value and type implementations to allow your provider to combine validation and other behaviors into a reusable bundle. This helps avoid duplication and ensures consistency. These implementations use the `CustomType` field in the parameter type. + +Refer to [Custom Types](/terraform/plugin/framework/handling-data/types/custom) for further details on creating provider-defined types and values. + +### Documentation + +Refer to [function documentation](/terraform/plugin/framework/functions/documentation) for information about the `Name`, `Description`, and `MarkdownDescription` fields available. + +## Reading Argument Data + +The [function implementation](/terraform/plugin/framework/functions/implementation) documentation covers the general methods for reading function argument data in function logic. + +When retrieving the argument value for this parameter: + +* If `CustomType` is set, use its associated value type. +* If `AllowUnknownValues` is enabled, you must use the [framework map type](/terraform/plugin/framework/handling-data/types/map). +* Otherwise, use the Go map of string keys to values of an appropriate pointer type to match the element type `map[string]*T` or [framework map type](/terraform/plugin/framework/handling-data/types/map). + +In this example, a function defines a single map of string parameter and accesses its argument value: + +```go +func (f ExampleFunction) Definition(ctx context.Context, req function.DefinitionRequest, resp *function.DefinitionResponse) { + resp.Definition = function.Definition{ + // ... other Definition fields ... + Parameters: []function.Parameter{ + function.MapParameter{ + ElementType: types.StringType, + Name: "map_param", + // ... potentially other MapParameter fields ... + }, + }, + } +} + +func (f ExampleFunction) Run(ctx context.Context, req function.RunRequest, resp *function.RunResponse) { + var mapArg map[string]*string // Go nil equals Terraform null + // var mapArg types.Map // e.g. with AllowUnknownValues + + resp.Error = function.ConcatFuncErrors(resp.Error, req.Arguments.Get(ctx, &mapArg)) + + // mapArg is now populated + // ... other logic ... +} +``` diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/functions/parameters/number.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/functions/parameters/number.mdx new file mode 100644 index 000000000..256d521ef --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/functions/parameters/number.mdx @@ -0,0 +1,100 @@ +--- +page_title: Number function parameters +description: >- + Learn how to use the arbitrary precision number function parameter type with + the Terraform plugin framework. +--- + +# Number function parameters + + + +Use [Float64 Parameter](/terraform/plugin/framework/functions/parameters/float64) for 64-bit floating point numbers. Use [Int64 Parameter](/terraform/plugin/framework/functions/parameters/int64) for 64-bit integer numbers. + + + +Number function parameters expect an arbitrary precision (generally over 64-bit, up to 512-bit) number value from a practitioner configuration. Values are accessible in function logic by the Go built-in `*big.Float` type or the [framework number type](/terraform/plugin/framework/handling-data/types/number). + +In this Terraform configuration example, a number parameter is set to the value greater than 64 bits: + +```hcl +provider::example::example(pow(2, 64) + 1) +``` + +## Function Definition + +Use the [`function.NumberParameter` type](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/function#NumberParameter) in the [function definition](/terraform/plugin/framework/functions/implementation#definition-method) to accept a number value. + +In this example, a function definition includes a first position number parameter: + +```go +func (f ExampleFunction) Definition(ctx context.Context, req function.DefinitionRequest, resp *function.DefinitionResponse) { + resp.Definition = function.Definition{ + // ... other Definition fields ... + Parameters: []function.Parameter{ + function.NumberParameter{ + Name: "number_param", + // ... potentially other NumberParameter fields ... + }, + }, + } +} +``` + +If the number value should be the element type of a [collection parameter type](/terraform/plugin/framework/functions/parameters#collection-parameter-types), set the `ElementType` field according to the [framework number type](/terraform/plugin/framework/handling-data/types/number). Refer to the collection parameter type documentation for additional details. + +If the number value should be a value type of an [object parameter type](/terraform/plugin/framework/functions/parameters#object-parameter-type), set the `AttributeTypes` map value according to the [framework number type](/terraform/plugin/framework/handling-data/types/number). Refer to the object parameter type documentation for additional details. + +### Allow Null Values + +By default, Terraform will not pass null values to the function logic. Use the `AllowNullValue` field to explicitly allow null values, if there is a meaningful distinction that should occur in function logic. Enabling `AllowNullValue` requires using a Go pointer type or [framework number type](/terraform/plugin/framework/handling-data/types/number) when reading argument data. + +### Allow Unknown Values + +By default, Terraform will not pass unknown values to the function logic. Use the `AllowUnknownValues` field to explicitly allow unknown values, if there is a meaningful distinction that should occur in function logic. Enabling `AllowUnknownValues` requires using a [framework number type](/terraform/plugin/framework/handling-data/types/number) when reading argument data. + +### Custom Types + +You may want to build your own data value and type implementations to allow your provider to combine validation and other behaviors into a reusable bundle. This helps avoid duplication and ensures consistency. These implementations use the `CustomType` field in the parameter type. + +Refer to [Custom Types](/terraform/plugin/framework/handling-data/types/custom) for further details on creating provider-defined types and values. + +### Documentation + +Refer to [function documentation](/terraform/plugin/framework/functions/documentation) for information about the `Name`, `Description`, and `MarkdownDescription` fields available. + +## Reading Argument Data + +The [function implementation](/terraform/plugin/framework/functions/implementation) documentation covers the general methods for reading function argument data in function logic. + +When retrieving the argument value for this parameter: + +* If `CustomType` is set, use its associated value type. +* If `AllowUnknownValues` is enabled, you must use the [framework number type](/terraform/plugin/framework/handling-data/types/number). +* Otherwise, use the Go built-in `*big.Float` type or [framework number type](/terraform/plugin/framework/handling-data/types/number). + +In this example, a function defines a single number parameter and accesses its argument value: + +```go +func (f ExampleFunction) Definition(ctx context.Context, req function.DefinitionRequest, resp *function.DefinitionResponse) { + resp.Definition = function.Definition{ + // ... other Definition fields ... + Parameters: []function.Parameter{ + function.NumberParameter{ + Name: "number_param", + // ... potentially other NumberParameter fields ... + }, + }, + } +} + +func (f ExampleFunction) Run(ctx context.Context, req function.RunRequest, resp *function.RunResponse) { + var numberArg *big.Float + // var numberArg types.Number // e.g. with AllowUnknownValues or AllowNullValue + + resp.Error = function.ConcatFuncErrors(resp.Error, req.Arguments.Get(ctx, &numberArg)) + + // numberArg is now populated + // ... other logic ... +} +``` diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/functions/parameters/object.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/functions/parameters/object.mdx new file mode 100644 index 000000000..41bdcbae4 --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/functions/parameters/object.mdx @@ -0,0 +1,128 @@ +--- +page_title: Object function parameters +description: >- + Learn how to use the object function parameter type with + the Terraform plugin framework. +--- + +# Object function parameters + +Object function parameters expect a single structure mapping explicit attribute names to type definitions from a practitioner configuration. Values are accessible in function logic by a Go structure type annotated with `tfsdk` field tags or the [framework object type](/terraform/plugin/framework/handling-data/types/object). + +Configurations must include all object attributes or a configuration error is raised. Configurations explicitly setting object attribute values to `null` will prevent this type of configuration error while leaving that object attribute value unset. The `AllowNullValue` setting does not need to be enabled for object attribute `null` values to work in this manner. + +In this Terraform configuration example, a object parameter is set to the mapped values of `attr1` to `"value1"`, `attr2` to `123`, and `attr3` to `null`: + +```hcl +provider::example::example({ + attr1 = "value1" + attr2 = 123 + attr3 = null +}) +``` + +## Function Definition + +Use the [`function.ObjectParameter` type](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/function#ObjectParameter) in the [function definition](/terraform/plugin/framework/functions/implementation#definition-method) to accept an object value. + +The `AttributeTypes` field must be defined, which represents a mapping of attribute names to [framework value types](/terraform/plugin/framework/handling-data/types). An attribute type may itself contain further collection or object types, if necessary. + +In this example, a function definition includes a first position object parameter: + +```go +func (f ExampleFunction) Definition(ctx context.Context, req function.DefinitionRequest, resp *function.DefinitionResponse) { + resp.Definition = function.Definition{ + // ... other Definition fields ... + Parameters: []function.Parameter{ + function.ObjectParameter{ + AttributeTypes: map[string]attr.Type{ + "attr1": types.StringType, + "attr2": types.Int64Type, + "attr3": types.BoolType, + }, + Name: "object_param", + // ... potentially other ObjectParameter fields ... + }, + }, + } +} +``` + +If the map value should be the element type of another [collection parameter type](/terraform/plugin/framework/functions/parameters#collection-parameter-types), set the `ElementType` field according to the [framework object type](/terraform/plugin/framework/handling-data/types/object). Refer to the collection parameter type documentation for additional details. + +If the map value should be a value type of an [object parameter type](/terraform/plugin/framework/functions/parameters#object-parameter-type), set the `AttributeTypes` map value according to the [framework object type](/terraform/plugin/framework/handling-data/types/object). Refer to the object parameter type documentation for additional details. + +### Allow Null Values + + + +A known object value with null attribute values will always be sent to the function logic, regardless of the `AllowNullValue` setting. Data handling must always account for this situation. + + + +By default, Terraform will not pass null values to the function logic. Use the `AllowNullValue` field to explicitly allow null values, if there is a meaningful distinction that should occur in function logic. Enabling `AllowNullValue` requires no changes when reading argument data. + +### Allow Unknown Values + +By default, Terraform will not pass unknown values to the function logic. Use the `AllowUnknownValues` field to explicitly allow unknown values, if there is a meaningful distinction that should occur in function logic. Enabling `AllowUnknownValues` requires using a [framework object type](/terraform/plugin/framework/handling-data/types/object) when reading argument data. + +### Custom Types + +You may want to build your own data value and type implementations to allow your provider to combine validation and other behaviors into a reusable bundle. This helps avoid duplication and ensures consistency. These implementations use the `CustomType` field in the parameter type. + +Refer to [Custom Types](/terraform/plugin/framework/handling-data/types/custom) for further details on creating provider-defined types and values. + +### Documentation + +Refer to [function documentation](/terraform/plugin/framework/functions/documentation) for information about the `Name`, `Description`, and `MarkdownDescription` fields available. + +## Reading Argument Data + +The [function implementation](/terraform/plugin/framework/functions/implementation) documentation covers the general methods for reading function argument data in function logic. + +When retrieving the argument value for this parameter: + +* If `CustomType` is set, use its associated value type. +* If `AllowUnknownValues` is enabled, you must use the [framework object type](/terraform/plugin/framework/handling-data/types/object). +* If `AllowNullValue` is enabled, you must use a pointer to the Go structure type annotated with `tfsdk` field tags or the [framework object type](/terraform/plugin/framework/handling-data/types/object). +* Otherwise, use the Go structure type annotated with `tfsdk` field tags or [framework object type](/terraform/plugin/framework/handling-data/types/object). + +In this example, a function defines a single object parameter and accesses its argument value: + +```go +func (f ExampleFunction) Definition(ctx context.Context, req function.DefinitionRequest, resp *function.DefinitionResponse) { + resp.Definition = function.Definition{ + // ... other Definition fields ... + Parameters: []function.Parameter{ + function.ObjectParameter{ + AttributeTypes: map[string]attr.Type{ + "attr1": types.StringType, + "attr2": types.Int64Type, + "attr3": types.BoolType, + }, + Name: "object_param", + }, + }, + } +} + +func (f ExampleFunction) Run(ctx context.Context, req function.RunRequest, resp *function.RunResponse) { + var objectArg struct{ + Attr1 *string `tfsdk:"attr1"` + Attr2 *int64 `tfsdk:"attr2"` + Attr3 *bool `tfsdk:"attr3"` + } + // e.g. with AllowNullValues + // var objectArg *struct{ + // Attr1 *string `tfsdk:"attr1"` + // Attr2 *int64 `tfsdk:"attr2"` + // Attr3 *bool `tfsdk:"attr3"` + // } + // var objectArg types.Object // e.g. with AllowUnknownValues or AllowNullValues + + resp.Error = function.ConcatFuncErrors(resp.Error, req.Arguments.Get(ctx, &objectArg)) + + // objectArg is now populated + // ... other logic ... +} +``` diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/functions/parameters/set.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/functions/parameters/set.mdx new file mode 100644 index 000000000..dc53f9952 --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/functions/parameters/set.mdx @@ -0,0 +1,104 @@ +--- +page_title: Set function parameters +description: >- + Learn how to use the set function parameter type with + the Terraform plugin framework. +--- + +# Set function parameters + +Set function parameters expect an unordered, unique collection of single element type value from a practitioner configuration. Values are accessible in function logic by a Go slice of an appropriate pointer type to match the element type `[]*T` or the [framework set type](/terraform/plugin/framework/handling-data/types/set). + +In this Terraform configuration example, a set of string parameter is set to the collection values `one` and `two`: + +```hcl +provider::example::example(["one", "two"]) +``` + +## Function Definition + +Use the [`function.SetParameter` type](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/function#SetParameter) in the [function definition](/terraform/plugin/framework/functions/implementation#definition-method) to accept a set value. + +The `ElementType` field must be defined, which represents the single [framework value type](/terraform/plugin/framework/handling-data/types) of every element of the set. An element type may itself contain further collection or object types, if necessary. + +In this example, a function definition includes a first position set of string parameter: + +```go +func (f ExampleFunction) Definition(ctx context.Context, req function.DefinitionRequest, resp *function.DefinitionResponse) { + resp.Definition = function.Definition{ + // ... other Definition fields ... + Parameters: []function.Parameter{ + function.SetParameter{ + ElementType: types.StringType, + Name: "set_param", + // ... potentially other SetParameter fields ... + }, + }, + } +} +``` + +If the set value should be the element type of another [collection parameter type](/terraform/plugin/framework/functions/parameters#collection-parameter-types), set the `ElementType` field according to the [framework set type](/terraform/plugin/framework/handling-data/types/set). Refer to the collection parameter type documentation for additional details. + +If the set value should be a value type of an [object parameter type](/terraform/plugin/framework/functions/parameters#object-parameter-type), set the `AttributeTypes` map value according to the [framework set type](/terraform/plugin/framework/handling-data/types/set). Refer to the object parameter type documentation for additional details. + +### Allow Null Values + + + +A known set value with null element values will always be sent to the function logic, regardless of the `AllowNullValue` setting. Data handling must always account for this situation. + + + +By default, Terraform will not pass null values to the function logic. Use the `AllowNullValue` field to explicitly allow null values, if there is a meaningful distinction that should occur in function logic. Enabling `AllowNullValue` requires no changes when reading argument data. + +### Allow Unknown Values + +By default, Terraform will not pass unknown values to the function logic. Use the `AllowUnknownValues` field to explicitly allow unknown values, if there is a meaningful distinction that should occur in function logic. Enabling `AllowUnknownValues` requires using a [framework set type](/terraform/plugin/framework/handling-data/types/set) when reading argument data. + +### Custom Types + +You may want to build your own data value and type implementations to allow your provider to combine validation and other behaviors into a reusable bundle. This helps avoid duplication and ensures consistency. These implementations use the `CustomType` field in the parameter type. + +Refer to [Custom Types](/terraform/plugin/framework/handling-data/types/custom) for further details on creating provider-defined types and values. + +### Documentation + +Refer to [function documentation](/terraform/plugin/framework/functions/documentation) for information about the `Name`, `Description`, and `MarkdownDescription` fields available. + +## Reading Argument Data + +The [function implementation](/terraform/plugin/framework/functions/implementation) documentation covers the general methods for reading function argument data in function logic. + +When retrieving the argument value for this parameter: + +* If `CustomType` is set, use its associated value type. +* If `AllowUnknownValues` is enabled, you must use the [framework set type](/terraform/plugin/framework/handling-data/types/set). +* Otherwise, use the Go slice of an appropriate pointer type to match the element type `[]*T` or [framework set type](/terraform/plugin/framework/handling-data/types/set). + +In this example, a function defines a single set of string parameter and accesses its argument value: + +```go +func (f ExampleFunction) Definition(ctx context.Context, req function.DefinitionRequest, resp *function.DefinitionResponse) { + resp.Definition = function.Definition{ + // ... other Definition fields ... + Parameters: []function.Parameter{ + function.SetParameter{ + ElementType: types.StringType, + Name: "set_param", + // ... potentially other SetParameter fields ... + }, + }, + } +} + +func (f ExampleFunction) Run(ctx context.Context, req function.RunRequest, resp *function.RunResponse) { + var setArg []*string // Go nil equals Terraform null + // var setArg types.Set // e.g. with AllowUnknownValues + + resp.Error = function.ConcatFuncErrors(resp.Error, req.Arguments.Get(ctx, &setArg)) + + // setArg is now populated + // ... other logic ... +} +``` diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/functions/parameters/string.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/functions/parameters/string.mdx new file mode 100644 index 000000000..bb449009c --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/functions/parameters/string.mdx @@ -0,0 +1,96 @@ +--- +page_title: String function parameters +description: >- + Learn how to use the string function parameter type with + the Terraform plugin framework. +--- + +# String function parameters + +String function parameters expect a collection of UTF-8 encoded bytes from a practitioner configuration. Values are accessible in function logic by the Go built-in `string` type, Go built-in `*string` type, or the [framework string type](/terraform/plugin/framework/handling-data/types/string). + +In this Terraform configuration example, a string parameter is set to the value `"hello world"`: + +```hcl +provider::example::example("hello world") +``` + +## Function Definition + +Use the [`function.StringParameter` type](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/function#StringParameter) in the [function definition](/terraform/plugin/framework/functions/implementation#definition-method) to accept a string value. + +In this example, a function definition includes a first position string parameter: + +```go +func (f ExampleFunction) Definition(ctx context.Context, req function.DefinitionRequest, resp *function.DefinitionResponse) { + resp.Definition = function.Definition{ + // ... other Definition fields ... + Parameters: []function.Parameter{ + function.StringParameter{ + Name: "string_param", + // ... potentially other StringParameter fields ... + }, + }, + } +} +``` + +If the string value should be the element type of a [collection parameter type](/terraform/plugin/framework/functions/parameters#collection-parameter-types), set the `ElementType` field according to the [framework string type](/terraform/plugin/framework/handling-data/types/string). Refer to the collection parameter type documentation for additional details. + +If the string value should be a value type of an [object parameter type](/terraform/plugin/framework/functions/parameters#object-parameter-type), set the `AttributeTypes` map value according to the [framework string type](/terraform/plugin/framework/handling-data/types/string). Refer to the object parameter type documentation for additional details. + +### Allow Null Values + +By default, Terraform will not pass null values to the function logic. Use the `AllowNullValue` field to explicitly allow null values, if there is a meaningful distinction that should occur in function logic. Enabling `AllowNullValue` requires using a Go pointer type or [framework string type](/terraform/plugin/framework/handling-data/types/string) when reading argument data. + +### Allow Unknown Values + +By default, Terraform will not pass unknown values to the function logic. Use the `AllowUnknownValues` field to explicitly allow unknown values, if there is a meaningful distinction that should occur in function logic. Enabling `AllowUnknownValues` requires using a [framework string type](/terraform/plugin/framework/handling-data/types/string) when reading argument data. + +### Custom Types + +You may want to build your own data value and type implementations to allow your provider to combine validation and other behaviors into a reusable bundle. This helps avoid duplication and ensures consistency. These implementations use the `CustomType` field in the parameter type. + +Refer to [Custom Types](/terraform/plugin/framework/handling-data/types/custom) for further details on creating provider-defined types and values. + +### Documentation + +Refer to [function documentation](/terraform/plugin/framework/functions/documentation) for information about the `Name`, `Description`, and `MarkdownDescription` fields available. + +## Reading Argument Data + +The [function implementation](/terraform/plugin/framework/functions/implementation) documentation covers the general methods for reading function argument data in function logic. + +When retrieving the argument value for this parameter: + +* If `CustomType` is set, use its associated value type. +* If `AllowUnknownValues` is enabled, you must use the [framework string type](/terraform/plugin/framework/handling-data/types/string). +* If `AllowNullValue` is enabled, you must use the Go built-in `*string` type or [framework string type](/terraform/plugin/framework/handling-data/types/string). +* Otherwise, use the Go built-in `string` type, Go built-in `*string` type, or [framework string type](/terraform/plugin/framework/handling-data/types/string). + +In this example, a function defines a single string parameter and accesses its argument value: + +```go +func (f ExampleFunction) Definition(ctx context.Context, req function.DefinitionRequest, resp *function.DefinitionResponse) { + resp.Definition = function.Definition{ + // ... other Definition fields ... + Parameters: []function.Parameter{ + function.StringParameter{ + Name: "string_param", + // ... potentially other StringParameter fields ... + }, + }, + } +} + +func (f ExampleFunction) Run(ctx context.Context, req function.RunRequest, resp *function.RunResponse) { + var stringArg string + // var stringArg *string // e.g. with AllowNullValue, where Go nil equals Terraform null + // var stringArg types.String // e.g. with AllowUnknownValues or AllowNullValue + + resp.Error = function.ConcatFuncErrors(resp.Error, req.Arguments.Get(ctx, &stringArg)) + + // stringArg is now populated + // ... other logic ... +} +``` diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/functions/returns/bool.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/functions/returns/bool.mdx new file mode 100644 index 000000000..0da2af0d0 --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/functions/returns/bool.mdx @@ -0,0 +1,66 @@ +--- +page_title: Boolean return values +description: >- + Learn how to use the boolean function return value type with the Terraform + plugin framework. +--- + +# Boolean return values + +Bool function return values expect a boolean true or false value from function logic. Set values in function logic with the Go built-in `bool` type, Go built-in `*bool` type, or the [framework bool type](/terraform/plugin/framework/handling-data/types/bool). + +## Function Definition + +Use the [`function.BoolReturn` type](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/function#BoolReturn) in the [function definition](/terraform/plugin/framework/functions/implementation#definition-method). + +In this example, a function definition includes a bool return: + +```go +func (f ExampleFunction) Definition(ctx context.Context, req function.DefinitionRequest, resp *function.DefinitionResponse) { + resp.Definition = function.Definition{ + // ... other Definition fields ... + Return: function.BoolReturn{ + // ... potentially other BoolReturn fields ... + }, + } +} +``` + +### Custom Types + +You may want to build your own data value and type implementations to allow your provider to combine validation and other behaviors into a reusable bundle. This helps avoid duplication and ensures consistency. These implementations use the `CustomType` field in the return type. + +Refer to [Custom Types](/terraform/plugin/framework/handling-data/types/custom) for further details on creating provider-defined types and values. + +### Documentation + +Return documentation is expected in the top-level function documentation. Refer to [function documentation](/terraform/plugin/framework/functions/documentation) for information about the `Summary`, `Description`, and `MarkdownDescription` fields available. + +## Setting Return Data + +The [function implementation](/terraform/plugin/framework/functions/implementation) documentation covers the general methods for setting function return data in function logic. + +When setting the value for this return: + +* If `CustomType` is set, use its associated value type. +* Otherwise, use the Go built-in `bool` type, Go built-in `*bool` type, or [framework bool type](/terraform/plugin/framework/handling-data/types/bool). + +In this example, a function defines a bool return and sets its value: + +```go +func (f ExampleFunction) Definition(ctx context.Context, req function.DefinitionRequest, resp *function.DefinitionResponse) { + resp.Definition = function.Definition{ + // ... other Definition fields ... + Return: function.BoolReturn{}, + } +} + +func (f ExampleFunction) Run(ctx context.Context, req function.RunRequest, resp *function.RunResponse) { + // ... other logic ... + + // hardcoded value for example brevity + result := true + + resp.Error = function.ConcatFuncErrors(resp.Error, resp.Result.Set(ctx, &result)) +} +``` diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/functions/returns/dynamic.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/functions/returns/dynamic.mdx new file mode 100644 index 000000000..844d5926a --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/functions/returns/dynamic.mdx @@ -0,0 +1,78 @@ +--- +page_title: Dynamic function return values +description: >- + Learn how to use dynamic function return value types with the Terraform + plugin framework. +--- + +# Dynamic function return values + + + +Static types should always be preferred over dynamic types, when possible. + +Developers creating a function with a dynamic return will need to have extensive knowledge of the [Terraform type system](/terraform/language/expressions/types) to understand how the value type returned can impact practitioner configuration. + +Refer to [Dynamic Data - Considerations](/terraform/plugin/framework/handling-data/dynamic-data#considerations) for more information. + + + +Dynamic function return can be **any** value type from function logic. Set values in function logic with the [framework dynamic type](/terraform/plugin/framework/handling-data/types/dynamic). + +## Function Definition + +Use the [`function.DynamicReturn` type](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/function#DynamicReturn) in the [function definition](/terraform/plugin/framework/functions/implementation#definition-method). + +In this example, a function definition includes a dynamic return: + +```go +func (f ExampleFunction) Definition(ctx context.Context, req function.DefinitionRequest, resp *function.DefinitionResponse) { + resp.Definition = function.Definition{ + // ... other Definition fields ... + Return: function.DynamicReturn{ + // ... potentially other DynamicReturn fields ... + }, + } +} +``` + +### Custom Types + +You may want to build your own data value and type implementations to allow your provider to combine validation and other behaviors into a reusable bundle. This helps avoid duplication and ensures consistency. These implementations use the `CustomType` field in the return type. + +Refer to [Custom Types](/terraform/plugin/framework/handling-data/types/custom) for further details on creating provider-defined types and values. + +### Documentation + +Return documentation is expected in the top-level function documentation. Refer to [function documentation](/terraform/plugin/framework/functions/documentation) for information about the `Summary`, `Description`, and `MarkdownDescription` fields available. + +## Setting Return Data + +The [function implementation](/terraform/plugin/framework/functions/implementation) documentation covers the general methods for setting function return data in function logic. + +When setting the value for this return: + +* If `CustomType` is set, use its associated value type. +* Otherwise, use the [framework dynamic type](/terraform/plugin/framework/handling-data/types/dynamic). + +In this example, a function defines a dynamic return and sets its value to a string: + +```go +func (f ExampleFunction) Definition(ctx context.Context, req function.DefinitionRequest, resp *function.DefinitionResponse) { + resp.Definition = function.Definition{ + // ... other Definition fields ... + Return: function.DynamicReturn{}, + } +} + +func (f ExampleFunction) Run(ctx context.Context, req function.RunRequest, resp *function.RunResponse) { + // ... other logic ... + + // hardcoded value for example brevity + result := types.DynamicValue(types.StringValue("hello world!")) + + resp.Error = function.ConcatFuncErrors(resp.Error, resp.Result.Set(ctx, &result)) +} +``` + +For more detail on working with dynamic values, see the [framework dynamic type](/terraform/plugin/framework/handling-data/types/dynamic) documentation. diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/functions/returns/float32.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/functions/returns/float32.mdx new file mode 100644 index 000000000..fbd0f48e5 --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/functions/returns/float32.mdx @@ -0,0 +1,72 @@ +--- +page_title: Float32 return values +description: >- + Learn how to use the 32-bit floating point function return value type with the + Terraform plugin framework. +--- + +# Float32 return values + + + +Use [Int32 Return](/terraform/plugin/framework/functions/returns/int32) for 32-bit integer numbers. Use [Number Return](/terraform/plugin/framework/functions/returns/number) for arbitrary precision numbers. + + + +Float32 function return expects a 32-bit floating point number value from function logic. Set values in function logic with the Go built-in `float32` type, Go built-in `*float32` type, or the [framework float32 type](/terraform/plugin/framework/handling-data/types/float32). + +## Function Definition + +Use the [`function.Float32Return` type](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/function#Float32Return) in the [function definition](/terraform/plugin/framework/functions/implementation#definition-method). + +In this example, a function definition includes a float32 return: + +```go +func (f ExampleFunction) Definition(ctx context.Context, req function.DefinitionRequest, resp *function.DefinitionResponse) { + resp.Definition = function.Definition{ + // ... other Definition fields ... + Return: function.Float32Return{ + // ... potentially other Float32Return fields ... + }, + } +} +``` + +### Custom Types + +You may want to build your own data value and type implementations to allow your provider to combine validation and other behaviors into a reusable bundle. This helps avoid duplication and ensures consistency. These implementations use the `CustomType` field in the return type. + +Refer to [Custom Types](/terraform/plugin/framework/handling-data/types/custom) for further details on creating provider-defined types and values. + +### Documentation + +Return documentation is expected in the top-level function documentation. Refer to [function documentation](/terraform/plugin/framework/functions/documentation) for information about the `Summary`, `Description`, and `MarkdownDescription` fields available. + +## Setting Return Data + +The [function implementation](/terraform/plugin/framework/functions/implementation) documentation covers the general methods for setting function return data in function logic. + +When setting the value for this return: + +* If `CustomType` is set, use its associated value type. +* Otherwise, use the Go built-in `float32` type, Go built-in `*float32` type, or [framework float32 type](/terraform/plugin/framework/handling-data/types/float32). + +In this example, a function defines a float32 return and sets its value: + +```go +func (f ExampleFunction) Definition(ctx context.Context, req function.DefinitionRequest, resp *function.DefinitionResponse) { + resp.Definition = function.Definition{ + // ... other Definition fields ... + Return: function.Float32Return{}, + } +} + +func (f ExampleFunction) Run(ctx context.Context, req function.RunRequest, resp *function.RunResponse) { + // ... other logic ... + + // hardcoded value for example brevity + var result float32 = 1.23 + + resp.Error = function.ConcatFuncErrors(resp.Error, resp.Result.Set(ctx, &result)) +} +``` diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/functions/returns/float64.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/functions/returns/float64.mdx new file mode 100644 index 000000000..2769c1d98 --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/functions/returns/float64.mdx @@ -0,0 +1,72 @@ +--- +page_title: Float64 return values +description: >- + Learn how to use the 64-bit floating point function return value type with the + Terraform plugin framework. +--- + +# Float64 return values + + + +Use [Int64 Return](/terraform/plugin/framework/functions/returns/int64) for 64-bit integer numbers. Use [Number Return](/terraform/plugin/framework/functions/returns/number) for arbitrary precision numbers. + + + +Float64 function return expects a 64-bit floating point number value from function logic. Set values in function logic with the Go built-in `float64` type, Go built-in `*float64` type, or the [framework float64 type](/terraform/plugin/framework/handling-data/types/float64). + +## Function Definition + +Use the [`function.Float64Return` type](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/function#Float64Return) in the [function definition](/terraform/plugin/framework/functions/implementation#definition-method). + +In this example, a function definition includes a float64 return: + +```go +func (f ExampleFunction) Definition(ctx context.Context, req function.DefinitionRequest, resp *function.DefinitionResponse) { + resp.Definition = function.Definition{ + // ... other Definition fields ... + Return: function.Float64Return{ + // ... potentially other Float64Return fields ... + }, + } +} +``` + +### Custom Types + +You may want to build your own data value and type implementations to allow your provider to combine validation and other behaviors into a reusable bundle. This helps avoid duplication and ensures consistency. These implementations use the `CustomType` field in the return type. + +Refer to [Custom Types](/terraform/plugin/framework/handling-data/types/custom) for further details on creating provider-defined types and values. + +### Documentation + +Return documentation is expected in the top-level function documentation. Refer to [function documentation](/terraform/plugin/framework/functions/documentation) for information about the `Summary`, `Description`, and `MarkdownDescription` fields available. + +## Setting Return Data + +The [function implementation](/terraform/plugin/framework/functions/implementation) documentation covers the general methods for setting function return data in function logic. + +When setting the value for this return: + +* If `CustomType` is set, use its associated value type. +* Otherwise, use the Go built-in `float64` type, Go built-in `*float64` type, or [framework float64 type](/terraform/plugin/framework/handling-data/types/float64). + +In this example, a function defines a float64 return and sets its value: + +```go +func (f ExampleFunction) Definition(ctx context.Context, req function.DefinitionRequest, resp *function.DefinitionResponse) { + resp.Definition = function.Definition{ + // ... other Definition fields ... + Return: function.Float64Return{}, + } +} + +func (f ExampleFunction) Run(ctx context.Context, req function.RunRequest, resp *function.RunResponse) { + // ... other logic ... + + // hardcoded value for example brevity + result := 1.23 + + resp.Error = function.ConcatFuncErrors(resp.Error, resp.Result.Set(ctx, &result)) +} +``` diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/functions/returns/index.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/functions/returns/index.mdx new file mode 100644 index 000000000..a81c5bc83 --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/functions/returns/index.mdx @@ -0,0 +1,60 @@ +--- +page_title: Function return values +description: >- + The Terraform plugin framework includes multiple built-in function return + value types and supports dynamic return values. A return describes the output + data in a function definition. +--- + +# Return values + +A return in a [function definition](/terraform/plugin/framework/functions/implementation#definition-method) describes the result data value from function logic. Every return type has an associated [value type](/terraform/plugin/framework/handling-data/types), although this data handling is simplified for function implementations over other provider concepts, such as resource implementations. + +## Available Return Types + +Function definitions support the following return types: + +- [Primitive](#primitive-return-types): Return that expects a single value, such as a boolean, number, or string. +- [Collection](#collection-return-types): Return that expects multiple values of a single element type, such as a list, map, or set. +- [Object](#object-return-type): Return that expects a structure of explicit attribute names. +- [Dynamic](#dynamic-return-type): Return that can be any value type. + +### Primitive Return Types + +Return types that expect a single data value, such as a boolean, number, or string. + +| Return Type | Use Case | +|----------------|----------| +| [Bool](/terraform/plugin/framework/functions/returns/bool) | Boolean true or false | +| [Float32](/terraform/plugin/framework/functions/returns/float32) | 32-bit floating point number | +| [Float64](/terraform/plugin/framework/functions/returns/float64) | 64-bit floating point number | +| [Int32](/terraform/plugin/framework/functions/returns/int32) | 32-bit integer number | +| [Int64](/terraform/plugin/framework/functions/returns/int64) | 64-bit integer number | +| [Number](/terraform/plugin/framework/functions/returns/number) | Arbitrary precision (generally over 64-bit, up to 512-bit) number | +| [String](/terraform/plugin/framework/functions/returns/string) | Collection of UTF-8 encoded characters | + +### Collection Return Types + +Return types that expect multiple values of a single element type, such as a list, map, or set. + +| Return Type | Use Case | +|----------------|----------| +| [List](/terraform/plugin/framework/functions/returns/list) | Ordered collection of single element type | +| [Map](/terraform/plugin/framework/functions/returns/map) | Mapping of arbitrary string keys to values of single element type | +| [Set](/terraform/plugin/framework/functions/returns/set) | Unordered, unique collection of single element type | + +### Object Return Type + +Return type that expects a structure of explicit attribute names. + +| Return Type | Use Case | +|----------------|----------| +| [Object](/terraform/plugin/framework/functions/returns/object) | Single structure mapping explicit attribute names | + +### Dynamic Return Type + +Return type that can be any value type, determined by the provider at runtime. + +| Return Type | Use Case | +|----------------|----------| +| [Dynamic](/terraform/plugin/framework/functions/returns/dynamic) | Return any value type of data, determined at runtime. | \ No newline at end of file diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/functions/returns/int32.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/functions/returns/int32.mdx new file mode 100644 index 000000000..b0c4b0c54 --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/functions/returns/int32.mdx @@ -0,0 +1,72 @@ +--- +page_title: Int32 return values +description: >- + Learn how to use the 32-bit integer function return value type with the + Terraform plugin framework. +--- + +# Int32 return values + + + +Use [Float32 Return](/terraform/plugin/framework/functions/returns/float32) for 32-bit floating point numbers. Use [Number Return](/terraform/plugin/framework/functions/returns/number) for arbitrary precision numbers. + + + +Int32 function return expects a 32-bit integer number value from function logic. Set values in function logic with the Go built-in `int32` type, Go built-in `*int32` type, or the [framework int32 type](/terraform/plugin/framework/handling-data/types/int32). + +## Function Definition + +Use the [`function.Int32Return` type](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/function#Int32Return) in the [function definition](/terraform/plugin/framework/functions/implementation#definition-method). + +In this example, a function definition includes a int32 return: + +```go +func (f ExampleFunction) Definition(ctx context.Context, req function.DefinitionRequest, resp *function.DefinitionResponse) { + resp.Definition = function.Definition{ + // ... other Definition fields ... + Return: function.Int32Return{ + // ... potentially other Int32Return fields ... + }, + } +} +``` + +### Custom Types + +You may want to build your own data value and type implementations to allow your provider to combine validation and other behaviors into a reusable bundle. This helps avoid duplication and ensures consistency. These implementations use the `CustomType` field in the return type. + +Refer to [Custom Types](/terraform/plugin/framework/handling-data/types/custom) for further details on creating provider-defined types and values. + +### Documentation + +Return documentation is expected in the top-level function documentation. Refer to [function documentation](/terraform/plugin/framework/functions/documentation) for information about the `Summary`, `Description`, and `MarkdownDescription` fields available. + +## Setting Return Data + +The [function implementation](/terraform/plugin/framework/functions/implementation) documentation covers the general methods for setting function return data in function logic. + +When setting the value for this return: + +* If `CustomType` is set, use its associated value type. +* Otherwise, use the Go built-in `int32` type, Go built-in `*int32` type, or [framework int32 type](/terraform/plugin/framework/handling-data/types/int32). + +In this example, a function defines a int32 return and sets its value: + +```go +func (f ExampleFunction) Definition(ctx context.Context, req function.DefinitionRequest, resp *function.DefinitionResponse) { + resp.Definition = function.Definition{ + // ... other Definition fields ... + Return: function.Int32Return{}, + } +} + +func (f ExampleFunction) Run(ctx context.Context, req function.RunRequest, resp *function.RunResponse) { + // ... other logic ... + + // hardcoded value for example brevity + result := 123 + + resp.Error = function.ConcatFuncErrors(resp.Error, resp.Result.Set(ctx, &result)) +} +``` diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/functions/returns/int64.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/functions/returns/int64.mdx new file mode 100644 index 000000000..a134eda66 --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/functions/returns/int64.mdx @@ -0,0 +1,72 @@ +--- +page_title: Int64 return values +description: >- + Learn how to use the 64-bit integer function return value type with the + Terraform plugin framework. +--- + +# Int64 return values + + + +Use [Float64 Return](/terraform/plugin/framework/functions/returns/float64) for 64-bit floating point numbers. Use [Number Return](/terraform/plugin/framework/functions/returns/number) for arbitrary precision numbers. + + + +Int64 function return expects a 64-bit integer number value from function logic. Set values in function logic with the Go built-in `int64` type, Go built-in `*int64` type, or the [framework int64 type](/terraform/plugin/framework/handling-data/types/int64). + +## Function Definition + +Use the [`function.Int64Return` type](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/function#Int64Return) in the [function definition](/terraform/plugin/framework/functions/implementation#definition-method). + +In this example, a function definition includes a int64 return: + +```go +func (f ExampleFunction) Definition(ctx context.Context, req function.DefinitionRequest, resp *function.DefinitionResponse) { + resp.Definition = function.Definition{ + // ... other Definition fields ... + Return: function.Int64Return{ + // ... potentially other Int64Return fields ... + }, + } +} +``` + +### Custom Types + +You may want to build your own data value and type implementations to allow your provider to combine validation and other behaviors into a reusable bundle. This helps avoid duplication and ensures consistency. These implementations use the `CustomType` field in the return type. + +Refer to [Custom Types](/terraform/plugin/framework/handling-data/types/custom) for further details on creating provider-defined types and values. + +### Documentation + +Return documentation is expected in the top-level function documentation. Refer to [function documentation](/terraform/plugin/framework/functions/documentation) for information about the `Summary`, `Description`, and `MarkdownDescription` fields available. + +## Setting Return Data + +The [function implementation](/terraform/plugin/framework/functions/implementation) documentation covers the general methods for setting function return data in function logic. + +When setting the value for this return: + +* If `CustomType` is set, use its associated value type. +* Otherwise, use the Go built-in `int64` type, Go built-in `*int64` type, or [framework int64 type](/terraform/plugin/framework/handling-data/types/int64). + +In this example, a function defines a int64 return and sets its value: + +```go +func (f ExampleFunction) Definition(ctx context.Context, req function.DefinitionRequest, resp *function.DefinitionResponse) { + resp.Definition = function.Definition{ + // ... other Definition fields ... + Return: function.Int64Return{}, + } +} + +func (f ExampleFunction) Run(ctx context.Context, req function.RunRequest, resp *function.RunResponse) { + // ... other logic ... + + // hardcoded value for example brevity + result := 123 + + resp.Error = function.ConcatFuncErrors(resp.Error, resp.Result.Set(ctx, &result)) +} +``` diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/functions/returns/list.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/functions/returns/list.mdx new file mode 100644 index 000000000..39cc953c7 --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/functions/returns/list.mdx @@ -0,0 +1,71 @@ +--- +page_title: List return values +description: >- + Learn how to use the list function return value type with the + Terraform plugin framework. +--- + +# List return values + +List function return expects an ordered collection of single element type value from function logic. Set values in function logic with a Go slice of an appropriate type to match the element type `[]T` or the [framework list type](/terraform/plugin/framework/handling-data/types/list). + +## Function Definition + +Use the [`function.ListReturn` type](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/function#ListReturn) in the [function definition](/terraform/plugin/framework/functions/implementation#definition-method). + +The `ElementType` field must be defined, which represents the single [framework value type](/terraform/plugin/framework/handling-data/types) of every element of the list. An element type may itself contain further collection or object types, if necessary. + +In this example, a function definition includes a list of string return: + +```go +func (f ExampleFunction) Definition(ctx context.Context, req function.DefinitionRequest, resp *function.DefinitionResponse) { + resp.Definition = function.Definition{ + // ... other Definition fields ... + Return: function.ListReturn{ + ElementType: types.StringType, + // ... potentially other ListReturn fields ... + }, + } +} +``` + +### Custom Types + +You may want to build your own data value and type implementations to allow your provider to combine validation and other behaviors into a reusable bundle. This helps avoid duplication and ensures consistency. These implementations use the `CustomType` field in the return type. + +Refer to [Custom Types](/terraform/plugin/framework/handling-data/types/custom) for further details on creating provider-defined types and values. + +### Documentation + +Return documentation is expected in the top-level function documentation. Refer to [function documentation](/terraform/plugin/framework/functions/documentation) for information about the `Summary`, `Description`, and `MarkdownDescription` fields available. + +## Setting Return Data + +The [function implementation](/terraform/plugin/framework/functions/implementation) documentation covers the general methods for setting function return data in function logic. + +When setting the value for this return: + +* If `CustomType` is set, use its associated value type. +* Otherwise, use a Go slice of an appropriate type to match the element type `[]T` or [framework list type](/terraform/plugin/framework/handling-data/types/list). + +In this example, a function defines a list of string return and sets its value: + +```go +func (f ExampleFunction) Definition(ctx context.Context, req function.DefinitionRequest, resp *function.DefinitionResponse) { + resp.Definition = function.Definition{ + // ... other Definition fields ... + Return: function.ListReturn{ + ElementType: types.StringType, + }, + } +} + +func (f ExampleFunction) Run(ctx context.Context, req function.RunRequest, resp *function.RunResponse) { + // ... other logic ... + + // hardcoded value for example brevity + result := []string{"one", "two"} + + resp.Error = function.ConcatFuncErrors(resp.Error, resp.Result.Set(ctx, &result)) +} +``` diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/functions/returns/map.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/functions/returns/map.mdx new file mode 100644 index 000000000..3ae20085a --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/functions/returns/map.mdx @@ -0,0 +1,74 @@ +--- +page_title: Map return values +description: >- + Learn how to use the map function return value type with the + Terraform plugin framework. +--- + +# Map return values + +Map function return expects a mapping of arbitrary string keys to values of single element type from function logic. Set values in function logic with a Go map of string keys to values of an appropriate type to match the element type `map[string]T` or the [framework map type](/terraform/plugin/framework/handling-data/types/map). + +## Function Definition + +Use the [`function.MapReturn` type](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/function#MapReturn) in the [function definition](/terraform/plugin/framework/functions/implementation#definition-method). + +The `ElementType` field must be defined, which represents the single [framework value type](/terraform/plugin/framework/handling-data/types) of every element of the map. An element type may itself contain further collection or object types, if necessary. + +In this example, a function definition includes a map of string return: + +```go +func (f ExampleFunction) Definition(ctx context.Context, req function.DefinitionRequest, resp *function.DefinitionResponse) { + resp.Definition = function.Definition{ + // ... other Definition fields ... + Return: function.MapReturn{ + ElementType: types.StringType, + // ... potentially other MapReturn fields ... + }, + } +} +``` + +### Custom Types + +You may want to build your own data value and type implementations to allow your provider to combine validation and other behaviors into a reusable bundle. This helps avoid duplication and ensures consistency. These implementations use the `CustomType` field in the return type. + +Refer to [Custom Types](/terraform/plugin/framework/handling-data/types/custom) for further details on creating provider-defined types and values. + +### Documentation + +Return documentation is expected in the top-level function documentation. Refer to [function documentation](/terraform/plugin/framework/functions/documentation) for information about the `Summary`, `Description`, and `MarkdownDescription` fields available. + +## Setting Return Data + +The [function implementation](/terraform/plugin/framework/functions/implementation) documentation covers the general methods for setting function return data in function logic. + +When setting the value for this return: + +* If `CustomType` is set, use its associated value type. +* Otherwise, use a Go map of string keys to values of an appropriate type to match the element type `map[string]T` or [framework map type](/terraform/plugin/framework/handling-data/types/map). + +In this example, a function defines a map of string return and sets its value: + +```go +func (f ExampleFunction) Definition(ctx context.Context, req function.DefinitionRequest, resp *function.DefinitionResponse) { + resp.Definition = function.Definition{ + // ... other Definition fields ... + Return: function.MapReturn{ + ElementType: types.StringType, + }, + } +} + +func (f ExampleFunction) Run(ctx context.Context, req function.RunRequest, resp *function.RunResponse) { + // ... other logic ... + + // hardcoded value for example brevity + result := map[string]string{ + "key1": "value1", + "key2": "value2", + } + + resp.Error = function.ConcatFuncErrors(resp.Error, resp.Result.Set(ctx, &result)) +} +``` diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/functions/returns/number.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/functions/returns/number.mdx new file mode 100644 index 000000000..74e6a2c01 --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/functions/returns/number.mdx @@ -0,0 +1,72 @@ +--- +page_title: Number return values +description: >- + Learn how to use the arbitrary precision number function return value type + with the Terraform plugin framework. +--- + +# Number return values + + + +Use [Float64 Return](/terraform/plugin/framework/functions/returns/float64) for 64-bit floating point numbers. Use [Int64 Return](/terraform/plugin/framework/functions/returns/int64) for 64-bit integer numbers. + + + +Number function return expects an arbitrary precision (generally over 64-bit, up to 512-bit) number value from function logic. Set values in function logic with the Go built-in `*big.Float` type or the [framework number type](/terraform/plugin/framework/handling-data/types/number). + +## Function Definition + +Use the [`function.NumberReturn` type](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/function#NumberReturn) in the [function definition](/terraform/plugin/framework/functions/implementation#definition-method). + +In this example, a function definition includes a number return: + +```go +func (f ExampleFunction) Definition(ctx context.Context, req function.DefinitionRequest, resp *function.DefinitionResponse) { + resp.Definition = function.Definition{ + // ... other Definition fields ... + Return: function.NumberReturn{ + // ... potentially other NumberReturn fields ... + }, + } +} +``` + +### Custom Types + +You may want to build your own data value and type implementations to allow your provider to combine validation and other behaviors into a reusable bundle. This helps avoid duplication and ensures consistency. These implementations use the `CustomType` field in the return type. + +Refer to [Custom Types](/terraform/plugin/framework/handling-data/types/custom) for further details on creating provider-defined types and values. + +### Documentation + +Return documentation is expected in the top-level function documentation. Refer to [function documentation](/terraform/plugin/framework/functions/documentation) for information about the `Summary`, `Description`, and `MarkdownDescription` fields available. + +## Setting Return Data + +The [function implementation](/terraform/plugin/framework/functions/implementation) documentation covers the general methods for setting function return data in function logic. + +When setting the value for this return: + +* If `CustomType` is set, use its associated value type. +* Otherwise, use the Go built-in `*big.Float` type or [framework number type](/terraform/plugin/framework/handling-data/types/number). + +In this example, a function defines a number return and sets its value: + +```go +func (f ExampleFunction) Definition(ctx context.Context, req function.DefinitionRequest, resp *function.DefinitionResponse) { + resp.Definition = function.Definition{ + // ... other Definition fields ... + Return: function.NumberReturn{}, + } +} + +func (f ExampleFunction) Run(ctx context.Context, req function.RunRequest, resp *function.RunResponse) { + // ... other logic ... + + // hardcoded value for example brevity + result := big.NewFloat(1.23) + + resp.Error = function.ConcatFuncErrors(resp.Error, resp.Result.Set(ctx, &result)) +} +``` diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/functions/returns/object.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/functions/returns/object.mdx new file mode 100644 index 000000000..7f4a354a3 --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/functions/returns/object.mdx @@ -0,0 +1,83 @@ +--- +page_title: Object return values +description: >- + Learn how to use the object function return value type with the Terraform + plugin framework. +--- + +# Object return values + +Object function return expects a single structure mapping explicit attribute names to type definitions from function logic. Set values in function logic with a Go structure type annotated with `tfsdk` field tags or the [framework map type](/terraform/plugin/framework/handling-data/types/map). + +## Function Definition + +Use the [`function.ObjectReturn` type](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/function#ObjectReturn) in the [function definition](/terraform/plugin/framework/functions/implementation#definition-method). + +The `AttributeTypes` field must be defined, which represents a mapping of attribute names to [framework value types](/terraform/plugin/framework/handling-data/types). An attribute type may itself contain further collection or object types, if necessary. + +In this example, a function definition includes an object return: + +```go +func (f ExampleFunction) Definition(ctx context.Context, req function.DefinitionRequest, resp *function.DefinitionResponse) { + resp.Definition = function.Definition{ + // ... other Definition fields ... + Return: function.ObjectReturn{ + AttributeTypes: map[string]attr.Type{ + "attr1": types.StringType, + "attr2": types.Int64Type, + }, + // ... potentially other ObjectReturn fields ... + }, + } +} +``` + +### Custom Types + +You may want to build your own data value and type implementations to allow your provider to combine validation and other behaviors into a reusable bundle. This helps avoid duplication and ensures consistency. These implementations use the `CustomType` field in the return type. + +Refer to [Custom Types](/terraform/plugin/framework/handling-data/types/custom) for further details on creating provider-defined types and values. + +### Documentation + +Return documentation is expected in the top-level function documentation. Refer to [function documentation](/terraform/plugin/framework/functions/documentation) for information about the `Summary`, `Description`, and `MarkdownDescription` fields available. + +## Setting Return Data + +The [function implementation](/terraform/plugin/framework/functions/implementation) documentation covers the general methods for setting function return data in function logic. + +When setting the value for this return: + +* If `CustomType` is set, use its associated value type. +* Otherwise, use a Go structure type annotated with `tfsdk` field tags or [framework map type](/terraform/plugin/framework/handling-data/types/map). + +In this example, a function defines a map of string return and sets its value: + +```go +func (f ExampleFunction) Definition(ctx context.Context, req function.DefinitionRequest, resp *function.DefinitionResponse) { + resp.Definition = function.Definition{ + // ... other Definition fields ... + Return: function.ObjectReturn{ + AttributeTypes: map[string]attr.Type{ + "attr1": types.StringType, + "attr2": types.Int64Type, + }, + }, + } +} + +func (f ExampleFunction) Run(ctx context.Context, req function.RunRequest, resp *function.RunResponse) { + // ... other logic ... + + // hardcoded structure type and value for example brevity + result := struct{ + Attr1 string `tfsdk:"attr1"` + Attr2 int64 `tfsdk:"attr2"` + }{ + Attr1: "value1", + Attr2: 123, + } + + resp.Error = function.ConcatFuncErrors(resp.Error, resp.Result.Set(ctx, &result)) +} +``` diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/functions/returns/set.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/functions/returns/set.mdx new file mode 100644 index 000000000..1c32dc777 --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/functions/returns/set.mdx @@ -0,0 +1,71 @@ +--- +page_title: Set return values +description: >- + Learn how to use the set function return value type with the Terraform + plugin framework. +--- + +# Set return values + +Set function return expects an unordered, unique collection of single element type value from function logic. Set values in function logic with a Go slice of an appropriate type to match the element type `[]T` or the [framework set type](/terraform/plugin/framework/handling-data/types/set). + +## Function Definition + +Use the [`function.SetReturn` type](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/function#SetReturn) in the [function definition](/terraform/plugin/framework/functions/implementation#definition-method). + +The `ElementType` field must be defined, which represents the single [framework value type](/terraform/plugin/framework/handling-data/types) of every element of the set. An element type may itself contain further collection or object types, if necessary. + +In this example, a function definition includes a set of string return: + +```go +func (f ExampleFunction) Definition(ctx context.Context, req function.DefinitionRequest, resp *function.DefinitionResponse) { + resp.Definition = function.Definition{ + // ... other Definition fields ... + Return: function.SetReturn{ + ElementType: types.StringType, + // ... potentially other SetReturn fields ... + }, + } +} +``` + +### Custom Types + +You may want to build your own data value and type implementations to allow your provider to combine validation and other behaviors into a reusable bundle. This helps avoid duplication and ensures consistency. These implementations use the `CustomType` field in the return type. + +Refer to [Custom Types](/terraform/plugin/framework/handling-data/types/custom) for further details on creating provider-defined types and values. + +### Documentation + +Return documentation is expected in the top-level function documentation. Refer to [function documentation](/terraform/plugin/framework/functions/documentation) for information about the `Summary`, `Description`, and `MarkdownDescription` fields available. + +## Setting Return Data + +The [function implementation](/terraform/plugin/framework/functions/implementation) documentation covers the general methods for setting function return data in function logic. + +When setting the value for this return: + +* If `CustomType` is set, use its associated value type. +* Otherwise, use a Go slice of an appropriate type to match the element type `[]T` or [framework set type](/terraform/plugin/framework/handling-data/types/set). + +In this example, a function defines a set of string return and sets its value: + +```go +func (f ExampleFunction) Definition(ctx context.Context, req function.DefinitionRequest, resp *function.DefinitionResponse) { + resp.Definition = function.Definition{ + // ... other Definition fields ... + Return: function.SetReturn{ + ElementType: types.StringType, + }, + } +} + +func (f ExampleFunction) Run(ctx context.Context, req function.RunRequest, resp *function.RunResponse) { + // ... other logic ... + + // hardcoded value for example brevity + result := []string{"one", "two"} + + resp.Error = function.ConcatFuncErrors(resp.Error, resp.Result.Set(ctx, &result)) +} +``` diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/functions/returns/string.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/functions/returns/string.mdx new file mode 100644 index 000000000..d80f9fa5b --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/functions/returns/string.mdx @@ -0,0 +1,66 @@ +--- +page_title: String return values +description: >- + Learn how to use the string function return value type with the Terraform + plugin framework. +--- + +# String return values + +String function return expects a collection of UTF-8 encoded bytes from function logic. Set values in function logic with the Go built-in `string` type, Go built-in `*string` type, or the [framework string type](/terraform/plugin/framework/handling-data/types/string). + +## Function Definition + +Use the [`function.StringReturn` type](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/function#StringReturn) in the [function definition](/terraform/plugin/framework/functions/implementation#definition-method). + +In this example, a function definition includes a string return: + +```go +func (f ExampleFunction) Definition(ctx context.Context, req function.DefinitionRequest, resp *function.DefinitionResponse) { + resp.Definition = function.Definition{ + // ... other Definition fields ... + Return: function.StringReturn{ + // ... potentially other StringReturn fields ... + }, + } +} +``` + +### Custom Types + +You may want to build your own data value and type implementations to allow your provider to combine validation and other behaviors into a reusable bundle. This helps avoid duplication and ensures consistency. These implementations use the `CustomType` field in the return type. + +Refer to [Custom Types](/terraform/plugin/framework/handling-data/types/custom) for further details on creating provider-defined types and values. + +### Documentation + +Return documentation is expected in the top-level function documentation. Refer to [function documentation](/terraform/plugin/framework/functions/documentation) for information about the `Summary`, `Description`, and `MarkdownDescription` fields available. + +## Setting Return Data + +The [function implementation](/terraform/plugin/framework/functions/implementation) documentation covers the general methods for setting function return data in function logic. + +When setting the value for this return: + +* If `CustomType` is set, use its associated value type. +* Otherwise, use the Go built-in `string` type, Go built-in `*string` type, or [framework string type](/terraform/plugin/framework/handling-data/types/string). + +In this example, a function defines a string return and sets its value: + +```go +func (f ExampleFunction) Definition(ctx context.Context, req function.DefinitionRequest, resp *function.DefinitionResponse) { + resp.Definition = function.Definition{ + // ... other Definition fields ... + Return: function.StringReturn{}, + } +} + +func (f ExampleFunction) Run(ctx context.Context, req function.RunRequest, resp *function.RunResponse) { + // ... other logic ... + + // hardcoded value for example brevity + result := "example" + + resp.Error = function.ConcatFuncErrors(resp.Error, resp.Result.Set(ctx, &result)) +} +``` diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/functions/testing.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/functions/testing.mdx new file mode 100644 index 000000000..dad993547 --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/functions/testing.mdx @@ -0,0 +1,246 @@ +--- +page_title: Testing functions +description: >- + Learn how to implement tests for provider-defined functions with the Terraform + plugin framework. +--- + +# Testing functions + +When a function is [implemented](/terraform/plugin/framework/functions/implementation), ensure the function behaves as expected. Follow [recommendations](#recommendations) to cover how practitioner configurations may call the function. + +There are two methodologies for testing provider-defined functions: + +* [Acceptance Testing](#acceptance-testing): Verify implementation using real Terraform configurations and commands. +* [Unit Testing](#unit-testing): Verify implementation using with Terraform and framework implementation details. + +Similar to other provider concepts, many provider developers prefer acceptance testing over unit testing. Acceptance testing guarantees the function implementation works exactly as expected in real world use cases without trying to determine Terraform or framework implementation details. Unit testing details are provided, however, for function implementations which warrant a broad amount of input value testing, such as generic data handling functions or to perform [fuzzing](https://go.dev/security/fuzz/). + +Testing examples on this page are dependent on the example [echo function implementation](/terraform/plugin/framework/functions/implementation). + +## Recommendations + +Testing a provider-defined function should ensure at least the following behaviors are covered: + +* Known values return the expected results. +* For any list, map, object, and set parameters, null values for collection elements or object attributes. The `AllowNullValue` parameter setting does not affect Terraform sending these types of null values. +* If any parameters enable `AllowNullValue`, null values for those arguments. +* If any parameters enable `AllowUnknownValues`, unknown values for those arguments. +* Any errors, such as argument validation errors. + +## Acceptance Testing + +Use the [plugin testing Go module](/terraform/plugin/testing) to implement real world testing with Terraform configurations and commands. The documentation for that Go module covers many more available testing features, however this section example gives a high level overview of how to start writing these tests. + +In this example, a `echo_function_test.go` file is created: + +```go +package provider_test + +import ( + "testing" + + "example.com/terraform-provider-example/internal/provider" + "github.com/hashicorp/terraform-plugin-framework/providerserver" + "github.com/hashicorp/terraform-plugin-go/tfprotov6" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/knownvalue" + "github.com/hashicorp/terraform-plugin-testing/statecheck" + "github.com/hashicorp/terraform-plugin-testing/tfversion" +) + +func TestEchoFunction_Valid(t *testing.T) { + t.Parallel() + + resource.UnitTest(t, resource.TestCase{ + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.SkipBelow(tfversion.Version1_8_0), + }, + ProtoV6ProviderFactories: map[string]func() (tfprotov6.ProviderServer, error) { + "example": providerserver.NewProtocol6WithError(provider.New()), + }, + Steps: []resource.TestStep{ + { + Config: ` + output "test" { + value = provider::example::echo("test-value") + }`, + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectKnownOutputValue("test", knownvalue.StringExact("test-value")), + }, + }, + }, + }) +} + +// The example implementation does not return any errors, however +// this acceptance test verifies how the function should behave if it did. +func TestEchoFunction_Invalid(t *testing.T) { + t.Parallel() + + resource.UnitTest(t, resource.TestCase{ + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.SkipBelow(tfversion.Version1_8_0), + }, + ProtoV6ProviderFactories: map[string]func() (tfprotov6.ProviderServer, error) { + "example": providerserver.NewProtocol6WithError(provider.New()), + }, + Steps: []resource.TestStep{ + { + Config: ` + output "test" { + value = provider::example::echo("invalid") + }`, + ExpectError: regexp.MustCompile(`error summary`), + }, + }, + }) +} + +// The example implementation does not enable AllowNullValue, however this +// acceptance test shows how to verify the behavior. +func TestEchoFunction_Null(t *testing.T) { + t.Parallel() + + resource.UnitTest(t, resource.TestCase{ + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.SkipBelow(tfversion.Version1_8_0), + }, + ProtoV6ProviderFactories: map[string]func() (tfprotov6.ProviderServer, error) { + "example": providerserver.NewProtocol6WithError(provider.New()), + }, + Steps: []resource.TestStep{ + { + Config: ` + output "test" { + value = provider::example::echo(null) + }`, + ExpectError: regexp.MustCompile(`Invalid Function Call`), + }, + }, + }) +} + +// The example implementation does not enable AllowUnknownValues, however this +// acceptance test shows how to verify the behavior. +func TestEchoFunction_Unknown(t *testing.T) { + resource.UnitTest(t, resource.TestCase{ + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.SkipBelow(tfversion.Version1_8_0), + }, + ProtoV6ProviderFactories: map[string]func() (tfprotov6.ProviderServer, error) { + "example": providerserver.NewProtocol6WithError(provider.New()), + }, + Steps: []resource.TestStep{ + { + Config: ` + terraform_data "test" { + input = "test-value" + } + + output "test" { + value = provider::example::echo(terraform_data.test.output) + }`, + ConfigPlanChecks: resource.ConfigPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectUnknownOutputValue("test"), + }, + }, + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectKnownOutputValue("test", knownvalue.StringExact("test-value")), + }, + }, + }, + }) +} +``` + +## Unit Testing + +Use the [`function.NewArgumentsData()` function](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/functions#NewArgumentsData) and [`function.NewResultData()` function](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/functions#NewResultData) as part of implementing a [Go test](https://go.dev/doc/tutorial/add-a-test). + +In this example, a `echo_function_test.go` file is created: + +```go +package provider_test + +import ( + "context" + "testing" + + "example.com/terraform-provider-example/internal/provider" + "github.com/google/go-cmp/cmp" + "github.com/hashicorp/terraform-plugin-framework/function" + "github.com/hashicorp/terraform-plugin-framework/types" +) + +func TestEchoFunctionRun(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + request function.RunRequest + expected function.RunResponse + }{ + // The example implementation uses the Go built-in string type, however + // if AllowNullValue was enabled and *string or types.String was used, + // this test case shows how the function would be expected to behave. + "null": { + request: function.RunRequest{ + Arguments: function.NewArgumentsData([]attr.Value{types.StringNull()}), + }, + expected: function.RunResponse{ + Result: function.NewResultData(types.StringNull()), + }, + }, + // The example implementation uses the Go built-in string type, however + // if AllowUnknownValues was enabled and types.String was used, + // this test case shows how the function would be expected to behave. + "unknown": { + request: function.RunRequest{ + Arguments: function.NewArgumentsData([]attr.Value{types.StringUnknown()}), + }, + expected: function.RunResponse{ + Result: function.NewResultData(types.StringUnknown()), + }, + }, + "value-valid": { + request: function.RunRequest{ + Arguments: function.NewArgumentsData([]attr.Value{types.StringValue("test-value")}), + }, + expected: function.RunResponse{ + Result: function.NewResultData(types.StringValue("test-value")), + }, + }, + // The example implementation does not return an error, however + // this test case shows how the function would be expected to behave if + // it did. + "value-invalid": { + request: function.RunRequest{ + Arguments: function.NewArgumentsData([]attr.Value{types.StringValue("")}), + }, + expected: function.RunResponse{ + Error: function.NewArgumentFuncError(0, "error summary: error detail"), + Result: function.NewResultData(types.StringUnknown()), + }, + }, + } + + for name, testCase := range testCases { + name, testCase := name, testCase + + t.Run(name, func(t *testing.T) { + t.Parallel() + + got := function.RunResponse{ + Result: function.NewResultData(types.StringUnknown()), + } + + provider.EchoFunction{}.Run(context.Background(), testCase.request, &got) + + if diff := cmp.Diff(got, testCase.expected); diff != "" { + t.Errorf("unexpected difference: %s", diff) + } + }) + } +} +``` diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/getting-started/code-walkthrough.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/getting-started/code-walkthrough.mdx new file mode 100644 index 000000000..8aecf5506 --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/getting-started/code-walkthrough.mdx @@ -0,0 +1,384 @@ +--- +page_title: Provider code walkthrough +description: >- + The Terraform plugin framework is an SDK that you can use to implement + Terraform providers. Learn how the framework can help you create a provider + by exploring its main components and libraries. +--- + +# Provider code walkthrough + +[Terraform providers](/terraform/language/providers) let Terraform communicate with third parties, such as cloud providers, SaaS providers, and other APIs. Terraform and Terraform providers use gRPC to communicate. Terraform operates as a gRPC client and providers operate as gRPC servers. + +Each provider defines resources that let Terraform manage infrastructure objects and data sources that let Terraform read data. Terraform practitioners then write configuration to define resources, such as compute storage or networking resources. Terraform then communicates this configuration to the provider, and the provider creates the infrastructure. + +This example provider shows the relationship between the required provider components. The resources and data sources in a typical provider interact with a cloud provider through an API, but the example only stores values in state. + +## Core Provider Components + +A Terraform plugin provider requires at least the following components: + +- [provider server](#provider-server) +- [provider](#provider) +- [resource](#resource) or [data source](#data-source) + +The provider wraps the resource(s) and/or data source(s), and can be used to configure a client which communicates with a 3rd party service via an API. +Resources are used to manage infrastructure objects. +Data sources are used to read infrastructure objects. + +## Provider Server + +Each provider must implement a gRPC server that supports Terraform-specific connection and handshake handling on startup. A [provider server](/terraform/plugin/framework/provider-servers) is required in order for a Terraform provider to: + +* expose resources that can be managed by Terraform core. +* expose data sources that can be read by Terraform core. + +The `main()` function is used for defining a provider server. + +The `provider.New()` returns a function which returns a type that satisfies the `provider.Provider` interface. The `provider.Provider` interface defines functions for obtaining the resource(s) and/or data source(s) from a provider. + +```go +package main + +import ( + "context" + "flag" + "log" + + "github.com/hashicorp/terraform-plugin-framework/providerserver" + + "github.com/example_namespace/terraform-provider-example/internal/provider" +) + +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{ + Address: "registry.terraform.io/example_namespace/example", + Debug: debug, + } + + err := providerserver.Serve(context.Background(), provider.New(), opts) + + if err != nil { + log.Fatal(err.Error()) + } +} +``` + +Refer to [Provider Servers](/terraform/plugin/framework/provider-servers) for more details. + +## Provider + +The provider wraps resources and data sources which are typically used for interacting with cloud providers, SaaS providers, or other APIs. + +In this example the provider wraps a resource and a data source which simply interact with Terraform state. Refer to the [tutorial](/terraform/tutorials/providers-plugin-framework/providers-plugin-framework-provider-configure) for an example of provider configuration that configures an API client. + +`New()` returns a function which returns a type that satisfies the `provider.Provider` interface. The `New()` function is called by the [provider server](#provider-server) to obtain the provider. + +The `exampleProvider` struct implements the `provider.Provider` interface. This interface defines the following functions: + +- [`Schema`](/terraform/plugin/framework/handling-data/schemas): This function returns a provider `schema.Schema` struct that defines the provider schema. Schemas specify the constraints of Terraform configuration blocks. They define what fields a provider, resource, or data source configuration block has, and give Terraform metadata about those fields. +- [`Configure`](/terraform/plugin/framework/providers#configure-method): This function lets you configure provider-level data or clients. These configuration values may be from the practitioner Terraform configuration as defined by the schema, environment variables, or other means such as reading vendor-specific configuration files. +- [`Resources`](/terraform/plugin/framework/providers#resources): This function returns a slice of functions that return types that implement the `resource.Resource` interface. Resources let Terraform manage infrastructure objects, such as a compute instance, an access policy, or disk. +- [`Data Sources`](/terraform/plugin/framework/providers#datasources): This function returns a slice of functions that return types which implement the `datasource.DataSource` interface. Data sources let Terraform reference external data. For example a database instance. + +The `exampleProvider` struct also implements the `provider.ProviderWithMetadata` interface which defines the `Metadata` function. The `Metadata` function returns metadata for the provider such as a `TypeName` and `Version`. The `TypeName` is used as a prefix within a provider by for naming [resources](#resource) and [data sources](#data-source). + +```go +package provider + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-framework/datasource" + "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 = (*exampleProvider)(nil) +var _ provider.ProviderWithMetadata = (*exampleProvider)(nil) + +type exampleProvider struct{} + +func New() func() provider.Provider { + return func() provider.Provider { + return &exampleProvider{} + } +} + +func (p *exampleProvider) Configure(ctx context.Context, req provider.ConfigureRequest, resp *provider.ConfigureResponse) { +} + +func (p *exampleProvider) Metadata(ctx context.Context, req provider.MetadataRequest, resp *provider.MetadataResponse) { + resp.TypeName = "example" +} + +func (p *exampleProvider) DataSources(ctx context.Context) []func() datasource.DataSource { + return []func() datasource.DataSource{ + NewDataSource, + } +} + +func (p *exampleProvider) Resources(ctx context.Context) []func() resource.Resource { + return []func() resource.Resource{ + NewResource, + } +} + +func (p *exampleProvider) Schema(ctx context.Context, req provider.SchemaRequest, resp *provider.SchemaResponse) { +} +``` + +Refer to [Providers](/terraform/plugin/framework/providers) for more details and configuration examples. + +## Resource + +A resource is typically used to manage infrastructure objects such as virtual networks and compute instances. + +In this example the resource simply interacts with Terraform state. + +`NewResource()` returns a function which returns a type that satisfies the `resource.Resource` interface. The provider calls the `NewResource()` function within `provider.Resources` to obtain an instance of the resource. + +The `exampleResource` struct implements the `resource.Resource` interface. This interface defines the following functions: + +- [`Metadata`](/terraform/plugin/framework/resources#metadata-method): This function returns the full name (`TypeName`) of the resource. The full name is used in [Terraform configuration](#resource-configuration) as `resource `. +- [`Schema`](/terraform/plugin/framework/handling-data/schemas): This function returns a resource `schema.Schema` struct that defines the resource schema. The schema specifies the constraints of the resource Terraform configuration block. It defines what fields a resource configuration block has, and gives Terraform metadata about those fields. For instance, defining whether a field is required. +- [`Create`](/terraform/plugin/framework/resources/create): This function lets the provider create a new resource of this type. +- [`Read`](/terraform/plugin/framework/resources/read): This function lets the provider read resource values in order to update state. +- [`Update`](/terraform/plugin/framework/resources/update): This function lets the provider update the resource and state. +- [`Delete`](/terraform/plugin/framework/resources/delete): This function lets the provider delete the resource. + +```go +package provider + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-log/tflog" +) + +var _ resource.Resource = (*exampleResource)(nil) + +type exampleResource struct { + provider exampleProvider +} + +func NewResource() resource.Resource { + return &exampleResource{} +} + +func (e *exampleResource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_resource" +} + +func (e *exampleResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "configurable_attribute": schema.StringAttribute{ + Optional: true, + }, + "id": schema.StringAttribute{ + Computed: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + }, + } +} + +type exampleResourceData struct { + ConfigurableAttribute types.String `tfsdk:"configurable_attribute"` + Id types.String `tfsdk:"id"` +} + +func (e *exampleResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + var data exampleResourceData + + diags := req.Config.Get(ctx, &data) + resp.Diagnostics.Append(diags...) + + if resp.Diagnostics.HasError() { + return + } + + // Create resource using 3rd party API. + + data.Id = types.StringValue("example-id") + + tflog.Trace(ctx, "created a resource") + + diags = resp.State.Set(ctx, &data) + resp.Diagnostics.Append(diags...) +} + +func (e *exampleResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + var data exampleResourceData + + diags := req.State.Get(ctx, &data) + resp.Diagnostics.Append(diags...) + + if resp.Diagnostics.HasError() { + return + } + + // Read resource using 3rd party API. + + diags = resp.State.Set(ctx, &data) + resp.Diagnostics.Append(diags...) +} + +func (e *exampleResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + var data exampleResourceData + + diags := req.Plan.Get(ctx, &data) + resp.Diagnostics.Append(diags...) + + if resp.Diagnostics.HasError() { + return + } + + // Update resource using 3rd party API. + + diags = resp.State.Set(ctx, &data) + resp.Diagnostics.Append(diags...) +} + +func (e *exampleResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + var data exampleResourceData + + diags := req.State.Get(ctx, &data) + resp.Diagnostics.Append(diags...) + + if resp.Diagnostics.HasError() { + return + } + + // Delete resource using 3rd party API. +} +``` + +Refer to [Resources](/terraform/plugin/framework/resources) for more details and configuration examples. + +## Data Source + +A data source is typically used to provide a read-only view of infrastructure objects. + +In this example the data source simply interacts with Terraform state. + +`NewDataSource()` returns a function which returns a type that satisfies the `datasource.DataSource` interface. The `NewDataSource()` function is used within the `provider.DataSources` function to make the data source available to the provider. + +The `exampleDataSource` struct implements the `datasource.DataSource` interface. This interface defines the following functions: + +- [`Metadata`](/terraform/plugin/framework/data-sources#metadata-method): This function returns the full name (`TypeName`) of the data source. The full name is used in [Terraform configuration](#data-source-configuration) as `data `. +- [`Schema`](/terraform/plugin/framework/handling-data/schemas): This function returns a data source `schema.Schema` struct that defines the data source schema. The schema specifies the constraints of the data source Terraform configuration block. It defines what fields a data source configuration block has, and gives Terraform metadata about those fields. For instance, defining whether a field is optional. +- [`Read`](/terraform/plugin/framework/data-sources#read-method): This function lets the provider read data source values in order to update state. + +```go +package provider + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-framework/datasource" + "github.com/hashicorp/terraform-plugin-framework/datasource/schema" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-log/tflog" +) + +var _ datasource.DataSource = (*exampleDataSource)(nil) + +type exampleDataSource struct { + provider exampleProvider +} + +func NewDataSource() datasource.DataSource { + return &exampleDataSource{} +} + +func (e *exampleDataSource) Metadata(ctx context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_datasource" +} + +func (e *exampleDataSource) Schema(ctx context.Context, req datasource.SchemaRequest, resp *datasource.SchemaResponse) { + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "configurable_attribute": schema.StringAttribute{ + MarkdownDescription: "Example configurable attribute", + Optional: true, + }, + "id": schema.StringAttribute{ + MarkdownDescription: "Example identifier", + Computed: true, + }, + }, + } +} + +type exampleDataSourceData struct { + ConfigurableAttribute types.String `tfsdk:"configurable_attribute"` + Id types.String `tfsdk:"id"` +} + +func (e *exampleDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { + var data exampleDataSourceData + + diags := req.Config.Get(ctx, &data) + resp.Diagnostics.Append(diags...) + + if resp.Diagnostics.HasError() { + return + } + + // Interact with 3rd party API to read data source. + + data.Id = types.StringValue("example-id") + + tflog.Trace(ctx, "read a data source") + + diags = resp.State.Set(ctx, &data) + resp.Diagnostics.Append(diags...) +} +``` + +Refer to [Data Sources](/terraform/plugin/framework/data-sources) for more details and configuration examples. + +## Terraform Configuration + +Refer to [terraform-provider-scaffolding-framework](https://github.com/hashicorp/terraform-provider-scaffolding-framework) for details on how to wire together a [provider server](#provider-server), [provider](#provider), [resource](#resource) and [data source](#data-source). + +Once wired together, run the provider by specifying configuration and executing `terraform apply`. + +### Resource Configuration + +```hcl +resource "example_resource" "example" { + configurable_attribute = "some-value" +} +``` + +The `configurable_attribute` is defined within the [schema](#resource) as a string type attribute. + +Examples of the various types of attributes and their representation within Terraform configuration and schema definitions are detailed in [Terraform Concepts](/terraform/plugin/framework/handling-data/terraform-concepts). + +### Data Source Configuration + +```hcl +data "example_datasource" "example" { + configurable_attribute = "some-value" +} +``` + +The `configurable_attribute` is defined within the [schema](#data-source) as a string type attribute. + +Examples of the various types of attributes and their representation within Terraform configuration and schema definitions are detailed in [Terraform Concepts](/terraform/plugin/framework/handling-data/terraform-concepts). diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/accessing-values.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/accessing-values.mdx new file mode 100644 index 000000000..a3d1777f4 --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/accessing-values.mdx @@ -0,0 +1,111 @@ +--- +page_title: Access state, configuration, and plan data +description: >- + Learn how to read values from Terraform's state, configuration, and plan with + the Terraform plugin framework. +--- + +# Access state, configuration, and plan data + +There are various points at which the provider needs access to the data from +the practitioner's configuration, Terraform's state, or generated plan. +The same patterns are used for accessing this data, regardless of +its source. + +The data is usually stored in a request object: + +```go +func (r ThingResource) Create(ctx context.Context, + req resource.CreateRequest, resp *resource.CreateResponse) +``` + +In this example, `req` holds the configuration and plan, and there is no state +value because the resource does not yet exist in state. + +## Get the Entire Configuration, Plan, or State + +One way to interact with configuration, plan, and state values is to convert +the entire configuration, plan, or state into a Go type, then treat them as +regular Go values. This has the benefit of letting the compiler check all your +code that accesses values, but requires defining a type to contain the values. + +Use the `Get` method to retrieve the first level of configuration, plan, and state data. + +```go +type ThingResourceModel struct { + Address types.Object `tfsdk:"address"` + Age types.Int64 `tfsdk:"age"` + Name types.String `tfsdk:"name"` + Pets types.List `tfsdk:"pets"` + Registered types.Bool `tfsdk:"registered"` + Tags types.Map `tfsdk:"tags"` +} + +func (r ThingResource) Create(ctx context.Context, + req resource.CreateRequest, resp *resource.CreateResponse) { + var plan ThingResourceModel + + diags := req.Plan.Get(ctx, &plan) + + resp.Diagnostics.Append(diags...) + + if resp.Diagnostics.HasError() { + return + } + + // values can now be accessed like plan.Name.ValueString() + // check if things are null with plan.Name.IsNull() + // check if things are unknown with plan.Name.IsUnknown() +} +``` + +The configuration, plan, and state data is represented as an object, and +accessed like an object. Refer to the [object type](/terraform/plugin/framework/handling-data/types/object) documentation for an +explanation on how objects can be converted into Go types. + +To descend into deeper nested data structures, the `types.List`, `types.Map`, and `types.Set` types each have an `ElementsAs()` method. The `types.Object` type has an `As()` method. + +## Get a Single Attribute or Block Value + +Use the `GetAttribute` method to retrieve a top level attribute or block value from the configuration, plan, and state. + +```go +func (r ThingResource) Read(ctx context.Context, + req resource.ReadRequest, resp *resource.ReadResponse) { + var name types.String + + diags := req.State.GetAttribute(ctx, path.Root("name"), &name) + + resp.Diagnostics.Append(diags...) + + if resp.Diagnostics.HasError() { + return + } + + // ... +} +``` + +## When Can a Value Be Unknown or Null? + +A lot of conversion rules say an error will be returned if a value is unknown +or null. It is safe to assume: + +* Required attributes will never be null or unknown in Create, Read, Update, or + Delete methods. +* Optional attributes that are not computed will never be unknown in Create, + Read, Update, or Delete methods. +* Computed attributes, whether optional or not, will never be null in the plan + for Create, Read, Update, or Delete methods. +* Computed attributes that are read-only (`Optional` is not `true`) will always + be unknown in the plan for Create, Read, Update, or Delete methods. They will + always be null in the configuration for Create, Read, Update, and Delete + methods. +* Required attributes will never be null in a provider's Configure method. They + may be unknown. +* The state never contains unknown values. +* The configuration for Create, Read, Update, and Delete methods never contains + unknown values. + +In any other circumstances, the provider is responsible for handling the +possibility that an unknown or null value may be presented to it. diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/attributes/bool.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/attributes/bool.mdx new file mode 100644 index 000000000..452c44cf1 --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/attributes/bool.mdx @@ -0,0 +1,134 @@ +--- +page_title: Boolean attributes +description: >- + Learn how to use boolean attributes with the Terraform plugin framework. +--- + + +# Boolean attributes + +Bool attributes store a boolean true or false value. Values are represented by a [bool type](/terraform/plugin/framework/handling-data/types/bool) in the framework. + +In this Terraform configuration example, a bool attribute named `example_attribute` is set to the value `true`: + +```hcl +resource "examplecloud_thing" "example" { + example_attribute = true +} +``` + +## Schema Definition + +Use one of the following attribute types to directly add a bool value to a [schema](/terraform/plugin/framework/handling-data/schemas) or [nested attribute type](/terraform/plugin/framework/handling-data/attributes#nested-attribute-types): + +| Schema Type | Attribute Type | +|-------------|----------------| +| [Data Source](/terraform/plugin/framework/data-sources) | [`schema.BoolAttribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/datasource/schema#BoolAttribute) | +| [Provider](/terraform/plugin/framework/providers) | [`schema.BoolAttribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/provider/schema#BoolAttribute) | +| [Resource](/terraform/plugin/framework/resources) | [`schema.BoolAttribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema#BoolAttribute) | +| [Ephemeral Resource](/terraform/plugin/framework/ephemeral-resources) | [`schema.BoolAttribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/ephemeral/schema#BoolAttribute) | + +In this example, a resource schema defines a top level required bool attribute named `example_attribute`: + +```go +func (r ThingResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "example_attribute": schema.BoolAttribute{ + Required: true, + // ... potentially other fields ... + }, + // ... potentially other attributes ... + }, + } +} +``` + +If the bool value should be the element type of a [collection attribute type](/terraform/plugin/framework/handling-data/attributes#collection-attribute-types), set the `ElementType` field according to the [bool type](/terraform/plugin/framework/handling-data/types/bool). Refer to the collection attribute type documentation for additional details. + +If the bool value should be a value type of an [object attribute type](/terraform/plugin/framework/handling-data/attributes#object-attribute-type), set the `AttributeTypes` map value according to the [bool type](/terraform/plugin/framework/handling-data/types/bool). Refer to the object attribute type documentation for additional details. + +### Configurability + +At least one of the `Computed`, `Optional`, or `Required` fields must be set to `true`. This defines how Terraform and the framework should expect data to set, whether the value is from the practitioner configuration or from the provider logic, such as API response value. + +The acceptable behaviors of these configurability options are: + +- `Required` only: The value must be practitioner configured to an eventually known value (not null), otherwise the framework will automatically raise an error diagnostic for the missing value. +- `Optional` only: The value may be practitioner configured to a known value or null. +- `Optional` and `Computed`: The value may be practitioner configured or the value may be set in provider logic when the practitioner configuration is null. +- `Computed` only: The value will be set in provider logic and any practitioner configuration causes the framework to automatically raise an error diagnostic for the unexpected configuration value. + +### Custom Types + +You may want to build your own attribute value and type implementations to allow your provider to combine validation, description, and plan customization behaviors into a reusable bundle. This helps avoid duplication or reimplementation and ensures consistency. These implementations use the `CustomType` field in the attribute type. + +Refer to [Custom Types](/terraform/plugin/framework/handling-data/types/custom) for further details on creating provider-defined types and values. + +### Deprecation + +Set the `DeprecationMessage` field to a practitioner-focused message for how to handle the deprecation. The framework will automatically raise a warning diagnostic with this message if the practitioner configuration contains a known value for the attribute. Terraform version 1.2.7 and later will raise a warning diagnostic in certain scenarios if the deprecated attribute value is referenced elsewhere in a practitioner configuration. The framework [deprecations](/terraform/plugin/framework/deprecations) documentation fully describes the recommended practices for deprecating an attribute or resource. + +Some practitioner-focused examples of a deprecation message include: + +- Configure `other_attribute` instead. This attribute will be removed in the next major version of the provider. +- Remove this attribute's configuration as it no longer is used and the attribute will be removed in the next major version of the provider. + +### Description + +The framework provides two description fields, `Description` and `MarkdownDescription`, which various tools use to show additional information about an attribute and its intended purpose. This includes, but is not limited to, [`terraform-plugin-docs`](https://github.com/hashicorp/terraform-plugin-docs) for automated provider documentation generation and [`terraform-ls`](https://github.com/hashicorp/terraform-ls) for Terraform configuration editor integrations. + +### Plan Modification + + + +Only managed resources implement this concept. + + + +The framework provides two plan modification fields for managed resource attributes, `Default` and `PlanModifiers`, which define resource and attribute value planning behaviors. The resource [default](/terraform/plugin/framework/resources/default) and [plan modification](/terraform/plugin/framework/resources/plan-modification) documentation covers these features more in-depth. + +#### Common Use Case Plan Modification + +The [`booldefault`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/booldefault) package defines common use case `Default` implementations: + +- [`StaticBool(bool)`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/booldefault#StaticBool): Define a static bool default value for the attribute. + +The [`boolplanmodifier`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/boolplanmodifier) package defines common use case `PlanModifiers` implementations: + +- [`RequiresReplace()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/boolplanmodifier#RequiresReplace): Marks the resource for replacement if the resource is being updated and the plan value does not match the prior state value. +- [`RequiresReplaceIf()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/boolplanmodifier#RequiresReplaceIf): Similar to `RequiresReplace()`, but also checks if a given function returns true. +- [`RequiresReplaceIfConfigured()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/boolplanmodifier#RequiresReplaceIfConfigured): Similar to `RequiresReplace()`, but also checks if the configuration value is not null. +- [`UseStateForUnknown()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/boolplanmodifier#UseStateForUnknown): Copies a known prior state value into the planned value. Use this when it is known that an unconfigured value will remain the same after a resource update. + +### Sensitive + +Set the `Sensitive` field if the attribute value should always be considered [sensitive data](/terraform/language/state/sensitive-data). In Terraform, this will generally mask the value in practitioner output. This setting cannot be conditionally set and does not impact how data is stored in the state. + +### WriteOnly + + + + Only managed resources implement this concept. + + + +Set the `WriteOnly` field to define a [write-only argument](/terraform/plugin/framework/resources/write-only-arguments). +Write-only arguments can accept [ephemeral values](/terraform/language/resources/ephemeral) +and are not persisted in the Terraform plan or state artifacts. Write-only arguments are supported in Terraform 1.11 and later. + +### Validation + +Set the `Validators` field to define [validation](/terraform/plugin/framework/validation#attribute-validation). This validation logic is ran in addition to any validation contained within a [custom type](#custom-types). + +#### Common Use Case Validators + +HashiCorp provides the additional [`terraform-plugin-framework-validators`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework-validators) Go module which contains validation logic for common use cases. The [`boolvalidator`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework-validators/boolvalidator) package within that module has bool attribute validators such as defining conflicting attributes. + +## Accessing Values + +The [accessing values](/terraform/plugin/framework/handling-data/accessing-values) documentation covers general methods for reading [schema](/terraform/plugin/framework/handling-data/schemas) (configuration, plan, and state) data, which is necessary before accessing an attribute value directly. The [bool type](/terraform/plugin/framework/handling-data/types/bool#accessing-values) documentation covers methods for interacting with the attribute value itself. + +## Setting Values + +The [bool type](/terraform/plugin/framework/handling-data/types/bool#setting-values) documentation covers methods for creating or setting the appropriate value. The [writing data](/terraform/plugin/framework/handling-data/writing-state) documentation covers general methods for writing [schema](/terraform/plugin/framework/handling-data/schemas) (plan and state) data, which is necessary afterwards. diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/attributes/dynamic.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/attributes/dynamic.mdx new file mode 100644 index 000000000..00eb1fc3e --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/attributes/dynamic.mdx @@ -0,0 +1,159 @@ +--- +page_title: Dynamic attributes +description: >- + Learn how to use dynamic attributes with the Terraform plugin framework. +--- + +# Dynamic attribute + + + +Static attribute types should always be preferred over dynamic attribute types, when possible. + +Developers dealing with dynamic attribute data will need to have extensive knowledge of the [Terraform type system](/terraform/language/expressions/types) to properly handle all potential practitioner configuration scenarios. + +Refer to [Dynamic Data - Considerations](/terraform/plugin/framework/handling-data/dynamic-data#considerations) for more information. + + + +Dynamic attributes can store **any** value. Values are represented by a [dynamic type](/terraform/plugin/framework/handling-data/types/dynamic) in the framework. + +In this Terraform configuration example, a dynamic attribute named `example_attribute` is set to the boolean value `true`: + +```hcl +resource "examplecloud_thing" "example" { + example_attribute = true +} +``` + +In this example, the same dynamic attribute is set to a tuple (not a list) of string values `one` and `two`: + +```hcl +resource "examplecloud_thing" "example" { + example_attribute = ["one", "two"] +} +``` + +In this example, the same dynamic attribute is set to an object type with mapped values of `attr1` to `"value1"` and `attr2` to `123`: + +```hcl +resource "examplecloud_thing" "example" { + example_attribute = { + attr1 = "value1" + attr2 = 123 + } +} +``` + + +## Schema Definition + +Use one of the following attribute types to directly add a dynamic value to a [schema](/terraform/plugin/framework/handling-data/schemas) or a [single nested attribute type](/terraform/plugin/framework/handling-data/attributes/single-nested): + +| Schema Type | Attribute Type | +|-------------|----------------| +| [Data Source](/terraform/plugin/framework/data-sources) | [`schema.DynamicAttribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/datasource/schema#DynamicAttribute) | +| [Provider](/terraform/plugin/framework/providers) | [`schema.DynamicAttribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/provider/schema#DynamicAttribute) | +| [Resource](/terraform/plugin/framework/resources) | [`schema.DynamicAttribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema#DynamicAttribute) | +| [Ephemeral Resource](/terraform/plugin/framework/ephemeral-resources) | [`schema.DynamicAttribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/ephemeral/schema#DynamicAttribute) | + +In this example, a resource schema defines a top level required dynamic attribute named `example_attribute`: + +```go +func (r ThingResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "example_attribute": schema.DynamicAttribute{ + Required: true, + // ... potentially other fields ... + }, + // ... potentially other attributes ... + }, + } +} +``` + +Dynamic values are not supported as the element type of a [collection type](/terraform/plugin/framework/handling-data/types#collection-types) or within [collection attribute types](/terraform/plugin/framework/handling-data/attributes#collection-attribute-types). + +If the dynamic value should be a value type of an [object attribute type](/terraform/plugin/framework/handling-data/attributes#object-attribute-type), set the `AttributeTypes` map value according to the [dynamic type](/terraform/plugin/framework/handling-data/types/dynamic). Refer to the object attribute type documentation for additional details. + +### Configurability + +At least one of the `Computed`, `Optional`, or `Required` fields must be set to `true`. This defines how Terraform and the framework should expect data to set, whether the value is from the practitioner configuration or from the provider logic, such as API response value. + +The acceptable behaviors of these configurability options are: + +- `Required` only: The value must be practitioner configured to an eventually known value (not null), otherwise the framework will automatically raise an error diagnostic for the missing value. +- `Optional` only: The value may be practitioner configured to a known value or null. +- `Optional` and `Computed`: The value may be practitioner configured or the value may be set in provider logic when the practitioner configuration is null. +- `Computed` only: The value will be set in provider logic and any practitioner configuration causes the framework to automatically raise an error diagnostic for the unexpected configuration value. + +### Custom Types + +You may want to build your own attribute value and type implementations to allow your provider to combine validation, description, and plan customization behaviors into a reusable bundle. This helps avoid duplication or reimplementation and ensures consistency. These implementations use the `CustomType` field in the attribute type. + +Refer to [Custom Types](/terraform/plugin/framework/handling-data/types/custom) for further details on creating provider-defined types and values. + +### Deprecation + +Set the `DeprecationMessage` field to a practitioner-focused message for how to handle the deprecation. The framework will automatically raise a warning diagnostic with this message if the practitioner configuration contains a known value for the attribute. Terraform version 1.2.7 and later will raise a warning diagnostic in certain scenarios if the deprecated attribute value is referenced elsewhere in a practitioner configuration. The framework [deprecations](/terraform/plugin/framework/deprecations) documentation fully describes the recommended practices for deprecating an attribute or resource. + +Some practitioner-focused examples of a deprecation message include: + +- Configure `other_attribute` instead. This attribute will be removed in the next major version of the provider. +- Remove this attribute's configuration as it no longer is used and the attribute will be removed in the next major version of the provider. + +### Description + +The framework provides two description fields, `Description` and `MarkdownDescription`, which various tools use to show additional information about an attribute and its intended purpose. This includes, but is not limited to, [`terraform-plugin-docs`](https://github.com/hashicorp/terraform-plugin-docs) for automated provider documentation generation and [`terraform-ls`](https://github.com/hashicorp/terraform-ls) for Terraform configuration editor integrations. + +### Plan Modification + + + +Only managed resources implement this concept. + + + +The framework provides two plan modification fields for managed resource attributes, `Default` and `PlanModifiers`, which define resource and attribute value planning behaviors. The resource [default](/terraform/plugin/framework/resources/default) and [plan modification](/terraform/plugin/framework/resources/plan-modification) documentation covers these features more in-depth. + +#### Common Use Case Plan Modification + +The [`dynamicdefault`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/dynamicdefault) package defines common use case `Default` implementations: + +- [`StaticValue(types.Dynamic)`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/dynamicdefault#StaticValue): Define a static default value for the attribute. + +The [`dynamicplanmodifier`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/dynamicplanmodifier) package defines common use case `PlanModifiers` implementations: + +- [`RequiresReplace()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/dynamicplanmodifier#RequiresReplace): Marks the resource for replacement if the resource is being updated and the plan value does not match the prior state value. +- [`RequiresReplaceIf()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/dynamicplanmodifier#RequiresReplaceIf): Similar to `RequiresReplace()`, but also checks if a given function returns true. +- [`RequiresReplaceIfConfigured()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/dynamicplanmodifier#RequiresReplaceIfConfigured): Similar to `RequiresReplace()`, but also checks if the configuration value is not null. +- [`UseStateForUnknown()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/dynamicplanmodifier#UseStateForUnknown): Copies a known prior state value into the planned value. Use this when it is known that an unconfigured value will remain the same after a resource update. + +### Sensitive + +Set the `Sensitive` field if the attribute value should always be considered [sensitive data](/terraform/language/state/sensitive-data). In Terraform, this will generally mask the value in practitioner output. This setting cannot be conditionally set and does not impact how data is stored in the state. + +### WriteOnly + + + + Only managed resources implement this concept. + + + +Set the `WriteOnly` field to define a [write-only argument](/terraform/plugin/framework/resources/write-only-arguments). +Write-only arguments can accept [ephemeral values](/terraform/language/resources/ephemeral) +and are not persisted in the Terraform plan or state artifacts. Write-only arguments are supported in Terraform 1.11 and later. + +### Validation + +Set the `Validators` field to define [validation](/terraform/plugin/framework/validation#attribute-validation). This validation logic is ran in addition to any validation contained within a [custom type](#custom-types). + +## Accessing Values + +The [accessing values](/terraform/plugin/framework/handling-data/accessing-values) documentation covers general methods for reading [schema](/terraform/plugin/framework/handling-data/schemas) (configuration, plan, and state) data, which is necessary before accessing an attribute value directly. The [dynamic type](/terraform/plugin/framework/handling-data/types/dynamic#accessing-values) documentation covers methods for interacting with the attribute value itself. + +## Setting Values + +The [dynamic type](/terraform/plugin/framework/handling-data/types/dynamic#setting-values) documentation covers methods for creating or setting the appropriate value. The [writing data](/terraform/plugin/framework/handling-data/writing-state) documentation covers general methods for writing [schema](/terraform/plugin/framework/handling-data/schemas) (plan and state) data, which is necessary afterwards. diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/attributes/float32.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/attributes/float32.mdx new file mode 100644 index 000000000..9fb24b70b --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/attributes/float32.mdx @@ -0,0 +1,140 @@ +--- +page_title: Float32 attributes +description: >- + Learn how to use 32-bit floating point attributes with the Terraform plugin + framework. +--- + +# Float32 attributes + + + +Use [Int32 Attribute](/terraform/plugin/framework/handling-data/attributes/int32) for 32-bit integer numbers. Use [Number Attribute](/terraform/plugin/framework/handling-data/attributes/number) for arbitrary precision numbers. + + + +Float32 attributes store a 32-bit floating point number. Values are represented by a [float32 type](/terraform/plugin/framework/handling-data/types/float32) in the framework. + +In this Terraform configuration example, a float32 attribute named `example_attribute` is set to the value `1.23`: + +```hcl +resource "examplecloud_thing" "example" { + example_attribute = 1.23 +} +``` + +## Schema Definition + +Use one of the following attribute types to directly add a float32 value to a [schema](/terraform/plugin/framework/handling-data/schemas) or [nested attribute type](/terraform/plugin/framework/handling-data/attributes#nested-attribute-types): + +| Schema Type | Attribute Type | +|-------------|----------------| +| [Data Source](/terraform/plugin/framework/data-sources) | [`schema.Float32Attribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/datasource/schema#Float32Attribute) | +| [Provider](/terraform/plugin/framework/providers) | [`schema.Float32Attribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/provider/schema#Float32Attribute) | +| [Resource](/terraform/plugin/framework/resources) | [`schema.Float32Attribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema#Float32Attribute) | +| [Ephemeral Resource](/terraform/plugin/framework/ephemeral-resources) | [`schema.Float32Attribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/ephemeral/schema#Float32Attribute) | + +In this example, a resource schema defines a top level required float32 attribute named `example_attribute`: + +```go +func (r ThingResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "example_attribute": schema.Float32Attribute{ + Required: true, + // ... potentially other fields ... + }, + // ... potentially other attributes ... + }, + } +} +``` + +If the float32 value should be the element type of a [collection attribute type](/terraform/plugin/framework/handling-data/attributes#collection-attribute-types), set the `ElementType` field according to the [float32 type](/terraform/plugin/framework/handling-data/types/float32). Refer to the collection attribute type documentation for additional details. + +If the float32 value should be a value type of an [object attribute type](/terraform/plugin/framework/handling-data/attributes#object-attribute-type), set the `AttributeTypes` map value according to the [float32 type](/terraform/plugin/framework/handling-data/types/float32). Refer to the object attribute type documentation for additional details. + +### Configurability + +At least one of the `Computed`, `Optional`, or `Required` fields must be set to `true`. This defines how Terraform and the framework should expect data to set, whether the value is from the practitioner configuration or from the provider logic, such as API response value. + +The acceptable behaviors of these configurability options are: + +- `Required` only: The value must be practitioner configured to an eventually known value (not null), otherwise the framework will automatically raise an error diagnostic for the missing value. +- `Optional` only: The value may be practitioner configured to a known value or null. +- `Optional` and `Computed`: The value may be practitioner configured or the value may be set in provider logic when the practitioner configuration is null. +- `Computed` only: The value will be set in provider logic and any practitioner configuration causes the framework to automatically raise an error diagnostic for the unexpected configuration value. + +### Custom Types + +You may want to build your own attribute value and type implementations to allow your provider to combine validation, description, and plan customization behaviors into a reusable bundle. This helps avoid duplication or reimplementation and ensures consistency. These implementations use the `CustomType` field in the attribute type. + +Refer to [Custom Types](/terraform/plugin/framework/handling-data/types/custom) for further details on creating provider-defined types and values. + +### Deprecation + +Set the `DeprecationMessage` field to a practitioner-focused message for how to handle the deprecation. The framework will automatically raise a warning diagnostic with this message if the practitioner configuration contains a known value for the attribute. Terraform version 1.2.7 and later will raise a warning diagnostic in certain scenarios if the deprecated attribute value is referenced elsewhere in a practitioner configuration. The framework [deprecations](/terraform/plugin/framework/deprecations) documentation fully describes the recommended practices for deprecating an attribute or resource. + +Some practitioner-focused examples of a deprecation message include: + +- Configure `other_attribute` instead. This attribute will be removed in the next major version of the provider. +- Remove this attribute's configuration as it no longer is used and the attribute will be removed in the next major version of the provider. + +### Description + +The framework provides two description fields, `Description` and `MarkdownDescription`, which various tools use to show additional information about an attribute and its intended purpose. This includes, but is not limited to, [`terraform-plugin-docs`](https://github.com/hashicorp/terraform-plugin-docs) for automated provider documentation generation and [`terraform-ls`](https://github.com/hashicorp/terraform-ls) for Terraform configuration editor integrations. + +### Plan Modification + + + +Only managed resources implement this concept. + + + +The framework provides two plan modification fields for managed resource attributes, `Default` and `PlanModifiers`, which define resource and attribute value planning behaviors. The resource [default](/terraform/plugin/framework/resources/default) and [plan modification](/terraform/plugin/framework/resources/plan-modification) documentation covers these features more in-depth. + +#### Common Use Case Plan Modification + +The [`float32default`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/float32default) package defines common use case `Default` implementations: + +- [`StaticFloat32(float32)`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/float32default#StaticFloat32): Define a static float32 default value for the attribute. + +The [`float32planmodifier`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/float32planmodifier) package defines common use case `PlanModifiers` implementations: + +- [`RequiresReplace()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/float32planmodifier#RequiresReplace): Marks the resource for replacement if the resource is being updated and the plan value does not match the prior state value. +- [`RequiresReplaceIf()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/float32planmodifier#RequiresReplaceIf): Similar to `RequiresReplace()`, but also checks if a given function returns true. +- [`RequiresReplaceIfConfigured()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/float32planmodifier#RequiresReplaceIfConfigured): Similar to `RequiresReplace()`, but also checks if the configuration value is not null. +- [`UseStateForUnknown()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/float32planmodifier#UseStateForUnknown): Copies a known prior state value into the planned value. Use this when it is known that an unconfigured value will remain the same after a resource update. + +### Sensitive + +Set the `Sensitive` field if the attribute value should always be considered [sensitive data](/terraform/language/state/sensitive-data). In Terraform, this will generally mask the value in practitioner output. This setting cannot be conditionally set and does not impact how data is stored in the state. + +### WriteOnly + + + + Only managed resources implement this concept. + + + +Set the `WriteOnly` field to define a [write-only argument](/terraform/plugin/framework/resources/write-only-arguments). +Write-only arguments can accept [ephemeral values](/terraform/language/resources/ephemeral) +and are not persisted in the Terraform plan or state artifacts. Write-only arguments are supported in Terraform 1.11 and later. + +### Validation + +Set the `Validators` field to define [validation](/terraform/plugin/framework/validation#attribute-validation). This validation logic is ran in addition to any validation contained within a [custom type](#custom-types). + +#### Common Use Case Validators + +HashiCorp provides the additional [`terraform-plugin-framework-validators`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework-validators) Go module which contains validation logic for common use cases. The [`float32validator`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework-validators/float32validator) package within that module has float32 attribute validators such as minimum, maximum, and defining conflicting attributes. + +## Accessing Values + +The [accessing values](/terraform/plugin/framework/handling-data/accessing-values) documentation covers general methods for reading [schema](/terraform/plugin/framework/handling-data/schemas) (configuration, plan, and state) data, which is necessary before accessing an attribute value directly. The [float32 type](/terraform/plugin/framework/handling-data/types/float32#accessing-values) documentation covers methods for interacting with the attribute value itself. + +## Setting Values + +The [float32 type](/terraform/plugin/framework/handling-data/types/float32#setting-values) documentation covers methods for creating or setting the appropriate value. The [writing data](/terraform/plugin/framework/handling-data/writing-state) documentation covers general methods for writing [schema](/terraform/plugin/framework/handling-data/schemas) (plan and state) data, which is necessary afterwards. diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/attributes/float64.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/attributes/float64.mdx new file mode 100644 index 000000000..b0c98c4b1 --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/attributes/float64.mdx @@ -0,0 +1,140 @@ +--- +page_title: Float64 attributes +description: >- + Learn how to use 64-bit floating point attributes with the Terraform plugin + framework. +--- + +# Float64 attributes + + + +Use [Int64 Attribute](/terraform/plugin/framework/handling-data/attributes/int64) for 64-bit integer numbers. Use [Number Attribute](/terraform/plugin/framework/handling-data/attributes/number) for arbitrary precision numbers. + + + +Float64 attributes store a 64-bit floating point number. Values are represented by a [float64 type](/terraform/plugin/framework/handling-data/types/float64) in the framework. + +In this Terraform configuration example, a float64 attribute named `example_attribute` is set to the value `1.23`: + +```hcl +resource "examplecloud_thing" "example" { + example_attribute = 1.23 +} +``` + +## Schema Definition + +Use one of the following attribute types to directly add a float64 value to a [schema](/terraform/plugin/framework/handling-data/schemas) or [nested attribute type](/terraform/plugin/framework/handling-data/attributes#nested-attribute-types): + +| Schema Type | Attribute Type | +|-------------|----------------| +| [Data Source](/terraform/plugin/framework/data-sources) | [`schema.Float64Attribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/datasource/schema#Float64Attribute) | +| [Provider](/terraform/plugin/framework/providers) | [`schema.Float64Attribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/provider/schema#Float64Attribute) | +| [Resource](/terraform/plugin/framework/resources) | [`schema.Float64Attribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema#Float64Attribute) | +| [Ephemeral Resource](/terraform/plugin/framework/ephemeral-resources) | [`schema.Float64Attribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/ephemeral/schema#Float64Attribute) | + +In this example, a resource schema defines a top level required float64 attribute named `example_attribute`: + +```go +func (r ThingResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "example_attribute": schema.Float64Attribute{ + Required: true, + // ... potentially other fields ... + }, + // ... potentially other attributes ... + }, + } +} +``` + +If the float64 value should be the element type of a [collection attribute type](/terraform/plugin/framework/handling-data/attributes#collection-attribute-types), set the `ElementType` field according to the [float64 type](/terraform/plugin/framework/handling-data/types/float64). Refer to the collection attribute type documentation for additional details. + +If the float64 value should be a value type of an [object attribute type](/terraform/plugin/framework/handling-data/attributes#object-attribute-type), set the `AttributeTypes` map value according to the [float64 type](/terraform/plugin/framework/handling-data/types/float64). Refer to the object attribute type documentation for additional details. + +### Configurability + +At least one of the `Computed`, `Optional`, or `Required` fields must be set to `true`. This defines how Terraform and the framework should expect data to set, whether the value is from the practitioner configuration or from the provider logic, such as API response value. + +The acceptable behaviors of these configurability options are: + +- `Required` only: The value must be practitioner configured to an eventually known value (not null), otherwise the framework will automatically raise an error diagnostic for the missing value. +- `Optional` only: The value may be practitioner configured to a known value or null. +- `Optional` and `Computed`: The value may be practitioner configured or the value may be set in provider logic when the practitioner configuration is null. +- `Computed` only: The value will be set in provider logic and any practitioner configuration causes the framework to automatically raise an error diagnostic for the unexpected configuration value. + +### Custom Types + +You may want to build your own attribute value and type implementations to allow your provider to combine validation, description, and plan customization behaviors into a reusable bundle. This helps avoid duplication or reimplementation and ensures consistency. These implementations use the `CustomType` field in the attribute type. + +Refer to [Custom Types](/terraform/plugin/framework/handling-data/types/custom) for further details on creating provider-defined types and values. + +### Deprecation + +Set the `DeprecationMessage` field to a practitioner-focused message for how to handle the deprecation. The framework will automatically raise a warning diagnostic with this message if the practitioner configuration contains a known value for the attribute. Terraform version 1.2.7 and later will raise a warning diagnostic in certain scenarios if the deprecated attribute value is referenced elsewhere in a practitioner configuration. The framework [deprecations](/terraform/plugin/framework/deprecations) documentation fully describes the recommended practices for deprecating an attribute or resource. + +Some practitioner-focused examples of a deprecation message include: + +- Configure `other_attribute` instead. This attribute will be removed in the next major version of the provider. +- Remove this attribute's configuration as it no longer is used and the attribute will be removed in the next major version of the provider. + +### Description + +The framework provides two description fields, `Description` and `MarkdownDescription`, which various tools use to show additional information about an attribute and its intended purpose. This includes, but is not limited to, [`terraform-plugin-docs`](https://github.com/hashicorp/terraform-plugin-docs) for automated provider documentation generation and [`terraform-ls`](https://github.com/hashicorp/terraform-ls) for Terraform configuration editor integrations. + +### Plan Modification + + + +Only managed resources implement this concept. + + + +The framework provides two plan modification fields for managed resource attributes, `Default` and `PlanModifiers`, which define resource and attribute value planning behaviors. The resource [default](/terraform/plugin/framework/resources/default) and [plan modification](/terraform/plugin/framework/resources/plan-modification) documentation covers these features more in-depth. + +#### Common Use Case Plan Modification + +The [`float64default`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/float64default) package defines common use case `Default` implementations: + +- [`StaticFloat64(float64)`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/float64default#StaticFloat64): Define a static float64 default value for the attribute. + +The [`float64planmodifier`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/float64planmodifier) package defines common use case `PlanModifiers` implementations: + +- [`RequiresReplace()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/float64planmodifier#RequiresReplace): Marks the resource for replacement if the resource is being updated and the plan value does not match the prior state value. +- [`RequiresReplaceIf()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/float64planmodifier#RequiresReplaceIf): Similar to `RequiresReplace()`, but also checks if a given function returns true. +- [`RequiresReplaceIfConfigured()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/float64planmodifier#RequiresReplaceIfConfigured): Similar to `RequiresReplace()`, but also checks if the configuration value is not null. +- [`UseStateForUnknown()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/float64planmodifier#UseStateForUnknown): Copies a known prior state value into the planned value. Use this when it is known that an unconfigured value will remain the same after a resource update. + +### Sensitive + +Set the `Sensitive` field if the attribute value should always be considered [sensitive data](/terraform/language/state/sensitive-data). In Terraform, this will generally mask the value in practitioner output. This setting cannot be conditionally set and does not impact how data is stored in the state. + +### WriteOnly + + + + Only managed resources implement this concept. + + + +Set the `WriteOnly` field to define a [write-only argument](/terraform/plugin/framework/resources/write-only-arguments). +Write-only arguments can accept [ephemeral values](/terraform/language/resources/ephemeral) +and are not persisted in the Terraform plan or state artifacts. Write-only arguments are supported in Terraform 1.11 and later. + +### Validation + +Set the `Validators` field to define [validation](/terraform/plugin/framework/validation#attribute-validation). This validation logic is ran in addition to any validation contained within a [custom type](#custom-types). + +#### Common Use Case Validators + +HashiCorp provides the additional [`terraform-plugin-framework-validators`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework-validators) Go module which contains validation logic for common use cases. The [`float64validator`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework-validators/float64validator) package within that module has float64 attribute validators such as minimum, maximum, and defining conflicting attributes. + +## Accessing Values + +The [accessing values](/terraform/plugin/framework/handling-data/accessing-values) documentation covers general methods for reading [schema](/terraform/plugin/framework/handling-data/schemas) (configuration, plan, and state) data, which is necessary before accessing an attribute value directly. The [float64 type](/terraform/plugin/framework/handling-data/types/float64#accessing-values) documentation covers methods for interacting with the attribute value itself. + +## Setting Values + +The [float64 type](/terraform/plugin/framework/handling-data/types/float64#setting-values) documentation covers methods for creating or setting the appropriate value. The [writing data](/terraform/plugin/framework/handling-data/writing-state) documentation covers general methods for writing [schema](/terraform/plugin/framework/handling-data/schemas) (plan and state) data, which is necessary afterwards. diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/attributes/index.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/attributes/index.mdx new file mode 100644 index 000000000..58397eacb --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/attributes/index.mdx @@ -0,0 +1,95 @@ +--- +page_title: Attributes +description: >- + The Terraform plugin framework includes multiple built-in attribute types + and supports custom and dynamic attribute types. Each attribute and block in a + Terraform resource, data source, or provider schema maps to a framework or + custom type. +--- + +# Attributes + +Attributes are value storing fields in resource, data source, or provider [schemas](/terraform/plugin/framework/handling-data/schemas). Every attribute has an associated [value type](/terraform/plugin/framework/handling-data/types), which describes the kind of data the attribute can hold. Attributes also can describe value plan modifiers (resources only) and value validators in addition to those defined by the value type. + +## Available Attribute Types + +Schemas support the following attribute types: + +- [Primitive](#primitive-attribute-types): Attribute that contains a single value, such as a boolean, number, or string. +- [Collection](#collection-attribute-types): Attribute that contains multiple values of a single element type, such as a list, map, or set. +- [Nested](#nested-attribute-types): Attribute that defines a structure of explicit attibute names to attribute definitions, potentially with a wrapping collection type, such as a single structure of attributes or a list of structures of attributes. +- [Object](#object-attribute-type): Attribute that defines a structure of explicit attribute names to type-only definitions. +- [Dynamic](#dynamic-attribute-type): Attribute that accepts any value type. + +### Primitive Attribute Types + +Attribute types that contain a single data value, such as a boolean, number, or string. + +| Attribute Type | Use Case | +|----------------|----------| +| [Bool](/terraform/plugin/framework/handling-data/attributes/bool) | Boolean true or false | +| [Float32](/terraform/plugin/framework/handling-data/attributes/float32) | 32-bit floating point number | +| [Float64](/terraform/plugin/framework/handling-data/attributes/float64) | 64-bit floating point number | +| [Int32](/terraform/plugin/framework/handling-data/attributes/int32) | 32-bit integer number | +| [Int64](/terraform/plugin/framework/handling-data/attributes/int64) | 64-bit integer number | +| [Number](/terraform/plugin/framework/handling-data/attributes/number) | Arbitrary precision (generally over 64-bit, up to 512-bit) number | +| [String](/terraform/plugin/framework/handling-data/attributes/string) | Collection of UTF-8 encoded characters | + +### Collection Attribute Types + +Attribute types that contain multiple values of a single element type, such as a list, map, or set. + +| Attribute Type | Use Case | +|----------------|----------| +| [List](/terraform/plugin/framework/handling-data/attributes/list) | Ordered collection of single element type | +| [Map](/terraform/plugin/framework/handling-data/attributes/map) | Mapping of arbitrary string keys to values of single element type | +| [Set](/terraform/plugin/framework/handling-data/attributes/set) | Unordered, unique collection of single element type | + +### Nested Attribute Types + + + +Only supported when using [protocol version 6](/terraform/plugin/framework/provider-servers). + + + +Attribute types that define a structure of explicit attibute names to attribute definitions, potentially with a wrapping collection type, such as a single structure of attributes or a list of structures of attributes. + +| Attribute Type | Use Case | +|----------------|----------| +| [List Nested](/terraform/plugin/framework/handling-data/attributes/list-nested) | Ordered collection of structures of attributes | +| [Map Nested](/terraform/plugin/framework/handling-data/attributes/map-nested) | Mapping of arbitrary string keys to structures of attributes | +| [Set Nested](/terraform/plugin/framework/handling-data/attributes/set-nested) | Unordered, unique collection of structures of attributes | +| [Single Nested](/terraform/plugin/framework/handling-data/attributes/single-nested) | Single structure of attributes | + +### Object Attribute Type + + + +Use [nested attribute types](#nested-attribute-types) where possible. Objects have limited capabilities. + + + +Attribute type that defines a structure of explicit attribute names to type-only definitions. + +| Attribute Type | Use Case | +|----------------|----------| +| [Object](/terraform/plugin/framework/handling-data/attributes/object) | Single structure mapping explicit attribute names to type definitions | + +### Dynamic Attribute Type + + + +Static attribute types should always be preferred over dynamic attribute types, when possible. + +Developers dealing with dynamic attribute data will need to have extensive knowledge of the [Terraform type system](/terraform/language/expressions/types) to properly handle all potential practitioner configuration scenarios. + +Refer to [Dynamic Data - Considerations](/terraform/plugin/framework/handling-data/dynamic-data#considerations) for more information. + + + +Attribute type that can be any value type, determined by Terraform or the provider at runtime. + +| Attribute Type | Use Case | +|----------------|----------| +| [Dynamic](/terraform/plugin/framework/handling-data/attributes/dynamic) | Any value type of data, determined at runtime. | \ No newline at end of file diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/attributes/int32.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/attributes/int32.mdx new file mode 100644 index 000000000..810b38d82 --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/attributes/int32.mdx @@ -0,0 +1,140 @@ +--- +page_title: Int32 attributes +description: >- + Learn how to use 32-bit integer attributes with the Terraform plugin + framework. +--- + +# Int32 attributes + + + +Use [Float32 Attribute](/terraform/plugin/framework/handling-data/attributes/float32) for 32-bit floating point numbers. Use [Number Attribute](/terraform/plugin/framework/handling-data/attributes/number) for arbitrary precision numbers. + + + +Int32 attributes store a 32-bit integer number. Values are represented by a [int32 type](/terraform/plugin/framework/handling-data/types/int32) in the framework. + +In this Terraform configuration example, an int32 attribute named `example_attribute` is set to the value `123`: + +```hcl +resource "examplecloud_thing" "example" { + example_attribute = 123 +} +``` + +## Schema Definition + +Use one of the following attribute types to directly add a int32 value to a [schema](/terraform/plugin/framework/handling-data/schemas) or [nested attribute type](/terraform/plugin/framework/handling-data/attributes#nested-attribute-types): + +| Schema Type | Attribute Type | +|-------------|----------------| +| [Data Source](/terraform/plugin/framework/data-sources) | [`schema.Int32Attribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/datasource/schema#Int32Attribute) | +| [Provider](/terraform/plugin/framework/providers) | [`schema.Int32Attribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/provider/schema#Int32Attribute) | +| [Resource](/terraform/plugin/framework/resources) | [`schema.Int32Attribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema#Int32Attribute) | +| [Ephemeral Resource](/terraform/plugin/framework/ephemeral-resources) | [`schema.Int32Attribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/ephemeral/schema#Int32Attribute) | + +In this example, a resource schema defines a top level required int32 attribute named `example_attribute`: + +```go +func (r ThingResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "example_attribute": schema.Int32Attribute{ + Required: true, + // ... potentially other fields ... + }, + // ... potentially other attributes ... + }, + } +} +``` + +If the int32 value should be the element type of a [collection attribute type](/terraform/plugin/framework/handling-data/attributes#collection-attribute-types), set the `ElementType` field according to the [int32 type](/terraform/plugin/framework/handling-data/types/int32). Refer to the collection attribute type documentation for additional details. + +If the int32 value should be a value type of an [object attribute type](/terraform/plugin/framework/handling-data/attributes#object-attribute-type), set the `AttributeTypes` map value according to the [int32 type](/terraform/plugin/framework/handling-data/types/int32). Refer to the object attribute type documentation for additional details. + +### Configurability + +At least one of the `Computed`, `Optional`, or `Required` fields must be set to `true`. This defines how Terraform and the framework should expect data to set, whether the value is from the practitioner configuration or from the provider logic, such as API response value. + +The acceptable behaviors of these configurability options are: + +- `Required` only: The value must be practitioner configured to an eventually known value (not null), otherwise the framework will automatically raise an error diagnostic for the missing value. +- `Optional` only: The value may be practitioner configured to a known value or null. +- `Optional` and `Computed`: The value may be practitioner configured or the value may be set in provider logic when the practitioner configuration is null. +- `Computed` only: The value will be set in provider logic and any practitioner configuration causes the framework to automatically raise an error diagnostic for the unexpected configuration value. + +### Custom Types + +You may want to build your own attribute value and type implementations to allow your provider to combine validation, description, and plan customization behaviors into a reusable bundle. This helps avoid duplication or reimplementation and ensures consistency. These implementations use the `CustomType` field in the attribute type. + +Refer to [Custom Types](/terraform/plugin/framework/handling-data/types/custom) for further details on creating provider-defined types and values. + +### Deprecation + +Set the `DeprecationMessage` field to a practitioner-focused message for how to handle the deprecation. The framework will automatically raise a warning diagnostic with this message if the practitioner configuration contains a known value for the attribute. Terraform version 1.2.7 and later will raise a warning diagnostic in certain scenarios if the deprecated attribute value is referenced elsewhere in a practitioner configuration. The framework [deprecations](/terraform/plugin/framework/deprecations) documentation fully describes the recommended practices for deprecating an attribute or resource. + +Some practitioner-focused examples of a deprecation message include: + +- Configure `other_attribute` instead. This attribute will be removed in the next major version of the provider. +- Remove this attribute's configuration as it no longer is used and the attribute will be removed in the next major version of the provider. + +### Description + +The framework provides two description fields, `Description` and `MarkdownDescription`, which various tools use to show additional information about an attribute and its intended purpose. This includes, but is not limited to, [`terraform-plugin-docs`](https://github.com/hashicorp/terraform-plugin-docs) for automated provider documentation generation and [`terraform-ls`](https://github.com/hashicorp/terraform-ls) for Terraform configuration editor integrations. + +### Plan Modification + + + +Only managed resources implement this concept. + + + +The framework provides two plan modification fields for managed resource attributes, `Default` and `PlanModifiers`, which define resource and attribute value planning behaviors. The resource [default](/terraform/plugin/framework/resources/default) and [plan modification](/terraform/plugin/framework/resources/plan-modification) documentation covers these features more in-depth. + +#### Common Use Case Plan Modification + +The [`int32default`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/int32default) package defines common use case `Default` implementations: + +- [`StaticInt32(int32)`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/int32default#StaticInt32): Define a static int32 default value for the attribute. + +The [`int32planmodifier`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/int32planmodifier) package defines common use case `PlanModifiers` implementations: + +- [`RequiresReplace()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/int32planmodifier#RequiresReplace): Marks the resource for replacement if the resource is being updated and the plan value does not match the prior state value. +- [`RequiresReplaceIf()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/int32planmodifier#RequiresReplaceIf): Similar to `RequiresReplace()`, but also checks if a given function returns true. +- [`RequiresReplaceIfConfigured()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/int32planmodifier#RequiresReplaceIfConfigured): Similar to `RequiresReplace()`, but also checks if the configuration value is not null. +- [`UseStateForUnknown()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/int32planmodifier#UseStateForUnknown): Copies a known prior state value into the planned value. Use this when it is known that an unconfigured value will remain the same after a resource update. + +### Sensitive + +Set the `Sensitive` field if the attribute value should always be considered [sensitive data](/terraform/language/state/sensitive-data). In Terraform, this will generally mask the value in practitioner output. This setting cannot be conditionally set and does not impact how data is stored in the state. + +### WriteOnly + + + + Only managed resources implement this concept. + + + +Set the `WriteOnly` field to define a [write-only argument](/terraform/plugin/framework/resources/write-only-arguments). +Write-only arguments can accept [ephemeral values](/terraform/language/resources/ephemeral) +and are not persisted in the Terraform plan or state artifacts. Write-only arguments are supported in Terraform 1.11 and later. + +### Validation + +Set the `Validators` field to define [validation](/terraform/plugin/framework/validation#attribute-validation). This validation logic is ran in addition to any validation contained within a [custom type](#custom-types). + +#### Common Use Case Validators + +HashiCorp provides the additional [`terraform-plugin-framework-validators`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework-validators) Go module which contains validation logic for common use cases. The [`int32validator`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework-validators/int32validator) package within that module has int32 attribute validators such as minimum, maximum, and defining conflicting attributes. + +## Accessing Values + +The [accessing values](/terraform/plugin/framework/handling-data/accessing-values) documentation covers general methods for reading [schema](/terraform/plugin/framework/handling-data/schemas) (configuration, plan, and state) data, which is necessary before accessing an attribute value directly. The [int32 type](/terraform/plugin/framework/handling-data/types/int32#accessing-values) documentation covers methods for interacting with the attribute value itself. + +## Setting Values + +The [int32 type](/terraform/plugin/framework/handling-data/types/int32#setting-values) documentation covers methods for creating or setting the appropriate value. The [writing data](/terraform/plugin/framework/handling-data/writing-state) documentation covers general methods for writing [schema](/terraform/plugin/framework/handling-data/schemas) (plan and state) data, which is necessary afterwards. diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/attributes/int64.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/attributes/int64.mdx new file mode 100644 index 000000000..dcea6652c --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/attributes/int64.mdx @@ -0,0 +1,140 @@ +--- +page_title: Int64 attributes +description: >- + Learn how to use 64-bit integer attributes with the Terraform plugin + framework. +--- + +# Int64 attributes + + + +Use [Float64 Attribute](/terraform/plugin/framework/handling-data/attributes/float64) for 64-bit floating point numbers. Use [Number Attribute](/terraform/plugin/framework/handling-data/attributes/number) for arbitrary precision numbers. + + + +Int64 attributes store a 64-bit integer number. Values are represented by a [int64 type](/terraform/plugin/framework/handling-data/types/int64) in the framework. + +In this Terraform configuration example, an int64 attribute named `example_attribute` is set to the value `123`: + +```hcl +resource "examplecloud_thing" "example" { + example_attribute = 123 +} +``` + +## Schema Definition + +Use one of the following attribute types to directly add a int64 value to a [schema](/terraform/plugin/framework/handling-data/schemas) or [nested attribute type](/terraform/plugin/framework/handling-data/attributes#nested-attribute-types): + +| Schema Type | Attribute Type | +|-------------|----------------| +| [Data Source](/terraform/plugin/framework/data-sources) | [`schema.Int64Attribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/datasource/schema#Int64Attribute) | +| [Provider](/terraform/plugin/framework/providers) | [`schema.Int64Attribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/provider/schema#Int64Attribute) | +| [Resource](/terraform/plugin/framework/resources) | [`schema.Int64Attribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema#Int64Attribute) | +| [Ephemeral Resource](/terraform/plugin/framework/ephemeral-resources) | [`schema.Int64Attribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/ephemeral/schema#Int64Attribute) | + +In this example, a resource schema defines a top level required int64 attribute named `example_attribute`: + +```go +func (r ThingResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "example_attribute": schema.Int64Attribute{ + Required: true, + // ... potentially other fields ... + }, + // ... potentially other attributes ... + }, + } +} +``` + +If the int64 value should be the element type of a [collection attribute type](/terraform/plugin/framework/handling-data/attributes#collection-attribute-types), set the `ElementType` field according to the [int64 type](/terraform/plugin/framework/handling-data/types/int64). Refer to the collection attribute type documentation for additional details. + +If the int64 value should be a value type of an [object attribute type](/terraform/plugin/framework/handling-data/attributes#object-attribute-type), set the `AttributeTypes` map value according to the [int64 type](/terraform/plugin/framework/handling-data/types/int64). Refer to the object attribute type documentation for additional details. + +### Configurability + +At least one of the `Computed`, `Optional`, or `Required` fields must be set to `true`. This defines how Terraform and the framework should expect data to set, whether the value is from the practitioner configuration or from the provider logic, such as API response value. + +The acceptable behaviors of these configurability options are: + +- `Required` only: The value must be practitioner configured to an eventually known value (not null), otherwise the framework will automatically raise an error diagnostic for the missing value. +- `Optional` only: The value may be practitioner configured to a known value or null. +- `Optional` and `Computed`: The value may be practitioner configured or the value may be set in provider logic when the practitioner configuration is null. +- `Computed` only: The value will be set in provider logic and any practitioner configuration causes the framework to automatically raise an error diagnostic for the unexpected configuration value. + +### Custom Types + +You may want to build your own attribute value and type implementations to allow your provider to combine validation, description, and plan customization behaviors into a reusable bundle. This helps avoid duplication or reimplementation and ensures consistency. These implementations use the `CustomType` field in the attribute type. + +Refer to [Custom Types](/terraform/plugin/framework/handling-data/types/custom) for further details on creating provider-defined types and values. + +### Deprecation + +Set the `DeprecationMessage` field to a practitioner-focused message for how to handle the deprecation. The framework will automatically raise a warning diagnostic with this message if the practitioner configuration contains a known value for the attribute. Terraform version 1.2.7 and later will raise a warning diagnostic in certain scenarios if the deprecated attribute value is referenced elsewhere in a practitioner configuration. The framework [deprecations](/terraform/plugin/framework/deprecations) documentation fully describes the recommended practices for deprecating an attribute or resource. + +Some practitioner-focused examples of a deprecation message include: + +- Configure `other_attribute` instead. This attribute will be removed in the next major version of the provider. +- Remove this attribute's configuration as it no longer is used and the attribute will be removed in the next major version of the provider. + +### Description + +The framework provides two description fields, `Description` and `MarkdownDescription`, which various tools use to show additional information about an attribute and its intended purpose. This includes, but is not limited to, [`terraform-plugin-docs`](https://github.com/hashicorp/terraform-plugin-docs) for automated provider documentation generation and [`terraform-ls`](https://github.com/hashicorp/terraform-ls) for Terraform configuration editor integrations. + +### Plan Modification + + + +Only managed resources implement this concept. + + + +The framework provides two plan modification fields for managed resource attributes, `Default` and `PlanModifiers`, which define resource and attribute value planning behaviors. The resource [default](/terraform/plugin/framework/resources/default) and [plan modification](/terraform/plugin/framework/resources/plan-modification) documentation covers these features more in-depth. + +#### Common Use Case Plan Modification + +The [`int64default`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/int64default) package defines common use case `Default` implementations: + +- [`StaticInt64(int64)`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/int64default#StaticInt64): Define a static int64 default value for the attribute. + +The [`int64planmodifier`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/int64planmodifier) package defines common use case `PlanModifiers` implementations: + +- [`RequiresReplace()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/int64planmodifier#RequiresReplace): Marks the resource for replacement if the resource is being updated and the plan value does not match the prior state value. +- [`RequiresReplaceIf()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/int64planmodifier#RequiresReplaceIf): Similar to `RequiresReplace()`, but also checks if a given function returns true. +- [`RequiresReplaceIfConfigured()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/int64planmodifier#RequiresReplaceIfConfigured): Similar to `RequiresReplace()`, but also checks if the configuration value is not null. +- [`UseStateForUnknown()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/int64planmodifier#UseStateForUnknown): Copies a known prior state value into the planned value. Use this when it is known that an unconfigured value will remain the same after a resource update. + +### Sensitive + +Set the `Sensitive` field if the attribute value should always be considered [sensitive data](/terraform/language/state/sensitive-data). In Terraform, this will generally mask the value in practitioner output. This setting cannot be conditionally set and does not impact how data is stored in the state. + +### WriteOnly + + + + Only managed resources implement this concept. + + + +Set the `WriteOnly` field to define a [write-only argument](/terraform/plugin/framework/resources/write-only-arguments). +Write-only arguments can accept [ephemeral values](/terraform/language/resources/ephemeral) +and are not persisted in the Terraform plan or state artifacts. Write-only arguments are supported in Terraform 1.11 and later. + +### Validation + +Set the `Validators` field to define [validation](/terraform/plugin/framework/validation#attribute-validation). This validation logic is ran in addition to any validation contained within a [custom type](#custom-types). + +#### Common Use Case Validators + +HashiCorp provides the additional [`terraform-plugin-framework-validators`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework-validators) Go module which contains validation logic for common use cases. The [`int64validator`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework-validators/int64validator) package within that module has int64 attribute validators such as minimum, maximum, and defining conflicting attributes. + +## Accessing Values + +The [accessing values](/terraform/plugin/framework/handling-data/accessing-values) documentation covers general methods for reading [schema](/terraform/plugin/framework/handling-data/schemas) (configuration, plan, and state) data, which is necessary before accessing an attribute value directly. The [int64 type](/terraform/plugin/framework/handling-data/types/int64#accessing-values) documentation covers methods for interacting with the attribute value itself. + +## Setting Values + +The [int64 type](/terraform/plugin/framework/handling-data/types/int64#setting-values) documentation covers methods for creating or setting the appropriate value. The [writing data](/terraform/plugin/framework/handling-data/writing-state) documentation covers general methods for writing [schema](/terraform/plugin/framework/handling-data/schemas) (plan and state) data, which is necessary afterwards. diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/attributes/list-nested.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/attributes/list-nested.mdx new file mode 100644 index 000000000..c3525e010 --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/attributes/list-nested.mdx @@ -0,0 +1,190 @@ +--- +page_title: List nested attributes +description: >- + Learn how to use list nested attributes with the Terraform plugin framework. +--- + +# List nested attributes + +List nested attributes store an ordered collection of nested objects. Values are represented by a [list type](/terraform/plugin/framework/handling-data/types/list) in the framework, containing elements of [object type](/terraform/plugin/framework/handling-data/types/object). + +In this Terraform configuration example, a list nested attribute named `example_attribute` is set to the ordered object values of `attr` to `"one"` and `attr` to `"two"`: + +```hcl +resource "examplecloud_thing" "example" { + example_attribute = [ + { + attr = "one" + }, + { + attr = "two" + }, + ] +} +``` + +## Schema Definition + +Use one of the following attribute types to directly add a list nested value to a [schema](/terraform/plugin/framework/handling-data/schemas) or [nested attribute type](/terraform/plugin/framework/handling-data/attributes#nested-attribute-types): + +| Schema Type | Attribute Type | +|-------------|----------------| +| [Data Source](/terraform/plugin/framework/data-sources) | [`schema.ListNestedAttribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/datasource/schema#ListNestedAttribute) | +| [Provider](/terraform/plugin/framework/providers) | [`schema.ListNestedAttribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/provider/schema#ListNestedAttribute) | +| [Resource](/terraform/plugin/framework/resources) | [`schema.ListNestedAttribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema#ListNestedAttribute) | +| [Ephemeral Resource](/terraform/plugin/framework/ephemeral-resources) | [`schema.ListNestedAttribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/ephemeral/schema#ListNestedAttribute) | + +The `NestedObject` field must be defined, which represents the [object value type](/terraform/plugin/framework/handling-data/types/object) of every element of the list. + +In this example, a resource schema defines a top level required list nested attribute named `example_attribute` with a required string attribute named `attr`: + +```go +func (r ThingResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "example_attribute": schema.ListNestedAttribute{ + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "attr": schema.StringAttribute{ + Required: true, + // ... potentially other fields ... + }, + }, + }, + Required: true, + // ... potentially other fields ... + }, + // ... potentially other attributes ... + }, + } +} +``` + +Its [value type](/terraform/plugin/framework/handling-data/types) would be represented as a `types.List` of `types.Object` where the `types.Object` is a mapping of `attr` to `types.String`. + +A nested attribute type may itself contain further collection or nested attribute types, if necessary. + +In this example, a resource schema defines a top level required list nested attribute named `example_attribute` with a required list of strings attribute named `attr1` and an optional single nested attribute named `attr2`: + +```go +func (r ThingResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "example_attribute": schema.ListNestedAttribute{ + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "attr1": schema.ListAttribute{ + ElementType: types.StringType, + Required: true, + // ... potentially other fields ... + }, + "attr2": schema.SingleNestedAttribute{ + Attributes: map[string]schema.Attribute{ /* ... */ }, + Optional: true, + // ... potentially other fields ... + }, + }, + }, + Required: true, + // ... potentially other fields ... + }, + // ... potentially other attributes ... + }, + } +} +``` + +Its [value type](/terraform/plugin/framework/handling-data/types) would be represented as a `types.List` of `types.Object` where the `types.Object` is a mapping of `attr1` to `types.List` of `types.String` and `attr2` to `types.Object`. + +### Configurability + + + +Only the list nested attribute itself is defined by the `schema.ListNestedAttribute` configurability fields. Nested attributes must define their own configurability fields within each attribute definition. + + + +At least one of the `Computed`, `Optional`, or `Required` fields must be set to `true`. This defines how Terraform and the framework should expect data to set, whether the value is from the practitioner configuration or from the provider logic, such as API response value. + +The acceptable behaviors of these configurability options are: + +- `Required` only: The value must be practitioner configured to an eventually known value (not null), otherwise the framework will automatically raise an error diagnostic for the missing value. +- `Optional` only: The value may be practitioner configured to a known value or null. +- `Optional` and `Computed`: The value may be practitioner configured or the value may be set in provider logic when the practitioner configuration is null. +- `Computed` only: The value will be set in provider logic and any practitioner configuration causes the framework to automatically raise an error diagnostic for the unexpected configuration value. + +### Custom Types + +You may want to build your own attribute value and type implementations to allow your provider to combine validation, description, and plan customization behaviors into a reusable bundle. This helps avoid duplication or reimplementation and ensures consistency. These implementations use the `CustomType` field in the attribute type. + +Refer to [Custom Types](/terraform/plugin/framework/handling-data/types/custom) for further details on creating provider-defined types and values. + +### Deprecation + +Set the `DeprecationMessage` field to a practitioner-focused message for how to handle the deprecation. The framework will automatically raise a warning diagnostic with this message if the practitioner configuration contains a known value for the attribute. Terraform version 1.2.7 and later will raise a warning diagnostic in certain scenarios if the deprecated attribute value is referenced elsewhere in a practitioner configuration. The framework [deprecations](/terraform/plugin/framework/deprecations) documentation fully describes the recommended practices for deprecating an attribute or resource. + +Some practitioner-focused examples of a deprecation message include: + +- Configure `other_attribute` instead. This attribute will be removed in the next major version of the provider. +- Remove this attribute's configuration as it no longer is used and the attribute will be removed in the next major version of the provider. + +### Description + +The framework provides two description fields, `Description` and `MarkdownDescription`, which various tools use to show additional information about an attribute and its intended purpose. This includes, but is not limited to, [`terraform-plugin-docs`](https://github.com/hashicorp/terraform-plugin-docs) for automated provider documentation generation and [`terraform-ls`](https://github.com/hashicorp/terraform-ls) for Terraform configuration editor integrations. + +### Plan Modification + + + +Only managed resources implement this concept. + + + +The framework provides two plan modification fields for managed resource attributes, `Default` and `PlanModifiers`, which define resource and attribute value planning behaviors. The resource [default](/terraform/plugin/framework/resources/default) and [plan modification](/terraform/plugin/framework/resources/plan-modification) documentation covers these features more in-depth. + +#### Common Use Case Plan Modification + +The [`listdefault`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/listdefault) package defines common use case `Default` implementations: + +- [`StaticValue(types.List)`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/listdefault#StaticValue): Define a static list default value for the attribute. + +The [`listplanmodifier`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/listplanmodifier) package defines common use case `PlanModifiers` implementations: + +- [`RequiresReplace()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/listplanmodifier#RequiresReplace): Marks the resource for replacement if the resource is being updated and the plan value does not match the prior state value. +- [`RequiresReplaceIf()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/listplanmodifier#RequiresReplaceIf): Similar to `RequiresReplace()`, but also checks if a given function returns true. +- [`RequiresReplaceIfConfigured()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/listplanmodifier#RequiresReplaceIfConfigured): Similar to `RequiresReplace()`, but also checks if the configuration value is not null. +- [`UseStateForUnknown()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/listplanmodifier#UseStateForUnknown): Copies a known prior state value into the planned value. Use this when it is known that an unconfigured value will remain the same after a resource update. + +### Sensitive + +Set the `Sensitive` field if the attribute value should always be considered [sensitive data](/terraform/language/state/sensitive-data). In Terraform, this will generally mask the value in practitioner output. This setting cannot be conditionally set and does not impact how data is stored in the state. + +### WriteOnly + + + + Only managed resources implement this concept. + + + +Set the `WriteOnly` field to define a [write-only argument](/terraform/plugin/framework/resources/write-only-arguments). +Write-only arguments can accept [ephemeral values](/terraform/language/resources/ephemeral) +and are not persisted in the Terraform plan or state artifacts. Write-only arguments are supported in Terraform 1.11 and later. + +If a nested attribute has the `WriteOnly` field set, all child attributes must also have `WriteOnly` set. + +### Validation + +Set the `Validators` field to define [validation](/terraform/plugin/framework/validation#attribute-validation). This validation logic is ran in addition to any validation contained within a [custom type](#custom-types). + +#### Common Use Case Validators + +HashiCorp provides the additional [`terraform-plugin-framework-validators`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework-validators) Go module which contains validation logic for common use cases. The [`listvalidator`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework-validators/listvalidator) package within that module has list attribute validators such as defining conflicting attributes. + +## Accessing Values + +The [accessing values](/terraform/plugin/framework/handling-data/accessing-values) documentation covers general methods for reading [schema](/terraform/plugin/framework/handling-data/schemas) (configuration, plan, and state) data, which is necessary before accessing an attribute value directly. The [list type](/terraform/plugin/framework/handling-data/types/list#accessing-values) documentation covers methods for interacting with the attribute value itself. + +## Setting Values + +The [list type](/terraform/plugin/framework/handling-data/types/list#setting-values) documentation covers methods for creating or setting the appropriate value. The [writing data](/terraform/plugin/framework/handling-data/writing-state) documentation covers general methods for writing [schema](/terraform/plugin/framework/handling-data/schemas) (plan and state) data, which is necessary afterwards. diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/attributes/list.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/attributes/list.mdx new file mode 100644 index 000000000..1753e9777 --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/attributes/list.mdx @@ -0,0 +1,157 @@ +--- +page_title: List attributes +description: >- + Learn how to use list attributes with the Terraform plugin framework. +--- + +# List attributes + +List attributes store an ordered collection of single element type. Values are represented by a [list type](/terraform/plugin/framework/handling-data/types/list) in the framework, containing elements of the element type. + +In this Terraform configuration example, a list of string attribute named `example_attribute` is set to the ordered values `"one"` and `"two"`: + +```hcl +resource "examplecloud_thing" "example" { + example_attribute = ["one", "two"] +} +``` + +## Schema Definition + +Use one of the following attribute types to directly add a list value to a [schema](/terraform/plugin/framework/handling-data/schemas) or [nested attribute type](/terraform/plugin/framework/handling-data/attributes#nested-attribute-types): + +| Schema Type | Attribute Type | +|-------------|----------------| +| [Data Source](/terraform/plugin/framework/data-sources) | [`schema.ListAttribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/datasource/schema#ListAttribute) | +| [Provider](/terraform/plugin/framework/providers) | [`schema.ListAttribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/provider/schema#ListAttribute) | +| [Resource](/terraform/plugin/framework/resources) | [`schema.ListAttribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema#ListAttribute) | +| [Ephemeral Resource](/terraform/plugin/framework/ephemeral-resources) | [`schema.ListAttribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/ephemeral/schema#ListAttribute) | + +The `ElementType` field must be defined, which represents the single [value type](/terraform/plugin/framework/handling-data/types) of every element of the list. + +In this example, a resource schema defines a top level required list of strings attribute named `example_attribute`: + +```go +func (r ThingResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "example_attribute": schema.ListAttribute{ + ElementType: types.StringType, + Required: true, + // ... potentially other fields ... + }, + // ... potentially other attributes ... + }, + } +} +``` + +An element type may itself contain further collection types, if necessary. + +In this example, a resource schema defines a top level required list of list of strings attribute named `example_attribute`: + +```go +func (r ThingResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "example_attribute": schema.ListAttribute{ + ElementType: types.ListType{ + ElemType: types.StringType, + }, + Required: true, + // ... potentially other fields ... + }, + // ... potentially other attributes ... + }, + } +} +``` + +If the list value should be the element type of a [collection attribute type](/terraform/plugin/framework/handling-data/attributes#collection-attribute-types), set the `ElementType` field according to the [list type](/terraform/plugin/framework/handling-data/types/list). Refer to the collection attribute type documentation for additional details. + +If the list value should be a value type of an [object attribute type](/terraform/plugin/framework/handling-data/attributes#object-attribute-type), set the `AttributeTypes` map value according to the [list type](/terraform/plugin/framework/handling-data/types/list). Refer to the object attribute type documentation for additional details. + +### Configurability + +At least one of the `Computed`, `Optional`, or `Required` fields must be set to `true`. This defines how Terraform and the framework should expect data to set, whether the value is from the practitioner configuration or from the provider logic, such as API response value. + +The acceptable behaviors of these configurability options are: + +- `Required` only: The value must be practitioner configured to an eventually known value (not null), otherwise the framework will automatically raise an error diagnostic for the missing value. +- `Optional` only: The value may be practitioner configured to a known value or null. +- `Optional` and `Computed`: The value may be practitioner configured or the value may be set in provider logic when the practitioner configuration is null. +- `Computed` only: The value will be set in provider logic and any practitioner configuration causes the framework to automatically raise an error diagnostic for the unexpected configuration value. + +### Custom Types + +You may want to build your own attribute value and type implementations to allow your provider to combine validation, description, and plan customization behaviors into a reusable bundle. This helps avoid duplication or reimplementation and ensures consistency. These implementations use the `CustomType` field in the attribute type. + +Refer to [Custom Types](/terraform/plugin/framework/handling-data/types/custom) for further details on creating provider-defined types and values. + +### Deprecation + +Set the `DeprecationMessage` field to a practitioner-focused message for how to handle the deprecation. The framework will automatically raise a warning diagnostic with this message if the practitioner configuration contains a known value for the attribute. Terraform version 1.2.7 and later will raise a warning diagnostic in certain scenarios if the deprecated attribute value is referenced elsewhere in a practitioner configuration. The framework [deprecations](/terraform/plugin/framework/deprecations) documentation fully describes the recommended practices for deprecating an attribute or resource. + +Some practitioner-focused examples of a deprecation message include: + +- Configure `other_attribute` instead. This attribute will be removed in the next major version of the provider. +- Remove this attribute's configuration as it no longer is used and the attribute will be removed in the next major version of the provider. + +### Description + +The framework provides two description fields, `Description` and `MarkdownDescription`, which various tools use to show additional information about an attribute and its intended purpose. This includes, but is not limited to, [`terraform-plugin-docs`](https://github.com/hashicorp/terraform-plugin-docs) for automated provider documentation generation and [`terraform-ls`](https://github.com/hashicorp/terraform-ls) for Terraform configuration editor integrations. + +### Plan Modification + + + +Only managed resources implement this concept. + + + +The framework provides two plan modification fields for managed resource attributes, `Default` and `PlanModifiers`, which define resource and attribute value planning behaviors. The resource [default](/terraform/plugin/framework/resources/default) and [plan modification](/terraform/plugin/framework/resources/plan-modification) documentation covers these features more in-depth. + +#### Common Use Case Plan Modification + +The [`listdefault`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/listdefault) package defines common use case `Default` implementations: + +- [`StaticValue(types.List)`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/listdefault#StaticValue): Define a static list default value for the attribute. + +The [`listplanmodifier`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/listplanmodifier) package defines common use case `PlanModifiers` implementations: + +- [`RequiresReplace()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/listplanmodifier#RequiresReplace): Marks the resource for replacement if the resource is being updated and the plan value does not match the prior state value. +- [`RequiresReplaceIf()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/listplanmodifier#RequiresReplaceIf): Similar to `RequiresReplace()`, but also checks if a given function returns true. +- [`RequiresReplaceIfConfigured()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/listplanmodifier#RequiresReplaceIfConfigured): Similar to `RequiresReplace()`, but also checks if the configuration value is not null. +- [`UseStateForUnknown()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/listplanmodifier#UseStateForUnknown): Copies a known prior state value into the planned value. Use this when it is known that an unconfigured value will remain the same after a resource update. + +### Sensitive + +Set the `Sensitive` field if the attribute value should always be considered [sensitive data](/terraform/language/state/sensitive-data). In Terraform, this will generally mask the value in practitioner output. This setting cannot be conditionally set and does not impact how data is stored in the state. + +### WriteOnly + + + + Only managed resources implement this concept. + + + +Set the `WriteOnly` field to define a [write-only argument](/terraform/plugin/framework/resources/write-only-arguments). +Write-only arguments can accept [ephemeral values](/terraform/language/resources/ephemeral) +and are not persisted in the Terraform plan or state artifacts. Write-only arguments are supported in Terraform 1.11 and later. + +### Validation + +Set the `Validators` field to define [validation](/terraform/plugin/framework/validation#attribute-validation). This validation logic is ran in addition to any validation contained within a [custom type](#custom-types). + +#### Common Use Case Validators + +HashiCorp provides the additional [`terraform-plugin-framework-validators`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework-validators) Go module which contains validation logic for common use cases. The [`listvalidator`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework-validators/listvalidator) package within that module has list attribute validators such as defining conflicting attributes. + +## Accessing Values + +The [accessing values](/terraform/plugin/framework/handling-data/accessing-values) documentation covers general methods for reading [schema](/terraform/plugin/framework/handling-data/schemas) (configuration, plan, and state) data, which is necessary before accessing an attribute value directly. The [list type](/terraform/plugin/framework/handling-data/types/list#accessing-values) documentation covers methods for interacting with the attribute value itself. + +## Setting Values + +The [list type](/terraform/plugin/framework/handling-data/types/list#setting-values) documentation covers methods for creating or setting the appropriate value. The [writing data](/terraform/plugin/framework/handling-data/writing-state) documentation covers general methods for writing [schema](/terraform/plugin/framework/handling-data/schemas) (plan and state) data, which is necessary afterwards. diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/attributes/map-nested.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/attributes/map-nested.mdx new file mode 100644 index 000000000..39b3a2a24 --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/attributes/map-nested.mdx @@ -0,0 +1,190 @@ +--- +page_title: Map nested attributes +description: >- + Learn how to use map nested attributes with the Terraform plugin framework. +--- + +# Map Nested Attribute + +Map nested attributes store mapping of arbitrary string keys to nested objects. Values are represented by a [map type](/terraform/plugin/framework/handling-data/types/map) in the framework, containing elements of [object type](/terraform/plugin/framework/handling-data/types/object). + +In this Terraform configuration example, a map nested attribute named `example_attribute` is set to the mapped values of `"key1"` to the object value of `attr` to `"one"` and `"key2"` to the object value of `attr` to `"two"`: + +```hcl +resource "examplecloud_thing" "example" { + example_attribute = { + "key1" = { + attr = "one" + }, + "key2" = { + attr = "two" + }, + } +} +``` + +## Schema Definition + +Use one of the following attribute types to directly add a map nested value to a [schema](/terraform/plugin/framework/handling-data/schemas) or [nested attribute type](/terraform/plugin/framework/handling-data/attributes#nested-attribute-types): + +| Schema Type | Attribute Type | +|-------------|----------------| +| [Data Source](/terraform/plugin/framework/data-sources) | [`schema.MapNestedAttribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/datasource/schema#MapNestedAttribute) | +| [Provider](/terraform/plugin/framework/providers) | [`schema.MapNestedAttribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/provider/schema#MapNestedAttribute) | +| [Resource](/terraform/plugin/framework/resources) | [`schema.MapNestedAttribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema#MapNestedAttribute) | +| [Ephemeral Resource](/terraform/plugin/framework/ephemeral-resources) | [`schema.MapNestedAttribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/ephemeral/schema#MapNestedAttribute) | + +The `NestedObject` field must be defined, which represents the [object value type](/terraform/plugin/framework/handling-data/types/object) of every element of the list. + +In this example, a resource schema defines a top level required map nested attribute named `example_attribute` with a required string attribute named `attr`: + +```go +func (r ThingResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "example_attribute": schema.MapNestedAttribute{ + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "attr": schema.StringAttribute{ + Required: true, + // ... potentially other fields ... + }, + }, + }, + Required: true, + // ... potentially other fields ... + }, + // ... potentially other attributes ... + }, + } +} +``` + +Its [value type](/terraform/plugin/framework/handling-data/types) would be represented as a `types.Map` of `types.Object` where the `types.Object` is a mapping of `attr` to `types.String`. + +A nested attribute type may itself contain further collection or nested attribute types, if necessary. + +In this example, a resource schema defines a top level required map nested attribute named `example_attribute` with a required list of strings attribute named `attr1` and an optional single nested attribute named `attr2`: + +```go +func (r ThingResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "example_attribute": schema.MapNestedAttribute{ + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "attr1": schema.ListAttribute{ + ElementType: types.StringType, + Required: true, + // ... potentially other fields ... + }, + "attr2": schema.SingleNestedAttribute{ + Attributes: map[string]schema.Attribute{ /* ... */ }, + Optional: true, + // ... potentially other fields ... + }, + }, + }, + Required: true, + // ... potentially other fields ... + }, + // ... potentially other attributes ... + }, + } +} +``` + +Its [value type](/terraform/plugin/framework/handling-data/types) would be represented as a `types.Map` of `types.Object` where the `types.Object` is a mapping of `attr1` to `types.List` of `types.String` and `attr2` to `types.Object`. + +### Configurability + + + +Only the map nested attribute itself is defined by the `schema.MapNestedAttribute` configurability fields. Nested attributes must define their own configurability fields within each attribute definition. + + + +At least one of the `Computed`, `Optional`, or `Required` fields must be set to `true`. This defines how Terraform and the framework should expect data to set, whether the value is from the practitioner configuration or from the provider logic, such as API response value. + +The acceptable behaviors of these configurability options are: + +- `Required` only: The value must be practitioner configured to an eventually known value (not null), otherwise the framework will automatically raise an error diagnostic for the missing value. +- `Optional` only: The value may be practitioner configured to a known value or null. +- `Optional` and `Computed`: The value may be practitioner configured or the value may be set in provider logic when the practitioner configuration is null. +- `Computed` only: The value will be set in provider logic and any practitioner configuration causes the framework to automatically raise an error diagnostic for the unexpected configuration value. + +### Custom Types + +You may want to build your own attribute value and type implementations to allow your provider to combine validation, description, and plan customization behaviors into a reusable bundle. This helps avoid duplication or reimplementation and ensures consistency. These implementations use the `CustomType` field in the attribute type. + +Refer to [Custom Types](/terraform/plugin/framework/handling-data/types/custom) for further details on creating provider-defined types and values. + +### Deprecation + +Set the `DeprecationMessage` field to a practitioner-focused message for how to handle the deprecation. The framework will automatically raise a warning diagnostic with this message if the practitioner configuration contains a known value for the attribute. Terraform version 1.2.7 and later will raise a warning diagnostic in certain scenarios if the deprecated attribute value is referenced elsewhere in a practitioner configuration. The framework [deprecations](/terraform/plugin/framework/deprecations) documentation fully describes the recommended practices for deprecating an attribute or resource. + +Some practitioner-focused examples of a deprecation message include: + +- Configure `other_attribute` instead. This attribute will be removed in the next major version of the provider. +- Remove this attribute's configuration as it no longer is used and the attribute will be removed in the next major version of the provider. + +### Description + +The framework provides two description fields, `Description` and `MarkdownDescription`, which various tools use to show additional information about an attribute and its intended purpose. This includes, but is not limited to, [`terraform-plugin-docs`](https://github.com/hashicorp/terraform-plugin-docs) for automated provider documentation generation and [`terraform-ls`](https://github.com/hashicorp/terraform-ls) for Terraform configuration editor integrations. + +### Plan Modification + + + +Only managed resources implement this concept. + + + +The framework provides two plan modification fields for managed resource attributes, `Default` and `PlanModifiers`, which define resource and attribute value planning behaviors. The resource [default](/terraform/plugin/framework/resources/default) and [plan modification](/terraform/plugin/framework/resources/plan-modification) documentation covers these features more in-depth. + +#### Common Use Case Plan Modification + +The [`mapdefault`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/mapdefault) package defines common use case `Default` implementations: + +- [`StaticValue(types.Map)`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/mapdefault#StaticValue): Define a static list default value for the attribute. + +The [`mapplanmodifier`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/mapplanmodifier) package defines common use case `PlanModifiers` implementations: + +- [`RequiresReplace()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/mapplanmodifier#RequiresReplace): Marks the resource for replacement if the resource is being updated and the plan value does not match the prior state value. +- [`RequiresReplaceIf()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/mapplanmodifier#RequiresReplaceIf): Similar to `RequiresReplace()`, but also checks if a given function returns true. +- [`RequiresReplaceIfConfigured()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/mapplanmodifier#RequiresReplaceIfConfigured): Similar to `RequiresReplace()`, but also checks if the configuration value is not null. +- [`UseStateForUnknown()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/mapplanmodifier#UseStateForUnknown): Copies a known prior state value into the planned value. Use this when it is known that an unconfigured value will remain the same after a resource update. + +### Sensitive + +Set the `Sensitive` field if the attribute value should always be considered [sensitive data](/terraform/language/state/sensitive-data). In Terraform, this will generally mask the value in practitioner output. This setting cannot be conditionally set and does not impact how data is stored in the state. + +### WriteOnly + + + + Only managed resources implement this concept. + + + +Set the `WriteOnly` field to define a [write-only argument](/terraform/plugin/framework/resources/write-only-arguments). +Write-only arguments can accept [ephemeral values](/terraform/language/resources/ephemeral) +and are not persisted in the Terraform plan or state artifacts. Write-only arguments are supported in Terraform 1.11 and later. + +If a nested attribute has the `WriteOnly` field set, all child attributes must also have `WriteOnly` set. + +### Validation + +Set the `Validators` field to define [validation](/terraform/plugin/framework/validation#attribute-validation). This validation logic is ran in addition to any validation contained within a [custom type](#custom-types). + +#### Common Use Case Validators + +HashiCorp provides the additional [`terraform-plugin-framework-validators`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework-validators) Go module which contains validation logic for common use cases. The [`mapvalidator`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework-validators/mapvalidator) package within that module has map attribute validators such as defining conflicting attributes. + +## Accessing Values + +The [accessing values](/terraform/plugin/framework/handling-data/accessing-values) documentation covers general methods for reading [schema](/terraform/plugin/framework/handling-data/schemas) (configuration, plan, and state) data, which is necessary before accessing an attribute value directly. The [map type](/terraform/plugin/framework/handling-data/types/map#accessing-values) documentation covers methods for interacting with the attribute value itself. + +## Setting Values + +The [map type](/terraform/plugin/framework/handling-data/types/map#setting-values) documentation covers methods for creating or setting the appropriate value. The [writing data](/terraform/plugin/framework/handling-data/writing-state) documentation covers general methods for writing [schema](/terraform/plugin/framework/handling-data/schemas) (plan and state) data, which is necessary afterwards. diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/attributes/map.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/attributes/map.mdx new file mode 100644 index 000000000..5c791e322 --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/attributes/map.mdx @@ -0,0 +1,160 @@ +--- +page_title: Map attributes +description: >- + Learn how to use map attributes with the Terraform plugin framework. +--- + +# Map attributes + +Map attributes store a mapping of arbitrary string keys to values of single element type. Values are represented by a [map type](/terraform/plugin/framework/handling-data/types/map) in the framework, containing elements of the element type. + +In this Terraform configuration example, a map of string attribute named `example_attribute` is set to the mapped values of `"key1"` to `"value1"` and `"key2"` to `"value2"`: + +```hcl +resource "examplecloud_thing" "example" { + example_attribute = { + "key1" = "value1" + "key2" = "value2" + } +} +``` + +## Schema Definition + +Use one of the following attribute types to directly add a map value to a [schema](/terraform/plugin/framework/handling-data/schemas) or [nested attribute type](/terraform/plugin/framework/handling-data/attributes#nested-attribute-types): + +| Schema Type | Attribute Type | +|-------------|----------------| +| [Data Source](/terraform/plugin/framework/data-sources) | [`schema.MapAttribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/datasource/schema#MapAttribute) | +| [Provider](/terraform/plugin/framework/providers) | [`schema.MapAttribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/provider/schema#MapAttribute) | +| [Resource](/terraform/plugin/framework/resources) | [`schema.MapAttribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema#MapAttribute) | +| [Ephemeral Resource](/terraform/plugin/framework/ephemeral-resources) | [`schema.MapAttribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/ephemeral/schema#MapAttribute) | + +The `ElementType` field must be defined, which represents the single [value type](/terraform/plugin/framework/handling-data/types) of every element of the map. + +In this example, a resource schema defines a top level required map of strings attribute named `example_attribute`: + +```go +func (r ThingResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "example_attribute": schema.MapAttribute{ + ElementType: types.StringType, + Required: true, + // ... potentially other fields ... + }, + // ... potentially other attributes ... + }, + } +} +``` + +An element type may itself contain further collection types, if necessary. + +In this example, a resource schema defines a top level required map of map of strings attribute named `example_attribute`: + +```go +func (r ThingResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "example_attribute": schema.MapAttribute{ + ElementType: types.MapType{ + ElemType: types.StringType, + }, + Required: true, + // ... potentially other fields ... + }, + // ... potentially other attributes ... + }, + } +} +``` + +If the map value should be the element type of a [collection attribute type](/terraform/plugin/framework/handling-data/attributes#collection-attribute-types), set the `ElementType` field according to the [map type](/terraform/plugin/framework/handling-data/types/map). Refer to the collection attribute type documentation for additional details. + +If the map value should be a value type of an [object attribute type](/terraform/plugin/framework/handling-data/attributes#object-attribute-type), set the `AttributeTypes` map value according to the [map type](/terraform/plugin/framework/handling-data/types/map). Refer to the object attribute type documentation for additional details. + +### Configurability + +At least one of the `Computed`, `Optional`, or `Required` fields must be set to `true`. This defines how Terraform and the framework should expect data to set, whether the value is from the practitioner configuration or from the provider logic, such as API response value. + +The acceptable behaviors of these configurability options are: + +- `Required` only: The value must be practitioner configured to an eventually known value (not null), otherwise the framework will automatically raise an error diagnostic for the missing value. +- `Optional` only: The value may be practitioner configured to a known value or null. +- `Optional` and `Computed`: The value may be practitioner configured or the value may be set in provider logic when the practitioner configuration is null. +- `Computed` only: The value will be set in provider logic and any practitioner configuration causes the framework to automatically raise an error diagnostic for the unexpected configuration value. + +### Custom Types + +You may want to build your own attribute value and type implementations to allow your provider to combine validation, description, and plan customization behaviors into a reusable bundle. This helps avoid duplication or reimplementation and ensures consistency. These implementations use the `CustomType` field in the attribute type. + +Refer to [Custom Types](/terraform/plugin/framework/handling-data/types/custom) for further details on creating provider-defined types and values. + +### Deprecation + +Set the `DeprecationMessage` field to a practitioner-focused message for how to handle the deprecation. The framework will automatically raise a warning diagnostic with this message if the practitioner configuration contains a known value for the attribute. Terraform version 1.2.7 and later will raise a warning diagnostic in certain scenarios if the deprecated attribute value is referenced elsewhere in a practitioner configuration. The framework [deprecations](/terraform/plugin/framework/deprecations) documentation fully describes the recommended practices for deprecating an attribute or resource. + +Some practitioner-focused examples of a deprecation message include: + +- Configure `other_attribute` instead. This attribute will be removed in the next major version of the provider. +- Remove this attribute's configuration as it no longer is used and the attribute will be removed in the next major version of the provider. + +### Description + +The framework provides two description fields, `Description` and `MarkdownDescription`, which various tools use to show additional information about an attribute and its intended purpose. This includes, but is not limited to, [`terraform-plugin-docs`](https://github.com/hashicorp/terraform-plugin-docs) for automated provider documentation generation and [`terraform-ls`](https://github.com/hashicorp/terraform-ls) for Terraform configuration editor integrations. + +### Plan Modification + + + +Only managed resources implement this concept. + + + +The framework provides two plan modification fields for managed resource attributes, `Default` and `PlanModifiers`, which define resource and attribute value planning behaviors. The resource [default](/terraform/plugin/framework/resources/default) and [plan modification](/terraform/plugin/framework/resources/plan-modification) documentation covers these features more in-depth. + +#### Common Use Case Plan Modification + +The [`mapdefault`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/mapdefault) package defines common use case `Default` implementations: + +- [`StaticValue(types.Map)`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/mapdefault#StaticValue): Define a static map default value for the attribute. + +The [`mapplanmodifier`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/mapplanmodifier) package defines common use case `PlanModifiers` implementations: + +- [`RequiresReplace()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/mapplanmodifier#RequiresReplace): Marks the resource for replacement if the resource is being updated and the plan value does not match the prior state value. +- [`RequiresReplaceIf()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/mapplanmodifier#RequiresReplaceIf): Similar to `RequiresReplace()`, but also checks if a given function returns true. +- [`RequiresReplaceIfConfigured()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/mapplanmodifier#RequiresReplaceIfConfigured): Similar to `RequiresReplace()`, but also checks if the configuration value is not null. +- [`UseStateForUnknown()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/mapplanmodifier#UseStateForUnknown): Copies a known prior state value into the planned value. Use this when it is known that an unconfigured value will remain the same after a resource update. + +### Sensitive + +Set the `Sensitive` field if the attribute value should always be considered [sensitive data](/terraform/language/state/sensitive-data). In Terraform, this will generally mask the value in practitioner output. This setting cannot be conditionally set and does not impact how data is stored in the state. + +### WriteOnly + + + + Only managed resources implement this concept. + + + +Set the `WriteOnly` field to define a [write-only argument](/terraform/plugin/framework/resources/write-only-arguments). +Write-only arguments can accept [ephemeral values](/terraform/language/resources/ephemeral) +and are not persisted in the Terraform plan or state artifacts. Write-only arguments are supported in Terraform 1.11 and later. + +### Validation + +Set the `Validators` field to define [validation](/terraform/plugin/framework/validation#attribute-validation). This validation logic is ran in addition to any validation contained within a [custom type](#custom-types). + +#### Common Use Case Validators + +HashiCorp provides the additional [`terraform-plugin-framework-validators`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework-validators) Go module which contains validation logic for common use cases. The [`mapvalidator`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework-validators/mapvalidator) package within that module has map attribute validators such as defining conflicting attributes. + +## Accessing Values + +The [accessing values](/terraform/plugin/framework/handling-data/accessing-values) documentation covers general methods for reading [schema](/terraform/plugin/framework/handling-data/schemas) (configuration, plan, and state) data, which is necessary before accessing an attribute value directly. The [map type](/terraform/plugin/framework/handling-data/types/map#accessing-values) documentation covers methods for interacting with the attribute value itself. + +## Setting Values + +The [map type](/terraform/plugin/framework/handling-data/types/map#setting-values) documentation covers methods for creating or setting the appropriate value. The [writing data](/terraform/plugin/framework/handling-data/writing-state) documentation covers general methods for writing [schema](/terraform/plugin/framework/handling-data/schemas) (plan and state) data, which is necessary afterwards. diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/attributes/number.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/attributes/number.mdx new file mode 100644 index 000000000..b3a40a049 --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/attributes/number.mdx @@ -0,0 +1,140 @@ +--- +page_title: Number attributes +description: >- + Learn how to use arbitrary precision number attributes with the Terraform + plugin framework. +--- + +# Number attributes + + + +Use [Float64 Attribute](/terraform/plugin/framework/handling-data/attributes/float64) for 64-bit floating point numbers. Use [Int64 Attribute](/terraform/plugin/framework/handling-data/attributes/int64) for 64-bit integer numbers. + + + +Number attributes store an arbitrary precision (generally over 64-bit, up to 512-bit) number. Values are represented by a [number type](/terraform/plugin/framework/handling-data/types/number) in the framework. + +In this Terraform configuration example, an number attribute named `example_attribute` is set to a value greater than 64 bits: + +```hcl +resource "examplecloud_thing" "example" { + example_attribute = pow(2, 64) + 1 +} +``` + +## Schema Definition + +Use one of the following attribute types to directly add a number value to a [schema](/terraform/plugin/framework/handling-data/schemas) or [nested attribute type](/terraform/plugin/framework/handling-data/attributes#nested-attribute-types): + +| Schema Type | Attribute Type | +|-------------|----------------| +| [Data Source](/terraform/plugin/framework/data-sources) | [`schema.NumberAttribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/datasource/schema#NumberAttribute) | +| [Provider](/terraform/plugin/framework/providers) | [`schema.NumberAttribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/provider/schema#NumberAttribute) | +| [Resource](/terraform/plugin/framework/resources) | [`schema.NumberAttribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema#NumberAttribute) | +| [Ephemeral Resource](/terraform/plugin/framework/ephemeral-resources) | [`schema.NumberAttribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/ephemeral/schema#NumberAttribute) | + +In this example, a resource schema defines a top level required number attribute named `example_attribute`: + +```go +func (r ThingResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "example_attribute": schema.NumberAttribute{ + Required: true, + // ... potentially other fields ... + }, + // ... potentially other attributes ... + }, + } +} +``` + +If the number value should be the element type of a [collection attribute type](/terraform/plugin/framework/handling-data/attributes#collection-attribute-types), set the `ElementType` field according to the [number type](/terraform/plugin/framework/handling-data/types/number). Refer to the collection attribute type documentation for additional details. + +If the number value should be a value type of an [object attribute type](/terraform/plugin/framework/handling-data/attributes#object-attribute-type), set the `AttributeTypes` map value according to the [number type](/terraform/plugin/framework/handling-data/types/number). Refer to the object attribute type documentation for additional details. + +### Configurability + +At least one of the `Computed`, `Optional`, or `Required` fields must be set to `true`. This defines how Terraform and the framework should expect data to set, whether the value is from the practitioner configuration or from the provider logic, such as API response value. + +The acceptable behaviors of these configurability options are: + +- `Required` only: The value must be practitioner configured to an eventually known value (not null), otherwise the framework will automatically raise an error diagnostic for the missing value. +- `Optional` only: The value may be practitioner configured to a known value or null. +- `Optional` and `Computed`: The value may be practitioner configured or the value may be set in provider logic when the practitioner configuration is null. +- `Computed` only: The value will be set in provider logic and any practitioner configuration causes the framework to automatically raise an error diagnostic for the unexpected configuration value. + +### Custom Types + +You may want to build your own attribute value and type implementations to allow your provider to combine validation, description, and plan customization behaviors into a reusable bundle. This helps avoid duplication or reimplementation and ensures consistency. These implementations use the `CustomType` field in the attribute type. + +Refer to [Custom Types](/terraform/plugin/framework/handling-data/types/custom) for further details on creating provider-defined types and values. + +### Deprecation + +Set the `DeprecationMessage` field to a practitioner-focused message for how to handle the deprecation. The framework will automatically raise a warning diagnostic with this message if the practitioner configuration contains a known value for the attribute. Terraform version 1.2.7 and later will raise a warning diagnostic in certain scenarios if the deprecated attribute value is referenced elsewhere in a practitioner configuration. The framework [deprecations](/terraform/plugin/framework/deprecations) documentation fully describes the recommended practices for deprecating an attribute or resource. + +Some practitioner-focused examples of a deprecation message include: + +- Configure `other_attribute` instead. This attribute will be removed in the next major version of the provider. +- Remove this attribute's configuration as it no longer is used and the attribute will be removed in the next major version of the provider. + +### Description + +The framework provides two description fields, `Description` and `MarkdownDescription`, which various tools use to show additional information about an attribute and its intended purpose. This includes, but is not limited to, [`terraform-plugin-docs`](https://github.com/hashicorp/terraform-plugin-docs) for automated provider documentation generation and [`terraform-ls`](https://github.com/hashicorp/terraform-ls) for Terraform configuration editor integrations. + +### Plan Modification + + + +Only managed resources implement this concept. + + + +The framework provides two plan modification fields for managed resource attributes, `Default` and `PlanModifiers`, which define resource and attribute value planning behaviors. The resource [default](/terraform/plugin/framework/resources/default) and [plan modification](/terraform/plugin/framework/resources/plan-modification) documentation covers these features more in-depth. + +#### Common Use Case Plan Modification + +The [`numberdefault`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/numberdefault) package defines common use case `Default` implementations: + +- [`StaticNumber(number)`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/numberdefault#StaticNumber): Define a static number default value for the attribute. + +The [`numberplanmodifier`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/numberplanmodifier) package defines common use case `PlanModifiers` implementations: + +- [`RequiresReplace()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/numberplanmodifier#RequiresReplace): Marks the resource for replacement if the resource is being updated and the plan value does not match the prior state value. +- [`RequiresReplaceIf()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/numberplanmodifier#RequiresReplaceIf): Similar to `RequiresReplace()`, but also checks if a given function returns true. +- [`RequiresReplaceIfConfigured()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/numberplanmodifier#RequiresReplaceIfConfigured): Similar to `RequiresReplace()`, but also checks if the configuration value is not null. +- [`UseStateForUnknown()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/numberplanmodifier#UseStateForUnknown): Copies a known prior state value into the planned value. Use this when it is known that an unconfigured value will remain the same after a resource update. + +### Sensitive + +Set the `Sensitive` field if the attribute value should always be considered [sensitive data](/terraform/language/state/sensitive-data). In Terraform, this will generally mask the value in practitioner output. This setting cannot be conditionally set and does not impact how data is stored in the state. + +### WriteOnly + + + + Only managed resources implement this concept. + + + +Set the `WriteOnly` field to define a [write-only argument](/terraform/plugin/framework/resources/write-only-arguments). +Write-only arguments can accept [ephemeral values](/terraform/language/resources/ephemeral) +and are not persisted in the Terraform plan or state artifacts. Write-only arguments are supported in Terraform 1.11 and later. + +### Validation + +Set the `Validators` field to define [validation](/terraform/plugin/framework/validation#attribute-validation). This validation logic is ran in addition to any validation contained within a [custom type](#custom-types). + +#### Common Use Case Validators + +HashiCorp provides the additional [`terraform-plugin-framework-validators`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework-validators) Go module which contains validation logic for common use cases. The [`numbervalidator`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework-validators/numbervalidator) package within that module has number attribute validators such as minimum, maximum, and defining conflicting attributes. + +## Accessing Values + +The [accessing values](/terraform/plugin/framework/handling-data/accessing-values) documentation covers general methods for reading [schema](/terraform/plugin/framework/handling-data/schemas) (configuration, plan, and state) data, which is necessary before accessing an attribute value directly. The [number type](/terraform/plugin/framework/handling-data/types/number#accessing-values) documentation covers methods for interacting with the attribute value itself. + +## Setting Values + +The [number type](/terraform/plugin/framework/handling-data/types/number#setting-values) documentation covers methods for creating or setting the appropriate value. The [writing data](/terraform/plugin/framework/handling-data/writing-state) documentation covers general methods for writing [schema](/terraform/plugin/framework/handling-data/schemas) (plan and state) data, which is necessary afterwards. diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/attributes/object.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/attributes/object.mdx new file mode 100644 index 000000000..a2328540e --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/attributes/object.mdx @@ -0,0 +1,194 @@ +--- +page_title: Object attributes +description: >- + Learn how to use object attributes with the Terraform plugin framework. +--- + +# Object attributes + + + +Use [nested attribute types](/terraform/plugin/framework/handling-data/attributes#nested-attribute-types) instead of object attribute types where possible. Object attributes have limited utility as they can only define type information. A [single nested attribute type](/terraform/plugin/framework/handling-data/attributes/single-nested) supports the same configuration syntax as an object while each nested attribute can be fully defined in terms of configurability, validation, etc. + + + +Object attributes are a single structure mapping explicit attribute names to type definitions. Values are represented by a [object type](/terraform/plugin/framework/handling-data/types/object) in the framework, containing sub-attribute values of the mapped types. + +In this Terraform configuration example, an object attribute named `example_attribute` is set to the mapped values of `attr1` to `"value1"` and `attr2` to `123`: + +```hcl +resource "examplecloud_thing" "example" { + example_attribute = { + attr1 = "value1" + attr2 = 123 + } +} +``` + +## Schema Definition + +Use one of the following attribute types to directly add a map value to a [schema](/terraform/plugin/framework/handling-data/schemas) or [nested attribute type](/terraform/plugin/framework/handling-data/attributes#nested-attribute-types): + +| Schema Type | Attribute Type | +|-------------|----------------| +| [Data Source](/terraform/plugin/framework/data-sources) | [`schema.ObjectAttribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/datasource/schema#ObjectAttribute) | +| [Provider](/terraform/plugin/framework/providers) | [`schema.ObjectAttribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/provider/schema#ObjectAttribute) | +| [Resource](/terraform/plugin/framework/resources) | [`schema.ObjectAttribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema#ObjectAttribute) | +| [Ephemeral Resource](/terraform/plugin/framework/ephemeral-resources) | [`schema.ObjectAttribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/ephemeral/schema#ObjectAttribute) | + +The `AttributeTypes` field must be defined, which represents the mapping of explicit string object attribute names to [value types](/terraform/plugin/framework/handling-data/types). + +In this example, a resource schema defines a top level required object attribute named `example_attribute` with a string sub-attribute named `attr1` and integer sub-attribute named `attr2`: + +```go +func (r ThingResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "example_attribute": schema.ObjectAttribute{ + AttributeTypes: map[string]attr.Type{ + "attr1": types.StringType, + "attr2": types.Int64Type, + }, + Required: true, + // ... potentially other fields ... + }, + // ... potentially other attributes ... + }, + } +} +``` + +A sub-attribute type may itself contain further collection types, if necessary. + +In this example, a resource schema defines a top level required object attribute named `example_attribute` with a list of strings sub-attribute named `attr1` and integer sub-attribute named `attr2`: + +```go +func (r ThingResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "example_attribute": schema.ObjectAttribute{ + AttributeTypes: map[string]attr.Type{ + "attr1": types.ListType{ + ElemType: types.StringType, + }, + "attr2": types.Int64Type, + }, + Required: true, + // ... potentially other fields ... + }, + // ... potentially other attributes ... + }, + } +} +``` + +If the object value should be the element type of a [collection attribute type](/terraform/plugin/framework/handling-data/attributes#collection-attribute-types), set the `ElementType` field according to the [object type](/terraform/plugin/framework/handling-data/types/object). Refer to the collection attribute type documentation for additional details. + +### Configurability + + + +Only the object attribute itself, not individual sub-attributes, can define its configurability. Use [nested attribute types](/terraform/plugin/framework/handling-data/attributes#nested-attribute-types) for full control of nested attribute capabilities. + + + +At least one of the `Computed`, `Optional`, or `Required` fields must be set to `true`. This defines how Terraform and the framework should expect data to set, whether the value is from the practitioner configuration or from the provider logic, such as API response value. + +The acceptable behaviors of these configurability options are: + +- `Required` only: The value must be practitioner configured to an eventually known value (not null), otherwise the framework will automatically raise an error diagnostic for the missing value. +- `Optional` only: The value may be practitioner configured to a known value or null. +- `Optional` and `Computed`: The value may be practitioner configured or the value may be set in provider logic when the practitioner configuration is null. +- `Computed` only: The value will be set in provider logic and any practitioner configuration causes the framework to automatically raise an error diagnostic for the unexpected configuration value. + +### Custom Types + +You may want to build your own attribute value and type implementations to allow your provider to combine validation, description, and plan customization behaviors into a reusable bundle. This helps avoid duplication or reimplementation and ensures consistency. These implementations use the `CustomType` field in the attribute type. + +Refer to [Custom Types](/terraform/plugin/framework/handling-data/types/custom) for further details on creating provider-defined types and values. + +### Deprecation + + + +Only the object attribute itself, not individual sub-attributes, can define deprecation. Use [nested attribute types](/terraform/plugin/framework/handling-data/attributes#nested-attribute-types) for full control of nested attribute capabilities. + + + +Set the `DeprecationMessage` field to a practitioner-focused message for how to handle the deprecation. The framework will automatically raise a warning diagnostic with this message if the practitioner configuration contains a known value for the attribute. Terraform version 1.2.7 and later will raise a warning diagnostic in certain scenarios if the deprecated attribute value is referenced elsewhere in a practitioner configuration. The framework [deprecations](/terraform/plugin/framework/deprecations) documentation fully describes the recommended practices for deprecating an attribute or resource. + +Some practitioner-focused examples of a deprecation message include: + +- Configure `other_attribute` instead. This attribute will be removed in the next major version of the provider. +- Remove this attribute's configuration as it no longer is used and the attribute will be removed in the next major version of the provider. + +### Description + + + +Only the object attribute itself, not individual sub-attributes, can define its description. Use [nested attribute types](/terraform/plugin/framework/handling-data/attributes#nested-attribute-types) for full control of nested attribute capabilities. + + + +The framework provides two description fields, `Description` and `MarkdownDescription`, which various tools use to show additional information about an attribute and its intended purpose. This includes, but is not limited to, [`terraform-plugin-docs`](https://github.com/hashicorp/terraform-plugin-docs) for automated provider documentation generation and [`terraform-ls`](https://github.com/hashicorp/terraform-ls) for Terraform configuration editor integrations. + +### Plan Modification + + + +Only managed resources implement this concept. + + + +The framework provides two plan modification fields for managed resource attributes, `Default` and `PlanModifiers`, which define resource and attribute value planning behaviors. The resource [default](/terraform/plugin/framework/resources/default) and [plan modification](/terraform/plugin/framework/resources/plan-modification) documentation covers these features more in-depth. + +#### Common Use Case Plan Modification + +The [`objectdefault`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/objectdefault) package defines common use case `Default` implementations: + +- [`StaticValue(types.Object)`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/objectdefault#StaticValue): Define a static object default value for the attribute. + +The [`objectplanmodifier`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/objectplanmodifier) package defines common use case `PlanModifiers` implementations: + +- [`RequiresReplace()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/objectplanmodifier#RequiresReplace): Marks the resource for replacement if the resource is being updated and the plan value does not match the prior state value. +- [`RequiresReplaceIf()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/objectplanmodifier#RequiresReplaceIf): Similar to `RequiresReplace()`, but also checks if a given function returns true. +- [`RequiresReplaceIfConfigured()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/objectplanmodifier#RequiresReplaceIfConfigured): Similar to `RequiresReplace()`, but also checks if the configuration value is not null. +- [`UseStateForUnknown()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/objectplanmodifier#UseStateForUnknown): Copies a known prior state value into the planned value. Use this when it is known that an unconfigured value will remain the same after a resource update. + +### Sensitive + + + +Only the object attribute itself, not individual sub-attributes, can define its sensitivity. Use [nested attribute types](/terraform/plugin/framework/handling-data/attributes#nested-attribute-types) for full control of nested attribute capabilities. + + + +Set the `Sensitive` field if the attribute value should always be considered [sensitive data](/terraform/language/state/sensitive-data). In Terraform, this will generally mask the value in practitioner output. This setting cannot be conditionally set and does not impact how data is stored in the state. + +### WriteOnly + + + + Only managed resources implement this concept. + + + +Set the `WriteOnly` field to define a [write-only argument](/terraform/plugin/framework/resources/write-only-arguments). +Write-only arguments can accept [ephemeral values](/terraform/language/resources/ephemeral) +and are not persisted in the Terraform plan or state artifacts. Write-only arguments are supported in Terraform 1.11 and later. + +### Validation + +Set the `Validators` field to define [validation](/terraform/plugin/framework/validation#attribute-validation). This validation logic is ran in addition to any validation contained within a [custom type](#custom-types). + +#### Common Use Case Validators + +HashiCorp provides the additional [`terraform-plugin-framework-validators`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework-validators) Go module which contains validation logic for common use cases. The [`objectvalidator`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework-validators/objectvalidator) package within that module has map attribute validators such as defining conflicting attributes. + +## Accessing Values + +The [accessing values](/terraform/plugin/framework/handling-data/accessing-values) documentation covers general methods for reading [schema](/terraform/plugin/framework/handling-data/schemas) (configuration, plan, and state) data, which is necessary before accessing an attribute value directly. The [object type](/terraform/plugin/framework/handling-data/types/object#accessing-values) documentation covers methods for interacting with the attribute value itself. + +## Setting Values + +The [object type](/terraform/plugin/framework/handling-data/types/object#setting-values) documentation covers methods for creating or setting the appropriate value. The [writing data](/terraform/plugin/framework/handling-data/writing-state) documentation covers general methods for writing [schema](/terraform/plugin/framework/handling-data/schemas) (plan and state) data, which is necessary afterwards. diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/attributes/set-nested.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/attributes/set-nested.mdx new file mode 100644 index 000000000..8a54622d4 --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/attributes/set-nested.mdx @@ -0,0 +1,176 @@ +--- +page_title: Set nested attributes +description: >- + Learn how to use set nested attributes with the Terraform plugin framework. +--- + +# Set nested attributes + +Set nested attributes store a unique, unordered collection of nested objects. Values are represented by a [set type](/terraform/plugin/framework/handling-data/types/set) in the framework, containing elements of [object type](/terraform/plugin/framework/handling-data/types/object). + +In this Terraform configuration example, a set nested attribute named `example_attribute` is set to the unordered object values of `attr` to `"one"` and `attr` to `"two"`: + +```hcl +resource "examplecloud_thing" "example" { + example_attribute = [ + { + attr = "one" + }, + { + attr = "two" + }, + ] +} +``` + +## Schema Definition + +Use one of the following attribute types to directly add a set nested value to a [schema](/terraform/plugin/framework/handling-data/schemas) or [nested attribute type](/terraform/plugin/framework/handling-data/attributes#nested-attribute-types): + +| Schema Type | Attribute Type | +|-------------|----------------| +| [Data Source](/terraform/plugin/framework/data-sources) | [`schema.SetNestedAttribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/datasource/schema#SetNestedAttribute) | +| [Provider](/terraform/plugin/framework/providers) | [`schema.SetNestedAttribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/provider/schema#SetNestedAttribute) | +| [Resource](/terraform/plugin/framework/resources) | [`schema.SetNestedAttribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema#SetNestedAttribute) | +| [Ephemeral Resource](/terraform/plugin/framework/ephemeral-resources) | [`schema.SetNestedAttribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/ephemeral/schema#SetNestedAttribute) | + +The `NestedObject` field must be defined, which represents the [object value type](/terraform/plugin/framework/handling-data/types/object) of every element of the set. + +In this example, a resource schema defines a top level required set nested attribute named `example_attribute` with a required string attribute named `attr`: + +```go +func (r ThingResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "example_attribute": schema.SetNestedAttribute{ + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "attr": schema.StringAttribute{ + Required: true, + // ... potentially other fields ... + }, + }, + }, + Required: true, + // ... potentially other fields ... + }, + // ... potentially other attributes ... + }, + } +} +``` + +Its [value type](/terraform/plugin/framework/handling-data/types) would be represented as a `types.Set` of `types.Object` where the `types.Object` is a mapping of `attr` to `types.String`. + +A nested attribute type may itself contain further collection or nested attribute types, if necessary. + +In this example, a resource schema defines a top level required set nested attribute named `example_attribute` with a required list of strings attribute named `attr1` and an optional single nested attribute named `attr2`: + +```go +func (r ThingResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "example_attribute": schema.SetNestedAttribute{ + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "attr1": schema.ListAttribute{ + ElementType: types.StringType, + Required: true, + // ... potentially other fields ... + }, + "attr2": schema.SingleNestedAttribute{ + Attributes: map[string]schema.Attribute{ /* ... */ }, + Optional: true, + // ... potentially other fields ... + }, + }, + }, + Required: true, + // ... potentially other fields ... + }, + // ... potentially other attributes ... + }, + } +} +``` + +Its [value type](/terraform/plugin/framework/handling-data/types) would be represented as a `types.Set` of `types.Object` where the `types.Object` is a mapping of `attr1` to `types.List` of `types.String` and `attr2` to `types.Object`. + +### Configurability + + + +Only the set nested attribute itself is defined by the `schema.SetNestedAttribute` configurability fields. Nested attributes must define their own configurability fields within each attribute definition. + + + +At least one of the `Computed`, `Optional`, or `Required` fields must be set to `true`. This defines how Terraform and the framework should expect data to set, whether the value is from the practitioner configuration or from the provider logic, such as API response value. + +The acceptable behaviors of these configurability options are: + +- `Required` only: The value must be practitioner configured to an eventually known value (not null), otherwise the framework will automatically raise an error diagnostic for the missing value. +- `Optional` only: The value may be practitioner configured to a known value or null. +- `Optional` and `Computed`: The value may be practitioner configured or the value may be set in provider logic when the practitioner configuration is null. +- `Computed` only: The value will be set in provider logic and any practitioner configuration causes the framework to automatically raise an error diagnostic for the unexpected configuration value. + +### Custom Types + +You may want to build your own attribute value and type implementations to allow your provider to combine validation, description, and plan customization behaviors into a reusable bundle. This helps avoid duplication or reimplementation and ensures consistency. These implementations use the `CustomType` field in the attribute type. + +Refer to [Custom Types](/terraform/plugin/framework/handling-data/types/custom) for further details on creating provider-defined types and values. + +### Deprecation + +Set the `DeprecationMessage` field to a practitioner-focused message for how to handle the deprecation. The framework will automatically raise a warning diagnostic with this message if the practitioner configuration contains a known value for the attribute. Terraform version 1.2.7 and later will raise a warning diagnostic in certain scenarios if the deprecated attribute value is referenced elsewhere in a practitioner configuration. The framework [deprecations](/terraform/plugin/framework/deprecations) documentation fully describes the recommended practices for deprecating an attribute or resource. + +Some practitioner-focused examples of a deprecation message include: + +- Configure `other_attribute` instead. This attribute will be removed in the next major version of the provider. +- Remove this attribute's configuration as it no longer is used and the attribute will be removed in the next major version of the provider. + +### Description + +The framework provides two description fields, `Description` and `MarkdownDescription`, which various tools use to show additional information about an attribute and its intended purpose. This includes, but is not limited to, [`terraform-plugin-docs`](https://github.com/hashicorp/terraform-plugin-docs) for automated provider documentation generation and [`terraform-ls`](https://github.com/hashicorp/terraform-ls) for Terraform configuration editor integrations. + +### Plan Modification + + + +Only managed resources implement this concept. + + + +The framework provides two plan modification fields for managed resource attributes, `Default` and `PlanModifiers`, which define resource and attribute value planning behaviors. The resource [default](/terraform/plugin/framework/resources/default) and [plan modification](/terraform/plugin/framework/resources/plan-modification) documentation covers these features more in-depth. + +#### Common Use Case Plan Modification + +The [`setdefault`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/setdefault) package defines common use case `Default` implementations: + +- [`StaticValue(types.List)`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/setdefault#StaticValue): Define a static set default value for the attribute. + +The [`setplanmodifier`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/setplanmodifier) package defines common use case `PlanModifiers` implementations: + +- [`RequiresReplace()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/setplanmodifier#RequiresReplace): Marks the resource for replacement if the resource is being updated and the plan value does not match the prior state value. +- [`RequiresReplaceIf()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/setplanmodifier#RequiresReplaceIf): Similar to `RequiresReplace()`, but also checks if a given function returns true. +- [`RequiresReplaceIfConfigured()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/setplanmodifier#RequiresReplaceIfConfigured): Similar to `RequiresReplace()`, but also checks if the configuration value is not null. +- [`UseStateForUnknown()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/setplanmodifier#UseStateForUnknown): Copies a known prior state value into the planned value. Use this when it is known that an unconfigured value will remain the same after a resource update. + +### Sensitive + +Set the `Sensitive` field if the attribute value should always be considered [sensitive data](/terraform/language/state/sensitive-data). In Terraform, this will generally mask the value in practitioner output. This setting cannot be conditionally set and does not impact how data is stored in the state. + +### Validation + +Set the `Validators` field to define [validation](/terraform/plugin/framework/validation#attribute-validation). This validation logic is ran in addition to any validation contained within a [custom type](#custom-types). + +#### Common Use Case Validators + +HashiCorp provides the additional [`terraform-plugin-framework-validators`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework-validators) Go module which contains validation logic for common use cases. The [`setvalidator`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework-validators/setvalidator) package within that module has set attribute validators such as defining conflicting attributes. + +## Accessing Values + +The [accessing values](/terraform/plugin/framework/handling-data/accessing-values) documentation covers general methods for reading [schema](/terraform/plugin/framework/handling-data/schemas) (configuration, plan, and state) data, which is necessary before accessing an attribute value directly. The [set type](/terraform/plugin/framework/handling-data/types/set#accessing-values) documentation covers methods for interacting with the attribute value itself. + +## Setting Values + +The [set type](/terraform/plugin/framework/handling-data/types/set#setting-values) documentation covers methods for creating or setting the appropriate value. The [writing data](/terraform/plugin/framework/handling-data/writing-state) documentation covers general methods for writing [schema](/terraform/plugin/framework/handling-data/schemas) (plan and state) data, which is necessary afterwards. diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/attributes/set.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/attributes/set.mdx new file mode 100644 index 000000000..01c612540 --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/attributes/set.mdx @@ -0,0 +1,145 @@ +--- +page_title: Set attributes +description: >- + Learn how to use set attributes with the Terraform plugin framework. +--- + +# Set attributes + +Set attributes store an unique, unordered collection of single element type. Values are represented by a [set type](/terraform/plugin/framework/handling-data/types/set) in the framework, containing elements of the element type. + +In this Terraform configuration example, a set of string attribute named `example_attribute` is set to the unordered values `"one"` and `"two"`: + +```hcl +resource "examplecloud_thing" "example" { + example_attribute = ["one", "two"] +} +``` + +## Schema Definition + +Use one of the following attribute types to directly add a set value to a [schema](/terraform/plugin/framework/handling-data/schemas) or [nested attribute type](/terraform/plugin/framework/handling-data/attributes#nested-attribute-types): + +| Schema Type | Attribute Type | +|-------------|----------------| +| [Data Source](/terraform/plugin/framework/data-sources) | [`schema.SetAttribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/datasource/schema#SetAttribute) | +| [Provider](/terraform/plugin/framework/providers) | [`schema.SetAttribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/provider/schema#SetAttribute) | +| [Resource](/terraform/plugin/framework/resources) | [`schema.SetAttribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema#SetAttribute) | +| [Ephemeral Resource](/terraform/plugin/framework/ephemeral-resources) | [`schema.SetAttribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/ephemeral/schema#SetAttribute) | + +The `ElementType` field must be defined, which represents the single [value type](/terraform/plugin/framework/handling-data/types) of every element of the set. + +In this example, a resource schema defines a top level required set of strings attribute named `example_attribute`: + +```go +func (r ThingResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "example_attribute": schema.SetAttribute{ + ElementType: types.StringType, + Required: true, + // ... potentially other fields ... + }, + // ... potentially other attributes ... + }, + } +} +``` + +An element type may itself contain further collection types, if necessary. + +In this example, a resource schema defines a top level required set of set of strings attribute named `example_attribute`: + +```go +func (r ThingResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "example_attribute": schema.SetAttribute{ + ElementType: types.SetType{ + ElemType: types.StringType, + }, + Required: true, + // ... potentially other fields ... + }, + // ... potentially other attributes ... + }, + } +} +``` + +If the set value should be the element type of a [collection attribute type](/terraform/plugin/framework/handling-data/attributes#collection-attribute-types), set the `ElementType` field according to the [set type](/terraform/plugin/framework/handling-data/types/set). Refer to the collection attribute type documentation for additional details. + +If the set value should be a value type of an [object attribute type](/terraform/plugin/framework/handling-data/attributes#object-attribute-type), set the `AttributeTypes` map value according to the [set type](/terraform/plugin/framework/handling-data/types/set). Refer to the object attribute type documentation for additional details. + +### Configurability + +At least one of the `Computed`, `Optional`, or `Required` fields must be set to `true`. This defines how Terraform and the framework should expect data to set, whether the value is from the practitioner configuration or from the provider logic, such as API response value. + +The acceptable behaviors of these configurability options are: + +- `Required` only: The value must be practitioner configured to an eventually known value (not null), otherwise the framework will automatically raise an error diagnostic for the missing value. +- `Optional` only: The value may be practitioner configured to a known value or null. +- `Optional` and `Computed`: The value may be practitioner configured or the value may be set in provider logic when the practitioner configuration is null. +- `Computed` only: The value will be set in provider logic and any practitioner configuration causes the framework to automatically raise an error diagnostic for the unexpected configuration value. + +### Custom Types + +You may want to build your own attribute value and type implementations to allow your provider to combine validation, description, and plan customization behaviors into a reusable bundle. This helps avoid duplication or reimplementation and ensures consistency. These implementations use the `CustomType` field in the attribute type. + +Refer to [Custom Types](/terraform/plugin/framework/handling-data/types/custom) for further details on creating provider-defined types and values. + +### Deprecation + +Set the `DeprecationMessage` field to a practitioner-focused message for how to handle the deprecation. The framework will automatically raise a warning diagnostic with this message if the practitioner configuration contains a known value for the attribute. Terraform version 1.2.7 and later will raise a warning diagnostic in certain scenarios if the deprecated attribute value is referenced elsewhere in a practitioner configuration. The framework [deprecations](/terraform/plugin/framework/deprecations) documentation fully describes the recommended practices for deprecating an attribute or resource. + +Some practitioner-focused examples of a deprecation message include: + +- Configure `other_attribute` instead. This attribute will be removed in the next major version of the provider. +- Remove this attribute's configuration as it no longer is used and the attribute will be removed in the next major version of the provider. + +### Description + +The framework provides two description fields, `Description` and `MarkdownDescription`, which various tools use to show additional information about an attribute and its intended purpose. This includes, but is not limited to, [`terraform-plugin-docs`](https://github.com/hashicorp/terraform-plugin-docs) for automated provider documentation generation and [`terraform-ls`](https://github.com/hashicorp/terraform-ls) for Terraform configuration editor integrations. + +### Plan Modification + + + +Only managed resources implement this concept. + + + +The framework provides two plan modification fields for managed resource attributes, `Default` and `PlanModifiers`, which define resource and attribute value planning behaviors. The resource [default](/terraform/plugin/framework/resources/default) and [plan modification](/terraform/plugin/framework/resources/plan-modification) documentation covers these features more in-depth. + +#### Common Use Case Plan Modification + +The [`setdefault`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/setdefault) package defines common use case `Default` implementations: + +- [`StaticValue(types.Set)`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/setdefault#StaticValue): Define a static set default value for the attribute. + +The [`setplanmodifier`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/setplanmodifier) package defines common use case `PlanModifiers` implementations: + +- [`RequiresReplace()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/setplanmodifier#RequiresReplace): Marks the resource for replacement if the resource is being updated and the plan value does not match the prior state value. +- [`RequiresReplaceIf()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/setplanmodifier#RequiresReplaceIf): Similar to `RequiresReplace()`, but also checks if a given function returns true. +- [`RequiresReplaceIfConfigured()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/setplanmodifier#RequiresReplaceIfConfigured): Similar to `RequiresReplace()`, but also checks if the configuration value is not null. +- [`UseStateForUnknown()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/setplanmodifier#UseStateForUnknown): Copies a known prior state value into the planned value. Use this when it is known that an unconfigured value will remain the same after a resource update. + +### Sensitive + +Set the `Sensitive` field if the attribute value should always be considered [sensitive data](/terraform/language/state/sensitive-data). In Terraform, this will generally mask the value in practitioner output. This setting cannot be conditionally set and does not impact how data is stored in the state. + +### Validation + +Set the `Validators` field to define [validation](/terraform/plugin/framework/validation#attribute-validation). This validation logic is ran in addition to any validation contained within a [custom type](#custom-types). + +#### Common Use Case Validators + +HashiCorp provides the additional [`terraform-plugin-framework-validators`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework-validators) Go module which contains validation logic for common use cases. The [`setvalidator`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework-validators/setvalidator) package within that module has set attribute validators such as defining conflicting attributes. + +## Accessing Values + +The [accessing values](/terraform/plugin/framework/handling-data/accessing-values) documentation covers general methods for reading [schema](/terraform/plugin/framework/handling-data/schemas) (configuration, plan, and state) data, which is necessary before accessing an attribute value directly. The [set type](/terraform/plugin/framework/handling-data/types/set#accessing-values) documentation covers methods for interacting with the attribute value itself. + +## Setting Values + +The [set type](/terraform/plugin/framework/handling-data/types/set#setting-values) documentation covers methods for creating or setting the appropriate value. The [writing data](/terraform/plugin/framework/handling-data/writing-state) documentation covers general methods for writing [schema](/terraform/plugin/framework/handling-data/schemas) (plan and state) data, which is necessary afterwards. diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/attributes/single-nested.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/attributes/single-nested.mdx new file mode 100644 index 000000000..fe483016a --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/attributes/single-nested.mdx @@ -0,0 +1,186 @@ +--- +page_title: Single nested attributes +description: >- + Learn how to use single nested attributes with the Terraform plugin framework. +--- + +# Single nested attributes + +Single nested attributes are a single structure mapping explicit attribute names to nested attribute definitions. Values are represented by a [object type](/terraform/plugin/framework/handling-data/types/object) in the framework, containing nested attribute values of the mapped attributes. + +In this Terraform configuration example, a single nested attribute named `example_attribute` is set to the mapped values of `attr1` to `"value1"` and `attr2` to `123`: + +```hcl +resource "examplecloud_thing" "example" { + example_attribute = { + attr1 = "value1" + attr2 = 123 + } +} +``` + +## Schema Definition + +Use one of the following attribute types to directly add a single nested value to a [schema](/terraform/plugin/framework/handling-data/schemas) or [nested attribute type](/terraform/plugin/framework/handling-data/attributes#nested-attribute-types): + +| Schema Type | Attribute Type | +|-------------|----------------| +| [Data Source](/terraform/plugin/framework/data-sources) | [`schema.SingleNestedAttribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/datasource/schema#SingleNestedAttribute) | +| [Provider](/terraform/plugin/framework/providers) | [`schema.SingleNestedAttribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/provider/schema#SingleNestedAttribute) | +| [Resource](/terraform/plugin/framework/resources) | [`schema.SingleNestedAttribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema#SingleNestedAttribute) | +| [Ephemeral Resource](/terraform/plugin/framework/ephemeral-resources) | [`schema.SingleNestedAttribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/ephemeral/schema#SingleNestedAttribute) | + +In most use cases, the `Attributes` field should be defined, which represents the mapping of explicit string attribute names to nested attributes. + +In this example, a resource schema defines a top level required single nested attribute named `example_attribute` with a required string attribute named `attr1` and optional integer attribute named `attr2`: + +```go +func (r ThingResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "example_attribute": schema.SingleNestedAttribute{ + Attributes: map[string]schema.Attribute{ + "attr1": schema.StringAttribute{ + Required: true, + // ... potentially other fields ... + }, + "attr2": schema.Int64Attribute{ + Optional: true, + // ... potentially other fields ... + }, + }, + Required: true, + // ... potentially other fields ... + }, + // ... potentially other attributes ... + }, + } +} +``` + +Its [value type](/terraform/plugin/framework/handling-data/types) would be represented as a `types.Object` with a mapping of `attr1` to `types.String` and `attr2` to `types.Int64`. + +A nested attribute type may itself contain further collection or nested attribute types, if necessary. + +In this example, a resource schema defines a top level required single nested attribute named `example_attribute` with a required list of strings attribute named `attr1` and an optional single attribute named `attr2`: + +```go +func (r ThingResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "example_attribute": schema.SingleNestedAttribute{ + Attributes: map[string]schema.Attribute{ + "attr1": schema.ListAttribute{ + ElementType: types.StringType, + Required: true, + // ... potentially other fields ... + }, + "attr2": schema.SingleNestedAttribute{ + Attributes: map[string]schema.Attribute{ /* ... */ }, + Optional: true, + // ... potentially other fields ... + }, + }, + Required: true, + // ... potentially other fields ... + }, + // ... potentially other attributes ... + }, + } +} +``` + +Its [value type](/terraform/plugin/framework/handling-data/types) would be represented as a `types.Object` with a mapping of `attr1` to `types.List` of `types.String` and `attr2` to `types.Object`. + +### Configurability + + + +Only the single nested attribute itself is defined by the `schema.SingleNestedAttribute` configurability fields. Nested attributes must define their own configurability fields within each attribute definition. + + + +At least one of the `Computed`, `Optional`, or `Required` fields must be set to `true`. This defines how Terraform and the framework should expect data to set, whether the value is from the practitioner configuration or from the provider logic, such as API response value. + +The acceptable behaviors of these configurability options are: + +- `Required` only: The value must be practitioner configured to an eventually known value (not null), otherwise the framework will automatically raise an error diagnostic for the missing value. +- `Optional` only: The value may be practitioner configured to a known value or null. +- `Optional` and `Computed`: The value may be practitioner configured or the value may be set in provider logic when the practitioner configuration is null. +- `Computed` only: The value will be set in provider logic and any practitioner configuration causes the framework to automatically raise an error diagnostic for the unexpected configuration value. + +### Custom Types + +You may want to build your own attribute value and type implementations to allow your provider to combine validation, description, and plan customization behaviors into a reusable bundle. This helps avoid duplication or reimplementation and ensures consistency. These implementations use the `CustomType` field in the attribute type. + +Refer to [Custom Types](/terraform/plugin/framework/handling-data/types/custom) for further details on creating provider-defined types and values. + +### Deprecation + +Set the `DeprecationMessage` field to a practitioner-focused message for how to handle the deprecation. The framework will automatically raise a warning diagnostic with this message if the practitioner configuration contains a known value for the attribute. Terraform version 1.2.7 and later will raise a warning diagnostic in certain scenarios if the deprecated attribute value is referenced elsewhere in a practitioner configuration. The framework [deprecations](/terraform/plugin/framework/deprecations) documentation fully describes the recommended practices for deprecating an attribute or resource. + +Some practitioner-focused examples of a deprecation message include: + +- Configure `other_attribute` instead. This attribute will be removed in the next major version of the provider. +- Remove this attribute's configuration as it no longer is used and the attribute will be removed in the next major version of the provider. + +### Description + +The framework provides two description fields, `Description` and `MarkdownDescription`, which various tools use to show additional information about an attribute and its intended purpose. This includes, but is not limited to, [`terraform-plugin-docs`](https://github.com/hashicorp/terraform-plugin-docs) for automated provider documentation generation and [`terraform-ls`](https://github.com/hashicorp/terraform-ls) for Terraform configuration editor integrations. + +### Plan Modification + + + +Only managed resources implement this concept. + + + +The framework provides two plan modification fields for managed resource attributes, `Default` and `PlanModifiers`, which define resource and attribute value planning behaviors. The resource [default](/terraform/plugin/framework/resources/default) and [plan modification](/terraform/plugin/framework/resources/plan-modification) documentation covers these features more in-depth. + +#### Common Use Case Plan Modification + +The [`objectdefault`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/objectdefault) package defines common use case `Default` implementations: + +- [`StaticValue(types.Object)`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/objectdefault#StaticValue): Define a static object default value for the attribute. + +The [`objectplanmodifier`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/objectplanmodifier) package defines common use case `PlanModifiers` implementations: + +- [`RequiresReplace()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/objectplanmodifier#RequiresReplace): Marks the resource for replacement if the resource is being updated and the plan value does not match the prior state value. +- [`RequiresReplaceIf()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/objectplanmodifier#RequiresReplaceIf): Similar to `RequiresReplace()`, but also checks if a given function returns true. +- [`RequiresReplaceIfConfigured()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/objectplanmodifier#RequiresReplaceIfConfigured): Similar to `RequiresReplace()`, but also checks if the configuration value is not null. +- [`UseStateForUnknown()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/objectplanmodifier#UseStateForUnknown): Copies a known prior state value into the planned value. Use this when it is known that an unconfigured value will remain the same after a resource update. + +### Sensitive + +Set the `Sensitive` field if the attribute value should always be considered [sensitive data](/terraform/language/state/sensitive-data). In Terraform, this will generally mask the value in practitioner output. This setting cannot be conditionally set and does not impact how data is stored in the state. + +### WriteOnly + + + + Only managed resources implement this concept. + + + +Set the `WriteOnly` field to define a [write-only argument](/terraform/plugin/framework/resources/write-only-arguments). +Write-only arguments can accept [ephemeral values](/terraform/language/resources/ephemeral) +and are not persisted in the Terraform plan or state artifacts. Write-only arguments are supported in Terraform 1.11 and later. + +If a nested attribute has the `WriteOnly` field set, all child attributes must also have `WriteOnly` set. + +### Validation + +Set the `Validators` field to define [validation](/terraform/plugin/framework/validation#attribute-validation). This validation logic is ran in addition to any validation contained within a [custom type](#custom-types). + +#### Common Use Case Validators + +HashiCorp provides the additional [`terraform-plugin-framework-validators`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework-validators) Go module which contains validation logic for common use cases. The [`objectvalidator`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework-validators/objectvalidator) package within that module has object attribute validators such as defining conflicting attributes. + +## Accessing Values + +The [accessing values](/terraform/plugin/framework/handling-data/accessing-values) documentation covers general methods for reading [schema](/terraform/plugin/framework/handling-data/schemas) (configuration, plan, and state) data, which is necessary before accessing an attribute value directly. The [object type](/terraform/plugin/framework/handling-data/types/object#accessing-values) documentation covers methods for interacting with the attribute value itself. + +## Setting Values + +The [object type](/terraform/plugin/framework/handling-data/types/object#setting-values) documentation covers methods for creating or setting the appropriate value. The [writing data](/terraform/plugin/framework/handling-data/writing-state) documentation covers general methods for writing [schema](/terraform/plugin/framework/handling-data/schemas) (plan and state) data, which is necessary afterwards. diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/attributes/string.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/attributes/string.mdx new file mode 100644 index 000000000..5989fd6eb --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/attributes/string.mdx @@ -0,0 +1,141 @@ +--- +page_title: String attributes +description: >- + Learn how to use string attributes with the Terraform plugin framework. +--- + +# String attributes + +String attributes store a collection of UTF-8 encoded bytes. Values are represented by a [string type](/terraform/plugin/framework/handling-data/types/string) in the framework. + +In this Terraform configuration example, a string attribute named `example_attribute` is set to the value `terraform`: + +```hcl +resource "examplecloud_thing" "example" { + example_attribute = "terraform" +} +``` + +## Schema Definition + +Use one of the following attribute types to directly add a string value to a [schema](/terraform/plugin/framework/handling-data/schemas) or [nested attribute type](/terraform/plugin/framework/handling-data/attributes#nested-attribute-types): + +| Schema Type | Attribute Type | +|-------------|----------------| +| [Data Source](/terraform/plugin/framework/data-sources) | [`schema.StringAttribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/datasource/schema#StringAttribute) | +| [Provider](/terraform/plugin/framework/providers) | [`schema.StringAttribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/provider/schema#StringAttribute) | +| [Resource](/terraform/plugin/framework/resources) | [`schema.StringAttribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema#StringAttribute) | +| [Ephemeral Resource](/terraform/plugin/framework/ephemeral-resources) | [`schema.StringAttribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/ephemeral/schema#StringAttribute) | + +In this example, a resource schema defines a top level required string attribute named `example_attribute`: + +```go +func (r ThingResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "example_attribute": schema.StringAttribute{ + Required: true, + // ... potentially other fields ... + }, + // ... potentially other attributes ... + }, + } +} +``` + +If the string value should be the element type of a [collection attribute type](/terraform/plugin/framework/handling-data/attributes#collection-attribute-types), set the `ElementType` field according to the [string type](/terraform/plugin/framework/handling-data/types/string). Refer to the collection attribute type documentation for additional details. + +If the string value should be a value type of an [object attribute type](/terraform/plugin/framework/handling-data/attributes#object-attribute-type), set the `AttrTypes` map value according to the [string type](/terraform/plugin/framework/handling-data/types/string). Refer to the object attribute type documentation for additional details. + +### Configurability + +At least one of the `Computed`, `Optional`, or `Required` fields must be set to `true`. This defines how Terraform and the framework should expect data to set, whether the value is from the practitioner configuration or from the provider logic, such as API response value. + +The acceptable behaviors of these configurability options are: + +- `Required` only: The value must be practitioner configured to an eventually known value (not null), otherwise the framework will automatically raise an error diagnostic for the missing value. +- `Optional` only: The value may be practitioner configured to a known value or null. +- `Optional` and `Computed`: The value may be practitioner configured or the value may be set in provider logic when the practitioner configuration is null. +- `Computed` only: The value will be set in provider logic and any practitioner configuration causes the framework to automatically raise an error diagnostic for the unexpected configuration value. + +### Custom Types + +You may want to build your own attribute value and type implementations to allow your provider to combine validation, description, and plan customization behaviors into a reusable bundle. This helps avoid duplication or reimplementation and ensures consistency. These implementations use the `CustomType` field in the attribute type. + +Refer to [Custom Types](/terraform/plugin/framework/handling-data/types/custom) for further details on creating provider-defined types and values. + +#### Common Use Case Types + +HashiCorp provides additional Go modules which contain custom string type implementations covering common use cases with validation and semantic equality logic: + +- [`terraform-plugin-framework-jsontypes`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework-jsontypes): JSON encoded strings, such as exact byte strings and normalized strings +- [`terraform-plugin-framework-nettypes`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework-nettypes): Networking strings, such as IPv4 addresses, IPv6 addresses, and CIDRs +- [`terraform-plugin-framework-timetypes`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework-timetypes): Timestamp strings, such as RFC3339 + +### Deprecation + +Set the `DeprecationMessage` field to a practitioner-focused message for how to handle the deprecation. The framework will automatically raise a warning diagnostic with this message if the practitioner configuration contains a known value for the attribute. Terraform version 1.2.7 and later will raise a warning diagnostic in certain scenarios if the deprecated attribute value is referenced elsewhere in a practitioner configuration. The framework [deprecations](/terraform/plugin/framework/deprecations) documentation fully describes the recommended practices for deprecating an attribute or resource. + +Some practitioner-focused examples of a deprecation message include: + +- Configure `other_attribute` instead. This attribute will be removed in the next major version of the provider. +- Remove this attribute's configuration as it no longer is used and the attribute will be removed in the next major version of the provider. + +### Description + +The framework provides two description fields, `Description` and `MarkdownDescription`, which various tools use to show additional information about an attribute and its intended purpose. This includes, but is not limited to, [`terraform-plugin-docs`](https://github.com/hashicorp/terraform-plugin-docs) for automated provider documentation generation and [`terraform-ls`](https://github.com/hashicorp/terraform-ls) for Terraform configuration editor integrations. + +### Plan Modification + + + +Only managed resources implement this concept. + + + +The framework provides two plan modification fields for managed resource attributes, `Default` and `PlanModifiers`, which define resource and attribute value planning behaviors. The resource [default](/terraform/plugin/framework/resources/default) and [plan modification](/terraform/plugin/framework/resources/plan-modification) documentation covers these features more in-depth. + +#### Common Use Case Plan Modification + +The [`stringdefault`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/stringdefault) package defines common use case `Default` implementations: + +- [`StaticString(string)`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/stringdefault#StaticString): Define a static string default value for the attribute. + +The [`stringplanmodifier`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier) package defines common use case `PlanModifiers` implementations: + +- [`RequiresReplace()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier#RequiresReplace): Marks the resource for replacement if the resource is being updated and the plan value does not match the prior state value. +- [`RequiresReplaceIf()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier#RequiresReplaceIf): Similar to `RequiresReplace()`, but also checks if a given function returns true. +- [`RequiresReplaceIfConfigured()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier#RequiresReplaceIfConfigured): Similar to `RequiresReplace()`, but also checks if the configuration value is not null. +- [`UseStateForUnknown()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier#UseStateForUnknown): Copies a known prior state value into the planned value. Use this when it is known that an unconfigured value will remain the same after a resource update. + +### Sensitive + +Set the `Sensitive` field if the attribute value should always be considered [sensitive data](/terraform/language/state/sensitive-data). In Terraform, this will generally mask the value in practitioner output. This setting cannot be conditionally set and does not impact how data is stored in the state. + +### WriteOnly + + + + Only managed resources implement this concept. + + + +Set the `WriteOnly` field to define a [write-only argument](/terraform/plugin/framework/resources/write-only-arguments). +Write-only arguments can accept [ephemeral values](/terraform/language/resources/ephemeral) +and are not persisted in the Terraform plan or state artifacts. Write-only arguments are supported in Terraform 1.11 and later. + +### Validation + +Set the `Validators` field to define [validation](/terraform/plugin/framework/validation#attribute-validation). This validation logic is ran in addition to any validation contained within a [custom type](#custom-types). + +#### Common Use Case Validators + +HashiCorp provides the additional [`terraform-plugin-framework-validators`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework-validators) Go module which contains validation logic for common use cases. The [`stringvalidator`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator) package within that module has string attribute validators such as length, regular expression pattern, and defining conflicting attributes. + +## Accessing Values + +The [accessing values](/terraform/plugin/framework/handling-data/accessing-values) documentation covers general methods for reading [schema](/terraform/plugin/framework/handling-data/schemas) (configuration, plan, and state) data, which is necessary before accessing an attribute value directly. The [string type](/terraform/plugin/framework/handling-data/types/string#accessing-values) documentation covers methods for interacting with the attribute value itself. + +## Setting Values + +The [string type](/terraform/plugin/framework/handling-data/types/string#setting-values) documentation covers methods for creating or setting the appropriate value. The [writing data](/terraform/plugin/framework/handling-data/writing-state) documentation covers general methods for writing [schema](/terraform/plugin/framework/handling-data/schemas) (plan and state) data, which is necessary afterwards. diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/blocks/index.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/blocks/index.mdx new file mode 100644 index 000000000..af96c5021 --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/blocks/index.mdx @@ -0,0 +1,25 @@ +--- +page_title: Blocks +description: >- + Learn how to use block types with the Terraform plugin framework. Blocks are + containers for nested attributes and blocks in a resource, data source, or + provider schema. +--- + +# Blocks + + + +Use [nested attribute types](/terraform/plugin/framework/handling-data/attributes#nested-attribute-types) instead of block types for new schema implementations. Block support is mainly for migrating legacy SDK-based providers. + + + +Blocks are containers for other attributes and blocks in resource, data source, or provider [schemas](/terraform/plugin/framework/handling-data/schemas). Every block has an associated [value type](/terraform/plugin/framework/handling-data/types), which describes the kind of data the block holds. Blocks also can describe value plan modifiers (resources only) and value validators in addition to those defined by the value type. + +## Available Block Types + +| Block Type | Use Case | +|----------------|----------| +| [List Nested](/terraform/plugin/framework/handling-data/blocks/list-nested) | Ordered collection of structures of attributes/blocks | +| [Set Nested](/terraform/plugin/framework/handling-data/blocks/set-nested) | Unordered, unique collection of structures of attributes/blocks | +| [Single Nested](/terraform/plugin/framework/handling-data/blocks/single-nested) | Single structure of attributes/blocks | diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/blocks/list-nested.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/blocks/list-nested.mdx new file mode 100644 index 000000000..373c69d0b --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/blocks/list-nested.mdx @@ -0,0 +1,169 @@ +--- +page_title: List nested blocks +description: >- + Learn how to implement the list nested block type with the Terraform plugin + framework. +--- + +# List nested blocks + + + +Use [nested attribute types](/terraform/plugin/framework/handling-data/attributes#nested-attribute-types) instead of block types for new schema implementations. Block support is mainly for migrating legacy SDK-based providers. + + + +List nested blocks are an ordered collection of structures mapping explicit attribute names to nested attribute and block definitions. Values are represented by a [list type](/terraform/plugin/framework/handling-data/types/list) in the framework, containing [object type](/terraform/plugin/framework/handling-data/types/object) of the mapped attributes and blocks. + +In this Terraform configuration example, a list nested block named `example_block` is set to an ordered collection of an object value of `attr1` to `"value1"` and `attr2` to `123` and an object value of `attr1` to `"value2"` and `attr2` to `456`: + +```hcl +resource "examplecloud_thing" "example" { + example_block { + attr1 = "value1" + attr2 = 123 + } + + example_block { + attr1 = "value2" + attr2 = 456 + } +} +``` + +## Schema Definition + + + +Blocks can only be defined on schemas or nested blocks within a schema, not underneath an attribute or nested attribute. + + + +Use one of the following block types to directly add a list nested value to a [schema](/terraform/plugin/framework/handling-data/schemas) or [block type](/terraform/plugin/framework/handling-data/blocks): + +| Schema Type | Attribute Type | +|-------------|----------------| +| [Data Source](/terraform/plugin/framework/data-sources) | [`schema.ListNestedBlock`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/datasource/schema#ListNestedBlock) | +| [Provider](/terraform/plugin/framework/providers) | [`schema.ListNestedBlock`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/provider/schema#ListNestedBlock) | +| [Resource](/terraform/plugin/framework/resources) | [`schema.ListNestedBlock`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema#ListNestedBlock) | +| [Ephemeral Resource](/terraform/plugin/framework/ephemeral-resources) | [`schema.ListNestedBlock`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/ephemeral/schema#ListNestedBlock) | + +The `NestedObject` field must be defined, which represents the [object value type](/terraform/plugin/framework/handling-data/types/object) of every element of the list. + +In this example, a resource schema defines a top level list nested block named `example_block` with a required string attribute named `attr1` and optional integer attribute named `attr2`: + +```go +func (r ThingResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + Blocks: map[string]schema.Block{ + "example_block": schema.ListNestedBlock{ + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "attr1": schema.StringAttribute{ + Required: true, + // ... potentially other fields ... + }, + "attr2": schema.Int64Attribute{ + Optional: true, + // ... potentially other fields ... + }, + }, + }, + // ... potentially other fields ... + }, + // ... potentially other blocks ... + }, + } +} +``` + +Its [value type](/terraform/plugin/framework/handling-data/types) would be represented as `types.List` of `types.Object` with a mapping of `attr1` to `types.String` and `attr2` to `types.Int64`. + +A block type may itself contain further collection or nested attribute/block types, if necessary. + +In this example, a resource schema defines a top level list nested block named `example_block` with a required list of strings attribute named `attr` and a single nested block named `block`: + +```go +func (r ThingResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + Blocks: map[string]schema.Block{ + "example_block": schema.ListNestedBlock{ + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "attr": schema.ListAttribute{ + ElementType: types.StringType, + Required: true, + // ... potentially other fields ... + }, + }, + Blocks: map[string]schema.Block{ + "block": schema.ListNestedBlock{ /* ... */ }, + }, + }, + // ... potentially other fields ... + }, + // ... potentially other blocks ... + }, + } +} +``` + +Its [value type](/terraform/plugin/framework/handling-data/types) would be represented as a `types.List` of `types.Object` with a mapping of `attr` to `types.List` of `types.String` and `block` to `types.Object`. + +### Custom Types + +You may want to build your own attribute value and type implementations to allow your provider to combine validation, description, and plan customization behaviors into a reusable bundle. This helps avoid duplication or reimplementation and ensures consistency. These implementations use the `CustomType` field in the attribute type. + +Refer to [Custom Types](/terraform/plugin/framework/handling-data/types/custom) for further details on creating provider-defined types and values. + +### Deprecation + +Set the `DeprecationMessage` field to a practitioner-focused message for how to handle the deprecation. The framework will automatically raise a warning diagnostic with this message if the practitioner configuration contains a known value for the attribute. Terraform version 1.2.7 and later will raise a warning diagnostic in certain scenarios if the deprecated attribute value is referenced elsewhere in a practitioner configuration. The framework [deprecations](/terraform/plugin/framework/deprecations) documentation fully describes the recommended practices for deprecating an attribute or resource. + +Some practitioner-focused examples of a deprecation message include: + +- Configure `other_attribute` instead. This attribute will be removed in the next major version of the provider. +- Remove this attribute's configuration as it no longer is used and the attribute will be removed in the next major version of the provider. + +### Description + +The framework provides two description fields, `Description` and `MarkdownDescription`, which various tools use to show additional information about an attribute and its intended purpose. This includes, but is not limited to, [`terraform-plugin-docs`](https://github.com/hashicorp/terraform-plugin-docs) for automated provider documentation generation and [`terraform-ls`](https://github.com/hashicorp/terraform-ls) for Terraform configuration editor integrations. + +### Plan Modification + + + +Only managed resources implement this concept. + + + +The framework provides two plan modification fields for managed resource attributes, `Default` and `PlanModifiers`, which define resource and attribute value planning behaviors. The resource [default](/terraform/plugin/framework/resources/default) and [plan modification](/terraform/plugin/framework/resources/plan-modification) documentation covers these features more in-depth. + +#### Common Use Case Plan Modification + +The [`listdefault`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/listdefault) package defines common use case `Default` implementations: + +- [`StaticValue(types.List)`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/listdefault#StaticValue): Define a static list default value for the attribute. + +The [`listplanmodifier`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/listplanmodifier) package defines common use case `PlanModifiers` implementations: + +- [`RequiresReplace()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/listplanmodifier#RequiresReplace): Marks the resource for replacement if the resource is being updated and the plan value does not match the prior state value. +- [`RequiresReplaceIf()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/listplanmodifier#RequiresReplaceIf): Similar to `RequiresReplace()`, but also checks if a given function returns true. +- [`RequiresReplaceIfConfigured()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/listplanmodifier#RequiresReplaceIfConfigured): Similar to `RequiresReplace()`, but also checks if the configuration value is not null. +- [`UseStateForUnknown()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/listplanmodifier#UseStateForUnknown): Copies a known prior state value into the planned value. Use this when it is known that an unconfigured value will remain the same after a resource update. + +### Validation + +Set the `Validators` field to define [validation](/terraform/plugin/framework/validation#attribute-validation). This validation logic is ran in addition to any validation contained within a [custom type](#custom-types). + +#### Common Use Case Validators + +HashiCorp provides the additional [`terraform-plugin-framework-validators`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework-validators) Go module which contains validation logic for common use cases. The [`listvalidator`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework-validators/listvalidator) package within that module has list attribute validators such as defining conflicting attributes. + +## Accessing Values + +The [accessing values](/terraform/plugin/framework/handling-data/accessing-values) documentation covers general methods for reading [schema](/terraform/plugin/framework/handling-data/schemas) (configuration, plan, and state) data, which is necessary before accessing an attribute value directly. The [list type](/terraform/plugin/framework/handling-data/types/list#accessing-values) documentation covers methods for interacting with the attribute value itself. + +## Setting Values + +The [list type](/terraform/plugin/framework/handling-data/types/list#setting-values) documentation covers methods for creating or setting the appropriate value. The [writing data](/terraform/plugin/framework/handling-data/writing-state) documentation covers general methods for writing [schema](/terraform/plugin/framework/handling-data/schemas) (plan and state) data, which is necessary afterwards. diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/blocks/set-nested.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/blocks/set-nested.mdx new file mode 100644 index 000000000..c7909aa0e --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/blocks/set-nested.mdx @@ -0,0 +1,168 @@ +--- +page_title: Set nested blocks +description: >- + Learn to implement the set nested block type with the Terraform plugin framework. +--- + +# Set nested blocks + + + +Use [nested attribute types](/terraform/plugin/framework/handling-data/attributes#nested-attribute-types) instead of block types for new schema implementations. Block support is mainly for migrating legacy SDK-based providers. + + + +Set nested blocks are an unique, unordered collection of structures mapping explicit attribute names to nested attribute and block definitions. Values are represented by a [set type](/terraform/plugin/framework/handling-data/types/set) in the framework, containing [object type](/terraform/plugin/framework/handling-data/types/object) of the mapped attributes and blocks. + +In this Terraform configuration example, a set nested block named `example_block` is set to an unordered collection of an object value of `attr1` to `"value1"` and `attr2` to `123` and an object value of `attr1` to `"value2"` and `attr2` to `456`: + +```hcl +resource "examplecloud_thing" "example" { + example_block { + attr1 = "value1" + attr2 = 123 + } + + example_block { + attr1 = "value2" + attr2 = 456 + } +} +``` + +## Schema Definition + + + +Blocks can only be defined on schemas or nested blocks within a schema, not underneath an attribute or nested attribute. + + + +Use one of the following block types to directly add a list nested value to a [schema](/terraform/plugin/framework/handling-data/schemas) or [block type](/terraform/plugin/framework/handling-data/blocks): + +| Schema Type | Attribute Type | +|-------------|----------------| +| [Data Source](/terraform/plugin/framework/data-sources) | [`schema.SetNestedBlock`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/datasource/schema#SetNestedBlock) | +| [Provider](/terraform/plugin/framework/providers) | [`schema.SetNestedBlock`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/provider/schema#SetNestedBlock) | +| [Resource](/terraform/plugin/framework/resources) | [`schema.SetNestedBlock`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema#SetNestedBlock) | +| [Ephemeral Resource](/terraform/plugin/framework/ephemeral-resources) | [`schema.SetNestedBlock`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/ephemeral/schema#SetNestedBlock) | + +The `NestedObject` field must be defined, which represents the [object value type](/terraform/plugin/framework/handling-data/types/object) of every element of the set. + +In this example, a resource schema defines a top level set nested block named `example_block` with a required string attribute named `attr1` and optional integer attribute named `attr2`: + +```go +func (r ThingResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + Blocks: map[string]schema.Attribute{ + "example_block": schema.SetNestedBlock{ + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "attr1": schema.StringAttribute{ + Required: true, + // ... potentially other fields ... + }, + "attr2": schema.Int64Attribute{ + Optional: true, + // ... potentially other fields ... + }, + }, + }, + // ... potentially other fields ... + }, + // ... potentially other blocks ... + }, + } +} +``` + +Its [value type](/terraform/plugin/framework/handling-data/types) would be represented as `types.Set` of `types.Object` with a mapping of `attr1` to `types.String` and `attr2` to `types.Int64`. + +A block type may itself contain further collection or nested attribute/block types, if necessary. + +In this example, a resource schema defines a top level set nested block named `example_block` with a required list of strings attribute named `attr` and a single nested block named `block`: + +```go +func (r ThingResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + Blocks: map[string]schema.Block{ + "example_block": schema.SetNestedBlock{ + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "attr": schema.ListAttribute{ + ElementType: types.StringType, + Required: true, + // ... potentially other fields ... + }, + }, + Blocks: map[string]schema.Block{ + "block": schema.SetNestedBlock{ /* ... */ }, + }, + }, + // ... potentially other fields ... + }, + // ... potentially other blocks ... + }, + } +} +``` + +Its [value type](/terraform/plugin/framework/handling-data/types) would be represented as a `types.Set` of `types.Object` with a mapping of `attr` to `types.List` of `types.String` and `block` to `types.Object`. + +### Custom Types + +You may want to build your own attribute value and type implementations to allow your provider to combine validation, description, and plan customization behaviors into a reusable bundle. This helps avoid duplication or reimplementation and ensures consistency. These implementations use the `CustomType` field in the attribute type. + +Refer to [Custom Types](/terraform/plugin/framework/handling-data/types/custom) for further details on creating provider-defined types and values. + +### Deprecation + +Set the `DeprecationMessage` field to a practitioner-focused message for how to handle the deprecation. The framework will automatically raise a warning diagnostic with this message if the practitioner configuration contains a known value for the attribute. Terraform version 1.2.7 and later will raise a warning diagnostic in certain scenarios if the deprecated attribute value is referenced elsewhere in a practitioner configuration. The framework [deprecations](/terraform/plugin/framework/deprecations) documentation fully describes the recommended practices for deprecating an attribute or resource. + +Some practitioner-focused examples of a deprecation message include: + +- Configure `other_attribute` instead. This attribute will be removed in the next major version of the provider. +- Remove this attribute's configuration as it no longer is used and the attribute will be removed in the next major version of the provider. + +### Description + +The framework provides two description fields, `Description` and `MarkdownDescription`, which various tools use to show additional information about an attribute and its intended purpose. This includes, but is not limited to, [`terraform-plugin-docs`](https://github.com/hashicorp/terraform-plugin-docs) for automated provider documentation generation and [`terraform-ls`](https://github.com/hashicorp/terraform-ls) for Terraform configuration editor integrations. + +### Plan Modification + + + +Only managed resources implement this concept. + + + +The framework provides two plan modification fields for managed resource attributes, `Default` and `PlanModifiers`, which define resource and attribute value planning behaviors. The resource [default](/terraform/plugin/framework/resources/default) and [plan modification](/terraform/plugin/framework/resources/plan-modification) documentation covers these features more in-depth. + +#### Common Use Case Plan Modification + +The [`setdefault`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/setdefault) package defines common use case `Default` implementations: + +- [`StaticValue(types.Set)`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/setdefault#StaticValue): Define a static list default value for the attribute. + +The [`setplanmodifier`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/setplanmodifier) package defines common use case `PlanModifiers` implementations: + +- [`RequiresReplace()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/setplanmodifier#RequiresReplace): Marks the resource for replacement if the resource is being updated and the plan value does not match the prior state value. +- [`RequiresReplaceIf()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/setplanmodifier#RequiresReplaceIf): Similar to `RequiresReplace()`, but also checks if a given function returns true. +- [`RequiresReplaceIfConfigured()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/setplanmodifier#RequiresReplaceIfConfigured): Similar to `RequiresReplace()`, but also checks if the configuration value is not null. +- [`UseStateForUnknown()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/setplanmodifier#UseStateForUnknown): Copies a known prior state value into the planned value. Use this when it is known that an unconfigured value will remain the same after a resource update. + +### Validation + +Set the `Validators` field to define [validation](/terraform/plugin/framework/validation#attribute-validation). This validation logic is ran in addition to any validation contained within a [custom type](#custom-types). + +#### Common Use Case Validators + +HashiCorp provides the additional [`terraform-plugin-framework-validators`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework-validators) Go module which contains validation logic for common use cases. The [`setvalidator`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework-validators/setvalidator) package within that module has set attribute validators such as defining conflicting attributes. + +## Accessing Values + +The [accessing values](/terraform/plugin/framework/handling-data/accessing-values) documentation covers general methods for reading [schema](/terraform/plugin/framework/handling-data/schemas) (configuration, plan, and state) data, which is necessary before accessing an attribute value directly. The [set type](/terraform/plugin/framework/handling-data/types/set#accessing-values) documentation covers methods for interacting with the attribute value itself. + +## Setting Values + +The [set type](/terraform/plugin/framework/handling-data/types/set#setting-values) documentation covers methods for creating or setting the appropriate value. The [writing data](/terraform/plugin/framework/handling-data/writing-state) documentation covers general methods for writing [schema](/terraform/plugin/framework/handling-data/schemas) (plan and state) data, which is necessary afterwards. diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/blocks/single-nested.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/blocks/single-nested.mdx new file mode 100644 index 000000000..4f3d41b09 --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/blocks/single-nested.mdx @@ -0,0 +1,160 @@ +--- +page_title: Single nested blocks +description: >- + Learn to implement the single nested block type with the Terraform plugin + framework. +--- + +# Single nested blocks + + + +Use [nested attribute types](/terraform/plugin/framework/handling-data/attributes#nested-attribute-types) instead of block types for new schema implementations. Block support is mainly for migrating legacy SDK-based providers. + + + +Single nested blocks are a single structure mapping explicit attribute names to nested attribute and block definitions. Values are represented by a [object type](/terraform/plugin/framework/handling-data/types/object) in the framework, containing nested attribute values of the mapped attributes and blocks. + +In this Terraform configuration example, a single nested block named `example_block` is set to the mapped values of `attr1` to `"value1"` and `attr2` to `123`: + +```hcl +resource "examplecloud_thing" "example" { + example_block { + attr1 = "value1" + attr2 = 123 + } +} +``` + +## Schema Definition + + + +Blocks can only be defined on schemas or nested blocks within a schema, not underneath an attribute or nested attribute. + + + +Use one of the following block types to directly add a single nested value to a [schema](/terraform/plugin/framework/handling-data/schemas) or [block type](/terraform/plugin/framework/handling-data/blocks): + +| Schema Type | Attribute Type | +|-------------|----------------| +| [Data Source](/terraform/plugin/framework/data-sources) | [`schema.SingleNestedBlock`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/datasource/schema#SingleNestedBlock) | +| [Provider](/terraform/plugin/framework/providers) | [`schema.SingleNestedBlock`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/provider/schema#SingleNestedBlock) | +| [Resource](/terraform/plugin/framework/resources) | [`schema.SingleNestedBlock`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema#SingleNestedBlock) | +| [Ephemeral Resource](/terraform/plugin/framework/ephemeral-resources) | [`schema.SingleNestedBlock`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/ephemeral/schema#SingleNestedBlock) | + +In most use cases, the `Attributes` or `Blocks` field should be defined, which represents the mapping of explicit string attribute names to nested attributes and/or blocks. + +In this example, a resource schema defines a top level single nested block named `example_block` with a required string attribute named `attr1` and optional integer attribute named `attr2`: + +```go +func (r ThingResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + Blocks: map[string]schema.Block{ + "example_block": schema.SingleNestedBlock{ + Attributes: map[string]schema.Attribute{ + "attr1": schema.StringAttribute{ + Required: true, + // ... potentially other fields ... + }, + "attr2": schema.Int64Attribute{ + Optional: true, + // ... potentially other fields ... + }, + }, + // ... potentially other fields ... + }, + // ... potentially other blocks ... + }, + } +} +``` + +Its [value type](/terraform/plugin/framework/handling-data/types) would be represented as a `types.Object` with a mapping of `attr1` to `types.String` and `attr2` to `types.Int64`. + +A block type may itself contain further collection or nested attribute/block types, if necessary. + +In this example, a resource schema defines a top level single nested block named `example_block` with a required list of strings attribute named `attr` and a single nested block named `block`: + +```go +func (r ThingResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + Blocks: map[string]schema.Block{ + "example_block": schema.SingleNestedBlock{ + Attributes: map[string]schema.Attribute{ + "attr": schema.ListAttribute{ + ElementType: types.StringType, + Required: true, + // ... potentially other fields ... + }, + }, + Blocks: map[string]schema.Block{ + "block": schema.SingleNestedBlock{ /* ... */ }, + }, + // ... potentially other fields ... + }, + // ... potentially other blocks ... + }, + } +} +``` + +Its [value type](/terraform/plugin/framework/handling-data/types) would be represented as a `types.Object` with a mapping of `attr` to `types.List` of `types.String` and `block` to `types.Object`. + +### Custom Types + +You may want to build your own attribute value and type implementations to allow your provider to combine validation, description, and plan customization behaviors into a reusable bundle. This helps avoid duplication or reimplementation and ensures consistency. These implementations use the `CustomType` field in the attribute type. + +Refer to [Custom Types](/terraform/plugin/framework/handling-data/types/custom) for further details on creating provider-defined types and values. + +### Deprecation + +Set the `DeprecationMessage` field to a practitioner-focused message for how to handle the deprecation. The framework will automatically raise a warning diagnostic with this message if the practitioner configuration contains a known value for the attribute. Terraform version 1.2.7 and later will raise a warning diagnostic in certain scenarios if the deprecated attribute value is referenced elsewhere in a practitioner configuration. The framework [deprecations](/terraform/plugin/framework/deprecations) documentation fully describes the recommended practices for deprecating an attribute or resource. + +Some practitioner-focused examples of a deprecation message include: + +- Configure `other_attribute` instead. This attribute will be removed in the next major version of the provider. +- Remove this attribute's configuration as it no longer is used and the attribute will be removed in the next major version of the provider. + +### Description + +The framework provides two description fields, `Description` and `MarkdownDescription`, which various tools use to show additional information about an attribute and its intended purpose. This includes, but is not limited to, [`terraform-plugin-docs`](https://github.com/hashicorp/terraform-plugin-docs) for automated provider documentation generation and [`terraform-ls`](https://github.com/hashicorp/terraform-ls) for Terraform configuration editor integrations. + +### Plan Modification + + + +Only managed resources implement this concept. + + + +The framework provides two plan modification fields for managed resource attributes, `Default` and `PlanModifiers`, which define resource and attribute value planning behaviors. The resource [default](/terraform/plugin/framework/resources/default) and [plan modification](/terraform/plugin/framework/resources/plan-modification) documentation covers these features more in-depth. + +#### Common Use Case Plan Modification + +The [`objectdefault`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/objectdefault) package defines common use case `Default` implementations: + +- [`StaticValue(types.Object)`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/objectdefault#StaticValue): Define a static object default value for the attribute. + +The [`objectplanmodifier`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/objectplanmodifier) package defines common use case `PlanModifiers` implementations: + +- [`RequiresReplace()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/objectplanmodifier#RequiresReplace): Marks the resource for replacement if the resource is being updated and the plan value does not match the prior state value. +- [`RequiresReplaceIf()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/objectplanmodifier#RequiresReplaceIf): Similar to `RequiresReplace()`, but also checks if a given function returns true. +- [`RequiresReplaceIfConfigured()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/objectplanmodifier#RequiresReplaceIfConfigured): Similar to `RequiresReplace()`, but also checks if the configuration value is not null. +- [`UseStateForUnknown()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/objectplanmodifier#UseStateForUnknown): Copies a known prior state value into the planned value. Use this when it is known that an unconfigured value will remain the same after a resource update. + +### Validation + +Set the `Validators` field to define [validation](/terraform/plugin/framework/validation#attribute-validation). This validation logic is ran in addition to any validation contained within a [custom type](#custom-types). + +#### Common Use Case Validators + +HashiCorp provides the additional [`terraform-plugin-framework-validators`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework-validators) Go module which contains validation logic for common use cases. The [`objectvalidator`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework-validators/objectvalidator) package within that module has object attribute validators such as defining conflicting attributes. + +## Accessing Values + +The [accessing values](/terraform/plugin/framework/handling-data/accessing-values) documentation covers general methods for reading [schema](/terraform/plugin/framework/handling-data/schemas) (configuration, plan, and state) data, which is necessary before accessing an attribute value directly. The [object type](/terraform/plugin/framework/handling-data/types/object#accessing-values) documentation covers methods for interacting with the attribute value itself. + +## Setting Values + +The [object type](/terraform/plugin/framework/handling-data/types/object#setting-values) documentation covers methods for creating or setting the appropriate value. The [writing data](/terraform/plugin/framework/handling-data/writing-state) documentation covers general methods for writing [schema](/terraform/plugin/framework/handling-data/schemas) (plan and state) data, which is necessary afterwards. diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/dynamic-data.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/dynamic-data.mdx new file mode 100644 index 000000000..07f845f41 --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/dynamic-data.mdx @@ -0,0 +1,223 @@ +--- +page_title: Handling dynamic data +description: >- + Learn how to handle data when using dynamic types in the Terraform plugin + framework. +--- + +# Handling dynamic data + + + +Static types should always be preferred over dynamic types, when possible. + + + +Dynamic data handling uses the [framework dynamic type](/terraform/plugin/framework/handling-data/types/dynamic) to communicate to Terraform that the value type of a specific field will be determined at runtime. This allows a provider developer to handle multiple value types of data with a single attribute, parameter, or return. + +Dynamic data can be defined with: +- [Dynamic attribute](/terraform/plugin/framework/handling-data/attributes/dynamic) +- A [dynamic](/terraform/plugin/framework/handling-data/types/dynamic) attribute type in an [object attribute](/terraform/plugin/framework/handling-data/attributes/object) +- [Dynamic function parameter](/terraform/plugin/framework/functions/parameters/dynamic) +- [Dynamic function return](/terraform/plugin/framework/functions/returns/dynamic) +- A [dynamic](/terraform/plugin/framework/handling-data/types/dynamic) attribute type in an [object parameter](/terraform/plugin/framework/functions/parameters/object) +- A [dynamic](/terraform/plugin/framework/handling-data/types/dynamic) attribute type in an [object return](/terraform/plugin/framework/functions/returns/object) + +Using dynamic data has a negative impact on practitioner experience when using Terraform and downstream tooling, like practitioner configuration editor integrations. Dynamics do not change how [Terraform's static type system](/terraform/language/expressions/types) behaves and all data consistency rules are applied the same as static types. Provider developers should understand all the below [considerations](#considerations) when creating a provider with a dynamic type. + +Only use a dynamic type when there is not a suitable static type alternative. + +## Considerations + +When dynamic data is used, Terraform will no longer have any static information about the value types expected for a given attribute, function parameter, or function return. This results in behaviors that the provider developer will need to account for with additional documentation, code, error messaging, etc. + +### Downstream Tooling + +Practitioner configuration editor integrations, like the Terraform VSCode extension and language server, cannot provide any static information when using dynamic data in configurations. This can result in practitioners using dynamic data in expressions (like [`for`](/terraform/language/expressions/for)) incorrectly that will only error at runtime. + +Given this example, a resource schema defines a top level computed [dynamic attribute](/terraform/plugin/framework/handling-data/attributes/dynamic) named `example_attribute`: + +```go +func (r ThingResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "example_attribute": schema.DynamicAttribute{ + Computed: true, + // ... potentially other fields ... + }, + // ... potentially other attributes ... + }, + } +} +``` + +The configuration below would be valid until a practitioner runs an apply. If the type of `example_attribute` is not iterable, then the practitioner will receive an error only when they run a command: + +```hcl +resource "examplecloud_thing" "example" {} + +output "dynamic_output" { + value = [for val in examplecloud_thing.example.example_attribute : val] +} +``` + +Results in the following error: + +```bash +│ Error: Iteration over non-iterable value +│ +│ on resource.tf line 15, in output "dynamic_output": +│ 15: value = [for val in examplecloud_thing.example.example_attribute : val] +│ ├──────────────── +│ │ examplecloud_thing.example.example_attribute is "string value" +│ +│ A value of type string cannot be used as the collection in a 'for' expression. +``` + +Dynamic data that is meant for practitioners to utilize in configurations should document all potential output types and expected usage to avoid confusing errors. + +### Handling All Possible Types + +Terraform will not [automatically convert](/terraform/language/expressions/types#type-conversion) values to conform to a static type, exposing provider developers to the Terraform type system directly. Provider developers will need to deal with this lack of type conversion by writing logic that handles [every possible type](/terraform/language/expressions/types#types) that Terraform supports. + +In this example, a resource schema defines a top level required [dynamic attribute](/terraform/plugin/framework/handling-data/attributes/dynamic) named `example_attribute`: + +```go +func (r ThingResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "example_attribute": schema.DynamicAttribute{ + Required: true, + // ... potentially other fields ... + }, + // ... potentially other attributes ... + }, + } +} +``` + +An example of handling every possible Terraform type that could be provided to a configuration would be: + +```go + // Example data model definition + // type ExampleModel struct { + // ExampleAttribute types.Dynamic `tfsdk:"example_attribute"` + // } + switch value := data.ExampleAttribute.UnderlyingValue().(type) { + case types.Bool: + // Handle boolean value + case types.Number: + // Handle float64, int64, and number values + case types.List: + // Handle list value + case types.Map: + // Handle map value + case types.Object: + // Handle object value + case types.Set: + // Handle set value + case types.String: + // Handle string value + case types.Tuple: + // Handle tuple value + } +``` + +When writing test configurations and debugging provider issues, developers will also want to understand how Terraform represents [complex type literals](/terraform/language/expressions/type-constraints#complex-type-literals). For example, Terraform does not provide any way to directly represent lists, maps, or sets. + + +### Handling Underlying Null and Unknown Values + +With dynamic data, in addition to typical [null](/terraform/plugin/framework/handling-data/terraform-concepts#null-values) and [unknown](/terraform/plugin/framework/handling-data/terraform-concepts#unknown-values) value handling, provider developers will need to implement additional logic to determine if an underlying value for a dynamic is null or unknown. + +#### Underlying Null + +In the configuration below, Terraform knows the underlying value type, `string`, but the underlying string value is null: + +```hcl +resource "examplecloud_thing" "example" { + example_attribute = var.null_string +} + +variable "null_string" { + type = string + default = null +} +``` + +This will result in a known dynamic value, with an underlying value that is a null [string type](/terraform/plugin/framework/handling-data/types/string). This can be detected utilizing the [`(types.Dynamic).IsUnderlyingValueNull()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types/basetypes#DynamicValue.IsUnderlyingValueNull) method. An equivalent framework value to this scenario would be: + +```go +dynValWithNullString := types.DynamicValue(types.StringNull()) +``` + +#### Underlying Unknown + +In the configuration below, Terraform knows the underlying value type of [`random_shuffle.result`](https://registry.terraform.io/providers/hashicorp/random/latest/docs/resources/shuffle#result), a `list(string)`, but the underlying list value is unknown: + +```hcl +resource "random_shuffle" "example" { + input = ["one", "two"] + result_count = 2 +} + +resource "examplecloud_thing" "this" { + example_attribute = random_shuffle.example.result +} +``` + +This will result in a known dynamic value, with an underlying value that is an unknown [list of string types](/terraform/plugin/framework/handling-data/types/list). This can be detected utilizing the [`(types.Dynamic).IsUnderlyingValueUnknown()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types/basetypes#DynamicValue.IsUnderlyingValueUnknown) method. An equivalent framework value to this scenario would be: + +```go +dynValWithUnknownList := types.DynamicValue(types.ListUnknown(types.StringType)) +``` + +### Understanding Type Consistency + +For [managed resources](/terraform/plugin/framework/resources), Terraform core implements [data consistency rules](https://github.com/hashicorp/terraform/blob/main/docs/resource-instance-change-lifecycle.md) between configuration, plan, and state data. With [dynamic attributes](/terraform/plugin/framework/handling-data/attributes/dynamic), these consistency rules are also applied to the **type** of data. + +For example, given a dynamic `example_attribute` that is computed and optional: + +```go +func (r ThingResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "example_attribute": schema.DynamicAttribute{ + Computed: true, + Optional: true, + // ... potentially other fields ... + }, + // ... potentially other attributes ... + }, + } +} +``` + +If a practitioner configures this resource as: + +```hcl +resource "examplecloud_thing" "example" { + # This literal expression is a tuple[string, string] + example_attribute = ["one", "two"] +} +``` + +Then the exact type must be planned and stored in state during `apply` as a [tuple](/terraform/plugin/framework/handling-data/types/tuple) with two [string](/terraform/plugin/framework/handling-data/types/string) element types. If provider code attempts to store this attribute as a different type, like a [list](/terraform/plugin/framework/handling-data/types/list) of strings, even with the same data values, Terraform will produce an error during apply: + +```bash +│ Error: Provider produced inconsistent result after apply +│ +│ When applying changes to examplecloud_thing.example, provider "provider[\"TYPE\"]" produced an unexpected new value: .example_attribute: wrong final value type: tuple required. +│ +│ This is a bug in the provider, which should be reported in the providers own issue tracker. +``` + +If a practitioner configures this same resource as: + +```hcl +resource "examplecloud_thing" "example" { + example_attribute = tolist(["one", "two"]) +} +``` + +Then the exact type must be planned and stored in state during `apply` as a [list](/terraform/plugin/framework/handling-data/types/list) of strings. diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/path-expressions.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/path-expressions.mdx new file mode 100644 index 000000000..80cca2a1d --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/path-expressions.mdx @@ -0,0 +1,185 @@ +--- +page_title: Path expressions +description: >- + Learn how to implement path expressions in the Terraform plugin framework. + Path expressions are logic built on top of paths, which may represent one or + more actual paths within schema data. +--- + + +# Path expressions + +Path expressions are logic built on top of [paths](/terraform/plugin/framework/paths), which may represent one or more actual paths within a schema or schema-based data. Expressions enable providers to work outside the restrictions of absolute paths and steps. + +## Usage + +Example uses include: + +- [Path based attribute validators](/terraform/plugin/framework/validation#path-based-attribute-validators), such as those in the [`terraform-plugin-framework-validators` module `schemavalidator` package](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/schemavalidator). + +Use cases which require exact locations, such as [diagnostics](/terraform/plugin/framework/diagnostics), implement [paths](/terraform/plugin/framework/paths). + +## Concepts + +Path expressions are an abstraction above [paths](/terraform/plugin/framework/paths). This page assumes knowledge of path concepts and implementations. + +At its core, expressions implement the following on top of paths: + +- Information that designates whether path information is intended to be absolute, similar to paths, or relative, where it is assumed it will be merged with other absolute path information. +- Parent steps, which enables backwards traversal towards the root of a schema in relative paths, after being merged with other absolute path information. +- Path matching, which enables path information to logically return one or more actual paths. + +Similar to paths, expressions are built using steps. There are expression steps which directly correspond to exact path steps, such as `AtListIndex()`, `AtMapKey()`, `AtName()`, `AtSetValue()`. Their implementation is the same. However, there are additional expression steps, such as `AtAnyListIndex()`, which cannot be represented in paths due to the potential for ambiguity. + +Path matching is the notion that each expression step implements a method that logically determines if a given exact path step should match. For example, the `AtAnyListIndex()` expression step will accept any exact path step for a list index. Path matching with an expression is a collection of matching each expression step against each exact path step, after resolving any potential parent steps. + +Every path expression must align with the schema definition or an error diagnostic will be raised when working with path matching within the framework. Provider-defined functionality that is schema-based, such as attribute validation and attribute plan modification, are provided an accurate current path expression since that functionality would not be able to determine its own path expression. + +## Building Path Expressions + +The framework implementation for path expressions is in the [`path` package](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/path), with the [`path.Expression` type](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/path#Expression) being the main provider developer interaction point. + +### Building Absolute Path Expressions + +Call the [`path.MatchRoot()` function](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/path#MatchRoot) with an attribute name or block name at the root of the schema to begin an absolute path expression. + +Given this example schema with a root attribute named `example_root_attribute`: + +```go +schema.Schema{ + Attributes: map[string]schema.Attribute{ + "example_root_attribute": schema.StringAttribute{ + Required: true, + }, + }, +} +``` + +The call to `path.MatchRoot()` which matches the location of `example_root_attribute` string value is: + +```go +path.MatchRoot("example_root_attribute") +``` + +For blocks, the beginning of a path expression is similarly defined. Attribute and block names cannot overlap, so the framework automatically handles whether a path expression is referring to an attribute or block to start. + +Given this example schema with a root block named `example_root_block`: + +```go +schema.Schema{ + Blocks: map[string]schema.Block{ + "example_root_block": schema.ListNestedBlock{ + NestedObject: schema.NestedBlockObject{/* ... */}, + }, + }, +} +``` + +The call to `path.MatchRoot()` which matches the location of `example_root_block` list value is: + +```go +path.MatchRoot("example_root_block") +``` + +Once a `path.Expression` is started, it supports a builder pattern, which allows for chaining method calls to construct a full path. + +This example shows a hypothetical path expression that points to any element of a list attribute to highlight the builder pattern: + +```go +path.MatchRoot("example_list_attribute").AtAnyListIndex() +``` + +This pattern can be extended to as many calls as necessary. The [Building Expression Steps section](#building-expression-steps) covers the different framework schema types and any special path step methods. + +### Building Relative Path Expressions + +Relative path expressions are, by nature, contextual to the actual path where they are defined in a schema. Call the [`path.MatchRelative()` function](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/path#MatchRelative) to begin a relative path expression. + +This example shows a relative path expression which references a child attribute: + +```go +schema.Schema{ + Attributes: map[string]schema.Attribute{ + "root_list_attribute": schema.ListNestedAttribute{ + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "nested_list_attribute": schema.ListNestedAttribute{ + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "deeply_nested_string_attribute": schema.StringAttribute{ + Required: true, + }, + }, + }, + Required: true, + Validators: []validator.List{ + exampleValidatorThatAcceptsExpressions( + path.MatchRelative().AtAnyListIndex().AtName("deeply_nested_string_attribute"), + ), + }, + }, + "nested_string_attribute": schema.StringAttribute{ + Required: true, + }, + }, + }, + Required: true, + }, + }, +} +``` + +This example shows a relative path expression which references a different attribute within the same list index: + +```go +schema.Schema{ + Attributes: map[string]schema.Attribute{ + "root_list_attribute": schema.ListNestedAttribute{ + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "nested_list_attribute": schema.ListNestedAttribute{ + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "deeply_nested_string_attribute": schema.StringAttribute{ + Required: true, + }, + }, + }, + Required: true, + Validators: []validator.List{ + exampleValidatorThatAcceptsExpressions( + path.MatchRelative().AtParent().AtName("nested_string_attribute"), + ), + }, + }, + "nested_string_attribute": schema.StringAttribute{ + Required: true, + }, + }, + }, + Required: true, + }, + }, +} +``` + +### Building Expression Steps + +Expressions follow similar schema type rules as paths, in particular [Building Attribute Paths](/terraform/plugin/framework/paths#building-attribute-paths), [Building Nested Attribute Paths](/terraform/plugin/framework/paths#building-nested-attribute-paths), and [Building Block Paths](/terraform/plugin/framework/paths#building-block-paths). + +The following list shows the [`path.Expression` type](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/path#Expression) methods that behave similar to [`path.Path` type](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/path#Path) methods. + +- `AtListIndex()` +- `AtMapKey()` +- `AtName()` +- `AtSetValue()` + +The following table shows the additional [`path.Expression` type](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/path#Expression) methods and their descriptions. + +| Expression Method | Description | +| ------------------ | ----------- | +| `AtAnyListIndex()` | Will return matches for any list index. Can be used anywhere `AtListIndex()` can be used. | +| `AtAnyMapKey()` | Will return matches for any map key. Can be used anywhere `AtMapKey()` can be used. | +| `AtAnySetValue()` | Will return matches for any set value. Can be used anywhere `AtSetValue()` can be used. | +| `AtParent()` | Will remove the last expression step, or put differently, will match the path closer to the root of the schema. | + diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/paths.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/paths.mdx new file mode 100644 index 000000000..1df50cf70 --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/paths.mdx @@ -0,0 +1,556 @@ +--- +page_title: Paths +description: >- + Learn how to implement paths in the Terraform plugin framework. Paths + represent a location within a schema or schema-based data. +--- + +# Paths + +An exact location within a [schema](/terraform/plugin/framework/handling-data/schemas) or schema-based data such as [`tfsdk.Config`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/tfsdk#Config), [`tfsdk.Plan`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/tfsdk#Plan), or [`tfsdk.State`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/tfsdk#State), is referred to as a path. + +## Usage + +Example uses in the framework include: + +- [Diagnostics](/terraform/plugin/framework/diagnostics) intended for a specific attribute, which allows Terraform to show the configuration file and line information. +- [Accessing configuration, plan, or state data](/terraform/plugin/framework/accessing-values) for a specific attribute. +- [Writing plan or state data](/terraform/plugin/framework/writing-state) for a specific attribute. + +More advanced use cases, such as [path based attribute validators](/terraform/plugin/framework/validation#path-based-attribute-validators), typically implement [path expressions](/terraform/plugin/framework/path-expressions) which enables additional logic beyond exact paths. + +## Concepts + +Paths are designed around the underlying Terraform implementation of a schema and schema-based data. Terraform schemas are a tree structure based on value storing attributes, which may have their own structural component, and structural blocks. Both attributes and blocks are associated with specific data types, some of which represent structural or collection types which hold further information which can be traversed. Each traversal into that further information is referred to as a path step. Paths are always absolute and start from the root, or top level, of a schema. + +Given the tree structure of Terraform schemas, descriptions of paths and their steps borrow certain hierarchy terminology such as parent and child. A parent path describes a path without one or more of the final steps of a given path, or put differently, a partial path closer to the root of the schema. A child path describes a path with one or more additional steps beyond a given path, or put differently, a path containing the given path but further from the root of the schema. + +Every path must align with the schema definition or an error diagnostic will be raised when working with paths within the framework. Provider-defined functionality that is schema-based, such as attribute validation and attribute plan modification, are provided an accurate current path since that functionality would not be able to determine its own path. + +[Path expressions](/terraform/plugin/framework/path-expressions) are an abstraction on top of paths, which enable additional use cases with provider-defined functionality, such as relative path, parent step, and wildcard step support. + +## Building Paths + +The framework implementation for paths is in the [`path` package](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/path), with the [`path.Path` type](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/path#Path) being the main provider developer interaction point. Call the [`path.Root()` function](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/path#Root) with an attribute name or block name at the root of the schema to begin a path. + +Given this example schema with a root attribute named `example_root_attribute`: + +```go +schema.Schema{ + Attributes: map[string]schema.Attribute{ + "example_root_attribute": schema.StringAttribute{ + Required: true, + }, + }, +} +``` + +The call to `path.Root()` which matches the location of `example_root_attribute` string value is: + +```go +path.Root("example_root_attribute") +``` + +For blocks, the beginning of a path is similarly defined. Attribute and block names cannot overlap, so the framework automatically handles whether a path is referring to an attribute or block to start. + +Given this example schema with a root block named `example_root_block`: + +```go +schema.Schema{ + Blocks: map[string]schema.Block{ + "example_root_block": schema.ListNestedBlock{ + NestedObject: schema.NestedBlockObject{/* ... */}, + }, + }, +} +``` + +The call to `path.Root()` which matches the location of `example_root_block` list value is: + +```go +path.Root("example_root_block") +``` + +Once a `path.Path` is started, it supports a builder pattern, which allows for chaining method calls to construct a full path. + +This example shows a hypothetical path that points to the first element of a list attribute to highlight the builder pattern: + +```go +path.Root("example_list_attribute").AtListIndex(0) +``` + +This pattern can be extended to as many calls as necessary. The different framework schema types and their associated path step methods are shown in the following sections. + +### Building Attribute Paths + +The following table shows the different [`path.Path` type](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/path#Path) methods associated with building paths for attribute implementations. Attribute types that cannot be traversed further are shown with N/A (not applicable). + +| Attribute Type | Child Path Method | +|---------------------------|--------------------------------------------------------------------------------------------------------| +| `schema.BoolAttribute` | N/A | +| `schema.DynamicAttribute` | N/A | +| `schema.Float32Attribute` | N/A | +| `schema.Float64Attribute` | N/A | +| `schema.Int32Attribute` | N/A | +| `schema.Int64Attribute` | N/A | +| `schema.ListAttribute` | `AtListIndex()` | +| `schema.MapAttribute` | `AtMapKey()` | +| `schema.NumberAttribute` | N/A | +| `schema.ObjectAttribute` | `AtName()` | +| `schema.SetAttribute` | `AtSetValue()` | +| `schema.StringAttribute` | N/A | + +Given following schema example: + +```go +schema.Schema{ + Attributes: map[string]schema.Attribute{ + "root_map_attribute": schema.MapAttribute{ + ElementType: types.StringType, + Required: true, + }, + }, +} +``` + +The path which matches the string value associated with a hypothetical map key of `example-key` of the `root_map_attribute` attribute is: + +```go +path.Root("root_map_attribute").AtMapKey("example-key") +``` + +Any type that supports a child path may define an element type that also supports a child path. Paths can continue to be built using the associated method with each level of the attribute type. + +Given following schema example: + +```go +schema.Schema{ + Attributes: map[string]schema.Attribute{ + "root_map_attribute": schema.MapAttribute{ + ElementType: types.ListType{ + ElemType: types.StringType, + }, + Required: true, + }, + }, +} +``` + +The path which matches the third string value associated with the list value of a hypothetical map key of `example-key` of the `root_map_attribute` attribute is: + +```go +path.Root("root_map_attribute").AtMapKey("example-key").AtListIndex(2) +``` + +Unless there is a very well defined data structure involved, the level of path specificity in the example above is fairly uncommon to manually build in provider logic though. Most provider logic will typically build a path to the value of an attribute (e.g. its first type) and work with that data, or potentially one level deeper in well known cases, since each level introduces additional complexity associated with potentially null or unknown values. Provider logic can instead use an iterative path building approach when dealing with attributes that have multiple levels. + +This example shows an iterative path building approach to handle any map keys and list indices in the above schema: + +```go +attributePath := path.Root("root_map_attribute") + +// attributeValue is an example types.Map value which was previously fetched, +// potentially using the path above. +for mapKey, mapValue := range attributeValue.Elements() { + mapKeyPath := attributePath.AtMapKey(mapKey) + + // ... + + for listIndex, listValue := range mapValue.Elements() { + listIndexPath := mapKeyPath.AtListIndex(listIndex) + + // ... + } +} +``` + +### Building Nested Attribute Paths + +The following table shows the different [`path.Path` type](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/path#Path) methods associated with building paths for nested attributes. + +| Nested Attribute Type | Child Path Method | +| ------------------------------ | ----------------- | +| `schema.ListNestedAttribute` | `AtListIndex()` | +| `schema.MapNestedAttribute` | `AtMapKey()` | +| `schema.SetNestedAttribute` | `AtSetValue()` | +| `schema.SingleNestedAttribute` | `AtName()` | + +Nested attributes eventually implement attributes at child paths, which follow the methods shown in the [Building Attribute Paths section](#building-attribute-paths). + +#### Building List Nested Attributes Paths + +An attribute that implements `schema.ListNestedAttribute` conceptually is a list containing objects with attribute names. + +Given the following schema example: + +```go +schema.Schema{ + Attributes: map[string]schema.Attribute{ + "root_list_attribute": schema.ListNestedAttribute{ + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "nested_string_attribute": schema.StringAttribute{ + Required: true, + }, + }, + }, + Required: true, + }, + }, +} +``` + +The path which matches the list associated with the `root_list_attribute` attribute is: + +```go +path.Root("root_list_attribute") +``` + +The path which matches the first object in the list associated with the `root_list_attribute` attribute is: + +```go +path.Root("root_list_attribute").AtListIndex(0) +``` + +The path which matches the `nested_string_attribute` string value in the first object in the list associated with `root_list_attribute` attribute is: + +```go +path.Root("root_list_attribute").AtListIndex(0).AtName("nested_string_attribute") +``` + +#### Building Map Nested Attributes Paths + +An attribute that implements `schema.MapNestedAttribute` conceptually is a map containing values of objects with attribute names. + +Given the following schema example: + +```go +schema.Schema{ + Attributes: map[string]schema.Attribute{ + "root_map_attribute": schema.MapNestedAttribute{ + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "nested_string_attribute": schema.StringAttribute{ + Required: true, + }, + }, + }, + Required: true, + }, + }, +} +``` + +The path which matches the map associated with the `root_map_attribute` attribute is: + +```go +path.Root("root_map_attribute") +``` + +The path which matches the a hypothetical `"example-key"` object in the map associated with the `root_map_attribute` attribute is: + +```go +path.Root("root_map_attribute").AtMapKey("example-key") +``` + +The path which matches the `nested_string_attribute` string value in a hypothetical `"example-key"` object in the map associated with `root_map_attribute` attribute is: + +```go +path.Root("root_map_attribute").AtMapKey("example-key").AtName("nested_string_attribute") +``` + +#### Building Set Nested Attributes Paths + +An attribute that implements `schema.SetNestedAttribute` conceptually is a set containing objects with attribute names. Attempting to build set nested attribute paths is extremely tedius as set element selection is based on the entire value of the element, including any null or unknown values. Avoid manual set-based path building. Instead, use functionality which supports [path expressions](/terraform/plugin/framework/path-expressions) as that supports wildcard path matching ([`path.Expression` type `AtAnySetValue()` method](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/path#Expression.AtAnySetValue)) or relative paths. + +Given the following schema example: + +```go +schema.Schema{ + Attributes: map[string]schema.Attribute{ + "root_set_attribute": schema.SetNestedAttribute{ + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "nested_string_attribute": schema.StringAttribute{ + Required: true, + }, + }, + }, + Required: true, + }, + }, +} +``` + +The path which matches the set associated with the `root_set_attribute` attribute is: + +```go +path.Root("root_set_attribute") +``` + +Examples below will presume a `nested_string_attribute` string value of `types.StringValue("example")` for brevity. In real world usage, the string value may be `types.StringNull()`, `types.StringUnknown()` or `types.StringValue("something-else")`, which are all considered different set paths from each other. Each additional attribute or block introduces exponentially more possible paths given each attribute or block value may be null, unknown, or a unique known value. + +The path which matches the object associated with the `root_set_attribute` block is: + +```go +path.Root("root_set_attribute").AtSetValue(types.ObjectValueMust( + map[string]attr.Type{ + "nested_string_attribute": types.StringType, + }, + map[string]attr.Value{ + "nested_string_attribute": types.StringValue("example"), + } +)) +``` + +The path which matches the `nested_string_attribute` string value associated with `root_set_attribute` block is: + +```go +path.Root("root_set_attribute").AtSetValue(types.ObjectValueMust( + map[string]attr.Type{ + "nested_string_attribute": types.StringType, + }, + map[string]attr.Value{ + "nested_string_attribute": types.StringValue("example"), + } +)).AtName("nested_string_attribute") +``` + +#### Building Single Nested Attributes Paths + +An attribute that implements `schema.SingleNestedAttribute` conceptually is an object with attribute names. + +Given the following schema example: + +```go +schema.Schema{ + Attributes: map[string]schema.Attribute{ + "root_grouped_attributes": schema.SingleNestedAttribute{ + Attributes: map[string]schema.Attribute{ + "nested_string_attribute": schema.StringAttribute{ + Required: true, + }, + }, + Required: true, + }, + }, +} +``` + +The path which matches the object associated with the `root_grouped_attributes` attribute is: + +```go +path.Root("root_grouped_attributes") +``` + +The path which matches the `nested_string_attribute` string value in the object associated with the `root_grouped_attributes` attribute is: + +```go +path.Root("root_grouped_attributes").AtName("nested_string_attribute") +``` + +### Building Block Paths + +The following table shows the different [`path.Path` type](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/path#Path) methods associated with building paths for blocks. + +| Block Type | Child Path Method | +| -------------------------- | ----------------- | +| `schema.ListNestedBlock` | `AtListIndex()` | +| `schema.SetNestedBlock` | `AtSetValue()` | +| `schema.SingleNestedBlock` | `AtName()` | + +Blocks can implement nested blocks. Paths can continue to be built using the associated method with each level of the block type. + +Blocks eventually implement attributes at child paths, which follow the methods shown in the [Building Attribute Paths section](#building-attribute-paths). Blocks cannot contain nested attributes. + +#### Building List Block Paths + +A block defined with `schema.ListNestedBlock` conceptually is a list containing objects with attribute or block names. + +Given following schema example: + +```go +schema.Schema{ + Blocks: map[string]schema.Block{ + "root_list_block": schema.ListNestedBlock{ + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "block_string_attribute": schema.StringAttribute{ + Required: true, + }, + }, + Blocks: map[string]schema.Block{ + "nested_list_block": schema.ListNestedBlock{ + Attributes: map[string]schema.Attribute{ + "nested_block_string_attribute": schema.StringAttribute{ + Required: true, + }, + }, + }, + }, + }, + }, + }, +} +``` + +The path which matches the list associated with the `root_list_block` block is: + +```go +path.Root("root_list_block") +``` + +The path which matches the first object in the list associated with the `root_list_block` block is: + +```go +path.Root("root_list_block").AtListIndex(0) +``` + +The path which matches the `block_string_attribute` string value in the first object in the list associated with `root_list_block` block is: + +```go +path.Root("root_list_block").AtListIndex(0).AtName("block_string_attribute") +``` + +The path which matches the `nested_list_block` list in the first object in the list associated with `root_list_block` block is: + +```go +path.Root("root_list_block").AtListIndex(0).AtName("nested_list_block") +``` + +The path which matches the `nested_block_string_attribute` string value in the first object in the list associated with the `nested_list_block` list in the first object in the list associated with `root_list_block` block is: + +```go +path.Root("root_list_block").AtListIndex(0).AtName("nested_list_block").AtListIndex(0).AtName("nested_block_string_attribute") +``` + +#### Building Set Block Paths + +A block defined with `schema.SetNestedBlock` conceptually is a set containing objects with attribute or block names. Attempting to build set block paths is extremely tedius as set element selection is based on the entire value of the element, including any null or unknown values. Avoid manual set-based path building. Instead, use functionality which supports [path expressions](/terraform/plugin/framework/path-expressions) as that supports wildcard path matching ([`path.Expression` type `AtAnySetValue()` method](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/path#Expression.AtAnySetValue)) or relative paths. + +Given following schema example: + +```go +schema.Schema{ + Blocks: map[string]schema.Block{ + "root_set_block": schema.SetNestedBlock{ + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "block_string_attribute": schema.StringAttribute{ + Optional: true, + }, + }, + }, + }, + }, +} +``` + +The path which matches the set associated with the `root_set_block` block is: + +```go +path.Root("root_set_block") +``` + +Examples below will presume a `block_string_attribute` string value of `types.StringValue("example")` for brevity. In real world usage, the string value may be `types.StringNull()`, `types.StringUnknown()` or `types.StringValue("something-else")`, which are all considered different set paths from each other. Each additional attribute or block introduces exponentially more possible paths given each attribute or block value may be null, unknown, or a unique known value. + +The path which matches the object associated with the `root_set_block` block is: + +```go +path.Root("root_set_block").AtSetValue(types.ObjectValueMust( + map[string]attr.Type{ + "block_string_attribute": types.StringType, + }, + map[string]attr.Value{ + "block_string_attribute": types.StringValue("example"), + } +)) +``` + +The path which matches the `block_string_attribute` string value associated with `root_set_block` block is: + +```go +path.Root("root_set_block").AtSetValue(types.ObjectValueMust( + map[string]attr.Type{ + "block_string_attribute": types.StringType, + }, + map[string]attr.Value{ + "block_string_attribute": types.StringValue("example"), + } +)).AtName("block_string_attribute") +``` + +#### Building Single Block Paths + +A block defined with `schema.SingleNestedBlock` conceptually is an object with attribute or block names. + +Given following schema example: + +```go +schema.Schema{ + Blocks: map[string]schema.Block{ + "root_single_block": schema.SingleNestedBlock{ + Attributes: map[string]schema.Attribute{ + "block_string_attribute": schema.StringAttribute{ + Required: true, + }, + }, + Blocks: map[string]schema.Block{ + "nested_single_block": schema.SingleNestedBlock{ + Attributes: map[string]schema.Attribute{ + "nested_block_string_attribute": schema.StringAttribute{ + Required: true, + }, + }, + }, + }, + }, + }, +} +``` + +The path which matches the object associated with the `root_single_block` block is: + +```go +path.Root("root_single_block") +``` + +The path which matches the `block_string_attribute` string value in the object associated with `root_single_block` block is: + +```go +path.Root("root_single_block").AtName("block_string_attribute") +``` + +The path which matches the `nested_single_block` object in the object associated with `root_single_block` block is: + +```go +path.Root("root_single_block").AtName("nested_single_block") +``` + +The path which matches the `nested_block_string_attribute` string value in the object associated with the `nested_single_block` in the object associated with `root_single_block` block is: + +```go +path.Root("root_single_block").AtName("nested_single_block").AtName("nested_block_string_attribute") +``` + +### Building Dynamic Attribute Paths + +An attribute that implements `schema.DynamicAttribute` does not have a statically defined type, as the underlying value type is determined at runtime. When building paths for dynamic values, always target the root dynamic attribute. + +Given the following schema example: + +```go +schema.Schema{ + Attributes: map[string]schema.Attribute{ + "root_dynamic_attribute": schema.DynamicAttribute{ + Required: true, + }, + }, +} +``` + +The path which matches the dynamic value associated with the `root_dynamic_attribute` attribute is: + +```go +path.Root("root_dynamic_attribute") +``` diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/schemas.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/schemas.mdx new file mode 100644 index 000000000..e406565e5 --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/schemas.mdx @@ -0,0 +1,99 @@ +--- +page_title: Schemas +description: >- + Learn how to define a schema using the Terraform plugin framework. Schemas + specify the constraints of Terraform configuration blocks. +--- + +# Schemas + +Schemas specify the constraints of Terraform configuration blocks. They define what fields a provider, +resource, or data source configuration block has, and give Terraform metadata +about those fields. You can think of the schema as the "type information" or +the "shape" of a resource, data source, or provider. + +Each concept has its own `schema` package and `Schema` type, which defines functionality available to that concept: + +- [Providers](/terraform/plugin/framework/providers): [`provider/schema.Schema`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/provider/schema#Schema) +- [Resources](/terraform/plugin/framework/resources): [`resource/schema.Schema`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema#Schema) +- [Data Sources](/terraform/plugin/framework/data-sources): [`datasource/schema.Schema`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/datasource/schema#Schema) +- [Ephemeral Resources](/terraform/plugin/framework/ephemeral-resources): [`ephemeral/schema.Schema`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/ephemeral/schema#Schema) + +During execution of the [`terraform validate`](/terraform/cli/commands/validate), [`terraform plan`](/terraform/cli/commands/plan) and [`terraform apply`](/terraform/cli/commands/apply) commands, Terraform calls the provider [`GetProviderSchema`](/terraform/plugin/framework/internals/rpcs#getproviderschema-rpc) RPC, in which the framework calls the [`provider.Provider` interface `Schema` method](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/provider#Provider.Schema), the [`resource.Resource` interface `Schema` method](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource#Resource.Schema), [`datasource.DataSource` interface `Schema` method](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/datasource#DataSource.Schema), and the [`ephemeral.EphemeralResource` interface `Schema` method](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/ephemeral#EphemeralResource.Schema) on each of the resource types, respectively. + +## Version + +-> Version is only valid for resources. + +Every schema has a version, which is an integer that allows you to track changes to your schemas. It is generally only used when +[upgrading resource state](/terraform/plugin/framework/resources/state-upgrade), to help massage resources created with earlier +schemas into the shape defined by the current schema. It will never be used for +provider or data source schemas and can be omitted. + +## DeprecationMessage + +Not every resource, data source, ephemeral resource, or provider will be supported forever. +Sometimes designs change or APIs are deprecated. Schemas that have their +`DeprecationMessage` property set will display that message as a warning when +that provider, data source, or resource is used. A good message will tell +practitioners that the provider, resource, or data source is deprecated, and +will indicate a migration strategy. + +## Description + +Various tooling like +[terraform-plugin-docs](https://github.com/hashicorp/terraform-plugin-docs) and +the [language server](https://github.com/hashicorp/terraform-ls) can use +metadata in the schema to generate documentation or offer a better editor +experience for practitioners. Use the `Description` property to add a description of a resource, data source, or provider that these tools can leverage. + +## MarkdownDescription + +Similar to the `Description` property, the `MarkdownDescription` is used to +provide a markdown-formatted version of the description to tooling like +[terraform-plugin-docs](https://github.com/hashicorp/terraform-plugin-docs). It +is a best practice to only alter the formatting, not the content, between the +`Description` and `MarkdownDescription`. + +At the moment, if the `MarkdownDescription` property is set it will always be +used instead of the `Description` property. It is possible that a different strategy may be employed in the future to surface descriptions to other tooling in a different format, so we recommend specifying both fields. + +## Unit Testing + +Schemas can be unit tested via each of the `schema.Schema` type `ValidateImplementation()` methods. This unit testing raises schema implementation issues more quickly in comparison to [acceptance tests](/terraform/plugin/framework/acctests), but does not replace the purpose of acceptance testing. + +In this example, a `thing_resource_test.go` file is created alongside the `thing_resource.go` implementation file: + +```go +import ( + "context" + "testing" + + // The fwresource import alias is so there is no collistion + // with the more typical acceptance testing import: + // "github.com/hashicorp/terraform-plugin-testing/helper/resource" + fwresource "github.com/hashicorp/terraform-plugin-framework/resource" +) + +func TestThingResourceSchema(t *testing.T) { + t.Parallel() + + ctx := context.Background() + schemaRequest := fwresource.SchemaRequest{} + schemaResponse := &fwresource.SchemaResponse{} + + // Instantiate the resource.Resource and call its Schema method + NewThingResource().Schema(ctx, schemaRequest, schemaResponse) + + if schemaResponse.Diagnostics.HasError() { + t.Fatalf("Schema method diagnostics: %+v", schemaResponse.Diagnostics) + } + + // Validate the schema + diagnostics := schemaResponse.Schema.ValidateImplementation(ctx) + + if diagnostics.HasError() { + t.Fatalf("Schema validation diagnostics: %+v", diagnostics) + } +} +``` diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/terraform-concepts.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/terraform-concepts.mdx new file mode 100644 index 000000000..d8602e8a3 --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/terraform-concepts.mdx @@ -0,0 +1,129 @@ +--- +page_title: Terraform data concepts +description: >- + Learn how the Terraform plugin framework handles data by mapping Terraform + configuration to schemas, attributes, and blocks. +--- + +# Terraform data concepts + +This page describes Terraform concepts as they relate to handling data within framework-based provider code. The [What is Terraform](/terraform/intro), [Terraform language](/terraform/language), and [Plugin Development](/terraform/plugin) documentation covers more general concepts behind Terraform's workflow, its configuration, and how it interacts with providers. + +## Schemas + +Schemas specify the data structure and types of a provider, resource, data source, or ephemeral resource that is exposed to Terraform. This includes the configuration written by practitioners, any planning data, and the state stored by Terraform which can be referenced in other configuration. Providers, resources, data sources, and ephemeral resources have their own concept-specific types and available functionality. + +Each part of the data within a schema is defined as either an attribute or block. In general, attributes set values and blocks are containers for other attributes and blocks. Each have differing configuration syntax and behaviors. + +Discover more about framework implementations of this concept in the [schema](/terraform/plugin/framework/handling-data/schemas) documentation. + +### Attributes + +Attributes set values in a schema, such as a string or list. The [Terraform type system](/terraform/language/expressions/types) documentation provides a general overview of the available kinds of data within Terraform, which is similar with type system concepts available in many programming languages. However, there are subtle differences with values discussed in the [type system](#type-system) section. + +Discover more about the framework implementations of this concept in the [attribute](/terraform/plugin/framework/handling-data/attributes) documentation. + +### Blocks + + + +Use [nested attributes](/terraform/plugin/framework/handling-data/attributes#nested-attribute-types) for new schema implementations. Block support is mainly for migrating prior SDK-based providers. + + + +A block is a container for other attributes and blocks. Terraform implements many top level blocks, such as `provider` and `resource`, while a schema supports nested blocks. + +Discover more about the framework implementations of this concept in the [block](/terraform/plugin/framework/handling-data/blocks) documentation. + +### Schema Based Data + +Schemas provide the structures and types for representing data with Terraform. This section discusses the concepts behind the different types of data influenced by a schema. + +#### Configuration + +As part of the Terraform workflow, a practitioner writes [configuration](/terraform/language) files, which are parsed into a graph of operations by Terraform commands. The structures and types of that configuration is the most visible aspect of a schema, since a schema has a direct effect on the expected configuration syntax. + +In Terraform operations where the configuration data is available to providers, the framework typically represents this as a `Config` field in the request type. + +#### Plan + + + +Only managed resources implement this data concept. + + + +As part of the Terraform workflow, any configuration changes are planned before they are applied into [state](/terraform/language/state) so practitioners have an opportunity to review whether those anticipated changes have their intended effect. Conceptually, this data represents the merging of configuration data and any prior state data. Terraform refers to this data as the proposed new state, while the framework more generally refers to this data as the plan. + +Plan data requires careful handling to prevent unexpected Terraform errors for practitioners. The framework implements various schema concepts and logic discussed in the [resource plan modification](/terraform/plugin/framework/resources/plan-modification) documentation. In-depth documentation about Terraform requirements is available in the [Terraform Resource Instance Change Lifecycle](https://github.com/hashicorp/terraform/blob/main/docs/resource-instance-change-lifecycle.md) documentation. + +In Terraform operations where the plan data is available to providers, the framework typically represents this as a `Plan` field in the request or response type. + +#### State + + + +Only managed resources and data sources implement this data concept. + + + +As part of the Terraform workflow, any data that should be stored for configuration references or future Terraform executions must be written to the [state](/terraform/language/state). This data must exactly match any configuration data, and if applicable, any plan data with [unknown values](#unknown-values) converted to known values. + +In Terraform operations where the plan data is available to providers, the framework typically represents this as a `State` field in the request or response type. + +## Type System + +The [Terraform type system](/terraform/language/expressions/types) supports deeper functionality than the standard type systems built into programming languages. While the types in the Terraform type system are similar to what is found in most languages, values have extra metadata associated with them. The framework implements its own [types](/terraform/plugin/framework/handling-data/types) to prevent the loss of this additional metadata that cannot be represented by Go built-in types. + +Important value metadata concepts when implementing a provider include: + +- [Null values](#null-values): Absence of a value. +- [Unknown values](#unknown-values): Value that is not yet known. + +As Terraform exposes additional value metadata to providers, the framework type system will be updated. Therefore, it is always recommended to use the framework type system to ensure all Terraform value functionality is available in provider code. + +### Null Values + +Null represents the absence of a Terraform value. It is usually +encountered with optional attributes that the practitioner neglected to specify +a value for, but can show up on any non-required attribute. Required attributes +can never be null. + +### Unknown Values + +Unknown represents a Terraform value that is not yet known. Terraform +uses a graph of providers, resources, and data sources to do things in the +right order, and when a provider, resource, or data source relies on a value +from another provider, resource, or data source that has not been resolved yet, +it represents that state by using the unknown value. For example: + +```hcl +resource "example_foo" "bar" { + hello = "world" + demo = true +} + +resource "example_baz" "quux" { + foo_id = example_foo.bar.id +} +``` + +In the example above, `example_baz.quux` is relying on the `id` attribute of +`example_foo.bar`. The `id` attribute of `example_foo.bar` isn't known until +after the apply. The plan would list it as `(known after apply)`. During the +plan phase, `example_baz.quux` would get an unknown value as the value for +`foo_id`. + +Because they can result from interpolations in the practitioner's config, +you have no control over what attributes may contain an unknown +value. However, by the time a resource is expected to be created, read, updated, or +deleted, only its computed attributes can be unknown. The rest are +guaranteed to have known values (or be null). + +For concepts such as resource and data source [configuration validation](/terraform/plugin/framework/validation), +this means that Terraform guarantees to call validation for a non-computed attribute +eventually with a known value, so the provider should typically avoid returning error diagnostics +when encountering an unknown value during validation. + +Provider configuration values [can also be unknown](/terraform/plugin/framework/providers#unknown-values), and +providers should handle that situation, even if that means just returning an error. diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/types/bool.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/types/bool.mdx new file mode 100644 index 000000000..22b69d83e --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/types/bool.mdx @@ -0,0 +1,107 @@ +--- +page_title: Boolean types +description: >- + Learn how to implement boolean value types with the Terraform plugin framework. +--- + +# Bool types + +Bool types store a boolean true or false value. + +By default, booleans from [schema](/terraform/plugin/framework/handling-data/schemas) (configuration, plan, and state) data are represented in the framework by [`types.BoolType`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#BoolType) and its associated value storage type of [`types.Bool`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#Bool). These types fully support Terraform's [type system concepts](/terraform/plugin/framework/handling-data/terraform-concepts) that cannot be represented in Go built-in types, such as `*bool`. Framework types can be [extended](#extending) by provider code or shared libraries to provide specific use case functionality. + +## Schema Definitions + +Use one of the following attribute types to directly add a bool value to a [schema](/terraform/plugin/framework/handling-data/schemas) or [nested attribute type](/terraform/plugin/framework/handling-data/attributes#nested-attribute-types): + +| Schema Type | Attribute Type | +|-------------|----------------| +| [Data Source](/terraform/plugin/framework/data-sources) | [`schema.BoolAttribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/datasource/schema#BoolAttribute) | +| [Provider](/terraform/plugin/framework/providers) | [`schema.BoolAttribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/provider/schema#BoolAttribute) | +| [Resource](/terraform/plugin/framework/resources) | [`schema.BoolAttribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema#BoolAttribute) | +| [Ephemeral Resource](/terraform/plugin/framework/ephemeral-resources) | [`schema.BoolAttribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/ephemeral/schema#BoolAttribute) | + +If the bool value should be the element type of a [collection attribute type](/terraform/plugin/framework/handling-data/attributes#collection-attribute-types), set the `ElemType` field to `types.BoolType` or the appropriate [custom type](#extending). + +If the bool value should be a value type of an [object attribute type](/terraform/plugin/framework/handling-data/attributes#object-attribute-type), set the `AttrTypes` map value to `types.BoolType` or the appropriate [custom type](#extending). + +## Accessing Values + + + +Review the [attribute documentation](/terraform/plugin/framework/handling-data/attributes/bool#accessing-values) to understand how schema-based data gets mapped into accessible values, such as a `types.Bool` in this case. + + + +Access `types.Bool` information via the following methods: + +* [`(types.Bool).IsNull() bool`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types/basetypes#BoolValue.IsNull): Returns true if the bool is null. +* [`(types.Bool).IsUnknown() bool`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types/basetypes#BoolValue.IsUnknown): Returns true if the bool is unknown. +* [`(types.Bool).ValueBool() bool`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types/basetypes#BoolValue.ValueBool): Returns the known bool, or `false` if null or unknown. +* [`(types.Bool).ValueBoolPointer() *bool`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types/basetypes#BoolValue.ValueBoolPointer): Returns a bool pointer to a known value, `nil` if null, or a pointer to `false` if unknown. + +In this example, a bool value is checked for being null or unknown value first, before accessing its known value: + +```go +// Example data model definition +// type ExampleModel struct { +// ExampleAttribute types.Bool `tfsdk:"example_attribute"` +// } +// +// This would be filled in, such as calling: req.Plan.Get(ctx, &data) +var data ExampleModel + +// optional logic for handling null value +if data.ExampleAttribute.IsNull() { + // ... +} + +// optional logic for handling unknown value +if data.ExampleAttribute.IsUnknown() { + // ... +} + +// myBool now contains a Go bool with the known value +myBool := data.ExampleAttribute.ValueBool() +``` + +## Setting Values + +Call one of the following to create a `types.Bool` value: + +* [`types.BoolNull()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#BoolNull): A null bool value. +* [`types.BoolUnknown()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#BoolUnknown): An unknown bool value. +* [`types.BoolValue(bool)`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#BoolValue): A known value. +* [`types.BoolPointerValue(*bool)`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#BoolPointerValue): A known value. + +In this example, a known bool value is created: + +```go +types.BoolValue(true) +``` + +Otherwise, for certain framework functionality that does not require `types` implementations directly, such as: + +* [`(tfsdk.State).SetAttribute()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/tfsdk#State.SetAttribute) +* [`types.ListValueFrom()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#ListValueFrom) +* [`types.MapValueFrom()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#MapValueFrom) +* [`types.ObjectValueFrom()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#ObjectValueFrom) +* [`types.SetValueFrom()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#SetValueFrom) + +A Go built-in `bool`, `*bool` (only with typed `nil`, `(*bool)(nil)`), or type alias of `bool` such as `type MyBoolType bool` can be used instead. + +In this example, a `bool` is directly used to set a bool attribute value: + +```go +diags := resp.State.SetAttribute(ctx, path.Root("example_attribute"), true) +``` + +In this example, a `types.List` of `types.Bool` is created from a `[]bool`: + +```go +listValue, diags := types.ListValueFrom(ctx, types.BoolType, []bool{true, false}) +``` + +## Extending + +The framework supports extending its base type implementations with [custom types](/terraform/plugin/framework/handling-data/types/custom). These can adjust expected provider code usage depending on their implementation. diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/types/custom.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/types/custom.mdx new file mode 100644 index 000000000..ad47f3372 --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/types/custom.mdx @@ -0,0 +1,435 @@ +--- +page_title: Custom types +description: >- + Learn how to implement custom types with the Terraform plugin framework. +--- + +# Custom types + +Use existing custom types or develop custom types to consistently define behaviors for a kind of value across schemas. Custom types are supported on top of any framework-defined type. + +Supported behaviors for custom types include: + +- Semantic Equality: Keep a prior value if a new value is inconsequentially different, such as preventing drift detection of JSON strings with differing property ordering. +- Validation: Raise warning and/or error diagnostics based on the value, such as not following a specified format or enumeration of values. + +## Example Use Cases + +- Encoded values, such as an XML or YAML string. +- Provider-specific values, such as an AWS ARN or AzureRM location string. +- Networking values, such as a MAC address. +- Time values, such as an ISO 8601 string. + +## Common Custom Types +The following Go modules contain custom type implementations covering common use cases with validation and semantic equality logic (where appropriate). +- [`terraform-plugin-framework-jsontypes`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework-jsontypes) + - JSON strings (both normalized and exact matching) +- [`terraform-plugin-framework-nettypes`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework-nettypes) + - IPv4/IPv6 addresses and CIDRs +- [`terraform-plugin-framework-timetypes`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework-timetypes) + - Timestamps (such as RFC3339) + +## Concepts + +Individual data value handling in the framework is performed by a pair of associated Go types: + +- Schema Types: Define the associated value type and logic to create a value. +- Value Types: Define behaviors associated with the value, data storage of the value, and data storage of the value state (null, unknown, or known). + +The framework defines a standard set these associated Go types referred to by the "base type" terminology. Extending these base types is referred to by the "custom type" terminology. + +## Using Custom Types + +Use a custom type by switching the schema definition and data handling from a framework-defined type to the custom type. + +### Schema Definition + +The framework schema types accept a `CustomType` field where applicable, such as the [`resource/schema.StringAttribute` type](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema#StringAttribute.CustomType). When the `CustomType` is omitted, the framework defaults to the associated base type. + +Implement the `CustomType` field in a schema type to switch from the base type to a custom type. + +In this example, a string attribute implements a custom type. + +```go +schema.StringAttribute{ + CustomType: CustomStringType{}, + // ... other fields ... +} +``` + +### Data Handling + +Each custom type will also include a value type, which must be used anywhere the value is referenced in data source, provider, or resource logic. + +Switch any usage of a base value type to the custom value type. Any logic will need to be updated to match the custom value type implementation. + +In this example, a custom value type is used in a data model approach: + +```go +type ThingModel struct { + // Instead of types.String + Timestamp CustomStringValue `tfsdk:"timestamp"` + // ... other fields ... +} +``` + +## Developing Custom Types + +Create a custom type by extending an existing framework schema type and its associated value type. Once created, define [semantic equality](#semantic-equality) and/or [validation](#validation) logic for the custom type. + +### Schema Type + +Extend a framework schema type by creating a Go type that implements one of the `github.com/hashicorp/terraform-plugin-framework/types/basetypes` package `*Typable` interfaces. + + + +The commonly used `types` package types are aliases to the `basetypes` package types mentioned in this table. + + + +| Framework Schema Type | Custom Schema Type Interface | +| --- | --- | +| [`basetypes.BoolType`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types/basetypes#BoolType) | [`basetypes.BoolTypable`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types/basetypes#BoolTypable) | +| [`basetypes.DynamicType`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types/basetypes#DynamicType) | [`basetypes.DynamicTypable`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types/basetypes#DynamicTypable) | +| [`basetypes.Float32Type`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types/basetypes#Float32Type) | [`basetypes.Float32Typable`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types/basetypes#Float32Typable) | +| [`basetypes.Float64Type`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types/basetypes#Float64Type) | [`basetypes.Float64Typable`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types/basetypes#Float64Typable) | +| [`basetypes.Int32Type`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types/basetypes#Int32Type) | [`basetypes.Int32Typable`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types/basetypes#Int32Typable) | +| [`basetypes.Int64Type`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types/basetypes#Int64Type) | [`basetypes.Int64Typable`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types/basetypes#Int64Typable) | +| [`basetypes.ListType`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types/basetypes#ListType) | [`basetypes.ListTypable`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types/basetypes#ListTypable) | +| [`basetypes.MapType`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types/basetypes#MapType) | [`basetypes.MapTypable`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types/basetypes#MapTypable) | +| [`basetypes.NumberType`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types/basetypes#NumberType) | [`basetypes.NumberTypable`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types/basetypes#NumberTypable) | +| [`basetypes.ObjectType`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types/basetypes#ObjectType) | [`basetypes.ObjectTypable`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types/basetypes#ObjectTypable) | +| [`basetypes.SetType`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types/basetypes#SetType) | [`basetypes.SetTypable`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types/basetypes#SetTypable) | +| [`basetypes.StringType`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types/basetypes#StringType) | [`basetypes.StringTypable`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types/basetypes#StringTypable) | + +It is recommended to use Go type embedding of the base type to simplify the implementation and ensure it is up to date with the latest data handling features of the framework. With type embedding, the following [`attr.Type`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/attr#Type) methods must be overridden by the custom type to prevent confusing errors: + +- `Equal(attr.Type) bool` +- `ValueFromTerraform(context.Context, tftypes.Value) (attr.Value, error)` +- `ValueType(context.Context) attr.Value` +- `String() string` +- `ValueFrom{TYPE}(context.Context, basetypes.{TYPE}Value) (basetypes.{TYPE}Valuable, diag.Diagnostics)` + - This method signature is different for each `*Typable` custom schema type interface listed above, for example `basetypes.StringTypable` is defined as [`ValueFromString`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types/basetypes#StringTypable) + +In this example, the `basetypes.StringTypable` interface is implemented to create a custom string type with an associated [value type](#value-type): + +```go +import ( + "context" + "fmt" + + "github.com/hashicorp/terraform-plugin-framework/attr" + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/types/basetypes" + "github.com/hashicorp/terraform-plugin-go/tftypes" +) + +// Ensure the implementation satisfies the expected interfaces +var _ basetypes.StringTypable = CustomStringType{} + +type CustomStringType struct { + basetypes.StringType + // ... potentially other fields ... +} + +func (t CustomStringType) Equal(o attr.Type) bool { + other, ok := o.(CustomStringType) + + if !ok { + return false + } + + return t.StringType.Equal(other.StringType) +} + +func (t CustomStringType) String() string { + return "CustomStringType" +} + +func (t CustomStringType) ValueFromString(ctx context.Context, in basetypes.StringValue) (basetypes.StringValuable, diag.Diagnostics) { + // CustomStringValue defined in the value type section + value := CustomStringValue{ + StringValue: in, + } + + return value, nil +} + +func (t CustomStringType) ValueFromTerraform(ctx context.Context, in tftypes.Value) (attr.Value, error) { + attrValue, err := t.StringType.ValueFromTerraform(ctx, in) + + if err != nil { + return nil, err + } + + stringValue, ok := attrValue.(basetypes.StringValue) + + if !ok { + return nil, fmt.Errorf("unexpected value type of %T", attrValue) + } + + stringValuable, diags := t.ValueFromString(ctx, stringValue) + + if diags.HasError() { + return nil, fmt.Errorf("unexpected error converting StringValue to StringValuable: %v", diags) + } + + return stringValuable, nil +} + +func (t CustomStringType) ValueType(ctx context.Context) attr.Value { + // CustomStringValue defined in the value type section + return CustomStringValue{} +} +``` + +### Value Type + +Extend a framework value type by creating a Go type that implements one of the `github.com/hashicorp/terraform-plugin-framework/types/basetypes` package `*Valuable` interfaces. + + + +The commonly used `types` package types are aliases to the `basetypes` package types mentioned in this table. + + + +| Framework Schema Type | Custom Schema Type Interface | +| --- | --- | +| [`basetypes.BoolValue`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types/basetypes#BoolValue) | [`basetypes.BoolValuable`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types/basetypes#BoolValuable) | +| [`basetypes.DynamicValue`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types/basetypes#DynamicValue) | [`basetypes.DynamicValuable`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types/basetypes#DynamicValuable) | +| [`basetypes.Float64Value`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types/basetypes#Float64Value) | [`basetypes.Float64Valuable`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types/basetypes#Float64Valuable) | +| [`basetypes.Int64Value`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types/basetypes#Int64Value) | [`basetypes.Int64Valuable`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types/basetypes#Int64Valuable) | +| [`basetypes.ListValue`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types/basetypes#ListValue) | [`basetypes.ListValuable`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types/basetypes#ListValuable) | +| [`basetypes.MapValue`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types/basetypes#MapValue) | [`basetypes.MapValuable`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types/basetypes#MapValuable) | +| [`basetypes.NumberValue`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types/basetypes#NumberValue) | [`basetypes.NumberValuable`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types/basetypes#NumberValuable) | +| [`basetypes.ObjectValue`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types/basetypes#ObjectValue) | [`basetypes.ObjectValuable`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types/basetypes#ObjectValuable) | +| [`basetypes.SetValue`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types/basetypes#SetValue) | [`basetypes.SetValuable`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types/basetypes#SetValuable) | +| [`basetypes.StringValue`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types/basetypes#StringValue) | [`basetypes.StringValuable`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types/basetypes#StringValuable) | + +It is recommended to use Go type embedding of the base type to simplify the implementation and ensure it is up to date with the latest data handling features of the framework. With type embedding, the following [`attr.Value`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/attr#Value) methods must be overridden by the custom type to prevent confusing errors: + +- `Equal(attr.Value) bool` +- `Type(context.Context) attr.Type` + + + +The overridden `Equal(attr.Value) bool` method should not contain [Semantic Equality](#semantic-equality) logic. `Equal` should only check the type of `attr.Value` and the underlying base value. + +An example of this can be found below with the `CustomStringValue` implementation. + + + +In this example, the `basetypes.StringValuable` interface is implemented to create a custom string value type with an associated [schema type](#schema-type): + +```go +import ( + "context" + "fmt" + + "github.com/hashicorp/terraform-plugin-framework/attr" + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/types/basetypes" + "github.com/hashicorp/terraform-plugin-go/tftypes" +) + +// Ensure the implementation satisfies the expected interfaces +var _ basetypes.StringValuable = CustomStringValue{} + +type CustomStringValue struct { + basetypes.StringValue + // ... potentially other fields ... +} + +func (v CustomStringValue) Equal(o attr.Value) bool { + other, ok := o.(CustomStringValue) + + if !ok { + return false + } + + return v.StringValue.Equal(other.StringValue) +} + +func (v CustomStringValue) Type(ctx context.Context) attr.Type { + // CustomStringType defined in the schema type section + return CustomStringType{} +} +``` + +From this point, the custom type can be extended with other behaviors. + +### Semantic Equality + +Semantic equality handling enables the value type to automatically keep a prior value when a new value is determined to be inconsequentially different. This handling can prevent unexpected drift detection for values and in some cases prevent Terraform data handling errors. + +This value type functionality is checked in the following scenarios: + +- When refreshing a data source, the response state value from the `Read` method logic is compared to the configuration value. +- When refreshing a resource, the response new state value from the `Read` method logic is compared to the request prior state value. +- When creating or updating a resource, the response new state value from the `Create` or `Update` method logic is compared to the request plan value. + +The framework will only call semantic equality logic if both the prior and new values are known. Null or unknown values are unnecessary to check. When working with collection types, the framework automatically calls semantic equality logic of element types. When working with object types, the framework automatically calls semantic equality of underlying attribute types. + +Implement the associated `github.com/hashicorp/terraform-plugin-framework/types/basetypes` package `*ValuableWithSemanticEquals` interface on the value type to define and enable this behavior. + +In this example, the custom string value type will preserve the prior value if the expected RFC3339 timestamps are considered equivalent: + +```go +// CustomStringValue defined in the value type section +// Ensure the implementation satisfies the expected interfaces +var _ basetypes.StringValuableWithSemanticEquals = CustomStringValue{} + +func (v CustomStringValue) StringSemanticEquals(ctx context.Context, newValuable basetypes.StringValuable) (bool, diag.Diagnostics) { + var diags diag.Diagnostics + + // The framework should always pass the correct value type, but always check + newValue, ok := newValuable.(CustomStringValue) + + if !ok { + diags.AddError( + "Semantic Equality Check Error", + "An unexpected value type was received while performing semantic equality checks. "+ + "Please report this to the provider developers.\n\n"+ + "Expected Value Type: "+fmt.Sprintf("%T", v)+"\n"+ + "Got Value Type: "+fmt.Sprintf("%T", newValuable), + ) + + return false, diags + } + + // Skipping error checking if CustomStringValue already implemented RFC3339 validation + priorTime, _ := time.Parse(time.RFC3339, v.StringValue.ValueString()) + + // Skipping error checking if CustomStringValue already implemented RFC3339 validation + newTime, _ := time.Parse(time.RFC3339, newValue.ValueString()) + + // If the times are equivalent, keep the prior value + return priorTime.Equal(newTime), diags +} +``` + +### Validation + +#### Value Validation + +Validation handling in custom value types can be enabled for schema attribute values, or provider-defined function parameters. + +Implement the [`xattr.ValidateableAttribute` interface](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/attr/xattr#ValidateableAttribute) on the custom value type to define and enable validation handling for a schema attribute, which will automatically raise warning and/or error diagnostics when a value is determined to be invalid. + +Implement the [`function.ValidateableParameter` interface](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/function#ValidateableParameter) on the custom value type to define and enable validation handling for a provider-defined function parameter, which will automatically raise an error when a value is determined to be invalid. + +If the custom value type is to be used for both schema attribute values and provider-defined function parameters, implement both interfaces. + +```go +// Implementation of the xattr.ValidateableAttribute interface +func (v CustomStringValue) ValidateAttribute(ctx context.Context, req xattr.ValidateAttributeRequest, resp *xattr.ValidateAttributeResponse) { + if v.IsNull() || v.IsUnknown() { + return + } + + err := v.validate(v.ValueString()) + + if err != nil { + resp.Diagnostics.AddAttributeError( + req.Path, + "Invalid RFC 3339 String Value", + "An unexpected error occurred while converting a string value that was expected to be RFC 3339 format. "+ + "The RFC 3339 string format is YYYY-MM-DDTHH:MM:SSZ, such as 2006-01-02T15:04:05Z or 2006-01-02T15:04:05+07:00.\n\n"+ + "Path: "+req.Path.String()+"\n"+ + "Given Value: "+v.ValueString()+"\n"+ + "Error: "+err.Error(), + ) + + return + } +} + +// Implementation of the function.ValidateableParameter interface +func (v CustomStringValue) ValidateParameter(ctx context.Context, req function.ValidateParameterRequest, resp *function.ValidateParameterResponse) { + if v.IsNull() || v.IsUnknown() { + return + } + + err := v.validate(v.ValueString()) + + if err != nil { + resp.Error = function.NewArgumentFuncError( + req.Position, + "Invalid RFC 3339 String Value: "+ + "An unexpected error occurred while converting a string value that was expected to be RFC 3339 format. "+ + "The RFC 3339 string format is YYYY-MM-DDTHH:MM:SSZ, such as 2006-01-02T15:04:05Z or 2006-01-02T15:04:05+07:00.\n\n"+ + fmt.Sprintf("Position: %d", req.Position)+"\n"+ + "Given Value: "+v.ValueString()+"\n"+ + "Error: "+err.Error(), + ) + } +} + +func (v CustomStringValue) validate(in string) error { + _, err := time.Parse(time.RFC3339, in) + + return err +} +``` + +#### Type Validation + + + +`Value` validation should be used in preference to `Type` validation. Refer to [Value Validation](#value-validation) for more information. + +The [`xattr.TypeWithValidate` interface](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/attr/xattr#TypeWithValidate) has been deprecated. Use the [`xattr.ValidateableAttribute` interface](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/attr/xattr#ValidateableAttribute), and [`function.ValidateableParameter` interface](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/function#ValidateableParameter) instead. + + + +Implement the [`xattr.TypeWithValidate`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/attr/xattr#TypeWithValidate) interface on the value type to define and enable this behavior. + + + +This functionality uses the lower level [`tftypes`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-go/tftypes) type system compared to other framework logic. + + + +In this example, the custom string value type will ensure the string is a valid RFC3339 timestamp: + +```go +// CustomStringType defined in the schema type section +func (t CustomStringType) Validate(ctx context.Context, value tftypes.Value, valuePath path.Path) diag.Diagnostics { + if value.IsNull() || !value.IsKnown() { + return nil + } + + var diags diag.Diagnostics + var valueString string + + if err := value.As(&valueString); err != nil { + diags.AddAttributeError( + valuePath, + "Invalid Terraform Value", + "An unexpected error occurred while attempting to convert a Terraform value to a string. "+ + "This generally is an issue with the provider schema implementation. "+ + "Please contact the provider developers.\n\n"+ + "Path: "+valuePath.String()+"\n"+ + "Error: "+err.Error(), + ) + + return diags + } + + if _, err := time.Parse(time.RFC3339, valueString); err != nil { + diags.AddAttributeError( + valuePath, + "Invalid RFC 3339 String Value", + "An unexpected error occurred while converting a string value that was expected to be RFC 3339 format. "+ + "The RFC 3339 string format is YYYY-MM-DDTHH:MM:SSZ, such as 2006-01-02T15:04:05Z or 2006-01-02T15:04:05+07:00.\n\n"+ + "Path: "+valuePath.String()+"\n"+ + "Given Value: "+valueString+"\n"+ + "Error: "+err.Error(), + ) + + return diags + } + + return diags +} +``` diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/types/dynamic.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/types/dynamic.mdx new file mode 100644 index 000000000..5da2afc86 --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/types/dynamic.mdx @@ -0,0 +1,155 @@ +--- +page_title: Dynamic types +description: >- + Learn how to implement dynamic types with the Terraform plugin framework. +--- + +# Dynamic types + + + +Static types should always be preferred over dynamic types, when possible. + +Developers dealing with dynamic data will need to have extensive knowledge of the [Terraform type system](/terraform/language/expressions/types) to properly handle all potential practitioner configuration scenarios. + +Refer to [Dynamic Data - Considerations](/terraform/plugin/framework/handling-data/dynamic-data#considerations) for more information. + + + +Dynamic is a container type that can have an underlying value of **any** type. + +By default, dynamic values from [schema](/terraform/plugin/framework/handling-data/schemas) (configuration, plan, and state) data are represented in the framework by [`types.DynamicType`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#DynamicType) and its associated value storage type of [`types.Dynamic`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#Dynamic). These types fully support Terraform's [type system concepts](/terraform/plugin/framework/handling-data/terraform-concepts) that cannot be represented in Go built-in types. Framework types can be [extended](#extending) by provider code or shared libraries to provide specific use case functionality. + +## Schema Definitions + +Use one of the following attribute types to directly add a dynamic value to a [schema](/terraform/plugin/framework/handling-data/schemas) or [nested attribute type](/terraform/plugin/framework/handling-data/attributes#nested-attribute-types): + +| Schema Type | Attribute Type | +|-------------|----------------| +| [Data Source](/terraform/plugin/framework/data-sources) | [`schema.DynamicAttribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/datasource/schema#DynamicAttribute) | +| [Provider](/terraform/plugin/framework/providers) | [`schema.DynamicAttribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/provider/schema#DynamicAttribute) | +| [Resource](/terraform/plugin/framework/resources) | [`schema.DynamicAttribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema#DynamicAttribute) | +| [Ephemeral Resource](/terraform/plugin/framework/ephemeral-resources) | [`schema.DynamicAttribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/ephemeral/schema#DynamicAttribute) | + +Dynamic values are not supported as the element type of a [collection type](/terraform/plugin/framework/handling-data/types#collection-types) or within [collection attribute types](/terraform/plugin/framework/handling-data/attributes#collection-attribute-types). + +If the dynamic value should be a value type of an [object attribute type](/terraform/plugin/framework/handling-data/attributes#object-attribute-type), set the `AttrTypes` map value to `types.DynamicType` or the appropriate [custom type](#extending). + +## Accessing Values + + + +Review the [attribute documentation](/terraform/plugin/framework/handling-data/attributes/dynamic#accessing-values) to understand how schema-based data gets mapped into accessible values, such as a `types.Dynamic` in this case. + + + +Access `types.Dynamic` information via the following methods: + +* [`(types.Dynamic).IsNull() bool`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types/basetypes#DynamicValue.IsNull): Returns true if the dynamic value is null. +* [`(types.Dynamic).IsUnderlyingValueNull() bool`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types/basetypes#DynamicValue.IsUnderlyingValueNull): Returns true if the dynamic value is known but the underlying value is null. See the [Dynamic Data section](/terraform/plugin/framework/handling-data/dynamic-data#handling-underlying-null-and-unknown-values) for more information about null underlying values. +* [`(types.Dynamic).IsUnknown() bool`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types/basetypes#DynamicValue.IsUnknown): Returns true if the dynamic value is unknown. +* [`(types.Dynamic).IsUnderlyingValueUnknown() bool`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types/basetypes#DynamicValue.IsUnderlyingValueUnknown): Returns true if the dynamic value is known but the underlying value is unknown. See the [Dynamic Data section](/terraform/plugin/framework/handling-data/dynamic-data#handling-underlying-null-and-unknown-values) for more information about unknown underlying values. +* [`(types.Dynamic).UnderlyingValue() attr.Value`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types/basetypes#DynamicValue.UnderlyingValue): Returns the underlying value of the dynamic container, will be `nil` if null or unknown. + +In this example, a dynamic value is checked for being null or unknown value first, before accessing its known value: + +```go +// Example data model definition +// type ExampleModel struct { +// ExampleAttribute types.Dynamic `tfsdk:"example_attribute"` +// } +// +// This would be filled in, such as calling: req.Plan.Get(ctx, &data) +var data ExampleModel + +// optional logic for handling null value +if data.ExampleAttribute.IsNull() { + // ... +} + +// optional logic for handling unknown value +if data.ExampleAttribute.IsUnknown() { + // ... +} + +// myDynamicVal now contains the underlying value, determined by Terraform at runtime +myDynamicVal := data.ExampleAttribute.UnderlyingValue() +``` + +### Handling the Underlying Value + +If a dynamic value is known, a [Go type switch](https://go.dev/tour/methods/16) can be used to access the type-specific methods for data handling: + +```go + switch value := data.ExampleAttribute.UnderlyingValue().(type) { + case types.Bool: + // Handle boolean value + case types.Number: + // Handle float64, int64, and number values + case types.List: + // Handle list value + case types.Map: + // Handle map value + case types.Object: + // Handle object value + case types.Set: + // Handle set value + case types.String: + // Handle string value + case types.Tuple: + // Handle tuple value + } +``` + + + +[Float64](/terraform/plugin/framework/handling-data/types/float64) and [Int64](/terraform/plugin/framework/handling-data/types/int64) framework types will never appear in the underlying value as both are represented as the Terraform type [`number`](/terraform/language/expressions/types#number). + + + +The type of the underlying value is determined at runtime by Terraform if the value is from configuration. Developers dealing with dynamic data will need to have extensive knowledge of the [Terraform type system](/terraform/language/expressions/types) to properly handle all potential practitioner configuration scenarios. + +Refer to the [Dynamic Data](/terraform/plugin/framework/handling-data/dynamic-data) documentation for more information. + +## Setting Values + +Call one of the following to create a `types.Dynamic` value: + +* [`types.DynamicNull()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#DynamicNull): A null dynamic value. +* [`types.DynamicUnknown()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#DynamicUnknown): An unknown dynamic value where the final static type is not known. Use `types.DynamicValue()` with an unknown value if the final static type is known. +* [`types.DynamicValue(attr.Value)`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#DynamicValue): A known dynamic value, with an underlying value determined by the `attr.Value` input. + +In this example, a known dynamic value is created, where the underlying value is a known string value: + +```go +types.DynamicValue(types.StringValue("hello world!")) +``` + +In this example, a known dynamic value is created, where the underlying value is a known object value: + +```go +elementTypes := map[string]attr.Type{ + "attr1": types.StringType, + "attr2": types.Int64Type, +} +elements := map[string]attr.Value{ + "attr1": types.StringValue("value"), + "attr2": types.Int64Value(123), +} +objectValue, diags := types.ObjectValue(elementTypes, elements) +// ... handle any diagnostics ... + +types.DynamicValue(objectValue) +``` + +There are no reflection rules defined for creating dynamic values, meaning they must be created using the `types` implementation. + +In this example, a `types.Dynamic` with a known boolean value is used to set a dynamic attribute value: + +```go +diags := resp.State.SetAttribute(ctx, path.Root("example_attribute"), types.DynamicValue(types.BoolValue(true))) +``` + +## Extending + +The framework supports extending its base type implementations with [custom types](/terraform/plugin/framework/handling-data/types/custom). These can adjust expected provider code usage depending on their implementation. diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/types/float32.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/types/float32.mdx new file mode 100644 index 000000000..d6f85ec9d --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/types/float32.mdx @@ -0,0 +1,122 @@ +--- +page_title: Float32 types +description: >- + Learn how to implement 32-bit floating point value types with the Terraform + plugin framework. +--- + +# Float32 types + + + +Use [Int32 Type](/terraform/plugin/framework/handling-data/types/int32) for 32-bit integer numbers. Use [Number Attribute](/terraform/plugin/framework/handling-data/types/number) for arbitrary precision numbers. + + + +Float32 types store a 32-bit floating point number. + +By default, float32 from [schema](/terraform/plugin/framework/handling-data/schemas) (configuration, plan, and state) data are represented in the framework by [`types.Float32Type`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#Float32Type) and its associated value storage type of [`types.Float32`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#Float32). These types fully support Terraform's [type system concepts](/terraform/plugin/framework/handling-data/terraform-concepts) that cannot be represented in Go built-in types, such as `*float32`. Framework types can be [extended](#extending) by provider code or shared libraries to provide specific use case functionality. + +## Schema Definitions + +Use one of the following attribute types to directly add a float32 value to a [schema](/terraform/plugin/framework/handling-data/schemas) or [nested attribute type](/terraform/plugin/framework/handling-data/attributes#nested-attribute-types): + +| Schema Type | Attribute Type | +|-------------|----------------| +| [Data Source](/terraform/plugin/framework/data-sources) | [`schema.Float32Attribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/datasource/schema#Float32Attribute) | +| [Provider](/terraform/plugin/framework/providers) | [`schema.Float32Attribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/provider/schema#Float32Attribute) | +| [Resource](/terraform/plugin/framework/resources) | [`schema.Float32Attribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema#Float32Attribute) | +| [Ephemeral Resource](/terraform/plugin/framework/ephemeral-resources) | [`schema.Float32Attribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/ephemeral/schema#Float32Attribute) | + +If the float32 value should be the element type of a [collection attribute type](/terraform/plugin/framework/handling-data/attributes#collection-attribute-types), set the `ElemType` field to `types.Float32Type` or the appropriate [custom type](#extending). + +If the float32 value should be a value type of an [object attribute type](/terraform/plugin/framework/handling-data/attributes#object-attribute-type), set the `AttrTypes` map value to `types.Float32Type` or the appropriate [custom type](#extending). + +## Accessing Values + + + +Review the [attribute documentation](/terraform/plugin/framework/handling-data/attributes/float32#accessing-values) to understand how schema-based data gets mapped into accessible values, such as a `types.Float32` in this case. + + + +Access `types.Float32` information via the following methods: + +* [`(types.Float32).IsNull() bool`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types/basetypes#Float32Value.IsNull): Returns true if the float32 is null. +* [`(types.Float32).IsUnknown() bool`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types/basetypes#Float32Value.IsUnknown): Returns true if the float32 is unknown. +* [`(types.Float32).ValueFloat32() float32`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types/basetypes#Float32Value.ValueFloat32): Returns the known float32, or `0.0` if null or unknown. +* [`(types.Float32).ValueFloat32Pointer() *float32`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types/basetypes#Float32Value.ValueFloat32Pointer): Returns a float32 pointer to a known value, `nil` if null, or a pointer to `0.0` if unknown. + +In this example, a float32 value is checked for being null or unknown value first, before accessing its known value: + +```go +// Example data model definition +// type ExampleModel struct { +// ExampleAttribute types.Float32 `tfsdk:"example_attribute"` +// } +// +// This would be filled in, such as calling: req.Plan.Get(ctx, &data) +var data ExampleModel + +// optional logic for handling null value +if data.ExampleAttribute.IsNull() { + // ... +} + +// optional logic for handling unknown value +if data.ExampleAttribute.IsUnknown() { + // ... +} + +// myFloat32 now contains a Go float32 with the known value +myFloat32 := data.ExampleAttribute.ValueFloat32() +``` + +## Setting Values + +Call one of the following to create a `types.Float32` value: + +* [`types.Float32Null()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#Float32Null): A null float32 value. +* [`types.Float32Unknown()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#Float32Unknown): An unknown float32 value. +* [`types.Float32Value(float32)`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#Float32Value): A known value. +* [`types.Float32PointerValue(*float32)`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#Float32PointerValue): A known value. + +In this example, a known float32 value is created: + +```go +types.Float32Value(1.23) +``` + +Otherwise, for certain framework functionality that does not require `types` implementations directly, such as: + +* [`(tfsdk.State).SetAttribute()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/tfsdk#State.SetAttribute) +* [`types.ListValueFrom()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#ListValueFrom) +* [`types.MapValueFrom()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#MapValueFrom) +* [`types.ObjectValueFrom()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#ObjectValueFrom) +* [`types.SetValueFrom()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#SetValueFrom) + +Numbers can be automatically converted from the following Go types, pointers to these types, or any aliases of these types, such `type MyNumber int`: + +* `int`, `int8`, `int16`, `int32`, `int64` +* `uint`, `uint8`, `uint16`, `uint32`, `uint64` +* `float32`, `float64` +* [`*big.Int`](https://pkg.go.dev/math/big#Int), [`*big.Float`](https://pkg.go.dev/math/big#Float) + +An error will be returned if the value of the number cannot be stored in the numeric type supplied because of an overflow or other loss of precision. + +In this example, a `float32` is directly used to set a float32 attribute value: + +```go +var value float32 = 1.23 +diags := resp.State.SetAttribute(ctx, path.Root("example_attribute"), value) +``` + +In this example, a `types.List` of `types.Float32` is created from a `[]float32`: + +```go +listValue, diags := types.ListValueFrom(ctx, types.Float32Type, []float32{1.2, 2.4}) +``` + +## Extending + +The framework supports extending its base type implementations with [custom types](/terraform/plugin/framework/handling-data/types/custom). These can adjust expected provider code usage depending on their implementation. diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/types/float64.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/types/float64.mdx new file mode 100644 index 000000000..b798c9040 --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/types/float64.mdx @@ -0,0 +1,121 @@ +--- +page_title: Float64 types +description: >- + Learn how to implement 64-bit floating point value types with the Terraform plugin + framework. +--- + +# Float64 types + + + +Use [Int64 Type](/terraform/plugin/framework/handling-data/types/int64) for 64-bit integer numbers. Use [Number Attribute](/terraform/plugin/framework/handling-data/types/number) for arbitrary precision numbers. + + + +Float64 types store a 64-bit floating point number. + +By default, float64 from [schema](/terraform/plugin/framework/handling-data/schemas) (configuration, plan, and state) data are represented in the framework by [`types.Float64Type`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#Float64Type) and its associated value storage type of [`types.Float64`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#Float64). These types fully support Terraform's [type system concepts](/terraform/plugin/framework/handling-data/terraform-concepts) that cannot be represented in Go built-in types, such as `*float64`. Framework types can be [extended](#extending) by provider code or shared libraries to provide specific use case functionality. + +## Schema Definitions + +Use one of the following attribute types to directly add a float64 value to a [schema](/terraform/plugin/framework/handling-data/schemas) or [nested attribute type](/terraform/plugin/framework/handling-data/attributes#nested-attribute-types): + +| Schema Type | Attribute Type | +|-------------|----------------| +| [Data Source](/terraform/plugin/framework/data-sources) | [`schema.Float64Attribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/datasource/schema#Float64Attribute) | +| [Provider](/terraform/plugin/framework/providers) | [`schema.Float64Attribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/provider/schema#Float64Attribute) | +| [Resource](/terraform/plugin/framework/resources) | [`schema.Float64Attribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema#Float64Attribute) | +| [Ephemeral Resource](/terraform/plugin/framework/ephemeral-resources) | [`schema.Float64Attribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/ephemeral/schema#Float64Attribute) | + +If the float64 value should be the element type of a [collection attribute type](/terraform/plugin/framework/handling-data/attributes#collection-attribute-types), set the `ElemType` field to `types.Float64Type` or the appropriate [custom type](#extending). + +If the float64 value should be a value type of an [object attribute type](/terraform/plugin/framework/handling-data/attributes#object-attribute-type), set the `AttrTypes` map value to `types.Float64Type` or the appropriate [custom type](#extending). + +## Accessing Values + + + +Review the [attribute documentation](/terraform/plugin/framework/handling-data/attributes/float64#accessing-values) to understand how schema-based data gets mapped into accessible values, such as a `types.Float64` in this case. + + + +Access `types.Float64` information via the following methods: + +* [`(types.Float64).IsNull() bool`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types/basetypes#Float64Value.IsNull): Returns true if the float64 is null. +* [`(types.Float64).IsUnknown() bool`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types/basetypes#Float64Value.IsUnknown): Returns true if the float64 is unknown. +* [`(types.Float64).ValueFloat64() float64`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types/basetypes#Float64Value.ValueFloat64): Returns the known float64, or `0.0` if null or unknown. +* [`(types.Float64).ValueFloat64Pointer() *float64`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types/basetypes#Float64Value.ValueFloat64Pointer): Returns a float64 pointer to a known value, `nil` if null, or a pointer to `0.0` if unknown. + +In this example, a float64 value is checked for being null or unknown value first, before accessing its known value: + +```go +// Example data model definition +// type ExampleModel struct { +// ExampleAttribute types.Float64 `tfsdk:"example_attribute"` +// } +// +// This would be filled in, such as calling: req.Plan.Get(ctx, &data) +var data ExampleModel + +// optional logic for handling null value +if data.ExampleAttribute.IsNull() { + // ... +} + +// optional logic for handling unknown value +if data.ExampleAttribute.IsUnknown() { + // ... +} + +// myFloat64 now contains a Go float64 with the known value +myFloat64 := data.ExampleAttribute.ValueFloat64() +``` + +## Setting Values + +Call one of the following to create a `types.Float64` value: + +* [`types.Float64Null()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#Float64Null): A null float64 value. +* [`types.Float64Unknown()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#Float64Unknown): An unknown float64 value. +* [`types.Float64Value(float64)`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#Float64Value): A known value. +* [`types.Float64PointerValue(*float64)`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#Float64PointerValue): A known value. + +In this example, a known float64 value is created: + +```go +types.Float64Value(1.23) +``` + +Otherwise, for certain framework functionality that does not require `types` implementations directly, such as: + +* [`(tfsdk.State).SetAttribute()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/tfsdk#State.SetAttribute) +* [`types.ListValueFrom()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#ListValueFrom) +* [`types.MapValueFrom()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#MapValueFrom) +* [`types.ObjectValueFrom()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#ObjectValueFrom) +* [`types.SetValueFrom()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#SetValueFrom) + +Numbers can be automatically converted from the following Go types, pointers to these types, or any aliases of these types, such `type MyNumber int`: + +* `int`, `int8`, `int16`, `int32`, `int64` +* `uint`, `uint8`, `uint16`, `uint32`, `uint64` +* `float32`, `float64` +* [`*big.Int`](https://pkg.go.dev/math/big#Int), [`*big.Float`](https://pkg.go.dev/math/big#Float) + +An error will be returned if the value of the number cannot be stored in the numeric type supplied because of an overflow or other loss of precision. + +In this example, a `float64` is directly used to set a float64 attribute value: + +```go +diags := resp.State.SetAttribute(ctx, path.Root("example_attribute"), 1.23) +``` + +In this example, a `types.List` of `types.Float64` is created from a `[]float64`: + +```go +listValue, diags := types.ListValueFrom(ctx, types.Float64Type, []float64{1.2, 2.4}) +``` + +## Extending + +The framework supports extending its base type implementations with [custom types](/terraform/plugin/framework/handling-data/types/custom). These can adjust expected provider code usage depending on their implementation. diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/types/index.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/types/index.mdx new file mode 100644 index 000000000..4e5f9103a --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/types/index.mdx @@ -0,0 +1,89 @@ +--- +page_title: Data types +description: >- + The Terraform plugin framework includes multiple built-in attribute types + and supports custom and dynamic attribute types. You can implement custom + types based off of the built-in attribute types. +--- + +# Data types + +Types are value storage and access mechanism for resource, data source, or provider [schema](/terraform/plugin/framework/handling-data/schemas) data. Every attribute and block has an associated type, which describes the kind of data. These types fully support Terraform's [type system concepts](/terraform/plugin/framework/handling-data/terraform-concepts) that cannot be represented in Go built-in types, such as `*string`. Framework types can be extended by implementing [custom types](/terraform/plugin/framework/handling-data/types/custom) in provider code or shared libraries to provide specific use case functionality. + +## Available Types + +The framework type system supports the following types: + +- [Primitive](#primitive-types): Type that contains a single value, such as a boolean, number, or string. +- [Collection](#collection-types): Type that contains multiple values of a single element type, such as a list, map, or set. +- [Object](#object-type): Type that defines a mapping of explicit attribute names to value types. +- [Tuple](#tuple-type): Type that defines an ordered collection of elements where each element has it's own type. +- [Dynamic](#dynamic-type): Container type that can contain any underlying value type. + +### Primitive Types + +Types that contain a single data value, such as a boolean, number, or string. These are directly associated with their [primitive attribute type](/terraform/plugin/framework/handling-data/attributes#primitive-attribute-types). + +| Type | Use Case | +|----------------|----------| +| [Bool](/terraform/plugin/framework/handling-data/types/bool) | Boolean true or false | +| [Float32](/terraform/plugin/framework/handling-data/types/float32) | 32-bit floating point number | +| [Float64](/terraform/plugin/framework/handling-data/types/float64) | 64-bit floating point number | +| [Int32](/terraform/plugin/framework/handling-data/types/int32) | 32-bit integer number | +| [Int64](/terraform/plugin/framework/handling-data/types/int64) | 64-bit integer number | +| [Number](/terraform/plugin/framework/handling-data/types/number) | Arbitrary precision (generally over 64-bit, up to 512-bit) number | +| [String](/terraform/plugin/framework/handling-data/types/string) | Collection of UTF-8 encoded characters | + +### Collection Types + +Types that contain multiple values of a single element type, such as a list, map, or set. + +These types are associated with: + +- [Collection attribute types](/terraform/plugin/framework/handling-data/attributes#collection-attribute-types) +- Collection-based [nested attribute type](/terraform/plugin/framework/handling-data/attributes#nested-attribute-types) (list, map, and set of [object type](#object-type)) +- Collection-based [nested block type](/terraform/plugin/framework/handling-data/blocks) (list and set of [object type](#object-type)) + +| Type | Use Case | +|----------------|----------| +| [List](/terraform/plugin/framework/handling-data/types/list) | Ordered collection of single element type | +| [Map](/terraform/plugin/framework/handling-data/types/map) | Mapping of arbitrary string keys to values of single element type | +| [Set](/terraform/plugin/framework/handling-data/types/set) | Unordered, unique collection of single element type | + +### Object Type + +Type that defines a mapping of explicit attribute names to value types. + +This type is associated with: + +- [Single nested attibute type](/terraform/plugin/framework/handling-data/attributes/single-nested) +- [Single nested block type](/terraform/plugin/framework/handling-data/blocks/single-nested) +- Nested object within collection-based [nested attribute type](/terraform/plugin/framework/handling-data/attributes#nested-attribute-types) (list, map, and set of [object type](#object-type)) +- Nested object within collection-based [nested block type](/terraform/plugin/framework/handling-data/blocks) (list and set of [object type](#object-type)) +- [Object attribute type](/terraform/plugin/framework/handling-data/attributes#collection-attribute-types) + +| Type | Use Case | +|----------------|----------| +| [Object](/terraform/plugin/framework/handling-data/types/object) | Mapping of explicit attribute names to values | + +### Tuple Type + +Type that defines an ordered collection of elements where each element has it's own type. + + + +This type intentionally includes less functionality than other types in the type system as it has limited real world application and therefore is not exposed to provider developers except when working with dynamic values. + + + +| Type | Use Case | +|----------------|----------| +| [Tuple](/terraform/plugin/framework/handling-data/types/tuple) | Ordered collection of multiple element types | + +### Dynamic Type + +Container type that can contain any underlying value type, determined by Terraform or the provider at runtime. + +| Type | Use Case | +|----------------|----------| +| [Dynamic](/terraform/plugin/framework/handling-data/types/dynamic) | Any value type of data, determined at runtime. | \ No newline at end of file diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/types/int32.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/types/int32.mdx new file mode 100644 index 000000000..b8525bc08 --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/types/int32.mdx @@ -0,0 +1,121 @@ +--- +page_title: Int32 types +description: >- + Learn how to implement 32-bit integer value types with the Terraform plugin + framework. +--- + +# Int32 types + + + +Use [Float32 Type](/terraform/plugin/framework/handling-data/types/float32) for 32-bit floating point numbers. Use [Number Attribute](/terraform/plugin/framework/handling-data/types/number) for arbitrary precision numbers. + + + +Int32 types store a 32-bit integer number. + +By default, int32 from [schema](/terraform/plugin/framework/handling-data/schemas) (configuration, plan, and state) data are represented in the framework by [`types.Int32Type`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#Int32Type) and its associated value storage type of [`types.Int32`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#Int32). These types fully support Terraform's [type system concepts](/terraform/plugin/framework/handling-data/terraform-concepts) that cannot be represented in Go built-in types, such as `*int32`. Framework types can be [extended](#extending) by provider code or shared libraries to provide specific use case functionality. + +## Schema Definitions + +Use one of the following attribute types to directly add a int32 value to a [schema](/terraform/plugin/framework/handling-data/schemas) or [nested attribute type](/terraform/plugin/framework/handling-data/attributes#nested-attribute-types): + +| Schema Type | Attribute Type | +|-------------|----------------| +| [Data Source](/terraform/plugin/framework/data-sources) | [`schema.Int32Attribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/datasource/schema#Int32Attribute) | +| [Provider](/terraform/plugin/framework/providers) | [`schema.Int32Attribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/provider/schema#Int32Attribute) | +| [Resource](/terraform/plugin/framework/resources) | [`schema.Int32Attribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema#Int32Attribute) | +| [Ephemeral Resource](/terraform/plugin/framework/ephemeral-resources) | [`schema.Int32Attribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/ephemeral/schema#Int32Attribute) | + +If the int32 value should be the element type of a [collection attribute type](/terraform/plugin/framework/handling-data/attributes#collection-attribute-types), set the `ElemType` field to `types.Int32Type` or the appropriate [custom type](#extending). + +If the int32 value should be a value type of an [object attribute type](/terraform/plugin/framework/handling-data/attributes#object-attribute-type), set the `AttrTypes` map value to `types.Int32Type` or the appropriate [custom type](#extending). + +## Accessing Values + + + +Review the [attribute documentation](/terraform/plugin/framework/handling-data/attributes/int32#accessing-values) to understand how schema-based data gets mapped into accessible values, such as a `types.Int32` in this case. + + + +Access `types.Int32` information via the following methods: + +* [`(types.Int32).IsNull() bool`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types/basetypes#Int32Value.IsNull): Returns true if the int32 is null. +* [`(types.Int32).IsUnknown() bool`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types/basetypes#Int32Value.IsUnknown): Returns true if the int32 is unknown. +* [`(types.Int32).ValueInt32() int32`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types/basetypes#Int32Value.ValueInt32): Returns the known int32, or `0` if null or unknown. +* [`(types.Int32).ValueInt32Pointer() *int32`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types/basetypes#Int32Value.ValueInt32Pointer): Returns a int32 pointer to a known value, `nil` if null, or a pointer to `0` if unknown. + +In this example, a int32 value is checked for being null or unknown value first, before accessing its known value: + +```go +// Example data model definition +// type ExampleModel struct { +// ExampleAttribute types.Int32 `tfsdk:"example_attribute"` +// } +// +// This would be filled in, such as calling: req.Plan.Get(ctx, &data) +var data ExampleModel + +// optional logic for handling null value +if data.ExampleAttribute.IsNull() { + // ... +} + +// optional logic for handling unknown value +if data.ExampleAttribute.IsUnknown() { + // ... +} + +// myInt32 now contains a Go int32 with the known value +myInt32 := data.ExampleAttribute.ValueInt32() +``` + +## Setting Values + +Call one of the following to create a `types.Int32` value: + +* [`types.Int32Null()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#Int32Null): A null int32 value. +* [`types.Int32Unknown()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#Int32Unknown): An unknown int32 value. +* [`types.Int32Value(int32)`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#Int32Value): A known value. +* [`types.Int32PointerValue(*int32)`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#Int32PointerValue): A known value. + +In this example, a known int32 value is created: + +```go +types.Int32Value(123) +``` + +Otherwise, for certain framework functionality that does not require `types` implementations directly, such as: + +* [`(tfsdk.State).SetAttribute()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/tfsdk#State.SetAttribute) +* [`types.ListValueFrom()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#ListValueFrom) +* [`types.MapValueFrom()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#MapValueFrom) +* [`types.ObjectValueFrom()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#ObjectValueFrom) +* [`types.SetValueFrom()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#SetValueFrom) + +Numbers can be automatically converted from the following Go types, pointers to these types, or any aliases of these types, such `type MyNumber int`: + +* `int`, `int8`, `int16`, `int32`, `int64` +* `uint`, `uint8`, `uint16`, `uint32`, `uint64` +* `float32`, `float64` +* [`*big.Int`](https://pkg.go.dev/math/big#Int), [`*big.Float`](https://pkg.go.dev/math/big#Float) + +An error will be returned if the value of the number cannot be stored in the numeric type supplied because of an overflow or other loss of precision. + +In this example, a `int32` is directly used to set a int32 attribute value: + +```go +diags := resp.State.SetAttribute(ctx, path.Root("example_attribute"), 123) +``` + +In this example, a `types.List` of `types.Int32` is created from a `[]int32`: + +```go +listValue, diags := types.ListValueFrom(ctx, types.Int32Type, []int32{123, 456}) +``` + +## Extending + +The framework supports extending its base type implementations with [custom types](/terraform/plugin/framework/handling-data/types/custom). These can adjust expected provider code usage depending on their implementation. diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/types/int64.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/types/int64.mdx new file mode 100644 index 000000000..7f741f5eb --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/types/int64.mdx @@ -0,0 +1,121 @@ +--- +page_title: Int64 types +description: >- + Learn how to implement 64-bit integer value types with the Terraform plugin + framework. +--- + +# Int64 types + + + +Use [Float64 Type](/terraform/plugin/framework/handling-data/types/float64) for 64-bit floating point numbers. Use [Number Attribute](/terraform/plugin/framework/handling-data/types/number) for arbitrary precision numbers. + + + +Int64 types store a 64-bit integer number. + +By default, int64 from [schema](/terraform/plugin/framework/handling-data/schemas) (configuration, plan, and state) data are represented in the framework by [`types.Int64Type`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#Int64Type) and its associated value storage type of [`types.Int64`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#Int64). These types fully support Terraform's [type system concepts](/terraform/plugin/framework/handling-data/terraform-concepts) that cannot be represented in Go built-in types, such as `*int64`. Framework types can be [extended](#extending) by provider code or shared libraries to provide specific use case functionality. + +## Schema Definitions + +Use one of the following attribute types to directly add a int64 value to a [schema](/terraform/plugin/framework/handling-data/schemas) or [nested attribute type](/terraform/plugin/framework/handling-data/attributes#nested-attribute-types): + +| Schema Type | Attribute Type | +|-------------|----------------| +| [Data Source](/terraform/plugin/framework/data-sources) | [`schema.Int64Attribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/datasource/schema#Int64Attribute) | +| [Provider](/terraform/plugin/framework/providers) | [`schema.Int64Attribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/provider/schema#Int64Attribute) | +| [Resource](/terraform/plugin/framework/resources) | [`schema.Int64Attribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema#Int64Attribute) | +| [Ephemeral Resource](/terraform/plugin/framework/ephemeral-resources) | [`schema.Int64Attribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/ephemeral/schema#Int64Attribute) | + +If the int64 value should be the element type of a [collection attribute type](/terraform/plugin/framework/handling-data/attributes#collection-attribute-types), set the `ElemType` field to `types.Int64Type` or the appropriate [custom type](#extending). + +If the int64 value should be a value type of an [object attribute type](/terraform/plugin/framework/handling-data/attributes#object-attribute-type), set the `AttrTypes` map value to `types.Int64Type` or the appropriate [custom type](#extending). + +## Accessing Values + + + +Review the [attribute documentation](/terraform/plugin/framework/handling-data/attributes/int64#accessing-values) to understand how schema-based data gets mapped into accessible values, such as a `types.Int64` in this case. + + + +Access `types.Int64` information via the following methods: + +* [`(types.Int64).IsNull() bool`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types/basetypes#Int64Value.IsNull): Returns true if the int64 is null. +* [`(types.Int64).IsUnknown() bool`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types/basetypes#Int64Value.IsUnknown): Returns true if the int64 is unknown. +* [`(types.Int64).ValueInt64() int64`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types/basetypes#Int64Value.ValueInt64): Returns the known int64, or `0` if null or unknown. +* [`(types.Int64).ValueInt64Pointer() *int64`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types/basetypes#Int64Value.ValueInt64Pointer): Returns a int64 pointer to a known value, `nil` if null, or a pointer to `0` if unknown. + +In this example, a int64 value is checked for being null or unknown value first, before accessing its known value: + +```go +// Example data model definition +// type ExampleModel struct { +// ExampleAttribute types.Int64 `tfsdk:"example_attribute"` +// } +// +// This would be filled in, such as calling: req.Plan.Get(ctx, &data) +var data ExampleModel + +// optional logic for handling null value +if data.ExampleAttribute.IsNull() { + // ... +} + +// optional logic for handling unknown value +if data.ExampleAttribute.IsUnknown() { + // ... +} + +// myInt64 now contains a Go int64 with the known value +myInt64 := data.ExampleAttribute.ValueInt64() +``` + +## Setting Values + +Call one of the following to create a `types.Int64` value: + +* [`types.Int64Null()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#Int64Null): A null int64 value. +* [`types.Int64Unknown()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#Int64Unknown): An unknown int64 value. +* [`types.Int64Value(int64)`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#Int64Value): A known value. +* [`types.Int64PointerValue(*int64)`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#Int64PointerValue): A known value. + +In this example, a known int64 value is created: + +```go +types.Int64Value(123) +``` + +Otherwise, for certain framework functionality that does not require `types` implementations directly, such as: + +* [`(tfsdk.State).SetAttribute()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/tfsdk#State.SetAttribute) +* [`types.ListValueFrom()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#ListValueFrom) +* [`types.MapValueFrom()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#MapValueFrom) +* [`types.ObjectValueFrom()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#ObjectValueFrom) +* [`types.SetValueFrom()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#SetValueFrom) + +Numbers can be automatically converted from the following Go types, pointers to these types, or any aliases of these types, such `type MyNumber int`: + +* `int`, `int8`, `int16`, `int32`, `int64` +* `uint`, `uint8`, `uint16`, `uint32`, `uint64` +* `float32`, `float64` +* [`*big.Int`](https://pkg.go.dev/math/big#Int), [`*big.Float`](https://pkg.go.dev/math/big#Float) + +An error will be returned if the value of the number cannot be stored in the numeric type supplied because of an overflow or other loss of precision. + +In this example, a `int64` is directly used to set a int64 attribute value: + +```go +diags := resp.State.SetAttribute(ctx, path.Root("example_attribute"), 123) +``` + +In this example, a `types.List` of `types.Int64` is created from a `[]int64`: + +```go +listValue, diags := types.ListValueFrom(ctx, types.Int64Type, []int64{123, 456}) +``` + +## Extending + +The framework supports extending its base type implementations with [custom types](/terraform/plugin/framework/handling-data/types/custom). These can adjust expected provider code usage depending on their implementation. diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/types/list.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/types/list.mdx new file mode 100644 index 000000000..9352d8f78 --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/types/list.mdx @@ -0,0 +1,125 @@ +--- +page_title: List types +description: >- + Learn how to implement list value types with the Terraform pluginprovider + framework. +--- + +# List types + +List types store an ordered collection of single element type. + +By default, lists from [schema](/terraform/plugin/framework/handling-data/schemas) (configuration, plan, and state) data are represented in the framework by [`types.ListType`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#ListType) and its associated value storage type of [`types.List`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#List). These types fully support Terraform's [type system concepts](/terraform/plugin/framework/handling-data/terraform-concepts) that cannot be represented in Go built-in types, such as a slice. Framework types can be [extended](#extending) by provider code or shared libraries to provide specific use case functionality. + +## Schema Definitions + +Use one of the following attribute types to directly add a list of a single element type to a [schema](/terraform/plugin/framework/handling-data/schemas) or [nested attribute type](/terraform/plugin/framework/handling-data/attributes#nested-attribute-types): + +| Schema Type | Attribute Type | +|-------------|----------------| +| [Data Source](/terraform/plugin/framework/data-sources) | [`schema.ListAttribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/datasource/schema#ListAttribute) | +| [Provider](/terraform/plugin/framework/providers) | [`schema.ListAttribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/provider/schema#ListAttribute) | +| [Resource](/terraform/plugin/framework/resources) | [`schema.ListAttribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema#ListAttribute) | +| [Ephemeral Resource](/terraform/plugin/framework/ephemeral-resources) | [`schema.ListAttribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/ephemeral/schema#ListAttribute) | + +Use one of the following attribute types to directly add a list of a nested attributes to a [schema](/terraform/plugin/framework/handling-data/schemas) or [nested attribute type](/terraform/plugin/framework/handling-data/attributes#nested-attribute-types): + +| Schema Type | Attribute or Block Type | +|-------------|-------------------------| +| [Data Source](/terraform/plugin/framework/data-sources) | [`schema.ListNestedAttribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/datasource/schema#ListNestedAttribute) | +| [Data Source](/terraform/plugin/framework/data-sources) | [`schema.ListNestedBlock`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/datasource/schema#ListNestedBlock) | +| [Provider](/terraform/plugin/framework/providers) | [`schema.ListNestedAttribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/provider/schema#ListNestedAttribute) | +| [Provider](/terraform/plugin/framework/providers) | [`schema.ListNestedBlock`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/provider/schema#ListNestedBlock) | +| [Resource](/terraform/plugin/framework/resources) | [`schema.ListNestedAttribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema#ListNestedAttribute) | +| [Resource](/terraform/plugin/framework/resources) | [`schema.ListNestedBlock`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema#ListNestedBlock) | +| [Ephemeral Resource](/terraform/plugin/framework/ephemeral-resources) | [`schema.ListNestedAttribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/ephemeral/schema#ListNestedAttribute) | +| [Ephemeral Resource](/terraform/plugin/framework/ephemeral-resources) | [`schema.ListNestedBlock`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/ephemeral/schema#ListNestedBlock) | + +If the list value should be the element type of another [collection attribute type](/terraform/plugin/framework/handling-data/attributes#collection-attribute-types), set the `ElementType` field to `types.ListType{ElemType: /* ... */}` or the appropriate [custom type](#extending). + +If the list value should be a value type of an [object attribute type](/terraform/plugin/framework/handling-data/attributes#object-attribute-type), set the `AttributeTypes` map value to `types.ListType{ElemType: /* ... */}` or the appropriate [custom type](#extending). + +## Accessing Values + + + +Review the [attribute documentation](/terraform/plugin/framework/handling-data/attributes/list#accessing-values) to understand how schema-based data gets mapped into accessible values, such as a `types.List` in this case. + + + +Access `types.List` information via the following methods: + +* [`(types.List).IsNull() bool`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types/basetypes#ListValue.IsNull): Returns `true` if the list is null. +* [`(types.List).IsUnknown() bool`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types/basetypes#ListValue.IsUnknown): Returns `true` if the list is unknown. Returns `false` if the number of elements is known, any of which may be unknown. +* [`(types.List).Elements() []attr.Value`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types/basetypes#ListValue.Elements): Returns the known `[]attr.Value` value, or `nil` if null or unknown. +* [`(types.List).ElementsAs(context.Context, any, bool) diag.Diagnostics`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types/basetypes#ListValue.ElementsAs): Converts the known values into the given Go type, if possible. It is recommended to use a slice of framework types to account for elements which may be unknown. + +In this example, a list of strings value is checked for being null or unknown value first, before accessing its known value elements as a `[]types.String`: + +```go +// Example data model definition +// type ExampleModel struct { +// ExampleAttribute types.List `tfsdk:"example_attribute"` +// } +// +// This would be filled in, such as calling: req.Plan.Get(ctx, &data) +var data ExampleModel + +// optional logic for handling null value +if data.ExampleAttribute.IsNull() { + // ... +} + +// optional logic for handling unknown value +if data.ExampleAttribute.IsUnknown() { + // ... +} + +elements := make([]types.String, 0, len(data.ExampleAttribute.Elements())) +diags := data.ExampleAttribute.ElementsAs(ctx, &elements, false) +``` + +## Setting Values + +Call one of the following to create a `types.List` value: + +* [`types.ListNull(attr.Type) types.List`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#ListNull): A null list value with the given element type. +* [`types.ListUnknown(attr.Type) types.List`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#ListUnknown): An unknown list value with the given element type. +* [`types.ListValue(attr.Type, []attr.Value) (types.List, diag.Diagnostics)`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#ListValue): A known value with the given element type and values. +* [`types.ListValueFrom(context.Context, attr.Type, any) (types.List, diag.Diagnostics)`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#ListValueFrom): A known value with the given element type and values. This can convert the source data from standard Go types into framework types as noted in the documentation for each element type, such as giving `[]*string` for a `types.List` of `types.String`. +* [`types.ListValueMust(attr.Type, []attr.Value) types.List`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#ListValueMust): A known value with the given element type and values. Any diagnostics are converted to a runtime panic. This is recommended only for testing or exhaustively tested logic. + +In this example, a known list value is created from framework types: + +```go +elements := []attr.Value{types.StringValue("one"), types.StringValue("two")} +listValue, diags := types.ListValue(types.StringType, elements) +``` + +Otherwise, for certain framework functionality that does not require `types` implementations directly, such as: + +* [`(tfsdk.State).SetAttribute()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/tfsdk#State.SetAttribute) +* [`types.ListValueFrom()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#ListValueFrom) +* [`types.MapValueFrom()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#MapValueFrom) +* [`types.ObjectValueFrom()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#ObjectValueFrom) +* [`types.SetValueFrom()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#SetValueFrom) + +A Go built-in slice type (`[]T`) or type alias of a slice type such as `type MyListType []T` can be used instead. + +In this example, a `[]string` is directly used to set a list attribute value: + +```go +elements := []string{"one", "two"} +diags := resp.State.SetAttribute(ctx, path.Root("example_attribute"), elements) +``` + +In this example, a `types.List` of `types.String` is created from a `[]string`: + +```go +elements := []string{"one", "two"} +listValue, diags := types.ListValueFrom(ctx, types.StringType, elements) +``` + +## Extending + +The framework supports extending its base type implementations with [custom types](/terraform/plugin/framework/handling-data/types/custom). These can adjust expected provider code usage depending on their implementation. diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/types/map.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/types/map.mdx new file mode 100644 index 000000000..a1b0415d5 --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/types/map.mdx @@ -0,0 +1,130 @@ +--- +page_title: Map types +description: >- + Learn how to implement mapping value types with the Terraform plugin + framework. +--- + +# Map type + +Map types store an ordered collection of single element type. + +By default, maps from [schema](/terraform/plugin/framework/handling-data/schemas) (configuration, plan, and state) data are represented in the framework by [`types.MapType`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#MapType) and its associated value storage type of [`types.Map`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#Map). These types fully support Terraform's [type system concepts](/terraform/plugin/framework/handling-data/terraform-concepts) that cannot be represented in Go built-in types, such as a map. Framework types can be [extended](#extending) by provider code or shared libraries to provide specific use case functionality. + +## Schema Definitions + +Use one of the following attribute types to directly add a map of a single element type to a [schema](/terraform/plugin/framework/handling-data/schemas) or [nested attribute type](/terraform/plugin/framework/handling-data/attributes#nested-attribute-types): + +| Schema Type | Attribute Type | +|-------------|----------------| +| [Data Source](/terraform/plugin/framework/data-sources) | [`schema.MapAttribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/datasource/schema#MapAttribute) | +| [Provider](/terraform/plugin/framework/providers) | [`schema.MapAttribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/provider/schema#MapAttribute) | +| [Resource](/terraform/plugin/framework/resources) | [`schema.MapAttribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema#MapAttribute) | +| [Ephemeral Resource](/terraform/plugin/framework/ephemeral-resources) | [`schema.MapAttribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/ephemeral/schema#MapAttribute) | + +Use one of the following attribute types to directly add a map of a nested attributes to a [schema](/terraform/plugin/framework/handling-data/schemas) or [nested attribute type](/terraform/plugin/framework/handling-data/attributes#nested-attribute-types): + +| Schema Type | Attribute Type | +|-------------|----------------| +| [Data Source](/terraform/plugin/framework/data-sources) | [`schema.MapNestedAttribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/datasource/schema#MapNestedAttribute) | +| [Provider](/terraform/plugin/framework/providers) | [`schema.MapNestedAttribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/provider/schema#MapNestedAttribute) | +| [Resource](/terraform/plugin/framework/resources) | [`schema.MapNestedAttribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema#MapNestedAttribute) | +| [Ephemeral Resource](/terraform/plugin/framework/ephemeral-resources) | [`schema.MapNestedAttribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/ephemeral/schema#MapNestedAttribute) | + +If the map value should be the element type of another [collection attribute type](/terraform/plugin/framework/handling-data/attributes#collection-attribute-types), set the `ElementType` field to `types.MapType{ElemType: /* ... */}` or the appropriate [custom type](#extending). + +If the map value should be a value type of an [object attribute type](/terraform/plugin/framework/handling-data/attributes#object-attribute-type), set the `AttributeTypes` map value to `types.MapType{ElemType: /* ... */}` or the appropriate [custom type](#extending). + +## Accessing Values + + + +Review the [attribute documentation](/terraform/plugin/framework/handling-data/attributes/map#accessing-values) to understand how schema-based data gets mapped into accessible values, such as a `types.Map` in this case. + + + +Access `types.Map` information via the following methods: + +* [`(types.Map).IsNull() bool`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types/basetypes#MapValue.IsNull): Returns `true` if the map is null. +* [`(types.Map).IsUnknown() bool`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types/basetypes#MapValue.IsUnknown): Returns `true` if the map is unknown. Returns `false` if the number of elements is known, any of which may be unknown. +* [`(types.Map).Elements() map[string]attr.Value`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types/basetypes#MapValue.Elements): Returns the known `map[string]attr.Value` value, or `nil` if null or unknown. +* [`(types.Map).ElementsAs(context.Context, any, bool) diag.Diagnostics`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types/basetypes#MapValue.ElementsAs): Converts the known values into the given Go type, if possible. It is recommended to use a map of framework types to account for elements which may be unknown. + +In this example, a map of strings value is checked for being null or unknown value first, before accessing its known value elements as a `map[string]types.String`: + +```go +// Example data model definition +// type ExampleModel struct { +// ExampleAttribute types.Map `tfsdk:"example_attribute"` +// } +// +// This would be filled in, such as calling: req.Plan.Get(ctx, &data) +var data ExampleModel + +// optional logic for handling null value +if data.ExampleAttribute.IsNull() { + // ... +} + +// optional logic for handling unknown value +if data.ExampleAttribute.IsUnknown() { + // ... +} + +elements := make(map[string]types.String, len(data.ExampleAttribute.Elements())) +diags := data.ExampleAttribute.ElementsAs(ctx, &elements, false) +``` + +## Setting Values + +Call one of the following to create a `types.Map` value: + +* [`types.MapNull(attr.Type) types.Map`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#MapNull): A null list value with the given element type. +* [`types.MapUnknown(attr.Type) types.Map`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#MapUnknown): An unknown list value with the given element type. +* [`types.MapValue(attr.Type, map[string]attr.Value) (types.Map, diag.Diagnostics)`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#MapValue): A known value with the given element type and values. +* [`types.MapValueFrom(context.Context, attr.Type, any) (types.Map, diag.Diagnostics)`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#MapValueFrom): A known value with the given element type and values. This can convert the source data from standard Go types into framework types as noted in the documentation for each element type, such as giving `map[string]*string` for a `types.Map` of `types.String`. +* [`types.MapValueMust(map[string]attr.Type, map[string]attr.Value) types.Map`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#MapValueMust): A known value with the given element type and values. Any diagnostics are converted to a runtime panic. This is recommended only for testing or exhaustively tested logic. + +In this example, a known map value is created from framework types: + +```go +elements := map[string]attr.Value{ + "key1": types.StringValue("value1"), + "key2": types.StringValue("value2"), +} +mapValue, diags := types.MapValue(types.StringType, elements) +``` + +Otherwise, for certain framework functionality that does not require `types` implementations directly, such as: + +* [`(tfsdk.State).SetAttribute()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/tfsdk#State.SetAttribute) +* [`types.ListValueFrom()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#ListValueFrom) +* [`types.MapValueFrom()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#MapValueFrom) +* [`types.ObjectValueFrom()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#ObjectValueFrom) +* [`types.SetValueFrom()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#SetValueFrom) + +A Go built-in map of string key type (`map[string]T`) or type alias of a map of string key type such as `type MyMapType map[string]T` can be used instead. + +In this example, a `map[string]string` is directly used to set a map attribute value: + +```go +elements := map[string]string{ + "key1": "value1", + "key2": "value2", +} +diags := resp.State.SetAttribute(ctx, path.Root("example_attribute"), elements) +``` + +In this example, a `types.Map` of `types.String` is created from a `map[string]string`: + +```go +elements := map[string]string{ + "key1": "value1", + "key2": "value2", +} +mapValue, diags := types.MapValueFrom(ctx, types.StringType, elements) +``` + +## Extending + +The framework supports extending its base type implementations with [custom types](/terraform/plugin/framework/handling-data/types/custom). These can adjust expected provider code usage depending on their implementation. diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/types/number.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/types/number.mdx new file mode 100644 index 000000000..aa1572107 --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/types/number.mdx @@ -0,0 +1,117 @@ +--- +page_title: Number types +description: >- + Learn how to implement arbitrary precision number value types with the Terraform plugin + framework. +--- + +# Number types + + + +Use [Float64 Type](/terraform/plugin/framework/handling-data/types/float64) for 64-bit floating point numbers. Use [Int64 Type](/terraform/plugin/framework/handling-data/types/int64) for 64-bit integer numbers. + + + +Number types store an arbitrary precision (generally more than 64-bit, up to 512-bit) number. + +By default, number from [schema](/terraform/plugin/framework/handling-data/schemas) (configuration, plan, and state) data are represented in the framework by [`types.NumberType`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#NumberType) and its associated value storage type of [`types.Number`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#Number). These types fully support Terraform's [type system concepts](/terraform/plugin/framework/handling-data/terraform-concepts) that cannot be represented in Go built-in types, such as `*big.Float`. Framework types can be [extended](#extending) by provider code or shared libraries to provide specific use case functionality. + +## Schema Definitions + +Use one of the following attribute types to directly add a number value to a [schema](/terraform/plugin/framework/handling-data/schemas) or [nested attribute type](/terraform/plugin/framework/handling-data/attributes#nested-attribute-types): + +| Schema Type | Attribute Type | +|-------------|----------------| +| [Data Source](/terraform/plugin/framework/data-sources) | [`schema.NumberAttribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/datasource/schema#NumberAttribute) | +| [Provider](/terraform/plugin/framework/providers) | [`schema.NumberAttribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/provider/schema#NumberAttribute) | +| [Resource](/terraform/plugin/framework/resources) | [`schema.NumberAttribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema#NumberAttribute) | +| [Ephemeral Resource](/terraform/plugin/framework/ephemeral-resources) | [`schema.NumberAttribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/ephemeral/schema#NumberAttribute) | + +If the number value should be the element type of a [collection attribute type](/terraform/plugin/framework/handling-data/attributes#collection-attribute-types), set the `ElemType` field to `types.NumberType` or the appropriate [custom type](#extending). + +If the number value should be a value type of an [object attribute type](/terraform/plugin/framework/handling-data/attributes#object-attribute-type), set the `AttrTypes` map value to `types.NumberType` or the appropriate [custom type](#extending). + +## Accessing Values + + + +Review the [attribute documentation](/terraform/plugin/framework/handling-data/attributes/number#accessing-values) to understand how schema-based data gets mapped into accessible values, such as a `types.Number` in this case. + + + +Access `types.Number` information via the following methods: + +* [`(types.Number).IsNull() bool`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types/basetypes#NumberValue.IsNull): Returns true if the number is null. +* [`(types.Number).IsUnknown() bool`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types/basetypes#NumberValue.IsUnknown): Returns true if the number is unknown. +* [`(types.Number).ValueBigFloat() *big.Float`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types/basetypes#NumberValue.ValueNumber): Returns the known number, or the equivalent of `0.0` if null or unknown. + +In this example, a number value is checked for being null or unknown value first, before accessing its known value: + +```go +// Example data model definition +// type ExampleModel struct { +// ExampleAttribute types.Number `tfsdk:"example_attribute"` +// } +// +// This would be filled in, such as calling: req.Plan.Get(ctx, &data) +var data ExampleModel + +// optional logic for handling null value +if data.ExampleAttribute.IsNull() { + // ... +} + +// optional logic for handling unknown value +if data.ExampleAttribute.IsUnknown() { + // ... +} + +// myNumber now contains a Go *big.Float with the known value +myNumber := data.ExampleAttribute.ValueBigFloat() +``` + +## Setting Values + +Call one of the following to create a `types.Number` value: + +* [`types.NumberNull()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#NumberNull): A null float64 value. +* [`types.NumberUnknown()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#NumberUnknown): An unknown float64 value. +* [`types.NumberValue(*big.Float)`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#NumberValue): A known value. + +In this example, a known number value is created: + +```go +types.NumberValue(big.NewFloat(1.23)) +``` + +Otherwise, for certain framework functionality that does not require `types` implementations directly, such as: + +* [`(tfsdk.State).SetAttribute()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/tfsdk#State.SetAttribute) +* [`types.ListValueFrom()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#ListValueFrom) +* [`types.MapValueFrom()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#MapValueFrom) +* [`types.ObjectValueFrom()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#ObjectValueFrom) +* [`types.SetValueFrom()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#SetValueFrom) + +Numbers can be automatically converted from the following Go types, pointers to these types, or any aliases of these types, such `type MyNumber int`: + +* `int`, `int8`, `int16`, `int32`, `int64` +* `uint`, `uint8`, `uint16`, `uint32`, `uint64` +* `float32`, `float64` +* [`*big.Int`](https://pkg.go.dev/math/big#Int), [`*big.Float`](https://pkg.go.dev/math/big#Float) + +In this example, a `*big.Float` is directly used to set a number attribute value: + +```go +diags := resp.State.SetAttribute(ctx, path.Root("example_attribute"), big.NewFloat(1.23)) +``` + +In this example, a `types.List` of `types.Number` is created from a `[]*big.Float`: + +```go +listValue, diags := types.ListValueFrom(ctx, types.NumberType, []*big.Float{big.NewFloat(1.2), big.NewFloat(2.4)}) +``` + +## Extending + +The framework supports extending its base type implementations with [custom types](/terraform/plugin/framework/handling-data/types/custom). These can adjust expected provider code usage depending on their implementation. diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/types/object.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/types/object.mdx new file mode 100644 index 000000000..a5aa0f62e --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/types/object.mdx @@ -0,0 +1,246 @@ +--- +page_title: Object types +description: >- + Learn how to implement object value types with the Terraform plugin framework. +--- + +# Object types + +Object types store a mapping of explicit attribute names to value types. Objects must declare all attribute values, even when null or unknown, unless the entire object is null or unknown. + +By default, objects from [schema](/terraform/plugin/framework/handling-data/schemas) (configuration, plan, and state) data are represented in the framework by [`types.ObjectType`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#ObjectType) and its associated value storage type of [`types.Object`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#Object). These types fully support Terraform's [type system concepts](/terraform/plugin/framework/handling-data/terraform-concepts) that cannot be represented in Go built-in types, such as a struct. Framework types can be [extended](#extending) by provider code or shared libraries to provide specific use case functionality. + +## Schema Definitions + + + +Use [nested attribute types](/terraform/plugin/framework/handling-data/attributes#nested-attribute-types) instead of object attribute types where possible. Object attributes have limited utility as they can only define type information. + + + +Use one of the following attribute types to directly add a single structure of a nested attributes to a [schema](/terraform/plugin/framework/handling-data/schemas) or [nested attribute type](/terraform/plugin/framework/handling-data/attributes#nested-attribute-types): + +| Schema Type | Attribute or Block Type | +|-------------|-------------------------| +| [Data Source](/terraform/plugin/framework/data-sources) | [`schema.SingleNestedAttribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/datasource/schema#SingleNestedAttribute) | +| [Data Source](/terraform/plugin/framework/data-sources) | [`schema.SingleNestedBlock`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/datasource/schema#SingleNestedBlock) | +| [Provider](/terraform/plugin/framework/providers) | [`schema.SingleNestedAttribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/provider/schema#SingleNestedAttribute) | +| [Provider](/terraform/plugin/framework/providers) | [`schema.SingleNestedBlock`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/provider/schema#SingleNestedBlock) | +| [Resource](/terraform/plugin/framework/resources) | [`schema.SingleNestedAttribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema#SingleNestedAttribute) | +| [Resource](/terraform/plugin/framework/resources) | [`schema.SingleNestedBlock`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema#SingleNestedBlock) | +| [Ephemeral Resource](/terraform/plugin/framework/ephemeral-resources) | [`schema.SingleNestedAttribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/ephemeral/schema#SingleNestedAttribute) | +| [Ephemeral Resource](/terraform/plugin/framework/ephemeral-resources) | [`schema.SingleNestedBlock`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/ephemeral/schema#SingleNestedBlock) | + +If a wrapping collection is needed on the structure of nested attributes, any of the other nested attribute and nested block types can be used. + +Use one of the following attribute types to directly add an object value directly to a [schema](/terraform/plugin/framework/handling-data/schemas) or [nested attribute type](/terraform/plugin/framework/handling-data/attributes#nested-attribute-types): + +| Schema Type | Attribute Type | +|-------------|----------------| +| [Data Source](/terraform/plugin/framework/data-sources) | [`schema.ObjectAttribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/datasource/schema#ObjectAttribute) | +| [Provider](/terraform/plugin/framework/providers) | [`schema.ObjectAttribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/provider/schema#ObjectAttribute) | +| [Resource](/terraform/plugin/framework/resources) | [`schema.ObjectAttribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema#ObjectAttribute) | +| [Ephemeral Resource](/terraform/plugin/framework/ephemeral-resources) | [`schema.ObjectAttribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/ephemeral/schema#ObjectAttribute) | + +If the object value should be the element type of another [collection attribute type](/terraform/plugin/framework/handling-data/attributes#collection-attribute-types), set the `ElementType` field to `types.ObjectType{AttrTypes: /* ... */}` or the appropriate [custom type](#extending). + +If the object value should be a value type of an [object attribute type](/terraform/plugin/framework/handling-data/attributes#object-attribute-type), set the `AttributeTypes` map value to `types.ObjectType{AttrTypes: /* ... */}` or the appropriate [custom type](#extending). + +## Accessing Values + + + +Review the associated [attribute documentation](/terraform/plugin/framework/handling-data/attributes) to understand how schema-based data gets mapped into accessible values, such as a `types.Object` in this case. + + + +Access `types.Object` information via the following methods: + +* [`(types.Object).IsNull() bool`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types/basetypes#ObjectValue.IsNull): Returns `true` if the object is null. +* [`(types.Object).IsUnknown() bool`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types/basetypes#ObjectValue.IsUnknown): Returns `true` if the object is unknown. +* [`(types.Object).Attributes() map[string]attr.Value`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types/basetypes#ObjectValue.Attributes): Returns the known `map[string]attr.Value` value, or `nil` if null or unknown. +* [`(types.Object).As(context.Context, any, ObjectAsOptions) diag.Diagnostics`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types/basetypes#ObjectValue.As): Converts the known values into the given Go type, if possible. It is recommended to use a struct of framework types to account for attributes which may be unknown. + +In this example, an object with a string attribute is checked for being null or unknown value first, before accessing its known value attributes as a Go struct type: + +```go +// Example data model definitions +// type ExampleModel struct { +// ExampleAttribute types.Object `tfsdk:"example_attribute"` +// } +// +// type ExampleAttributeModel struct { +// StringAttribute types.String `tfsdk:"string_attribute"` +// } +// +// This would be filled in, such as calling: req.Plan.Get(ctx, &data) +var data ExampleModel + +// optional logic for handling null value +if data.ExampleAttribute.IsNull() { + // ... +} + +// optional logic for handling unknown value +if data.ExampleAttribute.IsUnknown() { + // ... +} + +var exampleAttribute ExampleAttributeModel + +diags := data.ExampleAttribute.As(ctx, &exampleAttribute, basetypes.ObjectAsOptions{}) +// Object data now is accessible, such as: exampleAttribute.StringAttribute.StringValue() +``` + +## Setting Values + +Call one of the following to create a `types.Object` value: + +* [`types.ObjectNull(map[string]attr.Type) types.Object`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#ObjectNull): A null object value with the given element type. +* [`types.ObjectUnknown(map[string]attr.Type) types.Object`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#ObjectUnknown): An unknown object value with the given element type. +* [`types.ObjectValue(map[string]attr.Type, map[string]attr.Value) (types.Object, diag.Diagnostics)`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#ObjectValue): A known value with the given attribute type mapping and attribute values mapping. +* [`types.ObjectValueFrom(context.Context, map[string]attr.Type, any) (types.Object, diag.Diagnostics)`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#ObjectValueFrom): A known value with the given attribute type mapping and values. This can convert the source data from standard Go types into framework types as noted in the documentation for each type, such as giving a `struct` for a `types.Object`. +* [`types.ObjectValueMust(map[string]attr.Type, map[string]attr.Value) types.Object`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#ObjectValueMust): A known value with the given attribute type mapping and attribute value mapping. Any diagnostics are converted to a runtime panic. This is recommended only for testing or exhaustively tested logic. + +In this example, a known object value is created from framework types: + +```go +elementTypes := map[string]attr.Type{ + "attr1": types.StringType, + "attr2": types.Int64Type, +} +elements := map[string]attr.Value{ + "attr1": types.StringValue("value"), + "attr2": types.Int64Value(123), +} +objectValue, diags := types.ObjectValue(elementTypes, elements) +``` + +Otherwise, for certain framework functionality that does not require `types` implementations directly, such as: + +* [`(tfsdk.State).SetAttribute()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/tfsdk#State.SetAttribute) +* [`types.ListValueFrom()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#ListValueFrom) +* [`types.MapValueFrom()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#MapValueFrom) +* [`types.ObjectValueFrom()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#ObjectValueFrom) +* [`types.SetValueFrom()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#SetValueFrom) + +## Automatic conversion with `tfsdk` struct tags + +Objects can be automatically converted to and from any Go struct type that follows these constraints to prevent accidental data loss: + +* Every struct field must have a `tfsdk` struct tag and every attribute in the object must have a corresponding struct tag. The `tfsdk` struct tag must name an attribute in the object that it is being mapped or be set to `-` to explicitly declare it does not map to an attribute in the object. Duplicate `tfsdk` struct tags are not allowed. +* Every struct type must be an acceptable conversion type according to the type documentation, such as `*string` being acceptable for a string type. However, it is recommended to use framework types to simplify data modeling (one model type for accessing and setting data) and prevent errors when encountering unknown values from Terraform. + +In this example, a struct is directly used to set an object attribute value: + +```go +type ExampleAttributeModel struct { + Int64Attribute types.Int64 `tfsdk:"int64_attribute` + StringAttribute types.String `tfsdk:"string_attribute"` +} + +value := ExampleAttributeModel{ + Int64Attribute: types.Int64Value(123), + StringAttribute: types.StringValue("example"), +} + +diags := resp.State.SetAttribute(ctx, path.Root("example_attribute"), value) +``` + +In this example, a `types.Object` is created from a struct: + +```go +type ExampleAttributeModel struct { + Int64Attribute types.Int64 `tfsdk:"int64_attribute` + StringAttribute types.String `tfsdk:"string_attribute"` +} + +func (m ExampleAttributeModel) AttributeTypes() map[string]attr.Type { + return map[string]attr.Type{ + "int64_attribute": types.Int64Type, + "string_attribute": types.StringType, + } +} + +value := ExampleAttributeModel{ + Int64Attribute: types.Int64Value(123), + StringAttribute: types.StringValue("example"), +} + +objectValue, diags := types.ObjectValueFrom(ctx, value.AttributeTypes(), value) +``` + +### Struct Embedding + +Go struct types that utilize [struct embedding](https://go.dev/doc/effective_go#embedding) to promote fields with `tfsdk` tags are supported when converting to and from object types. + +In this example, a `types.Object` is created from a struct that embeds another struct type: + +```go +type ExampleAttributeModel struct { + Attr1 types.String `tfsdk:"attr_1"` + Attr2 types.Bool `tfsdk:"attr_2"` + CommonModel // This embedded struct type promotes the Attr3 field +} + +type CommonModel struct { + Attr3 types.Int64 `tfsdk:"attr_3"` +} + +func (m ExampleAttributeModel) AttributeTypes() map[string]attr.Type { + return map[string]attr.Type{ + "attr_1": types.StringType, + "attr_2": types.BoolType, + "attr_3": types.Int64Type, + } +} + +value := ExampleAttributeModel{ + Attr1: types.StringValue("example"), + Attr2: types.BoolValue(true), +} +// This field is promoted from CommonModel +value.Attr3 = types.Int64Value(123) + +objectValue, diags := types.ObjectValueFrom(ctx, value.AttributeTypes(), value) +``` + +#### Restrictions + +In addition to the constraints [listed above for object conversions](#automatic-conversion-with-tfsdk-struct-tags) using `tfsdk` tagged fields, embedded struct types have these additional restrictions: + +* Promoted fields cannot have duplicate `tfsdk` struct tags that conflict with any fields of structs they are embedded within. +```go +type thingResourceModel struct { + Attr1 types.String `tfsdk:"attr_1"` + Attr2 types.Bool `tfsdk:"attr_2"` + CommonModel +} + +type CommonModel struct { + // This field has a duplicate tfsdk tag, conflicting with (thingResourceModel).Attr1 + // and will raise an error diagnostic during conversion. + ConflictingAttr types.Int64 `tfsdk:"attr_1"` +} +``` +* Struct types embedded by pointers are not supported. +```go +type thingResourceModel struct { + Attr1 types.String `tfsdk:"attr_1"` + Attr2 types.Bool `tfsdk:"attr_2"` + // This type of struct embedding is not supported and will raise + // an error diagnostic during conversion. + // + // Remove the pointer to embed the struct by value. + *CommonModel +} + +type CommonModel struct { + Attr3 types.Int64 `tfsdk:"attr_3"` + Attr4 types.List `tfsdk:"attr_4"` +} +``` + +## Extending + +The framework supports extending its base type implementations with [custom types](/terraform/plugin/framework/handling-data/types/custom). These can adjust expected provider code usage depending on their implementation. diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/types/set.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/types/set.mdx new file mode 100644 index 000000000..d8970783c --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/types/set.mdx @@ -0,0 +1,124 @@ +--- +page_title: Set types +description: >- + Learn how to implement set value types with the Terraform plugin framework. +--- + +# Set types + +Set types store an ordered collection of single element type. + +By default, sets from [schema](/terraform/plugin/framework/handling-data/schemas) (configuration, plan, and state) data are represented in the framework by [`types.SetType`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#SetType) and its associated value storage type of [`types.Set`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#Set). These types fully support Terraform's [type system concepts](/terraform/plugin/framework/handling-data/terraform-concepts) that cannot be represented in Go built-in types, such as a slice. Framework types can be [extended](#extending) by provider code or shared libraries to provide specific use case functionality. + +## Schema Definitions + +Use one of the following attribute types to directly add a set of a single element type to a [schema](/terraform/plugin/framework/handling-data/schemas) or [nested attribute type](/terraform/plugin/framework/handling-data/attributes#nested-attribute-types): + +| Schema Type | Attribute Type | +|-------------|----------------| +| [Data Source](/terraform/plugin/framework/data-sources) | [`schema.SetAttribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/datasource/schema#SetAttribute) | +| [Provider](/terraform/plugin/framework/providers) | [`schema.SetAttribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/provider/schema#SetAttribute) | +| [Resource](/terraform/plugin/framework/resources) | [`schema.SetAttribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema#SetAttribute) | +| [Ephemeral Resource](/terraform/plugin/framework/ephemeral-resources) | [`schema.SetAttribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/ephemeral/schema#SetAttribute) | + +Use one of the following attribute types to directly add a set of a nested attributes to a [schema](/terraform/plugin/framework/handling-data/schemas) or [nested attribute type](/terraform/plugin/framework/handling-data/attributes#nested-attribute-types): + +| Schema Type | Attribute or Block Type | +|-------------|-------------------------| +| [Data Source](/terraform/plugin/framework/data-sources) | [`schema.SetNestedAttribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/datasource/schema#SetNestedAttribute) | +| [Data Source](/terraform/plugin/framework/data-sources) | [`schema.SetNestedBlock`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/datasource/schema#SetNestedBlock) | +| [Provider](/terraform/plugin/framework/providers) | [`schema.SetNestedAttribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/provider/schema#SetNestedAttribute) | +| [Provider](/terraform/plugin/framework/providers) | [`schema.SetNestedBlock`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/provider/schema#SetNestedBlock) | +| [Resource](/terraform/plugin/framework/resources) | [`schema.SetNestedAttribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema#SetNestedAttribute) | +| [Resource](/terraform/plugin/framework/resources) | [`schema.SetNestedBlock`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema#SetNestedBlock) | +| [Ephemeral Resource](/terraform/plugin/framework/ephemeral-resources) | [`schema.SetNestedAttribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/ephemeral/schema#SetNestedAttribute) | +| [Ephemeral Resource](/terraform/plugin/framework/ephemeral-resources) | [`schema.SetNestedBlock`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/ephemeral/schema#SetNestedBlock) | + +If the set value should be the element type of another [collection attribute type](/terraform/plugin/framework/handling-data/attributes#collection-attribute-types), set the `ElementType` field to `types.SetType{ElemType: /* ... */}` or the appropriate [custom type](#extending). + +If the set value should be a value type of an [object attribute type](/terraform/plugin/framework/handling-data/attributes#object-attribute-type), set the `AttributeTypes` map value to `types.SetType{ElemType: /* ... */}` or the appropriate [custom type](#extending). + +## Accessing Values + + + +Review the [attribute documentation](/terraform/plugin/framework/handling-data/attributes/set#accessing-values) to understand how schema-based data gets mapped into accessible values, such as a `types.Set` in this case. + + + +Access `types.Set` information via the following methods: + +* [`(types.Set).IsNull() bool`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types/basetypes#SetValue.IsNull): Returns `true` if the set is null. +* [`(types.Set).IsUnknown() bool`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types/basetypes#SetValue.IsUnknown): Returns `true` if the set is unknown. Returns `false` if the number of elements is known, any of which may be unknown. +* [`(types.Set).Elements() []attr.Value`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types/basetypes#SetValue.Elements): Returns the known `[]attr.Value` value, or `nil` if null or unknown. +* [`(types.Set).ElementsAs(context.Context, any, bool) diag.Diagnostics`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types/basetypes#SetValue.ElementsAs): Converts the known values into the given Go type, if possible. It is recommended to use a slice of framework types to account for elements which may be unknown. + +In this example, a set of strings value is checked for being null or unknown value first, before accessing its known value elements as a `[]types.String`: + +```go +// Example data model definition +// type ExampleModel struct { +// ExampleAttribute types.Set `tfsdk:"example_attribute"` +// } +// +// This would be filled in, such as calling: req.Plan.Get(ctx, &data) +var data ExampleModel + +// optional logic for handling null value +if data.ExampleAttribute.IsNull() { + // ... +} + +// optional logic for handling unknown value +if data.ExampleAttribute.IsUnknown() { + // ... +} + +elements := make([]types.String, 0, len(data.ExampleAttribute.Elements())) +diags := data.ExampleAttribute.ElementsAs(ctx, &elements, false) +``` + +## Setting Values + +Call one of the following to create a `types.Set` value: + +* [`types.SetNull(attr.Type) types.Set`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#SetNull): A null set value with the given element type. +* [`types.SetUnknown(attr.Type) types.Set`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#SetUnknown): An unknown set value with the given element type. +* [`types.SetValue(attr.Type, []attr.Value) (types.Set, diag.Diagnostics)`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#SetValue): A known value with the given element type and values. +* [`types.SetValueFrom(context.Context, attr.Type, any) (types.Set, diag.Diagnostics)`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#SetValueFrom): A known value with the given element type and values. This can convert the source data from standard Go types into framework types as noted in the documentation for each element type, such as giving `[]*string` for a `types.Set` of `types.String`. +* [`types.SetValueMust(attr.Type, []attr.Value) types.Set`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#SetValueMust): A known value with the given element type and values. Any diagnostics are converted to a runtime panic. This is recommended only for testing or exhaustively tested logic. + +In this example, a known set value is created from framework types: + +```go +elements := []attr.Value{types.StringValue("one"), types.StringValue("two")} +setValue, diags := types.SetValue(types.StringType, elements) +``` + +Otherwise, for certain framework functionality that does not require `types` implementations directly, such as: + +* [`(tfsdk.State).SetAttribute()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/tfsdk#State.SetAttribute) +* [`types.ListValueFrom()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#ListValueFrom) +* [`types.MapValueFrom()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#MapValueFrom) +* [`types.ObjectValueFrom()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#ObjectValueFrom) +* [`types.SetValueFrom()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#SetValueFrom) + +A Go built-in slice type (`[]T`) or type alias of a slice type such as `type MyListType []T` can be used instead. + +In this example, a `[]string` is directly used to set a set attribute value: + +```go +elements := []string{"one", "two"} +diags := resp.State.SetAttribute(ctx, path.Root("example_attribute"), elements) +``` + +In this example, a `types.Set` of `types.String` is created from a `[]string`: + +```go +elements := []string{"one", "two"} +setValue, diags := types.SetValueFrom(ctx, types.StringType, elements) +``` + +## Extending + +The framework supports extending its base type implementations with [custom types](/terraform/plugin/framework/handling-data/types/custom). These can adjust expected provider code usage depending on their implementation. diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/types/string.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/types/string.mdx new file mode 100644 index 000000000..65eb016cc --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/types/string.mdx @@ -0,0 +1,121 @@ +--- +page_title: String types +description: >- + Learn how to implement string value types with the Terraform plugin framework. +--- + +# String types + +String types store a collection of UTF-8 encoded bytes. + +By default, strings from [schema](/terraform/plugin/framework/handling-data/schemas) (configuration, plan, and state) data are represented in the framework by [`types.StringType`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#StringType) and its associated value storage type of [`types.String`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#String). These types fully support Terraform's [type system concepts](/terraform/plugin/framework/handling-data/terraform-concepts) that cannot be represented in Go built-in types, such as `*string`. Framework types can be [extended](#extending) by provider code or shared libraries to provide specific use case functionality. + +## Schema Definitions + +Use one of the following attribute types to directly add a string value to a [schema](/terraform/plugin/framework/handling-data/schemas) or [nested attribute type](/terraform/plugin/framework/handling-data/attributes#nested-attribute-types): + +| Schema Type | Attribute Type | +|-------------|----------------| +| [Data Source](/terraform/plugin/framework/data-sources) | [`schema.StringAttribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/datasource/schema#StringAttribute) | +| [Provider](/terraform/plugin/framework/providers) | [`schema.StringAttribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/provider/schema#StringAttribute) | +| [Resource](/terraform/plugin/framework/resources) | [`schema.StringAttribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema#StringAttribute) | +| [Ephemeral Resource](/terraform/plugin/framework/ephemeral-resources) | [`schema.StringAttribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/ephemeral/schema#StringAttribute) | + +If the string value should be the element type of a [collection attribute type](/terraform/plugin/framework/handling-data/attributes#collection-attribute-types), set the `ElemType` field to `types.StringType` or the appropriate [custom type](#extending). + +If the string value should be a value type of an [object attribute type](/terraform/plugin/framework/handling-data/attributes#object-attribute-type), set the `AttrTypes` map value to `types.StringType` or the appropriate [custom type](#extending). + +## Accessing Values + + + +Review the [attribute documentation](/terraform/plugin/framework/handling-data/attributes/string#accessing-values) to understand how schema-based data gets mapped into accessible values, such as a `types.String` in this case. + + + + + +The `(types.String).String()` method is reserved for debugging purposes and returns `""` if the value is null and `""` if the value is unknown. Use `(types.String).ValueString()` or `(types.String).ValueStringPointer()` for accessing a known string value. + + + +Access `types.String` information via the following methods: + +* [`(types.String).IsNull() bool`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types/basetypes#StringValue.IsNull): Returns true if the string is null. +* [`(types.String).IsUnknown() bool`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types/basetypes#StringValue.IsUnknown): Returns true if the string is unknown. +* [`(types.String).ValueString() string`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types/basetypes#StringValue.ValueString): Returns the known string, or an empty string if null or unknown. +* [`(types.String).ValueStringPointer() *string`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types/basetypes#StringValue.ValueStringPointer): Returns a string pointer to a known value, `nil` if null, or a pointer to an empty string if unknown. + +In this example, a string value is checked for being null or unknown value first, before accessing its known value: + +```go +// Example data model definition +// type ExampleModel struct { +// ExampleAttribute types.String `tfsdk:"example_attribute"` +// } +// +// This would be filled in, such as calling: req.Plan.Get(ctx, &data) +var data ExampleModel + +// optional logic for handling null value +if data.ExampleAttribute.IsNull() { + // ... +} + +// optional logic for handling unknown value +if data.ExampleAttribute.IsUnknown() { + // ... +} + +// myString now contains a Go string with the known value +myString := data.ExampleAttribute.ValueString() +``` + +## Setting Values + +Call one of the following to create a `types.String` value: + +* [`types.StringNull()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#StringNull): A null string value. +* [`types.StringUnknown()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#StringUnknown): An unknown string value. +* [`types.StringValue(string)`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#StringValue): A known value. +* [`types.StringPointerValue(*string)`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#StringPointerValue): A known value. + +In this example, a known string value is created: + +```go +types.StringValue("example value") +``` + +Otherwise, for certain framework functionality that does not require `types` implementations directly, such as: + +* [`(tfsdk.State).SetAttribute()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/tfsdk#State.SetAttribute) +* [`types.ListValueFrom()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#ListValueFrom) +* [`types.MapValueFrom()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#MapValueFrom) +* [`types.ObjectValueFrom()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#ObjectValueFrom) +* [`types.SetValueFrom()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#SetValueFrom) + +A Go built-in `string`, `*string` (only with typed `nil`, `(*string)(nil)`), or type alias of `string` such as `type MyStringType string` can be used instead. + +In this example, a `string` is directly used to set a string attribute value: + +```go +diags := resp.State.SetAttribute(ctx, path.Root("example_attribute"), "example value") +``` + +In this example, a `types.List` of `types.String` is created from a `[]string`: + +```go +listValue, diags := types.ListValueFrom(ctx, types.StringType, []string{"value one", "value two"}) +``` + +## Extending + +The framework supports extending its base type implementations with [custom types](/terraform/plugin/framework/handling-data/types/custom). These can adjust expected provider code usage depending on their implementation. + +### Common Use Case Types + +HashiCorp provides additional Go modules which contain custom string type implementations covering common use cases with validation and semantic equality logic: + +* [`terraform-plugin-framework-jsontypes`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework-jsontypes): JSON encoded strings, such as exact byte strings and normalized strings +* [`terraform-plugin-framework-nettypes`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework-nettypes): Networking strings, such as IPv4 addresses, IPv6 addresses, and CIDRs +* [`terraform-plugin-framework-timetypes`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework-timetypes): Timestamp strings, such as RFC3339 diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/types/tuple.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/types/tuple.mdx new file mode 100644 index 000000000..1276559a9 --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/types/tuple.mdx @@ -0,0 +1,55 @@ +--- +page_title: Tuple types +description: >- + Learn how to implement tuple value types with the Terraform plugin framework. +--- + +# Tuple types + + + +The tuple type doesn't have associated schema attributes as it has limited real world application. Provider developers will only encounter tuples when handling provider-defined function variadic parameters or dynamic values. + + + +Tuple types store an ordered collection of elements where each element has it's own type. Values must have **exactly** the same number of elements (no more and no fewer), and the value in each position must match the specified type for that position. + +The tuple type is used to express Terraform's [tuple type constraint](/terraform/language/expressions/type-constraints#tuple). + +## Schema Definitions + +The tuple type is not supported in schema definitions of provider, data sources, ephemeral resources, or managed resources as it has limited real world application. + +## Accessing Values + +Access `types.Tuple` information via the following methods: + +* [`(types.Tuple).IsNull() bool`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types/basetypes#TupleValue.IsNull): Returns `true` if the tuple is null. +* [`(types.Tuple).IsUnknown() bool`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types/basetypes#TupleValue.IsUnknown): Returns `true` if the tuple is unknown. Returns `false` if the number of elements is known, any of which may be unknown. +* [`(types.Tuple).Elements() []attr.Value`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types/basetypes#TupleValue.Elements): Returns the known `[]attr.Value` value, or `nil` if null or unknown. + +## Setting Values + +Call one of the following to create a `types.Tuple` value: + +* [`types.TupleNull([]attr.Type) types.Tuple`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#TupleNull): A null tuple value with the given element types. +* [`types.TupleUnknown([]attr.Type) types.Tuple`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#TupleUnknown): An unknown tuple value with the given element types. +* [`types.TupleValue([]attr.Type, []attr.Value) (types.Tuple, diag.Diagnostics)`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#TupleValue): A known value with the given element types and values. +* [`types.TupleValueMust([]attr.Type, []attr.Value) types.Tuple`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#TupleValueMust): A known value with the given element types and values. Any diagnostics are converted to a runtime panic. This is recommended only for testing or exhaustively tested logic. + +In this example, a known tuple value (`["one", true, 123]`) is created from framework types: + +```go +elementTypes := []attr.Type{ + types.StringType, + types.BoolType, + types.Int64Type, +} +elements := []attr.Value{ + types.StringValue("one"), + types.BoolValue(true), + types.Int64Value(123), +} + +tupleValue, diags := types.TupleValue(elementTypes, elements) +``` diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/writing-state.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/writing-state.mdx new file mode 100644 index 000000000..200ef6501 --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/writing-state.mdx @@ -0,0 +1,85 @@ +--- +page_title: Writing state +description: >- + Learn how to write and update the Terraform statefile with the Terraform + plugin framework. +--- + +# Writing state + +One of the primary jobs of a Terraform provider is to manage the provider's +resources and data sources in the [Terraform state](/terraform/language/state). Writing values to state +is something that provider developers will do frequently. + +The state that a provider developer wants to update is usually stored in a +response object: + +```go +func (r ThingResource) Create(ctx context.Context, + req resource.CreateRequest, resp *resource.CreateResponse) +``` + +In this example, `resp` holds the state that the provider developer should +update. + +## Replace the Entire State + +One way to set the state is to replace all the state values for a resource or +data source all at once. You need to define a type to contain the values. The benefit is that this allows the compiler to check all code that sets values on state, and only the final call to persist state can return an error. + +Use the `Set` method to store the entire state data. + +```go +type ThingResourceModel struct { + Address types.Object `tfsdk:"address"` + Age types.Int64 `tfsdk:"age"` + Name types.String `tfsdk:"name"` + Pets types.List `tfsdk:"pets"` + Registered types.Bool `tfsdk:"registered"` + Tags types.Map `tfsdk:"tags"` +} + +func (r ThingResource) Create(ctx context.Context, + req resource.CreateRequest, resp *resource.CreateResponse) { + var newState ThingResourceModel + + // ... + // update newState by modifying each property as usual for Go values + newState.Name = types.StringValue("J. Doe") + + // persist the values to state + diags := resp.State.Set(ctx, &newState) + + resp.Diagnostics.Append(diags...) + + if resp.Diagnostics.HasError() { + return + } +} +``` + +The state information is represented as an object, and gets persisted like an +object. Refer to the [object type](/terraform/plugin/framework/handling-data/types/object) documentation for an explanation on how +objects get persisted and what Go types are valid for persisting as an object. + +## Set a Single Attribute or Block Value + +Use the `SetAttribute` method to set an individual attribute or block value. + +-> The value must not be an untyped `nil`. Use a typed `nil` or `types` package null value function instead. For example with a `types.StringType` attribute, use `(*string)(nil)` or `types.StringNull()`. + +```go +func (r ThingResource) Read(ctx context.Context, + req resource.ReadRequest, resp *resource.ReadResponse) { + // ... + diags := resp.State.SetAttribute(ctx, path.Root("age"), 7) + + resp.Diagnostics.Append(diags...) + + if resp.Diagnostics.HasError() { + return + } +} +``` + +Refer to the [types](/terraform/plugin/framework/handling-data/types) documentation for more information about supported Go types. diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/index.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/index.mdx new file mode 100644 index 000000000..3595b4f4c --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/index.mdx @@ -0,0 +1,36 @@ +--- +page_title: Terraform plugin framework +description: >- + The Terraform plugin framework is an SDK that you can use to develop Terraform + providers. Learn how the plugin framework works with Terraform core. +--- + +# Terraform plugin framework + +The plugin framework is HashiCorp’s recommended way develop to Terraform Plugins on [protocol version 6](/terraform/plugin/terraform-plugin-protocol#protocol-version-6) or [protocol version 5](/terraform/plugin/terraform-plugin-protocol#protocol-version-5). + +We recommend using the framework to develop new providers because it offers significant advantages as compared to [Terraform Plugin SDKv2](/terraform/plugin/sdkv2). We also recommend migrating existing providers to the framework when possible. Refer to [Plugin Framework Benefits](/terraform/plugin/framework-benefits) for higher level details about how the framework makes provider development easier and [Plugin Framework Features](/terraform/plugin/framework/migrating/benefits) for a detailed functionality comparison between the SDKv2 and the framework. + +## Get Started + +- Try the [Terraform Plugin Framework tutorials](/terraform/tutorials/providers-plugin-framework). +- Clone the [terraform-provider-scaffolding-framework](https://github.com/hashicorp/terraform-provider-scaffolding-framework) template repository on GitHub. + +## Key Concepts + +- [Provider Servers](/terraform/plugin/framework/provider-servers) encapsulate all Terraform plugin details and handle all calls for provider, resource, and data source operations by implementing the [Terraform Plugin Protocol](/terraform/plugin/how-terraform-works#terraform-plugin-protocol). They are implemented as binaries that the Terraform CLI downloads, starts, and stops. +- [Providers](/terraform/plugin/framework/providers) are the top level abstraction that define the available resources and data sources for practitioners to use and may accept its own configuration, such as authentication information. +- [Schemas](/terraform/plugin/framework/handling-data/schemas) define available fields for provider, resource, or provisioner configuration block, and give Terraform metadata about those fields. +- [Resources](/terraform/plugin/framework/resources) are an abstraction that allow Terraform to manage infrastructure objects, such as a compute instance, an access policy, or disk. Providers act as a translation layer between Terraform and an API, offering one or more resources for practitioners to define in a configuration. +- [Data Sources](/terraform/plugin/framework/data-sources) are an abstraction that allow Terraform to reference external data. Providers have data sources that tell Terraform how to request external data and how to convert the response into a format that practitioners can interpolate. +- [Functions](/terraform/plugin/framework/functions) are an abstraction that allow Terraform to reference computational logic. Providers can implement their own custom logic functions to augment the Terraform configuration language [built-in functions](/terraform/language/functions). + +## Test and Publish + +- Learn to write [acceptance tests](/terraform/plugin/framework/acctests) for your provider. +- Learn to [publish your provider](/terraform/registry/providers/publishing) to the Terraform Registry. + +## Combine or Translate + +- [Combine your provider with other SDKv2 providers](/terraform/plugin/mux/combining-protocol-version-5-providers) using [protocol version 5](/terraform/plugin/how-terraform-works#protocol-version-5). +- [Combine your provider with other framework providers](/terraform/plugin/mux/combining-protocol-version-6-providers) using [protocol version 6](/terraform/plugin/how-terraform-works#protocol-version-6). diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/internals/index.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/internals/index.mdx new file mode 100644 index 000000000..3212e2b71 --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/internals/index.mdx @@ -0,0 +1,16 @@ +--- +page_title: Framework internals +description: >- + The Terraform plugin framework is a set of libraries implemented in Go. + Learn about the internal implementation details of the framework. +--- + +# Framework internals + +The following information describes some internals of the Terraform Plugin Framework in order to provide a more in-depth view of specific aspects of Framework behaviour. + +## RPCs + +Terraform core issues RPCs when Terraform commands are executed resulting in the subsequent execution of methods defined within providers. + +Refer to [RPCs](/terraform/plugin/framework/internals/rpcs) for details of the [sequence of RPCs](/terraform/plugin/terraform-plugin-protocol#rpcs-and-terraform-commands) that are issued when Terraform commands are executed and Framework functionality that is called as a consequence. diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/internals/rpcs.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/internals/rpcs.mdx new file mode 100644 index 000000000..4e12a18fe --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/internals/rpcs.mdx @@ -0,0 +1,214 @@ +--- +page_title: Framework RPCs +description: >- + Learn how Terraform uses RPCs to support provider functionality. +--- + +# RPCs and framework functionality + +The correlation between the Terraform command, the RPCs that are issued and the Terraform plugin framework methods that are called is as follows: + +## _terraform validate_ + +| Protocol RPCs | Framework Functionality | +|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| [GetProviderSchema](#getproviderschema-rpc) | `provider.Provider`, `resource.Resource`, and `datasource.DataSource` interface `Schema` method | +| [ValidateProviderConfig](#validateproviderconfig-rpc) / [ValidateResourceConfig](#validateresourceconfig-rpc) / [ValidateDataResourceConfig](#validatedataresourceconfig-rpc) | `provider.Provider`, `resource.Resource`, and `datasource.DataSource` interface `Schema` method validators, `ConfigValidators` method, and `ValidateConfig` method | + +## _terraform plan_ + +| Protocol RPCs | Framework Functionality | +|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| [GetProviderSchema](#getproviderschema-rpc) | `provider.Provider`, `resource.Resource`, and `datasource.DataSource` interface `Schema` method | +| [ValidateProviderConfig](#validateproviderconfig-rpc) / [ValidateResourceConfig](#validateresourceconfig-rpc) / [ValidateDataResourceConfig](#validatedataresourceconfig-rpc) | `provider.Provider`, `resource.Resource`, and `datasource.DataSource` interface `Schema` method validators, `ConfigValidators` method, and `ValidateConfig` method | +| [ConfigureProvider](#configureprovider-rpc) | `provider.Provider` interface `Configure` method | +| [ReadResource](#readresource-rpc) / [ReadDataSource](#readdatasource-rpc) | `resource.Resource` and `datasource.DataSource` interface `Read` method | +| [PlanResourceChange](#planresourcechange-rpc) | `resource.Resource` interface `Schema` method plan modifiers and `ModifyPlan` method | + +## _terraform apply_ + +| Protocol RPCs | Framework Functionality | +|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| [GetProviderSchema](#getproviderschema-rpc) | `provider.Provider`, `resource.Resource`, and `datasource.DataSource` interface `Schema` method | +| [ValidateProviderConfig](#validateproviderconfig-rpc) / [ValidateResourceConfig](#validateresourceconfig-rpc) / [ValidateDataResourceConfig](#validatedataresourceconfig-rpc) | `provider.Provider`, `resource.Resource`, and `datasource.DataSource` interface `Schema` method validators, `ConfigValidators` method, and `ValidateConfig` method | +| [ConfigureProvider](#configureprovider-rpc) | `provider.Provider` interface `Configure` method | +| [ReadResource](#readresource-rpc) / [ReadDataSource](#readdatasource-rpc) | `resource.Resource` and `datasource.DataSource` interface `Read` method | +| [PlanResourceChange](#planresourcechange-rpc) | `resource.Resource` interface `Schema` method plan modifiers and `ModifyPlan` method | +| [ApplyResourceChange](#applyresourcechange-rpc) | `resource.Resource` interface `Create`, `Update`, or `Delete` method | + + +## GetProviderSchema RPC + +### Summary + +![diagram: GetProviderSchema RPC Overview](/img/get-provider-schema-overview.png) + + +When _terraform validate | plan | apply_ are executed Terraform core issues the `GetProviderSchema` RPC. The RPC flows through the Terraform plugin framework and ultimately calls the `Schema` function on the provider and on each of the resources and data sources that the provider defines. + +### Detail + +![diagram: GetProviderSchema RPC Detail](/img/get-provider-schema-detail.png) + + +Within the Terraform plugin framework, `terraform-plugin-go` is used to expose gRPC endpoints defined by the Terraform plugin protocol. These endpoints implement the `tfplugin6.ProviderServer` interface for version 6 of the Terraform plugin protocol. The `terraform-plugin-go` module also defines a `tfprotov6.ProviderServer` interface which is implemented by the `terraform-plugin-framework` module, and includes the `GetProviderSchema` function. + +The `terraform-plugin-framework` module implements the `tfprotov6.ProviderServer` interface `GetProviderSchema` function from the `terraform-plugin-go` module in `proto6server.GetProviderSchema`. The `proto6server.GetProviderSchema` function calls `fwserver.GetProviderSchema` which then calls `fwserver.ProviderSchema`, `fwserver.ResourceSchemas` and `fwserver.DataSourceSchemas` functions. The `terraform-plugin-framework` module defines `provider.Provider`, `resource.Resource` and `datasource.DataSource` interfaces for providers, resources and data sources, respectively. Each of these interfaces include a `Schema` function which is implemented by the Terraform provider code written by the provider developer. + +In summary, the schemas for the provider and each of the resources and data sources are defined by the provider developer through implementation of the `Schema` function defined on the `provider.Provider`, `resource.Resource` and `datasource.DataSource` interfaces, respectively. For the `GetProviderSchema` RPC, the implementation of the `Schema` function in the `provider.Provider`, `resource.Resource` and `datasource.DataSource` interfaces represents the "touch-point" for where the RPC sent from Terraform core interacts with the code written by the provider developer. + +## ValidateConfig RPCs + +### Summary + +![diagram: ValidateConfig RPCs Overview](/img/validate-config-overview.png) + + +When _terraform validate | plan | apply_ are executed if the Terraform configuration contains configuration for the provider then Terraform core issues the `ValidateProviderConfig` RPC. Additionally, the `ValidateResourceConfig` and `ValidateDataResourceConfig` RPCs are issued for each of the resources and data sources that appear in the Terraform configuration. There is a 1:1 match between the schema returned from the `GetProviderSchema` RPC and the `ValidateConfig` RPCs. + +The `ValidateConfig` RPCs flow through the Terraform plugin framework and ultimately call the `Validate` function on each of the `ConfigValidators`, the `ValidateConfig` function on the provider, resource or data source, and each of the `Validate` functions defined on each of the attributes and blocks within the provider, resource or data source schema. + +The `ValidateResourceConfig` and `ValidateDataResourceConfig` RPCs additionally call `resource.Configure` and `datasource.Configure`, respectively. + +### Detail + +#### ValidateProviderConfig RPC + +![diagram: ValidateProviderConfig RPC Detail](/img/validate-provider-config-detail.png) + + +#### ValidateResourceConfig RPC + +![diagram: ValidateResourceConfig RPC Detail](/img/validate-resource-config-detail.png) + + +#### ValidateDataResourceConfig RPC + +![diagram: ValidateDataResourceConfig RPC Detail](/img/validate-data-resource-config-detail.png) + + +Within the Terraform plugin framework, `terraform-plugin-go` is used to expose gRPC endpoints defined by the Terraform plugin protocol. These endpoints implement the `tfplugin6.ProviderServer` interface for version 6 of the Terraform plugin protocol. The `terraform-plugin-go` module also defines `tfprotov6.Server` interfaces which are implemented within the `terraform-plugin-framework` module. + +The `terraform-plugin-framework` module contains `proto6server.ValidateConfig` functions which are implementations of the `tfprotov6.Server` interface `ValidateConfig` functions, respectively. Each of these functions then call `fwserver.ValidateConfig` functions, respectively. + +If the provider, resource or data source implements the `.WithConfigValidators` interface defined in the `terraform-plugin-framework` module, the `.ConfigValidators` function is called to retrieve a slice of `.ConfigValidator`, and then `.Validate` is called on each element in the slice sequentially. + +If the provider, resource or data source implements the `.WithValidateConfig` interface, the `.ValidateConfig` function is called. + +The `fwserver.SchemaValidate` function is then called which iterates over each of the attributes and blocks defined within the provider, resource or data source schema and calls the `validator.Validate` for each of the validators defined on the attribute or block. + +In summary: +- The `.ConfigValidators` and `.Validate` functions are optionally defined by the provider developer through implementation of the `.WithConfigValidators` interfaces. +- The `.ValidateConfig` functions are optionally defined by the provider developer through implementation of the `.WithValidateConfig` interfaces. +- The attribute and block validators are optionally specified by the provider developer by adding a type-specific slice of validators to the attribute or block (e.g., []validator.String{...} on a schema.StringAttribute). + +## ConfigureProvider RPC + +### Summary + +![diagram: ConfigureProvider RPC Overview](/img/configure-provider-overview.png) + + +When _terraform plan | apply_ are executed Terraform core issues the `ConfigureProvider` RPC. + +The `ConfigureProvider` RPC flows through the Terraform plugin framework and ultimately calls the `Configure` function on the provider. + +### Detail + +![diagram: ConfigureProvider RPC Detail](/img/configure-provider-detail.png) + + +Within the Terraform plugin framework, `terraform-plugin-go` is used to expose gRPC endpoints defined by the Terraform plugin protocol. These endpoints implement the `tfplugin6.ProviderServer` interface for version 6 of the Terraform plugin protocol. The `terraform-plugin-go` module also defines the `tfprotov6.ProviderServer` interface which is implemented within the `terraform-plugin-framework` module. + +The `terraform-plugin-framework` module contains a `proto6server.ConfigureProvider` function which is an implementation of the `tfprotov6.ProviderServer` interface `ConfigureProvider` function. The `proto6server.ConfigureProvider` function calls the `fwserver.ConfigureProvider` function. The `terraform-plugin-framework` defines the `provider.Provider` interface which contains a `Configure` function. The `Configure` function is implemented by the provider developer, and this function is called by the `fwserver.ConfigureProvider` function. + +In summary, the `provider.Provider` interface defines a `Configure` function which must be defined by the provider developer. + +## Read RPCs + +### Summary + +![diagram: Read RPC Overview](/img/read-overview.png) + + +When _terraform plan | apply_ are executed Terraform core issues the `ReadResource` and `ReadDataSource` RPCs. Note that the `ReadResource` RPC is only issued when a resource already exists in state. + +The `ReadResource` and `ReadDataSource` RPCs flow through the Terraform plugin framework and ultimately call the `Read` function on the resource and data source, respectively. + +### Detail + +#### ReadResource RPC + +![diagram: ReadResource RPC Detail](/img/read-resource-detail.png) + + +#### ReadDataSource RPC + +![diagram: ReadDataSource RPC Detail](/img/read-data-source-detail.png) + + +Within the Terraform plugin framework, `terraform-plugin-go` is used to expose gRPC endpoints defined by the Terraform plugin protocol. These endpoints implement the `tfplugin6.ProviderServer` interface for version 6 of the Terraform plugin protocol. The `terraform-plugin-go` module also defines the `tfprotov6.ProviderServer` interface which encompasses the `tfprotov6.ResourceServer` and `tfprotov6.DataSourceServer` interfaces. The `tfprotov6.ProviderServer` interface is implemented within the `terraform-plugin-framework` module. + +The `terraform-plugin-framework` module contains a `proto6server.ReadResource` function which is an implementation of the `tfprotov6.ResourceServer` interface `ReadResource` function. The `proto6server.ReadResource` function calls the `fwserver.ReadResource` function. The `terraform-plugin-framework` defines the `resource.ResourceWithConfigure` interface which contains a `Configure` function. If the resource implements the `resource.ResourceWithConfigure` interface then the `Configure` function that has been implemented by the provider developer is called. The `terraform-plugin-framework` defines the `resource.Resource` interface which contains a `Read` function which is called by the `fwserver.ReadResource` function. + +The `terraform-plugin-framework` module contains a `proto6server.ReadDataSource` function which is an implementation of the `tfprotov6.DataSourceServer` interface `ReadDataSource` function. The `proto6server.ReadDataSource` function calls the `fwserver.ReadDataSource` function. The `terraform-plugin-framework` defines the `datasource.DataSourceWithConfigure` interface which contains a `Configure` function. If the data source implements the `datasource.DataSourceWithConfigure` interface then the `Configure` function that has been implemented by the provider developer is called. The `terraform-plugin-framework` defines the `datasource.DataSource` interface which contains a `Read` function which is called by the `fwserver.ReadDataSource` function. + +In summary, the `resource.Resource` interface defines a `Read` function which is called by the `ReadResource` RPC and the `datasource.DataSource` interface defines a `Read` function which is called by the `ReadDataSource` RPC. All resources and data sources must have provider developer defined `Read` functions. + +## PlanResourceChange RPC + +### Summary + +![diagram: PlanResourceChange RPC Overview](/img/plan-resource-change-overview.png) + + +When _terraform plan | apply_ are executed Terraform core issues the `PlanResourceChange` RPC. Note that the `PlanResourceChange` RPC is only issued when a resource exists in configuration and/or a resource already exists in state. + +The `PlanResourceChange` RPC flows through the Terraform plugin framework and ultimately calls each of the `PlanModify` functions on each of the attributes and blocks within the resource schema and `ModifyPlan` on the resource if the `resource.ResourceWithModifyPlan` interface has been implemented. + +The `PlanResourceChange` RPC also calls `resource.Configure` if the `resource.ResourceWithConfigure` interface has been implemented. + +### Detail + +![diagram: PlanResourceChange RPC Detail](/img/plan-resource-change-detail.png) + + +Within the Terraform plugin framework, `terraform-plugin-go` is used to expose gRPC endpoints defined by the Terraform plugin protocol. These endpoints implement the `tfplugin6.ProviderServer` interface for version 6 of the Terraform plugin protocol. The `terraform-plugin-go` module also defines the `tfprotov6.ResourceServer` interface which is encompassed by the `tfplugin6.ProviderServer` interface. + +The `terraform-plugin-framework` module contains the `proto6server.PlanResourceChange` function which is an implementation of the `tfprotov6.ResourceServer` interface `PlanResourceChange` function. The `PlanResourceChange` function calls the `fwserver.PlanResourceChange` function. + +If the resource implements the `resource.ResourceWithConfigure` interface, then the `resource.Configure` function defined by the provider developer is called. + +All attributes that are null in the configuration are then marked as unknown in the plan so that a provider has the opportunity to update them. The `fwserver.SchemaModifyPlan` function is then called which iterates over each of the attributes and blocks defined within the resource schema and calls `planmodifier.PlanModify` for each of the plan modifiers defined on the attribute or block. + +If the resource implements the `resource.ResourceWithModifyPlan` interface then the provider developer defined `resource.ModifyPlan` function is called. + +In summary, `resource.Configure` is called on the resource if it implements the `resource.ResourceWithConfigure` interface. The `PlanModify` functions on all the plan modifiers defined on each of the attributes and blocks within the resource schema are executed. The `resource.ModifyPlan` function is called on the resource if it implements the `resource.ResourceWithModifyPlan` interface. + +## ApplyResourceChange RPC + +### Summary + +![diagram: ApplyResourceChange RPC Overview](/img/apply-resource-change-overview.png) + + +When terraform apply is executed Terraform core issues the `ApplyResourceChange` RPC. Note that the `ApplyResourceChange RPC` is only issued when a resource exists in configuration and/or a resource already exists in state. + +The `ApplyResourceChange` RPC flows through the Terraform plugin framework and ultimately calls either `resource.Create`, `resource.Update` or `resource.Delete` on the resource depending upon the contents of the state and the plan. + +The `ApplyResourceChange` RPC also calls `resource.Configure` method if the `resource.ResourceWithConfigure` interface has been implemented. + +### Detail + +![diagram: ApplyResourceChange RPC Detail](/img/apply-resource-change-detail.png) + + +Within the Terraform plugin framework, `terraform-plugin-go` is used to expose gRPC endpoints defined by the Terraform plugin protocol. These endpoints implement the `tfplugin6.ProviderServer` interface for version 6 of the Terraform plugin protocol. The `terraform-plugin-go` module also defines the `tfprotov6.ResourceServer` interface which is encompassed by the `tfplugin6.ProviderServer` interface. + +The `terraform-plugin-framework` module contains the `proto6server.ApplyResourceChange` function which is an implementation of the `tfprotov6.ResourceServer` interface `ApplyResourceChange` function. The `ApplyResourceChange` function calls the `fwserver.ApplyResourceChange` function. + +If the resource implements the `resource.ResourceWithConfigure` interface, then the `resource.Configure` function defined by the provider developer is called. + +The `resource.Resource` interface defined within `terraform-plugin-framework` contains functions for `Create`, `Update` and `Delete`. The provider developer must implement `Create`, `Update` and `Delete` functions for each resource. Whether the `Create`, `Update` or `Delete` function on the provider is called by the `fwserver.ApplyResourceChange` function depends on the contents of the state and the plan. + +In summary, the `ApplyResourcePlan` RPC will call `resource.Configure` on the resource if the resource implements the `resource.ResourceWithConfigure` interface. One of the provider developer defined `Create`, `Update` and `Delete` functions will be called by the `ApplyResourcePlan` RPC depending upon the contents of the state and the plan. diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/migrating/attributes-blocks/attribute-schema.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/migrating/attributes-blocks/attribute-schema.mdx new file mode 100644 index 000000000..4042f31d2 --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/migrating/attributes-blocks/attribute-schema.mdx @@ -0,0 +1,97 @@ +--- +page_title: Migrating attribute schema +description: >- + Learn how to iteratively migrate from the SDKv2 to the plugin framework using + the terraform-plugin-mux Go library. +--- + +# Migrating attribute schema + +Attributes define how users can configure values for your Terraform provider, resources, and data sources. Refer to +[Schemas - Attributes](/terraform/plugin/framework/handling-data/schemas#attributes) in the Framework documentation for details. + +This page explains how to migrate an attribute from SDKv2 to the plugin Framework. + +## SDKv2 +In SDKv2, attributes are defined by the `Schema` field in the provider, resource, or data source schema. The `Schema` +field maps each attribute name (string) to the attribute's `schema.Schema` struct. Both resources and data sources are +defined using the `schema.Resource` struct. + +The following code shows a basic implementation of attribute schema for a provider in SDKv2. + +```go +func ProviderExample() *schema.Provider { + return &schema.Provider{ + Schema: map[string]*schema.Schema{ + /* ... */ +}, +``` + +In SDKv2, resource and data source attributes are defined the same way on their respective types. + +```go +func resourceExample() *schema.Resource { + return &schema.Resource{ + Schema: map[string]*schema.Schema{ + /* ... */ +``` +## Framework + +In the Framework, you define attributes by setting the `Attributes` field on your provider, resource, or data type's +schema, as returned by the `Schema` method. The `schema.Schema` type returned by `SchemaResponse` includes an +`Attributes` field that maps each attribute name (string) to the attribute's definition. + +The following code shows how to define an attribute for a resource with the Framework. + +```go +func (r *ThingResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "example": /* ... */ +``` + +## Migration Notes + +Remember the following differences between SDKv2 and the Framework when completing the migration. + +- In SDKv2, attributes are defined by a map from attribute names to `schema.Schema` structs in the `Schema` field of +your resource's schema. In the Framework, attributes are defined by a map from attribute names to `schema.Attribute` +implementations in your resource's schema, which is returned by the resource `Schema` method. +- In SDKv2, the computed string `id` attribute was implicitly included in the schema. In the Framework, it must be explicitly defined in the schema. +- There are several differences between the implementation of attributes in SDKv2 and the Framework. Refer to the other +pages in the Attributes & Blocks section of this migration guide for more details. + +## Example + +### SDKv2 + +The following example shows the implementation of the `example_attribute` attribute for the `exampleDataSource` +data source. + +```go +func exampleDataSource() *schema.Resource { + return &schema.Resource{ + /* ... */ + Schema: map[string]*schema.Schema{ + "example_attribute": { + Type: schema.TypeString, + Required: true, + }, +``` + +### Framework + +The following shows the same section of provider code after the migration. + +This code implements the `example_attribute` attribute for the `exampleDataSource` data source with the Framework. + +```go +func (d *exampleDataSource) Schema(ctx context.Context, req datasource.SchemaRequest, resp *datasource.SchemaResponse) { + resp.Schema = schema.Schema{ + /* ... */ + Attributes: map[string]schema.Attribute{ + "example_attribute": schema.StringAttribute{ + Required: true, + }, + /* ... */ +``` diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/migrating/attributes-blocks/blocks-computed.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/migrating/attributes-blocks/blocks-computed.mdx new file mode 100644 index 000000000..1f4ee4bcc --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/migrating/attributes-blocks/blocks-computed.mdx @@ -0,0 +1,110 @@ +--- +page_title: Migrating computed blocks +description: >- + Learn how to igrate blocks with computed fields from SDKv2 to attribute + validators in the plugin framework. +--- + +# Migrating blocks with computed fields + +Some providers, resources, and data sources include repeatable nested blocks in their attributes. Some blocks contain +fields with `Computed: true`, which means that the provider code can define the value or that it could come from the +output of terraform apply (e.g., the ID of an EC2 instance). + +This page explains how to migrate computed-only blocks from SDKv2 to the Framework. Refer to +[Blocks](/terraform/plugin/framework/migrating/attributes-blocks/blocks) if you are looking for information about migrating blocks +that are practitioner configurable. + +## SDKv2 + +In SDKv2, blocks are defined by an attribute whose type is either `TypeList` or `TypeSet`, and whose `Elem` field is set to a +`schema.Resource` that contains a map of the block's attribute names to corresponding `schemaSchema` structs. + +```go +map[string]*schema.Schema{ + "example": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "nested_example": { + Type: schema.TypeString, + Computed: true, + /* ... */ +``` + +## Framework + +In the Framework, when working with protocol version 5, computed blocks are implemented using a `ListAttribute` which has an `ElementType` of `types.ObjectType`. + +```go +map[string]schema.Attribute{ +"example": schema.ListAttribute{ + Computed: true, + ElementType: types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "nested_example": types.StringType, + /* ... */ + +``` + +In the Framework, when working with protocol version 6, we recommend that you define computed blocks using nested +attributes. This example shows usage of `ListNestedAttribute` as this provides configuration references with list index +syntax as is the case when using `schema.TypeList` in SDKv2. `SingleNestedAttribute` is a good choice for single +underlying objects which results in a breaking change but also allows dropping `[0]` in configuration references. + +```go +map[string]schema.Attribute{ +"example": schema.ListNestedAttribute{ + Computed: true, + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "nested_example": schema.StringAttribute{ + Computed: true, + /* ... */ + +``` + +## Migration Notes + +- When using protocol version 5 specify `ElementType` as `types.ObjectType` when migrating blocks that are computed from SDKv2 to Framework. +- When using protocol version 6, use [nested attributes](/terraform/plugin/framework/schemas#attributes-1) when migrating blocks that are computed from SDKv2 to Framework. + +## Example + +### SDKv2 + +The following example shows the implementation of the `example_nested_block` with SDKv2. + +```go +Schema: map[string]*schema.Schema{ +"example_nested_block": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "example_block_attribute": { + Type: schema.TypeString, + Computed: true, + /* ... */ + }, +``` + +### Framework + +The following shows the same section of provider code after the migration. + +This code defines the `example_nested_block` block as an attribute on the schema, where the +`types.ObjectType` is being used to define the nested block. + +```go +map[string]schema.Attribute{ + "example_nested_block": schema.ListAttribute{ + ElementType: types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "example_block_attribute": types.StringType, + }, + }, + Computed: true, + /* ... */ +``` diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/migrating/attributes-blocks/blocks.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/migrating/attributes-blocks/blocks.mdx new file mode 100644 index 000000000..353029dee --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/migrating/attributes-blocks/blocks.mdx @@ -0,0 +1,159 @@ +--- +page_title: Migrating blocks +description: >- + Learn how to migrate blocks from SDKv2 to attribute validators in the plugin + framework. +--- + +# Migrating blocks + +Some providers, resources, and data sources include repeatable nested blocks in their attributes. These nested blocks +typically represent separate objects that are related to (or embedded within) the containing object. + +This page explains how to migrate nested blocks that are not computed (i.e., do not set +`Computed: true`) from SDKv2 to the Framework. Refer to +[Blocks with Computed Fields](/terraform/plugin/framework/migrating/attributes-blocks/blocks-computed) for more details +about migrating nested blocks that contain fields that are computed. + +The following table describes the mapping between [SDK Schema Fields](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-sdk/helper/schema#Schema) and the Framework. + +| SDK Schema Field | Framework | +|-----------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| Type | [ListNestedBlock](/terraform/plugin/framework/migrating/attributes-blocks/blocks), [SetNestedBlock](/terraform/plugin/framework/migrating/attributes-blocks/blocks) | +| ConfigMode | Schema must be explictly defined using [Attributes](/terraform/plugin/framework/migrating/attributes-blocks/attribute-schema) and [Blocks](/terraform/plugin/framework/migrating/attributes-blocks/blocks) | +| Required | [listvalidator.IsRequired](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework-validators/listvalidator#IsRequired), [setvalidator.IsRequired](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework-validators/setvalidator#IsRequired) | +| Optional | N/A - no implementation required | +| Computed | [Blocks with Computed Fields](/terraform/plugin/framework/migrating/attributes-blocks/blocks-computed) | +| ForceNew | [RequiresReplace](/terraform/plugin/framework/migrating/attributes-blocks/force-new) on `PlanModifiers` field on attribute within block or implementation of [ResourceWithModifyPlan](/terraform/plugin/framework/migrating/resources/plan-modification#framework) interface | +| DiffSuppressFunc | [PlanModifiers](/terraform/plugin/framework/migrating/resources/plan-modification#framework) field on attribute within block or implementation of [ResourceWithModifyPlan](/terraform/plugin/framework/migrating/resources/plan-modification#framework) interface | +| DiffSuppressOnRefresh | [Read](/terraform/plugin/framework/migrating/resources/crud) method on resource | +| Description | `Description` field on block | +| InputDefault | N/A - no longer valid | +| StateFunc | Requires implementation of bespoke logic before storing state, for instance in resource [Create method](/terraform/plugin/framework/migrating/resources/crud#framework-1) | +| Elem | `NestedObject` within block | +| MaxItems | Use [listValidator.SizeAtMost](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework-validators/listvalidator#SizeAtMost) or [setvalidator.SizeAtMost](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework-validators/setvalidator#SizeAtMost) on `Validators` field on `ListNestedBlock` or `SetNestedBlock` | +| MinItems | Use [listValidator.SizeAtLeast](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework-validators/listvalidator#SizeAtLeast) or [setvalidator.SizeAtLeast](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework-validators/setvalidator#SizeAtLeast) on `Validators` field on `ListNestedBlock` or `SetNestedBlock` | +| Set | N/A - no implementation required | | +| ComputedWhen | N/A - no longer valid | +| ConflictsWith | [Predefined Validators](/terraform/plugin/framework/migrating/attributes-blocks/validators-predefined) | +| ExactlyOneOf | [Predefined Validators](/terraform/plugin/framework/migrating/attributes-blocks/validators-predefined) | +| AtLeastOneOf | [Predefined Validators](/terraform/plugin/framework/migrating/attributes-blocks/validators-predefined) | +| RequiredWith | [Predefined Validators](/terraform/plugin/framework/migrating/attributes-blocks/validators-predefined) | +| Deprecated | `DeprecationMessage` field on attribute within block | +| ValidateFunc | [Predefined Validators](/terraform/plugin/framework/migrating/attributes-blocks/validators-predefined) and [Custom Validators](/terraform/plugin/framework/migrating/attributes-blocks/validators-custom) | +| ValidateDiagFunc | [Predefined Validators](/terraform/plugin/framework/migrating/attributes-blocks/validators-predefined) and [Custom Validators](/terraform/plugin/framework/migrating/attributes-blocks/validators-custom) | +| Sensitive | N/A - only supported on attributes | + + +## Nested Block Example + +The following example shows a nested block in Terraform resource configuration. The `subject` nested +block within the `tls_cert_request` resource configures the subject of a certificate request with the `common_name` and +`organization` attributes. + +```hcl +resource "tls_cert_request" "example" { + private_key_pem = file("private_key.pem") + + subject { + common_name = "example.com" + organization = "ACME Examples, Inc" + } +} +``` + + +## SDKv2 + +In SDKv2, blocks are defined by an attribute whose type is `TypeList` or `TypeSet` and whose `Elem` field is set to a +`schema.Resource` that contains a map of the block's attribute names to corresponding `schemaSchema` structs. + +```go +func resourceExample() *schema.Resource { + return &schema.Resource{ + /* ... */ + map[string]*schema.Schema{ + "example" = &schema.Schema{ + Type: schema.TypeList, + Optional: bool, + MaxItems: int, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "nested_example": { + Type: schema.TypeString, + Optional: bool, + /* ... */ +``` + +## Framework + +In the Framework, you implement nested blocks with the `Blocks` field of your provider, resource, or data source's +schema, as returned by the `Schema` method. The `Blocks` field maps the name of each block to a +`schema.Block` definition. + +```go +func (r *ThingResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + /* ... */ + Blocks: map[string]schema.Block{ + "example": schema.ListNestedBlock{ + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "nested_example": schema.StringAttribute{ + Optional: bool + /* ... */ +``` + +## Example + +### SDKv2 + +The following example shows the implementation of the `example_nested_block` nested block with SDKv2. + +```go +map[string]*schema.Schema{ + "example_attribute": &schema.Schema{ + Type: schema.TypeString, + /* ... */ + + "example_nested_block" = &schema.Schema{ + Type: schema.TypeList, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "example_block_attribute_one": { + Type: schema.TypeString, + /* ... */ + }, + "example_block_attribute_two": { + Type: schema.TypeString, + /* ... */ + }, + /* ... */ +``` + +### Framework + +The following example shows how the nested `example_nested_block` block +is defined with the Framework after the migration. + +```go +schema.Schema{ + Attributes: map[string]schema.Attribute{ + "example_attribute": schema.StringAttribute{ + /* ... */ + + Blocks: map[string]schema.Block{ + "example_nested_block": schema.ListNestedBlock{ + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "example_block_attribute_one": schema.StringAttribute{ + /* ... */ + }, + "example_block_attribute_two": schema.StringAttribute{ + /* ... */ + }, + Validators: []validator.List{ + listvalidator.SizeAtMost(1), + }, +``` diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/migrating/attributes-blocks/default-values.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/migrating/attributes-blocks/default-values.mdx new file mode 100644 index 000000000..f358d8d62 --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/migrating/attributes-blocks/default-values.mdx @@ -0,0 +1,105 @@ +--- +page_title: Migrating attribute default values +description: >- + Learn how to migrate attribute default values from SDKv2 by using an + attribute plan modifier in the plugin framework. +--- + +# Migrating attribute default values + +Default values support is only available in the Framework for resources. Handle default values for data source attributes within the [data source `Read` method](/terraform/plugin/framework/data-sources#read-method) and default values for provider attributes within the [provider `Configure` method](/terraform/plugin/framework/providers#configure-method). + +Default values set a value for an attribute when the Terraform configuration does not provide one. In SDKv2 and the +Framework default values are set via the `Default` field on an attribute's schema. +Refer to +[Default](/terraform/plugin/framework/resources/default) +in the Framework documentation for details. + +This page explains how to migrate attribute defaults in SDKv2 to the Framework. + +## SDKv2 + +In SDKv2, default values are defined for a primitive attribute type (i.e., `TypeBool`, `TypeFloat`, `TypeInt`, +`TypeString`) by the `Default` field on the attribute's schema. Alternatively, the `DefaultFunc` function is used to +compute a default value for an attribute. + +The following code shows a basic implementation of a default value for a primitive attribute type in SDKv2. + +```go +func resourceExample() *schema.Resource { + return &schema.Resource{ + /* ... */ + Schema: map[string]*schema.Schema{ + "attribute_example": { + Default: 2048, + /* ... */ + }, + /* ... */ +``` + +## Framework + +In the Framework, you set default values with the `Default` field on your attribute's definition. + +```go +import ( + "context" + + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/booldefault" +) + +func (r *resourceExample) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + /* ... */ + Attributes: map[string]schema.Attribute{ + "attribute_example": schema.BoolAttribute{ + Default: booldefault.StaticBool(true), + /* ... */ +``` + +## Migration Notes + +Remember the following differences between SDKv2 and the Framework when completing the migration. + +- In SDKv2, default values are set with the `Default` or `DefaultFunc` fields on an attribute's `schema.Schema` struct. +In the Framework, you must assign set the `Default` field on an attribute to set a default value. + +## Example + +### SDKv2 + +The following example shows the implementation of the `Default` field for the +`example_attribute` attribute on the `exampleResource` resource with SDKv2. + +```go +func exampleResource() *schema.Resource { + return &schema.Resource{ + Schema: map[string]*schema.Schema{ + "example_attribute": { + Default: 2048, + /* ... */ + }, + /* ... */ +``` + +### Framework + +The following shows the same section of code after the migration. + +This code implements the `PlanModifiers` field for the `example_attribute` attribute with the Framework. + +```go +func (r *exampleResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "example_attribute": schema.Int64Attribute{ + Default: int64default.StaticInt64(2048) + /* ... */ + }, + /* ... */ + }, + }, nil +} +``` diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/migrating/attributes-blocks/fields.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/migrating/attributes-blocks/fields.mdx new file mode 100644 index 000000000..6efe29b7a --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/migrating/attributes-blocks/fields.mdx @@ -0,0 +1,114 @@ +--- +page_title: Migrating attribute fields +description: >- + Learn how to migrate attribute required, optional, computed, and sensitive + fields from SDKv2 to the plugin framework. +--- + +# Migrating attribute fields + +A subset of attribute fields, such as required, optional, computed, or sensitive, define attribute behavior as boolean flags. Refer to +[Schemas - Attributes](/terraform/plugin/framework/handling-data/schemas#required) in the Framework documentation for details. + +The following table describes the mapping between [SDK Schema Fields](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-sdk/helper/schema#Schema) and the Framework. + +| SDK Schema Field | Framework | +|-----------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| Type | [Attribute Types](/terraform/plugin/framework/migrating/attributes-blocks/types) | +| ConfigMode | Schema must be explictly defined using [Attributes](/terraform/plugin/framework/migrating/attributes-blocks/attribute-schema) and [Blocks](/terraform/plugin/framework/migrating/attributes-blocks/blocks) | +| Required | `Required` field on attribute | +| Optional | `Optional` field on attribute | +| Computed | `Computed` field on attribute | +| ForceNew | [RequiresReplace](/terraform/plugin/framework/migrating/attributes-blocks/force-new) on `PlanModifiers` field on attribute or implementation of [ResourceWithModifyPlan](/terraform/plugin/framework/migrating/resources/plan-modification#framework) interface | +| DiffSuppressFunc | [Custom Types](/terraform/plugin/framework/handling-data/types/custom), [PlanModifiers](/terraform/plugin/framework/migrating/resources/plan-modification#framework) field on attribute, or implementation of [ResourceWithModifyPlan](/terraform/plugin/framework/migrating/resources/plan-modification#framework) interface | +| DiffSuppressOnRefresh | [Custom Types](/terraform/plugin/framework/handling-data/types/custom) semantic equality logic or manual logic in [Read](/terraform/plugin/framework/migrating/resources/crud) method on resource | +| Default | `Default` field on attribute using one of the predefined [Defaults](/terraform/plugin/framework/resources/default#common-use-case-attribute-defaults) or implementing one of the [`schema` package](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/defaults) interfaces | +| DefaultFunc | `Default` field on attribute using one of the predefined [Defaults](/terraform/plugin/framework/resources/default#common-use-case-attribute-defaults) or implementing one of the [`schema` package](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/defaults) interfaces | +| Description | `Description` field on attribute | +| InputDefault | N/A - no longer valid | +| StateFunc | Requires implementation of bespoke logic before storing state, for instance in resource [Create method](/terraform/plugin/framework/migrating/resources/crud#framework-1) | +| Elem | `ElementType` on [ListAttribute](/terraform/plugin/framework/migrating/attributes-blocks/types), [MapAttribute](/terraform/plugin/framework/migrating/attributes-blocks/types) or [SetAttribute](/terraform/plugin/framework/migrating/attributes-blocks/types). Refer to [Blocks](/terraform/plugin/framework/migrating/attributes-blocks/blocks) if `schema.Resource` is present in `Elem`. | +| MaxItems | Use [listValidator.SizeAtMost](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework-validators/listvalidator#SizeAtMost), [mapvalidator.SizeAtMost](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework-validators/mapvalidator#SizeAtMost) or [setvalidator.SizeAtMost](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework-validators/setvalidator#SizeAtMost) on `Validators` field on list, map or set attribute | +| MinItems | Use [listValidator.SizeAtLeast](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework-validators/listvalidator#SizeAtLeast), [mapvalidator.SizeAtLeast](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework-validators/mapvalidator#SizeAtLeast) or [setvalidator.SizeAtLeast](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework-validators/setvalidator#SizeAtLeast) on `Validators` field on list, map or set attribute | +| Set | N/A - no implementation required | | +| ComputedWhen | N/A - no longer valid | +| ConflictsWith | [Predefined Validators](/terraform/plugin/framework/migrating/attributes-blocks/validators-predefined) | +| ExactlyOneOf | [Predefined Validators](/terraform/plugin/framework/migrating/attributes-blocks/validators-predefined) | +| AtLeastOneOf | [Predefined Validators](/terraform/plugin/framework/migrating/attributes-blocks/validators-predefined) | +| RequiredWith | [Predefined Validators](/terraform/plugin/framework/migrating/attributes-blocks/validators-predefined) | +| Deprecated | `DeprecationMessage` field on attribute | +| ValidateFunc | [Predefined Validators](/terraform/plugin/framework/migrating/attributes-blocks/validators-predefined), [Custom Validators](/terraform/plugin/framework/migrating/attributes-blocks/validators-custom), or [Custom Types](/terraform/plugin/framework/handling-data/types/custom) validation logic | +| ValidateDiagFunc | [Predefined Validators](/terraform/plugin/framework/migrating/attributes-blocks/validators-predefined), [Custom Validators](/terraform/plugin/framework/migrating/attributes-blocks/validators-custom), or [Custom Types](/terraform/plugin/framework/handling-data/types/custom) validation logic | +| Sensitive | `Sensitive` field on attribute | + + +This page explains how to migrate the required, optional, computed, and sensitive attribute fields from SDKv2 to the +Framework. + +## SDKv2 + +In SDKv2, `Required`, `Optional`, `Computed`, and `Sensitive` are boolean fields on the attribute's schema. + +```go +func resourceExample() *schema.Resource { + return &schema.Resource{ + /* ... */ + Schema: map[string]*schema.Schema{ + "attribute_example": { + Required: bool + Optional: bool + Computed: bool + Sensitive: bool + /* ... */ +``` + +## Framework + +In the Framework, you set the same fields on the `schema.Attribute` implementation, with the same behavior. + +```go +func (r *ThingResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + /* ... */ + Attributes: map[string]schema.Attribute{ + "attribute_example": schema.XXXAttribute{ + Required: bool + Optional: bool + Computed: bool + Sensitive: bool + /* ... */ +``` + +## Example + +### SDKv2 + +The following example shows how the `example_attribute` attribute on the `exampleDataSource` data source is set to +be required with SDKv2. + +```go +func exampleDataSource() *schema.Resource { + return &schema.Resource{ + Schema: map[string]*schema.Schema{ + "example_attribute": { + Required: true, + /* ... */ + }, + /* ... */ +``` + +### Framework + +The following example shows how the `example_attribute` attribute on the `exampleDataSource` data source is set +to be required with the Framework. + +```go +func (d *exampleDataSource) Schema(ctx context.Context, req datasource.SchemaRequest, resp *datasource.SchemaResponse) { + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "example_attribute": schema.StringAttribute{ + Required: true, + /* ... */ + }, + /* ... */ +``` diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/migrating/attributes-blocks/force-new.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/migrating/attributes-blocks/force-new.mdx new file mode 100644 index 000000000..1a119eff6 --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/migrating/attributes-blocks/force-new.mdx @@ -0,0 +1,104 @@ +--- +page_title: Migrating attribute ForceNew triggers +description: >- + Learn how to migrate attribute ForceNew triggers in SDKv2 to attribute plan + modifiers in the framework. +--- + +# Migrating attribute ForceNew triggers + +In Terraform, sometimes a resource must be replaced when the value of an attribute changes. In SDKv2, this is +accomplished via the `ForceNew` field. In the Framework, you implement the same behavior via a `RequiresReplace` plan +modifier. Refer to +[Plan Modification - Attribute Plan Modification](/terraform/plugin/framework/resources/plan-modification#attribute-plan-modification) +in the Framework documentation for details. + +This page explains how to migrate this behavior from SDKv2 to the Framework. + +## SDKv2 + +In SDKv2, setting the `ForceNew` field on an attribute's `schema.Schema` triggers a replace (i.e., a destroy-create +cycle) whenever the attribute's value is changed. + +```go +func resourceExample() *schema.Resource { + return &schema.Resource{ + /* ... */ + Schema: map[string]*schema.Schema{ + "attribute_example": { + ForceNew: true + /* ... */ +``` + +## Framework + +In the Framework, you implement the same behavior by using the `resource.RequiresReplace` plan modifier on your +attribute's `schema.Attribute` implementation. + +```go +func (r *ThingResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + /* ... */ + Attributes: map[string]schema.Attribute{ + "attribute_example": schema.StringAttribute{ + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + /* ... */ +``` + +## Migration Notes + +Remember the following differences between SDKv2 and the Framework when completing the migration. + +- In both SDKv2 and Framework, `ForceNew` and `RequiresReplace`, respectively, only trigger a replace if the attribute +is not computed. In the Framework, if an attribute which is computed requires that the resource be replaced when it is +changed, implement a plan modifier that triggers the replacement. Refer to +[RequiresReplacePlanModifier](https://github.com/hashicorp/terraform-provider-random/blob/v3.4.1/internal/planmodifiers/attribute.go#L63) +for an example, but bear in mind that each implementation requires different logic and you may need to detect whether +the plan has already been modified. + +## Example + +### SDKv2 + +The following example shows the implementation of the `ForceNew` field of the +`exampleResource` resource's `example_attribute` attribute with SDKv2. + +```go +func exampleResource() *schema.Resource { + return &schema.Resource{ + Schema: map[string]*schema.Schema{ + "example_attribute": { + ForceNew: true, + Type: schema.TypeMap, + /* ... */ + }, + /* ... */ + }, + /* ... */ + } +} +``` + +### Framework + +The following shows the same section of provider code after the migration. + +This code forces the replacement of a `exampleResource` resource when the value of the `example_attribute` attribute is changed. +The example does this using the `PlanModifiers` field within the `exampleResource` attribute's schema. + +```go +func (r *exampleResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "example_attribute": schema.MapAttribute{ + PlanModifiers: []planmodifier.Map{ + mapplanmodifier.RequiresReplace(), + }, + /* ... */ + }, + /* ... */ + }, + } +} +``` diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/migrating/attributes-blocks/types.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/migrating/attributes-blocks/types.mdx new file mode 100644 index 000000000..86d633fa7 --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/migrating/attributes-blocks/types.mdx @@ -0,0 +1,176 @@ +--- +page_title: Migrating atrribute types +description: >- + Learn how to migrate attribute type from SDKv2 to the plugin Framework. +--- + +# Migrating attribute types + +An attribute either contains a primitive type, such as an integer or a string, or contains other attributes. Attributes +that contain other attributes are referred to as nested attributes. Refer to +[Schemas - Attributes](/terraform/plugin/framework/schemas#type) in the Framework documentation for details. + +This page explains how to migrate a primitive attribute from SDKv2 to the plugin Framework. For an example of +migrating a nested block to a nested attribute, refer to [Providers](/terraform/plugin/framework/migrating/providers#example-1) in +this guide. + +## SDKv2 + +In SDKv2, attribute types are defined by the `Type` field on the attribute's `schema.Schema` struct. + +```go +func resourceExample() *schema.Resource { + return &schema.Resource{ + /* ... */ + Schema: map[string]*schema.Schema{ + "bool_example": { + Type: schema.TypeBool, + /* ... */ + }, + "float64_example": { + Type: schema.TypeFloat, + /* ... */ + }, + "int64_example": { + Type: schema.TypeInt, + /* ... */ + }, + "list_example": { + Type: schema.TypeList, + Elem: &schema.Schema{ + Type: schema.TypeBool, + }, + /* ... */ + }, + "map_example": { + Type: schema.TypeMap, + Elem: &schema.Schema{ + Type: schema.TypeFloat, + }, + /* ... */ + }, + "set_example": { + Type: schema.TypeSet, + Elem: &schema.Schema{ + Type: schema.TypeInt, + }, + /* ... */ + }, + "string_example": { + Type: schema.TypeString, + /* ... */ + }, + /* ... */ +``` +## Framework + +In the Framework, you set your attribute's type with the attribute's `schema.Attribute` implementation. + +```go +func (r *ThingResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schea.Schema{ + /* ... */ + Attributes: map[string]schema.Attribute{ + "bool_example": schema.BoolAttribute{ + /* ... */ + }, + "float64_example": schema.Float64Attribute{ + /* ... */ + }, + "int64_example": schema.Int64Attribute{ + /* ... */ + }, + "list_example": schema.ListAttribute{ + ElementType: types.BoolType, + /* ... */ + }, + "map_example": schema.MapAttribute{ + ElementType: types.Float64Type, + /* ... */ + }, + "set_example": schema.SetAttribute{ + ElementType: types.Int64Type, + /* ... */ + }, + "string_example": schema.StringAttribute{ + /* ... */ + }, + /* ... */ +``` + +## Migration Notes + +Remember the following differences between SDKv2 and the Framework when completing the migration. + +- In the Framework, the `schema.Attribute` implementation determines the required details. + +## Examples + +### SDKv2 + +The following example shows the implementation of the type field of the `example_string_attribute` attribute +for the `exampleDataSource` data source with SDKv2. + +```go +func exampleDataSource() *schema.Resource { + return &schema.Resource{ + Schema: map[string]*schema.Schema{ + "example_string_attribute": { + Type: schema.TypeString, + /* ... */ + }, + /* ... */ +``` + +### Framework + +The following example shows how the type of the `example_string_attribute` attribute for the `exampleDataSource` data +source is defined with the Framework after the migration. + +```go +func (d *exampleDataSource) Schema(ctx context.Context, req datasource.SchemaRequest, resp *datasource.SchemaResponse) { + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "example_string_attribute": schema.StringAttribute{ + /* ... */ + }, + /* ... */ +``` + +### SDKv2 + +The following example shows the implementation of the type field of the `example_list_attribute` +attribute with SDKv2. + +```go +func exampleResource() *schema.Resource { + return &schema.Resource{ + Schema: map[string]*schema.Schema{ + "example_list_attribute": { + Type: schema.TypeList, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + /* ... */ + }, + /* ... */ +``` + +### Framework + +The following example shows how the type of the `example_list_attribute` attribute for the `exampleResource` resource +is defined with the Framework after the migration. + +```go +func (r *exampleResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "example_list_attribute": schema.ListAttribute{ + ElementType: types.StringType, + /* ... */ + }, + /* ... */ +``` + +Refer to [Terraform Concepts - Attributes](/terraform/plugin/framework/handling-data/terraform-concepts#attributes) +for further examples of different types of schema attributes in the Framework. diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/migrating/attributes-blocks/validators-custom.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/migrating/attributes-blocks/validators-custom.mdx new file mode 100644 index 000000000..8c33f4c01 --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/migrating/attributes-blocks/validators-custom.mdx @@ -0,0 +1,148 @@ +--- +page_title: Migrating attribute custom validators +description: >- + Learn how to migrate custom attribute validation functions from SDKv2 to + attribute validators in the Framework. Providers use custom validators to + check attribute values for required syntax, types, and acceptable values. +--- + +# Migrating attribute custom validators + +You can write custom validations that give users feedback about required syntax, types, and acceptable values in your +provider. The Framework has a collection of +[predefined validators](https://github.com/hashicorp/terraform-plugin-framework-validators). Refer to +[Predefined Validators](/terraform/plugin/framework/migrating/attributes-blocks/validators-predefined) to learn how to use them. + +This page explains how to migrate attribute validation functions from SDKv2 to attribute validators in the Framework. + +## SDKv2 + +In SDKv2, arbitrary validation logic can be applied to individual attributes by using `ValidateFunc` and/or +`ValidateDiagFunc`. + +The following example shows the implementation of a validation that ensures that an integer attribute has a value +greater than one. + +```go +func resourceExample() *schema.Resource { + return &schema.Resource{ + /* ... */ + Schema: map[string]*schema.Schema{ + "attribute_example": { + ValidateDiagFunc: validation.ToDiagFunc(validation.IntAtLeast(1)), + /* ... */ +``` + +## Framework + +In the Framework, you implement either type of validation by setting the `Validators` field on the `schema.Attribute` +implementation. + +The following example shows how to implement a validation that ensures that an integer attribute has a value +greater than one. + +```go +func (r *ThingResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + /* ... */ + Attributes: map[string]schema.Attribute{ + "attribute_example": schema.Int64Attribute{ + Validators: []validator.Int64{ + int64validator.AtLeast(1), + /* ... */ +``` + +## Migration Notes + +Remember the following details when migrating from SDKv2 to the Framework. + +- In SDKv2, `ValidateDiagFunc` is a field on `schema.Schema` that you can use to define custom validation functions. In +the Framework, `Validators` is a field on each `schema.Attribute` implementation that can be used for custom validations. +- Use [predefined validators](/terraform/plugin/framework/migrating/attributes-blocks/validators-predefined) when there is a validator that meets +your requirements. + +## Example + +### SDKv2 + +The following example shows the implementation of the `ValidateDiagFunc` field for +the `exampleResource`'s `example_attribute` attribute to validate that it's value is at least 1 (greater than zero). + +```go +func exampleResource() *schema.Resource { + return &schema.Resource{ + Schema: map[string]*schema.Schema{ + "example_attribute": { + ValidateDiagFunc: validation.ToDiagFunc(validation.IntAtLeast(1)), + }, + }, + } +} +``` + +### Framework + +The following shows the same section of provider code after the migration. + +This code validates that the `exampleResource`'s `example_attribute` attribute is greater than zero by using a custom `AtLeast` +validator. + +```go +func (r *exampleResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "example_attribute": schema.Int64Attribute{ + Required: true, + Validators: []validator.Int64{ + int64validator.AtLeast(1), + }, + }, + }, + } +} +``` + +This example code illustrates how you can implement your own validators. + +```go +var _ validator.Int64 = atLeastValidator{} + +// atLeastValidator validates that an integer Attribute's value is at least a certain value. +type atLeastValidator struct { + min int64 +} + +// Description describes the validation in plain text formatting. +func (v atLeastValidator) Description(_ context.Context) string { + return fmt.Sprintf("value must be at least %d", v.min) +} + +// MarkdownDescription describes the validation in Markdown formatting. +func (v atLeastValidator) MarkdownDescription(ctx context.Context) string { + return v.Description(ctx) +} + +// Validate performs the validation. +func (v atLeastValidator) ValidateInt64(ctx context.Context, request validator.Int64Request, response *validator.Int64Response) { + if req.ConfigValue.Int64Value() < v.min { + response.Diagnostics.Append(validatordiag.InvalidAttributeValueDiagnostic( + request.Path, + v.Description(ctx), + fmt.Sprintf("%d", req.ConfigValue.Int64Value()), + )) + } +} + +// AtLeast returns an AttributeValidator which ensures that any configured +// attribute value: +// +// - Is a number, which can be represented by a 64-bit integer. +// - Is exclusively greater than the given minimum. +// +// Null (unconfigured) and unknown (known after apply) values are skipped. +func AtLeast(min int64) validator.Int64 { + return atLeastValidator{ + min: min, + } +} +``` diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/migrating/attributes-blocks/validators-predefined.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/migrating/attributes-blocks/validators-predefined.mdx new file mode 100644 index 000000000..33c32dfd4 --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/migrating/attributes-blocks/validators-predefined.mdx @@ -0,0 +1,193 @@ +--- +page_title: Migrating attribute predefined validators +description: >- + Learn how to migrate the predefined ConflictsWith, ExactlyOneOf, AtLeastOneOf + and RequiredWith validators from SDKv2 to the framework. Providers use + predefined validators to check attribute values for required syntax, types, + and acceptable values. +--- + +# Migrating predefined attribute validators + +Attribute validators ensure that attributes do or do not contain specific values. You can use predefined validators for +many use cases, or implement custom validators. Refer to [Schemas - Validators](/terraform/plugin/framework/handling-data/schemas#validators) in +the Framework documentation for details. Refer to the +[Attributes - Custom Validators](/terraform/plugin/framework/migrating/attributes-blocks/validators-custom) page in this guide to learn how to +implement custom validators. + +The following table describes the mapping between predefined validators in SDKv2 and the Framework. You should use an +attribute validator for [per-attribute validation](/terraform/plugin/framework/validation#attribute-validation), or a +[data source validator](/terraform/plugin/framework/data-sources/validate-configuration), +[provider validator](/terraform/plugin/framework/providers/validate-configuration) or +[resource validator](/terraform/plugin/framework/resources/validate-configuration) for declaring validation at the level of the +data source, provider or resource, respectively. + +| SDK Attribute Field | Framework Attribute Validator | Framework Data Source Validator | Framework Provider Validator | Framework Resource Validator | +|---------------------|---------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------| +| AtLeastOneOf | {TYPE}validator.AtLeastOneOf() | [datasourcevalidator.AtLeastOneOf()](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework-validators/datasourcevalidator#AtLeastOneOf) | [providervalidator.AtLeastOneOf()](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework-validators/providervalidator#AtLeastOneOf) | [resourcevalidator.AtLeastOneOf()](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework-validators/resourcevalidator#AtLeastOneOf) | +| ConflictsWith | {TYPE}validator.ConflictsWith() | [datasourcevalidator.Conflicting()](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework-validators/datasourcevalidator#Conflicting) | [providervalidator.Conflicting()](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework-validators/providervalidator#Conflicting) | [resourcevalidator.Conflicting()](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework-validators/resourcevalidator#Conflicting) | +| ExactlyOneOf | {TYPE}validator.ExactlyOneOf() | [datasourcevalidator.ExactlyOneOf()](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework-validators/datasourcevalidator#ExactlyOneOf) | [providervalidator.ExactlyOneOf()](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework-validators/providervalidator#ExactlyOneOf) | [resourcevalidator.ExactlyOneOf()](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework-validators/resourcevalidator#ExactlyOneOf) | +| RequiredWith | {TYPE}validator.AlsoRequires() | [datasourcevalidator.RequiredTogether()](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework-validators/datasourcevalidator#RequiredTogether) | [providervalidator.RequiredTogether()](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework-validators/providervalidator#RequiredTogether) | [resourcevalidator.RequiredTogether()](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework-validators/resourcevalidator#RequiredTogether) | + +This page explains how to migrate a predefined validator from SDKv2 to the Framework. + +## SDKv2 + +In SDKv2, the `ConflictsWith`, `ExactlyOneOf`, `AtLeastOneOf`, and `RequiredWith` fields on an attribute's +`schema.Schema` struct perform predefined validations on the list of attributes set for these fields. + +```go +func resourceExample() *schema.Resource { + return &schema.Resource{ + /* ... */ + Schema: map[string]*schema.Schema{ + "attribute_example": { + ConflictsWith: []string{ /* ... */ }, + ExactlyOneOf: []string{ /* ... */ }, + AtLeastOneOf: []string{ /* ... */ }, + RequiredWith: []string{ /* ... */ }, + /* ... */ +``` +## Framework + +In the Framework, you implement either type of validation by setting the `Validators` field on the `schema.Attribute` +implementation. Validators that perform the same checks as the +predefined validators in SDKv2 are +[available for the Framework](https://github.com/hashicorp/terraform-plugin-framework-validators). If the predefined +validators do not meet your needs, you must define +[custom validators](/terraform/plugin/framework/migrating/attributes-blocks/validators-custom). + +```go +func (r *resourceExample) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + /* ... */ + Attributes: map[string]schema.Attribute{ + "attribute_example": schema.StringAttribute{ + Validators: []validator.String{ + stringvalidator.ConflictsWith( /* ... */ ), + /* ... */ +``` + +Configuration validators can also be defined for +[providers](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework-validators/providervalidator), +[resources](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework-validators/resourcevalidator) and +[data sources](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework-validators/datasourcevalidator) by +implementing `ProviderWithConfigValidators`, `ResourceWithConfigValidators`, and `DataSourceWithConfigValidators` +interfaces, respectively. + +```go +func (r *resourceExample) ConfigValidators(ctx context.Context) []resource.ConfigValidator { + return []resource.ConfigValidator{ + resourcevalidator.Conflicting( + /* ... */ + ), + /* ... */ +``` + +## Migration Notes + +Remember the following details when migrating from SDKv2 to the Framework. + +- In SDKv2, `ValidateDiagFunc` is a field on `schema.Schema` that you can use to define validation functions. In SDKv2, +there are also built-in validations. For example, `ConflictsWith` is a field on the `schema.Schema` struct in SDKv2. In +the Framework, `Validators` is a field on each `schema.Attribute` implementation. +- Validators replicating the behavior of `ConflictsWith`, `ExactlyOneOf`, `AtLeastOneOf`, and `RequiredWith` in SDKv2 are +available for the Framework in each of the type-specific packages of +[terraform-plugin-framework-validators](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework-validators). +- Define [custom validators](/terraform/plugin/framework/migrating/attributes-blocks/validators-custom) when the predefined validators do not meet +your requirements. + +## Example + +### SDKv2 + +The following example shows the implementation of the `ConflictsWith` field on the +provider's `example_block` block's `example_attribute_one` attribute. +This validator checks that the provider does not use the `example_attribute_one` attribute +when the `example_attribute_four` is being used. The example also uses the `RequiredWith` field to ensure that the +`example_attribute_two` attribute is configured when `example_attribute_one` is, and that the +`example_attribute_three` attribute is configured when `example_attribute_two` is. + +```go +func New() (*schema.Provider, error) { + return &schema.Provider{ + Schema: map[string]*schema.Schema{ + "example_block": { + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "example_attribute_one": { + ConflictsWith: []string{"example_block.0.example_attribute_four"}, + /* ... */ + }, + "example_attribute_two": { + RequiredWith: []string{"example_block.0.example_attribute_one"}, + /* ... */ + }, + "example_attribute_three": { + RequiredWith: []string{"example_block.0.example_attribute_two"}, + /* ... */ + }, + "example_attribute_four": { + ConflictsWith: []string{ + "example_block.0.example_attribute_one", + "example_block.0.example_attribute_two", + "example_block.0.example_attribute_three", + }, + /* ... */ + }, + }, + }, + }, + }, + }, nil +} +``` + +### Framework + +The following shows the same section of provider code after the migration. + +This code implements the `ConflictsWith` and `AlsoRequires` validators with the Framework. The validators are configured +via the `Validators` field of the provider's `example_block` block's attribute schema. + +```go +func (p *TlsProvider) Schema(_ context.Context, _ provider.SchemaRequest, resp *provider.SchemaResponse) { + resp.Schema = schema.Schema{ + Blocks: map[string]schema.Block{ + "example_block": schema.ListNestedBlock{ + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "example_attribute_one": schema.StringAttribute{ + Validators: []validator.String{ + stringvalidator.ConflictsWith(path.MatchRelative().AtParent().AtName("example_attribute_four")), + }, + /* ... */ + }, + "example_attribute_two": schema.StringAttribute{ + Validators: []validator.String{ + stringvalidator.AlsoRequires(path.MatchRelative().AtParent().AtName("example_attribute_one")), + }, + /* ... */ + }, + "example_attribute_three": schema.StringAttribute{ + Validators: []validator.String{ + stringvalidator.AlsoRequires(path.MatchRelative().AtParent().AtName("example_attribute_two")), + }, + /* ... */ + }, + "example_attribute_four": schema.BoolAttribute{ + Validators: []validator.Bool{ + boolvalidator.ConflictsWith( + path.MatchRelative().AtParent().AtName("example_attribute_one"), + path.MatchRelative().AtParent().AtName("example_attribute_two"), + path.MatchRelative().AtParent().AtName("example_attribute_three"), + ), + }, + /* ... */ + }, + }, + }, + }, + } +} +``` diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/migrating/benefits.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/migrating/benefits.mdx new file mode 100644 index 000000000..847b2c73f --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/migrating/benefits.mdx @@ -0,0 +1,523 @@ +--- +page_title: Benefits of migration +description: >- + The plugin framework is an updated SDK for Terraform providers that includes + improved data access, more consistent schema models, and other improvements + over the previous SDKv2. +--- + +# Benefits of migrating to the plugin framework + +We recommend using the plugin framework to develop your provider because it offers significant benefits in comparison to SDKv2. We designed the framework with feedback from thousands of existing providers, so the framework significantly improves upon the functionality available in SDKv2. + +This page is a continuation of the [Framework Benefits](/terraform/plugin/framework-benefits) page, which describes the higher level coding improvements over SDKv2. The following features are only available in the framework. + +## Expanded Access to Configuration, Plan, and State Data + +Providers receive up to three sources of schema-based data during Terraform operation requests: configuration, plan, and prior state. The SDKv2 combines this data into a single [`schema.ResourceData` type](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema#ResourceData), which you implement differently depending on the operation. Certain `ResourceData` methods are only valid during certain operations and trying to get data from an explicit source is problematic in many cases. + +In the following SDKv2 example, the code comments highlight issues with the single data type: + +```go +func ThingResourceCreate(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics { + d.Get("...") // plan unless unknown; no explicit access to configuration + d.GetChange("...") // extraneous old value, use d.Get() instead + d.HasChange("...") // always true, no prior state + d.Set("...") // saved into new state +} + +func ThingResourceRead(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics { + d.Get("...") // prior state + d.GetChange("...") // no changes as only prior state is available + d.HasChange("...") // always false + d.Set("...") // saved into new state +} + +func ThingResourceUpdate(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics { + d.Get("...") // plan unless unknown; no explicit access to configuration or prior state + d.GetChange("...") // prior state and plan unless unknown + d.HasChange("...") // comparison of prior state and plan + d.Set("...") // saved into new state +} + +func ThingResourceDelete(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics { + d.Get("...") // prior state + d.GetChange("...") // no changes as only prior state is available + d.HasChange("...") // always false + d.Set("...") // extraneous, resource destroy leaves no state +} +``` + +The framework alleviates these issues by exposing configuration, plan, and state data as separate attributes on request and response types that only expose the data available to the given operation. + +In the following framework example, the code comments show the available data that matches each operation. + +```go +func (r ThingResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + req.Config // configuration data + req.Plan // plan data + // No req.State as it is always null + // No resp.Config as configuration cannot be set by provider during creation + // No resp.Plan as plan cannot be set by provider during creation + resp.State // new state data to save +} + +func (r ThingResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.CreateResponse) { + // No req.Config as configuration cannot be read by provider during read + // No req.Plan as there is no plan during read + req.State // prior state data + // No resp.Config as configuration cannot be set by provider during read + // No resp.Plan as there is no plan during read + resp.State // new state data to save +} + +func (r ThingResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + req.Config // configuration data + req.Plan // plan data + req.State // prior state data + // No resp.Config as configuration cannot be set by provider during update + // No resp.Plan as plan cannot be set by provider during update + resp.State // new state data to save +} + +func (r ThingResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + // No req.Config as configuration cannot be read by provider during delete + // No req.Plan as it is always null + req.State // prior state data + // No resp.Config as configuration cannot be set by provider during delete + // No resp.Plan as it cannot be adjusted + resp.State // only available to explicitly remove on error +} +``` + +## Schema Data Models + +In the SDKv2, you must fetch configuration, plan, and state data separately for each attribute or type. In the framework, you can fetch all of the configuration, plan, and state data at once. This approach lets you declare a single data model for a schema, which guarantees correctness and consistency across operations. + +In the following SDKv2 example, you must fetch the data for each attribute unless you save the schema as a variable and reference it in the operation logic. + +```go +attribute1 := d.Get("attribute1") // any type +attribute2 := d.Get("attribute2") // any type +attribute3 := d.Get("attribute3") // any type +``` + +Some SDKv2 providers opted to type assert during these calls, which had the potential to cause Go runtime panics if they did not also check the assertion boolean. + +```go +// Example showing panic-safe SDK data handling +attribute1, ok := d.Get("attribute1").(bool) // assuming schema.TypeBool + +if !ok { + // provider-defined error handling +} + +attribute2, ok := d.Get("attribute2").(int) // assuming schema.TypeInt + +if !ok { + // provider-defined error handling +} + +attribute3, ok := d.Get("attribute3").(string) // assuming schema.TypeString + +if !ok { + // provider-defined error handling +} +``` + +The [Fully Exposed Value States section](#fully-exposed-value-states) goes into other issues and quirks with attempting to handle SDKv2 data. + +Data with the framework can be modeled as a custom type and the operation of getting or setting the data will return framework-defined errors, if necessary. + +In the following framework example, a provider-defined type receives all schema-based data. + +```go +// Example schema data model type +type ThingResourceModel struct { + Attribute1 types.Bool `tfsdk:"attribute1"` // assuming types.BoolType attribute + Attribute2 types.Int64 `tfsdk:"attribute2"` // assuming types.Int64Type attribute + Attribute3 types.String `tfsdk:"attribute3"` // assuming types.StringType attribute +} + +// In resource logic +var data ThingResourceModel + +diags := req.Plan.Get(ctx, &data) // framework-defined errors + +resp.Diagnostics.Append(diags...) + +if resp.Diagnostics.HasError() { + return +} +``` + +With `Required` attributes, you can replace the framework types in the schema data model with standard Go types (e.g. `bool`) to further simplify data handling, if desired. + +## Fully Exposed Value States + +Terraform supports three states for any value: null (missing), unknown ("known after apply"), and known. The SDKv2 does not expose or fully support null and unknown value states to providers. Instead, the `Get()` method on these value states returns Go type zero-values such as `""` for `schema.TypeString`, `0` for `schema.TypeInt`, and `false` for `schema.TypeBool`. Other methods, such as `GetOk()` and `GetOkExists()`, have slightly different functionality for each type and operation, especially for collection types. + +In the following SDKv2 example, the code comments explain issues with the single data type. + +```go +// Assuming a schema of: +// +// "string_attribute": &schema.Schema{ +// Computed: true, +// Optional: true, +// Type: schema.TypeString, +// } +// +// and a configuration that does not set the value (null state). +// +// resource “examplecloud_thing” “example” { +// # no string_attribute = “...” +// } + +func ThingResourceUpdate(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics { + d.Get("string_attribute") // "" + d.GetOk("string_attribute") // may return true depending on prior state + d.GetOkExists("string_attribute") // may return true depending on prior state +} +``` + +The framework type system fully exposes null, unknown, and known value states. You can reliably query each value with the `IsNull()` or `IsUnknown()` methods. + +In the following framework example, you can determine the correct value state. + +```go +// Assuming a schema of: +// +// "string_attribute": schema.StringAttribute{ +// Computed: true, +// Optional: true, +// } +// +// and a configuration that does not set the value (null state). +// +// resource “examplecloud_thing” “example” { +// # no string_attribute = “...” +// } + +func (r ThingResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + var config, plan types.String + + req.Config.GetAttribute(ctx, path.Root("string_attribute"), &config) + req.Plan.GetAttribute(ctx, path.Root("string_attribute"), &plan) + + config.IsNull() // true + config.IsUnknown() // false + config.ValueString() // "" + plan.IsNull() // true + plan.IsUnknown() // false + plan.ValueString() // "" +} +``` + +## Unrestricted Type System + +The framework type system exposes the majority of Terraform types and values. It is also extensible because it lets you define new types that are specific to your provider. + +### Custom Attribute Types + +You can implement custom types for your provider that expose data with convenient Go types, methods, and built-in validation. + +The following framework example uses a custom `timetypes.RFC3339Type` attribute type instead of `types.StringType`. The `timetypes.RFC3339Type` attribute type is associated with a `timetypes.RFC3339` value type. The attribute type automatically validates whether the string can be parsed as an RFC3339 timestamp and the value type exposes a `Time() time.Time` method for easier usage over a regular string value. + +```go +"rfc3339": schema.StringAttribute{ + CustomType: timetypes.RFC3339Type{}, + Required: true, +}, +``` + +The following framework example uses the custom `timetypes.RFC3339` value type to expose the `time.Time` value. + +```go +// Example schema data model +type ThingResourceModel struct{ + RFC3339 timetypes.RFC3339 `tfsdk:"rfc3339"` +} + +// In resource logic, omitting diagnostics handling for brevity +var data ThingResourceModel + +req.Plan.Get(ctx, &data) + +data.RFC3339.Time() // time.Time +``` + +### Complex Map Types + +The framework type system does not have any restrictions for using complex types as the value for a map type. SDKv2 restricted map values to string, number, and boolean types. + +This framework example declares a map type with a list of string values. + +```go +schema.MapAttribute{ + // ... other fields ... + ElementType: types.ListType{ + ElemType: types.StringType, + }, +} +``` + +If you need to declare additional schema behaviors for the map values, you can use map nesting mode in [Protocol Version 6 Nested Attributes](#protocol-version-6-nested-attributes), which is also only available in the framework. + +### Object Type + +The framework type system supports the Terraform object type, which you can use to declare attribute name to value mappings without additional schema behaviors. These differ from maps by requiring specific names and their values to always exist. SDKv2 did not directly expose this type. + +The following framework example declares an object type with two attributes. + +```go +schema.ObjectAttribute{ + // ... other fields ... + AttributeTypes: map[string]attr.Type{ + "bool_attribute": types.BoolType, + "string_attribute": types.StringType, + }, +} +``` + +If you need to declare additional schema behaviors for the object values, you can use the single nesting mode in [Protocol Version 6 Nested Attributes](#protocol-version-6-nested-attributes), which is also only available in the framework. + +### Protocol Version 6 Nested Attributes + +Protocol [version 6](/terraform/plugin/terraform-plugin-protocol) is the latest version of the protocol between Terraform and providers. Only the framework supports version 6. + +Version 6 lets you declare schemas with nested attributes in addition to blocks. Nested attributes support schema behaviors and practitioners using your provider can configure them with expressions instead of with dynamic blocks. Nested attributes support includes four nesting modes: + +- List: Ordered collection of nested attributes +- Map: Collection of string keys to nested attributes. +- Set: Unordered collection of nested attributes. +- Single: Single object of nested attributes that is useful for replacing list blocks with a single element. + +In the following configuration example, a schema uses a list block that is difficult to dynamically configure. + +```hcl +locals { + calls = toset([ + {call_me: “example1”, maybe: true}, + {call_me: “example2”, maybe: false}, + ]) +} + +resource “examplecloud_thing” “example” { + dynamic “list_block” { + for_each = local.calls + + content { + call_me = list_block.value.call_me + maybe = list_block.value.maybe + } + } +} +``` + +In the following configuration example, a schema uses list nested attributes to simplify the configuration. + +```hcl +locals { + calls = [ + {call_me: “example1”, maybe: true}, + {call_me: “example2”, maybe: false}, + ] +} + +resource “examplecloud_thing” “example” { + list_nested_attributes = local.calls # or a for expression, etc. +} +``` + +The following framework example shows the schema definition for the list nested attributes. + +```go +schema.ListNestedAttribute{ + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + “call_me”: schema.StringAttribute{ + Required: true, + }, + “maybe”: schema.BoolAttribute{ + Optional: true, + Sensitive: true, + }, + }, + }, + // ... other fields ... +} +``` + +## Unrestricted Validation Capabilities + +The framework exposes many more configuration validation integration points than SDKv2. It is also extensible with provider-defined types that implement validation in the type itself. + +### Collection Type Validation + +Attribute validation in the framework is not restricted by type. + +This framework example validates all list values against a set of acceptable values. + +```go +schema.ListAttribute{ + // ... other fields ... + ElementType: types.StringType, + Validators: []validator.List{ + listvalidator.StringValuesAre( + stringvalidator.OneOf("one", "two", "three"), + ), + }, +} +``` + +This framework example checks whether map keys are between 3 and 50 characters in length using validators available in [`terraform-plugin-framework-validators`](https://github.com/hashicorp/terraform-plugin-framework-validators). + +```go +schema.MapAttribute{ + // ... other fields ... + ElementType: types.StringType, + Validators: []validator.Map{ + mapvalidator.KeysAre( + stringvalidator.LengthBetween(3, 50), + ), + }, +} +``` + +### Type-Based Validation + +Attribute validation supports attribute types that declare their own validation, in addition to any validators on the attribute itself. + +In the following framework example, the custom type ensures that the string is a valid RFC3339 string, and the attribute can declare additional validation. + +```go +schema.StringAttribute{ + // ... other fields ... + CustomType: timetypes.RFC3339Type{}, // automatically validates string is RFC3339 + Validators: []validator.String{ + // additional validation, if desired + }, +} +``` + +### Declarative Schema Validation + +The framework supports schema-level validation with reusable and declarative validators. In certain cases, such as when you would use SDKv2 `AtLeastOneOf`, this approach can reduce overlapping validation errors and make logic easier to understand. + +In the following SDKv2 example, multiple attributes can raise multiple errors. + +```go +map[string]*schema.Schema{ + “attribute_one”: { + AtLeastOneOf: []string{“attribute_two”}, // does this need attribute_one? + // ... other fields ... + }, + “attribute_two”: { + AtLeastOneOf: []string{“attribute_one”}, // is this necessary? + // ... other fields ... + }, +} +``` + +In the following framework example, the validation logic raises a single error when the resource configuration does not include at least one of the specified attributes. + +```go +func (r ThingResource) ConfigValidators(_ context.Context) []resource.ConfigValidator { + return []resource.ConfigValidator{ + resourcevalidator.AtLeastOneOf( + path.MatchRoot("attribute_one"), + path.MatchRoot("attribute_two"), + ), + } +} +``` + +### Imperative Schema Validation + +The framework supports schema-level validation with custom logic in addition to the declarative validators. This support lets you fully customize the validation to implement complex validation logic. + +In the following framework example, the resource implements custom validation logic. + +```go +func (r ThingResource) ValidateConfig(ctx context.Context, req resource.ValidateConfigRequest, resp *resource.ValidateConfigResponse) { + // custom logic +} +``` + +## Path Expressions + +The framework includes a schema path implementation that lets you target attributes of any type or nesting level. This feature lets you build paths without knowing the special string syntax of the SDKv2, instead using Go ecosystem features such as suggestions from editor integrations. + +In the following framework example, the path is absolute to the first element of a list. + +```go +path.Root("list_attribute").AtListIndex(0) +``` + +Additionally, the framework supports expressions on top of these paths, which enables logic such as matching all indices in a list, relative paths, and parent paths. + +The following framework example validates whether the two attributes within the same list element conflict with each other. + +```go +schema.ListNestedAttribute{ + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + “attribute_one”: schema.StringAttribute{ + Validators: []validator.String{ + stringvalidator.ConflictsWith( + path.MatchRelative().AtParent().AtName(“attribute_two”), + ), + }, + // ... other fields ... + }, + “attribute_two”: { /* … */ }, + }, + }, + // ... other fields ... +} +``` + +## Import Warning Diagnostics + +The framework supports diagnostics through all Terraform operations. The SDKv2 does not support diagnostics with some operations, such as import. + +The following framework example returns a warning to practitioners when they import the resource. + +```go +func (r ThingResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { + resp.Diagnostics.AddWarning( + “Resource Import Considerations”, + “The API does return the password attribute, which will show as a plan ”+ + “difference in Terraform unless the lifecycle configuration block “+ + “ignore_changes argument includes password.” + ) + resource.ImportStatePassthroughID(ctx, path.Root("id"), req, resp) +} +``` + +## Destroy Plan Diagnostics + +With the framework, Terraform version 1.3 and later supports calling the provider when Terraform is planning to destroy a resource. SDKv2 does not support this functionality. + +In this framework example, the resource will raise a warning when planned for destruction to give practitioner more information: + +```go +func (r ThingResource) ModifyPlan(ctx context.Context, req resource.ModifyPlanRequest, resp *resource.ModifyPlanResponse) { + // If the entire plan is null, the resource is planned for destruction. + if req.Plan.Raw.IsNull() { + resp.Diagnostics.AddWarning( + "Resource Destruction Considerations", + "Applying this resource destruction will only remove the resource from the Terraform state "+ + "and will not call the deletion API due to API limitations. Manually use the web "+ + "interface to fully destroy this resource.", + ) + } +} +``` + +## Resource Private State Management + +Each provider can maintain resource private state data in Terraform state. Terraform never accesses resource private state or includes the information in plans, but providers can use this private data for advanced use cases. For example, a provider could use resource private state to store API ETag values that are not beneficial for practitioners. SDKv2 does not support this functionality. + +Refer to the [Manage Private State documentation](/terraform/plugin/framework/resources/private-state) for more information. diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/migrating/data-sources/index.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/migrating/data-sources/index.mdx new file mode 100644 index 000000000..eaa78e446 --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/migrating/data-sources/index.mdx @@ -0,0 +1,167 @@ +--- +page_title: Migrating data sources +description: >- + Learn how to migrate a data source from SDKv2 to the plugin framework. +--- + +# Migrating data sources + +Data sources let Terraform reference external data. Unlike resources, Terraform does not create, update, or delete +data sources, and makes no attempt to modify the underlying API. Data Sources are a read-only resource type, so they +only implement a subset of the operations that resources do. Refer to [Data Sources](/terraform/plugin/framework/data-sources) +in the Framework documentation for details. + +This page explains how to migrate a data source from SDKv2 to the plugin Framework. We also recommend reviewing these additional guides for data sources throughout the migration: +- [Timeouts](/terraform/plugin/framework/migrating/data-sources/timeouts): The data source uses timeouts during a read operation. + +## SDKv2 + +In SDKv2, data sources are defined by the `DataSourcesMap` field on the `schema.Provider` struct, which maps data source +names (strings) to their schema. The `schema.Resource` struct is used for both resources and data sources. + +The following example shows a typical implementation. + +```go +func New() *schema.Provider { + return &schema.Provider{ + DataSourcesMap: map[string]*schema.Resource{ + /* ... */ +}, +``` + +In SDKv2, you define both resources and data sources with `schema.Resource` structs. The following example shows a +resource struct. For clarity, the example omits fields that are not available for data sources. + +```go +schema.Resource { + Schema: map[string]*schema.Schema, + Read: ReadFunc, + ReadContext: ReadContextFunc, + ReadWithoutTimeout: ReadContextFunc, + DeprecationMessage: string, + Timeouts: *ResourceTimeout, + Description: string, +} +``` + +## Framework + +In the Framework, you define data sources by adding them to the map returned by your provider's `DataSources` method. + +The `DataSources` method on your `provider.Provider` returns a slice of functions that return types +that implement the `datasource.DataSource` interface for each data source your provider supports. + +The following code shows how you add a data source to your provider with the Framework. + +```go +func (p *provider) DataSources(ctx context.Context) []func() datasource.DataSource { + return []func() datasource.DataSource{ + /* ... */ + } +} +``` + +Like the `resource.Resource` interface, `datasource.DataSource` requires `Schema` and `Metadata` methods. +These methods work the same way for data sources as they do for resources. The `Read` method is also required. + +The `Schema` method returns a `schema.Schema` struct which defines your data source's attributes. + +The `Metadata` method returns a type name that you define. + +The `Read` method implements the logic for writing into the Terraform state. + +The following code shows how you define a `datasource.DataSource` which implements these methods with the +Framework. + +```go +type dataSourceExample struct{} + +func (d *dataSourceExample) Metadata(ctx context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) { + /* ... */ +} + +func (d *dataSourceExample) Schema(ctx context.Context, req datasource.SchemaRequest, resp *datasource.SchemaResponse) { + /* ... */ +} + +func (d *dataSourceExample) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { + /* ... */ +} +``` + +## Migration Notes + +Remember the following details when completing the migration from SDKv2 to the Framework. + +- As data sources are read-only, you only implement read functionality for your provider's data sources. Refer to the +[`Read` function](/terraform/plugin/framework/resources#read) for resources in the Framework documentation for more details. + +## Example + +### SDKv2 + +The following example shows an implementation of the `DataSourcesMap` field on the provider +schema with SDKv2. + +```go +func New() (*schema.Provider, error) { + return &schema.Provider { + DataSourcesMap: map[string]*schema.Resource { + "example_datasource": exampleDataSource(), + /* ... */ +``` + +The following example shows how the `ReadContext` function and `Schema` are defined for +the `exampleResource` data source with SDKv2. + +```go +func exampleDataSource() *schema.Resource { + return &schema.Resource{ + ReadContext: dataSourceRead, + + Schema: map[string]*schema.Schema{ + "example_attribute": { + Type: schema.TypeString, + Required: true, + }, + /* ... */ + }, + } +} +``` + +### Framework + +The following example shows how the `exampleDataSource` data source is defined with the Framework after +the migration. + +```go +func (p *provider) DataSources(context.Context) []func() datasource.DataSource { + return []func() datasource.DataSource{ + func() datasource.DataSource { + return &exampleDataSource{} + }, + } +} +``` + +This code defines the methods for the `exampleDataSource` data source with the +Framework. + +```go +func (d *exampleDataSource) Metadata(ctx context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) { + resp.TypeName = "example_datasource" +} + +func (d *exampleDataSource) Schema(ctx context.Context, req datasource.SchemaRequest, resp *datasource.SchemaResponse) { + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "example_attribute": schema.StringAttribute{ + Required: true, + }, + /* ... */ + +func (d *exampleDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { + /* ... */ +} +``` diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/migrating/data-sources/timeouts.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/migrating/data-sources/timeouts.mdx new file mode 100644 index 000000000..7b0105316 --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/migrating/data-sources/timeouts.mdx @@ -0,0 +1,130 @@ +--- +page_title: Migrating timeouts +description: >- + Learn how to migrate timeouts from SDKv2 to the framework. +--- + +# Migrating timeouts + +The Framework can be used in conjunction with the [terraform-plugin-framework-timeouts](https://github.com/hashicorp/terraform-plugin-framework-timeouts) module in order to allow defining timeouts in configuration and have them be available in `Read` functions. + +## Specifying Timeouts in Configuration + +Timeouts can be defined using either nested blocks or nested attributes. + +If you are writing a new provider using [terraform-plugin-framework](https://github.com/hashicorp/terraform-plugin-framework) +then we recommend using nested attributes. + +If you are migrating a provider from SDKv2 to the Framework and +you are already using timeouts you can either continue to use block syntax, or switch to using nested attributes. +However, switching to using nested attributes will require that practitioners that are using your provider update their +Terraform configuration. + +#### Block + +If your configuration is using a nested block to define timeouts, such as the following: + +```hcl +resource "timeouts_example" "example" { + /* ... */ + + timeouts { + read = "60m" + } +} +``` + +Import the [timeouts module](https://github.com/hashicorp/terraform-plugin-framework-timeouts). + +```go +import ( + /* ... */ + "github.com/hashicorp/terraform-plugin-framework-timeouts/datasource/timeouts" +) +```` + +You can use this module to mutate the `schema.Schema` as follows: + +```go +func (d *ThingDataSource) Schema(ctx context.Context, req datasource.SchemaRequest, resp *datasource.SchemaResponse) { + resp.Schema = schema.Schema{ + /* ... */ + + Blocks: map[string]schema.Block{ + "timeouts": timeouts.Block(ctx), + }, +``` + +#### Attribute + +If your configuration is using nested attributes to define timeouts, such as the following: + +```hcl +resource "timeouts_example" "example" { + /* ... */ + + timeouts = { + read = "60m" + } +} +``` + +You can use this module to mutate the `schema.Schema` as follows: + +```go +func (d *ThingDataSource) Schema(ctx context.Context, req datasource.SchemaRequest, resp *datasource.SchemaResponse) { + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + /* ... */ + "timeouts": timeouts.Attributes(ctx), + }, +``` + +## Updating Models + +Given a `Read` method which fetches the entire configuration: + +```go +func (e *exampleDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { + var data exampleDataSourceData + + diags := req.Config.Get(ctx, &data) + resp.Diagnostics.Append(diags...) +``` + +Modify the `exampleDataSourceData` model to include a field for timeouts using a [`timeouts.Value`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework-timeouts/datasource/timeouts#Value) type. + +```go +type exampleDataSourceData struct { + /* ... */ + Timeouts timeouts.Value `tfsdk:"timeouts"` +``` + +## Accessing Timeout in Read Method + +Call the [`timeouts.Read()` function](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework-timeouts/datasource/timeouts#Value.Read). + +```go +func (e *exampleDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { + var data exampleDataSourceData + + diags := req.Config.Get(ctx, &data) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + readTimeout, diags := data.Timeouts.Read(ctx, 20*time.Minute) + + resp.Diagnostics.Append(diags...) + + if resp.Diagnostics.HasError() { + return + } + + ctx, cancel := context.WithTimeout(ctx, readTimeout) + defer cancel() + + /* ... */ +} +``` diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/migrating/index.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/migrating/index.mdx new file mode 100644 index 000000000..f13455039 --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/migrating/index.mdx @@ -0,0 +1,48 @@ +--- +page_title: Migrating from SDKv2 to the plugin framework +description: >- + Learn how to migrate your provider from SDKv2 to the plugin framework. +--- + +# Overview + +This guide helps you migrate a Terraform provider from SDKv2 to the plugin Framework. We recommend migrating because the Framework has abstractions that make it easier to use, and it represents the future of Terraform plugin development. Refer to [Plugin Framework Benefits](/terraform/plugin/framework-benefits) for higher level details about how the framework makes provider development easier and [Feature Comparison](/terraform/plugin/framework/migrating/benefits) for a detailed functionality comparison between the SDKv2 and the framework. + +This guide provides information and examples for most common use cases, but it does not discuss every nuance of migration. You can ask additional migration questions in the [HashiCorp Discuss forum](https://discuss.hashicorp.com/c/terraform-providers/tf-plugin-sdk/43). To request additions or updates to this guide, submit issues or pull requests to the [`terraform-plugin-framework` repository](https://github.com/hashicorp/terraform-plugin-framework). + +In addition to this migration guide, we recommend referring to the main [Framework documentation](/terraform/plugin/framework) as you migrate your provider. + +## Requirements + +Before you migrate your provider to the Framework, ensure it meets the following requirements: + +- Go 1.22+ +- Built on the latest version of SDKv2 +- The provider is for use with Terraform >= 0.12.0 + +## Muxing + +Consider muxing when you need to migrate a provider that contains many resources or data sources. Muxing enables multiple underlying provider implementations to exist within the same logical provider server. This lets you migrate individual resources or data sources to the Framework one at a time. + +Refer to the [muxing](/terraform/plugin/framework/migrating/mux) page of the migration guide for additional details. + +## Testing Migration + +As you complete the migration, we recommend that you follow Test Driven Development by writing tests to ensure that the migration does not affect provider behavior. Refer to [Testing Migration](/terraform/plugin/framework/migrating/testing#testing-migration) for details and an example. + + +## Migration steps + +Take the following steps when you migrate a provider from SDKv2 to the Framework: + +- Ensure all [tests](/terraform/plugin/framework/migrating/testing#testing-migration) pass. +- Consider [finding SDKv2 resource data consistency errors](/terraform/plugin/sdkv2/resources/data-consistency-errors), which might affect migrating to the Framework. Some errors can be resolved and verified with SDKv2 code before migration, if desired, otherwise resolving these errors must be [part of the migration](/terraform/plugin/framework/migrating/resources/crud#resolving-data-consistency-errors). +- [Serve the provider](/terraform/plugin/framework/migrating/providers#serving-the-provider) via the Framework. + - Implement [muxing](/terraform/plugin/framework/migrating/mux), if you plan to migrate the provider iteratively. +- Update the [provider definition](/terraform/plugin/framework/migrating/providers#provider-definition) to use the Framework. +- Update the [provider schema](/terraform/plugin/framework/migrating/providers#provider-schema). +- Update each of the provider's resources and data sources. + - Update related [tests](/terraform/plugin/framework/migrating/testing) to use the Framework, and ensure that the tests fail. + - Migrate the [resource](/terraform/plugin/framework/migrating/resources) or [data source](/terraform/plugin/framework/migrating/data-sources). + - Verify that related tests now pass. +- If you used [muxing](/terraform/plugin/framework/migrating/mux), remove the muxing configuration. diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/migrating/mux.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/migrating/mux.mdx new file mode 100644 index 000000000..e0e57c4b7 --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/migrating/mux.mdx @@ -0,0 +1,283 @@ +--- +page_title: Migration using muxing +description: >- + Learn how to iteratively migrate from the SDKv2 to the plugin framework using + the terraform-plugin-mux Go library. +--- + +# Muxing + +Muxing enables multiple underlying provider implementations to exist within the same logical provider server via the [terraform-plugin-mux Go module](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-mux). Each underlying provider implementation serves different managed resources and data sources. Refer to the [Combining and Translating documentation](/terraform/plugin/mux) for full details about muxing configuration. + +## Use Cases + +Use muxing when: + +- You have an existing terraform-plugin-sdk based provider. +- The provider includes more than a few managed resources and data sources. +- You want to iteratively develop or release a version of your provider with only some of the managed resources and data sources migrated to the Framework. + +Otherwise for simplicity, it is recommended to migrate directly to the framework without temporarily introducing muxing. + +## Requirements + +- Ensure `github.com/hashicorp/terraform-plugin-sdk/v2` is upgraded to the latest version. For example, running the `go get github.com/hashicorp/terraform-plugin-sdk/v2@latest` command. +- Ensure existing acceptance testing is passing. Acceptance testing can be used to verify the muxing implementation before release. + +## Implementation + +1. Introduce a Go type implementing the Framework's [`provider.Provider` interface](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/provider#Provider). Refer to the [provider definition section](/terraform/plugin/framework/migrating/providers#provider-definition) of the migration guide for additional details. +1. Implement the `provider.Provider` type `Schema` and `Configure` methods so it is compatible for muxing. The schema and configuration handling must exactly match between all underlying providers of the mux server. Refer to the [provider schema section](/terraform/plugin/framework/migrating/providers#provider-schema) of the migration guide for additional details. +1. Introduce a mux server using terraform-plugin-mux functionality. This code eventually must be referenced by the codebase's `main()` function, which is responsible for starting the provider server. Refer to the [Mux Server Examples section](#mux-server-examples) for additional details. +1. Introduce an acceptance test for the mux server implementation. Refer to the [Testing Examples section](#testing-examples) for additional details. +1. Ensure `github.com/hashicorp/terraform-plugin-mux` is added to the provider Go module dependencies. For example, running the `go get github.com/hashicorp/terraform-plugin-mux@latest` command. + +### Mux Server Examples + +#### Terraform 0.12 Compatibility Example + +The following `main.go` example shows how to set up muxing for a provider that uses Protocol Version 5 to maintain compatibility with Terraform 0.12 and later. The example also shows how to use the `debug` flag to optionally run the provider in debug mode. + +```go +import ( + "context" + "flag" + "log" + + "github.com/hashicorp/terraform-plugin-framework/providerserver" + "github.com/hashicorp/terraform-plugin-go/tfprotov5" + "github.com/hashicorp/terraform-plugin-go/tfprotov5/tf5server" + "github.com/hashicorp/terraform-plugin-mux/tf5muxserver" + + "example.com/terraform-provider-examplecloud/internal/provider" +) + +func main() { + ctx := context.Background() + + var debug bool + + flag.BoolVar(&debug, "debug", false, "set to true to run the provider with support for debuggers like delve") + flag.Parse() + + providers := []func() tfprotov5.ProviderServer{ + providerserver.NewProtocol5(provider.New()), // Example terraform-plugin-framework provider + provider.Provider().GRPCProvider, // Example terraform-plugin-sdk provider + } + + muxServer, err := tf5muxserver.NewMuxServer(ctx, providers...) + + if err != nil { + log.Fatal(err) + } + + var serveOpts []tf5server.ServeOpt + + if debug { + serveOpts = append(serveOpts, tf5server.WithManagedDebug()) + } + + err = tf5server.Serve( + "registry.terraform.io//", + muxServer.ProviderServer, + serveOpts..., + ) + + if err != nil { + log.Fatal(err) + } +} +``` + +#### Terraform 1.X Compatibility Example + +The mux server can be setup to break compatibility with Terraform 0.12 through 1.1.6, but enable Protocol Version 6 capabilities in the Framework provider, such as nested attributes. + +The following `main.go` example shows how to set up muxing for a provider that upgrades the terraform-plugin-sdk based provider to Protocol Version 6 to support those new features in the Framework provider. The example also shows how to use the `debug` flag to optionally run the provider in debug mode. + +```go +import ( + "context" + "flag" + "log" + + "github.com/hashicorp/terraform-plugin-framework/providerserver" + "github.com/hashicorp/terraform-plugin-go/tfprotov6" + "github.com/hashicorp/terraform-plugin-go/tfprotov6/tf6server" + "github.com/hashicorp/terraform-plugin-mux/tf5to6server" + "github.com/hashicorp/terraform-plugin-mux/tf6muxserver" + + "example.com/terraform-provider-examplecloud/internal/provider" +) + +func main() { + ctx := context.Background() + + var debug bool + + flag.BoolVar(&debug, "debug", false, "set to true to run the provider with support for debuggers like delve") + flag.Parse() + + upgradedSdkServer, err := tf5to6server.UpgradeServer( + ctx, + provider.Provider().GRPCProvider, // Example terraform-plugin-sdk provider + ) + + if err != nil { + log.Fatal(err) + } + + providers := []func() tfprotov6.ProviderServer{ + providerserver.NewProtocol6(provider.New()()), // Example terraform-plugin-framework provider + func() tfprotov6.ProviderServer { + return upgradedSdkServer, + }, + } + + muxServer, err := tf6muxserver.NewMuxServer(ctx, providers...) + + if err != nil { + log.Fatal(err) + } + + var serveOpts []tf6server.ServeOpt + + if debug { + serveOpts = append(serveOpts, tf6server.WithManagedDebug()) + } + + err = tf6server.Serve( + "registry.terraform.io//", + muxServer.ProviderServer, + serveOpts..., + ) + + if err != nil { + log.Fatal(err) + } +} +``` + +### Testing Examples + +#### Protocol Version 5 + +The following acceptance test example would be included in the same Go package that defines the provider code to verify the muxing setup: + +```go +import ( + "context" + "testing" + + "github.com/hashicorp/terraform-plugin-go/tfprotov5" + "github.com/hashicorp/terraform-plugin-mux/tf5muxserver" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" +) + +func TestMuxServer(t *testing.T) { + resource.Test(t, resource.TestCase{ + ProtoV5ProviderFactories: map[string]func() (tfprotov5.ProviderServer, error) { + "examplecloud": func() (tfprotov5.ProviderServer, error) { + ctx := context.Background() + providers := []func() tfprotov5.ProviderServer{ + providerserver.NewProtocol5(New()), // Example terraform-plugin-framework provider + Provider().GRPCProvider, // Example terraform-plugin-sdk provider + } + + muxServer, err := tf5muxserver.NewMuxServer(ctx, providers...) + + if err != nil { + return nil, err + } + + return muxServer.ProviderServer(), nil + }, + }, + Steps: []resource.TestStep{ + { + Config: "... configuration including simplest data source or managed resource", + }, + }, + }) +} +``` + +#### Protocol Version 6 + +The following acceptance test example would be included in the same Go package that defines the provider code to verify the muxing setup: + +```go +import ( + "context" + "testing" + + "github.com/hashicorp/terraform-plugin-go/tfprotov6" + "github.com/hashicorp/terraform-plugin-mux/tf5to6server" + "github.com/hashicorp/terraform-plugin-mux/tf6muxserver" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" +) + +func TestMuxServer(t *testing.T) { + resource.Test(t, resource.TestCase{ + ProtoV6ProviderFactories: map[string]func() (tfprotov6.ProviderServer, error) { + "examplecloud": func() (tfprotov6.ProviderServer, error) { + ctx := context.Background() + + upgradedSdkServer, err := tf5to6server.UpgradeServer( + ctx, + Provider().GRPCProvider, // Example terraform-plugin-sdk provider + ) + + if err != nil { + return nil, err + } + + providers := []func() tfprotov6.ProviderServer{ + providerserver.NewProtocol6(New()), // Example terraform-plugin-framework provider + func() tfprotov6.ProviderServer { + return upgradedSdkServer + }, + } + + muxServer, err := tf6muxserver.NewMuxServer(ctx, providers...) + + if err != nil { + return nil, err + } + + return muxServer.ProviderServer(), nil + }, + }, + Steps: []resource.TestStep{ + { + Config: "... configuration including simplest data source or managed resource", + }, + }, + }) +} +``` + +## Tips + +- Only acceptance tests for migrated managed resources and data sources require testing code updates as noted in the [testing](/terraform/plugin/framework/migrating/testing) page of the migration guide. + +## Troubleshooting + +### PreparedConfig response from multiple servers + +Muxed providers may receive a new error, such as: + +```text +Error: Plugin error + + with provider["registry.terraform.io/example/examplecloud"], + on line 0: + (source code not available) + +The plugin returned an unexpected error from +plugin.(*GRPCProvider).ValidateProviderConfig: rpc error: code = Unknown desc += got different PrepareProviderConfig PreparedConfig response from multiple +servers, not sure which to use +``` + +If the terraform-plugin-sdk based provider was using [`Default`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-sdk/helper/schema#Schema.Default) or [`DefaultFunc`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-sdk/helper/schema#Schema.DefaultFunc), you must remove the usage of `Default` and `DefaultFunc` in that provider implementation. Transfer the logic into the provider [ConfigureFunc](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema#Provider.ConfigureFunc) or [ConfigureContextFunc](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema#Provider.ConfigureContextFunc), similar to how it must be implemented in a terraform-plugin-framework based provider. diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/migrating/providers/index.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/migrating/providers/index.mdx new file mode 100644 index 000000000..42fc0c54e --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/migrating/providers/index.mdx @@ -0,0 +1,343 @@ +--- +page_title: Migrating providers from SDKv2 to the framework +description: >- + Learn how to migrate a provider definition and schema from SDKv2 to the plugin + framework. +--- + +# Migrating providers + +Providers are Terraform plugins that define resources and data sources for practitioners to use. You serve your +providers with a provider server so they can interact with Terraform. + +This page explains how to migrate a provider server, definition, and schema from SDKv2 to the plugin Framework. + +## Serving the Provider + +You must update your provider's `main.go` file to serve Framework providers. Refer to [Provider Servers](/terraform/plugin/framework/provider-servers) in the Framework documentation for details. + + + +To iteratively migrate individual resources and data sources, refer to [muxing](/terraform/plugin/framework/migrating/mux) for the `main.go` implementation instead. The latter [provider definition](#provider-definition) and [provider schema](#provider-schema) sections are still applicable when muxing. + + + +### SDKv2 + +In SDKv2, the provider package's `main` function serves the provider by calling `plugin.Serve`. + +The following code shows a basic implementation for serving an SDKv2 provider. + +```go +func main() { + plugin.Serve( + &plugin.ServeOpts{ + ProviderFunc: provider.New, + ProviderAddr: "registry.terraform.io//", + }, + ) +} +``` + +### Framework + +In the Framework, you serve your provider by calling `providerserver.Serve` in your provider package's `main` function. +Refer to [Provider Servers](/terraform/plugin/framework/provider-servers) in the Framework documentation for details. + +The following code shows an equivalent implementation for serving a provider in the Framework. + +```go +func main() { + err := providerserver.Serve( + context.Background(), + provider.New, + providerserver.ServeOpts{ + Address: "registry.terraform.io//", + }, + ) + + if err != nil { + log.Fatal(err) + } +} +``` + +## Provider Definition + +Providers built with SDKv2 use a `schema.Provider` struct to define their behavior, while Framework providers use a +type that implements the `provider.Provider` interface, which you must define. Refer to [Providers](/terraform/plugin/framework/providers) in the Framework documentation for details. + +### SDKv2 + +The [`ProviderFunc`](/terraform/plugin/framework/migrating/providers#serving-the-provider) field on +`plugin.ServeOpts` requires a pointer to `schema.Provider`. This is typically satisfied by calling a function that +returns a pointer to `schema.Provider`. + +The `ResourcesMap` and `DataSourcesMap` fields each contain a map of strings to functions that each return a pointer +to a `schema.Resource` struct. + +The following example shows a basic implementation of an SDKv2 provider. + +```go +func New() *schema.Provider { + return &schema.Provider{ + Schema: map[string]*schema.Schema{}, + ConfigureContextFunc: configureContextFunc(), + ResourcesMap: map[string]*schema.Resource{ + "resource_example": resourceExample(), + }, + DataSourcesMap: map[string]*schema.Resource{ + "dataSource_example": dataSourceExample(), + }, + /* ... */ + } +} +``` + +### Framework + +In the Framework, the second argument to your `provider.Serve` function requires a function that returns a type +satisfying the `provider.Provider` interface. + +The following code shows a typical implementation. In this implementation, the `Resources` method returns a slice +of functions that return types that implement the `resource.Resource` interface. The `DataSources` method returns a +slice of functions that return types that implement the `datasource.DataSource` interface. +Refer to the [Resources](/terraform/plugin/framework/migrating/resources) and +[Data Sources](/terraform/plugin/framework/migrating/data-sources) pages in this guide to implement these functions for your +provider. + +```go +type exampleCloudProvider struct { +} + +func New() provider.Provider { + return &exampleCloudProvider{} +} + +func (p *exampleCloudProvider) Metadata(ctx context.Context, req provider.MetadataRequest, resp *provider.MetadataResponse) { + resp.TypeName = "examplecloud" +} + +func (p *exampleCloudProvider) Schema(ctx context.Context, req provider.SchemaRequest, resp *provider.SchemaResponse) { + resp.Schema = schema.Schema{} +} + +func (p *exampleCloudProvider) Configure(ctx context.Context, req provider.ConfigureRequest, resp *provider.ConfigureResponse) { +} + +func (p *exampleCloudProvider) Resources(ctx context.Context) []func() resource.Resource { + return []func() resource.Resource { + func() resource.Resource { + return resourceExample{} + }, + } +} + +func (p *exampleCloudProvider) DataSources(ctx context.Context) []func() datasource.DataSource { + return []func() datasource.DataSource { + func() datasource.DataSource { + return dataSourceExample{} + }, + } +} +``` + +### Migration Notes + +Remember the following differences between SDKv2 and the Framework when completing the migration. + +- In SDKv2, your provider's `New` function returns a `schema.Provider` struct. In the Framework, `New` returns a type +that you define which satisfies the `provider.Provider` interface. +- In SDKv2, `Schema` is a field on `schema.Provider` that contains `map[string]*schema.Schema`, which maps attribute +names to `schema.Schema` structs. In the Framework, `Schema` is a method you define on your provider's +`provider.Provider` interface that returns your provider's `schema.Schema` struct. +- In SDKv2, `ConfigureContextFunc` is a field on `schema.Provider` containing a function that configures the provider. +In the Framework, `Configure` is a function you define on your provider that configures your provider. +- In SDKv2, `ResourcesMap` is a field on `schema.Provider` containing `map[string]*schema.Resource`, which maps resource +names to `schema.Resource` structs. In the Framework, `Resources` is a method you define on your provider that +returns `[]func() resource.Resource`, which creates resource types that you define, which satisfy the +`resource.Resource` interface. +- In SDKv2, `DataSourcesMap` is a field on `schema.Provider` containing `map[string]*schema.Resource`, which maps data +source names to `schema.Resource` structs (data sources and resources both use `schema.Resource`). In the Framework, +`DataSources` is a method you define on your provider that returns `[]func() datasource.DataSource`, which +creates data source types that you define, which satisfy the `datasource.DataSource` interface. + +### Example + +#### SDKv2 + +The following example shows how to set up a provider schema, configuration, resources, and data sources using SDKv2. + +```go +func New() (*schema.Provider, error) { + return &schema.Provider{ + Schema: map[string]*schema.Schema{ + "attribute": { + /* ... */ + }, + }, + ConfigureContextFunc: configureProvider, + ResourcesMap: map[string]*schema.Resource{ + "exampleResource": exampleResource(), + /* ... */ + }, + DataSourcesMap: map[string]*schema.Resource{ + "exampleDataSource": exampleDataSource(), + /* ... */ + }, + }, nil +} +``` + +#### Framework + +The following shows the same section of provider code after the migration. + +```go +var _ provider.Provider = (*exampleProvider)(nil) + +func New() provider.Provider { + return &exampleProvider{} +} + +func (p *exampleProvider) Resources(_ context.Context) []func() resource.Resource { + return []func() resource.Resource{ + func() resource.Resource { + return &exampleResource{} + }, + /* ... */ + } +} + +func (p *exampleProvider) DataSources(_ context.Context) []func() datasource.DataSource { + return []func() datasource.DataSource{ + func() datasource.DataSource { + return &exampleDataSource{} + }, + /* ... */ + } +} + +func (p *exampleProvider) Schema(_ context.Context, _ provider.SchemaRequest, resp *provider.SchemaResponse) { + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "attribute": schema.SingleNestedBlock{ + /* ... */ + }, + }, + } +} + +func (p *exampleProvider) Metadata(_ context.Context, _ provider.MetadataRequest, resp *provider.MetadataResponse) { + resp.TypeName = "example" +} + +func (p *exampleProvider) Configure(_ context.Context, _ provider.ConfigureRequest, resp *provider.ConfigureResponse) { + /* ... */ +} +``` + +## Provider Schema + +A provider schema defines the attributes and behaviors of the provider itself. For example, a provider that connects to +a third-party API may define attributes for the base URL or a required authentication token. + +### SDKv2 + +In SDKv2, you implement a provider Schema by populating the `Schema` field on the `schema.Provider` struct. The `Schema` +field contains a `map[string]*schema.Schema`. Each map entry represents the name of the attribute and pointer to a +`schema.Schema` struct that defines that attribute's behavior. + +The following example defines the provider schema in the `Schema` field within the `schema.Provider` struct. + +```go +func New() *schema.Provider { + return &schema.Provider{ + Schema: map[string]*schema.Schema{ + /* ... */ + }, +``` + +### Framework + +In the Framework, the `Schema` method returns the provider schema. The `Schema` method is part of the +`provider.Provider` interface that your provider must implement. `Schema` returns a struct containing fields for +`Attributes` and `Blocks`. These `Attributes` and `Blocks` contain `map[string]schema.Attribute` and +`map[string]schema.Block`, respectively. Refer to [Providers - Schema](/terraform/plugin/framework/providers#schema) in the +Framework documentation for details. + +The following code shows the `Schema` method, which returns the provider schema. + +```go +func (p *ExampleCloudProvider) Schema(ctx context.Context, req provider.SchemaRequest, resp *provider.SchemaResponse) { + resp.Schema = schema.Schema{ + /* ... */ + } +} +``` + +Refer to the [Attributes](/terraform/plugin/framework/migrating/attributes-blocks/attribute-schema) and +[Blocks](/terraform/plugin/framework/migrating/attributes-blocks/blocks) pages in this migration guide to learn how to migrate +those fields to the Framework. + +### Migration Notes + +Remember the following differences between SDKv2 and the Framework when completing the migration. + +- In SDKv2, `schema.Schema` is a struct that defines attributes and behaviors (e.g., `Type`, `Optional`). In the +Framework `schema.Schema` is a struct that includes attributes and blocks. + +### Example + +This example shows how to use a nested block and a nested attribute for the SDKv2 and Framework examples, +respectively. Refer to the +[Blocks with Computed Fields](/terraform/plugin/framework/migrating/attributes-blocks/blocks-computed) page in this guide for more +details. + +#### SDKv2 + +The following example shows the configuration of the `example_attribute` attribute for the provider's `example_block` configuration block. + +```go +Schema: map[string]*schema.Schema{ + "example_block": { + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "example_attribute": { + Type: schema.TypeString, + Optional: true, + ValidateDiagFunc: validation.ToDiagFunc(validation.IsURLWithScheme(SupportedProxySchemesStr())), + ConflictsWith: []string{"example_block.0.another_attribute"}, + }, + /* ... */ +``` + +#### Framework + +The following shows the same section of provider code after the migration. + +This code implements the `example_attribute` attribute for the `example_Block` block with the Framework. + +```go +func (p *exampleProvider) Schema(_ context.Context, _ provider.SchemaRequest, resp *provider.SchemaResponse) { + resp.Schema = schema.Schema{ + /*...*/ + Blocks: map[string]schema.Block{ + "example_block": schema.ListNestedBlock{ + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "example_attribute": schema.StringAttribute{ + Optional: true, + Validators: []validator.String{ + attribute_validator.UrlWithScheme(supportedProxySchemesStr()...), + stringvalidator.ConflictsWith(path.MatchRelative().AtParent().AtName("another_attribute")), + }, + }, + }, + Validators: []validator.List{ + listvalidator.SizeAtMost(1), + }, +``` diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/migrating/resources/crud.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/migrating/resources/crud.mdx new file mode 100644 index 000000000..645c0aae3 --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/migrating/resources/crud.mdx @@ -0,0 +1,259 @@ +--- +page_title: CRUD functions +description: >- + Learn how to migrate resource create, read, update, and delete (CRUD) + functions from SDKv2 to the plugin framework. +--- + +# CRUD functions + +In Terraform, a resource represents a single instance of a given resource type. They modify a specific resource in the +API and in Terraform's state through a set of Create, Read, Update, and Delete (CRUD) functions. A resource's CRUD +functions implement the logic required to manage your resources with Terraform. Refer to +[Resources - Define Resources](/terraform/plugin/framework/resources#define-resources) in the Framework documentation for details. + +This page explains how to migrate a resource's CRUD functions from SDKv2 to the plugin Framework. + +## SDKv2 + +In SDKv2, a resource's CRUD functions are defined by populating the relevant fields (e.g., `CreateContext`, +`ReadContext`) on the `schema.Resource` struct. + +The following code shows a basic implementation of CRUD functions with SDKv2. + +```go +func resourceExample() *schema.Resource { + return &schema.Resource{ + CreateContext: create, + ReadContext: read, + UpdateContext: update, + DeleteContext: delete, + /* ... */ +``` + +## Framework +In the Framework, you implement CRUD functions for your resource by defining a type that implements the +`resource.Resource` interface. To define functions related to state upgrade, import, and plan modification, +implement their respective interfaces on your resource: `ResourceWithUpgradeState`, `ResourceWithImportState`, and +`ResourceWithModifyPlan`. + +The following code shows how you define a `resource.Resource` which implements CRUD functions with the Framework. + +```go +type resourceExample struct { + p provider +} + +func (r *resourceExample) Create(ctx context.Context, req resource.CreateRequest, resp +*resource.CreateResponse) { + /* ... */ +} + +func (r *resourceExample) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + /* ... */ +} + +func (r *resourceExample) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + /* ... */ +} + +func (r *resourceExample) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + /* ... */ +} +``` +## Migration Notes +Remember the following differences between SDKv2 and the Framework when completing the migration. + +- In SDKv2 it is not necessary to define functions for parts of the CRUD lifecycle that are not used by a given +resource. For instance, if the resource does not support in-place modification, you do not need to define an `Update` +function. In the Framework, you must implement each of the CRUD lifecycle functions on all resources to satisfy the +`Resource` interface, even if the function does nothing. +- In SDKv2, the `Update` function (even if empty or missing) would automatically copy the request plan to the response state. This could be problematic when the `Update` function also returned errors as additional steps were required to disable the automatic SDKv2 behavior. In the Framework, the `Update` method must be written to explicitly copy data from `req.Plan` to `resp.State` to actually update the resource's state and prevent `Provider produced inconsistent result after apply` errors from Terraform. +- In SDKv2, calling `d.SetId("")` would signal resource removal. In the Framework, this is replaced with `resp.State.RemoveResource()`. Resource removal should only occur during the `Read` method to prevent `Provider produced inconsistent result after apply` errors from Terraform during other operations. The `Delete` method will automatically call `resp.State.RemoveResource()` if there are no errors. +- In SDKv2, you get and set attribute values in Terraform's state by calling `Get()` and `Set()` on +`schema.ResourceData`. In the Framework, you get attribute values from the configuration and plan by accessing +`Config` and `Plan` on `resource.CreateRequest`. You set attribute values in Terraform's state by mutating `State` +on `resource.CreateResponse`. +- In SDKv2, certain resource schema definition and data consistency errors are only visible as Terraform warning logs by default. After migration, these errors will always be visible to practitioners and prevent further Terraform operations. The [SDKv2 resource data consistency errors documentation](/terraform/plugin/sdkv2/resources/data-consistency-errors) discusses how to find these errors in SDKv2 resources and potential solutions **prior** to migrating. See the [Resolving Data Consistency Errors](#resolving-data-consistency-errors) section for Plugin Framework solutions **during** migration. + +## Example + +### SDKv2 + +The following example from shows implementations of CRUD functions on the with SDKv2. +The `UpdateContext` function is not implemented because the provider does not support updating this resource. + +```go +func resourceExample() *schema.Resource { + return &schema.Resource{ + CreateContext: create, + ReadContext: readNil, + DeleteContext: RemoveResourceFromState, + /* ... */ +``` + +The following example shows the implementation of the `create()` function with SDKv2. The implementations of +the `readNil()` and `RemoveResourceFromState()` functions are not shown for brevity. + +```go +func create(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + if err := d.Set("example_attribute", "value"); err != nil { + diags = append(diags, diag.Errorf("err: %s", err)...) + return diags + } + + return nil +} +``` + +### Framework +The following shows the same section of provider code after the migration. + +This code implements the `Create` function for the `example_resource` resource with the Framework. + +```go +func (r *exampleResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + var plan exampleModel + + diags := req.Plan.Get(ctx, &plan) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + plan.ExampleAttribute = types.StringValue("value") + + diags = resp.State.Set(ctx, plan) + resp.Diagnostics.Append(diags...) +} +``` + +## Resolving Data Consistency Errors + + +See the [SDKv2 data consistency errors documentation](/terraform/plugin/sdkv2/resources/data-consistency-errors) for background info, debugging tips, and potential SDKv2 solutions. + + + +### Planned Value does not match Config Value + +If an SDKv2 resource is raising this type of error or [warning log](/terraform/plugin/sdkv2/resources/data-consistency-errors#checking-for-warning-logs): + +```text +TIMESTAMP [WARN] Provider "TYPE" produced an invalid plan for ADDRESS, but we are tolerating it because it is using the legacy plugin SDK. + The following problems may be the cause of any confusing errors from downstream operations: + - .ATTRIBUTE: planned value cty.StringVal("VALUE") does not match config value cty.StringVal("value") +``` + +This occurs for attribute schema definitions that are `Optional: true` and `Computed: true`; where the planned value, returned by the provider, does not match the attribute's config value or prior state value. For example, value's for an attribute of type string must match byte-for-byte. + +An example root cause of this issue could be from API normalization, such as a JSON string being returned from an API and stored in state with differing whitespace then what was originally in config. + +#### SDKv2 Example + +Here is an example of an SDKv2 resource schema and terraform config that simulates this data consistency error: + +```go +func thingResource() *schema.Resource { + return &schema.Resource{ + // ... + Schema: map[string]*schema.Schema{ + "word": { + Type: schema.TypeString, + Optional: true, + Computed: true, + StateFunc: func(word interface{}) string { + // This simulates an API returning the 'word' attribute as all uppercase, + // which is stored to state even if it doesn't match the config or prior value. + return strings.ToUpper(word.(string)) + }, + }, + }, + } +} +``` + +```hcl +resource "examplecloud_thing" "this" { + word = "value" +} +``` + +A [warning log](/terraform/plugin/sdkv2/resources/data-consistency-errors#checking-for-warning-logs) will be produced and the resulting state after applying a new resource will be `VALUE` instead of `value`. + +#### Migrating to Plugin Framework + +When a resource with this behavior and prior state is migrated to Plugin Framework, depending on the business logic, you could potentially see: + +- Resource drift in the plan; Terraform will always detect a change between the config and state value. If no [modification](/terraform/plugin/framework/resources/plan-modification) is implemented, you could see drift in the plan: +```hcl +resource "examplecloud_thing" "this" { + word = "value" +} +``` +```text +examplecloud_thing.this: Refreshing state... + +Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols: + ~ update in-place + +Terraform will perform the following actions: + + # examplecloud_thing.this will be updated in-place + ~ resource "examplecloud_thing" "this" { + ~ word = "VALUE" -> "value" + } + +Plan: 0 to add, 1 to change, 0 to destroy. +``` +- If you mimic the original SDKv2 behavior of storing a different value from config/prior value into state in the `Update` method, you will see an error like below: +```text +examplecloud_thing.this: Modifying... +╷ +│ Error: Provider produced inconsistent result after apply +│ +│ When applying changes to examplecloud_thing.this, provider "provider[\"TYPE\"]" produced an unexpected +│ new value: .word: was cty.StringVal("value"), but now cty.StringVal("VALUE"). +│ +│ This is a bug in the provider, which should be reported in the provider's own issue tracker. +``` + +#### Recommended Solution +To solve this issue, the provider code must preserve the config value or prior state value when producing the new state. The recommended way to implement this logic is by creating a [custom type](/terraform/plugin/framework/handling-data/types/custom) with [semantic equality logic](/terraform/plugin/framework/handling-data/types/custom#semantic-equality). A custom type can be shared across multiple resource attributes and will ensure that the semantic equality logic is invoked during the `Read`, `Create`, and `Update` methods respectively. + +For the above example, the semantic equality implementation below would resolve the resource drift and error: + + + +The example code below is a partial implementation of a custom type, please see the [Custom Value Type documentation](/terraform/plugin/framework/handling-data/types/custom#value-type) for guidance. + + + +```go +type CaseInsensitive struct { + basetypes.StringValue +} + +// ... custom value type implementation + +// StringSemanticEquals returns true if the given string value is semantically equal to the current string value. (case-insensitive) +func (v CaseInsensitive) StringSemanticEquals(_ context.Context, newValuable basetypes.StringValuable) (bool, diag.Diagnostics) { + var diags diag.Diagnostics + + newValue, ok := newValuable.(CaseInsensitive) + if !ok { + diags.AddError( + "Semantic Equality Check Error", + "An unexpected value type was received while performing semantic equality checks. "+ + "Please report this to the provider developers.\n\n"+ + "Expected Value Type: "+fmt.Sprintf("%T", v)+"\n"+ + "Got Value Type: "+fmt.Sprintf("%T", newValuable), + ) + + return false, diags + } + + return strings.EqualFold(newValue.ValueString(), v.ValueString()), diags +} +``` + diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/migrating/resources/import.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/migrating/resources/import.mdx new file mode 100644 index 000000000..c7ebb97aa --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/migrating/resources/import.mdx @@ -0,0 +1,138 @@ +--- +page_title: Resource import +description: >- + Learn how to migrate resource import functions from SDKv2 to the plugin + framework. Practitioners import resources to bring them under the control of + their Terraform projects. +--- + +# Resource import + +Practitioners can use the [`terraform import` command](/terraform/cli/commands/import) to let Terraform +begin managing existing infrastructure by importing an existing resource into their Terraform project's state. A +resource's importer function implements the logic to add a resource to Terraform's state. Refer to +[Resources - Import](/terraform/plugin/framework/resources/import) in the Framework documentation for details. + +This page explains how to migrate import functions from SDKv2 to the plugin Framework. + +## SDKv2 + +In SDKv2, the `Importer` field on the `schema.Resource` defines how the provider imports resources to Terraform's +state. The following example implements resource import with SDKv2. + +```go +func resourceExample() *schema.Resource { + return &schema.Resource{ + Importer: &schema.ResourceImporter{ + StateContext: StateContextFunc, + }, + /* ... */ +``` + +The `StateContextFunc` is the function called to import a resource into Terraform state. Any operations that +are needed to import the resource take place within this function and may result in the mutation of the `ResourceData` +that is passed into the function. The return value is a slice of `schema.ResourceData`. This slice might be as simple as returning the `ResourceData` that was passed into the function, or it may involve multiple fan out to multiple resources, such as AWS security groups. + +## Framework + +In the Framework, you implement the `ResourceWithImportState` interface on your `resource.Resource` type to allow +users to import a given resource. This interface requires that your type implement a `ImportState` function. + +The following code shows how you define an `ImportState` function with the Framework. + +```go +func (r *resourceExample) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { + /* ... */ +} +``` + +The `ImportState` function includes a `resource.ImportStateResponse`, which you use to set your resource's state. + +## Migration Notes + +Remember the following differences between SDKv2 and the Framework when completing the migration. + +- In both SDKv2 and Framework, there is no access to the configuration, state, or plan during import. The import +functions can only access the value (e.g., the resource's `ID`) supplied to the `terraform import` command. +- In SDKv2, you implement resource importing by populating the `Importer` field on the `schema.Resource` struct. In the +Framework, you define an `ImportState` function on the type which implements `resource.Resource`. This implementation +satisfies the `resource.ResourceWithImportState` interface. + +## Example + +In the simple use case in which `schema.ImportStatePassthroughContext` has been used with SDKv2, migrating to the Framework involves using the `resource.ImportStatePassthroughID` function. + +### SDKv2 + +```go +func resourceExample() *schema.Resource { + return &schema.Resource{ + Importer: &schema.ResourceImporter{ + StateContext: schema.ImportStatePassthroughContext, + }, + /* ... */ + } +} +``` + +### Framework + +```go +func (r *resourceExample) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { + resource.ImportStatePassthroughID(ctx, path.Root("id"), req, resp) +} +``` + +This example also shows one way to handle populating attributes with their default values during import. + +### SDKv2 + +The following example shows the import function for the `example_resource` resource with SDKv2. + +```go +func exampleResource() *schema.Resource { + return &schema.Resource{ + Importer: &schema.ResourceImporter{ + StateContext: importFunc, + }, + /* ... */ + } +} +``` + +The following example shows the implementation of the `importFunc` function with SDKv2. + +```go +func importFunc(ctx context.Context, d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { + d.SetId("id") + + if err := d.Set("attribute", "value"); err != nil { + return nil, fmt.Errorf("resource example import failed, error setting attribute: %w", err) + } + + return []*schema.ResourceData{d}, nil +} +``` + +### Framework +The following shows the same section of provider code after the migration. + +This code implements the `ResourceWithImportState` interface on the `exampleResource` type by defining an `ImportState` +function. + +```go +func (r *exampleResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { + id := req.ID + + state := exampleModel{ + ID: types.StringValue("id"), + Attribute: types.StringValue("value"), + } + + diags := resp.State.Set(ctx, &state) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } +} +``` diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/migrating/resources/index.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/migrating/resources/index.mdx new file mode 100644 index 000000000..73d383a3a --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/migrating/resources/index.mdx @@ -0,0 +1,219 @@ +--- +page_title: Migrating resources +description: >- + Learn how to migrate resources from SDKv2 to the plugin framework. +--- + +# Migrating resources + +Resources are an abstraction that allow Terraform to manage infrastructure objects by defining create, read, update, +and delete functionality that maps onto API operations. Resource schemas define what fields a resource has, give +Terraform metadata about those fields, and define how the resource behaves. Refer to +[Resources](/terraform/plugin/framework/resources) in the Framework documentation for details. + +This page explains how to migrate a resource's schema from SDKv2 to the plugin Framework. We also recommend reviewing these additional guides for resources throughout the migration: +- [Create, Read, Update, and Delete functions](/terraform/plugin/framework/migrating/resources/crud): The resource defines the logic to manage resources with Terraform. +- [Default](/terraform/plugin/framework/migrating/attributes-blocks/default-values): The resource specifies default values for attributes that are null within the configuration. +- [Import](/terraform/plugin/framework/migrating/resources/import): The resource defines the logic to add a resource to Terraform's state. +- [Plan modification](/terraform/plugin/framework/migrating/resources/plan-modification): The resource customizes the Terraform plan for known values or behaviors outside the practitioner's configuration. +- [State upgrade](/terraform/plugin/framework/migrating/resources/state-upgrade): The resource updates Terraform state information in advanced use cases. +- [Timeouts](/terraform/plugin/framework/migrating/resources/timeouts): The resource uses timeouts during create, read, update or delete operations. + +## SDKv2 +In SDKv2, resources are defined by the `ResourcesMap` field in the `schema.Provider` struct, which maps resource names +(strings) to their schema. Each schema is a `schema.Resource` struct that includes: + +- A `Schema` field, which defines resource attributes +- Fields for resource lifecycle functions such as `Create` and `CreateContext` +- Fields for functions to implement state upgrade (`StateUpgraders`), import (`Importer`), and customize diff +(`CustomizeDiff`) + +The following code shows a basic implementation of resource schema with SDKv2. + +```go +func New() *schema.Provider { + return &schema.Provider{ + ResourcesMap: map[string]*schema.Resource { + "resource_example": resourceExample(), + /* ... */ + }, + /* ... */ + } +} +``` + +SDKv2 defines the `schema.Resource` struct as follows. + +```go +schema.Resource{ + Schema map[string]*Schema + SchemaVersion int + MigrateState StateMigrateFunc + StateUpgraders []StateUpgrader + Create CreateFunc + Read ReadFunc + Update UpdateFunc + Delete DeleteFunc + Exists ExistsFunc + CreateContext CreateContextFunc + ReadContext ReadContextFunc + UpdateContext UpdateContextFunc + DeleteContext DeleteContextFunc + CreateWithoutTimeout CreateContextFunc + ReadWithoutTimeout ReadContextFunc + UpdateWithoutTimeout UpdateContextFunc + DeleteWithoutTimeout DeleteContextFunc + CustomizeDiff CustomizeDiffFunc + Importer *ResourceImporter + DeprecationMessage string + Timeouts *ResourceTimeout + Description string + UseJSONNumber bool +} +``` + +## Framework + +In the Framework, you define your provider's resources by adding them to your provider's `Resources` method. + +The `Resources` method on your `provider.Provider` returns a slice of functions that return types that +implement the `resource.Resource` interface for each resource your provider supports. + +The following code shows how you add a resource to your provider with the Framework. + +```go +func (p *provider) Resources(ctx context.Context) []func() resource.Resource { + return []func() resource.Resource{ + func() resource.Resource { + return resourceTypeExample{} + }, + } +} +``` + +The `resource.Resource` interface requires `Metadata`, `Schema`, `Create`, `Read`, `Update`, and `Delete` methods. + +The `Schema` method returns a `schema.Schema` struct which defines your resource's attributes. + +The `Metadata` method returns a type name that you define. + +The following code shows how you define a `resource.Resource` which implements these methods with the +Framework. + +```go +type resourceExample struct{} + +func (r *resourceExample) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + /* ... */ +} + +func (r *resourceExample) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + /* ... */ +} + +func (r *resourceExample) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + /* ... */ +} + +func (r *resourceExample) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + /* ... */ +} + +func (r *resourceExample) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + /* ... */ +} + +func (r *resourceExample) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + /* ... */ +} +``` + +Refer to the [Resources - CRUD functions](/terraform/plugin/framework/migrating/resources/crud) page in this guide to learn how to +implement the `resource.Resource` interface. + +## Migration Notes + +Remember the following differences between SDKv2 and the Framework when completing the migration. + +- SDKv2 uses `schema.Resource` structs to define resources. These structs have a `Schema` field which holds a +`schema.Schema` to define the resource's attributes and behavior. In the Framework, you define a type that implements +the `resource.Resource` interface, which includes a `Schema` method that returns your resource's schema. +- SDKv2 implements a resource's CRUD operations as functions on the `schema.Resource`. In the Framework, you define a +type that implements the `resource.Resource` interface. The resource interface contains the functions that define your resource's +CRUD operations. +- SDKv2 by default demotes certain resource schema definition and data consistency errors to only be visible as Terraform warning logs. After migration, these errors will always be visible to practitioners and prevent further Terraform operations. The [SDKv2 resource data consistency errors documentation](/terraform/plugin/sdkv2/resources/data-consistency-errors) discusses how to find these errors in SDKv2 resources and potential solutions **prior** to migrating. See the [CRUD - Resolving Data Consistency Errors](/terraform/plugin/framework/migrating/resources/crud#resolving-data-consistency-errors) section for Plugin Framework solutions **during** migration. + +## Example + +### SDKv2 + +In SDKv2, the `ResourcesMap` field on the `schema.Provider` struct holds a `map[string]*schemaResource`. A typical +pattern is to implement a function that returns `schema.Resource`. + +```go +func New() (*schema.Provider, error) { + return &schema.Provider { + ResourcesMap: map[string]*schema.Resource { + "example_resource": exampleResource(), + /* ... */ +``` + +This code defines the `example_resource` resource by mapping the resource name to the `exampleResource` struct. + +```go +func exampleResource() *schema.Resource { + return &schema.Resource{ + CreateContext: createResource, + DeleteContext: deleteResource, + ReadContext: readResource, + + Schema: map[string]*schema.Schema{ + "attribute": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{'a', 'b'}, false)), + }, + /* ... */ +``` + +### Framework + +The following shows the same section of provider code after the migration. + +```go +func (p *exampleProvider) Resources(_ context.Context) []func() resource.Resource { + return []func() resource.Resource{ + func() resource.Resource { + return &exampleResource{} + }, + /* ... */ + } +} +``` + +This code defines the `Schema` and `Metadata` methods for the `Resource`. + +```go +func (r *exampleResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = "example_resource" +} + +func (r *exampleResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + // Required attributes + "attribute": schema.StringAttribute{ + Required: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + Validators: []validator.String{ + stringvalidator.OneOf([]string{"a", "b"}...), + }, + }, + /* ... */ + }, + } +} +``` diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/migrating/resources/plan-modification.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/migrating/resources/plan-modification.mdx new file mode 100644 index 000000000..af8241279 --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/migrating/resources/plan-modification.mdx @@ -0,0 +1,182 @@ +--- +page_title: Plan modification +description: >- + Learn how to migrate resource CustomizeDiff functions in SDKv2 to + plan modifiers in the Terraform plugin framework. +--- + +# Plan modification + +Your provider can modify the Terraform plan to match the expected end state. This can include replacing unknown values +with expected known values or marking a resource that must be replaced. Refer to +[Plan Modification](/terraform/plugin/framework/resources/plan-modification) in the Framework documentation for details. + +This page explains how to migrate resource `CustomizeDiff` functions in SDKv2 to `PlanModifiers` in the plugin +Framework. + +## SDKv2 +In SDKv2, plan modification is implemented with the `CustomizeDiff` field on the `schema.Resource` struct. The following +code shows a basic implementation of plan modification with SDKv2. + +```go +func resourceExample() *schema.Resource { + return &schema.Resource{ + CustomizeDiff: CustomizeDiffFunc, + /* ... */ +``` + +## Framework + +In the Framework, you implement plan modification either by implementing the `ResourceWithModifyPlan` interface on your +resource type, or by implementing `PlanModifiers` on individual attributes. This page demonstrates how to implement the +plan modifiers on individual attributes. Refer to +[Attributes - Force New](/terraform/plugin/framework/migrating/attributes-blocks/force-new) in this guide for further information on how +to implement a plan modifier on an attribute. + +The `ResourceWithModifyPlan` interface requires a `ModifyPlan` function. + +The following code shows how you can implement the `ModifyPlan` function on your `resource.Resource` type. + +```go +func (r *resourceExample) ModifyPlan(ctx context.Context, req resource.ModifyPlanRequest, resp *resource.ModifyPlanResponse) { + /* ... */ +} +``` +## Migration Notes +Remember the following differences between SDKv2 and the Framework when completing the migration. + +- In SDKv2, you implement plan modification with the `CustomizeDiff` field on the `schema.Resource` struct. In the +Framework, you can either implement plan modification for the entire resource by implementing the +`ResourceWithModifyPlan` interface, or on individual attributes by adding `PlanModifiers` to your resource attributes. +- Many existing CustomizeDiff implementations may be better suited to implementation as attribute plan modifiers in the +Framework. + +## Example + +### SDKv2 + +In SDKv2, the `CustomizeDiff` field on the `schema.Resource` struct refers to a function or set of functions that +implement plan modification. + +The following example shows the use of `CustomizeDiff` to keep two attributes +synchronized (i.e., ensure that they contain the same value) with SDKv2. + +```go +func resourcePassword() *schema.Resource { + /* ... */ + customizeDiffFuncs = append(customizeDiffFuncs, planSyncIfChange("attribute_one", "attribute_two")) + customizeDiffFuncs = append(customizeDiffFuncs, planSyncIfChange("attribute_two", "attribute_one")) + + return &schema.Resource{ + /* ... */ + CustomizeDiff: customdiff.All( + customizeDiffFuncs..., + ), + } +} +``` + +The following example shows the implementation of the `planSyncIfChange` function. + +```go +func planSyncIfChange(key, keyToSync string) func(context.Context, *schema.ResourceDiff, interface{}) error { + return customdiff.IfValueChange( + key, + func(ctx context.Context, oldValue, newValue, meta interface{}) bool { + return oldValue != newValue + }, + func(_ context.Context, d *schema.ResourceDiff, _ interface{}) error { + return d.SetNew(keyToSync, d.Get(key)) + }, + ) +} +``` + +### Framework + +Many existing `CustomizeDiff` implementations would be better suited to migration to attribute plan modifiers in the +Framework. This code shows the implementation using attribute plan modifiers with the Framework. + +```go +func exampleSchema() schema.Schema { + return schema.Schema{ + /* ... */ + Attributes: map[string]schema.Attribute{ + /* ... */ + "attribute_one": schema.BoolAttribute{ + /* ... */ + PlanModifiers: []planmodifier.Bool{ + planmodifiers.SyncAttributePlanModifier(), + /* ... */ + }, + }, + + "attribute_two": schema.BoolAttribute{ + /* ... */ + PlanModifiers: []planmodifier.Bool{ + planmodifiers.SyncAttributePlanModifier(), + /* ... */ + }, + }, +``` + +The following shows an implementation of `SyncAttributePlanModifier` in the Framework. + +```go +func SyncAttributePlanModifier() planmodifier.Bool { + return &syncAttributePlanModifier{} +} + +type syncAttributePlanModifier struct { +} + +func (d *syncAttributePlanModifier) Description(ctx context.Context) string { + return "Ensures that attribute_one and attribute_two attributes are kept synchronised." +} + +func (d *syncAttributePlanModifier) MarkdownDescription(ctx context.Context) string { + return d.Description(ctx) +} + +func (d *syncAttributePlanModifier) PlanModifyBool(ctx context.Context, req planmodifier.BoolRequest, resp *planmodifier.BoolResponse) { + var attributeOne types.Bool + diags := req.Plan.GetAttribute(ctx, path.Root("attribute_one"), &attributeOne) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + var attributeTwo types.Bool + req.Plan.GetAttribute(ctx, path.Root("attribute_two"), &attributeTwo) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + if !attributeOne.IsNull() && !attributeTwo.IsNull() && (attributeOne.ValueBool() != attributeTwo.ValueBool()) { + resp.Diagnostics.AddError( + "attribute_one and attribute_two are both configured with different values", + "attribute_one is deprecated, use attribute_two instead", + ) + return + } + + // Default to true for both attribute_one and attribute_two when both are null. + if attributeOne.IsNull() && attributeTwo.IsNull() { + resp.PlanValue = types.BoolValue(true) + return + } + + // Default to using value for attribute_two if attribute_one is null + if attributeOne.IsNull() && !attributeTwo.IsNull() { + resp.PlanValue = numericConfig + return + } + + // Default to using value for attribute_one if attribute_two is null + if !attributeOne.IsNull() && attributeTwo.IsNull() { + resp.PlanValue = numberConfig + return + } +} +``` diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/migrating/resources/state-upgrade.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/migrating/resources/state-upgrade.mdx new file mode 100644 index 000000000..7b5c05b33 --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/migrating/resources/state-upgrade.mdx @@ -0,0 +1,158 @@ +--- +page_title: State upgrading +description: >- + Learn how to Migrate resource StateUpgraders in SDKv2 to UpgradeState in the + plugin framework. State upgraders let users update resources provisioned with + old schema configurations. +--- + +# State upgraders + +When you update a resource's implementation in your provider, some changes may not be compatible with old versions. You +can create state upgraders to automatically migrate resources provisioned with old schema configurations. Refer to +[State Upgrade](/terraform/plugin/framework/resources/state-upgrade) in the Framework documentation for details. + +This page explains how to migrate resource `StateUpgraders` in SDKv2 to `UpgradeState` in the plugin Framework. + +## SDKv2 + +In SDKv2, state upgraders are defined by populating the `StateUpgraders` field on the `schema.Resource` struct. Refer +to [State Migration](/terraform/plugin/sdkv2/resources/state-migration) in the SDKv2 documentation for details. + +The following code shows a basic implementation of the `stateUpgraders` field in SDKv2. + +```go +func resourceExample() *schema.Resource { + return &schema.Resource{ + StateUpgraders: []schema.StateUpgrader{ + { + Version: int, + Type: cty.Type, + Upgrade: StateUpgradeFunc, + }, + /* ... */ +``` + +## Framework + +In the Framework, you implement the `ResourceWithUpgradeState` interface on your resource to upgrade your +resource's state when required. + +The following code shows how you define an `UpgradeState` function with the Framework. + +```go +func (r *resourceExample) UpgradeState(context.Context) map[int64]resource.StateUpgrader { + return map[int64]resource.StateUpgrader{ + 0: { + PriorSchema: *schema.Schema, + StateUpgrader: func(context.Context, UpgradeStateRequest, *UpgradeStateResponse), + }, + /* ... */ +``` + +The `UpgradeState` function returns a map from state versions to structs that implement state upgrade from the given +version to the latest version. + +## Migration Notes + +Remember the following differences between SDKv2 and the Framework when completing the migration. + +- In SDKv2, you implement state upgraders populating the `StateUpgraders` field on the `schema.Resource` struct. In the +Framework, you define an `UpgradeState` function on the resource itself. +- In SDKv2, state upgraders apply each state upgrader in turn. For example, version 0 => version 1, version 1 => +version 2. In the Framework, each `UpgradeState` function is required to perform all of the necessary transformations in +a single step. For example, version 0 => version 2, version 1 => version 2. + +## Example + +### SDKv2 + +In SDKv2 the `schema.Resource` struct has a `StateUpgraders` field that holds `[]schema.StateUpgrader` struct(s). + +The following example from the shows the state upgrade functions for the `example_resource` +resource with SDKv2. + +```go +func exampleResource() *schema.Resource { + return &schema.Resource{ + Schema: exampleSchemaV2(), + SchemaVersion: 2, + StateUpgraders: []schema.StateUpgrader{ + { + Version: 0, + Type: exampleResourceV0().CoreConfigSchema().ImpliedType(), + Upgrade: exampleResourceStateUpgradeV0, + }, + { + Version: 1, + Type: exampleResourceV1().CoreConfigSchema().ImpliedType(), + Upgrade: exampleResourceStateUpgradeV1, + }, + }, + /* ... */ +``` + +The following example shows the implementation of the `exampleResourceStateUpgradeV0` function with SDKv2. + +```go +func exampleResourceStateUpgradeV0(_ context.Context, rawState map[string]interface{}, _ interface{}) (map[string]interface{}, error) { + if rawState == nil { + return nil, fmt.Errorf("example resource state upgrade failed, state is nil") + } + + rawState["example_attribute"] = "value" + + return rawState, nil +} +``` + +### Framework + +The following shows the same section of provider code after the migration. + +This code implements the `ResourceWithUpgradeState` interface on the `exampleResource` type by defining an +`UpgradeState` function. The `UpgradeState` function returns a map from each state version (int64) to a +`ResourceStateUpgrader` struct. + +```go +func (r *exampleResource) UpgradeState(context.Context) map[int64]resource.StateUpgrader { + schemaV0 := exampleSchemaV0() + schemaV1 := exampleSchemaV1() + + return map[int64]resource.StateUpgrader{ + 0: { + PriorSchema: &schemaV0, + StateUpgrader: upgradeExampleResourceStateV0toV2, + }, + 1: { + PriorSchema: &schemaV1, + StateUpgrader: upgradeExampleResourceStateV1toV2, + }, + } +} +``` + +This code implements the `upgradeExampleResourceStateV0toV2` state upgrade function. + +```go +func upgradeExampleResourceStateV0toV2(ctx context.Context, req resource.UpgradeStateRequest, resp *resource.UpgradeStateResponse) { + type modelV0 struct { + ID types.String `tfsdk:"id"` + } + + var exampleDataV0 modelV0 + + resp.Diagnostics.Append(req.State.Get(ctx, &exampleDataV0)...) + if resp.Diagnostics.HasError() { + return + } + + exampleDataV2 := exampleModelV2{ + ID: exampleDataV0.ID, + } + + exampleDataV2.ExampleAttribute = types.StringValue("value") + + diags := resp.State.Set(ctx, exampleDataV2) + resp.Diagnostics.Append(diags...) +``` diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/migrating/resources/timeouts.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/migrating/resources/timeouts.mdx new file mode 100644 index 000000000..a7a8d76c2 --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/migrating/resources/timeouts.mdx @@ -0,0 +1,136 @@ +--- +page_title: Timeouts +description: >- + Learn how to migrate timeouts from SDKv2 to the framework. +--- + +# Timeouts + +The Framework can be used in conjunction with the [terraform-plugin-framework-timeouts](https://github.com/hashicorp/terraform-plugin-framework-timeouts) module in order to allow defining timeouts in configuration and have them be available in CRUD functions. + +## Specifying Timeouts in Configuration + +Timeouts can be defined using either nested blocks or nested attributes. + +If you are writing a new provider using [terraform-plugin-framework](https://github.com/hashicorp/terraform-plugin-framework) +then we recommend using nested attributes. + +If you are migrating a provider from SDKv2 to the Framework and +you are already using timeouts you can either continue to use block syntax, or switch to using nested attributes. +However, switching to using nested attributes will require that practitioners that are using your provider update their +Terraform configuration. + +#### Block + +If your configuration is using a nested block to define timeouts, such as the following: + +```hcl +resource "timeouts_example" "example" { + /* ... */ + + timeouts { + create = "60m" + } +} +``` + +Import the [timeouts module](https://github.com/hashicorp/terraform-plugin-framework-timeouts). + +```go +import ( + /* ... */ + "github.com/hashicorp/terraform-plugin-framework-timeouts/resource/timeouts" +) +```` + +You can use this module to mutate the `schema.Schema` as follows: + +```go +func (t *exampleResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + /* ... */ + + Blocks: map[string]schema.Block{ + "timeouts": timeouts.Block(ctx, timeouts.Opts{ + Create: true, + }), + }, +``` + +#### Attribute + +If your configuration is using nested attributes to define timeouts, such as the following: + +```hcl +resource "timeouts_example" "example" { + /* ... */ + + timeouts = { + create = "60m" + } +} +``` + +You can use this module to mutate the `schema.Schema` as follows: + +```go +func (t *exampleResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + /* ... */ + "timeouts": timeouts.Attributes(ctx, timeouts.Opts{ + Create: true, + }), + }, +``` + +## Updating Models + +In functions in which the config, state or plan is being unmarshalled, for instance, the `Create` function, the model +will need to be updated. + +```go +func (e *exampleResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + var data exampleResourceData + + diags := req.Plan.Get(ctx, &data) + resp.Diagnostics.Append(diags...) +``` + +Modify the `exampleResourceData` model to include a field for timeouts using a [`timeouts.Value`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework-timeouts/resource/timeouts#Value) type. + +```go +type exampleResourceData struct { + /* ... */ + Timeouts timeouts.Value `tfsdk:"timeouts"` +``` + +## Accessing Timeouts in CRUD Functions + +Once the model has been populated with the config, state or plan the duration of the timeout can be accessed by calling +the appropriate helper function (e.g., [`timeouts.Create`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework-timeouts/resource/timeouts#Value.Create)) and then used to configure timeout behaviour, for instance: + +```go +func (e *exampleResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + var data exampleResourceData + + diags := req.Plan.Get(ctx, &data) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + createTimeout, diags := data.Timeouts.Create(ctx, 20*time.Minute) + + resp.Diagnostics.Append(diags...) + + if resp.Diagnostics.HasError() { + return + } + + ctx, cancel := context.WithTimeout(ctx, createTimeout) + defer cancel() + + /* ... */ +} +``` diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/migrating/schema/index.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/migrating/schema/index.mdx new file mode 100644 index 000000000..5afd215f2 --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/migrating/schema/index.mdx @@ -0,0 +1,140 @@ +--- +page_title: Migrating schema +description: >- + Learn how to migrate schema from SDKv2 to the plugin framework. +--- + +# Migrating schema + +Providers, resources, and data sources all use schema to define their attributes and behavior. Schemas specify the +constraints of Terraform configuration blocks and how the provider, resource, or data source behaves. Refer to +[Schemas](/terraform/plugin/framework/handling-data/schemas) in the Framework documentation for details. + +This page explains the differences between the schema used by SDKv2 and the Framework. We also recommend reviewing these additional schema guides throughout the migration: +/attributes-blocks/ +- [Attributes](/terraform/plugin/framework/migrating/attributes-blocks/attribute-schema) where the schema defines practitioner or provider data associated with a value and type. +- [Attribute types](/terraform/plugin/framework/migrating/attributes-blocks/types) where the schema defines the expected data structure and syntax. +- [Attribute fields](/terraform/plugin/framework/migrating/attributes-blocks/fields) where the behaviors of an attribute are defined, such as `Required`, `Optional`, `Computed`, and `Sensitive`. +- [Attribute defaults](/terraform/plugin/framework/migrating/attributes-blocks/default-values) where the schema defines a value for an attribute which should be automatically included in a Terraform plan if it is not configured by the practitioner. +- [Attributes without in-place updates](/terraform/plugin/framework/migrating/attributes-blocks/force-new) where the schema defines an attribute that requires resource replacement if the value is updated. +- [Attribute predefined validations](/terraform/plugin/framework/migrating/attributes-blocks/validators-predefined) and [custom validations](/terraform/plugin/framework/migrating/attributes-blocks/validators-custom) where the schema defines the syntax, constraints, or encoding expectations of a value. +- [Blocks](/terraform/plugin/framework/migrating/attributes-blocks/attribute-schema) and [computed blocks](/terraform/plugin/framework/migrating/attributes-blocks/blocks-computed) where the schema defines structural configuration sections of data, typically with nested attributes or further blocks. + + +## Schema Structs + +SDKv2 uses `schema.Schema` structs to define the structure, type, and behavior of values drawn from configuration, +state, or plan data. The same `schema.Schema` struct type is used for providers, resources, and data sources. The +schema struct is returned by the function that creates the provider, resource, or data source in question. + +The Framework uses `schema.Schema` structs for providers, resources, and data sources. The schema struct is returned by +a `Schema` method you define for the provider and each resource type and data source type. Refer to +[Framework](#framework) for details. + +## SDKv2 + +The following code shows basic implementations using `schema.Schema` structs to define schemas for providers, resources, +and data sources with SDKv2. + +```go +func New() *schema.Provider { + return &schema.Provider{ + Schema: map[string]*schema.Schema{}, + /* ... */ + } +} +``` + +```go +func resourceExample() *schema.Resource { + return &schema.Resource{ + Schema: map[string]*schema.Schema{}, + /* ... */ + } +} +``` + +```go +func dataSourceExample() *schema.Resource { + return &schema.Resource{ + Schema: map[string]*schema.Schema{}, + /* ... */ + } +} +``` + +SDKv2 defines the `schema.Schema` struct as follows. + +```go +type Schema struct { + Type ValueType + ConfigMode SchemaConfigMode + Required bool + Optional bool + Computed bool + ForceNew bool + DiffSuppressFunc SchemaDiffSuppressFunc + DiffSuppressOnRefresh bool + Default interface{} + DefaultFunc SchemaDefaultFunc + Description string + StateFunc SchemaStateFunc + Elem interface{} + MaxItems int + MinItems int + Set SchemaSetFunc + ConflictsWith []string + ExactlyOneOf []string + AtLeastOneOf []string + RequiredWith []string + Deprecated string + ValidateFunc SchemaValidateFunc + ValidateDiagFunc SchemaValidateDiagFunc + Sensitive bool +} +``` + +## Framework + +In the Framework, you implement `Schema` method for your provider, resources, and data sources. This function is +required by the `provider.Provider`, `resource.Resource`, and `datasource.DataSource` interfaces, respectively. + +The following code shows how you define the `Schema` method for your provider, resources, and data sources. + +```go +func (p *ExampleCloudProvider) Schema(ctx context.Context, req provider.SchemaRequest, resp *provider.SchemaResponse) { + resp.Schema = schema.Schema{/* ... */} +} +``` + +```go +func (r *resourceExample) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{/* ... */} +} +``` + +```go +func (r *dataSourceExample) Schema(ctx context.Context, req datasource.SchemaRequest, resp *datasource.SchemaResponse) { + resp.Schema = schema.Schema{/* ... */} +} +``` + +You use the `Attributes` field to define attributes for your provider, resources, and data sources. You use the +`Blocks` field to define named blocks. + +## Migration Notes + +Remember the following differences between SDKv2 and the Framework when completing the migration. + +- SDKv2 uses `schema.Schema` structs to define the provider, resources, and data sources. The Framework uses concept-specific +`schema.Schema` structs instead. +- In SDKv2, schema structs are returned when a provider, resource, or data type is created. In the Framework, the +provider and each resource and data type have a `Schema` method that returns the schema. +- In SDKv2, schema structs have a `Set` field which can be populated with a `SchemaSetFunc` which is used for hashing. +In the Framework, this is not required and does not need to be migrated. +- The `schema.Schema` struct includes fields that you use to define +[attributes](/terraform/plugin/framework/migrating/attributes-blocks/attribute-schema) and +[blocks](/terraform/plugin/framework/migrating/attributes-blocks/blocks) for your provider and each resource +and data source. +- When you populate the `Version` field in `schema.Schema` for a resource in the Framework, copy the `Version` +field in `schema.Schema` from the SDKv2 version of that resource. diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/migrating/testing.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/migrating/testing.mdx new file mode 100644 index 000000000..79eb6ea74 --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/migrating/testing.mdx @@ -0,0 +1,230 @@ +--- +page_title: Testing migration +description: >- + Learn how to write tests that verify that migrating from SDKv2 to the + Framework does not affect provider behavior. +--- + +# Testing + +During migration, you should [write tests](#testing-migration) to verify that the behaviour of your provider has not +been altered by the migration itself. You will also need to [update](#provider-factories) your tests too. + + + + If [muxing](/terraform/plugin/framework/migrating/mux), only migrated resources and data sources require immediate updates of testing code. + + + +## Testing Migration + +During migration, we recommend writing tests to verify that switching from SDKv2 to the Framework has not affected your +provider's behavior. These tests use identical configuration. The first test step applies a plan and generates state +with the SDKv2 version of the provider. The second test step generates a plan with the Framework version of the provider +and verifies that the plan is a no-op, indicating that migrating to the framework has not altered behaviour. + +Use the `ExternalProviders` field within a `resource.TestStep` to specify the configuration of a specific provider to +use during each test step. You can specify a version of the provider built on SDKv2 during the first test step, and +then you can use the version of the provider built on the Framework in subsequent test step(s) to verify that Terraform +CLI does not detect any planned changes. + +You must also update the [provider factories](#provider-factories) to use +the Framework. + +### Example + +These examples show how you can use external providers to generate state with a previous version of the provider +and then verify that there are no planned changes after migrating to the Framework. + +- The first `TestStep` uses `ExternalProviders` to cause `terraform apply` to execute with a previous version of the +provider, which is built on SDKv2. +- The second `TestStep` uses `ProtoV5ProviderFactories` so that the test uses the provider code contained within the +provider repository. The second step uses `ConfigPlanChecks` to verify that a no-op plan is generated. + +#### Managed Resource + +In this example configuration, all managed resource attribute values are compared between the SDK and Framework implementations: + +```go +func TestResource_UpgradeFromVersion(t *testing.T) { + /* ... */ + resource.Test(t, resource.TestCase{ + Steps: []resource.TestStep{ + { + ExternalProviders: map[string]resource.ExternalProvider{ + "": { + VersionConstraint: "", + Source: "hashicorp/", + }, + }, + Config: `resource "provider_resource" "example" { + /* ... */ + }`, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("provider_resource.example", "", ""), + /* ... */ + ), + }, + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "provider_resource" "example" { + /* ... */ + }`, + // ConfigPlanChecks is a terraform-plugin-testing feature. + // If acceptance testing is still using terraform-plugin-sdk/v2, + // use `PlanOnly: true` instead. When migrating to + // terraform-plugin-testing, switch to `ConfigPlanChecks` or you + // will likely experience test failures. + ConfigPlanChecks: resource.ConfigPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectEmptyPlan(), + }, + }, + }, + }, + }) +} +``` + +#### Data Source + + + +Prefer individually testing all attribute values of a data source instead of this testing pattern. Testing individual attribute values will catch unexpected data handling changes after migration and prevent any expected introduction of new attributes from causing test failures when using this pattern. + + + +Since data sources are refreshed every Terraform plan and do not use prior state during planning, additional configuration of a separate managed resource or output value is required to track value differences. The [`terraform_data` managed resource](/terraform/language/resources/terraform-data) is one option that is implicitly available for configurations in Terraform 1.4 and later. If testing on older versions of Terraform is required, use the [`null_resource` managed resource](https://registry.terraform.io/providers/hashicorp/null/latest/docs/resources/resource) with an associated `ExternalProviders` step configuration instead. + +In this example configuration, all data source attribute values are compared between the SDK and Framework implementations: + +```go +func TestDataSource_UpgradeFromVersion(t *testing.T) { + /* ... */ + resource.Test(t, resource.TestCase{ + Steps: []resource.TestStep{ + { + ExternalProviders: map[string]resource.ExternalProvider{ + "": { + VersionConstraint: "", + Source: "hashicorp/", + }, + }, + Config: `data "provider_datasource" "test" { + /* ... */ + } + + resource "terraform_data" "test" { + input = data.provider_datasource.test + }`, + }, + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `data "provider_datasource" "test" { + /* ... */ + } + + resource "terraform_data" "test" { + input = data.provider_datasource.test + }`, + // ConfigPlanChecks is a terraform-plugin-testing feature. + // If acceptance testing is still using terraform-plugin-sdk/v2, + // use `PlanOnly: true` instead. When migrating to + // terraform-plugin-testing, switch to `ConfigPlanChecks` or you + // will likely experience test failures. + ConfigPlanChecks: resource.ConfigPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectEmptyPlan(), + }, + }, + }, + }, + }) +} +``` + +## Provider Factories + +Existing tests should require minimal updates when migrating from SDKv2 to the Framework. The only critical change +relates to the provider factories that create the provider during the test case. Refer to [Acceptance Tests - Specify Providers](/terraform/plugin/framework/acctests#specify-providers) in the Framework +documentation for details. + +We also recommend writing tests that verify that switching from SDKv2 to the Framework has not affected provider +behavior. Refer to [Testing Migration](#testing-migration) for details. + +### SDKv2 + +In SDKv2, you use the `ProviderFactories` field on the `resource.TestCase` struct to obtain `schema.Provider`. + +The following example shows a test written in SDKv2. + +```go +resource.UnitTest(t, resource.TestCase{ + ProviderFactories: testProviders(), +``` + +### Framework + +In the Framework, use either the `ProtoV5ProviderFactories` or `ProtoV6ProviderFactories` field on the +`resource.TestCase` struct to obtain the `provider.Provider`, depending on the +[Terraform Plugin Protocol](/terraform/plugin/how-terraform-works#terraform-plugin-protocol) version your provider is using. + +The following example shows how you can write a test in the Framework for a Provider that uses protocol version 6. + +```go +resource.UnitTest(t, resource.TestCase{ + ProtoV6ProviderFactories: protoV6ProviderFactories(), +``` + +### Example + +#### SDKv2 + +The following code sample shows how to define provider factories within a test case when using SDKv2. + +```go +func TestDataSource_Exmple(t *testing.T) { + /* ... */ + resource.UnitTest(t, resource.TestCase{ + ProviderFactories: testProviders(), + /* ... */ + }) +} +``` + +The following shows how to generate provider factories when using SDKv2. + +```go +func testProviders() map[string]func() (*schema.Provider, error) { + return map[string]func() (*schema.Provider, error){ + "example": func() (*schema.Provider, error) { return New(), nil }, + } +} +``` + +#### Framework + +The following shows how to define provider factories within a test case when using the Framework. + +```go +func TestDataSource_Example(t *testing.T) { + /* ... */ + + resource.UnitTest(t, resource.TestCase{ + ProtoV5ProviderFactories: protoV5ProviderFactories(), + /* ... */ + }) +} +``` + +The following shows how to generate provider factories when using +the Framework. The call to `New` returns an instance of the provider. Refer to +[Provider Definition](/terraform/plugin/framework/migrating/providers#provider-definition) in this guide for details. + +```go +func protoV5ProviderFactories() map[string]func() (tfprotov5.ProviderServer, error) { + return map[string]func() (tfprotov5.ProviderServer, error){ + "example": providerserver.NewProtocol5WithError(New()), + } +} +``` diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/provider-servers.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/provider-servers.mdx new file mode 100644 index 000000000..a8d07bd5e --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/provider-servers.mdx @@ -0,0 +1,88 @@ +--- +page_title: Provider servers +description: >- + Learn how to implement a provider server in the Terraform plugin + framework. Provider servers are plugins that allow Terraform to interact with + APIs. +--- + +# Provider servers + +Before a [provider](/terraform/plugin/framework/providers) can be used with Terraform, it must implement a [gRPC server](https://grpc.io) that supports Terraform-specific connection and handshake handling on startup. The server must then implement the [Terraform Plugin Protocol](/terraform/plugin/how-terraform-works#terraform-plugin-protocol). + +The framework handles the majority of the server implementation details, however it is useful from a provider developer perspective to understand the provider server details at least at a high level. + +## Protocol Version + +The [Terraform Plugin Protocol](/terraform/plugin/how-terraform-works#terraform-plugin-protocol) defines the compatibility between Terraform CLI and the underlying provider. It is versioned, with newer versions implementing support for enhanced provider functionality but also requiring newer Terraform CLI versions. The framework implements two versions of the protocol. + +* **Version 6**: The latest and recommended version, [protocol version 6](/terraform/plugin/how-terraform-works#protocol-version-6) implements enhanced provider functionality and requires Terraform CLI 1.0 or later. If [combining providers with terraform-plugin-sdk provider code](/terraform/plugin/mux/combining-protocol-version-6-providers), such as [migrating to the framework](/terraform/plugin/framework/migrating), then Terraform CLI 1.1 or later is required. +* **Version 5**: The prior version, [protocol version 5](/terraform/plugin/how-terraform-works#protocol-version-5) implements base provider functionality and requires Terraform CLI 0.12 or later. + +Provider developers must choose either version 6 or version 5 and should consistently use that one version across implementations. + +## Implementations + +Terraform and provider developers have multiple ways to interact with the provider server implementation: + +* **Production**: Terraform CLI expects a binary that starts the provider server on process startup and stops the provider when called. +* **Developer Overrides Testing**: The [CLI configuration file](/terraform/cli/config/config-file#development-overrides-for-provider-developers) maps provider addresses to locally built binaries, which then operate similar to production. +* **Acceptance Testing**: The [acceptance testing framework](/terraform/plugin/framework/acctests) maps provider names to functions that directly start the provider server and will automatically stop the provider between test steps. +* **Debugging**: Provider developers, typically via a code editor or debugger tool, manually start a provider server for [debugging](/terraform/plugin/framework/debugging) which Terraform CLI is then configured to use rather than a normal binary. + +### Production and Developer Overrides + +Go language programs implement startup logic via a `main` function. Conventionally, this is done in a `main.go` file at the root of a project. The `main` function must eventually call the framework functionality for managing provider servers in the [`providerserver` package](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/providerserver). + +An example `main.go` file for starting a protocol version 6 provider server: + +```go +package main + +import ( + "context" + "flag" + "log" + + "github.com/example-namespace/terraform-provider-example/internal/provider" + "github.com/hashicorp/terraform-plugin-framework/providerserver" +) + +var ( + // Example version string that can be overwritten by a release process + version string = "dev" +) + +func main() { + opts := providerserver.ServeOpts{ + // TODO: Update this string with the published name of your provider. + Address: "registry.terraform.io/example-namespace/example", + } + + err := providerserver.Serve(context.Background(), provider.New(version), opts) + + if err != nil { + log.Fatal(err.Error()) + } +} +``` + +To configure the provider server for protocol version 5, set the [`providerserver.ServeOpts` type `ProtocolVersion` field](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/providerserver#ServeOpts.ProtocolVersion) to `5`: + +```go +opts := providerserver.ServeOpts{ + // TODO: Update this string with the published name of your provider. + Address: "registry.terraform.io/example-namespace/example", + ProtocolVersion: 5, +} +``` + +It is also possible to combine provider server implementations, such as migrating resources and data sources individually from [terraform-plugin-sdk/v2](/terraform/plugin/sdkv2) to the framework. This advanced use case would alter the `main.go` code further. Refer to the [Combining and Translating Providers](/terraform/plugin/mux) page for implementation details. + +### Acceptance Testing + +Refer to the [acceptance testing](/terraform/plugin/framework/acctests) page for implementation details. + +### Debugging + +Refer to the [debugging](/terraform/plugin/framework) page for implementation details. diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/providers/index.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/providers/index.mdx new file mode 100644 index 000000000..131047f55 --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/providers/index.mdx @@ -0,0 +1,346 @@ +--- +page_title: Providers +description: >- + Learn how to implement a provider in the Terraform plugin framework. + Providers, wrapped by a provider server, are plugins that allow Terraform to + interact with APIs. +--- + +# Providers + +Providers are Terraform plugins that define [resources](/terraform/plugin/framework/resources) and [data sources](/terraform/plugin/framework/data-sources) for practitioners to use. Providers are wrapped by a [provider server](/terraform/plugin/framework/provider-servers) for interacting with Terraform. + +This page describes the basic implementation details required for defining a provider. Further documentation is available for deeper provider concepts: + +- [Configure data sources](/terraform/plugin/framework/data-sources/configure) with provider-level data types or clients. +- [Configure resources](/terraform/plugin/framework/resources/configure) with provider-level data types or clients. +- [Configure ephemeral resources](/terraform/plugin/framework/ephemeral-resources/configure) with provider-level data types or clients. +- [Validate](/terraform/plugin/framework/providers/validate-configuration) practitioner configuration against acceptable values. + +## Define Provider Type + +Implement the [provider.Provider interface](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/provider#Provider). Each of the methods described in more detail below. + +In this example, a provider implementation is scaffolded: + +```go +// Ensure the implementation satisfies the provider.Provider interface. +var _ provider.Provider = &ExampleCloudProvider{} + +type ExampleCloudProvider struct{ + // Version is an example field that can be set with an actual provider + // version on release, "dev" when the provider is built and ran locally, + // and "test" when running acceptance testing. + Version string +} + +// Metadata satisfies the provider.Provider interface for ExampleCloudProvider +func (p *ExampleCloudProvider) Metadata(ctx context.Context, req provider.MetadataRequest, resp *provider.MetadataResponse) { + resp.TypeName = // provider specific implementation +} + +// Schema satisfies the provider.Provider interface for ExampleCloudProvider. +func (p *ExampleCloudProvider) Schema(ctx context.Context, req provider.SchemaRequest, resp *provider.SchemaResponse) { + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + // Provider specific implementation. + }, + } +} + +// Configure satisfies the provider.Provider interface for ExampleCloudProvider. +func (p *ExampleCloudProvider) Configure(ctx context.Context, req provider.ConfigureRequest, resp *provider.ConfigureResponse) { + // Provider specific implementation. +} + +// DataSources satisfies the provider.Provider interface for ExampleCloudProvider. +func (p *ExampleCloudProvider) DataSources(ctx context.Context) []func() datasource.DataSource { + return []func() datasource.DataSource{ + // Provider specific implementation + } +} + +// Resources satisfies the provider.Provider interface for ExampleCloudProvider. +func (p *ExampleCloudProvider) Resources(ctx context.Context) []func() resource.Resource { + return []func() resource.Resource{ + // Provider specific implementation + } +} +``` + +Conventionally, many providers also create a helper function named `New` which can simplify [provider server](/terraform/plugin/framework/provider-servers) implementations. + +```go +func New(version string) func() provider.Provider { + return func() provider.Provider { + return &ExampleCloudProvider{ + Version: version, + } + } +} +``` + +### Metadata Method + +The [`provider.Provider` interface `Metadata` method](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/provider#Provider.Metadata) defines information about the provider itself, such as its type name and version. This information is used to simplify creating data sources and resources. + +In this example, the provider type name is set to `examplecloud`: + +```go +func (p *ExampleCloudProvider) Metadata(ctx context.Context, req provider.MetadataRequest, resp *provider.MetadataResponse) { + resp.TypeName = "examplecloud" +} +``` + +### Schema Method + +The [`provider.Provider` interface `Schema` method](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/provider#Provider.Schema) defines a [schema](/terraform/plugin/framework/schemas) describing what data is available in the provider's configuration. This configuration block is used to offer practitioners the opportunity to supply values to the provider and configure its behavior, rather than needing to include those values in every resource and data source. It is usually used to gather credentials, endpoints, and the other data used to authenticate with the API, but it is not limited to those uses. + +During the [`terraform validate`](/terraform/cli/commands/validate), [`terraform plan`](/terraform/cli/commands/plan) and [`terraform apply`](/terraform/cli/commands/apply) commands, Terraform calls the provider [`GetProviderSchema`](/terraform/plugin/framework/internals/rpcs#getproviderschema-rpc) RPC, in which the framework calls the [`provider.Provider` interface `Schema` method](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/provider#Provider.Schema). + +In this example, a sample configuration and schema definition are provided: + +```go +// Example Terraform configuration: +// +// provider "examplecloud" { +// api_token = "v3rYs3cr3tt0k3n" +// endpoint = "https://example.com/" +// } + +func (p *ExampleCloudProvider) Schema(ctx context.Context, req provider.SchemaRequest, resp *provider.SchemaResponse) { + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "api_token": schema.StringAttribute{ + Optional: true, + }, + "endpoint": schema.StringAttribute{ + Optional: true, + }, + }, + } +} +``` + +If the provider does not accept practitioner Terraform configuration, leave the method defined, but empty. + +### Configure Method + +The [`provider.Provider` interface `Configure` method](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/provider#Provider.Configure) handles the configuration of any provider-level data or clients. These configuration values may be from the practitioner Terraform configuration, environment variables, or other means such as reading vendor-specific configuration files. + +During the [`terraform plan`](/terraform/cli/commands/plan) and [`terraform apply`](/terraform/cli/commands/apply) commands, Terraform calls the provider [`ConfigureProvider`](/terraform/plugin/framework/internals/rpcs#configureprovider-rpc) RPC, in which the framework calls the [`provider.Provider` interface `Configure` method](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/provider#Provider.Configure). + +This is the only chance the provider has to configure provider-level data or clients, so they need to be persisted if other data source or resource logic will need to reference them. Refer to the [Configure Data Sources](/terraform/plugin/framework/data-sources/configure) and [Configure Resources](/terraform/plugin/framework/resources/configure) pages for additional implementation details. + +If the logic needs to return [warning or error diagnostics](/terraform/plugin/framework/diagnostics), they can added into the [`provider.ConfigureResponse.Diagnostics` field](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/provider#ConfigureResponse.Diagnostics). + +In this example, the provider API token and endpoint are configured via environment variable or Terraform configuration: + +```go +type ExampleCloudProvider struct {} + +type ExampleCloudProviderModel struct { + ApiToken types.String `tfsdk:"api_token"` + Endpoint types.String `tfsdk:"endpoint"` +} + +func (p *ExampleCloudProvider) Schema(ctx context.Context, req provider.SchemaRequest, resp *provider.SchemaResponse) { + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "api_token": schema.StringAttribute{ + Optional: true, + }, + "endpoint": schema.StringAttribute{ + Optional: true, + }, + }, + } +} + +func (p *ExampleCloudProvider) Configure(ctx context.Context, req provider.ConfigureRequest, resp *provider.ConfigureResponse) { + // Check environment variables + apiToken := os.Getenv("EXAMPLECLOUD_API_TOKEN") + endpoint := os.Getenv("EXAMPLECLOUD_ENDPOINT") + + var data ExampleCloudProviderModel + + // Read configuration data into model + resp.Diagnostics.Append(req.Config.Get(ctx, &data)...) + + // Check configuration data, which should take precedence over + // environment variable data, if found. + if data.ApiToken.ValueString() != "" { + apiToken = data.ApiToken.ValueString() + } + + if data.Endpoint.ValueString() != "" { + endpoint = data.Endpoint.ValueString() + } + + if apiToken == "" { + resp.Diagnostics.AddError( + "Missing API Token Configuration", + "While configuring the provider, the API token was not found in "+ + "the EXAMPLECLOUD_API_TOKEN environment variable or provider "+ + "configuration block api_token attribute.", + ) + // Not returning early allows the logic to collect all errors. + } + + if endpoint == "" { + resp.Diagnostics.AddError( + "Missing Endpoint Configuration", + "While configuring the provider, the endpoint was not found in "+ + "the EXAMPLECLOUD_ENDPOINT environment variable or provider "+ + "configuration block endpoint attribute.", + ) + // Not returning early allows the logic to collect all errors. + } + + // Create data/clients and persist to resp.DataSourceData, resp.ResourceData, + // and resp.EphemeralResourceData as appropriate. +} +``` + +#### Unknown Values + +Not all values are guaranteed to be +[known](/terraform/plugin/framework/types#unknown) when `Configure` is called. +For example, if a practitioner interpolates a resource's unknown value into the block, +that value may show up as unknown depending on how the graph executes: + +```hcl +resource "random_string" "example" {} + +provider "examplecloud" { + api_token = random_string.example.result + endpoint = "https://example.com/" +} +``` + +In the example above, `random_string.example.result` is a read-only field on +`random_string.example` that won't be set until after `random_string.example` has been +applied. So the `Configure` method for the provider may report that the value +is unknown. You can choose how your provider handles this. If +some resources or data sources can be used without knowing that value, it may +be worthwhile to [emit a warning](/terraform/plugin/framework/diagnostics) and +check whether the value is set in resources and data sources before attempting +to use it. If resources and data sources can't provide any functionality +without knowing that value, it's often better to [return an +error](/terraform/plugin/framework/diagnostics), which will halt the apply. + +### Resources + +The [`provider.Provider` interface `Resources` method](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/provider#Provider.Resources) returns a slice of [resources](/terraform/plugin/framework/resources). Each element in the slice is a function to create a new `resource.Resource` so data is not inadvertently shared across multiple, disjointed resource instance operations unless explicitly coded. Information such as the resource type name is managed by the `resource.Resource` implementation. + +The [`provider.Provider` interface `Resources` method](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/provider#Provider.Resources) is called during execution of the [`terraform validate`](/terraform/cli/commands/validate), [`terraform plan`](/terraform/cli/commands/plan) and [`terraform apply`](/terraform/cli/commands/apply) commands when the [`ValidateResourceConfig`](/terraform/plugin/framework/internals/rpcs#validateconfig-rpcs), [`ReadResource`](/terraform/plugin/framework/internals/rpcs#read-rpcs), [`PlanResourceChange`](/terraform/plugin/framework/internals/rpcs#planresourcechange-rpc) and [`ApplyResourceChange`](/terraform/plugin/framework/internals/rpcs#applyresourcechange-rpc) RPCs are sent. + +In this example, the provider implements a single resource: + +```go +// With the provider.Provider implementation +func (p *ExampleCloudProvider) Resources(_ context.Context) []func() resource.Resource { + return []func() resource.Resource{ + NewThingResource, + } +} + +// With the resource.Resource implementation +func NewThingResource() resource.Resource { + return &ThingResource{} +} + +type ThingResource struct {} +``` + +Use Go slice techniques to include large numbers of resources outside the provider `Resources` method code. + +In this example, the provider codebase implements multiple "services" which group their own resources: + +```go +// With the provider.Provider implementation +func (p *ExampleCloudProvider) Resources(_ context.Context) []func() resource.Resource { + return []func() resource.Resource{ + servicex.Resources..., + servicey.Resources..., + } +} + +// With the servicex implementation +package servicex + +var Resources = []func() resource.Resource { + NewThingResource, + NewWidgetResource, +} + +func NewThingResource() resource.Resource { + return &ThingResource{} +} + +type ThingResource struct {} + +func NewWidgetResource() resource.Resource { + return &WidgetResource{} +} + +type WidgetResource struct {} +``` + +### DataSources + +The [`provider.Provider` interface `DataSources` method](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/provider#Provider.DataSources) returns a slice of [data sources](/terraform/plugin/framework/data-sources). Each element in the slice is a function to create a new `datasource.DataSource` so data is not inadvertently shared across multiple, disjointed datasource instance operations unless explicitly coded. Information such as the datasource type name is managed by the `datasource.DataSource` implementation. + +The [`provider.Provider` interface `DataSources` method](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/provider#Provider.DataSources) is called during execution of the [`terraform validate`](/terraform/cli/commands/validate), [`terraform plan`](/terraform/cli/commands/plan) and [`terraform apply`](/terraform/cli/commands/apply) commands when the [`ValidateDataResourceConfig`](/terraform/plugin/framework/internals/rpcs#validatedataresourceconfig-rpc) and [`ReadDataSource`](/terraform/plugin/framework/internals/rpcs#readdatasource-rpc) RPCs are sent. + +In this example, the provider implements a single data source: + +```go +// With the provider.Provider implementation +func (p *ExampleCloudProvider) DataSources(_ context.Context) []func() datasource.DataSource { + return []func() datasource.DataSource{ + NewThingDataSource, + } +} + +// With the datasource.DataSource implementation +func NewThingDataSource() datasource.DataSource { + return &ThingDataSource{} +} + +type ThingDataSource struct {} +``` + +Use Go slice techniques to include large numbers of data sources outside the provider `DataSources` method code. + +In this example, the provider codebase implements multiple "services" which group their own datasources: + +```go +// With the provider.Provider implementation +func (p *ExampleCloudProvider) DataSources(_ context.Context) []func() datasource.DataSource { + return []func() datasource.DataSource{ + servicex.DataSources..., + servicey.DataSources..., + } +} + +// With the servicex implementation +package servicex + +var DataSources = []func() datasource.DataSource { + NewThingDataSource, + NewWidgetDataSource, +} + +func NewThingDataSource() datasource.DataSource { + return &ThingDataSource{} +} + +type ThingDataSource struct {} + +func NewWidgetDataSource() datasource.DataSource { + return &WidgetDataSource{} +} + +type WidgetDataSource struct {} +``` diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/providers/validate-configuration.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/providers/validate-configuration.mdx new file mode 100644 index 000000000..f59330f1c --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/providers/validate-configuration.mdx @@ -0,0 +1,86 @@ +--- +page_title: Validate provider configuration +description: >- + Learn how to validate provider configurations with the Terraform plugin + framework. +--- + +# Validate provider configuration + +[Providers](/terraform/plugin/framework/providers) support validating an entire practitioner configuration in either declarative or imperative logic. Feedback, such as required syntax or acceptable combinations of values, is returned via [diagnostics](/terraform/plugin/framework/diagnostics). + +This page describes implementation details for validating entire provider configurations, typically referencing multiple attributes. Further documentation is available for other configuration validation concepts: + +- [Single attribute validation](/terraform/plugin/framework/validation#attribute-validation) is a schema-based mechanism for implementing attribute-specific validation logic. +- [Type validation](/terraform/plugin/framework/validation#type-validation) is a schema-based mechanism for implementing reusable validation logic for any attribute using the type. + +-> Configuration validation in Terraform occurs without provider configuration ("offline"), so therefore the provider `Configure` method will not have been called. To implement validation with a configured API client, use logic within the `Configure` method, which occurs during Terraform's planning phase. + +## ConfigValidators Method + +The [`provider.ProviderWithConfigValidators` interface](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/provider#ProviderWithConfigValidators) follows a similar pattern to attribute validation and allows for a more declarative approach. This enables consistent validation logic across multiple providers. Each validator intended for this interface must implement the [`provider.ConfigValidator` interface](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/provider#ConfigValidator). + +During execution of the [`terraform validate`](/terraform/cli/commands/validate), [`terraform plan`](/terraform/cli/commands/plan) and [`terraform apply`](/terraform/cli/commands/apply) commands, Terraform calls the provider [`ValidateProviderConfig`](/terraform/plugin/framework/internals/rpcs#validateproviderconfig-rpc) RPC, in which the framework calls the `ConfigValidators` method on providers that implement the [`provider.ProviderWithConfigValidators` interface](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/provider#ProviderWithConfigValidators). + +The [`terraform-plugin-framework-validators` Go module](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework-validators) has a collection of common use case provider configuration validators in the [`providervalidator` package](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework-validators/providervalidator). These use [path expressions](/terraform/plugin/framework/path-expressions) for matching attributes. + +This example will raise an error if a practitioner attempts to configure both `attribute_one` and `attribute_two`: + +```go +// Other methods to implement the provider.Provider interface are omitted for brevity +type ExampleCloudProvider struct {} + +func (p ExampleCloudProvider) ConfigValidators(ctx context.Context) []provider.ConfigValidator { + return []provider.ConfigValidator{ + providervalidator.Conflicting( + path.MatchRoot("attribute_one"), + path.MatchRoot("attribute_two"), + ), + } +} +``` + +## ValidateConfig Method + +The [`provider.ProviderWithValidateConfig` interface](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/provider#ProviderWithValidateConfig) is more imperative in design and is useful for validating unique functionality across multiple attributes that typically applies to a single provider. + +During execution of the [`terraform validate`](/terraform/cli/commands/validate), [`terraform plan`](/terraform/cli/commands/plan) and [`terraform apply`](/terraform/cli/commands/apply) commands, Terraform calls the provider [`ValidateProviderConfig`](/terraform/plugin/framework/internals/rpcs#validateproviderconfig-rpc) RPC, in which the framework calls the `ValidateConfig` method on providers that implement the [`provider.ProviderWithValidateConfig` interface](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/provider#ProviderWithValidateConfig). + +This example will raise a warning if a practitioner attempts to configure `attribute_one`, but not `attribute_two`: + +```go +// Other methods to implement the provider.Provider interface are omitted for brevity +type ExampleCloudProvider struct {} + +type ExampleCloudProviderModel struct { + AttributeOne types.String `tfsdk:"attribute_one"` + AttributeTwo types.String `tfsdk:"attribute_two"` +} + +func (p ExampleCloudProvider) ValidateConfig(ctx context.Context, req provider.ValidateConfigRequest, resp *provider.ValidateConfigResponse) { + var data ExampleCloudProviderModel + + resp.Diagnostics.Append(req.Config.Get(ctx, &data)...) + + if resp.Diagnostics.HasError() { + return + } + + // If attribute_one is not configured, return without warning. + if data.AttributeOne.IsNull() || data.AttributeOne.IsUnknown() { + return + } + + // If attribute_two is not null, return without warning. + if !data.AttributeTwo.IsNull() { + return + } + + resp.Diagnostics.AddAttributeWarning( + path.Root("attribute_two"), + "Missing Attribute Configuration", + "Expected attribute_two to be configured with attribute_one. "+ + "The provider may return unexpected results.", + ) +} +``` diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/resources/configure.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/resources/configure.mdx new file mode 100644 index 000000000..93dced950 --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/resources/configure.mdx @@ -0,0 +1,104 @@ +--- +page_title: Configure resources +description: >- + Learn how to implement resource configuration with provider or client data in + the Terraform plugin framework. +--- + +# Configure resources + +[Resources](/terraform/plugin/framework/resources) may require provider-level data or remote system clients to operate correctly. The framework supports the ability to configure this data and/or clients once within the provider, then pass that information to resources by adding the `Configure` method. + +## Prepare Provider Configure Method + +Implement the [`provider.ConfigureResponse.ResourceData` field](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/provider#ConfigureResponse.ResourceData) in the [`Provider` interface `Configure` method](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/provider#Provider.Configure). This value can be set to any type, whether an existing client or vendor SDK type, a provider-defined custom type, or the provider implementation itself. It is recommended to use pointer types so that resources can determine if this value was configured before attempting to use it. + +During execution of the [`terraform plan`](/terraform/cli/commands/plan) and [`terraform apply`](/terraform/cli/commands/apply) commands, Terraform calls the provider [`ConfigureProvider`](/terraform/plugin/framework/internals/rpcs#configureprovider-rpc) RPC, in which the framework calls the [`provider.Provider` interface `Configure` method](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/provider#Provider.Configure). + +In this example, the Go standard library [`net/http.Client`](https://pkg.go.dev/net/http#Client) is configured in the provider, and made available for resources: + +```go +// With the provider.Provider implementation +func (p *ExampleCloudProvider) Configure(ctx context.Context, req provider.ConfigureRequest, resp *provider.ConfigureResponse) { + resp.ResourceData = &http.Client{/* ... */} +} +``` + +In this example, the code defines an `ExampleClient` type that is made available for resources: + +```go +type ExampleClient struct { + /* ... */ +} + +// With the provider.Provider implementation +func (p *ExampleCloudProvider) Configure(ctx context.Context, req provider.ConfigureRequest, resp *provider.ConfigureResponse) { + resp.ResourceData = &ExampleClient{/* ... */} +} +``` + +In this example, the `ExampleCloudProvider` type itself is made available for resources: + +```go +// With the provider.Provider implementation +type ExampleCloudProvider struct { + /* ... */ +} + +func (p *ExampleCloudProvider) Configure(ctx context.Context, req provider.ConfigureRequest, resp *provider.ConfigureResponse) { + resp.ResourceData = p +} +``` + +## Define Resource Configure Method + +Implement the [`resource.ResourceWithConfigure` interface](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource#ResourceWithConfigure) which receives the provider configured data from the [`Provider` interface `Configure` method](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/provider#Provider.Configure) and saves it into the [`resource.Resource` interface](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource#Resource) implementation. + +The [`resource.ResourceWithConfigure` interface `Configure` method](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource#ResourceWithConfigure.Configure) is called during execution of the [`terraform validate`](/terraform/cli/commands/validate), [`terraform plan`](/terraform/cli/commands/plan) and [`terraform apply`](/terraform/cli/commands/apply) commands when the [`ValidateResourceConfig`](/terraform/plugin/framework/internals/rpcs#validateresourceconfig-rpc) RPC is sent. Additionally, the [`resource.ResourceWithConfigure` interface `Configure` method](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource#ResourceWithConfigure.Configure) is called during execution of the [`terraform plan`](/terraform/cli/commands/plan) and [`terraform apply`](/terraform/cli/commands/apply) commands when the [`ReadResource`](/terraform/plugin/framework/internals/rpcs#readresource-rpc) RPC is sent. + +-> Note that Terraform calling the [`ValidateResourceConfig`](/terraform/plugin/framework/internals/rpcs#validateresourceconfig-rpc) RPC would not call the [`ConfigureProvider`](/terraform/plugin/framework/internals/rpcs#configureprovider-rpc) RPC first, so implementations need to account for that situation. Configuration validation in Terraform occurs without provider configuration ("offline"). + +In this example, the provider configured the Go standard library [`net/http.Client`](https://pkg.go.dev/net/http#Client) which the resource uses during `Read`: + +```go +// With the resource.Resource implementation +type ThingResource struct { + client *http.Client +} + +func (r *ThingResource) Configure(ctx context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) { + // Always perform a nil check when handling ProviderData because Terraform + // sets that data after it calls the ConfigureProvider RPC. + if req.ProviderData == nil { + return + } + + client, ok := req.ProviderData.(*http.Client) + + if !ok { + resp.Diagnostics.AddError( + "Unexpected Resource Configure Type", + fmt.Sprintf("Expected *http.Client, got: %T. Please report this issue to the provider developers.", req.ProviderData), + ) + + return + } + + r.client = client +} + +func (r *ThingResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + // Prevent panic if the provider has not been configured. + if r.client == nil { + resp.Diagnostics.AddError( + "Unconfigured HTTP Client", + "Expected configured HTTP client. Please report this issue to the provider developers.", + ) + + return + } + + httpResp, err := r.client.Get("https://example.com") + /* ... */ +} +``` diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/resources/create.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/resources/create.mdx new file mode 100644 index 000000000..7d6431e5f --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/resources/create.mdx @@ -0,0 +1,172 @@ +--- +page_title: Create resources +description: >- + Learn how to implement resource creation in the Terraform plugin framework. +--- + +# Create resources + +Creation is part of the basic Terraform lifecycle for managing resources. During the [`terraform apply` command](/terraform/cli/commands/apply), Terraform calls the provider [`ApplyResourceChange`](/terraform/plugin/framework/internals/rpcs#applyresourcechange-rpc) RPC, in which the framework calls the [`resource.Resource` interface `Create` method](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource#Resource.Create). The request contains Terraform configuration and plan data. The response expects the applied Terraform state data, including any computed values. The data is defined by the [schema](/terraform/plugin/framework/handling-data/schemas) of the resource. + +Other resource lifecycle implementations include: + +- [Read](/terraform/plugin/framework/resources/read) resources by receiving Terraform prior state data, performing read logic, and saving refreshed Terraform state data. +- [Update](/terraform/plugin/framework/resources/update) resources in-place by receiving Terraform prior state, configuration, and plan data, performing update logic, and saving updated Terraform state data. +- [Delete](/terraform/plugin/framework/resources/delete) resources by receiving Terraform prior state data and performing deletion logic. + +## Define Create Method + +Implement the `Create` method by: + +1. [Accessing the data](/terraform/plugin/framework/accessing-values) from the [`resource.CreateRequest` type](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource#CreateRequest). Most use cases should access the plan data in the [`resource.CreateRequest.Plan` field](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource#CreateRequest.Plan). +1. Performing logic or external calls to create and/or run the resource. +1. [Writing state data](/terraform/plugin/framework/writing-state) into the [`resource.CreateResponse.State` field](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource#CreateResponse.State). + +If the logic needs to return [warning or error diagnostics](/terraform/plugin/framework/diagnostics), they can added into the [`resource.CreateResponse.Diagnostics` field](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource#CreateResponse.Diagnostics). + +In this example, the resource is setup to accept a configuration value that is sent in a service API creation call: + +```go +// ThingResource defines the resource implementation. +// Some resource.Resource interface methods are omitted for brevity. +type ThingResource struct { + // client is configured via a Configure method, which is not shown in this + // example for brevity. Refer to the Configure Resources documentation for + // additional details for setting up resources with external clients. + client *http.Client +} + +// ThingResourceModel describes the Terraform resource data model to match the +// resource schema. +type ThingResourceModel struct { + Name types.String `tfsdk:"name"` + Id types.String `tfsdk:"id"` +} + +// ThingResourceAPIModel describes the API data model. +type ThingResourceAPIModel struct { + Name string `json:"name"` + Id string `json:"id"` +} + +func (r ThingResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "name": schema.StringAttribute{ + MarkdownDescription: "Name of the thing to be saved in the service.", + Required: true, + }, + "id": schema.StringAttribute{ + Computed: true, + MarkdownDescription: "Service generated identifier for the thing.", + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + }, + MarkdownDescription: "Manages a thing.", + } +} + +func (r ThingResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + var data ThingResourceModel + + // Read Terraform plan data into the model + resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...) + + if resp.Diagnostics.HasError() { + return + } + + // Convert from Terraform data model into API data model + createReq := ThingResourceAPIModel{ + Name: data.Name.ValueString(), + } + + httpReqBody, err := json.Marshal(createReq) + + if err != nil { + resp.Diagnostics.AddError( + "Unable to Create Resource", + "An unexpected error occurred while creating the resource create request. "+ + "Please report this issue to the provider developers.\n\n"+ + "JSON Error: "+err.Error(), + ) + + return + } + + // Create HTTP request + httpReq := http.NewRequestWithContext( + ctx, + http.MethodPost, + "http://example.com/things", + bytes.NewBuffer(httpReqBody), + ) + + // Send HTTP request + httpResp, err := d.client.Do(httpReq) + defer httpResp.Body.Close() + + if err != nil { + resp.Diagnostics.AddError( + "Unable to Create Resource", + "An unexpected error occurred while attempting to create the resource. "+ + "Please retry the operation or report this issue to the provider developers.\n\n"+ + "HTTP Error: "+err.Error(), + ) + + return + } + + // Return error if the HTTP status code is not 200 OK + if httpResp.StatusCode != http.StatusOK { + resp.Diagnostics.AddError( + "Unable to Create Resource", + "An unexpected error occurred while attempting to create the resource. "+ + "Please retry the operation or report this issue to the provider developers.\n\n"+ + "HTTP Status: "+httpResp.Status, + ) + + return + } + + var createResp ThingResourceAPIModel + + err := json.NewDecoder(httpResp.Body).Decode(&createResp) + + if err != nil { + resp.Diagnostics.AddError( + "Unable to Create Resource", + "An unexpected error occurred while parsing the resource creation response. "+ + "Please report this issue to the provider developers.\n\n"+ + "JSON Error: "+err.Error(), + ) + + return + } + + // Convert from the API data model to the Terraform data model + // and set any unknown attribute values. + data.Id = types.StringValue(createResp.Id) + + // Save data into Terraform state + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} +``` + +## Caveats + +Note these caveats when implementing the `Create` method: + +* An error is returned if the response state contains unknown values. Set all attributes to either null or known values in the response. +* An error is returned if the response state has the `RemoveResource()` method called. This method is not valid during creation. +* An error is returned unless every null or known value in the request plan is saved exactly as-is into the response state. Only unknown plan values can be modified. +* Any response errors will cause Terraform to mark the resource as tainted for recreation on the next Terraform plan. + +## Recommendations + +Note these recommendations when implementing the `Create` method: + +* Get request data from the Terraform plan data over configuration data as the schema or resource may include [plan modification](/terraform/plugin/framework/resources/plan-modification) logic which sets plan values. +* Return errors that signify there is an existing resource. Terraform practitioners expect to be notified if an existing resource needs to be imported into Terraform rather than created. This prevents situations where multiple Terraform configurations unexpectedly manage the same underlying resource. diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/resources/default.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/resources/default.mdx new file mode 100644 index 000000000..5254d5c5b --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/resources/default.mdx @@ -0,0 +1,104 @@ +--- +page_title: Default values +description: >- + Learn how to set default values for resource attributes with the Terraform + plugin framework. +--- + +# Default values + +After [validation](/terraform/plugin/framework/validation) and before applying configuration changes, Terraform generates a plan that describes the expected values and behaviors of those changes. Resources can then tailor the plan to set default values on computed resource attributes that are null in the configuration. + +A Default can _only_ be added to a resource schema attribute. + +## When is a Default set? + +A Default is set during the [planning process](/terraform/plugin/framework/resources/plan-modification#plan-modification-process), immediately prior to the framework marking computed attributes that are null in the configuration as unknown in the plan. + +## Attribute Default + +You can supply the attribute type `Default` field with a default for that attribute. For example: + +```go +// Typically within the schema.Schema returned by Schema() for a resource. +schema.StringAttribute{ + // ... other Attribute configuration ... + + Default: stringdefault.StaticString("str"), +} + +schema.SetAttribute{ + // ... other Attribute configuration ... + + Default: setdefault.StaticValue( + types.SetValueMust( + types.StringType, + []attr.Value{ + types.StringValue("str"), + }, + ), + ), +}, +``` + +If defined, a default is applied to the current attribute providing that the attribute is null in the configuration. If any nested attributes define a default, then those are applied afterwards. Any default that returns an error will prevent Terraform from applying further defaults of that attribute as well as any nested attribute defaults. + +### Common Use Case Attribute Defaults + +The framework implements static value defaults in the typed packages under `resource/schema/`: + +| Schema Type | Built-In Default Functions | +|---|---| +| [`schema.BoolAttribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema#BoolAttribute) | [`resource/schema/booldefault` package](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/booldefault) | +| [`schema.DynamicAttribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema#DynamicAttribute) | [`resource/schema/dynamicdefault` package](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/dynamicdefault) | +| [`schema.Float32Attribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema#Float32Attribute) | [`resource/schema/float32default` package](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/float32default) | +| [`schema.Float64Attribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema#Float64Attribute) | [`resource/schema/float64default` package](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/float64default) | +| [`schema.Int32Attribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema#Int32Attribute) | [`resource/schema/int32default` package](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/int32default) | +| [`schema.Int64Attribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema#Int64Attribute) | [`resource/schema/int64default` package](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/int64default) | +| [`schema.ListAttribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema#ListAttribute) / [`schema.ListNestedAttribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema#ListNestedAttribute) | [`resource/schema/listdefault` package](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/listdefault) | +| [`schema.MapAttribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema#MapAttribute) / [`schema.MapNestedAttribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema#MapNestedAttribute) | [`resource/schema/mapdefault` package](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/mapdefault) | +| [`schema.NumberAttribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema#NumberAttribute) | [`resource/schema/numberdefault` package](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/numberdefault) | +| [`schema.ObjectAttribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema#ObjectAttribute) / [`schema.SingleNestedAttribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema#SingleNestedAttribute) | [`resource/schema/objectdefault` package](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/objectdefault) | +| [`schema.SetAttribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema#SetAttribute) / [`schema.SetNestedAttribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema#SetNestedAttribute) | [`resource/schema/setdefault` package](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/setdefault) | +| [`schema.StringAttribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema#StringAttribute) | [`resource/schema/stringdefault` package](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/stringdefault) | + +### Custom Default Implementations + +To create an attribute default, you must implement the one of the [`resource/schema/defaults` package](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/defaults) interfaces. For example: + +```go +// timeDefaultValue is a default that sets the value for a types.StringType +// attribute to the current time when it is not configured. The attribute +// must be marked as Optional and Computed. When setting the state during +// the resource Create, Read, or Update methods, this value must also be +// included or the Terraform CLI will generate an error. +type timeDefaultValue struct { + time time.Time +} + +// Description returns a plain text description of the default's behavior, suitable for a practitioner to understand its impact. +func (d timeDefaultValue) Description(ctx context.Context) string { + return fmt.Sprintf("If value is not configured, defaults to a string representation of the current time") +} + +// MarkdownDescription returns a markdown formatted description of the default's behavior, suitable for a practitioner to understand its impact. +func (d timeDefaultValue) MarkdownDescription(ctx context.Context) string { + return fmt.Sprintf("If value is not configured, defaults to a string representation of the current time") +} + +// DefaultString runs the logic of the default. Access to the path is available in `req`, while +// `resp` contains fields for updating the planned value, and returning diagnostics. +func (d timeDefaultValue) DefaultString(_ context.Context, req defaults.StringRequest, resp *defaults.StringResponse) { + resp.PlanValue = types.StringValue(d.time.Format(time.RFC3339)) +} +``` + +Optionally, you may also want to create a helper function to instantiate the default. For example: + +```go +func timeDefault(t time.Time) defaults.String { + return timeDefaultValue{ + time: t, + } +} +``` \ No newline at end of file diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/resources/delete.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/resources/delete.mdx new file mode 100644 index 000000000..37c706e7f --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/resources/delete.mdx @@ -0,0 +1,149 @@ +--- +page_title: Delete resources +description: >- + Learn how to implement resource deletion in the Terraform plugin framework. +--- + +# Delete resources + +Deletion is part of the basic Terraform lifecycle for managing resources. During the [`terraform apply` command](/terraform/cli/commands/apply), Terraform calls the provider [`ApplyResourceChange`](/terraform/plugin/framework/internals/rpcs#applyresourcechange-rpc) RPC, in which the framework calls the [`resource.Resource` interface `Delete` method](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource#Resource.Delete). The request contains Terraform prior state data. The response is only for returning diagnostics. The data is defined by the [schema](/terraform/plugin/framework/schemas) of the resource. + +Terraform 1.3 and later enables deletion planning, which resources can implement to return warning and error diagnostics. For additional information, refer to the [resource plan modification documentation](/terraform/plugin/framework/resources/plan-modification#resource-destroy-plan-diagnostics). + +Other resource lifecycle implementations include: + +- [Create](/terraform/plugin/framework/resources/create) resources by receiving Terraform configuration and plan data, performing creation logic, and saving Terraform state data. +- [Read](/terraform/plugin/framework/resources/read) resources by receiving Terraform prior state data, performing read logic, and saving refreshed Terraform state data. +- [Update](/terraform/plugin/framework/resources/update) resources in-place by receiving Terraform prior state, configuration, and plan data, performing update logic, and saving updated Terraform state data. + +## Define Delete Method + +Implement the `Delete` method by: + +1. [Accessing prior state data](/terraform/plugin/framework/accessing-values) from the [`resource.DeleteRequest.State` field](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource#DeleteRequest.State). +1. Performing logic or external calls to destroy the resource. + +If the logic needs to return [warning or error diagnostics](/terraform/plugin/framework/diagnostics), they can added into the [`resource.DeleteResponse.Diagnostics` field](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource#DeleteResponse.Diagnostics). + +In this example, the `Delete` function makes a HTTP call and returns successfully if the status code was 200 OK or 404 Not Found: + +```go +// ThingResource defines the resource implementation. +// Some resource.Resource interface methods are omitted for brevity. +type ThingResource struct { + // client is configured via a Configure method, which is not shown in this + // example for brevity. Refer to the Configure Resources documentation for + // additional details for setting up resources with external clients. + client *http.Client +} + +// ThingResourceModel describes the Terraform resource data model to match the +// resource schema. +type ThingResourceModel struct { + Name types.String `tfsdk:"name"` + Id types.String `tfsdk:"id"` +} + +// ThingResourceAPIModel describes the API data model. +type ThingResourceAPIModel struct { + Name string `json:"name"` + Id string `json:"id"` +} + +func (r ThingResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "name": schema.StringAttribute{ + MarkdownDescription: "Name of the thing to be saved in the service.", + Required: true, + }, + "id": schema.StringAttribute{ + Computed: true, + MarkdownDescription: "Service generated identifier for the thing.", + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + }, + MarkdownDescription: "Manages a thing.", + } +} + +func (r ThingResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + var data ThingResourceModel + + // Read Terraform prior state data into the model + resp.Diagnostics.Append(req.State.Get(ctx, &data)...) + + // Convert from Terraform data model into API data model + readReq := ThingResourceAPIModel{ + Id: data.Id.ValueString(), + Name: data.Name.ValueString(), + } + + httpReqBody, err := json.Marshal(readReq) + + if err != nil { + resp.Diagnostics.AddError( + "Unable to Delete Resource", + "An unexpected error occurred while creating the resource d request. "+ + "Please report this issue to the provider developers.\n\n"+ + "JSON Error: "+err.Error(), + ) + + return + } + + // Create HTTP request + httpReq := http.NewRequestWithContext( + ctx, + http.MethodDelete, + "http://example.com/things", + bytes.NewBuffer(httpReqBody), + ) + + // Send HTTP request + httpResp, err := d.client.Do(httpReq) + defer httpResp.Body.Close() + + if err != nil { + resp.Diagnostics.AddError( + "Unable to Delete Resource", + "An unexpected error occurred while attempting to delete the resource. "+ + "Please retry the operation or report this issue to the provider developers.\n\n"+ + "HTTP Error: "+err.Error(), + ) + + return + } + + // Return error if the HTTP status code is not 200 OK or 404 Not Found + if httpResp.StatusCode != http.StatusNotFound && httpResp.StatusCode != http.StatusOK { + resp.Diagnostics.AddError( + "Unable to Delete Resource", + "An unexpected error occurred while attempting to delete the resource. "+ + "Please retry the operation or report this issue to the provider developers.\n\n"+ + "HTTP Status: "+httpResp.Status, + ) + + return + } + + // If the logic reaches here, it implicitly succeeded and will remove + // the resource from state if there are no other errors. +} +``` + +## Caveats + +Note these caveats when implementing the `Delete` method: + +* An error is returned if the response state is set to anything other than null. +* Any response errors will cause Terraform to keep the resource under management. + +## Recommendations + +Note these recommendations when implementing the `Delete` method: + +* Ignore errors that signify the resource is no longer existent. +* Skip calling the response state `RemoveResource()` method. The framework automatically handles this logic with the response state if there are no error diagnostics. diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/resources/import.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/resources/import.mdx new file mode 100644 index 000000000..f8202686d --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/resources/import.mdx @@ -0,0 +1,100 @@ +--- +page_title: Resource import +description: >- + Learn how to support resource import using the Terraform plugin framework. +--- + +# Resource import + +Practitioners can use the [`terraform import` command](/terraform/cli/commands/import) to let Terraform begin managing existing infrastructure resources. Resources can implement the `ImportState` method, which must either specify enough Terraform state for the `Read` method to refresh [`resource.Resource`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource#Resource) or return an error. + +## Define Resource ImportState Method + +The [`resource.ResourceWithImportState` interface](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource#ResourceWithImportState) on the [`resource.Resource` interface](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource#Resource) implementation will enable practitioner support for importing an existing resource. + +Implement the `ImportState` method by: + +1. Accessing the import identifier from the [`resource.ImportStateRequest.ID` field](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource#ImportStateRequest) +1. [Writing state data](/terraform/plugin/framework/writing-state) into the [`resource.ImportStateResponse.State` field](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource#ImportStateResponse.State). + +In this example, the resource state has the `id` attribute set to the value passed into the [`terraform import` command](/terraform/cli/commands/import) using the [`resource.ImportStatePassthroughID` function](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource#ImportStatePassthroughID): + +```go +func (r *ThingResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { + resource.ImportStatePassthroughID(ctx, path.Root("id"), req, resp) +} +``` + +### Multiple Attributes + +When the `Read` method requires multiple attributes to refresh, you must write custom logic in the `ImportState` method. + +Implement the `ImportState` method by: + +1. Accessing the import identifier from the [`resource.ImportStateRequest.ID` field](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource#ImportStateRequest) +1. Performing the custom logic. +1. [Writing state data](/terraform/plugin/framework/writing-state) into the [`resource.ImportStateResponse.State` field](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource#ImportStateResponse.State). + +The `terraform import` command will need to accept the multiple attribute values as a single import identifier string. A typical convention is to use a separator character, such as a comma (`,`), between the values. The `ImportState` method will then need to parse the import identifier string into the multiple separate values and save them appropriately into the Terraform state. + +In this example, the resource requires two attributes to refresh state and accepts them as an import identifier of `attr_one,attr_two`: + +```go +func (r *ThingResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "attr_one": schema.StringAttribute{ + Required: true, + }, + "attr_two": schema.StringAttribute{ + Required: true, + }, + /* ... */ + }, + } +} + +func (r *ThingResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + var attrOne, attrTwo string + + resp.Diagnostics.Append(req.State.GetAttribute(ctx, path.Root("attr_one"), &attrOne)...) + resp.Diagnostics.Append(req.State.GetAttribute(ctx, path.Root("attr_two"), &attrTwo)...) + + if resp.Diagnostics.HasError() { + return + } + + // API call using attrOne and attrTwo +} + +func (r *ThingResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { + idParts := strings.Split(req.ID, ",") + + if len(idParts) != 2 || idParts[0] == "" || idParts[1] == "" { + resp.Diagnostics.AddError( + "Unexpected Import Identifier", + fmt.Sprintf("Expected import identifier with format: attr_one,attr_two. Got: %q", req.ID), + ) + return + } + + resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("attr_one"), idParts[0])...) + resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("attr_two"), idParts[1])...) +} +``` + +## Not Implemented + +If the resource does not support `terraform import`, skip the `ImportState` method implementation. + +When a practitioner runs `terraform import`, Terraform CLI will return: + +```console +$ terraform import example_resource.example some-identifier +example_resource.example: Importing from ID "some-identifier"... +╷ +│ Error: Resource Import Not Implemented +│ +│ This resource does not support import. Please contact the provider developer for additional information. +╵ +``` diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/resources/index.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/resources/index.mdx new file mode 100644 index 000000000..6aa9000c8 --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/resources/index.mdx @@ -0,0 +1,105 @@ +--- +page_title: Resources +description: >- + Learn how to build resources in the Terraform plugin framework. Resources + allow Terraform to manage infrastructure objects. +--- + +# Resources + +[Resources](/terraform/language/resources) are an abstraction that allow Terraform to manage infrastructure objects, such as a compute instance, an access policy, or disk. Terraform assumes that every resource: + +- operates as a pure key/value store, with values getting returned exactly as they were written. +- needs only one API call to update or return its state. +- can be be created, read, updated, and deleted. + +This page describes the initial implementation details required for supporting a resource within the provider. Resource lifecycle management functionality is also required: + +- [Create](/terraform/plugin/framework/resources/create) resources by receiving Terraform configuration and plan data, performing creation logic, and saving Terraform state data. +- [Read](/terraform/plugin/framework/resources/read) resources by receiving Terraform prior state data, performing read logic, and saving refreshed Terraform state data. +- [Update](/terraform/plugin/framework/resources/update) resources in-place by receiving Terraform prior state, configuration, and plan data, performing update logic, and saving updated Terraform state data. +- [Delete](/terraform/plugin/framework/resources/delete) resources by receiving Terraform prior state data and performing deletion logic. + +Further documentation is available for deeper resource concepts: + +- [Configure](/terraform/plugin/framework/resources/configure) resources with provider-level data types or clients. +- [Default](/terraform/plugin/framework/resources/default) for specifying a default value for an attribute that is null within the configuration. +- [Import state](/terraform/plugin/framework/resources/import) so practitioners can bring existing resources under Terraform lifecycle management. +- [Manage private state](/terraform/plugin/framework/resources/private-state) to store additional data in resource state that is not shown in plans. +- [Modify plans](/terraform/plugin/framework/resources/plan-modification) to enrich the output for expected resource behaviors during changes, or marking a resource for replacement if an in-place update cannot occur. +- [Upgrade state](/terraform/plugin/framework/resources/state-upgrade) to transparently update state data outside plans. +- [Validate](/terraform/plugin/framework/resources/validate-configuration) practitioner configuration against acceptable values. +- [Timeouts](/terraform/plugin/framework/resources/timeouts) in practitioner configuration for use in resource create, read, update and delete functions. +- [Write-only Arguments](/terraform/plugin/framework/resources/write-only-arguments) are special types of attributes that can accept [ephemeral values](/terraform/language/resources/ephemeral) and are not persisted in the Terraform plan or state artifacts. Write-only arguments are supported in Terraform 1.11 and later. + +## Define Resource Type + +Implement the [`resource.Resource` interface](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource#Resource). Ensure the [Add Resource To Provider](#add-resource-to-provider) documentation is followed so the resource becomes part of the provider implementation, and therefore available to practitioners. + +### Metadata Method + +The [`resource.Resource` interface `Metadata` method](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource#Resource.Metadata) defines the resource name as it would appear in Terraform configurations. This name should include the provider type prefix, an underscore, then the resource specific name. For example, a provider named `examplecloud` and a resource that reads "thing" resources would be named `examplecloud_thing`. + +In this example, the resource name in an `examplecloud` provider that reads "thing" resources is hardcoded to `examplecloud_thing`: + +```go +// With the resource.Resource implementation +func (r *ThingResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = "examplecloud_thing" +} +``` + +To simplify resource implementations, the [`provider.MetadataResponse.TypeName` field](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/provider#MetadataResponse.TypeName) from the [`provider.Provider` interface `Metadata` method](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/provider#Provider.Metadata) can set the provider name so it is available in the [`resource.MetadataRequest.ProviderTypeName` field](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource#MetadataRequest.ProviderTypeName). + +In this example, the provider defines the `examplecloud` name for itself, and the data source is named `examplecloud_thing`: + +```go +// With the provider.Provider implementation +func (p *ExampleCloudProvider) Metadata(ctx context.Context, req provider.MetadataRequest, resp *provider.MetadataResponse) { + resp.TypeName = "examplecloud" +} + +// With the resource.Resource implementation +func (d *ThingDataSource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_thing" +} +``` + +### Schema Method + +The [`resource.Resource` interface `Schema` method](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource#Resource.Schema) defines a [schema](/terraform/plugin/framework/schemas) describing what data is available in the resource's configuration, plan, and state. + +## Add Resource to Provider + +Resources become available to practitioners when they are included in the [provider](/terraform/plugin/framework/providers) implementation via the [`provider.Provider` interface `Resources` method](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/provider#Provider.Resources). + +In this example, the `ThingResource` type, which implements the `resource.Resource` interface, is added to the provider implementation: + +```go +// With the provider.Provider implementation +func (p *ExampleCloudProvider) Resources(_ context.Context) []func() resource.Resource { + return []func() resource.Resource{ + func() resource.Resource { + return &ThingResource{}, + }, + } +} +``` + +To simplify provider implementations, a named function can be created with the resource implementation. + +In this example, the `ThingResource` code includes an additional `NewThingResource` function, which simplifies the provider implementation: + +```go +// With the provider.Provider implementation +func (p *ExampleCloudProvider) Resources(_ context.Context) []func() resource.Resource { + return []func() resource.Resource{ + NewThingResource, + } +} + +// With the resource.Resource implementation +func NewThingResource() resource.Resource { + return &ThingResource{} +} +``` diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/resources/plan-modification.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/resources/plan-modification.mdx new file mode 100644 index 000000000..bcab40d8d --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/resources/plan-modification.mdx @@ -0,0 +1,206 @@ +--- +page_title: Plan modification +description: >- + Learn how to modify plan values and behaviors with the Terraform plugin + framework. +--- + +# Plan modification + +After [validation](/terraform/plugin/framework/validation) and before applying configuration changes, Terraform generates a plan that describes the expected values and behaviors of those changes. Resources can then tailor the plan to match the expected end state, prevent errant in-place updates, or return any [diagnostics](/terraform/plugin/framework/diagnostics). + +Terraform and the framework support multiple types of plan modification on resources: + +- Adjusting unknown attribute values, such as providing a known remote default value when a configuration is not present. +- Marking resources that should be replaced, such as when an in-place update is not supported for a change. +- Returning warning or error diagnostics on planned resource creation, update, or deletion. + +Plan modification can be added on resource schema attributes or an entire resource. Use resource-based plan modification if access to the [configured resource](/terraform/plugin/framework/resources/configure) is necessary. + +## Plan Modification Process + +When the provider receives a request to generate the plan for a resource change via the framework, the following occurs: + +1. Set any attributes with a null configuration value to the [default value](/terraform/plugin/framework/resources/default). +1. If the plan differs from the current resource state, the framework marks computed attributes that are null in the configuration as unknown in the plan. This is intended to prevent unexpected Terraform errors. Providers can later enter any values that may be known. +1. Run attribute plan modifiers. +1. Run resource plan modifiers. + +When the `Resource` interface `Update` method runs to apply a change, all attribute state values must match their associated planned values or Terraform will generate a `Provider produced inconsistent result` error. You can mark values as [unknown](/terraform/plugin/framework/types#unknown) in the plan if the full expected value is not known. + +Refer to the [Resource Instance Change Lifecycle document](https://github.com/hashicorp/terraform/blob/main/docs/resource-instance-change-lifecycle.md) for more details about the concepts and processes relevant to the plan and apply workflows. + +During the [`terraform plan`](/terraform/cli/commands/plan) and [`terraform apply`](/terraform/cli/commands/apply) commands, Terraform calls the provider [`PlanResourceChange`](/terraform/plugin/framework/internals/rpcs#planresourcechange-rpc) RPC, in which the framework calls the [`resource.Resource` interface `Schema` method](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource#Resource.Schema) attribute plan modifiers and the `ModifyPlan` method on resources that implement the [`resource.ResourceWithModifyPlan` interface](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource#ResourceWithModifyPlan). + +## Attribute Plan Modification + +You can supply the attribute type `PlanModifiers` field with a list of plan modifiers for that attribute. For example: + +```go +// Typically within the schema.Schema returned by Schema() for a resource. +schema.StringAttribute{ + // ... other Attribute configuration ... + + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, +} +``` + +If defined, plan modifiers are applied to the current attribute. If any nested attributes define plan modifiers, then those are applied afterwards. Any plan modifiers that return an error will prevent Terraform from applying further modifiers of that attribute as well as any nested attribute plan modifiers. + +### Common Use Case Attribute Plan Modifiers + +The framework implements some common use case modifiers in the typed packages under `resource/schema/`, such as `resource/schema/stringplanmodifier`: + +- `RequiresReplace()`: If the value of the attribute changes, in-place update is not possible and instead the resource should be replaced for the change to occur. Refer to the Go documentation for full details on its behavior. +- `RequiresReplaceIf()`: Similar to `resource.RequiresReplace()`, however it also accepts provider-defined conditional logic. Refer to the Go documentation for full details on its behavior. +- `RequiresReplaceIfConfigured()`: Similar to `resource.RequiresReplace()`, however it also will only trigger if the practitioner has configured a value. Refer to the Go documentation for full details on its behavior. +- `UseStateForUnknown()`: Copies the prior state value, if not null. This is useful for reducing `(known after apply)` plan outputs for computed attributes which are known to not change over time. + +### Creating Attribute Plan Modifiers + +To create an attribute plan modifier, you must implement the one of the [`planmodifier` package](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier) interfaces. For example: + +```go +// useStateForUnknownModifier implements the plan modifier. +type useStateForUnknownModifier struct{} + +// Description returns a human-readable description of the plan modifier. +func (m useStateForUnknownModifier) Description(_ context.Context) string { + return "Once set, the value of this attribute in state will not change." +} + +// MarkdownDescription returns a markdown description of the plan modifier. +func (m useStateForUnknownModifier) MarkdownDescription(_ context.Context) string { + return "Once set, the value of this attribute in state will not change." +} + +// PlanModifyBool implements the plan modification logic. +func (m useStateForUnknownModifier) PlanModifyBool(_ context.Context, req planmodifier.BoolRequest, resp *planmodifier.BoolResponse) { + // Do nothing if there is no state value. + if req.StateValue.IsNull() { + return + } + + // Do nothing if there is a known planned value. + if !req.PlanValue.IsUnknown() { + return + } + + // Do nothing if there is an unknown configuration value, otherwise interpolation gets messed up. + if req.ConfigValue.IsUnknown() { + return + } + + resp.PlanValue = req.StateValue +} +``` + +Optionally, you may also want to create a helper function to instantiate the plan modifier. For example: + +```go +// UseStateForUnknown returns a plan modifier that copies a known prior state +// value into the planned value. Use this when it is known that an unconfigured +// value will remain the same after a resource update. +// +// To prevent Terraform errors, the framework automatically sets unconfigured +// and Computed attributes to an unknown value "(known after apply)" on update. +// Using this plan modifier will instead display the prior state value in the +// plan, unless a prior plan modifier adjusts the value. +func UseStateForUnknown() planmodifier.Bool { + return useStateForUnknownModifier{} +} +``` + +### Caveats + +#### Terraform Data Consistency Rules + +Terraform core [implements data consistency rules](https://github.com/hashicorp/terraform/blob/main/docs/resource-instance-change-lifecycle.md) between configuration, plan, and state data. For example, if an attribute value is configured, it is never valid to change that value in the plan except being set to null on resource destroy. The framework does not raise its own targeted errors in many situations, so it is the responsibility of the developer to account for these rules when implementing plan modification logic. + +#### Prior State Under Lists and Sets + +Attribute plan modifiers under the following must take special consideration if they rely on prior state data: + +- List nested attributes +- List nested blocks +- Set nested attributes +- Set nested blocks + +These data structures are implemented based on array indexing, which the framework always sends the exact representation given across the protocol. If list/set elements are rearranged or removed, Terraform nor the framework performs any re-alignment of prior state for those elements. + +In this example, potentially unexpected prior state may be given to attribute plan modifier request: + +- A list nested attribute with two elements in configuration is saved into state +- The configuration for the first element is removed +- The list nested attribute with now one element still receives the prior state of the first element + +#### Checking Resource Change Operations + +Plan modifiers execute on all resource change operations: creation, update, and destroy. If the plan modification logic is sensitive to these details, check the request data to determine the current operation. + +Implement the following to check whether the resource is being created: + +```go +func (m ExampleModifier) PlanModifyString(_ context.Context, req planmodifier.StringRequest, resp *planmodifier.StringResponse) { + // Check if the resource is being created. + if req.State.Raw.IsNull() { + // ... + } + + // ... +} +``` + +Implement the following to check whether the resource is being destroyed: + +```go +func (m ExampleModifier) PlanModifyString(_ context.Context, req planmodifier.StringRequest, resp *planmodifier.StringResponse) { + // Check if the resource is being destroyed. + if req.Plan.Raw.IsNull() { + // ... + } + + // ... +} +``` + +## Resource Plan Modification + +Resources also support plan modification across all attributes. This is helpful when working with logic that applies to the resource as a whole, or in Terraform 1.3 and later, to return diagnostics during resource destruction. Implement the [`resource.ResourceWithModifyPlan` interface](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource#ResourceWithModifyPlan) to support resource-level plan modification. For example: + +```go +// Ensure the Resource satisfies the resource.ResourceWithModifyPlan interface. +// Other methods to implement the resource.Resource interface are omitted for brevity +var _ resource.ResourceWithModifyPlan = ThingResource{} + +type ThingResource struct {} + +func (r ThingResource) ModifyPlan(ctx context.Context, req resource.ModifyPlanRequest, resp *resource.ModifyPlanResponse) { + // Fill in logic. +} +``` + +### Resource Destroy Plan Diagnostics + +-> Support for handling resource destruction during planning is available in Terraform 1.3 and later. + +Implement the `ModifyPlan` method by checking if the [`resource.ModifyPlanRequest` type `Plan` field](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource#ModifyPlanRequest.Plan) is a `null` value: + +```go +func (r ThingResource) ModifyPlan(ctx context.Context, req resource.ModifyPlanRequest, resp *resource.ModifyPlanResponse) { + // If the entire plan is null, the resource is planned for destruction. + if req.Plan.Raw.IsNull() { + // Return an example warning diagnostic to practitioners. + resp.Diagnostics.AddWarning( + "Resource Destruction Considerations", + "Applying this resource destruction will only remove the resource from the Terraform state "+ + "and will not call the deletion API due to API limitations. Manually use the web "+ + "interface to fully destroy this resource.", + ) + } +} +``` + +Ensure the response plan remains entirely `null` when the request plan is entirely `null`. diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/resources/private-state.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/resources/private-state.mdx new file mode 100644 index 000000000..244b1a9b9 --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/resources/private-state.mdx @@ -0,0 +1,95 @@ +--- +page_title: Private state management +description: >- + Learn how to manage private state data in the Terraform plugin framework. + Private state is provider-only data storage for resources. +--- + +# Private state management + +Resource private state is provider maintained data that is stored in Terraform state alongside the schema-defined data. Private state is never accessed or exposed by Terraform plans, however providers can use this data storage for advanced use cases. + +## Usage + +Example uses in the framework include: + +* Storing and retrieving values that are not important to show to practitioners, but are required for API calls, such as ETags. +* Resource timeout functionality. + +## Concepts + +Private state data is byte data stored in the Terraform state and is intended for provider usage only (i.e., it is only used by the Framework and provider code). Providers have the ability to save this data during create, import, planning, read, and update operations and the ability to read this data during delete, planning, read, and update operations. + +## Accessing Private State Data + +Private state data can be read from a [privatestate.ProviderData](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/internal/privatestate#ProviderData) type in the `Private` field present in the _request_ that is passed into: + +| Resource Operation | Private State Data | +| --- | --- | +| Delete | [resource.DeleteRequest.Private](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource#DeleteRequest.Private) | +| Plan Modification ([resource.ResourceWithModifyPlan](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource#ResourceWithModifyPlan)) | [resource.ModifyPlanRequest.Private](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource#ModifyPlanRequest.Private) | +| Plan Modification (`planmodifier` package interfaces) | Request type `Private` fields | +| Read | [resource.ReadRequest.Private](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource#ReadRequest.Private) | +| Update | [resource.UpdateRequest.Private](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource#UpdateRequest.Private) + +Private state data can be saved to a [privatestate.ProviderData](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/internal/privatestate#ProviderData) type in the `Private` field present in the _response_ that is returned from: + +| Resource Operation | Private State Data | +| --- | --- | +| Create | [resource.CreateResponse.Private](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource#CreateResponse.Private) | +| Import | [resource.ImportStateResponse.Private](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource#ImportStateResponse.Private) | +| Plan Modification ([resource.ResourceWithModifyPlan](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource#ResourceWithModifyPlan)) | [resource.ModifyPlanResponse.Private](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource#ModifyPlanResponse.Private) | +| Plan Modification (`planmodifier` package interfaces) | Response type `Private` fields | +| Read | [resource.ReadResponse.Private](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource#ReadResponse.Private) | +| Update | [resource.UpdateResponse.Private](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource#UpdateResponse.Private) + +### Reading Private State Data + +Private state data can be read using the [GetKey](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/internal/privatestate#ProviderData.GetKey) +function. For example: + +```go +func (r *resourceExample) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + + value, diags := req.Private.GetKey(ctx, "key") + + resp.Diagnostics.Append(diags...) + + if value != nil { + // value will be []byte. + ... + } +} +``` + +If the key supplied is [reserved](#reserved-keys) for framework usage, an error diagnostic will be returned. + +If the key is valid but no private state data is found, nil is returned. + +### Saving Private State Data + +Private state data can be saved using the [SetKey](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/internal/privatestate#ProviderData.SetKey) +function. For example: + +```go +func (r *resourceExample) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + + value := []byte(`{"valid": "json", "utf8": "safe"}`) + + diags := resp.Private.SetKey(ctx, "key", value) + + resp.Diagnostics.Append(diags...) +} +``` + +If the key supplied is [reserved](#reserved-keys) for framework usage, an error diagnostic will be returned. + +If the value is not valid JSON and UTF-8 safe, an error diagnostic will be returned. + +To remove a key and its associated value, use `nil` or a zero-length value such as `[]byte{}`. + +### Reserved Keys + +Keys supplied to [GetKey](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/internal/privatestate#ProviderData.GetKey) and [SetKey](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/internal/privatestate#ProviderData.SetKey) are validated using [ValidateProviderDataKey](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/internal/privatestate#ValidateProviderDataKey). + +Keys using a period ('.') as a prefix cannot be used for provider private state data as they are reserved for framework usage. diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/resources/read.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/resources/read.mdx new file mode 100644 index 000000000..dc0161ae9 --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/resources/read.mdx @@ -0,0 +1,163 @@ +--- +page_title: Read resources +description: >- + Learn how to implement resource read in the Terraform plugin framework. +--- + +# Read resources + +Read (refresh) is part of the basic Terraform lifecycle for managing resources. During the [`terraform apply`](/terraform/cli/commands/apply), [`terraform plan`](/terraform/cli/commands/plan), and [`terraform refresh`](/terraform/cli/commands/refresh) commands, Terraform calls the provider [`ReadResource`](/terraform/plugin/framework/internals/rpcs#readresource-rpc) RPC, in which the framework calls the [`resource.Resource` interface `Read` method](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource#Resource.Read). The `Read` method is also executed after [resource import](/terraform/plugin/framework/resources/import). The request contains Terraform prior state data. The response contains the refreshed state data. The data is defined by the [schema](/terraform/plugin/framework/schemas) of the resource. + +Other resource lifecycle implementations include: + +- [Create](/terraform/plugin/framework/resources/create) resources by receiving Terraform configuration and plan data, performing creation logic, and saving Terraform state data. +- [Update](/terraform/plugin/framework/resources/update) resources in-place by receiving Terraform prior state, configuration, and plan data, performing update logic, and saving updated Terraform state data. +- [Delete](/terraform/plugin/framework/resources/delete) resources by receiving Terraform prior state data and performing deletion logic. + +## Define Read Method + +Implement the `Read` method by: + +1. [Accessing prior state data](/terraform/plugin/framework/accessing-values) from the [`resource.ReadRequest.State` field](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource#ReadRequest.State). +1. Retriving updated resource state, such as remote system information. +1. [Writing state data](/terraform/plugin/framework/writing-state) into the [`resource.ReadResponse.State` field](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource#ReadResponse.State). + +If the logic needs to return [warning or error diagnostics](/terraform/plugin/framework/diagnostics), they can added into the [`resource.ReadResponse.Diagnostics` field](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource#ReadResponse.Diagnostics). + +In this example, the `Read` function makes a HTTP call and refreshes the state data if the status code was 200 OK or removes the resource if 404 Not Found: + +```go +// ThingResource defines the resource implementation. +// Some resource.Resource interface methods are omitted for brevity. +type ThingResource struct { + // client is configured via a Configure method, which is not shown in this + // example for brevity. Refer to the Configure Resources documentation for + // additional details for setting up resources with external clients. + client *http.Client +} + +// ThingResourceModel describes the Terraform resource data model to match the +// resource schema. +type ThingResourceModel struct { + Name types.String `tfsdk:"name"` + Id types.String `tfsdk:"id"` +} + +// ThingResourceAPIModel describes the API data model. +type ThingResourceAPIModel struct { + Name string `json:"name"` + Id string `json:"id"` +} + +func (r ThingResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "name": schema.StringAttribute{ + MarkdownDescription: "Name of the thing to be saved in the service.", + Required: true, + }, + "id": schema.StringAttribute{ + Computed: true, + MarkdownDescription: "Service generated identifier for the thing.", + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + }, + MarkdownDescription: "Manages a thing.", + } +} + +func (r *ThingResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + var data ThingResourceModel + + // Read Terraform prior state data into the model + resp.Diagnostics.Append(req.State.Get(ctx, &data)...) + + // Convert from Terraform data model into API data model + readReq := ThingResourceAPIModel{ + Id: data.Id.ValueString(), + Name: data.Name.ValueString(), + } + + httpReqBody, err := json.Marshal(readReq) + + if err != nil { + resp.Diagnostics.AddError( + "Unable to Refresh Resource", + "An unexpected error occurred while creating the resource read request. "+ + "Please report this issue to the provider developers.\n\n"+ + "JSON Error: "+err.Error(), + ) + + return + } + + // Create HTTP request + httpReq := http.NewRequestWithContext( + ctx, + http.MethodPut, + "http://example.com/things", + bytes.NewBuffer(httpReqBody), + ) + + httpResp, err := d.client.Do(httpReq) + defer httpResp.Body.Close() + + if err != nil { + resp.Diagnostics.AddError( + "Unable to Refresh Resource", + "An unexpected error occurred while attempting to refresh resource state. "+ + "Please retry the operation or report this issue to the provider developers.\n\n"+ + "HTTP Error: "+err.Error(), + ) + + return + } + + // Treat HTTP 404 Not Found status as a signal to recreate resource + // and return early + if httpResp.StatusCode == http.StatusNotFound { + resp.State.RemoveResource(ctx) + + return + } + + var readResp ThingResourceAPIModel + + err := json.NewDecoder(httpResp.Body).Decode(&readResp) + + if err != nil { + resp.Diagnostics.AddError( + "Unable to Refresh Resource", + "An unexpected error occurred while parsing the resource read response. "+ + "Please report this issue to the provider developers.\n\n"+ + "JSON Error: "+err.Error(), + ) + + return + } + + // Convert from the API data model to the Terraform data model + // and refresh any attribute values. + data.Name = types.StringValue(readResp.Name) + + // Save updated data into Terraform state + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} +``` + +## Caveats + +Note these caveats when implementing the `Read` method: + +* An error is returned if the response state contains unknown values. Set all attributes to either null or known values in the response. +* Any response errors will cause Terraform to keep the prior resource state. + +## Recommendations + +Note these recommendations when implementing the `Read` method: + +* Ignore returning errors that signify the resource is no longer existent, call the response state `RemoveResource()` method, and return early. The next Terraform plan will recreate the resource. +* Refresh all possible values. This will ensure Terraform shows configuration drift and reduces import logic. +* Preserve the prior state value if the updated value is semantically equal. For example, JSON strings that have inconsequential object property reordering or whitespace differences. This prevents Terraform from showing extraneous drift in plans. diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/resources/state-move.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/resources/state-move.mdx new file mode 100644 index 000000000..c97dc57cd --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/resources/state-move.mdx @@ -0,0 +1,267 @@ +--- +page_title: State move +description: >- + Learn how to implement moving state data across managed resource types using + the Terraform plugin framework. +--- + +# State move + + + +State move across managed resource types is supported in Terraform 1.8 and later. + + + +Terraform is designed with each managed resource type being distinguished from all other types. To prevent data loss or unexpected data issues, Terraform will raise an error when practitioners attempt to refactor existing resource usage across resource types via the [`moved` configuration block](/terraform/language/modules/develop/refactoring) since data compatibility is not guaranteed. Provider developers can opt into explicitly enabling Terraform to allow these refactoring operations for a target resource type based on source resource type criteria. This criteria can include the source provider address, resource type name, and schema version. + +## Use Cases + +Example use cases include: + +* Renaming a resource type, such as API service name changes or for Terraform resource naming consistency. +* Splitting a resource type, into separate resource types for practitioner ease, such as a compute resource into Linux and Windows variants. +* Handing a resource type with API versioning quirks, such as multiple resource types representing the same real world resources with partially different configuration data/concepts. + +## Concepts + +A managed resource type has an associated [state](/terraform/language/state), which captures the structure and types of data for the resource type. Enabling state move support requires the provider to handle data transformation logic which takes in source resource type state as an input and outputs the equivalent target resource type state. + +When a plan is generated with a `moved` configuration block, Terraform will send a request to the provider with all the source resource state information (provider address, resource type, schema version) and target resource type. The framework will check the target resource to see if it defines state move support. + +The framework implementation does the following: + +* If no state move support is defined for the resource, an error diagnostic is returned. +* If state move support is defined for the resource, each provider defined implementation is called until one responds with error diagnostics or state data. +* If all implementations return without error diagnostics and state data, an error diagnostic is returned. + +## Implementation + +Implement the [`resource.ResourceWithMoveState` interface](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource#ResourceWithMoveState) for the [`resource.Resource`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource#Resource). That interface requires the `MoveState` method, which enables individual source resource criteria and logic for each source resource type to support. + +This example shows a `Resource` with the necessary `MoveState` method to implement the `ResourceWithMoveState` interface: + +```go +// Other Resource methods are omitted in this example +var _ resource.ResourceWithMoveState = &TargetResource{} + +type TargetResource struct{/* ... */} + +func (r *TargetResource) MoveState(ctx context.Context) []resource.StateMover { + return []resource.StateMover{ + { + // Optionally, the SourceSchema field can be defined. + StateMover: func(ctx context.Context, req resource.MoveStateRequest, resp *resource.MoveStateResponse) { /* ... */ }, + }, + // ... potentially more StateMover for each compatible source ... + } +} +``` + +Each [`resource.StateMover`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource#StateMover) implementation is expected to: + +* Check the [`resource.MoveStateRequest`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource#MoveStateRequest) for whether this implementation matches a known source resource. It is always recommended to check the `SourceTypeName`, `SourceSchemaVersion`, and `SourceProviderAddress` (without the hostname, unless needed for disambiguation). +* If not matching, return early without diagnostics or setting state data in the [`resource.MoveStateResponse`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource#MoveStateResponse). The framework will try the next implementation. +* If matching, wholly set the resource state from the source state. All state data must be populated in the [`resource.MoveStateResponse`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource#MoveStateResponse). The framework does not copy any source state data from the [`resource.MoveStateRequest`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource#MoveStateRequest). + +There are two approaches to implementing the provider logic for state moves in `StateMover`. The recommended approach is defining the source schema matching the source resource state, which allows for source state access similar to other parts of the framework. The second, more advanced, approach is accessing the source resource state using lower level data handlers. + +### StateMover With SourceSchema + +Implement the [`StateMover` type `SourceSchema` field](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource#StateMover.SourceSchema) to enable the framework to populate the [`resource.MoveStateRequest` type `SourceState` field](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource#MoveStateRequest.SourceState) for the provider defined state move logic. Access the request `SourceState` using methods such as [`Get()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/tfsdk#State.Get) or [`GetAttribute()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/tfsdk#State.GetAttribute). Write the [`resource.MoveStateResponse` type `TargetState` field](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource#MoveStateResponse.TargetState) using methods such as [`Set()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/tfsdk#State.Set) or [`SetAttribute()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/tfsdk#State.SetAttribute). + +This example shows a target resource that supports moving state from a source resource, using the `SourceSchema` approach: + +```go +// Other Resource methods are omitted in this example +var _ resource.Resource = &TargetResource{} +var _ resource.ResourceWithMoveState = &TargetResource{} + +type TargetResource struct{/* ... */} + +type TargetResourceModel struct { + Id types.String `tfsdk:"id"` + TargetAttribute types.Bool `tfsdk:"target_attribute"` +} + +type SourceResourceModel struct { + Id types.String `tfsdk:"id"` + SourceAttribute types.Bool `tfsdk:"source_attribute"` +} + +func (r *TargetResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ /* ... */ }, + "target_attribute": schema.BoolAttribute{ /* ... */ }, + }, + } +} + +func (r *TargetResource) MoveState(ctx context.Context) []resource.StateMover { + return []resource.StateMover{ + { + SourceSchema: &schema.Schema{ + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{}, + "source_attribute": schema.BoolAttribute{}, + }, + }, + StateMover: func(ctx context.Context, req resource.MoveStateRequest, resp *resource.MoveStateResponse) { + // Always verify the expected source before working with the data. + if req.SourceTypeName != "examplecloud_source" { + return + } + + if req.SourceSchemaVersion != 0 { + return + } + + // This only checks the provider address namespace and type + // since practitioners may use differing hostnames for the same + // provider, such as a network mirror. If necessary though, the + // hostname can be used for disambiguation. + if !strings.HasSuffix(req.SourceProviderAddress, "examplecorp/examplecloud") { + return + } + + var sourceStateData SourceResourceModel + + resp.Diagnostics.Append(req.SourceState.Get(ctx, &sourceStateData)...) + + if resp.Diagnostics.HasError() { + return + } + + targetStateData := TargetResourceModel{ + Id: sourceStateData.Id, + TargetAttribute: sourceStateData.SourceAttribute, + } + + resp.Diagnostics.Append(resp.TargetState.Set(ctx, targetStateData)...) + }, + }, + } +} +``` + +### StateMover Without SourceSchema + +Read source state data from the [`resource.MoveStateRequest` type `SourceRawState` field](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource#MoveStateRequest.SourceRawState). Write the [`resource.MoveStateResponse` type `TargetState` field](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource#MoveStateResponse.TargetState) using methods such as [`Set()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/tfsdk#State.Set) or [`SetAttribute()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/tfsdk#State.SetAttribute). + +This example shows a target resource that supports moving state from a source resource, using the `SourceRawState` approach for the request: + +```go +// Other Resource methods are omitted in this example +var _ resource.Resource = &TargetResource{} +var _ resource.ResourceWithMoveState = &TargetResource{} + +type TargetResource struct{/* ... */} + +type TargetResourceModel struct { + Id types.String `tfsdk:"id"` + TargetAttribute types.Bool `tfsdk:"target_attribute"` +} + +func (r *TargetResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ /* ... */ }, + "target_attribute": schema.BoolAttribute{ /* ... */ }, + }, + } +} + +func (r *TargetResource) MoveState(ctx context.Context) []resource.StateMover { + return []resource.StateMover{ + { + StateMover: func(ctx context.Context, req resource.MoveStateRequest, resp *resource.MoveStateResponse) { + // Always verify the expected source before working with the data. + if req.SourceTypeName != "examplecloud_source" { + return + } + + if req.SourceSchemaVersion != 0 { + return + } + + // This only checks the provider address namespace and type + // since practitioners may use differing hostnames for the same + // provider, such as a network mirror. If necessary though, the + // hostname can be used for disambiguation. + if !strings.HasSuffix(req.SourceProviderAddress, "examplecorp/examplecloud") { + return + } + + // Refer also to the RawState type JSON field which can be used + // with json.Unmarshal() + rawStateValue, err := req.SourceRawState.Unmarshal(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "id": tftypes.String, + "source_attribute": tftypes.Bool, + }, + }) + + if err != nil { + resp.Diagnostics.AddError( + "Unable to Unmarshal Source State", + err.Error(), + ) + + return + } + + var rawState map[string]tftypes.Value + + if err := rawStateValue.As(&rawState); err != nil { + resp.Diagnostics.AddError( + "Unable to Convert Source State", + err.Error(), + ) + + return + } + + var id *string + + if err := rawState["id"].As(&id); err != nil { + resp.Diagnostics.AddAttributeError( + path.Root("id"), + "Unable to Convert Source State", + err.Error(), + ) + + return + } + + var sourceAttribute *bool + + if err := rawState["source_attribute"].As(&sourceAttribute); err != nil { + resp.Diagnostics.AddAttributeError( + path.Root("source_attribute"), + "Unable to Convert Source State", + err.Error(), + ) + + return + } + + targetStateData := TargetResourceModel{ + Id: types.StringPointerValue(id), + TargetAttribute: types.BoolPointerValue(sourceAttribute), + } + + resp.Diagnostics.Append(resp.TargetState.Set(ctx, targetStateData)...) + }, + }, + } +} +``` + +## Caveats + +Note these caveats when implementing the `MoveState` method: + +* The `SourceState` will not always be `nil` if the schema does not match the source state. Always verify the implementation matches other request fields (`SourceTypeName`, etc.) beforehand. +* An error is returned if the response state contains unknown values. Set all attributes to either null or known values in the response. +* Any response errors will cause Terraform to keep the source resource state. diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/resources/state-upgrade.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/resources/state-upgrade.mdx new file mode 100644 index 000000000..31fd5c678 --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/resources/state-upgrade.mdx @@ -0,0 +1,294 @@ +--- +page_title: State upgrade +description: >- + Learn how to implement upgrading state data when provider schema changes from + one version of your Terraform framework provider to another. +--- + +# State upgrade + +A resource schema captures the structure and types of the resource [state](/terraform/language/state). Any state data that does not conform to the resource schema will generate errors or may not be persisted properly. Over time, it may be necessary for resources to make breaking changes to their schemas, such as changing an attribute type. Terraform supports versioning of these resource schemas and the current version is saved into the Terraform state. When the provider advertises a newer schema version, Terraform will call back to the provider to attempt to upgrade from the saved schema version to the one advertised. This operation is performed prior to planning, but with a configured provider. + +-> Some versions of Terraform CLI will also request state upgrades even when the current schema version matches the state version. The framework will automatically handle this request. + +## State Upgrade Process + +1. When generating a plan, Terraform CLI will request the current resource schema, which contains a version. +1. If Terraform CLI detects that an existing state with its saved version does not match the current version, Terraform CLI will request a state upgrade from the provider with the prior state version and expecting the state to match the current version. +1. The framework will check the resource to see if it defines state upgrade support: + * If no state upgrade support is defined, an error diagnostic is returned. + * If state upgrade support is defined, but not for the requested prior state version, an error diagnostic is returned. + * If state upgrade support is defined and has an implementation for the requested prior state version, the provider defined implementation is executed. + +## Implementing State Upgrade Support + +Ensure the [`schema.Schema` type `Version` field](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema#Schema.Version) for the [`resource.Resource`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource#Resource) is greater than `0`, then implement the [`resource.ResourceWithStateUpgrade` interface](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource#ResourceWithStateUpgrade) for the [`resource.Resource`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource#Resource). Conventionally the version is incremented by `1` for each state upgrade. + +This example shows a `Resource` with the necessary `StateUpgrade` method to implement the `ResourceWithStateUpgrade` interface: + +```go +// Other Resource methods are omitted in this example +var _ resource.Resource = &ThingResource{} +var _ resource.ResourceWithUpgradeState = &ThingResource{} + +type ThingResource struct{/* ... */} + +func (r *ThingResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + // ... other fields ... + + // This example conventionally declares that the resource has prior + // state versions of 0 and 1, while the current version is 2. + Version: 2, + } +} + +func (r *ThingResource) UpgradeState(ctx context.Context) map[int64]resource.StateUpgrader { + return map[int64]resource.StateUpgrader{ + // State upgrade implementation from 0 (prior state version) to 2 (Schema.Version) + 0: { + // Optionally, the PriorSchema field can be defined. + StateUpgrader: func(ctx context.Context, req resource.UpgradeStateRequest, resp *resource.UpgradeStateResponse) { /* ... */ }, + }, + // State upgrade implementation from 1 (prior state version) to 2 (Schema.Version) + 1: { + // Optionally, the PriorSchema field can be defined. + StateUpgrader: func(ctx context.Context, req resource.UpgradeStateRequest, resp *resource.UpgradeStateResponse) { /* ... */ }, + }, + } +} +``` + +Each [`resource.StateUpgrader`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource#StateUpgrader) implementation is expected to wholly upgrade the resource state from the prior version to the current version. The framework does not iterate through intermediate version implementations as incrementing versions by 1 is only conventional and not required. + +All state data must be populated in the [`resource.UpgradeStateResponse`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource#UpgradeStateResponse). The framework does not copy any prior state data from the [`resource.UpgradeStateRequest`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource#UpgradeStateRequest). + +There are two approaches to implementing the provider logic for state upgrades in `StateUpgrader`. The recommended approach is defining the prior schema matching the resource state, which allows for prior state access similar to other parts of the framework. The second, more advanced, approach is accessing the prior resource state using lower level data handlers. + +### StateUpgrader With PriorSchema + +Implement the [`StateUpgrader` type `PriorSchema` field](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource#StateUpgrader.PriorSchema) to enable the framework to populate the [`resource.UpgradeStateRequest` type `State` field](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource#UpgradeStateRequest.State) for the provider defined state upgrade logic. Access the request `State` using methods such as [`Get()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/tfsdk#State.Get) or [`GetAttribute()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/tfsdk#State.GetAttribute). Write the [`resource.UpgradeStateResponse` type `State` field](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource#UpgradeStateResponse.State) using methods such as [`Set()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/tfsdk#State.Set) or [`SetAttribute()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/tfsdk#State.SetAttribute). + +This example shows a resource that changes the type for two attributes, using the `PriorSchema` approach: + +```go +// Other Resource methods are omitted in this example +var _ resource.Resource = &ThingResource{} +var _ resource.ResourceWithUpgradeState = &ThingResource{} + +type ThingResource struct{/* ... */} + +type ThingResourceModelV0 struct { + Id string `tfsdk:"id"` + OptionalAttribute *bool `tfsdk:"optional_attribute"` + RequiredAttribute bool `tfsdk:"required_attribute"` +} + +type ThingResourceModelV1 struct { + Id string `tfsdk:"id"` + OptionalAttribute *string `tfsdk:"optional_attribute"` + RequiredAttribute string `tfsdk:"required_attribute"` +} + +func (r *ThingResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + Computed: true, + }, + "optional_attribute": schema.StringAttribute{ + // As compared to prior schema.BoolAttribute below + Optional: true, + }, + "required_attribute": schema.StringAttribute{ + // As compared to prior schema.BoolAttribute below + Required: true, + }, + }, + // The resource has a prior state version of 0, which had the attribute + // types of types.BoolType as shown below. + Version: 1, + } +} + +func (r *ThingResource) UpgradeState(ctx context.Context) map[int64]resource.StateUpgrader { + return map[int64]resource.StateUpgrader{ + // State upgrade implementation from 0 (prior state version) to 1 (Schema.Version) + 0: { + PriorSchema: &schema.Schema{ + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + Computed: true, + }, + "optional_attribute": schema.BoolAttribute{ + // As compared to current schema.StringAttribute above + Optional: true, + }, + "required_attribute": schema.BoolAttribute{ + // As compared to current schema.StringAttribute above + Required: true, + }, + }, + }, + StateUpgrader: func(ctx context.Context, req resource.UpgradeStateRequest, resp *resource.UpgradeStateResponse) { + var priorStateData ThingResourceModelV0 + + resp.Diagnostics.Append(req.State.Get(ctx, &priorStateData)...) + + if resp.Diagnostics.HasError() { + return + } + + upgradedStateData := ThingResourceModelV1{ + Id: priorStateData.Id, + RequiredAttribute: fmt.Sprintf("%t", priorStateData.RequiredAttribute), + } + + if priorStateData.OptionalAttribute != nil { + v := fmt.Sprintf("%t", *priorStateData.OptionalAttribute) + upgradedStateData.OptionalAttribute = &v + } + + resp.Diagnostics.Append(resp.State.Set(ctx, upgradedStateData)...) + }, + }, + } +} +``` + +### StateUpgrader Without PriorSchema + +Read prior state data from the [`resource.UpgradeStateRequest` type `RawState` field](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource#UpgradeStateRequest.RawState). Write the [`resource.UpgradeStateResponse` type `State` field](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource#UpgradeStateResponse.State) using methods such as [`Set()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/tfsdk#State.Set) or [`SetAttribute()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/tfsdk#State.SetAttribute), or for more advanced use cases, write the [`resource.UpgradeStateResponse` type `DynamicValue` field](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource#UpgradeStateResponse.DynamicValue). + +This example shows a resource that changes the type for two attributes, using the `RawState` approach for the request and `DynamicValue` approach for the response: + +```go +// Other Resource methods are omitted in this example +var _ resource.Resource = &ThingResource{} +var _ resource.ResourceWithUpgradeState = &ThingResource{} + +var ThingResourceTftypesDataV0 = tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "id": tftypes.String, + "optional_attribute": tftypes.Bool, + "required_attribute": tftypes.Bool, + }, +} + +var ThingResourceTftypesDataV1 = tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "id": tftypes.String, + "optional_attribute": tftypes.String, + "required_attribute": tftypes.String, + }, +} + +type ThingResource struct{/* ... */} + +func (r *ThingResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + Computed: true, + }, + "optional_attribute": schema.StringAttribute{ + // As compared to prior schema.BoolAttribute below + Optional: true, + }, + "required_attribute": schema.StringAttribute{ + // As compared to prior schema.BoolAttribute below + Required: true, + }, + }, + // The resource has a prior state version of 0, which had the attribute + // types of types.BoolType as shown below. + Version: 1, + } +} + +func (r *ThingResource) UpgradeState(ctx context.Context) map[int64]resource.StateUpgrader { + return map[int64]resource.StateUpgrader{ + // State upgrade implementation from 0 (prior state version) to 1 (Schema.Version) + 0: { + StateUpgrader: func(ctx context.Context, req resource.UpgradeStateRequest, resp *resource.UpgradeStateResponse) { + // Refer also to the RawState type JSON field which can be used + // with json.Unmarshal() + rawStateValue, err := req.RawState.Unmarshal(ThingResourceTftypesDataV0) + + if err != nil { + resp.Diagnostics.AddError( + "Unable to Unmarshal Prior State", + err.Error(), + ) + return + } + + var rawState map[string]tftypes.Value + + if err := rawStateValue.As(&rawState); err != nil { + resp.Diagnostics.AddError( + "Unable to Convert Prior State", + err.Error(), + ) + return + } + + var optionalAttributeString *string + + if !rawState["optional_attribute"].IsNull() { + var optionalAttribute bool + + if err := rawState["optional_attribute"].As(&optionalAttribute); err != nil { + resp.Diagnostics.AddAttributeError( + path.Root("optional_attribute"), + "Unable to Convert Prior State", + err.Error(), + ) + return + } + + v := fmt.Sprintf("%t", optionalAttribute) + optionalAttributeString = &v + } + + var requiredAttribute bool + + if err := rawState["required_attribute"].As(&requiredAttribute); err != nil { + resp.Diagnostics.AddAttributeError( + path.Root("required_attribute"), + "Unable to Convert Prior State", + err.Error(), + ) + return + } + + dynamicValue, err := tfprotov6.NewDynamicValue( + ThingResourceTftypesDataV1, + tftypes.NewValue(ThingResourceTftypesDataV1, map[string]tftypes.Value{ + "id": rawState["id"], + "optional_attribute": tftypes.NewValue(tftypes.String, optionalAttributeString), + "required_attribute": tftypes.NewValue(tftypes.String, fmt.Sprintf("%t", requiredAttribute)), + }), + ) + + if err != nil { + resp.Diagnostics.AddError( + "Unable to Convert Upgraded State", + err.Error(), + ) + return + } + + resp.DynamicValue = &dynamicValue + }, + }, + } +} +``` + +## Caveats + +Note these caveats when implementing the `UpgradeState` method: + +* An error is returned if the response state contains unknown values. Set all attributes to either null or known values in the response. +* Any response errors will cause Terraform to keep the prior resource state. diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/resources/timeouts.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/resources/timeouts.mdx new file mode 100644 index 000000000..db14bae0c --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/resources/timeouts.mdx @@ -0,0 +1,138 @@ +--- +page_title: Timeouts +description: >- + Learn how to implement timeouts with the Terraform plugin framework. +--- + +# Timeouts + +The reality of cloud infrastructure is that it typically takes time to perform operations such as booting operating systems, discovering services, and replicating state across network edges. As the provider developer you should take known delays in resource APIs into account in the CRUD functions of the resource. Terraform supports configurable timeouts to assist in these situations. + +The Framework can be used in conjunction with the [terraform-plugin-framework-timeouts](https://github.com/hashicorp/terraform-plugin-framework-timeouts) module in order to allow defining timeouts in configuration and have them be available in CRUD functions. + +## Specifying Timeouts in Configuration + +Timeouts can be defined using either nested blocks or nested attributes. + +If you are writing a new provider using [terraform-plugin-framework](https://github.com/hashicorp/terraform-plugin-framework) +then we recommend using nested attributes. + +If you are [migrating a provider from SDKv2 to the Framework](/terraform/plugin/framework/migrating) and +you are already using timeouts you can either continue to use block syntax, or switch to using nested attributes. +However, switching to using nested attributes will require that practitioners that are using your provider update their +Terraform configuration. + +#### Block + +If your configuration is using a nested block to define timeouts, such as the following: + +```hcl +resource "timeouts_example" "example" { + /* ... */ + + timeouts { + create = "60m" + } +} +``` + +Import the [timeouts module](https://github.com/hashicorp/terraform-plugin-framework-timeouts). + +```go +import ( + /* ... */ + "github.com/hashicorp/terraform-plugin-framework-timeouts/resource/timeouts" +) +```` + +You can use this module to mutate the `schema.Schema` as follows: + +```go +func (t *exampleResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + /* ... */ + + Blocks: map[string]schema.Block{ + "timeouts": timeouts.Block(ctx, timeouts.Opts{ + Create: true, + }), + }, +``` + +#### Attribute + +If your configuration is using nested attributes to define timeouts, such as the following: + +```hcl +resource "timeouts_example" "example" { + /* ... */ + + timeouts = { + create = "60m" + } +} +``` + +You can use this module to mutate the `schema.Schema` as follows: + +```go +func (t *exampleResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + /* ... */ + "timeouts": timeouts.Attributes(ctx, timeouts.Opts{ + Create: true, + }), + }, +``` + +## Updating Models + +In functions in which the config, state or plan is being unmarshalled, for instance, the `Create` function, the model +will need to be updated. + +```go +func (e *exampleResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + var data exampleResourceData + + diags := req.Plan.Get(ctx, &data) + resp.Diagnostics.Append(diags...) +``` + +Modify the `exampleResourceData` model to include a field for timeouts using a [`timeouts.Value`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework-timeouts/resource/timeouts#Value) type. + +```go +type exampleResourceData struct { + /* ... */ + Timeouts timeouts.Value `tfsdk:"timeouts"` +``` + +## Accessing Timeouts in CRUD Functions + +Once the model has been populated with the config, state or plan the duration of the timeout can be accessed by calling +the appropriate helper function (e.g., [`timeouts.Create`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework-timeouts/resource/timeouts#Value.Create)) and then used to configure timeout behaviour, for instance: + +```go +func (e *exampleResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + var data exampleResourceData + + diags := req.Plan.Get(ctx, &data) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + createTimeout, diags := data.Timeouts.Create(ctx, 20*time.Minute) + + resp.Diagnostics.Append(diags...) + + if resp.Diagnostics.HasError() { + return + } + + ctx, cancel := context.WithTimeout(ctx, createTimeout) + defer cancel() + + /* ... */ +} +``` diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/resources/update.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/resources/update.mdx new file mode 100644 index 000000000..a892b4161 --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/resources/update.mdx @@ -0,0 +1,211 @@ +--- +page_title: Update resources +description: >- + Learn how to implement in-place updating of resources in the Terraform + plugin framework. +--- + +# Update resources + +In-place update is part of the basic Terraform lifecycle for managing resources. During the [`terraform apply` command](/terraform/cli/commands/apply), Terraform calls the provider [`ApplyResourceChange`](/terraform/plugin/framework/internals/rpcs#applyresourcechange-rpc) RPC, in which the framework calls the [`resource.Resource` interface `Update` method](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource#Resource.Update). The request contains Terraform prior state, configuration, and plan data. The response contains updated state data. The data is defined by the [schema](/terraform/plugin/framework/schemas) of the resource. + +To ensure that Terraform plans replacement of a resource, rather than perform an in-place update, use the [`resource.RequiresReplace()` attribute plan modifier](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource#RequiresReplace) in the schema or implement [resource plan modification](/terraform/plugin/framework/resources/plan-modification). + +Other resource lifecycle implementations include: + +- [Create](/terraform/plugin/framework/resources/create) resources by receiving Terraform configuration and plan data, performing creation logic, and saving Terraform state data. +- [Read](/terraform/plugin/framework/resources/read) resources by receiving Terraform prior state data, performing read logic, and saving refreshed Terraform state data. +- [Delete](/terraform/plugin/framework/resources/delete) resources by receiving Terraform prior state data and performing deletion logic. + +## Define Update Method + +Implement the `Update` method by: + +1. [Accessing the data](/terraform/plugin/framework/accessing-values) from the [`resource.UpdateRequest` type](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource#UpdateRequest). Most use cases should access the plan data in the [`resource.UpdateRequest.Plan` field](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource#UpdateRequest.Plan). +1. Performing logic or external calls to modify the resource. +1. [Writing state data](/terraform/plugin/framework/writing-state) into the [`resource.UpdateResponse.State` field](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource#UpdateResponse.State). + +If the logic needs to return [warning or error diagnostics](/terraform/plugin/framework/diagnostics), they can added into the [`resource.UpdateResponse.Diagnostics` field](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource#UpdateResponse.Diagnostics). + +In this example, the `Update` function makes a HTTP call and returns successfully if the status code was 200 OK: + +```go +// ThingResource defines the resource implementation. +// Some resource.Resource interface methods are omitted for brevity. +type ThingResource struct { + // client is configured via a Configure method, which is not shown in this + // example for brevity. Refer to the Configure Resources documentation for + // additional details for setting up resources with external clients. + client *http.Client +} + +// ThingResourceModel describes the Terraform resource data model to match the +// resource schema. +type ThingResourceModel struct { + Name types.String `tfsdk:"name"` + Id types.String `tfsdk:"id"` +} + +// ThingResourceAPIModel describes the API data model. +type ThingResourceAPIModel struct { + Name string `json:"name"` + Id string `json:"id"` +} + +func (r ThingResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "name": schema.StringAttribute{ + MarkdownDescription: "Name of the thing to be saved in the service.", + Required: true, + }, + "id": schema.StringAttribute{ + Computed: true, + MarkdownDescription: "Service generated identifier for the thing.", + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + }, + MarkdownDescription: "Manages a thing.", + } +} + +func (r ThingResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + var data ThingResourceModel + + // Read Terraform plan data into the model + resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...) + + // Convert from Terraform data model into API data model + updateReq := ThingResourceAPIModel{ + Id: data.Id.ValueString(), + Name: data.Name.ValueString(), + } + + httpReqBody, err := json.Marshal(updateReq) + + if err != nil { + resp.Diagnostics.AddError( + "Unable to Update Resource", + "An unexpected error occurred while creating the resource update request. "+ + "Please report this issue to the provider developers.\n\n"+ + "JSON Error: "+err.Error(), + ) + + return + } + + // Create HTTP request + httpReq := http.NewRequestWithContext( + ctx, + http.MethodPut, + "http://example.com/things", + bytes.NewBuffer(httpReqBody), + ) + + // Send HTTP request + httpResp, err := d.client.Do(httpReq) + defer httpResp.Body.Close() + + if err != nil { + resp.Diagnostics.AddError( + "Unable to Update Resource", + "An unexpected error occurred while attempting to update the resource. "+ + "Please retry the operation or report this issue to the provider developers.\n\n"+ + "HTTP Error: "+err.Error(), + ) + + return + } + + // Return error if the HTTP status code is not 200 OK + if httpResp.StatusCode != http.StatusOK { + resp.Diagnostics.AddError( + "Unable to Update Resource", + "An unexpected error occurred while attempting to update the resource. "+ + "Please retry the operation or report this issue to the provider developers.\n\n"+ + "HTTP Status: "+httpResp.Status, + ) + + return + } + + // Save updated data into Terraform state + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} +``` + +## Caveats + +Note these caveats when implementing the `Update` method: + +* An error is returned if the response state is not set when `Update` is called by the framework. If the resource does not support modification and should always be recreated on configuration value updates, the `Update` logic can be left empty and ensure all configurable schema attributes implement the [`resource.RequiresReplace()` attribute plan modifier](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource#RequiresReplace). +* An error is returned if the response state contains unknown values. Set all attributes to either null or known values in the response. +* An error is returned if the response state has the `RemoveResource()` method called. This method is not valid during update. Return an error if the resource is no longer exists. +* An error is returned unless every null or known value in the request plan is saved exactly as-is into the response state. Only unknown plan values can be modified. + +## Recommendations + +Note these recommendations when implementing the `Update` method: + +* Get request data from the Terraform plan data over configuration data as the schema or resource may include [plan modification](/terraform/plugin/framework/resources/plan-modification) logic which sets plan values. +* Only successfully modified parts of the resource should be return updated data in the state response. +* Use the [`resource.UseStateForUnknown()` attribute plan modifier](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource#UseStateForUnknown) for `Computed` attributes that are known to not change during resource updates. This will enhance the Terraform plan to not show `=> (known after apply)` differences. + +## Additional Use Cases + +This section highlights implementation details for specific use cases. + +### Detect Specific Attribute Changes + +Certain update APIs require that only value changes are sent in the update request or require individual update API calls. Compare the attribute plan value to the attribute prior state value to determine individual attribute changes. + +In this example, the entire plan and prior state are fetched then the attribute values are compared: + +```go +type ThingResourceModel struct { + Name types.String `tfsdk:"name"` + // ... other attributes ... +} + +func (r ThingResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + var plan, state ThingResourceModel + + resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...) + resp.Diagnostics.Append(req.State.Get(ctx, &state)...) + + if resp.Diagnostics.HasError() { + return + } + + // Compare name attribute value between plan and prior state + if !plan.Name.Equal(state.Name) { + // name attribute was changed + } + + // ... further logic ... +} +``` + +In this example, the attribute is individually fetched from the plan and prior state then the values are compared: + +```go +func (r ThingResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + var namePlan, nameState types.String + + resp.Diagnostics.Append(req.Plan.GetAttribute(ctx, path.Root("name"), &namePlan)...) + resp.Diagnostics.Append(req.State.GetAttribute(ctx, path.Root("name"), &nameState)...) + + if resp.Diagnostics.HasError() { + return + } + + // Compare name attribute value between plan and prior state + if !namePlan.Equal(nameState) { + // name attribute was changed + } + + // ... further logic ... +} +``` diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/resources/validate-configuration.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/resources/validate-configuration.mdx new file mode 100644 index 000000000..4a1d93661 --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/resources/validate-configuration.mdx @@ -0,0 +1,86 @@ +--- +page_title: Validate resource configuration +description: >- + Learn how to validate resource configuration with the Terraform plugin + framework. +--- + +# Validate configuration + +[Resources](/terraform/plugin/framework/resources) support validating an entire practitioner configuration in either declarative or imperative logic. Feedback, such as required syntax or acceptable combinations of values, is returned via [diagnostics](/terraform/plugin/framework/diagnostics). + +This page describes implementation details for validating entire resource configurations, typically referencing multiple attributes. Further documentation is available for other configuration validation concepts: + +- [Single attribute validation](/terraform/plugin/framework/validation#attribute-validation) is a schema-based mechanism for implementing attribute-specific validation logic. +- [Type validation](/terraform/plugin/framework/validation#type-validation) is a schema-based mechanism for implementing reusable validation logic for any attribute using the type. + +-> Configuration validation in Terraform occurs without provider configuration ("offline"), so therefore the resource `Configure` method will not have been called. To implement validation with a configured API client, use [plan modification](/terraform/plugin/framework/resources/plan-modification#resource-plan-modification) instead, which occurs during Terraform's planning phase. + +## ConfigValidators Method + +The [`resource.ResourceWithConfigValidators` interface](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource#ResourceWithConfigValidators) follows a similar pattern to attribute validation and allows for a more declarative approach. This enables consistent validation logic across multiple resources. Each validator intended for this interface must implement the [`resource.ConfigValidator` interface](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource#ConfigValidator). + +During execution of the [`terraform validate`](/terraform/cli/commands/validate), [`terraform plan`](/terraform/cli/commands/plan) and [`terraform apply`](/terraform/cli/commands/apply) commands, Terraform calls the provider [`ValidateResourceConfig`](/terraform/plugin/framework/internals/rpcs#validateresourceconfig-rpc) RPC, in which the framework calls the `ConfigValidators` method on resources that implement the [`resource.ResourceWithConfigValidators` interface](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource#ResourceWithConfigValidators). + +The [`terraform-plugin-framework-validators` Go module](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework-validators) has a collection of common use case resource configuration validators in the [`resourcevalidator` package](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework-validators/resourcevalidator). These use [path expressions](/terraform/plugin/framework/path-expressions) for matching attributes. + +This example will raise an error if a practitioner attempts to configure both `attribute_one` and `attribute_two`: + +```go +// Other methods to implement the resource.Resource interface are omitted for brevity +type ThingResource struct {} + +func (r ThingResource) ConfigValidators(ctx context.Context) []resource.ConfigValidator { + return []resource.ConfigValidator{ + resourcevalidator.Conflicting( + path.MatchRoot("attribute_one"), + path.MatchRoot("attribute_two"), + ), + } +} +``` + +## ValidateConfig Method + +The [`resource.ResourceWithValidateConfig` interface](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource#ResourceWithValidateConfig) is more imperative in design and is useful for validating unique functionality across multiple attributes that typically applies to a single resource. + +During execution of the [`terraform validate`](/terraform/cli/commands/validate), [`terraform plan`](/terraform/cli/commands/plan) and [`terraform apply`](/terraform/cli/commands/apply) commands, Terraform calls the provider [`ValidateResourceConfig`](/terraform/plugin/framework/internals/rpcs#validateresourceconfig-rpc) RPC, in which the framework calls the `ValidateConfig` method on providers that implement the [`resource.ResourceWithValidateConfig` interface](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource#ResourceWithValidateConfig). + +This example will raise a warning if a practitioner attempts to configure `attribute_one`, but not `attribute_two`: + +```go +// Other methods to implement the resource.Resource interface are omitted for brevity +type ThingResource struct {} + +type ThingResourceModel struct { + AttributeOne types.String `tfsdk:"attribute_one"` + AttributeTwo types.String `tfsdk:"attribute_two"` +} + +func (r ThingResource) ValidateConfig(ctx context.Context, req resource.ValidateConfigRequest, resp *resource.ValidateConfigResponse) { + var data ThingResourceModel + + resp.Diagnostics.Append(req.Config.Get(ctx, &data)...) + + if resp.Diagnostics.HasError() { + return + } + + // If attribute_one is not configured, return without warning. + if data.AttributeOne.IsNull() || data.AttributeOne.IsUnknown() { + return + } + + // If attribute_two is not null, return without warning. + if !data.AttributeTwo.IsNull() { + return + } + + resp.Diagnostics.AddAttributeWarning( + path.Root("attribute_two"), + "Missing Attribute Configuration", + "Expected attribute_two to be configured with attribute_one. "+ + "The resource may return unexpected results.", + ) +} +``` diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/resources/write-only-arguments.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/resources/write-only-arguments.mdx new file mode 100644 index 000000000..ee61e2b99 --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/resources/write-only-arguments.mdx @@ -0,0 +1,114 @@ +--- +page_title: 'Plugin Development - Framework: Write-only Arguments' +description: >- + How to implement write-only arguments with the provider development framework. +--- + +# Write-only Arguments + +Write-only arguments are managed resource attributes that are configured by practitioners but are not persisted to the Terraform plan or state artifacts. Write-only arguments are supported in Terraform 1.11 and later. +Write-only arguments should be used to handle secret values that do not need to be persisted in Terraform state, such as passwords, API keys, etc. +The provider is expected to be the terminal point for an ephemeral value, +which should either use the value by making the appropriate change to the API or ignore the value. Write-only arguments can accept [ephemeral values](/terraform/language/resources/ephemeral) and are not required to be consistent between plan and apply operations. + +## General Concepts + +The following are high level differences between `Required`/`Optional` arguments and write-only arguments: + +- Write-only arguments can accept ephemeral and non-ephemeral values. + +- Write-only arguments cannot be used with set attributes, set nested attributes, and set nested blocks. + +- Write-only argument values are only available in the configuration. The prior state, planned state, and final state values for +write-only arguments should always be `null`. + - Provider developers do not need to explicitly set write-only argument values to `null` after using them as the plugin framework will handle the nullification of write-only arguments for all RPCs. + +- Any value that is set for a write-only argument in the state or plan (during [Plan Modification](/terraform/plugin/framework/resources/plan-modification)) by the provider will be reverted to `null` by plugin framework before the RPC response is sent to Terraform. + +- Write-only argument values cannot produce a Terraform plan difference. + - This is because the prior state value for a write-only argument will always be `null` and the planned/final state value will also be `null`, therefore, it cannot produce a diff on its own. + - The one exception to this case is if the write-only argument is added to `requires_replace` during Plan Modification (i.e., using the [`RequiresReplace()`](/terraform/plugin/framework/resources/plan-modification#requiresreplace) plan modifier), in that case, the write-only argument will always cause a diff/trigger a resource recreation. + +- Since write-only arguments can accept ephemeral values, write-only argument configuration values are not expected to be consistent between plan and apply. + +## Schema + +An attribute can be made write-only by setting the `WriteOnly` field to `true` in the schema. Attributes with `WriteOnly` set to `true` must also have +one of `Required` or `Optional` set to `true`. If a list nested, map nested, or single nested attribute has `WriteOnly` set to `true`, all child attributes must also have `WriteOnly` set to `true`. +A set nested block cannot have any child attributes with `WriteOnly` set to `true`. `Computed` cannot be set to true for write-only arguments. + +**Schema example:** + +```go +"password_wo": schema.StringAttribute{ + Required: true, + WriteOnly: true, +}, +``` + +## Retrieving Write-only Values + +Write-only argument values should be retrieved from the configuration instead of the plan. Refer to [accessing values](/terraform/plugin/framework/handling-data/accessing-values) for more details on +retrieving values from configuration. + +## PreferWriteOnlyAttribute Validators + +The `PreferWriteOnlyAttribute()` validators available in the [`terraform-plugin-framework-validators` Go module](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework-validators) +can be used when you have a write-only version of an existing attribute, and you want to encourage practitioners to use the write-only version whenever possible. + +The validator returns a warning if the Terraform client is 1.11 or above and the value of the existing attribute is non-null. + +`PreferWriteOnlyAttribute()` is available as a resource-level validator in the [`resourcevalidator` package](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework-validators/resourcevalidator) or +as an attribute-level validator in the `[type]validator` packages (i.e., [`stringvalidator` package](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator)) + +Usage: + +```go +// Resource-level validator +// Used inside a resource.Resource type ConfigValidators method + _ = []resource.ConfigValidator{ + // Throws a warning diagnostic encouraging practitioners to use + // password_wo if password has a known value + resourcevalidator.PreferWriteOnlyAttribute( + path.MatchRoot("password"), + path.MatchRoot("password_wo"), + ), + } + +// Attribute-level validator +// Used within a Schema method of a Resource + _ = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "password": schema.StringAttribute{ + Optional: true, + Validators: []validator.String{ + // Throws a warning diagnostic encouraging practitioners to use + // password_wo if password has a known value. + stringvalidator.PreferWriteOnlyAttribute( + path.MatchRoot("password_wo"), + ), + }, + }, + "password_wo": schema.StringAttribute{ + WriteOnly: true, + Optional: true, + }, + }, + } +``` + +```hcl +resource "example_db_instance" "ex" { + username = "foo" + password = "bar" # returns a warning encouraging practitioners to use `password_wo` instead. +} +``` + +## Best Practices + +Since write-only arguments have no prior values, user intent or value changes cannot be determined with a write-only argument alone. To determine when to use/not use a write-only argument value in your provider, we recommend one of the following: + +- Pair write-only arguments with a configuration attribute (required or optional) to “trigger” the use of the write-only argument + - For example, a `password_wo` write-only argument can be paired with a configured `password_wo_version` attribute. When the `password_wo_version` is modified, the provider will send the `password_wo` value to the API. +- Use a keepers attribute (which is used in the [Random Provider](https://registry.terraform.io/providers/hashicorp/random/latest/docs#resource-keepers)) that will take in arbitrary key-pair values. Whenever there is a change to the `keepers` attribute, the provider will use the write-only argument value. +- Use the resource's [private state](/terraform/plugin/framework/resources/private-state) to store secure hashes of write-only argument values, the provider will then use the hash to determine if a write-only argument value has changed in later Terraform runs. \ No newline at end of file diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/validation.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/validation.mdx new file mode 100644 index 000000000..fedfbafb9 --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/validation.mdx @@ -0,0 +1,518 @@ +--- +page_title: Validation +description: >- + Learn how to validate configuration values using the Terraform plugin framework. +--- + +# Validation + +The framework can return [diagnostics](/terraform/plugin/framework/diagnostics) feedback for values in provider, resource, and data source configurations or [errors](/terraform/plugin/framework/functions/errors) feedback for values in function parameters. This allows you to write validations that give users feedback about required syntax, types, and acceptable values. + +This page describes single attribute, parameter, and type validation concepts that can be used in any data source schema, provider schema, resource schema, or function definition. Further documentation is available for other configuration validation concepts: + +- [Data source validation](/terraform/plugin/framework/data-sources/validate-configuration) for multiple attributes declaratively or imperatively. +- [Provider validation](/terraform/plugin/framework/providers/validate-configuration) for multiple attributes declaratively or imperatively. +- [Resource validation](/terraform/plugin/framework/resources/validate-configuration) for multiple attributes declaratively or imperatively. +- [Ephemeral Resource validation](/terraform/plugin/framework/ephemeral-resources/validate-configuration) for multiple attributes declaratively or imperatively. + +-> **Note:** When implementing validation logic, configuration values may be [unknown](/terraform/plugin/framework/types#unknown) based on the source of the value. Implementations must account for this case, typically by returning early without returning new diagnostics. + +During execution of the [`terraform validate`](/terraform/cli/commands/validate), [`terraform plan`](/terraform/cli/commands/plan), [`terraform apply`](/terraform/cli/commands/apply) and [`terraform destroy`](/terraform/cli/commands/destroy) commands, Terraform calls the provider [`ValidateProviderConfig`](/terraform/plugin/framework/internals/rpcs#validateproviderconfig-rpc), [`ValidateResourceConfig`](/terraform/plugin/framework/internals/rpcs#validateresourceconfig-rpc), [`ValidateDataResourceConfig`](/terraform/plugin/framework/internals/rpcs#validatedataresourceconfig-rpc), and `ValidateEphemeralResourceConfig` RPCs. + +## Default Terraform CLI Validation + +The [Terraform configuration language](/terraform/language) is declarative and an implementation of [HashiCorp Configuration Language](https://github.com/hashicorp/hcl) (HCL). The Terraform CLI is responsible for reading and parsing configurations for validity, based on Terraform's concepts such as `resource` blocks and associated syntax. The Terraform CLI automatically handles basic validation of value type and behavior information based on the provider, resource, or data source schema. For example, the Terraform CLI returns an error when a string value is given where a list value is expected and also when a required attribute is missing from a configuration. + +Terraform CLI syntax and basic schema checks occur during the [`terraform apply`](/terraform/cli/commands/apply), [`terraform destroy`](/terraform/cli/commands/destroy), [`terraform plan`](/terraform/cli/commands/plan), and [`terraform validate`](/terraform/cli/commands/validate) commands. Any additional validation you define with the framework occurs directly after these checks are complete. + +## Attribute Validation + +You can introduce validation on attributes using the generic framework-defined types such as [`types.String`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#String). To do this, supply the `Validators` field with a list of validations, and the framework will return diagnostics from all validators. For example: + +```go +// Typically within the schema.Schema returned by Schema() for a provider, +// resource, or data source. +schema.StringAttribute{ + // ... other Attribute configuration ... + + Validators: []validator.String{ + // These are example validators from terraform-plugin-framework-validators + stringvalidator.LengthBetween(10, 256), + stringvalidator.RegexMatches( + regexp.MustCompile(`^[a-z0-9]+$`), + "must contain only lowercase alphanumeric characters", + ), + }, +} +``` + +All validators in the slice will always be run, regardless of whether previous validators returned an error or not. + +### Common Use Case Attribute Validators + +You can implement attribute validators from the [terraform-plugin-framework-validators Go module](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework-validators), which contains validation handling for many common use cases such as string contents and integer ranges. + +### Creating Attribute Validators + +If there is not an attribute validator in `terraform-plugin-framework-validators` that meets a specific use case, a provider-defined attribute validator can be created. + +To create an attribute validator, you must implement at least one of the [`validator` package](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/schema/validator) interfaces. For example: + +```go +type stringLengthBetweenValidator struct { + Max int + Min int +} + +// Description returns a plain text description of the validator's behavior, suitable for a practitioner to understand its impact. +func (v stringLengthBetweenValidator) Description(ctx context.Context) string { + return fmt.Sprintf("string length must be between %d and %d", v.Min, v.Max) +} + +// MarkdownDescription returns a markdown formatted description of the validator's behavior, suitable for a practitioner to understand its impact. +func (v stringLengthBetweenValidator) MarkdownDescription(ctx context.Context) string { + return fmt.Sprintf("string length must be between `%d` and `%d`", v.Min, v.Max) +} + +// Validate runs the main validation logic of the validator, reading configuration data out of `req` and updating `resp` with diagnostics. +func (v stringLengthBetweenValidator) ValidateString(ctx context.Context, req validator.StringRequest, resp *validator.StringResponse) { + // If the value is unknown or null, there is nothing to validate. + if req.ConfigValue.IsUnknown() || req.ConfigValue.IsNull() { + return + } + + strLen := len(req.ConfigValue.ValueString()) + + if strLen < v.Min || strLen > v.Max { + resp.Diagnostics.AddAttributeError( + req.AttributePath, + "Invalid String Length", + fmt.Sprintf("String length must be between %d and %d, got: %d.", v.Min, v.Max, strLen), + ) + + return + } +} +``` + +Optionally and depending on the complexity, it may be desirable to also create a helper function to instantiate the validator. For example: + +```go +func stringLengthBetween(minLength int, maxLength int) stringLengthBetweenValidator { + return stringLengthBetweenValidator{ + Max: maxLength, + Min: minLength, + } +} +``` + +#### Path Based Attribute Validators + +Attribute validators that need to accept [paths](/terraform/plugin/framework/paths) to reference other attribute data should instead prefer [path expressions](/terraform/plugin/framework/path-expressions). This allows consumers to use either absolute paths starting at the root of a [schema](/terraform/plugin/framework/schemas), or relative paths based on the current attribute path where the validator is called. + +Path expressions may represent one or more actual paths in the data. To find those paths, the process is called path matching. Depending on the actual data, a path match may return a parent path for null or unknown values, since any underlying paths of those null or unknown values would also represent the same value. This framework behavior is used to prevent false positives of returning no paths for null or unknown values. + +The general structure for working with path expressions in an attribute validator is: + +- Merge the given path expression(s) with the current attribute path expression, such as calling the request type `PathExpression` field [`MergeExpressions()` method](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/path#Expression.MergeExpressions). +- Loop through each merged path expression to get the matching paths within the data, such as calling the request type `Config` field [`PathMatches()` method](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/tfsdk#Config.PathMatches). +- Loop through each matched path to get the generic `attr.Value` value, such as calling the request type `Config` field [`GetAttribute()` method](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/tfsdk#Config.GetAttribute). +- Perform null and unknown value checks on the `attr.Value`, such as the [`IsNull()` method](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/attr#Value.IsNull) and [`IsUnknown()` method](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/attr#Value.IsUnknown). +- If the `attr.Value` is not null and not unknown, then use [`tfsdk.ValueAs()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/tfsdk#ValueAs) using the expected value implementation as the target. + +The following example shows a generic path based attribute validator that returns an error if `types.Int64` values at the given path expressions are less than the current attribute `types.Int64` value. + +```go +// Ensure our implementation satisfies the validator.Int64 interface. +var _ validator.Int64 = &int64IsGreaterThanValidator{} + +// int64IsGreaterThanValidator is the underlying type implementing Int64IsGreaterThan. +type int64IsGreaterThanValidator struct { + expressions path.Expressions +} + +// Description returns a plaintext string describing the validator. +func (v int64IsGreaterThanValidator) Description(_ context.Context) string { + return fmt.Sprintf("If configured, must be greater than %s attributes", v.expressions) +} + +// MarkdownDescription returns a Markdown formatted string describing the validator. +func (v int64IsGreaterThanValidator) MarkdownDescription(ctx context.Context) string { + return v.Description(ctx) +} + +// Validate performs the validation logic for the validator. +func (v int64IsGreaterThanValidator) ValidateInt64(ctx context.Context, req validator.Int64Request, resp *validator.Int64Response) { + // If the current attribute configuration is null or unknown, there + // cannot be any value comparisons, so exit early without error. + if req.ConfigValue.IsNull() || req.ConfigValue.IsUnknown() { + return + } + + // Combine the given path expressions with the current attribute path + // expression. This call automatically handles relative and absolute + // expressions. + expressions := req.AttributePathExpression.MergeExpressions(v.expressions...) + + // For each expression, find matching paths. + for _, expression := range expressions { + // Find paths matching the expression in the configuration data. + matchedPaths, diags := req.Config.PathMatches(ctx, expression) + + resp.Diagnostics.Append(diags...) + + // Collect all errors + if diags.HasError() { + continue + } + + // For each matched path, get the data and compare. + for _, matchedPath := range matchedPaths { + // Fetch the generic attr.Value at the given path. This ensures any + // potential parent value of a different type, which can be a null + // or unknown value, can be safely checked without raising a type + // conversion error. + var matchedPathValue attr.Value + + diags := req.Config.GetAttribute(ctx, matchedPath, &matchedPathValue) + + resp.Diagnostics.Append(diags...) + + // Collect all errors + if diags.HasError() { + continue + } + + // If the matched path value is null or unknown, we cannot compare + // values, so continue to other matched paths. + if matchedPathValue.IsNull() || matchedPathValue.IsUnknown() { + continue + } + + // Now that we know the matched path value is not null or unknown, + // it is safe to attempt converting it to the intended attr.Value + // implementation, in this case a types.Int64 value. + var matchedPathConfig types.Int64 + + diags = tfsdk.ValueAs(ctx, matchedPathValue, &matchedPathConfig) + + resp.Diagnostics.Append(diags...) + + // If the matched path value was not able to be converted from + // attr.Value to the intended types.Int64 implementation, it most + // likely means that the path expression was not pointing at a + // types.Int64Type attribute. Collect the error and continue to + // other matched paths. + if diags.HasError() { + continue + } + + if matchedPathConfig.ValueInt64() >= attributeConfig.ValueInt64() { + resp.Diagnostics.AddAttributeError( + matchedPath, + "Invalid Attribute Value", + fmt.Sprintf("Must be less than %s value: %d", req.AttributePath, attributeConfig.ValueInt64()), + ) + } + } + } +} + +// Int64IsGreaterThan checks that any Int64 values in the paths described by the +// path.Expression are less than the current attribute value. +func Int64IsGreaterThan(expressions ...path.Expression) validator.Int64 { + return &int64IsGreaterThanValidator{ + expressions: expressions, + } +} +``` + +## Parameter Validation + +You can introduce validation on function parameters using the generic framework-defined types such as [`types.String`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#String). To do this, supply the `Validators` field with a list of validations, and the framework will return errors from all validators. For example: + +```go +// Typically within the function.Definition for a function. +function.StringParameter{ + // ... other Parameter configuration ... + + Validators: []function.StringParameterValidator{ + stringvalidator.LengthBetween(10, 256), + }, +}, +``` + +All validators in the slice will always be run, regardless of whether previous validators returned an error or not. + +### Creating Parameter Validators + +To create a parameter validator, you must implement at least one of the [`function` package](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/function) `ParameterValidator` interfaces. For example: + +```go +// Ensure the implementation satisfies the expected interfaces +var ( + _ function.StringParameterValidator = stringLengthBetweenValidator{} +) + +type stringLengthBetweenValidator struct { + Max int + Min int +} + +// Validate runs the main validation logic of the validator, reading configuration data out of `req` and updating `resp` with diagnostics. +func (v stringLengthBetweenValidator) ValidateParameterString(ctx context.Context, req validator.StringParameterValidatorRequest, resp *validator.StringParameterValidatorResponse) { + // If the value is unknown or null, there is nothing to validate. + if req.Value.IsUnknown() || req.Value.IsNull() { + return + } + + strLen := len(req.Value.ValueString()) + + if strLen < v.Min || strLen > v.Max { + resp.Error = function.NewArgumentFuncError( + req.ArgumentPosition, + fmt.Sprintf("Invalid String Length: String length must be between %d and %d, got: %d.", v.Min, v.Max, strLen), + ) + + return + } +} +``` + +Optionally and depending on the complexity, it may be desirable to also create a helper function to instantiate the validator. For example: + +```go +func stringLengthBetween(minLength int, maxLength int) stringLengthBetweenValidator { + return stringLengthBetweenValidator{ + Max: maxLength, + Min: minLength, + } +} +``` + +A single validator type can be used as both an attribute validator and a parameter validator, as long as the validator implements the appropriate interfaces. For example: + +```go +var ( + _ validator.String = stringLengthBetweenValidator{} + _ function.StringParameterValidator = stringLengthBetweenValidator{} + +) +type stringLengthBetweenValidator struct { + Max int + Min int +} + +// Description returns a plain text description of the attribute validator's behavior, suitable for a practitioner to understand its impact. +func (v stringLengthBetweenValidator) Description(ctx context.Context) string { + return fmt.Sprintf("string length must be between %d and %d", v.Min, v.Max) +} + +// MarkdownDescription returns a markdown formatted description of the attribute validator's behavior, suitable for a practitioner to understand its impact. +func (v stringLengthBetweenValidator) MarkdownDescription(ctx context.Context) string { + return fmt.Sprintf("string length must be between `%d` and `%d`", v.Min, v.Max) +} + +// Validate runs the main validation logic of the attribute validator, reading configuration data out of `req` and updating `resp` with diagnostics. +func (v stringLengthBetweenValidator) ValidateString(ctx context.Context, req validator.StringRequest, resp *validator.StringResponse) { + // If the value is unknown or null, there is nothing to validate. + if req.ConfigValue.IsUnknown() || req.ConfigValue.IsNull() { + return + } + + strLen := len(req.ConfigValue.ValueString()) + + if strLen < v.Min || strLen > v.Max { + resp.Diagnostics.AddAttributeError( + req.AttributePath, + "Invalid String Length", + fmt.Sprintf("String length must be between %d and %d, got: %d.", v.Min, v.Max, strLen), + ) + + return + } +} + +// Validate runs the main validation logic of the parameter validator, reading configuration data out of `req` and updating `resp` with diagnostics. +func (v stringLengthBetweenValidator) ValidateParameterString(ctx context.Context, req validator.StringParameterValidatorRequest, resp *validator.StringParameterValidatorResponse) { + // If the value is unknown or null, there is nothing to validate. + if req.Value.IsUnknown() || req.Value.IsNull() { + return + } + + strLen := len(req.Value.ValueString()) + + if strLen < v.Min || strLen > v.Max { + resp.Error = function.NewArgumentFuncError( + req.ArgumentPosition, + fmt.Sprintf("Invalid String Length: String length must be between %d and %d, got: %d.", v.Min, v.Max, strLen), + ) + + return + } +} +``` + +## Value Validation + +Validation of custom value types can be used for both attribute values and provider-defined function parameter values. This can be useful if you have consistent validation rules for a specific value type across multiple attributes or function parameters. + +When you implement validation on a custom value type associated with a schema attribute, you do not need to declare the same validation on the attribute, but you can supply additional validations in that manner. For example: + +```go +// Typically within the schema.Schema returned by Schema() for a provider, +// resource, or data source. +schema.StringAttribute{ + // ... other Attribute configuration ... + + // This is an example type with a corresponding custom value type + // which implements its own validation + CustomType: computeInstanceIdentifierType, + + // This is optional, example validation that is checked in addition + // to any validation performed by the custom value type + Validators: []validator.String{ + stringvalidator.LengthBetween(10, 256), + }, +} +``` + +### Defining Value Validation + +To support validation for a custom value type, you must implement [`xattr.ValidateableAttribute` interface](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/attr/xattr#ValidateableAttribute) for attribute validation, or [`function.ValidateableParameter` interface](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/function#ValidateableParameter) for provider-defined function parameter validation. + +Both interfaces can be implemented if the same custom value type is used for both attributes and function parameters, for example: + +```go +// Ensure the implementation satisfies the expected interfaces +var ( + _ basetypes.StringValuable = computeInstanceIdentifierValue{} + _ xattr.ValidateableAttribute = computeInstanceIdentifierValue{} + _ function.ValidateableParameter = computeInstanceIdentifierValue{} +) + +// Other methods to implement the attr.Value interface are omitted for brevity +type computeInstanceIdentifierValue struct { + basetypes.StringValue +} + +// Implementation of the xattr.ValidateableAttribute interface +func (v computeInstanceIdentifierValue) ValidateAttribute(ctx context.Context, req xattr.ValidateAttributeRequest, resp *xattr.ValidateAttributeResponse) { + if v.IsNull() || v.IsUnknown() { + return + } + + if !v.isValid(v.ValueString()) { + resp.Diagnostics.AddAttributeError( + req.Path, + "Compute Instance Type Validation Error", + fmt.Sprintf("Missing `instance-` prefix, got: %s", v.ValueString()), + ) + + return + } +} + +// Implementation of the function.ValidateableParameter interface +func (v computeInstanceIdentifierValue) ValidateParameter(ctx context.Context, req function.ValidateParameterRequest, resp *function.ValidateParameterResponse) { + if v.IsNull() || v.IsUnknown() { + return + } + + if !v.isValid(v.ValueString()) { + resp.Error = function.NewArgumentFuncError( + req.Position, + fmt.Sprintf("Compute Instance Type Validation Error: Missing `instance-` prefix, got: %s", v.ValueString()), + ) + + return + } +} + +func (v computeInstanceIdentifierValue) isValid(in string) bool { + return strings.HasPrefix(in, "instance-") +} +``` + +## Type Validation + + + +`Value` validation should be used in preference to `Type` validation. Refer to [Value Validation](#value-validation) for more information. + + + +You may want to create a custom type to simplify schemas if your provider contains common attribute values with consistent validation rules. When you implement validation on a type, you do not need to declare the same validation on the attribute, but you can supply additional validations in that manner. For example: + +```go +// Typically within the schema.Schema returned by Schema() for a provider, +// resource, or data source. +schema.StringAttribute{ + // ... other Attribute configuration ... + + // This is an example type which implements its own validation + CustomType: computeInstanceIdentifierType, + + // This is optional, example validation that is checked in addition + // to any validation performed by the type + Validators: []validator.String{ + stringvalidator.LengthBetween(10, 256), + }, +} +``` + +### Defining Type Validation + + + +The [`xattr.TypeWithValidate` interface](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/attr/xattr#TypeWithValidate) has been deprecated. Refer to [Defining Value Validation](#defining-value-validation) for more information about using [`xattr.ValidateableAttribute` interface](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/attr/xattr#ValidateableAttribute), and [`function.ValidateableParameter` interface](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/function#ValidateableParameter) instead. + + + +To support validation within a type, you must implement the [`xattr.TypeWithValidate` interface](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/attr/xattr#TypeWithValidate). For example: + +```go +// Ensure type satisfies xattr.TypeWithValidate interface +var _ xattr.TypeWithValidate = computeInstanceIdentifierType{} + +// Other methods to implement the attr.Type interface are omitted for brevity +type computeInstanceIdentifierType struct {} + +func (t computeInstanceIdentifierType) Validate(ctx context.Context, tfValue tftypes.Value, path path.Path) diag.Diagnostics { + var diags diag.Diagnostics + + if !tfValue.Type().Equal(tftypes.String) { + diags.AddAttributeError( + path, + "Compute Instance Type Validation Error", + fmt.Sprintf("Expected String value, received %T with value: %v", tfValue, tfValue), + ) + return diags + } + + if !tfValue.IsKnown() || tfValue.IsNull() { + return diags + } + + var value string + err := tfValue.As(&value) + + if err != nil { + diags.AddAttributeError( + path, + "Compute Instance Type Validation Error", + fmt.Sprintf("Cannot convert value to string: %s", err), + ) + return diags + } + + if !strings.HasPrefix(value, "instance-") { + diags.AddAttributeError( + path, + "Compute Instance Type Validation Error", + fmt.Sprintf("Missing `instance-` prefix, got: %s", value), + ) + return diags + } +} +``` diff --git a/content/terraform-plugin-framework/v1.15.x/img/.gitkeep b/content/terraform-plugin-framework/v1.15.x/img/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/content/terraform-plugin-framework/v1.15.x/img/README.md b/content/terraform-plugin-framework/v1.15.x/img/README.md new file mode 100644 index 000000000..66e6d2d63 --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/img/README.md @@ -0,0 +1,25 @@ +# Image Generation + +The images are exported as PNG from Whimsical. + +`Export` => `Size 2x` => `Include Background`. + +| Image | Whimsical | +|------------------------------------------|---------------------------------------------------------------------------------------------| +| apply-resource-change-detail.png | https://whimsical.com/applyresourcechange-FK9oXfU3ugzRyj4YNWuHZU | +| apply-resource-change-overview.png | https://whimsical.com/applyresourcechange-FK9oXfU3ugzRyj4YNWuHZU | +| configure-provider-detail.png | https://whimsical.com/configureprovider-5kUd5iMxCRQ1hVs4QiE9B8 | +| configure-provider-overview.png | https://whimsical.com/configureprovider-5kUd5iMxCRQ1hVs4QiE9B8 | +| get-provider-schema-detail.png | https://whimsical.com/getproviderschema-5dwaqjyiFoUv9DPTk9RYy1 | +| get-provider-schema-overview.png | https://whimsical.com/getproviderschema-5dwaqjyiFoUv9DPTk9RYy1 | +| plan-resource-change-detail.png | https://whimsical.com/planresourcechange-S5G9LdER9CuqRCjBmqDG5m | +| plan-resource-change-overview.png | https://whimsical.com/planresourcechange-S5G9LdER9CuqRCjBmqDG5m | +| read-data-source-detail.png | https://whimsical.com/read-resource-datasource-UGcyGChMxm8w12dh5emiD8 | +| read-overview.png | https://whimsical.com/read-resource-datasource-UGcyGChMxm8w12dh5emiD8 | +| read-resource-detail.png | https://whimsical.com/read-resource-datasource-UGcyGChMxm8w12dh5emiD8 | +| validate-config-overview.png | https://whimsical.com/validate-provider-resource-dataresource-config-E51CLC4wycgKoZfsTjZuVA | +| validate-data-resource-config-detail.png | https://whimsical.com/validate-provider-resource-dataresource-config-E51CLC4wycgKoZfsTjZuVA | +| validate-provider-config-detail.png | https://whimsical.com/validate-provider-resource-dataresource-config-E51CLC4wycgKoZfsTjZuVA | +| validate-resource-config-detail.png | https://whimsical.com/validate-provider-resource-dataresource-config-E51CLC4wycgKoZfsTjZuVA | + + diff --git a/content/terraform-plugin-framework/v1.15.x/img/apply-resource-change-detail.png b/content/terraform-plugin-framework/v1.15.x/img/apply-resource-change-detail.png new file mode 100644 index 000000000..0aec59100 Binary files /dev/null and b/content/terraform-plugin-framework/v1.15.x/img/apply-resource-change-detail.png differ diff --git a/content/terraform-plugin-framework/v1.15.x/img/apply-resource-change-overview.png b/content/terraform-plugin-framework/v1.15.x/img/apply-resource-change-overview.png new file mode 100644 index 000000000..7b22b5d26 Binary files /dev/null and b/content/terraform-plugin-framework/v1.15.x/img/apply-resource-change-overview.png differ diff --git a/content/terraform-plugin-framework/v1.15.x/img/configure-provider-detail.png b/content/terraform-plugin-framework/v1.15.x/img/configure-provider-detail.png new file mode 100644 index 000000000..d17fa9aea Binary files /dev/null and b/content/terraform-plugin-framework/v1.15.x/img/configure-provider-detail.png differ diff --git a/content/terraform-plugin-framework/v1.15.x/img/configure-provider-overview.png b/content/terraform-plugin-framework/v1.15.x/img/configure-provider-overview.png new file mode 100644 index 000000000..653b3db45 Binary files /dev/null and b/content/terraform-plugin-framework/v1.15.x/img/configure-provider-overview.png differ diff --git a/content/terraform-plugin-framework/v1.15.x/img/get-provider-schema-detail.png b/content/terraform-plugin-framework/v1.15.x/img/get-provider-schema-detail.png new file mode 100644 index 000000000..1958583ea Binary files /dev/null and b/content/terraform-plugin-framework/v1.15.x/img/get-provider-schema-detail.png differ diff --git a/content/terraform-plugin-framework/v1.15.x/img/get-provider-schema-overview.png b/content/terraform-plugin-framework/v1.15.x/img/get-provider-schema-overview.png new file mode 100644 index 000000000..d87f34d44 Binary files /dev/null and b/content/terraform-plugin-framework/v1.15.x/img/get-provider-schema-overview.png differ diff --git a/content/terraform-plugin-framework/v1.15.x/img/plan-resource-change-detail.png b/content/terraform-plugin-framework/v1.15.x/img/plan-resource-change-detail.png new file mode 100644 index 000000000..7402d85d2 Binary files /dev/null and b/content/terraform-plugin-framework/v1.15.x/img/plan-resource-change-detail.png differ diff --git a/content/terraform-plugin-framework/v1.15.x/img/plan-resource-change-overview.png b/content/terraform-plugin-framework/v1.15.x/img/plan-resource-change-overview.png new file mode 100644 index 000000000..cd21fc053 Binary files /dev/null and b/content/terraform-plugin-framework/v1.15.x/img/plan-resource-change-overview.png differ diff --git a/content/terraform-plugin-framework/v1.15.x/img/read-data-source-detail.png b/content/terraform-plugin-framework/v1.15.x/img/read-data-source-detail.png new file mode 100644 index 000000000..f74b9bda6 Binary files /dev/null and b/content/terraform-plugin-framework/v1.15.x/img/read-data-source-detail.png differ diff --git a/content/terraform-plugin-framework/v1.15.x/img/read-overview.png b/content/terraform-plugin-framework/v1.15.x/img/read-overview.png new file mode 100644 index 000000000..edaa30bf7 Binary files /dev/null and b/content/terraform-plugin-framework/v1.15.x/img/read-overview.png differ diff --git a/content/terraform-plugin-framework/v1.15.x/img/read-resource-detail.png b/content/terraform-plugin-framework/v1.15.x/img/read-resource-detail.png new file mode 100644 index 000000000..a43a0cf61 Binary files /dev/null and b/content/terraform-plugin-framework/v1.15.x/img/read-resource-detail.png differ diff --git a/content/terraform-plugin-framework/v1.15.x/img/validate-config-overview.png b/content/terraform-plugin-framework/v1.15.x/img/validate-config-overview.png new file mode 100644 index 000000000..db1e36856 Binary files /dev/null and b/content/terraform-plugin-framework/v1.15.x/img/validate-config-overview.png differ diff --git a/content/terraform-plugin-framework/v1.15.x/img/validate-data-resource-config-detail.png b/content/terraform-plugin-framework/v1.15.x/img/validate-data-resource-config-detail.png new file mode 100644 index 000000000..78191ad32 Binary files /dev/null and b/content/terraform-plugin-framework/v1.15.x/img/validate-data-resource-config-detail.png differ diff --git a/content/terraform-plugin-framework/v1.15.x/img/validate-provider-config-detail.png b/content/terraform-plugin-framework/v1.15.x/img/validate-provider-config-detail.png new file mode 100644 index 000000000..7c02dc2a9 Binary files /dev/null and b/content/terraform-plugin-framework/v1.15.x/img/validate-provider-config-detail.png differ diff --git a/content/terraform-plugin-framework/v1.15.x/img/validate-resource-config-detail.png b/content/terraform-plugin-framework/v1.15.x/img/validate-resource-config-detail.png new file mode 100644 index 000000000..b6217cecd Binary files /dev/null and b/content/terraform-plugin-framework/v1.15.x/img/validate-resource-config-detail.png differ