terraformgraph 1.0.2__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.
@@ -1,6 +1,6 @@
1
1
  """terraformgraph - Create architecture diagrams from Terraform configurations."""
2
2
 
3
- __version__ = "1.0.2"
3
+ __version__ = "1.0.3"
4
4
 
5
5
  from .aggregator import ResourceAggregator
6
6
  from .config_loader import ConfigLoader
@@ -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/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
- print(f"Warning: No .tf files found in {directory}")
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
- print(f"Warning: Could not parse {file_path}: {e}")
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
- print(f"Warning: Module path not found: {module_path}")
205
+ logger.warning("Module path not found: %s", module_path)
203
206
  return ParseResult()
204
207
 
205
208
  # Check cache
@@ -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(svg.getScreenCTM().inverse());
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(svg.getScreenCTM().inverse());
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.2
3
+ Version: 1.0.3
4
4
  Summary: Generate interactive architecture diagrams from Terraform configurations
5
- Author-email: Your Name <your.email@example.com>
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
@@ -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=fnxp13XW7PX9_BGYUGceV13yE__mbRTYmGqdS-UuxqE,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=jekIKiWLbRjYGXIUaTdxnD6nInpLoX7xoHaqyqrOPPM,6668
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.2.dist-info/licenses/LICENSE,sha256=14dWZLNvm9xaUFeRin_gDB_iynn6m604g5GsutxqYsM,1068
13
- terraformgraph-1.0.2.dist-info/METADATA,sha256=moAQ8GUvfQ6aywg-N9FgAF5pz9FkMctkYD41Su2LZyI,4815
14
- terraformgraph-1.0.2.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
15
- terraformgraph-1.0.2.dist-info/entry_points.txt,sha256=dONSqeGLyLFZCNcNjJiyLXDa23j-iwAq3u6Qtv6RI0w,60
16
- terraformgraph-1.0.2.dist-info/top_level.txt,sha256=iquGz8nmBrUGI8SQKJcYrq8ea7UHnBMEXRel0QuNJkI,15
17
- terraformgraph-1.0.2.dist-info/RECORD,,