runbooks 1.1.4__py3-none-any.whl → 1.1.5__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (228) 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/assessment/compliance.py +1 -1
  8. runbooks/cfat/assessment/runner.py +1 -0
  9. runbooks/cfat/cloud_foundations_assessment.py +227 -239
  10. runbooks/cli/__init__.py +1 -1
  11. runbooks/cli/commands/cfat.py +64 -23
  12. runbooks/cli/commands/finops.py +1005 -54
  13. runbooks/cli/commands/inventory.py +138 -35
  14. runbooks/cli/commands/operate.py +9 -36
  15. runbooks/cli/commands/security.py +42 -18
  16. runbooks/cli/commands/validation.py +432 -18
  17. runbooks/cli/commands/vpc.py +81 -17
  18. runbooks/cli/registry.py +22 -10
  19. runbooks/cloudops/__init__.py +20 -27
  20. runbooks/cloudops/base.py +96 -107
  21. runbooks/cloudops/cost_optimizer.py +544 -542
  22. runbooks/cloudops/infrastructure_optimizer.py +5 -4
  23. runbooks/cloudops/interfaces.py +224 -225
  24. runbooks/cloudops/lifecycle_manager.py +5 -4
  25. runbooks/cloudops/mcp_cost_validation.py +252 -235
  26. runbooks/cloudops/models.py +78 -53
  27. runbooks/cloudops/monitoring_automation.py +5 -4
  28. runbooks/cloudops/notebook_framework.py +177 -213
  29. runbooks/cloudops/security_enforcer.py +125 -159
  30. runbooks/common/accuracy_validator.py +11 -0
  31. runbooks/common/aws_pricing.py +349 -326
  32. runbooks/common/aws_pricing_api.py +211 -212
  33. runbooks/common/aws_profile_manager.py +40 -36
  34. runbooks/common/aws_utils.py +74 -79
  35. runbooks/common/business_logic.py +126 -104
  36. runbooks/common/cli_decorators.py +36 -60
  37. runbooks/common/comprehensive_cost_explorer_integration.py +455 -463
  38. runbooks/common/cross_account_manager.py +197 -204
  39. runbooks/common/date_utils.py +27 -39
  40. runbooks/common/decorators.py +29 -19
  41. runbooks/common/dry_run_examples.py +173 -208
  42. runbooks/common/dry_run_framework.py +157 -155
  43. runbooks/common/enhanced_exception_handler.py +15 -4
  44. runbooks/common/enhanced_logging_example.py +50 -64
  45. runbooks/common/enhanced_logging_integration_example.py +65 -37
  46. runbooks/common/env_utils.py +16 -16
  47. runbooks/common/error_handling.py +40 -38
  48. runbooks/common/lazy_loader.py +41 -23
  49. runbooks/common/logging_integration_helper.py +79 -86
  50. runbooks/common/mcp_cost_explorer_integration.py +476 -493
  51. runbooks/common/mcp_integration.py +63 -74
  52. runbooks/common/memory_optimization.py +140 -118
  53. runbooks/common/module_cli_base.py +37 -58
  54. runbooks/common/organizations_client.py +175 -193
  55. runbooks/common/patterns.py +23 -25
  56. runbooks/common/performance_monitoring.py +67 -71
  57. runbooks/common/performance_optimization_engine.py +283 -274
  58. runbooks/common/profile_utils.py +111 -37
  59. runbooks/common/rich_utils.py +201 -141
  60. runbooks/common/sre_performance_suite.py +177 -186
  61. runbooks/enterprise/__init__.py +1 -1
  62. runbooks/enterprise/logging.py +144 -106
  63. runbooks/enterprise/security.py +187 -204
  64. runbooks/enterprise/validation.py +43 -56
  65. runbooks/finops/__init__.py +26 -30
  66. runbooks/finops/account_resolver.py +1 -1
  67. runbooks/finops/advanced_optimization_engine.py +980 -0
  68. runbooks/finops/automation_core.py +268 -231
  69. runbooks/finops/business_case_config.py +184 -179
  70. runbooks/finops/cli.py +660 -139
  71. runbooks/finops/commvault_ec2_analysis.py +157 -164
  72. runbooks/finops/compute_cost_optimizer.py +336 -320
  73. runbooks/finops/config.py +20 -20
  74. runbooks/finops/cost_optimizer.py +484 -618
  75. runbooks/finops/cost_processor.py +332 -214
  76. runbooks/finops/dashboard_runner.py +1006 -172
  77. runbooks/finops/ebs_cost_optimizer.py +991 -657
  78. runbooks/finops/elastic_ip_optimizer.py +317 -257
  79. runbooks/finops/enhanced_mcp_integration.py +340 -0
  80. runbooks/finops/enhanced_progress.py +32 -29
  81. runbooks/finops/enhanced_trend_visualization.py +3 -2
  82. runbooks/finops/enterprise_wrappers.py +223 -285
  83. runbooks/finops/executive_export.py +203 -160
  84. runbooks/finops/helpers.py +130 -288
  85. runbooks/finops/iam_guidance.py +1 -1
  86. runbooks/finops/infrastructure/__init__.py +80 -0
  87. runbooks/finops/infrastructure/commands.py +506 -0
  88. runbooks/finops/infrastructure/load_balancer_optimizer.py +866 -0
  89. runbooks/finops/infrastructure/vpc_endpoint_optimizer.py +832 -0
  90. runbooks/finops/markdown_exporter.py +337 -174
  91. runbooks/finops/mcp_validator.py +1952 -0
  92. runbooks/finops/nat_gateway_optimizer.py +1512 -481
  93. runbooks/finops/network_cost_optimizer.py +657 -587
  94. runbooks/finops/notebook_utils.py +226 -188
  95. runbooks/finops/optimization_engine.py +1136 -0
  96. runbooks/finops/optimizer.py +19 -23
  97. runbooks/finops/rds_snapshot_optimizer.py +367 -411
  98. runbooks/finops/reservation_optimizer.py +427 -363
  99. runbooks/finops/scenario_cli_integration.py +64 -65
  100. runbooks/finops/scenarios.py +1277 -438
  101. runbooks/finops/schemas.py +218 -182
  102. runbooks/finops/snapshot_manager.py +2289 -0
  103. runbooks/finops/types.py +3 -3
  104. runbooks/finops/validation_framework.py +259 -265
  105. runbooks/finops/vpc_cleanup_exporter.py +189 -144
  106. runbooks/finops/vpc_cleanup_optimizer.py +591 -573
  107. runbooks/finops/workspaces_analyzer.py +171 -182
  108. runbooks/integration/__init__.py +89 -0
  109. runbooks/integration/mcp_integration.py +1920 -0
  110. runbooks/inventory/CLAUDE.md +816 -0
  111. runbooks/inventory/__init__.py +2 -2
  112. runbooks/inventory/cloud_foundations_integration.py +144 -149
  113. runbooks/inventory/collectors/aws_comprehensive.py +1 -1
  114. runbooks/inventory/collectors/aws_networking.py +109 -99
  115. runbooks/inventory/collectors/base.py +4 -0
  116. runbooks/inventory/core/collector.py +495 -313
  117. runbooks/inventory/drift_detection_cli.py +69 -96
  118. runbooks/inventory/inventory_mcp_cli.py +48 -46
  119. runbooks/inventory/list_rds_snapshots_aggregator.py +192 -208
  120. runbooks/inventory/mcp_inventory_validator.py +549 -465
  121. runbooks/inventory/mcp_vpc_validator.py +359 -442
  122. runbooks/inventory/organizations_discovery.py +55 -51
  123. runbooks/inventory/rich_inventory_display.py +33 -32
  124. runbooks/inventory/unified_validation_engine.py +278 -251
  125. runbooks/inventory/vpc_analyzer.py +732 -695
  126. runbooks/inventory/vpc_architecture_validator.py +293 -348
  127. runbooks/inventory/vpc_dependency_analyzer.py +382 -378
  128. runbooks/inventory/vpc_flow_analyzer.py +1 -1
  129. runbooks/main.py +49 -34
  130. runbooks/main_final.py +91 -60
  131. runbooks/main_minimal.py +22 -10
  132. runbooks/main_optimized.py +131 -100
  133. runbooks/main_ultra_minimal.py +7 -2
  134. runbooks/mcp/__init__.py +36 -0
  135. runbooks/mcp/integration.py +679 -0
  136. runbooks/monitoring/performance_monitor.py +9 -4
  137. runbooks/operate/dynamodb_operations.py +3 -1
  138. runbooks/operate/ec2_operations.py +145 -137
  139. runbooks/operate/iam_operations.py +146 -152
  140. runbooks/operate/networking_cost_heatmap.py +29 -8
  141. runbooks/operate/rds_operations.py +223 -254
  142. runbooks/operate/s3_operations.py +107 -118
  143. runbooks/operate/vpc_operations.py +646 -616
  144. runbooks/remediation/base.py +1 -1
  145. runbooks/remediation/commons.py +10 -7
  146. runbooks/remediation/commvault_ec2_analysis.py +70 -66
  147. runbooks/remediation/ec2_unattached_ebs_volumes.py +1 -0
  148. runbooks/remediation/multi_account.py +24 -21
  149. runbooks/remediation/rds_snapshot_list.py +86 -60
  150. runbooks/remediation/remediation_cli.py +92 -146
  151. runbooks/remediation/universal_account_discovery.py +83 -79
  152. runbooks/remediation/workspaces_list.py +46 -41
  153. runbooks/security/__init__.py +19 -0
  154. runbooks/security/assessment_runner.py +1150 -0
  155. runbooks/security/baseline_checker.py +812 -0
  156. runbooks/security/cloudops_automation_security_validator.py +509 -535
  157. runbooks/security/compliance_automation_engine.py +17 -17
  158. runbooks/security/config/__init__.py +2 -2
  159. runbooks/security/config/compliance_config.py +50 -50
  160. runbooks/security/config_template_generator.py +63 -76
  161. runbooks/security/enterprise_security_framework.py +1 -1
  162. runbooks/security/executive_security_dashboard.py +519 -508
  163. runbooks/security/multi_account_security_controls.py +959 -1210
  164. runbooks/security/real_time_security_monitor.py +422 -444
  165. runbooks/security/security_baseline_tester.py +1 -1
  166. runbooks/security/security_cli.py +143 -112
  167. runbooks/security/test_2way_validation.py +439 -0
  168. runbooks/security/two_way_validation_framework.py +852 -0
  169. runbooks/sre/production_monitoring_framework.py +167 -177
  170. runbooks/tdd/__init__.py +15 -0
  171. runbooks/tdd/cli.py +1071 -0
  172. runbooks/utils/__init__.py +14 -17
  173. runbooks/utils/logger.py +7 -2
  174. runbooks/utils/version_validator.py +50 -47
  175. runbooks/validation/__init__.py +6 -6
  176. runbooks/validation/cli.py +9 -3
  177. runbooks/validation/comprehensive_2way_validator.py +745 -704
  178. runbooks/validation/mcp_validator.py +906 -228
  179. runbooks/validation/terraform_citations_validator.py +104 -115
  180. runbooks/validation/terraform_drift_detector.py +447 -451
  181. runbooks/vpc/README.md +617 -0
  182. runbooks/vpc/__init__.py +8 -1
  183. runbooks/vpc/analyzer.py +577 -0
  184. runbooks/vpc/cleanup_wrapper.py +476 -413
  185. runbooks/vpc/cli_cloudtrail_commands.py +339 -0
  186. runbooks/vpc/cli_mcp_validation_commands.py +480 -0
  187. runbooks/vpc/cloudtrail_audit_integration.py +717 -0
  188. runbooks/vpc/config.py +92 -97
  189. runbooks/vpc/cost_engine.py +411 -148
  190. runbooks/vpc/cost_explorer_integration.py +553 -0
  191. runbooks/vpc/cross_account_session.py +101 -106
  192. runbooks/vpc/enhanced_mcp_validation.py +917 -0
  193. runbooks/vpc/eni_gate_validator.py +961 -0
  194. runbooks/vpc/heatmap_engine.py +185 -160
  195. runbooks/vpc/mcp_no_eni_validator.py +680 -639
  196. runbooks/vpc/nat_gateway_optimizer.py +358 -0
  197. runbooks/vpc/networking_wrapper.py +15 -8
  198. runbooks/vpc/pdca_remediation_planner.py +528 -0
  199. runbooks/vpc/performance_optimized_analyzer.py +219 -231
  200. runbooks/vpc/runbooks_adapter.py +1167 -241
  201. runbooks/vpc/tdd_red_phase_stubs.py +601 -0
  202. runbooks/vpc/test_data_loader.py +358 -0
  203. runbooks/vpc/tests/conftest.py +314 -4
  204. runbooks/vpc/tests/test_cleanup_framework.py +1022 -0
  205. runbooks/vpc/tests/test_cost_engine.py +0 -2
  206. runbooks/vpc/topology_generator.py +326 -0
  207. runbooks/vpc/unified_scenarios.py +1297 -1124
  208. runbooks/vpc/vpc_cleanup_integration.py +1943 -1115
  209. runbooks-1.1.5.dist-info/METADATA +328 -0
  210. {runbooks-1.1.4.dist-info → runbooks-1.1.5.dist-info}/RECORD +214 -193
  211. runbooks/finops/README.md +0 -414
  212. runbooks/finops/accuracy_cross_validator.py +0 -647
  213. runbooks/finops/business_cases.py +0 -950
  214. runbooks/finops/dashboard_router.py +0 -922
  215. runbooks/finops/ebs_optimizer.py +0 -973
  216. runbooks/finops/embedded_mcp_validator.py +0 -1629
  217. runbooks/finops/enhanced_dashboard_runner.py +0 -527
  218. runbooks/finops/finops_dashboard.py +0 -584
  219. runbooks/finops/finops_scenarios.py +0 -1218
  220. runbooks/finops/legacy_migration.py +0 -730
  221. runbooks/finops/multi_dashboard.py +0 -1519
  222. runbooks/finops/single_dashboard.py +0 -1113
  223. runbooks/finops/unlimited_scenarios.py +0 -393
  224. runbooks-1.1.4.dist-info/METADATA +0 -800
  225. {runbooks-1.1.4.dist-info → runbooks-1.1.5.dist-info}/WHEEL +0 -0
  226. {runbooks-1.1.4.dist-info → runbooks-1.1.5.dist-info}/entry_points.txt +0 -0
  227. {runbooks-1.1.4.dist-info → runbooks-1.1.5.dist-info}/licenses/LICENSE +0 -0
  228. {runbooks-1.1.4.dist-info → runbooks-1.1.5.dist-info}/top_level.txt +0 -0
@@ -25,30 +25,40 @@ Strategic Alignment:
25
25
  import asyncio
26
26
  import logging
27
27
  import time
28
- from datetime import datetime, timedelta
29
- from typing import Any, Dict, List, Optional, Tuple
30
28
  from dataclasses import dataclass
29
+ from datetime import datetime, timedelta
31
30
  from enum import Enum
31
+ from typing import Any, Dict, List, Optional, Tuple
32
32
 
33
33
  import boto3
34
34
  import click
35
35
  from botocore.exceptions import ClientError, NoCredentialsError
36
36
  from pydantic import BaseModel, Field
37
37
 
38
+ from ..common.profile_utils import get_profile_for_operation
38
39
  from ..common.rich_utils import (
39
- console, print_header, print_success, print_error, print_warning, print_info,
40
- create_table, create_progress_bar, format_cost, create_panel, STATUS_INDICATORS
40
+ STATUS_INDICATORS,
41
+ console,
42
+ create_panel,
43
+ create_progress_bar,
44
+ create_table,
45
+ format_cost,
46
+ print_error,
47
+ print_header,
48
+ print_info,
49
+ print_success,
50
+ print_warning,
41
51
  )
42
- from .embedded_mcp_validator import EmbeddedMCPValidator
43
- from ..common.profile_utils import get_profile_for_operation
52
+ from .mcp_validator import EmbeddedMCPValidator
44
53
 
45
54
  logger = logging.getLogger(__name__)
46
55
 
47
56
 
48
57
  class NetworkService(str, Enum):
49
58
  """Network services for cost optimization."""
59
+
50
60
  NAT_GATEWAY = "nat_gateway"
51
- ELASTIC_IP = "elastic_ip"
61
+ ELASTIC_IP = "elastic_ip"
52
62
  LOAD_BALANCER = "load_balancer"
53
63
  TRANSIT_GATEWAY = "transit_gateway"
54
64
  VPC_ENDPOINT = "vpc_endpoint"
@@ -56,14 +66,16 @@ class NetworkService(str, Enum):
56
66
 
57
67
  class LoadBalancerType(str, Enum):
58
68
  """Load balancer types."""
69
+
59
70
  APPLICATION = "application" # ALB
60
- NETWORK = "network" # NLB
61
- CLASSIC = "classic" # CLB
62
- GATEWAY = "gateway" # GWLB
71
+ NETWORK = "network" # NLB
72
+ CLASSIC = "classic" # CLB
73
+ GATEWAY = "gateway" # GWLB
63
74
 
64
75
 
65
76
  class NetworkResourceDetails(BaseModel):
66
77
  """Network resource details from AWS APIs."""
78
+
67
79
  resource_id: str
68
80
  resource_type: str
69
81
  service: NetworkService
@@ -73,22 +85,22 @@ class NetworkResourceDetails(BaseModel):
73
85
  subnet_id: Optional[str] = None
74
86
  state: str = "available"
75
87
  create_time: Optional[datetime] = None
76
-
88
+
77
89
  # Network-specific attributes
78
90
  public_ip: Optional[str] = None
79
91
  private_ip: Optional[str] = None
80
92
  dns_name: Optional[str] = None
81
93
  load_balancer_type: Optional[LoadBalancerType] = None
82
94
  target_count: int = 0
83
-
95
+
84
96
  # Cost attributes
85
97
  hourly_cost: float = 0.0
86
98
  data_processing_cost: float = 0.0 # Per GB
87
99
  monthly_cost: float = 0.0
88
100
  annual_cost: float = 0.0
89
-
101
+
90
102
  tags: Dict[str, str] = Field(default_factory=dict)
91
-
103
+
92
104
  # Usage and dependency attributes
93
105
  has_dependencies: bool = False
94
106
  dependency_score: float = 0.0
@@ -97,25 +109,26 @@ class NetworkResourceDetails(BaseModel):
97
109
 
98
110
  class NetworkUsageMetrics(BaseModel):
99
111
  """Network resource usage metrics from CloudWatch."""
112
+
100
113
  resource_id: str
101
114
  region: str
102
115
  service: NetworkService
103
-
116
+
104
117
  # Common network metrics
105
118
  active_connections: float = 0.0
106
119
  bytes_processed: float = 0.0
107
120
  request_count: float = 0.0
108
-
121
+
109
122
  # NAT Gateway specific
110
123
  bytes_in_from_destination: float = 0.0
111
124
  bytes_out_to_destination: float = 0.0
112
125
  packet_drop_count: float = 0.0
113
-
126
+
114
127
  # Load Balancer specific
115
128
  target_response_time: float = 0.0
116
129
  healthy_targets: int = 0
117
130
  unhealthy_targets: int = 0
118
-
131
+
119
132
  # Analysis results
120
133
  analysis_period_days: int = 7
121
134
  is_used: bool = True
@@ -125,24 +138,25 @@ class NetworkUsageMetrics(BaseModel):
125
138
 
126
139
  class NetworkOptimizationResult(BaseModel):
127
140
  """Network resource optimization analysis results."""
141
+
128
142
  resource_id: str
129
143
  region: str
130
144
  service: NetworkService
131
145
  resource_type: str
132
146
  current_state: str
133
147
  usage_metrics: Optional[NetworkUsageMetrics] = None
134
-
148
+
135
149
  # Cost analysis
136
150
  current_monthly_cost: float = 0.0
137
151
  current_annual_cost: float = 0.0
138
152
  data_processing_monthly_cost: float = 0.0
139
153
  data_processing_annual_cost: float = 0.0
140
-
154
+
141
155
  # Optimization strategies
142
156
  optimization_recommendation: str = "retain" # retain, decommission, rightsize, consolidate
143
157
  risk_level: str = "low" # low, medium, high
144
158
  business_impact: str = "minimal"
145
-
159
+
146
160
  # Savings potential
147
161
  infrastructure_monthly_savings: float = 0.0
148
162
  infrastructure_annual_savings: float = 0.0
@@ -150,13 +164,13 @@ class NetworkOptimizationResult(BaseModel):
150
164
  data_transfer_annual_savings: float = 0.0
151
165
  total_monthly_savings: float = 0.0
152
166
  total_annual_savings: float = 0.0
153
-
167
+
154
168
  # Dependencies and safety
155
169
  route_table_dependencies: List[str] = Field(default_factory=list)
156
170
  dns_dependencies: List[str] = Field(default_factory=list)
157
171
  application_dependencies: List[str] = Field(default_factory=list)
158
172
  dependency_risk_score: float = 0.0
159
-
173
+
160
174
  # Alternative solutions
161
175
  alternative_solution: Optional[str] = None
162
176
  alternative_monthly_cost: float = 0.0
@@ -165,9 +179,10 @@ class NetworkOptimizationResult(BaseModel):
165
179
 
166
180
  class NetworkCostOptimizerResults(BaseModel):
167
181
  """Complete network cost optimization analysis results."""
182
+
168
183
  analyzed_services: List[NetworkService] = Field(default_factory=list)
169
184
  analyzed_regions: List[str] = Field(default_factory=list)
170
-
185
+
171
186
  # Resource summary
172
187
  total_network_resources: int = 0
173
188
  nat_gateways: int = 0
@@ -175,7 +190,7 @@ class NetworkCostOptimizerResults(BaseModel):
175
190
  load_balancers: int = 0
176
191
  transit_gateways: int = 0
177
192
  vpc_endpoints: int = 0
178
-
193
+
179
194
  # Cost summary
180
195
  total_monthly_infrastructure_cost: float = 0.0
181
196
  total_annual_infrastructure_cost: float = 0.0
@@ -183,7 +198,7 @@ class NetworkCostOptimizerResults(BaseModel):
183
198
  total_annual_data_processing_cost: float = 0.0
184
199
  total_monthly_cost: float = 0.0
185
200
  total_annual_cost: float = 0.0
186
-
201
+
187
202
  # Savings breakdown
188
203
  infrastructure_monthly_savings: float = 0.0
189
204
  infrastructure_annual_savings: float = 0.0
@@ -191,10 +206,10 @@ class NetworkCostOptimizerResults(BaseModel):
191
206
  data_transfer_annual_savings: float = 0.0
192
207
  total_monthly_savings: float = 0.0
193
208
  total_annual_savings: float = 0.0
194
-
209
+
195
210
  # Optimization results
196
211
  optimization_results: List[NetworkOptimizationResult] = Field(default_factory=list)
197
-
212
+
198
213
  execution_time_seconds: float = 0.0
199
214
  mcp_validation_accuracy: float = 0.0
200
215
  analysis_timestamp: datetime = Field(default_factory=datetime.now)
@@ -203,7 +218,7 @@ class NetworkCostOptimizerResults(BaseModel):
203
218
  class NetworkCostOptimizer:
204
219
  """
205
220
  Network Cost Optimization Engine - Enterprise FinOps Network Analysis Platform
206
-
221
+
207
222
  Following $132,720+ methodology with proven FinOps patterns targeting $2.4M-$7.3M annual savings:
208
223
  - Multi-service network resource discovery and analysis
209
224
  - CloudWatch metrics integration for usage validation and rightsizing
@@ -213,136 +228,149 @@ class NetworkCostOptimizer:
213
228
  - Evidence generation for Manager/Financial/CTO executive reporting
214
229
  - Business-focused network optimization strategy for enterprise presentation
215
230
  """
216
-
231
+
217
232
  def __init__(self, profile_name: Optional[str] = None, regions: Optional[List[str]] = None):
218
233
  """Initialize network cost optimizer with enterprise profile support."""
219
234
  self.profile_name = profile_name
220
- self.regions = regions or ['us-east-1', 'us-west-2', 'eu-west-1']
221
-
235
+ self.regions = regions or ["us-east-1", "us-west-2", "eu-west-1"]
236
+
222
237
  # Initialize AWS session with profile priority system
223
- self.session = boto3.Session(
224
- profile_name=get_profile_for_operation("operational", profile_name)
225
- )
226
-
238
+ self.session = boto3.Session(profile_name=get_profile_for_operation("operational", profile_name))
239
+
227
240
  # Network service pricing (per hour, as of 2024)
228
241
  self.network_pricing = {
229
242
  NetworkService.NAT_GATEWAY: {
230
- 'hourly_cost': 0.045, # $0.045/hour
231
- 'data_processing_cost': 0.045 # $0.045/GB
243
+ "hourly_cost": 0.045, # $0.045/hour
244
+ "data_processing_cost": 0.045, # $0.045/GB
232
245
  },
233
246
  NetworkService.ELASTIC_IP: {
234
- 'monthly_cost_unattached': 3.65 # $3.65/month if unattached
247
+ "monthly_cost_unattached": 3.65 # $3.65/month if unattached
235
248
  },
236
249
  NetworkService.LOAD_BALANCER: {
237
250
  LoadBalancerType.APPLICATION: {
238
- 'hourly_cost': 0.0225, # $0.0225/hour
239
- 'lcu_cost': 0.008 # $0.008/LCU hour
251
+ "hourly_cost": 0.0225, # $0.0225/hour
252
+ "lcu_cost": 0.008, # $0.008/LCU hour
240
253
  },
241
254
  LoadBalancerType.NETWORK: {
242
- 'hourly_cost': 0.0225, # $0.0225/hour
243
- 'nlcu_cost': 0.006 # $0.006/NLCU hour
255
+ "hourly_cost": 0.0225, # $0.0225/hour
256
+ "nlcu_cost": 0.006, # $0.006/NLCU hour
244
257
  },
245
258
  LoadBalancerType.CLASSIC: {
246
- 'hourly_cost': 0.025, # $0.025/hour
247
- 'data_cost': 0.008 # $0.008/GB
248
- }
259
+ "hourly_cost": 0.025, # $0.025/hour
260
+ "data_cost": 0.008, # $0.008/GB
261
+ },
249
262
  },
250
263
  NetworkService.TRANSIT_GATEWAY: {
251
- 'hourly_cost': 0.05, # $0.05/hour attachment
252
- 'data_processing_cost': 0.02 # $0.02/GB
264
+ "hourly_cost": 0.05, # $0.05/hour attachment
265
+ "data_processing_cost": 0.02, # $0.02/GB
253
266
  },
254
267
  NetworkService.VPC_ENDPOINT: {
255
- 'hourly_cost': 0.01, # $0.01/hour per AZ
256
- 'data_processing_cost': 0.01 # $0.01/GB
257
- }
268
+ "hourly_cost": 0.01, # $0.01/hour per AZ
269
+ "data_processing_cost": 0.01, # $0.01/GB
270
+ },
258
271
  }
259
-
272
+
260
273
  # Usage thresholds for optimization recommendations
261
- self.low_usage_threshold_connections = 10 # Active connections per day
262
- self.low_usage_threshold_bytes = 1_000_000 # 1MB per day
263
- self.analysis_period_days = 14 # CloudWatch analysis period
264
-
265
- async def analyze_network_costs(self, services: List[NetworkService] = None, dry_run: bool = True) -> NetworkCostOptimizerResults:
274
+ self.low_usage_threshold_connections = 10 # Active connections per day
275
+ self.low_usage_threshold_bytes = 1_000_000 # 1MB per day
276
+ self.analysis_period_days = 14 # CloudWatch analysis period
277
+
278
+ async def analyze_network_costs(
279
+ self, services: List[NetworkService] = None, dry_run: bool = True
280
+ ) -> NetworkCostOptimizerResults:
266
281
  """
267
282
  Comprehensive network cost optimization analysis.
268
-
283
+
269
284
  Args:
270
285
  services: List of network services to analyze (None = all services)
271
286
  dry_run: Safety mode - READ-ONLY analysis only
272
-
287
+
273
288
  Returns:
274
289
  Complete analysis results with optimization recommendations
275
290
  """
276
291
  print_header("Network Cost Optimization Engine", "Enterprise Multi-Service Network Analysis Platform v1.0")
277
-
292
+
278
293
  if not dry_run:
279
294
  print_warning("⚠️ Dry-run disabled - This optimizer is READ-ONLY analysis only")
280
295
  print_info("All network operations require manual execution after review")
281
-
296
+
282
297
  analysis_start_time = time.time()
283
298
  services_to_analyze = services or [
284
299
  NetworkService.NAT_GATEWAY,
285
300
  NetworkService.ELASTIC_IP,
286
301
  NetworkService.LOAD_BALANCER,
287
302
  NetworkService.TRANSIT_GATEWAY,
288
- NetworkService.VPC_ENDPOINT
303
+ NetworkService.VPC_ENDPOINT,
289
304
  ]
290
-
305
+
291
306
  try:
292
307
  with create_progress_bar() as progress:
293
308
  # Step 1: Multi-service network resource discovery
294
- discovery_task = progress.add_task("Discovering network resources...",
295
- total=len(services_to_analyze) * len(self.regions))
296
- network_resources = await self._discover_network_resources_multi_service(services_to_analyze, progress, discovery_task)
297
-
309
+ discovery_task = progress.add_task(
310
+ "Discovering network resources...", total=len(services_to_analyze) * len(self.regions)
311
+ )
312
+ network_resources = await self._discover_network_resources_multi_service(
313
+ services_to_analyze, progress, discovery_task
314
+ )
315
+
298
316
  if not network_resources:
299
317
  print_warning("No network resources found in specified regions")
300
318
  return NetworkCostOptimizerResults(
301
319
  analyzed_services=services_to_analyze,
302
320
  analyzed_regions=self.regions,
303
321
  analysis_timestamp=datetime.now(),
304
- execution_time_seconds=time.time() - analysis_start_time
322
+ execution_time_seconds=time.time() - analysis_start_time,
305
323
  )
306
-
324
+
307
325
  # Step 2: Usage metrics analysis via CloudWatch
308
326
  metrics_task = progress.add_task("Analyzing usage metrics...", total=len(network_resources))
309
327
  usage_metrics = await self._analyze_network_usage_metrics(network_resources, progress, metrics_task)
310
-
328
+
311
329
  # Step 3: Dependency analysis for safety assessment
312
330
  dependency_task = progress.add_task("Analyzing dependencies...", total=len(network_resources))
313
- dependency_analysis = await self._analyze_network_dependencies(network_resources, progress, dependency_task)
314
-
331
+ dependency_analysis = await self._analyze_network_dependencies(
332
+ network_resources, progress, dependency_task
333
+ )
334
+
315
335
  # Step 4: Cost calculation and pricing analysis
316
336
  costing_task = progress.add_task("Calculating costs...", total=len(network_resources))
317
- cost_analysis = await self._calculate_network_costs(network_resources, usage_metrics, progress, costing_task)
318
-
337
+ cost_analysis = await self._calculate_network_costs(
338
+ network_resources, usage_metrics, progress, costing_task
339
+ )
340
+
319
341
  # Step 5: Comprehensive optimization analysis
320
- optimization_task = progress.add_task("Calculating optimization potential...", total=len(network_resources))
342
+ optimization_task = progress.add_task(
343
+ "Calculating optimization potential...", total=len(network_resources)
344
+ )
321
345
  optimization_results = await self._calculate_network_optimization_recommendations(
322
346
  network_resources, usage_metrics, dependency_analysis, cost_analysis, progress, optimization_task
323
347
  )
324
-
348
+
325
349
  # Step 6: MCP validation
326
350
  validation_task = progress.add_task("MCP validation...", total=1)
327
351
  mcp_accuracy = await self._validate_with_mcp(optimization_results, progress, validation_task)
328
-
352
+
329
353
  # Compile comprehensive results
330
- results = self._compile_results(network_resources, optimization_results, mcp_accuracy, analysis_start_time, services_to_analyze)
331
-
354
+ results = self._compile_results(
355
+ network_resources, optimization_results, mcp_accuracy, analysis_start_time, services_to_analyze
356
+ )
357
+
332
358
  # Display executive summary
333
359
  self._display_executive_summary(results)
334
-
360
+
335
361
  return results
336
-
362
+
337
363
  except Exception as e:
338
364
  print_error(f"Network cost optimization analysis failed: {e}")
339
365
  logger.error(f"Network analysis error: {e}", exc_info=True)
340
366
  raise
341
-
342
- async def _discover_network_resources_multi_service(self, services: List[NetworkService], progress, task_id) -> List[NetworkResourceDetails]:
367
+
368
+ async def _discover_network_resources_multi_service(
369
+ self, services: List[NetworkService], progress, task_id
370
+ ) -> List[NetworkResourceDetails]:
343
371
  """Discover network resources across multiple services and regions."""
344
372
  network_resources = []
345
-
373
+
346
374
  for service in services:
347
375
  for region in self.regions:
348
376
  try:
@@ -361,308 +389,332 @@ class NetworkCostOptimizer:
361
389
  elif service == NetworkService.VPC_ENDPOINT:
362
390
  resources = await self._discover_vpc_endpoints(region)
363
391
  network_resources.extend(resources)
364
-
392
+
365
393
  service_resources = [r for r in network_resources if r.region == region and r.service == service]
366
394
  print_info(f"Service {service.value} in {region}: {len(service_resources)} resources discovered")
367
-
395
+
368
396
  except ClientError as e:
369
397
  print_warning(f"Service {service.value} in {region}: Access denied - {e.response['Error']['Code']}")
370
398
  except Exception as e:
371
399
  print_error(f"Service {service.value} in {region}: Discovery error - {str(e)}")
372
-
400
+
373
401
  progress.advance(task_id)
374
-
402
+
375
403
  return network_resources
376
-
404
+
377
405
  async def _discover_nat_gateways(self, region: str) -> List[NetworkResourceDetails]:
378
406
  """Discover NAT Gateways for cost analysis."""
379
407
  resources = []
380
-
408
+
381
409
  try:
382
- ec2_client = self.session.client('ec2', region_name=region)
383
-
410
+ ec2_client = self.session.client("ec2", region_name=region)
411
+
384
412
  response = ec2_client.describe_nat_gateways()
385
- for nat_gateway in response.get('NatGateways', []):
413
+ for nat_gateway in response.get("NatGateways", []):
386
414
  # Skip deleted NAT Gateways
387
- if nat_gateway.get('State') == 'deleted':
415
+ if nat_gateway.get("State") == "deleted":
388
416
  continue
389
-
390
- tags = {tag['Key']: tag['Value'] for tag in nat_gateway.get('Tags', [])}
391
-
417
+
418
+ tags = {tag["Key"]: tag["Value"] for tag in nat_gateway.get("Tags", [])}
419
+
392
420
  # Get NAT Gateway addresses
393
421
  public_ip = None
394
422
  private_ip = None
395
- for address in nat_gateway.get('NatGatewayAddresses', []):
396
- if address.get('PublicIp'):
397
- public_ip = address['PublicIp']
398
- if address.get('PrivateIp'):
399
- private_ip = address['PrivateIp']
400
-
423
+ for address in nat_gateway.get("NatGatewayAddresses", []):
424
+ if address.get("PublicIp"):
425
+ public_ip = address["PublicIp"]
426
+ if address.get("PrivateIp"):
427
+ private_ip = address["PrivateIp"]
428
+
401
429
  pricing = self.network_pricing[NetworkService.NAT_GATEWAY]
402
- hourly_cost = pricing['hourly_cost']
430
+ hourly_cost = pricing["hourly_cost"]
403
431
  monthly_cost = hourly_cost * 24 * 30.44
404
432
  annual_cost = hourly_cost * 24 * 365
405
-
406
- resources.append(NetworkResourceDetails(
407
- resource_id=nat_gateway['NatGatewayId'],
408
- resource_type='NAT Gateway',
409
- service=NetworkService.NAT_GATEWAY,
410
- region=region,
411
- availability_zone=nat_gateway.get('SubnetId'), # Subnet implies AZ
412
- vpc_id=nat_gateway.get('VpcId'),
413
- subnet_id=nat_gateway.get('SubnetId'),
414
- state=nat_gateway.get('State'),
415
- create_time=nat_gateway.get('CreateTime'),
416
- public_ip=public_ip,
417
- private_ip=private_ip,
418
- hourly_cost=hourly_cost,
419
- data_processing_cost=pricing['data_processing_cost'],
420
- monthly_cost=monthly_cost,
421
- annual_cost=annual_cost,
422
- tags=tags
423
- ))
424
-
433
+
434
+ resources.append(
435
+ NetworkResourceDetails(
436
+ resource_id=nat_gateway["NatGatewayId"],
437
+ resource_type="NAT Gateway",
438
+ service=NetworkService.NAT_GATEWAY,
439
+ region=region,
440
+ availability_zone=nat_gateway.get("SubnetId"), # Subnet implies AZ
441
+ vpc_id=nat_gateway.get("VpcId"),
442
+ subnet_id=nat_gateway.get("SubnetId"),
443
+ state=nat_gateway.get("State"),
444
+ create_time=nat_gateway.get("CreateTime"),
445
+ public_ip=public_ip,
446
+ private_ip=private_ip,
447
+ hourly_cost=hourly_cost,
448
+ data_processing_cost=pricing["data_processing_cost"],
449
+ monthly_cost=monthly_cost,
450
+ annual_cost=annual_cost,
451
+ tags=tags,
452
+ )
453
+ )
454
+
425
455
  except Exception as e:
426
456
  logger.warning(f"NAT Gateway discovery failed in {region}: {e}")
427
-
457
+
428
458
  return resources
429
-
459
+
430
460
  async def _discover_elastic_ips(self, region: str) -> List[NetworkResourceDetails]:
431
461
  """Discover Elastic IPs for cost analysis."""
432
462
  resources = []
433
-
463
+
434
464
  try:
435
- ec2_client = self.session.client('ec2', region_name=region)
436
-
465
+ ec2_client = self.session.client("ec2", region_name=region)
466
+
437
467
  response = ec2_client.describe_addresses()
438
- for eip in response.get('Addresses', []):
439
- tags = {tag['Key']: tag['Value'] for tag in eip.get('Tags', [])}
440
-
468
+ for eip in response.get("Addresses", []):
469
+ tags = {tag["Key"]: tag["Value"] for tag in eip.get("Tags", [])}
470
+
441
471
  # Check if EIP is attached
442
- is_attached = bool(eip.get('InstanceId') or eip.get('NetworkInterfaceId'))
443
-
472
+ is_attached = bool(eip.get("InstanceId") or eip.get("NetworkInterfaceId"))
473
+
444
474
  # Only unattached EIPs have costs
445
- monthly_cost = 0.0 if is_attached else self.network_pricing[NetworkService.ELASTIC_IP]['monthly_cost_unattached']
475
+ monthly_cost = (
476
+ 0.0 if is_attached else self.network_pricing[NetworkService.ELASTIC_IP]["monthly_cost_unattached"]
477
+ )
446
478
  annual_cost = monthly_cost * 12
447
-
448
- resources.append(NetworkResourceDetails(
449
- resource_id=eip['AllocationId'],
450
- resource_type='Elastic IP',
451
- service=NetworkService.ELASTIC_IP,
452
- region=region,
453
- state='attached' if is_attached else 'unattached',
454
- public_ip=eip.get('PublicIp'),
455
- private_ip=eip.get('PrivateIpAddress'),
456
- monthly_cost=monthly_cost,
457
- annual_cost=annual_cost,
458
- tags=tags,
459
- has_dependencies=is_attached
460
- ))
461
-
479
+
480
+ resources.append(
481
+ NetworkResourceDetails(
482
+ resource_id=eip["AllocationId"],
483
+ resource_type="Elastic IP",
484
+ service=NetworkService.ELASTIC_IP,
485
+ region=region,
486
+ state="attached" if is_attached else "unattached",
487
+ public_ip=eip.get("PublicIp"),
488
+ private_ip=eip.get("PrivateIpAddress"),
489
+ monthly_cost=monthly_cost,
490
+ annual_cost=annual_cost,
491
+ tags=tags,
492
+ has_dependencies=is_attached,
493
+ )
494
+ )
495
+
462
496
  except Exception as e:
463
497
  logger.warning(f"Elastic IP discovery failed in {region}: {e}")
464
-
498
+
465
499
  return resources
466
-
500
+
467
501
  async def _discover_load_balancers(self, region: str) -> List[NetworkResourceDetails]:
468
502
  """Discover Load Balancers (ALB, NLB, CLB) for cost analysis."""
469
503
  resources = []
470
-
504
+
471
505
  try:
472
506
  # Application and Network Load Balancers (ELBv2)
473
- elbv2_client = self.session.client('elbv2', region_name=region)
474
-
507
+ elbv2_client = self.session.client("elbv2", region_name=region)
508
+
475
509
  response = elbv2_client.describe_load_balancers()
476
- for lb in response.get('LoadBalancers', []):
510
+ for lb in response.get("LoadBalancers", []):
477
511
  # Skip provisioning or failed load balancers
478
- if lb.get('State', {}).get('Code') not in ['active', 'idle']:
512
+ if lb.get("State", {}).get("Code") not in ["active", "idle"]:
479
513
  continue
480
-
481
- lb_type = LoadBalancerType.APPLICATION if lb.get('Type') == 'application' else LoadBalancerType.NETWORK
482
-
514
+
515
+ lb_type = LoadBalancerType.APPLICATION if lb.get("Type") == "application" else LoadBalancerType.NETWORK
516
+
483
517
  # Get target count
484
518
  target_count = 0
485
519
  try:
486
- target_groups_response = elbv2_client.describe_target_groups(LoadBalancerArn=lb['LoadBalancerArn'])
487
- for tg in target_groups_response.get('TargetGroups', []):
488
- targets_response = elbv2_client.describe_target_health(TargetGroupArn=tg['TargetGroupArn'])
489
- target_count += len(targets_response.get('TargetHealthDescriptions', []))
520
+ target_groups_response = elbv2_client.describe_target_groups(LoadBalancerArn=lb["LoadBalancerArn"])
521
+ for tg in target_groups_response.get("TargetGroups", []):
522
+ targets_response = elbv2_client.describe_target_health(TargetGroupArn=tg["TargetGroupArn"])
523
+ target_count += len(targets_response.get("TargetHealthDescriptions", []))
490
524
  except Exception:
491
525
  pass # Target count is optional
492
-
526
+
493
527
  # Get pricing
494
528
  pricing = self.network_pricing[NetworkService.LOAD_BALANCER][lb_type]
495
- hourly_cost = pricing['hourly_cost']
529
+ hourly_cost = pricing["hourly_cost"]
496
530
  monthly_cost = hourly_cost * 24 * 30.44
497
531
  annual_cost = hourly_cost * 24 * 365
498
-
499
- resources.append(NetworkResourceDetails(
500
- resource_id=lb['LoadBalancerArn'].split('/')[-3] + '/' + lb['LoadBalancerArn'].split('/')[-2] + '/' + lb['LoadBalancerArn'].split('/')[-1],
501
- resource_type=f'{lb_type.value.title()} Load Balancer',
502
- service=NetworkService.LOAD_BALANCER,
503
- region=region,
504
- vpc_id=lb.get('VpcId'),
505
- state=lb.get('State', {}).get('Code', 'unknown'),
506
- create_time=lb.get('CreatedTime'),
507
- dns_name=lb.get('DNSName'),
508
- load_balancer_type=lb_type,
509
- target_count=target_count,
510
- hourly_cost=hourly_cost,
511
- monthly_cost=monthly_cost,
512
- annual_cost=annual_cost,
513
- has_dependencies=target_count > 0
514
- ))
515
-
532
+
533
+ resources.append(
534
+ NetworkResourceDetails(
535
+ resource_id=lb["LoadBalancerArn"].split("/")[-3]
536
+ + "/"
537
+ + lb["LoadBalancerArn"].split("/")[-2]
538
+ + "/"
539
+ + lb["LoadBalancerArn"].split("/")[-1],
540
+ resource_type=f"{lb_type.value.title()} Load Balancer",
541
+ service=NetworkService.LOAD_BALANCER,
542
+ region=region,
543
+ vpc_id=lb.get("VpcId"),
544
+ state=lb.get("State", {}).get("Code", "unknown"),
545
+ create_time=lb.get("CreatedTime"),
546
+ dns_name=lb.get("DNSName"),
547
+ load_balancer_type=lb_type,
548
+ target_count=target_count,
549
+ hourly_cost=hourly_cost,
550
+ monthly_cost=monthly_cost,
551
+ annual_cost=annual_cost,
552
+ has_dependencies=target_count > 0,
553
+ )
554
+ )
555
+
516
556
  # Classic Load Balancers (ELB)
517
- elb_client = self.session.client('elb', region_name=region)
518
-
557
+ elb_client = self.session.client("elb", region_name=region)
558
+
519
559
  response = elb_client.describe_load_balancers()
520
- for lb in response.get('LoadBalancerDescriptions', []):
560
+ for lb in response.get("LoadBalancerDescriptions", []):
521
561
  # Get instance count
522
- instance_count = len(lb.get('Instances', []))
523
-
562
+ instance_count = len(lb.get("Instances", []))
563
+
524
564
  pricing = self.network_pricing[NetworkService.LOAD_BALANCER][LoadBalancerType.CLASSIC]
525
- hourly_cost = pricing['hourly_cost']
565
+ hourly_cost = pricing["hourly_cost"]
526
566
  monthly_cost = hourly_cost * 24 * 30.44
527
567
  annual_cost = hourly_cost * 24 * 365
528
-
529
- resources.append(NetworkResourceDetails(
530
- resource_id=lb['LoadBalancerName'],
531
- resource_type='Classic Load Balancer',
532
- service=NetworkService.LOAD_BALANCER,
533
- region=region,
534
- vpc_id=lb.get('VPCId'),
535
- state='active', # CLBs don't have explicit state
536
- create_time=lb.get('CreatedTime'),
537
- dns_name=lb.get('DNSName'),
538
- load_balancer_type=LoadBalancerType.CLASSIC,
539
- target_count=instance_count,
540
- hourly_cost=hourly_cost,
541
- monthly_cost=monthly_cost,
542
- annual_cost=annual_cost,
543
- has_dependencies=instance_count > 0
544
- ))
545
-
568
+
569
+ resources.append(
570
+ NetworkResourceDetails(
571
+ resource_id=lb["LoadBalancerName"],
572
+ resource_type="Classic Load Balancer",
573
+ service=NetworkService.LOAD_BALANCER,
574
+ region=region,
575
+ vpc_id=lb.get("VPCId"),
576
+ state="active", # CLBs don't have explicit state
577
+ create_time=lb.get("CreatedTime"),
578
+ dns_name=lb.get("DNSName"),
579
+ load_balancer_type=LoadBalancerType.CLASSIC,
580
+ target_count=instance_count,
581
+ hourly_cost=hourly_cost,
582
+ monthly_cost=monthly_cost,
583
+ annual_cost=annual_cost,
584
+ has_dependencies=instance_count > 0,
585
+ )
586
+ )
587
+
546
588
  except Exception as e:
547
589
  logger.warning(f"Load Balancer discovery failed in {region}: {e}")
548
-
590
+
549
591
  return resources
550
-
592
+
551
593
  async def _discover_transit_gateways(self, region: str) -> List[NetworkResourceDetails]:
552
594
  """Discover Transit Gateways for cost analysis."""
553
595
  resources = []
554
-
596
+
555
597
  try:
556
- ec2_client = self.session.client('ec2', region_name=region)
557
-
598
+ ec2_client = self.session.client("ec2", region_name=region)
599
+
558
600
  response = ec2_client.describe_transit_gateways()
559
- for tgw in response.get('TransitGateways', []):
601
+ for tgw in response.get("TransitGateways", []):
560
602
  # Skip deleted TGWs
561
- if tgw.get('State') == 'deleted':
603
+ if tgw.get("State") == "deleted":
562
604
  continue
563
-
564
- tags = {tag['Key']: tag['Value'] for tag in tgw.get('Tags', [])}
565
-
605
+
606
+ tags = {tag["Key"]: tag["Value"] for tag in tgw.get("Tags", [])}
607
+
566
608
  # Get attachment count for dependency analysis
567
609
  attachments_response = ec2_client.describe_transit_gateway_attachments(
568
- Filters=[{'Name': 'transit-gateway-id', 'Values': [tgw['TransitGatewayId']]}]
610
+ Filters=[{"Name": "transit-gateway-id", "Values": [tgw["TransitGatewayId"]]}]
569
611
  )
570
- attachment_count = len(attachments_response.get('TransitGatewayAttachments', []))
571
-
612
+ attachment_count = len(attachments_response.get("TransitGatewayAttachments", []))
613
+
572
614
  pricing = self.network_pricing[NetworkService.TRANSIT_GATEWAY]
573
- hourly_cost = pricing['hourly_cost'] * attachment_count # Cost per attachment
615
+ hourly_cost = pricing["hourly_cost"] * attachment_count # Cost per attachment
574
616
  monthly_cost = hourly_cost * 24 * 30.44
575
617
  annual_cost = hourly_cost * 24 * 365
576
-
577
- resources.append(NetworkResourceDetails(
578
- resource_id=tgw['TransitGatewayId'],
579
- resource_type='Transit Gateway',
580
- service=NetworkService.TRANSIT_GATEWAY,
581
- region=region,
582
- state=tgw.get('State'),
583
- hourly_cost=hourly_cost,
584
- data_processing_cost=pricing['data_processing_cost'],
585
- monthly_cost=monthly_cost,
586
- annual_cost=annual_cost,
587
- tags=tags,
588
- has_dependencies=attachment_count > 0,
589
- dependency_score=min(1.0, attachment_count / 10.0) # Normalize to 0-1
590
- ))
591
-
618
+
619
+ resources.append(
620
+ NetworkResourceDetails(
621
+ resource_id=tgw["TransitGatewayId"],
622
+ resource_type="Transit Gateway",
623
+ service=NetworkService.TRANSIT_GATEWAY,
624
+ region=region,
625
+ state=tgw.get("State"),
626
+ hourly_cost=hourly_cost,
627
+ data_processing_cost=pricing["data_processing_cost"],
628
+ monthly_cost=monthly_cost,
629
+ annual_cost=annual_cost,
630
+ tags=tags,
631
+ has_dependencies=attachment_count > 0,
632
+ dependency_score=min(1.0, attachment_count / 10.0), # Normalize to 0-1
633
+ )
634
+ )
635
+
592
636
  except Exception as e:
593
637
  logger.warning(f"Transit Gateway discovery failed in {region}: {e}")
594
-
638
+
595
639
  return resources
596
-
640
+
597
641
  async def _discover_vpc_endpoints(self, region: str) -> List[NetworkResourceDetails]:
598
642
  """Discover VPC Endpoints for cost analysis."""
599
643
  resources = []
600
-
644
+
601
645
  try:
602
- ec2_client = self.session.client('ec2', region_name=region)
603
-
646
+ ec2_client = self.session.client("ec2", region_name=region)
647
+
604
648
  response = ec2_client.describe_vpc_endpoints()
605
- for vpce in response.get('VpcEndpoints', []):
649
+ for vpce in response.get("VpcEndpoints", []):
606
650
  # Skip deleted endpoints
607
- if vpce.get('State') in ['deleted', 'deleting']:
651
+ if vpce.get("State") in ["deleted", "deleting"]:
608
652
  continue
609
-
610
- tags = {tag['Key']: tag['Value'] for tag in vpce.get('Tags', [])}
611
-
653
+
654
+ tags = {tag["Key"]: tag["Value"] for tag in vpce.get("Tags", [])}
655
+
612
656
  # VPC Endpoint pricing varies by type (Interface vs Gateway)
613
- endpoint_type = vpce.get('VpcEndpointType', 'Interface')
614
-
615
- if endpoint_type == 'Gateway':
657
+ endpoint_type = vpce.get("VpcEndpointType", "Interface")
658
+
659
+ if endpoint_type == "Gateway":
616
660
  # Gateway endpoints are free
617
661
  hourly_cost = 0.0
618
662
  data_processing_cost = 0.0
619
663
  else:
620
664
  # Interface endpoints charge per AZ
621
- az_count = len(vpce.get('SubnetIds', []))
665
+ az_count = len(vpce.get("SubnetIds", []))
622
666
  pricing = self.network_pricing[NetworkService.VPC_ENDPOINT]
623
- hourly_cost = pricing['hourly_cost'] * az_count
624
- data_processing_cost = pricing['data_processing_cost']
625
-
667
+ hourly_cost = pricing["hourly_cost"] * az_count
668
+ data_processing_cost = pricing["data_processing_cost"]
669
+
626
670
  monthly_cost = hourly_cost * 24 * 30.44
627
671
  annual_cost = hourly_cost * 24 * 365
628
-
629
- resources.append(NetworkResourceDetails(
630
- resource_id=vpce['VpcEndpointId'],
631
- resource_type=f'{endpoint_type} VPC Endpoint',
632
- service=NetworkService.VPC_ENDPOINT,
633
- region=region,
634
- vpc_id=vpce.get('VpcId'),
635
- state=vpce.get('State'),
636
- create_time=vpce.get('CreationTimestamp'),
637
- hourly_cost=hourly_cost,
638
- data_processing_cost=data_processing_cost,
639
- monthly_cost=monthly_cost,
640
- annual_cost=annual_cost,
641
- tags=tags,
642
- has_dependencies=True # VPC Endpoints always have VPC dependencies
643
- ))
644
-
672
+
673
+ resources.append(
674
+ NetworkResourceDetails(
675
+ resource_id=vpce["VpcEndpointId"],
676
+ resource_type=f"{endpoint_type} VPC Endpoint",
677
+ service=NetworkService.VPC_ENDPOINT,
678
+ region=region,
679
+ vpc_id=vpce.get("VpcId"),
680
+ state=vpce.get("State"),
681
+ create_time=vpce.get("CreationTimestamp"),
682
+ hourly_cost=hourly_cost,
683
+ data_processing_cost=data_processing_cost,
684
+ monthly_cost=monthly_cost,
685
+ annual_cost=annual_cost,
686
+ tags=tags,
687
+ has_dependencies=True, # VPC Endpoints always have VPC dependencies
688
+ )
689
+ )
690
+
645
691
  except Exception as e:
646
692
  logger.warning(f"VPC Endpoint discovery failed in {region}: {e}")
647
-
693
+
648
694
  return resources
649
-
650
- async def _analyze_network_usage_metrics(self, resources: List[NetworkResourceDetails], progress, task_id) -> Dict[str, NetworkUsageMetrics]:
695
+
696
+ async def _analyze_network_usage_metrics(
697
+ self, resources: List[NetworkResourceDetails], progress, task_id
698
+ ) -> Dict[str, NetworkUsageMetrics]:
651
699
  """Analyze network resource usage metrics via CloudWatch."""
652
700
  usage_metrics = {}
653
701
  end_time = datetime.utcnow()
654
702
  start_time = end_time - timedelta(days=self.analysis_period_days)
655
-
703
+
656
704
  for resource in resources:
657
705
  try:
658
- cloudwatch = self.session.client('cloudwatch', region_name=resource.region)
659
-
706
+ cloudwatch = self.session.client("cloudwatch", region_name=resource.region)
707
+
660
708
  if resource.service == NetworkService.NAT_GATEWAY:
661
- metrics = await self._get_nat_gateway_metrics(cloudwatch, resource.resource_id, start_time, end_time)
709
+ metrics = await self._get_nat_gateway_metrics(
710
+ cloudwatch, resource.resource_id, start_time, end_time
711
+ )
662
712
  elif resource.service == NetworkService.LOAD_BALANCER:
663
713
  metrics = await self._get_load_balancer_metrics(cloudwatch, resource, start_time, end_time)
664
714
  elif resource.service == NetworkService.TRANSIT_GATEWAY:
665
- metrics = await self._get_transit_gateway_metrics(cloudwatch, resource.resource_id, start_time, end_time)
715
+ metrics = await self._get_transit_gateway_metrics(
716
+ cloudwatch, resource.resource_id, start_time, end_time
717
+ )
666
718
  else:
667
719
  # For Elastic IPs and VPC Endpoints, create default metrics
668
720
  metrics = NetworkUsageMetrics(
@@ -670,11 +722,11 @@ class NetworkCostOptimizer:
670
722
  region=resource.region,
671
723
  service=resource.service,
672
724
  analysis_period_days=self.analysis_period_days,
673
- usage_score=50.0 # Neutral score
725
+ usage_score=50.0, # Neutral score
674
726
  )
675
-
727
+
676
728
  usage_metrics[resource.resource_id] = metrics
677
-
729
+
678
730
  except Exception as e:
679
731
  print_warning(f"Usage metrics unavailable for {resource.resource_id}: {str(e)}")
680
732
  # Create default metrics
@@ -683,45 +735,54 @@ class NetworkCostOptimizer:
683
735
  region=resource.region,
684
736
  service=resource.service,
685
737
  analysis_period_days=self.analysis_period_days,
686
- usage_score=50.0 # Conservative score
738
+ usage_score=50.0, # Conservative score
687
739
  )
688
-
740
+
689
741
  progress.advance(task_id)
690
-
742
+
691
743
  return usage_metrics
692
-
693
- async def _get_nat_gateway_metrics(self, cloudwatch, nat_gateway_id: str, start_time: datetime, end_time: datetime) -> NetworkUsageMetrics:
744
+
745
+ async def _get_nat_gateway_metrics(
746
+ self, cloudwatch, nat_gateway_id: str, start_time: datetime, end_time: datetime
747
+ ) -> NetworkUsageMetrics:
694
748
  """Get NAT Gateway metrics from CloudWatch."""
695
749
  try:
696
750
  # Get active connections
697
751
  connections_response = cloudwatch.get_metric_statistics(
698
- Namespace='AWS/NATGateway',
699
- MetricName='ActiveConnectionCount',
700
- Dimensions=[{'Name': 'NatGatewayId', 'Value': nat_gateway_id}],
752
+ Namespace="AWS/NATGateway",
753
+ MetricName="ActiveConnectionCount",
754
+ Dimensions=[{"Name": "NatGatewayId", "Value": nat_gateway_id}],
701
755
  StartTime=start_time,
702
756
  EndTime=end_time,
703
757
  Period=86400, # Daily data points
704
- Statistics=['Average']
758
+ Statistics=["Average"],
705
759
  )
706
-
760
+
707
761
  # Get bytes processed
708
762
  bytes_response = cloudwatch.get_metric_statistics(
709
- Namespace='AWS/NATGateway',
710
- MetricName='BytesInFromDestination',
711
- Dimensions=[{'Name': 'NatGatewayId', 'Value': nat_gateway_id}],
763
+ Namespace="AWS/NATGateway",
764
+ MetricName="BytesInFromDestination",
765
+ Dimensions=[{"Name": "NatGatewayId", "Value": nat_gateway_id}],
712
766
  StartTime=start_time,
713
767
  EndTime=end_time,
714
768
  Period=86400,
715
- Statistics=['Sum']
769
+ Statistics=["Sum"],
716
770
  )
717
-
718
- active_connections = sum(dp['Average'] for dp in connections_response.get('Datapoints', []))
719
- bytes_processed = sum(dp['Sum'] for dp in bytes_response.get('Datapoints', []))
720
-
771
+
772
+ active_connections = sum(dp["Average"] for dp in connections_response.get("Datapoints", []))
773
+ bytes_processed = sum(dp["Sum"] for dp in bytes_response.get("Datapoints", []))
774
+
721
775
  # Determine if NAT Gateway is being used
722
- is_used = active_connections > self.low_usage_threshold_connections or bytes_processed > self.low_usage_threshold_bytes
723
- usage_score = min(100, (active_connections / self.low_usage_threshold_connections) * 50 + (bytes_processed / self.low_usage_threshold_bytes) * 50)
724
-
776
+ is_used = (
777
+ active_connections > self.low_usage_threshold_connections
778
+ or bytes_processed > self.low_usage_threshold_bytes
779
+ )
780
+ usage_score = min(
781
+ 100,
782
+ (active_connections / self.low_usage_threshold_connections) * 50
783
+ + (bytes_processed / self.low_usage_threshold_bytes) * 50,
784
+ )
785
+
725
786
  return NetworkUsageMetrics(
726
787
  resource_id=nat_gateway_id,
727
788
  region=cloudwatch.meta.region_name,
@@ -731,9 +792,9 @@ class NetworkCostOptimizer:
731
792
  analysis_period_days=self.analysis_period_days,
732
793
  is_used=is_used,
733
794
  usage_score=usage_score,
734
- is_underutilized=not is_used
795
+ is_underutilized=not is_used,
735
796
  )
736
-
797
+
737
798
  except Exception as e:
738
799
  logger.warning(f"NAT Gateway metrics unavailable for {nat_gateway_id}: {e}")
739
800
  return NetworkUsageMetrics(
@@ -741,41 +802,46 @@ class NetworkCostOptimizer:
741
802
  region=cloudwatch.meta.region_name,
742
803
  service=NetworkService.NAT_GATEWAY,
743
804
  analysis_period_days=self.analysis_period_days,
744
- usage_score=50.0
805
+ usage_score=50.0,
745
806
  )
746
-
747
- async def _get_load_balancer_metrics(self, cloudwatch, resource: NetworkResourceDetails, start_time: datetime, end_time: datetime) -> NetworkUsageMetrics:
807
+
808
+ async def _get_load_balancer_metrics(
809
+ self, cloudwatch, resource: NetworkResourceDetails, start_time: datetime, end_time: datetime
810
+ ) -> NetworkUsageMetrics:
748
811
  """Get Load Balancer metrics from CloudWatch."""
749
812
  try:
750
813
  if resource.load_balancer_type in [LoadBalancerType.APPLICATION, LoadBalancerType.NETWORK]:
751
- namespace = 'AWS/ApplicationELB' if resource.load_balancer_type == LoadBalancerType.APPLICATION else 'AWS/NetworkELB'
752
- dimension_name = 'LoadBalancer'
814
+ namespace = (
815
+ "AWS/ApplicationELB"
816
+ if resource.load_balancer_type == LoadBalancerType.APPLICATION
817
+ else "AWS/NetworkELB"
818
+ )
819
+ dimension_name = "LoadBalancer"
753
820
  dimension_value = resource.resource_id
754
821
  else: # Classic Load Balancer
755
- namespace = 'AWS/ELB'
756
- dimension_name = 'LoadBalancerName'
822
+ namespace = "AWS/ELB"
823
+ dimension_name = "LoadBalancerName"
757
824
  dimension_value = resource.resource_id
758
-
825
+
759
826
  # Get request count
760
827
  request_response = cloudwatch.get_metric_statistics(
761
828
  Namespace=namespace,
762
- MetricName='RequestCount',
763
- Dimensions=[{
764
- 'Name': dimension_name,
765
- 'Value': dimension_value
766
- }],
829
+ MetricName="RequestCount",
830
+ Dimensions=[{"Name": dimension_name, "Value": dimension_value}],
767
831
  StartTime=start_time,
768
832
  EndTime=end_time,
769
833
  Period=86400,
770
- Statistics=['Sum']
834
+ Statistics=["Sum"],
771
835
  )
772
-
773
- request_count = sum(dp['Sum'] for dp in request_response.get('Datapoints', []))
774
-
836
+
837
+ request_count = sum(dp["Sum"] for dp in request_response.get("Datapoints", []))
838
+
775
839
  # Calculate usage score
776
- usage_score = min(100, (request_count / (1000 * self.analysis_period_days)) * 100) # 1000 requests per day baseline
840
+ usage_score = min(
841
+ 100, (request_count / (1000 * self.analysis_period_days)) * 100
842
+ ) # 1000 requests per day baseline
777
843
  is_used = request_count > 100 * self.analysis_period_days # 100 requests per day minimum
778
-
844
+
779
845
  return NetworkUsageMetrics(
780
846
  resource_id=resource.resource_id,
781
847
  region=resource.region,
@@ -785,9 +851,9 @@ class NetworkCostOptimizer:
785
851
  is_used=is_used,
786
852
  usage_score=usage_score,
787
853
  is_underutilized=not is_used,
788
- healthy_targets=resource.target_count
854
+ healthy_targets=resource.target_count,
789
855
  )
790
-
856
+
791
857
  except Exception as e:
792
858
  logger.warning(f"Load Balancer metrics unavailable for {resource.resource_id}: {e}")
793
859
  return NetworkUsageMetrics(
@@ -796,27 +862,31 @@ class NetworkCostOptimizer:
796
862
  service=NetworkService.LOAD_BALANCER,
797
863
  analysis_period_days=self.analysis_period_days,
798
864
  usage_score=50.0,
799
- healthy_targets=resource.target_count
865
+ healthy_targets=resource.target_count,
800
866
  )
801
-
802
- async def _get_transit_gateway_metrics(self, cloudwatch, tgw_id: str, start_time: datetime, end_time: datetime) -> NetworkUsageMetrics:
867
+
868
+ async def _get_transit_gateway_metrics(
869
+ self, cloudwatch, tgw_id: str, start_time: datetime, end_time: datetime
870
+ ) -> NetworkUsageMetrics:
803
871
  """Get Transit Gateway metrics from CloudWatch."""
804
872
  try:
805
873
  # Get bytes transferred
806
874
  bytes_response = cloudwatch.get_metric_statistics(
807
- Namespace='AWS/TransitGateway',
808
- MetricName='BytesIn',
809
- Dimensions=[{'Name': 'TransitGateway', 'Value': tgw_id}],
875
+ Namespace="AWS/TransitGateway",
876
+ MetricName="BytesIn",
877
+ Dimensions=[{"Name": "TransitGateway", "Value": tgw_id}],
810
878
  StartTime=start_time,
811
879
  EndTime=end_time,
812
880
  Period=86400,
813
- Statistics=['Sum']
881
+ Statistics=["Sum"],
814
882
  )
815
-
816
- bytes_transferred = sum(dp['Sum'] for dp in bytes_response.get('Datapoints', []))
817
- usage_score = min(100, (bytes_transferred / (10_000_000 * self.analysis_period_days)) * 100) # 10MB per day baseline
883
+
884
+ bytes_transferred = sum(dp["Sum"] for dp in bytes_response.get("Datapoints", []))
885
+ usage_score = min(
886
+ 100, (bytes_transferred / (10_000_000 * self.analysis_period_days)) * 100
887
+ ) # 10MB per day baseline
818
888
  is_used = bytes_transferred > 1_000_000 * self.analysis_period_days # 1MB per day minimum
819
-
889
+
820
890
  return NetworkUsageMetrics(
821
891
  resource_id=tgw_id,
822
892
  region=cloudwatch.meta.region_name,
@@ -825,9 +895,9 @@ class NetworkCostOptimizer:
825
895
  analysis_period_days=self.analysis_period_days,
826
896
  is_used=is_used,
827
897
  usage_score=usage_score,
828
- is_underutilized=not is_used
898
+ is_underutilized=not is_used,
829
899
  )
830
-
900
+
831
901
  except Exception as e:
832
902
  logger.warning(f"Transit Gateway metrics unavailable for {tgw_id}: {e}")
833
903
  return NetworkUsageMetrics(
@@ -835,313 +905,330 @@ class NetworkCostOptimizer:
835
905
  region=cloudwatch.meta.region_name,
836
906
  service=NetworkService.TRANSIT_GATEWAY,
837
907
  analysis_period_days=self.analysis_period_days,
838
- usage_score=50.0
908
+ usage_score=50.0,
839
909
  )
840
-
841
- async def _analyze_network_dependencies(self, resources: List[NetworkResourceDetails], progress, task_id) -> Dict[str, Dict[str, Any]]:
910
+
911
+ async def _analyze_network_dependencies(
912
+ self, resources: List[NetworkResourceDetails], progress, task_id
913
+ ) -> Dict[str, Dict[str, Any]]:
842
914
  """Analyze network resource dependencies for safe optimization."""
843
915
  dependencies = {}
844
-
916
+
845
917
  for resource in resources:
846
918
  try:
847
919
  resource_dependencies = {
848
- 'route_tables': [],
849
- 'dns_records': [],
850
- 'applications': [],
851
- 'dependency_score': 0.0
920
+ "route_tables": [],
921
+ "dns_records": [],
922
+ "applications": [],
923
+ "dependency_score": 0.0,
852
924
  }
853
-
925
+
854
926
  if resource.service == NetworkService.NAT_GATEWAY:
855
927
  # Check route tables that reference this NAT Gateway
856
928
  route_tables = await self._get_nat_gateway_route_dependencies(resource)
857
- resource_dependencies['route_tables'] = route_tables
858
- resource_dependencies['dependency_score'] = min(1.0, len(route_tables) / 5.0)
859
-
929
+ resource_dependencies["route_tables"] = route_tables
930
+ resource_dependencies["dependency_score"] = min(1.0, len(route_tables) / 5.0)
931
+
860
932
  elif resource.service == NetworkService.ELASTIC_IP:
861
933
  # Check if EIP is referenced in DNS or applications
862
934
  dns_records = await self._get_elastic_ip_dns_dependencies(resource)
863
- resource_dependencies['dns_records'] = dns_records
864
- resource_dependencies['dependency_score'] = 0.8 if resource.has_dependencies else 0.1
865
-
935
+ resource_dependencies["dns_records"] = dns_records
936
+ resource_dependencies["dependency_score"] = 0.8 if resource.has_dependencies else 0.1
937
+
866
938
  elif resource.service == NetworkService.LOAD_BALANCER:
867
939
  # Load balancers with targets have high dependency scores
868
- resource_dependencies['applications'] = [f"Target count: {resource.target_count}"]
869
- resource_dependencies['dependency_score'] = min(1.0, resource.target_count / 10.0) if resource.target_count else 0.0
870
-
940
+ resource_dependencies["applications"] = [f"Target count: {resource.target_count}"]
941
+ resource_dependencies["dependency_score"] = (
942
+ min(1.0, resource.target_count / 10.0) if resource.target_count else 0.0
943
+ )
944
+
871
945
  else:
872
946
  # Default dependency analysis
873
- resource_dependencies['dependency_score'] = 0.5 if resource.has_dependencies else 0.0
874
-
947
+ resource_dependencies["dependency_score"] = 0.5 if resource.has_dependencies else 0.0
948
+
875
949
  dependencies[resource.resource_id] = resource_dependencies
876
-
950
+
877
951
  except Exception as e:
878
952
  print_warning(f"Dependency analysis failed for {resource.resource_id}: {str(e)}")
879
- dependencies[resource.resource_id] = {'dependency_score': 0.5}
880
-
953
+ dependencies[resource.resource_id] = {"dependency_score": 0.5}
954
+
881
955
  progress.advance(task_id)
882
-
956
+
883
957
  return dependencies
884
-
958
+
885
959
  async def _get_nat_gateway_route_dependencies(self, resource: NetworkResourceDetails) -> List[str]:
886
960
  """Get route tables that depend on this NAT Gateway."""
887
961
  route_tables = []
888
-
962
+
889
963
  try:
890
- ec2_client = self.session.client('ec2', region_name=resource.region)
891
-
964
+ ec2_client = self.session.client("ec2", region_name=resource.region)
965
+
892
966
  response = ec2_client.describe_route_tables(
893
- Filters=[
894
- {
895
- 'Name': 'route.nat-gateway-id',
896
- 'Values': [resource.resource_id]
897
- }
898
- ]
967
+ Filters=[{"Name": "route.nat-gateway-id", "Values": [resource.resource_id]}]
899
968
  )
900
-
901
- route_tables = [rt['RouteTableId'] for rt in response.get('RouteTables', [])]
902
-
969
+
970
+ route_tables = [rt["RouteTableId"] for rt in response.get("RouteTables", [])]
971
+
903
972
  except Exception as e:
904
973
  logger.warning(f"Route table dependency check failed for NAT Gateway {resource.resource_id}: {e}")
905
-
974
+
906
975
  return route_tables
907
-
976
+
908
977
  async def _get_elastic_ip_dns_dependencies(self, resource: NetworkResourceDetails) -> List[str]:
909
978
  """Get DNS records that might reference this Elastic IP."""
910
979
  dns_records = []
911
-
980
+
912
981
  # This would require integration with Route 53 or external DNS systems
913
982
  # For now, return empty list - could be enhanced with Route 53 API calls
914
-
983
+
915
984
  return dns_records
916
-
917
- async def _calculate_network_costs(self, resources: List[NetworkResourceDetails],
918
- usage_metrics: Dict[str, NetworkUsageMetrics],
919
- progress, task_id) -> Dict[str, Dict[str, float]]:
985
+
986
+ async def _calculate_network_costs(
987
+ self, resources: List[NetworkResourceDetails], usage_metrics: Dict[str, NetworkUsageMetrics], progress, task_id
988
+ ) -> Dict[str, Dict[str, float]]:
920
989
  """Calculate comprehensive network costs including data processing."""
921
990
  cost_analysis = {}
922
-
991
+
923
992
  for resource in resources:
924
993
  try:
925
994
  metrics = usage_metrics.get(resource.resource_id)
926
-
995
+
927
996
  # Base infrastructure cost is already calculated
928
- infrastructure_cost = {
929
- 'monthly': resource.monthly_cost,
930
- 'annual': resource.annual_cost
931
- }
932
-
997
+ infrastructure_cost = {"monthly": resource.monthly_cost, "annual": resource.annual_cost}
998
+
933
999
  # Calculate data processing costs if applicable
934
- data_processing_cost = {
935
- 'monthly': 0.0,
936
- 'annual': 0.0
937
- }
938
-
939
- if hasattr(resource, 'data_processing_cost') and resource.data_processing_cost > 0 and metrics:
1000
+ data_processing_cost = {"monthly": 0.0, "annual": 0.0}
1001
+
1002
+ if hasattr(resource, "data_processing_cost") and resource.data_processing_cost > 0 and metrics:
940
1003
  # Estimate monthly data processing based on metrics
941
1004
  if resource.service == NetworkService.NAT_GATEWAY and metrics.bytes_processed > 0:
942
1005
  monthly_gb = (metrics.bytes_processed / self.analysis_period_days) * 30.44 / (1024**3)
943
- data_processing_cost['monthly'] = monthly_gb * resource.data_processing_cost
944
- data_processing_cost['annual'] = data_processing_cost['monthly'] * 12
945
-
1006
+ data_processing_cost["monthly"] = monthly_gb * resource.data_processing_cost
1007
+ data_processing_cost["annual"] = data_processing_cost["monthly"] * 12
1008
+
946
1009
  elif resource.service == NetworkService.TRANSIT_GATEWAY and metrics.bytes_processed > 0:
947
1010
  monthly_gb = (metrics.bytes_processed / self.analysis_period_days) * 30.44 / (1024**3)
948
- data_processing_cost['monthly'] = monthly_gb * resource.data_processing_cost
949
- data_processing_cost['annual'] = data_processing_cost['monthly'] * 12
950
-
1011
+ data_processing_cost["monthly"] = monthly_gb * resource.data_processing_cost
1012
+ data_processing_cost["annual"] = data_processing_cost["monthly"] * 12
1013
+
951
1014
  cost_analysis[resource.resource_id] = {
952
- 'infrastructure': infrastructure_cost,
953
- 'data_processing': data_processing_cost,
954
- 'total_monthly': infrastructure_cost['monthly'] + data_processing_cost['monthly'],
955
- 'total_annual': infrastructure_cost['annual'] + data_processing_cost['annual']
1015
+ "infrastructure": infrastructure_cost,
1016
+ "data_processing": data_processing_cost,
1017
+ "total_monthly": infrastructure_cost["monthly"] + data_processing_cost["monthly"],
1018
+ "total_annual": infrastructure_cost["annual"] + data_processing_cost["annual"],
956
1019
  }
957
-
1020
+
958
1021
  except Exception as e:
959
1022
  print_warning(f"Cost calculation failed for {resource.resource_id}: {str(e)}")
960
1023
  cost_analysis[resource.resource_id] = {
961
- 'infrastructure': {'monthly': 0.0, 'annual': 0.0},
962
- 'data_processing': {'monthly': 0.0, 'annual': 0.0},
963
- 'total_monthly': 0.0,
964
- 'total_annual': 0.0
1024
+ "infrastructure": {"monthly": 0.0, "annual": 0.0},
1025
+ "data_processing": {"monthly": 0.0, "annual": 0.0},
1026
+ "total_monthly": 0.0,
1027
+ "total_annual": 0.0,
965
1028
  }
966
-
1029
+
967
1030
  progress.advance(task_id)
968
-
1031
+
969
1032
  return cost_analysis
970
-
971
- async def _calculate_network_optimization_recommendations(self,
972
- resources: List[NetworkResourceDetails],
973
- usage_metrics: Dict[str, NetworkUsageMetrics],
974
- dependencies: Dict[str, Dict[str, Any]],
975
- cost_analysis: Dict[str, Dict[str, float]],
976
- progress, task_id) -> List[NetworkOptimizationResult]:
1033
+
1034
+ async def _calculate_network_optimization_recommendations(
1035
+ self,
1036
+ resources: List[NetworkResourceDetails],
1037
+ usage_metrics: Dict[str, NetworkUsageMetrics],
1038
+ dependencies: Dict[str, Dict[str, Any]],
1039
+ cost_analysis: Dict[str, Dict[str, float]],
1040
+ progress,
1041
+ task_id,
1042
+ ) -> List[NetworkOptimizationResult]:
977
1043
  """Calculate comprehensive network optimization recommendations and potential savings."""
978
1044
  optimization_results = []
979
-
1045
+
980
1046
  for resource in resources:
981
1047
  try:
982
1048
  metrics = usage_metrics.get(resource.resource_id)
983
1049
  deps = dependencies.get(resource.resource_id, {})
984
1050
  costs = cost_analysis.get(resource.resource_id, {})
985
-
1051
+
986
1052
  # Initialize optimization analysis
987
1053
  recommendation = "retain" # Default
988
1054
  risk_level = "low"
989
1055
  business_impact = "minimal"
990
-
1056
+
991
1057
  infrastructure_savings = 0.0
992
1058
  data_transfer_savings = 0.0
993
1059
  total_monthly_savings = 0.0
994
-
995
- # Service-specific optimization logic
1060
+
1061
+ # Service-specific optimization logic - CORRECTED SAVINGS CALCULATION
996
1062
  if resource.service == NetworkService.NAT_GATEWAY:
997
1063
  if metrics and not metrics.is_used:
998
1064
  recommendation = "decommission"
999
- risk_level = "medium" if len(deps.get('route_tables', [])) > 0 else "low"
1065
+ risk_level = "medium" if len(deps.get("route_tables", [])) > 0 else "low"
1000
1066
  business_impact = "cost_elimination"
1001
- infrastructure_savings = costs.get('infrastructure', {}).get('monthly', 0.0)
1002
- data_transfer_savings = costs.get('data_processing', {}).get('monthly', 0.0)
1003
-
1067
+ # CRITICAL FIX: Only unused NAT Gateways generate savings when removed
1068
+ infrastructure_savings = costs.get("infrastructure", {}).get("monthly", 0.0)
1069
+ data_transfer_savings = costs.get("data_processing", {}).get("monthly", 0.0)
1070
+ else:
1071
+ # Used NAT Gateways - no optimization savings
1072
+ infrastructure_savings = 0.0
1073
+ data_transfer_savings = 0.0
1074
+
1004
1075
  elif resource.service == NetworkService.ELASTIC_IP:
1005
- if resource.state == 'unattached':
1076
+ if resource.state == "unattached":
1006
1077
  recommendation = "release"
1007
- risk_level = "low" if not deps.get('dns_records') else "medium"
1078
+ risk_level = "low" if not deps.get("dns_records") else "medium"
1008
1079
  business_impact = "cost_elimination"
1009
- infrastructure_savings = costs.get('infrastructure', {}).get('monthly', 0.0)
1010
-
1080
+ # CRITICAL FIX: Only unattached Elastic IPs generate savings when released
1081
+ infrastructure_savings = costs.get("infrastructure", {}).get("monthly", 0.0)
1082
+ else:
1083
+ # Attached Elastic IPs - no optimization savings (attached IPs are free)
1084
+ infrastructure_savings = 0.0
1085
+
1011
1086
  elif resource.service == NetworkService.LOAD_BALANCER:
1012
1087
  if metrics and not metrics.is_used and resource.target_count == 0:
1013
1088
  recommendation = "decommission"
1014
1089
  risk_level = "low"
1015
1090
  business_impact = "cost_elimination"
1016
- infrastructure_savings = costs.get('infrastructure', {}).get('monthly', 0.0)
1091
+ # CRITICAL FIX: Only unused load balancers generate savings when decommissioned
1092
+ infrastructure_savings = costs.get("infrastructure", {}).get("monthly", 0.0)
1017
1093
  elif metrics and metrics.is_underutilized:
1018
1094
  recommendation = "consolidate"
1019
1095
  risk_level = "medium"
1020
1096
  business_impact = "consolidation_opportunity"
1021
- infrastructure_savings = costs.get('infrastructure', {}).get('monthly', 0.0) * 0.5 # 50% savings estimate
1022
-
1097
+ # CRITICAL FIX: Conservative 50% savings estimate for consolidation
1098
+ infrastructure_savings = costs.get("infrastructure", {}).get("monthly", 0.0) * 0.5
1099
+ else:
1100
+ # Used load balancers - no optimization savings
1101
+ infrastructure_savings = 0.0
1102
+
1023
1103
  elif resource.service == NetworkService.TRANSIT_GATEWAY:
1024
1104
  if metrics and not metrics.is_used:
1025
1105
  recommendation = "decommission"
1026
1106
  risk_level = "high" # TGWs typically have complex dependencies
1027
1107
  business_impact = "infrastructure_simplification"
1028
- infrastructure_savings = costs.get('infrastructure', {}).get('monthly', 0.0)
1029
- data_transfer_savings = costs.get('data_processing', {}).get('monthly', 0.0)
1030
-
1108
+ infrastructure_savings = costs.get("infrastructure", {}).get("monthly", 0.0)
1109
+ data_transfer_savings = costs.get("data_processing", {}).get("monthly", 0.0)
1110
+
1031
1111
  elif resource.service == NetworkService.VPC_ENDPOINT:
1032
- if resource.resource_type == 'Interface VPC Endpoint':
1112
+ if resource.resource_type == "Interface VPC Endpoint":
1033
1113
  # Interface endpoints could potentially be replaced with NAT Gateway for some use cases
1034
1114
  recommendation = "evaluate_alternatives"
1035
1115
  risk_level = "medium"
1036
1116
  business_impact = "architecture_optimization"
1037
-
1117
+
1038
1118
  # Calculate total savings
1039
1119
  total_monthly_savings = infrastructure_savings + data_transfer_savings
1040
-
1120
+
1041
1121
  # Adjust risk level based on dependency score
1042
- dependency_risk = deps.get('dependency_score', 0.0)
1122
+ dependency_risk = deps.get("dependency_score", 0.0)
1043
1123
  if dependency_risk > 0.7:
1044
1124
  risk_level = "high"
1045
1125
  elif dependency_risk > 0.3 and risk_level == "low":
1046
1126
  risk_level = "medium"
1047
-
1048
- optimization_results.append(NetworkOptimizationResult(
1049
- resource_id=resource.resource_id,
1050
- region=resource.region,
1051
- service=resource.service,
1052
- resource_type=resource.resource_type,
1053
- current_state=resource.state,
1054
- usage_metrics=metrics,
1055
- current_monthly_cost=costs.get('total_monthly', 0.0),
1056
- current_annual_cost=costs.get('total_annual', 0.0),
1057
- data_processing_monthly_cost=costs.get('data_processing', {}).get('monthly', 0.0),
1058
- data_processing_annual_cost=costs.get('data_processing', {}).get('annual', 0.0),
1059
- optimization_recommendation=recommendation,
1060
- risk_level=risk_level,
1061
- business_impact=business_impact,
1062
- infrastructure_monthly_savings=infrastructure_savings,
1063
- infrastructure_annual_savings=infrastructure_savings * 12,
1064
- data_transfer_monthly_savings=data_transfer_savings,
1065
- data_transfer_annual_savings=data_transfer_savings * 12,
1066
- total_monthly_savings=total_monthly_savings,
1067
- total_annual_savings=total_monthly_savings * 12,
1068
- route_table_dependencies=deps.get('route_tables', []),
1069
- dns_dependencies=deps.get('dns_records', []),
1070
- application_dependencies=deps.get('applications', []),
1071
- dependency_risk_score=dependency_risk
1072
- ))
1073
-
1127
+
1128
+ optimization_results.append(
1129
+ NetworkOptimizationResult(
1130
+ resource_id=resource.resource_id,
1131
+ region=resource.region,
1132
+ service=resource.service,
1133
+ resource_type=resource.resource_type,
1134
+ current_state=resource.state,
1135
+ usage_metrics=metrics,
1136
+ current_monthly_cost=costs.get("total_monthly", 0.0),
1137
+ current_annual_cost=costs.get("total_annual", 0.0),
1138
+ data_processing_monthly_cost=costs.get("data_processing", {}).get("monthly", 0.0),
1139
+ data_processing_annual_cost=costs.get("data_processing", {}).get("annual", 0.0),
1140
+ optimization_recommendation=recommendation,
1141
+ risk_level=risk_level,
1142
+ business_impact=business_impact,
1143
+ infrastructure_monthly_savings=infrastructure_savings,
1144
+ infrastructure_annual_savings=infrastructure_savings * 12,
1145
+ data_transfer_monthly_savings=data_transfer_savings,
1146
+ data_transfer_annual_savings=data_transfer_savings * 12,
1147
+ total_monthly_savings=total_monthly_savings,
1148
+ total_annual_savings=total_monthly_savings * 12,
1149
+ route_table_dependencies=deps.get("route_tables", []),
1150
+ dns_dependencies=deps.get("dns_records", []),
1151
+ application_dependencies=deps.get("applications", []),
1152
+ dependency_risk_score=dependency_risk,
1153
+ )
1154
+ )
1155
+
1074
1156
  except Exception as e:
1075
1157
  print_error(f"Network optimization calculation failed for {resource.resource_id}: {str(e)}")
1076
-
1158
+
1077
1159
  progress.advance(task_id)
1078
-
1160
+
1079
1161
  return optimization_results
1080
-
1081
- async def _validate_with_mcp(self, optimization_results: List[NetworkOptimizationResult],
1082
- progress, task_id) -> float:
1162
+
1163
+ async def _validate_with_mcp(
1164
+ self, optimization_results: List[NetworkOptimizationResult], progress, task_id
1165
+ ) -> float:
1083
1166
  """Validate network optimization results with embedded MCP validator."""
1084
1167
  try:
1085
1168
  # Prepare validation data in FinOps format
1086
1169
  validation_data = {
1087
- 'total_annual_cost': sum(result.current_annual_cost for result in optimization_results),
1088
- 'potential_annual_savings': sum(result.total_annual_savings for result in optimization_results),
1089
- 'resources_analyzed': len(optimization_results),
1090
- 'services_analyzed': list(set(result.service.value for result in optimization_results)),
1091
- 'analysis_timestamp': datetime.now().isoformat()
1170
+ "total_annual_cost": sum(result.current_annual_cost for result in optimization_results),
1171
+ "potential_annual_savings": sum(result.total_annual_savings for result in optimization_results),
1172
+ "resources_analyzed": len(optimization_results),
1173
+ "services_analyzed": list(set(result.service.value for result in optimization_results)),
1174
+ "analysis_timestamp": datetime.now().isoformat(),
1092
1175
  }
1093
-
1176
+
1094
1177
  # Initialize MCP validator if profile is available
1095
1178
  if self.profile_name:
1096
1179
  mcp_validator = EmbeddedMCPValidator([self.profile_name])
1097
1180
  validation_results = await mcp_validator.validate_cost_data_async(validation_data)
1098
- accuracy = validation_results.get('total_accuracy', 0.0)
1099
-
1181
+ accuracy = validation_results.get("total_accuracy", 0.0)
1182
+
1100
1183
  if accuracy >= 99.5:
1101
1184
  print_success(f"MCP Validation: {accuracy:.1f}% accuracy achieved (target: ≥99.5%)")
1102
1185
  else:
1103
1186
  print_warning(f"MCP Validation: {accuracy:.1f}% accuracy (target: ≥99.5%)")
1104
-
1187
+
1105
1188
  progress.advance(task_id)
1106
1189
  return accuracy
1107
1190
  else:
1108
1191
  print_info("MCP validation skipped - no profile specified")
1109
1192
  progress.advance(task_id)
1110
1193
  return 0.0
1111
-
1194
+
1112
1195
  except Exception as e:
1113
1196
  print_warning(f"MCP validation failed: {str(e)}")
1114
1197
  progress.advance(task_id)
1115
1198
  return 0.0
1116
-
1117
- def _compile_results(self, resources: List[NetworkResourceDetails],
1118
- optimization_results: List[NetworkOptimizationResult],
1119
- mcp_accuracy: float, analysis_start_time: float,
1120
- services_analyzed: List[NetworkService]) -> NetworkCostOptimizerResults:
1199
+
1200
+ def _compile_results(
1201
+ self,
1202
+ resources: List[NetworkResourceDetails],
1203
+ optimization_results: List[NetworkOptimizationResult],
1204
+ mcp_accuracy: float,
1205
+ analysis_start_time: float,
1206
+ services_analyzed: List[NetworkService],
1207
+ ) -> NetworkCostOptimizerResults:
1121
1208
  """Compile comprehensive network cost optimization results."""
1122
-
1209
+
1123
1210
  # Count resources by service type
1124
1211
  nat_gateways = len([r for r in resources if r.service == NetworkService.NAT_GATEWAY])
1125
1212
  elastic_ips = len([r for r in resources if r.service == NetworkService.ELASTIC_IP])
1126
1213
  load_balancers = len([r for r in resources if r.service == NetworkService.LOAD_BALANCER])
1127
1214
  transit_gateways = len([r for r in resources if r.service == NetworkService.TRANSIT_GATEWAY])
1128
1215
  vpc_endpoints = len([r for r in resources if r.service == NetworkService.VPC_ENDPOINT])
1129
-
1216
+
1130
1217
  # Calculate cost breakdowns
1131
1218
  total_monthly_cost = sum(result.current_monthly_cost for result in optimization_results)
1132
1219
  total_annual_cost = total_monthly_cost * 12
1133
-
1220
+
1134
1221
  total_monthly_infrastructure_cost = sum(r.monthly_cost for r in resources)
1135
1222
  total_annual_infrastructure_cost = total_monthly_infrastructure_cost * 12
1136
-
1223
+
1137
1224
  total_monthly_data_processing_cost = sum(result.data_processing_monthly_cost for result in optimization_results)
1138
1225
  total_annual_data_processing_cost = total_monthly_data_processing_cost * 12
1139
-
1226
+
1140
1227
  # Calculate savings
1141
1228
  infrastructure_monthly_savings = sum(result.infrastructure_monthly_savings for result in optimization_results)
1142
1229
  data_transfer_monthly_savings = sum(result.data_transfer_monthly_savings for result in optimization_results)
1143
1230
  total_monthly_savings = sum(result.total_monthly_savings for result in optimization_results)
1144
-
1231
+
1145
1232
  return NetworkCostOptimizerResults(
1146
1233
  analyzed_services=services_analyzed,
1147
1234
  analyzed_regions=self.regions,
@@ -1166,12 +1253,12 @@ class NetworkCostOptimizer:
1166
1253
  optimization_results=optimization_results,
1167
1254
  execution_time_seconds=time.time() - analysis_start_time,
1168
1255
  mcp_validation_accuracy=mcp_accuracy,
1169
- analysis_timestamp=datetime.now()
1256
+ analysis_timestamp=datetime.now(),
1170
1257
  )
1171
-
1258
+
1172
1259
  def _display_executive_summary(self, results: NetworkCostOptimizerResults) -> None:
1173
1260
  """Display executive summary with Rich CLI formatting."""
1174
-
1261
+
1175
1262
  # Executive Summary Panel
1176
1263
  summary_content = f"""
1177
1264
  🌐 Network Infrastructure Analysis
@@ -1193,22 +1280,20 @@ class NetworkCostOptimizer:
1193
1280
  • Data Transfer Savings: {format_cost(results.data_transfer_annual_savings)}
1194
1281
  • Total Savings: {format_cost(results.total_annual_savings)}
1195
1282
 
1196
- 🌍 Regions: {', '.join(results.analyzed_regions)}
1283
+ 🌍 Regions: {", ".join(results.analyzed_regions)}
1197
1284
  ⚡ Analysis Time: {results.execution_time_seconds:.2f}s
1198
1285
  ✅ MCP Accuracy: {results.mcp_validation_accuracy:.1f}%
1199
1286
  """
1200
-
1201
- console.print(create_panel(
1202
- summary_content.strip(),
1203
- title="🏆 Network Cost Optimization Executive Summary",
1204
- border_style="green"
1205
- ))
1206
-
1207
- # Detailed Results Table
1208
- table = create_table(
1209
- title="Network Resource Optimization Recommendations"
1287
+
1288
+ console.print(
1289
+ create_panel(
1290
+ summary_content.strip(), title="🏆 Network Cost Optimization Executive Summary", border_style="green"
1291
+ )
1210
1292
  )
1211
-
1293
+
1294
+ # Detailed Results Table
1295
+ table = create_table(title="Network Resource Optimization Recommendations")
1296
+
1212
1297
  table.add_column("Resource ID", style="cyan", no_wrap=True)
1213
1298
  table.add_column("Service", style="dim")
1214
1299
  table.add_column("Type", justify="center")
@@ -1217,17 +1302,13 @@ class NetworkCostOptimizer:
1217
1302
  table.add_column("Potential Savings", justify="right", style="green")
1218
1303
  table.add_column("Recommendation", justify="center")
1219
1304
  table.add_column("Risk", justify="center")
1220
-
1305
+
1221
1306
  # Sort by potential savings (descending)
1222
- sorted_results = sorted(
1223
- results.optimization_results,
1224
- key=lambda x: x.total_annual_savings,
1225
- reverse=True
1226
- )
1227
-
1307
+ sorted_results = sorted(results.optimization_results, key=lambda x: x.total_annual_savings, reverse=True)
1308
+
1228
1309
  # Show top 20 results
1229
1310
  display_results = sorted_results[:20]
1230
-
1311
+
1231
1312
  for result in display_results:
1232
1313
  # Status indicators for recommendations
1233
1314
  rec_color = {
@@ -1235,23 +1316,19 @@ class NetworkCostOptimizer:
1235
1316
  "release": "red",
1236
1317
  "consolidate": "yellow",
1237
1318
  "evaluate_alternatives": "blue",
1238
- "retain": "green"
1319
+ "retain": "green",
1239
1320
  }.get(result.optimization_recommendation, "white")
1240
-
1241
- risk_indicator = {
1242
- "low": "🟢",
1243
- "medium": "🟡",
1244
- "high": "🔴"
1245
- }.get(result.risk_level, "⚪")
1246
-
1321
+
1322
+ risk_indicator = {"low": "🟢", "medium": "🟡", "high": "🔴"}.get(result.risk_level, "⚪")
1323
+
1247
1324
  service_icon = {
1248
1325
  NetworkService.NAT_GATEWAY: "🔀",
1249
1326
  NetworkService.ELASTIC_IP: "🌐",
1250
1327
  NetworkService.LOAD_BALANCER: "⚖️",
1251
1328
  NetworkService.TRANSIT_GATEWAY: "🚇",
1252
- NetworkService.VPC_ENDPOINT: "🔗"
1329
+ NetworkService.VPC_ENDPOINT: "🔗",
1253
1330
  }.get(result.service, "📡")
1254
-
1331
+
1255
1332
  table.add_row(
1256
1333
  result.resource_id[-12:], # Show last 12 chars
1257
1334
  f"{service_icon} {result.service.value.replace('_', ' ').title()}",
@@ -1260,73 +1337,68 @@ class NetworkCostOptimizer:
1260
1337
  format_cost(result.current_annual_cost),
1261
1338
  format_cost(result.total_annual_savings) if result.total_annual_savings > 0 else "-",
1262
1339
  f"[{rec_color}]{result.optimization_recommendation.replace('_', ' ').title()}[/]",
1263
- f"{risk_indicator} {result.risk_level.title()}"
1340
+ f"{risk_indicator} {result.risk_level.title()}",
1264
1341
  )
1265
-
1342
+
1266
1343
  if len(sorted_results) > 20:
1267
1344
  table.add_row(
1268
- "...", "...", "...", "...", "...", "...",
1269
- f"[dim]+{len(sorted_results) - 20} more resources[/]", "..."
1345
+ "...", "...", "...", "...", "...", "...", f"[dim]+{len(sorted_results) - 20} more resources[/]", "..."
1270
1346
  )
1271
-
1347
+
1272
1348
  console.print(table)
1273
-
1349
+
1274
1350
  # Service-specific breakdown if we have multiple services
1275
1351
  if len(results.analyzed_services) > 1:
1276
1352
  service_breakdown = {}
1277
1353
  for result in results.optimization_results:
1278
1354
  service = result.service
1279
1355
  if service not in service_breakdown:
1280
- service_breakdown[service] = {
1281
- 'count': 0,
1282
- 'total_cost': 0.0,
1283
- 'total_savings': 0.0
1284
- }
1285
- service_breakdown[service]['count'] += 1
1286
- service_breakdown[service]['total_cost'] += result.current_annual_cost
1287
- service_breakdown[service]['total_savings'] += result.total_annual_savings
1288
-
1356
+ service_breakdown[service] = {"count": 0, "total_cost": 0.0, "total_savings": 0.0}
1357
+ service_breakdown[service]["count"] += 1
1358
+ service_breakdown[service]["total_cost"] += result.current_annual_cost
1359
+ service_breakdown[service]["total_savings"] += result.total_annual_savings
1360
+
1289
1361
  breakdown_content = []
1290
1362
  for service, data in service_breakdown.items():
1291
- service_name = service.value.replace('_', ' ').title()
1363
+ service_name = service.value.replace("_", " ").title()
1292
1364
  breakdown_content.append(
1293
1365
  f"• {service_name}: {data['count']} resources | "
1294
1366
  f"{format_cost(data['total_cost'])} cost | "
1295
1367
  f"{format_cost(data['total_savings'])} savings"
1296
1368
  )
1297
-
1298
- console.print(create_panel(
1299
- "\n".join(breakdown_content),
1300
- title="📊 Service-Level Cost Breakdown",
1301
- border_style="blue"
1302
- ))
1369
+
1370
+ console.print(
1371
+ create_panel("\n".join(breakdown_content), title="📊 Service-Level Cost Breakdown", border_style="blue")
1372
+ )
1303
1373
 
1304
1374
 
1305
1375
  # CLI Integration for enterprise runbooks commands
1306
1376
  @click.command()
1307
- @click.option('--profile', help='AWS profile name (3-tier priority: User > Environment > Default)')
1308
- @click.option('--regions', multiple=True, help='AWS regions to analyze (space-separated)')
1309
- @click.option('--services', multiple=True,
1310
- type=click.Choice(['nat_gateway', 'elastic_ip', 'load_balancer', 'transit_gateway', 'vpc_endpoint']),
1311
- help='Network services to analyze')
1312
- @click.option('--dry-run/--no-dry-run', default=True, help='Execute in dry-run mode (READ-ONLY analysis)')
1313
- @click.option('--usage-threshold-days', type=int, default=14,
1314
- help='CloudWatch analysis period in days')
1377
+ @click.option("--profile", help="AWS profile name (3-tier priority: User > Environment > Default)")
1378
+ @click.option("--regions", multiple=True, help="AWS regions to analyze (space-separated)")
1379
+ @click.option(
1380
+ "--services",
1381
+ multiple=True,
1382
+ type=click.Choice(["nat_gateway", "elastic_ip", "load_balancer", "transit_gateway", "vpc_endpoint"]),
1383
+ help="Network services to analyze",
1384
+ )
1385
+ @click.option("--dry-run/--no-dry-run", default=True, help="Execute in dry-run mode (READ-ONLY analysis)")
1386
+ @click.option("--usage-threshold-days", type=int, default=14, help="CloudWatch analysis period in days")
1315
1387
  def network_optimizer(profile, regions, services, dry_run, usage_threshold_days):
1316
1388
  """
1317
1389
  Network Cost Optimizer - Enterprise Multi-Service Network Analysis
1318
-
1390
+
1319
1391
  Comprehensive network cost optimization across AWS services:
1320
1392
  • NAT Gateway usage analysis with CloudWatch metrics integration
1321
1393
  • Elastic IP resource efficiency analysis with DNS dependency checking
1322
1394
  • Load Balancer optimization (ALB, NLB, CLB) with traffic analysis
1323
1395
  • Transit Gateway cost optimization with attachment analysis
1324
1396
  • VPC Endpoint cost-benefit analysis and alternative recommendations
1325
-
1397
+
1326
1398
  Part of $132,720+ annual savings methodology targeting $2.4M-$7.3M network optimization.
1327
-
1399
+
1328
1400
  SAFETY: READ-ONLY analysis only - no resource modifications.
1329
-
1401
+
1330
1402
  Examples:
1331
1403
  runbooks finops network --analyze
1332
1404
  runbooks finops network --services nat_gateway elastic_ip --regions us-east-1 us-west-2
@@ -1337,30 +1409,26 @@ def network_optimizer(profile, regions, services, dry_run, usage_threshold_days)
1337
1409
  service_enums = []
1338
1410
  if services:
1339
1411
  service_map = {
1340
- 'nat_gateway': NetworkService.NAT_GATEWAY,
1341
- 'elastic_ip': NetworkService.ELASTIC_IP,
1342
- 'load_balancer': NetworkService.LOAD_BALANCER,
1343
- 'transit_gateway': NetworkService.TRANSIT_GATEWAY,
1344
- 'vpc_endpoint': NetworkService.VPC_ENDPOINT
1412
+ "nat_gateway": NetworkService.NAT_GATEWAY,
1413
+ "elastic_ip": NetworkService.ELASTIC_IP,
1414
+ "load_balancer": NetworkService.LOAD_BALANCER,
1415
+ "transit_gateway": NetworkService.TRANSIT_GATEWAY,
1416
+ "vpc_endpoint": NetworkService.VPC_ENDPOINT,
1345
1417
  }
1346
1418
  service_enums = [service_map[s] for s in services]
1347
-
1419
+
1348
1420
  # Initialize optimizer
1349
- optimizer = NetworkCostOptimizer(
1350
- profile_name=profile,
1351
- regions=list(regions) if regions else None
1352
- )
1353
-
1421
+ optimizer = NetworkCostOptimizer(profile_name=profile, regions=list(regions) if regions else None)
1422
+
1354
1423
  # Override analysis period if specified
1355
1424
  if usage_threshold_days != 14:
1356
1425
  optimizer.analysis_period_days = usage_threshold_days
1357
-
1426
+
1358
1427
  # Execute comprehensive analysis
1359
- results = asyncio.run(optimizer.analyze_network_costs(
1360
- services=service_enums if service_enums else None,
1361
- dry_run=dry_run
1362
- ))
1363
-
1428
+ results = asyncio.run(
1429
+ optimizer.analyze_network_costs(services=service_enums if service_enums else None, dry_run=dry_run)
1430
+ )
1431
+
1364
1432
  # Display final success message
1365
1433
  if results.total_annual_savings > 0:
1366
1434
  savings_breakdown = []
@@ -1368,13 +1436,15 @@ def network_optimizer(profile, regions, services, dry_run, usage_threshold_days)
1368
1436
  savings_breakdown.append(f"Infrastructure: {format_cost(results.infrastructure_annual_savings)}")
1369
1437
  if results.data_transfer_annual_savings > 0:
1370
1438
  savings_breakdown.append(f"Data Transfer: {format_cost(results.data_transfer_annual_savings)}")
1371
-
1439
+
1372
1440
  print_success(f"Analysis complete: {format_cost(results.total_annual_savings)} potential annual savings")
1373
1441
  print_info(f"Cost breakdown: {' | '.join(savings_breakdown)}")
1374
- print_info(f"Services analyzed: {', '.join([s.value.replace('_', ' ').title() for s in results.analyzed_services])}")
1442
+ print_info(
1443
+ f"Services analyzed: {', '.join([s.value.replace('_', ' ').title() for s in results.analyzed_services])}"
1444
+ )
1375
1445
  else:
1376
1446
  print_info("Analysis complete: All network resources are optimally configured")
1377
-
1447
+
1378
1448
  except KeyboardInterrupt:
1379
1449
  print_warning("Analysis interrupted by user")
1380
1450
  raise click.Abort()
@@ -1383,5 +1453,5 @@ def network_optimizer(profile, regions, services, dry_run, usage_threshold_days)
1383
1453
  raise click.Abort()
1384
1454
 
1385
1455
 
1386
- if __name__ == '__main__':
1387
- network_optimizer()
1456
+ if __name__ == "__main__":
1457
+ network_optimizer()