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
@@ -35,12 +35,13 @@ from pathlib import Path
35
35
  from typing import Any, Dict, List, Optional, Tuple, Union
36
36
 
37
37
  import boto3
38
- from rich.console import Console
39
- from rich.progress import BarColumn, Progress, SpinnerColumn, TaskProgressColumn, TextColumn, TimeElapsedColumn
38
+ from rich.progress import BarColumn, SpinnerColumn, TaskProgressColumn, TextColumn, TimeElapsedColumn
39
+ from runbooks.common.rich_utils import Progress
40
40
  from rich.table import Table
41
41
 
42
42
  from ..common.profile_utils import get_profile_for_operation, resolve_profile_for_operation_silent
43
43
  from ..common.rich_utils import (
44
+ Console,
44
45
  console as rich_console,
45
46
  create_table,
46
47
  format_cost,
@@ -58,10 +59,10 @@ class EnhancedMCPValidator:
58
59
  Provides enterprise-grade validation using actual MCP servers from .mcp.json configuration,
59
60
  with 4-way cross-validation: runbooks inventory + direct AWS APIs + MCP servers + terraform state.
60
61
  Ensures ≥99.5% accuracy for enterprise compliance with comprehensive drift detection.
61
-
62
+
62
63
  Enhanced Features:
63
64
  - Real MCP server integration from .mcp.json configuration
64
- - Enterprise AWS profile override priority system (User > Environment > Default)
65
+ - Enterprise AWS profile override priority system (User > Environment > Default)
65
66
  - Multi-server validation: aws-api, cost-explorer, iam, cloudwatch, terraform-mcp
66
67
  - 4-way validation: runbooks + direct APIs + MCP servers + terraform drift
67
68
  - Real-time variance detection with configurable tolerance
@@ -70,11 +71,16 @@ class EnhancedMCPValidator:
70
71
  - Complete audit trails with evidence-based validation
71
72
  """
72
73
 
73
- def __init__(self, user_profile: Optional[str] = None, console: Optional[Console] = None,
74
- mcp_config_path: Optional[str] = None, terraform_directory: Optional[str] = None):
74
+ def __init__(
75
+ self,
76
+ user_profile: Optional[str] = None,
77
+ console: Optional[Console] = None,
78
+ mcp_config_path: Optional[str] = None,
79
+ terraform_directory: Optional[str] = None,
80
+ ):
75
81
  """
76
82
  Initialize enhanced MCP validator with enterprise profile management and MCP server integration.
77
-
83
+
78
84
  Args:
79
85
  user_profile: User-specified profile (--profile parameter) - takes priority over environment
80
86
  console: Rich console for output (optional)
@@ -87,16 +93,16 @@ class EnhancedMCPValidator:
87
93
  self.tolerance_percent = 5.0 # ±5% tolerance for resource count validation
88
94
  self.validation_cache = {} # Cache for performance optimization
89
95
  self.cache_ttl = 300 # 5 minutes cache TTL
90
-
96
+
91
97
  # MCP Server Integration
92
98
  self.mcp_config_path = mcp_config_path or "/Volumes/Working/1xOps/CloudOps-Runbooks/.mcp.json"
93
99
  self.mcp_servers = {}
94
100
  self.mcp_processes = {} # Track running MCP server processes
95
-
101
+
96
102
  # AWS Profile Management following proven patterns
97
103
  self.enterprise_profiles = self._resolve_enterprise_profiles()
98
104
  self.aws_sessions = {}
99
-
105
+
100
106
  # Terraform integration
101
107
  self.terraform_directory = terraform_directory or "/Volumes/Working/1xOps/CloudOps-Runbooks/terraform-aws"
102
108
  self.terraform_cache = {} # Cache terraform state parsing
@@ -104,18 +110,18 @@ class EnhancedMCPValidator:
104
110
 
105
111
  # Supported AWS services for inventory validation
106
112
  self.supported_services = {
107
- 'ec2': 'EC2 Instances',
108
- 's3': 'S3 Buckets',
109
- 'rds': 'RDS Instances',
110
- 'lambda': 'Lambda Functions',
111
- 'vpc': 'VPCs',
112
- 'iam': 'IAM Roles',
113
- 'cloudformation': 'CloudFormation Stacks',
114
- 'elbv2': 'Load Balancers',
115
- 'route53': 'Route53 Hosted Zones',
116
- 'sns': 'SNS Topics',
117
- 'eni': 'Network Interfaces',
118
- 'ebs': 'EBS Volumes'
113
+ "ec2": "EC2 Instances",
114
+ "s3": "S3 Buckets",
115
+ "rds": "RDS Instances",
116
+ "lambda": "Lambda Functions",
117
+ "vpc": "VPCs",
118
+ "iam": "IAM Roles",
119
+ "cloudformation": "CloudFormation Stacks",
120
+ "elbv2": "Load Balancers",
121
+ "route53": "Route53 Hosted Zones",
122
+ "sns": "SNS Topics",
123
+ "eni": "Network Interfaces",
124
+ "ebs": "EBS Volumes",
119
125
  }
120
126
 
121
127
  # Initialize components
@@ -126,7 +132,7 @@ class EnhancedMCPValidator:
126
132
  def _resolve_enterprise_profiles(self) -> Dict[str, str]:
127
133
  """
128
134
  Resolve enterprise AWS profiles using proven 3-tier priority system.
129
-
135
+
130
136
  Returns:
131
137
  Dict mapping operation types to resolved profile names
132
138
  """
@@ -136,7 +142,7 @@ class EnhancedMCPValidator:
136
142
  "operational": resolve_profile_for_operation_silent("operational", self.user_profile),
137
143
  "single_account": resolve_profile_for_operation_silent("single_account", self.user_profile),
138
144
  }
139
-
145
+
140
146
  def _load_mcp_configuration(self) -> None:
141
147
  """Load and parse MCP server configuration from .mcp.json."""
142
148
  try:
@@ -144,90 +150,88 @@ class EnhancedMCPValidator:
144
150
  print_warning(f"MCP configuration not found: {self.mcp_config_path}")
145
151
  self.mcp_servers = {}
146
152
  return
147
-
148
- with open(self.mcp_config_path, 'r') as f:
153
+
154
+ with open(self.mcp_config_path, "r") as f:
149
155
  config = json.load(f)
150
-
156
+
151
157
  self.mcp_servers = config.get("mcpServers", {})
152
-
158
+
153
159
  # Log MCP server availability
154
160
  available_servers = list(self.mcp_servers.keys())
155
- relevant_servers = [s for s in available_servers if s in ['aws-api', 'cost-explorer', 'iam', 'cloudwatch', 'terraform-mcp']]
156
-
157
- print_info(f"MCP servers available: {len(available_servers)} total, {len(relevant_servers)} validation-relevant")
161
+ relevant_servers = [
162
+ s for s in available_servers if s in ["aws-api", "cost-explorer", "iam", "cloudwatch", "terraform-mcp"]
163
+ ]
164
+
165
+ print_info(
166
+ f"MCP servers available: {len(available_servers)} total, {len(relevant_servers)} validation-relevant"
167
+ )
158
168
  if relevant_servers:
159
169
  self.console.log(f"[dim cyan]Validation servers: {', '.join(relevant_servers)}[/]")
160
-
170
+
161
171
  except Exception as e:
162
172
  print_warning(f"Failed to load MCP configuration: {str(e)}")
163
173
  self.mcp_servers = {}
164
-
174
+
165
175
  def _substitute_environment_variables(self, server_config: Dict[str, Any]) -> Dict[str, Any]:
166
176
  """
167
177
  Substitute environment variables in MCP server configuration with resolved profiles.
168
-
178
+
169
179
  Args:
170
180
  server_config: MCP server configuration dictionary
171
-
181
+
172
182
  Returns:
173
183
  Configuration with environment variables resolved
174
184
  """
175
185
  config = server_config.copy()
176
-
186
+
177
187
  if "env" in config:
178
188
  env = config["env"].copy()
179
-
189
+
180
190
  # Substitute profile environment variables with resolved enterprise profiles
181
191
  profile_substitutions = {
182
192
  "${AWS_BILLING_PROFILE}": self.enterprise_profiles["billing"],
183
193
  "${AWS_MANAGEMENT_PROFILE}": self.enterprise_profiles["management"],
184
194
  "${AWS_CENTRALISED_OPS_PROFILE}": self.enterprise_profiles["operational"],
185
195
  }
186
-
196
+
187
197
  for key, value in env.items():
188
198
  if isinstance(value, str):
189
199
  for placeholder, resolved_profile in profile_substitutions.items():
190
200
  if placeholder in value:
191
201
  env[key] = value.replace(placeholder, resolved_profile)
192
202
  self.console.log(f"[dim]MCP {key}: {placeholder} → {resolved_profile}[/]")
193
-
203
+
194
204
  config["env"] = env
195
-
205
+
196
206
  return config
197
-
207
+
198
208
  async def _start_mcp_server(self, server_name: str, server_config: Dict[str, Any]) -> Optional[subprocess.Popen]:
199
209
  """
200
210
  Start an MCP server process with resolved environment variables.
201
-
211
+
202
212
  Args:
203
213
  server_name: Name of the MCP server
204
214
  server_config: Server configuration dictionary
205
-
215
+
206
216
  Returns:
207
217
  Popen process object if successful, None if failed
208
218
  """
209
219
  try:
210
220
  # Substitute environment variables
211
221
  resolved_config = self._substitute_environment_variables(server_config)
212
-
222
+
213
223
  # Build command
214
224
  command = [resolved_config["command"]] + resolved_config.get("args", [])
215
225
  env = os.environ.copy()
216
226
  env.update(resolved_config.get("env", {}))
217
-
227
+
218
228
  # Start process
219
229
  self.console.log(f"[dim]Starting MCP server: {server_name}[/]")
220
- process = subprocess.Popen(
221
- command,
222
- stdout=subprocess.PIPE,
223
- stderr=subprocess.PIPE,
224
- env=env,
225
- text=True
226
- )
227
-
230
+ process = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, env=env, text=True)
231
+
228
232
  # Give process time to start
229
233
  await asyncio.sleep(2)
230
-
234
+
231
235
  # Check if process is still running
232
236
  if process.poll() is None:
233
237
  self.mcp_processes[server_name] = process
@@ -237,11 +241,11 @@ class EnhancedMCPValidator:
237
241
  stdout, stderr = process.communicate()
238
242
  print_warning(f"MCP server '{server_name}' failed to start: {stderr[:100]}")
239
243
  return None
240
-
244
+
241
245
  except Exception as e:
242
246
  print_warning(f"Failed to start MCP server '{server_name}': {str(e)}")
243
247
  return None
244
-
248
+
245
249
  def _stop_mcp_servers(self) -> None:
246
250
  """Stop all running MCP server processes."""
247
251
  for server_name, process in self.mcp_processes.items():
@@ -251,13 +255,13 @@ class EnhancedMCPValidator:
251
255
  self.console.log(f"[dim]Stopped MCP server: {server_name}[/]")
252
256
  except Exception as e:
253
257
  self.console.log(f"[yellow]Warning: Could not stop MCP server {server_name}: {str(e)}[/]")
254
-
258
+
255
259
  self.mcp_processes.clear()
256
260
 
257
261
  def _initialize_aws_sessions(self) -> None:
258
262
  """Initialize AWS sessions for all enterprise profiles with enhanced error handling."""
259
263
  successful_sessions = 0
260
-
264
+
261
265
  for operation_type, profile_name in self.enterprise_profiles.items():
262
266
  try:
263
267
  # Validate profile exists in AWS config
@@ -265,42 +269,50 @@ class EnhancedMCPValidator:
265
269
  if profile_name not in available_profiles:
266
270
  print_warning(f"Profile '{profile_name}' not found in AWS config for {operation_type}")
267
271
  continue
268
-
272
+
269
273
  session = boto3.Session(profile_name=profile_name)
270
-
274
+
271
275
  # Test session validity with timeout
272
276
  try:
273
277
  sts_client = session.client("sts")
274
278
  identity = sts_client.get_caller_identity()
275
-
279
+
276
280
  self.aws_sessions[operation_type] = {
277
281
  "session": session,
278
282
  "profile": profile_name,
279
283
  "account_id": identity.get("Account"),
280
284
  "user_id": identity.get("UserId", "Unknown"),
281
- "region": session.region_name or "us-east-1"
285
+ "region": session.region_name or "us-east-1",
282
286
  }
283
-
287
+
284
288
  successful_sessions += 1
285
- print_info(f"✅ MCP session for {operation_type}: {profile_name[:30]}... → Account {identity.get('Account', 'Unknown')}")
286
-
289
+ print_info(
290
+ f"✅ MCP session for {operation_type}: {profile_name[:30]}... → Account {identity.get('Account', 'Unknown')}"
291
+ )
292
+
287
293
  except Exception as sts_error:
288
294
  if "expired" in str(sts_error).lower() or "token" in str(sts_error).lower():
289
- print_warning(f"AWS SSO token expired for {operation_type}. Run: aws sso login --profile {profile_name}")
295
+ print_warning(
296
+ f"AWS SSO token expired for {operation_type}. Run: aws sso login --profile {profile_name}"
297
+ )
290
298
  else:
291
299
  print_warning(f"STS validation failed for {operation_type}: {str(sts_error)[:40]}")
292
-
300
+
293
301
  except Exception as e:
294
302
  print_warning(f"Session creation failed for {operation_type} ({profile_name[:20]}...): {str(e)[:40]}")
295
-
303
+
296
304
  # Log overall session status
297
305
  total_profiles = len(self.enterprise_profiles)
298
- self.console.log(f"[dim]AWS sessions: {successful_sessions}/{total_profiles} profiles initialized successfully[/]")
299
-
306
+ self.console.log(
307
+ f"[dim]AWS sessions: {successful_sessions}/{total_profiles} profiles initialized successfully[/]"
308
+ )
309
+
300
310
  if successful_sessions == 0:
301
311
  print_error("No AWS sessions could be initialized. Check profile configuration and SSO status.")
302
312
  elif successful_sessions < total_profiles:
303
- print_warning(f"Only {successful_sessions}/{total_profiles} AWS sessions initialized. Some validations may be limited.")
313
+ print_warning(
314
+ f"Only {successful_sessions}/{total_profiles} AWS sessions initialized. Some validations may be limited."
315
+ )
304
316
 
305
317
  def _discover_terraform_state_files(self) -> None:
306
318
  """Discover terraform state files and configurations in the terraform directory."""
@@ -309,22 +321,22 @@ class EnhancedMCPValidator:
309
321
  if not terraform_path.exists():
310
322
  print_warning(f"Terraform directory not found: {self.terraform_directory}")
311
323
  return
312
-
324
+
313
325
  # Look for terraform configuration files and state references
314
326
  config_files = []
315
327
  state_references = []
316
-
328
+
317
329
  # Search for terraform files recursively
318
330
  for tf_file in terraform_path.rglob("*.tf"):
319
331
  config_files.append(str(tf_file))
320
-
321
- # Search for state configuration files
332
+
333
+ # Search for state configuration files
322
334
  for state_file in terraform_path.rglob("state.tf"):
323
335
  state_references.append(str(state_file))
324
-
336
+
325
337
  self.terraform_state_files = state_references
326
338
  print_info(f"Discovered {len(config_files)} terraform files, {len(state_references)} state configurations")
327
-
339
+
328
340
  except Exception as e:
329
341
  print_warning(f"Failed to discover terraform files: {str(e)[:50]}")
330
342
  self.terraform_state_files = []
@@ -332,17 +344,17 @@ class EnhancedMCPValidator:
332
344
  def _parse_terraform_state_config(self, state_file: str) -> Dict[str, Any]:
333
345
  """
334
346
  Parse terraform state configuration to extract resource declarations.
335
-
347
+
336
348
  Args:
337
349
  state_file: Path to terraform state.tf file
338
-
350
+
339
351
  Returns:
340
352
  Dictionary containing parsed terraform configuration
341
353
  """
342
354
  try:
343
- with open(state_file, 'r') as f:
355
+ with open(state_file, "r") as f:
344
356
  content = f.read()
345
-
357
+
346
358
  # Extract account ID from directory structure
347
359
  account_id = None
348
360
  path_parts = Path(state_file).parts
@@ -352,73 +364,73 @@ class EnhancedMCPValidator:
352
364
  if potential_account.isdigit() and len(potential_account) == 12:
353
365
  account_id = potential_account
354
366
  break
355
-
367
+
356
368
  # Extract backend configuration
357
369
  backend_bucket = None
358
370
  backend_key = None
359
371
  dynamodb_table = None
360
-
372
+
361
373
  # Simple parsing for S3 backend configuration
362
- lines = content.split('\n')
374
+ lines = content.split("\n")
363
375
  in_backend = False
364
376
  for line in lines:
365
377
  line = line.strip()
366
378
  if 'backend "s3"' in line:
367
379
  in_backend = True
368
380
  continue
369
- if in_backend and line.startswith('bucket'):
370
- backend_bucket = line.split('=')[1].strip().strip('"')
371
- elif in_backend and line.startswith('key'):
372
- backend_key = line.split('=')[1].strip().strip('"')
373
- elif in_backend and line.startswith('dynamodb_table'):
374
- dynamodb_table = line.split('=')[1].strip().strip('"')
375
- elif in_backend and line == '}':
381
+ if in_backend and line.startswith("bucket"):
382
+ backend_bucket = line.split("=")[1].strip().strip('"')
383
+ elif in_backend and line.startswith("key"):
384
+ backend_key = line.split("=")[1].strip().strip('"')
385
+ elif in_backend and line.startswith("dynamodb_table"):
386
+ dynamodb_table = line.split("=")[1].strip().strip('"')
387
+ elif in_backend and line == "}":
376
388
  in_backend = False
377
-
389
+
378
390
  return {
379
- 'file_path': state_file,
380
- 'account_id': account_id,
381
- 'backend_bucket': backend_bucket,
382
- 'backend_key': backend_key,
383
- 'dynamodb_table': dynamodb_table,
384
- 'directory': str(Path(state_file).parent),
385
- 'parsed_timestamp': datetime.now().isoformat(),
391
+ "file_path": state_file,
392
+ "account_id": account_id,
393
+ "backend_bucket": backend_bucket,
394
+ "backend_key": backend_key,
395
+ "dynamodb_table": dynamodb_table,
396
+ "directory": str(Path(state_file).parent),
397
+ "parsed_timestamp": datetime.now().isoformat(),
386
398
  }
387
-
399
+
388
400
  except Exception as e:
389
401
  print_warning(f"Failed to parse terraform state file {state_file}: {str(e)[:50]}")
390
402
  return {
391
- 'file_path': state_file,
392
- 'error': str(e),
393
- 'parsed_timestamp': datetime.now().isoformat(),
403
+ "file_path": state_file,
404
+ "error": str(e),
405
+ "parsed_timestamp": datetime.now().isoformat(),
394
406
  }
395
407
 
396
408
  def _get_terraform_declared_resources(self, account_id: Optional[str] = None) -> Dict[str, Any]:
397
409
  """
398
410
  Extract resource declarations from terraform configuration files.
399
-
411
+
400
412
  Args:
401
413
  account_id: AWS account ID to filter terraform configurations
402
-
414
+
403
415
  Returns:
404
416
  Dictionary containing terraform declared resources by type
405
417
  """
406
418
  try:
407
419
  declared_resources = {
408
- 'ec2': 0,
409
- 's3': 0,
410
- 'rds': 0,
411
- 'lambda': 0,
412
- 'vpc': 0,
413
- 'iam': 0,
414
- 'cloudformation': 0,
415
- 'elbv2': 0,
416
- 'route53': 0,
417
- 'sns': 0
420
+ "ec2": 0,
421
+ "s3": 0,
422
+ "rds": 0,
423
+ "lambda": 0,
424
+ "vpc": 0,
425
+ "iam": 0,
426
+ "cloudformation": 0,
427
+ "elbv2": 0,
428
+ "route53": 0,
429
+ "sns": 0,
418
430
  }
419
-
431
+
420
432
  config_files = []
421
-
433
+
422
434
  # If account_id provided, look for account-specific terraform files
423
435
  if account_id:
424
436
  account_path = Path(self.terraform_directory) / "account" / account_id
@@ -428,76 +440,76 @@ class EnhancedMCPValidator:
428
440
  # Look in all terraform files
429
441
  terraform_path = Path(self.terraform_directory)
430
442
  config_files.extend(terraform_path.rglob("*.tf"))
431
-
443
+
432
444
  resource_patterns = {
433
- 'ec2': ['aws_instance', 'aws_launch_template'],
434
- 's3': ['aws_s3_bucket'],
435
- 'rds': ['aws_db_instance', 'aws_rds_cluster'],
436
- 'lambda': ['aws_lambda_function'],
437
- 'vpc': ['aws_vpc'],
438
- 'iam': ['aws_iam_role', 'aws_iam_user'],
439
- 'cloudformation': ['aws_cloudformation_stack'],
440
- 'elbv2': ['aws_lb', 'aws_alb'],
441
- 'route53': ['aws_route53_zone'],
442
- 'sns': ['aws_sns_topic']
445
+ "ec2": ["aws_instance", "aws_launch_template"],
446
+ "s3": ["aws_s3_bucket"],
447
+ "rds": ["aws_db_instance", "aws_rds_cluster"],
448
+ "lambda": ["aws_lambda_function"],
449
+ "vpc": ["aws_vpc"],
450
+ "iam": ["aws_iam_role", "aws_iam_user"],
451
+ "cloudformation": ["aws_cloudformation_stack"],
452
+ "elbv2": ["aws_lb", "aws_alb"],
453
+ "route53": ["aws_route53_zone"],
454
+ "sns": ["aws_sns_topic"],
443
455
  }
444
-
456
+
445
457
  # Parse terraform files for resource declarations
446
458
  for config_file in config_files:
447
459
  try:
448
- with open(config_file, 'r') as f:
460
+ with open(config_file, "r") as f:
449
461
  content = f.read()
450
-
462
+
451
463
  # Count resource declarations
452
464
  for service, patterns in resource_patterns.items():
453
465
  for pattern in patterns:
454
466
  declared_resources[service] += content.count(f'resource "{pattern}"')
455
-
467
+
456
468
  except Exception as e:
457
469
  continue # Skip files that can't be read
458
-
470
+
459
471
  return {
460
- 'account_id': account_id,
461
- 'declared_resources': declared_resources,
462
- 'files_parsed': len(config_files),
463
- 'data_source': 'terraform_configuration_files',
464
- 'timestamp': datetime.now().isoformat(),
472
+ "account_id": account_id,
473
+ "declared_resources": declared_resources,
474
+ "files_parsed": len(config_files),
475
+ "data_source": "terraform_configuration_files",
476
+ "timestamp": datetime.now().isoformat(),
465
477
  }
466
-
478
+
467
479
  except Exception as e:
468
480
  print_warning(f"Failed to extract terraform declared resources: {str(e)[:50]}")
469
481
  return {
470
- 'account_id': account_id,
471
- 'declared_resources': {service: 0 for service in self.supported_services.keys()},
472
- 'error': str(e),
473
- 'data_source': 'terraform_configuration_error',
474
- 'timestamp': datetime.now().isoformat(),
482
+ "account_id": account_id,
483
+ "declared_resources": {service: 0 for service in self.supported_services.keys()},
484
+ "error": str(e),
485
+ "data_source": "terraform_configuration_error",
486
+ "timestamp": datetime.now().isoformat(),
475
487
  }
476
488
 
477
489
  async def validate_with_mcp_servers(self, runbooks_inventory: Dict[str, Any]) -> Dict[str, Any]:
478
490
  """
479
491
  Enhanced validation using real MCP servers from .mcp.json configuration.
480
-
492
+
481
493
  Provides comprehensive 4-way validation:
482
494
  1. Runbooks inventory data
483
- 2. Direct AWS API calls
495
+ 2. Direct AWS API calls
484
496
  3. Real MCP server responses
485
497
  4. Terraform state drift detection
486
-
498
+
487
499
  Args:
488
500
  runbooks_inventory: Inventory data from runbooks collection
489
-
501
+
490
502
  Returns:
491
503
  Enhanced validation results with MCP server integration
492
504
  """
493
505
  validation_results = {
494
506
  "validation_timestamp": datetime.now().isoformat(),
495
- "validation_method": "enhanced_mcp_server_integration",
507
+ "validation_method": "enhanced_mcp_server_integration",
496
508
  "mcp_integration": {
497
509
  "config_loaded": bool(self.mcp_servers),
498
510
  "servers_available": list(self.mcp_servers.keys()),
499
511
  "servers_started": {},
500
- "validation_sources": []
512
+ "validation_sources": [],
501
513
  },
502
514
  "enterprise_profiles": self.enterprise_profiles,
503
515
  "profiles_validated": 0,
@@ -508,35 +520,32 @@ class EnhancedMCPValidator:
508
520
  "start_time": time.time(),
509
521
  "mcp_server_startup_time": 0,
510
522
  "validation_execution_time": 0,
511
- "total_execution_time": 0
512
- }
523
+ "total_execution_time": 0,
524
+ },
513
525
  }
514
526
 
515
527
  self.console.log(f"[blue]⚡ Starting enhanced MCP server validation[/]")
516
-
528
+
517
529
  # Start relevant MCP servers
518
530
  await self._start_relevant_mcp_servers(validation_results)
519
-
531
+
520
532
  # Execute validation with all available sources
