terraform/internal/rpcapi
Daniel Schmidt c3cc222a1f
Some checks failed
build / Determine intended Terraform version (push) Has been cancelled
build / Determine Go toolchain version (push) Has been cancelled
Quick Checks / Unit Tests (push) Has been cancelled
Quick Checks / Race Tests (push) Has been cancelled
Quick Checks / End-to-end Tests (push) Has been cancelled
Quick Checks / Code Consistency Checks (push) Has been cancelled
build / Generate release metadata (push) Has been cancelled
build / Build for freebsd_386 (push) Has been cancelled
build / Build for linux_386 (push) Has been cancelled
build / Build for openbsd_386 (push) Has been cancelled
build / Build for windows_386 (push) Has been cancelled
build / Build for darwin_amd64 (push) Has been cancelled
build / Build for freebsd_amd64 (push) Has been cancelled
build / Build for linux_amd64 (push) Has been cancelled
build / Build for openbsd_amd64 (push) Has been cancelled
build / Build for solaris_amd64 (push) Has been cancelled
build / Build for windows_amd64 (push) Has been cancelled
build / Build for freebsd_arm (push) Has been cancelled
build / Build for linux_arm (push) Has been cancelled
build / Build for darwin_arm64 (push) Has been cancelled
build / Build for linux_arm64 (push) Has been cancelled
build / Build for windows_arm64 (push) Has been cancelled
build / Build Docker image for linux_386 (push) Has been cancelled
build / Build Docker image for linux_amd64 (push) Has been cancelled
build / Build Docker image for linux_arm (push) Has been cancelled
build / Build Docker image for linux_arm64 (push) Has been cancelled
build / Build e2etest for linux_386 (push) Has been cancelled
build / Build e2etest for windows_386 (push) Has been cancelled
build / Build e2etest for darwin_amd64 (push) Has been cancelled
build / Build e2etest for linux_amd64 (push) Has been cancelled
build / Build e2etest for windows_amd64 (push) Has been cancelled
build / Build e2etest for linux_arm (push) Has been cancelled
build / Build e2etest for darwin_arm64 (push) Has been cancelled
build / Build e2etest for linux_arm64 (push) Has been cancelled
build / Run e2e test for linux_386 (push) Has been cancelled
build / Run e2e test for windows_386 (push) Has been cancelled
build / Run e2e test for darwin_amd64 (push) Has been cancelled
build / Run e2e test for linux_amd64 (push) Has been cancelled
build / Run e2e test for windows_amd64 (push) Has been cancelled
build / Run e2e test for linux_arm (push) Has been cancelled
build / Run e2e test for linux_arm64 (push) Has been cancelled
build / Run terraform-exec test for linux amd64 (push) Has been cancelled
separate tests between different modes
2026-01-30 16:13:59 +01:00
..
dynrpcserver Upgrade protoc and protoc-gen-go-grpc versions to matching terraform-plugin-go (#37647) 2025-10-22 14:46:18 +01:00
terraform1 Upgrade protoc and protoc-gen-go-grpc versions to matching terraform-plugin-go (#37647) 2025-10-22 14:46:18 +01:00
testdata stacks: ensure an empty plan produces a planned status 2026-01-30 16:13:59 +01:00
cli.go Rebrand Terraform Cloud to HCP Terraform 2024-04-23 16:12:59 -04:00
convert.go stacks: split the terraform1 RPC package into per-service packages (#35513) 2024-08-07 17:33:51 +02:00
convert_test.go rpcapi: Add HashiCorp copyright comments 2023-11-15 12:38:55 -08:00
dependencies.go Address go vet 1.24 failures (#36485) 2025-02-12 16:51:53 +00:00
dependencies_provider_schema.go Rename schema.Block to Body (#36629) 2025-03-04 16:33:43 +01:00
dependencies_test.go stacks: split the terraform1 RPC package into per-service packages (#35513) 2024-08-07 17:33:51 +02:00
grpc_helpers.go rpcapi: Add HashiCorp copyright comments 2023-11-15 12:38:55 -08:00
grpc_testing.go stacks: include moved and removed in API (#35360) 2024-06-20 11:04:22 +02:00
handles.go migrate command for terraform stacks (#36482) 2025-03-19 10:39:50 +01:00
internal_client.go stacks: split the terraform1 RPC package into per-service packages (#35513) 2024-08-07 17:33:51 +02:00
internal_client_test.go stacks: split the terraform1 RPC package into per-service packages (#35513) 2024-08-07 17:33:51 +02:00
packages.go stacks: split the terraform1 RPC package into per-service packages (#35513) 2024-08-07 17:33:51 +02:00
packages_test.go udpate code which was failing staticckeck 2024-09-11 14:09:12 -04:00
plugin.go migrate command for terraform stacks (#36482) 2025-03-19 10:39:50 +01:00
README.md rpcapi: A README summarizing the role and behavior of this package 2023-11-28 12:07:49 -08:00
resource_identity.go stacks: refactor plan, state, and removed tracking with tree structures for efficient lookups (#36850) 2025-04-16 14:05:51 +02:00
resource_identity_test.go stacks: add rpc call to list resource identities of a stack 2025-03-12 09:54:44 +01:00
server.go rpcapi: Allow stopping long-running operations 2024-05-27 15:44:06 -07:00
setup.go stacks: split the terraform1 RPC package into per-service packages (#35513) 2024-08-07 17:33:51 +02:00
setup_test.go stacks: split the terraform1 RPC package into per-service packages (#35513) 2024-08-07 17:33:51 +02:00
stacks.go stacks: add apply time progress message test case 2026-01-30 16:13:59 +01:00
stacks_grpc_client.go Add log noting remote operations that have started will continue if command is interrupted 2025-05-18 13:36:48 -04:00
stacks_grpc_client_test.go Upgrade protoc and protoc-gen-go-grpc versions to matching terraform-plugin-go (#37647) 2025-10-22 14:46:18 +01:00
stacks_inspector.go stacks: complete stack output implementation for plan and apply (#35723) 2024-09-16 11:36:36 +02:00
stacks_test.go separate tests between different modes 2026-01-30 16:13:59 +01:00
stopper.go rpcapi: Allow stopping long-running operations 2024-05-27 15:44:06 -07:00
telemetry.go rpcapi: Add HashiCorp copyright comments 2023-11-15 12:38:55 -08:00
telemetry_test.go Update for newer telemetry 2025-10-03 10:20:40 -04:00

Terraform Core RPC API

This directory contains package rpcapi, which is the main implementation code for the Terraform Core RPC API.

What follows here is documentation aimed at those who are maintaining or otherwise contributing to this code. This is not end-user-oriented documentation; information on how to use the RPC API as an external caller belongs elsewhere.

NOTE WELL! The RPC API is currently experimental, existing primarily as a vehicle for the Terraform Stacks private preview. It is subject to arbitrary breaking changes -- even in patch releases -- until the Terraform Stacks features are considered stable.

What is the RPC API?

The RPC API is an integration point for making use of some Terraform Core functionality within external software.

It's primarily aimed at entirely separate programs using its gRPC server configured using HashiCorp's go-plugin library, although it does also have an internal access point for use by the parts of this codebase that are architecturally part of Terraform CLI rather than Terraform Core, to help reinforce that architectural boundary despite them both currently living in the same codebase.

The relationship between this package's implementation of the RPC API and external callers of that API is mechanically similar to the relationship between the Terraform Plugin Framework/SDK and Terraform Core: it's a client/server protocol using gRPC as the transport, over a local socket.

The protocol buffers definition for the API lives in terraform1.proto, which when included in a tagged commit of this repository acts as the source of truth for a particular version of the API.

RPC API services

The RPC API exposes a few different services that each wrap different parts of Terraform Core's functionality. These are broad thematic groupings, but they are all part of the same API and in particular values returned by one service are often accepted as input to another.

  • Setup: This is a special service that's used only to prepare the other services for use by performing a negotiation handshake.

    Clients must always make exactly one call to Setup.Handshake before interacting with any other part of this API. That call acts as a capability negotiation which might therefore influence the behavior of other subequent calls as a measure of forward and backward compatibility.

  • Dependencies: Deals with some cross-cutting concerns related to dependencies such as remote source packages (e.g. external modules) and providers.

  • Stacks: Provides external access to the Terraform Stacks runtime, including planning and applying changes to the infrastructure described by a stack configuration.

API Object Handles

To allow passing live objects between different services and different functions within the same service, the RPC API uses handles, which are int64 values that each uniquely identify a live object of a particular type.

Handles are typically (but not always) created by RPC functions whose names start with the prefix Open, and are later closed by functions whose names start with Close.

Handles persist between calls to the same RPC API process, but are automatically discarded when that process shuts down. Depending on the handle type, this automatic discarding may or may not be equivalent to explicitly closing the handle, and so callers should typically explicitly close handles for objects they no longer intend to use.

Objects represented by handles can sometimes depend on other objects. In that case, it might be necessary to close one handle before closing another.

Internally, handles are represented as values of the handle generic type, which is parameterized by the type of the underlying object the handle is representing. This therefore allows a measure of type safety to help avoid mistakes like using the wrong kind of handle when calling a function.

In the wire protocol the handle type information is erased, and so when accepting handles from a client the service implementation must check that the given handle is of the expected type.

Currently handles are unique across objects of all types, but that's an implementation detail that clients are not allowed to rely on. If designing a service which can accept handles of multiple different types, always design it to accept each handle type as a separate request field, and never rely on the system's internal state about what type each handle has, so that we can give the best possible feedback to clients when they have their own bugs that cause mixups between different handle types.

Handshake Dynamic Initialization

In order to allow clients to dynamically negotiate capabilities at runtime, the server implementation of this API uses an extra indirection over the real service implementations that's implemented in the subdirectory dynrpcserver.

The service implementations registered with the gRPC server are actually instances of the wrapper stubs in that package. Initially those stubs are all wrapping nothing at all, and so all calls to the service functions will return errors.

During a Setup.Handshake call, the system finally instantiates the real service implementations that are implemented within this package directory. The exact details of what types are instantiated and how they are populated can vary based on the negotiated capabilities, allowing some flexibility in how we will handle requests based on those capabilities.

The dynrpcserver stubs are automatically generated by a go:generate directive in that package based on the protocol buffers definitions. Therefore each time we change the set of service functions or the request and response types for those functions we must first run make protobuf to regenerate the protocol buffers stubs, and then go generate ./internal/rpcapi/... to update the dynrpcserver stubs to match.

API Entry Points

The main entry point is rpcapi.CLICommandFactory, which returns a factory function intended for use with the github.com/mitchellh/cli module that Terraform CLI uses to route execution into its various subcommands.

Terraform CLI's package main binds the subcommand rpcapi directly to the factory returned by that function, thereby providing the smallest possible amount of Terraform CLI execution before reaching the RPC API. This is intentional to help reinforce that rpcapi is an alternative to using Terraform CLI, rather than part of Terraform CLI itself, despite the unavoidable use of some of its early entry-point code to get up and running.

When Terraform CLI itself needs to access Terraform Core functionality that's exposed by the RPC API, an alternative entry point is rpcapi.NewInternalClient. This function returns an object which provides access to gRPC clients just as would be used by an external caller accessing the API when using go-plugin, but arranges for its requests to be routed via local buffers in-process rather than using a socket.

The intent of this "internal client" is to reinforce the architectural boundary between Terraform CLI and Terraform Core despite them living in the same codebase. Commands that interact with the internal client could potentially be factored out into separate codebases in future with only minimal modification to use go-plugin to arrange access instead of using the internal client.

At the time of writing this documentation there is plenty of surface area in Terraform CLI that predates the RPC API which accesses Terraform Core functionality which itself predates the RPC API, and therefore those calls are made directly via normal function calls. It's fine to continue maintaining those callers and callees until there's a strong reason to update them, but most new functionality should be mediated through the RPC API.

In particular, the RPC API is the only public interface to the Terraform Stacks runtime, and so any Terraform CLI code which is orchestrating the Stacks runtime must access it through the RPC API internal client, and must not directly import anything under ./internal/stacks.