Appendix & Resources

6 minute read

Quick Reference

Provider Development Cheat Sheet

// Provider Structure
provider.go          // Provider implementation
provider_schema.go   // Provider schema
resource_*.go        // Resource implementations
datasource_*.go      // Data source implementations
client/             // API client
validators/         // Custom validators
planmodifiers/      // Custom plan modifiers

// Essential Imports
import (
    "context"
    "github.com/hashicorp/terraform-plugin-framework/provider"
    "github.com/hashicorp/terraform-plugin-framework/resource"
    "github.com/hashicorp/terraform-plugin-framework/datasource"
    "github.com/hashicorp/terraform-plugin-framework/types"
    "github.com/hashicorp/terraform-plugin-log/tflog"
)

// Schema Definition
schema.Schema{
    Attributes: map[string]schema.Attribute{
        "id": schema.StringAttribute{Computed: true},
        "name": schema.StringAttribute{Required: true},
        "optional": schema.StringAttribute{Optional: true},
    },
}

// CRUD Operations
func (r *Resource) Create(ctx, req, resp) { /* implementation */ }
func (r *Resource) Read(ctx, req, resp) { /* implementation */ }
func (r *Resource) Update(ctx, req, resp) { /* implementation */ }
func (r *Resource) Delete(ctx, req, resp) { /* implementation */ }

// Common Patterns
// Get plan/state
resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...)

// Set state
resp.Diagnostics.Append(resp.State.Set(ctx, &state)...)

// Add error
resp.Diagnostics.AddError("Title", "Details")

// Logging
tflog.Debug(ctx, "Message", map[string]interface{}{"key": "value"})

Common Commands

# Development
go mod init github.com/example/terraform-provider-NAME
go mod tidy
go generate ./...
go build
go test ./...
go test -v -run TestResourceName

# Acceptance Tests
TF_ACC=1 go test ./internal/provider/ -v -timeout 30m

# Local Installation
go build -o terraform-provider-NAME
mkdir -p ~/.terraform.d/plugins/registry.terraform.io/example/NAME/99.0.0/darwin_amd64/
cp terraform-provider-NAME ~/.terraform.d/plugins/.../

# Debugging
export TF_LOG=TRACE
export TF_LOG_PROVIDER=TRACE
export TF_LOG_PATH=terraform.log
go run main.go --debug

# Publishing
git tag v1.0.0
git push origin v1.0.0
goreleaser release --clean

Type System Reference

Framework Types

// String
types.StringValue("value")
types.StringNull()
types.StringUnknown()
plan.Name.ValueString()
plan.Name.IsNull()
plan.Name.IsUnknown()

// Number
types.Int64Value(42)
types.Float64Value(3.14)
plan.Count.ValueInt64()
plan.Price.ValueFloat64()

// Bool
types.BoolValue(true)
plan.Enabled.ValueBool()

// List
types.ListValue(types.StringType, []attr.Value{
    types.StringValue("item1"),
    types.StringValue("item2"),
})
var items []string
plan.Items.ElementsAs(ctx, &items, false)

// Set
types.SetValue(types.StringType, values)

// Map
types.MapValue(types.StringType, mapValues)
var m map[string]string
plan.Tags.ElementsAs(ctx, &m, false)

// Object
types.ObjectValue(attrTypes, attrValues)
types.ObjectValueFrom(ctx, attrTypes, structValue)

Type Conversions

// List to []string
var items []string
diags := plan.Items.ElementsAs(ctx, &items, false)

// Map to map[string]string
var tags map[string]string
diags := plan.Tags.ElementsAs(ctx, &tags, false)

// Struct to Object
objValue, diags := types.ObjectValueFrom(ctx, attrTypes, structValue)

// Object to Struct
var structValue MyStruct
diags := objValue.As(ctx, &structValue, basetypes.ObjectAsOptions{})

Validator Reference

Built-in Validators

import (
    "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
    "github.com/hashicorp/terraform-plugin-framework-validators/int64validator"
    "github.com/hashicorp/terraform-plugin-framework-validators/listvalidator"
)

// String validators
stringvalidator.LengthBetween(1, 255)
stringvalidator.LengthAtLeast(1)
stringvalidator.LengthAtMost(100)
stringvalidator.RegexMatches(regexp, "message")
stringvalidator.OneOf("value1", "value2")
stringvalidator.NoneOf("invalid1", "invalid2")

// Number validators
int64validator.Between(0, 100)
int64validator.AtLeast(1)
int64validator.AtMost(10)
int64validator.OneOf(1, 2, 3)

// List validators
listvalidator.SizeAtLeast(1)
listvalidator.SizeAtMost(10)
listvalidator.SizeBetween(1, 5)
listvalidator.ValueStringsAre(stringvalidator.LengthAtLeast(1))

