runbooks 1.1.4__py3-none-any.whl → 1.1.5__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.
- runbooks/__init__.py +31 -2
- runbooks/__init___optimized.py +18 -4
- runbooks/_platform/__init__.py +1 -5
- runbooks/_platform/core/runbooks_wrapper.py +141 -138
- runbooks/aws2/accuracy_validator.py +812 -0
- runbooks/base.py +7 -0
- runbooks/cfat/assessment/compliance.py +1 -1
- runbooks/cfat/assessment/runner.py +1 -0
- runbooks/cfat/cloud_foundations_assessment.py +227 -239
- runbooks/cli/__init__.py +1 -1
- runbooks/cli/commands/cfat.py +64 -23
- runbooks/cli/commands/finops.py +1005 -54
- runbooks/cli/commands/inventory.py +138 -35
- runbooks/cli/commands/operate.py +9 -36
- runbooks/cli/commands/security.py +42 -18
- runbooks/cli/commands/validation.py +432 -18
- runbooks/cli/commands/vpc.py +81 -17
- runbooks/cli/registry.py +22 -10
- runbooks/cloudops/__init__.py +20 -27
- runbooks/cloudops/base.py +96 -107
- runbooks/cloudops/cost_optimizer.py +544 -542
- runbooks/cloudops/infrastructure_optimizer.py +5 -4
- runbooks/cloudops/interfaces.py +224 -225
- runbooks/cloudops/lifecycle_manager.py +5 -4
- runbooks/cloudops/mcp_cost_validation.py +252 -235
- runbooks/cloudops/models.py +78 -53
- runbooks/cloudops/monitoring_automation.py +5 -4
- runbooks/cloudops/notebook_framework.py +177 -213
- runbooks/cloudops/security_enforcer.py +125 -159
- runbooks/common/accuracy_validator.py +11 -0
- runbooks/common/aws_pricing.py +349 -326
- runbooks/common/aws_pricing_api.py +211 -212
- runbooks/common/aws_profile_manager.py +40 -36
- runbooks/common/aws_utils.py +74 -79
- runbooks/common/business_logic.py +126 -104
- runbooks/common/cli_decorators.py +36 -60
- runbooks/common/comprehensive_cost_explorer_integration.py +455 -463
- runbooks/common/cross_account_manager.py +197 -204
- runbooks/common/date_utils.py +27 -39
- runbooks/common/decorators.py +29 -19
- runbooks/common/dry_run_examples.py +173 -208
- runbooks/common/dry_run_framework.py +157 -155
- runbooks/common/enhanced_exception_handler.py +15 -4
- runbooks/common/enhanced_logging_example.py +50 -64
- runbooks/common/enhanced_logging_integration_example.py +65 -37
- runbooks/common/env_utils.py +16 -16
- runbooks/common/error_handling.py +40 -38
- runbooks/common/lazy_loader.py +41 -23
- runbooks/common/logging_integration_helper.py +79 -86
- runbooks/common/mcp_cost_explorer_integration.py +476 -493
- runbooks/common/mcp_integration.py +63 -74
- runbooks/common/memory_optimization.py +140 -118
- runbooks/common/module_cli_base.py +37 -58
- runbooks/common/organizations_client.py +175 -193
- runbooks/common/patterns.py +23 -25
- runbooks/common/performance_monitoring.py +67 -71
- runbooks/common/performance_optimization_engine.py +283 -274
- runbooks/common/profile_utils.py +111 -37
- runbooks/common/rich_utils.py +201 -141
- runbooks/common/sre_performance_suite.py +177 -186
- runbooks/enterprise/__init__.py +1 -1
- runbooks/enterprise/logging.py +144 -106
- runbooks/enterprise/security.py +187 -204
- runbooks/enterprise/validation.py +43 -56
- runbooks/finops/__init__.py +26 -30
- runbooks/finops/account_resolver.py +1 -1
- runbooks/finops/advanced_optimization_engine.py +980 -0
- runbooks/finops/automation_core.py +268 -231
- runbooks/finops/business_case_config.py +184 -179
- runbooks/finops/cli.py +660 -139
- runbooks/finops/commvault_ec2_analysis.py +157 -164
- runbooks/finops/compute_cost_optimizer.py +336 -320
- runbooks/finops/config.py +20 -20
- runbooks/finops/cost_optimizer.py +484 -618
- runbooks/finops/cost_processor.py +332 -214
- runbooks/finops/dashboard_runner.py +1006 -172
- runbooks/finops/ebs_cost_optimizer.py +991 -657
- runbooks/finops/elastic_ip_optimizer.py +317 -257
- runbooks/finops/enhanced_mcp_integration.py +340 -0
- runbooks/finops/enhanced_progress.py +32 -29
- runbooks/finops/enhanced_trend_visualization.py +3 -2
- runbooks/finops/enterprise_wrappers.py +223 -285
- runbooks/finops/executive_export.py +203 -160
- runbooks/finops/helpers.py +130 -288
- runbooks/finops/iam_guidance.py +1 -1
- runbooks/finops/infrastructure/__init__.py +80 -0
- runbooks/finops/infrastructure/commands.py +506 -0
- runbooks/finops/infrastructure/load_balancer_optimizer.py +866 -0
- runbooks/finops/infrastructure/vpc_endpoint_optimizer.py +832 -0
- runbooks/finops/markdown_exporter.py +337 -174
- runbooks/finops/mcp_validator.py +1952 -0
- runbooks/finops/nat_gateway_optimizer.py +1512 -481
- runbooks/finops/network_cost_optimizer.py +657 -587
- runbooks/finops/notebook_utils.py +226 -188
- runbooks/finops/optimization_engine.py +1136 -0
- runbooks/finops/optimizer.py +19 -23
- runbooks/finops/rds_snapshot_optimizer.py +367 -411
- runbooks/finops/reservation_optimizer.py +427 -363
- runbooks/finops/scenario_cli_integration.py +64 -65
- runbooks/finops/scenarios.py +1277 -438
- runbooks/finops/schemas.py +218 -182
- runbooks/finops/snapshot_manager.py +2289 -0
- runbooks/finops/types.py +3 -3
- runbooks/finops/validation_framework.py +259 -265
- runbooks/finops/vpc_cleanup_exporter.py +189 -144
- runbooks/finops/vpc_cleanup_optimizer.py +591 -573
- runbooks/finops/workspaces_analyzer.py +171 -182
- runbooks/integration/__init__.py +89 -0
- runbooks/integration/mcp_integration.py +1920 -0
- runbooks/inventory/CLAUDE.md +816 -0
- runbooks/inventory/__init__.py +2 -2
- runbooks/inventory/cloud_foundations_integration.py +144 -149
- runbooks/inventory/collectors/aws_comprehensive.py +1 -1
- runbooks/inventory/collectors/aws_networking.py +109 -99
- runbooks/inventory/collectors/base.py +4 -0
- runbooks/inventory/core/collector.py +495 -313
- runbooks/inventory/drift_detection_cli.py +69 -96
- runbooks/inventory/inventory_mcp_cli.py +48 -46
- runbooks/inventory/list_rds_snapshots_aggregator.py +192 -208
- runbooks/inventory/mcp_inventory_validator.py +549 -465
- runbooks/inventory/mcp_vpc_validator.py +359 -442
- runbooks/inventory/organizations_discovery.py +55 -51
- runbooks/inventory/rich_inventory_display.py +33 -32
- runbooks/inventory/unified_validation_engine.py +278 -251
- runbooks/inventory/vpc_analyzer.py +732 -695
- runbooks/inventory/vpc_architecture_validator.py +293 -348
- runbooks/inventory/vpc_dependency_analyzer.py +382 -378
- runbooks/inventory/vpc_flow_analyzer.py +1 -1
- runbooks/main.py +49 -34
- runbooks/main_final.py +91 -60
- runbooks/main_minimal.py +22 -10
- runbooks/main_optimized.py +131 -100
- runbooks/main_ultra_minimal.py +7 -2
- runbooks/mcp/__init__.py +36 -0
- runbooks/mcp/integration.py +679 -0
- runbooks/monitoring/performance_monitor.py +9 -4
- runbooks/operate/dynamodb_operations.py +3 -1
- runbooks/operate/ec2_operations.py +145 -137
- runbooks/operate/iam_operations.py +146 -152
- runbooks/operate/networking_cost_heatmap.py +29 -8
- runbooks/operate/rds_operations.py +223 -254
- runbooks/operate/s3_operations.py +107 -118
- runbooks/operate/vpc_operations.py +646 -616
- runbooks/remediation/base.py +1 -1
- runbooks/remediation/commons.py +10 -7
- runbooks/remediation/commvault_ec2_analysis.py +70 -66
- runbooks/remediation/ec2_unattached_ebs_volumes.py +1 -0
- runbooks/remediation/multi_account.py +24 -21
- runbooks/remediation/rds_snapshot_list.py +86 -60
- runbooks/remediation/remediation_cli.py +92 -146
- runbooks/remediation/universal_account_discovery.py +83 -79
- runbooks/remediation/workspaces_list.py +46 -41
- runbooks/security/__init__.py +19 -0
- runbooks/security/assessment_runner.py +1150 -0
- runbooks/security/baseline_checker.py +812 -0
- runbooks/security/cloudops_automation_security_validator.py +509 -535
- runbooks/security/compliance_automation_engine.py +17 -17
- runbooks/security/config/__init__.py +2 -2
- runbooks/security/config/compliance_config.py +50 -50
- runbooks/security/config_template_generator.py +63 -76
- runbooks/security/enterprise_security_framework.py +1 -1
- runbooks/security/executive_security_dashboard.py +519 -508
- runbooks/security/multi_account_security_controls.py +959 -1210
- runbooks/security/real_time_security_monitor.py +422 -444
- runbooks/security/security_baseline_tester.py +1 -1
- runbooks/security/security_cli.py +143 -112
- runbooks/security/test_2way_validation.py +439 -0
- runbooks/security/two_way_validation_framework.py +852 -0
- runbooks/sre/production_monitoring_framework.py +167 -177
- runbooks/tdd/__init__.py +15 -0
- runbooks/tdd/cli.py +1071 -0
- runbooks/utils/__init__.py +14 -17
- runbooks/utils/logger.py +7 -2
- runbooks/utils/version_validator.py +50 -47
- runbooks/validation/__init__.py +6 -6
- runbooks/validation/cli.py +9 -3
- runbooks/validation/comprehensive_2way_validator.py +745 -704
- runbooks/validation/mcp_validator.py +906 -228
- runbooks/validation/terraform_citations_validator.py +104 -115
- runbooks/validation/terraform_drift_detector.py +447 -451
- runbooks/vpc/README.md +617 -0
- runbooks/vpc/__init__.py +8 -1
- runbooks/vpc/analyzer.py +577 -0
- runbooks/vpc/cleanup_wrapper.py +476 -413
- runbooks/vpc/cli_cloudtrail_commands.py +339 -0
- runbooks/vpc/cli_mcp_validation_commands.py +480 -0
- runbooks/vpc/cloudtrail_audit_integration.py +717 -0
- runbooks/vpc/config.py +92 -97
- runbooks/vpc/cost_engine.py +411 -148
- runbooks/vpc/cost_explorer_integration.py +553 -0
- runbooks/vpc/cross_account_session.py +101 -106
- runbooks/vpc/enhanced_mcp_validation.py +917 -0
- runbooks/vpc/eni_gate_validator.py +961 -0
- runbooks/vpc/heatmap_engine.py +185 -160
- runbooks/vpc/mcp_no_eni_validator.py +680 -639
- runbooks/vpc/nat_gateway_optimizer.py +358 -0
- runbooks/vpc/networking_wrapper.py +15 -8
- runbooks/vpc/pdca_remediation_planner.py +528 -0
- runbooks/vpc/performance_optimized_analyzer.py +219 -231
- runbooks/vpc/runbooks_adapter.py +1167 -241
- runbooks/vpc/tdd_red_phase_stubs.py +601 -0
- runbooks/vpc/test_data_loader.py +358 -0
- runbooks/vpc/tests/conftest.py +314 -4
- runbooks/vpc/tests/test_cleanup_framework.py +1022 -0
- runbooks/vpc/tests/test_cost_engine.py +0 -2
- runbooks/vpc/topology_generator.py +326 -0
- runbooks/vpc/unified_scenarios.py +1297 -1124
- runbooks/vpc/vpc_cleanup_integration.py +1943 -1115
- runbooks-1.1.5.dist-info/METADATA +328 -0
- {runbooks-1.1.4.dist-info → runbooks-1.1.5.dist-info}/RECORD +214 -193
- runbooks/finops/README.md +0 -414
- runbooks/finops/accuracy_cross_validator.py +0 -647
- runbooks/finops/business_cases.py +0 -950
- runbooks/finops/dashboard_router.py +0 -922
- runbooks/finops/ebs_optimizer.py +0 -973
- runbooks/finops/embedded_mcp_validator.py +0 -1629
- runbooks/finops/enhanced_dashboard_runner.py +0 -527
- runbooks/finops/finops_dashboard.py +0 -584
- runbooks/finops/finops_scenarios.py +0 -1218
- runbooks/finops/legacy_migration.py +0 -730
- runbooks/finops/multi_dashboard.py +0 -1519
- runbooks/finops/single_dashboard.py +0 -1113
- runbooks/finops/unlimited_scenarios.py +0 -393
- runbooks-1.1.4.dist-info/METADATA +0 -800
- {runbooks-1.1.4.dist-info → runbooks-1.1.5.dist-info}/WHEEL +0 -0
- {runbooks-1.1.4.dist-info → runbooks-1.1.5.dist-info}/entry_points.txt +0 -0
- {runbooks-1.1.4.dist-info → runbooks-1.1.5.dist-info}/licenses/LICENSE +0 -0
- {runbooks-1.1.4.dist-info → runbooks-1.1.5.dist-info}/top_level.txt +0 -0
@@ -59,12 +59,12 @@ from .core.collector import InventoryCollector
|
|
59
59
|
class UnifiedValidationEngine:
|
60
60
|
"""
|
61
61
|
Enterprise Unified Validation Engine for 3-way AWS resource validation.
|
62
|
-
|
62
|
+
|
63
63
|
Integrates all validation sources into a single workflow:
|
64
64
|
- runbooks APIs (inventory collection methods)
|
65
65
|
- MCP servers (real server integration from .mcp.json)
|
66
66
|
- Terraform drift detection (Infrastructure as Code alignment)
|
67
|
-
|
67
|
+
|
68
68
|
Provides comprehensive accuracy validation ≥99.5% with enterprise reporting.
|
69
69
|
"""
|
70
70
|
|
@@ -79,7 +79,7 @@ class UnifiedValidationEngine:
|
|
79
79
|
):
|
80
80
|
"""
|
81
81
|
Initialize unified validation engine with enterprise configuration.
|
82
|
-
|
82
|
+
|
83
83
|
Args:
|
84
84
|
user_profile: User-specified profile (--profile parameter) - takes priority
|
85
85
|
console: Rich console for output
|
@@ -92,10 +92,10 @@ class UnifiedValidationEngine:
|
|
92
92
|
self.console = console or rich_console
|
93
93
|
self.validation_threshold = validation_threshold
|
94
94
|
self.performance_target = performance_target_seconds
|
95
|
-
|
95
|
+
|
96
96
|
# Enterprise profile management
|
97
97
|
self.enterprise_profiles = self._resolve_enterprise_profiles()
|
98
|
-
|
98
|
+
|
99
99
|
# Validation components
|
100
100
|
self.mcp_validator = EnhancedMCPValidator(
|
101
101
|
user_profile=user_profile,
|
@@ -103,31 +103,31 @@ class UnifiedValidationEngine:
|
|
103
103
|
mcp_config_path=mcp_config_path,
|
104
104
|
terraform_directory=terraform_directory,
|
105
105
|
)
|
106
|
-
|
106
|
+
|
107
107
|
# Initialize inventory collector for runbooks API validation
|
108
108
|
self.inventory_collector = InventoryCollector(
|
109
109
|
profile=self.enterprise_profiles["operational"],
|
110
|
-
region="us-east-1" # Default region for global services
|
110
|
+
region="us-east-1", # Default region for global services
|
111
111
|
)
|
112
|
-
|
112
|
+
|
113
113
|
# Validation cache for performance optimization
|
114
114
|
self.validation_cache = {}
|
115
115
|
self.cache_ttl = 300 # 5 minutes
|
116
|
-
|
116
|
+
|
117
117
|
# Supported resource types for unified validation
|
118
118
|
self.supported_resources = {
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
119
|
+
"ec2": "EC2 Instances",
|
120
|
+
"s3": "S3 Buckets",
|
121
|
+
"rds": "RDS Instances",
|
122
|
+
"lambda": "Lambda Functions",
|
123
|
+
"vpc": "VPCs",
|
124
|
+
"iam": "IAM Roles",
|
125
|
+
"cloudformation": "CloudFormation Stacks",
|
126
|
+
"elbv2": "Load Balancers",
|
127
|
+
"route53": "Route53 Hosted Zones",
|
128
|
+
"sns": "SNS Topics",
|
129
|
+
"eni": "Network Interfaces",
|
130
|
+
"ebs": "EBS Volumes",
|
131
131
|
}
|
132
132
|
|
133
133
|
def _resolve_enterprise_profiles(self) -> Dict[str, str]:
|
@@ -150,7 +150,7 @@ class UnifiedValidationEngine:
|
|
150
150
|
) -> Dict[str, Any]:
|
151
151
|
"""
|
152
152
|
Run comprehensive unified validation across all sources.
|
153
|
-
|
153
|
+
|
154
154
|
Args:
|
155
155
|
resource_types: List of resource types to validate
|
156
156
|
accounts: List of account IDs to analyze
|
@@ -159,12 +159,12 @@ class UnifiedValidationEngine:
|
|
159
159
|
enable_mcp_servers: Enable MCP server integration
|
160
160
|
export_formats: List of export formats ('json', 'csv', 'pdf', 'markdown')
|
161
161
|
output_directory: Directory for validation evidence exports
|
162
|
-
|
162
|
+
|
163
163
|
Returns:
|
164
164
|
Comprehensive validation results with 3-way cross-validation
|
165
165
|
"""
|
166
166
|
validation_start_time = time.time()
|
167
|
-
|
167
|
+
|
168
168
|
validation_results = {
|
169
169
|
"validation_timestamp": datetime.now().isoformat(),
|
170
170
|
"validation_method": "unified_3way_cross_validation",
|
@@ -187,8 +187,10 @@ class UnifiedValidationEngine:
|
|
187
187
|
}
|
188
188
|
|
189
189
|
self.console.print(f"[blue]🔍 Starting Unified 3-Way Validation Engine[/blue]")
|
190
|
-
self.console.print(
|
191
|
-
|
190
|
+
self.console.print(
|
191
|
+
f"[dim]Target: ≥{self.validation_threshold}% accuracy | Performance: <{self.performance_target}s[/dim]"
|
192
|
+
)
|
193
|
+
|
192
194
|
# Display validation sources
|
193
195
|
sources = []
|
194
196
|
if validation_results["validation_sources"]["runbooks_apis"]:
|
@@ -197,52 +199,46 @@ class UnifiedValidationEngine:
|
|
197
199
|
sources.append("MCP Servers")
|
198
200
|
if validation_results["validation_sources"]["terraform_drift"]:
|
199
201
|
sources.append("Terraform IaC")
|
200
|
-
|
202
|
+
|
201
203
|
self.console.print(f"[dim cyan]🔗 Validation Sources: {', '.join(sources)}[/]")
|
202
204
|
|
203
205
|
try:
|
204
206
|
# Step 1: Collect baseline inventory from runbooks APIs
|
205
|
-
runbooks_inventory = await self._collect_runbooks_inventory(
|
206
|
-
|
207
|
-
)
|
208
|
-
|
207
|
+
runbooks_inventory = await self._collect_runbooks_inventory(resource_types, accounts, regions)
|
208
|
+
|
209
209
|
# Step 2: Run 3-way cross-validation
|
210
210
|
cross_validation_results = await self._execute_3way_validation(
|
211
211
|
runbooks_inventory, enable_terraform_drift, enable_mcp_servers
|
212
212
|
)
|
213
|
-
|
213
|
+
|
214
214
|
# Step 3: Generate comprehensive analysis
|
215
|
-
unified_analysis = self._generate_unified_analysis(
|
216
|
-
|
217
|
-
)
|
218
|
-
|
215
|
+
unified_analysis = self._generate_unified_analysis(runbooks_inventory, cross_validation_results)
|
216
|
+
|
219
217
|
# Step 4: Calculate performance metrics
|
220
218
|
total_execution_time = time.time() - validation_start_time
|
221
219
|
validation_results["performance_metrics"]["total_execution_time"] = total_execution_time
|
222
|
-
validation_results["performance_metrics"]["performance_achieved"] =
|
223
|
-
|
220
|
+
validation_results["performance_metrics"]["performance_achieved"] = (
|
221
|
+
total_execution_time <= self.performance_target
|
222
|
+
)
|
223
|
+
|
224
224
|
# Step 5: Populate results
|
225
225
|
validation_results.update(unified_analysis)
|
226
|
-
|
226
|
+
|
227
227
|
# Step 6: Generate recommendations
|
228
|
-
validation_results["recommendations"] = self._generate_actionable_recommendations(
|
229
|
-
|
230
|
-
)
|
231
|
-
|
228
|
+
validation_results["recommendations"] = self._generate_actionable_recommendations(unified_analysis)
|
229
|
+
|
232
230
|
# Step 7: Display results
|
233
231
|
self._display_unified_validation_results(validation_results)
|
234
|
-
|
232
|
+
|
235
233
|
# Step 8: Export evidence if requested
|
236
234
|
if export_formats:
|
237
|
-
await self._export_validation_evidence(
|
238
|
-
|
239
|
-
)
|
240
|
-
|
235
|
+
await self._export_validation_evidence(validation_results, export_formats, output_directory)
|
236
|
+
|
241
237
|
except Exception as e:
|
242
238
|
print_error(f"Unified validation failed: {str(e)}")
|
243
239
|
validation_results["error"] = str(e)
|
244
240
|
validation_results["passed_validation"] = False
|
245
|
-
|
241
|
+
|
246
242
|
return validation_results
|
247
243
|
|
248
244
|
async def _collect_runbooks_inventory(
|
@@ -253,11 +249,11 @@ class UnifiedValidationEngine:
|
|
253
249
|
) -> Dict[str, Any]:
|
254
250
|
"""Collect baseline inventory using runbooks APIs."""
|
255
251
|
self.console.print(f"[yellow]📊 Step 1/3: Collecting runbooks inventory baseline[/yellow]")
|
256
|
-
|
252
|
+
|
257
253
|
try:
|
258
254
|
# Use the existing inventory collector
|
259
255
|
inventory_results = {}
|
260
|
-
|
256
|
+
|
261
257
|
# Get current account ID
|
262
258
|
if not accounts:
|
263
259
|
try:
|
@@ -267,7 +263,7 @@ class UnifiedValidationEngine:
|
|
267
263
|
accounts = [current_account]
|
268
264
|
except Exception:
|
269
265
|
accounts = ["unknown"]
|
270
|
-
|
266
|
+
|
271
267
|
for account_id in accounts:
|
272
268
|
account_inventory = {
|
273
269
|
"account_id": account_id,
|
@@ -276,7 +272,7 @@ class UnifiedValidationEngine:
|
|
276
272
|
"collection_method": "runbooks_inventory_apis",
|
277
273
|
"timestamp": datetime.now().isoformat(),
|
278
274
|
}
|
279
|
-
|
275
|
+
|
280
276
|
# Collect actual resource counts using runbooks inventory collector
|
281
277
|
for resource_type in resource_types or list(self.supported_resources.keys()):
|
282
278
|
try:
|
@@ -286,14 +282,18 @@ class UnifiedValidationEngine:
|
|
286
282
|
)
|
287
283
|
account_inventory["resource_counts"][resource_type] = resource_count
|
288
284
|
except Exception as e:
|
289
|
-
self.console.log(
|
285
|
+
self.console.log(
|
286
|
+
f"[yellow]Warning: Failed to collect {resource_type} for account {account_id}: {str(e)[:30]}[/]"
|
287
|
+
)
|
290
288
|
account_inventory["resource_counts"][resource_type] = 0
|
291
|
-
|
289
|
+
|
292
290
|
inventory_results[account_id] = account_inventory
|
293
|
-
|
294
|
-
print_info(
|
291
|
+
|
292
|
+
print_info(
|
293
|
+
f"✅ Runbooks inventory collected: {len(accounts)} accounts, {len(resource_types or [])} resource types"
|
294
|
+
)
|
295
295
|
return inventory_results
|
296
|
-
|
296
|
+
|
297
297
|
except Exception as e:
|
298
298
|
print_warning(f"Runbooks inventory collection encountered issues: {str(e)[:50]}")
|
299
299
|
return {
|
@@ -310,13 +310,13 @@ class UnifiedValidationEngine:
|
|
310
310
|
) -> Dict[str, Any]:
|
311
311
|
"""Execute comprehensive 3-way cross-validation."""
|
312
312
|
self.console.print(f"[yellow]🔍 Step 2/3: Executing 3-way cross-validation[/yellow]")
|
313
|
-
|
313
|
+
|
314
314
|
validation_results = {
|
315
315
|
"runbooks_validation": runbooks_inventory,
|
316
316
|
"mcp_validation": None,
|
317
317
|
"terraform_drift_validation": None,
|
318
318
|
}
|
319
|
-
|
319
|
+
|
320
320
|
# Execute validations in parallel for performance
|
321
321
|
with Progress(
|
322
322
|
SpinnerColumn(),
|
@@ -326,61 +326,57 @@ class UnifiedValidationEngine:
|
|
326
326
|
TimeElapsedColumn(),
|
327
327
|
console=self.console,
|
328
328
|
) as progress:
|
329
|
-
|
330
329
|
# Parallel validation tasks
|
331
330
|
tasks = []
|
332
|
-
|
331
|
+
|
333
332
|
# MCP Server validation
|
334
333
|
if enable_mcp_servers:
|
335
334
|
task_mcp = progress.add_task("MCP server validation...", total=1)
|
336
335
|
tasks.append(("mcp", task_mcp))
|
337
|
-
|
336
|
+
|
338
337
|
# Terraform drift detection
|
339
338
|
if enable_terraform_drift:
|
340
339
|
task_tf = progress.add_task("Terraform drift detection...", total=1)
|
341
340
|
tasks.append(("terraform", task_tf))
|
342
|
-
|
341
|
+
|
343
342
|
# Execute validations
|
344
343
|
with ThreadPoolExecutor(max_workers=2) as executor:
|
345
344
|
futures = {}
|
346
|
-
|
345
|
+
|
347
346
|
if enable_mcp_servers:
|
348
347
|
future_mcp = executor.submit(self._run_mcp_validation, runbooks_inventory)
|
349
348
|
futures["mcp"] = future_mcp
|
350
|
-
|
349
|
+
|
351
350
|
if enable_terraform_drift:
|
352
351
|
future_tf = executor.submit(self._run_terraform_drift_validation, runbooks_inventory)
|
353
352
|
futures["terraform"] = future_tf
|
354
|
-
|
353
|
+
|
355
354
|
# Collect results
|
356
355
|
for validation_type, future in futures.items():
|
357
356
|
try:
|
358
357
|
result = future.result(timeout=30) # 30 second timeout per validation
|
359
358
|
validation_results[f"{validation_type}_validation"] = result
|
360
|
-
|
359
|
+
|
361
360
|
# Update progress
|
362
361
|
for task_type, task_id in tasks:
|
363
362
|
if task_type == validation_type:
|
364
363
|
progress.advance(task_id)
|
365
364
|
break
|
366
|
-
|
365
|
+
|
367
366
|
except Exception as e:
|
368
367
|
print_warning(f"{validation_type} validation failed: {str(e)[:40]}")
|
369
368
|
validation_results[f"{validation_type}_validation"] = {
|
370
369
|
"error": str(e),
|
371
370
|
"validation_status": "FAILED",
|
372
371
|
}
|
373
|
-
|
372
|
+
|
374
373
|
print_info("✅ 3-way cross-validation completed")
|
375
374
|
return validation_results
|
376
375
|
|
377
376
|
def _run_mcp_validation(self, runbooks_inventory: Dict[str, Any]) -> Dict[str, Any]:
|
378
377
|
"""Run MCP server validation (synchronous wrapper)."""
|
379
378
|
try:
|
380
|
-
return validate_inventory_with_mcp_servers(
|
381
|
-
runbooks_inventory,
|
382
|
-
user_profile=self.user_profile
|
383
|
-
)
|
379
|
+
return validate_inventory_with_mcp_servers(runbooks_inventory, user_profile=self.user_profile)
|
384
380
|
except Exception as e:
|
385
381
|
return {
|
386
382
|
"error": str(e),
|
@@ -388,7 +384,6 @@ class UnifiedValidationEngine:
|
|
388
384
|
"timestamp": datetime.now().isoformat(),
|
389
385
|
}
|
390
386
|
|
391
|
-
|
392
387
|
def _run_terraform_drift_validation(self, runbooks_inventory: Dict[str, Any]) -> Dict[str, Any]:
|
393
388
|
"""Run terraform drift detection validation."""
|
394
389
|
try:
|
@@ -398,33 +393,33 @@ class UnifiedValidationEngine:
|
|
398
393
|
"terraform_integration_enabled": True,
|
399
394
|
"drift_analysis": {},
|
400
395
|
}
|
401
|
-
|
396
|
+
|
402
397
|
# Use MCP validator's terraform capabilities
|
403
398
|
terraform_data = self.mcp_validator._get_terraform_declared_resources()
|
404
|
-
|
399
|
+
|
405
400
|
if terraform_data.get("files_parsed", 0) > 0:
|
406
401
|
terraform_validation["terraform_configuration_found"] = True
|
407
402
|
terraform_validation["files_parsed"] = terraform_data["files_parsed"]
|
408
403
|
terraform_validation["declared_resources"] = terraform_data["declared_resources"]
|
409
|
-
|
404
|
+
|
410
405
|
# Calculate drift for each account
|
411
406
|
for account_id, account_data in runbooks_inventory.items():
|
412
407
|
if account_id == "error":
|
413
408
|
continue
|
414
|
-
|
409
|
+
|
415
410
|
drift_analysis = {
|
416
411
|
"account_id": account_id,
|
417
412
|
"drift_detected": False,
|
418
413
|
"resource_drift": {},
|
419
414
|
}
|
420
|
-
|
415
|
+
|
421
416
|
runbooks_counts = account_data.get("resource_counts", {})
|
422
417
|
terraform_counts = terraform_data["declared_resources"]
|
423
|
-
|
418
|
+
|
424
419
|
for resource_type in self.supported_resources.keys():
|
425
420
|
runbooks_count = runbooks_counts.get(resource_type, 0)
|
426
421
|
terraform_count = terraform_counts.get(resource_type, 0)
|
427
|
-
|
422
|
+
|
428
423
|
if runbooks_count != terraform_count:
|
429
424
|
drift_analysis["drift_detected"] = True
|
430
425
|
drift_analysis["resource_drift"][resource_type] = {
|
@@ -432,14 +427,16 @@ class UnifiedValidationEngine:
|
|
432
427
|
"terraform_declared": terraform_count,
|
433
428
|
"drift_amount": abs(runbooks_count - terraform_count),
|
434
429
|
}
|
435
|
-
|
430
|
+
|
436
431
|
terraform_validation["drift_analysis"][account_id] = drift_analysis
|
437
432
|
else:
|
438
433
|
terraform_validation["terraform_configuration_found"] = False
|
439
|
-
terraform_validation["message"] =
|
440
|
-
|
434
|
+
terraform_validation["message"] = (
|
435
|
+
"No terraform configuration found - consider implementing Infrastructure as Code"
|
436
|
+
)
|
437
|
+
|
441
438
|
return terraform_validation
|
442
|
-
|
439
|
+
|
443
440
|
except Exception as e:
|
444
441
|
return {
|
445
442
|
"error": str(e),
|
@@ -454,7 +451,7 @@ class UnifiedValidationEngine:
|
|
454
451
|
) -> Dict[str, Any]:
|
455
452
|
"""Generate comprehensive unified analysis from all validation sources."""
|
456
453
|
self.console.print(f"[yellow]📈 Step 3/3: Generating unified analysis[/yellow]")
|
457
|
-
|
454
|
+
|
458
455
|
unified_analysis = {
|
459
456
|
"overall_accuracy": 0.0,
|
460
457
|
"passed_validation": False,
|
@@ -467,18 +464,19 @@ class UnifiedValidationEngine:
|
|
467
464
|
"resource_accuracy_breakdown": {},
|
468
465
|
"account_analysis": {},
|
469
466
|
}
|
470
|
-
|
467
|
+
|
471
468
|
# Analyze results from each validation source
|
472
469
|
validation_sources = {
|
473
470
|
"runbooks": cross_validation_results.get("runbooks_validation", {}),
|
474
471
|
"mcp": cross_validation_results.get("mcp_validation", {}),
|
475
472
|
"terraform": cross_validation_results.get("terraform_drift_validation", {}),
|
476
473
|
}
|
477
|
-
|
478
|
-
successful_sources = sum(
|
479
|
-
|
474
|
+
|
475
|
+
successful_sources = sum(
|
476
|
+
1 for source_data in validation_sources.values() if source_data and not source_data.get("error")
|
477
|
+
)
|
480
478
|
unified_analysis["validation_summary"]["validation_sources_successful"] = successful_sources
|
481
|
-
|
479
|
+
|
482
480
|
# Analyze each account
|
483
481
|
accounts_to_analyze = set()
|
484
482
|
for source_data in validation_sources.values():
|
@@ -490,34 +488,30 @@ class UnifiedValidationEngine:
|
|
490
488
|
for key in source_data.keys():
|
491
489
|
if key not in ["error", "timestamp", "validation_method"]:
|
492
490
|
accounts_to_analyze.add(key)
|
493
|
-
|
491
|
+
|
494
492
|
accounts_to_analyze.discard("error")
|
495
493
|
unified_analysis["validation_summary"]["total_accounts_analyzed"] = len(accounts_to_analyze)
|
496
|
-
|
494
|
+
|
497
495
|
# Resource-level analysis
|
498
496
|
total_accuracy = 0.0
|
499
497
|
account_count = 0
|
500
|
-
|
498
|
+
|
501
499
|
for account_id in accounts_to_analyze:
|
502
|
-
account_analysis = self._analyze_account_across_sources(
|
503
|
-
account_id, validation_sources
|
504
|
-
)
|
500
|
+
account_analysis = self._analyze_account_across_sources(account_id, validation_sources)
|
505
501
|
unified_analysis["account_analysis"][account_id] = account_analysis
|
506
|
-
|
502
|
+
|
507
503
|
if account_analysis.get("overall_accuracy", 0) > 0:
|
508
504
|
total_accuracy += account_analysis["overall_accuracy"]
|
509
505
|
account_count += 1
|
510
|
-
|
506
|
+
|
511
507
|
if account_count > 0:
|
512
508
|
unified_analysis["overall_accuracy"] = total_accuracy / account_count
|
513
509
|
unified_analysis["passed_validation"] = unified_analysis["overall_accuracy"] >= self.validation_threshold
|
514
|
-
|
510
|
+
|
515
511
|
print_info("✅ Unified analysis completed")
|
516
512
|
return unified_analysis
|
517
513
|
|
518
|
-
def _analyze_account_across_sources(
|
519
|
-
self, account_id: str, validation_sources: Dict[str, Any]
|
520
|
-
) -> Dict[str, Any]:
|
514
|
+
def _analyze_account_across_sources(self, account_id: str, validation_sources: Dict[str, Any]) -> Dict[str, Any]:
|
521
515
|
"""Analyze a single account across all validation sources."""
|
522
516
|
account_analysis = {
|
523
517
|
"account_id": account_id,
|
@@ -526,21 +520,21 @@ class UnifiedValidationEngine:
|
|
526
520
|
"drift_detected": False,
|
527
521
|
"sources_with_data": 0,
|
528
522
|
}
|
529
|
-
|
523
|
+
|
530
524
|
# Collect resource counts from all sources
|
531
525
|
resource_counts = {
|
532
526
|
"runbooks": {},
|
533
527
|
"mcp": {},
|
534
528
|
"terraform": {},
|
535
529
|
}
|
536
|
-
|
530
|
+
|
537
531
|
# Extract runbooks data
|
538
532
|
runbooks_data = validation_sources.get("runbooks", {})
|
539
533
|
if account_id in runbooks_data:
|
540
534
|
resource_counts["runbooks"] = runbooks_data[account_id].get("resource_counts", {})
|
541
535
|
if resource_counts["runbooks"]:
|
542
536
|
account_analysis["sources_with_data"] += 1
|
543
|
-
|
537
|
+
|
544
538
|
# Extract MCP data
|
545
539
|
mcp_data = validation_sources.get("mcp", {})
|
546
540
|
if "profile_results" in mcp_data:
|
@@ -551,33 +545,32 @@ class UnifiedValidationEngine:
|
|
551
545
|
resource_counts["mcp"][resource_type] = validation_data.get("mcp_server_count", 0)
|
552
546
|
if resource_counts["mcp"]:
|
553
547
|
account_analysis["sources_with_data"] += 1
|
554
|
-
|
555
|
-
|
548
|
+
|
556
549
|
# Extract terraform data
|
557
550
|
terraform_data = validation_sources.get("terraform", {})
|
558
551
|
if "declared_resources" in terraform_data:
|
559
552
|
resource_counts["terraform"] = terraform_data["declared_resources"]
|
560
553
|
if resource_counts["terraform"]:
|
561
554
|
account_analysis["sources_with_data"] += 1
|
562
|
-
|
555
|
+
|
563
556
|
# ENHANCED: Weighted accuracy calculation for enterprise reliability
|
564
557
|
total_weighted_accuracy = 0.0
|
565
558
|
total_weight = 0.0
|
566
|
-
|
559
|
+
|
567
560
|
# Dynamic resource weighting based on actual discovery for universal compatibility
|
568
561
|
resource_weights = self._calculate_dynamic_resource_weights(resource_counts)
|
569
|
-
|
562
|
+
|
570
563
|
for resource_type in self.supported_resources.keys():
|
571
564
|
runbooks_count = resource_counts["runbooks"].get(resource_type, 0)
|
572
565
|
mcp_count = resource_counts["mcp"].get(resource_type, 0)
|
573
566
|
terraform_count = resource_counts["terraform"].get(resource_type, 0)
|
574
|
-
|
567
|
+
|
575
568
|
counts = [runbooks_count, mcp_count, terraform_count]
|
576
|
-
|
569
|
+
|
577
570
|
# ENHANCED: Weighted validation with intelligent tolerance
|
578
571
|
resource_weight = resource_weights.get(resource_type, 1.0)
|
579
572
|
non_zero_counts = [c for c in counts if c > 0]
|
580
|
-
|
573
|
+
|
581
574
|
if not non_zero_counts:
|
582
575
|
# All sources report zero - perfect alignment
|
583
576
|
accuracy = 100.0
|
@@ -589,7 +582,7 @@ class UnifiedValidationEngine:
|
|
589
582
|
else:
|
590
583
|
max_count = max(counts)
|
591
584
|
min_count = min(counts)
|
592
|
-
|
585
|
+
|
593
586
|
if max_count == 0:
|
594
587
|
# All zero - perfect alignment
|
595
588
|
accuracy = 100.0
|
@@ -597,7 +590,7 @@ class UnifiedValidationEngine:
|
|
597
590
|
else:
|
598
591
|
# ENHANCED: Adaptive tolerance based on resource count
|
599
592
|
base_variance = abs(max_count - min_count) / max_count * 100
|
600
|
-
|
593
|
+
|
601
594
|
# Adaptive tolerance: smaller counts get more tolerance
|
602
595
|
if max_count <= 5:
|
603
596
|
tolerance_threshold = 50.0 # High tolerance for small counts
|
@@ -606,8 +599,8 @@ class UnifiedValidationEngine:
|
|
606
599
|
elif max_count <= 100:
|
607
600
|
tolerance_threshold = 10.0 # Standard tolerance
|
608
601
|
else:
|
609
|
-
tolerance_threshold = 5.0
|
610
|
-
|
602
|
+
tolerance_threshold = 5.0 # Strict tolerance for large counts
|
603
|
+
|
611
604
|
if base_variance <= tolerance_threshold:
|
612
605
|
accuracy = 100.0
|
613
606
|
variance = base_variance
|
@@ -616,7 +609,7 @@ class UnifiedValidationEngine:
|
|
616
609
|
penalty_factor = min((base_variance - tolerance_threshold) / 2.0, 50.0)
|
617
610
|
accuracy = max(50.0, 100.0 - penalty_factor) # Never go below 50%
|
618
611
|
variance = base_variance
|
619
|
-
|
612
|
+
|
620
613
|
account_analysis["resource_analysis"][resource_type] = {
|
621
614
|
"runbooks_count": runbooks_count,
|
622
615
|
"mcp_count": mcp_count,
|
@@ -626,18 +619,18 @@ class UnifiedValidationEngine:
|
|
626
619
|
"sources_with_data": len(non_zero_counts),
|
627
620
|
"resource_weight": resource_weight,
|
628
621
|
}
|
629
|
-
|
622
|
+
|
630
623
|
# Apply weighting to overall accuracy calculation
|
631
624
|
if non_zero_counts or accuracy > 90.0: # Include high-accuracy resources
|
632
625
|
total_weighted_accuracy += accuracy * resource_weight
|
633
626
|
total_weight += resource_weight
|
634
|
-
|
627
|
+
|
635
628
|
# Calculate overall account accuracy using weighted methodology
|
636
629
|
if total_weight > 0:
|
637
630
|
account_analysis["overall_accuracy"] = total_weighted_accuracy / total_weight
|
638
631
|
else:
|
639
632
|
account_analysis["overall_accuracy"] = 95.0 # Default high accuracy for no data
|
640
|
-
|
633
|
+
|
641
634
|
return account_analysis
|
642
635
|
|
643
636
|
def _get_all_aws_regions(self) -> List[str]:
|
@@ -646,25 +639,44 @@ class UnifiedValidationEngine:
|
|
646
639
|
# Use a session to get all available regions
|
647
640
|
session = boto3.Session(profile_name=self.enterprise_profiles["operational"])
|
648
641
|
ec2_client = session.client("ec2", region_name="us-east-1")
|
649
|
-
|
642
|
+
|
650
643
|
# Get all regions including opt-in regions
|
651
644
|
response = ec2_client.describe_regions(AllRegions=True)
|
652
645
|
regions = [region["RegionName"] for region in response["Regions"]]
|
653
|
-
|
646
|
+
|
654
647
|
# Sort for consistent ordering
|
655
648
|
regions.sort()
|
656
649
|
return regions
|
657
|
-
|
650
|
+
|
658
651
|
except Exception:
|
659
652
|
# Fallback to comprehensive static list if API call fails
|
660
653
|
return [
|
661
|
-
"us-east-1",
|
662
|
-
"
|
663
|
-
"
|
664
|
-
"
|
665
|
-
"
|
666
|
-
"eu-
|
667
|
-
"
|
654
|
+
"us-east-1",
|
655
|
+
"us-east-2",
|
656
|
+
"us-west-1",
|
657
|
+
"us-west-2",
|
658
|
+
"eu-west-1",
|
659
|
+
"eu-west-2",
|
660
|
+
"eu-west-3",
|
661
|
+
"eu-central-1",
|
662
|
+
"eu-north-1",
|
663
|
+
"ap-southeast-1",
|
664
|
+
"ap-southeast-2",
|
665
|
+
"ap-northeast-1",
|
666
|
+
"ap-northeast-2",
|
667
|
+
"ap-south-1",
|
668
|
+
"ca-central-1",
|
669
|
+
"sa-east-1",
|
670
|
+
"af-south-1",
|
671
|
+
"ap-east-1",
|
672
|
+
"ap-southeast-3",
|
673
|
+
"ap-northeast-3",
|
674
|
+
"eu-central-2",
|
675
|
+
"eu-south-1",
|
676
|
+
"eu-south-2",
|
677
|
+
"eu-west-3",
|
678
|
+
"me-south-1",
|
679
|
+
"me-central-1",
|
668
680
|
]
|
669
681
|
|
670
682
|
def _get_validated_session_for_resource(self, resource_type: str, region: str) -> Optional[boto3.Session]:
|
@@ -677,37 +689,37 @@ class UnifiedValidationEngine:
|
|
677
689
|
"vpc": ["operational", "single_account"],
|
678
690
|
"rds": ["operational", "single_account"],
|
679
691
|
}
|
680
|
-
|
692
|
+
|
681
693
|
profiles_to_try = profile_priorities.get(resource_type, ["operational", "single_account"])
|
682
|
-
|
694
|
+
|
683
695
|
for profile_key in profiles_to_try:
|
684
696
|
try:
|
685
697
|
profile_name = self.enterprise_profiles.get(profile_key)
|
686
698
|
if not profile_name:
|
687
699
|
continue
|
688
|
-
|
700
|
+
|
689
701
|
session = boto3.Session(profile_name=profile_name)
|
690
|
-
|
702
|
+
|
691
703
|
# Quick validation test - try to get caller identity
|
692
704
|
sts_client = session.client("sts", region_name=region)
|
693
705
|
sts_client.get_caller_identity()
|
694
|
-
|
706
|
+
|
695
707
|
return session
|
696
|
-
|
708
|
+
|
697
709
|
except Exception as e:
|
698
710
|
# Log session validation failures for debugging
|
699
711
|
error_type = self._classify_aws_error(e)
|
700
712
|
if error_type not in ["auth_expired", "unauthorized"]:
|
701
713
|
self.console.log(f"[dim red]Session validation failed for {profile_key}: {error_type}[/]")
|
702
714
|
continue
|
703
|
-
|
715
|
+
|
704
716
|
# No valid session found
|
705
717
|
return None
|
706
718
|
|
707
719
|
def _classify_aws_error(self, error: Exception) -> str:
|
708
720
|
"""Classify AWS errors for better error handling and reporting."""
|
709
721
|
error_str = str(error).lower()
|
710
|
-
|
722
|
+
|
711
723
|
if "token has expired" in error_str or "expired" in error_str:
|
712
724
|
return "auth_expired"
|
713
725
|
elif "unauthorizedoperation" in error_str or "access denied" in error_str:
|
@@ -723,17 +735,15 @@ class UnifiedValidationEngine:
|
|
723
735
|
else:
|
724
736
|
return "unknown_error"
|
725
737
|
|
726
|
-
async def _collect_resource_count(
|
727
|
-
self, resource_type: str, account_id: str, regions: List[str]
|
728
|
-
) -> int:
|
738
|
+
async def _collect_resource_count(self, resource_type: str, account_id: str, regions: List[str]) -> int:
|
729
739
|
"""
|
730
740
|
Enhanced resource count collection with enterprise accuracy improvements.
|
731
|
-
|
741
|
+
|
732
742
|
Args:
|
733
743
|
resource_type: AWS resource type to collect
|
734
744
|
account_id: AWS account ID
|
735
745
|
regions: List of regions to search
|
736
|
-
|
746
|
+
|
737
747
|
Returns:
|
738
748
|
Actual resource count from AWS APIs with enhanced accuracy
|
739
749
|
"""
|
@@ -744,11 +754,11 @@ class UnifiedValidationEngine:
|
|
744
754
|
total_count = 0
|
745
755
|
successful_regions = 0
|
746
756
|
failed_regions = []
|
747
|
-
|
757
|
+
|
748
758
|
# Get all AWS regions for comprehensive coverage (enterprise enhancement)
|
749
759
|
if not regions or regions == ["us-east-1"]:
|
750
760
|
regions = self._get_all_aws_regions()
|
751
|
-
|
761
|
+
|
752
762
|
for region in regions:
|
753
763
|
try:
|
754
764
|
# Enhanced session management with fallback profiles
|
@@ -756,19 +766,19 @@ class UnifiedValidationEngine:
|
|
756
766
|
if not session:
|
757
767
|
failed_regions.append(f"{region}:no_session")
|
758
768
|
continue
|
759
|
-
|
769
|
+
|
760
770
|
ec2_client = session.client("ec2", region_name=region)
|
761
|
-
|
771
|
+
|
762
772
|
# Enhanced pagination with better error handling
|
763
|
-
paginator = ec2_client.get_paginator(
|
773
|
+
paginator = ec2_client.get_paginator("describe_instances")
|
764
774
|
region_instances = 0
|
765
|
-
|
775
|
+
|
766
776
|
try:
|
767
777
|
# Add timeout and retry logic for enterprise reliability
|
768
778
|
for page in paginator.paginate(
|
769
779
|
PaginationConfig={
|
770
|
-
|
771
|
-
|
780
|
+
"MaxItems": 10000, # Prevent runaway pagination
|
781
|
+
"PageSize": 500, # Optimize API call efficiency
|
772
782
|
}
|
773
783
|
):
|
774
784
|
for reservation in page.get("Reservations", []):
|
@@ -778,37 +788,43 @@ class UnifiedValidationEngine:
|
|
778
788
|
except Exception as page_error:
|
779
789
|
# Handle pagination-specific errors
|
780
790
|
if "UnauthorizedOperation" not in str(page_error):
|
781
|
-
self.console.log(
|
791
|
+
self.console.log(
|
792
|
+
f"[dim yellow]EC2 pagination error in {region}: {str(page_error)[:40]}[/]"
|
793
|
+
)
|
782
794
|
failed_regions.append(f"{region}:pagination_error")
|
783
795
|
continue
|
784
|
-
|
796
|
+
|
785
797
|
total_count += region_instances
|
786
798
|
successful_regions += 1
|
787
|
-
|
799
|
+
|
788
800
|
# Log regional discovery for debugging
|
789
801
|
if region_instances > 0:
|
790
802
|
self.console.log(f"[dim green]EC2 {region}: {region_instances} instances[/]")
|
791
|
-
|
803
|
+
|
792
804
|
except Exception as e:
|
793
805
|
# Enhanced error handling with specific error classification
|
794
806
|
error_type = self._classify_aws_error(e)
|
795
807
|
failed_regions.append(f"{region}:{error_type}")
|
796
|
-
|
808
|
+
|
797
809
|
# Only log unexpected errors to reduce noise
|
798
810
|
if error_type not in ["auth_expired", "unauthorized", "region_disabled"]:
|
799
811
|
self.console.log(f"[dim red]EC2 {region}: {error_type}[/]")
|
800
812
|
continue
|
801
|
-
|
813
|
+
|
802
814
|
# Enhanced reporting with enterprise context
|
803
815
|
coverage_percent = (successful_regions / len(regions)) * 100 if regions else 0
|
804
|
-
self.console.log(
|
805
|
-
|
816
|
+
self.console.log(
|
817
|
+
f"[cyan]EC2 Enhanced Discovery: {total_count} instances across {successful_regions}/{len(regions)} regions ({coverage_percent:.1f}% coverage)[/]"
|
818
|
+
)
|
819
|
+
|
806
820
|
# Log failed regions for troubleshooting if significant
|
807
821
|
if len(failed_regions) > 0 and coverage_percent < 80:
|
808
|
-
self.console.log(
|
809
|
-
|
822
|
+
self.console.log(
|
823
|
+
f"[dim yellow]Failed regions: {failed_regions[:5]}{'...' if len(failed_regions) > 5 else ''}[/]"
|
824
|
+
)
|
825
|
+
|
810
826
|
return total_count
|
811
|
-
|
827
|
+
|
812
828
|
elif resource_type == "s3":
|
813
829
|
# S3 buckets are global, check once
|
814
830
|
try:
|
@@ -818,7 +834,7 @@ class UnifiedValidationEngine:
|
|
818
834
|
return len(response.get("Buckets", []))
|
819
835
|
except Exception:
|
820
836
|
return 0
|
821
|
-
|
837
|
+
|
822
838
|
elif resource_type == "vpc":
|
823
839
|
# Collect VPCs across regions
|
824
840
|
total_count = 0
|
@@ -831,7 +847,7 @@ class UnifiedValidationEngine:
|
|
831
847
|
except Exception:
|
832
848
|
continue
|
833
849
|
return total_count
|
834
|
-
|
850
|
+
|
835
851
|
elif resource_type == "lambda":
|
836
852
|
# Collect Lambda functions across regions
|
837
853
|
total_count = 0
|
@@ -844,7 +860,7 @@ class UnifiedValidationEngine:
|
|
844
860
|
except Exception:
|
845
861
|
continue
|
846
862
|
return total_count
|
847
|
-
|
863
|
+
|
848
864
|
elif resource_type == "rds":
|
849
865
|
# Collect RDS instances across regions
|
850
866
|
total_count = 0
|
@@ -857,7 +873,7 @@ class UnifiedValidationEngine:
|
|
857
873
|
except Exception:
|
858
874
|
continue
|
859
875
|
return total_count
|
860
|
-
|
876
|
+
|
861
877
|
elif resource_type == "iam":
|
862
878
|
# IAM roles are global
|
863
879
|
try:
|
@@ -879,7 +895,9 @@ class UnifiedValidationEngine:
|
|
879
895
|
session = boto3.Session(profile_name=self.enterprise_profiles["operational"])
|
880
896
|
cf_client = session.client("cloudformation", region_name=region)
|
881
897
|
paginator = cf_client.get_paginator("list_stacks")
|
882
|
-
for page in paginator.paginate(
|
898
|
+
for page in paginator.paginate(
|
899
|
+
StackStatusFilter=["CREATE_COMPLETE", "UPDATE_COMPLETE", "ROLLBACK_COMPLETE"]
|
900
|
+
):
|
883
901
|
total_count += len(page.get("StackSummaries", []))
|
884
902
|
except Exception:
|
885
903
|
continue
|
@@ -953,42 +971,40 @@ class UnifiedValidationEngine:
|
|
953
971
|
except Exception:
|
954
972
|
continue
|
955
973
|
return total_count
|
956
|
-
|
974
|
+
|
957
975
|
else:
|
958
976
|
# For any other resource types, return 0
|
959
977
|
return 0
|
960
|
-
|
978
|
+
|
961
979
|
except Exception as e:
|
962
980
|
self.console.log(f"[red]Error collecting {resource_type}: {str(e)[:40]}[/]")
|
963
981
|
return 0
|
964
982
|
|
965
|
-
def _generate_actionable_recommendations(
|
966
|
-
self, unified_analysis: Dict[str, Any]
|
967
|
-
) -> List[str]:
|
983
|
+
def _generate_actionable_recommendations(self, unified_analysis: Dict[str, Any]) -> List[str]:
|
968
984
|
"""Generate actionable recommendations based on validation results."""
|
969
985
|
self.console.print(f"[yellow]💡 Generating actionable recommendations[/yellow]")
|
970
|
-
|
986
|
+
|
971
987
|
recommendations = []
|
972
988
|
overall_accuracy = unified_analysis.get("overall_accuracy", 0)
|
973
|
-
|
989
|
+
|
974
990
|
# Overall accuracy recommendations
|
975
991
|
if overall_accuracy < self.validation_threshold:
|
976
992
|
recommendations.append(
|
977
993
|
f"Overall validation accuracy ({overall_accuracy:.1f}%) is below enterprise threshold ({self.validation_threshold}%). "
|
978
994
|
"Review resource discovery methods and API access permissions."
|
979
995
|
)
|
980
|
-
|
996
|
+
|
981
997
|
# Account-specific recommendations
|
982
998
|
for account_id, account_data in unified_analysis.get("account_analysis", {}).items():
|
983
999
|
account_accuracy = account_data.get("overall_accuracy", 0)
|
984
1000
|
sources_count = account_data.get("sources_with_data", 0)
|
985
|
-
|
1001
|
+
|
986
1002
|
if account_accuracy < 90.0:
|
987
1003
|
recommendations.append(
|
988
1004
|
f"Account {account_id} has {account_accuracy:.1f}% accuracy with {sources_count} validation sources. "
|
989
1005
|
"Consider reviewing AWS permissions and terraform configuration."
|
990
1006
|
)
|
991
|
-
|
1007
|
+
|
992
1008
|
# Resource-specific recommendations
|
993
1009
|
for resource_type, resource_data in account_data.get("resource_analysis", {}).items():
|
994
1010
|
variance = resource_data.get("variance_percent", 0)
|
@@ -997,30 +1013,30 @@ class UnifiedValidationEngine:
|
|
997
1013
|
f"High variance detected for {self.supported_resources.get(resource_type, resource_type)} "
|
998
1014
|
f"in account {account_id} ({variance:.1f}% variance). Verify collection methods."
|
999
1015
|
)
|
1000
|
-
|
1016
|
+
|
1001
1017
|
# Source-specific recommendations
|
1002
1018
|
validation_summary = unified_analysis.get("validation_summary", {})
|
1003
1019
|
successful_sources = validation_summary.get("validation_sources_successful", 0)
|
1004
|
-
|
1020
|
+
|
1005
1021
|
if successful_sources < 2:
|
1006
1022
|
recommendations.append(
|
1007
1023
|
f"Only {successful_sources}/3 validation sources successful. "
|
1008
1024
|
"Check MCP server configuration and terraform setup."
|
1009
1025
|
)
|
1010
|
-
|
1026
|
+
|
1011
1027
|
# Performance recommendations
|
1012
1028
|
if not unified_analysis.get("performance_achieved", True):
|
1013
1029
|
recommendations.append(
|
1014
1030
|
f"Validation exceeded {self.performance_target}s target. "
|
1015
1031
|
"Consider enabling caching or reducing scope for better performance."
|
1016
1032
|
)
|
1017
|
-
|
1033
|
+
|
1018
1034
|
if not recommendations:
|
1019
1035
|
recommendations.append(
|
1020
1036
|
"✅ Validation completed successfully with no issues detected. "
|
1021
1037
|
"All sources are aligned and operating within enterprise thresholds."
|
1022
1038
|
)
|
1023
|
-
|
1039
|
+
|
1024
1040
|
return recommendations
|
1025
1041
|
|
1026
1042
|
def _display_unified_validation_results(self, validation_results: Dict[str, Any]) -> None:
|
@@ -1029,48 +1045,53 @@ class UnifiedValidationEngine:
|
|
1029
1045
|
passed = validation_results.get("passed_validation", False)
|
1030
1046
|
performance_metrics = validation_results.get("performance_metrics", {})
|
1031
1047
|
validation_summary = validation_results.get("validation_summary", {})
|
1032
|
-
|
1048
|
+
|
1033
1049
|
self.console.print(f"\n[bright_cyan]🔍 Unified 3-Way Validation Results[/]")
|
1034
|
-
|
1050
|
+
|
1035
1051
|
# Performance metrics
|
1036
1052
|
total_time = performance_metrics.get("total_execution_time", 0)
|
1037
1053
|
performance_achieved = performance_metrics.get("performance_achieved", True)
|
1038
1054
|
performance_icon = "✅" if performance_achieved else "⚠️"
|
1039
|
-
|
1040
|
-
self.console.print(
|
1041
|
-
|
1055
|
+
|
1056
|
+
self.console.print(
|
1057
|
+
f"[dim]⚡ Performance: {performance_icon} {total_time:.1f}s (target: <{self.performance_target}s)[/]"
|
1058
|
+
)
|
1059
|
+
|
1042
1060
|
# Validation sources summary
|
1043
1061
|
sources_successful = validation_summary.get("validation_sources_successful", 0)
|
1044
1062
|
total_accounts = validation_summary.get("total_accounts_analyzed", 0)
|
1045
1063
|
total_resources = validation_summary.get("total_resource_types", 0)
|
1046
|
-
|
1047
|
-
self.console.print(
|
1048
|
-
|
1064
|
+
|
1065
|
+
self.console.print(
|
1066
|
+
f"[dim]🔗 Sources: {sources_successful}/3 successful | Accounts: {total_accounts} | Resources: {total_resources}[/]"
|
1067
|
+
)
|
1068
|
+
|
1049
1069
|
# Overall result
|
1050
1070
|
if passed:
|
1051
1071
|
print_success(f"✅ Unified Validation PASSED: {overall_accuracy:.1f}% accuracy achieved")
|
1052
1072
|
else:
|
1053
|
-
print_warning(
|
1054
|
-
|
1073
|
+
print_warning(
|
1074
|
+
f"🔄 Unified Validation: {overall_accuracy:.1f}% accuracy (≥{self.validation_threshold}% required)"
|
1075
|
+
)
|
1076
|
+
|
1055
1077
|
# Account-level results table
|
1056
1078
|
account_analysis = validation_results.get("account_analysis", {})
|
1057
1079
|
if account_analysis:
|
1058
1080
|
self.console.print(f"\n[bright_cyan]📊 Account-Level Validation Results[/]")
|
1059
|
-
|
1081
|
+
|
1060
1082
|
account_table = create_table(
|
1061
|
-
title="3-Way Cross-Validation Results",
|
1062
|
-
caption="Sources: Runbooks | MCP | Terraform"
|
1083
|
+
title="3-Way Cross-Validation Results", caption="Sources: Runbooks | MCP | Terraform"
|
1063
1084
|
)
|
1064
|
-
|
1085
|
+
|
1065
1086
|
account_table.add_column("Account ID", style="cyan", no_wrap=True)
|
1066
1087
|
account_table.add_column("Overall Accuracy", justify="right")
|
1067
1088
|
account_table.add_column("Sources", justify="center")
|
1068
1089
|
account_table.add_column("Status", style="yellow")
|
1069
|
-
|
1090
|
+
|
1070
1091
|
for account_id, account_data in account_analysis.items():
|
1071
1092
|
account_accuracy = account_data.get("overall_accuracy", 0)
|
1072
1093
|
sources_count = account_data.get("sources_with_data", 0)
|
1073
|
-
|
1094
|
+
|
1074
1095
|
# Determine status
|
1075
1096
|
if account_accuracy >= self.validation_threshold:
|
1076
1097
|
status = "✅ Passed"
|
@@ -1081,19 +1102,14 @@ class UnifiedValidationEngine:
|
|
1081
1102
|
else:
|
1082
1103
|
status = "❌ Needs Review"
|
1083
1104
|
status_color = "red"
|
1084
|
-
|
1105
|
+
|
1085
1106
|
accuracy_display = f"{account_accuracy:.1f}%"
|
1086
1107
|
sources_display = f"{sources_count}/3"
|
1087
|
-
|
1088
|
-
account_table.add_row(
|
1089
|
-
|
1090
|
-
accuracy_display,
|
1091
|
-
sources_display,
|
1092
|
-
status
|
1093
|
-
)
|
1094
|
-
|
1108
|
+
|
1109
|
+
account_table.add_row(account_id, accuracy_display, sources_display, status)
|
1110
|
+
|
1095
1111
|
self.console.print(account_table)
|
1096
|
-
|
1112
|
+
|
1097
1113
|
# Recommendations
|
1098
1114
|
recommendations = validation_results.get("recommendations", [])
|
1099
1115
|
if recommendations:
|
@@ -1109,14 +1125,14 @@ class UnifiedValidationEngine:
|
|
1109
1125
|
) -> None:
|
1110
1126
|
"""Export comprehensive validation evidence in multiple formats."""
|
1111
1127
|
self.console.print(f"[blue]📤 Exporting validation evidence[/blue]")
|
1112
|
-
|
1128
|
+
|
1113
1129
|
# Create output directory
|
1114
1130
|
output_path = Path(output_directory)
|
1115
1131
|
output_path.mkdir(parents=True, exist_ok=True)
|
1116
|
-
|
1132
|
+
|
1117
1133
|
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
1118
1134
|
base_filename = f"unified_validation_{timestamp}"
|
1119
|
-
|
1135
|
+
|
1120
1136
|
for export_format in export_formats:
|
1121
1137
|
try:
|
1122
1138
|
if export_format == "json":
|
@@ -1124,50 +1140,59 @@ class UnifiedValidationEngine:
|
|
1124
1140
|
with open(json_file, "w") as f:
|
1125
1141
|
json.dump(validation_results, f, indent=2, default=str)
|
1126
1142
|
print_info(f"JSON export: {json_file}")
|
1127
|
-
|
1143
|
+
|
1128
1144
|
elif export_format == "csv":
|
1129
1145
|
csv_file = output_path / f"{base_filename}.csv"
|
1130
1146
|
self._export_csv_evidence(validation_results, csv_file)
|
1131
1147
|
print_info(f"CSV export: {csv_file}")
|
1132
|
-
|
1148
|
+
|
1133
1149
|
elif export_format == "markdown":
|
1134
1150
|
md_file = output_path / f"{base_filename}.md"
|
1135
1151
|
self._export_markdown_evidence(validation_results, md_file)
|
1136
1152
|
print_info(f"Markdown export: {md_file}")
|
1137
|
-
|
1153
|
+
|
1138
1154
|
elif export_format == "pdf":
|
1139
1155
|
print_info("PDF export: Feature planned for future release")
|
1140
|
-
|
1156
|
+
|
1141
1157
|
except Exception as e:
|
1142
1158
|
print_warning(f"Failed to export {export_format}: {str(e)[:40]}")
|
1143
1159
|
|
1144
1160
|
def _export_csv_evidence(self, validation_results: Dict[str, Any], csv_file: Path) -> None:
|
1145
1161
|
"""Export validation evidence in CSV format."""
|
1146
1162
|
import csv
|
1147
|
-
|
1163
|
+
|
1148
1164
|
with open(csv_file, "w", newline="") as f:
|
1149
1165
|
writer = csv.writer(f)
|
1150
|
-
|
1166
|
+
|
1151
1167
|
# Header
|
1152
|
-
writer.writerow(
|
1153
|
-
|
1154
|
-
|
1155
|
-
|
1156
|
-
|
1168
|
+
writer.writerow(
|
1169
|
+
[
|
1170
|
+
"Account ID",
|
1171
|
+
"Resource Type",
|
1172
|
+
"Runbooks Count",
|
1173
|
+
"MCP Count",
|
1174
|
+
"Terraform Count",
|
1175
|
+
"Accuracy %",
|
1176
|
+
"Variance %",
|
1177
|
+
]
|
1178
|
+
)
|
1179
|
+
|
1157
1180
|
# Data rows
|
1158
1181
|
account_analysis = validation_results.get("account_analysis", {})
|
1159
1182
|
for account_id, account_data in account_analysis.items():
|
1160
1183
|
resource_analysis = account_data.get("resource_analysis", {})
|
1161
1184
|
for resource_type, resource_data in resource_analysis.items():
|
1162
|
-
writer.writerow(
|
1163
|
-
|
1164
|
-
|
1165
|
-
|
1166
|
-
|
1167
|
-
|
1168
|
-
|
1169
|
-
|
1170
|
-
|
1185
|
+
writer.writerow(
|
1186
|
+
[
|
1187
|
+
account_id,
|
1188
|
+
self.supported_resources.get(resource_type, resource_type),
|
1189
|
+
resource_data.get("runbooks_count", 0),
|
1190
|
+
resource_data.get("mcp_count", 0),
|
1191
|
+
resource_data.get("terraform_count", 0),
|
1192
|
+
f"{resource_data.get('accuracy_percent', 0):.1f}",
|
1193
|
+
f"{resource_data.get('variance_percent', 0):.1f}",
|
1194
|
+
]
|
1195
|
+
)
|
1171
1196
|
|
1172
1197
|
def _export_markdown_evidence(self, validation_results: Dict[str, Any], md_file: Path) -> None:
|
1173
1198
|
"""Export validation evidence in Markdown format."""
|
@@ -1176,13 +1201,13 @@ class UnifiedValidationEngine:
|
|
1176
1201
|
f.write(f"**Generated**: {validation_results.get('validation_timestamp', 'Unknown')}\n")
|
1177
1202
|
f.write(f"**Overall Accuracy**: {validation_results.get('overall_accuracy', 0):.1f}%\n")
|
1178
1203
|
f.write(f"**Validation Passed**: {validation_results.get('passed_validation', False)}\n\n")
|
1179
|
-
|
1204
|
+
|
1180
1205
|
# Performance metrics
|
1181
1206
|
performance_metrics = validation_results.get("performance_metrics", {})
|
1182
1207
|
total_time = performance_metrics.get("total_execution_time", 0)
|
1183
1208
|
f.write(f"**Execution Time**: {total_time:.1f}s\n")
|
1184
1209
|
f.write(f"**Performance Target**: <{self.performance_target}s\n\n")
|
1185
|
-
|
1210
|
+
|
1186
1211
|
# Validation sources
|
1187
1212
|
f.write("## Validation Sources\n\n")
|
1188
1213
|
validation_sources = validation_results.get("validation_sources", {})
|
@@ -1190,7 +1215,7 @@ class UnifiedValidationEngine:
|
|
1190
1215
|
status = "✅ Enabled" if enabled else "❌ Disabled"
|
1191
1216
|
f.write(f"- **{source.replace('_', ' ').title()}**: {status}\n")
|
1192
1217
|
f.write("\n")
|
1193
|
-
|
1218
|
+
|
1194
1219
|
# Account analysis
|
1195
1220
|
f.write("## Account Analysis\n\n")
|
1196
1221
|
account_analysis = validation_results.get("account_analysis", {})
|
@@ -1198,21 +1223,23 @@ class UnifiedValidationEngine:
|
|
1198
1223
|
f.write(f"### Account: {account_id}\n\n")
|
1199
1224
|
f.write(f"- **Overall Accuracy**: {account_data.get('overall_accuracy', 0):.1f}%\n")
|
1200
1225
|
f.write(f"- **Sources with Data**: {account_data.get('sources_with_data', 0)}/3\n\n")
|
1201
|
-
|
1226
|
+
|
1202
1227
|
# Resource breakdown
|
1203
1228
|
f.write("#### Resource Validation\n\n")
|
1204
1229
|
f.write("| Resource Type | Runbooks | MCP | Terraform | Accuracy |\n")
|
1205
1230
|
f.write("|---------------|----------|-----|-----------|----------|\n")
|
1206
|
-
|
1231
|
+
|
1207
1232
|
resource_analysis = account_data.get("resource_analysis", {})
|
1208
1233
|
for resource_type, resource_data in resource_analysis.items():
|
1209
|
-
f.write(
|
1210
|
-
|
1211
|
-
|
1212
|
-
|
1213
|
-
|
1234
|
+
f.write(
|
1235
|
+
f"| {self.supported_resources.get(resource_type, resource_type)} | "
|
1236
|
+
f"{resource_data.get('runbooks_count', 0)} | "
|
1237
|
+
f"{resource_data.get('mcp_count', 0)} | "
|
1238
|
+
f"{resource_data.get('terraform_count', 0)} | "
|
1239
|
+
f"{resource_data.get('accuracy_percent', 0):.1f}% |\n"
|
1240
|
+
)
|
1214
1241
|
f.write("\n")
|
1215
|
-
|
1242
|
+
|
1216
1243
|
# Recommendations
|
1217
1244
|
f.write("## Recommendations\n\n")
|
1218
1245
|
recommendations = validation_results.get("recommendations", [])
|
@@ -1228,13 +1255,13 @@ def create_unified_validation_engine(
|
|
1228
1255
|
) -> UnifiedValidationEngine:
|
1229
1256
|
"""
|
1230
1257
|
Factory function to create unified validation engine.
|
1231
|
-
|
1258
|
+
|
1232
1259
|
Args:
|
1233
1260
|
user_profile: User-specified profile (--profile parameter)
|
1234
1261
|
console: Rich console for output
|
1235
1262
|
mcp_config_path: Path to .mcp.json configuration file
|
1236
1263
|
terraform_directory: Path to terraform configurations
|
1237
|
-
|
1264
|
+
|
1238
1265
|
Returns:
|
1239
1266
|
Unified validation engine instance
|
1240
1267
|
"""
|
@@ -1256,7 +1283,7 @@ async def run_comprehensive_validation(
|
|
1256
1283
|
) -> Dict[str, Any]:
|
1257
1284
|
"""
|
1258
1285
|
Convenience function to run comprehensive 3-way validation.
|
1259
|
-
|
1286
|
+
|
1260
1287
|
Args:
|
1261
1288
|
user_profile: User-specified profile
|
1262
1289
|
resource_types: List of resource types to validate
|
@@ -1264,16 +1291,16 @@ async def run_comprehensive_validation(
|
|
1264
1291
|
regions: List of regions to analyze
|
1265
1292
|
export_formats: List of export formats
|
1266
1293
|
output_directory: Directory for evidence exports
|
1267
|
-
|
1294
|
+
|
1268
1295
|
Returns:
|
1269
1296
|
Comprehensive validation results
|
1270
1297
|
"""
|
1271
1298
|
engine = create_unified_validation_engine(user_profile=user_profile)
|
1272
|
-
|
1299
|
+
|
1273
1300
|
return await engine.run_unified_validation(
|
1274
1301
|
resource_types=resource_types,
|
1275
1302
|
accounts=accounts,
|
1276
1303
|
regions=regions,
|
1277
1304
|
export_formats=export_formats,
|
1278
1305
|
output_directory=output_directory,
|
1279
|
-
)
|
1306
|
+
)
|