Advanced Lab
Lab Overview
Build a production-grade provider incorporating all advanced techniques: custom functions, complex attributes, performance optimization, state management, validation, middleware, and comprehensive testing.
Project: Advanced CacheCloud Provider
Extend the CacheCloud provider with advanced features for enterprise use.
Part 1: Provider-Defined Functions
Implement CIDR Calculation Function
// internal/provider/function_cidr_subnet.go
package provider
import (
"context"
"fmt"
"math"
"net"
"github.com/hashicorp/terraform-plugin-framework/function"
)
type CIDRSubnetFunction struct{}
func NewCIDRSubnetFunction() function.Function {
return &CIDRSubnetFunction{}
}
func (f *CIDRSubnetFunction) Metadata(ctx context.Context, req function.MetadataRequest, resp *function.MetadataResponse) {
resp.Name = "cidr_subnet"
}
func (f *CIDRSubnetFunction) Definition(ctx context.Context, req function.DefinitionRequest, resp *function.DefinitionResponse) {
resp.Definition = function.Definition{
Summary: "Calculate CIDR subnet",
Description: "Calculates a subnet address within a given IP network address prefix.",
Parameters: []function.Parameter{
function.StringParameter{
Name: "prefix",
Description: "Base CIDR block (e.g., '10.0.0.0/16')",
},
function.Int64Parameter{
Name: "newbits",
Description: "Number of additional bits for subnet",
},
function.Int64Parameter{
Name: "netnum",
Description: "Subnet number",
},
},
Return: function.StringReturn{},
}
}
func (f *CIDRSubnetFunction) Run(ctx context.Context, req function.RunRequest, resp *function.RunResponse) {
var prefix string
var newbits, netnum int64
resp.Error = function.ConcatFuncErrors(resp.Error, req.Arguments.Get(ctx, &prefix, &newbits, &netnum))
if resp.Error != nil {
return
}
ip, ipnet, err := net.ParseCIDR(prefix)
if err != nil {
resp.Error = function.NewArgumentFuncError(0, fmt.Sprintf("Invalid CIDR notation: %s", err))
return
}
originalPrefixLen, _ := ipnet.Mask.Size()
newPrefixLen := originalPrefixLen + int(newbits)
if newPrefixLen > 32 {
resp.Error = function.NewArgumentFuncError(1, "New prefix length exceeds 32 bits")
return
}
maxSubnets := int64(math.Pow(2, float64(newbits)))
if netnum >= maxSubnets {
resp.Error = function.NewArgumentFuncError(2,
fmt.Sprintf("Subnet number %d exceeds maximum %d", netnum, maxSubnets-1))
return
}
ipInt := ipToInt(ip.To4())
subnetSize := int64(math.Pow(2, float64(32-newPrefixLen)))
subnetIP := intToIP(ipInt + (netnum * subnetSize))
result := fmt.Sprintf("%s/%d", subnetIP.String(), newPrefixLen)
resp.Error = function.ConcatFuncErrors(resp.Error, resp.Result.Set(ctx, result))
}
func ipToInt(ip net.IP) int64 {
return int64(ip[0])<<24 | int64(ip[1])<<16 | int64(ip[2])<<8 | int64(ip[3])
}
func intToIP(n int64) net.IP {
return net.IPv4(byte(n>>24), byte(n>>16), byte(n>>8), byte(n))
}
Register Function
// internal/provider/provider.go
func (p *provider) Functions(ctx context.Context) []function.Function {
return []function.Function{
NewCIDRSubnetFunction(),
}
}
Test Function
// internal/provider/function_cidr_subnet_test.go
func TestCIDRSubnetFunction(t *testing.T) {
tests := []struct {
name string
prefix string
newbits int64
netnum int64
expected string
expectErr bool
}{
{
name: "basic subnet",
prefix: "10.0.0.0/16",
newbits: 8,
netnum: 0,
expected: "10.0.0.0/24",
},
{
name: "second subnet",
prefix: "10.0.0.0/16",
newbits: 8,
netnum: 1,
expected: "10.0.1.0/24",
},
{
name: "invalid CIDR",
prefix: "not-a-cidr",
newbits: 8,
netnum: 0,
expectErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
f := NewCIDRSubnetFunction()
req := function.RunRequest{
Arguments: function.NewArgumentsData([]attr.Value{
types.StringValue(tt.prefix),
types.Int64Value(tt.newbits),
types.Int64Value(tt.netnum),
}),
}
resp := function.RunResponse{
Result: function.NewResultData(types.StringType),
}
f.Run(context.Background(), req, &resp)
if tt.expectErr {
assert.True(t, resp.Error != nil)
} else {
assert.Nil(t, resp.Error)
var result string
resp.Result.Get(context.Background(), &result)
assert.Equal(t, tt.expected, result)
}
})
}
}
Part 2: Complex Nested Attributes
Implement Advanced Cluster Resource
// internal/provider/resource_cluster.go
func (r *ClusterResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) {
resp.Schema = schema.Schema{
Description: "Manages a cache cluster",
Version: 1,
Attributes: map[string]schema.Attribute{
"id": schema.StringAttribute{
Computed: true,
},
"name": schema.StringAttribute{
Required: true,
},
"network_config": schema.SingleNestedAttribute{
Required: true,
Description: "Network configuration",
Attributes: map[string]schema.Attribute{
"vpc_id": schema.StringAttribute{
Required: true,
},
"subnets": schema.ListNestedAttribute{
Required: true,
NestedObject: schema.NestedAttributeObject{
Attributes: map[string]schema.Attribute{
"id": schema.StringAttribute{
Required: true,
},
"availability_zone": schema.StringAttribute{
Required: true,
},
"cidr_block": schema.StringAttribute{
Computed: true,
},
},
},
},
"security_groups": schema.SetAttribute{
Optional: true,
ElementType: types.StringType,
},
},
},
"nodes": schema.ListNestedAttribute{
Required: true,
NestedObject: schema.NestedAttributeObject{
Attributes: map[string]schema.Attribute{
"id": schema.StringAttribute{
Computed: true,
},
"availability_zone": schema.StringAttribute{
Required: true,
},
"instance_type": schema.StringAttribute{
Required: true,
},
"status": schema.StringAttribute{
Computed: true,
},
},
},
},
"backup_config": schema.SingleNestedAttribute{
Optional: true,
Validators: []validator.Object{
ValidateBackupConfig(),
},
Attributes: map[string]schema.Attribute{
"enabled": schema.BoolAttribute{
Required: true,
},
"schedule": schema.StringAttribute{
Optional: true,
},
"retention_days": schema.Int64Attribute{
Optional: true,
},
},
},
},
}
}
Part 3: Performance Optimization
Implement Client with Middleware
// internal/client/client.go
package client
type Client struct {
httpClient *http.Client
baseURL string
apiKey string
cache *sync.Map
}
func NewClient(apiKey string, opts ...Option) *Client {
// Base transport with connection pooling
baseTransport := &http.Transport{
MaxIdleConns: 100,
MaxIdleConnsPerHost: 10,
IdleConnTimeout: 90 * time.Second,
TLSHandshakeTimeout: 10 * time.Second,
}
// Build middleware chain
transport := chainMiddleware(baseTransport,
NewLoggingMiddleware(),
NewRetryMiddleware(3),
NewMetricsMiddleware(),
NewRateLimitMiddleware(100, 10), // 100 req/s, burst of 10
NewCachingMiddleware(5*time.Minute),
NewAuthMiddleware(apiKey),
)
client := &Client{
httpClient: &http.Client{
Transport: transport,
Timeout: 30 * time.Second,
},
baseURL: "https://api.cachecloud.example.com",
apiKey: apiKey,
cache: &sync.Map{},
}
for _, opt := range opts {
opt(client)
}
return client
}
// Batch operation for efficiency
func (c *Client) BatchGetCaches(ctx context.Context, ids []string) ([]*Cache, error) {
// Check cache first
uncached := []string{}
results := make([]*Cache, 0, len(ids))
for _, id := range ids {
if cached, found := c.cache.Load(id); found {
results = append(results, cached.(*Cache))
} else {
uncached = append(uncached, id)
}
}
if len(uncached) == 0 {
return results, nil
}
// Fetch uncached in parallel
type result struct {
cache *Cache
err error
}
resultsChan := make(chan result, len(uncached))
semaphore := make(chan struct{}, 10) // Max 10 concurrent
var wg sync.WaitGroup
for _, id := range uncached {
wg.Add(1)
go func(cacheID string) {
defer wg.Done()
semaphore <- struct{}{}
defer func() { <-semaphore }()
cache, err := c.GetCache(ctx, cacheID)
resultsChan <- result{cache: cache, err: err}
}(id)
}
go func() {
wg.Wait()
close(resultsChan)
}()
for res := range resultsChan {
if res.err != nil {
return nil, res.err
}
results = append(results, res.cache)
c.cache.Store(res.cache.ID, res.cache)
}
return results, nil
}
Part 4: State Management
Implement State Upgrader
// internal/provider/resource_cluster_upgrade.go
func (r *ClusterResource) UpgradeState(ctx context.Context) map[int64]resource.StateUpgrader {
return map[int64]resource.StateUpgrader{
0: {
PriorSchema: &schema.Schema{
Attributes: map[string]schema.Attribute{
"id": schema.StringAttribute{
Computed: true,
},
"name": schema.StringAttribute{
Required: true,
},
"vpc_id": schema.StringAttribute{
Required: true,
},
"subnet_ids": schema.ListAttribute{
Required: true,
ElementType: types.StringType,
},
},
},
StateUpgrader: func(ctx context.Context, req resource.UpgradeStateRequest, resp *resource.UpgradeStateResponse) {
type V0State struct {
ID types.String `tfsdk:"id"`
Name types.String `tfsdk:"name"`
VPCID types.String `tfsdk:"vpc_id"`
SubnetIDs types.List `tfsdk:"subnet_ids"`
}
var v0State V0State
resp.Diagnostics.Append(req.State.Get(ctx, &v0State)...)
if resp.Diagnostics.HasError() {
return
}
// Fetch subnet details from API to populate new structure
var subnetIDs []string
resp.Diagnostics.Append(v0State.SubnetIDs.ElementsAs(ctx, &subnetIDs, false)...)
client := req.ProviderMeta.(*Client)
subnets := make([]SubnetModel, 0, len(subnetIDs))
for _, subnetID := range subnetIDs {
subnet, err := client.GetSubnet(ctx, subnetID)
if err != nil {
resp.Diagnostics.AddError("Migration Error",
fmt.Sprintf("Failed to fetch subnet %s: %s", subnetID, err))
return
}
subnets = append(subnets, SubnetModel{
ID: types.StringValue(subnet.ID),
AvailabilityZone: types.StringValue(subnet.AZ),
CIDRBlock: types.StringValue(subnet.CIDR),
})
}
// Build new state
newState := ClusterResourceModel{
ID: v0State.ID,
Name: v0State.Name,
NetworkConfig: &NetworkConfigModel{
VPCID: v0State.VPCID,
Subnets: subnets,
},
}
resp.Diagnostics.Append(resp.State.Set(ctx, newState)...)
},
},
}
}
Part 5: Advanced Validation
Implement Cross-Field Validator
// internal/provider/validators.go
type clusterValidator struct{}
func ValidateCluster() validator.Object {
return clusterValidator{}
}
func (v clusterValidator) ValidateObject(ctx context.Context, req validator.ObjectRequest, resp *validator.ObjectResponse) {
var config struct {
Name types.String `tfsdk:"name"`
NetworkConfig *NetworkConfigModel `tfsdk:"network_config"`
Nodes []NodeModel `tfsdk:"nodes"`
}
resp.Diagnostics.Append(req.ConfigValue.As(ctx, &config, basetypes.ObjectAsOptions{})...)
if resp.Diagnostics.HasError() {
return
}
// Validate node count matches subnet count for HA
if len(config.Nodes) > 1 {
var subnets []SubnetModel
if config.NetworkConfig != nil {
// Extract subnets...
if len(config.Nodes) != len(subnets) {
resp.Diagnostics.AddError(
"Invalid Configuration",
fmt.Sprintf(
"For high availability, node count (%d) should match subnet count (%d)",
len(config.Nodes),
len(subnets),
),
)
}
}
}
// Validate nodes in different AZs
azMap := make(map[string]bool)
for _, node := range config.Nodes {
az := node.AvailabilityZone.ValueString()
if azMap[az] {
resp.Diagnostics.AddWarning(
"Suboptimal Configuration",
fmt.Sprintf("Multiple nodes in availability zone '%s' reduces fault tolerance", az),
)
}
azMap[az] = true
}
}
Part 6: Comprehensive Testing
Integration Test
// internal/provider/resource_cluster_test.go
func TestClusterResource_CompleteLifecycle(t *testing.T) {
resource.Test(t, resource.TestCase{
ProtoV6ProviderFactories: testAccProtoV6ProviderFactories,
Steps: []resource.TestStep{
// Create
{
Config: testClusterConfig("test-cluster", 3),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr("cachecloud_cluster.test", "name", "test-cluster"),
resource.TestCheckResourceAttr("cachecloud_cluster.test", "nodes.#", "3"),
testCheckClusterExists("cachecloud_cluster.test"),
),
},
// Update - add node
{
Config: testClusterConfig("test-cluster", 4),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr("cachecloud_cluster.test", "nodes.#", "4"),
),
},
// Import
{
ResourceName: "cachecloud_cluster.test",
ImportState: true,
ImportStateVerify: true,
},
// Update - enable backups
{
Config: testClusterConfigWithBackup("test-cluster", 4),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr("cachecloud_cluster.test", "backup_config.enabled", "true"),
resource.TestCheckResourceAttr("cachecloud_cluster.test", "backup_config.schedule", "0 2 * * *"),
),
},
},
})
}
func testClusterConfig(name string, nodeCount int) string {
nodes := ""
for i := 0; i < nodeCount; i++ {
nodes += fmt.Sprintf(`
{
availability_zone = "us-west-2%c"
instance_type = "cache.t3.micro"
},`, 'a'+i)
}
return fmt.Sprintf(`
resource "cachecloud_cluster" "test" {
name = %[1]q
network_config = {
vpc_id = "vpc-12345"
subnets = [
{ id = "subnet-a", availability_zone = "us-west-2a" },
{ id = "subnet-b", availability_zone = "us-west-2b" },
{ id = "subnet-c", availability_zone = "us-west-2c" },
]
}
nodes = [%[2]s
]
}
`, name, nodes)
}
Part 7: Complete Example Configuration
Terraform Usage
terraform {
required_providers {
cachecloud = {
source = "example/cachecloud"
version = "~> 2.0"
}
}
}
provider "cachecloud" {
api_key = var.api_key
}
# Use provider function to calculate subnets
locals {
vpc_cidr = "10.0.0.0/16"
subnet_count = 3
subnets = [
for i in range(local.subnet_count) :
provider::cachecloud::cidr_subnet(local.vpc_cidr, 8, i)
]
}
# Create cluster with advanced configuration
resource "cachecloud_cluster" "production" {
name = "prod-cluster"
network_config = {
vpc_id = aws_vpc.main.id
subnets = [
for i, subnet in aws_subnet.private : {
id = subnet.id
availability_zone = subnet.availability_zone
}
]
security_groups = [aws_security_group.cache.id]
}
nodes = [
{
availability_zone = "us-west-2a"
instance_type = "cache.r6g.large"
},
{
availability_zone = "us-west-2b"
instance_type = "cache.r6g.large"
},
{
availability_zone = "us-west-2c"
instance_type = "cache.r6g.large"
}
]
backup_config = {
enabled = true
schedule = "0 2 * * *" # Daily at 2 AM
retention_days = 7
}
}
output "cluster_endpoint" {
value = cachecloud_cluster.production.endpoint
}
Verification
Run Tests
# Unit tests
go test ./internal/provider/... -v
# Acceptance tests
TF_ACC=1 go test ./internal/provider/... -v -timeout 30m
# Benchmarks
go test ./internal/client/... -bench=. -benchmem
# Coverage
go test ./... -coverprofile=coverage.out
go tool cover -html=coverage.out
Performance Validation
# Profile CPU
go test -bench=. -cpuprofile=cpu.prof
go tool pprof cpu.prof
# Profile memory
go test -bench=. -memprofile=mem.prof
go tool pprof mem.prof
Key Takeaways
- ✅ Implemented provider-defined functions
- ✅ Designed complex nested attributes
- ✅ Optimized performance with middleware
- ✅ Handled state migrations gracefully
- ✅ Created advanced validators
- ✅ Built comprehensive test suite
- ✅ Applied production-grade patterns
Congratulations!
You’ve completed Module 14 and built an advanced, production-ready Terraform provider with sophisticated features!
Next Module: Debugging & Troubleshooting →
Previous: ← Advanced Testing Patterns
Next Module: Debugging & Troubleshooting →
Last updated: July 1, 2026