Appendix & Resources
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
- Terraform Plugin Framework: https://developer.hashicorp.com/terraform/plugin/framework
- Plugin Development Guide: https://developer.hashicorp.com/terraform/plugin
- Provider Design Principles: https://developer.hashicorp.com/terraform/plugin/best-practices
- Terraform Registry: https://registry.terraform.io/
GitHub Repositories
- Plugin Framework: https://github.com/hashicorp/terraform-plugin-framework
- Plugin SDK: https://github.com/hashicorp/terraform-plugin-sdk
- Plugin Testing: https://github.com/hashicorp/terraform-plugin-testing
- Plugin Docs: https://github.com/hashicorp/terraform-plugin-docs
Example Providers
- AWS Provider: https://github.com/hashicorp/terraform-provider-aws
- Random Provider: https://github.com/hashicorp/terraform-provider-random
- Scaffolding: https://github.com/hashicorp/terraform-provider-scaffolding-framework
Community Resources
Forums and Discussion
- HashiCorp Discuss: https://discuss.hashicorp.com/c/terraform-providers
- Terraform GitHub Discussions: https://github.com/hashicorp/terraform/discussions
- Stack Overflow: terraform-provider tag
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
- Build Your Provider: Start with your API of choice
- Contribute: Improve existing providers
- Share: Publish your provider to the registry
- Learn More: Explore advanced patterns
- 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