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
@@ -14,7 +14,7 @@ Features:
14
14
  - Comprehensive session validation and metadata tracking
15
15
 
16
16
  Author: CloudOps Runbooks Team
17
- Version: 0.9.1
17
+ Version: latest version
18
18
  """
19
19
 
20
20
  import threading
@@ -45,6 +45,7 @@ _cache_lock = threading.Lock()
45
45
  @dataclass
46
46
  class CrossAccountSession:
47
47
  """Enhanced cross-account session with comprehensive metadata and refresh capabilities"""
48
+
48
49
  account_id: str
49
50
  account_name: Optional[str]
50
51
  session: Optional[boto3.Session]
@@ -57,19 +58,19 @@ class CrossAccountSession:
57
58
  last_refresh_timestamp: Optional[float] = None # Enhanced: Track refresh cycles
58
59
  refresh_count: int = 0 # Enhanced: Count refresh operations
59
60
  next_refresh_time: Optional[float] = None # Enhanced: Calculated refresh time
60
-
61
+
61
62
  def __post_init__(self):
62
63
  if self.creation_timestamp is None:
63
64
  self.creation_timestamp = time.time()
64
-
65
+
65
66
  def is_expired(self, session_ttl_minutes: int = 240) -> bool:
66
67
  """Check if session is expired based on TTL (enhanced default: 4-hour)"""
67
68
  if not self.session_expires:
68
69
  # If no explicit expiry, use creation time + TTL
69
70
  return (time.time() - self.creation_timestamp) > (session_ttl_minutes * 60)
70
-
71
+
71
72
  return time.time() > self.session_expires
72
-
73
+
73
74
  def needs_refresh(self, session_ttl_minutes: int = 240, auto_refresh_threshold: float = 0.9) -> bool:
74
75
  """Enhanced: Check if session needs preemptive refresh"""
75
76
  if not self.session_expires:
@@ -77,31 +78,31 @@ class CrossAccountSession:
77
78
  ttl_seconds = session_ttl_minutes * 60
78
79
  refresh_time = self.creation_timestamp + (ttl_seconds * auto_refresh_threshold)
79
80
  return time.time() >= refresh_time
80
-
81
+
81
82
  # Use explicit expiry time
82
83
  refresh_time = self.session_expires - ((session_ttl_minutes * 60) * (1 - auto_refresh_threshold))
83
84
  return time.time() >= refresh_time
84
-
85
+
85
86
  def calculate_next_refresh(self, session_ttl_minutes: int = 240, auto_refresh_threshold: float = 0.9):
86
87
  """Enhanced: Calculate next refresh time"""
87
88
  if self.session_expires:
88
89
  ttl_seconds = self.session_expires - time.time()
89
90
  else:
90
91
  ttl_seconds = session_ttl_minutes * 60
91
-
92
+
92
93
  self.next_refresh_time = time.time() + (ttl_seconds * auto_refresh_threshold)
93
-
94
+
94
95
  def to_dict(self) -> Dict:
95
96
  """Convert to dictionary for serialization (excluding session object)"""
96
97
  data = self.__dict__.copy()
97
- data.pop('session', None) # Remove session object for serialization
98
+ data.pop("session", None) # Remove session object for serialization
98
99
  return data
99
100
 
100
101
 
101
102
  class EnhancedCrossAccountManager:
102
103
  """
103
104
  Enhanced cross-account session manager for enterprise 61-account operations.
104
-
105
+
105
106
  This manager provides optimized cross-account access using:
106
107
  - STS AssumeRole with multiple role pattern fallbacks
107
108
  - Session caching and reuse for performance
@@ -109,16 +110,16 @@ class EnhancedCrossAccountManager:
109
110
  - Integration with Organizations API for account discovery
110
111
  - Rich progress indicators and comprehensive error handling
