Skip to content

Latest commit

 

History

History
370 lines (291 loc) · 13.3 KB

File metadata and controls

370 lines (291 loc) · 13.3 KB
page_title description
Provider: Migrating from SDKv2 to the Framework
Migrate a provider definition and schema from SDKv2 to the plugin Framework.

Provider

Providers are Terraform plugins that define resources and data sources for practitioners to use. You serve your providers with a provider server so they can interact with Terraform.

This page explains how to migrate a provider server, definition, and schema from SDKv2 to the plugin Framework.

Serving the Provider

You must update your provider's main.go file to serve Framework providers. Refer to Provider Servers in the Framework documentation for details.

SDKv2

In SDKv2, the provider package's main function serves the provider by calling plugin.Serve.

The following code shows a basic implementation for serving an SDKv2 provider.

func main() {
    plugin.Serve(
        &plugin.ServeOpts{
            ProviderFunc: provider.New,
            ProviderAddr: "registry.terraform.io/<namespace>/<provider_name>",
        },
    )
}

Framework

In the Framework, you serve your provider by calling providerserver.Serve in your provider package's main function. Refer to Provider Servers in the Framework documentation for details.

The following code shows an equivalent implementation for serving a provider in the Framework.

func main() {
    err := providerserver.Serve(
        context.Background(),
        provider.New,
        providerserver.ServeOpts{
            Address: "registry.terraform.io/<namespace>/<provider_name>",
        },
    )

    if err != nil {
        log.Fatal(err)
    }
}

Muxing

Muxing lets you use two versions of the same provider concurrently, with each serving different resources or data sources. Refer to the Combining and Translating documentation for full details about muxing configuration. Use muxing when you want to release a version of your provider with only some of the resources and data sources migrated to the Framework. You may want to do this if your provider manages a large number of resources and data sources.

The following example shows how to set up muxing for a provider that uses protocol version 5 to maintain compatibility with Terraform >= 0.12.0. The example also shows how to use the debug flag to optionally run the provider in debug mode.

func main() {
    ctx := context.Background()

    var debug bool

    flag.BoolVar(&debug, "debug", false, "set to true to run the provider with support for debuggers like delve")
    flag.Parse()

    providers := []func() tfprotov5.ProviderServer{
        providerserver.NewProtocol5(provider.New()),
        provider.Provider().GRPCProvider,
    }

    muxServer, err := tf5muxserver.NewMuxServer(ctx, providers...)
    if err != nil {
        log.Fatal(err)
    }

    var serveOpts []tf5server.ServeOpt

    if debug {
        serveOpts = append(serveOpts, tf5server.WithManagedDebug())
    }

    err = tf5server.Serve(
        "registry.terraform.io/<namespace>/<provider_name>",
        muxServer.ProviderServer,
        serveOpts...,
    )
    if err != nil {
        log.Fatal(err)
    }
}

Provider Definition

Providers built with SDKv2 use a schema.Provider struct to define their behavior, while Framework providers use a type that implements the provider.Provider interface, which you must define. Refer to Providers in the Framework documentation for details.

SDKv2

The ProviderFunc field on plugin.ServeOpts requires a pointer to schema.Provider. This is typically satisfied by calling a function that returns a pointer to schema.Provider.

The ResourcesMap and DataSourcesMap fields each contain a map of strings to functions that each return a pointer to a schema.Resource struct.

The following example shows a basic implementation of an SDKv2 provider.

func New() *schema.Provider {
    return &schema.Provider{
        Schema:         map[string]*schema.Schema{},
        ConfigureContextFunc:   configureContextFunc(),
        ResourcesMap:   map[string]*schema.Resource{
            "resource_example": resourceExample(),
        },
        DataSourcesMap: map[string]*schema.Resource{
            "dataSource_example": dataSourceExample(),
        },
        /* ... */
    }
}

Framework

In the Framework, the second argument to your provider.Serve function requires a function that returns a type satisfying the provider.Provider interface.

The following code shows a typical implementation. In this implementation, the Resources method returns a slice of functions that return types that implement the resource.Resource interface. The DataSources method returns a slice of functions that return types that implement the datasource.DataSource interface. Refer to the Resources and Data Sources pages in this guide to implement these functions for your provider.

type ExampleCloudProvider struct {
}

func New() provider.Provider {
    return &ExampleCloudProvider{}
}

func (p *ExampleCloudProvider) Schema(ctx context.Context, req provider.SchemaRequest, resp *provider.SchemaResponse) {
    resp.Schema = schema.Schema{}
}

func (p *ExampleCloudProvider) Configure(ctx context.Context, req provider.ConfigureRequest, resp *provider.ConfigureResponse) {
}

func (p *ExampleCloudProvider) Resources(ctx context.Context) []func() resource.Resource {
    return []func() resource.Resource {
        func() resource.Resource {
            return resourceExample{},
        },
    }
}

func (p *ExampleCloudProvider) GetDataSources(ctx context.Context) []func() datasource.DataSource {
    return []func() datasource.DataSource {
        func() datasource.DataSource {
            return dataSourceExample{},
        },
    }
}

Migration Notes

Remember the following differences between SDKv2 and the Framework when completing the migration.

  • In SDKv2, your provider's New function returns a schema.Provider struct. In the Framework, New returns a type that you define which satisfies the provider.Provider interface.
  • In SDKv2, Schema is a field on schema.Provider that contains map[string]*schema.Schema, which maps attribute names to schema.Schema structs. In the Framework, Schema is a method you define on your provider's provider.Provider interface that returns your provider's schema.Schema struct.
  • In SDKv2, ConfigureContextFunc is a field on schema.Provider containing a function that configures the provider. In the Framework, Configure is a function you define on your provider that configures your provider.
  • In SDKv2, ResourcesMap is a field on schema.Provider containing map[string]*schema.Resource, which maps resource names to schema.Resource structs. In the Framework, Resources is a method you define on your provider that returns []func() resource.Resource, which creates resource types that you define, which satisfy the resource.Resource interface.
  • In SDKv2, DataSourcesMap is a field on schema.Provider containing map[string]*schema.Resource, which maps data source names to schema.Resource structs (data sources and resources both use schema.Resource). In the Framework, DataSources is a method you define on your provider that returns []func() datasource.DataSource, which creates data source types that you define, which satisfy the datasource.DataSource interface.

Example

SDKv2

The following example shows how to set up a provider schema, configuration, resources, and data sources using SDKv2.

func New() (*schema.Provider, error) {
    return &schema.Provider{
        Schema: map[string]*schema.Schema{
            "attribute": {
                /* ... */
            },
        },
        ConfigureContextFunc: configureProvider,
        ResourcesMap: map[string]*schema.Resource{
            "exampleResource": exampleResource(),
            /* ... */
        },
        DataSourcesMap: map[string]*schema.Resource{
            "exampleDataSource": exampleDataSource(),
            /* ... */
        },
    }, nil
}

Framework

The following shows the same section of provider code after the migration.

var _ provider.Provider = (*exampleProvider)(nil)

func New() provider.Provider {
    return &exampleProvider{}
}

func (p *exampleProvider) Resources(_ context.Context) []func() resource.Resource {
    return []func() resource.Resource{
        func() resource.Resource {
            return &exampleResource{}
        },
        /* ... */
    }
}

func (p *exampleProvider) DataSources(_ context.Context) []func() datasource.DataSource {
    return []func() datasource.DataSource{
        func() datasource.DataSource {
            return &exampleDataSource{},
        },
        /* ... */
    }
}

func (p *exampleProvider) Schema(_ context.Context, _ provider.SchemaRequest, resp *provider.SchemaResponse) {
    resp.Schema = schema.Schema{
        Attributes: map[string]schema.Attribute{
            "attribute": schema.SingleNestedBlock{
                /* ... */
            },
        },
    }
}

func (p *exampleProvider) Configure(ctx context.Context, req provider.ConfigureRequest, res *provider.ConfigureResponse) {
    /* ... */
}

Provider Schema

A provider schema defines the attributes and behaviors of the provider itself. For example, a provider that connects to a third-party API may define attributes for the base URL or a required authentication token.

SDKv2

In SDKv2, you implement a provider Schema by populating the Schema field on the schema.Provider struct. The Schema field contains a map[string]*schema.Schema. Each map entry represents the name of the attribute and pointer to a schema.Schema struct that defines that attribute's behavior.

The following example defines the provider schema in the Schema field within the schema.Provider struct.

func New() *schema.Provider {
    return &schema.Provider{
        Schema: map[string]*schema.Schema{
            /* ... */
        },

Framework

In the Framework, the Schema method returns the provider schema. The Schema method is part of the provider.Provider interface that your provider must implement. Schema returns a struct containing fields for Attributes and Blocks. These Attributes and Blocks contain map[string]schema.Attribute and map[string]schema.Block, respectively. Refer to Providers - Schema in the Framework documentation for details.

The following code shows the Schema method, which returns the provider schema.

func (p *ExampleCloudProvider) Schema(ctx context.Context, req provider.SchemaRequest, resp *provider.SchemaResponse) {
    resp.Schema = schema.Schema{
        /* ... */
    }
}

Refer to the Attributes and Blocks pages in this migration guide to learn how to migrate those fields to the Framework.

Migration Notes

Remember the following differences between SDKv2 and the Framework when completing the migration.

  • In SDKv2, schema.Schema is a struct that defines attributes and behaviors (e.g., Type, Optional). In the Framework schema.Schema is a struct that includes attributes and blocks.

Example

This example shows how to use a nested block and a nested attribute for the SDKv2 and Framework examples, respectively. Refer to the Blocks with Computed Fields page in this guide for more details.

SDKv2

The following example shows the configuration of the example_attribute attribute for the provider's example_block configuration block.

Schema: map[string]*schema.Schema{
    "example_block": {
        Type:     schema.TypeList,
        Optional: true,
        MaxItems: 1,
        Elem: &schema.Resource{
            Schema: map[string]*schema.Schema{
                "example_attribute": {
                    Type:             schema.TypeString,
                    Optional:         true,
                    ValidateDiagFunc: validation.ToDiagFunc(validation.IsURLWithScheme(SupportedProxySchemesStr())),
                    ConflictsWith:    []string{"example_block.0.another_attribute"},
                },
                /* ... */

Framework

The following shows the same section of provider code after the migration.

This code implements the example_attribute attribute for the example_Block block with the Framework.

func (p *exampleProvider) Schema(_ context.Context, _ provider.SchemaRequest, resp *provider.SchemaResponse) {
    resp.Schema = schema.Schema{
        Attributes: map[string]schema.Attribute{
            "example_block": schema.SingleNestedBlock{
                Optional: true,
                Attributes: map[string]schema.Attribute{
                    "example_attribute": schema.StringAttribute{
                        Optional: true,
                        Validators: []validator.String{
                            attribute_validator.UrlWithScheme(supportedProxySchemesStr()...),
                            stringvalidator.ConflictsWith(path.MatchRelative().AtParent().AtName("another_attribute")),
                        },