runbooks 0.9.9__py3-none-any.whl → 1.0.0__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 (71) hide show
  1. runbooks/cfat/cloud_foundations_assessment.py +626 -0
  2. runbooks/cloudops/cost_optimizer.py +95 -33
  3. runbooks/common/aws_pricing.py +388 -0
  4. runbooks/common/aws_pricing_api.py +205 -0
  5. runbooks/common/aws_utils.py +2 -2
  6. runbooks/common/comprehensive_cost_explorer_integration.py +979 -0
  7. runbooks/common/cross_account_manager.py +606 -0
  8. runbooks/common/enhanced_exception_handler.py +4 -0
  9. runbooks/common/env_utils.py +96 -0
  10. runbooks/common/mcp_integration.py +49 -2
  11. runbooks/common/organizations_client.py +579 -0
  12. runbooks/common/profile_utils.py +96 -2
  13. runbooks/finops/cost_optimizer.py +2 -1
  14. runbooks/finops/elastic_ip_optimizer.py +13 -9
  15. runbooks/finops/embedded_mcp_validator.py +31 -0
  16. runbooks/finops/enhanced_trend_visualization.py +3 -2
  17. runbooks/finops/markdown_exporter.py +217 -2
  18. runbooks/finops/nat_gateway_optimizer.py +57 -20
  19. runbooks/finops/vpc_cleanup_exporter.py +28 -26
  20. runbooks/finops/vpc_cleanup_optimizer.py +370 -16
  21. runbooks/inventory/__init__.py +10 -1
  22. runbooks/inventory/cloud_foundations_integration.py +409 -0
  23. runbooks/inventory/core/collector.py +1148 -88
  24. runbooks/inventory/discovery.md +389 -0
  25. runbooks/inventory/drift_detection_cli.py +327 -0
  26. runbooks/inventory/inventory_mcp_cli.py +171 -0
  27. runbooks/inventory/inventory_modules.py +4 -7
  28. runbooks/inventory/mcp_inventory_validator.py +2149 -0
  29. runbooks/inventory/mcp_vpc_validator.py +23 -6
  30. runbooks/inventory/organizations_discovery.py +91 -1
  31. runbooks/inventory/rich_inventory_display.py +129 -1
  32. runbooks/inventory/unified_validation_engine.py +1292 -0
  33. runbooks/inventory/verify_ec2_security_groups.py +3 -1
  34. runbooks/inventory/vpc_analyzer.py +825 -7
  35. runbooks/inventory/vpc_flow_analyzer.py +36 -42
  36. runbooks/main.py +654 -35
  37. runbooks/monitoring/performance_monitor.py +11 -7
  38. runbooks/operate/dynamodb_operations.py +6 -5
  39. runbooks/operate/ec2_operations.py +3 -2
  40. runbooks/operate/networking_cost_heatmap.py +4 -3
  41. runbooks/operate/s3_operations.py +13 -12
  42. runbooks/operate/vpc_operations.py +49 -1
  43. runbooks/remediation/base.py +1 -1
  44. runbooks/remediation/commvault_ec2_analysis.py +6 -1
  45. runbooks/remediation/ec2_unattached_ebs_volumes.py +6 -3
  46. runbooks/remediation/rds_snapshot_list.py +5 -3
  47. runbooks/validation/__init__.py +21 -1
  48. runbooks/validation/comprehensive_2way_validator.py +1996 -0
  49. runbooks/validation/mcp_validator.py +904 -94
  50. runbooks/validation/terraform_citations_validator.py +363 -0
  51. runbooks/validation/terraform_drift_detector.py +1098 -0
  52. runbooks/vpc/cleanup_wrapper.py +231 -10
  53. runbooks/vpc/config.py +310 -62
  54. runbooks/vpc/cross_account_session.py +308 -0
  55. runbooks/vpc/heatmap_engine.py +96 -29
  56. runbooks/vpc/manager_interface.py +9 -9
  57. runbooks/vpc/mcp_no_eni_validator.py +1551 -0
  58. runbooks/vpc/networking_wrapper.py +14 -8
  59. runbooks/vpc/runbooks.inventory.organizations_discovery.log +0 -0
  60. runbooks/vpc/runbooks.security.report_generator.log +0 -0
  61. runbooks/vpc/runbooks.security.run_script.log +0 -0
  62. runbooks/vpc/runbooks.security.security_export.log +0 -0
  63. runbooks/vpc/tests/test_cost_engine.py +1 -1
  64. runbooks/vpc/unified_scenarios.py +73 -3
  65. runbooks/vpc/vpc_cleanup_integration.py +512 -78
  66. {runbooks-0.9.9.dist-info → runbooks-1.0.0.dist-info}/METADATA +94 -52
  67. {runbooks-0.9.9.dist-info → runbooks-1.0.0.dist-info}/RECORD +71 -49
  68. {runbooks-0.9.9.dist-info → runbooks-1.0.0.dist-info}/WHEEL +0 -0
  69. {runbooks-0.9.9.dist-info → runbooks-1.0.0.dist-info}/entry_points.txt +0 -0
  70. {runbooks-0.9.9.dist-info → runbooks-1.0.0.dist-info}/licenses/LICENSE +0 -0
  71. {runbooks-0.9.9.dist-info → runbooks-1.0.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,409 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Cloud Foundations Integration Module
4
+ Integrates proven patterns into runbooks inventory module using enterprise finops architecture
5
+
6
+ This module extracts and integrates valuable patterns while maintaining runbooks architecture:
7
+ - Enhanced multi-account session management
8
+ - Specialized service discovery capabilities
9
+ - Enterprise-grade error handling and performance
10
+ - Rich CLI integration with runbooks standards
11
+
12
+ Strategic Alignment:
13
+ - "Do one thing and do it well" - Enhance existing inventory, don't duplicate
14
+ - "Move Fast, But Not So Fast We Crash" - Proven patterns with quality validation
15
+ - Maintain 3 strategic objectives alignment
16
+ """
17
+
18
+ from typing import Dict, List, Optional, Any, Tuple
19
+ import asyncio
20
+ from concurrent.futures import ThreadPoolExecutor
21
+ import time
22
+ import logging
23
+ from dataclasses import dataclass, field
24
+ from pathlib import Path
25
+
26
+ import boto3
27
+ from botocore.exceptions import ClientError, ProfileNotFound
28
+
29
+ # Import runbooks enterprise standards
30
+ from runbooks.common.rich_utils import (
31
+ console, print_header, print_success, print_warning, print_error,
32
+ create_table, create_progress_bar, create_panel
33
+ )
34
+ from runbooks.common.profile_utils import get_profile_for_operation
35
+
36
+ __version__ = "1.0.0"
37
+
38
+
39
+ @dataclass
40
+ class EnhancedAccountInfo:
41
+ """
42
+ Enhanced account information based on runbooks inventory account patterns
43
+ Supports enterprise multi-account operations with session management
44
+ """
45
+ account_id: str
46
+ account_name: str
47
+ account_status: str
48
+ account_email: str
49
+ joined_method: str
50
+ organizational_unit: Optional[str] = None
51
+ account_type: str = "member" # management, member, suspended
52
+ session_cache: Dict[str, Any] = field(default_factory=dict)
53
+ last_accessed: Optional[float] = None
54
+
55
+ def __post_init__(self):
56
+ """Initialize session cache with TTL management"""
57
+ self.last_accessed = time.time()
58
+
59
+ @property
60
+ def is_session_expired(self) -> bool:
61
+ """Check if session cache is expired (4-hour TTL)"""
62
+ if not self.last_accessed:
63
+ return True
64
+ return (time.time() - self.last_accessed) > (4 * 3600) # 4 hours TTL
65
+
66
+
67
+ class CloudFoundationsAccountManager:
68
+ """
69
+ Enhanced Account Manager integrating proven runbooks inventory patterns
70
+
71
+ Key Features:
72
+ - Multi-account organization discovery with filtering
73
+ - 4-hour TTL session management for 60+ account operations
74
+ - Enhanced error handling and graceful degradation
75
+ - Rich CLI integration with runbooks standards
76
+ - Cross-account role assumption with caching
77
+
78
+ Enhanced from: runbooks.inventory patterns with proven finops integration
79
+ """
80
+
81
+ def __init__(self, profile: Optional[str] = None):
82
+ """Initialize account manager with profile management"""
83
+ self.profile = get_profile_for_operation("management", profile)
84
+ self.accounts: Dict[str, EnhancedAccountInfo] = {}
85
+ self.session_cache: Dict[str, Dict[str, Any]] = {}
86
+
87
+ # Initialize base session
88
+ try:
89
+ self.base_session = boto3.Session(profile_name=self.profile)
90
+ print_success(f"Initialized Cloud Foundations Account Manager with profile: {self.profile}")
91
+ except ProfileNotFound as e:
92
+ print_error(f"Profile not found: {self.profile}")
93
+ raise
94
+
95
+ async def discover_organization_structure(self) -> Dict[str, List[EnhancedAccountInfo]]:
96
+ """
97
+ Enhanced organization discovery with structure analysis
98
+
99
+ Returns:
100
+ Dictionary organized by organizational units with account lists
101
+
102
+ Enhanced from: runbooks inventory organization discovery patterns with improved filtering and structure
103
+ """
104
+ print_header("Organization Discovery", __version__)
105
+
106
+ try:
107
+ with create_progress_bar() as progress:
108
+ discovery_task = progress.add_task("Discovering organization structure...", total=100)
109
+
110
+ # Step 1: Discover organization accounts (40%)
111
+ accounts = await self._discover_accounts()
112
+ progress.update(discovery_task, advance=40)
113
+
114
+ # Step 2: Get organizational unit structure (30%)
115
+ ou_structure = await self._discover_organizational_units()
116
+ progress.update(discovery_task, advance=30)
117
+
118
+ # Step 3: Map accounts to OUs (30%)
119
+ structured_accounts = await self._map_accounts_to_ous(accounts, ou_structure)
120
+ progress.update(discovery_task, advance=30)
121
+
122
+ print_success(f"Discovered {len(accounts)} accounts across {len(structured_accounts)} organizational units")
123
+ return structured_accounts
124
+
125
+ except ClientError as e:
126
+ print_error(f"Organization discovery failed: {e}")
127
+ raise
128
+
129
+ async def _discover_accounts(self) -> List[EnhancedAccountInfo]:
130
+ """
131
+ Discover all organization accounts with enhanced filtering
132
+ Based on proven runbooks inventory patterns
133
+ """
134
+ try:
135
+ orgs_client = self.base_session.client('organizations')
136
+ accounts = []
137
+
138
+ # Use paginator for large organizations (60+ accounts)
139
+ paginator = orgs_client.get_paginator('list_accounts')
140
+
141
+ for page in paginator.paginate():
142
+ for account_data in page['Accounts']:
143
+ # Filter active accounts only (decommissioning filter)
144
+ if account_data['Status'] == 'ACTIVE':
145
+ account_info = EnhancedAccountInfo(
146
+ account_id=account_data['Id'],
147
+ account_name=account_data['Name'],
148
+ account_status=account_data['Status'],
149
+ account_email=account_data['Email'],
150
+ joined_method=account_data['JoinedMethod'],
151
+ account_type='management' if account_data['Id'] == self._get_management_account_id() else 'member'
152
+ )
153
+ accounts.append(account_info)
154
+ self.accounts[account_data['Id']] = account_info
155
+
156
+ return accounts
157
+
158
+ except ClientError as e:
159
+ if e.response['Error']['Code'] == 'AWSOrganizationsNotInUseException':
160
+ print_warning("Account is not part of an AWS Organization")
161
+ return []
162
+ raise
163
+
164
+ async def _discover_organizational_units(self) -> Dict[str, Dict[str, Any]]:
165
+ """Discover organizational unit structure"""
166
+ try:
167
+ orgs_client = self.base_session.client('organizations')
168
+
169
+ # Get root and traverse OU structure
170
+ roots = orgs_client.list_roots()['Roots']
171
+ ou_structure = {}
172
+
173
+ for root in roots:
174
+ root_id = root['Id']
175
+ ou_structure[root_id] = {
176
+ 'Name': root['Name'],
177
+ 'Type': 'ROOT',
178
+ 'Children': await self._get_ou_children(orgs_client, root_id)
179
+ }
180
+
181
+ return ou_structure
182
+
183
+ except ClientError as e:
184
+ print_warning(f"Could not discover OU structure: {e}")
185
+ return {}
186
+
187
+ async def _get_ou_children(self, orgs_client, parent_id: str) -> List[Dict[str, Any]]:
188
+ """Recursively get OU children"""
189
+ children = []
190
+
191
+ try:
192
+ paginator = orgs_client.get_paginator('list_organizational_units_for_parent')
193
+ for page in paginator.paginate(ParentId=parent_id):
194
+ for ou in page['OrganizationalUnits']:
195
+ child_info = {
196
+ 'Id': ou['Id'],
197
+ 'Name': ou['Name'],
198
+ 'Type': 'ORGANIZATIONAL_UNIT',
199
+ 'Children': await self._get_ou_children(orgs_client, ou['Id'])
200
+ }
201
+ children.append(child_info)
202
+
203
+ except ClientError as e:
204
+ print_warning(f"Could not get children for {parent_id}: {e}")
205
+
206
+ return children
207
+
208
+ async def _map_accounts_to_ous(self,
209
+ accounts: List[EnhancedAccountInfo],
210
+ ou_structure: Dict[str, Dict[str, Any]]) -> Dict[str, List[EnhancedAccountInfo]]:
211
+ """Map accounts to their organizational units"""
212
+ mapped_accounts = {}
213
+
214
+ try:
215
+ orgs_client = self.base_session.client('organizations')
216
+
217
+ for account in accounts:
218
+ try:
219
+ # Find which OU this account belongs to
220
+ parents = orgs_client.list_parents(ChildId=account.account_id)['Parents']
221
+
222
+ for parent in parents:
223
+ parent_id = parent['Id']
224
+ parent_type = parent['Type']
225
+
226
+ # Create OU key for grouping
227
+ ou_key = f"{parent_type}:{parent_id}"
228
+ if ou_key not in mapped_accounts:
229
+ mapped_accounts[ou_key] = []
230
+
231
+ account.organizational_unit = parent_id
232
+ mapped_accounts[ou_key].append(account)
233
+
234
+ except ClientError as e:
235
+ print_warning(f"Could not map account {account.account_id} to OU: {e}")
236
+ # Add to ungrouped accounts
237
+ if 'ungrouped' not in mapped_accounts:
238
+ mapped_accounts['ungrouped'] = []
239
+ mapped_accounts['ungrouped'].append(account)
240
+
241
+ except Exception as e:
242
+ print_warning(f"Account to OU mapping encountered issues: {e}")
243
+
244
+ return mapped_accounts
245
+
246
+ def get_cross_account_session(self,
247
+ target_account_id: str,
248
+ role_name: str = "OrganizationAccountAccessRole") -> Optional[boto3.Session]:
249
+ """
250
+ Get cross-account session with enhanced caching and TTL management
251
+
252
+ Based on runbooks inventory patterns with 4-hour TTL optimization
253
+ """
254
+ session_key = f"{target_account_id}_{role_name}"
255
+
256
+ # Check session cache with TTL validation
257
+ if (session_key in self.session_cache and
258
+ not self._is_session_expired(self.session_cache[session_key])):
259
+ return self.session_cache[session_key]['session']
260
+
261
+ # Create new cross-account session
262
+ try:
263
+ sts_client = self.base_session.client('sts')
264
+ role_arn = f"arn:aws:iam::{target_account_id}:role/{role_name}"
265
+
266
+ response = sts_client.assume_role(
267
+ RoleArn=role_arn,
268
+ RoleSessionName=f"CloudOpsRunbooks-CF-{target_account_id}",
269
+ DurationSeconds=14400 # 4 hours maximum
270
+ )
271
+
272
+ credentials = response['Credentials']
273
+ cross_account_session = boto3.Session(
274
+ aws_access_key_id=credentials['AccessKeyId'],
275
+ aws_secret_access_key=credentials['SecretAccessKey'],
276
+ aws_session_token=credentials['SessionToken']
277
+ )
278
+
279
+ # Cache session with TTL
280
+ self.session_cache[session_key] = {
281
+ 'session': cross_account_session,
282
+ 'expires_at': credentials['Expiration'],
283
+ 'created_at': time.time()
284
+ }
285
+
286
+ return cross_account_session
287
+
288
+ except ClientError as e:
289
+ if 'AccessDenied' in str(e):
290
+ print_warning(f"Cross-account access denied for {target_account_id} (role: {role_name})")
291
+ else:
292
+ print_warning(f"Cross-account session creation failed for {target_account_id}: {e}")
293
+ return None
294
+
295
+ def _is_session_expired(self, session_info: Dict[str, Any]) -> bool:
296
+ """Check if cached session is expired"""
297
+ if 'expires_at' in session_info:
298
+ return time.time() >= session_info['expires_at'].timestamp()
299
+ return True
300
+
301
+ def _get_management_account_id(self) -> Optional[str]:
302
+ """Get the management account ID"""
303
+ try:
304
+ orgs_client = self.base_session.client('organizations')
305
+ org_info = orgs_client.describe_organization()
306
+ return org_info['Organization']['MasterAccountId']
307
+ except ClientError:
308
+ return None
309
+
310
+ def display_organization_summary(self, structured_accounts: Dict[str, List[EnhancedAccountInfo]]):
311
+ """
312
+ Display organization summary with Rich CLI formatting
313
+ Enterprise-ready visualization of multi-account structure
314
+ """
315
+ print_header("Organization Structure Summary", __version__)
316
+
317
+ # Create summary table
318
+ table = create_table(
319
+ title="Multi-Account Organization Structure",
320
+ caption=f"Discovered via profile: {self.profile}"
321
+ )
322
+
323
+ table.add_column("Organizational Unit", style="cyan", no_wrap=True)
324
+ table.add_column("Account Count", justify="right", style="green")
325
+ table.add_column("Account Types", style="blue")
326
+ table.add_column("Status", style="yellow")
327
+
328
+ total_accounts = 0
329
+ management_accounts = 0
330
+ member_accounts = 0
331
+
332
+ for ou_key, accounts in structured_accounts.items():
333
+ account_types = []
334
+ active_count = 0
335
+
336
+ for account in accounts:
337
+ total_accounts += 1
338
+ if account.account_type == 'management':
339
+ management_accounts += 1
340
+ account_types.append('Management')
341
+ else:
342
+ member_accounts += 1
343
+ account_types.append('Member')
344
+
345
+ if account.account_status == 'ACTIVE':
346
+ active_count += 1
347
+
348
+ ou_display = ou_key.replace('ORGANIZATIONAL_UNIT:', 'OU: ').replace('ROOT:', 'Root: ')
349
+ table.add_row(
350
+ ou_display,
351
+ str(len(accounts)),
352
+ ', '.join(set(account_types)),
353
+ f"{active_count}/{len(accounts)} Active"
354
+ )
355
+
356
+ console.print(table)
357
+
358
+ # Summary panel
359
+ summary_text = f"""
360
+ Total Accounts: {total_accounts}
361
+ Management Accounts: {management_accounts}
362
+ Member Accounts: {member_accounts}
363
+ Organizational Units: {len(structured_accounts)}
364
+ Session Cache: {len(self.session_cache)} active sessions
365
+ """
366
+
367
+ summary_panel = create_panel(
368
+ summary_text,
369
+ title="Organization Summary",
370
+ style="green"
371
+ )
372
+ console.print(summary_panel)
373
+
374
+
375
+ async def main():
376
+ """
377
+ Demonstration of Cloud Foundations integration
378
+ Shows enhanced multi-account discovery capabilities
379
+ """
380
+ import argparse
381
+
382
+ parser = argparse.ArgumentParser(
383
+ description="Cloud Foundations Integration - Enhanced Multi-Account Discovery"
384
+ )
385
+ parser.add_argument(
386
+ '--profile',
387
+ help='AWS profile to use (defaults to management profile detection)'
388
+ )
389
+ args = parser.parse_args()
390
+
391
+ # Initialize enhanced account manager
392
+ try:
393
+ account_manager = CloudFoundationsAccountManager(profile=args.profile)
394
+
395
+ # Discover organization structure
396
+ structured_accounts = await account_manager.discover_organization_structure()
397
+
398
+ # Display results with Rich formatting
399
+ account_manager.display_organization_summary(structured_accounts)
400
+
401
+ print_success("Cloud Foundations integration demonstration completed successfully")
402
+
403
+ except Exception as e:
404
+ print_error(f"Integration demonstration failed: {e}")
405
+ raise
406
+
407
+
408
+ if __name__ == "__main__":
409
+ asyncio.run(main())