111
112
  """
112
-
113
+
113
114
  # Standard role patterns for cross-account access
114
115
  STANDARD_ROLE_PATTERNS = [
115
116
  "OrganizationAccountAccessRole", # AWS Organizations default
116
- "AWSControlTowerExecution", # AWS Control Tower
117
- "OrganizationAccountAccess", # Alternative naming
118
- "CrossAccountAccessRole", # Custom pattern
119
- "ReadOnlyAccess", # Fallback for read-only operations
117
+ "AWSControlTowerExecution", # AWS Control Tower
118
+ "OrganizationAccountAccess", # Alternative naming
119
+ "CrossAccountAccessRole", # Custom pattern
120
+ "ReadOnlyAccess", # Fallback for read-only operations
120
121
  ]
121
-
122
+
122
123
  def __init__(
123
124
  self,
124
125
  base_profile: Optional[str] = None,
@@ -127,11 +128,11 @@ class EnhancedCrossAccountManager:
127
128
  session_ttl_minutes: int = 240, # Enhanced: 4-hour TTL for enterprise operations
128
129
  enable_session_cache: bool = True,
129
130
  auto_refresh_threshold: float = 0.9, # Auto-refresh at 90% of TTL (216 minutes)
130
- enable_preemptive_refresh: bool = True # Preemptive session refresh capability
131
+ enable_preemptive_refresh: bool = True, # Preemptive session refresh capability
131
132
  ):
132
133
  """
133
134
  Initialize enhanced cross-account session manager.
134
-
135
+
135
136
  Args:
136
137
  base_profile: Base profile for assuming roles
137
138
  role_patterns: Custom role patterns to try (defaults to STANDARD_ROLE_PATTERNS)
@@ -148,7 +149,7 @@ class EnhancedCrossAccountManager:
148
149
  self.enable_session_cache = enable_session_cache
149
150
  self.auto_refresh_threshold = auto_refresh_threshold
150
151
  self.enable_preemptive_refresh = enable_preemptive_refresh
151
-
152
+
152
153
  # Initialize base session for role assumptions
153
154
  if base_profile:
154
155
  self.base_session = create_management_session(base_profile)
@@ -156,180 +157,174 @@ class EnhancedCrossAccountManager:
156
157
  # Use profile resolution for management operations
157
158
  management_profile = get_profile_for_operation("management", None)
158
159
  self.base_session = boto3.Session(profile_name=management_profile)
159
-
160
+
160
161
  # Performance metrics
161
162
  self.metrics = {
162
- 'sessions_created': 0,
163
- 'sessions_cached': 0,
164
- 'sessions_failed': 0,
165
- 'cache_hits': 0,
166
- 'cache_misses': 0,
167
- 'total_api_calls': 0,
163
+ "sessions_created": 0,
164
+ "sessions_cached": 0,
165
+ "sessions_failed": 0,
166
+ "cache_hits": 0,
167
+ "cache_misses": 0,
168
+ "total_api_calls": 0,
168
169
  }
169
-
170
+
170
171
  print_info(f"🔐 Enhanced cross-account manager initialized")
171
172
  print_info(f" Role patterns: {len(self.role_patterns)} configured")
172
173
  print_info(f" Session caching: {'enabled' if enable_session_cache else 'disabled'}")
173
174
  print_info(f" Session TTL: {session_ttl_minutes} minutes (4-hour enterprise standard)")
174
- print_info(f" Auto-refresh: {'enabled' if enable_preemptive_refresh else 'disabled'} at {auto_refresh_threshold:.0%} TTL")
175
+ print_info(
176
+ f" Auto-refresh: {'enabled' if enable_preemptive_refresh else 'disabled'} at {auto_refresh_threshold:.0%} TTL"
177
+ )
175
178
 
176
179
  def _get_cached_session(self, account_id: str) -> Optional[CrossAccountSession]:
177
180
  """Get cached session if valid and not expired"""
178
181
  if not self.enable_session_cache:
179
182
  return None
180
-
183
+
181
184
  with _cache_lock:
182
185
  cached_session = _SESSION_CACHE.get(account_id)
183
186
  if cached_session and not cached_session.is_expired(self.session_ttl_minutes):
184
- self.metrics['cache_hits'] += 1
187
+ self.metrics["cache_hits"] += 1
185
188
  return cached_session
186
189
  elif cached_session:
187
190
  # Remove expired session from cache
188
191
  del _SESSION_CACHE[account_id]
189
-
190
- self.metrics['cache_misses'] += 1
192
+
193
+ self.metrics["cache_misses"] += 1
191
194
  return None
192
195
 
193
196
  def _cache_session(self, session: CrossAccountSession):
194
197
  """Cache session for reuse"""
195
- if not self.enable_session_cache or session.status != 'success':
198
+ if not self.enable_session_cache or session.status != "success":
196
199
  return
197
-
200
+
198
201
  with _cache_lock:
199
202
  _SESSION_CACHE[session.account_id] = session
200
-
203
+
201
204
  print_info(f"💾 Cached session for account {session.account_id}")
202
205
 
203
206
  async def create_cross_account_sessions_from_accounts(
204
- self,
205
- accounts: List[OrganizationAccount]
207
+ self, accounts: List[OrganizationAccount]
206
208
  ) -> List[CrossAccountSession]:
207
209
  """
208
210
  Create cross-account sessions from OrganizationAccount objects.
209
-
211
+
210
212
  Args:
211
213
  accounts: List of OrganizationAccount objects
212
-
214
+
213
215
  Returns:
214
216
  List of CrossAccountSession objects
215
217
  """
216
218
  # Filter active accounts
217
- active_accounts = [acc for acc in accounts if acc.status == 'ACTIVE']
218
-
219
+ active_accounts = [acc for acc in accounts if acc.status == "ACTIVE"]
220
+
219
221
  print_info(f"🌐 Creating cross-account sessions for {len(active_accounts)} active accounts")
220
-
222
+
221
223
  return await self._create_sessions_parallel(active_accounts)
222
224
 
223
225
  async def create_cross_account_sessions_from_organization(
224
- self,
225
- management_profile: Optional[str] = None
226
+ self, management_profile: Optional[str] = None
226
227
  ) -> List[CrossAccountSession]:
227
228
  """
228
229
  Create cross-account sessions by discovering accounts from Organizations API.
229
-
230
+
230
231
  Args:
231
232
  management_profile: Profile for Organizations API access
232
-
233
+
233
234
  Returns:
234
235
  List of CrossAccountSession objects
235
236
  """
236
237
  print_info("🏢 Discovering accounts from Organizations API...")
237
-
238
+
238
239
  # Use unified Organizations client to discover accounts
239
240
  orgs_client = get_unified_organizations_client(management_profile or self.base_profile)
240
241
  accounts = await orgs_client.get_organization_accounts()
241
-
242
+
242
243
  if not accounts:
243
244
  print_warning("No accounts discovered from Organizations API")
244
245
  return []
245
-
246
+
246
247
  return await self.create_cross_account_sessions_from_accounts(accounts)
247
248
 
248
- async def _create_sessions_parallel(
249
- self,
250
- accounts: List[OrganizationAccount]
251
- ) -> List[CrossAccountSession]:
249
+ async def _create_sessions_parallel(self, accounts: List[OrganizationAccount]) -> List[CrossAccountSession]:
252
250
  """Create sessions in parallel for performance"""
253
-
251
+
254
252
  sessions = []
255
-
253
+
256
254
  with create_progress_bar() as progress:
257
255
  task = progress.add_task("Creating cross-account sessions...", total=len(accounts))
258
-
256
+
259
257
  # Use ThreadPoolExecutor for parallel session creation
260
258
  with ThreadPoolExecutor(max_workers=self.max_workers) as executor:
261
259
  future_to_account = {
262
- executor.submit(self._create_single_session, account): account
263
- for account in accounts
260
+ executor.submit(self._create_single_session, account): account for account in accounts
264
261
  }
265
-
262
+
266
263
  for future in as_completed(future_to_account):
267
264
  account = future_to_account[future]
268
265
  try:
269
266
  session = future.result()
270
267
  sessions.append(session)
271
-
268
+
272
269
  # Update progress with status
273
- if session.status == 'success':
274
- self.metrics['sessions_created'] += 1
275
- elif session.status == 'cached':
276
- self.metrics['sessions_cached'] += 1
270
+ if session.status == "success":
271
+ self.metrics["sessions_created"] += 1
272
+ elif session.status == "cached":
273
+ self.metrics["sessions_cached"] += 1
277
274
  else:
278
- self.metrics['sessions_failed'] += 1
279
-
275
+ self.metrics["sessions_failed"] += 1
276
+
280
277
  progress.advance(task)
281
-
278
+
282
279
  except Exception as e:
283
280
  print_error(f"❌ Unexpected error creating session for {account.account_id}: {e}")
284
- sessions.append(CrossAccountSession(
285
- account_id=account.account_id,
286
- account_name=account.name,
287
- session=None,
288
- status='error',
289
- error_message=str(e)
290
- ))
281
+ sessions.append(
282
+ CrossAccountSession(
283
+ account_id=account.account_id,
284
+ account_name=account.name,
285
+ session=None,
286
+ status="error",
287
+ error_message=str(e),
288
+ )
289
+ )
291
290
  progress.advance(task)
292
291
 
293
292
  # Summary
294
- successful = len([s for s in sessions if s.status in ['success', 'cached']])
295
- failed = len([s for s in sessions if s.status in ['failed', 'error']])
296
-
293
+ successful = len([s for s in sessions if s.status in ["success", "cached"]])
294
+ failed = len([s for s in sessions if s.status in ["failed", "error"]])
295
+
297
296
  print_success(f"✅ Session creation complete: {successful} successful, {failed} failed")
298
-
297
+
299
298
  return sessions
300
299
 
301
300
  def _create_single_session(self, account: OrganizationAccount) -> CrossAccountSession:
302
301
  """
303
302
  Create a single cross-account session with caching and role pattern fallback.
304
-
303
+
305
304
  This is the core implementation handling caching, role patterns, and error handling.
306
305
  """
307
306
  # Check cache first
308
307
  cached_session = self._get_cached_session(account.account_id)
309
308
  if cached_session:
310
309
  print_info(f"💾 Using cached session for {account.account_id}")
311
- cached_session.status = 'cached' # Mark as cached for metrics
310
+ cached_session.status = "cached" # Mark as cached for metrics
312
311
  return cached_session
313
-
312
+
314
313
  # Try each role pattern
315
314
  for role_name in self.role_patterns:
316
315
  try:
317
- session = self._assume_role_and_create_session(
318
- account.account_id,
319
- account.name,
320
- role_name
321
- )
322
-
323
- if session.status == 'success':
316
+ session = self._assume_role_and_create_session(account.account_id, account.name, role_name)
317
+
318
+ if session.status == "success":
324
319
  # Cache successful session
325
320
  self._cache_session(session)
326
321
  return session
327
-
322
+
328
323
  except ClientError as e:
329
- error_code = e.response.get('Error', {}).get('Code', '')
330
-
324
+ error_code = e.response.get("Error", {}).get("Code", "")
325
+
331
326
  # Continue to next role pattern for certain errors
332
- if error_code in ['AccessDenied', 'NoSuchEntity']:
327
+ if error_code in ["AccessDenied", "NoSuchEntity"]:
333
328
  continue
334
329
  else:
335
330
  # For other errors, return failure
@@ -337,175 +332,177 @@ class EnhancedCrossAccountManager:
337
332
  account_id=account.account_id,
338
333
  account_name=account.name,
339
334
  session=None,
340
- status='failed',
341
- error_message=f"AWS API error: {error_code}"
335
+ status="failed",
336
+ error_message=f"AWS API error: {error_code}",
342
337
  )
343
-
338
+
344
339
  except Exception as e:
345
340
  # For unexpected errors, continue to next role pattern
346
341
  continue
347
-
342
+
348
343
  # If no role patterns worked
349
344
  return CrossAccountSession(
350
345
  account_id=account.account_id,
351
346
  account_name=account.name,
352
347
  session=None,
353
- status='failed',
348
+ status="failed",
354
349
  role_used=None,
355
- error_message=f"Unable to assume any role pattern: {', '.join(self.role_patterns)}"
350
+ error_message=f"Unable to assume any role pattern: {', '.join(self.role_patterns)}",
356
351
  )
357
352
 
358
353
  def _assume_role_and_create_session(
359
- self,
360
- account_id: str,
361
- account_name: Optional[str],
362
- role_name: str
354
+ self, account_id: str, account_name: Optional[str], role_name: str
363
355
  ) -> CrossAccountSession:
364
356
  """Assume role and create session with validation"""
365
-
357
+
366
358
  role_arn = f"arn:aws:iam::{account_id}:role/{role_name}"
367
359
  session_name = f"CloudOpsRunbooks-{account_id[:12]}-{int(time.time())}"
368
-
360
+
369
361
  try:
370
362
  # Step 1: Assume role using base session
371
- sts_client = self.base_session.client('sts')
363
+ sts_client = self.base_session.client("sts")
372
364
  assume_role_response = sts_client.assume_role(
373
365
  RoleArn=role_arn,
374
366
  RoleSessionName=session_name,
375
- DurationSeconds=3600 # 1 hour (default)
367
+ DurationSeconds=3600, # 1 hour (default)
376
368
  )
377
-
378
- credentials = assume_role_response['Credentials']
379
- expiration = credentials['Expiration'].timestamp()
380
-
381
- self.metrics['total_api_calls'] += 1
382
-
369
+
370
+ credentials = assume_role_response["Credentials"]
371
+ expiration = credentials["Expiration"].timestamp()
372
+
373
+ self.metrics["total_api_calls"] += 1
374
+
383
375
  # Step 2: Create session with assumed role credentials
384
376
  assumed_session = boto3.Session(
385
- aws_access_key_id=credentials['AccessKeyId'],
386
- aws_secret_access_key=credentials['SecretAccessKey'],
387
- aws_session_token=credentials['SessionToken']
377
+ aws_access_key_id=credentials["AccessKeyId"],
378
+ aws_secret_access_key=credentials["SecretAccessKey"],
379
+ aws_session_token=credentials["SessionToken"],
388
380
  )
389
-
381
+
390
382
  # Step 3: Validate session with STS call
391
- assumed_sts = assumed_session.client('sts')
383
+ assumed_sts = assumed_session.client("sts")
392
384
  identity = assumed_sts.get_caller_identity()
393
- self.metrics['total_api_calls'] += 1
394
-
385
+ self.metrics["total_api_calls"] += 1
386
+
395
387
  # Verify we're in the correct account
396
- if identity['Account'] != account_id:
388
+ if identity["Account"] != account_id:
397
389
  return CrossAccountSession(
398
390
  account_id=account_id,
399
391
  account_name=account_name,
400
392
  session=None,
401
- status='failed',
402
- error_message=f"Role assumption returned wrong account: {identity['Account']}"
393
+ status="failed",
394
+ error_message=f"Role assumption returned wrong account: {identity['Account']}",
403
395
  )
404
-
396
+
405
397
  return CrossAccountSession(
406
398
  account_id=account_id,
407
399
  account_name=account_name,
408
400
  session=assumed_session,
409
- status='success',
401
+ status="success",
410
402
  role_used=role_name,
411
403
  assumed_role_arn=role_arn,
412
- session_expires=expiration
404
+ session_expires=expiration,
413
405
  )
414
-
406
+
415
407
  except ClientError as e:
416
- error_code = e.response.get('Error', {}).get('Code', '')
408
+ error_code = e.response.get("Error", {}).get("Code", "")
417
409
  return CrossAccountSession(
418
410
  account_id=account_id,
419
411
  account_name=account_name,
420
412
  session=None,
421
- status='failed',
422
- error_message=f"Failed to assume {role_name}: {error_code}"
413
+ status="failed",
414
+ error_message=f"Failed to assume {role_name}: {error_code}",
423
415
  )
424
416
 
425
417
  def get_successful_sessions(self, sessions: List[CrossAccountSession]) -> List[CrossAccountSession]:
426
418
  """Get only successful sessions for operations"""
427
- successful = [s for s in sessions if s.status in ['success', 'cached']]
419
+ successful = [s for s in sessions if s.status in ["success", "cached"]]
428
420
  print_info(f"🎯 {len(successful)}/{len(sessions)} sessions ready for cross-account operations")
429
421
  return successful
430
422
 
431
423
  def get_session_by_account_id(
432
- self,
433
- sessions: List[CrossAccountSession],
434
- account_id: str
424
+ self, sessions: List[CrossAccountSession], account_id: str
435
425
  ) -> Optional[CrossAccountSession]:
436
426
  """Get session for specific account ID"""
437
427
  for session in sessions:
438
- if session.account_id == account_id and session.status in ['success', 'cached']:
428
+ if session.account_id == account_id and session.status in ["success", "cached"]:
439
429
  return session
440
430
  return None
441
431
 
442
432
  def refresh_expired_sessions(self, sessions: List[CrossAccountSession]) -> List[CrossAccountSession]:
443
433
  """Enhanced: Refresh expired sessions with preemptive refresh support"""
444
434
  refreshed_sessions = []
445
-
435
+
446
436
  for session in sessions:
447
437
  should_refresh = False
448
438
  refresh_reason = ""
449
-
450
- if session.status in ['success', 'cached']:
439
+
440
+ if session.status in ["success", "cached"]:
451
441
  if session.is_expired(self.session_ttl_minutes):
452
442
  should_refresh = True
453
443
  refresh_reason = "expired"
454
- elif (self.enable_preemptive_refresh and
455
- session.needs_refresh(self.session_ttl_minutes, self.auto_refresh_threshold)):
444
+ elif self.enable_preemptive_refresh and session.needs_refresh(
445
+ self.session_ttl_minutes, self.auto_refresh_threshold
446
+ ):
456
447
  should_refresh = True
457
448
  refresh_reason = "preemptive"
458
-
449
+
459
450
  if should_refresh:
460
451
  print_info(f"🔄 Refreshing {refresh_reason} session for {session.account_id}")
461
-
452
+
462
453
  # Create new session
463
454
  account = OrganizationAccount(
464
455
  account_id=session.account_id,
465
456
  name=session.account_name or session.account_id,
466
457
  email="refresh@system",
467
458
  status="ACTIVE",
468
- joined_method="REFRESH"
459
+ joined_method="REFRESH",
469
460
  )
470
-
461
+
471
462
  new_session = self._create_single_session(account)
472
-
463
+
473
464
  # Enhanced: Copy refresh metadata
474
- if new_session.status == 'success':
465
+ if new_session.status == "success":
475
466
  new_session.refresh_count = session.refresh_count + 1
476
467
  new_session.last_refresh_timestamp = time.time()
477
468
  new_session.calculate_next_refresh(self.session_ttl_minutes, self.auto_refresh_threshold)
478
469
  print_info(f"✅ Session refreshed successfully (refresh #{new_session.refresh_count})")
479
-
470
+
480
471
  refreshed_sessions.append(new_session)
481
472
  else:
482
473
  refreshed_sessions.append(session)
483
-
474
+
484
475
  return refreshed_sessions
485
476
 
486
477
  def get_session_summary(self, sessions: List[CrossAccountSession]) -> Dict:
487
478
  """Enhanced: Get comprehensive session summary with refresh metrics"""
