Creating Terraform Scripts from Draw.io XML Files!
Introduction
The concept and the idea originated from a realization that drawings from open-source tools, such as Draw.io, are essentially structured XML files. If the diagram explicitly maps out an infrastructure architecture, it would be logical to analyze that XML file and programmatically generate corresponding Terraform scripts.
> Disclaimer: note that this application is currently in its alpha stage; as an early-access release, it requires more rigorous, “bulletproof” testing and further functional enhancements before it reaches full maturity. This tool marks the first in a planned series of automation utilities I am developing to significantly accelerate my personal productivity and streamline technical workflows.
A word on the external tool used: Draw.io
The core of this application’s utility is its deep integration with Draw.io, a premier open-source diagramming tool. Rather than forcing users into a proprietary design environment, the generator leverages Draw.io’s flexible, web-based interface and its extensive library of cloud architecture shapes. By treating the exported Draw.io XML as a structured data source, the application can precisely map visual components and their interconnections to valid Terraform resource blocks. This heavy reliance on an open-source standard ensures that users can design their infrastructure using a familiar, free, and highly accessible tool, while the backend handles the complex translation from a visual drawing to production-ready code.
Terraform Script Generator: A Diagram-to-Code Pipeline
The Terraform Script Generator is a web-based utility designed to streamline the translation of cloud infrastructure diagrams, specifically those created in Draw.io, into deployable Terraform configurations. This automation bridges the gap between architectural planning and infrastructure implementation, particularly for Cloud platforms like IBM (but not only), but adaptable to other platforms as well. The generator employs a client-server architecture, utilizing a user-friendly frontend to receive design inputs and a robust backend to handle the analytical heavy lifting.
terraform-builder/
│
├── backend/ # Backend Python modules
│ ├── app.py # Flask API server
│ ├── drawio_parser.py # XML parser for Draw.io
│ └── terraform_generator.py # Terraform code generator
│
├── frontend/ # Frontend web files
│ ├── index.html # Main UI
│ ├── styles.css # Styling
│ └── app.js # Client-side logic
│
├── k8s/ # Kubernetes deployment files
│ ├── deployment.yaml # Deployment and Service manifests
│ ├── configmap.yaml # Application configuration
│ └── README.md # Kubernetes deployment guide
│
├── Docs/ # Documentation
│ └── Architecture.md # This file
│
├── scripts/ # Utility scripts
│ ├── start.sh # Start application
│ ├── stop.sh # Stop application
│ └── validate_terraform.py # Terraform validation
│
├── _diagrams/ # Example diagrams
│ └── d1.drawio # Sample IBM Cloud diagram
│
├── _images/ # Project images
├── _sources/ # Reference sources
│
├── Dockerfile # Docker image definition
├── requirements.txt # Python dependencies
├── .gitignore # Git ignore rules
└── README.md # Project documentation
The Backend: Analytical Engine with Flask and Python
At the heart of the system is a Python backend built with the Flask web framework. This application is responsible for orchestrating the overall process, from serving the main user interface to exposing REST API endpoints for diagram parsing and code generation. It integrates two key internal modules: DrawioParser and TerraformGenerator. The application logic is designed for extensibility, capable of handling complex diagrams and multi-stage processing, supporting features like file uploading, health checks, and state management via JSON for multi-step workflows.
# app.py - Main Flask application for the Terraform Builder
"""
Flask Backend for Terraform Builder Application
Provides API endpoints for parsing Draw.io diagrams and generating Terraform code
"""
from flask import Flask, request, jsonify, send_from_directory
from flask_cors import CORS
import os
from drawio_parser import DrawioParser
from terraform_generator import TerraformGenerator
app = Flask(__name__, static_folder='../frontend', static_url_path='')
CORS(app)
# Initialize parsers and generators
parser = DrawioParser()
generator = TerraformGenerator()
@app.route('/')
def index():
"""Serve the main application page"""
return send_from_directory(app.static_folder, 'index.html')
@app.route('/api/parse', methods=['POST'])
def parse_diagram():
"""Parse a Draw.io diagram and return components"""
try:
if 'file' in request.files:
# Handle file upload
file = request.files['file']
if file.filename == '':
return jsonify({'error': 'No file selected'}), 400
# Save temporarily and parse
temp_path = '/tmp/temp_diagram.drawio'
file.save(temp_path)
result = parser.parse_file(temp_path)
os.remove(temp_path)
elif 'content' in request.json:
# Handle XML content
content = request.json['content']
result = parser.parse_content(content)
else:
return jsonify({'error': 'No diagram data provided'}), 400
return jsonify({
'success': True,
'components': result['components'],
'connections': result['connections'],
'summary': parser.get_resource_summary()
})
except Exception as e:
return jsonify({'error': str(e)}), 500
@app.route('/api/generate', methods=['POST'])
def generate_terraform():
"""Generate Terraform code from parsed components"""
try:
data = request.json
components = data.get('components', [])
provider = data.get('provider', 'ibm')
if not components:
return jsonify({'error': 'No components provided'}), 400
# Create generator with selected provider
gen = TerraformGenerator(provider=provider)
# Generate Terraform files
terraform_files = gen.generate_from_components(components)
return jsonify({
'success': True,
'files': terraform_files
})
except Exception as e:
return jsonify({'error': str(e)}), 500
@app.route('/api/parse-and-generate', methods=['POST'])
def parse_and_generate():
"""Parse diagram and generate Terraform code in one step"""
try:
provider = 'ibm' # Default provider
custom_provider_url = None
if 'file' in request.files:
# Handle file upload
file = request.files['file']
if file.filename == '':
return jsonify({'error': 'No file selected'}), 400
# Save temporarily and parse
temp_path = '/tmp/temp_diagram.drawio'
file.save(temp_path)
result = parser.parse_file(temp_path)
os.remove(temp_path)
# Get provider from form data if available
provider = request.form.get('provider', 'ibm')
custom_provider_url = request.form.get('custom_provider_url')
elif 'content' in request.json:
# Handle XML content
content = request.json['content']
provider = request.json.get('provider', 'ibm')
custom_provider_url = request.json.get('custom_provider_url')
result = parser.parse_content(content)
else:
return jsonify({'error': 'No diagram data provided'}), 400
# Create generator with selected provider and custom URL if provided
gen = TerraformGenerator(provider=provider, custom_provider_url=custom_provider_url)
# Generate Terraform files
terraform_files = gen.generate_from_components(result['components'])
response_data = {
'success': True,
'components': result['components'],
'connections': result['connections'],
'summary': parser.get_resource_summary(),
'terraform': terraform_files,
'provider': provider
}
# Add custom provider info if used
if custom_provider_url and gen.custom_provider_config:
response_data['custom_provider'] = gen.custom_provider_config
# Add warning if IBM components detected with non-IBM provider
if provider != 'ibm' and hasattr(gen, 'skipped_resources') and gen.skipped_resources:
response_data['warning'] = {
'message': f'This diagram contains {len(gen.skipped_resources)} IBM Cloud-specific component(s) that were not generated for the {provider.upper()} provider.',
'suggestion': 'Consider selecting "IBM Cloud" as the provider to generate these resources.',
'skipped_count': len(gen.skipped_resources)
}
return jsonify(response_data)
except Exception as e:
return jsonify({'error': str(e)}), 500
@app.route('/api/health', methods=['GET'])
def health_check():
"""Health check endpoint"""
return jsonify({
'status': 'healthy',
'service': 'terraform-builder-api'
})
@app.route('/api/diagram-preview', methods=['POST'])
def diagram_preview():
"""Generate a preview URL for the diagram"""
try:
data = request.json
content = data.get('content', '')
if not content:
return jsonify({'error': 'No diagram content provided'}), 400
# Return the content for client-side rendering
return jsonify({
'success': True,
'content': content,
'preview_available': True
})
except Exception as e:
return jsonify({'error': str(e)}), 500
@app.route('/api/save-files', methods=['POST'])
def save_files():
"""Save generated Terraform files to timestamped output folder"""
try:
from datetime import datetime
import shutil
data = request.json
files = data.get('files', {})
diagram_name = data.get('diagram_name', 'diagram')
if not files:
return jsonify({'error': 'No files provided'}), 400
# Remove file extension from diagram name
diagram_name = diagram_name.replace('.drawio', '').replace('.xml', '')
# Create timestamp
timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
# Create output directory structure
output_base = 'output'
output_dir = os.path.join(output_base, f"{diagram_name}_{timestamp}")
# Create directory if it doesn't exist
os.makedirs(output_dir, exist_ok=True)
# Save each file
saved_files = []
for filename, content in files.items():
file_path = os.path.join(output_dir, filename)
with open(file_path, 'w') as f:
f.write(content)
saved_files.append(file_path)
return jsonify({
'success': True,
'output_dir': output_dir,
'files': saved_files,
'message': f'Files saved to {output_dir}'
})
except Exception as e:
return jsonify({'error': str(e)}), 500
@app.route('/api/save-and-zip-files', methods=['POST'])
def save_and_zip_files():
"""Save generated Terraform files to timestamped output folder and return as ZIP"""
try:
from datetime import datetime
import shutil
import zipfile
from io import BytesIO
from flask import send_file
data = request.json
files = data.get('files', {})
diagram_name = data.get('diagram_name', 'diagram')
if not files:
return jsonify({'error': 'No files provided'}), 400
# Remove file extension from diagram name
diagram_name = diagram_name.replace('.drawio', '').replace('.xml', '')
# Create timestamp
timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
folder_name = f"{diagram_name}_{timestamp}"
# Create output directory structure
output_base = 'output'
output_dir = os.path.join(output_base, folder_name)
# Create directory if it doesn't exist
os.makedirs(output_dir, exist_ok=True)
# Save each file to disk
for filename, content in files.items():
file_path = os.path.join(output_dir, filename)
with open(file_path, 'w') as f:
f.write(content)
# Create ZIP file in memory
zip_buffer = BytesIO()
with zipfile.ZipFile(zip_buffer, 'w', zipfile.ZIP_DEFLATED) as zip_file:
for filename, content in files.items():
# Add files to ZIP with folder structure
zip_file.writestr(f"{folder_name}/{filename}", content)
zip_buffer.seek(0)
zip_name = f"{folder_name}.zip"
# Create response with ZIP file
return send_file(
zip_buffer,
mimetype='application/zip',
as_attachment=True,
download_name=zip_name
)
except Exception as e:
return jsonify({'error': str(e)}), 500
if __name__ == '__main__':
# Run the Flask app
port = int(os.environ.get('PORT', 5002))
debug = os.environ.get('DEBUG', 'True').lower() == 'true'
print(f"Starting Terraform Builder API on port {port}")
print(f"Debug mode: {debug}")
app.run(host='0.0.0.0', port=port, debug=debug)
# Made with Bob
Diagram Interpretation: Parsing the Draw.io XML
Draw.io architecture diagrams are essentially saved as structured XML files. The backend utilizes the standard xml.etree.ElementTree library to read this XML and extract the critical components. It parses the nested mxCell elements to identify individual cloud resources based on specific, predefined shape patterns and styles (e.g., IBM Cloud shapes). This interpreter doesn't just list components; it also detects connections between them to understand the relationships and generate resource summaries, laying the foundation for accurate script generation.
# drawio_parser.py - Parser for Draw.io XML files containing IBM Cloud diagrams
"""
Draw.io XML Parser for IBM Cloud Diagrams
Parses Draw.io XML files and extracts IBM Cloud components
"""
import xml.etree.ElementTree as ET
from typing import Dict, List, Any
class DrawioParser:
"""Parser for Draw.io XML files containing cloud diagrams"""
# Mapping of Draw.io shapes to generic resource types
# These will be converted to provider-specific resources by the generator
CLOUD_SHAPES = {
# Core Cloud resources
'mxgraph.ibm_cloud.ibm-cloud': 'resource_group',
'mxgraph.ibm.box;prType=cloud': 'resource_group',
# VPC and Networking
'mxgraph.ibm_cloud.ibm-cloud--vpc': 'vpc',
'mxgraph.ibm.box;prType=vpc': 'vpc',
'mxgraph.ibm.vpc': 'vpc',
'mxgraph.ibm_cloud.ibm-cloud--subnets': 'subnet',
'mxgraph.ibm_cloud.floating-ip': 'floating_ip',
'mxgraph.ibm_cloud.load-balancer--vpc': 'load_balancer',
'mxgraph.ibm_cloud.network--public': 'public_gateway',
# Compute
'mxgraph.ibm_cloud.server--proxy': 'compute_instance',
'mxgraph.ibm_cloud.application--web': 'app_service',
# Storage and Databases
'mxgraph.ibm_cloud.data--base': 'database',
# Watson Services (IBM-specific, will be skipped for other providers)
'mxgraph.ibm_cloud.ibm-watsonx--orchestrate': 'watson_service',
'mxgraph.ibm_cloud.watsonx-governance': 'watson_service',
'mxgraph.ibm_cloud.watsonx-ai': 'watson_service',
# Zones (for organizational purposes)
'mxgraph.ibm.box;prType=zone': 'zone',
}
# Service names for IBM Cloud resources
SERVICE_NAMES = {
'watsonx--orchestrate': 'watsonx-orchestrate',
'watsonx-governance': 'watsonx-governance',
'watsonx-ai': 'watsonx-ai',
}
def __init__(self):
self.components = []
self.connections = []
def parse_file(self, file_path: str) -> Dict[str, Any]:
"""Parse a Draw.io XML file and extract IBM Cloud components"""
try:
tree = ET.parse(file_path)
root = tree.getroot()
# Find all mxCell elements
for diagram in root.findall('.//diagram'):
model = diagram.find('.//mxGraphModel')
if model is not None:
self._parse_model(model)
return {
'components': self.components,
'connections': self.connections
}
except Exception as e:
raise Exception(f"Error parsing Draw.io file: {str(e)}")
def parse_content(self, xml_content: str) -> Dict[str, Any]:
"""Parse Draw.io XML content string"""
try:
# Log the first 200 characters for debugging
print(f"[DEBUG] Received XML content (first 200 chars): {xml_content[:200]}")
print(f"[DEBUG] XML content length: {len(xml_content)}")
print(f"[DEBUG] XML content type: {type(xml_content)}")
# Validate content
if not xml_content or not xml_content.strip():
raise Exception("Empty XML content received")
if not xml_content.strip().startswith('<'):
raise Exception(f"Not a diagram file (error on line 1 at column 1: Start tag expected, '<' not found). Received: {xml_content[:100]}")
root = ET.fromstring(xml_content)
# Find all mxCell elements
for diagram in root.findall('.//diagram'):
model = diagram.find('.//mxGraphModel')
if model is not None:
self._parse_model(model)
return {
'components': self.components,
'connections': self.connections
}
except ET.ParseError as e:
print(f"[ERROR] XML Parse Error: {str(e)}")
print(f"[ERROR] Content that failed: {xml_content[:500]}")
raise Exception(f"Not a diagram file (error on line 1 at column 1: Start tag expected, '<' not found)")
except Exception as e:
print(f"[ERROR] General parsing error: {str(e)}")
raise Exception(f"Error parsing Draw.io content: {str(e)}")
def _parse_model(self, model: ET.Element):
"""Parse the mxGraphModel element"""
root_element = model.find('.//root')
if root_element is None:
return
for cell in root_element.findall('.//mxCell'):
self._parse_cell(cell)
def _parse_cell(self, cell: ET.Element):
"""Parse individual mxCell elements"""
cell_id = cell.get('id')
if not cell_id:
return
value = cell.get('value', '')
style = cell.get('style', '')
# Check if this is a cloud component
for shape_key, resource_type in self.CLOUD_SHAPES.items():
if shape_key in style:
component = self._extract_component(cell_id, value, style, shape_key, resource_type)
if component:
self.components.append(component)
break
# Check for connections (edges)
source = cell.get('source')
target = cell.get('target')
if source and target:
self.connections.append({
'source': source,
'target': target,
'id': cell_id
})
def _extract_component(self, cell_id: str, value: str, style: str, shape_key: str, resource_type: str) -> Dict[str, Any]:
"""Extract component information from a cell"""
# Determine the service name
service_name = None
for key, service in self.SERVICE_NAMES.items():
if key in value.lower() or key in shape_key:
service_name = service
break
# Extract geometry if available
geometry = self._extract_geometry(style)
component = {
'id': cell_id,
'name': value.strip() if value else self._generate_name(shape_key),
'type': resource_type,
'shape': shape_key,
'service': service_name,
'geometry': geometry
}
return component
def _extract_geometry(self, style: str) -> Dict[str, Any]:
"""Extract geometry information from style string"""
geometry = {}
# This is a simplified extraction - can be enhanced
return geometry
def _generate_name(self, shape_key: str) -> str:
"""Generate a default name based on the shape type"""
# Extract the last part of the shape key
parts = shape_key.split('.')
if len(parts) > 0:
name = parts[-1].replace('-', '_').replace('__', '_')
return name
return 'unnamed_resource'
def get_resource_summary(self) -> Dict[str, int]:
"""Get a summary of resources by type"""
summary = {}
for component in self.components:
resource_type = component['type']
summary[resource_type] = summary.get(resource_type, 0) + 1
return summary
# Made with Bob
Code Generation: Turning Components into Terraform

