Terraform
Providers
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:
- Configure data sources with provider-level data types or clients.
- Configure resources with provider-level data types or clients.
- Configure ephemeral resources with provider-level data types or clients.
- Validate practitioner configuration against acceptable values.
Define Provider Type
Implement the provider.Provider interface. Each of the methods described in more detail below.
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
}
// Metadata satisfies the provider.Provider interface for ExampleCloudProvider
func (p *ExampleCloudProvider) Metadata(ctx context.Context, req provider.MetadataRequest, resp *provider.MetadataResponse) {
resp.TypeName = // provider specific implementation
}
// Schema satisfies the provider.Provider interface for ExampleCloudProvider.
func (p *ExampleCloudProvider) Schema(ctx context.Context, req provider.SchemaRequest, resp *provider.SchemaResponse) {
resp.Schema = schema.Schema{
Attributes: map[string]schema.Attribute{
// Provider specific implementation.
},
}
}
// 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.
func New(version string) func() provider.Provider {
return func() provider.Provider {
return &ExampleCloudProvider{
Version: version,
}
}
}
Metadata Method
The provider.Provider
interface Metadata
method defines information about the provider itself, such as its type name and version. This information is used to simplify creating data sources and resources.
In this example, the provider type name is set to examplecloud
:
func (p *ExampleCloudProvider) Metadata(ctx context.Context, req provider.MetadataRequest, resp *provider.MetadataResponse) {
resp.TypeName = "examplecloud"
}
Schema Method
The provider.Provider
interface Schema
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.
During the terraform validate
, terraform plan
and terraform apply
commands, Terraform calls the provider GetProviderSchema
RPC, in which the framework calls the provider.Provider
interface Schema
method.
In this example, a sample configuration and schema definition are provided:
// Example Terraform configuration:
//
// provider "examplecloud" {
// api_token = "v3rYs3cr3tt0k3n"
// endpoint = "https://example.com/"
// }
func (p *ExampleCloudProvider) Schema(ctx context.Context, req provider.SchemaRequest, resp *provider.SchemaResponse) {
resp.Schema = schema.Schema{
Attributes: map[string]schema.Attribute{
"api_token": schema.StringAttribute{
Optional: true,
},
"endpoint": schema.StringAttribute{
Optional: true,
},
},
}
}
If the provider does not accept practitioner Terraform configuration, leave the method defined, but empty.
Configure Method
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.
During the terraform plan
and terraform apply
commands, Terraform calls the provider ConfigureProvider
RPC, in which the framework calls the provider.Provider
interface Configure
method.
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.
If the logic needs to return warning or error diagnostics, they can added into the provider.ConfigureResponse.Diagnostics
field.
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) Schema(ctx context.Context, req provider.SchemaRequest, resp *provider.SchemaResponse) {
resp.Schema = schema.Schema{
Attributes: map[string]schema.Attribute{
"api_token": schema.StringAttribute{
Optional: true,
},
"endpoint": schema.StringAttribute{
Optional: true,
},
},
}
}
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.ValueString() != "" {
apiToken = data.ApiToken.ValueString()
}
if data.Endpoint.ValueString() != "" {
endpoint = data.Endpoint.ValueString()
}
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, resp.ResourceData,
// and resp.EphemeralResourceData as appropriate.
}
Unknown Values
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:
resource "random_string" "example" {}
provider "examplecloud" {
api_token = random_string.example.result
endpoint = "https://example.com/"
}
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.
Resources
The provider.Provider
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.
The provider.Provider
interface Resources
method is called during execution of the terraform validate
, terraform plan
and terraform apply
commands when the ValidateResourceConfig
, ReadResource
, PlanResourceChange
and ApplyResourceChange
RPCs are sent.
In this example, the provider implements a single resource:
// With the provider.Provider implementation
func (p *ExampleCloudProvider) Resources(_ context.Context) []func() resource.Resource {
return []func() resource.Resource{
NewThingResource,
}
}
// With the resource.Resource implementation
func NewThingResource() 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 implementation
func (p *ExampleCloudProvider) Resources(_ context.Context) []func() resource.Resource {
return []func() resource.Resource{
servicex.Resources...,
servicey.Resources...,
}
}
// With the servicex implementation
package servicex
var Resources = []func() resource.Resource {
NewThingResource,
NewWidgetResource,
}
func NewThingResource() resource.Resource {
return &ThingResource{}
}
type ThingResource struct {}
func NewWidgetResource() resource.Resource {
return &WidgetResource{}
}
type WidgetResource struct {}
DataSources
The provider.Provider
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.
The provider.Provider
interface DataSources
method is called during execution of the terraform validate
, terraform plan
and terraform apply
commands when the ValidateDataResourceConfig
and ReadDataSource
RPCs are sent.
In this example, the provider implements a single data source:
// With the provider.Provider implementation
func (p *ExampleCloudProvider) DataSources(_ context.Context) []func() datasource.DataSource {
return []func() datasource.DataSource{
NewThingDataSource,
}
}
// With the datasource.DataSource implementation
func NewThingDataSource() 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 implementation
func (p *ExampleCloudProvider) DataSources(_ context.Context) []func() datasource.DataSource {
return []func() datasource.DataSource{
servicex.DataSources...,
servicey.DataSources...,
}
}
// With the servicex implementation
package servicex
var DataSources = []func() datasource.DataSource {
NewThingDataSource,
NewWidgetDataSource,
}
func NewThingDataSource() datasource.DataSource {
return &ThingDataSource{}
}
type ThingDataSource struct {}
func NewWidgetDataSource() datasource.DataSource {
return &WidgetDataSource{}
}
type WidgetDataSource struct {}