diff --git a/website/data/plugin-framework-nav-data.json b/website/data/plugin-framework-nav-data.json index df52085b6..6d8f359d8 100644 --- a/website/data/plugin-framework-nav-data.json +++ b/website/data/plugin-framework-nav-data.json @@ -7,8 +7,21 @@ "path": "" }, { - "title": "Learn Collection", - "href": "https://learn.hashicorp.com/collections/terraform/providers-plugin-framework?utm_source=WEBSITE&utm_medium=WEB_IO&utm_offer=ARTICLE_PAGE&utm_content=DOCS" + "title": "Getting Started", + "routes": [ + { + "title": "Provider Code Walkthrough", + "path": "getting-started/code-walkthrough" + }, + { + "title": "Tutorials", + "href": "https://learn.hashicorp.com/collections/terraform/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", @@ -102,28 +115,45 @@ ] }, { - "title": "Schemas", - "path": "schemas" - }, - { - "title": "Paths", - "path": "paths" - }, - { - "title": "Path Expressions", - "path": "path-expressions" - }, - { - "title": "Attribute Types", - "path": "types" - }, - { - "title": "Accessing State, Config, and Plan", - "path": "accessing-values" - }, - { - "title": "Writing State", - "path": "writing-state" + "title": "Handling Data", + "routes": [ + { + "title": "Terraform Concepts", + "path": "handling-data/terraform-concepts" + }, + { + "title": "Schemas", + "path": "handling-data/schemas" + }, + { + "title": "Attributes", + "path": "handling-data/attributes" + }, + { + "title": "Blocks", + "path": "handling-data/blocks" + }, + { + "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": "Conversion Rules", + "path": "handling-data/conversion-rules" + } + ] }, { "title": "Returning Errors and Warnings", diff --git a/website/docs/plugin/framework/getting-started/code-walkthrough.mdx b/website/docs/plugin/framework/getting-started/code-walkthrough.mdx new file mode 100644 index 000000000..32db1b1ec --- /dev/null +++ b/website/docs/plugin/framework/getting-started/code-walkthrough.mdx @@ -0,0 +1,380 @@ +--- +page_title: 'Plugin Development - Framework: Getting Started - Code Walkthrough' +description: >- + How to setup and configure a simple plugin 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](/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](/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](/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`](/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`](/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`](/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`](/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](/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`](/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`](/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`](/plugin/framework/resources/create): This function lets the provider create a new resource of this type. +- [`Read`](/plugin/framework/resources/read): This function lets the provider read resource values in order to update state. +- [`Update`](/plugin/framework/resources/update): This function lets the provider update the resource and state. +- [`Delete`](/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](/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`](/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`](/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`](/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](/plugin/framework/data-sources) for more details and configuration examples. + +## Terraform Configuration + +With the definitions we have for [provider server](#provider-server), [provider](#provider), [resource](#resource) and [data source](#data-source), we can 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 is detailed in [Core Configuration Concepts](/plugin/framework/getting-started/core-configuration-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 is detailed in [Core Configuration Concepts](/plugin/framework/getting-started/core-configuration-concepts). diff --git a/website/docs/plugin/framework/handling-data/accessing-values.mdx b/website/docs/plugin/framework/handling-data/accessing-values.mdx new file mode 100644 index 000000000..e86e863b3 --- /dev/null +++ b/website/docs/plugin/framework/handling-data/accessing-values.mdx @@ -0,0 +1,116 @@ +--- +page_title: 'Plugin Development - Framework: Access State, Config, and Plan' +description: |- + How to read values from state, config, and plan in the Terraform plugin + framework. +--- + +# Accessing State, Config, and Plan + +-> **Note:** The Plugin Framework is in beta. + +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 [conversion rules](#conversion-rules) 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. + +Refer to the [conversion rules](/plugin/framework/handling-data/conversion-rules#converting-from-framework-types-to-go-types) +for more information about supported Go types. diff --git a/website/docs/plugin/framework/types.mdx b/website/docs/plugin/framework/handling-data/attributes.mdx similarity index 100% rename from website/docs/plugin/framework/types.mdx rename to website/docs/plugin/framework/handling-data/attributes.mdx diff --git a/website/docs/plugin/framework/handling-data/blocks.mdx b/website/docs/plugin/framework/handling-data/blocks.mdx new file mode 100644 index 000000000..c9558d04b --- /dev/null +++ b/website/docs/plugin/framework/handling-data/blocks.mdx @@ -0,0 +1,209 @@ +--- +page_title: 'Plugin Development - Framework: Handling Data - Blocks' +description: >- + Blocks. +--- + +# Blocks + +The Terraform language uses a block as a container for other attributes and blocks. Terraform implements many top level blocks, such as `provider` and `resource`, while providers can implement nested blocks in their schema to enable practitioners to configure data. + +-> Use [nested attributes](/plugin/framework/handling-data/terraform-concepts#nested-attributes) for new schema implementations. Block support is mainly for migrating prior SDK-based providers. + +In this example, the Terraform-defined `resource` block contains a provider-defined `ami` attribute and `network_interface` block. + +```hcl +resource "aws_instance" "example" { + ami = "abc123" + + network_interface { + # ... + } +} +``` + +The configuration syntax for a provider-defined block is a type (block name) and the body is delimited by the `{` and `}` characters. Each block has an associated nesting mode, which declares whether a block is repeatable. The available nesting modes are: + +- List: Ordered collection of objects +- Set: Unordered collection of objects +- Single: One object + +For list and set blocks, configurations can implement [`dynamic` block expressions](/language/expressions/dynamic-blocks) to reduce hardcoded block values. + +## Terraform Configuration + +Use the following syntax in Terraform configuration for nested blocks: + +```hcl +resource "example_resource" "example" { + list_nested_block { + bool_attribute = true + float64_attribute = 1234.5 + int64_attribute = 9223372036854775807 + list_attribute = ["list-element", "list-element"] + list_nested_nested_block { + bool_attribute = true + } + list_nested_nested_block { + bool_attribute = false + } + } + list_nested_block { + bool_attribute = true + float64_attribute = 1234.5 + int64_attribute = 9223372036854775807 + list_attribute = ["list-element", "list-element"] + list_nested_nested_block { + bool_attribute = true + } + list_nested_nested_block { + bool_attribute = false + } + } + + set_nested_block { + map_attribute = { "map-key-1" : "map-value-1" } + number_attribute = 1.79769313486231570814527423731704356798070e+1000 + object_attribute = { + bool_attribute = true + float64_attribute = 1234.5 + int64_attribute = 9223372036854775807 + list_attribute = ["obj-list-element", "obj-list-element"] + map_attribute = { "obj-map-key-1" : "obj-map-value-1" } + number_attribute = 1.79769313486231570814527423731704356798070e+1000 + set_attribute = ["obj-set-element-1", "obj-set-element-2"] + string_attribute = "obj-string" + } + set_attribute = ["set-element-1", "set-element-2"] + set_nested_nested_block { + bool_attribute = true + } + set_nested_nested_block { + bool_attribute = false + } + } + set_nested_block { + map_attribute = { "map-key-1" : "map-value-1" } + number_attribute = 1.79769313486231570814527423731704356798070e+1000 + object_attribute = { + bool_attribute = false + float64_attribute = 1234.5 + int64_attribute = 9223372036854775807 + list_attribute = ["obj-list-element", "obj-list-element"] + map_attribute = { "obj-map-key-1" : "obj-map-value-1" } + number_attribute = 1.79769313486231570814527423731704356798070e+1000 + set_attribute = ["obj-set-element-1", "obj-set-element-2"] + string_attribute = "obj-string" + } + set_attribute = ["set-element-1", "set-element-2"] + set_nested_nested_block { + bool_attribute = true + } + set_nested_nested_block { + bool_attribute = false + } + } + + single_nested_block { + bool_attribute = true + float64_attribute = 1234.5 + } +} +``` + +## Schema + +Define nested blocks in the schema as follows: + +```go +func (e *exampleResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + Blocks: map[string]schema.Block{ + "list_nested_block": schema.ListNestedBlock{ + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "bool_attribute": schema.BoolAttribute{ + Optional: true, + }, + "float64_attribute": schema.Float64Attribute{ + Optional: true, + }, + + "int64_attribute": schema.Int64Attribute{ + Optional: true, + }, + "list_attribute": schema.ListAttribute{ + Optional: true, + ElementType: types.StringType, + }, + }, + Blocks: map[string]schema.Block{ + "list_nested_nested_block": schema.ListNestedBlock{ + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "bool_attribute": schema.BoolAttribute{ + Optional: true, + }, + }, + }, + }, + }, + }, + }, + + "set_nested_block": schema.SetNestedBlock{ + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "map_attribute": schema.MapAttribute{ + Optional: true, + ElementType: types.StringType, + }, + "number_attribute": schema.NumberAttribute{ + Optional: true, + }, + "object_attribute": schema.ObjectAttribute{ + Optional: true, + AttributeTypes: map[string]attr.Type{ + "bool_attribute": types.BoolType, + "float64_attribute": types.Float64Type, + "int64_attribute": types.Int64Type, + "list_attribute": types.ListType{ElemType: types.StringType}, + "map_attribute": types.MapType{ElemType: types.StringType}, + "number_attribute": types.NumberType, + "set_attribute": types.ListType{ElemType: types.StringType}, + "string_attribute": types.StringType, + }, + }, + "set_attribute": schema.SetAttribute{ + Optional: true, + ElementType: types.StringType, + }, + }, + Blocks: map[string]schema.Block{ + "set_nested_nested_block": schema.SetNestedBlock{ + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "bool_attribute": schema.BoolAttribute{ + Optional: true, + }, + }, + }, + }, + }, + }, + }, + + "single_nested_block": schema.SingleNestedBlock{ + Attributes: map[string]schema.Attribute{ + "bool_attribute": schema.BoolAttribute{ + Optional: true, + }, + "float64_attribute": schema.Float64Attribute{ + Optional: true, + }, + }, + }, + }, + } +} +``` diff --git a/website/docs/plugin/framework/accessing-values.mdx b/website/docs/plugin/framework/handling-data/conversion-rules.mdx similarity index 53% rename from website/docs/plugin/framework/accessing-values.mdx rename to website/docs/plugin/framework/handling-data/conversion-rules.mdx index 44f71aec9..c1feaee86 100644 --- a/website/docs/plugin/framework/accessing-values.mdx +++ b/website/docs/plugin/framework/handling-data/conversion-rules.mdx @@ -1,124 +1,20 @@ --- -page_title: 'Plugin Development - Framework: Access State, Config, and Plan' +page_title: 'Plugin Development - Framework: Conversion Rules' description: |- - How to read values from state, config, and plan in the Terraform provider - development framework. + Converting from Framework Types to Go types and from Go types + to Framework types. --- -# Accessing State, Config, and Plan +# Conversion Rules --> **Note:** The Plugin Framework is in beta. - -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 [conversion rules](#conversion-rules) 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. - -## Conversion Rules +## Converting from Framework Types to Go Types !> **Warning:** It can be tempting to use Go types instead of `attr.Value` implementations when the provider doesn't care about the distinction between an empty value, unknown, and null. But if Terraform has a null or unknown value and the provider asks the framework to store it in a type that can't hold it, -`Get` will return an error. Make sure the types you are using can hold the +[`Get`](plugin/framework/handling-data/accessing-values#get-the-entire-configuration-plan-or-state) +will return an error. Make sure the types you are using can hold the values they might contain! ### String @@ -192,7 +88,8 @@ otherwise, the conversion rules for that type will apply. ### Detected Interfaces -`Get` detects and utilizes the following interfaces, if the target implements +[`Get`](/plugin/framework/handling-data/accessing-values#get-the-entire-configuration-plan-or-state) +detects and utilizes the following interfaces, if the target implements them. #### ValueConverter @@ -236,3 +133,109 @@ It will be considered capable of handling null values, and those methods will be used to populate it and retrieve its value. The `interface{}` being passed and retrieved will be of a type that can be passed to [`tftypes.NewValue`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-go/tftypes#NewValue). + +## Converting from Go Types to Framework Types + +The following is a list of schema types and the Go types they know how to +accept in [`Set`](/plugin/framework/handling-data/writing-state#replace-the-entire-state) +and [`SetAttribute`](/plugin/framework/handling-data/writing-state#set-a-single-attribute-or-block-value). + +### String + +Strings can be automatically created from Go's `string` type (or any aliases of +it, like `type MyString string`). + +### Number + +Numbers can be automatically created from the following numeric types (or any +aliases of them, like `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) + +### Boolean + +Booleans can be automatically created from Go's `bool` type (or any aliases of +it, like `type MyBoolean bool`). + +### List + +Lists can be automatically created from any Go slice type (or alias of a Go +slice type, like `type MyList []string`), with the elements either being +`attr.Value` implementations or being converted according to these rules. + +### Map + +Maps can be automatically created from any Go map type with string keys (or any +alias of a Go map type with string keys, like `type MyMap map[string]int`), +with the elements either being `attr.Value` implementations or being converted +according to these rules. + +### Object + +Objects can be automatically created from any Go struct type with that follows +these constraints: + +* Every property on the struct must have a `tfsdk` struct tag. +* The `tfsdk` struct tag must name an attribute in the object that it is being +mapped to or be set to `-` to explicitly declare it does not map to an +attribute in the object. +* Every attribute in the object must have a corresponding struct tag. + +These rules help prevent typos and human error from unwittingly discarding +information by failing as early, consistently, and loudly as possible. + +Properties can either be `attr.Value` implementations or will be converted +according to these rules. + +### Pointers + +A nil pointer will be treated as a null value. Otherwise, the rules for the +type the pointer is referencing apply. + +### Detected Interfaces + +`Set` detects and utilizes the following interfaces, if the target implements +them. + +#### ValueCreator + +If a value is set on a Go type that implements the [`tftypes.ValueCreator` +interface](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-go/tftypes#ValueCreator), +that interface will be delegated to to handle the conversion. + +#### Unknownable + +If a value is set on a Go type that fills the `Unknownable` interface: + +```go +type Unknownable interface { + SetUnknown(context.Context, bool) error + SetValue(context.Context, interface{}) error + GetUnknown(context.Context) bool + GetValue(context.Context) interface{} +} +``` + +It will be used to convert the value. The `interface{}` being passed and +retrieved will be of a type that can be passed to +[`tftypes.NewValue`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-go/tftypes#NewValue). + +#### Nullable + +If a value is set on a Go type that fills the `Nullable` interface: + +```go +type Nullable interface { + SetNull(context.Context, bool) error + SetValue(context.Context, interface{}) error + GetNull(context.Context) bool + GetValue(context.Context) interface{} +} +``` + +It will be used to convert the value. The `interface{}` being passed and +retrieved will be of a type that can be passed to +[`tftypes.NewValue`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-go/tftypes#NewValue). diff --git a/website/docs/plugin/framework/path-expressions.mdx b/website/docs/plugin/framework/handling-data/path-expressions.mdx similarity index 100% rename from website/docs/plugin/framework/path-expressions.mdx rename to website/docs/plugin/framework/handling-data/path-expressions.mdx diff --git a/website/docs/plugin/framework/paths.mdx b/website/docs/plugin/framework/handling-data/paths.mdx similarity index 97% rename from website/docs/plugin/framework/paths.mdx rename to website/docs/plugin/framework/handling-data/paths.mdx index fa9cd37e9..f3c602089 100644 --- a/website/docs/plugin/framework/paths.mdx +++ b/website/docs/plugin/framework/handling-data/paths.mdx @@ -9,7 +9,7 @@ description: >- -> **Note:** The Plugin Framework is in beta. -An exact location within a [schema](/plugin/framework/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. +An exact location within a [schema](/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 diff --git a/website/docs/plugin/framework/schemas.mdx b/website/docs/plugin/framework/handling-data/schemas.mdx similarity index 97% rename from website/docs/plugin/framework/schemas.mdx rename to website/docs/plugin/framework/handling-data/schemas.mdx index 554c04d11..48014a597 100644 --- a/website/docs/plugin/framework/schemas.mdx +++ b/website/docs/plugin/framework/handling-data/schemas.mdx @@ -74,7 +74,7 @@ The value is the implementation details for the attribute. -> Only supported when using protocol version 6. -Nested attributes enable provider developers to define objects of attributes which fully support attribute behaviors and practitioners to configure these directly using [expressions](/language/expressions). +[Nested attributes](/plugin/framework/handling-data/terraform-concepts#nested-attributes) enable provider developers to define objects of attributes which fully support attribute behaviors and practitioners to configure these directly using [expressions](/language/expressions). #### SingleNestedAttribute diff --git a/website/docs/plugin/framework/handling-data/terraform-concepts.mdx b/website/docs/plugin/framework/handling-data/terraform-concepts.mdx new file mode 100644 index 000000000..9edef640a --- /dev/null +++ b/website/docs/plugin/framework/handling-data/terraform-concepts.mdx @@ -0,0 +1,729 @@ +--- +page_title: 'Plugin Development - Framework: Handling Data - Terraform Concepts' +description: >- + Configuration, Schemas, Attributes and Blocks. +--- + +# Terraform Concepts + +Schemas specify the fields that a provider, resource or data source configuration can have. + +The fields defined within the schema are either attributes or blocks. + +Attributes and blocks within Terraform configuration have different syntax, either using, or omitting an equals sign in their definition, respectively. + +Providers, resources and data sources have their own type-specific schema, which use type-specific attributes and blocks. + +The following examples use a resource but the same schema attribute and block types are also defined with the schema for providers and data sources. The examples focus on type-related information, the `Optional` field is included just to provide working examples but refer to [Schema](/plugin/framework/handling-data/schemas) for information about the other fields that can be defined within attributes and blocks. + +## Attributes + +### Simple Attributes + +Simple attributes include attributes for boolean, float64, int64, number and string. + +#### Terraform Configuration + +Use the following syntax in Terraform configuration for simple attributes: + +```hcl +resource "example_resource" "example" { + bool_attribute = true + + float64_attribute = 1234.5 + + int64_attribute = 9223372036854775807 + + number_attribute = 1.79769313486231570814527423731704356798070e+1000 + + string_attribute = "string" +``` + +#### Schema + +Define simple attributes in the schema as follows: + +```go +func (e *exampleResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "bool_attribute": schema.BoolAttribute{ + Optional: true, + }, + + "float64_attribute": schema.Float64Attribute{ + Optional: true, + }, + + "int64_attribute": schema.Int64Attribute{ + Optional: true, + }, + + "number_attribute": schema.NumberAttribute{ + Optional: true, + }, + + "string_attribute": schema.StringAttribute{ + Optional: true, + }, + }, + } +} +``` + +### Collection Attributes + +Collection attributes include attributes for lists, maps and sets. + +#### Terraform Configuration + +Use the following syntax in Terraform configuration for collection attributes: + +```hcl +resource "example_resource" "example" { + list_attribute = ["list-element", "list-element"] + + map_attribute = { "map-key-1" : "map-value-1" } + + set_attribute = ["set-element-1", "set-element-2"] +} +``` + +#### Schema + +Define collection attributes in the schema as follows: + +```go +func (e *exampleResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "list_attribute": schema.ListAttribute{ + Optional: true, + ElementType: types.StringType, + }, + + "map_attribute": schema.MapAttribute{ + Optional: true, + ElementType: types.StringType, + }, + + "set_attribute": schema.SetAttribute{ + Optional: true, + ElementType: types.StringType, + }, + }, + } +} +``` + +### Object Attribute + +Use an object attribute when the attribute is an atomic unit and all fields defined within it need to be specified. + +Nested attributes (i.e., ListNestedAttribute, MapNestedAttribute, SetNestedAttribute and SingleNestedAttribute) should be used when any of the inner fields should have their own flags or metadata (e.g., Required, Optional, Sensitive etc). + +#### Terraform Configuration + +Use the following syntax in Terraform configuration for object attributes: + +```hcl +resource "example_resource" "example" { + object_attribute = { + bool_attribute = true + float64_attribute = 1234.5 + int64_attribute = 9223372036854775807 + list_attribute = ["obj-list-element", "obj-list-element"] + map_attribute = { "obj-map-key-1" : "obj-map-value-1" } + number_attribute = 1.79769313486231570814527423731704356798070e+1000 + set_attribute = ["obj-set-element-1", "obj-set-element-2"] + string_attribute = "obj-string" + } +} +``` + +#### Schema + +Define object attributes in the schema as follows: + +```go +func (e *exampleResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "object_attribute": schema.ObjectAttribute{ + Optional: true, + AttributeTypes: map[string]attr.Type{ + "bool_attribute": types.BoolType, + "float64_attribute": types.Float64Type, + "int64_attribute": types.Int64Type, + "list_attribute": types.ListType{ElemType: types.StringType}, + "map_attribute": types.MapType{ElemType: types.StringType}, + "number_attribute": types.NumberType, + "set_attribute": types.ListType{ElemType: types.StringType}, + "string_attribute": types.StringType, + }, + }, + }, + } +} +``` + +### Nested Attributes + +#### Terraform Configuration + +Use the following syntax in Terraform configuration for nested attributes: + +```hcl +resource "example_resource" "example" { + list_nested_attribute = [ + { + int64_attribute = 9223372036854775807 + list_attribute = ["list-element", "list-element"] + }, + { + int64_attribute = 9223372036854775807 + list_attribute = ["list-element", "list-element"] + } + ] + + map_nested_attribute = { + "one" = { + map_attribute = { "map-key-1" : "map-value-1" } + number_attribute = 1.79769313486231570814527423731704356798070e+1000 + }, + "two" = { + map_attribute = { "map-key-1" : "map-value-1" } + number_attribute = 1.79769313486231570814527423731704356798070e+1000 + } + } + + set_nested_attribute = [ + { + object_attribute = { + bool_attribute = true + float64_attribute = 1234.5 + int64_attribute = 9223372036854775807 + list_attribute = ["obj-list-element", "obj-list-element"] + map_attribute = { "obj-map-key-1" : "obj-map-value-1" } + number_attribute = 1.79769313486231570814527423731704356798070e+1000 + set_attribute = ["obj-set-element-1", "obj-set-element-2"] + string_attribute = "obj-string" + } + set_attribute = ["set-element-1", "set-element-2"] + string_attribute = "string" + }, + { + object_attribute = { + bool_attribute = false + float64_attribute = 1234.5 + int64_attribute = 9223372036854775807 + list_attribute = ["obj-list-element", "obj-list-element"] + map_attribute = { "obj-map-key-1" : "obj-map-value-1" } + number_attribute = 1.79769313486231570814527423731704356798070e+1000 + set_attribute = ["obj-set-element-1", "obj-set-element-2"] + string_attribute = "obj-string" + } + set_attribute = ["set-element-1", "set-element-2"] + string_attribute = "string" + } + ] + + single_nested_attribute = { + bool_attribute = true + float64_attribute = 1234.5 + } +} +``` + +#### Schema + +Define nested attributes in the schema as follows: + +```go +func (e *exampleResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "list_nested_attribute": schema.ListNestedAttribute{ + Optional: true, + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "int64_attribute": schema.Int64Attribute{ + Optional: true, + }, + "list_attribute": schema.ListAttribute{ + Optional: true, + ElementType: types.StringType, + }, + }, + }, + }, + + "map_nested_attribute": schema.MapNestedAttribute{ + Optional: true, + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "map_attribute": schema.MapAttribute{ + Optional: true, + ElementType: types.StringType, + }, + "number_attribute": schema.NumberAttribute{ + Optional: true, + }, + }, + }, + }, + + "set_nested_attribute": schema.SetNestedAttribute{ + Optional: true, + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "object_attribute": schema.ObjectAttribute{ + Optional: true, + AttributeTypes: map[string]attr.Type{ + "bool_attribute": types.BoolType, + "float64_attribute": types.Float64Type, + "int64_attribute": types.Int64Type, + "list_attribute": types.ListType{ElemType: types.StringType}, + "map_attribute": types.MapType{ElemType: types.StringType}, + "number_attribute": types.NumberType, + "set_attribute": types.ListType{ElemType: types.StringType}, + "string_attribute": types.StringType, + }, + }, + + "set_attribute": schema.SetAttribute{ + Optional: true, + ElementType: types.StringType, + }, + "string_attribute": schema.StringAttribute{ + Optional: true, + }, + }, + }, + }, + + "single_nested_attribute": schema.SingleNestedAttribute{ + Optional: true, + Attributes: map[string]schema.Attribute{ + "bool_attribute": schema.BoolAttribute{ + Optional: true, + }, + "float64_attribute": schema.Float64Attribute{ + Optional: true, + }, + }, + }, + }, + } +} +``` + +## Blocks + +### Nested Blocks + +#### Terraform Configuration + +Use the following syntax in Terraform configuration for nested blocks: + +```hcl +resource "example_resource" "example" { + list_nested_block { + bool_attribute = true + float64_attribute = 1234.5 + int64_attribute = 9223372036854775807 + list_attribute = ["list-element", "list-element"] + list_nested_nested_block { + bool_attribute = true + } + list_nested_nested_block { + bool_attribute = false + } + } + list_nested_block { + bool_attribute = true + float64_attribute = 1234.5 + int64_attribute = 9223372036854775807 + list_attribute = ["list-element", "list-element"] + list_nested_nested_block { + bool_attribute = true + } + list_nested_nested_block { + bool_attribute = false + } + } + + set_nested_block { + map_attribute = { "map-key-1" : "map-value-1" } + number_attribute = 1.79769313486231570814527423731704356798070e+1000 + object_attribute = { + bool_attribute = true + float64_attribute = 1234.5 + int64_attribute = 9223372036854775807 + list_attribute = ["obj-list-element", "obj-list-element"] + map_attribute = { "obj-map-key-1" : "obj-map-value-1" } + number_attribute = 1.79769313486231570814527423731704356798070e+1000 + set_attribute = ["obj-set-element-1", "obj-set-element-2"] + string_attribute = "obj-string" + } + set_attribute = ["set-element-1", "set-element-2"] + set_nested_nested_block { + bool_attribute = true + } + set_nested_nested_block { + bool_attribute = false + } + } + set_nested_block { + map_attribute = { "map-key-1" : "map-value-1" } + number_attribute = 1.79769313486231570814527423731704356798070e+1000 + object_attribute = { + bool_attribute = false + float64_attribute = 1234.5 + int64_attribute = 9223372036854775807 + list_attribute = ["obj-list-element", "obj-list-element"] + map_attribute = { "obj-map-key-1" : "obj-map-value-1" } + number_attribute = 1.79769313486231570814527423731704356798070e+1000 + set_attribute = ["obj-set-element-1", "obj-set-element-2"] + string_attribute = "obj-string" + } + set_attribute = ["set-element-1", "set-element-2"] + set_nested_nested_block { + bool_attribute = true + } + set_nested_nested_block { + bool_attribute = false + } + } + + single_nested_block { + bool_attribute = true + float64_attribute = 1234.5 + } +} +``` + +#### Schema + +Define nested blocks in the schema as follows: + +```go +func (e *exampleResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + Blocks: map[string]schema.Block{ + "list_nested_block": schema.ListNestedBlock{ + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "bool_attribute": schema.BoolAttribute{ + Optional: true, + }, + "float64_attribute": schema.Float64Attribute{ + Optional: true, + }, + + "int64_attribute": schema.Int64Attribute{ + Optional: true, + }, + "list_attribute": schema.ListAttribute{ + Optional: true, + ElementType: types.StringType, + }, + }, + Blocks: map[string]schema.Block{ + "list_nested_nested_block": schema.ListNestedBlock{ + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "bool_attribute": schema.BoolAttribute{ + Optional: true, + }, + }, + }, + }, + }, + }, + }, + + "set_nested_block": schema.SetNestedBlock{ + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "map_attribute": schema.MapAttribute{ + Optional: true, + ElementType: types.StringType, + }, + "number_attribute": schema.NumberAttribute{ + Optional: true, + }, + "object_attribute": schema.ObjectAttribute{ + Optional: true, + AttributeTypes: map[string]attr.Type{ + "bool_attribute": types.BoolType, + "float64_attribute": types.Float64Type, + "int64_attribute": types.Int64Type, + "list_attribute": types.ListType{ElemType: types.StringType}, + "map_attribute": types.MapType{ElemType: types.StringType}, + "number_attribute": types.NumberType, + "set_attribute": types.ListType{ElemType: types.StringType}, + "string_attribute": types.StringType, + }, + }, + "set_attribute": schema.SetAttribute{ + Optional: true, + ElementType: types.StringType, + }, + }, + Blocks: map[string]schema.Block{ + "set_nested_nested_block": schema.SetNestedBlock{ + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "bool_attribute": schema.BoolAttribute{ + Optional: true, + }, + }, + }, + }, + }, + }, + }, + + "single_nested_block": schema.SingleNestedBlock{ + Attributes: map[string]schema.Attribute{ + "bool_attribute": schema.BoolAttribute{ + Optional: true, + }, + "float64_attribute": schema.Float64Attribute{ + Optional: true, + }, + }, + }, + }, + } +} +``` + +## Custom Types + +You can use custom types for both attributes and blocks. + +### Custom Type and Value + +For instance, a minimal implementation of a custom type for `ListType` and `List` that leverages embedding looks as follows: + +```go +type CustomListType struct { + types.ListType +} + +func (c CustomListType) ValueFromTerraform(ctx context.Context, in tftypes.Value) (attr.Value, error) { + val, err := c.ListType.ValueFromTerraform(ctx, in) + + return CustomListValue{ + // unchecked type assertion + val.(types.List), + }, err +} + +type CustomListValue struct { + types.List +} + +func (c CustomListValue) DoSomething(ctx context.Context) { + tflog.Info(ctx, "called DoSomething on CustomListValue") +} +``` + +### Terraform Configuration + +Using the custom type does not require any changes to the Terraform configuration. + +```hcl +resource "example_resource" "example" { + list_attribute = ["list-element", "list-element"] + + list_nested_attribute = [ + { + int64_attribute = 9223372036854775807 + list_attribute = ["list-element", "list-element"] + }, + { + int64_attribute = 9223372036854775807 + list_attribute = ["list-element", "list-element"] + } + ] + + list_nested_block { + bool_attribute = true + float64_attribute = 1234.5 + int64_attribute = 9223372036854775807 + list_attribute = ["list-element", "list-element"] + list_nested_nested_block { + bool_attribute = true + } + list_nested_nested_block { + bool_attribute = false + } + } + list_nested_block { + bool_attribute = true + float64_attribute = 1234.5 + int64_attribute = 9223372036854775807 + list_attribute = ["list-element", "list-element"] + list_nested_nested_block { + bool_attribute = true + } + list_nested_nested_block { + bool_attribute = false + } + } +} +``` + +### Schema + +Use the custom type in the schema as follows: + +```go +func (e *exampleResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "list_attribute": schema.ListAttribute{ + Optional: true, + ElementType: types.StringType, + CustomType: CustomListType{ + types.ListType{ + ElemType: types.StringType, + }, + }, + }, + + "list_nested_attribute": schema.ListNestedAttribute{ + Optional: true, + CustomType: CustomListType{ + types.ListType{ + ElemType: types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "int64_attribute": types.Int64Type, + "list_attribute": types.ListType{ + ElemType: types.StringType, + }, + }, + }, + }, + }, + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "int64_attribute": schema.Int64Attribute{ + Optional: true, + }, + "list_attribute": schema.ListAttribute{ + Optional: true, + ElementType: types.StringType, + }, + }, + }, + }, + }, + + Blocks: map[string]schema.Block{ + "list_nested_block": schema.ListNestedBlock{ + CustomType: CustomListType{ + types.ListType{ + ElemType: types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "bool_attribute": types.BoolType, + "float64_attribute": types.Float64Type, + "int64_attribute": types.Int64Type, + "list_attribute": types.ListType{ + ElemType: types.StringType, + }, + "list_nested_nested_block": types.ListType{ + ElemType: types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "bool_attribute": types.BoolType, + }, + }, + }, + }, + }, + }, + }, + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "bool_attribute": schema.BoolAttribute{ + Optional: true, + }, + "float64_attribute": schema.Float64Attribute{ + Optional: true, + }, + + "int64_attribute": schema.Int64Attribute{ + Optional: true, + }, + "list_attribute": schema.ListAttribute{ + Optional: true, + ElementType: types.StringType, + }, + }, + Blocks: map[string]schema.Block{ + "list_nested_nested_block": schema.ListNestedBlock{ + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "bool_attribute": schema.BoolAttribute{ + Optional: true, + }, + }, + }, + }, + }, + }, + }, + }, + } +} +``` + +### Model + +The custom type value is then used within the model. + +Where previously the model would have looked as follows: + +```go +type exampleResourceData struct { + ListAttribute types.List `tfsdk:"list_attribute"` + ListNestedAttribute types.List `tfsdk:"list_nested_attribute"` + ListNestedBlock types.List `tfsdk:"list_nested_block"` +} +``` + +The custom type value is used by updating the model to: + +```go +type exampleResourceData struct { + ListAttribute CustomListValue `tfsdk:"list_attribute"` + ListNestedAttribute CustomListValue `tfsdk:"list_nested_attribute"` + ListNestedBlock CustomListValue `tfsdk:"list_nested_block"` +} +``` + +### Create + +The functions on `CustomListValue` are then available. + +```go +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 + } + + data.ListAttribute.DoSomething(ctx) + data.ListNestedAttribute.DoSomething(ctx) + data.ListNestedBlock.DoSomething(ctx) + + /*...*/ +} +``` diff --git a/website/docs/plugin/framework/handling-data/writing-state.mdx b/website/docs/plugin/framework/handling-data/writing-state.mdx new file mode 100644 index 000000000..a9adaf084 --- /dev/null +++ b/website/docs/plugin/framework/handling-data/writing-state.mdx @@ -0,0 +1,88 @@ +--- +page_title: 'Plugin Development - Framework: Writing State' +description: >- + How to write and update the Terraform statefile using the provider development + framework. +--- + +# Writing State + +-> **Note:** The Plugin Framework is in beta. + +One of the primary jobs of a Terraform provider is to manage the provider's +resources and data sources in the [Terraform state](/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 [conversion rules](#conversion-rules) 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 [conversion rules](/plugin/framework/handling-data/conversion-rules#converting-from-go-types-to-framework-types) +for more information about supported Go types. \ No newline at end of file diff --git a/website/docs/plugin/framework/migrating/attributes-blocks/attribute-schema.mdx b/website/docs/plugin/framework/migrating/attributes-blocks/attribute-schema.mdx index c07cf3dc6..37a303d43 100644 --- a/website/docs/plugin/framework/migrating/attributes-blocks/attribute-schema.mdx +++ b/website/docs/plugin/framework/migrating/attributes-blocks/attribute-schema.mdx @@ -9,7 +9,7 @@ description: >- -> **Note:** The Plugin Framework is in beta. Attributes define how users can configure values for your Terraform provider, resources, and data sources. Refer to -[Schemas - Attributes](/plugin/framework/schemas#attributes) in the Framework documentation for details. +[Schemas - Attributes](/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. @@ -62,47 +62,34 @@ pages in the Attributes & Blocks section of this migration guide for more detail ## Example -The following examples show how to migrate portions of the -[http](https://github.com/hashicorp/terraform-provider-http) provider. - -For a complete example, clone the -`terraform-provider-http` repository and compare the `data_source.go` file in -[v2.2.0](https://github.com/hashicorp/terraform-provider-http/blob/v2.2.0/internal/provider/data_source.go) -with the `data_source_http.go` file -[after the migration](https://github.com/hashicorp/terraform-provider-http/blob/8527d5b4546b54cdef246a13befc5745dbbbf740/internal/provider/data_source_http.go). - - ### SDKv2 -The following example from the `data_source.go` file shows the implementation of the `url` attribute for the `http` +The following example shows the implementation of the `example_attribute` attribute for the `exampleDataSource` data source. ```go -func dataSource() *schema.Resource { +func exampleDataSource() *schema.Resource { return &schema.Resource{ /* ... */ Schema: map[string]*schema.Schema{ - "url": { - Description: "The URL for the request. Supported schemes are `http` and `https`.", + "example_attribute": { Type: schema.TypeString, Required: true, }, ``` - ### Framework The following shows the same section of provider code after the migration. -This code implements the `url` attribute for the `http` data source with the Framework. +This code implements the `example_attribute` attribute for the `exampleDataSource` data source with the Framework. ```go -func (d *httpDataSource) Schema(ctx context.Context, req datasource.SchemaRequest, resp *datasource.SchemaResponse) { +func (d *exampleDataSource) Schema(ctx context.Context, req datasource.SchemaRequest, resp *datasource.SchemaResponse) { return schema.Schema{ /* ... */ Attributes: map[string]schema.Attribute{ - "url": schema.StringAttribute{ - Description: "The URL for the request. Supported schemes are `http` and `https`.", + "example_attribute": schema.StringAttribute{ Required: true, }, /* ... */ diff --git a/website/docs/plugin/framework/migrating/attributes-blocks/blocks-computed.mdx b/website/docs/plugin/framework/migrating/attributes-blocks/blocks-computed.mdx index 3a5110c29..5ca7564cf 100644 --- a/website/docs/plugin/framework/migrating/attributes-blocks/blocks-computed.mdx +++ b/website/docs/plugin/framework/migrating/attributes-blocks/blocks-computed.mdx @@ -73,28 +73,18 @@ map[string]schema.Attribute{ ## Example -The following examples show how to migrate portions of the [tls](https://github.com/hashicorp/terraform-provider-tls) -provider. - -For a complete example, clone the -`terraform-provider-tls` repository and compare the `data_source_certificate.go` file in -[v3.4.0](https://github.com/hashicorp/terraform-provider-tls/blob/v3.4.0/internal/provider/data_source_certificate.go) -with -[v4.0.1](https://github.com/hashicorp/terraform-provider-tls/blob/v4.0.1/internal/provider/data_source_certificate.go). - ### SDKv2 -The following example from the `data_source_certificate.go` file shows the implementation of the `certificates` nested -block on the `certificate` data source's schema. +The following example shows the implementation of the `example_nested_block` with SDKv2. ```go Schema: map[string]*schema.Schema{ -"certificates": { +"example_nested_block": { Type: schema.TypeList, Computed: true, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ - "signature_algorithm": { + "example_block_attribute": { Type: schema.TypeString, Computed: true, /* ... */ @@ -105,15 +95,15 @@ Schema: map[string]*schema.Schema{ The following shows the same section of provider code after the migration. -This code defines the `certificates` block as an attribute on the `certificate` data source's schema, where the +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{ - "certificates": schema.ListAttribute{ + "example_nested_block": schema.ListAttribute{ ElementType: types.ObjectType{ AttrTypes: map[string]attr.Type{ - "signature_algorithm": types.StringType, + "example_block_attribute": types.StringType, }, }, Computed: true, diff --git a/website/docs/plugin/framework/migrating/attributes-blocks/blocks.mdx b/website/docs/plugin/framework/migrating/attributes-blocks/blocks.mdx index fd3d3f390..a84dd55d7 100644 --- a/website/docs/plugin/framework/migrating/attributes-blocks/blocks.mdx +++ b/website/docs/plugin/framework/migrating/attributes-blocks/blocks.mdx @@ -75,39 +75,28 @@ func (r *ThingResource) Schema(ctx context.Context, req resource.SchemaRequest, /* ... */ ``` - ## Example -The following examples show how to migrate portions of the [tls](https://github.com/hashicorp/terraform-provider-tls) -provider. - -For a complete example, clone the -`terraform-provider-tls` repository and compare the `common_cert.go` file in -[v3.4.0](https://github.com/hashicorp/terraform-provider-tls/blob/v3.4.0/internal/provider/common_cert.go) -with the `resource_cert_request.go` file in -[v4.0.1](https://github.com/hashicorp/terraform-provider-tls/blob/v4.0.1/internal/provider/resource_cert_request.go). - ### SDKv2 -The following example from the `common_cert.go` file shows the implementation of the `subject` nested block on the -`cert_request` resource's schema with SDKv2. +The following example shows the implementation of the `example_nested_block` nested block with SDKv2. ```go map[string]*schema.Schema{ - "private_key_pem": &schema.Schema{ + "example_attribute": &schema.Schema{ Type: schema.TypeString, /* ... */ - "subject" = &schema.Schema{ + "example_nested_block" = &schema.Schema{ Type: schema.TypeList, MaxItems: 1, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ - "organization": { + "example_block_attribute_one": { Type: schema.TypeString, /* ... */ }, - "common_name": { + "example_block_attribute_two": { Type: schema.TypeString, /* ... */ }, @@ -116,23 +105,23 @@ map[string]*schema.Schema{ ### Framework -The following example from the `resource_cert_request.go` file shows how the nested `subject` block on the -`cert_request` resource is defined with the Framework after the migration. +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{ - "private_key_pem": schema.StringAttribute{ + "example_attribute": schema.StringAttribute{ /* ... */ Blocks: map[string]schema.Block{ - "subject": schema.ListNestedBlock{ + "example_nested_block": schema.ListNestedBlock{ NestedObject: schema.NestedBlockObject{ Attributes: map[string]schema.Attribute{ - "organization": schema.StringAttribute{ + "example_block_attribute_one": schema.StringAttribute{ /* ... */ }, - "common_name": schema.StringAttribute{ + "example_block_attribute_two": schema.StringAttribute{ /* ... */ }, Validators: validator.List{ diff --git a/website/docs/plugin/framework/migrating/attributes-blocks/default-values.mdx b/website/docs/plugin/framework/migrating/attributes-blocks/default-values.mdx index a1ee6b465..b0322793d 100644 --- a/website/docs/plugin/framework/migrating/attributes-blocks/default-values.mdx +++ b/website/docs/plugin/framework/migrating/attributes-blocks/default-values.mdx @@ -61,24 +61,16 @@ In the Framework, you must implement an attribute plan modifier to set default v ## Example -The following examples show how to migrate portions of the [tls](https://github.com/hashicorp/terraform-provider-tls) -provider. - -For a complete example, clone the -`terraform-provider-tls` repository and compare the `resource_private_key.go` file in -[v3.4.0](https://github.com/hashicorp/terraform-provider-tls/blob/v3.4.0/internal/provider/resource_private_key.go) with the file -[after the migration](https://github.com/hashicorp/terraform-provider-tls/blob/4dafb105818e45a88532f917e7b170ee2a9bb092/internal/provider/resource_private_key.go). - ### SDKv2 -The following example from the `resource_private_key.go` file shows the implementation of the `Default` field for the -`rsa_bits` attribute on the `tls_private_key` resource with SDKv2. +The following example shows the implementation of the `Default` field for the +`example_attribute` attribute on the `exampleResource` resource with SDKv2. ```go -func resourcePrivateKey() *schema.Resource { +func exampleResource() *schema.Resource { return &schema.Resource{ Schema: map[string]*schema.Schema{ - "rsa_bits": { + "example_attribute": { Default: 2048, /* ... */ }, @@ -89,13 +81,13 @@ func resourcePrivateKey() *schema.Resource { The following shows the same section of code after the migration. -This code implements the `PlanModifiers` field for the `rsa_bits` attribute with the Framework. +This code implements the `PlanModifiers` field for the `example_attribute` attribute with the Framework. ```go -func (r *privateKeyResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { +func (r *exampleResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { resp.Schema = schema.Schema{ Attributes: map[string]schema.Attribute{ - "rsa_bits": schema.Int64Attribute{ + "example_attribute": schema.Int64Attribute{ PlanModifiers: []planmodifier.Int64{ attribute_plan_modifier.Int64DefaultValue(types.Int64Value(2048)), /* ... */ @@ -108,8 +100,8 @@ func (r *privateKeyResource) Schema(ctx context.Context, req resource.SchemaRequ } ``` -The following example from the `attribute_plan_modifiers.go` file implements the `DefaultValue` attribute plan modifier -that the `rsa_bits` attribute uses. +The following example implements the `Int64DefaultValue` attribute plan modifier +that the `example_attribute` attribute uses. ```go func Int64DefaultValue(v types.Int64) planmodifier.Int64 { diff --git a/website/docs/plugin/framework/migrating/attributes-blocks/fields.mdx b/website/docs/plugin/framework/migrating/attributes-blocks/fields.mdx index a5f22c0e2..3dea7f97c 100644 --- a/website/docs/plugin/framework/migrating/attributes-blocks/fields.mdx +++ b/website/docs/plugin/framework/migrating/attributes-blocks/fields.mdx @@ -9,7 +9,7 @@ description: >- -> **Note:** The Plugin Framework is in beta. A subset of attribute fields, such as required, optional, computed, or sensitive, define attribute behavior as boolean flags. Refer to -[Schemas - Attributes](/plugin/framework/schemas#required) in the Framework documentation for details. +[Schemas - Attributes](/plugin/framework/handling-data/schemas#required) in the Framework documentation for details. This page explains how to migrate the required, optional, computed, and sensitive attribute fields from SDKv2 to the Framework. @@ -50,25 +50,16 @@ func (r *ThingResource) Schema(ctx context.Context, req resource.SchemaRequest, ## Example -The following examples show how to migrate portions of the [http](https://github.com/hashicorp/terraform-provider-http) -provider. - -For a complete example, clone the -`terraform-provider-http` repository and compare the `data_source.go` file in -[v2.2.0](https://github.com/hashicorp/terraform-provider-http/blob/v2.2.0/internal/provider/data_source.go) -and the `data_source_http.go` file -[after the migration](https://github.com/hashicorp/terraform-provider-http/blob/8527d5b4546b54cdef246a13befc5745dbbbf740/internal/provider/data_source_http.go). - ### SDKv2 -The following example from the `data_source.go` file shows how the `url` attribute on the `http` data source is set to +The following example shows how the `example_attribute` attribute on the `exampleDataSource` data source is set to be required with SDKv2. ```go -func dataSource() *schema.Resource { +func exampleDataSource() *schema.Resource { return &schema.Resource{ Schema: map[string]*schema.Schema{ - "url": { + "example_attribute": { Required: true, /* ... */ }, @@ -77,14 +68,14 @@ func dataSource() *schema.Resource { ### Framework -The following example from the `data_source_http.go` file shows how the `url` attribute on the `http` data source is set +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 *httpDataSource) Schema(ctx context.Context, req datasource.SchemaRequest, resp *datasource.SchemaResponse) { +func (d *exampleDataSource) Schema(ctx context.Context, req datasource.SchemaRequest, resp *datasource.SchemaResponse) { resp.Schema = schema.Schema{ Attributes: map[string]schema.Attribute{ - "url": schema.StringAttribute{ + "example_attribute": schema.StringAttribute{ Required: true, /* ... */ }, diff --git a/website/docs/plugin/framework/migrating/attributes-blocks/force-new.mdx b/website/docs/plugin/framework/migrating/attributes-blocks/force-new.mdx index 592586cec..00d9837f0 100644 --- a/website/docs/plugin/framework/migrating/attributes-blocks/force-new.mdx +++ b/website/docs/plugin/framework/migrating/attributes-blocks/force-new.mdx @@ -60,25 +60,18 @@ the plan has already been modified. ## Example -The following examples show how to migrate portions of the -[random](https://github.com/hashicorp/terraform-provider-random) provider. - -For a complete example, clone the -`terraform-random-provider` repository and compare the `resource_password.go` file in -[v3.3.2](https://github.com/hashicorp/terraform-provider-random/blob/v3.3.2/internal/provider/resource_password.go) -with the file [after the migration](https://github.com/hashicorp/terraform-provider-random/blob/04292d3e31f71ff16b82512082baed037bb1069c/internal/provider/resource_password.go). - ### SDKv2 -The following example from the `resource_password.go` file shows the implementation of the `ForceNew` field of the -`random_password` resource's `keepers` attribute with SDKv2. +The following example shows the implementation of the `ForceNew` field of the +`exampleResource` resource's `example_attribute` attribute with SDKv2. ```go -func resourcePassword() *schema.Resource { +func exampleResource() *schema.Resource { return &schema.Resource{ Schema: map[string]*schema.Schema{ - "keepers": { + "example_attribute": { ForceNew: true, + Type: schema.TypeMap, /* ... */ }, /* ... */ @@ -92,14 +85,14 @@ func resourcePassword() *schema.Resource { The following shows the same section of provider code after the migration. -This code forces the replacement of a `random_password` resource when the value of the `keepers` attribute is changed. -The example does this using the `PlanModifiers` field within the `random_password` attribute's schema. +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 *passwordResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { +func (r *exampleResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { resp.Schema = schema.Schema{ Attributes: map[string]schema.Attribute{ - "keepers": schema.MapAttribute{ + "example_attribute": schema.MapAttribute{ PlanModifiers: []planmodifier.Map{ mapplanmodifier.RequiresReplace(), }, diff --git a/website/docs/plugin/framework/migrating/attributes-blocks/types.mdx b/website/docs/plugin/framework/migrating/attributes-blocks/types.mdx index 9c0863753..4495b764f 100644 --- a/website/docs/plugin/framework/migrating/attributes-blocks/types.mdx +++ b/website/docs/plugin/framework/migrating/attributes-blocks/types.mdx @@ -106,27 +106,18 @@ Remember the following differences between SDKv2 and the Framework when completi - In the Framework, the `schema.Attribute` implementation determines the required details. -## Example - -The following examples show how to migrate portions of the -[http](https://github.com/hashicorp/terraform-provider-http) provider. - -For a complete example, clone the -`terraform-provider-http` repository and compare the `data_source.go` file in -[v2.2.0](https://github.com/hashicorp/terraform-provider-http/blob/v2.2.0/internal/provider/data_source.go) -and the `data_source_http.go` file -[after the migration](https://github.com/hashicorp/terraform-provider-http/blob/8527d5b4546b54cdef246a13befc5745dbbbf740/internal/provider/data_source_http.go). +## Examples ### SDKv2 -The following example from the `data_source.go` file shows the implementation of the type field of the `url` attribute -for the `http` data source with 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 dataSource() *schema.Resource { +func exampleDataSource() *schema.Resource { return &schema.Resource{ Schema: map[string]*schema.Schema{ - "url": { + "example_string_attribute": { Type: schema.TypeString, /* ... */ }, @@ -135,38 +126,29 @@ func dataSource() *schema.Resource { ### Framework -The following example from the `data_source_http.go` file shows how the type of the `url` attribute for the `http` data +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 *httpDataSource) Schema(ctx context.Context, req datasource.SchemaRequest, resp *datasource.SchemaResponse) { +func (d *exampleDataSource) Schema(ctx context.Context, req datasource.SchemaRequest, resp *datasource.SchemaResponse) { resp.Schema = schema.Schema{ Attributes: map[string]schema.Attribute{ - "url": schema.StringAttribute{ + "example_string_attribute": schema.StringAttribute{ /* ... */ }, /* ... */ ``` -The following examples show how to migrate portions of the -[tls](https://github.com/hashicorp/terraform-provider-tls) provider. - -For a complete example, clone the -`terraform-provider-tls` repository and compare the `common_cert.go` file in -[v3.4.0](https://github.com/hashicorp/terraform-provider-tls/blob/v3.4.0/internal/provider/common_cert.go) -and the `resource_cert_request.go` file -[after the migration](https://github.com/hashicorp/terraform-provider-tls/blob/4dafb105818e45a88532f917e7b170ee2a9bb092/internal/provider/resource_cert_request.go). - ### SDKv2 -The following example from the `common_cert.go` file shows the implementation of the type field of the `dns_names` +The following example shows the implementation of the type field of the `example_list_attribute` attribute with SDKv2. ```go -func resourceCertRequest() *schema.Resource { +func exampleResource() *schema.Resource { return &schema.Resource{ Schema: map[string]*schema.Schema{ - "dns_names": { + "example_list_attribute": { Type: schema.TypeList, Elem: &schema.Schema{ Type: schema.TypeString, @@ -178,16 +160,19 @@ func resourceCertRequest() *schema.Resource { ### Framework -The following example from the `data_source_http.go` file shows how the type of the `url` attribute for the `http` data -source is defined with the Framework after the migration. +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 *certRequestResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { +func (r *exampleResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { resp.Schema = schema.Schema{ Attributes: map[string]schema.Attribute{ - "dns_names": schema.ListAttribute{ + "example_list_attribute": schema.ListAttribute{ ElementType: types.StringType, /* ... */ }, /* ... */ ``` + +Refer to [Core Configuration Concepts - Attributes](/plugin/framework/getting-started/core-configuration-concepts#attributes) +for further examples of different types of schema attributes in the Framework. diff --git a/website/docs/plugin/framework/migrating/attributes-blocks/validators-custom.mdx b/website/docs/plugin/framework/migrating/attributes-blocks/validators-custom.mdx index d0d85be17..f4684e28e 100644 --- a/website/docs/plugin/framework/migrating/attributes-blocks/validators-custom.mdx +++ b/website/docs/plugin/framework/migrating/attributes-blocks/validators-custom.mdx @@ -63,24 +63,16 @@ your requirements. ## Example -The following examples show how to migrate portions of the -[random](https://github.com/hashicorp/terraform-provider-random) provider. - -For a complete example, clone the -`terraform-provider-random` repository and compare the `resource_password.go` file in -[v3.3.2](https://github.com/hashicorp/terraform-provider-random/blob/v3.3.2/internal/provider/resource_password.go) -with the file [after the migration](https://github.com/hashicorp/terraform-provider-random/blob/04292d3e31f71ff16b82512082baed037bb1069c/internal/provider/resource_password.go). - ### SDKv2 -The following example from the `resource_password.go` file shows the implementation of the `ValidateDiagFunc` field for -the `random_password`'s `length` attribute to validate that it's value is at least 1 (greater than zero). +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 resourcePassword() *schema.Resource { +func exampleResource() *schema.Resource { return &schema.Resource{ Schema: map[string]*schema.Schema{ - "length": { + "example_attribute": { ValidateDiagFunc: validation.ToDiagFunc(validation.IntAtLeast(1)), }, }, @@ -92,14 +84,14 @@ func resourcePassword() *schema.Resource { The following shows the same section of provider code after the migration. -This code validates that the `random_password`'s `length` attribute is greater than zero by using a custom `AtLeast` +This code validates that the `exampleResource`'s `example_attribute` attribute is greater than zero by using a custom `AtLeast` validator. ```go -func (r *passwordResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { +func (r *exampleResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { resp.Schema = schema.Schema{ Attributes: map[string]schema.Attribute{ - "length": schema.Int64Attribute{ + "example_attribute": schema.Int64Attribute{ Required: true, Validators: []validator.Int64{ int64validator.AtLeast(1), @@ -110,9 +102,7 @@ func (r *passwordResource) Schema(ctx context.Context, req resource.SchemaReques } ``` -This example code is taken from -[terraform-plugin-framework-validators](https://github.com/hashicorp/terraform-plugin-framework-validators/blob/v0.4.0/int64validator/at_least.go) -to illustrate how you can implement your own validators. +This example code illustrates how you can implement your own validators. ```go var _ validator.Int64 = atLeastValidator{} diff --git a/website/docs/plugin/framework/migrating/attributes-blocks/validators-predefined.mdx b/website/docs/plugin/framework/migrating/attributes-blocks/validators-predefined.mdx index 1a98af6e0..97d0c4183 100644 --- a/website/docs/plugin/framework/migrating/attributes-blocks/validators-predefined.mdx +++ b/website/docs/plugin/framework/migrating/attributes-blocks/validators-predefined.mdx @@ -10,7 +10,7 @@ description: >- -> **Note:** The Plugin Framework is in beta. 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](/plugin/framework/schemas#validators) in +many use cases, or implement custom validators. Refer to [Schemas - Validators](/plugin/framework/handling-data/schemas#validators) in the Framework documentation for details. Refer to the [Attributes - Custom Validators](/plugin/framework/migrating/attributes-blocks/validators-custom) page in this guide to learn how to implement custom validators. @@ -85,42 +85,40 @@ your requirements. ## Example -The following examples show how to migrate portions of the [tls](https://github.com/hashicorp/terraform-provider-tls) -provider. - -For a complete example, clone the -`terraform-provider-tls` repository and compare the `provider.go` file in -[v3.4.0](https://github.com/hashicorp/terraform-provider-tls/blob/v3.4.0/internal/provider/provider.go) -with [v4.0.1](https://github.com/hashicorp/terraform-provider-tls/blob/v4.0.1/internal/provider/provider.go). - ### SDKv2 -The following example from the `provider.go` file shows the implementation of the `ConflictsWith` field on the -provider's `proxy` block's `url` attribute. This validator checks that the provider does not use the `url` attribute -when the proxy's url is set through the environment. The example also uses the `RequiredWith` field to ensure that the -`password` attribute is configured when `username` is, and vice-versa. +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{ - "proxy": { + "example_block": { Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ - "url": { - ConflictsWith: []string{"proxy.0.from_env"}, + "example_attribute_one": { + ConflictsWith: []string{"example_block.0.example_attribute_four"}, /* ... */ }, - "username": { - RequiredWith: []string{"proxy.0.url"}, + "example_attribute_two": { + RequiredWith: []string{"example_block.0.example_attribute_one"}, /* ... */ }, - "password": { - RequiredWith: []string{"proxy.0.username"}, + "example_attribute_three": { + RequiredWith: []string{"example_block.0.example_attribute_two"}, /* ... */ }, - "from_env": { - ConflictsWith: []string{"proxy.0.url", "proxy.0.username", "proxy.0.password"}, + "example_attribute_four": { + ConflictsWith: []string{ + "example_block.0.example_attribute_one", + "example_block.0.example_attribute_two", + "example_block.0.example_attribute_three", + }, /* ... */ }, }, @@ -136,39 +134,39 @@ func New() (*schema.Provider, error) { 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 `proxy` block's attribute schema. +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{ - "proxy": schema.ListNestedBlock{ + "example_block": schema.ListNestedBlock{ NestedObject: schema.NestedBlockObject{ Attributes: map[string]schema.Attribute{ - "url": schema.StringAttribute{ + "example_attribute_one": schema.StringAttribute{ Validators: []validator.String{ - stringvalidator.ConflictsWith(path.MatchRelative().AtParent().AtName("from_env")), + stringvalidator.ConflictsWith(path.MatchRelative().AtParent().AtName("example_attribute_four")), }, /* ... */ }, - "username": schema.StringAttribute{ + "example_attribute_two": schema.StringAttribute{ Validators: []validator.String{ - stringvalidator.AlsoRequires(path.MatchRelative().AtParent().AtName("url")), + stringvalidator.AlsoRequires(path.MatchRelative().AtParent().AtName("example_attribute_one")), }, /* ... */ }, - "password": schema.StringAttribute{ + "example_attribute_three": schema.StringAttribute{ Validators: []validator.String{ - stringvalidator.AlsoRequires(path.MatchRelative().AtParent().AtName("username")), + stringvalidator.AlsoRequires(path.MatchRelative().AtParent().AtName("example_attribute_two")), }, /* ... */ }, - "from_env": schema.BoolAttribute{ + "example_attribute_four": schema.BoolAttribute{ Validators: []validator.Bool{ boolvalidator.ConflictsWith( - path.MatchRelative().AtParent().AtName("url"), - path.MatchRelative().AtParent().AtName("username"), - path.MatchRelative().AtParent().AtName("password"), + path.MatchRelative().AtParent().AtName("example_attribute_one"), + path.MatchRelative().AtParent().AtName("example_attribute_two"), + path.MatchRelative().AtParent().AtName("example_attribute_three"), ), }, /* ... */ diff --git a/website/docs/plugin/framework/migrating/data-sources/index.mdx b/website/docs/plugin/framework/migrating/data-sources/index.mdx index 78ab7b19c..8a01bbca7 100644 --- a/website/docs/plugin/framework/migrating/data-sources/index.mdx +++ b/website/docs/plugin/framework/migrating/data-sources/index.mdx @@ -100,39 +100,29 @@ Remember the following details when completing the migration from SDKv2 to the F ## Example -The following examples show how to migrate portions of the [http](https://github.com/hashicorp/terraform-provider-http) -provider. - -For a complete example, clone the -`terraform-provider-http` repository and compare the `data_source.go` file in -[v2.2.0](https://github.com/hashicorp/terraform-provider-http/blob/v2.2.0/internal/provider/data_source.go) -and the `data_source_http.go` file -[after the migration](https://github.com/hashicorp/terraform-provider-http/blob/8527d5b4546b54cdef246a13befc5745dbbbf740/internal/provider/data_source_http.go). - ### SDKv2 -The following example from the `provider.go` file shows an implementation of the `DataSourcesMap` field on the provider +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 { - "http": dataSource(), + "example_datasource": exampleDataSource(), /* ... */ ``` -The following example from the `data_source.go` file shows how the `ReadContext` function and `Schema` are defined for -the `http` data source with SDKv2. +The following example shows how the `ReadContext` function and `Schema` are defined for +the `exampleResource` data source with SDKv2. ```go -func dataSource() *schema.Resource { +func exampleDataSource() *schema.Resource { return &schema.Resource{ ReadContext: dataSourceRead, Schema: map[string]*schema.Schema{ - "url": { - Description: "The URL for the request. Supported schemes are `http` and `https`.", + "example_attribute": { Type: schema.TypeString, Required: true, }, @@ -144,40 +134,36 @@ func dataSource() *schema.Resource { ### Framework -The following example from the `provider.go` file shows how the `http` data source is defined with the Framework after +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 &httpDataSource{} + return &exampleDataSource{} }, } } ``` -This code from the `data_source_http.go` file defines the methods for the `http` data source with the +This code defines the methods for the `exampleDataSource` data source with the Framework. ```go -func (d *httpDataSource) Metadata(ctx context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) { - // This is unconventional in that the data source name matches the provider name. - // Typically these should have the provider name, an underscore, then the type name. - // e.g. http_request - resp.TypeName = "http" +func (d *exampleDataSource) Metadata(ctx context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) { + resp.TypeName = "example_datasource" } -func (d *httpDataSource) Schema(ctx context.Context, req datasource.SchemaRequest, resp *datasource.SchemaResponse) { +func (d *exampleDataSource) Schema(ctx context.Context, req datasource.SchemaRequest, resp *datasource.SchemaResponse) { resp.Schema = schema.Schema{ Attributes: map[string]schema.Attribute{ - "url": schema.StringAttribute{ - Description: "The URL for the request. Supported schemes are `http` and `https`.", + "example_attribute": schema.StringAttribute{ Required: true, }, /* ... */ -func (d *httpDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { +func (d *exampleDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { /* ... */ } ``` diff --git a/website/docs/plugin/framework/migrating/data-sources/timeouts.mdx b/website/docs/plugin/framework/migrating/data-sources/timeouts.mdx index 8a47edfad..0204cd445 100644 --- a/website/docs/plugin/framework/migrating/data-sources/timeouts.mdx +++ b/website/docs/plugin/framework/migrating/data-sources/timeouts.mdx @@ -8,7 +8,7 @@ description: >- -> **Note:** The Plugin Framework is in beta. -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. +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 diff --git a/website/docs/plugin/framework/migrating/providers/index.mdx b/website/docs/plugin/framework/migrating/providers/index.mdx index f11e28fee..114916124 100644 --- a/website/docs/plugin/framework/migrating/providers/index.mdx +++ b/website/docs/plugin/framework/migrating/providers/index.mdx @@ -203,14 +203,6 @@ creates data source types that you define, which satisfy the `datasource.DataSou ### Example -The following examples show how to migrate portions of the [tls](https://github.com/hashicorp/terraform-provider-tls) -provider. - -For a complete example, clone the -`terraform-provider-tls` repository and compare `provider.go` in -[v3.4.0](https://github.com/hashicorp/terraform-provider-tls/blob/v3.4.0/internal/provider/provider.go) -with the file [after the migration](https://github.com/hashicorp/terraform-provider-tls/blob/4dafb105818e45a88532f917e7b170ee2a9bb092/internal/provider/provider.go). - #### SDKv2 The following example shows how to set up a provider schema, configuration, resources, and data sources using SDKv2. @@ -219,17 +211,17 @@ The following example shows how to set up a provider schema, configuration, reso func New() (*schema.Provider, error) { return &schema.Provider{ Schema: map[string]*schema.Schema{ - "proxy": { + "attribute": { /* ... */ }, }, ConfigureContextFunc: configureProvider, ResourcesMap: map[string]*schema.Resource{ - "tls_private_key": resourcePrivateKey(), + "exampleResource": exampleResource(), /* ... */ }, DataSourcesMap: map[string]*schema.Resource{ - "tls_public_key": dataSourcePublicKey(), + "exampleDataSource": exampleDataSource(), /* ... */ }, }, nil @@ -241,41 +233,41 @@ func New() (*schema.Provider, error) { The following shows the same section of provider code after the migration. ```go -var _ provider.Provider = (*TlsProvider)(nil) +var _ provider.Provider = (*exampleProvider)(nil) func New() provider.Provider { - return &TlsProvider{} + return &exampleProvider{} } -func (p *TlsProvider) Resources(_ context.Context) []func() resource.Resource { +func (p *exampleProvider) Resources(_ context.Context) []func() resource.Resource { return []func() resource.Resource{ func() resource.Resource { - return &privateKeyResource{} + return &exampleResource{} }, /* ... */ } } -func (p *TlsProvider) DataSources(_ context.Context) []func() datasource.DataSource { +func (p *exampleProvider) DataSources(_ context.Context) []func() datasource.DataSource { return []func() datasource.DataSource{ func() datasource.DataSource { - return &publicKeyDataSource{}, + return &exampleDataSource{}, }, /* ... */ } } -func (p *TlsProvider) Schema(_ context.Context, _ provider.SchemaRequest, resp *provider.SchemaResponse) { +func (p *exampleProvider) Schema(_ context.Context, _ provider.SchemaRequest, resp *provider.SchemaResponse) { resp.Schema = schema.Schema{ Attributes: map[string]schema.Attribute{ - "proxy": schema.SingleNestedBlock{ + "attribute": schema.SingleNestedBlock{ /* ... */ }, }, } } -func (p *TlsProvider) Configure(ctx context.Context, req provider.ConfigureRequest, res *provider.ConfigureResponse) { +func (p *exampleProvider) Configure(ctx context.Context, req provider.ConfigureRequest, res *provider.ConfigureResponse) { /* ... */ } ``` @@ -332,38 +324,28 @@ Framework `schema.Schema` is a struct that includes attributes and blocks. ### Example -The following examples show how to migrate portions of the [tls](https://github.com/hashicorp/terraform-provider-tls) -provider. - -For a complete example, clone the -`terraform-provider-tls` repository and compare `provider.go` in -[v3.4.0](https://github.com/hashicorp/terraform-provider-tls/blob/v3.4.0/internal/provider/provider.go) -with the file [after the migration](https://github.com/hashicorp/terraform-provider-tls/blob/4dafb105818e45a88532f917e7b170ee2a9bb092/internal/provider/provider.go). - -This example also shows how to use a nested block and a nested attribute for the SDKv2 and Framework examples, +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](/plugin/framework/migrating/attributes-blocks/blocks-computed) page in this guide for more details. #### SDKv2 -The following example from the `provider.go` file shows the configuration of the `url` attribute for the provider's`proxy` configuration block. +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{ - "proxy": { + "example_block": { Type: schema.TypeList, Optional: true, MaxItems: 1, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ - "url": { + "example_attribute": { Type: schema.TypeString, Optional: true, ValidateDiagFunc: validation.ToDiagFunc(validation.IsURLWithScheme(SupportedProxySchemesStr())), - ConflictsWith: []string{"proxy.0.from_env"}, - Description: "URL used to connect to the Proxy. " + - fmt.Sprintf("Accepted schemes are: `%s`. ", strings.Join(SupportedProxySchemesStr(), "`, `")), + ConflictsWith: []string{"example_block.0.another_attribute"}, }, /* ... */ ``` @@ -372,22 +354,19 @@ Schema: map[string]*schema.Schema{ The following shows the same section of provider code after the migration. -This code implements the `url` attribute for the `proxy` block with the Framework. +This code implements the `example_attribute` attribute for the `example_Block` block with the Framework. ```go -func (p *TlsProvider) Schema(_ context.Context, _ provider.SchemaRequest, resp *provider.SchemaResponse) { +func (p *exampleProvider) Schema(_ context.Context, _ provider.SchemaRequest, resp *provider.SchemaResponse) { resp.Schema = schema.Schema{ Attributes: map[string]schema.Attribute{ - "proxy": schema.SingleNestedBlock{ + "example_block": schema.SingleNestedBlock{ Optional: true, Attributes: map[string]schema.Attribute{ - "url": schema.StringAttribute{ + "example_attribute": schema.StringAttribute{ Optional: true, Validators: []validator.String{ attribute_validator.UrlWithScheme(supportedProxySchemesStr()...), - stringvalidator.ConflictsWith(path.MatchRelative().AtParent().AtName("from_env")), + stringvalidator.ConflictsWith(path.MatchRelative().AtParent().AtName("another_attribute")), }, - MarkdownDescription: "URL used to connect to the Proxy. " + - fmt.Sprintf("Accepted schemes are: `%s`. ", strings.Join(supportedProxySchemesStr(), "`, `")), - }, ``` diff --git a/website/docs/plugin/framework/migrating/resources/crud.mdx b/website/docs/plugin/framework/migrating/resources/crud.mdx index 39d2669b1..a4f97c895 100644 --- a/website/docs/plugin/framework/migrating/resources/crud.mdx +++ b/website/docs/plugin/framework/migrating/resources/crud.mdx @@ -78,46 +78,26 @@ on `resource.CreateResponse`. ## Example -The following examples show how to migrate portions of the -[random](https://github.com/hashicorp/terraform-provider-random) provider. - -For a complete example, clone the -`terraform-provider-random` repository and compare the `resource_password.go` file in -[v3.3.2](https://github.com/hashicorp/terraform-provider-random/blob/v3.3.2/internal/provider/resource_password.go) -with [v3.4.1](https://github.com/hashicorp/terraform-provider-random/blob/v3.4.1/internal/provider/resource_password.go). - ### SDKv2 -The following example from the `resource_password.go` file shows implementations of CRUD functions on the -`random_password` resource with SDKv2. The `UpdateContext` function is not implemented because the provider does not -support updating this resource. +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 resourcePassword() *schema.Resource { +func resourceExample() *schema.Resource { return &schema.Resource{ - CreateContext: createPassword, + CreateContext: create, ReadContext: readNil, DeleteContext: RemoveResourceFromState, /* ... */ ``` -The following example shows the implementation of the `createPassword()` function with SDKv2. The implementations of +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 createPassword(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - diags := createStringFunc(true)(ctx, d, meta) - if diags.HasError() { - return diags - } - - hash, err := generateHash(d.Get("result").(string)) - if err != nil { - diags = append(diags, diag.Errorf("err: %s", err)...) - return diags - } - - if err := d.Set("bcrypt_hash", hash); err != nil { +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 } @@ -129,11 +109,11 @@ func createPassword(ctx context.Context, d *schema.ResourceData, meta interface{ ### Framework The following shows the same section of provider code after the migration. -This code implements the `Create` function for the `random_password` resource with the Framework. +This code implements the `Create` function for the `example_resource` resource with the Framework. ```go -func (r *passwordResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { - var plan passwordModelV2 +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...) @@ -141,33 +121,7 @@ func (r *passwordResource) Create(ctx context.Context, req resource.CreateReques return } - params := random.StringParams{ - Length: plan.Length.ValueInt64(), - Upper: plan.Upper.ValueBool(), - MinUpper: plan.MinUpper.ValueInt64(), - Lower: plan.Lower.ValueBool(), - MinLower: plan.MinLower.ValueInt64(), - Numeric: plan.Numeric.ValueBool(), - MinNumeric: plan.MinNumeric.ValueInt64(), - Special: plan.Special.ValueBool(), - MinSpecial: plan.MinSpecial.ValueInt64(), - OverrideSpecial: plan.OverrideSpecial.ValueString(), - } - - result, err := random.CreateString(params) - if err != nil { - resp.Diagnostics.Append(diagnostics.RandomReadError(err.Error())...) - return - } - - hash, err := generateHash(string(result)) - if err != nil { - resp.Diagnostics.Append(diagnostics.HashGenerationError(err.Error())...) - } - - plan.BcryptHash = types.StringValue(hash) - plan.ID = types.StringValue("none") - plan.Result = types.StringValue(string(result)) + plan.ExampleAttribute = types.StringValue("value") diags = resp.State.Set(ctx, plan) resp.Diagnostics.Append(diags...) diff --git a/website/docs/plugin/framework/migrating/resources/import.mdx b/website/docs/plugin/framework/migrating/resources/import.mdx index eb5ff8b48..b93adcd22 100644 --- a/website/docs/plugin/framework/migrating/resources/import.mdx +++ b/website/docs/plugin/framework/migrating/resources/import.mdx @@ -84,69 +84,31 @@ func (r *resourceExample) ImportState(ctx context.Context, req resource.ImportSt } ``` -The following examples show how to migrate portions of the -[random](https://github.com/hashicorp/terraform-provider-random) provider. - -For a complete example, clone the -`terraform-provider-random` repository and compare the `resource_password.go` file in -[v3.3.2](https://github.com/hashicorp/terraform-provider-random/blob/v3.3.2/internal/provider/resource_password.go) -with [v3.4.1](https://github.com/hashicorp/terraform-provider-random/blob/v3.4.1/internal/provider/resource_password.go). - This example also shows one way to handle populating attributes with their default values during import. ### SDKv2 -The following example from the `resource_password.go` file shows the import function for the `random_password` resource -with SDKv2. +The following example shows the import function for the `example_resource` resource with SDKv2. ```go -func resourcePassword() *schema.Resource { +func exampleResource() *schema.Resource { return &schema.Resource{ Importer: &schema.ResourceImporter{ - StateContext: importPasswordFunc, + StateContext: importFunc, }, /* ... */ } } ``` -The following example shows the implementation of the `importPasswordFunc` function with SDKv2. +The following example shows the implementation of the `importFunc` function with SDKv2. ```go -func importPasswordFunc(ctx context.Context, d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { - for k, v := range passwordSchemaV2() { - if v.Default == nil { - continue - } - if err := d.Set(k, v.Default); err != nil { - return nil, fmt.Errorf("error setting %s: %w", k, err) - } - } - - for _, key := range []string{"number", "numeric"} { - if err := d.Set(key, true); err != nil { - return nil, fmt.Errorf("error setting %s: %w", key, err) - } - } - - val := d.Id() - d.SetId("none") - - if err := d.Set("result", val); err != nil { - return nil, fmt.Errorf("resource password import failed, error setting result: %w", err) - } +func importFunc(ctx context.Context, d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { + d.SetId("id") - if err := d.Set("length", len(val)); err != nil { - return nil, fmt.Errorf("error setting length: %w", err) - } - - hash, err := generateHash(val) - if err != nil { - return nil, fmt.Errorf("resource password import failed, generate hash error: %w", err) - } - - if err := d.Set("bcrypt_hash", hash); err != nil { - return nil, fmt.Errorf("resource password import failed, error setting bcrypt_hash: %w", err) + 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 @@ -156,34 +118,18 @@ func importPasswordFunc(ctx context.Context, d *schema.ResourceData, meta interf ### Framework The following shows the same section of provider code after the migration. -This code implements the `ResourceWithImportState` interface on the `passwordResource` type by defining an `ImportState` +This code implements the `ResourceWithImportState` interface on the `exampleResource` type by defining an `ImportState` function. ```go -func (r *passwordResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { +func (r *exampleResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { id := req.ID - state := passwordModelV2{ - ID: types.StringValue("none"), - Result: types.StringValue(id), - Length: types.Int64Value(int64(len(id))), - Special: types.BoolValue(true), - Upper: types.BoolValue(true), - Lower: types.BoolValue(true), - Numeric: types.BoolValue(true), - MinSpecial: types.Int64Value(0), - MinUpper: types.Int64Value(0), - MinLower: types.Int64Value(0), - MinNumeric: types.Int64Value(0), + state := exampleModel{ + ID: types.StringValue("id"), + Attribute: types.StringValue("value"), } - hash, err := generateHash(id) - if err != nil { - resp.Diagnostics.Append(diagnostics.HashGenerationError(err.Error())...) - } - - state.BcryptHash = types.StringValue(hash) - diags := resp.State.Set(ctx, &state) resp.Diagnostics.Append(diags...) if resp.Diagnostics.HasError() { diff --git a/website/docs/plugin/framework/migrating/resources/index.mdx b/website/docs/plugin/framework/migrating/resources/index.mdx index 055e69d4b..827ebee47 100644 --- a/website/docs/plugin/framework/migrating/resources/index.mdx +++ b/website/docs/plugin/framework/migrating/resources/index.mdx @@ -145,48 +145,34 @@ CRUD operations. ## Example -The following examples show how to migrate portions of the -[tls](https://github.com/hashicorp/terraform-provider-tls) provider. - -For a complete example, -clone the `terraform-provider-tls` repository and compare the `resource_private_key.go` file in -[v3.4.0](https://github.com/hashicorp/terraform-provider-tls/blob/v3.4.0/internal/provider/resource_private_key.go) with the file -[after the migration](https://github.com/hashicorp/terraform-provider-tls/blob/4dafb105818e45a88532f917e7b170ee2a9bb092/internal/provider/resource_private_key.go). - ### 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`. -The following example from the `provider.go` file defines a `tls_private_key` resource within the provider schema. - ```go func New() (*schema.Provider, error) { return &schema.Provider { ResourcesMap: map[string]*schema.Resource { - "tls_private_key": resourcePrivateKey(), + "example_resource": exampleResource(), /* ... */ ``` -The following example from the `resource_private_key.go` file defines the resource schema. +This code defines the `example_resource` resource by mapping the resource name to the `exampleResource` struct. ```go -func resourcePrivateKey() *schema.Resource { +func exampleResource() *schema.Resource { return &schema.Resource{ - CreateContext: createResourcePrivateKey, - DeleteContext: deleteResourcePrivateKey, - ReadContext: readResourcePrivateKey, - - Description: "Creates a PEM /* ... */", + CreateContext: createResource, + DeleteContext: deleteResource, + ReadContext: readResource, Schema: map[string]*schema.Schema{ - "algorithm": { + "attribute": { Type: schema.TypeString, Required: true, ForceNew: true, - ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice(SupportedAlgorithmsStr(), false)), - Description: "Name of the algorithm to use when generating the private key. " + - "Currently-supported values are `RSA`, `ECDSA` and `ED25519`.", + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{'a', 'b'}, false)), }, /* ... */ ``` @@ -195,41 +181,36 @@ func resourcePrivateKey() *schema.Resource { The following shows the same section of provider code after the migration. -This code defines the `tls_private_key` resource by mapping the resource name to the `privateKeyResourceType` struct. - ```go -func (p *TlsProvider) Resources(_ context.Context) []func() resource.Resource { +func (p *exampleProvider) Resources(_ context.Context) []func() resource.Resource { return []func() resource.Resource{ func() resource.Resource { - return &privateKeyResource{} + return &exampleResource{} }, /* ... */ } } ``` -This code defines the `Schema` and `Metadata` methods for the `privateKeyResource`. +This code defines the `Schema` and `Metadata` methods for the `Resource`. ```go -func (r *privateKeyResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { - resp.TypeName = "tls_private_key" +func (r *exampleResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = "example_resource" } -func (r *privateKeyResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) { +func (r *exampleResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) { resp.Schema = schema.Schema{ Attributes: map[string]schema.Attribute{ // Required attributes - "algorithm": schema.StringAttribute{ + "attribute": schema.StringAttribute{ Required: true, PlanModifiers: []planmodifier.String{ stringplanmodifier.RequiresReplace(), }, Validators: []validator.String{ - stringvalidator.OneOf(supportedAlgorithmsAttrValue()...), + stringvalidator.OneOf([]string{'a', 'b'}...), }, - Description: "Name of the algorithm to use when generating the private key. " + - fmt.Sprintf("Currently-supported values are: `%s`. ", strings.Join(supportedAlgorithmsStr(), "`, `")), - }, /* ... */ }, } diff --git a/website/docs/plugin/framework/migrating/resources/plan-modification.mdx b/website/docs/plugin/framework/migrating/resources/plan-modification.mdx index b418980ce..7255f0037 100644 --- a/website/docs/plugin/framework/migrating/resources/plan-modification.mdx +++ b/website/docs/plugin/framework/migrating/resources/plan-modification.mdx @@ -55,27 +55,19 @@ Framework. ## Example -The following examples show how to migrate portions of the -[random](https://github.com/hashicorp/terraform-provider-random) provider. - -For a complete example, clone the -`terraform-provider-random` repository and compare the `resource_password.go` file in -[v3.3.2](https://github.com/hashicorp/terraform-provider-random/blob/v3.3.2/internal/provider/resource_password.go) -with [v3.4.1](https://github.com/hashicorp/terraform-provider-random/blob/v3.4.1/internal/provider/resource_password.go). - ### 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 from the `resource_password.go` files shows the use of `CustomizeDiff` to keep two attributes +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("number", "numeric")) - customizeDiffFuncs = append(customizeDiffFuncs, planSyncIfChange("numeric", "number")) + customizeDiffFuncs = append(customizeDiffFuncs, planSyncIfChange("attribute_one", "attribute_two")) + customizeDiffFuncs = append(customizeDiffFuncs, planSyncIfChange("attribute_two", "attribute_one")) return &schema.Resource{ /* ... */ @@ -108,83 +100,83 @@ Many existing `CustomizeDiff` implementations would be better suited to migratio Framework. This code shows the implementation using attribute plan modifiers with the Framework. ```go -func passwordSchemaV2() schema.Schema { +func exampleSchema() schema.Schema { return schema.Schema{ /* ... */ Attributes: map[string]schema.Attribute{ /* ... */ - "number": schema.BoolAttribute{ + "attribute_one": schema.BoolAttribute{ /* ... */ PlanModifiers: []planmodifier.Bool{ - planmodifiers.NumberNumericAttributePlanModifier(), + planmodifiers.SyncAttributePlanModifier(), /* ... */ }, }, - "numeric": schema.BoolAttribute{ + "attribute_two": schema.BoolAttribute{ /* ... */ PlanModifiers: []planmodifier.Bool{ - planmodifiers.NumberNumericAttributePlanModifier(), + planmodifiers.SyncAttributePlanModifier(), /* ... */ }, }, ``` -The following shows an implementation of `NumberNumericAttributePlanModifier` in the Framework. +The following shows an implementation of `SyncAttributePlanModifier` in the Framework. ```go -func NumberNumericAttributePlanModifier() planmodifier.Bool { - return &numberNumericAttributePlanModifier{} +func SyncAttributePlanModifier() planmodifier.Bool { + return &syncAttributePlanModifier{} } -type numberNumericAttributePlanModifier struct { +type syncAttributePlanModifier struct { } -func (d *numberNumericAttributePlanModifier) Description(ctx context.Context) string { - return "Ensures that number and numeric attributes are kept synchronised." +func (d *syncAttributePlanModifier) Description(ctx context.Context) string { + return "Ensures that attribute_one and attribute_two attributes are kept synchronised." } -func (d *numberNumericAttributePlanModifier) MarkdownDescription(ctx context.Context) string { +func (d *syncAttributePlanModifier) MarkdownDescription(ctx context.Context) string { return d.Description(ctx) } -func (d *numberNumericAttributePlanModifier) PlanModifyBool(ctx context.Context, req planmodifier.BoolRequest, resp *planmodifier.BoolResponse) { - var numberConfig types.Bool - diags := req.Config.GetAttribute(ctx, path.Root("number"), &numberConfig) +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 numericConfig types.Bool - req.Config.GetAttribute(ctx, path.Root("numeric"), &numericConfig) + var attributeTwo types.Bool + req.Plan.GetAttribute(ctx, path.Root("attribute_two"), &attributeTwo) resp.Diagnostics.Append(diags...) if resp.Diagnostics.HasError() { return } - if !numberConfig.IsNull() && !numericConfig.IsNull() && (numberConfig.ValueBool() != numericConfig.ValueBool()) { + if !attributeOne.IsNull() && !attributeTwo.IsNull() && (attributeOne.ValueBool() != attributeTwo.ValueBool()) { resp.Diagnostics.AddError( - "Number and numeric are both configured with different values", - "Number is deprecated, use numeric instead", + "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 number and numeric when both are null. - if numberConfig.IsNull() && numericConfig.IsNull() { + // 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 numeric if number is null - if numberConfig.IsNull() && !numericConfig.IsNull() { + // 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 number if numeric is null - if !numberConfig.IsNull() && numericConfig.IsNull() { + // Default to using value for attribute_one if attribute_two is null + if !attributeOne.IsNull() && attributeTwo.IsNull() { resp.PlanValue = numberConfig return } diff --git a/website/docs/plugin/framework/migrating/resources/state-upgrade.mdx b/website/docs/plugin/framework/migrating/resources/state-upgrade.mdx index c73cad1be..1cc1b5307 100644 --- a/website/docs/plugin/framework/migrating/resources/state-upgrade.mdx +++ b/website/docs/plugin/framework/migrating/resources/state-upgrade.mdx @@ -66,60 +66,42 @@ a single step. For example, version 0 => version 2, version 1 => version 2. ## Example -The following examples show how to migrate portions of the -[random](https://github.com/hashicorp/terraform-provider-random) provider. - -For a complete example, clone the -`terraform-provider-random` repository and compare the `resource_password.go` file in -[v3.3.2](https://github.com/hashicorp/terraform-provider-random/blob/v3.3.2/internal/provider/resource_password.go) -with [v3.4.1](https://github.com/hashicorp/terraform-provider-random/blob/v3.4.1/internal/provider/resource_password.go). - ### SDKv2 In SDKv2 the `schema.Resource` struct has a `StateUpgraders` field that holds `[]schema.StateUpgrader` struct(s). -The following example from the `resource_password.go` file shows the state upgrade functions for the `random_password` +The following example from the shows the state upgrade functions for the `example_resource` resource with SDKv2. ```go -func resourcePassword() *schema.Resource { +func exampleResource() *schema.Resource { return &schema.Resource{ - Schema: passwordSchemaV2(), + Schema: exampleSchemaV2(), SchemaVersion: 2, StateUpgraders: []schema.StateUpgrader{ { Version: 0, - Type: resourcePasswordV0().CoreConfigSchema().ImpliedType(), - Upgrade: resourcePasswordStateUpgradeV0, + Type: exampleResourceV0().CoreConfigSchema().ImpliedType(), + Upgrade: exampleResourceStateUpgradeV0, }, { Version: 1, - Type: resourcePasswordV1().CoreConfigSchema().ImpliedType(), - Upgrade: resourcePasswordStringStateUpgradeV1, + Type: exampleResourceV1().CoreConfigSchema().ImpliedType(), + Upgrade: exampleResourceStateUpgradeV1, }, }, /* ... */ ``` -The following example shows the implementation of the `resourcePasswordStateUpgradeV0` function with SDKv2. +The following example shows the implementation of the `exampleResourceStateUpgradeV0` function with SDKv2. ```go -func resourcePasswordStateUpgradeV0(_ context.Context, rawState map[string]interface{}, _ interface{}) (map[string]interface{}, error) { +func exampleResourceStateUpgradeV0(_ context.Context, rawState map[string]interface{}, _ interface{}) (map[string]interface{}, error) { if rawState == nil { - return nil, fmt.Errorf("resource password state upgrade failed, state is nil") + return nil, fmt.Errorf("example resource state upgrade failed, state is nil") } - result, ok := rawState["result"].(string) - if !ok { - return nil, fmt.Errorf("resource password state upgrade failed, result is not a string: %T", rawState["result"]) - } - - hash, err := generateHash(result) - if err != nil { - return nil, fmt.Errorf("resource password state upgrade failed, generate hash error: %w", err) - } - - rawState["bcrypt_hash"] = hash + rawState["example_attribute"] = "value" return rawState, nil } @@ -129,80 +111,49 @@ func resourcePasswordStateUpgradeV0(_ context.Context, rawState map[string]inter The following shows the same section of provider code after the migration. -This code implements the `ResourceWithUpgradeState` interface on the `passwordResource` type by defining an +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 *passwordResource) UpgradeState(context.Context) map[int64]resource.StateUpgrader { - schemaV0 := passwordSchemaV0() - schemaV1 := passwordSchemaV1() +func (r *exampleResource) UpgradeState(context.Context) map[int64]resource.StateUpgrader { + schemaV0 := exampleSchemaV0() + schemaV1 := exampleSchemaV1() return map[int64]resource.StateUpgrader{ 0: { PriorSchema: &schemaV0, - StateUpgrader: upgradePasswordStateV0toV2, + StateUpgrader: upgradeExampleResourceStateV0toV2, }, 1: { PriorSchema: &schemaV1, - StateUpgrader: upgradePasswordStateV1toV2, + StateUpgrader: upgradeExampleResourceStateV1toV2, }, } } ``` -This code implements the `upgradePasswordStateV0toV2` state upgrade function. +This code implements the `upgradeExampleResourceStateV0toV2` state upgrade function. ```go -func upgradePasswordStateV0toV2(ctx context.Context, req resource.UpgradeStateRequest, resp *resource.UpgradeStateResponse) { +func upgradeExampleResourceStateV0toV2(ctx context.Context, req resource.UpgradeStateRequest, resp *resource.UpgradeStateResponse) { type modelV0 struct { - ID types.String `tfsdk:"id"` - Keepers types.Map `tfsdk:"keepers"` - Length types.Int64 `tfsdk:"length"` - Special types.Bool `tfsdk:"special"` - Upper types.Bool `tfsdk:"upper"` - Lower types.Bool `tfsdk:"lower"` - Number types.Bool `tfsdk:"number"` - MinNumeric types.Int64 `tfsdk:"min_numeric"` - MinUpper types.Int64 `tfsdk:"min_upper"` - MinLower types.Int64 `tfsdk:"min_lower"` - MinSpecial types.Int64 `tfsdk:"min_special"` - OverrideSpecial types.String `tfsdk:"override_special"` - Result types.String `tfsdk:"result"` + ID types.String `tfsdk:"id"` } - var passwordDataV0 modelV0 + var exampleDataV0 modelV0 - resp.Diagnostics.Append(req.State.Get(ctx, &passwordDataV0)...) + resp.Diagnostics.Append(req.State.Get(ctx, &exampleDataV0)...) if resp.Diagnostics.HasError() { return } - passwordDataV2 := passwordModelV2{ - Keepers: passwordDataV0.Keepers, - Length: passwordDataV0.Length, - Special: passwordDataV0.Special, - Upper: passwordDataV0.Upper, - Lower: passwordDataV0.Lower, - Number: passwordDataV0.Number, - Numeric: passwordDataV0.Number, - MinNumeric: passwordDataV0.MinNumeric, - MinUpper: passwordDataV0.MinUpper, - MinLower: passwordDataV0.MinLower, - MinSpecial: passwordDataV0.MinSpecial, - OverrideSpecial: passwordDataV0.OverrideSpecial, - Result: passwordDataV0.Result, - ID: passwordDataV0.ID, - } - - hash, err := generateHash(passwordDataV2.Result.ValueString()) - if err != nil { - resp.Diagnostics.Append(diagnostics.HashGenerationError(err.Error())...) - return + exampleDataV2 := exampleModelV2{ + ID: exampleDataV0.ID, } - passwordDataV2.BcryptHash = types.StringValue(hash) + exampleDataV2.ExampleAttribute = types.StringValue("value") - diags := resp.State.Set(ctx, passwordDataV2) + diags := resp.State.Set(ctx, exampleDataV2) resp.Diagnostics.Append(diags...) ``` diff --git a/website/docs/plugin/framework/migrating/schema/index.mdx b/website/docs/plugin/framework/migrating/schema/index.mdx index 541ccbd39..1890dba75 100644 --- a/website/docs/plugin/framework/migrating/schema/index.mdx +++ b/website/docs/plugin/framework/migrating/schema/index.mdx @@ -10,7 +10,7 @@ description: >- 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](/plugin/framework/schemas) in the Framework documentation for details. +[Schemas](/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/ diff --git a/website/docs/plugin/framework/migrating/testing.mdx b/website/docs/plugin/framework/migrating/testing.mdx index a5aaa8793..5422bf592 100644 --- a/website/docs/plugin/framework/migrating/testing.mdx +++ b/website/docs/plugin/framework/migrating/testing.mdx @@ -28,45 +28,39 @@ the Framework. ### Example -The following example is taken from -[v3.0.1](https://github.com/hashicorp/terraform-provider-http/blob/v3.0.1/internal/provider/data_source_http_test.go) -of the http provider. - This example shows how you can use external providers to generate a state file 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 `v2.2.0` of the http +- 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 your -repository. The second step also uses `PlanOnly` to verify that a no-op plan is generated. +- The second `TestStep` uses `ProtoV5ProviderFactories` so that the test uses the provider code contained within the +http provider repository. The second step also uses `PlanOnly` to verify that a no-op plan is generated. ```go -func TestDataSource_UpgradeFromVersion2_2_0(t *testing.T) { +func TestDataSource_UpgradeFromVersion(t *testing.T) { /* ... */ resource.Test(t, resource.TestCase{ Steps: []resource.TestStep{ { ExternalProviders: map[string]resource.ExternalProvider{ - "http": { - VersionConstraint: "2.2.0", - Source: "hashicorp/http", + "": { + VersionConstraint: "", + Source: "hashicorp/", }, }, - Config: fmt.Sprintf(` - data "http" "http_test" { - url = "%s/200" - }`, testHttpMock.server.URL), + Config: `data "provider_datasource" "example" { + /* ... */ + }`, Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr("data.http.http_test", "response_body", "1.0.0"), + resource.TestCheckResourceAttr("data.provider_datasource.example", "", ""), /* ... */ ), }, { ProtoV5ProviderFactories: protoV5ProviderFactories(), - Config: fmt.Sprintf(` - data "http" "http_test" { - url = "%s/200" - }`, testHttpMock.server.URL), + Config: `data "provider_datasource" "example" { + /* ... */ + }`, PlanOnly: true, }, }, @@ -109,21 +103,12 @@ resource.UnitTest(t, resource.TestCase{ ### Example -The following examples show how to migrate portions of the [http](https://github.com/hashicorp/terraform-provider-http) -provider. - -To review a complete example, clone the -`terraform-provider-http` repository and compare the `data_source_test.go` file in -[v2.2.0](https://github.com/hashicorp/terraform-provider-http/blob/v2.2.0/internal/provider/data_source_test.go) with the `data_source_http_test.go` file in -[v3.0.1](https://github.com/hashicorp/terraform-provider-http/blob/v3.0.1/internal/provider/data_source_http_test.go). - #### SDKv2 -The following code sample is from the `data_source_http_test.go` file and shows how to define provider factories within -a test case when using SDKv2. +The following code sample shows how to define provider factories within a test case when using SDKv2. ```go -func TestDataSource_http200(t *testing.T) { +func TestDataSource_Exmple(t *testing.T) { /* ... */ resource.UnitTest(t, resource.TestCase{ ProviderFactories: testProviders(), @@ -132,24 +117,22 @@ func TestDataSource_http200(t *testing.T) { } ``` -The following code sample is from the `provider_test.go` file and shows how to generate provider factories when using -SDKv2. +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){ - "http": func() (*schema.Provider, error) { return New(), nil }, + "example": func() (*schema.Provider, error) { return New(), nil }, } } ``` #### Framework -The following code sample is from the `data_source_http_test.go` file and shows how to define provider factories within -a test case when using the Framework. +The following shows how to define provider factories within a test case when using the Framework. ```go -func TestDataSource_200(t *testing.T) { +func TestDataSource_Example(t *testing.T) { /* ... */ resource.UnitTest(t, resource.TestCase{ @@ -159,14 +142,14 @@ func TestDataSource_200(t *testing.T) { } ``` -The following code sample is from the `provider_test.go` file and shows how to generate provider factories when using +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](/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){ - "http": providerserver.NewProtocol5WithError(New()), + "example": providerserver.NewProtocol5WithError(New()), } } ``` diff --git a/website/docs/plugin/framework/writing-state.mdx b/website/docs/plugin/framework/writing-state.mdx deleted file mode 100644 index 5ee5ca4ce..000000000 --- a/website/docs/plugin/framework/writing-state.mdx +++ /dev/null @@ -1,192 +0,0 @@ ---- -page_title: 'Plugin Development - Framework: Writing State' -description: >- - How to write and update the Terraform statefile using the provider development - framework. ---- - -# Writing State - --> **Note:** The Plugin Framework is in beta. - -One of the primary jobs of a Terraform provider is to manage the provider's -resources and data sources in the [Terraform state](/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 [conversion rules](#conversion-rules) 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 [conversion rules](#conversion-rules) for more information about supported Go types. - -## Conversion Rules - -The following is a list of schema types and the Go types they know how to -accept in `Set` and `SetAttribute`. - -### String - -Strings can be automatically created from Go's `string` type (or any aliases of -it, like `type MyString string`). - -### Number - -Numbers can be automatically created from the following numeric types (or any -aliases of them, like `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) - -### Boolean - -Booleans can be automatically created from Go's `bool` type (or any aliases of -it, like `type MyBoolean bool`). - -### List - -Lists can be automatically created from any Go slice type (or alias of a Go -slice type, like `type MyList []string`), with the elements either being -`attr.Value` implementations or being converted according to these rules. - -### Map - -Maps can be automatically created from any Go map type with string keys (or any -alias of a Go map type with string keys, like `type MyMap map[string]int`), -with the elements either being `attr.Value` implementations or being converted -according to these rules. - -### Object - -Objects can be automatically created from any Go struct type with that follows -these constraints: - -* Every property on the struct must have a `tfsdk` struct tag. -* The `tfsdk` struct tag must name an attribute in the object that it is being - mapped to or be set to `-` to explicitly declare it does not map to an - attribute in the object. -* Every attribute in the object must have a corresponding struct tag. - -These rules help prevent typos and human error from unwittingly discarding -information by failing as early, consistently, and loudly as possible. - -Properties can either be `attr.Value` implementations or will be converted -according to these rules. - -### Pointers - -A nil pointer will be treated as a null value. Otherwise, the rules for the -type the pointer is referencing apply. - -### Detected Interfaces - -`Set` detects and utilizes the following interfaces, if the target implements -them. - -#### ValueCreator - -If a value is set on a Go type that implements the [`tftypes.ValueCreator` -interface](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-go/tftypes#ValueCreator), -that interface will be delegated to to handle the conversion. - -#### Unknownable - -If a value is set on a Go type that fills the `Unknownable` interface: - -```go -type Unknownable interface { - SetUnknown(context.Context, bool) error - SetValue(context.Context, interface{}) error - GetUnknown(context.Context) bool - GetValue(context.Context) interface{} -} -``` - -It will be used to convert the value. The `interface{}` being passed and -retrieved will be of a type that can be passed to -[`tftypes.NewValue`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-go/tftypes#NewValue). - -#### Nullable - -If a value is set on a Go type that fills the `Nullable` interface: - -```go -type Nullable interface { - SetNull(context.Context, bool) error - SetValue(context.Context, interface{}) error - GetNull(context.Context) bool - GetValue(context.Context) interface{} -} -``` - -It will be used to convert the value. The `interface{}` being passed and -retrieved will be of a type that can be passed to -[`tftypes.NewValue`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-go/tftypes#NewValue).