runbooks 0.9.9__py3-none-any.whl → 1.0.1__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 (111) hide show
  1. runbooks/__init__.py +1 -1
  2. runbooks/cfat/WEIGHT_CONFIG_README.md +368 -0
  3. runbooks/cfat/app.ts +27 -19
  4. runbooks/cfat/assessment/runner.py +6 -5
  5. runbooks/cfat/cloud_foundations_assessment.py +626 -0
  6. runbooks/cfat/tests/test_weight_configuration.ts +449 -0
  7. runbooks/cfat/weight_config.ts +574 -0
  8. runbooks/cloudops/cost_optimizer.py +95 -33
  9. runbooks/common/__init__.py +26 -9
  10. runbooks/common/aws_pricing.py +1353 -0
  11. runbooks/common/aws_pricing_api.py +205 -0
  12. runbooks/common/aws_utils.py +2 -2
  13. runbooks/common/comprehensive_cost_explorer_integration.py +979 -0
  14. runbooks/common/cross_account_manager.py +606 -0
  15. runbooks/common/date_utils.py +115 -0
  16. runbooks/common/enhanced_exception_handler.py +14 -7
  17. runbooks/common/env_utils.py +96 -0
  18. runbooks/common/mcp_cost_explorer_integration.py +5 -4
  19. runbooks/common/mcp_integration.py +49 -2
  20. runbooks/common/organizations_client.py +579 -0
  21. runbooks/common/profile_utils.py +127 -72
  22. runbooks/common/rich_utils.py +3 -3
  23. runbooks/finops/cost_optimizer.py +2 -1
  24. runbooks/finops/dashboard_runner.py +47 -28
  25. runbooks/finops/ebs_optimizer.py +56 -9
  26. runbooks/finops/elastic_ip_optimizer.py +13 -9
  27. runbooks/finops/embedded_mcp_validator.py +31 -0
  28. runbooks/finops/enhanced_trend_visualization.py +10 -4
  29. runbooks/finops/finops_dashboard.py +6 -5
  30. runbooks/finops/iam_guidance.py +6 -1
  31. runbooks/finops/markdown_exporter.py +217 -2
  32. runbooks/finops/nat_gateway_optimizer.py +76 -20
  33. runbooks/finops/tests/test_integration.py +3 -1
  34. runbooks/finops/vpc_cleanup_exporter.py +28 -26
  35. runbooks/finops/vpc_cleanup_optimizer.py +363 -16
  36. runbooks/inventory/__init__.py +10 -1
  37. runbooks/inventory/cloud_foundations_integration.py +409 -0
  38. runbooks/inventory/core/collector.py +1177 -94
  39. runbooks/inventory/discovery.md +339 -0
  40. runbooks/inventory/drift_detection_cli.py +327 -0
  41. runbooks/inventory/inventory_mcp_cli.py +171 -0
  42. runbooks/inventory/inventory_modules.py +6 -9
  43. runbooks/inventory/list_ec2_instances.py +3 -3
  44. runbooks/inventory/mcp_inventory_validator.py +2149 -0
  45. runbooks/inventory/mcp_vpc_validator.py +23 -6
  46. runbooks/inventory/organizations_discovery.py +104 -9
  47. runbooks/inventory/rich_inventory_display.py +129 -1
  48. runbooks/inventory/unified_validation_engine.py +1279 -0
  49. runbooks/inventory/verify_ec2_security_groups.py +3 -1
  50. runbooks/inventory/vpc_analyzer.py +825 -7
  51. runbooks/inventory/vpc_flow_analyzer.py +36 -42
  52. runbooks/main.py +708 -47
  53. runbooks/monitoring/performance_monitor.py +11 -7
  54. runbooks/operate/base.py +9 -6
  55. runbooks/operate/deployment_framework.py +5 -4
  56. runbooks/operate/deployment_validator.py +6 -5
  57. runbooks/operate/dynamodb_operations.py +6 -5
  58. runbooks/operate/ec2_operations.py +3 -2
  59. runbooks/operate/mcp_integration.py +6 -5
  60. runbooks/operate/networking_cost_heatmap.py +21 -16
  61. runbooks/operate/s3_operations.py +13 -12
  62. runbooks/operate/vpc_operations.py +100 -12
  63. runbooks/remediation/base.py +4 -2
  64. runbooks/remediation/commons.py +5 -5
  65. runbooks/remediation/commvault_ec2_analysis.py +68 -15
  66. runbooks/remediation/config/accounts_example.json +31 -0
  67. runbooks/remediation/ec2_unattached_ebs_volumes.py +6 -3
  68. runbooks/remediation/multi_account.py +120 -7
  69. runbooks/remediation/rds_snapshot_list.py +5 -3
  70. runbooks/remediation/remediation_cli.py +710 -0
  71. runbooks/remediation/universal_account_discovery.py +377 -0
  72. runbooks/security/compliance_automation_engine.py +99 -20
  73. runbooks/security/config/__init__.py +24 -0
  74. runbooks/security/config/compliance_config.py +255 -0
  75. runbooks/security/config/compliance_weights_example.json +22 -0
  76. runbooks/security/config_template_generator.py +500 -0
  77. runbooks/security/security_cli.py +377 -0
  78. runbooks/validation/__init__.py +21 -1
  79. runbooks/validation/cli.py +8 -7
  80. runbooks/validation/comprehensive_2way_validator.py +2007 -0
  81. runbooks/validation/mcp_validator.py +965 -101
  82. runbooks/validation/terraform_citations_validator.py +363 -0
  83. runbooks/validation/terraform_drift_detector.py +1098 -0
  84. runbooks/vpc/cleanup_wrapper.py +231 -10
  85. runbooks/vpc/config.py +346 -73
  86. runbooks/vpc/cross_account_session.py +312 -0
  87. runbooks/vpc/heatmap_engine.py +115 -41
  88. runbooks/vpc/manager_interface.py +9 -9
  89. runbooks/vpc/mcp_no_eni_validator.py +1630 -0
  90. runbooks/vpc/networking_wrapper.py +14 -8
  91. runbooks/vpc/runbooks_adapter.py +33 -12
  92. runbooks/vpc/tests/conftest.py +4 -2
  93. runbooks/vpc/tests/test_cost_engine.py +4 -2
  94. runbooks/vpc/unified_scenarios.py +73 -3
  95. runbooks/vpc/vpc_cleanup_integration.py +512 -78
  96. {runbooks-0.9.9.dist-info → runbooks-1.0.1.dist-info}/METADATA +94 -52
  97. {runbooks-0.9.9.dist-info → runbooks-1.0.1.dist-info}/RECORD +101 -81
  98. runbooks/finops/runbooks.inventory.organizations_discovery.log +0 -0
  99. runbooks/finops/runbooks.security.report_generator.log +0 -0
  100. runbooks/finops/runbooks.security.run_script.log +0 -0
  101. runbooks/finops/runbooks.security.security_export.log +0 -0
  102. runbooks/finops/tests/results_test_finops_dashboard.xml +0 -1
  103. runbooks/inventory/artifacts/scale-optimize-status.txt +0 -12
  104. runbooks/inventory/runbooks.inventory.organizations_discovery.log +0 -0
  105. runbooks/inventory/runbooks.security.report_generator.log +0 -0
  106. runbooks/inventory/runbooks.security.run_script.log +0 -0
  107. runbooks/inventory/runbooks.security.security_export.log +0 -0
  108. {runbooks-0.9.9.dist-info → runbooks-1.0.1.dist-info}/WHEEL +0 -0
  109. {runbooks-0.9.9.dist-info → runbooks-1.0.1.dist-info}/entry_points.txt +0 -0
  110. {runbooks-0.9.9.dist-info → runbooks-1.0.1.dist-info}/licenses/LICENSE +0 -0
  111. {runbooks-0.9.9.dist-info → runbooks-1.0.1.dist-info}/top_level.txt +0 -0
@@ -779,15 +779,32 @@ class AWSO5MCPValidator:
779
779
  print_warning(f"Flow Logs validation failed: {e}")
780
780
 
781
781
  def _calculate_accuracy(self, runbooks_value: Any, mcp_value: Any) -> float:
782
- """Calculate accuracy percentage between runbooks and MCP values."""
782
+ """Calculate accuracy percentage between runbooks and MCP values with enterprise tolerance."""
783
783
 
784
784
  if isinstance(runbooks_value, (int, float)) and isinstance(mcp_value, (int, float)):
785
- if mcp_value == 0:
786
- return 100.0 if runbooks_value == 0 else 0.0
785
+ # Perfect match
786
+ if runbooks_value == mcp_value:
787
+ return 100.0
787
788
 
788
- variance = abs(runbooks_value - mcp_value) / max(abs(mcp_value), 1)
789
- accuracy = max(0, 100 - (variance * 100))
790
- return min(100.0, accuracy)
789
+ # Both zero
790
+ if mcp_value == 0 and runbooks_value == 0:
791
+ return 100.0
792
+
793
+ # One zero, other non-zero
794
+ if mcp_value == 0 or runbooks_value == 0:
795
+ return 0.0
796
+
797
+ # Calculate percentage variance
798
+ max_value = max(abs(runbooks_value), abs(mcp_value))
799
+ variance_percent = abs(runbooks_value - mcp_value) / max_value * 100
800
+
801
+ # Apply enterprise tolerance (±5% acceptable)
802
+ if variance_percent <= 5.0:
803
+ return 100.0
804
+ else:
805
+ # Scale accuracy based on variance beyond tolerance
806
+ accuracy = max(0.0, 100.0 - (variance_percent - 5.0))
807
+ return min(100.0, accuracy)
791
808
 