521
533
  with Progress(
522
534
  SpinnerColumn(),
523
535
  TextColumn("[progress.description]{task.description}"),
524
536
  BarColumn(),
525
- TaskProgressColumn(),
537
+ TaskProgressColumn(),
526
538
  TimeElapsedColumn(),
527
539
  console=self.console,
528
540
  ) as progress:
529
541
  task = progress.add_task("MCP server validation...", total=len(self.aws_sessions))
530
-
542
+
531
543
  # Parallel execution for <20s target
532
544
  with ThreadPoolExecutor(max_workers=min(3, len(self.aws_sessions))) as executor:
533
545
  future_to_operation = {}
534
546
  for operation_type, session_info in self.aws_sessions.items():
535
547
  future = executor.submit(
536
- self._validate_operation_with_mcp_servers,
537
- operation_type,
538
- session_info,
539
- runbooks_inventory
548
+ self._validate_operation_with_mcp_servers, operation_type, session_info, runbooks_inventory
540
549
  )
541
550
  future_to_operation[future] = operation_type
542
551
 
@@ -555,17 +564,17 @@ class EnhancedMCPValidator:
555
564
  # Finalize results and cleanup
556
565
  self._finalize_mcp_validation_results(validation_results)
557
566
  self._stop_mcp_servers()
558
-
567
+
559
568
  return validation_results
560
-
569
+
561
570
  async def _start_relevant_mcp_servers(self, validation_results: Dict[str, Any]) -> None:
562
571
  """Start MCP servers relevant to validation operations."""
563
572
  startup_start = time.time()
564
-
573
+
565
574
  # Priority servers for validation
566
- relevant_servers = ['aws-api', 'cost-explorer', 'iam', 'cloudwatch']
575
+ relevant_servers = ["aws-api", "cost-explorer", "iam", "cloudwatch"]
567
576
  started_servers = []
568
-
577
+
569
578
  for server_name in relevant_servers:
570
579
  if server_name in self.mcp_servers:
571
580
  server_config = self.mcp_servers[server_name]
@@ -575,30 +584,30 @@ class EnhancedMCPValidator:
575
584
  validation_results["mcp_integration"]["servers_started"][server_name] = {
576
585
  "status": "started",
577
586
  "pid": process.pid,
578
- "profile_used": self._get_server_profile(server_config)
587
+ "profile_used": self._get_server_profile(server_config),
579
588
  }
580
589
  else:
581
590
  validation_results["mcp_integration"]["servers_started"][server_name] = {
582
591
  "status": "failed",
583
- "error": "Failed to start process"
592
+ "error": "Failed to start process",
584
593
  }
585
-
594
+
586
595
  validation_results["mcp_integration"]["validation_sources"] = [
587
596
  "runbooks_inventory",
588
- "direct_aws_apis",
589
- f"mcp_servers_{len(started_servers)}"
597
+ "direct_aws_apis",
598
+ f"mcp_servers_{len(started_servers)}",
590
599
  ]
591
-
600
+
592
601
  if self.terraform_state_files:
593
602
  validation_results["mcp_integration"]["validation_sources"].append("terraform_state")
594
-
603
+
595
604
  validation_results["performance_metrics"]["mcp_server_startup_time"] = time.time() - startup_start
596
-
605
+
597
606
  if started_servers:
598
607
  print_success(f"✅ MCP servers started: {', '.join(started_servers)}")
599
608
  else:
600
609
  print_warning("⚠️ No MCP servers started - using direct API validation only")
601
-
610
+
602
611
  def _get_server_profile(self, server_config: Dict[str, Any]) -> Optional[str]:
603
612
  """Extract the profile name used by an MCP server configuration."""
604
613
  env = server_config.get("env", {})
@@ -606,29 +615,35 @@ class EnhancedMCPValidator:
606
615
  if "PROFILE" in key and isinstance(value, str) and not value.startswith("${"):
607
616
  return value
608
617
  return None
609
-
610
- def _validate_operation_with_mcp_servers(self, operation_type: str, session_info: Dict[str, Any],
611
- runbooks_inventory: Dict[str, Any]) -> Optional[Dict[str, Any]]:
618
+
619
+ def _validate_operation_with_mcp_servers(
620
+ self, operation_type: str, session_info: Dict[str, Any], runbooks_inventory: Dict[str, Any]
621
+ ) -> Optional[Dict[str, Any]]:
612
622
  """Validate a single operation using all available validation sources."""
613
623
  try:
614
624
  session = session_info["session"]
615
625
  profile_name = session_info["profile"]
616
626
  account_id = session_info["account_id"]
617
-
627
+
618
628
  # Get validation data from all sources
619
629
  runbooks_data = self._extract_runbooks_inventory_data(runbooks_inventory, operation_type, account_id)
620
630
  direct_aws_data = asyncio.run(self._get_independent_inventory_data(session, profile_name))
621
631
  mcp_server_data = self._get_mcp_server_data(operation_type, account_id)
622
632
  terraform_data = self._get_terraform_declared_resources(account_id)
623
-
633
+
624
634
  # Calculate comprehensive validation accuracy
625
635
  validation_result = self._calculate_comprehensive_accuracy(
626
- runbooks_data, direct_aws_data, mcp_server_data, terraform_data,
627
- operation_type, profile_name, account_id
636
+ runbooks_data,
637
+ direct_aws_data,
638
+ mcp_server_data,
639
+ terraform_data,
640
+ operation_type,
641
+ profile_name,
642
+ account_id,
628
643
  )
629
-
644
+
630
645
  return validation_result
631
-
646
+
632
647
  except Exception as e:
633
648
  return {
634
649
  "operation_type": operation_type,
@@ -637,17 +652,17 @@ class EnhancedMCPValidator:
637
652
  "overall_accuracy_percent": 0.0,
638
653
  "passed_validation": False,
639
654
  "error": str(e),
640
- "validation_status": "ERROR"
655
+ "validation_status": "ERROR",
641
656
  }
642
-
657
+
643
658
  def _get_mcp_server_data(self, operation_type: str, account_id: Optional[str]) -> Dict[str, Any]:
644
659
  """
645
660
  Get validation data from MCP servers (placeholder for actual MCP client implementation).
646
-
661
+
647
662
  Args:
648
663
  operation_type: Type of operation (billing, management, operational)
649
664
  account_id: AWS account ID for context
650
-
665
+
651
666
  Returns:
652
667
  MCP server validation data
653
668
  """
@@ -659,28 +674,34 @@ class EnhancedMCPValidator:
659
674
  "account_id": account_id,
660
675
  "resource_counts": {},
661
676
  "servers_queried": [],
662
- "validation_timestamp": datetime.now().isoformat()
677
+ "validation_timestamp": datetime.now().isoformat(),
663
678
  }
664
-
679
+
665
680
  # Check which servers are running and could provide data
666
681
  for server_name, process in self.mcp_processes.items():
667
682
  if process and process.poll() is None: # Server is running
668
683
  mcp_data["servers_queried"].append(server_name)
669
-
684
+
670
685
  # For demonstration, populate with placeholder data structure
671
686
  # Real implementation would use MCP client to query running servers
672
687
  for service in self.supported_services.keys():
673
688
  mcp_data["resource_counts"][service] = 0 # Placeholder
674
-
689
+
675
690
  return mcp_data
676
691
 
677
- def _calculate_comprehensive_accuracy(self, runbooks_data: Dict, direct_aws_data: Dict,
678
- mcp_server_data: Dict, terraform_data: Dict,
679
- operation_type: str, profile_name: str,
680
- account_id: Optional[str]) -> Dict[str, Any]:
692
+ def _calculate_comprehensive_accuracy(
693
+ self,
694
+ runbooks_data: Dict,
695
+ direct_aws_data: Dict,
696
+ mcp_server_data: Dict,
697
+ terraform_data: Dict,
698
+ operation_type: str,
699
+ profile_name: str,
700
+ account_id: Optional[str],
701
+ ) -> Dict[str, Any]:
681
702
  """
682
703
  Calculate comprehensive accuracy across all validation sources.
683
-
704
+
684
705
  Args:
685
706
  runbooks_data: Data from runbooks inventory
686
707
  direct_aws_data: Data from direct AWS API calls
@@ -689,7 +710,7 @@ class EnhancedMCPValidator:
689
710
  operation_type: Operation type being validated
690
711
  profile_name: AWS profile name
691
712
  account_id: AWS account ID
692
-
713
+
693
714
  Returns:
694
715
  Comprehensive validation result
695
716
  """
@@ -702,7 +723,7 @@ class EnhancedMCPValidator:
702
723
  resource_validations = {}
703
724
  total_variance = 0.0
704
725
  valid_comparisons = 0
705
-
726
+
706
727
  # Comprehensive validation for each resource type
707
728
  for resource_type in self.supported_services.keys():
708
729
  runbooks_count = runbooks_counts.get(resource_type, 0)
@@ -713,7 +734,7 @@ class EnhancedMCPValidator:
713
734
  # Calculate variance across all sources
714
735
  all_counts = [runbooks_count, direct_aws_count, mcp_server_count, terraform_count]
715
736
  active_counts = [c for c in all_counts if c > 0]
716
-
737
+
717
738
  if not active_counts:
718
739
  # All sources report zero - perfect alignment
719
740
  accuracy_percent = 100.0
@@ -742,7 +763,7 @@ class EnhancedMCPValidator:
742
763
  "variance_percent": variance,
743
764
  "validation_status": validation_status,
744
765
  "passed_validation": accuracy_percent >= self.validation_threshold,
745
- "sources_with_data": len(active_counts)
766
+ "sources_with_data": len(active_counts),
746
767
  }
747
768
 
748
769
  # Include in total variance calculation
@@ -767,9 +788,9 @@ class EnhancedMCPValidator:
767
788
  "runbooks_inventory": bool(runbooks_counts),
768
789
  "direct_aws_apis": bool(direct_aws_counts),
769
790
  "mcp_servers": len(mcp_server_data.get("servers_queried", [])),
770
- "terraform_state": bool(terraform_counts)
791
+ "terraform_state": bool(terraform_counts),
771
792
  },
772
- "accuracy_category": self._categorize_inventory_accuracy(overall_accuracy)
793
+ "accuracy_category": self._categorize_inventory_accuracy(overall_accuracy),
773
794
  }
774
795
 
775
796
  except Exception as e:
@@ -780,19 +801,19 @@ class EnhancedMCPValidator:
780
801
  "overall_accuracy_percent": 0.0,
781
802
  "passed_validation": False,
782
803
  "error": str(e),
783
- "validation_status": "ERROR"
804
+ "validation_status": "ERROR",
784
805
  }
785
-
806
+
786
807
  def _finalize_mcp_validation_results(self, validation_results: Dict[str, Any]) -> None:
787
808
  """Finalize MCP validation results with comprehensive metrics."""
788
809
  profile_results = validation_results["profile_results"]
789
-
810
+
790
811
  # Calculate performance metrics
791
812
  start_time = validation_results["performance_metrics"]["start_time"]
792
813
  validation_results["performance_metrics"]["total_execution_time"] = time.time() - start_time
793
814
  validation_results["performance_metrics"]["validation_execution_time"] = (
794
- validation_results["performance_metrics"]["total_execution_time"] -
795
- validation_results["performance_metrics"]["mcp_server_startup_time"]
815
+ validation_results["performance_metrics"]["total_execution_time"]
816
+ - validation_results["performance_metrics"]["mcp_server_startup_time"]
796
817
  )
797
818
 
798
819
  if not profile_results:
@@ -811,7 +832,7 @@ class EnhancedMCPValidator:
811
832
 
812
833
  # Display enhanced results
813
834
  self._display_mcp_validation_results(validation_results)
814
-
835
+
815
836
  def _display_mcp_validation_results(self, results: Dict[str, Any]) -> None:
816
837
  """Display enhanced MCP validation results with server integration details."""
817
838
  overall_accuracy = results.get("total_accuracy", 0)
@@ -820,36 +841,38 @@ class EnhancedMCPValidator:
820
841
  performance_metrics = results.get("performance_metrics", {})
821
842
 
822
843
  self.console.print(f"\n[bright_cyan]🔍 Enhanced MCP Server Validation Results[/]")
823
-
844
+
824
845
  # Display MCP integration summary
825
846
  servers_started = mcp_integration.get("servers_started", {})
826
847
  if servers_started:
827
848
  successful_servers = [name for name, info in servers_started.items() if info.get("status") == "started"]
828
849
  failed_servers = [name for name, info in servers_started.items() if info.get("status") == "failed"]
829
-
850
+
830
851
  if successful_servers:
831
852
  self.console.print(f"[dim green]✅ MCP Servers: {', '.join(successful_servers)}[/]")
832
853
  if failed_servers:
833
854
  self.console.print(f"[dim red]❌ Failed Servers: {', '.join(failed_servers)}[/]")
834
-
855
+
835
856
  # Display validation sources
836
857
  validation_sources = mcp_integration.get("validation_sources", [])
837
858
  self.console.print(f"[dim cyan]🔗 Validation Sources: {', '.join(validation_sources)}[/]")
838
-
859
+
839
860
  # Display performance metrics
840
861
  total_time = performance_metrics.get("total_execution_time", 0)
841
862
  startup_time = performance_metrics.get("mcp_server_startup_time", 0)
842
863
  validation_time = performance_metrics.get("validation_execution_time", 0)
843
-
844
- self.console.print(f"[dim]⚡ Performance: {total_time:.1f}s total ({startup_time:.1f}s startup, {validation_time:.1f}s validation)[/]")
845
-
864
+
865
+ self.console.print(
866
+ f"[dim]⚡ Performance: {total_time:.1f}s total ({startup_time:.1f}s startup, {validation_time:.1f}s validation)[/]"
867
+ )
868
+
846
869
  # Display per-operation results
847
870
  for result in results.get("profile_results", []):
848
871
  operation_type = result.get("operation_type", "Unknown")
849
872
  accuracy = result.get("overall_accuracy_percent", 0)
850
873
  status = result.get("validation_status", "UNKNOWN")
851
874
  account_id = result.get("account_id", "Unknown")
852
-
875
+
853
876
  # Determine display formatting
854
877
  if status == "PASSED" and accuracy >= 99.5:
855
878
  icon = "✅"
@@ -863,27 +886,32 @@ class EnhancedMCPValidator:
863
886
  else:
864
887
  icon = "❌"
865
888
  color = "red"
866
-
867
- self.console.print(f"[dim] {operation_type:12s} ({account_id}): {icon} [{color}]{accuracy:.1f}% accuracy[/]")
868
-
889
+
890
+ self.console.print(
891
+ f"[dim] {operation_type:12s} ({account_id}): {icon} [{color}]{accuracy:.1f}% accuracy[/]"
892
+ )
893
+
869
894
  # Show resource-level details for significant variances
870
895
  resource_validations = result.get("resource_validations", {})
871
896
  for resource_type, resource_data in resource_validations.items():
872
897
  if resource_data.get("variance_percent", 0) > 10: # Show resources with >10% variance
873
898
  variance = resource_data["variance_percent"]
874
899
  sources_count = resource_data["sources_with_data"]
875
- self.console.print(f"[dim] {self.supported_services.get(resource_type, resource_type):15s}: ⚠️ {variance:.1f}% variance ({sources_count} sources)[/]")
900
+ self.console.print(
901
+ f"[dim] {self.supported_services.get(resource_type, resource_type):15s}: ⚠️ {variance:.1f}% variance ({sources_count} sources)[/]"
902
+ )
876
903
 
877
904
  # Overall validation summary
878
905
  if passed:
879
906
  print_success(f"✅ Enhanced MCP Validation PASSED: {overall_accuracy:.1f}% accuracy achieved")
880
907
  else:
881
908
  print_warning(f"⚠️ Enhanced MCP Validation: {overall_accuracy:.1f}% accuracy (≥99.5% required)")
882
-
909
+
883
910
  print_info(f"Enterprise compliance: {results.get('profiles_validated', 0)} operations validated")
884
911
 
885
- def _extract_runbooks_inventory_data(self, runbooks_inventory: Dict[str, Any],
886
- operation_type: str, account_id: Optional[str] = None) -> Dict[str, Any]:
912
+ def _extract_runbooks_inventory_data(
913
+ self, runbooks_inventory: Dict[str, Any], operation_type: str, account_id: Optional[str] = None
914
+ ) -> Dict[str, Any]:
887
915
  """
888
916
  Extract inventory data from runbooks results for comprehensive validation.
889
917
  Enhanced to work with operation types instead of profile names.
@@ -892,7 +920,7 @@ class EnhancedMCPValidator:
892
920
  # Handle various runbooks inventory data structures
893
921
  resource_counts = {}
894
922
  regions_discovered = []
895
-
923
+
896
924
  # Try operation_type key first
897
925
  if operation_type in runbooks_inventory:
898
926
  operation_data = runbooks_inventory[operation_type]
@@ -907,14 +935,16 @@ class EnhancedMCPValidator:
907
935
  else:
908
936
  resource_counts = runbooks_inventory.get("resource_counts", {})
909
937
  regions_discovered = runbooks_inventory.get("regions", [])
910
-
938
+
911
939
  return {
912
940
  "operation_type": operation_type,
913
941
  "account_id": account_id,
914
942
  "resource_counts": resource_counts,
915
943
  "regions_discovered": regions_discovered,
916
944
  "data_source": "runbooks_inventory_collection",
917
- "extraction_method": f"operation_type_{operation_type}" if operation_type in runbooks_inventory else "fallback"
945
+ "extraction_method": f"operation_type_{operation_type}"
946
+ if operation_type in runbooks_inventory
947
+ else "fallback",
918
948
  }
919
949
  except Exception as e:
920
950
  self.console.log(f"[yellow]Warning: Error extracting runbooks inventory data: {str(e)}[/]")
@@ -924,13 +954,13 @@ class EnhancedMCPValidator:
924
954
  "resource_counts": {},
925
955
  "regions_discovered": [],
926
956
  "data_source": "runbooks_inventory_collection_error",
927
- "error": str(e)
957
+ "error": str(e),
928
958
  }
929
959
 
930
960
  async def validate_inventory_data_async(self, runbooks_inventory: Dict[str, Any]) -> Dict[str, Any]:
931
961
  """
932
962
  Enhanced 3-way validation: runbooks inventory vs AWS API vs terraform state.
933
-
963
+
934
964
  Provides comprehensive drift detection between declared infrastructure
935
965
  and actual deployed resources with enterprise accuracy requirements.
936
966
 
@@ -951,18 +981,20 @@ class EnhancedMCPValidator:
951
981
  "terraform_integration": {
952
982
  "enabled": len(self.terraform_state_files) > 0,
953
983
  "state_files_discovered": len(self.terraform_state_files),
954
- "drift_analysis": {}
984
+ "drift_analysis": {},
955
985
  },
956
986
  }
957
987
 
958
988
  # Enhanced parallel processing with terraform integration for <20s performance target
959
- self.console.log(f"[blue]⚡ Starting enhanced 3-way validation with {min(5, len(self.aws_sessions))} workers[/]")
960
-
989
+ self.console.log(
990
+ f"[blue]⚡ Starting enhanced 3-way validation with {min(5, len(self.aws_sessions))} workers[/]"
991
+ )
992
+
961
993
  with Progress(
962
994
  SpinnerColumn(),
963
995
  TextColumn("[progress.description]{task.description}"),
964
996
  BarColumn(),
965
- TaskProgressColumn(),
997
+ TaskProgressColumn(),
966
998
  TimeElapsedColumn(),
967
999
  console=self.console,
968
1000
  ) as progress:
@@ -972,8 +1004,11 @@ class EnhancedMCPValidator:
972
1004
  with ThreadPoolExecutor(max_workers=min(5, len(self.aws_sessions))) as executor:
973
1005
  # Submit all validation tasks
974
1006
  future_to_profile = {}
975
- for profile, session in self.aws_sessions.items():
976
- future = executor.submit(self._validate_profile_with_drift_detection, profile, session, runbooks_inventory)
1007
+ for profile, session_info in self.aws_sessions.items():
1008
+ session = session_info["session"] # Extract boto3.Session from dict
1009
+ future = executor.submit(
1010
+ self._validate_profile_with_drift_detection, profile, session, runbooks_inventory
1011
+ )
977
1012
  future_to_profile[future] = profile
978
1013
 
979
1014
  # Collect results as they complete (maintain progress visibility)
@@ -992,7 +1027,9 @@ class EnhancedMCPValidator:
992
1027
  self._finalize_enhanced_validation_results(validation_results)
993
1028
  return validation_results
994
1029
 
995
- def _validate_profile_with_drift_detection(self, profile: str, session: boto3.Session, runbooks_inventory: Dict[str, Any]) -> Optional[Dict[str, Any]]:
1030
+ def _validate_profile_with_drift_detection(
1031
+ self, profile: str, session: boto3.Session, runbooks_inventory: Dict[str, Any]
1032
+ ) -> Optional[Dict[str, Any]]:
996
1033
  """Enhanced validation with 3-way drift detection: runbooks vs API vs terraform."""
997
1034
  try:
998
1035
  # Get AWS account ID for terraform state correlation
@@ -1014,11 +1051,7 @@ class EnhancedMCPValidator:
1014
1051
 
1015
1052
  # Calculate 3-way accuracy and drift detection
1016
1053
  drift_result = self._calculate_drift_analysis(
1017
- runbooks_inventory_data,
1018
- aws_inventory_data,
1019
- terraform_data,
1020
- profile,
1021
- account_id
1054
+ runbooks_inventory_data, aws_inventory_data, terraform_data, profile, account_id
1022
1055
  )
1023
1056
  return drift_result
1024
1057
 
@@ -1031,10 +1064,12 @@ class EnhancedMCPValidator:
1031
1064
  "error": str(e),
1032
1065
  "validation_status": "ERROR",
1033
1066
  "account_id": None,
1034
- "drift_analysis": {}
1067
+ "drift_analysis": {},
1035
1068
  }
1036
1069
 
1037
- def _validate_profile_inventory_sync(self, profile: str, session: boto3.Session, runbooks_inventory: Dict[str, Any]) -> Optional[Dict[str, Any]]:
1070
+ def _validate_profile_inventory_sync(
1071
+ self, profile: str, session: boto3.Session, runbooks_inventory: Dict[str, Any]
1072
+ ) -> Optional[Dict[str, Any]]:
1038
1073
  """Synchronous wrapper for profile inventory validation (for parallel execution)."""
1039
1074
  try:
1040
1075
  # Get independent resource counts from AWS API
@@ -1051,17 +1086,19 @@ class EnhancedMCPValidator:
1051
1086
  # Return None for failed validations (handled in calling function)
1052
1087
  return None
1053
1088
 
1054
- def _calculate_drift_analysis(self, runbooks_data: Dict, aws_data: Dict, terraform_data: Dict, profile: str, account_id: Optional[str]) -> Dict[str, Any]:
1089
+ def _calculate_drift_analysis(
1090
+ self, runbooks_data: Dict, aws_data: Dict, terraform_data: Dict, profile: str, account_id: Optional[str]
1091
+ ) -> Dict[str, Any]:
1055
1092
  """
1056
1093
  Calculate comprehensive drift analysis between runbooks, AWS API, and terraform.
1057
-
1094
+
1058
1095
  Args:
1059
1096
  runbooks_data: Inventory data from runbooks
1060
1097
  aws_data: Inventory data from AWS API
1061
1098
  terraform_data: Declared resources from terraform
1062
1099
  profile: Profile name for validation
1063
1100
  account_id: AWS account ID
1064
-
1101
+
1065
1102
  Returns:
1066
1103
  Comprehensive drift analysis with accuracy metrics
1067
1104
  """
@@ -1073,7 +1110,7 @@ class EnhancedMCPValidator:
1073
1110
  resource_drift_analysis = {}
1074
1111
  total_variance = 0.0
1075
1112
  valid_comparisons = 0
1076
-
1113
+
1077
1114
  # Analyze each resource type across all 3 sources
1078
1115
  for resource_type in self.supported_services.keys():
1079
1116
  runbooks_count = runbooks_counts.get(resource_type, 0)
@@ -1084,10 +1121,10 @@ class EnhancedMCPValidator:
1084
1121
  api_drift = abs(runbooks_count - aws_count) if runbooks_count > 0 or aws_count > 0 else 0
1085
1122
  iac_drift = abs(aws_count - terraform_count) if aws_count > 0 or terraform_count > 0 else 0
1086
1123
  total_drift = abs(runbooks_count - terraform_count) if runbooks_count > 0 or terraform_count > 0 else 0
1087
-
1124
+
1088
1125
  # Determine max count for percentage calculations
1089
1126
  max_count = max(runbooks_count, aws_count, terraform_count)
1090
-
1127
+
1091
1128
  # Calculate accuracy percentages
1092
1129
  if max_count == 0:
1093
1130
  # All sources report zero - perfect alignment
@@ -1112,10 +1149,14 @@ class EnhancedMCPValidator:
1112
1149
  recommendations = []
1113
1150
  if iac_drift > 0:
1114
1151
  if aws_count > terraform_count:
1115
- recommendations.append(f"Consider updating terraform to declare {aws_count - terraform_count} additional {resource_type} resources")
1152
+ recommendations.append(
1153
+ f"Consider updating terraform to declare {aws_count - terraform_count} additional {resource_type} resources"
1154
+ )
1116
1155
  elif terraform_count > aws_count:
1117
- recommendations.append(f"Investigate {terraform_count - aws_count} terraform-declared {resource_type} resources not found in AWS")
1118
-
1156
+ recommendations.append(
1157
+ f"Investigate {terraform_count - aws_count} terraform-declared {resource_type} resources not found in AWS"
1158
+ )
1159
+
1119
1160
  if api_drift > 0:
1120
1161
  recommendations.append(f"Review inventory collection accuracy for {resource_type} resources")
1121
1162
 
@@ -1131,12 +1172,12 @@ class EnhancedMCPValidator:
1131
1172
  "overall_accuracy_percent": overall_accuracy,
1132
1173
  "drift_status": drift_status,
1133
1174
  "passed_validation": overall_accuracy >= self.validation_threshold,
1134
- "recommendations": recommendations
1175
+ "recommendations": recommendations,
1135
1176
  }
1136
1177
 
1137
1178
  # Include in total variance calculation if any resources exist
1138
1179
  if max_count > 0:
1139
- total_variance += (100.0 - overall_accuracy)
1180
+ total_variance += 100.0 - overall_accuracy
1140
1181
  valid_comparisons += 1
1141
1182
 
1142
1183
  # Calculate overall metrics
@@ -1146,15 +1187,20 @@ class EnhancedMCPValidator:
1146
1187
  # Generate account-level recommendations
1147
1188
  account_recommendations = []
1148
1189
  high_drift_resources = [
1149
- resource for resource, data in resource_drift_analysis.items()
1190
+ resource
1191
+ for resource, data in resource_drift_analysis.items()
1150
1192
  if data["drift_status"] != "NO_DRIFT" and data["total_drift"] > 0
1151
1193
  ]
1152
-
1194
+
1153
1195
  if high_drift_resources:
1154
- account_recommendations.append(f"Review terraform configuration for {len(high_drift_resources)} resource types with detected drift")
1155
-
1196
+ account_recommendations.append(
1197
+ f"Review terraform configuration for {len(high_drift_resources)} resource types with detected drift"
1198
+ )
1199
+
1156
1200
  if terraform_data.get("files_parsed", 0) == 0:
1157
- account_recommendations.append("No terraform configuration found for this account - consider implementing Infrastructure as Code")
1201
+ account_recommendations.append(
1202
+ "No terraform configuration found for this account - consider implementing Infrastructure as Code"
1203
+ )
1158
1204
 
1159
1205
  return {
1160
1206
  "profile": profile,
@@ -1171,9 +1217,12 @@ class EnhancedMCPValidator:
1171
1217
  "total_resource_types": len(resource_drift_analysis),
1172
1218
  "drift_detected": len(high_drift_resources),
1173
1219
  "no_drift": len(resource_drift_analysis) - len(high_drift_resources),
1174
- "highest_drift_resource": max(resource_drift_analysis.keys(),
1175
- key=lambda x: resource_drift_analysis[x]["total_drift"]) if resource_drift_analysis else None
1176
- }
1220
+ "highest_drift_resource": max(
1221
+ resource_drift_analysis.keys(), key=lambda x: resource_drift_analysis[x]["total_drift"]
1222
+ )
1223
+ if resource_drift_analysis
1224
+ else None,
1225
+ },
1177
1226
  }
1178
1227
 
1179
1228
  except Exception as e:
@@ -1184,7 +1233,7 @@ class EnhancedMCPValidator:
1184
1233
  "passed_validation": False,
1185
1234
  "error": str(e),
1186
1235
  "validation_status": "ERROR",
1187
- "drift_analysis": {}
1236
+ "drift_analysis": {},
1188
1237
  }
1189
1238
 
1190
1239
  async def _get_independent_inventory_data(self, session: boto3.Session, profile: str) -> Dict[str, Any]:
@@ -1204,10 +1253,10 @@ class EnhancedMCPValidator:
1204
1253
  if session is None:
1205
1254
  print_warning(f"Session not initialized for {profile}, using default profile")
1206
1255
  session = boto3.Session(profile_name=profile)
1207
-
1256
+
1208
1257
  ec2_client = session.client("ec2", region_name="us-east-1")
1209
1258
  regions_response = ec2_client.describe_regions()
1210
- regions = [region['RegionName'] for region in regions_response['Regions']]
1259
+ regions = [region["RegionName"] for region in regions_response["Regions"]]
1211
1260
  inventory_data["regions_discovered"] = regions
1212
1261
  except Exception as e:
1213
1262
  print_warning(f"Could not discover regions for {profile}: {str(e)[:50]}")
@@ -1216,57 +1265,59 @@ class EnhancedMCPValidator:
1216
1265
 
1217
1266
  # Validate resource counts for each supported service
1218
1267
  resource_counts = {}
1219
-
1268
+
1220
1269
  # EC2 Instances - Enhanced comprehensive discovery
1221
1270
  try:
1222
1271
  total_ec2_instances = 0
1223
1272
  successful_regions = 0
1224
1273
  failed_regions = 0
1225
-
1274
+
1226
1275
  # Use all available regions for comprehensive coverage
1227
1276
  for region in regions:
1228
1277
  try:
1229
1278
  ec2_client = session.client("ec2", region_name=region)
1230
-
1279
+
1231
1280
  # Get all instances using pagination for large accounts
1232
- paginator = ec2_client.get_paginator('describe_instances')
1281
+ paginator = ec2_client.get_paginator("describe_instances")
1233
1282
  region_instances = 0
1234
-
1283
+
1235
1284
  for page in paginator.paginate():
1236
- for reservation in page.get('Reservations', []):
1285
+ for reservation in page.get("Reservations", []):
1237
1286
  # Count all instances regardless of state for accurate inventory
1238
- instances = reservation.get('Instances', [])
1287
+ instances = reservation.get("Instances", [])
1239
1288
  region_instances += len(instances)
1240
-
1289
+
1241
1290
  total_ec2_instances += region_instances
1242
1291
  successful_regions += 1
1243
-
1292
+
1244
1293
  # Log progress for debugging
1245
1294
  if region_instances > 0:
1246
1295
  self.console.log(f"[dim] EC2 {region}: {region_instances} instances[/]")
1247
-
1296
+
1248
1297
  except Exception as e:
1249
1298
  failed_regions += 1
1250
1299
  # Log specific errors for troubleshooting
1251
1300
  if "UnauthorizedOperation" not in str(e):
1252
1301
  self.console.log(f"[dim yellow] EC2 {region}: Access denied or unavailable[/]")
1253
-
1254
- resource_counts['ec2'] = total_ec2_instances
1255
-
1302
+
1303
+ resource_counts["ec2"] = total_ec2_instances
1304
+
1256
1305
  # Track validation quality metrics
1257
- self.console.log(f"[dim]EC2 validation: {successful_regions} regions accessible, {failed_regions} failed[/]")
1258
-
1306
+ self.console.log(
1307
+ f"[dim]EC2 validation: {successful_regions} regions accessible, {failed_regions} failed[/]"
1308
+ )
1309
+
1259
1310
  except Exception as e:
1260
1311
  self.console.log(f"[red]EC2 validation failed: {str(e)[:50]}[/]")
1261
- resource_counts['ec2'] = 0
1312
+ resource_counts["ec2"] = 0
1262
1313
 
1263
1314
  # S3 Buckets (global service)
1264
1315
  try:
1265
1316
  s3_client = session.client("s3", region_name="us-east-1")
1266
1317
  buckets_response = s3_client.list_buckets()
1267
- resource_counts['s3'] = len(buckets_response.get('Buckets', []))
1318
+ resource_counts["s3"] = len(buckets_response.get("Buckets", []))
1268
1319
  except Exception:
1269
- resource_counts['s3'] = 0
1320
+ resource_counts["s3"] = 0
1270
1321
 
1271
1322
  # RDS Instances - Enhanced comprehensive discovery
1272
1323
  try:
@@ -1274,23 +1325,23 @@ class EnhancedMCPValidator:
1274
1325
  for region in regions:
1275
1326
  try:
1276
1327
  rds_client = session.client("rds", region_name=region)
1277
-
1328
+
1278
1329
  # Use pagination for large RDS deployments
1279
- paginator = rds_client.get_paginator('describe_db_instances')
1330
+ paginator = rds_client.get_paginator("describe_db_instances")
1280
1331
  region_instances = 0
1281
-
1332
+
1282
1333
  for page in paginator.paginate():
1283
- region_instances += len(page.get('DBInstances', []))
1284
-
1334
+ region_instances += len(page.get("DBInstances", []))
1335
+
1285
1336
  total_rds_instances += region_instances
1286
-
1337
+
1287
1338
  if region_instances > 0:
1288
1339
  self.console.log(f"[dim] RDS {region}: {region_instances} instances[/]")
1289
1340
  except Exception:
1290
1341
  continue
1291
- resource_counts['rds'] = total_rds_instances
1342
+ resource_counts["rds"] = total_rds_instances
1292
1343
  except Exception:
1293
- resource_counts['rds'] = 0
1344
+ resource_counts["rds"] = 0
1294
1345
 
1295
1346
  # Lambda Functions - Enhanced comprehensive discovery
1296
1347
  try:
@@ -1298,23 +1349,23 @@ class EnhancedMCPValidator:
1298
1349
  for region in regions:
1299
1350
  try:
1300
1351
  lambda_client = session.client("lambda", region_name=region)
1301
-
1352
+
1302
1353
  # Use pagination for large Lambda deployments
1303
- paginator = lambda_client.get_paginator('list_functions')
1354
+ paginator = lambda_client.get_paginator("list_functions")
1304
1355
  region_functions = 0
1305
-
1356
+
1306
1357
  for page in paginator.paginate():
1307
- region_functions += len(page.get('Functions', []))
1308
-
1358
+ region_functions += len(page.get("Functions", []))
1359
+
1309
1360
  total_lambda_functions += region_functions
1310
-
1361
+
1311
1362
  if region_functions > 0:
1312
1363
  self.console.log(f"[dim] Lambda {region}: {region_functions} functions[/]")
1313
1364
  except Exception:
1314
1365
  continue
1315
- resource_counts['lambda'] = total_lambda_functions
1366
+ resource_counts["lambda"] = total_lambda_functions
1316
1367
  except Exception:
1317
- resource_counts['lambda'] = 0
1368
+ resource_counts["lambda"] = 0
1318
1369
 
1319
1370
  # VPCs - Enhanced comprehensive discovery
1320
1371
  try:
@@ -1322,43 +1373,43 @@ class EnhancedMCPValidator:
1322
1373
  for region in regions:
1323
1374
  try:
1324
1375
  ec2_client = session.client("ec2", region_name=region)
1325
-
1376
+
1326
1377
  # Use pagination for VPC discovery
1327
- paginator = ec2_client.get_paginator('describe_vpcs')
1378
+ paginator = ec2_client.get_paginator("describe_vpcs")
1328
1379
  region_vpcs = 0
1329
-
1380
+
1330
1381
  for page in paginator.paginate():
1331
- region_vpcs += len(page.get('Vpcs', []))
1332
-
1382
+ region_vpcs += len(page.get("Vpcs", []))
1383
+
1333
1384
  total_vpcs += region_vpcs
1334
-
1385
+
1335
1386
  if region_vpcs > 0:
1336
1387
  self.console.log(f"[dim] VPC {region}: {region_vpcs} VPCs[/]")
1337
1388
  except Exception:
1338
1389
  continue
1339
- resource_counts['vpc'] = total_vpcs
1390
+ resource_counts["vpc"] = total_vpcs
1340
1391
  except Exception:
1341
- resource_counts['vpc'] = 0
1392
+ resource_counts["vpc"] = 0
1342
1393
 
1343
1394
  # IAM Roles (global service) - Enhanced discovery with pagination
1344
1395
  try:
1345
1396
  iam_client = session.client("iam", region_name="us-east-1")
1346
-
1397
+
1347
1398
  # Use pagination for large IAM role deployments
1348
- paginator = iam_client.get_paginator('list_roles')
1399
+ paginator = iam_client.get_paginator("list_roles")
1349
1400
  total_roles = 0
1350
-
1401
+
1351
1402
  for page in paginator.paginate():
1352
- total_roles += len(page.get('Roles', []))
1353
-
1354
- resource_counts['iam'] = total_roles
1355
-
1403
+ total_roles += len(page.get("Roles", []))
1404
+
1405
+ resource_counts["iam"] = total_roles
1406
+
1356
1407
  if total_roles > 0:
1357
1408
  self.console.log(f"[dim] IAM: {total_roles} roles discovered[/]")
1358
-
1409
+
1359
1410
  except Exception as e:
1360
1411
  self.console.log(f"[yellow]IAM roles discovery failed: {str(e)[:40]}[/]")
1361
- resource_counts['iam'] = 0
1412
+ resource_counts["iam"] = 0
1362
1413
 
1363
1414
  # CloudFormation Stacks - Enhanced comprehensive discovery