// Combining validators
stringvalidator.All(
    stringvalidator.LengthBetween(1, 100),
    stringvalidator.RegexMatches(regexp.MustCompile(`^[a-z]+$`), "lowercase only"),
)

stringvalidator.Any(
    stringvalidator.OneOf("option1", "option2"),
    stringvalidator.RegexMatches(customPattern, "custom format"),
)

Custom Validator Template

type myValidator struct{}

func (v myValidator) Description(ctx context.Context) string {
    return "description"
}

func (v myValidator) MarkdownDescription(ctx context.Context) string {
    return "markdown description"
}

func (v myValidator) ValidateString(ctx context.Context, req validator.StringRequest, resp *validator.StringResponse) {
    if req.ConfigValue.IsNull() || req.ConfigValue.IsUnknown() {
        return
    }
    
    value := req.ConfigValue.ValueString()
    
    if !isValid(value) {
        resp.Diagnostics.AddAttributeError(
            req.Path,
            "Validation Error",
            "Error message",
        )
    }
}

Plan Modifier Reference

Built-in Plan Modifiers

import (
    "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
    "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier"
    "github.com/hashicorp/terraform-plugin-framework/resource/schema/int64planmodifier"
)

// String modifiers
stringplanmodifier.RequiresReplace()
stringplanmodifier.RequiresReplaceIfConfigured()
stringplanmodifier.RequiresReplaceIf(func(...) bool {...})
stringplanmodifier.UseStateForUnknown()

// Number modifiers
int64planmodifier.RequiresReplace()
int64planmodifier.UseStateForUnknown()

// List modifiers
listplanmodifier.RequiresReplace()
listplanmodifier.UseStateForUnknown()

Custom Plan Modifier Template

type myModifier struct{}

func (m myModifier) Description(ctx context.Context) string {
    return "description"
}

func (m myModifier) MarkdownDescription(ctx context.Context) string {
    return "markdown description"
}

func (m myModifier) PlanModifyString(ctx context.Context, req planmodifier.StringRequest, resp *planmodifier.StringResponse) {
    // Access plan value
    planValue := req.PlanValue
    
    // Access state value
    stateValue := req.StateValue
    
    // Access config value
    configValue := req.ConfigValue
    
    // Modify plan
    resp.PlanValue = types.StringValue("modified")
    
    // Require replace
    resp.RequiresReplace = true
}

Testing Reference

Acceptance Test Template

func TestResourceName_basic(t *testing.T) {
    resource.Test(t, resource.TestCase{
        PreCheck:                 func() { testAccPreCheck(t) },
        ProtoV6ProviderFactories: testAccProtoV6ProviderFactories,
        Steps: []resource.TestStep{
            // Create and Read testing
            {
                Config: testAccResourceConfig("test"),
                Check: resource.ComposeAggregateTestCheckFunc(
                    resource.TestCheckResourceAttr("provider_resource.test", "name", "test"),
                    resource.TestCheckResourceAttrSet("provider_resource.test", "id"),
                ),
            },
            // ImportState testing
            {
                ResourceName:      "provider_resource.test",
                ImportState:       true,
                ImportStateVerify: true,
            },
            // Update and Read testing
            {
                Config: testAccResourceConfig("updated"),
                Check: resource.ComposeAggregateTestCheckFunc(
                    resource.TestCheckResourceAttr("provider_resource.test", "name", "updated"),
                ),
            },
            // Delete testing automatically occurs in TestCase
        },
    })
}

func testAccResourceConfig(name string) string {
    return fmt.Sprintf(`
resource "provider_resource" "test" {
  name = %[1]q
}
`, name)
}

Test Check Functions

// Attribute checks
resource.TestCheckResourceAttr("resource.name", "attribute", "value")
resource.TestCheckResourceAttrSet("resource.name", "attribute")
resource.TestCheckNoResourceAttr("resource.name", "attribute")
resource.TestCheckResourceAttrPair("resource1.name", "attr1", "resource2.name", "attr2")

// Type-specific checks
resource.TestCheckTypeSetElemAttr("resource.name", "set_attr.*", "value")
resource.TestCheckTypeSetElemNestedAttrs("resource.name", "nested_set.*", map[string]string{
    "key": "value",
})

// Regex matching
resource.TestMatchResourceAttr("resource.name", "attribute", regexp.MustCompile(`pattern`))

// Custom checks
func(s *terraform.State) error {
    rs := s.RootModule().Resources["resource.name"]
    // Custom logic
    return nil
}

Error Patterns

Common Errors and Solutions

// Error: cannot unmarshal
// Solution: Use ElementsAs for collections
var items []string
diags := plan.Items.ElementsAs(ctx, &items, false)

// Error: value is null
// Solution: Check before accessing
if !plan.Value.IsNull() && !plan.Value.IsUnknown() {
    value := plan.Value.ValueString()
}

// Error: persistent diff
// Solution: Normalize values
planValue := normalizeCIDR(plan.CIDR.ValueString())
stateValue := normalizeCIDR(state.CIDR.ValueString())

// Error: state not refreshing
// Solution: Implement Read properly
func (r *Resource) Read(ctx, req, resp) {
    // Always fetch from API
    current, err := r.client.Get(ctx, state.ID.ValueString())
    // Update all attributes
    state.Name = types.StringValue(current.Name)
}

Performance Optimization Patterns

// Batch API calls
caches, err := client.BatchGet(ctx, ids)

// Cache with TTL
cache := expirable.NewLRU[string, *Data](1000, nil, 5*time.Minute)

// Rate limiting
limiter := rate.NewLimiter(rate.Limit(100), 10)
limiter.Wait(ctx)

// Connection pooling
transport := &http.Transport{
    MaxIdleConns:        100,
    MaxIdleConnsPerHost: 10,
}

// Parallel operations with semaphore
semaphore := make(chan struct{}, 10)
for _, item := range items {
    semaphore <- struct{}{}
    go func() {
        defer func() { <-semaphore }()
        process(item)
    }()
}

Official Resources

Documentation

GitHub Repositories

Example Providers

Community Resources

Forums and Discussion

Learning Resources

  • HashiCorp Learn: Provider development tutorials
  • Terraform Up & Running: Book by Yevgeniy Brikman
  • Provider Development Workshops: HashiCorp conference sessions

Tools and Utilities

Development Tools

# Code generation
terraform-plugin-docs

# Testing
terraform-plugin-testing

# Debugging
delve (dlv)

# Profiling
go tool pprof

# Linting
golangci-lint

CI/CD Templates

# GitHub Actions
name: Test
on: [push, pull_request]
jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - uses: actions/setup-go@v4
        with:
          go-version: '1.21'
      - run: go test -v ./...
      
  acc-test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - uses: actions/setup-go@v4
      - run: TF_ACC=1 go test -v -timeout 30m
        env:
          API_KEY: $

Glossary

Key Terms

Attribute: A named field in a schema that holds data

Computed: An attribute whose value is determined by the provider

Data Source: Read-only resource for querying external data

Diagnostic: Error or warning message returned to Terraform

Framework: The newer Terraform plugin development toolkit (Plugin Framework)

Optional: An attribute that may be omitted in configuration

Plan: The proposed set of changes Terraform will apply

Plan Modifier: Logic that modifies planned values before apply

Provider: The plugin that manages resources via an API

Required: An attribute that must be specified in configuration

Resource: A managed infrastructure component

Schema: The structure defining attributes and their types

SDK: The older Terraform plugin development toolkit (Plugin SDK)

State: The current known values of managed resources

tflog: The logging package for provider development

types: The package containing Framework type definitions

Validator: Logic that validates configuration values

Provider Development Checklist

Before Starting

  • Review API documentation
  • Design resource schemas
  • Plan data sources needed
  • Set up development environment
  • Choose provider name (not used by others)

During Development

  • Implement provider configuration
  • Implement resources (CRUD)
  • Implement data sources
  • Add validation
  • Add plan modifiers
  • Write acceptance tests
  • Add structured logging
  • Generate documentation
  • Test locally
  • Handle errors properly

Before Publishing

  • Complete test coverage
  • Documentation generated
  • Examples provided
  • CHANGELOG maintained
  • LICENSE file present
  • README comprehensive
  • Version tagged
  • GPG key configured
  • goreleaser configured
  • CI/CD configured

After Publishing

  • Monitor issues
  • Respond to community
  • Maintain compatibility
  • Update dependencies
  • Release regularly
  • Deprecate features gracefully

Congratulations!

🎉 You’ve completed the Terraform Provider Development Tutorial!

You now have comprehensive knowledge to build, test, publish, and maintain production-grade Terraform providers.

What You’ve Learned

✅ Provider architecture and design
✅ Schema definition and types
✅ CRUD operations implementation
✅ State management
✅ API client integration
✅ Testing strategies
✅ Validation and error handling
✅ Documentation generation
✅ Publishing to registry
✅ Advanced techniques
✅ Debugging and troubleshooting
✅ Production best practices

Next Steps

  1. Build Your Provider: Start with your API of choice
  2. Contribute: Improve existing providers
  3. Share: Publish your provider to the registry
  4. Learn More: Explore advanced patterns
  5. Join Community: Participate in discussions

Stay Connected

  • Follow HashiCorp updates
  • Join provider development discussions
  • Share your experience
  • Help others learn

Happy Provider Building! 🚀


Start Building: ← Back to Home

Last updated: July 3, 2026

Updated: