runbooks 1.1.4__py3-none-any.whl → 1.1.6__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.
Files changed (273) hide show
  1. runbooks/__init__.py +31 -2
  2. runbooks/__init___optimized.py +18 -4
  3. runbooks/_platform/__init__.py +1 -5
  4. runbooks/_platform/core/runbooks_wrapper.py +141 -138
  5. runbooks/aws2/accuracy_validator.py +812 -0
  6. runbooks/base.py +7 -0
  7. runbooks/cfat/assessment/compliance.py +1 -1
  8. runbooks/cfat/assessment/runner.py +1 -0
  9. runbooks/cfat/cloud_foundations_assessment.py +227 -239
  10. runbooks/cli/__init__.py +1 -1
  11. runbooks/cli/commands/cfat.py +64 -23
  12. runbooks/cli/commands/finops.py +1005 -54
  13. runbooks/cli/commands/inventory.py +135 -91
  14. runbooks/cli/commands/operate.py +9 -36
  15. runbooks/cli/commands/security.py +42 -18
  16. runbooks/cli/commands/validation.py +432 -18
  17. runbooks/cli/commands/vpc.py +81 -17
  18. runbooks/cli/registry.py +22 -10
  19. runbooks/cloudops/__init__.py +20 -27
  20. runbooks/cloudops/base.py +96 -107
  21. runbooks/cloudops/cost_optimizer.py +544 -542
  22. runbooks/cloudops/infrastructure_optimizer.py +5 -4
  23. runbooks/cloudops/interfaces.py +224 -225
  24. runbooks/cloudops/lifecycle_manager.py +5 -4
  25. runbooks/cloudops/mcp_cost_validation.py +252 -235
  26. runbooks/cloudops/models.py +78 -53
  27. runbooks/cloudops/monitoring_automation.py +5 -4
  28. runbooks/cloudops/notebook_framework.py +177 -213
  29. runbooks/cloudops/security_enforcer.py +125 -159
  30. runbooks/common/accuracy_validator.py +17 -12
  31. runbooks/common/aws_pricing.py +349 -326
  32. runbooks/common/aws_pricing_api.py +211 -212
  33. runbooks/common/aws_profile_manager.py +40 -36
  34. runbooks/common/aws_utils.py +74 -79
  35. runbooks/common/business_logic.py +126 -104
  36. runbooks/common/cli_decorators.py +36 -60
  37. runbooks/common/comprehensive_cost_explorer_integration.py +455 -463
  38. runbooks/common/cross_account_manager.py +197 -204
  39. runbooks/common/date_utils.py +27 -39
  40. runbooks/common/decorators.py +29 -19
  41. runbooks/common/dry_run_examples.py +173 -208
  42. runbooks/common/dry_run_framework.py +157 -155
  43. runbooks/common/enhanced_exception_handler.py +15 -4
  44. runbooks/common/enhanced_logging_example.py +50 -64
  45. runbooks/common/enhanced_logging_integration_example.py +65 -37
  46. runbooks/common/env_utils.py +16 -16
  47. runbooks/common/error_handling.py +40 -38
  48. runbooks/common/lazy_loader.py +41 -23
  49. runbooks/common/logging_integration_helper.py +79 -86
  50. runbooks/common/mcp_cost_explorer_integration.py +476 -493
  51. runbooks/common/mcp_integration.py +99 -79
  52. runbooks/common/memory_optimization.py +140 -118
  53. runbooks/common/module_cli_base.py +37 -58
  54. runbooks/common/organizations_client.py +175 -193
  55. runbooks/common/patterns.py +23 -25
  56. runbooks/common/performance_monitoring.py +67 -71
  57. runbooks/common/performance_optimization_engine.py +283 -274
  58. runbooks/common/profile_utils.py +111 -37
  59. runbooks/common/rich_utils.py +315 -141
  60. runbooks/common/sre_performance_suite.py +177 -186
  61. runbooks/enterprise/__init__.py +1 -1
  62. runbooks/enterprise/logging.py +144 -106
  63. runbooks/enterprise/security.py +187 -204
  64. runbooks/enterprise/validation.py +43 -56
  65. runbooks/finops/__init__.py +26 -30
  66. runbooks/finops/account_resolver.py +1 -1
  67. runbooks/finops/advanced_optimization_engine.py +980 -0
  68. runbooks/finops/automation_core.py +268 -231
  69. runbooks/finops/business_case_config.py +184 -179
  70. runbooks/finops/cli.py +660 -139
  71. runbooks/finops/commvault_ec2_analysis.py +157 -164
  72. runbooks/finops/compute_cost_optimizer.py +336 -320
  73. runbooks/finops/config.py +20 -20
  74. runbooks/finops/cost_optimizer.py +484 -618
  75. runbooks/finops/cost_processor.py +332 -214
  76. runbooks/finops/dashboard_runner.py +1006 -172
  77. runbooks/finops/ebs_cost_optimizer.py +991 -657
  78. runbooks/finops/elastic_ip_optimizer.py +317 -257
  79. runbooks/finops/enhanced_mcp_integration.py +340 -0
  80. runbooks/finops/enhanced_progress.py +32 -29
  81. runbooks/finops/enhanced_trend_visualization.py +3 -2
  82. runbooks/finops/enterprise_wrappers.py +223 -285
  83. runbooks/finops/executive_export.py +203 -160
  84. runbooks/finops/helpers.py +130 -288
  85. runbooks/finops/iam_guidance.py +1 -1
  86. runbooks/finops/infrastructure/__init__.py +80 -0
  87. runbooks/finops/infrastructure/commands.py +506 -0
  88. runbooks/finops/infrastructure/load_balancer_optimizer.py +866 -0
  89. runbooks/finops/infrastructure/vpc_endpoint_optimizer.py +832 -0
  90. runbooks/finops/markdown_exporter.py +337 -174
  91. runbooks/finops/mcp_validator.py +1952 -0
  92. runbooks/finops/nat_gateway_optimizer.py +1512 -481
  93. runbooks/finops/network_cost_optimizer.py +657 -587
  94. runbooks/finops/notebook_utils.py +226 -188
  95. runbooks/finops/optimization_engine.py +1136 -0
  96. runbooks/finops/optimizer.py +19 -23
  97. runbooks/finops/rds_snapshot_optimizer.py +367 -411
  98. runbooks/finops/reservation_optimizer.py +427 -363
  99. runbooks/finops/scenario_cli_integration.py +64 -65
  100. runbooks/finops/scenarios.py +1277 -438
  101. runbooks/finops/schemas.py +218 -182
  102. runbooks/finops/snapshot_manager.py +2289 -0
  103. runbooks/finops/types.py +3 -3
  104. runbooks/finops/validation_framework.py +259 -265
  105. runbooks/finops/vpc_cleanup_exporter.py +189 -144
  106. runbooks/finops/vpc_cleanup_optimizer.py +591 -573
  107. runbooks/finops/workspaces_analyzer.py +171 -182
  108. runbooks/integration/__init__.py +89 -0
  109. runbooks/integration/mcp_integration.py +1920 -0
  110. runbooks/inventory/CLAUDE.md +816 -0
  111. runbooks/inventory/__init__.py +2 -2
  112. runbooks/inventory/aws_decorators.py +2 -3
  113. runbooks/inventory/check_cloudtrail_compliance.py +2 -4
  114. runbooks/inventory/check_controltower_readiness.py +152 -151
  115. runbooks/inventory/check_landingzone_readiness.py +85 -84
  116. runbooks/inventory/cloud_foundations_integration.py +144 -149
  117. runbooks/inventory/collectors/aws_comprehensive.py +1 -1
  118. runbooks/inventory/collectors/aws_networking.py +109 -99
  119. runbooks/inventory/collectors/base.py +4 -0
  120. runbooks/inventory/core/collector.py +495 -313
  121. runbooks/inventory/core/formatter.py +11 -0
  122. runbooks/inventory/draw_org_structure.py +8 -9
  123. runbooks/inventory/drift_detection_cli.py +69 -96
  124. runbooks/inventory/ec2_vpc_utils.py +2 -2
  125. runbooks/inventory/find_cfn_drift_detection.py +5 -7
  126. runbooks/inventory/find_cfn_orphaned_stacks.py +7 -9
  127. runbooks/inventory/find_cfn_stackset_drift.py +5 -6
  128. runbooks/inventory/find_ec2_security_groups.py +48 -42
  129. runbooks/inventory/find_landingzone_versions.py +4 -6
  130. runbooks/inventory/find_vpc_flow_logs.py +7 -9
  131. runbooks/inventory/inventory_mcp_cli.py +48 -46
  132. runbooks/inventory/inventory_modules.py +103 -91
  133. runbooks/inventory/list_cfn_stacks.py +9 -10
  134. runbooks/inventory/list_cfn_stackset_operation_results.py +1 -3
  135. runbooks/inventory/list_cfn_stackset_operations.py +79 -57
  136. runbooks/inventory/list_cfn_stacksets.py +8 -10
  137. runbooks/inventory/list_config_recorders_delivery_channels.py +49 -39
  138. runbooks/inventory/list_ds_directories.py +65 -53
  139. runbooks/inventory/list_ec2_availability_zones.py +2 -4
  140. runbooks/inventory/list_ec2_ebs_volumes.py +32 -35
  141. runbooks/inventory/list_ec2_instances.py +23 -28
  142. runbooks/inventory/list_ecs_clusters_and_tasks.py +26 -34
  143. runbooks/inventory/list_elbs_load_balancers.py +22 -20
  144. runbooks/inventory/list_enis_network_interfaces.py +26 -33
  145. runbooks/inventory/list_guardduty_detectors.py +2 -4
  146. runbooks/inventory/list_iam_policies.py +2 -4
  147. runbooks/inventory/list_iam_roles.py +5 -7
  148. runbooks/inventory/list_iam_saml_providers.py +4 -6
  149. runbooks/inventory/list_lambda_functions.py +38 -38
  150. runbooks/inventory/list_org_accounts.py +6 -8
  151. runbooks/inventory/list_org_accounts_users.py +55 -44
  152. runbooks/inventory/list_rds_db_instances.py +31 -33
  153. runbooks/inventory/list_rds_snapshots_aggregator.py +192 -208
  154. runbooks/inventory/list_route53_hosted_zones.py +3 -5
  155. runbooks/inventory/list_servicecatalog_provisioned_products.py +37 -41
  156. runbooks/inventory/list_sns_topics.py +2 -4
  157. runbooks/inventory/list_ssm_parameters.py +4 -7
  158. runbooks/inventory/list_vpc_subnets.py +2 -4
  159. runbooks/inventory/list_vpcs.py +7 -10
  160. runbooks/inventory/mcp_inventory_validator.py +554 -468
  161. runbooks/inventory/mcp_vpc_validator.py +359 -442
  162. runbooks/inventory/organizations_discovery.py +63 -55
  163. runbooks/inventory/recover_cfn_stack_ids.py +7 -8
  164. runbooks/inventory/requirements.txt +0 -1
  165. runbooks/inventory/rich_inventory_display.py +35 -34
  166. runbooks/inventory/run_on_multi_accounts.py +3 -5
  167. runbooks/inventory/unified_validation_engine.py +281 -253
  168. runbooks/inventory/verify_ec2_security_groups.py +1 -1
  169. runbooks/inventory/vpc_analyzer.py +735 -697
  170. runbooks/inventory/vpc_architecture_validator.py +293 -348
  171. runbooks/inventory/vpc_dependency_analyzer.py +384 -380
  172. runbooks/inventory/vpc_flow_analyzer.py +1 -1
  173. runbooks/main.py +49 -34
  174. runbooks/main_final.py +91 -60
  175. runbooks/main_minimal.py +22 -10
  176. runbooks/main_optimized.py +131 -100
  177. runbooks/main_ultra_minimal.py +7 -2
  178. runbooks/mcp/__init__.py +36 -0
  179. runbooks/mcp/integration.py +679 -0
  180. runbooks/monitoring/performance_monitor.py +9 -4
  181. runbooks/operate/dynamodb_operations.py +3 -1
  182. runbooks/operate/ec2_operations.py +145 -137
  183. runbooks/operate/iam_operations.py +146 -152
  184. runbooks/operate/networking_cost_heatmap.py +29 -8
  185. runbooks/operate/rds_operations.py +223 -254
  186. runbooks/operate/s3_operations.py +107 -118
  187. runbooks/operate/vpc_operations.py +646 -616
  188. runbooks/remediation/base.py +1 -1
  189. runbooks/remediation/commons.py +10 -7
  190. runbooks/remediation/commvault_ec2_analysis.py +70 -66
  191. runbooks/remediation/ec2_unattached_ebs_volumes.py +1 -0
  192. runbooks/remediation/multi_account.py +24 -21
  193. runbooks/remediation/rds_snapshot_list.py +86 -60
  194. runbooks/remediation/remediation_cli.py +92 -146
  195. runbooks/remediation/universal_account_discovery.py +83 -79
  196. runbooks/remediation/workspaces_list.py +46 -41
  197. runbooks/security/__init__.py +19 -0
  198. runbooks/security/assessment_runner.py +1150 -0
  199. runbooks/security/baseline_checker.py +812 -0
  200. runbooks/security/cloudops_automation_security_validator.py +509 -535
  201. runbooks/security/compliance_automation_engine.py +17 -17
  202. runbooks/security/config/__init__.py +2 -2
  203. runbooks/security/config/compliance_config.py +50 -50
  204. runbooks/security/config_template_generator.py +63 -76
  205. runbooks/security/enterprise_security_framework.py +1 -1
  206. runbooks/security/executive_security_dashboard.py +519 -508
  207. runbooks/security/multi_account_security_controls.py +959 -1210
  208. runbooks/security/real_time_security_monitor.py +422 -444
  209. runbooks/security/security_baseline_tester.py +1 -1
  210. runbooks/security/security_cli.py +143 -112
  211. runbooks/security/test_2way_validation.py +439 -0
  212. runbooks/security/two_way_validation_framework.py +852 -0
  213. runbooks/sre/production_monitoring_framework.py +167 -177
  214. runbooks/tdd/__init__.py +15 -0
  215. runbooks/tdd/cli.py +1071 -0
  216. runbooks/utils/__init__.py +14 -17
  217. runbooks/utils/logger.py +7 -2
  218. runbooks/utils/version_validator.py +50 -47
  219. runbooks/validation/__init__.py +6 -6
  220. runbooks/validation/cli.py +9 -3
  221. runbooks/validation/comprehensive_2way_validator.py +745 -704
  222. runbooks/validation/mcp_validator.py +906 -228
  223. runbooks/validation/terraform_citations_validator.py +104 -115
  224. runbooks/validation/terraform_drift_detector.py +461 -454
  225. runbooks/vpc/README.md +617 -0
  226. runbooks/vpc/__init__.py +8 -1
  227. runbooks/vpc/analyzer.py +577 -0
  228. runbooks/vpc/cleanup_wrapper.py +476 -413
  229. runbooks/vpc/cli_cloudtrail_commands.py +339 -0
  230. runbooks/vpc/cli_mcp_validation_commands.py +480 -0
  231. runbooks/vpc/cloudtrail_audit_integration.py +717 -0
  232. runbooks/vpc/config.py +92 -97
  233. runbooks/vpc/cost_engine.py +411 -148
  234. runbooks/vpc/cost_explorer_integration.py +553 -0
  235. runbooks/vpc/cross_account_session.py +101 -106
  236. runbooks/vpc/enhanced_mcp_validation.py +917 -0
  237. runbooks/vpc/eni_gate_validator.py +961 -0
  238. runbooks/vpc/heatmap_engine.py +185 -160
  239. runbooks/vpc/mcp_no_eni_validator.py +680 -639
  240. runbooks/vpc/nat_gateway_optimizer.py +358 -0
  241. runbooks/vpc/networking_wrapper.py +15 -8
  242. runbooks/vpc/pdca_remediation_planner.py +528 -0
  243. runbooks/vpc/performance_optimized_analyzer.py +219 -231
  244. runbooks/vpc/runbooks_adapter.py +1167 -241
  245. runbooks/vpc/tdd_red_phase_stubs.py +601 -0
  246. runbooks/vpc/test_data_loader.py +358 -0
  247. runbooks/vpc/tests/conftest.py +314 -4
  248. runbooks/vpc/tests/test_cleanup_framework.py +1022 -0
  249. runbooks/vpc/tests/test_cost_engine.py +0 -2
  250. runbooks/vpc/topology_generator.py +326 -0
  251. runbooks/vpc/unified_scenarios.py +1297 -1124
  252. runbooks/vpc/vpc_cleanup_integration.py +1943 -1115
  253. runbooks-1.1.6.dist-info/METADATA +327 -0
  254. runbooks-1.1.6.dist-info/RECORD +489 -0
  255. runbooks/finops/README.md +0 -414
  256. runbooks/finops/accuracy_cross_validator.py +0 -647
  257. runbooks/finops/business_cases.py +0 -950
  258. runbooks/finops/dashboard_router.py +0 -922
  259. runbooks/finops/ebs_optimizer.py +0 -973
  260. runbooks/finops/embedded_mcp_validator.py +0 -1629
  261. runbooks/finops/enhanced_dashboard_runner.py +0 -527
  262. runbooks/finops/finops_dashboard.py +0 -584
  263. runbooks/finops/finops_scenarios.py +0 -1218
  264. runbooks/finops/legacy_migration.py +0 -730
  265. runbooks/finops/multi_dashboard.py +0 -1519
  266. runbooks/finops/single_dashboard.py +0 -1113
  267. runbooks/finops/unlimited_scenarios.py +0 -393
  268. runbooks-1.1.4.dist-info/METADATA +0 -800
  269. runbooks-1.1.4.dist-info/RECORD +0 -468
  270. {runbooks-1.1.4.dist-info → runbooks-1.1.6.dist-info}/WHEEL +0 -0
  271. {runbooks-1.1.4.dist-info → runbooks-1.1.6.dist-info}/entry_points.txt +0 -0
  272. {runbooks-1.1.4.dist-info → runbooks-1.1.6.dist-info}/licenses/LICENSE +0 -0
  273. {runbooks-1.1.4.dist-info → runbooks-1.1.6.dist-info}/top_level.txt +0 -0
@@ -37,34 +37,48 @@ import tempfile
37
37
  import asyncio
38
38
 
39
39
  from runbooks.common.rich_utils import (
40
- console, print_header, print_success, print_error, print_warning, print_info,
41
- create_table, create_progress_bar, format_cost, create_panel
40
+ console,
41
+ print_header,
42
+ print_success,
43
+ print_error,
44
+ print_warning,
45
+ print_info,
46
+ create_table,
47
+ create_progress_bar,
48
+ format_cost,
49
+ create_panel,
42
50
  )
43
51
  from runbooks.common.mcp_integration import EnterpriseMCPIntegrator
44
52
  from runbooks.common.profile_utils import get_profile_for_operation, create_cost_session
45
53
  from runbooks.finops.cost_processor import DualMetricCostProcessor
46
54
 
47
- @dataclass
55
+
56
+ @dataclass
48
57
  class TerraformResource:
49
58
  """Terraform resource representation."""
59
+
50
60
  resource_type: str
51
61
  resource_name: str
52
62
  resource_id: str
53
63
  resource_attributes: Dict[str, Any]
54
64
  terraform_address: str
55
-
65
+
66
+
56
67
  @dataclass
57
68
  class RunbooksResource:
58
69
  """Runbooks discovered resource representation."""
70
+
59
71
  resource_type: str
60
72
  resource_id: str
61
73
  resource_attributes: Dict[str, Any]
62
74
  discovery_module: str
63
75
  discovery_timestamp: str
64
76
 
77
+
65
78
  @dataclass
66
79
  class CostCorrelation:
67
80
  """Cost correlation data for drift analysis."""
81
+
68
82
  resource_id: str
69
83
  monthly_cost: float
70
84
  yearly_cost_estimate: float
@@ -73,9 +87,11 @@ class CostCorrelation:
73
87
  service_category: str
74
88
  cost_center: Optional[str] = None
75
89
 
76
- @dataclass
90
+
91
+ @dataclass
77
92
  class DriftAnalysis:
78
93
  """Infrastructure drift analysis result with cost correlation."""
94
+
79
95
  resource_id: str
80
96
  resource_type: str
81
97
  drift_type: str # 'missing_from_terraform', 'missing_from_runbooks', 'configuration_drift'
@@ -87,33 +103,35 @@ class DriftAnalysis:
87
103
  risk_level: str
88
104
  cost_correlation: Optional[CostCorrelation] = None
89
105
 
106
+
90
107
  @dataclass
91
108
  class TerraformDriftResult:
92
109
  """Complete terraform drift detection result with cost correlation."""
110
+
93
111
  drift_detection_id: str
94
112
  detection_timestamp: datetime
95
113
  terraform_state_path: str
96
114
  runbooks_evidence_path: str
97
-
115
+
98
116
  # Drift metrics
99
117
  total_resources_terraform: int
100
118
  total_resources_runbooks: int
101
119
  resources_in_sync: int
102
120
  resources_with_drift: int
103
121
  drift_percentage: float
104
-
122
+
105
123
  # Cost correlation metrics
106
124
  total_monthly_cost_impact: float
107
125
  high_cost_drifts: int
108
126
  cost_correlation_coverage: float
109
127
  mcp_validation_accuracy: float
110
-
128
+
111
129
  # Detailed analysis
112
130
  drift_analysis: List[DriftAnalysis]
113
131
  missing_from_terraform: List[str]
114
132
  missing_from_runbooks: List[str]
115
133
  configuration_drifts: List[str]
116
-
134
+
117
135
  # Business assessment
118
136
  overall_risk_level: str
119
137
  compliance_impact: str
@@ -121,19 +139,20 @@ class TerraformDriftResult:
121
139
  estimated_remediation_effort: str
122
140
  cost_optimization_potential: str
123
141
 
142
+
124
143
  class TerraformDriftDetector:
125
144
  """
126
145
  Enhanced terraform drift detector with cost correlation and MCP validation.
127
-
146
+
128
147
  Compares runbooks resource discoveries with terraform state to identify
129
148
  infrastructure drift, missing resources, and configuration discrepancies.
130
149
  Includes cost correlation analysis and MCP cross-validation for enterprise accuracy.
131
150
  """
132
-
151
+
133
152
  def __init__(self, terraform_state_dir: Optional[str] = None, user_profile: Optional[str] = None):
134
153
  """
135
154
  Initialize enhanced terraform drift detector.
136
-
155
+
137
156
  Args:
138
157
  terraform_state_dir: Directory containing terraform state files
139
158
  user_profile: AWS profile for cost analysis and MCP validation
@@ -142,21 +161,21 @@ class TerraformDriftDetector:
142
161
  self.drift_evidence_dir = Path("validation-evidence") / "terraform-drift"
143
162
  self.drift_evidence_dir.mkdir(parents=True, exist_ok=True)
144
163
  self.user_profile = user_profile
145
-
164
+
146
165
  # Initialize cost processor and MCP integrator
147
166
  billing_profile = get_profile_for_operation("billing", user_profile)
148
167
  try:
149
- self.cost_session = create_cost_session(billing_profile)
168
+ self.cost_session = create_cost_session(profile_name=billing_profile)
150
169
  self.cost_processor = DualMetricCostProcessor(self.cost_session, billing_profile)
151
170
  print_success(f"💰 Cost Explorer integration initialized: {billing_profile}")
152
171
  except Exception as e:
153
172
  print_warning(f"Cost Explorer integration limited: {str(e)[:50]}...")
154
173
  self.cost_session = None
155
174
  self.cost_processor = None
156
-
175
+
157
176
  # Initialize MCP integration for cross-validation
158
177
  self.mcp_integrator = EnterpriseMCPIntegrator(user_profile)
159
-
178
+
160
179
  print_header("Enhanced Terraform Drift Detector", "2.0.0")
161
180
  print_info(f"🏗️ Terraform State Directory: {self.terraform_state_dir}")
162
181
  print_info(f"📊 Drift Evidence Directory: {self.drift_evidence_dir}")
@@ -168,31 +187,31 @@ class TerraformDriftDetector:
168
187
  runbooks_evidence_file: str,
169
188
  terraform_state_file: Optional[str] = None,
170
189
  resource_types: Optional[List[str]] = None,
171
- enable_cost_correlation: bool = True
190
+ enable_cost_correlation: bool = True,
172
191
  ) -> TerraformDriftResult:
173
192
  """
174
193
  Detect infrastructure drift between runbooks and terraform with cost correlation.
175
-
194
+
176
195
  Args:
177
196
  runbooks_evidence_file: Path to runbooks evidence file
178
197
  terraform_state_file: Path to terraform state file (optional)
179
198
  resource_types: Specific resource types to analyze (optional)
180
199
  enable_cost_correlation: Enable cost correlation analysis (default: True)
181
-
200
+
182
201
  Returns:
183
202
  Complete drift detection results with cost impact analysis
184
203
  """
185
204
  detection_start = datetime.now()
186
205
  drift_id = f"drift_{detection_start.strftime('%Y%m%d_%H%M%S')}"
187
-
206
+
188
207
  print_info(f"🔍 Detecting infrastructure drift: {drift_id}")
189
208
  print_info(f"📄 Runbooks evidence: {Path(runbooks_evidence_file).name}")
190
-
209
+
191
210
  try:
192
211
  # Load runbooks evidence
193
212
  runbooks_resources = self._load_runbooks_evidence(runbooks_evidence_file)
194
213
  print_success(f"📋 Runbooks resources loaded: {len(runbooks_resources)}")
195
-
214
+
196
215
  # Load terraform state
197
216
  if terraform_state_file and Path(terraform_state_file).exists():
198
217
  terraform_resources = self._load_terraform_state(terraform_state_file)
@@ -207,36 +226,47 @@ class TerraformDriftDetector:
207
226
  # Generate mock terraform state for demonstration
208
227
  terraform_resources = self._generate_mock_terraform_state(runbooks_resources)
209
228
  state_source = "generated_for_demonstration"
210
-
229
+
211
230
  print_success(f"🏗️ Terraform resources loaded: {len(terraform_resources)}")
212
-
231
+
213
232
  # Perform drift analysis
214
233
  drift_analysis = await self._analyze_infrastructure_drift(
215
234
  runbooks_resources, terraform_resources, resource_types, enable_cost_correlation
216
235
  )
217
-
236
+
218
237
  # Cost correlation analysis
219
238
  cost_metrics = await self._calculate_cost_correlation_metrics(drift_analysis)
220
-
239
+
221
240
  # MCP validation for enhanced accuracy
222
- mcp_validation_result = await self._perform_mcp_validation(drift_analysis, runbooks_resources, terraform_resources)
223
-
241
+ mcp_validation_result = await self._perform_mcp_validation(
242
+ drift_analysis, runbooks_resources, terraform_resources
243
+ )
244
+
245
+ # CRITICAL: Enforce quality gate - block on <99.5% accuracy
246
+ accuracy_score = mcp_validation_result.get("accuracy_score", 0.0)
247
+ if accuracy_score < 99.5:
248
+ error_msg = f"MCP validation accuracy {accuracy_score:.1f}% below required threshold 99.5%"
249
+ console.print(f"[red]❌ MCP Validation FAILED: {accuracy_score:.1f}% < 99.5% required[/red]")
250
+ raise ValueError(error_msg)
251
+
252
+ console.print(f"[green]✅ MCP Validation PASSED: {accuracy_score:.1f}% ≥ 99.5% required[/green]")
253
+
224
254
  # Calculate metrics
225
255
  total_tf = len(terraform_resources)
226
256
  total_rb = len(runbooks_resources)
227
257
  drifts_found = len(drift_analysis)
228
258
  resources_in_sync = max(0, min(total_tf, total_rb) - drifts_found)
229
259
  drift_percentage = (drifts_found / max(total_tf, total_rb) * 100) if max(total_tf, total_rb) > 0 else 0
230
-
260
+
231
261
  # Business impact assessment
232
262
  overall_risk = self._assess_overall_risk(drift_analysis, drift_percentage)
233
263
  compliance_impact = self._assess_compliance_impact(drift_analysis)
234
264
  remediation_priority = self._assess_remediation_priority(drift_analysis, overall_risk)
235
265
  remediation_effort = self._estimate_remediation_effort(drift_analysis)
236
-
266
+
237
267
  # Generate cost optimization assessment
238
268
  cost_optimization_potential = self._assess_cost_optimization_potential(drift_analysis, cost_metrics)
239
-
269
+
240
270
  drift_result = TerraformDriftResult(
241
271
  drift_detection_id=drift_id,
242
272
  detection_timestamp=detection_start,
@@ -248,30 +278,34 @@ class TerraformDriftDetector:
248
278
  resources_with_drift=drifts_found,
249
279
  drift_percentage=drift_percentage,
250
280
  # Cost correlation metrics
251
- total_monthly_cost_impact=cost_metrics.get('total_monthly_cost', 0.0),
252
- high_cost_drifts=cost_metrics.get('high_cost_drifts', 0),
253
- cost_correlation_coverage=cost_metrics.get('correlation_coverage', 0.0),
254
- mcp_validation_accuracy=mcp_validation_result.get('accuracy_score', 95.0),
281
+ total_monthly_cost_impact=cost_metrics.get("total_monthly_cost", 0.0),
282
+ high_cost_drifts=cost_metrics.get("high_cost_drifts", 0),
283
+ cost_correlation_coverage=cost_metrics.get("correlation_coverage", 0.0),
284
+ mcp_validation_accuracy=mcp_validation_result.get("accuracy_score", 0.0), # Pessimistic default
255
285
  # Analysis details
256
286
  drift_analysis=drift_analysis,
257
- missing_from_terraform=[d.resource_id for d in drift_analysis if d.drift_type == 'missing_from_terraform'],
258
- missing_from_runbooks=[d.resource_id for d in drift_analysis if d.drift_type == 'missing_from_runbooks'],
259
- configuration_drifts=[d.resource_id for d in drift_analysis if d.drift_type == 'configuration_drift'],
287
+ missing_from_terraform=[
288
+ d.resource_id for d in drift_analysis if d.drift_type == "missing_from_terraform"
289
+ ],
290
+ missing_from_runbooks=[
291
+ d.resource_id for d in drift_analysis if d.drift_type == "missing_from_runbooks"
292
+ ],
293
+ configuration_drifts=[d.resource_id for d in drift_analysis if d.drift_type == "configuration_drift"],
260
294
  overall_risk_level=overall_risk,
261
295
  compliance_impact=compliance_impact,
262
296
  remediation_priority=remediation_priority,
263
297
  estimated_remediation_effort=remediation_effort,
264
- cost_optimization_potential=cost_optimization_potential
298
+ cost_optimization_potential=cost_optimization_potential,
265
299
  )
266
-
300
+
267
301
  # Display results
268
302
  self._display_drift_results(drift_result)
269
-
303
+
270
304
  # Generate evidence
271
305
  evidence_file = self._generate_drift_evidence(drift_result)
272
-
306
+
273
307
  return drift_result
274
-
308
+
275
309
  except Exception as e:
276
310
  print_error(f"❌ Drift detection failed: {str(e)}")
277
311
  raise
@@ -279,152 +313,162 @@ class TerraformDriftDetector:
279
313
  def _load_runbooks_evidence(self, evidence_file: str) -> List[RunbooksResource]:
280
314
  """Load runbooks evidence file and extract resources."""
281
315
  resources = []
282
-
316
+
283
317
  try:
284
318
  evidence_path = Path(evidence_file)
285
-
286
- if evidence_path.suffix == '.json':
287
- with open(evidence_path, 'r') as f:
319
+
320
+ if evidence_path.suffix == ".json":
321
+ with open(evidence_path, "r") as f:
288
322
  data = json.load(f)
289
-
323
+
290
324
  # Handle different evidence file formats
291
- if 'vpc_details' in data:
325
+ if "vpc_details" in data:
292
326
  # VPC discovery format
293
- for vpc in data.get('vpc_details', []):
294
- resources.append(RunbooksResource(
295
- resource_type='aws_vpc',
296
- resource_id=vpc.get('VpcId', ''),
297
- resource_attributes=vpc,
298
- discovery_module='vpc',
299
- discovery_timestamp=data.get('timestamp', '')
300
- ))
301
-
327
+ for vpc in data.get("vpc_details", []):
328
+ resources.append(
329
+ RunbooksResource(
330
+ resource_type="aws_vpc",
331
+ resource_id=vpc.get("VpcId", ""),
332
+ resource_attributes=vpc,
333
+ discovery_module="vpc",
334
+ discovery_timestamp=data.get("timestamp", ""),
335
+ )
336
+ )
337
+
302
338
  # Add subnets
303
- for subnet in vpc.get('Subnets', []):
304
- resources.append(RunbooksResource(
305
- resource_type='aws_subnet',
306
- resource_id=subnet.get('SubnetId', ''),
307
- resource_attributes=subnet,
308
- discovery_module='vpc',
309
- discovery_timestamp=data.get('timestamp', '')
310
- ))
311
-
312
- elif 'services' in data:
313
- # Inventory discovery format
314
- for service_name, service_data in data.get('services', {}).items():
339
+ for subnet in vpc.get("Subnets", []):
340
+ resources.append(
341
+ RunbooksResource(
342
+ resource_type="aws_subnet",
343
+ resource_id=subnet.get("SubnetId", ""),
344
+ resource_attributes=subnet,
345
+ discovery_module="vpc",
346
+ discovery_timestamp=data.get("timestamp", ""),
347
+ )
348
+ )
349
+
350
+ elif "services" in data:
351
+ # Inventory discovery format
352
+ for service_name, service_data in data.get("services", {}).items():
315
353
  if isinstance(service_data, list):
316
354
  for resource in service_data:
317
- resources.append(RunbooksResource(
318
- resource_type=f'aws_{service_name.lower()}',
319
- resource_id=resource.get('id', resource.get('Id', '')),
320
- resource_attributes=resource,
321
- discovery_module='inventory',
322
- discovery_timestamp=data.get('timestamp', '')
323
- ))
324
-
325
- elif 'cost_breakdown' in data:
355
+ resources.append(
356
+ RunbooksResource(
357
+ resource_type=f"aws_{service_name.lower()}",
358
+ resource_id=resource.get("id", resource.get("Id", "")),
359
+ resource_attributes=resource,
360
+ discovery_module="inventory",
361
+ discovery_timestamp=data.get("timestamp", ""),
362
+ )
363
+ )
364
+
365
+ elif "cost_breakdown" in data:
326
366
  # FinOps discovery format - extract service usage
327
- for service in data.get('cost_breakdown', []):
328
- resources.append(RunbooksResource(
329
- resource_type=f"aws_{service.get('Service', 'unknown').lower().replace(' ', '_')}",
330
- resource_id=f"{service.get('Account', 'unknown')}_{service.get('Service', 'unknown')}",
331
- resource_attributes=service,
332
- discovery_module='finops',
333
- discovery_timestamp=data.get('timestamp', '')
334
- ))
335
-
336
- elif evidence_path.suffix == '.csv':
367
+ for service in data.get("cost_breakdown", []):
368
+ resources.append(
369
+ RunbooksResource(
370
+ resource_type=f"aws_{service.get('Service', 'unknown').lower().replace(' ', '_')}",
371
+ resource_id=f"{service.get('Account', 'unknown')}_{service.get('Service', 'unknown')}",
372
+ resource_attributes=service,
373
+ discovery_module="finops",
374
+ discovery_timestamp=data.get("timestamp", ""),
375
+ )
376
+ )
377
+
378
+ elif evidence_path.suffix == ".csv":
337
379
  # Handle CSV inventory format
338
380
  import csv
339
- with open(evidence_path, 'r') as f:
381
+
382
+ with open(evidence_path, "r") as f:
340
383
  csv_reader = csv.DictReader(f)
341
384
  for row in csv_reader:
342
- resources.append(RunbooksResource(
343
- resource_type=row.get('Resource Type', '').lower().replace(' ', '_'),
344
- resource_id=row.get('Resource ID', ''),
345
- resource_attributes=dict(row),
346
- discovery_module='inventory_csv',
347
- discovery_timestamp=datetime.now().isoformat()
348
- ))
349
-
385
+ resources.append(
386
+ RunbooksResource(
387
+ resource_type=row.get("Resource Type", "").lower().replace(" ", "_"),
388
+ resource_id=row.get("Resource ID", ""),
389
+ resource_attributes=dict(row),
390
+ discovery_module="inventory_csv",
391
+ discovery_timestamp=datetime.now().isoformat(),
392
+ )
393
+ )
394
+
350
395
  except Exception as e:
351
396
  print_warning(f"Error loading runbooks evidence: {e}")
352
397
  # Return minimal resource list for demonstration
353
398
  resources = [
354
399
  RunbooksResource(
355
- resource_type='aws_vpc',
356
- resource_id='vpc-demo123',
357
- resource_attributes={'State': 'available'},
358
- discovery_module='runbooks_discovery',
359
- discovery_timestamp=datetime.now().isoformat()
400
+ resource_type="aws_vpc",
401
+ resource_id="vpc-demo123",
402
+ resource_attributes={"State": "available"},
403
+ discovery_module="runbooks_discovery",
404
+ discovery_timestamp=datetime.now().isoformat(),
360
405
  )
361
406
  ]
362
-
407
+
363
408
  return resources
364
409
 
365
410
  def _discover_terraform_state(self) -> Optional[str]:
366
411
  """Attempt to discover terraform state files."""
367
412
  if not self.terraform_state_dir.exists():
368
413
  return None
369
-
414
+
370
415
  # Look for common terraform state files
371
- state_patterns = [
372
- "terraform.tfstate",
373
- "*.tfstate",
374
- "terraform.tfstate.backup",
375
- ".terraform/terraform.tfstate"
376
- ]
377
-
416
+ state_patterns = ["terraform.tfstate", "*.tfstate", "terraform.tfstate.backup", ".terraform/terraform.tfstate"]
417
+
378
418
  for pattern in state_patterns:
379
419
  state_files = list(self.terraform_state_dir.glob(pattern))
380
420
  if state_files and state_files[0].stat().st_size > 0:
381
421
  print_info(f"📄 Discovered terraform state: {state_files[0].name}")
382
422
  return str(state_files[0])
383
-
423
+
384
424
  return None
385
425
 
386
426
  def _load_terraform_state(self, state_file: str) -> List[TerraformResource]:
387
427
  """Load terraform state file and extract resources."""
388
428
  resources = []
389
-
429
+
390
430
  try:
391
- with open(state_file, 'r') as f:
431
+ with open(state_file, "r") as f:
392
432
  state_data = json.load(f)
393
-
433
+
394
434
  # Extract resources from terraform state format
395
- for resource in state_data.get('resources', []):
396
- for instance in resource.get('instances', []):
397
- resources.append(TerraformResource(
398
- resource_type=resource.get('type', ''),
399
- resource_name=resource.get('name', ''),
400
- resource_id=instance.get('attributes', {}).get('id', ''),
401
- resource_attributes=instance.get('attributes', {}),
402
- terraform_address=f"{resource.get('type', '')}.{resource.get('name', '')}"
403
- ))
404
-
435
+ for resource in state_data.get("resources", []):
436
+ for instance in resource.get("instances", []):
437
+ resources.append(
438
+ TerraformResource(
439
+ resource_type=resource.get("type", ""),
440
+ resource_name=resource.get("name", ""),
441
+ resource_id=instance.get("attributes", {}).get("id", ""),
442
+ resource_attributes=instance.get("attributes", {}),
443
+ terraform_address=f"{resource.get('type', '')}.{resource.get('name', '')}",
444
+ )
445
+ )
446
+
405
447
  except Exception as e:
406
448
  print_warning(f"Error loading terraform state: {e}")
407
-
449
+
408
450
  return resources
409
451
 
410
452
  def _generate_mock_terraform_state(self, runbooks_resources: List[RunbooksResource]) -> List[TerraformResource]:
411
453
  """Generate mock terraform state for demonstration."""
412
454
  print_info("🏗️ Generating mock terraform state for drift detection demo...")
413
-
455
+
414
456
  terraform_resources = []
415
-
457
+
416
458
  # Create terraform resources based on runbooks discoveries
417
459
  for rb_resource in runbooks_resources:
418
460
  # Simulate some resources being in terraform
419
461
  if hash(rb_resource.resource_id) % 3 != 0: # ~67% of resources in terraform
420
- terraform_resources.append(TerraformResource(
421
- resource_type=rb_resource.resource_type,
422
- resource_name=f"{rb_resource.resource_type}_managed",
423
- resource_id=rb_resource.resource_id,
424
- resource_attributes=rb_resource.resource_attributes.copy(),
425
- terraform_address=f"{rb_resource.resource_type}.managed_{rb_resource.resource_id.replace('-', '_')}"
426
- ))
427
-
462
+ terraform_resources.append(
463
+ TerraformResource(
464
+ resource_type=rb_resource.resource_type,
465
+ resource_name=f"{rb_resource.resource_type}_managed",
466
+ resource_id=rb_resource.resource_id,
467
+ resource_attributes=rb_resource.resource_attributes.copy(),
468
+ terraform_address=f"{rb_resource.resource_type}.managed_{rb_resource.resource_id.replace('-', '_')}",
469
+ )
470
+ )
471
+
428
472
  # Add some terraform-only resources to simulate drift
429
473
  terraform_only_resources = [
430
474
  TerraformResource(
@@ -432,17 +476,17 @@ class TerraformDriftDetector:
432
476
  resource_name="terraform_managed_bucket",
433
477
  resource_id="terraform-managed-bucket-123",
434
478
  resource_attributes={"bucket": "terraform-managed-bucket-123", "versioning": {"enabled": True}},
435
- terraform_address="aws_s3_bucket.terraform_managed_bucket"
479
+ terraform_address="aws_s3_bucket.terraform_managed_bucket",
436
480
  ),
437
481
  TerraformResource(
438
482
  resource_type="aws_security_group",
439
483
  resource_name="terraform_managed_sg",
440
484
  resource_id="sg-terraform123",
441
485
  resource_attributes={"name": "terraform-managed-sg", "description": "Managed by terraform"},
442
- terraform_address="aws_security_group.terraform_managed_sg"
443
- )
486
+ terraform_address="aws_security_group.terraform_managed_sg",
487
+ ),
444
488
  ]
445
-
489
+
446
490
  terraform_resources.extend(terraform_only_resources)
447
491
  return terraform_resources
448
492
 
@@ -451,140 +495,148 @@ class TerraformDriftDetector:
451
495
  runbooks_resources: List[RunbooksResource],
452
496
  terraform_resources: List[TerraformResource],
453
497
  resource_types: Optional[List[str]] = None,
454
- enable_cost_correlation: bool = True
498
+ enable_cost_correlation: bool = True,
455
499
  ) -> List[DriftAnalysis]:
456
500
  """Analyze infrastructure drift between runbooks and terraform."""
457
501
  drift_analyses = []
458
-
502
+
459
503
  # Create lookup maps
460
504
  rb_by_id = {r.resource_id: r for r in runbooks_resources}
461
505
  tf_by_id = {r.resource_id: r for r in terraform_resources}
462
-
506
+
463
507
  all_resource_ids = set(rb_by_id.keys()) | set(tf_by_id.keys())
464
-
508
+
465
509
  print_info(f"🔍 Analyzing {len(all_resource_ids)} unique resources for drift...")
466
-
510
+
467
511
  for resource_id in all_resource_ids:
468
512
  rb_resource = rb_by_id.get(resource_id)
469
513
  tf_resource = tf_by_id.get(resource_id)
470
-
514
+
471
515
  # Filter by resource types if specified
472
516
  if resource_types:
473
- resource_type = (rb_resource.resource_type if rb_resource else tf_resource.resource_type)
517
+ resource_type = rb_resource.resource_type if rb_resource else tf_resource.resource_type
474
518
  if resource_type not in resource_types:
475
519
  continue
476
-
520
+
477
521
  if rb_resource and not tf_resource:
478
522
  # Missing from terraform
479
- drift_analyses.append(DriftAnalysis(
480
- resource_id=resource_id,
481
- resource_type=rb_resource.resource_type,
482
- drift_type='missing_from_terraform',
483
- terraform_config=None,
484
- runbooks_config=rb_resource.resource_attributes,
485
- drift_details=[
486
- f"Resource discovered by runbooks but not managed by terraform",
487
- f"Discovery module: {rb_resource.discovery_module}",
488
- f"Discovery time: {rb_resource.discovery_timestamp}"
489
- ],
490
- business_impact="Unmanaged infrastructure increases compliance risk",
491
- remediation_recommendation="Import resource into terraform or add to lifecycle management",
492
- risk_level="medium"
493
- ))
494
-
523
+ drift_analyses.append(
524
+ DriftAnalysis(
525
+ resource_id=resource_id,
526
+ resource_type=rb_resource.resource_type,
527
+ drift_type="missing_from_terraform",
528
+ terraform_config=None,
529
+ runbooks_config=rb_resource.resource_attributes,
530
+ drift_details=[
531
+ f"Resource discovered by runbooks but not managed by terraform",
532
+ f"Discovery module: {rb_resource.discovery_module}",
533
+ f"Discovery time: {rb_resource.discovery_timestamp}",
534
+ ],
535
+ business_impact="Unmanaged infrastructure increases compliance risk",
536
+ remediation_recommendation="Import resource into terraform or add to lifecycle management",
537
+ risk_level="medium",
538
+ )
539
+ )
540
+
495
541
  elif tf_resource and not rb_resource:
496
542
  # Missing from runbooks discovery
497
- drift_analyses.append(DriftAnalysis(
498
- resource_id=resource_id,
499
- resource_type=tf_resource.resource_type,
500
- drift_type='missing_from_runbooks',
501
- terraform_config=tf_resource.resource_attributes,
502
- runbooks_config=None,
503
- drift_details=[
504
- f"Resource managed by terraform but not discovered by runbooks",
505
- f"Terraform address: {tf_resource.terraform_address}",
506
- "May indicate discovery gap or resource accessibility issue"
507
- ],
508
- business_impact="Discovery gap may affect monitoring and cost visibility",
509
- remediation_recommendation="Verify runbooks discovery scope and AWS permissions",
510
- risk_level="low"
511
- ))
512
-
543
+ drift_analyses.append(
544
+ DriftAnalysis(
545
+ resource_id=resource_id,
546
+ resource_type=tf_resource.resource_type,
547
+ drift_type="missing_from_runbooks",
548
+ terraform_config=tf_resource.resource_attributes,
549
+ runbooks_config=None,
550
+ drift_details=[
551
+ f"Resource managed by terraform but not discovered by runbooks",
552
+ f"Terraform address: {tf_resource.terraform_address}",
553
+ "May indicate discovery gap or resource accessibility issue",
554
+ ],
555
+ business_impact="Discovery gap may affect monitoring and cost visibility",
556
+ remediation_recommendation="Verify runbooks discovery scope and AWS permissions",
557
+ risk_level="low",
558
+ )
559
+ )
560
+
513
561
  elif rb_resource and tf_resource:
514
562
  # Check for configuration drift
515
563
  config_diffs = self._compare_resource_configurations(
516
564
  rb_resource.resource_attributes, tf_resource.resource_attributes
517
565
  )
518
-
566
+
519
567
  if config_diffs:
520
- drift_analyses.append(DriftAnalysis(
521
- resource_id=resource_id,
522
- resource_type=rb_resource.resource_type,
523
- drift_type='configuration_drift',
524
- terraform_config=tf_resource.resource_attributes,
525
- runbooks_config=rb_resource.resource_attributes,
526
- drift_details=config_diffs,
527
- business_impact="Configuration drift may indicate unauthorized changes",
528
- remediation_recommendation="Review terraform plan and apply updates to align state",
529
- risk_level="medium" if len(config_diffs) > 3 else "low"
530
- ))
531
-
568
+ drift_analyses.append(
569
+ DriftAnalysis(
570
+ resource_id=resource_id,
571
+ resource_type=rb_resource.resource_type,
572
+ drift_type="configuration_drift",
573
+ terraform_config=tf_resource.resource_attributes,
574
+ runbooks_config=rb_resource.resource_attributes,
575
+ drift_details=config_diffs,
576
+ business_impact="Configuration drift may indicate unauthorized changes",
577
+ remediation_recommendation="Review terraform plan and apply updates to align state",
578
+ risk_level="medium" if len(config_diffs) > 3 else "low",
579
+ )
580
+ )
581
+
532
582
  # Add cost correlation to drift analyses if enabled
533
583
  if enable_cost_correlation and self.cost_processor:
534
584
  print_info(f"💰 Calculating cost correlation for {len(drift_analyses)} drift items...")
535
-
585
+
536
586
  with create_progress_bar() as progress:
537
587
  cost_task = progress.add_task("Correlating costs...", total=len(drift_analyses))
538
-
588
+
539
589
  for drift in drift_analyses:
540
590
  try:
541
591
  # Get cost correlation for this resource
542
- cost_correlation = await self._get_resource_cost_correlation(drift.resource_id, drift.resource_type)
592
+ cost_correlation = await self._get_resource_cost_correlation(
593
+ drift.resource_id, drift.resource_type
594
+ )
543
595
  drift.cost_correlation = cost_correlation
544
-
596
+
545
597
  # Update business impact based on cost correlation
546
- if cost_correlation and cost_correlation.cost_impact_level == 'high':
598
+ if cost_correlation and cost_correlation.cost_impact_level == "high":
547
599
  drift.business_impact += f" HIGH COST IMPACT: ${cost_correlation.monthly_cost:.2f}/month"
548
- if drift.risk_level == 'low':
549
- drift.risk_level = 'medium'
550
-
600
+ if drift.risk_level == "low":
601
+ drift.risk_level = "medium"
602
+
551
603
  except Exception as e:
552
604
  print_warning(f"Cost correlation failed for {drift.resource_id}: {str(e)[:30]}...")
553
-
605
+
554
606
  progress.advance(cost_task)
555
-
607
+
556
608
  return drift_analyses
557
609
 
558
610
  def _compare_resource_configurations(self, rb_config: Dict, tf_config: Dict) -> List[str]:
559
611
  """Compare resource configurations to identify drift."""
560
612
  differences = []
561
-
613
+
562
614
  # Compare common attributes that might indicate drift
563
615
  common_keys = set(rb_config.keys()) & set(tf_config.keys())
564
-
616
+
565
617
  for key in common_keys:
566
618
  rb_value = rb_config[key]
567
619
  tf_value = tf_config[key]
568
-
620
+
569
621
  # Skip certain keys that are expected to differ
570
- if key in ['last_modified', 'creation_date', 'timestamp', 'discovery_timestamp']:
622
+ if key in ["last_modified", "creation_date", "timestamp", "discovery_timestamp"]:
571
623
  continue
572
-
624
+
573
625
  if rb_value != tf_value:
574
626
  differences.append(f"{key}: runbooks='{rb_value}' vs terraform='{tf_value}'")
575
-
627
+
576
628
  # Check for keys only in one source
577
629
  rb_only = set(rb_config.keys()) - set(tf_config.keys())
578
630
  tf_only = set(tf_config.keys()) - set(rb_config.keys())
579
-
631
+
580
632
  for key in rb_only:
581
- if key not in ['discovery_module', 'discovery_timestamp']:
633
+ if key not in ["discovery_module", "discovery_timestamp"]:
582
634
  differences.append(f"{key}: only in runbooks ('{rb_config[key]}')")
583
-
635
+
584
636
  for key in tf_only:
585
- if key not in ['terraform_address']:
637
+ if key not in ["terraform_address"]:
586
638
  differences.append(f"{key}: only in terraform ('{tf_config[key]}')")
587
-
639
+
588
640
  return differences[:10] # Limit to first 10 differences
589
641
 
590
642
  def _assess_overall_risk(self, drift_analysis: List[DriftAnalysis], drift_percentage: float) -> str:
@@ -594,7 +646,7 @@ class TerraformDriftDetector:
594
646
  elif drift_percentage <= 10:
595
647
  return "low"
596
648
  elif drift_percentage <= 25:
597
- return "medium"
649
+ return "medium"
598
650
  elif drift_percentage <= 50:
599
651
  return "high"
600
652
  else:
@@ -602,8 +654,8 @@ class TerraformDriftDetector:
602
654
 
603
655
  def _assess_compliance_impact(self, drift_analysis: List[DriftAnalysis]) -> str:
604
656
  """Assess compliance impact of detected drift."""
605
- high_impact_drifts = [d for d in drift_analysis if d.drift_type == 'missing_from_terraform']
606
-
657
+ high_impact_drifts = [d for d in drift_analysis if d.drift_type == "missing_from_terraform"]
658
+
607
659
  if len(high_impact_drifts) == 0:
608
660
  return "minimal"
609
661
  elif len(high_impact_drifts) <= 3:
@@ -615,11 +667,11 @@ class TerraformDriftDetector:
615
667
 
616
668
  def _assess_remediation_priority(self, drift_analysis: List[DriftAnalysis], overall_risk: str) -> str:
617
669
  """Assess remediation priority."""
618
- critical_drifts = [d for d in drift_analysis if d.risk_level in ['high', 'critical']]
619
-
620
- if overall_risk in ['high', 'critical'] or len(critical_drifts) > 0:
670
+ critical_drifts = [d for d in drift_analysis if d.risk_level in ["high", "critical"]]
671
+
672
+ if overall_risk in ["high", "critical"] or len(critical_drifts) > 0:
621
673
  return "immediate"
622
- elif overall_risk == 'medium':
674
+ elif overall_risk == "medium":
623
675
  return "high"
624
676
  else:
625
677
  return "medium"
@@ -627,7 +679,7 @@ class TerraformDriftDetector:
627
679
  def _estimate_remediation_effort(self, drift_analysis: List[DriftAnalysis]) -> str:
628
680
  """Estimate remediation effort required."""
629
681
  total_drifts = len(drift_analysis)
630
-
682
+
631
683
  if total_drifts == 0:
632
684
  return "none"
633
685
  elif total_drifts <= 5:
@@ -643,37 +695,38 @@ class TerraformDriftDetector:
643
695
  """Get cost correlation data for a specific resource."""
644
696
  if not self.cost_processor:
645
697
  return None
646
-
698
+
647
699
  try:
648
700
  # Map resource type to service category
649
701
  service_category = self._map_resource_to_service(resource_type)
650
-
702
+
651
703
  # Generate mock cost data based on resource type and ID
652
704
  # In production, this would query Cost Explorer API with resource tags
653
705
  monthly_cost = self._estimate_resource_cost(resource_type, resource_id)
654
706
  yearly_cost = monthly_cost * 12
655
-
707
+
656
708
  # Determine cost impact level
657
709
  if monthly_cost >= 100:
658
- cost_impact_level = 'high'
710
+ cost_impact_level = "high"
659
711
  elif monthly_cost >= 20:
660
- cost_impact_level = 'medium'
712
+ cost_impact_level = "medium"
661
713
  else:
662
- cost_impact_level = 'low'
663
-
714
+ cost_impact_level = "low"
715
+
664
716
  # Simulate cost trend (would be based on historical data in production)
665
717
  import random
666
- cost_trend = random.choice(['stable', 'increasing', 'decreasing'])
667
-
718
+
719
+ cost_trend = random.choice(["stable", "increasing", "decreasing"])
720
+
668
721
  return CostCorrelation(
669
722
  resource_id=resource_id,
670
723
  monthly_cost=monthly_cost,
671
724
  yearly_cost_estimate=yearly_cost,
672
725
  cost_trend=cost_trend,
673
726
  cost_impact_level=cost_impact_level,
674
- service_category=service_category
727
+ service_category=service_category,
675
728
  )
676
-
729
+
677
730
  except Exception as e:
678
731
  print_warning(f"Failed to get cost correlation for {resource_id}: {e}")
679
732
  return None
@@ -681,45 +734,45 @@ class TerraformDriftDetector:
681
734
  def _map_resource_to_service(self, resource_type: str) -> str:
682
735
  """Map terraform resource type to AWS service category."""
683
736
  mapping = {
684
- 'aws_instance': 'Amazon EC2',
685
- 'aws_ec2_instance': 'Amazon EC2',
686
- 'aws_s3_bucket': 'Amazon S3',
687
- 'aws_rds_instance': 'Amazon RDS',
688
- 'aws_dynamodb_table': 'Amazon DynamoDB',
689
- 'aws_lambda_function': 'AWS Lambda',
690
- 'aws_vpc': 'Amazon VPC',
691
- 'aws_subnet': 'Amazon VPC',
692
- 'aws_security_group': 'Amazon VPC',
693
- 'aws_nat_gateway': 'Amazon VPC',
694
- 'aws_elastic_ip': 'Amazon EC2',
695
- 'aws_load_balancer': 'Elastic Load Balancing'
737
+ "aws_instance": "Amazon EC2",
738
+ "aws_ec2_instance": "Amazon EC2",
739
+ "aws_s3_bucket": "Amazon S3",
740
+ "aws_rds_instance": "Amazon RDS",
741
+ "aws_dynamodb_table": "Amazon DynamoDB",
742
+ "aws_lambda_function": "AWS Lambda",
743
+ "aws_vpc": "Amazon VPC",
744
+ "aws_subnet": "Amazon VPC",
745
+ "aws_security_group": "Amazon VPC",
746
+ "aws_nat_gateway": "Amazon VPC",
747
+ "aws_elastic_ip": "Amazon EC2",
748
+ "aws_load_balancer": "Elastic Load Balancing",
696
749
  }
697
- return mapping.get(resource_type.lower(), 'Other AWS Services')
750
+ return mapping.get(resource_type.lower(), "Other AWS Services")
698
751
 
699
752
  def _estimate_resource_cost(self, resource_type: str, resource_id: str) -> float:
700
753
  """Estimate monthly cost for a resource (mock implementation)."""
701
754
  # Cost estimates based on typical AWS pricing
702
755
  cost_estimates = {
703
- 'aws_instance': 30.0,
704
- 'aws_ec2_instance': 30.0,
705
- 'aws_s3_bucket': 5.0,
706
- 'aws_rds_instance': 75.0,
707
- 'aws_dynamodb_table': 15.0,
708
- 'aws_lambda_function': 2.0,
709
- 'aws_vpc': 0.0, # VPC itself is free
710
- 'aws_subnet': 0.0, # Subnet itself is free
711
- 'aws_security_group': 0.0, # Security group is free
712
- 'aws_nat_gateway': 45.0,
713
- 'aws_elastic_ip': 3.65, # $0.005/hour when not attached
714
- 'aws_load_balancer': 18.25
756
+ "aws_instance": 30.0,
757
+ "aws_ec2_instance": 30.0,
758
+ "aws_s3_bucket": 5.0,
759
+ "aws_rds_instance": 75.0,
760
+ "aws_dynamodb_table": 15.0,
761
+ "aws_lambda_function": 2.0,
762
+ "aws_vpc": 0.0, # VPC itself is free
763
+ "aws_subnet": 0.0, # Subnet itself is free
764
+ "aws_security_group": 0.0, # Security group is free
765
+ "aws_nat_gateway": 45.0,
766
+ "aws_elastic_ip": 3.65, # $0.005/hour when not attached
767
+ "aws_load_balancer": 18.25,
715
768
  }
716
-
769
+
717
770
  base_cost = cost_estimates.get(resource_type.lower(), 10.0)
718
-
771
+
719
772
  # Add some variation based on resource ID hash for realistic simulation
720
773
  variation_factor = (hash(resource_id) % 50) / 100.0 # ±50% variation
721
774
  final_cost = base_cost * (1 + variation_factor)
722
-
775
+
723
776
  return round(final_cost, 2)
724
777
 
725
778
  async def _calculate_cost_correlation_metrics(self, drift_analysis: List[DriftAnalysis]) -> Dict[str, Any]:
@@ -727,67 +780,74 @@ class TerraformDriftDetector:
727
780
  total_monthly_cost = 0.0
728
781
  high_cost_drifts = 0
729
782
  resources_with_cost_data = 0
730
-
783
+
731
784
  for drift in drift_analysis:
732
785
  if drift.cost_correlation:
733
786
  total_monthly_cost += drift.cost_correlation.monthly_cost
734
787
  resources_with_cost_data += 1
735
-
736
- if drift.cost_correlation.cost_impact_level == 'high':
788
+
789
+ if drift.cost_correlation.cost_impact_level == "high":
737
790
  high_cost_drifts += 1
738
-
791
+
739
792
  total_drifts = len(drift_analysis)
740
793
  correlation_coverage = (resources_with_cost_data / total_drifts * 100) if total_drifts > 0 else 0
741
-
794
+
742
795
  return {
743
- 'total_monthly_cost': total_monthly_cost,
744
- 'high_cost_drifts': high_cost_drifts,
745
- 'correlation_coverage': correlation_coverage,
746
- 'resources_with_cost_data': resources_with_cost_data
796
+ "total_monthly_cost": total_monthly_cost,
797
+ "high_cost_drifts": high_cost_drifts,
798
+ "correlation_coverage": correlation_coverage,
799
+ "resources_with_cost_data": resources_with_cost_data,
747
800
  }
748
801
 
749
- async def _perform_mcp_validation(self, drift_analysis: List[DriftAnalysis],
750
- runbooks_resources: List[RunbooksResource],
751
- terraform_resources: List[TerraformResource]) -> Dict[str, Any]:
802
+ async def _perform_mcp_validation(
803
+ self,
804
+ drift_analysis: List[DriftAnalysis],
805
+ runbooks_resources: List[RunbooksResource],
806
+ terraform_resources: List[TerraformResource],
807
+ ) -> Dict[str, Any]:
752
808
  """Perform MCP validation for drift detection accuracy."""
753
809
  try:
754
810
  print_info("🔍 Performing MCP cross-validation...")
755
-
811
+
756
812
  # Create validation data structure
757
813
  validation_data = {
758
- 'drift_analysis': [asdict(d) for d in drift_analysis],
759
- 'total_runbooks_resources': len(runbooks_resources),
760
- 'total_terraform_resources': len(terraform_resources),
761
- 'validation_timestamp': datetime.now().isoformat()
814
+ "drift_analysis": [asdict(d) for d in drift_analysis],
815
+ "total_runbooks_resources": len(runbooks_resources),
816
+ "total_terraform_resources": len(terraform_resources),
817
+ "validation_timestamp": datetime.now().isoformat(),
762
818
  }
763
-
764
- # Run MCP validation (simulated high accuracy)
819
+
820
+ # Run MCP validation with real accuracy calculation
765
821
  validation_result = await self.mcp_integrator.validate_vpc_operations(validation_data)
766
-
767
- # Calculate drift-specific accuracy metrics
768
- accuracy_score = 99.2 # High accuracy for direct comparison
769
-
822
+
823
+ # Use real accuracy from MCP validation (no hardcoded values)
824
+ accuracy_score = validation_result.accuracy_score # Real calculated accuracy
825
+
770
826
  return {
771
- 'success': validation_result.success,
772
- 'accuracy_score': accuracy_score,
773
- 'validation_timestamp': datetime.now().isoformat(),
774
- 'resources_validated': len(drift_analysis)
827
+ "success": validation_result.success,
828
+ "accuracy_score": accuracy_score,
829
+ "validation_timestamp": datetime.now().isoformat(),
830
+ "resources_validated": len(drift_analysis),
831
+ "total_validations": validation_result.performance_metrics.get("total_validations", 0),
832
+ "successful_validations": validation_result.performance_metrics.get("successful_validations", 0),
775
833
  }
776
-
834
+
777
835
  except Exception as e:
778
836
  print_warning(f"MCP validation error: {str(e)[:50]}...")
779
837
  return {
780
- 'success': False,
781
- 'accuracy_score': 95.0, # Default accuracy
782
- 'validation_timestamp': datetime.now().isoformat(),
783
- 'error': str(e)
838
+ "success": False,
839
+ "accuracy_score": 0.0, # Honest failure - no optimistic defaults
840
+ "validation_timestamp": datetime.now().isoformat(),
841
+ "error": str(e),
784
842
  }
785
843
 
786
- def _assess_cost_optimization_potential(self, drift_analysis: List[DriftAnalysis], cost_metrics: Dict[str, Any]) -> str:
844
+ def _assess_cost_optimization_potential(
845
+ self, drift_analysis: List[DriftAnalysis], cost_metrics: Dict[str, Any]
846
+ ) -> str:
787
847
  """Assess cost optimization potential from drift analysis."""
788
- total_cost = cost_metrics.get('total_monthly_cost', 0.0)
789
- high_cost_drifts = cost_metrics.get('high_cost_drifts', 0)
790
-
848
+ total_cost = cost_metrics.get("total_monthly_cost", 0.0)
849
+ high_cost_drifts = cost_metrics.get("high_cost_drifts", 0)
850
+
791
851
  if total_cost == 0:
792
852
  return "minimal"
793
853
  elif total_cost >= 500:
@@ -799,93 +859,67 @@ class TerraformDriftDetector:
799
859
 
800
860
  def _display_drift_results(self, drift_result: TerraformDriftResult):
801
861
  """Display drift detection results."""
802
-
862
+
803
863
  # Create drift summary table
804
864
  drift_table = create_table(
805
865
  title="Infrastructure Drift Analysis",
806
866
  columns=[
807
867
  {"name": "Metric", "style": "cyan", "width": 25},
808
868
  {"name": "Value", "style": "white", "justify": "right"},
809
- {"name": "Assessment", "style": "yellow", "justify": "center"}
810
- ]
811
- )
812
-
813
- drift_table.add_row(
814
- "Resources in Terraform",
815
- str(drift_result.total_resources_terraform),
816
- "📊"
817
- )
818
-
819
- drift_table.add_row(
820
- "Resources in Runbooks",
821
- str(drift_result.total_resources_runbooks),
822
- "🔍"
869
+ {"name": "Assessment", "style": "yellow", "justify": "center"},
870
+ ],
823
871
  )
824
-
825
- drift_table.add_row(
826
- "Resources in Sync",
827
- str(drift_result.resources_in_sync),
828
- "✅"
829
- )
830
-
872
+
873
+ drift_table.add_row("Resources in Terraform", str(drift_result.total_resources_terraform), "📊")
874
+
875
+ drift_table.add_row("Resources in Runbooks", str(drift_result.total_resources_runbooks), "🔍")
876
+
877
+ drift_table.add_row("Resources in Sync", str(drift_result.resources_in_sync), "✅")
878
+
831
879
  drift_table.add_row(
832
880
  "Resources with Drift",
833
881
  str(drift_result.resources_with_drift),
834
- "⚠️" if drift_result.resources_with_drift > 0 else "✅"
882
+ "⚠️" if drift_result.resources_with_drift > 0 else "✅",
835
883
  )
836
-
884
+
837
885
  drift_table.add_row(
838
886
  "Drift Percentage",
839
887
  f"{drift_result.drift_percentage:.1f}%",
840
- self._get_drift_status_emoji(drift_result.drift_percentage)
888
+ self._get_drift_status_emoji(drift_result.drift_percentage),
841
889
  )
842
-
890
+
843
891
  drift_table.add_row(
844
892
  "Overall Risk Level",
845
893
  drift_result.overall_risk_level.upper(),
846
- self._get_risk_status_emoji(drift_result.overall_risk_level)
894
+ self._get_risk_status_emoji(drift_result.overall_risk_level),
847
895
  )
848
-
896
+
849
897
  # Add cost correlation metrics
898
+ drift_table.add_row("Monthly Cost Impact", format_cost(drift_result.total_monthly_cost_impact), "💰")
899
+
850
900
  drift_table.add_row(
851
- "Monthly Cost Impact",
852
- format_cost(drift_result.total_monthly_cost_impact),
853
- "💰"
854
- )
855
-
856
- drift_table.add_row(
857
- "High Cost Drifts",
858
- str(drift_result.high_cost_drifts),
859
- "🔥" if drift_result.high_cost_drifts > 0 else "✅"
860
- )
861
-
862
- drift_table.add_row(
863
- "Cost Correlation Coverage",
864
- f"{drift_result.cost_correlation_coverage:.1f}%",
865
- "📊"
866
- )
867
-
868
- drift_table.add_row(
869
- "MCP Validation Accuracy",
870
- f"{drift_result.mcp_validation_accuracy:.1f}%",
871
- "🔍"
901
+ "High Cost Drifts", str(drift_result.high_cost_drifts), "🔥" if drift_result.high_cost_drifts > 0 else "✅"
872
902
  )
873
-
903
+
904
+ drift_table.add_row("Cost Correlation Coverage", f"{drift_result.cost_correlation_coverage:.1f}%", "📊")
905
+
906
+ drift_table.add_row("MCP Validation Accuracy", f"{drift_result.mcp_validation_accuracy:.1f}%", "🔍")
907
+
874
908
  console.print(drift_table)
875
-
909
+
876
910
  # Display drift details if any
877
911
  if drift_result.drift_analysis:
878
912
  print_warning(f"⚠️ {len(drift_result.drift_analysis)} infrastructure drift(s) detected:")
879
-
913
+
880
914
  for i, drift in enumerate(drift_result.drift_analysis[:5], 1): # Show first 5
881
915
  cost_info = ""
882
916
  if drift.cost_correlation:
883
917
  cost_info = f" (${drift.cost_correlation.monthly_cost:.2f}/month, {drift.cost_correlation.cost_impact_level} impact)"
884
918
  print_info(f" {i}. {drift.resource_type} ({drift.resource_id}): {drift.drift_type}{cost_info}")
885
-
919
+
886
920
  if len(drift_result.drift_analysis) > 5:
887
921
  print_info(f" ... and {len(drift_result.drift_analysis) - 5} more")
888
-
922
+
889
923
  # Business impact panel
890
924
  impact_text = f"""🏗️ Infrastructure Alignment Assessment with Cost Correlation
891
925
 
@@ -911,19 +945,14 @@ Estimated Effort: {drift_result.estimated_remediation_effort}
911
945
  • Compliance documentation and audit trail support
912
946
  • Risk mitigation through systematic drift resolution"""
913
947
 
914
- risk_color = {
915
- 'low': 'green',
916
- 'medium': 'yellow',
917
- 'high': 'red',
918
- 'critical': 'red'
919
- }.get(drift_result.overall_risk_level, 'white')
948
+ risk_color = {"low": "green", "medium": "yellow", "high": "red", "critical": "red"}.get(
949
+ drift_result.overall_risk_level, "white"
950
+ )
920
951
 
921
952
  impact_panel = create_panel(
922
- impact_text,
923
- title="Infrastructure Drift Impact Assessment",
924
- border_style=risk_color
953
+ impact_text, title="Infrastructure Drift Impact Assessment", border_style=risk_color
925
954
  )
926
-
955
+
927
956
  console.print(impact_panel)
928
957
 
929
958
  def _get_drift_status_emoji(self, drift_percentage: float) -> str:
@@ -939,138 +968,115 @@ Estimated Effort: {drift_result.estimated_remediation_effort}
939
968
 
940
969
  def _get_risk_status_emoji(self, risk_level: str) -> str:
941
970
  """Get risk status emoji."""
942
- return {
943
- 'low': '✅',
944
- 'medium': '🟡',
945
- 'high': '🟠',
946
- 'critical': '🔴'
947
- }.get(risk_level, '⚪')
971
+ return {"low": "✅", "medium": "🟡", "high": "🟠", "critical": "🔴"}.get(risk_level, "⚪")
948
972
 
949
973
  def _generate_drift_evidence(self, drift_result: TerraformDriftResult) -> str:
950
974
  """Generate drift detection evidence file."""
951
975
  timestamp = drift_result.detection_timestamp.strftime("%Y%m%d_%H%M%S")
952
976
  evidence_file = self.drift_evidence_dir / f"terraform_drift_analysis_{timestamp}.json"
953
-
977
+
954
978
  evidence_data = {
955
- 'drift_analysis_metadata': {
956
- 'analysis_id': drift_result.drift_detection_id,
957
- 'timestamp': drift_result.detection_timestamp.isoformat(),
958
- 'framework_version': '1.0.0',
959
- 'enterprise_coordination': 'qa-testing-specialist → cloud-architect',
960
- 'strategic_objective': 'infrastructure_alignment_validation'
979
+ "drift_analysis_metadata": {
980
+ "analysis_id": drift_result.drift_detection_id,
981
+ "timestamp": drift_result.detection_timestamp.isoformat(),
982
+ "framework_version": "1.0.0",
983
+ "enterprise_coordination": "qa-testing-specialist → cloud-architect",
984
+ "strategic_objective": "infrastructure_alignment_validation",
961
985
  },
962
- 'drift_detection_results': asdict(drift_result),
963
- 'enterprise_assessment': {
964
- 'governance_alignment': drift_result.overall_risk_level != 'critical',
965
- 'compliance_documentation': 'comprehensive',
966
- 'audit_trail': 'complete',
967
- 'risk_mitigation_required': drift_result.remediation_priority in ['immediate', 'high']
986
+ "drift_detection_results": asdict(drift_result),
987
+ "enterprise_assessment": {
988
+ "governance_alignment": drift_result.overall_risk_level != "critical",
989
+ "compliance_documentation": "comprehensive",
990
+ "audit_trail": "complete",
991
+ "risk_mitigation_required": drift_result.remediation_priority in ["immediate", "high"],
992
+ },
993
+ "business_recommendations": self._generate_drift_recommendations(drift_result),
994
+ "compliance_attestation": {
995
+ "infrastructure_governance": True,
996
+ "drift_detection": "automated",
997
+ "remediation_tracking": "available",
998
+ "audit_evidence": "comprehensive",
968
999
  },
969
- 'business_recommendations': self._generate_drift_recommendations(drift_result),
970
- 'compliance_attestation': {
971
- 'infrastructure_governance': True,
972
- 'drift_detection': 'automated',
973
- 'remediation_tracking': 'available',
974
- 'audit_evidence': 'comprehensive'
975
- }
976
1000
  }
977
-
978
- with open(evidence_file, 'w') as f:
1001
+
1002
+ with open(evidence_file, "w") as f:
979
1003
  json.dump(evidence_data, f, indent=2, default=str)
980
-
1004
+
981
1005
  print_success(f"📄 Drift evidence generated: {evidence_file.name}")
982
1006
  return str(evidence_file)
983
1007
 
984
1008
  def _generate_drift_recommendations(self, drift_result: TerraformDriftResult) -> List[str]:
985
1009
  """Generate drift-specific recommendations."""
986
1010
  recommendations = []
987
-
1011
+
988
1012
  if drift_result.drift_percentage == 0:
989
- recommendations.extend([
990
- "✅ Infrastructure alignment validated - no drift detected",
991
- "🏗️ Terraform state and runbooks discoveries in sync",
992
- "📊 Continue monitoring for future drift detection"
993
- ])
1013
+ recommendations.extend(
1014
+ [
1015
+ " Infrastructure alignment validated - no drift detected",
1016
+ "🏗️ Terraform state and runbooks discoveries in sync",
1017
+ "📊 Continue monitoring for future drift detection",
1018
+ ]
1019
+ )
994
1020
  else:
995
- recommendations.extend([
996
- f"⚠️ {drift_result.drift_percentage:.1f}% infrastructure drift detected - remediation required",
997
- f"🔧 Priority: {drift_result.remediation_priority} (estimated effort: {drift_result.estimated_remediation_effort})",
998
- f"📋 Review {len(drift_result.missing_from_terraform)} resources missing from terraform management"
999
- ])
1000
-
1021
+ recommendations.extend(
1022
+ [
1023
+ f"⚠️ {drift_result.drift_percentage:.1f}% infrastructure drift detected - remediation required",
1024
+ f"🔧 Priority: {drift_result.remediation_priority} (estimated effort: {drift_result.estimated_remediation_effort})",
1025
+ f"📋 Review {len(drift_result.missing_from_terraform)} resources missing from terraform management",
1026
+ ]
1027
+ )
1028
+
1001
1029
  if drift_result.missing_from_runbooks:
1002
1030
  recommendations.append("🔍 Investigate runbooks discovery gaps and AWS permission scope")
1003
-
1031
+
1004
1032
  if drift_result.configuration_drifts:
1005
1033
  recommendations.append("⚙️ Review terraform plan and apply configuration updates")
1006
-
1007
- recommendations.extend([
1008
- "🏗️ Implement automated drift detection in CI/CD pipeline",
1009
- "📊 Establish drift monitoring dashboards for continuous governance",
1010
- "💼 Document infrastructure governance processes for compliance"
1011
- ])
1012
-
1034
+
1035
+ recommendations.extend(
1036
+ [
1037
+ "🏗️ Implement automated drift detection in CI/CD pipeline",
1038
+ "📊 Establish drift monitoring dashboards for continuous governance",
1039
+ "💼 Document infrastructure governance processes for compliance",
1040
+ ]
1041
+ )
1042
+
1013
1043
  return recommendations
1014
1044
 
1045
+
1015
1046
  # CLI interface for drift detection
1016
1047
  async def main():
1017
1048
  """Main CLI interface for terraform drift detection."""
1018
1049
  import argparse
1019
-
1020
- parser = argparse.ArgumentParser(
1021
- description="Terraform Drift Detector - Infrastructure Alignment Validation"
1022
- )
1023
- parser.add_argument(
1024
- "--runbooks-evidence",
1025
- required=True,
1026
- help="Path to runbooks evidence file (JSON or CSV)"
1027
- )
1028
- parser.add_argument(
1029
- "--terraform-state",
1030
- help="Path to terraform state file (optional - will auto-discover)"
1031
- )
1050
+
1051
+ parser = argparse.ArgumentParser(description="Terraform Drift Detector - Infrastructure Alignment Validation")
1052
+ parser.add_argument("--runbooks-evidence", required=True, help="Path to runbooks evidence file (JSON or CSV)")
1053
+ parser.add_argument("--terraform-state", help="Path to terraform state file (optional - will auto-discover)")
1032
1054
  parser.add_argument(
1033
1055
  "--terraform-state-dir",
1034
1056
  default="terraform",
1035
- help="Directory containing terraform state files (default: terraform)"
1036
- )
1037
- parser.add_argument(
1038
- "--resource-types",
1039
- nargs="+",
1040
- help="Specific resource types to analyze (e.g., aws_vpc aws_subnet)"
1057
+ help="Directory containing terraform state files (default: terraform)",
1041
1058
  )
1042
1059
  parser.add_argument(
1043
- "--export-evidence",
1044
- action="store_true",
1045
- help="Export drift analysis evidence file"
1060
+ "--resource-types", nargs="+", help="Specific resource types to analyze (e.g., aws_vpc aws_subnet)"
1046
1061
  )
1047
- parser.add_argument(
1048
- "--profile",
1049
- help="AWS profile for cost correlation analysis"
1050
- )
1051
- parser.add_argument(
1052
- "--disable-cost-correlation",
1053
- action="store_true",
1054
- help="Disable cost correlation analysis"
1055
- )
1056
-
1062
+ parser.add_argument("--export-evidence", action="store_true", help="Export drift analysis evidence file")
1063
+ parser.add_argument("--profile", help="AWS profile for cost correlation analysis")
1064
+ parser.add_argument("--disable-cost-correlation", action="store_true", help="Disable cost correlation analysis")
1065
+
1057
1066
  args = parser.parse_args()
1058
-
1067
+
1059
1068
  # Initialize enhanced drift detector
1060
- detector = TerraformDriftDetector(
1061
- terraform_state_dir=args.terraform_state_dir,
1062
- user_profile=args.profile
1063
- )
1064
-
1069
+ detector = TerraformDriftDetector(terraform_state_dir=args.terraform_state_dir, user_profile=args.profile)
1070
+
1065
1071
  try:
1066
1072
  # Run enhanced drift detection with cost correlation
1067
1073
  drift_result = await detector.detect_infrastructure_drift(
1068
1074
  runbooks_evidence_file=args.runbooks_evidence,
1069
1075
  terraform_state_file=args.terraform_state,
1070
1076
  resource_types=args.resource_types,
1071
- enable_cost_correlation=not args.disable_cost_correlation
1077
+ enable_cost_correlation=not args.disable_cost_correlation,
1072
1078
  )
1073
-
1079
+
1074
1080
  # Summary with cost correlation
1075
1081
  if drift_result.drift_percentage == 0:
1076
1082
  print_success("✅ INFRASTRUCTURE ALIGNED: No drift detected")
@@ -1082,12 +1088,12 @@ async def main():
1082
1088
  else:
1083
1089
  print_error(f"🚨 SIGNIFICANT DRIFT: {drift_result.drift_percentage:.1f}% - immediate attention required")
1084
1090
  print_error(f"💰 HIGH COST RISK: {format_cost(drift_result.total_monthly_cost_impact)}/month")
1085
-
1091
+
1086
1092
  print_info(f"📊 Overall Risk Level: {drift_result.overall_risk_level.upper()}")
1087
1093
  print_info(f"🔧 Remediation Priority: {drift_result.remediation_priority.upper()}")
1088
1094
  print_info(f"💰 Cost Optimization Potential: {drift_result.cost_optimization_potential}")
1089
1095
  print_info(f"🔍 MCP Validation Accuracy: {drift_result.mcp_validation_accuracy:.1f}%")
1090
-
1096
+
1091
1097
  except Exception as e:
1092
1098
  print_error(f"❌ Drift detection failed: {str(e)}")
1093
1099
  raise
@@ -1095,4 +1101,5 @@ async def main():
1095
1101
 
1096
1102
  if __name__ == "__main__":
1097
1103
  import asyncio
1098
- asyncio.run(main())
1104
+
1105
+ asyncio.run(main())