1364
1415
  try:
@@ -1366,67 +1417,69 @@ class EnhancedMCPValidator:
1366
1417
  for region in regions:
1367
1418
  try:
1368
1419
  cf_client = session.client("cloudformation", region_name=region)
1369
-
1420
+
1370
1421
  # Use pagination for large CloudFormation deployments
1371
- paginator = cf_client.get_paginator('list_stacks')
1422
+ paginator = cf_client.get_paginator("list_stacks")
1372
1423
  region_stacks = 0
1373
-
1374
- for page in paginator.paginate(StackStatusFilter=['CREATE_COMPLETE', 'UPDATE_COMPLETE', 'ROLLBACK_COMPLETE']):
1375
- region_stacks += len(page.get('StackSummaries', []))
1376
-
1424
+
1425
+ for page in paginator.paginate(
1426
+ StackStatusFilter=["CREATE_COMPLETE", "UPDATE_COMPLETE", "ROLLBACK_COMPLETE"]
1427
+ ):
1428
+ region_stacks += len(page.get("StackSummaries", []))
1429
+
1377
1430
  total_stacks += region_stacks
1378
-
1431
+
1379
1432
  if region_stacks > 0:
1380
1433
  self.console.log(f"[dim] CloudFormation {region}: {region_stacks} stacks[/]")
1381
1434
  except Exception:
1382
1435
  continue
1383
- resource_counts['cloudformation'] = total_stacks
1436
+ resource_counts["cloudformation"] = total_stacks
1384
1437
  except Exception:
1385
- resource_counts['cloudformation'] = 0
1438
+ resource_counts["cloudformation"] = 0
1386
1439
 
1387
- # Load Balancers (ELBv2) - Enhanced comprehensive discovery
1440
+ # Load Balancers (ELBv2) - Enhanced comprehensive discovery
1388
1441
  try:
1389
1442
  total_load_balancers = 0
1390
1443
  for region in regions:
1391
1444
  try:
1392
1445
  elbv2_client = session.client("elbv2", region_name=region)
1393
-
1446
+
1394
1447
  # Use pagination for large load balancer deployments
1395
- paginator = elbv2_client.get_paginator('describe_load_balancers')
1448
+ paginator = elbv2_client.get_paginator("describe_load_balancers")
1396
1449
  region_lbs = 0
1397
-
1450
+
1398
1451
  for page in paginator.paginate():
1399
- region_lbs += len(page.get('LoadBalancers', []))
1400
-
1452
+ region_lbs += len(page.get("LoadBalancers", []))
1453
+
1401
1454
  total_load_balancers += region_lbs
1402
-
1455
+
1403
1456
  if region_lbs > 0:
1404
1457
  self.console.log(f"[dim] ELBv2 {region}: {region_lbs} load balancers[/]")
1405
1458
  except Exception:
1406
1459
  continue
1407
- resource_counts['elbv2'] = total_load_balancers
1460
+ resource_counts["elbv2"] = total_load_balancers
1408
1461
  except Exception:
1409
- resource_counts['elbv2'] = 0
1462
+ resource_counts["elbv2"] = 0
1410
1463
 
1411
1464
  # Route53 Hosted Zones (global service) - Enhanced discovery
1412
1465
  try:
1413
1466
  route53_client = session.client("route53", region_name="us-east-1")
1414
-
1467
+
1415
1468
  # Use pagination for large Route53 deployments
1416
- paginator = route53_client.get_paginator('list_hosted_zones')
1469
+ paginator = route53_client.get_paginator("list_hosted_zones")
1417
1470
  total_hosted_zones = 0
1418
-
1471
+
1419
1472
  for page in paginator.paginate():
1420
- total_hosted_zones += len(page.get('HostedZones', []))
1421
-
1422
- resource_counts['route53'] = total_hosted_zones
1423
-
1473
+ total_hosted_zones += len(page.get("HostedZones", []))
1474
+
1475
+ resource_counts["route53"] = total_hosted_zones
1476
+
1424
1477
  if total_hosted_zones > 0:
1425
1478
  self.console.log(f"[dim] Route53: {total_hosted_zones} hosted zones[/]")
1426
-
1479
+
1427
1480
  except Exception as e:
1428
1481
  self.console.log(f"[yellow]Route53 discovery failed: {str(e)[:40]}[/]")
1429
- resource_counts['route53'] = 0
1482
+ resource_counts["route53"] = 0
1430
1483
 
1431
1484
  # SNS Topics - Enhanced comprehensive discovery
1432
1485
  try:
@@ -1434,23 +1487,23 @@ class EnhancedMCPValidator:
1434
1487
  for region in regions:
1435
1488
  try:
1436
1489
  sns_client = session.client("sns", region_name=region)
1437
-
1490
+
1438
1491
  # Use pagination for large SNS deployments
1439
- paginator = sns_client.get_paginator('list_topics')
1492
+ paginator = sns_client.get_paginator("list_topics")
1440
1493
  region_topics = 0
1441
-
1494
+
1442
1495
  for page in paginator.paginate():
1443
- region_topics += len(page.get('Topics', []))
1444
-
1496
+ region_topics += len(page.get("Topics", []))
1497
+
1445
1498
  total_topics += region_topics
1446
-
1499
+
1447
1500
  if region_topics > 0:
1448
1501
  self.console.log(f"[dim] SNS {region}: {region_topics} topics[/]")
1449
1502
  except Exception:
1450
1503
  continue
1451
- resource_counts['sns'] = total_topics
1504
+ resource_counts["sns"] = total_topics
1452
1505
  except Exception:
1453
- resource_counts['sns'] = 0
1506
+ resource_counts["sns"] = 0
1454
1507
 
1455
1508
  # Network Interfaces (ENI) - Enhanced comprehensive discovery
1456
1509
  try:
@@ -1458,23 +1511,23 @@ class EnhancedMCPValidator:
1458
1511
  for region in regions:
1459
1512
  try:
1460
1513
  ec2_client = session.client("ec2", region_name=region)
1461
-
1514
+
1462
1515
  # Use pagination for large ENI deployments
1463
- paginator = ec2_client.get_paginator('describe_network_interfaces')
1516
+ paginator = ec2_client.get_paginator("describe_network_interfaces")
1464
1517
  region_enis = 0
1465
-
1518
+
1466
1519
  for page in paginator.paginate():
1467
- region_enis += len(page.get('NetworkInterfaces', []))
1468
-
1520
+ region_enis += len(page.get("NetworkInterfaces", []))
1521
+
1469
1522
  total_enis += region_enis
1470
-
1523
+
1471
1524
  if region_enis > 0:
1472
1525
  self.console.log(f"[dim] ENI {region}: {region_enis} network interfaces[/]")
1473
1526
  except Exception:
1474
1527
  continue
1475
- resource_counts['eni'] = total_enis
1528
+ resource_counts["eni"] = total_enis
1476
1529
  except Exception:
1477
- resource_counts['eni'] = 0
1530
+ resource_counts["eni"] = 0
1478
1531
 
1479
1532
  # EBS Volumes - Enhanced comprehensive discovery
1480
1533
  try:
@@ -1482,23 +1535,23 @@ class EnhancedMCPValidator:
1482
1535
  for region in regions:
1483
1536
  try:
1484
1537
  ec2_client = session.client("ec2", region_name=region)
1485
-
1538
+
1486
1539
  # Use pagination for large EBS deployments
1487
- paginator = ec2_client.get_paginator('describe_volumes')
1540
+ paginator = ec2_client.get_paginator("describe_volumes")
1488
1541
  region_volumes = 0
1489
-
1542
+
1490
1543
  for page in paginator.paginate():
1491
- region_volumes += len(page.get('Volumes', []))
1492
-
1544
+ region_volumes += len(page.get("Volumes", []))
1545
+
1493
1546
  total_volumes += region_volumes
1494
-
1547
+
1495
1548
  if region_volumes > 0:
1496
1549
  self.console.log(f"[dim] EBS {region}: {region_volumes} volumes[/]")
1497
1550
  except Exception:
1498
1551
  continue
1499
- resource_counts['ebs'] = total_volumes
1552
+ resource_counts["ebs"] = total_volumes
1500
1553
  except Exception:
1501
- resource_counts['ebs'] = 0
1554
+ resource_counts["ebs"] = 0
1502
1555
 
1503
1556
  inventory_data["resource_counts"] = resource_counts
1504
1557
 
@@ -1516,11 +1569,11 @@ class EnhancedMCPValidator:
1516
1569
  def _extract_runbooks_inventory_data(self, runbooks_inventory: Dict[str, Any], profile: str) -> Dict[str, Any]:
1517
1570
  """
1518
1571
  Extract inventory data from runbooks results for comparison.
1519
-
1572
+
1520
1573
  Args:
1521
1574
  runbooks_inventory: Inventory results from runbooks collection
1522
1575
  profile: Profile name for data extraction
1523
-
1576
+
1524
1577
  Returns:
1525
1578
  Extracted inventory data in standardized format
1526
1579
  """
@@ -1534,13 +1587,13 @@ class EnhancedMCPValidator:
1534
1587
  # Fallback: Look for direct resource keys (legacy format)
1535
1588
  resource_counts = runbooks_inventory.get("resource_counts", {})
1536
1589
  regions_discovered = runbooks_inventory.get("regions", [])
1537
-
1590
+
1538
1591
  return {
1539
1592
  "profile": profile,
1540
1593
  "resource_counts": resource_counts,
1541
1594
  "regions_discovered": regions_discovered,
1542
1595
  "data_source": "runbooks_inventory_collection",
1543
- "extraction_method": "profile_nested" if profile in runbooks_inventory else "direct_keys"
1596
+ "extraction_method": "profile_nested" if profile in runbooks_inventory else "direct_keys",
1544
1597
  }
1545
1598
  except Exception as e:
1546
1599
  self.console.log(f"[yellow]Warning: Error extracting runbooks inventory data for {profile}: {str(e)}[/]")
@@ -1549,18 +1602,18 @@ class EnhancedMCPValidator:
1549
1602
  "resource_counts": {},
1550
1603
  "regions_discovered": [],
1551
1604
  "data_source": "runbooks_inventory_collection_error",
1552
- "error": str(e)
1605
+ "error": str(e),
1553
1606
  }
1554
1607
 
1555
1608
  def _calculate_inventory_accuracy(self, runbooks_data: Dict, aws_data: Dict, profile: str) -> Dict[str, Any]:
1556
1609
  """
1557
1610
  Calculate accuracy between runbooks and AWS API inventory data.
1558
-
1611
+
1559
1612
  Args:
1560
1613
  runbooks_data: Inventory data from runbooks
1561
1614
  aws_data: Inventory data from AWS API
1562
1615
  profile: Profile name for validation
1563
-
1616
+
1564
1617
  Returns:
1565
1618
  Accuracy metrics with resource-level breakdown
1566
1619
  """
@@ -1583,11 +1636,15 @@ class EnhancedMCPValidator:
1583
1636
  elif runbooks_count == 0 and aws_count > 0:
1584
1637
  # Runbooks missing resources - accuracy issue
1585
1638
  accuracy_percent = 0.0
1586
- self.console.log(f"[red]⚠️ Profile {profile} {resource_type}: Runbooks shows 0 but MCP shows {aws_count}[/]")
1639
+ self.console.log(
1640
+ f"[red]⚠️ Profile {profile} {resource_type}: Runbooks shows 0 but MCP shows {aws_count}[/]"
1641
+ )
1587
1642
  elif aws_count == 0 and runbooks_count > 0:
1588
- # MCP missing data - moderate accuracy issue
1643
+ # MCP missing data - moderate accuracy issue
1589
1644
  accuracy_percent = 50.0 # Give partial credit as MCP may have different access
1590
- self.console.log(f"[yellow]⚠️ Profile {profile} {resource_type}: MCP shows 0 but Runbooks shows {runbooks_count}[/]")
1645
+ self.console.log(
1646
+ f"[yellow]⚠️ Profile {profile} {resource_type}: MCP shows 0 but Runbooks shows {runbooks_count}[/]"
1647
+ )
1591
1648
  else:
1592
1649
  # Both have values - calculate variance-based accuracy
1593
1650
  max_count = max(runbooks_count, aws_count)
@@ -1600,7 +1657,7 @@ class EnhancedMCPValidator:
1600
1657
  "accuracy_percent": accuracy_percent,
1601
1658
  "variance_count": abs(runbooks_count - aws_count),
1602
1659
  "variance_percent": abs(runbooks_count - aws_count) / max(max(runbooks_count, aws_count), 1) * 100,
1603
- "passed_validation": accuracy_percent >= self.validation_threshold
1660
+ "passed_validation": accuracy_percent >= self.validation_threshold,
1604
1661
  }
1605
1662
 
1606
1663
  if runbooks_count > 0 or aws_count > 0: # Only count non-zero comparisons