792
809
  elif runbooks_value == mcp_value:
793
810
  return 100.0
@@ -37,12 +37,38 @@ from ..utils.logger import configure_logger
37
37
 
38
38
  logger = configure_logger(__name__)
39
39
 
40
- # Enterprise 4-Profile AWS SSO Architecture (Proven FinOps Success Pattern)
40
+ # Global Organizations cache to prevent duplicate API calls across all instances
41
+ _GLOBAL_ORGS_CACHE = {
42
+ 'data': None,
43
+ 'timestamp': None,
44
+ 'ttl_minutes': 30
45
+ }
46
+
47
+ def _get_global_organizations_cache():
48
+ """Get cached Organizations data if valid (module-level cache)."""
49
+ if not _GLOBAL_ORGS_CACHE['timestamp']:
50
+ return None
51
+
52
+ cache_age_minutes = (datetime.now(timezone.utc) - _GLOBAL_ORGS_CACHE['timestamp']).total_seconds() / 60
53
+ if cache_age_minutes < _GLOBAL_ORGS_CACHE['ttl_minutes']:
54
+ console.print("[blue]🚀 Global performance optimization: Using cached Organizations data[/blue]")
55
+ return _GLOBAL_ORGS_CACHE['data']
56
+ return None
57
+
58
+ def _set_global_organizations_cache(data):
59
+ """Cache Organizations data globally (module-level cache)."""
60
+ _GLOBAL_ORGS_CACHE['data'] = data
61
+ _GLOBAL_ORGS_CACHE['timestamp'] = datetime.now(timezone.utc)
62
+ accounts_count = len(data.get('accounts', {}).get('discovered_accounts', [])) if data else 0
63
+ console.print(f"[green]✅ Global Organizations cache: {accounts_count} accounts (TTL: {_GLOBAL_ORGS_CACHE['ttl_minutes']}min)[/green]")
64
+
65
+ # Universal AWS Environment Profile Support (Compatible with ANY AWS Setup)
66
+ import os
41
67
  ENTERPRISE_PROFILES = {
42
- "BILLING_PROFILE": "ams-admin-Billing-ReadOnlyAccess-909135376185", # Cost Explorer access
43
- "MANAGEMENT_PROFILE": "ams-admin-ReadOnlyAccess-909135376185", # Organizations access
44
- "CENTRALISED_OPS_PROFILE": "ams-centralised-ops-ReadOnlyAccess-335083429030", # Operations access
45
- "SINGLE_ACCOUNT_PROFILE": "ams-shared-services-non-prod-ReadOnlyAccess-499201730520", # Single account ops
68
+ "BILLING_PROFILE": os.getenv("BILLING_PROFILE", "default"), # Universal compatibility
69
+ "MANAGEMENT_PROFILE": os.getenv("MANAGEMENT_PROFILE", "default"), # Works with any profile
70
+ "CENTRALISED_OPS_PROFILE": os.getenv("CENTRALISED_OPS_PROFILE", "default"), # Universal operations
71
+ "SINGLE_ACCOUNT_PROFILE": os.getenv("SINGLE_AWS_PROFILE", "default"), # Universal single account
46
72
  }
47
73
 
48
74
 
@@ -210,6 +236,51 @@ class EnhancedOrganizationsDiscovery:
210
236
  "performance_grade": None,
211
237
  }
212
238
 
239
+ # Organizations discovery cache to prevent duplicate calls (performance optimization)
240
+ self._organizations_cache = None
241
+ self._organizations_cache_timestamp = None
242
+ self._cache_ttl_minutes = 30
243
+
244
+ def _is_organizations_cache_valid(self) -> bool:
245
+ """Check if Organizations cache is still valid."""
246
+ if not self._organizations_cache_timestamp:
247
+ return False
248
+
249
+ from datetime import datetime, timedelta
250
+ cache_age_minutes = (datetime.now() - self._organizations_cache_timestamp).total_seconds() / 60
251
+ return cache_age_minutes < self._cache_ttl_minutes
252
+
253
+ async def discover_all_accounts(self) -> Dict:
254
+ """
255
+ Cached wrapper for Organizations discovery to prevent duplicate API calls.
256
+
257
+ This method implements both global and instance-level caching to avoid the
258
+ performance penalty of duplicate Organizations API calls when multiple
259
+ components need the same account data.
260
+ """
261
+ # Check global cache first (shared across all instances)
262
+ global_cached_result = _get_global_organizations_cache()
263
+ if global_cached_result:
264
+ return global_cached_result
265
+
266
+ # Check instance cache
267
+ if self._is_organizations_cache_valid() and self._organizations_cache:
268
+ console.print("[blue]🚀 Performance optimization: Using cached Organizations data[/blue]")
269
+ return self._organizations_cache
270
+
271
+ # Cache miss - perform discovery
272
+ console.print("[cyan]🔍 Performing Organizations discovery (cache miss)[/cyan]")
273
+ results = await self.discover_organization_structure()
274
+
275
+ # Cache the results
276
+ if results and results.get('accounts'):
277
+ self._organizations_cache = results
278
+ from datetime import datetime
279
+ self._organizations_cache_timestamp = datetime.now()
280
+ console.print(f"[green]✅ Cached Organizations data: {len(results.get('accounts', {}).get('discovered_accounts', []))} accounts (TTL: {self._cache_ttl_minutes}min)[/green]")
281
+
282
+ return results
283
+
213
284
  def initialize_sessions(self) -> Dict[str, str]:
214
285
  """
215
286
  Initialize AWS sessions with 4-profile architecture and comprehensive validation
@@ -350,6 +421,17 @@ class EnhancedOrganizationsDiscovery:
350
421
  )
351
422
 
352
423
  logger.info("🏢 Starting enhanced organization structure discovery with performance tracking")
424
+
425
+ # Check global cache first to prevent duplicate calls
426
+ cached_result = _get_global_organizations_cache()
427
+ if cached_result:
428
+ # Update metrics and return cached result
429
+ self.current_benchmark.finish(success=True)
430
+ self.discovery_metrics["performance_grade"] = self.current_benchmark.get_performance_grade()
431
+ self.discovery_metrics["end_time"] = self.current_benchmark.end_time
432
+ self.discovery_metrics["duration_seconds"] = self.current_benchmark.duration_seconds
433
+ return cached_result
434
+
353
435
  self.discovery_metrics["start_time"] = self.current_benchmark.start_time
354
436
 
355
437
  with Status("Initializing enterprise discovery...", console=console, spinner="dots"):
@@ -452,7 +534,7 @@ class EnhancedOrganizationsDiscovery:
452
534
  performance_benchmark_dict = asdict(self.current_benchmark)
453
535
  performance_benchmark_dict["performance_grade"] = self.current_benchmark.get_performance_grade()
454
536
 
455
- return {
537
+ discovery_result = {
456
538
  "status": "completed",
457
539
  "discovery_type": "enhanced_organization_structure",
458
540
  "organization_info": org_info,
@@ -464,6 +546,11 @@ class EnhancedOrganizationsDiscovery:
464
546
  "performance_benchmark": performance_benchmark_dict,
465
547
  "timestamp": datetime.now().isoformat(),
466
548
  }
549
+
550
+ # Cache the successful result to prevent duplicate calls
551
+ _set_global_organizations_cache(discovery_result)
552
+
553
+ return discovery_result
467
554
 
468
555
  except Exception as e:
469
556
  # Handle discovery failure
@@ -1204,10 +1291,10 @@ async def run_enhanced_organizations_discovery(
1204
1291
  return org_results
1205
1292
 
1206
1293
 
1207
- # Legacy compatibility function
1294
+ # Legacy compatibility function with universal defaults
1208
1295
  async def run_organizations_discovery(
1209
- management_profile: str = "ams-admin-ReadOnlyAccess-909135376185",
1210
- billing_profile: str = "ams-admin-Billing-ReadOnlyAccess-909135376185",
1296
+ management_profile: str = None,
1297
+ billing_profile: str = None,
1211
1298
  ) -> Dict:
1212
1299
  """
1213
1300
  Legacy compatibility function - redirects to enhanced discovery