488
479
  refresh_stats = {
489
- 'sessions_needing_refresh': len([s for s in sessions if s.status in ['success', 'cached']
490
- and s.needs_refresh(self.session_ttl_minutes, self.auto_refresh_threshold)]),
491
- 'refreshed_sessions': len([s for s in sessions if s.refresh_count > 0]),
492
- 'total_refresh_operations': sum(s.refresh_count for s in sessions),
493
- 'sessions_with_next_refresh_time': len([s for s in sessions if s.next_refresh_time is not None])
480
+ "sessions_needing_refresh": len(
481
+ [
482
+ s
483
+ for s in sessions
484
+ if s.status in ["success", "cached"]
485
+ and s.needs_refresh(self.session_ttl_minutes, self.auto_refresh_threshold)
486
+ ]
487
+ ),
488
+ "refreshed_sessions": len([s for s in sessions if s.refresh_count > 0]),
489
+ "total_refresh_operations": sum(s.refresh_count for s in sessions),
490
+ "sessions_with_next_refresh_time": len([s for s in sessions if s.next_refresh_time is not None]),
494
491
  }
495
-
492
+
496
493
  return {
497
- 'total_sessions': len(sessions),
498
- 'successful_sessions': len([s for s in sessions if s.status == 'success']),
499
- 'cached_sessions': len([s for s in sessions if s.status == 'cached']),
500
- 'failed_sessions': len([s for s in sessions if s.status == 'failed']),
501
- 'error_sessions': len([s for s in sessions if s.status == 'error']),
502
- 'metrics': self.metrics.copy(),
503
- 'refresh_metrics': refresh_stats, # Enhanced: Refresh statistics
504
- 'role_patterns_configured': len(self.role_patterns),
505
- 'session_ttl_minutes': self.session_ttl_minutes,
506
- 'cache_enabled': self.enable_session_cache,
507
- 'preemptive_refresh_enabled': self.enable_preemptive_refresh, # Enhanced
508
- 'auto_refresh_threshold': self.auto_refresh_threshold, # Enhanced
494
+ "total_sessions": len(sessions),
495
+ "successful_sessions": len([s for s in sessions if s.status == "success"]),
496
+ "cached_sessions": len([s for s in sessions if s.status == "cached"]),
497
+ "failed_sessions": len([s for s in sessions if s.status == "failed"]),
498
+ "error_sessions": len([s for s in sessions if s.status == "error"]),
499
+ "metrics": self.metrics.copy(),
500
+ "refresh_metrics": refresh_stats, # Enhanced: Refresh statistics
501
+ "role_patterns_configured": len(self.role_patterns),
502
+ "session_ttl_minutes": self.session_ttl_minutes,
503
+ "cache_enabled": self.enable_session_cache,
504
+ "preemptive_refresh_enabled": self.enable_preemptive_refresh, # Enhanced
505
+ "auto_refresh_threshold": self.auto_refresh_threshold, # Enhanced
509
506
  }
510
507
 
511
508
  def clear_session_cache(self):
@@ -513,94 +510,90 @@ class EnhancedCrossAccountManager:
513
510
  with _cache_lock:
514
511
  cache_size = len(_SESSION_CACHE)
515
512
  _SESSION_CACHE.clear()
516
-
513
+
517
514
  print_info(f"🗑️ Cleared {cache_size} cached sessions")
518
515
 
519
516
  def get_cache_statistics(self) -> Dict:
520
517
  """Get cache statistics"""
521
518
  with _cache_lock:
522
519
  cache_size = len(_SESSION_CACHE)
523
- expired_count = sum(1 for s in _SESSION_CACHE.values()
524
- if s.is_expired(self.session_ttl_minutes))
525
-
520
+ expired_count = sum(1 for s in _SESSION_CACHE.values() if s.is_expired(self.session_ttl_minutes))
521
+
526
522
  return {
527
- 'cache_size': cache_size,
528
- 'expired_sessions': expired_count,
529
- 'cache_hits': self.metrics['cache_hits'],
530
- 'cache_misses': self.metrics['cache_misses'],
531
- 'hit_rate': (
532
- self.metrics['cache_hits'] /
533
- (self.metrics['cache_hits'] + self.metrics['cache_misses'])
534
- if (self.metrics['cache_hits'] + self.metrics['cache_misses']) > 0 else 0
535
- )
523
+ "cache_size": cache_size,
524
+ "expired_sessions": expired_count,
525
+ "cache_hits": self.metrics["cache_hits"],
526
+ "cache_misses": self.metrics["cache_misses"],
527
+ "hit_rate": (
528
+ self.metrics["cache_hits"] / (self.metrics["cache_hits"] + self.metrics["cache_misses"])
529
+ if (self.metrics["cache_hits"] + self.metrics["cache_misses"]) > 0
530
+ else 0
531
+ ),
536
532
  }
