// Copyright IBM Corp. 2014, 2026 // SPDX-License-Identifier: BUSL-1.1 package rpcapi import ( "context" "fmt" "net" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" "google.golang.org/grpc/test/bufconn" "github.com/hashicorp/terraform/internal/rpcapi/terraform1/dependencies" "github.com/hashicorp/terraform/internal/rpcapi/terraform1/packages" "github.com/hashicorp/terraform/internal/rpcapi/terraform1/setup" "github.com/hashicorp/terraform/internal/rpcapi/terraform1/stacks" ) // Client is a client for the RPC API. // // This just wraps a raw gRPC client connection and provides a more convenient // API to access its services. type Client struct { // conn should be a connection to a server that has already completed // the Setup.Handshake call. conn *grpc.ClientConn // serverCaps should be from the result of the Setup.Handshake call // previously made to the server that conn is connected to. serverCaps *setup.ServerCapabilities close func(context.Context) error } // NewInternalClient returns a client for the RPC API that uses in-memory // buffers to allow callers within the same Terraform CLI process to access // the RPC API without any sockets or child processes. // // This is intended for exposing Terraform Core functionality through Terraform // CLI, to establish an explicit interface between those two sides without // the overhead of forking a child process containing exactly the same code. // // Callers should call the Close method of the returned client once they are // done using it, or else they will leak goroutines. func NewInternalClient(ctx context.Context, clientCaps *setup.ClientCapabilities) (*Client, error) { fakeListener := bufconn.Listen(4 * 1024 * 1024 /* buffer size */) srv := grpc.NewServer() registerGRPCServices(srv, &serviceOpts{}) go func() { if err := srv.Serve(fakeListener); err != nil { // We can't actually return an error here, but this should // not arise with our fake listener anyway so we'll just panic. panic(err) } }() fakeDialer := func(ctx context.Context, fakeAddr string) (net.Conn, error) { return fakeListener.DialContext(ctx) } clientConn, err := grpc.DialContext( ctx, "testfake", grpc.WithContextDialer(fakeDialer), grpc.WithTransportCredentials(insecure.NewCredentials()), ) if err != nil { return nil, fmt.Errorf("failed to connect to RPC API: %w", err) } // We perform the setup step on the caller's behalf, so that they can // immediately use the main services. (The caller would otherwise need // to do this immediately on return anyway, or the result would be // useless.) setupClient := setup.NewSetupClient(clientConn) setupResp, err := setupClient.Handshake(ctx, &setup.Handshake_Request{ Capabilities: clientCaps, }) if err != nil { return nil, fmt.Errorf("setup failed: %w", err) } var client *Client client = &Client{ conn: clientConn, serverCaps: setupResp.Capabilities, close: func(ctx context.Context) error { clientConn.Close() srv.Stop() fakeListener.Close() client.conn = nil client.serverCaps = nil client.close = func(context.Context) error { return nil } return nil }, } return client, nil } // Close frees the internal buffers and terminates the goroutines that handle // the internal RPC API connection. // // Any service clients previously returned by other methods become invalid // as soon as this method is called, and must not be used any further. func (c *Client) Close(ctx context.Context) error { return c.close(ctx) } // ServerCapabilities returns the server's response to capability negotiation. // // Callers must not modify anything reachable through the returned pointer. func (c *Client) ServerCapabilities() *setup.ServerCapabilities { return c.serverCaps } // Dependencies returns a client for the Dependencies service of the RPC API. func (c *Client) Dependencies() dependencies.DependenciesClient { return dependencies.NewDependenciesClient(c.conn) } // Packages returns a client for the Packages service of the RPC API. func (c *Client) Packages() packages.PackagesClient { return packages.NewPackagesClient(c.conn) } // Stacks returns a client for the Stacks service of the RPC API. func (c *Client) Stacks() stacks.StacksClient { return stacks.NewStacksClient(c.conn) }