terraformgraph 1.0.1__py3-none-any.whl → 1.0.3__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- terraformgraph/__init__.py +1 -1
- terraformgraph/aggregator.py +0 -256
- terraformgraph/layout.py +2 -2
- terraformgraph/main.py +16 -10
- terraformgraph/parser.py +6 -3
- terraformgraph/renderer.py +23 -3
- {terraformgraph-1.0.1.dist-info → terraformgraph-1.0.3.dist-info}/METADATA +7 -5
- terraformgraph-1.0.3.dist-info/RECORD +17 -0
- terraformgraph-1.0.1.dist-info/RECORD +0 -17
- {terraformgraph-1.0.1.dist-info → terraformgraph-1.0.3.dist-info}/WHEEL +0 -0
- {terraformgraph-1.0.1.dist-info → terraformgraph-1.0.3.dist-info}/entry_points.txt +0 -0
- {terraformgraph-1.0.1.dist-info → terraformgraph-1.0.3.dist-info}/licenses/LICENSE +0 -0
- {terraformgraph-1.0.1.dist-info → terraformgraph-1.0.3.dist-info}/top_level.txt +0 -0
terraformgraph/__init__.py
CHANGED
terraformgraph/aggregator.py
CHANGED
|
@@ -46,262 +46,6 @@ class AggregatedResult:
|
|
|
46
46
|
global_services: List[LogicalService] = field(default_factory=list)
|
|
47
47
|
|
|
48
48
|
|
|
49
|
-
# Define which resource types should be aggregated together
|
|
50
|
-
AGGREGATION_RULES = {
|
|
51
|
-
# Load Balancing: ALB + listeners + target groups = one ALB
|
|
52
|
-
'alb': {
|
|
53
|
-
'primary': ['aws_lb'],
|
|
54
|
-
'aggregate': ['aws_lb_listener', 'aws_lb_target_group', 'aws_lb_target_group_attachment'],
|
|
55
|
-
'icon': 'aws_lb',
|
|
56
|
-
'display_name': 'Load Balancer',
|
|
57
|
-
'is_vpc': True,
|
|
58
|
-
},
|
|
59
|
-
# ECS: cluster + services + task definitions = one ECS
|
|
60
|
-
'ecs': {
|
|
61
|
-
'primary': ['aws_ecs_cluster'],
|
|
62
|
-
'aggregate': ['aws_ecs_service', 'aws_ecs_task_definition'],
|
|
63
|
-
'icon': 'aws_ecs_cluster',
|
|
64
|
-
'display_name': 'ECS Cluster',
|
|
65
|
-
'is_vpc': True,
|
|
66
|
-
},
|
|
67
|
-
# VPC: vpc + subnets + gateways + route tables = one VPC
|
|
68
|
-
'vpc': {
|
|
69
|
-
'primary': ['aws_vpc'],
|
|
70
|
-
'aggregate': ['aws_subnet', 'aws_internet_gateway', 'aws_nat_gateway',
|
|
71
|
-
'aws_route_table', 'aws_route', 'aws_route_table_association',
|
|
72
|
-
'aws_eip', 'aws_vpc_endpoint', 'aws_db_subnet_group'],
|
|
73
|
-
'icon': 'aws_vpc',
|
|
74
|
-
'display_name': 'VPC',
|
|
75
|
-
'is_vpc': True,
|
|
76
|
-
},
|
|
77
|
-
# Security Groups: aggregate all SGs
|
|
78
|
-
'security': {
|
|
79
|
-
'primary': ['aws_security_group'],
|
|
80
|
-
'aggregate': ['aws_security_group_rule'],
|
|
81
|
-
'icon': 'aws_security_group',
|
|
82
|
-
'display_name': 'Security Groups',
|
|
83
|
-
'is_vpc': True,
|
|
84
|
-
},
|
|
85
|
-
# S3: buckets (aggregate policies, versioning, etc.)
|
|
86
|
-
's3': {
|
|
87
|
-
'primary': ['aws_s3_bucket'],
|
|
88
|
-
'aggregate': ['aws_s3_bucket_policy', 'aws_s3_bucket_versioning',
|
|
89
|
-
'aws_s3_bucket_lifecycle_configuration', 'aws_s3_bucket_notification',
|
|
90
|
-
'aws_s3_bucket_cors_configuration', 'aws_s3_bucket_public_access_block',
|
|
91
|
-
'aws_s3_bucket_ownership_controls', 'aws_s3_bucket_server_side_encryption_configuration'],
|
|
92
|
-
'icon': 'aws_s3_bucket',
|
|
93
|
-
'display_name': 'S3 Buckets',
|
|
94
|
-
'is_vpc': False,
|
|
95
|
-
},
|
|
96
|
-
# SQS: aggregate all queues
|
|
97
|
-
'sqs': {
|
|
98
|
-
'primary': ['aws_sqs_queue'],
|
|
99
|
-
'aggregate': ['aws_sqs_queue_policy'],
|
|
100
|
-
'icon': 'aws_sqs_queue',
|
|
101
|
-
'display_name': 'SQS Queues',
|
|
102
|
-
'is_vpc': False,
|
|
103
|
-
},
|
|
104
|
-
# SNS: aggregate topics
|
|
105
|
-
'sns': {
|
|
106
|
-
'primary': ['aws_sns_topic'],
|
|
107
|
-
'aggregate': ['aws_sns_topic_policy', 'aws_sns_topic_subscription'],
|
|
108
|
-
'icon': 'aws_sns_topic',
|
|
109
|
-
'display_name': 'SNS Topics',
|
|
110
|
-
'is_vpc': False,
|
|
111
|
-
},
|
|
112
|
-
# Cognito: user pool + clients + domain
|
|
113
|
-
'cognito': {
|
|
114
|
-
'primary': ['aws_cognito_user_pool'],
|
|
115
|
-
'aggregate': ['aws_cognito_user_pool_client', 'aws_cognito_user_pool_domain',
|
|
116
|
-
'aws_cognito_identity_pool', 'aws_cognito_identity_pool_roles_attachment',
|
|
117
|
-
'aws_cognito_log_delivery_configuration'],
|
|
118
|
-
'icon': 'aws_cognito_user_pool',
|
|
119
|
-
'display_name': 'Cognito',
|
|
120
|
-
'is_vpc': False,
|
|
121
|
-
},
|
|
122
|
-
# KMS: keys + aliases
|
|
123
|
-
'kms': {
|
|
124
|
-
'primary': ['aws_kms_key'],
|
|
125
|
-
'aggregate': ['aws_kms_alias'],
|
|
126
|
-
'icon': 'aws_kms_key',
|
|
127
|
-
'display_name': 'KMS Keys',
|
|
128
|
-
'is_vpc': False,
|
|
129
|
-
},
|
|
130
|
-
# Secrets Manager
|
|
131
|
-
'secrets': {
|
|
132
|
-
'primary': ['aws_secretsmanager_secret'],
|
|
133
|
-
'aggregate': ['aws_secretsmanager_secret_version'],
|
|
134
|
-
'icon': 'aws_secretsmanager_secret',
|
|
135
|
-
'display_name': 'Secrets Manager',
|
|
136
|
-
'is_vpc': False,
|
|
137
|
-
},
|
|
138
|
-
# Route53
|
|
139
|
-
'route53': {
|
|
140
|
-
'primary': ['aws_route53_zone'],
|
|
141
|
-
'aggregate': ['aws_route53_record'],
|
|
142
|
-
'icon': 'aws_route53_zone',
|
|
143
|
-
'display_name': 'Route 53',
|
|
144
|
-
'is_vpc': False,
|
|
145
|
-
},
|
|
146
|
-
# ACM Certificates
|
|
147
|
-
'acm': {
|
|
148
|
-
'primary': ['aws_acm_certificate'],
|
|
149
|
-
'aggregate': ['aws_acm_certificate_validation'],
|
|
150
|
-
'icon': 'aws_acm_certificate',
|
|
151
|
-
'display_name': 'Certificates',
|
|
152
|
-
'is_vpc': False,
|
|
153
|
-
},
|
|
154
|
-
# CloudWatch
|
|
155
|
-
'cloudwatch': {
|
|
156
|
-
'primary': ['aws_cloudwatch_log_group', 'aws_cloudwatch_metric_alarm'],
|
|
157
|
-
'aggregate': ['aws_cloudwatch_log_resource_policy', 'aws_cloudwatch_log_delivery',
|
|
158
|
-
'aws_cloudwatch_log_delivery_source', 'aws_cloudwatch_log_delivery_destination',
|
|
159
|
-
'aws_cloudwatch_dashboard'],
|
|
160
|
-
'icon': 'aws_cloudwatch_metric_alarm',
|
|
161
|
-
'display_name': 'CloudWatch',
|
|
162
|
-
'is_vpc': False,
|
|
163
|
-
},
|
|
164
|
-
# EventBridge
|
|
165
|
-
'eventbridge': {
|
|
166
|
-
'primary': ['aws_cloudwatch_event_rule', 'aws_cloudwatch_event_bus'],
|
|
167
|
-
'aggregate': ['aws_cloudwatch_event_target', 'aws_cloudwatch_event_archive'],
|
|
168
|
-
'icon': 'aws_cloudwatch_event_rule',
|
|
169
|
-
'display_name': 'EventBridge',
|
|
170
|
-
'is_vpc': False,
|
|
171
|
-
},
|
|
172
|
-
# WAF
|
|
173
|
-
'waf': {
|
|
174
|
-
'primary': ['aws_wafv2_web_acl'],
|
|
175
|
-
'aggregate': ['aws_wafv2_web_acl_association', 'aws_wafv2_rule_group'],
|
|
176
|
-
'icon': 'aws_wafv2_web_acl',
|
|
177
|
-
'display_name': 'WAF',
|
|
178
|
-
'is_vpc': False,
|
|
179
|
-
},
|
|
180
|
-
# IAM
|
|
181
|
-
'iam': {
|
|
182
|
-
'primary': ['aws_iam_role'],
|
|
183
|
-
'aggregate': ['aws_iam_policy', 'aws_iam_role_policy', 'aws_iam_role_policy_attachment',
|
|
184
|
-
'aws_iam_instance_profile'],
|
|
185
|
-
'icon': 'aws_iam_role',
|
|
186
|
-
'display_name': 'IAM Roles',
|
|
187
|
-
'is_vpc': False,
|
|
188
|
-
},
|
|
189
|
-
# ECR
|
|
190
|
-
'ecr': {
|
|
191
|
-
'primary': ['aws_ecr_repository'],
|
|
192
|
-
'aggregate': [],
|
|
193
|
-
'icon': 'aws_ecr_repository',
|
|
194
|
-
'display_name': 'ECR',
|
|
195
|
-
'is_vpc': False,
|
|
196
|
-
},
|
|
197
|
-
# DynamoDB
|
|
198
|
-
'dynamodb': {
|
|
199
|
-
'primary': ['aws_dynamodb_table'],
|
|
200
|
-
'aggregate': [],
|
|
201
|
-
'icon': 'aws_dynamodb_table',
|
|
202
|
-
'display_name': 'DynamoDB',
|
|
203
|
-
'is_vpc': False,
|
|
204
|
-
},
|
|
205
|
-
# SES
|
|
206
|
-
'ses': {
|
|
207
|
-
'primary': ['aws_ses_domain_identity'],
|
|
208
|
-
'aggregate': ['aws_ses_domain_dkim', 'aws_ses_domain_mail_from',
|
|
209
|
-
'aws_ses_identity_notification_topic', 'aws_ses_configuration_set'],
|
|
210
|
-
'icon': 'aws_ses_domain_identity',
|
|
211
|
-
'display_name': 'SES',
|
|
212
|
-
'is_vpc': False,
|
|
213
|
-
},
|
|
214
|
-
# CloudFront
|
|
215
|
-
'cloudfront': {
|
|
216
|
-
'primary': ['aws_cloudfront_distribution'],
|
|
217
|
-
'aggregate': ['aws_cloudfront_origin_access_control'],
|
|
218
|
-
'icon': 'aws_cloudfront_distribution',
|
|
219
|
-
'display_name': 'CloudFront',
|
|
220
|
-
'is_vpc': False,
|
|
221
|
-
},
|
|
222
|
-
# Bedrock
|
|
223
|
-
'bedrock': {
|
|
224
|
-
'primary': ['aws_bedrockagent_knowledge_base'],
|
|
225
|
-
'aggregate': [],
|
|
226
|
-
'icon': 'aws_bedrockagent_knowledge_base',
|
|
227
|
-
'display_name': 'Bedrock KB',
|
|
228
|
-
'is_vpc': False,
|
|
229
|
-
},
|
|
230
|
-
# Budgets
|
|
231
|
-
'budgets': {
|
|
232
|
-
'primary': ['aws_budgets_budget'],
|
|
233
|
-
'aggregate': [],
|
|
234
|
-
'icon': 'aws_budgets_budget',
|
|
235
|
-
'display_name': 'Budgets',
|
|
236
|
-
'is_vpc': False,
|
|
237
|
-
},
|
|
238
|
-
# EC2 (standalone instances like DevOps agent)
|
|
239
|
-
'ec2': {
|
|
240
|
-
'primary': ['aws_instance'],
|
|
241
|
-
'aggregate': ['aws_launch_template'],
|
|
242
|
-
'icon': 'aws_instance',
|
|
243
|
-
'display_name': 'EC2',
|
|
244
|
-
'is_vpc': True,
|
|
245
|
-
},
|
|
246
|
-
# MongoDB Atlas (external)
|
|
247
|
-
'mongodb': {
|
|
248
|
-
'primary': ['mongodbatlas_cluster'],
|
|
249
|
-
'aggregate': ['mongodbatlas_network_peering', 'mongodbatlas_project_ip_access_list'],
|
|
250
|
-
'icon': 'aws_dynamodb_table', # Use DynamoDB icon as fallback
|
|
251
|
-
'display_name': 'MongoDB Atlas',
|
|
252
|
-
'is_vpc': False,
|
|
253
|
-
},
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
# High-level connections between service types
|
|
257
|
-
LOGICAL_CONNECTIONS = [
|
|
258
|
-
# Internet -> WAF -> CloudFront -> ALB
|
|
259
|
-
('cloudfront', 'alb', 'HTTPS', 'data_flow'),
|
|
260
|
-
('waf', 'alb', 'protects', 'default'),
|
|
261
|
-
('waf', 'cognito', 'protects', 'default'),
|
|
262
|
-
|
|
263
|
-
# ALB -> ECS
|
|
264
|
-
('alb', 'ecs', 'routes to', 'data_flow'),
|
|
265
|
-
|
|
266
|
-
# ECS -> various services
|
|
267
|
-
('ecs', 'sqs', 'sends/receives', 'data_flow'),
|
|
268
|
-
('ecs', 's3', 'reads/writes', 'data_flow'),
|
|
269
|
-
('ecs', 'dynamodb', 'queries', 'data_flow'),
|
|
270
|
-
('ecs', 'secrets', 'reads', 'default'),
|
|
271
|
-
('ecs', 'bedrock', 'invokes', 'data_flow'),
|
|
272
|
-
|
|
273
|
-
# S3 -> SQS (notifications)
|
|
274
|
-
('s3', 'sqs', 'triggers', 'trigger'),
|
|
275
|
-
|
|
276
|
-
# SNS for alerts
|
|
277
|
-
('cloudwatch', 'sns', 'alerts', 'trigger'),
|
|
278
|
-
('sqs', 'sns', 'DLQ alerts', 'trigger'),
|
|
279
|
-
|
|
280
|
-
# Encryption
|
|
281
|
-
('kms', 's3', 'encrypts', 'encrypt'),
|
|
282
|
-
('kms', 'sqs', 'encrypts', 'encrypt'),
|
|
283
|
-
('kms', 'sns', 'encrypts', 'encrypt'),
|
|
284
|
-
('kms', 'secrets', 'encrypts', 'encrypt'),
|
|
285
|
-
|
|
286
|
-
# DNS
|
|
287
|
-
('route53', 'alb', 'resolves', 'default'),
|
|
288
|
-
('route53', 'cloudfront', 'resolves', 'default'),
|
|
289
|
-
|
|
290
|
-
# Certificates
|
|
291
|
-
('acm', 'alb', 'TLS', 'default'),
|
|
292
|
-
('acm', 'cloudfront', 'TLS', 'default'),
|
|
293
|
-
|
|
294
|
-
# Cognito auth
|
|
295
|
-
('cognito', 'alb', 'authenticates', 'default'),
|
|
296
|
-
|
|
297
|
-
# ECR -> ECS
|
|
298
|
-
('ecr', 'ecs', 'images', 'data_flow'),
|
|
299
|
-
|
|
300
|
-
# External
|
|
301
|
-
('ecs', 'mongodb', 'queries', 'data_flow'),
|
|
302
|
-
]
|
|
303
|
-
|
|
304
|
-
|
|
305
49
|
class ResourceAggregator:
|
|
306
50
|
"""Aggregates Terraform resources into logical services."""
|
|
307
51
|
|
terraformgraph/layout.py
CHANGED
|
@@ -88,13 +88,13 @@ class LayoutEngine:
|
|
|
88
88
|
st = service.service_type
|
|
89
89
|
if st in ('cloudfront', 'waf', 'route53', 'acm', 'cognito'):
|
|
90
90
|
edge_services.append(service)
|
|
91
|
-
elif st in ('alb', 'ecs', 'ec2', 'security', 'vpc'):
|
|
91
|
+
elif st in ('alb', 'ecs', 'ec2', 'security_groups', 'security', 'vpc'):
|
|
92
92
|
vpc_services.append(service)
|
|
93
93
|
elif st in ('s3', 'dynamodb', 'mongodb'):
|
|
94
94
|
data_services.append(service)
|
|
95
95
|
elif st in ('sqs', 'sns', 'eventbridge'):
|
|
96
96
|
messaging_services.append(service)
|
|
97
|
-
elif st in ('kms', 'secrets', 'iam'):
|
|
97
|
+
elif st in ('kms', 'secrets', 'secrets_manager', 'iam'):
|
|
98
98
|
security_services.append(service)
|
|
99
99
|
else:
|
|
100
100
|
other_services.append(service)
|
terraformgraph/main.py
CHANGED
|
@@ -6,14 +6,17 @@ Generates AWS infrastructure diagrams from Terraform code using official AWS ico
|
|
|
6
6
|
Creates high-level architectural diagrams with logical service groupings.
|
|
7
7
|
|
|
8
8
|
Usage:
|
|
9
|
-
# Parse a directory directly (
|
|
10
|
-
terraformgraph -t ./infrastructure
|
|
9
|
+
# Parse a directory directly (generates diagram.html by default)
|
|
10
|
+
terraformgraph -t ./infrastructure
|
|
11
11
|
|
|
12
12
|
# Parse a specific environment subdirectory
|
|
13
|
-
terraformgraph -t ./infrastructure -e dev
|
|
13
|
+
terraformgraph -t ./infrastructure -e dev
|
|
14
|
+
|
|
15
|
+
# With custom output path
|
|
16
|
+
terraformgraph -t ./infrastructure -o my-diagram.html
|
|
14
17
|
|
|
15
18
|
# With custom icons path
|
|
16
|
-
terraformgraph -t ./infrastructure -i /path/to/icons
|
|
19
|
+
terraformgraph -t ./infrastructure -i /path/to/icons
|
|
17
20
|
"""
|
|
18
21
|
|
|
19
22
|
import argparse
|
|
@@ -33,14 +36,17 @@ def main():
|
|
|
33
36
|
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
34
37
|
epilog='''
|
|
35
38
|
Examples:
|
|
36
|
-
# Parse a directory (
|
|
37
|
-
terraformgraph -t ./infrastructure
|
|
39
|
+
# Parse a directory (generates diagram.html by default)
|
|
40
|
+
terraformgraph -t ./infrastructure
|
|
38
41
|
|
|
39
42
|
# Parse a specific environment subdirectory
|
|
40
|
-
terraformgraph -t ./infrastructure -e dev
|
|
43
|
+
terraformgraph -t ./infrastructure -e dev
|
|
44
|
+
|
|
45
|
+
# With custom output path
|
|
46
|
+
terraformgraph -t ./infrastructure -o my-diagram.html
|
|
41
47
|
|
|
42
48
|
# With custom icons path
|
|
43
|
-
terraformgraph -t ./infrastructure -i /path/to/icons
|
|
49
|
+
terraformgraph -t ./infrastructure -i /path/to/icons
|
|
44
50
|
'''
|
|
45
51
|
)
|
|
46
52
|
|
|
@@ -64,8 +70,8 @@ Examples:
|
|
|
64
70
|
|
|
65
71
|
parser.add_argument(
|
|
66
72
|
'-o', '--output',
|
|
67
|
-
|
|
68
|
-
help='Output file path (HTML)'
|
|
73
|
+
default='diagram.html',
|
|
74
|
+
help='Output file path (HTML). Default: diagram.html'
|
|
69
75
|
)
|
|
70
76
|
|
|
71
77
|
parser.add_argument(
|
terraformgraph/parser.py
CHANGED
|
@@ -4,6 +4,7 @@ Terraform HCL Parser
|
|
|
4
4
|
Parses Terraform files and extracts AWS resources and their relationships.
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
|
+
import logging
|
|
7
8
|
import re
|
|
8
9
|
from dataclasses import dataclass, field
|
|
9
10
|
from pathlib import Path
|
|
@@ -11,6 +12,8 @@ from typing import Any, Dict, List, Optional
|
|
|
11
12
|
|
|
12
13
|
import hcl2
|
|
13
14
|
|
|
15
|
+
logger = logging.getLogger(__name__)
|
|
16
|
+
|
|
14
17
|
|
|
15
18
|
@dataclass
|
|
16
19
|
class TerraformResource:
|
|
@@ -130,7 +133,7 @@ class TerraformParser:
|
|
|
130
133
|
# Parse all .tf files in directory
|
|
131
134
|
tf_files = list(directory.glob("*.tf"))
|
|
132
135
|
if not tf_files:
|
|
133
|
-
|
|
136
|
+
logger.warning("No .tf files found in %s", directory)
|
|
134
137
|
|
|
135
138
|
for tf_file in tf_files:
|
|
136
139
|
self._parse_file(tf_file, result, module_path="")
|
|
@@ -153,7 +156,7 @@ class TerraformParser:
|
|
|
153
156
|
with open(file_path, 'r') as f:
|
|
154
157
|
content = hcl2.load(f)
|
|
155
158
|
except Exception as e:
|
|
156
|
-
|
|
159
|
+
logger.warning("Could not parse %s: %s", file_path, e)
|
|
157
160
|
return
|
|
158
161
|
|
|
159
162
|
# Extract resources
|
|
@@ -199,7 +202,7 @@ class TerraformParser:
|
|
|
199
202
|
module_path = self.infrastructure_path / '.modules' / source
|
|
200
203
|
|
|
201
204
|
if not module_path.exists():
|
|
202
|
-
|
|
205
|
+
logger.warning("Module path not found: %s", module_path)
|
|
203
206
|
return ParseResult()
|
|
204
207
|
|
|
205
208
|
# Check cache
|
terraformgraph/renderer.py
CHANGED
|
@@ -250,7 +250,7 @@ class SVGRenderer:
|
|
|
250
250
|
data-target="{html.escape(connection.target_id)}"
|
|
251
251
|
data-conn-type="{connection.connection_type}"
|
|
252
252
|
data-label="{html.escape(label)}">
|
|
253
|
-
<path class="connection-hitarea" d="{path}"/>
|
|
253
|
+
<path class="connection-hitarea" d="{path}" fill="none" stroke="transparent" stroke-width="15"/>
|
|
254
254
|
<path class="connection-path" d="{path}" fill="none" stroke="{stroke_color}"
|
|
255
255
|
stroke-width="1.5" {dash_attr} marker-end="{marker}" opacity="0.7"/>
|
|
256
256
|
</g>
|
|
@@ -676,6 +676,11 @@ class HTMLRenderer:
|
|
|
676
676
|
|
|
677
677
|
function startDrag(e) {{
|
|
678
678
|
e.preventDefault();
|
|
679
|
+
|
|
680
|
+
// Guard against null CTM (can happen during rendering)
|
|
681
|
+
const ctm = svg.getScreenCTM();
|
|
682
|
+
if (!ctm) return;
|
|
683
|
+
|
|
679
684
|
dragging = e.currentTarget;
|
|
680
685
|
dragging.classList.add('dragging');
|
|
681
686
|
dragging.style.cursor = 'grabbing';
|
|
@@ -683,7 +688,15 @@ class HTMLRenderer:
|
|
|
683
688
|
const pt = svg.createSVGPoint();
|
|
684
689
|
pt.x = e.clientX;
|
|
685
690
|
pt.y = e.clientY;
|
|
686
|
-
const svgP = pt.matrixTransform(
|
|
691
|
+
const svgP = pt.matrixTransform(ctm.inverse());
|
|
692
|
+
|
|
693
|
+
// Validate coordinates to prevent NaN issues
|
|
694
|
+
if (isNaN(svgP.x) || isNaN(svgP.y)) {{
|
|
695
|
+
dragging.classList.remove('dragging');
|
|
696
|
+
dragging.style.cursor = 'grab';
|
|
697
|
+
dragging = null;
|
|
698
|
+
return;
|
|
699
|
+
}}
|
|
687
700
|
|
|
688
701
|
const id = dragging.dataset.serviceId;
|
|
689
702
|
const pos = servicePositions[id] || {{ x: 0, y: 0 }};
|
|
@@ -697,10 +710,17 @@ class HTMLRenderer:
|
|
|
697
710
|
function drag(e) {{
|
|
698
711
|
if (!dragging) return;
|
|
699
712
|
|
|
713
|
+
// Guard against null CTM
|
|
714
|
+
const ctm = svg.getScreenCTM();
|
|
715
|
+
if (!ctm) return;
|
|
716
|
+
|
|
700
717
|
const pt = svg.createSVGPoint();
|
|
701
718
|
pt.x = e.clientX;
|
|
702
719
|
pt.y = e.clientY;
|
|
703
|
-
const svgP = pt.matrixTransform(
|
|
720
|
+
const svgP = pt.matrixTransform(ctm.inverse());
|
|
721
|
+
|
|
722
|
+
// Validate coordinates to prevent NaN issues
|
|
723
|
+
if (isNaN(svgP.x) || isNaN(svgP.y)) return;
|
|
704
724
|
|
|
705
725
|
let newX = svgP.x - offset.x;
|
|
706
726
|
let newY = svgP.y - offset.y;
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: terraformgraph
|
|
3
|
-
Version: 1.0.
|
|
3
|
+
Version: 1.0.3
|
|
4
4
|
Summary: Generate interactive architecture diagrams from Terraform configurations
|
|
5
|
-
Author-email:
|
|
5
|
+
Author-email: Ferdinando Bonsegna <1bonsegnaferdinando@gmail.com>
|
|
6
6
|
License: MIT
|
|
7
7
|
Project-URL: Homepage, https://github.com/ferdinandobons/terraformgraph
|
|
8
8
|
Project-URL: Documentation, https://github.com/ferdinandobons/terraformgraph#readme
|
|
@@ -75,9 +75,11 @@ pip install -e .
|
|
|
75
75
|
Generate a diagram from a Terraform directory:
|
|
76
76
|
|
|
77
77
|
```bash
|
|
78
|
-
terraformgraph -t ./infrastructure
|
|
78
|
+
terraformgraph -t ./infrastructure
|
|
79
79
|
```
|
|
80
80
|
|
|
81
|
+
This creates `diagram.html` in the current directory.
|
|
82
|
+
|
|
81
83
|
### With Environment Subdirectories
|
|
82
84
|
|
|
83
85
|
If your Terraform is organized by environment:
|
|
@@ -91,7 +93,7 @@ terraformgraph -t ./infrastructure -e prod -o prod-diagram.html
|
|
|
91
93
|
For beautiful AWS service icons, download the [AWS Architecture Icons](https://aws.amazon.com/architecture/icons/) and extract them:
|
|
92
94
|
|
|
93
95
|
```bash
|
|
94
|
-
terraformgraph -t ./infrastructure -i ./AWS_Icons
|
|
96
|
+
terraformgraph -t ./infrastructure -i ./AWS_Icons
|
|
95
97
|
```
|
|
96
98
|
|
|
97
99
|
## Command Line Options
|
|
@@ -101,7 +103,7 @@ terraformgraph -t ./infrastructure -i ./AWS_Icons -o diagram.html
|
|
|
101
103
|
| `-t, --terraform` | Yes | Path to Terraform directory |
|
|
102
104
|
| `-e, --environment` | No | Environment subdirectory (dev, staging, prod) |
|
|
103
105
|
| `-i, --icons` | No | Path to AWS icons directory |
|
|
104
|
-
| `-o, --output` |
|
|
106
|
+
| `-o, --output` | No | Output HTML file path (default: `diagram.html`) |
|
|
105
107
|
| `-v, --verbose` | No | Enable debug output |
|
|
106
108
|
|
|
107
109
|
## Configuration
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
terraformgraph/__init__.py,sha256=P8TG3q97jSeON2wd2ZDuvPGfnU3X-hQOcpVTASlENWo,469
|
|
2
|
+
terraformgraph/__main__.py,sha256=E1MUfKa9LQ2_zosMoerGFHMXqWzLc9D9WO2ZUyMyN5Y,134
|
|
3
|
+
terraformgraph/aggregator.py,sha256=ynudJ-h2UgRLURIDznuaP-VFJ9WuZiIJchkwEj8khb8,5613
|
|
4
|
+
terraformgraph/config_loader.py,sha256=D5EIRqFX6Gz1BNu2b2pbG_YWqcTrUKE2iebyR58FMMo,2195
|
|
5
|
+
terraformgraph/icons.py,sha256=5moYr_u4yP-Miyt0GVULavjJueZGdTwUh0W9qNJszSo,42396
|
|
6
|
+
terraformgraph/layout.py,sha256=-Chz2ETrzZ5yF6UoXlMC7kOGdfLyQ7pHuJOCxG7c0gc,8257
|
|
7
|
+
terraformgraph/main.py,sha256=jekIKiWLbRjYGXIUaTdxnD6nInpLoX7xoHaqyqrOPPM,6668
|
|
8
|
+
terraformgraph/parser.py,sha256=jobw6EwDguzGJ1QoWXvwAZZRVsrGRo1ne62H8qivXtE,12583
|
|
9
|
+
terraformgraph/renderer.py,sha256=Hj1gdhLdfwbc-7k00aonaBpdlQSgYtZQMwFTtRwKQ0U,42971
|
|
10
|
+
terraformgraph/config/aggregation_rules.yaml,sha256=BDkT37jCAQ0fKYqGmDJcuo6XjvfS1up5Yk1DXE4aP8M,4340
|
|
11
|
+
terraformgraph/config/logical_connections.yaml,sha256=ECOByIfuC5PStsukEkulEB8ixe4F28Q5Ak7xCs0yogA,2901
|
|
12
|
+
terraformgraph-1.0.3.dist-info/licenses/LICENSE,sha256=14dWZLNvm9xaUFeRin_gDB_iynn6m604g5GsutxqYsM,1068
|
|
13
|
+
terraformgraph-1.0.3.dist-info/METADATA,sha256=g3f_rnKI4xv8qmZxsSdnfnb9uIc5wbNgRbv3K45aruA,4832
|
|
14
|
+
terraformgraph-1.0.3.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
|
|
15
|
+
terraformgraph-1.0.3.dist-info/entry_points.txt,sha256=dONSqeGLyLFZCNcNjJiyLXDa23j-iwAq3u6Qtv6RI0w,60
|
|
16
|
+
terraformgraph-1.0.3.dist-info/top_level.txt,sha256=iquGz8nmBrUGI8SQKJcYrq8ea7UHnBMEXRel0QuNJkI,15
|
|
17
|
+
terraformgraph-1.0.3.dist-info/RECORD,,
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
terraformgraph/__init__.py,sha256=NeUERqcQFeEDBB3zRE5b-y1MtTz85WrtpmO5nVBOgk4,469
|
|
2
|
-
terraformgraph/__main__.py,sha256=E1MUfKa9LQ2_zosMoerGFHMXqWzLc9D9WO2ZUyMyN5Y,134
|
|
3
|
-
terraformgraph/aggregator.py,sha256=fWTZbiwac8CEGJeQlN3X3FNAmHh5mPBsx-3dTnzaLVc,14238
|
|
4
|
-
terraformgraph/config_loader.py,sha256=D5EIRqFX6Gz1BNu2b2pbG_YWqcTrUKE2iebyR58FMMo,2195
|
|
5
|
-
terraformgraph/icons.py,sha256=5moYr_u4yP-Miyt0GVULavjJueZGdTwUh0W9qNJszSo,42396
|
|
6
|
-
terraformgraph/layout.py,sha256=xqua1i-y3PQ_10-ZHMM43vpob87dJHbvKvs48gFp8Iw,8219
|
|
7
|
-
terraformgraph/main.py,sha256=FA4npH6hSJRQun_y-A9by39WxOIcRX69zdkRWsJRVec,6576
|
|
8
|
-
terraformgraph/parser.py,sha256=BDRTbQIMrTr-B_I4F17udT3hskAhN3jE5uajpp_OdZU,12525
|
|
9
|
-
terraformgraph/renderer.py,sha256=mAen2OxvuH7it8pAWbO6aMS75WuW8VMSUwxufxpyJ4g,42233
|
|
10
|
-
terraformgraph/config/aggregation_rules.yaml,sha256=BDkT37jCAQ0fKYqGmDJcuo6XjvfS1up5Yk1DXE4aP8M,4340
|
|
11
|
-
terraformgraph/config/logical_connections.yaml,sha256=ECOByIfuC5PStsukEkulEB8ixe4F28Q5Ak7xCs0yogA,2901
|
|
12
|
-
terraformgraph-1.0.1.dist-info/licenses/LICENSE,sha256=14dWZLNvm9xaUFeRin_gDB_iynn6m604g5GsutxqYsM,1068
|
|
13
|
-
terraformgraph-1.0.1.dist-info/METADATA,sha256=fZwChDSsNcUp7laN-XmXnQLoOEF4F1RDr5zMNP1HZqg,4767
|
|
14
|
-
terraformgraph-1.0.1.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
|
|
15
|
-
terraformgraph-1.0.1.dist-info/entry_points.txt,sha256=dONSqeGLyLFZCNcNjJiyLXDa23j-iwAq3u6Qtv6RI0w,60
|
|
16
|
-
terraformgraph-1.0.1.dist-info/top_level.txt,sha256=iquGz8nmBrUGI8SQKJcYrq8ea7UHnBMEXRel0QuNJkI,15
|
|
17
|
-
terraformgraph-1.0.1.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|