Once the diagrams are decoded into component and connection lists, the backend initiates the script generation phase. It programmatically creates three core Terraform configuration files: main.tf for main resources, variables.tf for parameterization, and providers.tf for provider definitions. This process involves sophisticated logic to map Draw.io shapes to corresponding Terraform resource blocks, sanitize names, manage variables, and format the output according to the HashiCorp Configuration Language (HCL). The system is built for modularity, allowing it to generate standardized code for various resource types and cloud providers.
# terraform_generator.py - Generates Terraform code from parsed diagrams
"""
Terraform Code Generator for IBM Cloud Resources
Generates Terraform configuration files from parsed Draw.io diagrams
"""
from typing import Dict, List, Any
import json
class TerraformGenerator:
"""Generates Terraform configuration for multiple cloud providers"""
# Resource type mappings: generic type -> provider-specific resource type
RESOURCE_MAPPINGS = {
'vpc': {
'ibm': 'ibm_is_vpc',
'aws': 'aws_vpc',
'google': 'google_compute_network',
'azure': 'azurerm_virtual_network',
'kubernetes': None # K8s doesn't have VPC concept
},
'subnet': {
'ibm': 'ibm_is_subnet',
'aws': 'aws_subnet',
'google': 'google_compute_subnetwork',
'azure': 'azurerm_subnet',
'kubernetes': None
},
'compute_instance': {
'ibm': 'ibm_is_instance',
'aws': 'aws_instance',
'google': 'google_compute_instance',
'azure': 'azurerm_virtual_machine',
'kubernetes': 'kubernetes_deployment'
},
'load_balancer': {
'ibm': 'ibm_is_lb',
'aws': 'aws_lb',
'google': 'google_compute_forwarding_rule',
'azure': 'azurerm_lb',
'kubernetes': 'kubernetes_service'
},
'database': {
'ibm': 'ibm_database',
'aws': 'aws_db_instance',
'google': 'google_sql_database_instance',
'azure': 'azurerm_postgresql_server',
'kubernetes': 'kubernetes_stateful_set'
},
'resource_group': {
'ibm': 'ibm_resource_group',
'aws': None, # AWS uses tags instead
'google': 'google_project',
'azure': 'azurerm_resource_group',
'kubernetes': 'kubernetes_namespace'
},
'floating_ip': {
'ibm': 'ibm_is_floating_ip',
'aws': 'aws_eip',
'google': 'google_compute_address',
'azure': 'azurerm_public_ip',
'kubernetes': None
},
'public_gateway': {
'ibm': 'ibm_is_public_gateway',
'aws': 'aws_nat_gateway',
'google': 'google_compute_router_nat',
'azure': 'azurerm_nat_gateway',
'kubernetes': None
},
'app_service': {
'ibm': 'ibm_resource_instance',
'aws': 'aws_elastic_beanstalk_application',
'google': 'google_app_engine_application',
'azure': 'azurerm_app_service',
'kubernetes': 'kubernetes_deployment'
},
'watson_service': {
'ibm': 'ibm_resource_instance',
'aws': None, # AWS doesn't have Watson equivalent
'google': None, # Google doesn't have Watson equivalent
'azure': None, # Azure doesn't have Watson equivalent
'kubernetes': None
},
'zone': {
'ibm': None, # Organizational only
'aws': None,
'google': 'google_compute_zone',
'azure': None,
'kubernetes': None
}
}
# Provider configurations
PROVIDER_CONFIGS = {
'ibm': {
'source': 'IBM-Cloud/ibm',
'version': '>= 1.60.0',
'docs': 'https://registry.terraform.io/providers/IBM-Cloud/ibm/latest/docs'
},
'azure': {
'source': 'hashicorp/azurerm',
'version': '>= 3.0.0',
'docs': 'https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs'
},
'google': {
'source': 'hashicorp/google',
'version': '>= 5.0.0',
'docs': 'https://registry.terraform.io/providers/hashicorp/google/latest/docs'
},
'aws': {
'source': 'hashicorp/aws',
'version': '>= 5.0.0',
'docs': 'https://registry.terraform.io/providers/hashicorp/aws/latest/docs'
},
'kubernetes': {
'source': 'hashicorp/kubernetes',
'version': '>= 2.0.0',
'docs': 'https://registry.terraform.io/providers/hashicorp/kubernetes/latest/docs'
},
'terraform': {
'source': 'hashicorp/tfe',
'version': '>= 0.50.0',
'docs': 'https://registry.terraform.io/providers/hashicorp/tfe/latest/docs'
},
'instana': {
'source': 'instana/instana',
'version': '>= 3.0.0',
'docs': 'https://registry.terraform.io/providers/instana/instana/latest/docs'
},
'vault': {
'source': 'hashicorp/vault',
'version': '>= 4.0.0',
'docs': 'https://registry.terraform.io/providers/hashicorp/vault/latest/docs'
},
'turbonomic': {
'source': 'IBM/turbonomic',
'version': '>= 1.0.0',
'docs': 'https://registry.terraform.io/providers/IBM/turbonomic/latest/docs'
}
}
def __init__(self, provider='ibm', custom_provider_url=None):
self.provider = provider
self.custom_provider_url = custom_provider_url
self.custom_provider_config = None
# If custom provider URL is provided, parse it
if custom_provider_url and provider == 'other':
self.custom_provider_config = self._parse_custom_provider_url(custom_provider_url)
if self.custom_provider_config:
self.provider = self.custom_provider_config['name']
self.resources = []
self.variables = {}
self.providers = {}
def _parse_custom_provider_url(self, url: str) -> Dict[str, str] | None:
"""Parse a GitHub provider URL and extract provider information
Expected format: https://github.com/{org}/{repo}
Where repo is typically: terraform-provider-{name}
Returns:
Dict with 'name', 'source', 'version', and 'docs' keys
"""
import re
# Match GitHub URL pattern
pattern = r'https://github\.com/([\w-]+)/(terraform-provider-([\w-]+))'
match = re.match(pattern, url)
if not match:
return None
org = match.group(1)
repo = match.group(2)
provider_name = match.group(3)
# Build provider configuration
return {
'name': provider_name,
'source': f'{org}/{provider_name}',
'version': '>= 1.0.0', # Default version constraint
'docs': f'https://registry.terraform.io/providers/{org}/{provider_name}/latest/docs',
'github_url': url
}
def generate_from_components(self, components: List[Dict[str, Any]]) -> Dict[str, str]:
"""Generate Terraform files from parsed components"""
self.resources = []
self.variables = {}
# Process each component
for idx, component in enumerate(components):
resource = self._generate_resource(component, idx)
if resource:
self.resources.append(resource)
# Generate the three required files
main_tf = self._generate_main_tf()
variables_tf = self._generate_variables_tf()
providers_tf = self._generate_providers_tf()
return {
'main.tf': main_tf,
'variables.tf': variables_tf,
'providers.tf': providers_tf
}
def _generate_resource(self, component: Dict[str, Any], idx: int) -> Dict[str, Any] | None:
"""Generate a Terraform resource from a component"""
generic_type = component.get('type') # Generic type from parser
name = component.get('name', f'resource_{idx}')
service = component.get('service')
# Sanitize name for Terraform
tf_name = self._sanitize_name(name)
# Get provider-specific resource type from mapping
if generic_type not in self.RESOURCE_MAPPINGS:
return None
provider_resource_type = self.RESOURCE_MAPPINGS[generic_type].get(self.provider)
# If this resource type is not supported by the provider, skip it
if provider_resource_type is None:
if not hasattr(self, 'skipped_resources'):
self.skipped_resources = []
self.skipped_resources.append({
'name': name,
'type': generic_type,
'reason': f'Not supported by {self.provider} provider'
})
return None
# Generate provider-specific resource based on generic type
if generic_type == 'vpc':
return self._generate_vpc_resource_multi(tf_name, name, provider_resource_type)
elif generic_type == 'subnet':
return self._generate_subnet_resource(tf_name, name, provider_resource_type)
elif generic_type == 'compute_instance':
return self._generate_compute_resource(tf_name, name, provider_resource_type)
elif generic_type == 'load_balancer':
return self._generate_load_balancer_resource(tf_name, name, provider_resource_type)
elif generic_type == 'database':
return self._generate_database_resource(tf_name, name, provider_resource_type)
elif generic_type == 'resource_group':
return self._generate_resource_group_multi(tf_name, name, provider_resource_type)
elif generic_type == 'floating_ip':
return self._generate_floating_ip_resource(tf_name, name, provider_resource_type)
elif generic_type == 'public_gateway':
return self._generate_gateway_resource(tf_name, name, provider_resource_type)
elif generic_type == 'app_service':
return self._generate_app_service_resource(tf_name, name, provider_resource_type)
elif generic_type == 'watson_service':
return self._generate_watson_service_resource(tf_name, name, service or '', provider_resource_type)
return None
def _generate_vpc_resource(self, tf_name: str, display_name: str) -> Dict[str, Any]:
"""Generate VPC resource configuration"""
self.variables[f'{tf_name}_name'] = {
'description': f'Name for the {display_name} VPC',
'type': 'string',
'default': display_name
}
return {
'type': 'ibm_is_vpc',
'name': tf_name,
'config': {
'name': f'var.{tf_name}_name',
'resource_group': 'var.resource_group_id',
'tags': ['var.tags']
}
}
def _generate_resource_instance(self, tf_name: str, display_name: str, service: str) -> Dict[str, Any]:
"""Generate resource instance configuration for Watson services"""
if not service:
service = 'watsonx-ai'
# Map service names to IBM Cloud service names
service_map = {
'watsonx-orchestrate': 'watsonx-orchestrate',
'watsonx-governance': 'watsonx-governance',
'watsonx-ai': 'watsonx-ai'
}
service_name = service_map.get(service, service)
self.variables[f'{tf_name}_name'] = {
'description': f'Name for the {display_name} instance',
'type': 'string',
'default': display_name
}
self.variables[f'{tf_name}_plan'] = {
'description': f'Service plan for {display_name}',
'type': 'string',
'default': 'lite'
}
service_map = {
'watsonx-orchestrate': 'watsonx-orchestrate',
'watsonx-governance': 'watsonx-governance',
'watsonx-ai': 'watsonx-ai'
}
service_name = service_map.get(service, service)
return {
'type': 'ibm_resource_instance',
'name': tf_name,
'config': {
'name': f'var.{tf_name}_name',
'service': f'"{service_name}"',
'plan': f'var.{tf_name}_plan',
'location': 'var.region',
'resource_group_id': 'var.resource_group_id',
'tags': ['var.tags']
}
}
# Multi-provider resource generators
def _generate_vpc_resource_multi(self, tf_name: str, display_name: str, resource_type: str) -> Dict[str, Any]:
"""Generate VPC/Network resource for any provider"""
self.variables[f'{tf_name}_name'] = {
'description': f'Name for the {display_name} network',
'type': 'string',
'default': display_name
}
config = {'name': f'var.{tf_name}_name'}
# Provider-specific configurations
if self.provider == 'ibm':
config['resource_group'] = 'var.resource_group_id'
config['tags'] = 'var.tags'
elif self.provider == 'aws':
config['cidr_block'] = '"10.0.0.0/16"'
config['tags'] = 'var.tags'
elif self.provider == 'google':
config['project'] = 'var.project_id'
config['auto_create_subnetworks'] = 'false'
elif self.provider == 'azure':
config['location'] = 'var.location'
config['resource_group_name'] = 'azurerm_resource_group.main.name'
config['address_space'] = '["10.0.0.0/16"]'
config['tags'] = 'var.tags'
return {'type': resource_type, 'name': tf_name, 'config': config}
def _generate_subnet_resource(self, tf_name: str, display_name: str, resource_type: str) -> Dict[str, Any]:
"""Generate subnet resource for any provider"""
self.variables[f'{tf_name}_name'] = {
'description': f'Name for the {display_name} subnet',
'type': 'string',
'default': display_name
}
config = {'name': f'var.{tf_name}_name'}
if self.provider == 'ibm':
config['vpc'] = 'ibm_is_vpc.main.id'
config['zone'] = '"us-south-1"'
config['ipv4_cidr_block'] = '"10.0.1.0/24"'
elif self.provider == 'aws':
config['vpc_id'] = 'aws_vpc.main.id'
config['cidr_block'] = '"10.0.1.0/24"'
config['availability_zone'] = '"us-east-1a"'
config['tags'] = 'var.tags'
elif self.provider == 'google':
config['network'] = 'google_compute_network.main.id'
config['ip_cidr_range'] = '"10.0.1.0/24"'
config['region'] = 'var.region'
elif self.provider == 'azure':
config['resource_group_name'] = 'azurerm_resource_group.main.name'
config['virtual_network_name'] = 'azurerm_virtual_network.main.name'
config['address_prefixes'] = '["10.0.1.0/24"]'
return {'type': resource_type, 'name': tf_name, 'config': config}
def _generate_compute_resource(self, tf_name: str, display_name: str, resource_type: str) -> Dict[str, Any]:
"""Generate compute instance resource for any provider"""
self.variables[f'{tf_name}_name'] = {
'description': f'Name for the {display_name} instance',
'type': 'string',
'default': display_name
}
config = {'name': f'var.{tf_name}_name'}
if self.provider == 'ibm':
config['profile'] = '"cx2-2x4"'
config['image'] = '"r006-14140f94-fcc4-11e9-96e7-a72723715315"'
config['zone'] = '"us-south-1"'
config['vpc'] = 'ibm_is_vpc.main.id'
config['primary_network_interface'] = '{subnet = ibm_is_subnet.main.id}'
elif self.provider == 'aws':
config['ami'] = '"ami-0c55b159cbfafe1f0"'
config['instance_type'] = '"t2.micro"'
config['subnet_id'] = 'aws_subnet.main.id'
config['tags'] = 'var.tags'
elif self.provider == 'google':
config['machine_type'] = '"e2-medium"'
config['zone'] = '"us-central1-a"'
config['boot_disk'] = '{initialize_params {image = "debian-cloud/debian-11"}}'
config['network_interface'] = '{network = google_compute_network.main.id, subnetwork = google_compute_subnetwork.main.id}'
elif self.provider == 'azure':
config['location'] = 'var.location'
config['resource_group_name'] = 'azurerm_resource_group.main.name'
config['network_interface_ids'] = '[azurerm_network_interface.main.id]'
config['vm_size'] = '"Standard_B2s"'
config['os_disk'] = '{caching = "ReadWrite", storage_account_type = "Standard_LRS"}'
elif self.provider == 'kubernetes':
config['metadata'] = '{name = var.%s_name, namespace = var.namespace}' % tf_name
config['spec'] = '{replicas = 2, selector {match_labels = {app = var.%s_name}}, template {metadata {labels = {app = var.%s_name}}, spec {container {name = var.%s_name, image = "nginx:latest"}}}}' % (tf_name, tf_name, tf_name)
return {'type': resource_type, 'name': tf_name, 'config': config}
def _generate_load_balancer_resource(self, tf_name: str, display_name: str, resource_type: str) -> Dict[str, Any]:
"""Generate load balancer resource for any provider"""
self.variables[f'{tf_name}_name'] = {
'description': f'Name for the {display_name} load balancer',
'type': 'string',
'default': display_name
}
config = {'name': f'var.{tf_name}_name'}
if self.provider == 'ibm':
config['subnets'] = '[ibm_is_subnet.main.id]'
elif self.provider == 'aws':
config['load_balancer_type'] = '"application"'
config['subnets'] = '[aws_subnet.main.id]'
config['tags'] = 'var.tags'
elif self.provider == 'google':
config['ip_protocol'] = '"TCP"'
config['port_range'] = '"80"'
config['target'] = 'google_compute_target_pool.main.self_link'
elif self.provider == 'azure':
config['location'] = 'var.location'
config['resource_group_name'] = 'azurerm_resource_group.main.name'
elif self.provider == 'kubernetes':
config['metadata'] = '{name = var.%s_name, namespace = var.namespace}' % tf_name
config['spec'] = '{type = "LoadBalancer", selector = {app = "myapp"}, port {port = 80, target_port = 8080}}'
return {'type': resource_type, 'name': tf_name, 'config': config}
def _generate_database_resource(self, tf_name: str, display_name: str, resource_type: str) -> Dict[str, Any]:
"""Generate database resource for any provider"""
self.variables[f'{tf_name}_name'] = {
'description': f'Name for the {display_name} database',
'type': 'string',
'default': display_name
}
config = {'name': f'var.{tf_name}_name'}
if self.provider == 'ibm':
config['service'] = '"databases-for-postgresql"'
config['plan'] = '"standard"'
config['location'] = 'var.region'
config['resource_group_id'] = 'var.resource_group_id'
elif self.provider == 'aws':
config['engine'] = '"postgres"'
config['engine_version'] = '"14.7"'
config['instance_class'] = '"db.t3.micro"'
config['allocated_storage'] = '20'
config['username'] = '"admin"'
config['password'] = '"changeme"'
config['skip_final_snapshot'] = 'true'
config['tags'] = 'var.tags'
elif self.provider == 'google':
config['database_version'] = '"POSTGRES_14"'
config['region'] = 'var.region'
config['settings'] = '{tier = "db-f1-micro"}'
elif self.provider == 'azure':
config['location'] = 'var.location'
config['resource_group_name'] = 'azurerm_resource_group.main.name'
config['sku_name'] = '"B_Gen5_1"'
config['version'] = '"11"'
config['administrator_login'] = '"psqladmin"'
config['administrator_login_password'] = '"H@Sh1CoR3!"'
elif self.provider == 'kubernetes':
config['metadata'] = '{name = var.%s_name, namespace = var.namespace}' % tf_name
config['spec'] = '{replicas = 1, selector {match_labels = {app = "database"}}, template {metadata {labels = {app = "database"}}, spec {container {name = "postgres", image = "postgres:14"}}}}'
return {'type': resource_type, 'name': tf_name, 'config': config}
def _generate_resource_group_multi(self, tf_name: str, display_name: str, resource_type: str) -> Dict[str, Any]:
"""Generate resource group/namespace for any provider"""
self.variables[f'{tf_name}_name'] = {
'description': f'Name for the {display_name} resource group',
'type': 'string',
'default': display_name
}
config = {'name': f'var.{tf_name}_name'}
if self.provider == 'ibm':
pass # IBM resource group only needs name
elif self.provider == 'google':
config['project_id'] = f'var.{tf_name}_name'
elif self.provider == 'azure':
config['location'] = 'var.location'
elif self.provider == 'kubernetes':
config['metadata'] = '{name = var.%s_name}' % tf_name
return {'type': resource_type, 'name': tf_name, 'config': config}
def _generate_floating_ip_resource(self, tf_name: str, display_name: str, resource_type: str) -> Dict[str, Any]:
"""Generate floating/elastic IP resource for any provider"""
self.variables[f'{tf_name}_name'] = {
'description': f'Name for the {display_name} IP',
'type': 'string',
'default': display_name
}
config = {'name': f'var.{tf_name}_name'}
if self.provider == 'ibm':
config['zone'] = '"us-south-1"'
elif self.provider == 'aws':
config['vpc'] = 'true'
config['tags'] = 'var.tags'
elif self.provider == 'google':
config['region'] = 'var.region'
elif self.provider == 'azure':
config['location'] = 'var.location'
config['resource_group_name'] = 'azurerm_resource_group.main.name'
config['allocation_method'] = '"Static"'
return {'type': resource_type, 'name': tf_name, 'config': config}
def _generate_gateway_resource(self, tf_name: str, display_name: str, resource_type: str) -> Dict[str, Any]:
"""Generate NAT gateway resource for any provider"""
self.variables[f'{tf_name}_name'] = {
'description': f'Name for the {display_name} gateway',
'type': 'string',
'default': display_name
}
config = {'name': f'var.{tf_name}_name'}
if self.provider == 'ibm':
config['vpc'] = 'ibm_is_vpc.main.id'
config['zone'] = '"us-south-1"'
elif self.provider == 'aws':
config['subnet_id'] = 'aws_subnet.main.id'
config['allocation_id'] = 'aws_eip.main.id'
config['tags'] = 'var.tags'
elif self.provider == 'google':
config['router'] = 'google_compute_router.main.name'
config['region'] = 'var.region'
config['nat_ip_allocate_option'] = '"AUTO_ONLY"'
config['source_subnetwork_ip_ranges_to_nat'] = '"ALL_SUBNETWORKS_ALL_IP_RANGES"'
elif self.provider == 'azure':
config['location'] = 'var.location'
config['resource_group_name'] = 'azurerm_resource_group.main.name'
return {'type': resource_type, 'name': tf_name, 'config': config}
def _generate_app_service_resource(self, tf_name: str, display_name: str, resource_type: str) -> Dict[str, Any]:
"""Generate application service resource for any provider"""
self.variables[f'{tf_name}_name'] = {
'description': f'Name for the {display_name} application',
'type': 'string',
'default': display_name
}
config = {'name': f'var.{tf_name}_name'}
if self.provider == 'ibm':
config['service'] = '"cloud-object-storage"'
config['plan'] = '"lite"'
config['location'] = 'var.region'
elif self.provider == 'aws':
config['description'] = f'"{display_name} application"'
elif self.provider == 'google':
config['location_id'] = 'var.region'
elif self.provider == 'azure':
config['location'] = 'var.location'
config['resource_group_name'] = 'azurerm_resource_group.main.name'
config['app_service_plan_id'] = 'azurerm_app_service_plan.main.id'
elif self.provider == 'kubernetes':
config['metadata'] = '{name = var.%s_name, namespace = var.namespace}' % tf_name
config['spec'] = '{replicas = 3, selector {match_labels = {app = var.%s_name}}, template {metadata {labels = {app = var.%s_name}}, spec {container {name = var.%s_name, image = "nginx:latest", port {container_port = 80}}}}}' % (tf_name, tf_name, tf_name)
return {'type': resource_type, 'name': tf_name, 'config': config}
def _generate_watson_service_resource(self, tf_name: str, display_name: str, service: str, resource_type: str) -> Dict[str, Any] | None:
"""Generate Watson service resource (IBM only)"""
if self.provider != 'ibm':
return None
return self._generate_resource_instance(tf_name, display_name, service or 'watsonx-ai')
def _generate_resource_group(self, tf_name: str, display_name: str) -> Dict[str, Any]:
"""Generate resource group configuration"""
self.variables[f'{tf_name}_name'] = {
'description': f'Name for the {display_name} resource group',
'type': 'string',
'default': display_name
}
return {
'type': 'ibm_resource_group',
'name': tf_name,
'config': {
'name': f'var.{tf_name}_name'
}
}
def _generate_main_tf(self) -> str:
"""Generate main.tf content"""
# Use custom provider config if available, otherwise use predefined
if self.custom_provider_config:
provider_config = self.custom_provider_config
provider_name = self.custom_provider_config['name']
else:
provider_config = self.PROVIDER_CONFIGS.get(self.provider, self.PROVIDER_CONFIGS['ibm'])
provider_name = self.provider
lines = [
f'# {provider_name.upper()} Terraform Configuration',
'# Generated from Draw.io diagram',
f'# Provider: {provider_name}',
f'# Documentation: {provider_config["docs"]}',
'',
'# Terraform configuration',
'terraform {',
' required_version = ">= 1.0"',
' required_providers {',
f' {provider_name} = {{',
f' source = "{provider_config["source"]}"',
f' version = "{provider_config["version"]}"',
' }',
' }',
'}',
''
]
# Add resources
if self.resources:
for resource in self.resources:
lines.extend(self._format_resource(resource))
lines.append('')
else:
# No resources generated - add helpful message
lines.append('# No resources generated.')
if hasattr(self, 'skipped_resources') and self.skipped_resources:
lines.append('# This diagram contains IBM Cloud-specific components that are not')
lines.append(f'# supported by the {provider_name} provider.')
lines.append('#')
lines.append('# Skipped IBM Cloud resources:')
for skipped in self.skipped_resources:
lines.append(f'# - {skipped["name"]} ({skipped["type"]})')
lines.append('#')
lines.append('# To generate IBM Cloud resources, please select "IBM Cloud" as the provider.')
lines.append('')
return '\n'.join(lines)
def _format_resource(self, resource: Dict[str, Any]) -> List[str]:
"""Format a resource as Terraform HCL"""
lines = [
f'resource "{resource["type"]}" "{resource["name"]}" {{'
]
for key, value in resource['config'].items():
if isinstance(value, list):
lines.append(f' {key} = {value[0]}')
else:
lines.append(f' {key} = {value}')
lines.append('}')
return lines
def _generate_variables_tf(self) -> str:
"""Generate variables.tf content"""
provider_vars = self._get_provider_variables()
lines = [
f'# Variables for {self.provider.upper()} resources',
'# Generated from Draw.io diagram',
''
]
# Add provider-specific variables
lines.extend(provider_vars)
lines.append('')
# Add component-specific variables
for var_name, var_config in self.variables.items():
lines.append(f'variable "{var_name}" {{')
lines.append(f' description = "{var_config["description"]}"')
lines.append(f' type = {var_config["type"]}')
if 'default' in var_config:
if isinstance(var_config['default'], str):
lines.append(f' default = "{var_config["default"]}"')
else:
lines.append(f' default = {var_config["default"]}')
lines.append('}')
lines.append('')
return '\n'.join(lines)
def _generate_providers_tf(self) -> str:
"""Generate providers.tf content"""
provider_config = self._get_provider_config()
lines = [
f'# {self.provider.upper()} Provider Configuration',
'# Generated from Draw.io diagram',
''
]
lines.extend(provider_config)
lines.append('')
return '\n'.join(lines)
def _get_provider_variables(self) -> List[str]:
"""Get provider-specific variables"""
# For custom providers, return generic variables
if self.custom_provider_config:
return [
'variable "api_key" {',
' description = "API Key for authentication"',
' type = string',
' sensitive = true',
'}',
'',
'variable "region" {',
' description = "Region for resources"',
' type = string',
' default = "us-south"',
'}',
'',
'variable "tags" {',
' description = "Tags for resources"',
' type = list(string)',
' default = ["terraform", "drawio-generated"]',
'}'
]
if self.provider == 'ibm':
return [
'variable "ibmcloud_api_key" {',
' description = "IBM Cloud API Key"',
' type = string',
' sensitive = true',
'}',
'',
'variable "region" {',
' description = "IBM Cloud region"',
' type = string',
' default = "us-south"',
'}',
'',
'variable "resource_group_id" {',
' description = "Resource group ID"',
' type = string',
'}',
'',
'variable "tags" {',
' description = "Tags for resources"',
' type = list(string)',
' default = ["terraform", "drawio-generated"]',
'}'
]
elif self.provider == 'azure':
return [
'variable "subscription_id" {',
' description = "Azure Subscription ID"',
' type = string',
'}',
'',
'variable "tenant_id" {',
' description = "Azure Tenant ID"',
' type = string',
'}',
'',
'variable "location" {',
' description = "Azure region"',
' type = string',
' default = "eastus"',
'}',
'',
'variable "tags" {',
' description = "Tags for resources"',
' type = map(string)',
' default = {',
' Environment = "Development"',
' ManagedBy = "Terraform"',
' }',
'}'
]
elif self.provider == 'google':
return [
'variable "project_id" {',
' description = "Google Cloud Project ID"',
' type = string',
'}',
'',
'variable "region" {',
' description = "Google Cloud region"',
' type = string',
' default = "us-central1"',
'}',
'',
'variable "labels" {',
' description = "Labels for resources"',
' type = map(string)',
' default = {',
' environment = "development"',
' managed_by = "terraform"',
' }',
'}'
]
elif self.provider == 'aws':
return [
'variable "region" {',
' description = "AWS region"',
' type = string',
' default = "us-east-1"',
'}',
'',
'variable "tags" {',
' description = "Tags for resources"',
' type = map(string)',
' default = {',
' Environment = "Development"',
' ManagedBy = "Terraform"',
' }',
'}'
]
elif self.provider == 'kubernetes':
return [
'variable "config_path" {',
' description = "Path to kubeconfig file"',
' type = string',
' default = "~/.kube/config"',
'}',
'',
'variable "config_context" {',
' description = "Kubernetes context to use"',
' type = string',
' default = ""',
'}',
'',
'variable "namespace" {',
' description = "Default namespace"',
' type = string',
' default = "default"',
'}'
]
elif self.provider == 'terraform':
return [
'variable "tfe_token" {',
' description = "Terraform Cloud/Enterprise API Token"',
' type = string',
' sensitive = true',
'}',
'',
'variable "tfe_hostname" {',
' description = "Terraform Cloud/Enterprise hostname"',
' type = string',
' default = "app.terraform.io"',
'}',
'',
'variable "organization" {',
' description = "Terraform Cloud/Enterprise organization name"',
' type = string',
'}'
]
return []
def _get_provider_config(self) -> List[str]:
"""Get provider-specific configuration"""
# For custom providers, return generic configuration
if self.custom_provider_config:
provider_name = self.custom_provider_config['name']
return [
f'provider "{provider_name}" {{',
' # Configure provider-specific settings here',
' # Refer to provider documentation for available options',
f' # Documentation: {self.custom_provider_config["docs"]}',
'}'
]
if self.provider == 'ibm':
return [
'provider "ibm" {',
' ibmcloud_api_key = var.ibmcloud_api_key',
' region = var.region',
'}'
]
elif self.provider == 'azure':
return [
'provider "azurerm" {',
' features {}',
' subscription_id = var.subscription_id',
' tenant_id = var.tenant_id',
'}'
]
elif self.provider == 'google':
return [
'provider "google" {',
' project = var.project_id',
' region = var.region',
'}'
]
elif self.provider == 'aws':
return [
'provider "aws" {',
' region = var.region',
'}'
]
elif self.provider == 'kubernetes':
return [
'provider "kubernetes" {',
' config_path = var.config_path',
' config_context = var.config_context',
'}'
]
elif self.provider == 'terraform':
return [
'provider "tfe" {',
' token = var.tfe_token',
' hostname = var.tfe_hostname',
'}'
]
return []
def _sanitize_name(self, name: str) -> str:
"""Sanitize a name for use in Terraform"""
# Remove special characters and replace spaces with underscores
sanitized = name.lower()
sanitized = sanitized.replace(' ', '_')
sanitized = sanitized.replace('-', '_')
sanitized = sanitized.replace('.', '_')
# Remove any non-alphanumeric characters except underscores
sanitized = ''.join(c for c in sanitized if c.isalnum() or c == '_')
# Ensure it starts with a letter
if sanitized and not sanitized[0].isalpha():
sanitized = 'res_' + sanitized
return sanitized or 'unnamed_resource'
# Made with Bob
Strategic Versatility: From a Curated List to Custom Templates

