Complex Attribute Types

7 minute read

Complex Attributes Overview

Complex attribute types enable sophisticated resource configurations that mirror complex API structures while maintaining user-friendly Terraform syntax.

Attribute Type Hierarchy

Primitive Types:
├── String
├── Number (Int64, Float64)
└── Bool

Collection Types:
├── List
├── Set
└── Map

Complex Types:
├── Single Nested Attribute
├── List Nested Attribute
├── Set Nested Attribute
├── Map Nested Attribute
└── Dynamic (runtime type)

Nested Attributes

Single Nested Attribute

For singular complex objects.

schema.SingleNestedAttribute{
    Description: "Network configuration",
    Required:    true,
    Attributes: map[string]schema.Attribute{
        "vpc_id": schema.StringAttribute{
            Description: "VPC identifier",
            Required:    true,
        },
        "subnet_ids": schema.ListAttribute{
            Description: "Subnet identifiers",
            Required:    true,
            ElementType: types.StringType,
        },
        "security_group_ids": schema.SetAttribute{
            Description: "Security group identifiers",
            Optional:    true,
            ElementType: types.StringType,
        },
    },
}

Terraform Usage

resource "cachecloud_cache" "example" {
  name = "my-cache"
  
  network_config = {
    vpc_id = "vpc-12345"
    subnet_ids = [
      "subnet-abc",
      "subnet-def"
    ]
    security_group_ids = [
      "sg-123",
      "sg-456"
    ]
  }
}

Model Struct

type CacheResourceModel struct {
    Name          types.String        `tfsdk:"name"`
    NetworkConfig *NetworkConfigModel `tfsdk:"network_config"`
}

type NetworkConfigModel struct {
    VPCID            types.String `tfsdk:"vpc_id"`
    SubnetIDs        types.List   `tfsdk:"subnet_ids"`
    SecurityGroupIDs types.Set    `tfsdk:"security_group_ids"`
}

List Nested Attributes

For collections of complex objects.

Schema Definition

schema.ListNestedAttribute{
    Description: "Node configurations",
    Required:    true,
    NestedObject: schema.NestedAttributeObject{
        Attributes: map[string]schema.Attribute{
            "availability_zone": schema.StringAttribute{
                Description: "Availability zone",
                Required:    true,
            },
            "instance_type": schema.StringAttribute{
                Description: "Instance type",
                Required:    true,
            },
            "engine_version": schema.StringAttribute{
                Description: "Engine version",
                Optional:    true,
                Computed:    true,
            },
        },
    },
}

Usage

resource "cachecloud_cluster" "example" {
  name = "my-cluster"
  
  nodes = [
    {
      availability_zone = "us-west-2a"
      instance_type     = "cache.t3.micro"
      engine_version    = "6.2"
    },
    {
      availability_zone = "us-west-2b"
      instance_type     = "cache.t3.micro"
      engine_version    = "6.2"
    },
    {
      availability_zone = "us-west-2c"
      instance_type     = "cache.t3.small"
    }
  ]
}

Model

type ClusterResourceModel struct {
    Name  types.String `tfsdk:"name"`
    Nodes types.List   `tfsdk:"nodes"` // List of NodeModel
}

type NodeModel struct {
    AvailabilityZone types.String `tfsdk:"availability_zone"`
    InstanceType     types.String `tfsdk:"instance_type"`
    EngineVersion    types.String `tfsdk:"engine_version"`
}

Reading Values

func (r *ClusterResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
    var plan ClusterResourceModel
    resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...)
    
    // Extract nodes from list
    var nodes []NodeModel
    resp.Diagnostics.Append(plan.Nodes.ElementsAs(ctx, &nodes, false)...)
    if resp.Diagnostics.HasError() {
        return
    }
    
    // Process each node
    for i, node := range nodes {
        tflog.Debug(ctx, "Processing node", map[string]interface{}{
            "index": i,
            "az":    node.AvailabilityZone.ValueString(),
            "type":  node.InstanceType.ValueString(),
        })
        
        // Create node via API...
    }
}

Set Nested Attributes

For unordered collections of unique objects.

Schema

schema.SetNestedAttribute{
    Description: "Backup configurations",
    Optional:    true,
    NestedObject: schema.NestedAttributeObject{
        Attributes: map[string]schema.Attribute{
            "schedule": schema.StringAttribute{
                Description: "Cron schedule",
                Required:    true,
            },
            "retention_days": schema.Int64Attribute{
                Description: "Retention in days",
                Required:    true,
            },
            "enabled": schema.BoolAttribute{
                Description: "Whether backup is enabled",
                Optional:    true,
                Computed:    true,
                Default:     booldefault.StaticBool(true),
            },
        },
    },
}

Usage

resource "cachecloud_cache" "example" {
  name = "my-cache"
  
  backups = [
    {
      schedule       = "0 2 * * *"  # Daily at 2 AM
      retention_days = 7
      enabled        = true
    },
    {
      schedule       = "0 0 * * 0"  # Weekly on Sunday
      retention_days = 30
    }
  ]
}

Working with Sets

var backups []BackupModel
resp.Diagnostics.Append(plan.Backups.ElementsAs(ctx, &backups, false)...)

// Note: Order not guaranteed with sets
for _, backup := range backups {
    tflog.Info(ctx, "Backup configuration", map[string]interface{}{
        "schedule":  backup.Schedule.ValueString(),
        "retention": backup.RetentionDays.ValueInt64(),
    })
}

Map Nested Attributes

For key-value pairs where values are complex objects.

Schema

schema.MapNestedAttribute{
    Description: "Environment-specific configurations",
    Optional:    true,
    NestedObject: schema.NestedAttributeObject{
        Attributes: map[string]schema.Attribute{
            "instance_count": schema.Int64Attribute{
                Description: "Number of instances",
                Required:    true,
            },
            "instance_type": schema.StringAttribute{
                Description: "Instance type",
                Required:    true,
            },
            "auto_scaling": schema.BoolAttribute{
                Description: "Enable auto-scaling",
                Optional:    true,
            },
        },
    },
}

Usage

resource "cachecloud_deployment" "example" {
  name = "my-deployment"
  
  environments = {
    dev = {
      instance_count = 1
      instance_type  = "cache.t3.micro"
      auto_scaling   = false
    }
    staging = {
      instance_count = 2
      instance_type  = "cache.t3.small"
      auto_scaling   = true
    }
    production = {
      instance_count = 5
      instance_type  = "cache.r6g.large"
      auto_scaling   = true
    }
  }
}

Model

type DeploymentResourceModel struct {
    Name         types.String `tfsdk:"name"`
    Environments types.Map    `tfsdk:"environments"` // Map of EnvironmentModel
}

type EnvironmentModel struct {
    InstanceCount types.Int64  `tfsdk:"instance_count"`
    InstanceType  types.String `tfsdk:"instance_type"`
    AutoScaling   types.Bool   `tfsdk:"auto_scaling"`
}

Processing Map

var environments map[string]EnvironmentModel
resp.Diagnostics.Append(plan.Environments.ElementsAs(ctx, &environments, false)...)

// Iterate over environments
for envName, envConfig := range environments {
    tflog.Info(ctx, "Environment configuration", map[string]interface{}{
        "environment": envName,
        "count":       envConfig.InstanceCount.ValueInt64(),
        "type":        envConfig.InstanceType.ValueString(),
    })
    
    // Create environment via API...
}

Deep Nesting

Multi-Level Nested Structures

schema.SingleNestedAttribute{
    Description: "Advanced configuration",
    Optional:    true,
    Attributes: map[string]schema.Attribute{
        "networking": schema.SingleNestedAttribute{
            Required: true,
            Attributes: map[string]schema.Attribute{
                "vpc": schema.SingleNestedAttribute{
                    Required: true,
                    Attributes: map[string]schema.Attribute{
                        "id": schema.StringAttribute{
                            Required: true,
                        },
                        "cidr_block": schema.StringAttribute{
                            Required: true,
                            Validators: []validator.String{
                                stringvalidator.RegexMatches(
                                    regexp.MustCompile(`^(\d{1,3}\.){3}\d{1,3}/\d{1,2}$`),
                                    "must be valid CIDR notation",
                                ),
                            },
                        },
                    },
                },
                "subnets": schema.ListNestedAttribute{
                    Required: true,
                    NestedObject: schema.NestedAttributeObject{
                        Attributes: map[string]schema.Attribute{
                            "id": schema.StringAttribute{
                                Required: true,
                            },
                            "availability_zone": schema.StringAttribute{
                                Required: true,
                            },
                            "route_table": schema.SingleNestedAttribute{
                                Optional: true,
                                Attributes: map[string]schema.Attribute{
                                    "id": schema.StringAttribute{
                                        Required: true,
                                    },
                                    "routes": schema.ListNestedAttribute{
                                        Optional: true,
                                        NestedObject: schema.NestedAttributeObject{
                                            Attributes: map[string]schema.Attribute{
                                                "destination": schema.StringAttribute{
                                                    Required: true,
                                                },
                                                "target": schema.StringAttribute{
                                                    Required: true,
                                                },
                                            },
                                        },
                                    },
                                },
                            },
                        },
                    },
                },
            },
        },
    },
}

