Advanced Lab

8 minute read

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

Updated: