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.
Files changed (247) hide show
  1. runbooks/__init__.py +31 -2
  2. runbooks/__init___optimized.py +18 -4
  3. runbooks/_platform/__init__.py +1 -5
  4. runbooks/_platform/core/runbooks_wrapper.py +141 -138
  5. runbooks/aws2/accuracy_validator.py +812 -0
  6. runbooks/base.py +7 -0
  7. runbooks/cfat/WEIGHT_CONFIG_README.md +1 -1
  8. runbooks/cfat/assessment/compliance.py +8 -8
  9. runbooks/cfat/assessment/runner.py +1 -0
  10. runbooks/cfat/cloud_foundations_assessment.py +227 -239
  11. runbooks/cfat/models.py +6 -2
  12. runbooks/cfat/tests/__init__.py +6 -1
  13. runbooks/cli/__init__.py +13 -0
  14. runbooks/cli/commands/cfat.py +274 -0
  15. runbooks/cli/commands/finops.py +1164 -0
  16. runbooks/cli/commands/inventory.py +379 -0
  17. runbooks/cli/commands/operate.py +239 -0
  18. runbooks/cli/commands/security.py +248 -0
  19. runbooks/cli/commands/validation.py +825 -0
  20. runbooks/cli/commands/vpc.py +310 -0
  21. runbooks/cli/registry.py +107 -0
  22. runbooks/cloudops/__init__.py +23 -30
  23. runbooks/cloudops/base.py +96 -107
  24. runbooks/cloudops/cost_optimizer.py +549 -547
  25. runbooks/cloudops/infrastructure_optimizer.py +5 -4
  26. runbooks/cloudops/interfaces.py +226 -227
  27. runbooks/cloudops/lifecycle_manager.py +5 -4
  28. runbooks/cloudops/mcp_cost_validation.py +252 -235
  29. runbooks/cloudops/models.py +78 -53
  30. runbooks/cloudops/monitoring_automation.py +5 -4
  31. runbooks/cloudops/notebook_framework.py +179 -215
  32. runbooks/cloudops/security_enforcer.py +125 -159
  33. runbooks/common/accuracy_validator.py +11 -0
  34. runbooks/common/aws_pricing.py +349 -326
  35. runbooks/common/aws_pricing_api.py +211 -212
  36. runbooks/common/aws_profile_manager.py +341 -0
  37. runbooks/common/aws_utils.py +75 -80
  38. runbooks/common/business_logic.py +127 -105
  39. runbooks/common/cli_decorators.py +36 -60
  40. runbooks/common/comprehensive_cost_explorer_integration.py +456 -464
  41. runbooks/common/cross_account_manager.py +198 -205
  42. runbooks/common/date_utils.py +27 -39
  43. runbooks/common/decorators.py +235 -0
  44. runbooks/common/dry_run_examples.py +173 -208
  45. runbooks/common/dry_run_framework.py +157 -155
  46. runbooks/common/enhanced_exception_handler.py +15 -4
  47. runbooks/common/enhanced_logging_example.py +50 -64
  48. runbooks/common/enhanced_logging_integration_example.py +65 -37
  49. runbooks/common/env_utils.py +16 -16
  50. runbooks/common/error_handling.py +40 -38
  51. runbooks/common/lazy_loader.py +41 -23
  52. runbooks/common/logging_integration_helper.py +79 -86
  53. runbooks/common/mcp_cost_explorer_integration.py +478 -495
  54. runbooks/common/mcp_integration.py +63 -74
  55. runbooks/common/memory_optimization.py +140 -118
  56. runbooks/common/module_cli_base.py +37 -58
  57. runbooks/common/organizations_client.py +176 -194
  58. runbooks/common/patterns.py +204 -0
  59. runbooks/common/performance_monitoring.py +67 -71
  60. runbooks/common/performance_optimization_engine.py +283 -274
  61. runbooks/common/profile_utils.py +248 -39
  62. runbooks/common/rich_utils.py +643 -92
  63. runbooks/common/sre_performance_suite.py +177 -186
  64. runbooks/enterprise/__init__.py +1 -1
  65. runbooks/enterprise/logging.py +144 -106
  66. runbooks/enterprise/security.py +187 -204
  67. runbooks/enterprise/validation.py +43 -56
  68. runbooks/finops/__init__.py +29 -33
  69. runbooks/finops/account_resolver.py +1 -1
  70. runbooks/finops/advanced_optimization_engine.py +980 -0
  71. runbooks/finops/automation_core.py +268 -231
  72. runbooks/finops/business_case_config.py +184 -179
  73. runbooks/finops/cli.py +660 -139
  74. runbooks/finops/commvault_ec2_analysis.py +157 -164
  75. runbooks/finops/compute_cost_optimizer.py +336 -320
  76. runbooks/finops/config.py +20 -20
  77. runbooks/finops/cost_optimizer.py +488 -622
  78. runbooks/finops/cost_processor.py +332 -214
  79. runbooks/finops/dashboard_runner.py +1006 -172
  80. runbooks/finops/ebs_cost_optimizer.py +991 -657
  81. runbooks/finops/elastic_ip_optimizer.py +317 -257
  82. runbooks/finops/enhanced_mcp_integration.py +340 -0
  83. runbooks/finops/enhanced_progress.py +40 -37
  84. runbooks/finops/enhanced_trend_visualization.py +3 -2
  85. runbooks/finops/enterprise_wrappers.py +230 -292
  86. runbooks/finops/executive_export.py +203 -160
  87. runbooks/finops/helpers.py +130 -288
  88. runbooks/finops/iam_guidance.py +1 -1
  89. runbooks/finops/infrastructure/__init__.py +80 -0
  90. runbooks/finops/infrastructure/commands.py +506 -0
  91. runbooks/finops/infrastructure/load_balancer_optimizer.py +866 -0
  92. runbooks/finops/infrastructure/vpc_endpoint_optimizer.py +832 -0
  93. runbooks/finops/markdown_exporter.py +338 -175
  94. runbooks/finops/mcp_validator.py +1952 -0
  95. runbooks/finops/nat_gateway_optimizer.py +1513 -482
  96. runbooks/finops/network_cost_optimizer.py +657 -587
  97. runbooks/finops/notebook_utils.py +226 -188
  98. runbooks/finops/optimization_engine.py +1136 -0
  99. runbooks/finops/optimizer.py +25 -29
  100. runbooks/finops/rds_snapshot_optimizer.py +367 -411
  101. runbooks/finops/reservation_optimizer.py +427 -363
  102. runbooks/finops/scenario_cli_integration.py +77 -78
  103. runbooks/finops/scenarios.py +1278 -439
  104. runbooks/finops/schemas.py +218 -182
  105. runbooks/finops/snapshot_manager.py +2289 -0
  106. runbooks/finops/tests/test_finops_dashboard.py +3 -3
  107. runbooks/finops/tests/test_reference_images_validation.py +2 -2
  108. runbooks/finops/tests/test_single_account_features.py +17 -17
  109. runbooks/finops/tests/validate_test_suite.py +1 -1
  110. runbooks/finops/types.py +3 -3
  111. runbooks/finops/validation_framework.py +263 -269
  112. runbooks/finops/vpc_cleanup_exporter.py +191 -146
  113. runbooks/finops/vpc_cleanup_optimizer.py +593 -575
  114. runbooks/finops/workspaces_analyzer.py +171 -182
  115. runbooks/hitl/enhanced_workflow_engine.py +1 -1
  116. runbooks/integration/__init__.py +89 -0
  117. runbooks/integration/mcp_integration.py +1920 -0
  118. runbooks/inventory/CLAUDE.md +816 -0
  119. runbooks/inventory/README.md +3 -3
  120. runbooks/inventory/Tests/common_test_data.py +30 -30
  121. runbooks/inventory/__init__.py +2 -2
  122. runbooks/inventory/cloud_foundations_integration.py +144 -149
  123. runbooks/inventory/collectors/aws_comprehensive.py +28 -11
  124. runbooks/inventory/collectors/aws_networking.py +111 -101
  125. runbooks/inventory/collectors/base.py +4 -0
  126. runbooks/inventory/core/collector.py +495 -313
  127. runbooks/inventory/discovery.md +2 -2
  128. runbooks/inventory/drift_detection_cli.py +69 -96
  129. runbooks/inventory/find_ec2_security_groups.py +1 -1
  130. runbooks/inventory/inventory_mcp_cli.py +48 -46
  131. runbooks/inventory/list_rds_snapshots_aggregator.py +192 -208
  132. runbooks/inventory/mcp_inventory_validator.py +549 -465
  133. runbooks/inventory/mcp_vpc_validator.py +359 -442
  134. runbooks/inventory/organizations_discovery.py +56 -52
  135. runbooks/inventory/rich_inventory_display.py +33 -32
  136. runbooks/inventory/unified_validation_engine.py +278 -251
  137. runbooks/inventory/vpc_analyzer.py +733 -696
  138. runbooks/inventory/vpc_architecture_validator.py +293 -348
  139. runbooks/inventory/vpc_dependency_analyzer.py +382 -378
  140. runbooks/inventory/vpc_flow_analyzer.py +3 -3
  141. runbooks/main.py +152 -9147
  142. runbooks/main_final.py +91 -60
  143. runbooks/main_minimal.py +22 -10
  144. runbooks/main_optimized.py +131 -100
  145. runbooks/main_ultra_minimal.py +7 -2
  146. runbooks/mcp/__init__.py +36 -0
  147. runbooks/mcp/integration.py +679 -0
  148. runbooks/metrics/dora_metrics_engine.py +2 -2
  149. runbooks/monitoring/performance_monitor.py +9 -4
  150. runbooks/operate/dynamodb_operations.py +3 -1
  151. runbooks/operate/ec2_operations.py +145 -137
  152. runbooks/operate/iam_operations.py +146 -152
  153. runbooks/operate/mcp_integration.py +1 -1
  154. runbooks/operate/networking_cost_heatmap.py +33 -10
  155. runbooks/operate/privatelink_operations.py +1 -1
  156. runbooks/operate/rds_operations.py +223 -254
  157. runbooks/operate/s3_operations.py +107 -118
  158. runbooks/operate/vpc_endpoints.py +1 -1
  159. runbooks/operate/vpc_operations.py +648 -618
  160. runbooks/remediation/base.py +1 -1
  161. runbooks/remediation/commons.py +10 -7
  162. runbooks/remediation/commvault_ec2_analysis.py +71 -67
  163. runbooks/remediation/ec2_unattached_ebs_volumes.py +1 -0
  164. runbooks/remediation/multi_account.py +24 -21
  165. runbooks/remediation/rds_snapshot_list.py +91 -65
  166. runbooks/remediation/remediation_cli.py +92 -146
  167. runbooks/remediation/universal_account_discovery.py +83 -79
  168. runbooks/remediation/workspaces_list.py +49 -44
  169. runbooks/security/__init__.py +19 -0
  170. runbooks/security/assessment_runner.py +1150 -0
  171. runbooks/security/baseline_checker.py +812 -0
  172. runbooks/security/cloudops_automation_security_validator.py +509 -535
  173. runbooks/security/compliance_automation_engine.py +17 -17
  174. runbooks/security/config/__init__.py +2 -2
  175. runbooks/security/config/compliance_config.py +50 -50
  176. runbooks/security/config_template_generator.py +63 -76
  177. runbooks/security/enterprise_security_framework.py +1 -1
  178. runbooks/security/executive_security_dashboard.py +519 -508
  179. runbooks/security/integration_test_enterprise_security.py +5 -3
  180. runbooks/security/multi_account_security_controls.py +959 -1210
  181. runbooks/security/real_time_security_monitor.py +422 -444
  182. runbooks/security/run_script.py +1 -1
  183. runbooks/security/security_baseline_tester.py +1 -1
  184. runbooks/security/security_cli.py +143 -112
  185. runbooks/security/test_2way_validation.py +439 -0
  186. runbooks/security/two_way_validation_framework.py +852 -0
  187. runbooks/sre/mcp_reliability_engine.py +6 -6
  188. runbooks/sre/production_monitoring_framework.py +167 -177
  189. runbooks/tdd/__init__.py +15 -0
  190. runbooks/tdd/cli.py +1071 -0
  191. runbooks/utils/__init__.py +14 -17
  192. runbooks/utils/logger.py +7 -2
  193. runbooks/utils/version_validator.py +51 -48
  194. runbooks/validation/__init__.py +6 -6
  195. runbooks/validation/cli.py +9 -3
  196. runbooks/validation/comprehensive_2way_validator.py +754 -708
  197. runbooks/validation/mcp_validator.py +906 -228
  198. runbooks/validation/terraform_citations_validator.py +104 -115
  199. runbooks/validation/terraform_drift_detector.py +447 -451
  200. runbooks/vpc/README.md +617 -0
  201. runbooks/vpc/__init__.py +8 -1
  202. runbooks/vpc/analyzer.py +577 -0
  203. runbooks/vpc/cleanup_wrapper.py +476 -413
  204. runbooks/vpc/cli_cloudtrail_commands.py +339 -0
  205. runbooks/vpc/cli_mcp_validation_commands.py +480 -0
  206. runbooks/vpc/cloudtrail_audit_integration.py +717 -0
  207. runbooks/vpc/config.py +92 -97
  208. runbooks/vpc/cost_engine.py +411 -148
  209. runbooks/vpc/cost_explorer_integration.py +553 -0
  210. runbooks/vpc/cross_account_session.py +101 -106
  211. runbooks/vpc/enhanced_mcp_validation.py +917 -0
  212. runbooks/vpc/eni_gate_validator.py +961 -0
  213. runbooks/vpc/heatmap_engine.py +190 -162
  214. runbooks/vpc/mcp_no_eni_validator.py +681 -640
  215. runbooks/vpc/nat_gateway_optimizer.py +358 -0
  216. runbooks/vpc/networking_wrapper.py +15 -8
  217. runbooks/vpc/pdca_remediation_planner.py +528 -0
  218. runbooks/vpc/performance_optimized_analyzer.py +219 -231
  219. runbooks/vpc/runbooks_adapter.py +1167 -241
  220. runbooks/vpc/tdd_red_phase_stubs.py +601 -0
  221. runbooks/vpc/test_data_loader.py +358 -0
  222. runbooks/vpc/tests/conftest.py +314 -4
  223. runbooks/vpc/tests/test_cleanup_framework.py +1022 -0
  224. runbooks/vpc/tests/test_cost_engine.py +0 -2
  225. runbooks/vpc/topology_generator.py +326 -0
  226. runbooks/vpc/unified_scenarios.py +1302 -1129
  227. runbooks/vpc/vpc_cleanup_integration.py +1943 -1115
  228. runbooks-1.1.5.dist-info/METADATA +328 -0
  229. {runbooks-1.1.3.dist-info → runbooks-1.1.5.dist-info}/RECORD +233 -200
  230. runbooks/finops/README.md +0 -414
  231. runbooks/finops/accuracy_cross_validator.py +0 -647
  232. runbooks/finops/business_cases.py +0 -950
  233. runbooks/finops/dashboard_router.py +0 -922
  234. runbooks/finops/ebs_optimizer.py +0 -956
  235. runbooks/finops/embedded_mcp_validator.py +0 -1629
  236. runbooks/finops/enhanced_dashboard_runner.py +0 -527
  237. runbooks/finops/finops_dashboard.py +0 -584
  238. runbooks/finops/finops_scenarios.py +0 -1218
  239. runbooks/finops/legacy_migration.py +0 -730
  240. runbooks/finops/multi_dashboard.py +0 -1519
  241. runbooks/finops/single_dashboard.py +0 -1113
  242. runbooks/finops/unlimited_scenarios.py +0 -393
  243. runbooks-1.1.3.dist-info/METADATA +0 -799
  244. {runbooks-1.1.3.dist-info → runbooks-1.1.5.dist-info}/WHEEL +0 -0
  245. {runbooks-1.1.3.dist-info → runbooks-1.1.5.dist-info}/entry_points.txt +0 -0
  246. {runbooks-1.1.3.dist-info → runbooks-1.1.5.dist-info}/licenses/LICENSE +0 -0
  247. {runbooks-1.1.3.dist-info → runbooks-1.1.5.dist-info}/top_level.txt +0 -0
@@ -1,14 +1,14 @@
1
1
  #!/usr/bin/env python3
2
2
  """
3
3
  Elastic IP Resource Efficiency Analyzer - Enterprise FinOps Analysis Platform
4
- Strategic Business Focus: Elastic IP resource efficiency optimization for Manager, Financial, and CTO stakeholders
4
+ Strategic Business Focus: Elastic IP resource efficiency optimization for Manager, Financial, and CTO stakeholders
5
5
 
6
6
  Strategic Achievement: Part of $132,720+ annual savings methodology (380-757% ROI achievement)
7
7
  Business Impact: $1.8M-$3.1M annual savings potential across enterprise accounts
8
8
  Technical Foundation: Enterprise-grade Elastic IP discovery and attachment validation
9
9
 
10
10
  This module provides comprehensive Elastic IP resource efficiency analysis following proven FinOps patterns:
11
- - Multi-region Elastic IP discovery across all AWS regions
11
+ - Multi-region Elastic IP discovery across all AWS regions
12
12
  - Instance attachment validation and DNS dependency checking
13
13
  - Cost savings calculation ($3.65/month per unattached EIP)
14
14
  - Safety analysis (ensure EIPs aren't referenced in DNS, load balancers, etc.)
@@ -16,7 +16,7 @@ This module provides comprehensive Elastic IP resource efficiency analysis follo
16
16
 
17
17
  Strategic Alignment:
18
18
  - "Do one thing and do it well": Elastic IP resource efficiency specialization
19
- - "Move Fast, But Not So Fast We Crash": Safety-first analysis approach
19
+ - "Move Fast, But Not So Fast We Crash": Safety-first analysis approach
20
20
  - Enterprise FAANG SDLC: Evidence-based optimization with audit trails
21
21
  - Universal $132K Cost Optimization Methodology: Manager scenarios prioritized over generic patterns
22
22
  """