Usage

resource "cachecloud_infrastructure" "example" {
  name = "my-infra"
  
  advanced_config = {
    networking = {
      vpc = {
        id         = "vpc-12345"
        cidr_block = "10.0.0.0/16"
      }
      
      subnets = [
        {
          id                = "subnet-abc"
          availability_zone = "us-west-2a"
          
          route_table = {
            id = "rtb-123"
            routes = [
              {
                destination = "0.0.0.0/0"
                target      = "igw-456"
              },
              {
                destination = "10.1.0.0/16"
                target      = "pcx-789"
              }
            ]
          }
        }
      ]
    }
  }
}

Dynamic Types

For runtime-determined types.

Schema

schema.DynamicAttribute{
    Description: "Flexible configuration",
    Optional:    true,
}

Usage

resource "cachecloud_config" "string_example" {
  name   = "string-config"
  config = "simple-string"
}

resource "cachecloud_config" "map_example" {
  name = "map-config"
  config = {
    key1 = "value1"
    key2 = "value2"
  }
}

resource "cachecloud_config" "list_example" {
  name   = "list-config"
  config = ["item1", "item2", "item3"]
}

Handling Dynamic Types

func (r *ConfigResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
    var plan ConfigResourceModel
    resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...)
    
    // Get dynamic value
    configValue := plan.Config
    
    switch {
    case configValue.IsNull():
        // Handle null
    case configValue.IsUnderlyingValueString():
        var str string
        resp.Diagnostics.Append(configValue.Get(ctx, &str)...)
        // Handle string
    case configValue.IsUnderlyingValueMap():
        var m map[string]attr.Value
        resp.Diagnostics.Append(configValue.Get(ctx, &m)...)
        // Handle map
    case configValue.IsUnderlyingValueList():
        var list []attr.Value
        resp.Diagnostics.Append(configValue.Get(ctx, &list)...)
        // Handle list
    }
}

Computed Nested Attributes

Schema with Computed Nested

schema.SingleNestedAttribute{
    Description: "Cache statistics",
    Computed:    true,
    Attributes: map[string]schema.Attribute{
        "hit_rate": schema.Float64Attribute{
            Description: "Cache hit rate percentage",
            Computed:    true,
        },
        "connections": schema.Int64Attribute{
            Description: "Current connections",
            Computed:    true,
        },
        "memory_usage": schema.SingleNestedAttribute{
            Computed: true,
            Attributes: map[string]schema.Attribute{
                "used_bytes": schema.Int64Attribute{
                    Computed: true,
                },
                "max_bytes": schema.Int64Attribute{
                    Computed: true,
                },
                "percentage": schema.Float64Attribute{
                    Computed: true,
                },
            },
        },
    },
}

Populating Computed Values

// After API call
stats := apiResponse.Statistics

statsModel := &StatisticsModel{
    HitRate:     types.Float64Value(stats.HitRate),
    Connections: types.Int64Value(stats.Connections),
    MemoryUsage: &MemoryUsageModel{
        UsedBytes:  types.Int64Value(stats.Memory.Used),
        MaxBytes:   types.Int64Value(stats.Memory.Max),
        Percentage: types.Float64Value(float64(stats.Memory.Used) / float64(stats.Memory.Max) * 100),
    },
}

state.Statistics = statsModel
resp.Diagnostics.Append(resp.State.Set(ctx, &state)...)

Attribute Validators

Cross-Attribute Validation

schema.SingleNestedAttribute{
    Description: "Backup configuration",
    Optional:    true,
    Validators: []validator.Object{
        objectvalidator.All(
            // Custom validator
            backupConfigValidator{},
        ),
    },
    Attributes: map[string]schema.Attribute{
        "enabled": schema.BoolAttribute{
            Required: true,
        },
        "schedule": schema.StringAttribute{
            Optional: true,
        },
        "retention_days": schema.Int64Attribute{
            Optional: true,
        },
    },
}

type backupConfigValidator struct{}

func (v backupConfigValidator) Description(ctx context.Context) string {
    return "Validates backup configuration consistency"
}

func (v backupConfigValidator) MarkdownDescription(ctx context.Context) string {
    return "Validates backup configuration consistency"
}

