How to Work with Terraform Types¶
This guide shows you how to use pyvider.cty with Terraform, including parsing Terraform type strings and working with Terraform data structures.
Parsing Terraform Type Strings¶
Terraform uses type strings like list(string) or object({name=string,age=number}). pyvider.cty can parse these:
from pyvider.cty.parser import parse_tf_type_to_ctytype
# Parse simple types
string_type = parse_tf_type_to_ctytype("string")
number_type = parse_tf_type_to_ctytype("number")
bool_type = parse_tf_type_to_ctytype("bool")
# Parse collection types
list_type = parse_tf_type_to_ctytype("list(string)")
map_type = parse_tf_type_to_ctytype("map(number)")
set_type = parse_tf_type_to_ctytype("set(string)")
# Parse complex object types
object_type = parse_tf_type_to_ctytype(
"object({name=string,age=number,active=bool})"
)
# Use the parsed type
data = {"name": "Alice", "age": 30, "active": True}
value = object_type.validate(data)
Common Terraform Patterns¶
Variable Validation¶
from pyvider.cty.parser import parse_tf_type_to_ctytype
def validate_terraform_variable(var_type_string, var_value):
"""Validate a Terraform variable against its type."""
try:
var_type = parse_tf_type_to_ctytype(var_type_string)
return var_type.validate(var_value)
except Exception as e:
raise ValueError(f"Variable validation failed: {e}")
# Example
vpc_config_type = "object({cidr=string,subnets=list(string)})"
vpc_config_data = {
"cidr": "10.0.0.0/16",
"subnets": ["10.0.1.0/24", "10.0.2.0/24"]
}
validated = validate_terraform_variable(vpc_config_type, vpc_config_data)
Resource Schema¶
# Define a resource schema matching Terraform
resource_schema = parse_tf_type_to_ctytype("""
object({
name = string,
instance_type = string,
ami = string,
tags = map(string)
})
""")
# Validate resource configuration
resource_config = {
"name": "web-server",
"instance_type": "t2.micro",
"ami": "ami-12345",
"tags": {
"Environment": "prod",
"Owner": "ops-team"
}
}
resource_value = resource_schema.validate(resource_config)
Working with Terraform Modules¶
Module Input Variables¶
from pyvider.cty.parser import parse_tf_type_to_ctytype
# Parse module variable types
module_vars = {
"vpc_cidr": parse_tf_type_to_ctytype("string"),
"availability_zones": parse_tf_type_to_ctytype("list(string)"),
"enable_nat": parse_tf_type_to_ctytype("bool"),
"tags": parse_tf_type_to_ctytype("map(string)")
}
# Validate module inputs
module_inputs = {
"vpc_cidr": "10.0.0.0/16",
"availability_zones": ["us-east-1a", "us-east-1b"],
"enable_nat": True,
"tags": {"Project": "demo"}
}
validated_inputs = {}
for var_name, var_type in module_vars.items():
validated_inputs[var_name] = var_type.validate(module_inputs[var_name])
Module Outputs¶
# Define output types
output_schema = parse_tf_type_to_ctytype("""
object({
vpc_id = string,
subnet_ids = list(string),
nat_gateway_id = string
})
""")
# Validate module outputs
module_outputs = {
"vpc_id": "vpc-12345",
"subnet_ids": ["subnet-111", "subnet-222"],
"nat_gateway_id": "nat-99999"
}
validated_outputs = output_schema.validate(module_outputs)
Complex Terraform Types¶
Nested Objects¶
# Parse complex nested structure
nested_type = parse_tf_type_to_ctytype("""
object({
network = object({
vpc_id = string,
subnets = list(object({
id = string,
cidr = string,
az = string
}))
}),
compute = object({
instance_type = string,
count = number
})
})
""")
# Validate nested data
infrastructure = {
"network": {
"vpc_id": "vpc-12345",
"subnets": [
{"id": "subnet-1", "cidr": "10.0.1.0/24", "az": "us-east-1a"},
{"id": "subnet-2", "cidr": "10.0.2.0/24", "az": "us-east-1b"}
]
},
"compute": {
"instance_type": "t2.micro",
"count": 3
}
}
infra_value = nested_type.validate(infrastructure)
Optional Attributes¶
from pyvider.cty import CtyObject, CtyString, CtyNumber, CtyBool
# Define type with optional fields (Terraform style)
instance_type = CtyObject(
attribute_types={
"ami": CtyString(),
"instance_type": CtyString(),
"key_name": CtyString(),
"monitoring": CtyBool(),
"user_data": CtyString()
},
optional_attributes={"key_name", "monitoring", "user_data"}
)
# Valid with only required fields
minimal_instance = instance_type.validate({
"ami": "ami-12345",
"instance_type": "t2.micro"
})
# Optional fields are null
assert minimal_instance['key_name'].is_null
Provider Development¶
Resource State¶
from pyvider.cty.codec import cty_to_msgpack, cty_from_msgpack
# Define resource state schema
state_schema = parse_tf_type_to_ctytype("""
object({
id = string,
name = string,
status = string,
created_at = string
})
""")
# Create and serialize state
state_data = {
"id": "resource-123",
"name": "my-resource",
"status": "active",
"created_at": "2025-01-24T10:00:00Z"
}
state_value = state_schema.validate(state_data)
# Serialize for Terraform
state_msgpack = cty_to_msgpack(state_value, state_schema)
# Later, deserialize
restored_state = cty_from_msgpack(state_msgpack, state_schema)
Schema Definition¶
# Define provider schema
provider_schema = {
"resources": {
"example_instance": parse_tf_type_to_ctytype("""
object({
name = string,
size = string,
region = string,
tags = map(string)
})
"""),
"example_network": parse_tf_type_to_ctytype("""
object({
cidr = string,
name = string
})
""")
}
}
def validate_resource(resource_type, config):
"""Validate resource configuration."""
if resource_type not in provider_schema["resources"]:
raise ValueError(f"Unknown resource type: {resource_type}")
schema = provider_schema["resources"][resource_type]
return schema.validate(config)
Handling Terraform Data¶
Reading terraform.tfstate¶
import json
def load_terraform_state(state_file):
"""Load and validate Terraform state file."""
with open(state_file) as f:
state = json.load(f)
# Extract resources
resources = []
for resource in state.get("resources", []):
# Parse resource schema from type
resource_type = resource["type"]
instances = resource.get("instances", [])
for instance in instances:
# Validate instance attributes
attributes = instance.get("attributes", {})
resources.append({
"type": resource_type,
"attributes": attributes
})
return resources
Working with HCL¶
# While pyvider.cty doesn't parse HCL directly,
# you can work with the parsed structures
# After parsing HCL to Python dict (using python-hcl2 or similar):
hcl_data = {
"resource": {
"aws_instance": {
"web": {
"ami": "ami-12345",
"instance_type": "t2.micro",
"tags": {"Name": "web-server"}
}
}
}
}
# Extract and validate resource config
resource_config = hcl_data["resource"]["aws_instance"]["web"]
instance_schema = parse_tf_type_to_ctytype("""
object({
ami = string,
instance_type = string,
tags = map(string)
})
""")
validated = instance_schema.validate(resource_config)
Best Practices¶
- Parse type strings once: Cache parsed type schemas
- Validate early: Check types before Terraform execution
- Use MessagePack: For Terraform provider communication
- Handle optional fields: Many Terraform attributes are optional
- Test with real data: Use actual Terraform configs for testing
- Document schemas: Keep schema documentation up to date
Common Patterns¶
Configuration Validator¶
class TerraformConfigValidator:
"""Validate Terraform configurations."""
def __init__(self):
self.schemas = {}
def register_schema(self, name, type_string):
"""Register a schema for validation."""
self.schemas[name] = parse_tf_type_to_ctytype(type_string)
def validate(self, schema_name, data):
"""Validate data against a registered schema."""
if schema_name not in self.schemas:
raise ValueError(f"Unknown schema: {schema_name}")
schema = self.schemas[schema_name]
return schema.validate(data)
# Usage
validator = TerraformConfigValidator()
validator.register_schema("vpc", "object({cidr=string,name=string})")
validator.register_schema("subnet", "object({cidr=string,vpc_id=string})")
vpc_config = validator.validate("vpc", {
"cidr": "10.0.0.0/16",
"name": "main-vpc"
})
Type Introspection¶
def describe_terraform_type(type_string):
"""Describe a Terraform type in detail."""
cty_type = parse_tf_type_to_ctytype(type_string)
# Get type information
info = {
"type_name": type(cty_type).__name__,
"is_collection": hasattr(cty_type, 'element_type'),
"is_structural": hasattr(cty_type, 'attribute_types')
}
if info["is_structural"]:
info["attributes"] = list(cty_type.attribute_types.keys())
return info
# Usage
type_info = describe_terraform_type("object({name=string,count=number})")
print(type_info)