tfpluginschema

package module
v0.10.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Apr 20, 2026 License: MPL-2.0 Imports: 25 Imported by: 2

README

tfpluginschema

A Go library for downloading and retrieving schemas from Terraform/OpenTofu providers using the Terraform Plugin Protocol (v5 and v6).

Overview

tfpluginschema provides a unified interface to interact with Terraform provider plugins, supporting both protocol v5 and v6. It can automatically download providers from the OpenTofu registry, extract them, and retrieve their schemas including provider configuration, resources, data sources, and functions.

Features

  • Multi-protocol support: Works with both Terraform Plugin Protocol v5 and v6
  • Automatic provider download: Downloads and extracts providers from the OpenTofu registry
  • Schema retrieval: Get complete schemas or individual resource/data source/function/ephemeral schemas
  • Caching: Built-in caching for both downloads and schemas
  • Cross-platform: Supports multiple operating systems and architectures

Installation

go get github.com/matt-FFFFFF/tfpluginschema

Quick Start

package main

import (
    "fmt"
    "log"

    "github.com/matt-FFFFFF/tfpluginschema"
)

func main() {
    // Create a new server instance
    server := tfpluginschema.NewServer(nil)
    defer server.Cleanup()

    // Define a provider request
    request := tfpluginschema.Request{
        Namespace: "hashicorp",
        Name:      "azurerm",
        Version:   "4.36.0",
    }

    // Download the provider (optional - automatically done when getting schema)
    if err := server.Get(request); err != nil {
        log.Fatal(err)
    }

    // Get the complete provider schema
    schema, err := server.GetProviderSchema(request)
    if err != nil {
        log.Fatal(err)
    }

    fmt.Println(string(schema))
}

Exported Types

Request

Represents a provider request with namespace, name, and version information.

type Request struct {
    Namespace string // Provider namespace (e.g., "Azure")
    Name      string // Provider name (e.g., "azapi")
    Version   string // Provider version (e.g., "2.5.0")
}

Methods:

  • String() string - Returns the OpenTofu registry download URL for the provider
Server

The main server struct that manages provider downloads and schema caching.

type Server struct {
    // private fields
}

Constructor:

  • NewServer(l *slog.Logger) *Server - Creates a new server instance with optional logger

Methods:

  • Get(request Request) error - Downloads and extracts the specified provider
  • GetResourceSchema(request Request, resource string) ([]byte, error) - Retrieves schema for a specific resource
  • GetDataSourceSchema(request Request, dataSource string) ([]byte, error) - Retrieves schema for a specific data source
  • GetFunctionSchema(request Request, function string) ([]byte, error) - Retrieves schema for a specific function
  • GetEphemeralResourceSchema(request Request, resource string) ([]byte, error) - Retrieves schema for an ephemeral resource
  • GetProviderSchema(request Request) ([]byte, error) - Retrieves the complete provider schema
  • Cleanup() - Removes temporary directories and cleans up resources

Usage Examples

Getting a Resource Schema
server := tfpluginschema.NewServer(nil)
defer server.Cleanup()

request := tfpluginschema.Request{
    Namespace: "hashicorp",
    Name:      "aws",
    Version:   "5.0.0",
}

// Get schema for aws_instance resource
resourceSchema, err := server.GetResourceSchema(request, "aws_instance")
if err != nil {
    log.Fatal(err)
}

fmt.Println(string(resourceSchema))
Getting a Data Source Schema
server := tfpluginschema.NewServer(nil)
defer server.Cleanup()

request := tfpluginschema.Request{
    Namespace: "hashicorp",
    Name:      "aws",
    Version:   "5.0.0",
}

// Get schema for aws_ami data source
dataSourceSchema, err := server.GetDataSourceSchema(request, "aws_ami")
if err != nil {
    log.Fatal(err)
}

fmt.Println(string(dataSourceSchema))
Getting a Function Schema
server := tfpluginschema.NewServer(nil)
defer server.Cleanup()

request := tfpluginschema.Request{
    Namespace: "hashicorp",
    Name:      "aws",
    Version:   "5.0.0",
}

// Get schema for a provider function (if available)
functionSchema, err := server.GetFunctionSchema(request, "some_function")
if err != nil {
    log.Fatal(err)
}

fmt.Println(string(functionSchema))
Custom Logging
import "log/slog"

// Create a custom logger
logger := slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{
    Level: slog.LevelDebug,
}))

server := tfpluginschema.NewServer(logger)
defer server.Cleanup()

// Server will now use custom logger for all operations

CLI

tfpluginschema --ns <namespace> -n <name> \
  [--version-constraint VERSION] [--registry opentofu|terraform] \
  <command>

Global flags:

Flag Alias Description
--namespace --ns Provider namespace (required).
--name -n Provider name (required).
--version-constraint --vc Concrete version or constraint. Empty = latest.
--registry -r opentofu (default) or terraform.
--cache-dir Cache directory. Overrides $TFPLUGINSCHEMA_CACHE_DIR.
--force-fetch Always re-download.
--quiet Suppress cache hit: / downloading: status on stderr.

Commands:

Command Description
provider schema Provider configuration schema as JSON.
resource list Newline-separated resource type names.
resource schema [name] Full schema for one resource, or all.
datasource list Newline-separated data source names.
datasource schema [name] Full schema for one data source, or all.
function list Newline-separated function names.
function schema [name] Full schema for one function, or all.
ephemeral list Newline-separated ephemeral resource names.
ephemeral schema [name] Full schema for one ephemeral resource, or all.
version list All versions the registry advertises.
Examples
# List versions (OpenTofu registry by default).
tfpluginschema --ns hashicorp -n aws version list

# Provider configuration schema, pinned version.
tfpluginschema --ns hashicorp -n aws --vc 5.0.0 provider schema

# Just the resource type names for the latest version.
tfpluginschema --ns hashicorp -n aws resource list

# Schema for one resource.
tfpluginschema --ns hashicorp -n aws --vc 5.0.0 resource schema aws_instance

# Schema for one data source.
tfpluginschema --ns hashicorp -n aws --vc 5.0.0 datasource schema aws_ami

# Use the HashiCorp registry.
tfpluginschema -r terraform --ns Azure -n azapi resource list

# Dump every resource schema at once.
tfpluginschema --ns hashicorp -n aws --vc 5.0.0 resource schema

Architecture

The library consists of several key components:

  1. Server: Main orchestrator that handles downloads, caching, and schema retrieval
  2. RPC Client: Handles communication with provider plugins using gRPC
  3. Protocol Support: Supports both Terraform Plugin Protocol v5 and v6
  4. Schema Processing: Automatically decodes base64-encoded type information
  5. Caching: In-memory caching of both downloaded providers and retrieved schemas

Protocol Support

The library automatically detects and supports both Terraform Plugin Protocol versions:

  • Protocol v5: Legacy protocol used by older providers
  • Protocol v6: Current protocol with enhanced features

The universal client interface abstracts away the protocol differences, providing a consistent API regardless of the underlying protocol version.

Caching

The library implements three levels of caching:

  1. Persistent on-disk provider cache: Downloaded provider binaries are stored on disk in a predictable layout and reused across runs.
  2. In-memory download cache: Prevents redundant work within a single Server instance.
  3. Schema cache: Caches retrieved schemas to avoid repeated RPC calls.

The on-disk cache is preserved across runs. The in-memory caches are scoped to the lifetime of a Server instance.

Provider cache layout

Downloaded providers are extracted into a registry-qualified, namespaced path:

<cacheDir>/<registry-type>/<namespace>/terraform-provider-<name>/<version>/<os>_<arch>/