@@ -32,19 +32,29 @@ import click
32
32
  from botocore.exceptions import ClientError, NoCredentialsError
33
33
  from pydantic import BaseModel, Field
34
34
 
35
+ from ..common.aws_pricing import calculate_annual_cost, get_service_monthly_cost
36
+ from ..common.profile_utils import get_profile_for_operation
35
37
  from ..common.rich_utils import (
36
- console, print_header, print_success, print_error, print_warning, print_info,
37
- create_table, create_progress_bar, format_cost, create_panel, STATUS_INDICATORS
38
+ STATUS_INDICATORS,
39
+ console,
40
+ create_panel,
41
+ create_progress_bar,
42
+ create_table,
43
+ format_cost,
44
+ print_error,
45
+ print_header,
46
+ print_info,
47
+ print_success,
48
+ print_warning,
38
49
  )
39
- from ..common.aws_pricing import get_service_monthly_cost, calculate_annual_cost
40
- from .embedded_mcp_validator import EmbeddedMCPValidator
41
- from ..common.profile_utils import get_profile_for_operation
50
+ from .mcp_validator import EmbeddedMCPValidator
42
51
 
43
52
  logger = logging.getLogger(__name__)
44
53
 
45
54
 
46
55
  class ElasticIPDetails(BaseModel):
47
56
  """Elastic IP details from EC2 API."""
57
+
48
58
  allocation_id: str
49
59
  public_ip: str
50
60
  region: str
@@ -60,6 +70,7 @@ class ElasticIPDetails(BaseModel):
60
70
 
61
71
  class ElasticIPOptimizationResult(BaseModel):
62
72
  """Elastic IP optimization analysis results."""
73
+
63
74
  allocation_id: str
64
75
  public_ip: str
65
76
  region: str
@@ -67,7 +78,7 @@ class ElasticIPOptimizationResult(BaseModel):
67
78
  is_attached: bool
68
79
  instance_id: Optional[str] = None
69
80
  monthly_cost: float = 0.0 # Calculated dynamically per region
70
- annual_cost: float = 0.0 # Calculated dynamically (monthly * 12)
81
+ annual_cost: float = 0.0 # Calculated dynamically (monthly * 12)
71
82
  optimization_recommendation: str = "retain" # retain, release
72
83
  risk_level: str = "low" # low, medium, high
73
84
  business_impact: str = "minimal"
@@ -79,6 +90,7 @@ class ElasticIPOptimizationResult(BaseModel):
79
90
 
80
91
  class ElasticIPOptimizerResults(BaseModel):
81
92
  """Complete Elastic IP optimization analysis results."""
93
+
82
94
  total_elastic_ips: int = 0
83
95
  attached_elastic_ips: int = 0
84
96
  unattached_elastic_ips: int = 0
@@ -96,7 +108,7 @@ class ElasticIPOptimizerResults(BaseModel):
96
108
  class ElasticIPOptimizer:
97
109
  """
98
110
  Elastic IP Resource Efficiency Analyzer - Enterprise FinOps Analysis Engine
99
-
111
+
100
112
  Following $132,720+ methodology with proven FinOps patterns targeting $1.8M-$3.1M annual savings:
101
113
  - Multi-region discovery and analysis across enterprise accounts
102
114
  - Instance attachment validation with safety controls
@@ -105,92 +117,111 @@ class ElasticIPOptimizer:
105
117
  - Evidence generation for Manager/Financial/CTO executive reporting
106
118
  - Business-focused naming for executive presentation readiness
107
119
  """
