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