• HashiCorp Developer

  • HashiCorp Cloud Platform
  • Terraform
  • Packer
  • Consul
  • Vault
  • Boundary
  • Nomad
  • Waypoint
  • Vagrant
Terraform
  • Install
  • Tutorials
    • About the Docs
    • Configuration Language
    • Terraform CLI
    • Terraform Cloud
    • Terraform Enterprise
    • CDK for Terraform
    • Provider Use
    • Plugin Development
    • Registry Publishing
    • Integration Program
  • Registry(opens in new tab)
  • Try Cloud(opens in new tab)
  • Sign up
Plugin Development

Framework

Skip to main content
  • Framework
    • Provider Code Walkthrough
    • Tutorials
    • Clone Template Repository
      (opens in new tab)
  • Provider Servers
  • Returning Errors and Warnings
  • Validation
  • Acceptance Tests
  • Debugging

  • Resources

  • Tutorial Library
  • Certifications
  • Community Forum
    (opens in new tab)
  • Support
    (opens in new tab)
  • GitHub
    (opens in new tab)
  • Terraform Registry
    (opens in new tab)
  1. Developer
  2. Terraform
  3. Plugin Development
  4. Framework
  5. Getting Started
  6. Provider Code Walkthrough
  • Plugin Framework
  • v1.0.x
  • v0.17.x
  • v0.16.x
  • v0.15.x
  • v0.14.x
  • v0.13.x
  • v0.12.x
  • v0.11.x
  • v0.10.x
  • v0.9.x
  • v0.8.x
  • v0.7.x

»Code Walkthrough

Terraform 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
  • resource or 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 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.

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 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 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 to obtain the provider.

The exampleProvider struct implements the provider.Provider interface. This interface defines the following functions:

  • Schema: 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: 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: 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: 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 and data sources.

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 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: This function returns the full name (TypeName) of the resource. The full name is used in Terraform configuration as resource <full name> <alias>.
  • Schema: 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: This function lets the provider create a new resource of this type.
  • Read: This function lets the provider read resource values in order to update state.
  • Update: This function lets the provider update the resource and state.
  • Delete: This function lets the provider delete the resource.
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 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: This function returns the full name (TypeName) of the data source. The full name is used in Terraform configuration as data <full name> <alias>.
  • Schema: 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: This function lets the provider read data source values in order to update state.
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 for more details and configuration examples.

Terraform Configuration

Refer to terraform-provider-scaffolding-framework for details on how to wire together a provider server, provider, resource and data source.

Once wired together, run the provider by specifying configuration and executing terraform apply.

Resource Configuration

resource "example_resource" "example" {
  configurable_attribute = "some-value"
}

The configurable_attribute is defined within the schema 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.

Data Source Configuration

data "example_datasource" "example" {
  configurable_attribute = "some-value"
}

The configurable_attribute is defined within the schema 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.

Edit this page on GitHub

On this page

  1. Code Walkthrough
  2. Core Provider Components
  3. Provider Server
  4. Provider
  5. Resource
  6. Data Source
  7. Terraform Configuration
Give Feedback(opens in new tab)
  • Certifications
  • System Status
  • Terms of Use
  • Security
  • Privacy
  • Trademark Policy
  • Trade Controls
  • Give Feedback(opens in new tab)