Python library and CLI tool for generating Terraform JSON configurations using full Python programming capabilities
System for compiling registered Terraform objects to JSON format and extending functionality through transformation hooks. This includes global object registry management, compilation control, and an extensible hook system for modifying objects during the compilation process.
Functions for converting registered Terraform objects into Terraform JSON configuration format and managing the global object registry.
def compile() -> dict:
"""
Compile all registered Terraform objects to dictionary format.
Returns:
dict - Complete Terraform configuration as Python dictionary
The compilation process:
1. Collects all registered TFObject instances
2. Applies transformation hooks for each object type
3. Organizes objects by Terraform type (resource, data, provider, etc.)
4. Generates properly formatted Terraform JSON
Example:
terraform_json = compile()
print(terraform_json) # Complete Terraform configuration
"""
def reset() -> None:
"""
Clear all registered objects from the global registry.
Use this function to start fresh configurations or between test runs.
After calling reset(), previously created objects will no longer be
included in compilation output.
Example:
# Create some resources
Resource('aws_instance', 'web')
compile() # Includes the instance
reset() # Clear registry
compile() # Empty configuration
"""Extensible system for transforming Terraform objects during compilation, enabling custom modifications, validation, and schema transformations.
class TFObject:
@classmethod
def add_hook(cls, object_type: str, hook_function: Callable[[dict], dict]) -> None:
"""
Add a transformation hook for objects of the specified type.
Parameters:
- object_type: str - Terraform object type to hook (e.g., 'resource', 'provider')
- hook_function: Callable - Function that transforms the complete output dictionary
Hook function signature:
def hook_function(output: dict) -> dict:
# output: Complete Terraform output dictionary for this object type
# Return: Modified output dictionary
Note: This is the low-level hook interface. For easier use with specific
resource types, use Resource.add_hook() or Provider.add_hook() instead.
Example:
def my_hook(output):
# Modify entire resource section
if 'resource' in output:
for resource_type in output['resource']:
for resource_name in output['resource'][resource_type]:
# Add common tags to all resources
output['resource'][resource_type][resource_name].setdefault('tags', {})
output['resource'][resource_type][resource_name]['tags']['ManagedBy'] = 'Terraformpy'
return output
TFObject.add_hook('resource', my_hook)
"""
# High-level hook interfaces for specific object types:
# For Resource and Data objects (TypedObject subclasses):
Resource.add_hook(resource_type: str, hook_function: Callable[[str, dict], dict]) -> None
Data.add_hook(data_type: str, hook_function: Callable[[str, dict], dict]) -> None
# For Provider, Variable, Output, Module objects (NamedObject subclasses):
Provider.add_hook(provider_name: str, hook_function: Callable[[dict], dict]) -> None
Variable.add_hook(variable_name: str, hook_function: Callable[[dict], dict]) -> None
Output.add_hook(output_name: str, hook_function: Callable[[dict], dict]) -> None
Module.add_hook(module_name: str, hook_function: Callable[[dict], dict]) -> None
@classmethod
def compile(cls) -> dict:
"""
Class method version of compile() function.
Returns:
dict - Complete Terraform configuration dictionary
This is equivalent to the standalone compile() function.
"""
@classmethod
def reset(cls) -> None:
"""
Class method version of reset() function.
Clears all registered objects from the global registry.
This is equivalent to the standalone reset() function.
"""from terraformpy import Resource, Variable, Output, compile, reset
# Create Terraform objects
env = Variable('environment', default='dev')
web_server = Resource('aws_instance', 'web',
instance_type='t3.micro',
tags={'Environment': env.ref()}
)
Output('instance_id', value=web_server.id)
# Compile to JSON
terraform_config = compile()
print(terraform_config)
# Clear for next configuration
reset()
# Verify registry is empty
empty_config = compile()
print(empty_config) # Should be empty or minimalfrom terraformpy import Resource, TFObject
# Hook to add standard tags to all EC2 instances
def add_standard_tags(object_id, attrs):
"""Add standard tags to EC2 instances."""
attrs['tags'] = attrs.get('tags', {})
attrs['tags'].update({
'ManagedBy': 'Terraformpy',
'CreatedAt': '2023-01-01' # Could use datetime.now()
})
return attrs
# Hook to enforce naming convention
def enforce_naming_convention(object_id, attrs):
"""Ensure instance names follow convention."""
tags = attrs.get('tags', {})
if 'Name' in tags and not tags['Name'].startswith('app-'):
tags['Name'] = f"app-{tags['Name']}"
attrs['tags'] = tags
return attrs
# Register hooks
TFObject.add_hook('aws_instance', add_standard_tags)
TFObject.add_hook('aws_instance', enforce_naming_convention)
# Create resources - hooks will be applied automatically
web_server = Resource('aws_instance', 'web',
instance_type='t3.micro',
tags={'Name': 'webserver', 'Environment': 'prod'}
)
# Compile - hooks transform the resource
config = compile()
# The instance will have:
# - Name: 'app-webserver' (enforced naming)
# - ManagedBy: 'Terraformpy' (standard tag)
# - CreatedAt: '2023-01-01' (standard tag)
# - Environment: 'prod' (original tag preserved)from terraformpy import Resource
from terraformpy.hooks.aws import install_aws_security_group_attributes_as_blocks_hook
# Install the AWS security group hook
install_aws_security_group_attributes_as_blocks_hook()
# Create security group - hook will transform rule format
web_sg = Resource('aws_security_group', 'web',
name='web-sg',
description='Security group for web servers',
# These attributes will be transformed by the hook
ingress=[
{
'from_port': 80,
'to_port': 80,
'protocol': 'tcp',
'cidr_blocks': ['0.0.0.0/0']
},
{
'from_port': 443,
'to_port': 443,
'protocol': 'tcp',
'cidr_blocks': ['0.0.0.0/0']
}
]
)
# Compile - AWS hook transforms ingress rules to proper format
config = compile()from terraformpy import Resource, TFObject
def validate_instance_type(object_id, attrs):
"""Validate EC2 instance types for cost control."""
allowed_types = ['t3.nano', 't3.micro', 't3.small']
instance_type = attrs.get('instance_type', '')
if instance_type not in allowed_types:
raise ValueError(f"Instance type {instance_type} not allowed for {object_id}. "
f"Allowed types: {allowed_types}")
return attrs
def add_cost_center(object_id, attrs):
"""Ensure all resources have cost center tag."""
attrs['tags'] = attrs.get('tags', {})
if 'CostCenter' not in attrs['tags']:
attrs['tags']['CostCenter'] = 'engineering'
return attrs
# Register validation and tagging hooks
TFObject.add_hook('aws_instance', validate_instance_type)
TFObject.add_hook('aws_instance', add_cost_center)
# This will pass validation
web_server = Resource('aws_instance', 'web',
instance_type='t3.micro',
ami='ami-12345678'
)
# This will raise ValueError during compilation
try:
expensive_server = Resource('aws_instance', 'expensive',
instance_type='c5.24xlarge', # Not in allowed list
ami='ami-12345678'
)
compile() # Validation hook will raise error here
except ValueError as e:
print(f"Validation failed: {e}")from terraformpy import Resource, TFObject
def add_dependency_tags(object_id, attrs):
"""Add dependency information to resources."""
# Extract resource type and name from object_id
# Format: "resource_type.resource_name"
resource_type, resource_name = object_id.split('.', 1)
attrs['tags'] = attrs.get('tags', {})
attrs['tags']['ResourceType'] = resource_type
attrs['tags']['ResourceName'] = resource_name
return attrs
# Hook to automatically add VPC tags to subnet resources
def add_vpc_context(object_id, attrs):
"""Add VPC context to subnet resources."""
if 'aws_subnet' in object_id:
attrs['tags'] = attrs.get('tags', {})
attrs['tags']['NetworkTier'] = 'private' if 'private' in object_id else 'public'
return attrs
# Register hooks for multiple resource types
for resource_type in ['aws_instance', 'aws_security_group', 'aws_subnet']:
TFObject.add_hook(resource_type, add_dependency_tags)
TFObject.add_hook('aws_subnet', add_vpc_context)
# Create resources
private_subnet = Resource('aws_subnet', 'private_web',
vpc_id='${aws_vpc.main.id}',
cidr_block='10.0.1.0/24'
)
web_server = Resource('aws_instance', 'web_server',
subnet_id=private_subnet.id,
instance_type='t3.micro'
)
# Compile with hooks applied
config = compile()from terraformpy import Resource, Variable, TFObject
import os
def environment_specific_hook(object_id, attrs):
"""Apply different transformations based on environment."""
environment = os.getenv('ENVIRONMENT', 'dev')
attrs['tags'] = attrs.get('tags', {})
attrs['tags']['Environment'] = environment
if environment == 'production':
# Production-specific settings
attrs['tags']['Backup'] = 'daily'
attrs['tags']['Monitoring'] = 'enabled'
# Enable detailed monitoring for production instances
if 'aws_instance' in object_id:
attrs['monitoring'] = True
attrs['ebs_optimized'] = True
elif environment == 'development':
# Development-specific settings
attrs['tags']['Backup'] = 'none'
attrs['tags']['AutoShutdown'] = '6pm'
return attrs
# Register environment-aware hook
for resource_type in ['aws_instance', 'aws_rds_instance', 'aws_efs_file_system']:
TFObject.add_hook(resource_type, environment_specific_hook)
# Create resources - hooks will apply environment-specific settings
web_server = Resource('aws_instance', 'web',
instance_type='t3.micro',
ami='ami-12345678'
)
# Environment determines the final configuration
config = compile()from terraformpy.hooks.aws import (
install_aws_security_group_attributes_as_blocks_hook,
fill_in_optional_aws_security_group_rules_attrs
)
# Install the built-in AWS security group transformation hook
install_aws_security_group_attributes_as_blocks_hook()
# The hook function can also be used directly
def custom_sg_hook(object_id, attrs):
"""Custom wrapper around AWS security group hook."""
# Apply the built-in transformation first
attrs = fill_in_optional_aws_security_group_rules_attrs(object_id, attrs)
# Add custom logic
attrs['tags'] = attrs.get('tags', {})
attrs['tags']['SecurityLevel'] = 'standard'
return attrs
# Register custom hook that includes built-in functionality
TFObject.add_hook('aws_security_group', custom_sg_hook)Install with Tessl CLI
npx tessl i tessl/pypi-terraformpy