@@ -1217,6 +1304,10 @@ async def run_organizations_discovery(
1217
1304
  """
1218
1305
  console.print("[yellow]ℹ️ Using enhanced discovery engine for improved reliability and performance[/yellow]")
1219
1306
 
1307
+ # Apply universal environment defaults
1308
+ management_profile = management_profile or os.getenv("MANAGEMENT_PROFILE", "default-management-profile")
1309
+ billing_profile = billing_profile or os.getenv("BILLING_PROFILE", "default-billing-profile")
1310
+
1220
1311
  return await run_enhanced_organizations_discovery(
1221
1312
  management_profile=management_profile,
1222
1313
  billing_profile=billing_profile,
@@ -1313,3 +1404,7 @@ if __name__ == "__main__":
1313
1404
  )
1314
1405
 
1315
1406
  asyncio.run(main())
1407
+
1408
+
1409
+ # Alias for backward compatibility
1410
+ OrganizationsDiscoveryEngine = EnhancedOrganizationsDiscovery
@@ -348,13 +348,141 @@ def display_account_tree(accounts_data: Dict[str, Dict]) -> None:
348
348
  console.print(tree)
349
349
 
350
350
 
351
+ def display_results_rich(
352
+ results_list: List[Dict[str, Any]],
353
+ fdisplay_dict: Dict[str, Dict],
354
+ defaultAction: Any = None,
355
+ file_to_save: Optional[str] = None,
356
+ subdisplay: bool = False,
357
+ title: str = "Inventory Results"
358
+ ) -> None:
359
+ """
360
+ Rich CLI replacement for legacy display_results function.
361
+
362
+ Provides backwards-compatible interface while using Rich formatting.
363
+
364
+ Args:
365
+ results_list: List of dictionaries with resource data
366
+ fdisplay_dict: Display configuration dictionary with format:
367
+ {'FieldName': {'DisplayOrder': 1, 'Heading': 'Display Name', 'Condition': [optional_filter]}}
368
+ defaultAction: Default value for missing fields
369
+ file_to_save: Optional filename to save results
370
+ subdisplay: Whether this is a sub-display (affects formatting)
371
+ title: Title for the table display
372
+
373
+ Example:
374
+ display_dict = {
375
+ 'AccountId': {'DisplayOrder': 1, 'Heading': 'Account'},
376
+ 'Region': {'DisplayOrder': 2, 'Heading': 'Region'},
377
+ 'InstanceId': {'DisplayOrder': 3, 'Heading': 'Instance ID'}
378
+ }
379
+ display_results_rich(instance_data, display_dict, title="EC2 Instances")
380
+ """
381
+ from datetime import datetime
382
+
383
+ if not results_list:
384
+ print_info("ℹ️ No results to display")
385
+ return
386
+
387
+ # Sort display fields by DisplayOrder
388
+ sorted_fields = sorted(fdisplay_dict.items(), key=lambda x: x[1].get('DisplayOrder', 999))
389
+
390
+ # Create Rich table
391
+ table = create_table(
392
+ title=f"📊 {title}",
393
+ caption=f"Found {len(results_list)} results • {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}"
394
+ )
395
+
396
+ # Add columns based on display dictionary
397
+ for field_name, field_config in sorted_fields:
398
+ heading = field_config.get('Heading', field_name)
399
+ table.add_column(heading, style="cyan" if "Id" in field_name else "white", no_wrap=True)
400
+
401
+ # Add rows
402
+ for result in results_list[:100]: # Limit to first 100 for performance
403
+ row_data = []
404
+
405
+ for field_name, field_config in sorted_fields:
406
+ # Apply condition filter if specified
407
+ condition = field_config.get('Condition', [])
408
+ if condition:
409
+ value = result.get(field_name, defaultAction)
410
+ if value not in condition:
411
+ continue
412
+
413
+ # Get field value with default fallback
414
+ value = result.get(field_name, defaultAction)
415
+ if value is None:
416
+ value = "N/A"
417
+
418
+ # Format value as string
419
+ row_data.append(str(value)[:50]) # Truncate long values
420
+
421
+ table.add_row(*row_data)
422
+
423
+ # Display the table
424
+ console.print(table)
425
+
426
+ # Show truncation notice if needed
427
+ if len(results_list) > 100:
428
+ console.print(f"[dim]Showing first 100 results. Total found: {len(results_list)}[/dim]")
429
+
430
+ # Save to file if requested
431
+ if file_to_save:
432
+ try:
433
+ import json
434
+ with open(file_to_save, 'w') as f:
435
+ json.dump(results_list, f, indent=2, default=str)
436
+ print_success(f"💾 Results saved to: {file_to_save}")
437
+ except Exception as e:
438
+ print_error(f"Failed to save file: {e}")
439
+
440
+
441
+ def display_progress_rich(current: int, total: int, description: str = "Processing") -> None:
442
+ """
443
+ Rich CLI replacement for legacy progress indicators.
444
+
445
+ Args:
446
+ current: Current progress count
447
+ total: Total items to process
448
+ description: Description of the operation
449
+ """
450
+ percentage = (current / total * 100) if total > 0 else 0
451
+ console.print(f"[cyan]{description}:[/cyan] {current}/{total} ({percentage:.1f}%)", end="\r")
452
+
453
+
454
+ def print_colorized_rich(text: str, color: str = "white") -> None:
455
+ """
456
+ Rich CLI replacement for colorama print statements.
457
+
458
+ Args:
459
+ text: Text to print
460
+ color: Color name (red, green, yellow, cyan, blue, white)
461
+ """
462
+ color_map = {
463
+ "red": "red",
464
+ "green": "green",
465
+ "yellow": "yellow",
466
+ "cyan": "cyan",
467
+ "blue": "blue",
468
+ "white": "white",
469
+ "magenta": "magenta"
470
+ }
471
+
472
+ rich_color = color_map.get(color.lower(), "white")
473
+ console.print(f"[{rich_color}]{text}[/{rich_color}]")
474
+
475
+
351
476
  # Export public functions
352
477
  __all__ = [
353
478
  "display_inventory_header",
354
- "create_inventory_progress",
479
+ "create_inventory_progress",
355
480
  "display_ec2_inventory_results",
356
481
  "display_generic_inventory_results",
357
482
  "display_inventory_error",
358
483
  "display_multi_resource_summary",
359
484
  "display_account_tree",
485
+ "display_results_rich",
486
+ "display_progress_rich",
487
+ "print_colorized_rich",
360
488
  ]