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.
- 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 +135 -91
- 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 +17 -12
- 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 +99 -79
- 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 +315 -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/aws_decorators.py +2 -3
- runbooks/inventory/check_cloudtrail_compliance.py +2 -4
- runbooks/inventory/check_controltower_readiness.py +152 -151
- runbooks/inventory/check_landingzone_readiness.py +85 -84
- 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/core/formatter.py +11 -0
- runbooks/inventory/draw_org_structure.py +8 -9
- runbooks/inventory/drift_detection_cli.py +69 -96
- runbooks/inventory/ec2_vpc_utils.py +2 -2
- runbooks/inventory/find_cfn_drift_detection.py +5 -7
- runbooks/inventory/find_cfn_orphaned_stacks.py +7 -9
- runbooks/inventory/find_cfn_stackset_drift.py +5 -6
- runbooks/inventory/find_ec2_security_groups.py +48 -42
- runbooks/inventory/find_landingzone_versions.py +4 -6
- runbooks/inventory/find_vpc_flow_logs.py +7 -9
- runbooks/inventory/inventory_mcp_cli.py +48 -46
- runbooks/inventory/inventory_modules.py +103 -91
- runbooks/inventory/list_cfn_stacks.py +9 -10
- runbooks/inventory/list_cfn_stackset_operation_results.py +1 -3
- runbooks/inventory/list_cfn_stackset_operations.py +79 -57
- runbooks/inventory/list_cfn_stacksets.py +8 -10
- runbooks/inventory/list_config_recorders_delivery_channels.py +49 -39
- runbooks/inventory/list_ds_directories.py +65 -53
- runbooks/inventory/list_ec2_availability_zones.py +2 -4
- runbooks/inventory/list_ec2_ebs_volumes.py +32 -35
- runbooks/inventory/list_ec2_instances.py +23 -28
- runbooks/inventory/list_ecs_clusters_and_tasks.py +26 -34
- runbooks/inventory/list_elbs_load_balancers.py +22 -20
- runbooks/inventory/list_enis_network_interfaces.py +26 -33
- runbooks/inventory/list_guardduty_detectors.py +2 -4
- runbooks/inventory/list_iam_policies.py +2 -4
- runbooks/inventory/list_iam_roles.py +5 -7
- runbooks/inventory/list_iam_saml_providers.py +4 -6
- runbooks/inventory/list_lambda_functions.py +38 -38
- runbooks/inventory/list_org_accounts.py +6 -8
- runbooks/inventory/list_org_accounts_users.py +55 -44
- runbooks/inventory/list_rds_db_instances.py +31 -33
- runbooks/inventory/list_rds_snapshots_aggregator.py +192 -208
- runbooks/inventory/list_route53_hosted_zones.py +3 -5
- runbooks/inventory/list_servicecatalog_provisioned_products.py +37 -41
- runbooks/inventory/list_sns_topics.py +2 -4
- runbooks/inventory/list_ssm_parameters.py +4 -7
- runbooks/inventory/list_vpc_subnets.py +2 -4
- runbooks/inventory/list_vpcs.py +7 -10
- runbooks/inventory/mcp_inventory_validator.py +554 -468
- runbooks/inventory/mcp_vpc_validator.py +359 -442
- runbooks/inventory/organizations_discovery.py +63 -55
- runbooks/inventory/recover_cfn_stack_ids.py +7 -8
- runbooks/inventory/requirements.txt +0 -1
- runbooks/inventory/rich_inventory_display.py +35 -34
- runbooks/inventory/run_on_multi_accounts.py +3 -5
- runbooks/inventory/unified_validation_engine.py +281 -253
- runbooks/inventory/verify_ec2_security_groups.py +1 -1
- runbooks/inventory/vpc_analyzer.py +735 -697
- runbooks/inventory/vpc_architecture_validator.py +293 -348
- runbooks/inventory/vpc_dependency_analyzer.py +384 -380
- 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 +461 -454
- 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.6.dist-info/METADATA +327 -0
- runbooks-1.1.6.dist-info/RECORD +489 -0
- 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/RECORD +0 -468
- {runbooks-1.1.4.dist-info → runbooks-1.1.6.dist-info}/WHEEL +0 -0
- {runbooks-1.1.4.dist-info → runbooks-1.1.6.dist-info}/entry_points.txt +0 -0
- {runbooks-1.1.4.dist-info → runbooks-1.1.6.dist-info}/licenses/LICENSE +0 -0
- {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.
|
39
|
-
from
|
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__(
|
74
|
-
|
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
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
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,
|
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 = [
|
156
|
-
|
157
|
-
|
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
|
-
|
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(
|
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(
|
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(
|
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(
|
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,
|
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(
|
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(
|
370
|
-
backend_bucket = line.split(
|
371
|
-
elif in_backend and line.startswith(
|
372
|
-
backend_key = line.split(
|
373
|
-
elif in_backend and line.startswith(
|
374
|
-
dynamodb_table = line.split(
|
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
|
-
|
380
|
-
|
381
|
-
|
382
|
-
|
383
|
-
|
384
|
-
|
385
|
-
|
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
|
-
|
392
|
-
|
393
|
-
|
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
|
-
|
409
|
-
|
410
|
-
|
411
|
-
|
412
|
-
|
413
|
-
|
414
|
-
|
415
|
-
|
416
|
-
|
417
|
-
|
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
|
-
|
434
|
-
|
435
|
-
|
436
|
-
|
437
|
-
|
438
|
-
|
439
|
-
|
440
|
-
|
441
|
-
|
442
|
-
|
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,
|
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
|
-
|
461
|
-
|
462
|
-
|
463
|
-
|
464
|
-
|
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
|
-
|
471
|
-
|
472
|
-
|
473
|
-
|
474
|
-
|
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 = [
|
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(
|
611
|
-
|
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,
|
627
|
-
|
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(
|
678
|
-
|
679
|
-
|
680
|
-
|
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(
|
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(
|
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(
|
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(
|
886
|
-
|
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}"
|
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(
|
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,
|
976
|
-
|
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(
|
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(
|
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(
|
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(
|
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(
|
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 +=
|
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
|
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(
|
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(
|
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(
|
1175
|
-
|
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[
|
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(
|
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(
|
1285
|
+
for reservation in page.get("Reservations", []):
|
1237
1286
|
# Count all instances regardless of state for accurate inventory
|
1238
|
-
instances = reservation.get(
|
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[
|
1255
|
-
|
1302
|
+
|
1303
|
+
resource_counts["ec2"] = total_ec2_instances
|
1304
|
+
|
1256
1305
|
# Track validation quality metrics
|
1257
|
-
self.console.log(
|
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[
|
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[
|
1318
|
+
resource_counts["s3"] = len(buckets_response.get("Buckets", []))
|
1268
1319
|
except Exception:
|
1269
|
-
resource_counts[
|
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(
|
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(
|
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[
|
1342
|
+
resource_counts["rds"] = total_rds_instances
|
1292
1343
|
except Exception:
|
1293
|
-
resource_counts[
|
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(
|
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(
|
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[
|
1366
|
+
resource_counts["lambda"] = total_lambda_functions
|
1316
1367
|
except Exception:
|
1317
|
-
resource_counts[
|
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(
|
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(
|
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[
|
1390
|
+
resource_counts["vpc"] = total_vpcs
|
1340
1391
|
except Exception:
|
1341
|
-
resource_counts[
|
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(
|
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(
|
1353
|
-
|
1354
|
-
resource_counts[
|
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[
|
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(
|
1422
|
+
paginator = cf_client.get_paginator("list_stacks")
|
1372
1423
|
region_stacks = 0
|
1373
|
-
|
1374
|
-
for page in paginator.paginate(
|
1375
|
-
|
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[
|
1436
|
+
resource_counts["cloudformation"] = total_stacks
|
1384
1437
|
except Exception:
|
1385
|
-
resource_counts[
|
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(
|
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(
|
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[
|
1460
|
+
resource_counts["elbv2"] = total_load_balancers
|
1408
1461
|
except Exception:
|
1409
|
-
resource_counts[
|
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(
|
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(
|
1421
|
-
|
1422
|
-
resource_counts[
|
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[
|
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(
|
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(
|
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[
|
1504
|
+
resource_counts["sns"] = total_topics
|
1452
1505
|
except Exception:
|
1453
|
-
resource_counts[
|
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(
|
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(
|
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[
|
1528
|
+
resource_counts["eni"] = total_enis
|
1476
1529
|
except Exception:
|
1477
|
-
resource_counts[
|
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(
|
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(
|
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[
|
1552
|
+
resource_counts["ebs"] = total_volumes
|
1500
1553
|
except Exception:
|
1501
|
-
resource_counts[
|
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(
|
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(
|
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(
|
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)
|
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": (
|
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(
|
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(
|
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
|
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(
|
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 {
|
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(
|
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
|
-
|
2012
|
-
|
2013
|
-
|
2014
|
-
|
2015
|
-
|
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 {
|
2024
|
-
|
2093
|
+
return {"error": str(e)}
|
2094
|
+
|
2025
2095
|
return {
|
2026
|
-
|
2027
|
-
|
2028
|
-
|
2029
|
-
|
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[
|
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(
|
2054
|
-
|
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(
|
2076
|
-
|
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(
|
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(
|
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(
|
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(
|
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(
|
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
|