Current versions of the application excel at interpreting IBM Cloud-specific shapes and generating highly accurate HCL for that environment (to be tested for sure). However, this is only the beginning of our multi-cloud vision. Future iterations will expand this specialized support to other major providers like AWS and Azure, moving beyond generic detection to deep resource mapping. We are also developing a robust “Generator Template” feature that will allow users to extend the system’s capabilities independently; by simply providing a repository URL for any GitHub-hosted Terraform provider, users will eventually be able to generate bespoke configurations for even the most niche infrastructure requirements.
The Front-end — HTML And Javascript
The frontend of the Terraform Builder is a modern, responsive single-page application built using a standard web stack of HTML5, CSS3, and vanilla JavaScript. The interface, defined in index.html, features a clean two-column layout that guides users through the workflow of either creating diagrams in Draw.io or uploading existing XML files. Styling is managed through styles.css, which implements a professional look-and-feel inspired by the IBM Design System, utilizing the IBM Plex font family. The client-side logic resides in app.js, which orchestrates the user interaction—handling drag-and-drop file uploads, managing the provider selection (including custom GitHub templates), and dynamically updating the UI with generated HCL code. This JavaScript layer also facilitates the multi-file preview and download functionality, ensuring a seamless experience without the need for complex frontend frameworks.
Output Samples

