Terraform — Interfaces#

Interface catalog#

providers.Interface#

  • Package: internal/providers
  • File: internal/providers/provider.go:17
  • Methods (35+): GetProviderSchema(), GetResourceIdentitySchemas(), ValidateProviderConfig(), ValidateResourceConfig(), ValidateDataResourceConfig(), ValidateEphemeralResourceConfig(), ValidateListResourceConfig(), UpgradeResourceState(), UpgradeResourceIdentity(), ConfigureProvider(), Stop() error, ReadResource(), PlanResourceChange(), ApplyResourceChange(), ImportResourceState(), GenerateResourceConfig(), MoveResourceState(), ReadDataSource(), OpenEphemeralResource(), RenewEphemeralResource(), CloseEphemeralResource(), CallFunction(), ListResource(), ValidateStateStoreConfig(), ConfigureStateStore(), ReadStateBytes(), WriteStateBytes(), LockState(), UnlockState(), GetStates(), DeleteState(), PlanAction(), InvokeAction(), ValidateActionConfig(), Close() error
  • Purpose: Defines the complete contract for a Terraform provider plugin — the component that actually communicates with cloud APIs. Every resource CRUD, schema negotiation, state upgrade, ephemeral resource lifecycle, function call, and state store operation is declared here.
  • Implementations:
    • internal/plugin.GRPCProvider — Protocol 5 gRPC client stub (go-plugin subprocess)
    • internal/plugin6.GRPCProvider — Protocol 6 gRPC client stub
    • internal/providers/mock_provider.go — Programmable test mock
    • internal/grpcwrap — adapts providers.Interface to serve as a gRPC server (used for test providers embedded in Terraform itself)
  • Design quality: Intentionally broad — this is a value-object protocol boundary, not a use-case interface. The breadth reflects the complete provider protocol rather than the interface segregation principle (ISP). The companion StateStoreChunkSizeSetter optional interface carves out one stateful negotiation concern. The interface grew over time as new resource types (ephemeral, list, actions, state stores) were added to the protocol.

backend.Backend#

  • Package: internal/backend
  • File: internal/backend/backend.go:44
  • Methods: ConfigSchema() *configschema.Block, PrepareConfig(cty.Value) (cty.Value, tfdiags.Diagnostics), Configure(cty.Value) tfdiags.Diagnostics, StateMgr(workspace string) (statemgr.Full, tfdiags.Diagnostics), DeleteWorkspace(name string, force bool) tfdiags.Diagnostics, Workspaces() ([]string, tfdiags.Diagnostics)
  • Purpose: Minimal contract for state-storage backends. Provides configuration lifecycle (schema → validate → configure) and workspace-aware state manager creation. Notably does not include operation execution — that is split into backendrun.OperationsBackend.
  • Implementations: internal/backend/local.Local, internal/cloud.Cloud, internal/backend/remote.Remote, plus nine remote-state backends (S3, GCS, Azure, Consul, k8s, PG, OCI, COS, OSS) each as separate Go modules.
  • Design quality: Well-segregated. The clean separation from OperationsBackend means the majority of backends (pure remote state) implement only 6 methods and have no dependency on the operations packages.

backendrun.OperationsBackend#

  • Package: internal/backend/backendrun
  • File: internal/backend/backendrun/operation.go:38
  • Methods: Embeds backend.Backend + Operation(context.Context, *Operation) (*RunningOperation, error), ServiceDiscoveryAliases() ([]HostAlias, error)
  • Purpose: Extension of Backend for the two backends that actually execute Terraform operations (local and HCP Terraform remote). Operation() is non-blocking — it returns a RunningOperation whose context the caller blocks on.
  • Implementations: internal/backend/local.Local, internal/cloud.Cloud, internal/backend/remote.Remote
  • Design quality: Good use of interface extension via embedding. The comment in the source explicitly notes that most backends should not implement this — it’s an opt-in enrichment for operation-capable backends.

terraform.EvalContext#

  • Package: internal/terraform
  • File: internal/terraform/eval_context.go:36
  • Methods (30+): StopCtx() context.Context, Path() addrs.ModuleInstance, Hook(func(Hook) (HookAction, error)) error, Input() UIInput, InitProvider(), Provider(), ProviderSchema(), CloseProvider(), ConfigureProvider(), ProviderInput(), SetProviderInput(), Provisioner(), ProvisionerSchema(), ClosePlugins(), EvaluateBlock(), EvaluateExpr(), EvaluateReplaceTriggeredBy(), EvaluateImportIdExpression(), EvaluateImportReferences(), NamedValues(), Changes(), State(), RefreshState(), Checks(), Instances(), Deferrals(), EphemeralResources(), GetEncryptionKey(), WithPath() EvalContext, Actions()
  • Purpose: The evaluation context threaded through every graph node during a walk. Provides each node access to providers, state, planned changes, named values (variables/outputs/locals), schema, and all other runtime resources. Acts as a service locator scoped to a module instance path.
  • Implementations: BuiltinEvalContext (sole production implementation); MockEvalContext for tests.
  • Design quality: Large interface by design — it is the central service locator for the graph walk. The WithPath() method returning a new EvalContext scoped to a child module is an elegant design that avoids global mutable state while supporting deeply nested module calls. The single production implementation is a sign that this is an internal abstraction for testability, not for extensibility.

terraform.GraphNodeExecutable#

  • Package: internal/terraform
  • File: internal/terraform/execute.go:10
  • Methods: Execute(EvalContext, walkOperation) tfdiags.Diagnostics
  • Purpose: The single method that makes a DAG node “executable” during a graph walk. Implemented by every node type that performs actual work (resource planning/applying, variable evaluation, output setting, module calls, provider initialization/closing, etc.). Nodes that do not implement this interface are skipped by the walker.
  • Implementations: ~20+ concrete node types: NodeAbstractResourceInstance, NodeApplyableResourceInstance, NodeDestroyableResourceInstance, NodeApplyableOutput, NodeDestroyableOutput, NodeModuleExpand, nodeExpandApplyableModuleVariable, NodeApplyableProvider, NodeCloseProvider, etc.
  • Design quality: Exemplary ISP adherence — a single-method interface that orthogonally composes with the ~15 other behavioral mix-in interfaces used by the same node types. The pattern allows the walker to check Execute capability separately from schema-attachment capability, reference capability, provider-consumer capability, etc.

terraform.GraphTransformer#

  • Package: internal/terraform
  • File: internal/terraform/transform.go:15
  • Methods: Transform(*Graph) error
  • Purpose: A single step in the graph build pipeline. Each GraphTransformer receives the entire graph and may add vertices, add edges, remove vertices, or validate structure. BasicGraphBuilder runs a slice of these sequentially to construct operation-specific graphs.
  • Implementations: 30+ concrete transformers: TransitiveReductionTransformer, ReferenceTransformer, ProviderTransformer, OrphanResourceInstanceTransformer, ModuleExpansionTransformer, TargetsTransformer, CountBoundaryTransformer, etc.
  • Companion: GraphVertexTransformer — a narrower variant for per-vertex replacement, Transform(dag.Vertex) (dag.Vertex, error).
  • Design quality: Pipeline/chain-of-responsibility pattern via a minimal interface. Composability is high — new transforms can be added without modifying existing ones. The GraphTransformMulti combinator function shows the pattern is self-aware.

terraform.Hook#

  • Package: internal/terraform
  • File: internal/terraform/hook.go:56
  • Methods (~20): PreApply(), PostApply(), PreDiff(), PostDiff(), PreProvisionInstance(), PostProvisionInstance(), PreProvisionInstanceStep(), PostProvisionInstanceStep(), ProvisionOutput(), PreRefresh(), PostRefresh(), PreImportState(), PostImportState(), PrePlanImport(), PostPlanImport(), PreApplyImport(), PostApplyImport(), and action hooks
  • Purpose: Observer interface for Terraform lifecycle events. Used by the CLI to drive progress output (the UiHook) and by the test framework. Each hook method returns (HookAction, error) — returning HookActionHalt cancels the in-progress operation.
  • Implementations: UiHook (CLI progress), NilHook (no-op base for embedding), CountHook and StateHook (internal state tracking during apply), test mocks.
  • Design quality: The NilHook embedding pattern is idiomatic for large observer interfaces — implementors embed NilHook and override only the methods they care about. The HookAction return type giving the observer the ability to halt execution is unusual and powerful.

statemgr.Full (composed interface)#

  • Package: internal/states/statemgr
  • File: internal/states/statemgr/statemgr.go:26
  • Composed from: Storage (= Transient + Persistent) + Locker
    • Transient: WriteState(*states.State), State() *states.State
    • Persistent: Refresher (RefreshState() error) + Persister (PersistState(*schemarepo.Schemas) error) + OutputReader (GetRootOutputValues())
    • Locker: Lock(info *LockInfo) (string, error), Unlock(id string) error
  • Purpose: The full state manager contract returned by backend.Backend.StateMgr(). Separates in-memory transient state (fast reads/writes within one Terraform run) from persistent storage (shared across processes) with an optional distributed lock.
  • Implementations: remote.State (wraps a remote.Client), filesystem.Filesystem, inmem.State, plus one per remote backend (S3, GCS, etc.)
  • Design quality: Excellent example of interface composition. Each sub-interface is independently useful: Locker is checked separately (if locker, ok := stateMgr.(statemgr.Locker); ok), OutputReader is a refinement allowing special permissions for reading outputs vs. the full state. The fine-grained composition enables progressive enhancement without forcing all implementations to provide all capabilities.

Interface patterns#

  • Size distribution: Bimodal. Core protocol interfaces (providers.Interface, EvalContext) are intentionally broad (30–35 methods) because they represent complete wire protocols or service locators. Behavioral mix-in interfaces for graph nodes are single- or two-method (GraphNodeExecutable, GraphTransformer, GraphNodeDynamicExpandable, GraphNodeReferenceable). No medium-size interfaces — the design makes a deliberate choice between “complete protocol” and “single capability”.

  • Embedding: Used extensively for composition:

    • backendrun.OperationsBackend embeds backend.Backend
    • statemgr.Full composes five interfaces via two layers of embedding
    • statemgr.Storage = Transient + Persistent
    • NilHook is designed to be embedded by partial Hook implementors
  • Implicit satisfaction: Mixed. Provider interfaces (providers.Interface) are defined by the framework (consumer side), and provider binary implementations satisfy them at runtime via gRPC. Graph-node mix-in interfaces are defined by transformers (also consumer side) — nodes silently gain capabilities by implementing matching method sets. The statemgr interfaces are defined by the framework and satisfied by backends.

  • stdlib interfaces used: Minimal. error is used in Stop() error and Close() error. context.Context appears in EvalContext.StopCtx() and OperationsBackend.Operation(). io.Reader/io.Writer are not primary abstractions here — the codebase uses custom UIOutput/UIInput interfaces instead.


Key abstractions#

  1. providers.Interface — The most load-bearing interface in the entire codebase. Every interaction with a cloud provider flows through this 35-method contract. The dual gRPC protocol implementations (Protocol 5 and 6) both satisfy it, and the mock implementation enables comprehensive integration testing without real cloud credentials. This interface is the Terraform provider protocol.

  2. terraform.GraphNodeExecutable — The most architecturally elegant interface: a single method that separates “what a node does” from “what structural roles a node plays”. The ~20 orthogonal behavioral mix-in interfaces (GraphNodeProvider, GraphNodeReferenceable, GraphNodeDynamicExpandable, etc.) compose with it to describe a node’s full identity without inheritance.

  3. terraform.GraphTransformer — Enables the composable pipeline that builds operation-specific graphs. The ~30 implementations of this one-method interface are what make PlanGraphBuilder and ApplyGraphBuilder differ — they use different []GraphTransformer slices. Adding new graph behavior means adding a new GraphTransformer, not modifying existing ones.

  4. backend.Backend + backendrun.OperationsBackend — The clean two-level split defines who does what: Backend owns state, OperationsBackend owns execution. This is why nine remote-state backends (S3, GCS, etc.) can exist as separate Go modules with no dependency on operation execution code.

  5. statemgr.Full — The composed state manager interface shows ISP applied correctly at scale: five fine-grained interfaces compose into one aggregate that backends return, while callers that need only locking or only output-reading can type-assert to the narrower interface they require.


Interface-driven extensibility#

Terraform uses interfaces at two distinct scales of extensibility:

External extensibility (provider protocol): providers.Interface is the plugin contract. Third-party providers — AWS, GCP, Azure, Kubernetes, and thousands of community providers — implement this contract as out-of-process gRPC servers. They never import Terraform’s internal/ packages; the interface is expressed purely as a gRPC wire protocol via tfplugin5.proto / tfplugin6.proto. The grpcwrap package translates between the Go interface and the gRPC protocol in both directions.

Internal extensibility (graph node capabilities): The ~15 behavioral mix-in interfaces in internal/terraform/transform_*.go make the graph build pipeline open for extension. Each GraphTransformer inspects the graph vertices via type assertions against these interfaces and connects or decorates nodes accordingly. Adding a new resource capability means:

  1. Define a new GraphNodeXxx interface (1 method)
  2. Implement it on relevant node types
  3. Write a GraphTransformer that acts on nodes satisfying that interface

The two-level approach (gRPC for external plugins, in-process interfaces for internal graph nodes) gives Terraform complete isolation from provider code while keeping internal composition efficient and testable.