Overview of Terraform Plugin Framework
mainterraform-plugin-go and is intended to provide the same power and versatility while abstracting away implementation details and reducing repetitive, verbose tasks common in provider development.repository·main·Indexed 18 days ago
https://github.com/hashicorp/terraform-plugin-frameworkA Go module used to build Terraform providers, providing a higher-level abstraction over terraform-plugin-go to simplify provider development. It is compatible with Terraform v0.12 and above and requires Go 1.25 or later. The framework focuses on unit testability, compatibility, discoverability, Go-nativeness, and managing verbosity.
terraform-plugin-go and is intended to provide the same power and versatility while abstracting away implementation details and reducing repetitive, verbose tasks common in provider development.The Terraform Plugin Framework's design involves a fundamental choice in how it represents core concepts like Providers, Resources, and Data Sources: as structs (values) or interfaces (types).
This distinction affects how you implement and compose your provider logic, specifically regarding runtime mutability and how patterns are shared across your codebase.
The framework provides two ways to think about schemas:
tfprotov6.Schema: The underlying type used by the Terraform plugin-go ecosystem. It is highly verbose and requires manual management of Block fields and SchemaAttribute slices.framework.Schema (Proposed/Conceptual): A higher-level, less verbose wrapper. It uses Go maps where keys are the attribute or block names, reducing boilerplate. It handles defaults for fields like DescriptionKind and simplifies nested block definitions using a NestedBlockType.Key benefits of the framework-specific schema approach:
Name fields.tftypes or tfprotov6 imports.Terraform distinguishes between two levels of feedback to support human workflows:
In the terraform-plugin-framework, it is recommended to use diagnostics rather than simple Go error values or panic calls when you want to provide contextualized feedback (such as warnings) to the user.
When working with attribute paths, it is important to distinguish between two primary use cases:
Concrete values point to a single, specific, and unique value (e.g., a specific index in a list or a specific key in a map). These are primarily used in diagnostics to tell a user exactly where an error occurred.
Patterns are used to describe a set or a rule that matches multiple values. This is primarily used in validation helpers. For example:
Patterns are a superset of concrete values; a pattern can be written so restrictively that it only matches a single attribute.
Validation functions receive an *tftypes.AttributePath parameter. This allows you to:
While the framework can automatically wrap errors with path information, passing the *tftypes.AttributePath directly is the most flexible approach for implementors.
The Terraform Plugin Framework handles diagnostics (errors and warnings) using a dedicated interface-based approach rather than extending the standard Go error interface.
This design choice is made to:
error interface would force warnings to be treated as errors by standard Go error handling, which is undesirable.Severity(), Summary(), and Detail()) without the constraints or verbosity of the standard error package requirements.ValidationErrorDiagnostic) that can implement additional capabilities, such as logging or path attribution, while remaining compatible with the core Diagnostic interface.The framework's architecture can be conceptualized through three implementation patterns. While the actual framework implementation is specific, these patterns illustrate the different ways a developer might interact with the API surface:
In this pattern, resources and providers are defined as structs containing function fields. This treats the resource as a collection of behaviors (functions) assigned to a value.
In this pattern, resources and providers are defined as interfaces. This treats the resource as a type that must satisfy a specific contract of methods (e.g., Create, Read, Update, Delete).
This pattern uses structs for state and configuration, but delegates core logic to an interface (e.g., a ResourceImplementation interface). This allows for a combination of structured data and polymorphic behavior.
// Example of the Mixed Pattern approach
type Resource struct {
Schema *tfprotov5.Schema
Implementation func(p ProviderState) ResourceImplementation
}
type ResourceImplementation interface {
Create(context.Context) []*tfprotov5.Diagnostic
Read(context.Context) []*tfprotov5.Diagnostic
Update(context.Context) []*tfprotov5.Diagnostic
Destroy(context.Context) []*tfprotov5.Diagnostic
}Data Source level validation can be implemented using two patterns:
DataSourceWithConfigValidators interface by providing a ConfigValidators(context.Context) []DataSourceConfigValidator method. This is used for reusable, predefined validation rules (like ConflictingAttributes).DataSourceWithValidateConfig interface by providing a ValidateConfig(context.Context, ValidateDataSourceConfigRequest, *ValidateDataSourceConfigResponse) method. This is used for complex, custom logic that cannot be expressed declaratively.Validation requests (ValidateDataSourceConfigRequest) provide access to the tfsdk.Config and the TypeName of the data source.
// Declarative approach
func (d *customDataSource) ConfigValidators(ctx context.Context) DataSourceConfigValidators {
return DataSourceConfigValidators{
ConflictingAttributes(
tftypes.NewAttributePath().AttributeName("first_attribute"),
tftypes.NewAttributePath().AttributeName("second_attribute"),
),
}
}The Terraform Plugin Protocol version 6 introduced a significant change in how nested data is handled.
Previously, complex nested structures could only be implemented using Blocks. Version 6 introduced the nested_type field within the Attribute message. This allows an Attribute to encode nested object schema information directly, effectively allowing attributes to act as containers for nested data (similar to how blocks work) while maintaining the properties of an attribute.
This shift allows for more flexible schema definitions and better integration with modern Terraform features.
The Terraform Plugin Framework uses a Request and Response pattern for validation to ensure long-term compatibility and allow for context-specific data. Instead of passing raw, typed parameters (like attr.Value or path), validation functions receive a request object and a pointer to a response object. This allows the framework to evolve the underlying data structures without breaking existing provider implementations.
For example, instead of:
func(ctx, path, value) error
The framework prefers:
func(ctx, ValidateRequest, *ValidateResponse)
Resource import is the process of bringing existing, unmanaged resources under Terraform's management by creating their corresponding state in the Terraform statefile.
When a user executes the terraform import command, the Terraform CLI forwards a request to the provider containing a resource address (parsed into a type_name) and an import identifier (id). The provider is responsible for responding with the relevant resource state(s).
Terraform supports two main interaction patterns:
resource.name[0]).# Example of a 1:1 import command
terraform import aws_security_group.example sg-12345678