108
-
120
+
109
121
  def __init__(self, profile_name: Optional[str] = None, regions: Optional[List[str]] = None):
110
122
  """Initialize Elastic IP optimizer with enterprise profile support."""
111
123
  self.profile_name = profile_name
112
124
  self.regions = regions or [
113
- 'us-east-1', 'us-west-2', 'us-east-2', 'us-west-1',
114
- 'eu-west-1', 'eu-central-1', 'ap-southeast-1', 'ap-northeast-1'
125
+ "us-east-1",
126
+ "us-west-2",
127
+ "us-east-2",
128
+ "us-west-1",
129
+ "eu-west-1",
130
+ "eu-central-1",
131
+ "ap-southeast-1",
132
+ "ap-northeast-1",
115
133
  ]
116
-
134
+
117
135
  # Initialize AWS session with profile priority system
118
- self.session = boto3.Session(
119
- profile_name=get_profile_for_operation("operational", profile_name)
120
- )
121
-
136
+ self.session = boto3.Session(profile_name=get_profile_for_operation("operational", profile_name))
137
+
122
138
  # Dynamic Elastic IP pricing - Enterprise compliance (no hardcoded values)
123
139
  # Pricing will be calculated dynamically per region using AWS Pricing API
124
-
140
+
125
141
  # All AWS regions for comprehensive discovery
126
142
  self.all_regions = [
127
- 'us-east-1', 'us-east-2', 'us-west-1', 'us-west-2',
128
- 'af-south-1', 'ap-east-1', 'ap-south-1', 'ap-northeast-1',
129
- 'ap-northeast-2', 'ap-northeast-3', 'ap-southeast-1', 'ap-southeast-2',
130
- 'ca-central-1', 'eu-central-1', 'eu-west-1', 'eu-west-2',
131
- 'eu-west-3', 'eu-south-1', 'eu-north-1', 'me-south-1',
132
- 'sa-east-1'
143
+ "us-east-1",
144
+ "us-east-2",
145
+ "us-west-1",
146
+ "us-west-2",
147
+ "af-south-1",
148
+ "ap-east-1",
149
+ "ap-south-1",
150
+ "ap-northeast-1",
151
+ "ap-northeast-2",
152
+ "ap-northeast-3",
153
+ "ap-southeast-1",
154
+ "ap-southeast-2",
155
+ "ca-central-1",
156
+ "eu-central-1",
157
+ "eu-west-1",
158
+ "eu-west-2",
159
+ "eu-west-3",
160
+ "eu-south-1",
161
+ "eu-north-1",
162
+ "me-south-1",
163
+ "sa-east-1",
133
164
  ]
134
-
165
+
135
166
  async def analyze_elastic_ips(self, dry_run: bool = True) -> ElasticIPOptimizerResults:
136
167
  """
137
168
  Comprehensive Elastic IP cost optimization analysis.
138
-
169
+
139
170
  Args:
140
171
  dry_run: Safety mode - READ-ONLY analysis only
141
-
172
+
142
173
  Returns:
143
174
  Complete analysis results with optimization recommendations
144
175
  """
145
176
  print_header("Elastic IP Resource Efficiency Analyzer", "Enterprise FinOps Analysis Platform v1.0")
146
-
177
+
147
178
  if not dry_run:
148
179
  print_warning("⚠️ Dry-run disabled - This optimizer is READ-ONLY analysis only")
149
180
  print_info("All Elastic IP operations require manual execution after review")
150
-
181
+
151
182
  analysis_start_time = time.time()
152
-
183
+
153
184
  try:
154
185
  with create_progress_bar() as progress:
155
186
  # Step 1: Multi-region Elastic IP discovery
156
187
  discovery_task = progress.add_task("Discovering Elastic IPs...", total=len(self.regions))
157
188
  elastic_ips = await self._discover_elastic_ips_multi_region(progress, discovery_task)
158
-
189
+
159
190
  if not elastic_ips:
160
191
  print_warning("No Elastic IPs found in specified regions")
161
192
  return ElasticIPOptimizerResults(
162
193
  analyzed_regions=self.regions,
163
194
  analysis_timestamp=datetime.now(),
164
- execution_time_seconds=time.time() - analysis_start_time
195
+ execution_time_seconds=time.time() - analysis_start_time,
165
196
  )
166
-
167
- # Step 2: Attachment validation analysis
197
+
198
+ # Step 2: Attachment validation analysis
168
199
  attachment_task = progress.add_task("Validating attachments...", total=len(elastic_ips))
169
200
  validated_elastic_ips = await self._validate_attachments(elastic_ips, progress, attachment_task)
170
-
201
+
171
202
  # Step 3: DNS dependency analysis for safety
172
203
  dns_task = progress.add_task("Checking DNS dependencies...", total=len(elastic_ips))
173
204
  dns_dependencies = await self._analyze_dns_dependencies(validated_elastic_ips, progress, dns_task)
174
-
205
+
175
206
  # Step 4: Cost optimization analysis
176
207
  optimization_task = progress.add_task("Calculating optimization potential...", total=len(elastic_ips))
177
208
  optimization_results = await self._calculate_optimization_recommendations(
178
209
  validated_elastic_ips, dns_dependencies, progress, optimization_task
179
210
  )
180
-
211
+
181
212
  # Step 5: MCP validation
182
213
  validation_task = progress.add_task("MCP validation...", total=1)
183
214
  mcp_accuracy = await self._validate_with_mcp(optimization_results, progress, validation_task)
184
-
215
+
185
216
  # Compile comprehensive results
186
217
  attached_count = sum(1 for result in optimization_results if result.is_attached)
187
218
  unattached_count = len(optimization_results) - attached_count
188
-
219
+
189
220
  total_monthly_cost = sum(result.monthly_cost for result in optimization_results if not result.is_attached)
190
221
  total_annual_cost = total_monthly_cost * 12
191
222
  potential_monthly_savings = sum(result.potential_monthly_savings for result in optimization_results)
192
223
  potential_annual_savings = potential_monthly_savings * 12
193
-
224
+
194
225
  results = ElasticIPOptimizerResults(
195
226
  total_elastic_ips=len(elastic_ips),
196
227
  attached_elastic_ips=attached_count,
@@ -203,185 +234,195 @@ class ElasticIPOptimizer:
203
234
  potential_annual_savings=potential_annual_savings,
204
235
  execution_time_seconds=time.time() - analysis_start_time,
205
236
  mcp_validation_accuracy=mcp_accuracy,
206
- analysis_timestamp=datetime.now()
237
+ analysis_timestamp=datetime.now(),
207
238
  )
208
-
239
+
209
240
  # Display executive summary
210
241
  self._display_executive_summary(results)
211
-
242
+
212
243
  return results
213
-
244
+
214
245
  except Exception as e:
215
246
  print_error(f"Elastic IP optimization analysis failed: {e}")
216
247
  logger.error(f"Elastic IP analysis error: {e}", exc_info=True)
217
248
  raise
218
-
249
+
219
250
  async def _discover_elastic_ips_multi_region(self, progress, task_id) -> List[ElasticIPDetails]:
220
251
  """Discover Elastic IPs across multiple regions."""
221
252
  elastic_ips = []