The application streamlines the transition from design to deployment by delivering the generated HCL scripts in a structured, timestamped format, ensuring every architectural iteration is captured and organized. By programmatically mapping visual components from a Draw.io diagram directly into main.tf, variables.tf, and providers.tf, the tool dramatically accelerates the infrastructure provisioning process, eliminating the manual overhead of writing boilerplate code. However, while this visual-to-code automation provides a high-speed starting point that reflects the intended design, it is crucial that these scripts are thoroughly reviewed and tested in a development environment before production use. This workflow allows architects to focus on the high-level logic while the application handles the technical translation, significantly reducing time-to-market without bypassing the essential step of human validation.
# IBM Terraform Configuration
# Generated from Draw.io diagram
# Provider: ibm
# Documentation: https://registry.terraform.io/providers/IBM-Cloud/ibm/latest/docs
# Terraform configuration
terraform {
required_version = ">= 1.0"
required_providers {
ibm = {
source = "IBM-Cloud/ibm"
version = ">= 1.60.0"
}
}
}
resource "ibm_resource_group" "ibm_cloud" {
name = var.ibm_cloud_name
}
resource "ibm_is_vpc" "vpc" {
name = var.vpc_name
resource_group = var.resource_group_id
tags = var.tags
}
resource "ibm_resource_group" "ibm_cloud" {
name = var.ibm_cloud_name
}
resource "ibm_database" "data_base" {
name = var.data_base_name
service = "databases-for-postgresql"
plan = "standard"
location = var.region
resource_group_id = var.resource_group_id
}
resource "ibm_is_public_gateway" "network_public" {
name = var.network_public_name
vpc = ibm_is_vpc.main.id
zone = "us-south-1"
}
resource "ibm_is_instance" "rev_" {
name = var.rev__name
profile = "cx2-2x4"
image = "r006-14140f94-fcc4-11e9-96e7-a72723715315"
zone = "us-south-1"
vpc = ibm_is_vpc.main.id
primary_network_interface = {subnet = ibm_is_subnet.main.id}
}
resource "ibm_resource_instance" "application_web" {
name = var.application_web_name
service = "cloud-object-storage"
plan = "lite"
location = var.region
}
resource "ibm_resource_group" "ibm_cloud" {
name = var.ibm_cloud_name
}
resource "ibm_database" "data_base" {
name = var.data_base_name
service = "databases-for-postgresql"
plan = "standard"
location = var.region
resource_group_id = var.resource_group_id
}
resource "ibm_is_public_gateway" "network_public" {
name = var.network_public_name
vpc = ibm_is_vpc.main.id
zone = "us-south-1"
}
resource "ibm_is_instance" "rev_" {
name = var.rev__name
profile = "cx2-2x4"
image = "r006-14140f94-fcc4-11e9-96e7-a72723715315"
zone = "us-south-1"
vpc = ibm_is_vpc.main.id
primary_network_interface = {subnet = ibm_is_subnet.main.id}
}
resource "ibm_is_floating_ip" "floating_ip" {
name = var.floating_ip_name
zone = "us-south-1"
}
resource "ibm_resource_instance" "application_web" {
name = var.application_web_name
service = "cloud-object-storage"
plan = "lite"
location = var.region
}
resource "ibm_is_lb" "load_balancer_vpc" {
name = var.load_balancer_vpc_name
subnets = [ibm_is_subnet.main.id]
}
resource "ibm_is_floating_ip" "floating_ip" {
name = var.floating_ip_name
zone = "us-south-1"
}
# IBM Provider Configuration
# Generated from Draw.io diagram
provider "ibm" {
ibmcloud_api_key = var.ibmcloud_api_key
region = var.region
}
# Variables for IBM resources
# Generated from Draw.io diagram
variable "ibmcloud_api_key" {
description = "IBM Cloud API Key"
type = string
sensitive = true
}
variable "region" {
description = "IBM Cloud region"
type = string
default = "us-south"
}
variable "resource_group_id" {
description = "Resource group ID"
type = string
}
variable "tags" {
description = "Tags for resources"
type = list(string)
default = ["terraform", "drawio-generated"]
}
variable "ibm_cloud_name" {
description = "Name for the ibm_cloud resource group"
type = string
default = "ibm_cloud"
}
variable "vpc_name" {
description = "Name for the VPC network"
type = string
default = "VPC"
}
variable "data_base_name" {
description = "Name for the data_base database"
type = string
default = "data_base"
}
variable "network_public_name" {
description = "Name for the network_public gateway"
type = string
default = "network_public"
}
variable "rev__name" {
description = "Name for the Rev. instance"
type = string
default = "Rev."
}
variable "application_web_name" {
description = "Name for the application_web application"
type = string
default = "application_web"
}
variable "floating_ip_name" {
description = "Name for the floating_ip IP"
type = string
default = "floating_ip"
}
variable "load_balancer_vpc_name" {
description = "Name for the load_balancer_vpc load balancer"
type = string
default = "load_balancer_vpc"
}
Deployment
Beyond local execution, the Terraform Builder is engineered for seamless cloud-native deployment. The inclusion of a multi-stage Dockerfile ensures the application is fully containerized, providing a consistent environment across different hosting platforms. For teams utilizing orchestration, the project provides a complete set of Kubernetes manifests: deployment.yaml defines a scalable architecture with dual replicas and integrated liveness/readiness probes for high availability, while configmap.yaml centralizes environment-specific settings. This "production-ready" approach allows the generator to be easily hosted on platforms like IBM Cloud Code Engine, Red Hat OpenShift, or any standard Kubernetes cluster, transforming it from a local utility into a shared enterprise service.
Conclusion
In conclusion, the Terraform Builder provides a comprehensive, end-to-end bridge between visual architectural design and automated infrastructure deployment. By combining a sleek, user friendly frontend with a powerful Python-driven backend, the application successfully transforms static Draw.io diagrams into functional, multi-file Terraform configurations. With its production-ready containerization and Kubernetes manifests, the tool is prepared for immediate deployment in cloud-native environments. While currently optimized for IBM Cloud in its alpha release, the foundational architecture is already in place to support a vast ecosystem of providers and custom templates, representing a significant step forward in personal productivity and the democratization of Infrastructure as Code.
>>> Thanks for reading <<<
Links
- Code repository for the project: https://github.com/aairom/terraform-builder
- Hashicorp Terraform Providers Registry: https://registry.terraform.io/
- IBM Terraform Modules: https://github.com/terraform-ibm-modules/terraform-ibm-landing-zone
- Draw.io: https://www.drawio.com/
- IBM Bob: https://bob.ibm.com/






Top comments (0)