537
533
 
538
534
 
539
535
  # Convenience functions for easy integration
540
536
 
537
+
541
538
  async def create_cross_account_sessions(
542
539
  base_profile: Optional[str] = None,
543
540
  management_profile: Optional[str] = None,
544
541
  role_patterns: Optional[List[str]] = None,
545
- max_workers: int = 10
542
+ max_workers: int = 10,
546
543
  ) -> List[CrossAccountSession]:
547
544
  """
548
545
  Convenience function to create cross-account sessions from Organizations API.
549
-
546
+
550
547
  Args:
551
548
  base_profile: Base profile for assuming roles
552
549
  management_profile: Profile for Organizations API access
553
550
  role_patterns: Custom role patterns to try
554
551
  max_workers: Maximum parallel workers
555
-
552
+
556
553
  Returns:
557
554
  List of CrossAccountSession objects
558
555
  """
559
556
  manager = EnhancedCrossAccountManager(
560
- base_profile=base_profile,
561
- role_patterns=role_patterns,
562
- max_workers=max_workers
557
+ base_profile=base_profile, role_patterns=role_patterns, max_workers=max_workers
563
558
  )
564
-
559
+
565
560
  return await manager.create_cross_account_sessions_from_organization(management_profile)
566
561
 
567
562
 
568
- def convert_sessions_to_profiles_compatibility(
569
- sessions: List[CrossAccountSession]
570
- ) -> Tuple[List[str], Dict[str, str]]:
563
+ def convert_sessions_to_profiles_compatibility(sessions: List[CrossAccountSession]) -> Tuple[List[str], Dict[str, str]]:
571
564
  """
572
565
  Convert sessions to profile format for compatibility with existing VPC module.
573
-
566
+
574
567
  This function provides backward compatibility for modules expecting profile names.
575
568
  Note: This is a bridge function - modules should migrate to use sessions directly.
576
-
569
+
577
570
  Returns:
578
571
  Tuple of (profile_list, account_metadata) for compatibility
579
572
  """
580
- successful_sessions = [s for s in sessions if s.status in ['success', 'cached']]
581
-
573
+ successful_sessions = [s for s in sessions if s.status in ["success", "cached"]]
574
+
582
575
  # Create temporary profile identifiers (session-based)
583
576
  profile_list = [f"session:{s.account_id}" for s in successful_sessions]
584
-
577
+
585
578
  # Create account metadata
586
579
  account_metadata = {
587
580
  s.account_id: {
588
- 'id': s.account_id,
589
- 'name': s.account_name or s.account_id,
590
- 'profile_identifier': f"session:{s.account_id}",
591
- 'role_used': s.role_used,
592
- 'session_available': True
581
+ "id": s.account_id,
582
+ "name": s.account_name or s.account_id,
583
+ "profile_identifier": f"session:{s.account_id}",
584
+ "role_used": s.role_used,
585
+ "session_available": True,
593
586
  }
594
587
  for s in successful_sessions
595
588
  }
596
-
589
+
597
590
  return profile_list, account_metadata
598
591
 
599
592
 
600
593
  # Export public interface
601
594
  __all__ = [
602
- 'EnhancedCrossAccountManager',
603
- 'CrossAccountSession',
604
- 'create_cross_account_sessions',
605
- 'convert_sessions_to_profiles_compatibility',
606
- ]
595
+ "EnhancedCrossAccountManager",
596
+ "CrossAccountSession",
597
+ "create_cross_account_sessions",
598
+ "convert_sessions_to_profiles_compatibility",
599
+ ]