@@ -1666,14 +1723,14 @@ class EnhancedMCPValidator:
1666
1723
  "total_accounts": len(valid_results),
1667
1724
  "accounts_with_drift": 0,
1668
1725
  "resource_types_with_drift": set(),
1669
- "terraform_coverage": 0
1726
+ "terraform_coverage": 0,
1670
1727
  }
1671
-
1728
+
1672
1729
  for result in valid_results:
1673
1730
  # Check if account has terraform coverage
1674
1731
  if result.get("terraform_files_parsed", 0) > 0:
1675
1732
  drift_summary["terraform_coverage"] += 1
1676
-
1733
+
1677
1734
  # Collect drift analysis
1678
1735
  has_drift = False
1679
1736
  resource_drift = result.get("resource_drift_analysis", {})
@@ -1681,7 +1738,7 @@ class EnhancedMCPValidator:
1681
1738
  if drift_data.get("drift_status", "NO_DRIFT") != "NO_DRIFT":
1682
1739
  has_drift = True
1683
1740
  drift_summary["resource_types_with_drift"].add(resource_type)
1684
-
1741
+
1685
1742
  # Aggregate resource summary
1686
1743
  if resource_type not in resource_summary:
1687
1744
  resource_summary[resource_type] = {
@@ -1689,17 +1746,19 @@ class EnhancedMCPValidator:
1689
1746
  "total_aws": 0,
1690
1747
  "total_terraform": 0,
1691
1748
  "accuracy_scores": [],
1692
- "drift_incidents": 0
1749
+ "drift_incidents": 0,
1693
1750
  }
1694
-
1751
+
1695
1752
  resource_summary[resource_type]["total_runbooks"] += drift_data.get("runbooks_count", 0)
1696
1753
  resource_summary[resource_type]["total_aws"] += drift_data.get("aws_api_count", 0)
1697
1754
  resource_summary[resource_type]["total_terraform"] += drift_data.get("terraform_count", 0)
1698
- resource_summary[resource_type]["accuracy_scores"].append(drift_data.get("overall_accuracy_percent", 0))
1699
-
1755
+ resource_summary[resource_type]["accuracy_scores"].append(
1756
+ drift_data.get("overall_accuracy_percent", 0)
1757
+ )
1758
+
1700
1759
  if drift_data.get("drift_status", "NO_DRIFT") != "NO_DRIFT":
1701
1760
  resource_summary[resource_type]["drift_incidents"] += 1
1702
-
1761
+
1703
1762
  if has_drift:
1704
1763
  drift_summary["accounts_with_drift"] += 1
1705
1764
 
@@ -1714,10 +1773,16 @@ class EnhancedMCPValidator:
1714
1773
  validation_results["terraform_integration"]["drift_analysis"] = {
1715
1774
  "total_accounts": drift_summary["total_accounts"],
1716
1775
  "accounts_with_drift": drift_summary["accounts_with_drift"],
1717
- "drift_percentage": (drift_summary["accounts_with_drift"] / drift_summary["total_accounts"] * 100) if drift_summary["total_accounts"] > 0 else 0,
1776
+ "drift_percentage": (drift_summary["accounts_with_drift"] / drift_summary["total_accounts"] * 100)
1777
+ if drift_summary["total_accounts"] > 0
1778
+ else 0,
1718
1779
  "resource_types_with_drift": len(drift_summary["resource_types_with_drift"]),
1719
1780
  "terraform_coverage_accounts": drift_summary["terraform_coverage"],
1720
- "terraform_coverage_percentage": (drift_summary["terraform_coverage"] / drift_summary["total_accounts"] * 100) if drift_summary["total_accounts"] > 0 else 0
1781
+ "terraform_coverage_percentage": (
1782
+ drift_summary["terraform_coverage"] / drift_summary["total_accounts"] * 100
1783
+ )
1784
+ if drift_summary["total_accounts"] > 0
1785
+ else 0,
1721
1786
  }
1722
1787
 
1723
1788
  # Display enhanced results with drift analysis
@@ -1745,11 +1810,7 @@ class EnhancedMCPValidator:
1745
1810
  for result in valid_results:
1746
1811
  for resource_type, resource_data in result.get("resource_accuracies", {}).items():
1747
1812
  if resource_type not in resource_summary:
1748
- resource_summary[resource_type] = {
1749
- "total_runbooks": 0,
1750
- "total_aws": 0,
1751
- "accuracy_scores": []
1752
- }
1813
+ resource_summary[resource_type] = {"total_runbooks": 0, "total_aws": 0, "accuracy_scores": []}
1753
1814
  resource_summary[resource_type]["total_runbooks"] += resource_data["runbooks_count"]
1754
1815
  resource_summary[resource_type]["total_aws"] += resource_data["aws_api_count"]
1755
1816
  resource_summary[resource_type]["accuracy_scores"].append(resource_data["accuracy_percent"])
@@ -1795,7 +1856,9 @@ class EnhancedMCPValidator:
1795
1856
  color = "red"
1796
1857
 
1797
1858
  # Profile summary
1798
- self.console.print(f"[dim] {profile[:30]}: {icon} [{color}]{accuracy:.1f}% accuracy[/] [dim]({category})[/][/dim]")
1859
+ self.console.print(
1860
+ f"[dim] {profile[:30]}: {icon} [{color}]{accuracy:.1f}% accuracy[/] [dim]({category})[/][/dim]"
1861
+ )
1799
1862
 
1800
1863
  # Resource-level breakdown
1801
1864
  resource_accuracies = profile_result.get("resource_accuracies", {})
@@ -1824,7 +1887,7 @@ class EnhancedMCPValidator:
1824
1887
  avg_accuracy = summary.get("average_accuracy", 0)
1825
1888
  total_runbooks = summary.get("total_runbooks", 0)
1826
1889
  total_aws = summary.get("total_aws", 0)
1827
-
1890
+
1828
1891
  summary_icon = "✅" if avg_accuracy >= 99.5 else "⚠️" if avg_accuracy >= 90.0 else "❌"
