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