222
-
253
+
223
254
  for region in self.regions:
224
255
  try:
225
- ec2_client = self.session.client('ec2', region_name=region)
226
-
256
+ ec2_client = self.session.client("ec2", region_name=region)
257
+
227
258
  # Get all Elastic IPs in region
228
259
  response = ec2_client.describe_addresses()
229
-
230
- for address in response.get('Addresses', []):
260
+
261
+ for address in response.get("Addresses", []):
231
262
  # Extract tags
232
- tags = {tag['Key']: tag['Value'] for tag in address.get('Tags', [])}
233
-
263
+ tags = {tag["Key"]: tag["Value"] for tag in address.get("Tags", [])}
264
+
234
265
  # Determine attachment status
235
- is_attached = 'AssociationId' in address
236
-
237
- elastic_ips.append(ElasticIPDetails(
238
- allocation_id=address['AllocationId'],
239
- public_ip=address['PublicIp'],
240
- region=region,
241
- domain=address.get('Domain', 'vpc'),
242
- instance_id=address.get('InstanceId'),
243
- association_id=address.get('AssociationId'),
244
- network_interface_id=address.get('NetworkInterfaceId'),
245
- network_interface_owner_id=address.get('NetworkInterfaceOwnerId'),
246
- private_ip_address=address.get('PrivateIpAddress'),
247
- tags=tags,
248
- is_attached=is_attached
249
- ))
250
-
251
- print_info(f"Region {region}: {len([eip for eip in elastic_ips if eip.region == region])} Elastic IPs discovered")
252
-
266
+ is_attached = "AssociationId" in address
267
+
268
+ elastic_ips.append(
269
+ ElasticIPDetails(
270
+ allocation_id=address["AllocationId"],
271
+ public_ip=address["PublicIp"],
272
+ region=region,
273
+ domain=address.get("Domain", "vpc"),
274
+ instance_id=address.get("InstanceId"),
275
+ association_id=address.get("AssociationId"),
276
+ network_interface_id=address.get("NetworkInterfaceId"),
277
+ network_interface_owner_id=address.get("NetworkInterfaceOwnerId"),
278
+ private_ip_address=address.get("PrivateIpAddress"),
279
+ tags=tags,
280
+ is_attached=is_attached,
281
+ )
282
+ )
283
+
284
+ print_info(
285
+ f"Region {region}: {len([eip for eip in elastic_ips if eip.region == region])} Elastic IPs discovered"
286
+ )
287
+
253
288
  except ClientError as e:
254
289
  print_warning(f"Region {region}: Access denied or region unavailable - {e.response['Error']['Code']}")
255
290
  except Exception as e:
256
291
  print_error(f"Region {region}: Discovery error - {str(e)}")
257
-
292
+
258
293
  progress.advance(task_id)
259
-
294
+
260
295
  return elastic_ips
261
-
262
- async def _validate_attachments(self, elastic_ips: List[ElasticIPDetails], progress, task_id) -> List[ElasticIPDetails]:
296
+
297
+ async def _validate_attachments(
298
+ self, elastic_ips: List[ElasticIPDetails], progress, task_id
299
+ ) -> List[ElasticIPDetails]:
263
300
  """Validate Elastic IP attachments and instance details."""
264
301
  validated_ips = []
265
-
302
+
266
303
  for elastic_ip in elastic_ips:
267
304
  try:
268
305
  # Additional validation for attached EIPs
269
306
  if elastic_ip.is_attached and elastic_ip.instance_id:
270
- ec2_client = self.session.client('ec2', region_name=elastic_ip.region)
271
-
307
+ ec2_client = self.session.client("ec2", region_name=elastic_ip.region)
308
+
272
309
  # Verify instance still exists and is running
273
310
  try:
274
311
  response = ec2_client.describe_instances(InstanceIds=[elastic_ip.instance_id])
275
- instance_found = len(response.get('Reservations', [])) > 0
276
-
312
+ instance_found = len(response.get("Reservations", [])) > 0
313
+
277
314
  if instance_found:
278
- instance = response['Reservations'][0]['Instances'][0]
279
- elastic_ip.is_attached = instance['State']['Name'] in ['running', 'stopped', 'stopping', 'starting']
315
+ instance = response["Reservations"][0]["Instances"][0]
316
+ elastic_ip.is_attached = instance["State"]["Name"] in [
317
+ "running",
318
+ "stopped",
319
+ "stopping",
320
+ "starting",
321
+ ]
280
322
  else:
281
323
  elastic_ip.is_attached = False
282
-
324
+
283
325
  except ClientError:
284
326
  # Instance not found - EIP is effectively unattached
285
327
  elastic_ip.is_attached = False
286
-
328
+
287
329
  validated_ips.append(elastic_ip)
288
-
330
+
289
331
  except Exception as e:
290
332
  print_warning(f"Validation failed for {elastic_ip.public_ip}: {str(e)}")
291
333
  validated_ips.append(elastic_ip) # Add with original status
292
-
334
+
293
335
  progress.advance(task_id)
294
-
336
+
295
337
  return validated_ips
296
-
297
- async def _analyze_dns_dependencies(self, elastic_ips: List[ElasticIPDetails], progress, task_id) -> Dict[str, List[str]]:
338
+
339
+ async def _analyze_dns_dependencies(
340
+ self, elastic_ips: List[ElasticIPDetails], progress, task_id
341
+ ) -> Dict[str, List[str]]:
298
342
  """Analyze potential DNS dependencies for Elastic IPs."""
299
343
  dns_dependencies = {}
300
-
344
+
301
345
  for elastic_ip in elastic_ips:
302
346
  try:
303
347
  dns_refs = []
304
-
348
+
305
349
  # Check Route 53 hosted zones for this IP
306
350
  try:
307
- route53_client = self.session.client('route53')
351
+ route53_client = self.session.client("route53")
308
352
  hosted_zones = route53_client.list_hosted_zones()
309
-
310
- for zone in hosted_zones.get('HostedZones', []):
353
+
354
+ for zone in hosted_zones.get("HostedZones", []):
311
355
  try:
312
- records = route53_client.list_resource_record_sets(
313
- HostedZoneId=zone['Id']
314
- )
315
-
316
- for record in records.get('ResourceRecordSets', []):
317
- if record['Type'] == 'A':
318
- for resource_record in record.get('ResourceRecords', []):
319
- if resource_record.get('Value') == elastic_ip.public_ip:
356
+ records = route53_client.list_resource_record_sets(HostedZoneId=zone["Id"])
357
+
358
+ for record in records.get("ResourceRecordSets", []):
359
+ if record["Type"] == "A":
360
+ for resource_record in record.get("ResourceRecords", []):
361
+ if resource_record.get("Value") == elastic_ip.public_ip:
320
362
  dns_refs.append(f"Route53: {record['Name']} -> {elastic_ip.public_ip}")
321
-
363
+
322
364
  except ClientError:
323
365
  # Zone not accessible or other error - continue
324
366
  pass
325
-
367
+
326
368
  except ClientError:
327
369
  # Route 53 not accessible - skip DNS check
328
370
  pass
329
-
371
+
330
372
  # Check Application Load Balancers (ALB)
331
373
  try:
332
- elbv2_client = self.session.client('elbv2', region_name=elastic_ip.region)
374
+ elbv2_client = self.session.client("elbv2", region_name=elastic_ip.region)
333
375
  load_balancers = elbv2_client.describe_load_balancers()
334
-
335
- for lb in load_balancers.get('LoadBalancers', []):
336
- if elastic_ip.public_ip in lb.get('CanonicalHostedZoneId', ''):
376
+
377
+ for lb in load_balancers.get("LoadBalancers", []):
378
+ if elastic_ip.public_ip in lb.get("CanonicalHostedZoneId", ""):
337
379
  dns_refs.append(f"ALB: {lb['LoadBalancerName']} references EIP")
338
-
380
+
339
381
  except ClientError:
340
382
  # ELB not accessible - skip check
341
383
  pass
342
-
384
+
343
385
  dns_dependencies[elastic_ip.allocation_id] = dns_refs
344
-
386
+
345
387
  except Exception as e:
346
388
  print_warning(f"DNS analysis failed for {elastic_ip.public_ip}: {str(e)}")
347
389
  dns_dependencies[elastic_ip.allocation_id] = []
348
-
390
+
349
391
  progress.advance(task_id)
350
-
392
+
351
393
  return dns_dependencies
352
-
353
- async def _calculate_optimization_recommendations(self,
354
- elastic_ips: List[ElasticIPDetails],
355
- dns_dependencies: Dict[str, List[str]],
356
- progress, task_id) -> List[ElasticIPOptimizationResult]:
394
+
395
+ async def _calculate_optimization_recommendations(
396
+ self, elastic_ips: List[ElasticIPDetails], dns_dependencies: Dict[str, List[str]], progress, task_id
397
+ ) -> List[ElasticIPOptimizationResult]:
357
398
  """Calculate optimization recommendations and potential savings."""
358
399
  optimization_results = []
359
-
400
+
360
401
  for elastic_ip in elastic_ips:
361
402
  try:
362
403
  dns_refs = dns_dependencies.get(elastic_ip.allocation_id, [])
363
-
404
+
364
405
  # Calculate current costs (only unattached EIPs are charged) - Dynamic pricing
365
406
  if elastic_ip.is_attached:
366
407
  monthly_cost = 0.0 # Attached EIPs are free
367
408
  else:
368
409
  monthly_cost = get_service_monthly_cost("elastic_ip", elastic_ip.region)
369
410
  annual_cost = calculate_annual_cost(monthly_cost)
370
-
411
+
371
412
  # Determine optimization recommendation
372
413
  recommendation = "retain" # Default: keep the Elastic IP
373
414
  risk_level = "low"
374
415
  business_impact = "minimal"
375
416
  potential_monthly_savings = 0.0
376
-
417
+
377
418
  # Safety checks
378
419
  safety_checks = {
379
420
  "is_unattached": not elastic_ip.is_attached,
380
421
  "no_dns_references": len(dns_refs) == 0,
381
422
  "no_instance_dependency": elastic_ip.instance_id is None,
382
- "safe_to_release": False
423
+ "safe_to_release": False,
383
424
  }
384
-
425
+
385
426
  if not elastic_ip.is_attached:
386
427
  if not dns_refs:
387
428
  # Unattached with no DNS references - safe to release
@@ -402,71 +443,74 @@ class ElasticIPOptimizer:
402
443
  risk_level = "low"
403
444
  business_impact = "none"
404
445
  potential_monthly_savings = 0.0
405
-
406
- optimization_results.append(ElasticIPOptimizationResult(
407
- allocation_id=elastic_ip.allocation_id,
408
- public_ip=elastic_ip.public_ip,
409
- region=elastic_ip.region,
410
- domain=elastic_ip.domain,
411
- is_attached=elastic_ip.is_attached,
412
- instance_id=elastic_ip.instance_id,
413
- monthly_cost=monthly_cost,
414
- annual_cost=annual_cost,
415
- optimization_recommendation=recommendation,
416
- risk_level=risk_level,
417
- business_impact=business_impact,
418
- potential_monthly_savings=potential_monthly_savings,
419
- potential_annual_savings=potential_monthly_savings * 12,
420
- safety_checks=safety_checks,
421
- dns_references=dns_refs
422
- ))
423
-
446
+
447
+ optimization_results.append(
448
+ ElasticIPOptimizationResult(
449
+ allocation_id=elastic_ip.allocation_id,
450
+ public_ip=elastic_ip.public_ip,
451
+ region=elastic_ip.region,
452
+ domain=elastic_ip.domain,
453
+ is_attached=elastic_ip.is_attached,
454
+ instance_id=elastic_ip.instance_id,
455
+ monthly_cost=monthly_cost,
456
+ annual_cost=annual_cost,
457
+ optimization_recommendation=recommendation,
458
+ risk_level=risk_level,
459
+ business_impact=business_impact,
460
+ potential_monthly_savings=potential_monthly_savings,
461
+ potential_annual_savings=potential_monthly_savings * 12,
462
+ safety_checks=safety_checks,
463
+ dns_references=dns_refs,
464
+ )
465
+ )
466
+
424
467
  except Exception as e:
425
468
  print_error(f"Optimization calculation failed for {elastic_ip.public_ip}: {str(e)}")
426
-
469
+
427
470
  progress.advance(task_id)
428
-
471
+
429
472
  return optimization_results
430
-
431
- async def _validate_with_mcp(self, optimization_results: List[ElasticIPOptimizationResult],
432
- progress, task_id) -> float:
473
+
474
+ async def _validate_with_mcp(
475
+ self, optimization_results: List[ElasticIPOptimizationResult], progress, task_id
476
+ ) -> float:
433
477
  """Validate optimization results with embedded MCP validator."""
434
478
  try:
435
479
  # Prepare validation data in FinOps format
436
480
  validation_data = {
437
- 'total_annual_cost': sum(result.annual_cost for result in optimization_results),
438
- 'potential_annual_savings': sum(result.potential_annual_savings for result in optimization_results),
439
- 'elastic_ips_analyzed': len(optimization_results),
440
- 'regions_analyzed': list(set(result.region for result in optimization_results)),
441
- 'analysis_timestamp': datetime.now().isoformat()
481
+ "total_annual_cost": sum(result.annual_cost for result in optimization_results),
482
+ "potential_annual_savings": sum(result.potential_annual_savings for result in optimization_results),
483
+ "elastic_ips_analyzed": len(optimization_results),
484
+ "regions_analyzed": list(set(result.region for result in optimization_results)),
485
+ "analysis_timestamp": datetime.now().isoformat(),
442
486
  }
443
-
487
+
444
488
  # Initialize MCP validator if profile is available
445
489
  if self.profile_name:
446
490
  mcp_validator = EmbeddedMCPValidator([self.profile_name])
447
491
  validation_results = await mcp_validator.validate_cost_data_async(validation_data)
448
- accuracy = validation_results.get('total_accuracy', 0.0)
449
-
492
+ accuracy = validation_results.get("total_accuracy", 0.0)
493
+
450
494
  if accuracy >= 99.5:
451
495
  print_success(f"MCP Validation: {accuracy:.1f}% accuracy achieved (target: ≥99.5%)")
452
496
  else:
453
497
  print_warning(f"MCP Validation: {accuracy:.1f}% accuracy (target: ≥99.5%)")
454
-
498
+
455
499
  progress.advance(task_id)
456
500
  return accuracy
457
501
  else:
458
502
  print_info("MCP validation skipped - no profile specified")
459
503
  progress.advance(task_id)
460
504
  return 0.0
461
-
505
+
462
506
  except Exception as e:
463
507
  print_warning(f"MCP validation failed: {str(e)}")
464
508
  progress.advance(task_id)
465
509
  return 0.0
466
-
510
+
467
511
  def _display_executive_summary(self, results: ElasticIPOptimizerResults) -> None:
468
512
  """Display executive summary with Rich CLI formatting."""
469
-
513
+
470
514
  # Executive Summary Panel
471
515
  summary_content = f"""
472
516
  💰 Total Annual Cost: {format_cost(results.total_annual_cost)}
@@ -474,22 +518,22 @@ class ElasticIPOptimizer:
474
518
  🎯 Elastic IPs Analyzed: {results.total_elastic_ips}
475
519
  📎 Attached EIPs: {results.attached_elastic_ips}
476
520
  🔓 Unattached EIPs: {results.unattached_elastic_ips}
477
- 🌍 Regions: {', '.join(results.analyzed_regions)}
521
+ 🌍 Regions: {", ".join(results.analyzed_regions)}
478
522
  ⚡ Analysis Time: {results.execution_time_seconds:.2f}s
479
523
  ✅ MCP Accuracy: {results.mcp_validation_accuracy:.1f}%
480
524
  """
481
-
482
- console.print(create_panel(
483
- summary_content.strip(),
484
- title="🏆 Elastic IP Resource Efficiency Analysis Summary",
485
- border_style="green"
486
- ))
487
-
488
- # Detailed Results Table
489
- table = create_table(
490
- title="Elastic IP Optimization Recommendations"
525
+
526
+ console.print(
527
+ create_panel(
528
+ summary_content.strip(),
529
+ title="🏆 Elastic IP Resource Efficiency Analysis Summary",
530
+ border_style="green",
531
+ )
491
532
  )
492
-
533
+
534
+ # Detailed Results Table
535
+ table = create_table(title="Elastic IP Optimization Recommendations")
536
+
493
537
  table.add_column("Elastic IP", style="cyan", no_wrap=True)
494
538
  table.add_column("Region", style="dim")
495
539
  table.add_column("Status", justify="center")
@@ -498,31 +542,21 @@ class ElasticIPOptimizer:
498
542
  table.add_column("Recommendation", justify="center")
499
543
  table.add_column("Risk Level", justify="center")
500
544
  table.add_column("DNS Refs", justify="center", style="dim")
501
-
545
+
502
546
  # Sort by potential savings (descending)
503
- sorted_results = sorted(
504
- results.optimization_results,
505
- key=lambda x: x.potential_annual_savings,
506
- reverse=True
507
- )
508
-
547
+ sorted_results = sorted(results.optimization_results, key=lambda x: x.potential_annual_savings, reverse=True)
548
+
509
549
  for result in sorted_results:
510
550
  # Status indicators
511
551
  status_indicator = "🔗 Attached" if result.is_attached else "🔓 Unattached"
512
-
552
+
513
553
  # Recommendation colors
514
- rec_color = {
515
- "release": "red",
516
- "investigate": "yellow",
517
- "retain": "green"
518
- }.get(result.optimization_recommendation, "white")
519
-
520
- risk_indicator = {
521
- "low": "🟢",
522
- "medium": "🟡",
523
- "high": "🔴"
524
- }.get(result.risk_level, "⚪")
525
-
554
+ rec_color = {"release": "red", "investigate": "yellow", "retain": "green"}.get(
555
+ result.optimization_recommendation, "white"
556
+ )
557
+
558
+ risk_indicator = {"low": "🟢", "medium": "🟡", "high": "🔴"}.get(result.risk_level, "")
559
+
526
560
  table.add_row(
527
561
  result.public_ip,
528
562
  result.region,
@@ -531,11 +565,11 @@ class ElasticIPOptimizer:
531
565
  format_cost(result.potential_annual_savings) if result.potential_annual_savings > 0 else "-",
532
566
  f"[{rec_color}]{result.optimization_recommendation.title()}[/]",
533
567
  f"{risk_indicator} {result.risk_level.title()}",
534
- str(len(result.dns_references))
568
+ str(len(result.dns_references)),
535
569
  )
536
-
570
+
537
571
  console.print(table)
538
-
572
+
539
573
  # Optimization Summary by Recommendation
540
574
  if results.optimization_results:
541
575
  recommendations_summary = {}
@@ -545,64 +579,84 @@ class ElasticIPOptimizer:
545
579
  recommendations_summary[rec] = {"count": 0, "savings": 0.0}
546
580
  recommendations_summary[rec]["count"] += 1
547
581
  recommendations_summary[rec]["savings"] += result.potential_annual_savings
548
-
582
+
549
583
  rec_content = []
550
584
  for rec, data in recommendations_summary.items():
551
- rec_content.append(f"• {rec.title()}: {data['count']} Elastic IPs ({format_cost(data['savings'])} potential savings)")
552
-
553
- console.print(create_panel(
554
- "\n".join(rec_content),
555
- title="📋 Recommendations Summary",
556
- border_style="blue"
557
- ))
558
-
559
- def export_results(self, results: ElasticIPOptimizerResults,
560
- output_file: Optional[str] = None,
561
- export_format: str = "json") -> str:
585
+ rec_content.append(
586
+ f"• {rec.title()}: {data['count']} Elastic IPs ({format_cost(data['savings'])} potential savings)"
587
+ )
588
+
589
+ console.print(create_panel("\n".join(rec_content), title="📋 Recommendations Summary", border_style="blue"))
590
+
591
+ def export_results(
592
+ self, results: ElasticIPOptimizerResults, output_file: Optional[str] = None, export_format: str = "json"
593
+ ) -> str:
562
594
  """
563
595
  Export optimization results to various formats.
564
-
596
+
565
597
  Args:
566
598
  results: Optimization analysis results
567
599
  output_file: Output file path (optional)
568
600
  export_format: Export format (json, csv, markdown)
569
-
601
+
570
602
  Returns:
571
603
  Path to exported file
572
604
  """
573
605
  timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
574
-
606
+
575
607
  if not output_file:
576
608
  output_file = f"elastic_ip_optimization_{timestamp}.{export_format}"
577
-
609
+
578
610
  try:
579
611
  if export_format.lower() == "json":
580
612
  import json
581
- with open(output_file, 'w') as f:
613
+
614
+ with open(output_file, "w") as f:
582
615
  json.dump(results.dict(), f, indent=2, default=str)
583
-
616
+
584
617
  elif export_format.lower() == "csv":
585
618
  import csv
586
- with open(output_file, 'w', newline='') as f:
619
+
620
+ with open(output_file, "w", newline="") as f:
587
621
  writer = csv.writer(f)
588
- writer.writerow([
589
- 'Allocation ID', 'Public IP', 'Region', 'Domain', 'Attached',
590
- 'Instance ID', 'Monthly Cost', 'Annual Cost',
591
- 'Potential Monthly Savings', 'Potential Annual Savings',
592
- 'Recommendation', 'Risk Level', 'DNS References'
593
- ])
622
+ writer.writerow(
623
+ [
624
+ "Allocation ID",
625
+ "Public IP",
626
+ "Region",
627
+ "Domain",
628
+ "Attached",
629
+ "Instance ID",
630
+ "Monthly Cost",
631
+ "Annual Cost",
632
+ "Potential Monthly Savings",
633
+ "Potential Annual Savings",
634
+ "Recommendation",
635
+ "Risk Level",
636
+ "DNS References",
637
+ ]
638
+ )
594
639
  for result in results.optimization_results:
595
- writer.writerow([
596
- result.allocation_id, result.public_ip, result.region,
597
- result.domain, result.is_attached, result.instance_id or '',
598
- f"${result.monthly_cost:.2f}", f"${result.annual_cost:.2f}",
599
- f"${result.potential_monthly_savings:.2f}", f"${result.potential_annual_savings:.2f}",
600
- result.optimization_recommendation, result.risk_level,
601
- len(result.dns_references)
602
- ])
603
-
640
+ writer.writerow(
641
+ [
642
+ result.allocation_id,
643
+ result.public_ip,
644
+ result.region,
645
+ result.domain,
646
+ result.is_attached,
647
+ result.instance_id or "",
648
+ f"${result.monthly_cost:.2f}",
649
+ f"${result.annual_cost:.2f}",
650
+ f"${result.potential_monthly_savings:.2f}",
651
+ f"${result.potential_annual_savings:.2f}",
652
+ result.optimization_recommendation,
653
+ result.risk_level,
654
+ len(result.dns_references),
655
+ ]
656
+ )
657
+
604
658
  elif export_format.lower() == "markdown":
605
- with open(output_file, 'w') as f:
659
+ with open(output_file, "w") as f:
606
660
  f.write(f"# Elastic IP Cost Optimization Report\n\n")
607
661
  f.write(f"**Analysis Date**: {results.analysis_timestamp}\n")
608
662
  f.write(f"**Total Elastic IPs**: {results.total_elastic_ips}\n")
@@ -611,16 +665,22 @@ class ElasticIPOptimizer:
611
665
  f.write(f"**Total Annual Cost**: ${results.total_annual_cost:.2f}\n")
612
666
  f.write(f"**Potential Annual Savings**: ${results.potential_annual_savings:.2f}\n\n")
613
667
  f.write(f"## Optimization Recommendations\n\n")
614
- f.write(f"| Public IP | Region | Status | Annual Cost | Potential Savings | Recommendation | Risk |\n")
615
- f.write(f"|-----------|--------|--------|-------------|-------------------|----------------|------|\n")
668
+ f.write(
669
+ f"| Public IP | Region | Status | Annual Cost | Potential Savings | Recommendation | Risk |\n"
670
+ )
671
+ f.write(
672
+ f"|-----------|--------|--------|-------------|-------------------|----------------|------|\n"
673
+ )
616
674
  for result in results.optimization_results:
617
675
  status = "Attached" if result.is_attached else "Unattached"
618
676
  f.write(f"| {result.public_ip} | {result.region} | {status} | ${result.annual_cost:.2f} | ")
619
- f.write(f"${result.potential_annual_savings:.2f} | {result.optimization_recommendation} | {result.risk_level} |\n")
620
-
677
+ f.write(
678
+ f"${result.potential_annual_savings:.2f} | {result.optimization_recommendation} | {result.risk_level} |\n"
679
+ )
680
+
621
681
  print_success(f"Results exported to: {output_file}")
622
682
  return output_file
623
-
683
+
624
684
  except Exception as e:
625
685
  print_error(f"Export failed: {str(e)}")
626
686
  raise
@@ -628,20 +688,21 @@ class ElasticIPOptimizer:
628
688
 
629
689
  # CLI Integration for enterprise runbooks commands
630
690
  @click.command()
631
- @click.option('--profile', help='AWS profile name (3-tier priority: User > Environment > Default)')
632
- @click.option('--regions', multiple=True, help='AWS regions to analyze (space-separated)')
633
- @click.option('--dry-run/--no-dry-run', default=True, help='Execute in dry-run mode (READ-ONLY analysis)')
634
- @click.option('--export-format', type=click.Choice(['json', 'csv', 'markdown']),
635
- default='json', help='Export format for results')
636
- @click.option('--output-file', help='Output file path for results export')
691
+ @click.option("--profile", help="AWS profile name (3-tier priority: User > Environment > Default)")
692
+ @click.option("--regions", multiple=True, help="AWS regions to analyze (space-separated)")
693
+ @click.option("--dry-run/--no-dry-run", default=True, help="Execute in dry-run mode (READ-ONLY analysis)")
694
+ @click.option(
695
+ "--export-format", type=click.Choice(["json", "csv", "markdown"]), default="json", help="Export format for results"
696
+ )
697
+ @click.option("--output-file", help="Output file path for results export")
637
698
  def elastic_ip_optimizer(profile, regions, dry_run, export_format, output_file):
638
699
  """
639
700
  Elastic IP Cost Optimizer - Enterprise Multi-Region Analysis
640
-
701
+
641
702
  Part of $132,720+ annual savings methodology targeting direct cost elimination.
642
-
703
+
643
704
  SAFETY: READ-ONLY analysis only - no resource modifications.
644
-
705
+
645
706
  Examples:
646
707
  runbooks finops elastic-ip --cleanup
647
708
  runbooks finops elastic-ip --profile my-profile --regions us-east-1 us-west-2
@@ -649,24 +710,23 @@ def elastic_ip_optimizer(profile, regions, dry_run, export_format, output_file):
649
710
  """
650
711
  try:
651
712
  # Initialize optimizer
652
- optimizer = ElasticIPOptimizer(
653
- profile_name=profile,
654
- regions=list(regions) if regions else None
655
- )
656
-
713
+ optimizer = ElasticIPOptimizer(profile_name=profile, regions=list(regions) if regions else None)
714
+
657
715
  # Execute analysis
658
716
  results = asyncio.run(optimizer.analyze_elastic_ips(dry_run=dry_run))
659
-
717
+
660
718
  # Export results if requested
661
- if output_file or export_format != 'json':
719
+ if output_file or export_format != "json":
662
720
  optimizer.export_results(results, output_file, export_format)
663
-
721
+
664
722
  # Display final success message
665
723
  if results.potential_annual_savings > 0:
666
- print_success(f"Analysis complete: {format_cost(results.potential_annual_savings)} potential annual savings identified")
724
+ print_success(
725
+ f"Analysis complete: {format_cost(results.potential_annual_savings)} potential annual savings identified"
726
+ )
667
727
  else:
668
728
  print_info("Analysis complete: All Elastic IPs are optimally configured")
669
-
729
+
670
730
  except KeyboardInterrupt:
671
731
  print_warning("Analysis interrupted by user")
672
732
  raise click.Abort()
@@ -675,5 +735,5 @@ def elastic_ip_optimizer(profile, regions, dry_run, export_format, output_file):
675
735
  raise click.Abort()
676
736
 
677
737
 
678
- if __name__ == '__main__':
679
- elastic_ip_optimizer()
738
+ if __name__ == "__main__":
739
+ elastic_ip_optimizer()