1829
1892
  self.console.print(
1830
1893
  f"[dim] {self.supported_services.get(resource_type, resource_type):20s}: {summary_icon} "
@@ -1838,21 +1901,23 @@ class EnhancedMCPValidator:
1838
1901
  terraform_integration = results.get("terraform_integration", {})
1839
1902
 
1840
1903
  self.console.print(f"\n[bright_cyan]🔍 Enhanced Inventory Validation with Drift Detection[/]")
1841
-
1904
+
1842
1905
  # Display terraform integration status
1843
1906
  if terraform_integration.get("enabled", False):
1844
1907
  tf_files = terraform_integration.get("state_files_discovered", 0)
1845
1908
  drift_analysis = terraform_integration.get("drift_analysis", {})
1846
-
1909
+
1847
1910
  self.console.print(f"[dim]🏗️ Terraform Integration: {tf_files} state files discovered[/]")
1848
-
1911
+
1849
1912
  if drift_analysis:
1850
1913
  total_accounts = drift_analysis.get("total_accounts", 0)
1851
1914
  accounts_with_drift = drift_analysis.get("accounts_with_drift", 0)
1852
1915
  drift_percentage = drift_analysis.get("drift_percentage", 0)
1853
1916
  tf_coverage = drift_analysis.get("terraform_coverage_percentage", 0)
1854
-
1855
- self.console.print(f"[dim]📊 Drift Analysis: {accounts_with_drift}/{total_accounts} accounts ({drift_percentage:.1f}%) with detected drift[/]")
1917
+
1918
+ self.console.print(
1919
+ f"[dim]📊 Drift Analysis: {accounts_with_drift}/{total_accounts} accounts ({drift_percentage:.1f}%) with detected drift[/]"
1920
+ )
1856
1921
  self.console.print(f"[dim]🎯 IaC Coverage: {tf_coverage:.1f}% accounts have terraform configuration[/]")
1857
1922
 
1858
1923
  # Display per-profile results with enhanced drift breakdown
@@ -1877,7 +1942,7 @@ class EnhancedMCPValidator:
1877
1942
  # Profile summary with drift information
1878
1943
  drift_count = drift_summary.get("drift_detected", 0)
1879
1944
  total_resources = drift_summary.get("total_resource_types", 0)
1880
-
1945
+
1881
1946
  self.console.print(f"[dim] {profile[:30]} ({account_id}): {icon} [{color}]{accuracy:.1f}% accuracy[/]")
1882
1947
  if drift_count > 0:
1883
1948
  self.console.print(f"[dim] 🔄 Drift detected in {drift_count}/{total_resources} resource types[/]")
@@ -1885,21 +1950,25 @@ class EnhancedMCPValidator:
1885
1950
  # Enhanced resource-level breakdown with 3-way comparison
1886
1951
  drift_analysis = profile_result.get("resource_drift_analysis", {})
1887
1952
  for resource_type, drift_data in drift_analysis.items():
1888
- if drift_data.get("runbooks_count", 0) > 0 or drift_data.get("aws_api_count", 0) > 0 or drift_data.get("terraform_count", 0) > 0:
1953
+ if (
1954
+ drift_data.get("runbooks_count", 0) > 0
1955
+ or drift_data.get("aws_api_count", 0) > 0
1956
+ or drift_data.get("terraform_count", 0) > 0
1957
+ ):
1889
1958
  drift_status = drift_data.get("drift_status", "NO_DRIFT")
1890
1959
  resource_icon = "✅" if drift_status == "NO_DRIFT" else "🔄" if "DRIFT" in drift_status else "⚠️"
1891
-
1960
+
1892
1961
  runbooks_count = drift_data.get("runbooks_count", 0)
1893
1962
  aws_count = drift_data.get("aws_api_count", 0)
1894
1963
  terraform_count = drift_data.get("terraform_count", 0)
1895
1964
  overall_acc = drift_data.get("overall_accuracy_percent", 0)
1896
-
1965
+
1897
1966
  self.console.print(
1898
1967
  f"[dim] {self.supported_services.get(resource_type, resource_type):20s}: {resource_icon} "
1899
1968
  f"Runbooks: {runbooks_count:3d} | AWS: {aws_count:3d} | Terraform: {terraform_count:3d} | "
1900
1969
  f"Accuracy: {overall_acc:5.1f}%[/dim]"
1901
1970
  )
1902
-
1971
+
1903
1972
  # Show recommendations for drift
1904
1973
  recommendations = drift_data.get("recommendations", [])
1905
1974
  for rec in recommendations[:1]: # Show first recommendation only
@@ -1915,34 +1984,33 @@ class EnhancedMCPValidator:
1915
1984
  print_success(f"✅ Enhanced Validation PASSED: {overall_accuracy:.1f}% accuracy achieved")
1916
1985
  else:
1917
1986
  print_warning(f"🔄 Enhanced Validation: {overall_accuracy:.1f}% accuracy with drift detected")
1918
-
1987
+
1919
1988
  print_info(f"Enterprise compliance: {results.get('profiles_validated', 0)} profiles validated")
1920
1989
 
1921
1990
  # Enhanced resource validation summary with terraform comparison
1922
1991
  resource_summary = results.get("resource_validation_summary", {})
1923
1992
  if resource_summary:
1924
1993
  self.console.print(f"\n[bright_cyan]📊 Enhanced Resource Validation Summary[/]")
1925
-
1994
+
1926
1995
  # Create drift analysis table
1927
1996
  drift_table = create_table(
1928
- title="Infrastructure Drift Analysis",
1929
- caption="3-way comparison: Runbooks | AWS API | Terraform IaC"
1997
+ title="Infrastructure Drift Analysis", caption="3-way comparison: Runbooks | AWS API | Terraform IaC"
1930
1998
  )
1931
-
1999
+
1932
2000
  drift_table.add_column("Resource Type", style="cyan", no_wrap=True)
1933
2001
  drift_table.add_column("Runbooks", style="green", justify="right")
1934
2002
  drift_table.add_column("AWS API", style="blue", justify="right")
1935
2003
  drift_table.add_column("Terraform", style="magenta", justify="right")
1936
2004
  drift_table.add_column("Accuracy", justify="right")
1937
2005
  drift_table.add_column("Drift Status", style="yellow")
1938
-
2006
+
1939
2007
  for resource_type, summary in resource_summary.items():
1940
2008
  avg_accuracy = summary.get("average_accuracy", 0)
1941
2009
  total_runbooks = summary.get("total_runbooks", 0)
1942
2010
  total_aws = summary.get("total_aws", 0)
1943
2011
  total_terraform = summary.get("total_terraform", 0)
1944
2012
  drift_incidents = summary.get("drift_incidents", 0)
1945
-
2013
+
1946
2014
  # Determine status
1947
2015
  if drift_incidents > 0:
1948
2016
  status = f"🔄 {drift_incidents} drift(s)"
@@ -1950,18 +2018,18 @@ class EnhancedMCPValidator:
1950
2018
  else:
1951
2019
  status = "✅ Aligned"
1952
2020
  status_style = "green"
1953
-
2021
+
1954
2022
  accuracy_icon = "✅" if avg_accuracy >= 99.5 else "⚠️" if avg_accuracy >= 90.0 else "❌"
1955
-
2023
+
1956
2024
  drift_table.add_row(
1957
2025
  self.supported_services.get(resource_type, resource_type),
1958
2026
  str(total_runbooks),
1959
2027
  str(total_aws),
1960
2028
  str(total_terraform),
1961
2029
  f"{accuracy_icon} {avg_accuracy:5.1f}%",
1962
- status
2030
+ status,
1963
2031
  )
1964
-
2032
+
1965
2033
  self.console.print(drift_table)
1966
2034
 
1967
2035
  def validate_inventory_data(self, runbooks_inventory: Dict[str, Any]) -> Dict[str, Any]:
@@ -1974,74 +2042,76 @@ class EnhancedMCPValidator:
1974
2042
 
1975
2043
  return loop.run_until_complete(self.validate_inventory_data_async(runbooks_inventory))
1976
2044
 
1977
- def validate_resource_counts(self, resource_counts: Dict[str, int], profile: Optional[str] = None) -> Dict[str, Any]:
2045
+ def validate_resource_counts(
2046
+ self, resource_counts: Dict[str, int], profile: Optional[str] = None
2047
+ ) -> Dict[str, Any]:
1978
2048
  """
1979
2049
  Cross-validate individual resource counts with AWS API.
1980
-
2050
+
1981
2051
  Args:
1982
2052
  resource_counts: Dictionary of resource types to counts (e.g., {'ec2': 45, 's3': 12})
1983
2053
  profile: Profile to use for validation (uses first available if None)
1984
-
2054
+
1985
2055
  Returns:
1986
2056
  Resource-level validation results
1987
2057
  """
1988
2058
  profile = profile or (self.profiles[0] if self.profiles else None)
1989
2059
  if not profile or profile not in self.aws_sessions:
1990
- return {'error': 'No valid profile for resource count validation'}
1991
-
2060
+ return {"error": "No valid profile for resource count validation"}
2061
+
1992
2062
  session_info = self.aws_sessions[profile]
1993
2063
  session = session_info["session"] # Extract actual boto3.Session object
1994
2064
  validations = {}
1995
-
2065
+
1996
2066
  # Get MCP resource counts
1997
2067
  try:
1998
2068
  mcp_data = asyncio.run(self._get_independent_inventory_data(session, profile))
1999
- mcp_counts = mcp_data.get('resource_counts', {})
2000
-
2069
+ mcp_counts = mcp_data.get("resource_counts", {})
2070
+
2001
2071
  # Validate each resource type
2002
2072
  for resource_type, runbooks_count in resource_counts.items():
2003
2073
  if resource_type in self.supported_services:
2004
2074
  mcp_count = mcp_counts.get(resource_type, 0)
2005
-
2075
+
2006
2076
  variance = 0.0
2007
2077
  if runbooks_count > 0:
2008
2078
  variance = abs(runbooks_count - mcp_count) / runbooks_count * 100
2009
-
2079
+
2010
2080
  validations[resource_type] = {
2011
- 'runbooks_count': runbooks_count,
2012
- 'mcp_count': mcp_count,
2013
- 'variance_percent': variance,
2014
- 'passed': variance <= self.tolerance_percent,
2015
- 'status': 'PASSED' if variance <= self.tolerance_percent else 'VARIANCE'
2081
+ "runbooks_count": runbooks_count,
2082
+ "mcp_count": mcp_count,
2083
+ "variance_percent": variance,
2084
+ "passed": variance <= self.tolerance_percent,
2085
+ "status": "PASSED" if variance <= self.tolerance_percent else "VARIANCE",
2016
2086
  }
2017
-
2087
+
2018
2088
  # Display resource validation results
2019
2089
  self._display_resource_count_validation(validations)
2020
-
2090
+
2021
2091
  except Exception as e:
2022
2092
  print_error(f"Resource count validation failed: {str(e)[:50]}")
2023
- return {'error': str(e)}
2024
-
2093
+ return {"error": str(e)}
2094
+
2025
2095
  return {
2026
- 'resources': validations,
2027
- 'validated_count': len(validations),
2028
- 'passed_count': sum(1 for v in validations.values() if v['passed']),
2029
- 'timestamp': datetime.now().isoformat()
2096
+ "resources": validations,
2097
+ "validated_count": len(validations),
2098
+ "passed_count": sum(1 for v in validations.values() if v["passed"]),
2099
+ "timestamp": datetime.now().isoformat(),
2030
2100
  }
2031
-
2101
+
2032
2102
  def _display_resource_count_validation(self, validations: Dict[str, Dict]) -> None:
2033
2103
  """Display resource count validation results."""
2034
2104
  if validations:
2035
2105
  self.console.print("\n[bright_cyan]Resource Count MCP Validation:[/bright_cyan]")
2036
-
2106
+
2037
2107
  for resource_type, validation in validations.items():
2038
- if validation['passed']:
2108
+ if validation["passed"]:
2039
2109
  icon = "✅"
2040
2110
  color = "green"
2041
2111
  else:
2042
2112
  icon = "⚠️"
2043
2113
  color = "yellow"
2044
-
2114
+
2045
2115
  resource_name = self.supported_services.get(resource_type, resource_type)
2046
2116
  self.console.print(
2047
2117
  f"[dim] {resource_name:20s}: {icon} [{color}]"
@@ -2050,30 +2120,38 @@ class EnhancedMCPValidator:
2050
2120
  )
2051
2121
 
2052
2122
 
2053
- def create_enhanced_mcp_validator(user_profile: Optional[str] = None, console: Optional[Console] = None,
2054
- mcp_config_path: Optional[str] = None, terraform_directory: Optional[str] = None) -> EnhancedMCPValidator:
2123
+ def create_enhanced_mcp_validator(
2124
+ user_profile: Optional[str] = None,
2125
+ console: Optional[Console] = None,
2126
+ mcp_config_path: Optional[str] = None,
2127
+ terraform_directory: Optional[str] = None,
2128
+ ) -> EnhancedMCPValidator:
2055
2129
  """
2056
2130
  Factory function to create enhanced MCP validator with real server integration.
2057
-
2131
+
2058
2132
  Args:
2059
2133
  user_profile: User-specified profile (--profile parameter) - takes priority
2060
2134
  console: Rich console for output
2061
2135
  mcp_config_path: Path to .mcp.json configuration file
2062
2136
  terraform_directory: Path to terraform configurations
2063
-
2137
+
2064
2138
  Returns:
2065
2139
  Enhanced MCP validator instance
2066
2140
  """
2067
2141
  return EnhancedMCPValidator(
2068
- user_profile=user_profile,
2069
- console=console,
2142
+ user_profile=user_profile,
2143
+ console=console,
2070
2144
  mcp_config_path=mcp_config_path,
2071
- terraform_directory=terraform_directory
2145
+ terraform_directory=terraform_directory,
2072
2146
  )
2073
2147
 
2074
2148
 
2075
- def validate_inventory_with_mcp_servers(runbooks_inventory: Dict[str, Any], user_profile: Optional[str] = None,
2076
- mcp_config_path: Optional[str] = None, terraform_directory: Optional[str] = None) -> Dict[str, Any]:
2149
+ def validate_inventory_with_mcp_servers(
2150
+ runbooks_inventory: Dict[str, Any],
2151
+ user_profile: Optional[str] = None,
2152
+ mcp_config_path: Optional[str] = None,
2153
+ terraform_directory: Optional[str] = None,
2154
+ ) -> Dict[str, Any]:
2077
2155
  """
2078
2156
  Enhanced convenience function to validate inventory results using real MCP servers.
2079
2157
 
@@ -2087,42 +2165,50 @@ def validate_inventory_with_mcp_servers(runbooks_inventory: Dict[str, Any], user
2087
2165
  Enhanced validation results with MCP server integration and drift detection
2088
2166
  """
2089
2167
  validator = create_enhanced_mcp_validator(
2090
- user_profile=user_profile,
2091
- mcp_config_path=mcp_config_path,
2092
- terraform_directory=terraform_directory
2168
+ user_profile=user_profile, mcp_config_path=mcp_config_path, terraform_directory=terraform_directory
2093
2169
  )
2094
2170
  return asyncio.run(validator.validate_with_mcp_servers(runbooks_inventory))
2095
2171
 
2096
2172
 
2097
2173
  # Legacy compatibility - maintain backward compatibility with existing code
2098
- def create_inventory_mcp_validator(profiles: List[str], console: Optional[Console] = None, terraform_directory: Optional[str] = None) -> EnhancedMCPValidator:
2174
+ def create_inventory_mcp_validator(
2175
+ profiles: List[str], console: Optional[Console] = None, terraform_directory: Optional[str] = None
2176
+ ) -> EnhancedMCPValidator:
2099
2177
  """Legacy compatibility function for existing code."""
2100
2178
  # Convert profile list to single user profile (use first profile)
2101
2179
  user_profile = profiles[0] if profiles else None
2102
- return create_enhanced_mcp_validator(user_profile=user_profile, console=console, terraform_directory=terraform_directory)
2180
+ return create_enhanced_mcp_validator(
2181
+ user_profile=user_profile, console=console, terraform_directory=terraform_directory
2182
+ )
2103
2183
 
2104
2184
 
2105
- def validate_inventory_results_with_mcp(profiles: List[str], runbooks_inventory: Dict[str, Any], terraform_directory: Optional[str] = None) -> Dict[str, Any]:
2185
+ def validate_inventory_results_with_mcp(
2186
+ profiles: List[str], runbooks_inventory: Dict[str, Any], terraform_directory: Optional[str] = None
2187
+ ) -> Dict[str, Any]:
2106
2188
  """Legacy compatibility function for existing code."""
2107
2189
  user_profile = profiles[0] if profiles else None
2108
- return validate_inventory_with_mcp_servers(runbooks_inventory, user_profile=user_profile, terraform_directory=terraform_directory)
2190
+ return validate_inventory_with_mcp_servers(
2191
+ runbooks_inventory, user_profile=user_profile, terraform_directory=terraform_directory
2192
+ )
2109
2193
 
2110
2194
 
2111
- def generate_drift_report(profiles: List[str], runbooks_inventory: Dict[str, Any], terraform_directory: Optional[str] = None) -> Dict[str, Any]:
2195
+ def generate_drift_report(
2196
+ profiles: List[str], runbooks_inventory: Dict[str, Any], terraform_directory: Optional[str] = None
2197
+ ) -> Dict[str, Any]:
2112
2198
  """
2113
2199
  Generate comprehensive infrastructure drift report.
2114
-
2200
+
2115
2201
  Args:
2116
2202
  profiles: List of AWS profiles to analyze
2117
2203
  runbooks_inventory: Inventory results from runbooks collection
2118
2204
  terraform_directory: Path to terraform configurations
2119
-
2205
+
2120
2206
  Returns:
2121
2207
  Comprehensive drift analysis report with recommendations
2122
2208
  """
2123
2209
  validator = create_inventory_mcp_validator(profiles, terraform_directory=terraform_directory)
2124
2210
  validation_results = validator.validate_inventory_data(runbooks_inventory)
2125
-
2211
+
2126
2212
  # Extract drift-specific information for reporting
2127
2213
  drift_report = {
2128
2214
  "report_type": "infrastructure_drift_analysis",
@@ -2131,9 +2217,9 @@ def generate_drift_report(profiles: List[str], runbooks_inventory: Dict[str, Any
2131
2217
  "accounts_analyzed": validation_results.get("profiles_validated", 0),
2132
2218
  "overall_accuracy": validation_results.get("total_accuracy", 0),
2133
2219
  "drift_detected": not validation_results.get("passed_validation", False),
2134
- "detailed_analysis": []
2220
+ "detailed_analysis": [],
2135
2221
  }
2136
-
2222
+
2137
2223
  # Add detailed per-account drift analysis
2138
2224
  for profile_result in validation_results.get("profile_results", []):
2139
2225
  account_drift = {
@@ -2143,8 +2229,8 @@ def generate_drift_report(profiles: List[str], runbooks_inventory: Dict[str, Any
2143
2229
  "drift_summary": profile_result.get("drift_summary", {}),
2144
2230
  "terraform_coverage": profile_result.get("terraform_files_parsed", 0) > 0,
2145
2231
  "recommendations": profile_result.get("account_recommendations", []),
2146
- "resource_drift_details": profile_result.get("resource_drift_analysis", {})
2232
+ "resource_drift_details": profile_result.get("resource_drift_analysis", {}),
2147
2233
  }
2148
2234
  drift_report["detailed_analysis"].append(account_drift)
2149
-
2150
- return drift_report
2235
+
2236
+ return drift_report