From 221ae8fccc58d1d98de827a7e0ed5314e0daba4e Mon Sep 17 00:00:00 2001 From: Benjamin Bennett Date: Thu, 15 Dec 2022 13:42:06 +0000 Subject: [PATCH] Improvements to Documentation (#584) * Cleaning up docs (#583) * Consolidating attribute documentation in the attributes page (#583) --- website/data/plugin-framework-nav-data.json | 4 + .../getting-started/code-walkthrough.mdx | 4 +- .../framework/handling-data/attributes.mdx | 195 ++++- .../plugin/framework/handling-data/blocks.mdx | 2 +- .../framework/handling-data/custom-types.mdx | 283 +++++++ .../framework/handling-data/schemas.mdx | 80 +- .../handling-data/terraform-concepts.mdx | 728 +----------------- 7 files changed, 475 insertions(+), 821 deletions(-) create mode 100644 website/docs/plugin/framework/handling-data/custom-types.mdx diff --git a/website/data/plugin-framework-nav-data.json b/website/data/plugin-framework-nav-data.json index 6d8f359d8..b8c781b21 100644 --- a/website/data/plugin-framework-nav-data.json +++ b/website/data/plugin-framework-nav-data.json @@ -152,6 +152,10 @@ { "title": "Conversion Rules", "path": "handling-data/conversion-rules" + }, + { + "title": "Custom Types", + "path": "handling-data/custom-types" } ] }, diff --git a/website/docs/plugin/framework/getting-started/code-walkthrough.mdx b/website/docs/plugin/framework/getting-started/code-walkthrough.mdx index 32db1b1ec..c0d607f1b 100644 --- a/website/docs/plugin/framework/getting-started/code-walkthrough.mdx +++ b/website/docs/plugin/framework/getting-started/code-walkthrough.mdx @@ -353,7 +353,9 @@ Refer to [Data Sources](/plugin/framework/data-sources) for more details and con ## 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`. +Refer to [terraform-provider-scaffolding-framework](https://github.com/hashicorp/terraform-provider-scaffolding-framework) for details on how to wire together a [provider server](#provider-server), [provider](#provider), [resource](#resource) and [data source](#data-source). + +Once wired together, run the provider by specifying configuration and executing `terraform apply`. ### Resource Configuration diff --git a/website/docs/plugin/framework/handling-data/attributes.mdx b/website/docs/plugin/framework/handling-data/attributes.mdx index 86962f91d..c2b118562 100644 --- a/website/docs/plugin/framework/handling-data/attributes.mdx +++ b/website/docs/plugin/framework/handling-data/attributes.mdx @@ -405,56 +405,175 @@ Call one of the following to create a `types.Set`: * [`types.SetValueFrom(context.Context, attr.Type, any) (types.Set, diag.Diagnostics)`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#SetValueFrom): A known value with the given element type and values. Can convert from standard Go types, using the [conversion rules](/plugin/framework/accessing-values#conversion-rules). * [`types.SetValueMust(map[string]attr.Type, map[string]attr.Value) types.Set`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#SetValueMust): A known value with the given element type and values. Any diagnostics are converted to a runtime panic. This is recommended only for testing or exhaustively tested logic. -## Create Provider-Defined Types and Values +### Nested Attributes -You may want to build your own attribute value and type implementations to allow your provider to combine validation, description, and plan customization behaviors into a reusable bundle. This helps avoid duplication or reimplementation and ensures consistency. +-> Only supported when using protocol version 6. -~> **Important:** Specifying plan customization for attribute types is not yet -supported, limiting their utility. Support is expected in the near future. +[Nested attributes](/plugin/framework/handling-data/attributes#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). -### `attr.Type` Interface +#### SingleNestedAttribute -Use the [`attr.Type` -interface](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/attr#Type) -to implement an attribute type. It tells Terraform about its constraints and tells the framework how to create new attribute values from the information Terraform supplies. `attr.Type` has the following methods. +With single nested attributes, the attribute behaves like an object. The practitioner can only specify one definition of the nested attributes. -| Method | Description | -| -------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `TerraformType` | Returns the [`tftypes.Type` value](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-go/tftypes#Type) that describes its type constraints. This is how Terraform will know what type of values it can accept. | -| `ValueFromTerraform` | Returns an attribute value from the [`tftypes.Value`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-go/tftypes#Value) that Terraform supplies, or to return an error if it cannot. This error should not be used for validation purposes, and is expected to indicate programmer error, not practitioner error. | -| `Equal` | Returns true if the attribute type is considered equal to the passed attribute type. | +```tf +resource "example_foo" "bar" { + nested_attribute = { + hello = "world" + demo = true + } +} +``` -### `AttributePathStepper` Interface +```go +func (e *exampleResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "nested_attribute": schema.SingleNestedAttribute{ + /* ... */ + Attributes: map[string]schema.Attribute{ + "hello": schema.StringAttribute{ + /* ... */ + }, + "demo": schema.BoolAttribute{ + /* ... */ + }, + }, + }, + }, + } +} +``` -All attribute types must implement the [`tftypes.AttributePathStepper` -interface](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-go/tftypes#AttributePathStepper), -so the framework can access element or attribute types using attribute paths. +#### ListNestedAttribute -### `xattr.TypeWithValidation` Interface +With list nested attributes, the attribute behaves like a list of objects. The practitioner can +specify any number of groups of these attributes. -If validation for type values is desired, use the [`xattr.TypeWithValidation` interface](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/attr/xattr#TypeWithValidation) to include validation logic for type values. The framework will call this functionality when validating all values based on the schema. +```tf +resource "example_foo" "bar" { + nested_attribute = [ + { + hello = "world" + demo = true + }, + { + hello = "moon" + demo = false + }, + ] +} +``` -| Method | Description | -| ---------- | ------------------------------------------------------------- | -| `Validate` | Returns any warning or error diagnostics for the given value. | +```go +func (e *exampleResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "nested_attribute": schema.ListNestedAttribute{ + /* ... */ + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "hello": schema.StringAttribute{ + /* ... */ + }, + "demo": schema.BoolAttribute{ + /* ... */ + }, + }, + }, + }, + }, + } +} +``` -### Type-Specific Interfaces +#### MapNestedAttribute -| Case | Interface | Description | -| --------------------------- | -------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| Elements of the same type | [`TypeWithElementType`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/attr#TypeWithElementType) | Attribute types that contain elements of the same type, like maps and lists, are required to implement `attr.TypeWithElementType`, which adds `WithElementType` and `ElementType` methods to the `attr.Type` interface. `WithElementType` must return a copy of the attribute type, but with its element type set to the passed type. `ElementType` must return the attribute type's element type. | -| Elements of different types | [`TypeWithElementTypes`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/attr#TypeWithElementType) | Attribute types that contain elements of differing types, like tuples, are required to implement the `attr.TypeWithElementTypes`, which adds `WithElementTypes` and `ElementTypes` methods to the `attr.Type` interface. `WithElementTypes` must return a copy of the attribute type, but with its element types set to the passed element types. `ElementTypes` must return the attribute type's element types. | -| Contain attributes | [`TypeWithAttributeTypes`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/attr#TypeWithAttributeTypes) | Attribute types that contain attributes, like objects, are required to implement the `attr.TypeWithAttributeTypes` interface, which adds `WithAttributeTypes` and `AttributeTypes` methods to the `attr.Type` interface. `WithAttributeTypes` must return a copy of the attribute type, but with its attribute types set to the passed attribute types. `AttributeTypes` must return the attribute type's attribute types. | +With map nested attributes, the attribute behaves like a map of objects. The practitioner can +specify any number of groups of these attributes, with string keys associated +with each group. -### `attr.Value` Interface +```tf +resource "example_foo" "bar" { + nested_attribute = { + "red" = { + hello = "world" + demo = true + }, + "blue" = { + hello = "moon" + demo = false + }, + } +} +``` + +```go +func (e *exampleResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "nested_attribute": schema.MapNestedAttribute{ + /* ... */ + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "hello": schema.StringAttribute{ + /* ... */ + }, + "demo": schema.BoolAttribute{ + /* ... */ + }, + }, + }, + }, + }, + } +} +``` + +#### SetNestedAttribute + +With set nested attributes, the attributes behave like a set of objects. The practitioner can +specify any number of groups of these attributes. + +```tf +resource "example_foo" "bar" { + nested_attribute = [ + { + hello = "world" + demo = true + }, + { + hello = "moon" + demo = false + }, + ] +} +``` + +```go +func (e *exampleResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "nested_attribute": schema.SetNestedAttribute{ + Optional: true, + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "hello": schema.StringAttribute{ + /* ... */ + }, + "demo": schema.BoolAttribute{ + /* ... */ + }, + }, + }, + }, + }, + } +} +``` + +## Create Provider-Defined Types and Values + +You may want to build your own attribute value and type implementations to allow your provider to combine validation, description, and plan customization behaviors into a reusable bundle. This helps avoid duplication or reimplementation and ensures consistency. -Use the [`attr.Value` -interface](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/attr#Value) -to implement an attribute value. It tells the framework how to express that -attribute value in a way that Terraform will understand. `attr.Value` has the -following methods. +Refer to [Custom Types](plugin/framework/handling-data/custom-types) for further details on creating provider-defined types and values. -| Method | Description | -| ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `ToTerraformValue` | Returns a Go type that is valid input for [`tftypes.NewValue`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-go/tftypes#NewValue) for the `tftypes.Type` specified by the `attr.Type` that creates the `attr.Value`. | -| `Equal` | Returns true if the passed attribute value should be considered to the attribute value the method is being called on. The passed attribute value is not guaranteed to be of the same Go type. | diff --git a/website/docs/plugin/framework/handling-data/blocks.mdx b/website/docs/plugin/framework/handling-data/blocks.mdx index c9558d04b..c9d85f614 100644 --- a/website/docs/plugin/framework/handling-data/blocks.mdx +++ b/website/docs/plugin/framework/handling-data/blocks.mdx @@ -8,7 +8,7 @@ description: >- 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. +-> Use [nested attributes](/plugin/framework/handling-data/attributes#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. diff --git a/website/docs/plugin/framework/handling-data/custom-types.mdx b/website/docs/plugin/framework/handling-data/custom-types.mdx new file mode 100644 index 000000000..95ce4ebab --- /dev/null +++ b/website/docs/plugin/framework/handling-data/custom-types.mdx @@ -0,0 +1,283 @@ +--- +page_title: 'Plugin Development - Framework: Handling Data - Custom Types' +description: >- + Custom Types. +--- + +# Custom Types + +You can use custom types for both attributes and blocks. + +~> **Important:** Specifying plan customization for attribute types is not yet +supported, limiting their utility. Support is expected in the near future. + +### `attr.Type` Interface + +Use the [`attr.Type` +interface](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/attr#Type) +to implement an attribute type. It tells Terraform about its constraints and tells the framework how to create new attribute values from the information Terraform supplies. `attr.Type` has the following methods. + +| Method | Description | +|----------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `TerraformType` | Returns the [`tftypes.Type` value](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-go/tftypes#Type) that describes its type constraints. This is how Terraform will know what type of values it can accept. | +| `ValueFromTerraform` | Returns an attribute value from the [`tftypes.Value`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-go/tftypes#Value) that Terraform supplies, or to return an error if it cannot. This error should not be used for validation purposes, and is expected to indicate programmer error, not practitioner error. | +| `Equal` | Returns true if the attribute type is considered equal to the passed attribute type. | + +### `AttributePathStepper` Interface + +All attribute types must implement the [`tftypes.AttributePathStepper` +interface](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-go/tftypes#AttributePathStepper), +so the framework can access element or attribute types using attribute paths. + +### `xattr.TypeWithValidation` Interface + +If validation for type values is desired, use the [`xattr.TypeWithValidation` interface](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/attr/xattr#TypeWithValidation) to include validation logic for type values. The framework will call this functionality when validating all values based on the schema. + +| Method | Description | +|------------|---------------------------------------------------------------| +| `Validate` | Returns any warning or error diagnostics for the given value. | + +### Type-Specific Interfaces + +| Case | Interface | Description | +|-----------------------------|----------------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| Elements of the same type | [`TypeWithElementType`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/attr#TypeWithElementType) | Attribute types that contain elements of the same type, like maps and lists, are required to implement `attr.TypeWithElementType`, which adds `WithElementType` and `ElementType` methods to the `attr.Type` interface. `WithElementType` must return a copy of the attribute type, but with its element type set to the passed type. `ElementType` must return the attribute type's element type. | +| Elements of different types | [`TypeWithElementTypes`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/attr#TypeWithElementType) | Attribute types that contain elements of differing types, like tuples, are required to implement the `attr.TypeWithElementTypes`, which adds `WithElementTypes` and `ElementTypes` methods to the `attr.Type` interface. `WithElementTypes` must return a copy of the attribute type, but with its element types set to the passed element types. `ElementTypes` must return the attribute type's element types. | +| Contain attributes | [`TypeWithAttributeTypes`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/attr#TypeWithAttributeTypes) | Attribute types that contain attributes, like objects, are required to implement the `attr.TypeWithAttributeTypes` interface, which adds `WithAttributeTypes` and `AttributeTypes` methods to the `attr.Type` interface. `WithAttributeTypes` must return a copy of the attribute type, but with its attribute types set to the passed attribute types. `AttributeTypes` must return the attribute type's attribute types. | + +### `attr.Value` Interface + +Use the [`attr.Value` +interface](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/attr#Value) +to implement an attribute value. It tells the framework how to express that +attribute value in a way that Terraform will understand. `attr.Value` has the +following methods. + +| Method | Description | +|--------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `ToTerraformValue` | Returns a Go type that is valid input for [`tftypes.NewValue`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-go/tftypes#NewValue) for the `tftypes.Type` specified by the `attr.Type` that creates the `attr.Value`. | +| `Equal` | Returns true if the passed attribute value should be considered to the attribute value the method is being called on. The passed attribute value is not guaranteed to be of the same Go type. | + +## Custom Type and Value + +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/schemas.mdx b/website/docs/plugin/framework/handling-data/schemas.mdx index 2279e01cc..fa9823587 100644 --- a/website/docs/plugin/framework/handling-data/schemas.mdx +++ b/website/docs/plugin/framework/handling-data/schemas.mdx @@ -68,85 +68,7 @@ value for that field. The value is the implementation details for the attribute. -### Nested Attributes - --> Only supported when using protocol version 6. - -[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 - -With single nested attributes, the attribute behaves like an object. The practitioner can only specify one definition of the nested attributes. - -```tf -resource "example_foo" "bar" { - nested_attribute = { - hello = "world" - demo = true - } -} -``` - -#### ListNestedAttribute - -With list nested attributes, the attribute behaves like a list of objects. The practitioner can -specify any number of groups of these attributes. - -```tf -resource "example_foo" "bar" { - nested_attribute = [ - { - hello = "world" - demo = true - }, - { - hello = "moon" - demo = false - }, - ] -} -``` - -#### MapNestedAttribute - -With map nested attributes, the attribute behaves like a map of objects. The practitioner can -specify any number of groups of these attributes, with string keys associated -with each group. - -```tf -resource "example_foo" "bar" { - nested_attribute = { - "red" = { - hello = "world" - demo = true - }, - "blue" = { - hello = "moon" - demo = false - }, - } -} -``` - -#### SetNestedAttribute - -With set nested attributes, the attributes behave like a set of objects. The practitioner can -specify any number of groups of these attributes. - -```tf -resource "example_foo" "bar" { - nested_attribute = [ - { - hello = "world" - demo = true - }, - { - hello = "moon" - demo = false - }, - ] -} -``` +Refer to [Attributes](/plugin/framework/handling-data/attributes) for further details of the different types of attributes. ### Required diff --git a/website/docs/plugin/framework/handling-data/terraform-concepts.mdx b/website/docs/plugin/framework/handling-data/terraform-concepts.mdx index 9edef640a..6d1a52dbf 100644 --- a/website/docs/plugin/framework/handling-data/terraform-concepts.mdx +++ b/website/docs/plugin/framework/handling-data/terraform-concepts.mdx @@ -18,712 +18,36 @@ The following examples use a resource but the same schema attribute and block ty ## 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, - }, - }, - }, - }, - } -} -``` +Attributes are used to set values. + +| Attribute Type | Description | +|-----------------------|---------------------------------------------------------------------------------| +| BoolAttribute | Boolean values (i.e., true/false) | +| Float64Attribute | 64 bit floating point number values | +| Int64Attribute | 64 bit integer number values | +| NumberAttribute | Generic number with up to 512 bits of floating point or integer precision | +| StringAttribute | String values | +| ListAttribute | List with a single element type (e.g., types.StringType) | +| MapAttribute | Map with a single element type (e.g., types.Int64Type) | +| SetAttribute | Set with a single element type (e.g., types.BoolType) | +| ObjectAttribute | Object with only type information for underlying attributes | +| ListNestedAttribute | List containing nested objects where the object attributes can be fully defined | +| MapNestedAttribute | Map containing nested objects where the object attributes can be fully defined | +| SetNestedAttribute | Set containing nested objects where the object attributes can be fully defined | +| SingleNestedAttribute | Single object where the object attributes can be fully defined | + +Refer to [Attributes - Terraform Configuration and Schema](/plugin/framework/handling-data/attributes#terraform-configuration-and-schema) for examples of Terraform configuration and schema that illustrate the usage of the various types of schema attributes. ## 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 +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. - diags := req.Config.Get(ctx, &data) - resp.Diagnostics.Append(diags...) +-> Use [nested attributes](/plugin/framework/handling-data/attributes#nested-attributes) for new schema implementations. Block support is mainly for migrating prior SDK-based providers. - if resp.Diagnostics.HasError() { - return - } +The available nesting modes are: - data.ListAttribute.DoSomething(ctx) - data.ListNestedAttribute.DoSomething(ctx) - data.ListNestedBlock.DoSomething(ctx) +- List: Ordered collection of objects +- Set: Unordered collection of objects +- Single: One object - /*...*/ -} -``` +Refer to [Blocks - Terraform Configuration](/plugin/framework/handling-data/blocks#terraform-configuration) for examples of Terraform configuration and schema that illustrate the usage of nested blocks.