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
@@ -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
+ ]