func (v backupConfigValidator) ValidateObject(ctx context.Context, req validator.ObjectRequest, resp *validator.ObjectResponse) {
    var config struct {
        Enabled       types.Bool   `tfsdk:"enabled"`
        Schedule      types.String `tfsdk:"schedule"`
        RetentionDays types.Int64  `tfsdk:"retention_days"`
    }
    
    resp.Diagnostics.Append(req.ConfigValue.As(ctx, &config, basetypes.ObjectAsOptions{})...)
    if resp.Diagnostics.HasError() {
        return
    }
    
    // If enabled, schedule and retention must be set
    if config.Enabled.ValueBool() {
        if config.Schedule.IsNull() {
            resp.Diagnostics.AddAttributeError(
                req.Path.AtName("schedule"),
                "Missing Required Attribute",
                "Schedule must be specified when backup is enabled",
            )
        }
        if config.RetentionDays.IsNull() {
            resp.Diagnostics.AddAttributeError(
                req.Path.AtName("retention_days"),
                "Missing Required Attribute",
                "Retention days must be specified when backup is enabled",
            )
        }
    }
}

Building Complex Values

Creating Nested Objects

// Create nested object from API response
networkConfig := &NetworkConfigModel{
    VPCID: types.StringValue(apiResp.VPC.ID),
}

// Build subnet list
subnetValues := make([]attr.Value, 0, len(apiResp.Subnets))
for _, subnet := range apiResp.Subnets {
    subnetValues = append(subnetValues, types.StringValue(subnet.ID))
}

var diags diag.Diagnostics
networkConfig.SubnetIDs, diags = types.ListValue(types.StringType, subnetValues)
resp.Diagnostics.Append(diags...)

// Build security group set
sgValues := make([]attr.Value, 0, len(apiResp.SecurityGroups))
for _, sg := range apiResp.SecurityGroups {
    sgValues = append(sgValues, types.StringValue(sg.ID))
}
networkConfig.SecurityGroupIDs, diags = types.SetValue(types.StringType, sgValues)
resp.Diagnostics.Append(diags...)

state.NetworkConfig = networkConfig

Creating List of Objects

// Create list of node objects
nodeObjects := make([]NodeModel, 0, len(apiResp.Nodes))
for _, node := range apiResp.Nodes {
    nodeObjects = append(nodeObjects, NodeModel{
        AvailabilityZone: types.StringValue(node.AZ),
        InstanceType:     types.StringValue(node.Type),
        EngineVersion:    types.StringValue(node.Version),
    })
}

// Convert to types.List
nodeType := types.ObjectType{
    AttrTypes: map[string]attr.Type{
        "availability_zone": types.StringType,
        "instance_type":     types.StringType,
        "engine_version":    types.StringType,
    },
}

nodeAttrValues := make([]attr.Value, 0, len(nodeObjects))
for _, node := range nodeObjects {
    objVal, diags := types.ObjectValueFrom(ctx, nodeType.AttrTypes, node)
    resp.Diagnostics.Append(diags...)
    nodeAttrValues = append(nodeAttrValues, objVal)
}

state.Nodes, diags = types.ListValue(nodeType, nodeAttrValues)
resp.Diagnostics.Append(diags...)

Best Practices

1. Appropriate Nesting

// ✅ Good: Logical grouping
network_config = {
  vpc_id     = "..."
  subnet_ids = [...]
}

// ❌ Bad: Excessive nesting
config = {
  network = {
    vpc = {
      id = {
        value = "..."
      }
    }
  }
}

2. Clear Structure

// ✅ Good: Intuitive structure
nodes = [
  {
    availability_zone = "us-west-2a"
    instance_type     = "cache.t3.micro"
  }
]

// ❌ Bad: Confusing structure
nodes = {
  "0" = {
    config = ["us-west-2a", "cache.t3.micro"]
  }
}

3. Type Selection

// ✅ List: Ordered items
ordered_rules = [rule1, rule2, rule3]

// ✅ Set: Unordered, unique items
security_groups = [sg1, sg2]

// ✅ Map: Key-value associations
tags = {
  Environment = "prod"
  Team        = "platform"
}

Key Takeaways

  • ✅ Use nested attributes for complex structures
  • ✅ Choose appropriate collection types (list/set/map)
  • ✅ Keep nesting depth reasonable (2-3 levels)
  • ✅ Implement cross-attribute validation
  • ✅ Provide clear documentation
  • ✅ Handle null/unknown values properly
  • ✅ Build complex values methodically
  • ✅ Test complex attribute scenarios

What’s Next?

Learn performance optimization techniques for provider efficiency.

Next: Performance Optimization →


Previous: ← Provider-Defined Functions
Next: Performance Optimization →

Last updated: July 1, 2026

Updated: