Providers are Terraform plugins that define resources and data sources for practitioners to use. Providers are wrapped by a provider server for interacting with Terraform.
This page describes the basic implementation details required for defining a provider. Further documentation is available for deeper provider concepts:
In this example, a provider implementation is scaffolded:
// Ensure the implementation satisfies the provider.Provider interface.var_ provider.Provider =&ExampleCloudProvider{}type ExampleCloudProvider struct{// Version is an example field that can be set with an actual provider// version on release, "dev" when the provider is built and ran locally,// and "test" when running acceptance testing.
Version string}// GetSchema satisfies the provider.Provider interface for exampleProvider.func(p *ExampleCloudProvider)GetSchema(ctx context.Context)(tfsdk.Schema, diag.Diagnostics){return tfsdk.Schema{
Attributes:map[string]tfsdk.Attribute{// Provider specific implementation.},},nil}// Configure satisfies the provider.Provider interface for ExampleCloudProvider.func(p *ExampleCloudProvider)Configure(ctx context.Context, req provider.ConfigureRequest, resp *provider.ConfigureResponse){// Provider specific implementation.}// DataSources satisfies the provider.Provider interface for ExampleCloudProvider.func(p *ExampleCloudProvider)DataSources(ctx context.Context)[]func() datasource.DataSource {return[]func() datasource.DataSource{// Provider specific implementation}}// Resources satisfies the provider.Provider interface for ExampleCloudProvider.func(p *ExampleCloudProvider)Resources(ctx context.Context)[]func() resource.Resource {return[]func() resource.Resource{// Provider specific implementation}}
// Ensure the implementation satisfies the provider.Provider interface.var_ provider.Provider =&ExampleCloudProvider{}type ExampleCloudProvider struct{// Version is an example field that can be set with an actual provider// version on release, "dev" when the provider is built and ran locally,// and "test" when running acceptance testing. Version string}// GetSchema satisfies the provider.Provider interface for exampleProvider.func(p *ExampleCloudProvider)GetSchema(ctx context.Context)(tfsdk.Schema, diag.Diagnostics){return tfsdk.Schema{ Attributes:map[string]tfsdk.Attribute{// Provider specific implementation.},},nil}// Configure satisfies the provider.Provider interface for ExampleCloudProvider.func(p *ExampleCloudProvider)Configure(ctx context.Context, req provider.ConfigureRequest, resp *provider.ConfigureResponse){// Provider specific implementation.}// DataSources satisfies the provider.Provider interface for ExampleCloudProvider.func(p *ExampleCloudProvider)DataSources(ctx context.Context)[]func() datasource.DataSource {return[]func() datasource.DataSource{// Provider specific implementation}}// Resources satisfies the provider.Provider interface for ExampleCloudProvider.func(p *ExampleCloudProvider)Resources(ctx context.Context)[]func() resource.Resource {return[]func() resource.Resource{// Provider specific implementation}}
Conventionally, many providers also create a helper function named New which can simplify provider server implementations.
The provider.Provider interface GetSchema method defines a schema describing what data is available in the provider's configuration. This configuration block is used to offer practitioners the opportunity to supply values to the provider and configure its behavior, rather than needing to include those values in every resource and data source. It is usually used to gather credentials, endpoints, and the other data used to authenticate with the API, but it is not limited to those uses.
In this example, a sample configuration and schema definition are provided:
The provider.Provider interface Configure method handles the configuration of any provider-level data or clients. These configuration values may be from the practitioner Terraform configuration, environment variables, or other means such as reading vendor-specific configuration files.
This is the only chance the provider has to configure provider-level data or clients, so they need to be persisted if other data source or resource logic will need to reference them. Refer to the Configure Data Sources and Configure Resources pages for additional implementation details.
In this example, the provider API token and endpoint are configured via environment variable or Terraform configuration:
type ExampleCloudProvider struct{}type ExampleCloudProviderModel struct{
ApiToken types.String `tfsdk:"api_token"`
Endpoint types.String `tfsdk:"endpoint"`}func(p *ExampleCloudProvider)GetSchema(ctx context.Context)(tfsdk.Schema, diag.Diagnostics){return tfsdk.Schema{
Attributes:map[string]tfsdk.Attribute{"api_token":{
Optional:true,
Type: types.StringType,},"endpoint":{
Optional:true,
Type: types.StringType,},},},nil}func(p *ExampleCloudProvider)Configure(ctx context.Context, req provider.ConfigureRequest, resp *provider.ConfigureResponse){// Check environment variables
apiToken := os.Getenv("EXAMPLECLOUD_API_TOKEN")
endpoint := os.Getenv("EXAMPLECLOUD_ENDPOINT")var data ExampleCloudProviderModel
// Read configuration data into model
resp.Diagnostics.Append(req.Config.Get(ctx,&data)...)// Check configuration data, which should take precedence over// environment variable data, if found.if data.ApiToken.Value !=""{
apiToken = data.ApiToken.Value
}if data.Endpoint.Value !=""{
endpoint = data.Endpoint.Value
}if apiToken ==""{
resp.Diagnostics.AddError("Missing API Token Configuration","While configuring the provider, the API token was not found in "+"the EXAMPLECLOUD_API_TOKEN environment variable or provider "+"configuration block api_token attribute.",)// Not returning early allows the logic to collect all errors.}if endpoint ==""{
resp.Diagnostics.AddError("Missing Endpoint Configuration","While configuring the provider, the endpoint was not found in "+"the EXAMPLECLOUD_ENDPOINT environment variable or provider "+"configuration block endpoint attribute.",)// Not returning early allows the logic to collect all errors.}// Create data/clients and persist to resp.DataSourceData and// resp.ResourceData as appropriate.}
type ExampleCloudProvider struct{}type ExampleCloudProviderModel struct{ ApiToken types.String `tfsdk:"api_token"` Endpoint types.String `tfsdk:"endpoint"`}func(p *ExampleCloudProvider)GetSchema(ctx context.Context)(tfsdk.Schema, diag.Diagnostics){return tfsdk.Schema{ Attributes:map[string]tfsdk.Attribute{"api_token":{ Optional:true, Type: types.StringType,},"endpoint":{ Optional:true, Type: types.StringType,},},},nil}func(p *ExampleCloudProvider)Configure(ctx context.Context, req provider.ConfigureRequest, resp *provider.ConfigureResponse){// Check environment variables apiToken := os.Getenv("EXAMPLECLOUD_API_TOKEN") endpoint := os.Getenv("EXAMPLECLOUD_ENDPOINT")var data ExampleCloudProviderModel
// Read configuration data into model resp.Diagnostics.Append(req.Config.Get(ctx,&data)...)// Check configuration data, which should take precedence over// environment variable data, if found.if data.ApiToken.Value !=""{ apiToken = data.ApiToken.Value
}if data.Endpoint.Value !=""{ endpoint = data.Endpoint.Value
}if apiToken ==""{ resp.Diagnostics.AddError("Missing API Token Configuration","While configuring the provider, the API token was not found in "+"the EXAMPLECLOUD_API_TOKEN environment variable or provider "+"configuration block api_token attribute.",)// Not returning early allows the logic to collect all errors.}if endpoint ==""{ resp.Diagnostics.AddError("Missing Endpoint Configuration","While configuring the provider, the endpoint was not found in "+"the EXAMPLECLOUD_ENDPOINT environment variable or provider "+"configuration block endpoint attribute.",)// Not returning early allows the logic to collect all errors.}// Create data/clients and persist to resp.DataSourceData and// resp.ResourceData as appropriate.}
Not all values are guaranteed to be
known when Configure is called.
For example, if a practitioner interpolates a resource's unknown value into the block,
that value may show up as unknown depending on how the graph executes:
In the example above, random_string.example.result is a read-only field on
random_string.example that won't be set until after random_string.example has been
applied. So the Configure method for the provider may report that the value
is unknown. You can choose how your provider handles this. If
some resources or data sources can be used without knowing that value, it may
be worthwhile to emit a warning and
check whether the value is set in resources and data sources before attempting
to use it. If resources and data sources can't provide any functionality
without knowing that value, it's often better to return an
error, which will halt the apply.
The provider.ProviderWithResources interface Resources method returns a slice of resources. Each element in the slice is a function to create a new resource.Resource so data is not inadvertently shared across multiple, disjointed resource instance operations unless explicitly coded. Information such as the resource type name is managed by the resource.Resource implementation.
In this example, the provider implements a single resource:
// With the provider.Provider implementationfunc(p *ExampleCloudProvider)Resources(_ context.Context)[]func() resource.Resource {return[]func() resource.Resource{
NewThingResource,}}// With the resource.Resource implementationfuncNewThingResource() resource.Resource {return&ThingResource{}}type ThingResource struct{}
// With the provider.Provider implementationfunc(p *ExampleCloudProvider)Resources(_ context.Context)[]func() resource.Resource {return[]func() resource.Resource{ NewThingResource,}}// With the resource.Resource implementationfuncNewThingResource() resource.Resource {return&ThingResource{}}type ThingResource struct{}
Use Go slice techniques to include large numbers of resources outside the provider Resources method code.
In this example, the provider codebase implements multiple "services" which group their own resources:
// With the provider.Provider implementationfunc(p *ExampleCloudProvider)Resources(_ context.Context)[]func() resource.Resource {return[]func() resource.Resource{
servicex.Resources...,
servicey.Resources...,}}// With the servicex implementationpackage servicex
var Resources =[]func() resource.Resource {
NewThingResource,
NewWidgetResource,}funcNewThingResource() resource.Resource {return&}type ThingResource struct{}funcNewWidgetResource() resource.Resource {return&WidgetResource{}}type WidgetResource struct{}
// With the provider.Provider implementationfunc(p *ExampleCloudProvider)Resources(_ context.Context)[]func() resource.Resource {return[]func() resource.Resource{ servicex.Resources..., servicey.Resources...,}}// With the servicex implementationpackage servicex
var Resources =[]func() resource.Resource { NewThingResource, NewWidgetResource,}funcNewThingResource() resource.Resource {return&}type ThingResource struct{}funcNewWidgetResource() resource.Resource {return&WidgetResource{}}type WidgetResource struct{}
The provider.ProviderWithDataSources interface DataSources method returns a slice of data sources. Each element in the slice is a function to create a new datasource.DataSource so data is not inadvertently shared across multiple, disjointed datasource instance operations unless explicitly coded. Information such as the datasource type name is managed by the datasource.DataSource implementation.
In this example, the provider implements a single data source:
// With the provider.Provider implementationfunc(p *ExampleCloudProvider)DataSources(_ context.Context)[]func() datasource.DataSource {return[]func() datasource.DataSource{
NewThingDataSource,}}// With the datasource.DataSource implementationfuncNewThingDataSource() datasource.DataSource {return&ThingDataSource{}}type ThingDataSource struct{}
// With the provider.Provider implementationfunc(p *ExampleCloudProvider)DataSources(_ context.Context)[]func() datasource.DataSource {return[]func() datasource.DataSource{ NewThingDataSource,}}// With the datasource.DataSource implementationfuncNewThingDataSource() datasource.DataSource {return&ThingDataSource{}}type ThingDataSource struct{}
Use Go slice techniques to include large numbers of data sources outside the provider DataSources method code.
In this example, the provider codebase implements multiple "services" which group their own datasources:
// With the provider.Provider implementationfunc(p *ExampleCloudProvider)DataSources(_ context.Context)[]func() datasource.DataSource {return[]func() datasource.DataSource{
servicex.DataSources...,
servicey.DataSources...,}}// With the servicex implementationpackage servicex
var DataSources =[]func() datasource.DataSource {
NewThingDataSource,
NewWidgetDataSource,}funcNewThingDataSource() datasource.DataSource {return&}type ThingDataSource struct{}funcNewWidgetDataSource() datasource.DataSource {return&WidgetDataSource{}}type WidgetDataSource struct{}
// With the provider.Provider implementationfunc(p *ExampleCloudProvider)DataSources(_ context.Context)[]func() datasource.DataSource {return[]func() datasource.DataSource{ servicex.DataSources..., servicey.DataSources...,}}// With the servicex implementationpackage servicex
var DataSources =[]func() datasource.DataSource { NewThingDataSource, NewWidgetDataSource,}funcNewThingDataSource() datasource.DataSource {return&}type ThingDataSource struct{}funcNewWidgetDataSource() datasource.DataSource {return&WidgetDataSource{}}type WidgetDataSource struct{}