Where <registry-type> is opentofu or terraform (from Request.RegistryType). Including the registry type and namespace avoids collisions between providers with the same name and version published by different namespaces or registries.

The default <cacheDir> is os.UserCacheDir()/tfpluginschema (for example ~/.cache/tfpluginschema on Linux). It can be overridden with:

  • The TFPLUGINSCHEMA_CACHE_DIR environment variable.
  • The --cache-dir CLI flag.
  • The tfpluginschema.WithCacheDir("/path") option to NewServer.
Bypassing the cache

To always re-download providers, use:

  • The --force-fetch CLI flag.
  • The tfpluginschema.WithForceFetch(true) option passed to NewServer.

Current public API notes:

  • NewServer(l *slog.Logger, opts ...ServerOption) *Server accepts a logger and zero or more ServerOption values.
  • Request includes RegistryType in addition to provider-identifying fields such as namespace, name, and version.
Observing cache hits / misses

The CLI prints cache hit: or downloading: messages to stderr for each request. Pass --quiet to suppress them. Library users can register a callback to observe the same signal:

server := tfpluginschema.NewServer(nil,
    tfpluginschema.WithCacheStatusFunc(func(req tfpluginschema.Request, status tfpluginschema.CacheStatus) {
        log.Printf("%s: %s/%s %s", status, req.Namespace, req.Name, req.Version)
    }),
)

Error Handling

The library defines specific error types for different failure scenarios:

  • ErrPluginNotFound: Provider not found in registry
  • ErrPluginApi: API communication errors
  • ErrNotImplemented: Unimplemented functionality

Dependencies

  • github.com/hashicorp/go-plugin - Plugin framework
  • github.com/hashicorp/go-hclog - Logging
  • google.golang.org/grpc - gRPC communication
  • google.golang.org/protobuf - Protocol buffer support

License

This project follows the same license as specified in the source code.

Contributing

Contributions are welcome! Please ensure that:

  1. All exported types and methods are properly documented
  2. Tests are included for new functionality
  3. Code follows Go best practices and conventions
  4. Integration tests pass with real providers

Notes

  • The library uses the OpenTofu registry (https://registry.opentofu.org) by default
  • Temporary files and legacy temporary directories are cleaned up when Server.Cleanup() is called
  • The library handles cross-platform provider downloads automatically
  • Base64-encoded type information in schemas is automatically decoded for easier consumption

Acknowledgements

Thanks to the OpenTofu community for their contributions and the maintainers of the OpenTofu plugin protocol. This library builds upon their work to provide a seamless experience for Go developers working with Terraform and OpenTofu providers.

Documentation

Index

Examples

Constants

View Source
const EnvCacheDir = "TFPLUGINSCHEMA_CACHE_DIR"

EnvCacheDir is the environment variable used to override the provider cache directory. When set (and non-empty), its value is used as the root of the provider cache instead of the default.

Variables

View Source
var (
	ErrPluginNotFound = fmt.Errorf("plugin not found")
	ErrPluginApi      = fmt.Errorf("plugin API error")
)
View Source
var (
	// ErrNotImplemented is returned when a method is not implemented
	ErrNotImplemented = errors.New("not implemented")
)

Functions

func GetLatestVersionMatch added in v0.5.0

func GetLatestVersionMatch(versions goversion.Collection, constraints goversion.Constraints) (*goversion.Version, error)

GetLatestVersionMatch returns the latest version from the provided collection that matches the given constraints. The versions collection must be sorted in ascending order. If no versions match the constraints, an error is returned. If the constraints are nil or empty, the latest version is returned.

Types

type CacheStatus added in v0.9.0

type CacheStatus int

CacheStatus indicates whether a provider was served from the local cache or had to be downloaded from the registry.

const (
	// CacheStatusMiss indicates the provider was not in the cache and was
	// downloaded from the registry.
	CacheStatusMiss CacheStatus = iota
	// CacheStatusHit indicates the provider was served from the local cache.
	CacheStatusHit
)

func (CacheStatus) String added in v0.9.0

func (c CacheStatus) String() string

String returns a human-readable form of the CacheStatus.

type CacheStatusFunc added in v0.9.0

type CacheStatusFunc func(request Request, status CacheStatus)

CacheStatusFunc is invoked by the Server after resolving a provider request to report whether the provider binary was found in the local cache (CacheStatusHit) or was not in the cache and a download was attempted (CacheStatusMiss). The callback fires after the download/extract attempt completes and after the Server's internal lock has been released, so a miss does not imply the download was successful — it indicates only that the cache did not satisfy the request and a fetch was attempted. The request passed in has a concrete (fixed) version.

type ContextKey added in v0.2.0

type ContextKey struct{}

ContextKey is a type used to store the server instance in the context.

type RegistryType added in v0.7.0

type RegistryType string

RegistryType represents the type of provider registry to use.

const (
	// RegistryTypeOpenTofu represents the OpenTofu registry (default).
	RegistryTypeOpenTofu RegistryType = "opentofu"
	// RegistryTypeTerraform represents the Terraform registry.
	RegistryTypeTerraform RegistryType = "terraform"
)

func (RegistryType) BaseURL added in v0.7.0

func (r RegistryType) BaseURL() string

BaseURL returns the base URL for the registry API. It defaults to OpenTofu registry for empty or unknown registry types.

type Request

type Request struct {
	Namespace    string       // Namespace of the provider (e.g., "Azure")
	Name         string       // Name of the provider (e.g., "azapi")
	Version      string       // Version of the provider (e.g., "2.5.0") or constraint (e.g., ">=1.0.0", "~>2.1")
	RegistryType RegistryType // Registry to use (defaults to OpenTofu if not specified)
}

Request is a request structure used to specify the details of a plugin so that it can be downloaded. Note that the request fields are case-sensitive.

func (Request) String

func (r Request) String() string

String returns a string representation of the Request in the format: "https://{registry}/v1/providers/{namespace}/{name}/{version}/download/{os}/{arch}" where {registry} is either registry.opentofu.org (default) or registry.terraform.io. This format is used to construct the URL for downloading the plugin. Note: String is a best-effort representation. Server.Get validates the request's components before constructing the URL, so callers using the public Server API do not need to pre-validate Request fields.

type Server

type Server struct {
	// contains filtered or unexported fields
}

Server is a struct that manages the plugin download and caching process.

func NewServer

func NewServer(l *slog.Logger, opts ...ServerOption) *Server

NewServer creates a new Server instance with an optional logger and zero or more ServerOption values for customization (cache directory, force fetch, cache-status callback, ...). If no logger is provided, it defaults to a logger that discards all logs.

Example

ExampleNewServer demonstrates how to create a new server instance, download a provider, and retrieve its schema. It uses the Azure azapi provider as an example.

package main

import (
	"fmt"
	"slices"

	"github.com/matt-FFFFFF/tfpluginschema"
)

func main() {
	s := tfpluginschema.NewServer(nil)
	defer s.Cleanup()
	request := tfpluginschema.Request{
		Namespace: "Azure",
		Name:      "azapi",
		Version:   "2.5.0",
	}

	provSchema, err := s.GetProviderSchema(request)
	if err != nil {
		panic(err)
	}

	attrs := make([]string, 0, len(provSchema.Block.Attributes))
	for name := range provSchema.Block.Attributes {
		attrs = append(attrs, name)
	}
	slices.Sort(attrs)
	for _, name := range attrs {
		fmt.Printf("Attribute: %s\n", name)
	}

}
Output:
Attribute: auxiliary_tenant_ids
Attribute: client_certificate
Attribute: client_certificate_password
Attribute: client_certificate_path
Attribute: client_id
Attribute: client_id_file_path
Attribute: client_secret
Attribute: client_secret_file_path
Attribute: custom_correlation_request_id
Attribute: default_location
Attribute: default_name
Attribute: default_tags
Attribute: disable_correlation_request_id
Attribute: disable_default_output
Attribute: disable_terraform_partner_id
Attribute: enable_preflight
Attribute: endpoint
Attribute: environment
Attribute: ignore_no_op_changes
Attribute: maximum_busy_retry_attempts
Attribute: oidc_azure_service_connection_id
Attribute: oidc_request_token
Attribute: oidc_request_url
Attribute: oidc_token
Attribute: oidc_token_file_path
Attribute: partner_id
Attribute: skip_provider_registration
Attribute: subscription_id
Attribute: tenant_id
Attribute: use_aks_workload_identity
Attribute: use_cli
Attribute: use_msi
Attribute: use_oidc
Example (TerraformRegistry)

ExampleNewServer_terraformRegistry demonstrates how to use the Terraform registry instead of the default OpenTofu registry. It uses the HashiCorp random provider as an example.

package main

import (
	"fmt"
	"slices"

	"github.com/matt-FFFFFF/tfpluginschema"
)

func main() {
	s := tfpluginschema.NewServer(nil)
	defer s.Cleanup()
	request := tfpluginschema.Request{
		Namespace:    "Azure",
		Name:         "azapi",
		Version:      "2.5.0",
		RegistryType: tfpluginschema.RegistryTypeTerraform,
	}

	provSchema, err := s.GetProviderSchema(request)
	if err != nil {
		panic(err)
	}

	attrs := make([]string, 0, len(provSchema.Block.Attributes))
	for name := range provSchema.Block.Attributes {
		attrs = append(attrs, name)
	}
	slices.Sort(attrs)
	for _, name := range attrs {
		fmt.Printf("Attribute: %s\n", name)
	}

}
Output:
Attribute: auxiliary_tenant_ids
Attribute: client_certificate
Attribute: client_certificate_password
Attribute: client_certificate_path
Attribute: client_id
Attribute: client_id_file_path
Attribute: client_secret
Attribute: client_secret_file_path
Attribute: custom_correlation_request_id
Attribute: default_location
Attribute: default_name
Attribute: default_tags
Attribute: disable_correlation_request_id
Attribute: disable_default_output
Attribute: disable_terraform_partner_id
Attribute: enable_preflight
Attribute: endpoint
Attribute: environment
Attribute: ignore_no_op_changes
Attribute: maximum_busy_retry_attempts
Attribute: oidc_azure_service_connection_id
Attribute: oidc_request_token
Attribute: oidc_request_url
Attribute: oidc_token
Attribute: oidc_token_file_path
Attribute: partner_id
Attribute: skip_provider_registration
Attribute: subscription_id
Attribute: tenant_id
Attribute: use_aks_workload_identity
Attribute: use_cli
Attribute: use_msi
Attribute: use_oidc

func (*Server) CacheDir added in v0.9.0

func (s *Server) CacheDir() string

CacheDir returns the root directory used by the Server to cache downloaded providers.

func (*Server) Cleanup

func (s *Server) Cleanup()

Cleanup removes the Server's in-memory state and any legacy temporary directory used for plugin downloads.

func (*Server) Get

func (s *Server) Get(request Request) error

Get retrieves the plugin for the specified request, downloading it if necessary. The GetXxx methods (GetResourceSchema, GetDataSourceSchema, etc.) will call this method anyway, so it is not necessary to call Get directly unless you want to ensure the plugin is downloaded first.

Providers are extracted into a predictable on-disk cache (see CacheDir and the TFPLUGINSCHEMA_CACHE_DIR environment variable). Subsequent calls for the same provider/version/os/arch are served from the cache. Pass WithForceFetch(true) to NewServer to bypass the cache and always download. Cleanup() removes only the Server's in-memory state and any legacy temp directory; the persistent cache is preserved across runs.

func (*Server) GetAvailableVersions added in v0.5.0

func (s *Server) GetAvailableVersions(req VersionsRequest) (goversion.Collection, error)

GetAvailableVersions fetches the available versions for the given provider from the plugin registry. It caches the results to avoid redundant network calls. It returns a sorted collection of versions.

func (*Server) GetDataSourceSchema

func (s *Server) GetDataSourceSchema(request Request, dataSource string) (*tfjson.Schema, error)

GetDataSourceSchema retrieves the schema for a specific data source from the provider.

func (*Server) GetEphemeralResourceSchema added in v0.2.0

func (s *Server) GetEphemeralResourceSchema(request Request, ephemeralResource string) (*tfjson.Schema, error)

GetEphemeralResourceSchema retrieves the schema for a specific ephemeral resource from the provider.

func (*Server) GetFunctionSchema

func (s *Server) GetFunctionSchema(request Request, function string) (*tfjson.FunctionSignature, error)

GetFunctionSchema retrieves the schema for a specific function from the provider.

func (*Server) GetProviderSchema added in v0.3.0

func (s *Server) GetProviderSchema(request Request) (*tfjson.Schema, error)

GetProviderSchema retrieves the schema for the provider configuration.

func (*Server) GetResourceSchema

func (s *Server) GetResourceSchema(request Request, resource string) (*tfjson.Schema, error)

GetResourceSchema retrieves the schema for a specific resource from the provider.

func (*Server) ListDataSources added in v0.4.0

func (s *Server) ListDataSources(request Request) ([]string, error)

ListDataSources retrieves the list of data source names from the provider.

func (*Server) ListEphemeralResources added in v0.4.0

func (s *Server) ListEphemeralResources(request Request) ([]string, error)

ListEphemeralResources retrieves the list of ephemeral resource names from the provider.

func (*Server) ListFunctions added in v0.4.0

func (s *Server) ListFunctions(request Request) ([]string, error)

ListFunctions retrieves the list of function names from the provider.

func (*Server) ListResources added in v0.4.0

func (s *Server) ListResources(request Request) ([]string, error)

ListResources retrieves the list of resource names from the provider.

type ServerOption added in v0.9.0

type ServerOption func(*Server)

ServerOption configures a Server at construction time.

func WithCacheDir added in v0.9.0

func WithCacheDir(dir string) ServerOption

WithCacheDir overrides the provider cache directory used by the Server. An empty dir is ignored and the default is used instead.

func WithCacheStatusFunc added in v0.9.0

func WithCacheStatusFunc(fn CacheStatusFunc) ServerOption

WithCacheStatusFunc installs a callback invoked after the Server resolves a provider to indicate whether the cache was hit or the provider was downloaded. Useful for CLIs wishing to report download/cache activity.

func WithForceFetch added in v0.9.0

func WithForceFetch(force bool) ServerOption

WithForceFetch configures the Server to always re-download providers, bypassing any existing cache entries. Downloads still populate the cache.

func WithHTTPClient added in v0.9.0

func WithHTTPClient(c *http.Client) ServerOption

WithHTTPClient overrides the HTTP client used by the Server for registry and download requests. Useful for setting timeouts, custom transports, or for testing via httptest. A nil client is ignored.

type VersionsRequest added in v0.5.0

type VersionsRequest struct {
	Namespace    string       // Namespace of the provider (e.g., "hashicorp")
	Name         string       // Name of the provider (e.g., "aws")
	RegistryType RegistryType // Registry to use (defaults to OpenTofu if not specified)
}

func (VersionsRequest) String added in v0.5.0

func (v VersionsRequest) String() string

String returns a best-effort URL for the versions endpoint. It does not panic on invalid input; callers using the public Server API go through validateVersionsRequest, which rejects unsafe namespace/name values before a URL is ever constructed.

Directories

Path Synopsis
cmd
tfpluginschema command

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL