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
@@ -5,6 +5,7 @@ import boto3
5
5
  from typing import Optional, List, Dict, Any
6
6
  from runbooks.common.rich_utils import console
7
7
  from datetime import datetime
8
+ from botocore.config import Config
8
9
 
9
10
  # Enhanced caching system for enterprise performance
10
11
  _profile_cache: Dict[str, str] = {}
@@ -14,6 +15,14 @@ _cache_ttl: int = 300 # 5 minutes TTL for enterprise session management
14
15
  _session_id: Optional[str] = None # Track session consistency
15
16
  _session_cache: Dict[str, boto3.Session] = {} # Cache AWS sessions
16
17
 
18
+ # Timeout configuration for AWS API calls (prevents execution flow hangs)
19
+ _AWS_CLIENT_CONFIG = Config(
20
+ connect_timeout=30, # Connection timeout: 30 seconds
21
+ read_timeout=60, # Read timeout: 60 seconds
22
+ retries={"max_attempts": 3, "mode": "adaptive"},
23
+ )
24
+
25
+
17
26
  def _get_session_id() -> str:
18
27
  """Generate consistent session ID for cache scoping"""
19
28
  global _session_id
@@ -21,10 +30,9 @@ def _get_session_id() -> str:
21
30
  _session_id = f"session_{int(time.time())}"
22
31
  return _session_id
23
32
 
33
+
24
34
  def get_profile_for_operation(
25
- operation_type: str,
26
- user_specified_profile: Optional[str] = None,
27
- profiles: Optional[List[str]] = None
35
+ operation_type: str, user_specified_profile: Optional[str] = None, profiles: Optional[List[str]] = None
28
36
  ) -> str:
29
37
  """
30
38
  Enhanced profile resolution with intelligent caching and enterprise logging optimization.
@@ -54,9 +62,7 @@ def get_profile_for_operation(
54
62
  profile_cache_key = f"{_get_session_id()}:{user_specified_profile or 'default'}"
55
63
 
56
64
  # Return cached result if still valid and within TTL
57
- if (profile_cache_key in _profile_cache and
58
- _cache_timestamp and
59
- current_time - _cache_timestamp < _cache_ttl):
65
+ if profile_cache_key in _profile_cache and _cache_timestamp and current_time - _cache_timestamp < _cache_ttl:
60
66
  return _profile_cache[profile_cache_key]
61
67
 
62
68
  # Update cache timestamp only when cache is actually refreshed
@@ -114,6 +120,7 @@ def get_profile_for_operation(
114
120
  console.log("[yellow]Please run: aws configure sso or aws configure[/]")
115
121
  raise SystemExit(1)
116
122
 
123
+
117
124
  def validate_profile_access(profile_name: str, operation_description: str = "") -> bool:
118
125
  """
119
126
  Validate that the specified profile has proper AWS access with caching.
@@ -130,14 +137,12 @@ def validate_profile_access(profile_name: str, operation_description: str = "")
130
137
  current_time = time.time()
131
138
  cache_key = f"validation:{profile_name}"
132
139
 
133
- if (cache_key in _validation_cache and
134
- _cache_timestamp and
135
- current_time - _cache_timestamp < _cache_ttl):
140
+ if cache_key in _validation_cache and _cache_timestamp and current_time - _cache_timestamp < _cache_ttl:
136
141
  return _validation_cache[cache_key]
137
142
 
138
143
  try:
139
144
  session = boto3.Session(profile_name=profile_name)
140
- sts_client = session.client('sts')
145
+ sts_client = session.client("sts")
141
146
  sts_client.get_caller_identity()
142
147
 
143
148
  # Cache successful validation
@@ -149,6 +154,7 @@ def validate_profile_access(profile_name: str, operation_description: str = "")
149
154
  console.log(f"[yellow]Profile {profile_name} validation failed: {e}[/]")
150
155
  return False
151
156
 
157
+
152
158
  def get_account_id_from_profile(profile_name: str) -> Optional[str]:
153
159
  """
154
160
  Extract account ID from AWS profile.
@@ -161,53 +167,232 @@ def get_account_id_from_profile(profile_name: str) -> Optional[str]:
161
167
  """
162
168
  try:
163
169
  session = boto3.Session(profile_name=profile_name)
164
- sts_client = session.client('sts')
170
+ sts_client = session.client("sts")
165
171
  response = sts_client.get_caller_identity()
166
- return response.get('Account')
172
+ return response.get("Account")
167
173
  except Exception:
168
174
  return None
169
175
 
176
+
177
+ def auto_discover_enterprise_profiles() -> Dict[str, Optional[str]]:
178
+ """
179
+ Auto-discover enterprise AWS SSO profiles for streamlined initialization.
180
+
181
+ Searches for profiles matching common enterprise naming patterns:
182
+ - *Billing* or *billing* for BILLING_PROFILE
183
+ - *Management* or *management* for MANAGEMENT_PROFILE
184
+ - *Ops* or *ops* for CENTRALISED_OPS_PROFILE
185
+ - Single account profiles for SINGLE_AWS_PROFILE
186
+
187
+ Returns:
188
+ Dict mapping profile types to discovered profile names
189
+ """
190
+ available_profiles = boto3.Session().available_profiles
191
+ discovered = {"billing": None, "management": None, "centralised_ops": None, "single_aws": None}
192
+
193
+ # Search patterns for enterprise profiles
194
+ for profile in available_profiles:
195
+ profile_lower = profile.lower()
196
+
197
+ # Billing profile detection
198
+ if ("billing" in profile_lower or "cost" in profile_lower) and not discovered["billing"]:
199
+ discovered["billing"] = profile
200
+
201
+ # Management profile detection
202
+ elif ("management" in profile_lower or "admin" in profile_lower) and not discovered["management"]:
203
+ discovered["management"] = profile
204
+
205
+ # Operations profile detection
206
+ elif (
207
+ "ops" in profile_lower or "operational" in profile_lower or "centralised" in profile_lower
208
+ ) and not discovered["centralised_ops"]:
209
+ discovered["centralised_ops"] = profile
210
+
211
+ # Single account detection (typically shorter names or containing 'single')
212
+ elif ("single" in profile_lower or len(profile) < 20) and not discovered["single_aws"]:
213
+ discovered["single_aws"] = profile
214
+
215
+ # Log discovered profiles for transparency
216
+ for profile_type, profile_name in discovered.items():
217
+ if profile_name:
218
+ console.log(f"[green]✅ Auto-discovered {profile_type}: {profile_name}[/green]")
219
+ else:
220
+ console.log(f"[yellow]⚠️ No profile found for {profile_type}[/yellow]")
221
+
222
+ return discovered
223
+
224
+
225
+ def setup_enterprise_environment_variables(discovered_profiles: Optional[Dict[str, Optional[str]]] = None) -> None:
226
+ """
227
+ Setup enterprise environment variables from discovered profiles.
228
+
229
+ Args:
230
+ discovered_profiles: Optional pre-discovered profiles dict
231
+ """
232
+ if not discovered_profiles:
233
+ discovered_profiles = auto_discover_enterprise_profiles()
234
+
235
+ # Set environment variables if not already set
236
+ env_mappings = {
237
+ "BILLING_PROFILE": discovered_profiles.get("billing"),
238
+ "MANAGEMENT_PROFILE": discovered_profiles.get("management"),
239
+ "CENTRALISED_OPS_PROFILE": discovered_profiles.get("centralised_ops"),
240
+ "SINGLE_AWS_PROFILE": discovered_profiles.get("single_aws"),
241
+ }
242
+
243
+ for env_var, profile_name in env_mappings.items():
244
+ if profile_name and not os.getenv(env_var):
245
+ os.environ[env_var] = profile_name
246
+ console.log(f"[blue]📋 Set {env_var}={profile_name}[/blue]")
247
+ elif os.getenv(env_var):
248
+ console.log(f"[dim]Using existing {env_var}={os.getenv(env_var)}[/dim]")
249
+
250
+
170
251
  def create_cost_session(profile_name: Optional[str] = None) -> boto3.Session:
171
252
  """
172
- Create AWS session optimized for cost operations (Cost Explorer).
253
+ Create AWS session optimized for cost operations (Cost Explorer) with token error handling.
173
254
 
174
255
  Args:
175
256
  profile_name: AWS profile name for cost operations
176
257
 
177
258
  Returns:
178
259
  Configured boto3 Session for cost operations
260
+
261
+ Raises:
262
+ SystemExit: When authentication fails with clear user guidance
179
263
  """
264
+ from botocore.exceptions import TokenRetrievalError, NoCredentialsError
265
+ from .rich_utils import console, print_error, print_info
266
+
180
267
  cost_profile = get_profile_for_operation("billing", profile_name)
181
268
 
182
- # Use cached session if available
269
+ # Use cached session if available and validate it's still working
183
270
  session_key = f"cost:{cost_profile}"
184
271
  if session_key in _session_cache:
185
- return _session_cache[session_key]
272
+ cached_session = _session_cache[session_key]
273
+ # Quick validation that the cached session still works
274
+ try:
275
+ # Test with a minimal STS call to check if credentials are valid (with timeout)
276
+ sts_client = cached_session.client("sts", config=_AWS_CLIENT_CONFIG)
277
+ sts_client.get_caller_identity()
278
+ return cached_session
279
+ except (TokenRetrievalError, NoCredentialsError):
280
+ # Remove invalid cached session
281
+ del _session_cache[session_key]
282
+ console.log("[yellow]⚠️ Cached session expired, creating new session[/]")
283
+
284
+ try:
285
+ session = boto3.Session(profile_name=cost_profile)
286
+ # Test the session to ensure credentials are valid (with timeout)
287
+ sts_client = session.client("sts", config=_AWS_CLIENT_CONFIG)
288
+ sts_client.get_caller_identity()
289
+
290
+ # Cache only if session works
291
+ _session_cache[session_key] = session
292
+ return session
293
+
294
+ except TokenRetrievalError as e:
295
+ print_error("🔐 AWS SSO token has expired")
296
+ print_info("💡 To fix this issue:")
297
+ print_info(f" 1. Run: [cyan]aws sso login --profile {cost_profile}[/]")
298
+ print_info(" 2. Or try: [cyan]aws sso login[/] (if using default profile)")
299
+ print_info(" 3. Verify your internet connection")
300
+ print_info(" 4. Check if your AWS SSO session has expired")
301
+ console.log(f"[dim]Profile used: {cost_profile}[/]")
302
+ console.log(f"[dim]Error details: {str(e)}[/]")
303
+ raise SystemExit(1)
304
+
305
+ except NoCredentialsError as e:
306
+ print_error("🔐 No AWS credentials configured")
307
+ print_info("💡 To fix this issue:")
308
+ print_info(" 1. Configure AWS CLI: [cyan]aws configure[/]")
309
+ print_info(" 2. Or setup SSO: [cyan]aws configure sso[/]")
310
+ print_info(f" 3. Or set profile: [cyan]export AWS_PROFILE={cost_profile}[/]")
311
+ console.log(f"[dim]Profile attempted: {cost_profile}[/]")
312
+ raise SystemExit(1)
313
+
314
+ except Exception as e:
315
+ print_error(f"🔐 Authentication failed for profile: {cost_profile}")
316
+ print_info("💡 To fix this issue:")
317
+ print_info(" 1. Verify the profile exists: [cyan]aws configure list-profiles[/]")
318
+ print_info(" 2. Check profile permissions for cost analysis")
319
+ print_info(" 3. Ensure profile has Cost Explorer access")
320
+ console.log(f"[dim]Error details: {str(e)}[/]")
321
+ raise SystemExit(1)
186
322
 
187
- session = boto3.Session(profile_name=cost_profile)
188
- _session_cache[session_key] = session
189
- return session
190
323
 
191
324
  def create_management_session(profile_name: Optional[str] = None) -> boto3.Session:
192
325
  """
193
- Create AWS session optimized for management operations (Organizations).
326
+ Create AWS session optimized for management operations (Organizations) with token error handling.
194
327
 
195
328
  Args:
196
329
  profile_name: AWS profile name for management operations
197
330
 
198
331
  Returns:
199
332
  Configured boto3 Session for management operations
333
+
334
+ Raises:
335
+ SystemExit: When authentication fails with clear user guidance
200
336
  """
337
+ from botocore.exceptions import TokenRetrievalError, NoCredentialsError
338
+ from .rich_utils import console, print_error, print_info
339
+
201
340
  mgmt_profile = get_profile_for_operation("management", profile_name)
202
341
 
203
- # Use cached session if available
342
+ # Use cached session if available and validate it's still working
204
343
  session_key = f"management:{mgmt_profile}"
205
344
  if session_key in _session_cache:
206
- return _session_cache[session_key]
345
+ cached_session = _session_cache[session_key]
346
+ # Quick validation that the cached session still works
347
+ try:
348
+ # Test with a minimal STS call to check if credentials are valid (with timeout)
349
+ sts_client = cached_session.client("sts", config=_AWS_CLIENT_CONFIG)
350
+ sts_client.get_caller_identity()
351
+ return cached_session
352
+ except (TokenRetrievalError, NoCredentialsError):
353
+ # Remove invalid cached session
354
+ del _session_cache[session_key]
355
+ console.log("[yellow]⚠️ Cached session expired, creating new session[/]")
356
+
357
+ try:
358
+ session = boto3.Session(profile_name=mgmt_profile)
359
+ # Test the session to ensure credentials are valid (with timeout)
360
+ sts_client = session.client("sts", config=_AWS_CLIENT_CONFIG)
361
+ sts_client.get_caller_identity()
362
+
363
+ # Cache only if session works
364
+ _session_cache[session_key] = session
365
+ return session
366
+
367
+ except TokenRetrievalError as e:
368
+ print_error("🔐 AWS SSO token has expired")
369
+ print_info("💡 To fix this issue:")
370
+ print_info(f" 1. Run: [cyan]aws sso login --profile {mgmt_profile}[/]")
371
+ print_info(" 2. Or try: [cyan]aws sso login[/] (if using default profile)")
372
+ print_info(" 3. Verify your internet connection")
373
+ print_info(" 4. Check if your AWS SSO session has expired")
374
+ console.log(f"[dim]Profile used: {mgmt_profile}[/]")
375
+ console.log(f"[dim]Error details: {str(e)}[/]")
376
+ raise SystemExit(1)
377
+
378
+ except NoCredentialsError as e:
379
+ print_error("🔐 No AWS credentials configured")
380
+ print_info("💡 To fix this issue:")
381
+ print_info(" 1. Configure AWS CLI: [cyan]aws configure[/]")
382
+ print_info(" 2. Or setup SSO: [cyan]aws configure sso[/]")
383
+ print_info(f" 3. Or set profile: [cyan]export AWS_PROFILE={mgmt_profile}[/]")
384
+ console.log(f"[dim]Profile attempted: {mgmt_profile}[/]")
385
+ raise SystemExit(1)
386
+
387
+ except Exception as e:
388
+ print_error(f"🔐 Authentication failed for profile: {mgmt_profile}")
389
+ print_info("💡 To fix this issue:")
390
+ print_info(" 1. Verify the profile exists: [cyan]aws configure list-profiles[/]")
391
+ print_info(" 2. Check profile permissions for management operations")
392
+ print_info(" 3. Ensure profile has Organizations access")
393
+ console.log(f"[dim]Error details: {str(e)}[/]")
394
+ raise SystemExit(1)
207
395
 
208
- session = boto3.Session(profile_name=mgmt_profile)
209
- _session_cache[session_key] = session
210
- return session
211
396
 
212
397
  def create_operational_session(profile_name: Optional[str] = None) -> boto3.Session:
213
398
  """
@@ -230,6 +415,7 @@ def create_operational_session(profile_name: Optional[str] = None) -> boto3.Sess
230
415
  _session_cache[session_key] = session
231
416
  return session
232
417
 
418
+
233
419
  def get_current_profile_info(profile_name: Optional[str] = None) -> Dict[str, Any]:
234
420
  """
235
421
  Get current profile information including account ID and region.
@@ -242,28 +428,26 @@ def get_current_profile_info(profile_name: Optional[str] = None) -> Dict[str, An
242
428
  """
243
429
  try:
244
430
  session = boto3.Session(profile_name=profile_name)
245
- sts_client = session.client('sts')
431
+ sts_client = session.client("sts", config=_AWS_CLIENT_CONFIG)
246
432
  identity = sts_client.get_caller_identity()
247
433
 
248
434
  return {
249
- 'profile_name': profile_name or 'default',
250
- 'account_id': identity.get('Account'),
251
- 'user_arn': identity.get('Arn'),
252
- 'region': session.region_name or 'us-east-1'
435
+ "profile_name": profile_name or "default",
436
+ "account_id": identity.get("Account"),
437
+ "user_arn": identity.get("Arn"),
438
+ "region": session.region_name or "us-east-1",
253
439
  }
254
440
  except Exception as e:
255
441
  return {
256
- 'profile_name': profile_name or 'default',
257
- 'error': str(e),
258
- 'account_id': None,
259
- 'user_arn': None,
260
- 'region': None
442
+ "profile_name": profile_name or "default",
443
+ "error": str(e),
444
+ "account_id": None,
445
+ "user_arn": None,
446
+ "region": None,
261
447
  }
262
448
 
263
- def resolve_profile_for_operation_silent(
264
- operation_type: str,
265
- user_specified_profile: Optional[str] = None
266
- ) -> str:
449
+
450
+ def resolve_profile_for_operation_silent(operation_type: str, user_specified_profile: Optional[str] = None) -> str:
267
451
  """
268
452
  Silent version of profile resolution without logging.
269
453
 
@@ -291,6 +475,7 @@ def resolve_profile_for_operation_silent(
291
475
 
292
476
  return user_specified_profile or "default"
293
477
 
478
+
294
479
  def list_available_profiles() -> List[str]:
295
480
  """
296
481
  Get list of all available AWS profiles.
@@ -300,6 +485,7 @@ def list_available_profiles() -> List[str]:
300
485
  """
301
486
  return boto3.Session().available_profiles
302
487
 
488
+
303
489
  def clear_profile_cache() -> None:
304
490
  """Clear the profile cache for testing or troubleshooting."""
305
491
  global _profile_cache, _validation_cache, _session_cache, _cache_timestamp, _session_id
@@ -307,4 +493,27 @@ def clear_profile_cache() -> None:
307
493
  _validation_cache.clear()
308
494
  _session_cache.clear()
309
495
  _cache_timestamp = None
310
- _session_id = None
496
+ _session_id = None
497
+
498
+
499
+ def create_timeout_protected_client(session: boto3.Session, service_name: str, region_name: Optional[str] = None):
500
+ """
501
+ Create AWS service client with timeout protection to prevent execution flow hangs.
502
+
503
+ This function should be used by all FinOps modules to create AWS clients with
504
+ enterprise-grade timeout protection and retry configuration.
505
+
506
+ Args:
507
+ session: boto3 Session to use for client creation
508
+ service_name: AWS service name (e.g., 'ec2', 'ce', 'workspaces', 'rds')
509
+ region_name: AWS region name (optional)
510
+
511
+ Returns:
512
+ AWS service client with timeout protection
513
+
514
+ Example:
515
+ session = create_cost_session()
516
+ ce_client = create_timeout_protected_client(session, 'ce', 'us-east-1')
517
+ ec2_client = create_timeout_protected_client(session, 'ec2', region_name)
518
+ """
519
+ return session.client(service_name, region_name=region_name, config=_AWS_CLIENT_CONFIG)