Component · Providers

Provider Plugin System

Providers are external processes communicating over gRPC via stdin/stdout. GRPCProvider implements providers.Interface by translating Go method calls into protobuf RPC calls.

Provider plugin lifecycle
┌─────────────────── 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 — the contract

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
}
GRPCProvider — gRPC wrapper
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
    // ...
}

Core operations on GRPCProvider

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
}
Request / response types
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.

Factory pattern & reattach

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.