Component · Providers
Providers are external processes communicating over gRPC via stdin/stdout.
GRPCProvider implements providers.Interface by translating
Go method calls into protobuf RPC calls.
┌─────────────────── Terraform process ─────────────────────────────────────┐ │ │ │ ProviderTransformer → NodeApplyableProvider.Execute() │ │ │ │ │ ├─► ctx.InitProvider(addr, config) │ │ │ └── contextPlugins.NewProviderInstance(addr) │ │ │ └── providers.Factory() │ │ │ │ │ │ │ ┌────────────┼─────────────────────────────────────────┐ │ │ │ │ go-plugin │ launch subprocess │ │ │ │ │ ▼ │ │ │ │ │ os.StartProcess(providerBinaryPath) │ │ │ │ │ ├── Provider writes a "ready" line to stdout │ │ │ │ │ │ containing its gRPC listener address │ │ │ │ │ └── go-plugin reads it → establishes gRPC conn │ │ │ │ │ │ │ │ │ │ Returns: *plugin.GRPCProvider │ │ │ │ └─────────────────────────────────────────────────────-─┘ │ │ │ │ │ ├─► ctx.ConfigureProvider(addr, configVal) │ │ │ └── GRPCProvider.ConfigureProvider(req) │ │ │ → proto.ProviderClient.ConfigureProvider(ctx, protoReq) │ │ │ │ │ │ [plan walk] ──────────────────────────────────────────────────────── │ │ │ │ │ ├─► GRPCProvider.ReadResource(req) [context_plan.go refresh] │ │ ├─► GRPCProvider.PlanResourceChange(req) [context_plan.go plan] │ │ │ │ │ │ [apply walk] ─────────────────────────────────────────────────────── │ │ │ │ │ ├─► GRPCProvider.ApplyResourceChange(req) [context_apply.go apply] │ │ │ │ │ └─► ctx.CloseProvider(addr) │ │ └── GRPCProvider.Close() → kill subprocess │ │ │ └──────────────────────────────────────────────────────────────────────────────┘ ┌──────────────── Provider subprocess (e.g. terraform-provider-aws) ──────────┐ │ │ │ Implements tfprotov6.ProviderServer (protobuf service) │ │ ConfigureProvider → initialize AWS SDK client │ │ ReadResource → call AWS API to get current state │ │ PlanResourceChange → diff desired vs current, validate │ │ ApplyResourceChange→ call AWS API to create/update/delete │ │ │ └───────────────────────────────────────────────────────────────────────────────┘
providers.Interface is the complete API surface Terraform exposes to
provider implementations. GRPCProvider implements it by marshalling
each call into protobuf and sending it over gRPC. Test doubles implement it directly
in memory.
internal/providers/provider.go:17
GitHub
type Interface interface {
// ── Schema ──────────────────────────────────────────────────
// Returns schema for every resource type, data source, and the
// provider configuration block itself.
GetProviderSchema() GetProviderSchemaResponse
// ── Configuration ───────────────────────────────────────────
ValidateProviderConfig(ValidateProviderConfigRequest) ValidateProviderConfigResponse
ConfigureProvider(ConfigureProviderRequest) ConfigureProviderResponse
// ── Managed resources ───────────────────────────────────────
ValidateResourceConfig(ValidateResourceConfigRequest) ValidateResourceConfigResponse
UpgradeResourceState(UpgradeResourceStateRequest) UpgradeResourceStateResponse
// Called during refresh walk: read actual state from cloud API
ReadResource(ReadResourceRequest) ReadResourceResponse
// Called during plan walk: compute Before→After diff
PlanResourceChange(PlanResourceChangeRequest) PlanResourceChangeResponse
// Called during apply walk: make the change real
ApplyResourceChange(ApplyResourceChangeRequest) ApplyResourceChangeResponse
// ── Data sources ────────────────────────────────────────────
ValidateDataResourceConfig(ValidateDataResourceConfigRequest) ValidateDataResourceConfigResponse
ReadDataSource(ReadDataSourceRequest) ReadDataSourceResponse
// ── Import ──────────────────────────────────────────────────
ImportResourceState(ImportResourceStateRequest) ImportResourceStateResponse
// ── Ephemeral resources (credentials, tokens) ───────────────
OpenEphemeralResource(OpenEphemeralResourceRequest) OpenEphemeralResourceResponse
RenewEphemeralResource(RenewEphemeralResourceRequest) RenewEphemeralResourceResponse
CloseEphemeralResource(CloseEphemeralResourceRequest) CloseEphemeralResourceResponse
// ── Provider-defined functions ──────────────────────────────
CallFunction(CallFunctionRequest) CallFunctionResponse
// Stop cancels in-progress operations.
Stop() error
// Close shuts down the provider process.
Close() error
}
internal/plugin/grpc_provider.go:53
GitHub
// GRPCProvider implements providers.Interface using a gRPC connection
// to an out-of-process provider binary.
type GRPCProvider struct {
// PluginClient manages the subprocess lifetime.
PluginClient *plugin.Client
// TestServer is non-nil only during in-process testing.
TestServer tfplugin6.ProviderServer
// Addr is the provider's registry address (for diagnostics).
Addr addrs.Provider
// gRPC client stub (generated from .proto file).
client tfplugin6.ProviderClient
// schema cache — fetched once with GetProviderSchema
mu sync.Mutex
schema providers.ProviderSchema
// ...
}
internal/plugin/grpc_provider.go:535, 620, 742
GitHub
// ReadResource — used during the refresh phase.
// Sends the current state to the provider and gets back the
// actual current state from the cloud API.
func (p *GRPCProvider) ReadResource(
r providers.ReadResourceRequest,
) (resp providers.ReadResourceResponse) {
// Marshal cty.Value → protobuf DynamicValue
protoReq := &proto.ReadResource_Request{
TypeName: r.TypeName,
CurrentState: &proto.DynamicValue{Msgpack: marshaledState},
Private: r.Private,
}
protoResp, err := p.client.ReadResource(p.ctx, protoReq)
// Unmarshal protobuf DynamicValue → cty.Value
resp.NewState = unmarshalDynamicValue(protoResp.NewState, schema)
return resp
}
// PlanResourceChange — used during plan walk.
// Provider computes what the resource will look like after apply,
// filling in any computed attributes with known or unknown values.
func (p *GRPCProvider) PlanResourceChange(
r providers.PlanResourceChangeRequest,
) (resp providers.PlanResourceChangeResponse) {
// ...
protoResp, err := p.client.PlanResourceChange(p.ctx, protoReq)
// resp.PlannedState contains the After value (may have unknowns)
return resp
}
// ApplyResourceChange — used during apply walk.
// Provider makes real API calls and returns the final state.
func (p *GRPCProvider) ApplyResourceChange(
r providers.ApplyResourceChangeRequest,
) (resp providers.ApplyResourceChangeResponse) {
// ...
protoResp, err := p.client.ApplyResourceChange(p.ctx, protoReq)
// resp.NewState contains actual post-apply resource state
return resp
}
internal/providers/provider.go (request/response structs)
GitHub
type PlanResourceChangeRequest struct {
TypeName string
PriorState cty.Value // refreshed current state
ProposedNewState cty.Value // config evaluated to cty.Value
Config cty.Value // raw config (may include unknowns)
PriorPrivate []byte // opaque provider metadata from prior state
ProviderMeta cty.Value
}
type PlanResourceChangeResponse struct {
PlannedState cty.Value // final desired state (may have unknowns)
RequiresReplace cty.PathSet // attributes that force resource replacement
PlannedPrivate []byte // opaque metadata to pass to ApplyResourceChange
Diagnostics tfdiags.Diagnostics
}
type ApplyResourceChangeRequest struct {
TypeName string
PriorState cty.Value // the Before value from the plan
PlannedState cty.Value // the After value from the plan
Config cty.Value
PlannedPrivate []byte
ProviderMeta cty.Value
}
type ApplyResourceChangeResponse struct {
NewState cty.Value // actual post-apply state
Private []byte // provider metadata to store in state
Diagnostics tfdiags.Diagnostics
}
Unknown values: During PlanResourceChange, a provider
may return cty.DynamicVal for attributes computed at apply time
(e.g., an EC2 instance ID). Terraform propagates these unknowns through the
expression evaluator so dependent resources also plan with unknowns.
Providers are created lazily via a providers.Factory function.
The factory is stored in contextPlugins and called by
EvalContext.InitProvider the first time a provider is needed in the graph walk.
internal/providers/provider.go (Factory type)
GitHub
// Factory is a function that creates a new provider instance.
// Called once per provider address per operation.
type Factory func() (Interface, error)
// FactoryFixed returns a factory that always returns the same instance.
// Used in tests to inject mock providers.
func FactoryFixed(p Interface) Factory {
return func() (Interface, error) { return p, nil }
}
For local development, Terraform supports provider reattach:
a running provider process can be reused across multiple Terraform runs by
setting TF_REATTACH_PROVIDERS. The environment variable contains
a JSON map from provider address to gRPC listener address, read by
getproviders/reattach.