runbooks 0.9.8__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 (75) hide show
  1. runbooks/__init__.py +1 -1
  2. runbooks/cfat/cloud_foundations_assessment.py +626 -0
  3. runbooks/cloudops/cost_optimizer.py +95 -33
  4. runbooks/common/aws_pricing.py +388 -0
  5. runbooks/common/aws_pricing_api.py +205 -0
  6. runbooks/common/aws_utils.py +2 -2
  7. runbooks/common/comprehensive_cost_explorer_integration.py +979 -0
  8. runbooks/common/cross_account_manager.py +606 -0
  9. runbooks/common/enhanced_exception_handler.py +4 -0
  10. runbooks/common/env_utils.py +96 -0
  11. runbooks/common/mcp_integration.py +49 -2
  12. runbooks/common/organizations_client.py +579 -0
  13. runbooks/common/profile_utils.py +96 -2
  14. runbooks/common/rich_utils.py +3 -0
  15. runbooks/finops/cost_optimizer.py +2 -1
  16. runbooks/finops/elastic_ip_optimizer.py +13 -9
  17. runbooks/finops/embedded_mcp_validator.py +31 -0
  18. runbooks/finops/enhanced_trend_visualization.py +3 -2
  19. runbooks/finops/markdown_exporter.py +441 -0
  20. runbooks/finops/nat_gateway_optimizer.py +57 -20
  21. runbooks/finops/optimizer.py +2 -0
  22. runbooks/finops/single_dashboard.py +2 -2
  23. runbooks/finops/vpc_cleanup_exporter.py +330 -0
  24. runbooks/finops/vpc_cleanup_optimizer.py +895 -40
  25. runbooks/inventory/__init__.py +10 -1
  26. runbooks/inventory/cloud_foundations_integration.py +409 -0
  27. runbooks/inventory/core/collector.py +1148 -88
  28. runbooks/inventory/discovery.md +389 -0
  29. runbooks/inventory/drift_detection_cli.py +327 -0
  30. runbooks/inventory/inventory_mcp_cli.py +171 -0
  31. runbooks/inventory/inventory_modules.py +4 -7
  32. runbooks/inventory/mcp_inventory_validator.py +2149 -0
  33. runbooks/inventory/mcp_vpc_validator.py +23 -6
  34. runbooks/inventory/organizations_discovery.py +91 -1
  35. runbooks/inventory/rich_inventory_display.py +129 -1
  36. runbooks/inventory/unified_validation_engine.py +1292 -0
  37. runbooks/inventory/verify_ec2_security_groups.py +3 -1
  38. runbooks/inventory/vpc_analyzer.py +825 -7
  39. runbooks/inventory/vpc_flow_analyzer.py +36 -42
  40. runbooks/main.py +969 -42
  41. runbooks/monitoring/performance_monitor.py +11 -7
  42. runbooks/operate/dynamodb_operations.py +6 -5
  43. runbooks/operate/ec2_operations.py +3 -2
  44. runbooks/operate/networking_cost_heatmap.py +4 -3
  45. runbooks/operate/s3_operations.py +13 -12
  46. runbooks/operate/vpc_operations.py +50 -2
  47. runbooks/remediation/base.py +1 -1
  48. runbooks/remediation/commvault_ec2_analysis.py +6 -1
  49. runbooks/remediation/ec2_unattached_ebs_volumes.py +6 -3
  50. runbooks/remediation/rds_snapshot_list.py +5 -3
  51. runbooks/validation/__init__.py +21 -1
  52. runbooks/validation/comprehensive_2way_validator.py +1996 -0
  53. runbooks/validation/mcp_validator.py +904 -94
  54. runbooks/validation/terraform_citations_validator.py +363 -0
  55. runbooks/validation/terraform_drift_detector.py +1098 -0
  56. runbooks/vpc/cleanup_wrapper.py +231 -10
  57. runbooks/vpc/config.py +310 -62
  58. runbooks/vpc/cross_account_session.py +308 -0
  59. runbooks/vpc/heatmap_engine.py +96 -29
  60. runbooks/vpc/manager_interface.py +9 -9
  61. runbooks/vpc/mcp_no_eni_validator.py +1551 -0
  62. runbooks/vpc/networking_wrapper.py +14 -8
  63. runbooks/vpc/runbooks.inventory.organizations_discovery.log +0 -0
  64. runbooks/vpc/runbooks.security.report_generator.log +0 -0
  65. runbooks/vpc/runbooks.security.run_script.log +0 -0
  66. runbooks/vpc/runbooks.security.security_export.log +0 -0
  67. runbooks/vpc/tests/test_cost_engine.py +1 -1
  68. runbooks/vpc/unified_scenarios.py +3269 -0
  69. runbooks/vpc/vpc_cleanup_integration.py +516 -82
  70. {runbooks-0.9.8.dist-info → runbooks-1.0.0.dist-info}/METADATA +94 -52
  71. {runbooks-0.9.8.dist-info → runbooks-1.0.0.dist-info}/RECORD +75 -51
  72. {runbooks-0.9.8.dist-info → runbooks-1.0.0.dist-info}/WHEEL +0 -0
  73. {runbooks-0.9.8.dist-info → runbooks-1.0.0.dist-info}/entry_points.txt +0 -0
  74. {runbooks-0.9.8.dist-info → runbooks-1.0.0.dist-info}/licenses/LICENSE +0 -0
  75. {runbooks-0.9.8.dist-info → runbooks-1.0.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,96 @@
1
+ """
2
+ Environment Variable Utilities - Enterprise Hardcoded Value Elimination
3
+
4
+ This module provides enterprise-compliant environment variable utilities
5
+ that enforce zero hardcoded defaults policy across the entire codebase.
6
+ """
7
+
8
+ import os
9
+ from typing import Union
10
+
11
+
12
+ def get_required_env(var_name: str) -> str:
13
+ """
14
+ Get required string environment variable - NO hardcoded defaults.
15
+
16
+ Args:
17
+ var_name: Environment variable name
18
+
19
+ Returns:
20
+ Environment variable value
21
+
22
+ Raises:
23
+ ValueError: If environment variable is not set
24
+ """
25
+ value = os.getenv(var_name)
26
+ if value is None:
27
+ raise ValueError(f"Environment variable {var_name} required - no hardcoded defaults allowed")
28
+ return value
29
+
30
+
31
+ def get_required_env_int(var_name: str) -> int:
32
+ """
33
+ Get required integer environment variable - NO hardcoded defaults.
34
+
35
+ Args:
36
+ var_name: Environment variable name
37
+
38
+ Returns:
39
+ Environment variable value as integer
40
+
41
+ Raises:
42
+ ValueError: If environment variable is not set or not a valid integer
43
+ """
44
+ value = get_required_env(var_name)
45
+ try:
46
+ return int(value)
47
+ except ValueError as e:
48
+ raise ValueError(f"Environment variable {var_name} must be a valid integer, got: {value}") from e
49
+
50
+
51
+ def get_required_env_float(var_name: str) -> float:
52
+ """
53
+ Get required float environment variable - NO hardcoded defaults.
54
+
55
+ Args:
56
+ var_name: Environment variable name
57
+
58
+ Returns:
59
+ Environment variable value as float
60
+
61
+ Raises:
62
+ ValueError: If environment variable is not set or not a valid float
63
+ """
64
+ value = get_required_env(var_name)
65
+ try:
66
+ return float(value)
67
+ except ValueError as e:
68
+ raise ValueError(f"Environment variable {var_name} must be a valid float, got: {value}") from e
69
+
70
+
71
+ def get_required_env_bool(var_name: str) -> bool:
72
+ """
73
+ Get required boolean environment variable - NO hardcoded defaults.
74
+
75
+ Args:
76
+ var_name: Environment variable name
77
+
78
+ Returns:
79
+ Environment variable value as boolean
80
+
81
+ Raises:
82
+ ValueError: If environment variable is not set
83
+ """
84
+ value = get_required_env(var_name).lower()
85
+ if value in ('true', '1', 'yes', 'on'):
86
+ return True
87
+ elif value in ('false', '0', 'no', 'off'):
88
+ return False
89
+ else:
90
+ raise ValueError(f"Environment variable {var_name} must be a valid boolean (true/false), got: {value}")
91
+
92
+
93
+ # Legacy compatibility function names for existing code
94
+ _get_required_env_float = get_required_env_float
95
+ _get_required_env_int = get_required_env_int
96
+ _get_required_env = get_required_env
@@ -150,6 +150,11 @@ class EnterpriseMCPIntegrator:
150
150
  self.validation_threshold = 99.5 # Enterprise accuracy requirement
151
151
  self.tolerance_percent = 5.0 # ±5% tolerance for validation
152
152
 
153
+ # Organizations API caching to prevent duplicate calls
154
+ self._organizations_cache = {}
155
+ self._organizations_cache_timestamp = None
156
+ self._cache_ttl_minutes = 30 # 30-minute TTL for Organizations data
157
+
153
158
  # Initialize enterprise profile architecture
154
159
  self._initialize_enterprise_profiles()
155
160
 
@@ -176,6 +181,27 @@ class EnterpriseMCPIntegrator:
176
181
  except Exception as e:
177
182
  print_error(f"Failed to initialize {profile_type} profile: {str(e)}")
178
183
 
184
+ def _is_organizations_cache_valid(self) -> bool:
185
+ """Check if Organizations cache is still valid."""
186
+ if not self._organizations_cache_timestamp:
187
+ return False
188
+
189
+ cache_age_minutes = (datetime.now() - self._organizations_cache_timestamp).total_seconds() / 60
190
+ return cache_age_minutes < self._cache_ttl_minutes
191
+
192
+ def get_cached_organization_accounts(self) -> Optional[List[Dict[str, Any]]]:
193
+ """Get cached Organizations data if valid."""
194
+ if self._is_organizations_cache_valid() and 'accounts' in self._organizations_cache:
195
+ print_info("Using cached Organizations data (performance optimization)")
196
+ return self._organizations_cache['accounts']
197
+ return None
198
+
199
+ def cache_organization_accounts(self, accounts: List[Dict[str, Any]]) -> None:
200
+ """Cache Organizations data for performance optimization."""
201
+ self._organizations_cache = {'accounts': accounts}
202
+ self._organizations_cache_timestamp = datetime.now()
203
+ print_success(f"Cached Organizations data: {len(accounts)} accounts (TTL: {self._cache_ttl_minutes}min)")
204
+
179
205
  async def validate_inventory_operations(self, inventory_data: Dict[str, Any]) -> MCPValidationResult:
180
206
  """
181
207
  Validate inventory operations using MCP integration.
@@ -496,11 +522,32 @@ class EnterpriseMCPIntegrator:
496
522
  async def _validate_resource_counts(self, inventory_data: Dict, progress, task) -> None:
497
523
  """Validate resource counts across services."""
498
524
  try:
499
- resources = inventory_data.get("resources", [])
525
+ # Enhanced: Handle both dict and string inputs for robust data structure handling
526
+ if isinstance(inventory_data, str):
527
+ # Handle case where inventory_data is a JSON string
528
+ try:
529
+ inventory_data = json.loads(inventory_data)
530
+ except json.JSONDecodeError:
531
+ print_warning(f"Invalid JSON string in inventory data")
532
+ return
533
+
534
+ resources = inventory_data.get("resources", []) if isinstance(inventory_data, dict) else []
535
+
536
+ # Enhanced: Ensure resources is always a list
537
+ if not isinstance(resources, list):
538
+ resources = []
539
+
500
540
  service_counts = {}
501
541
 
502
542
  for resource in resources:
503
- service = resource.get("service", "unknown")
543
+ # Enhanced: Handle both dict and string resource entries
544
+ if isinstance(resource, dict):
545
+ service = resource.get("service", "unknown")
546
+ elif isinstance(resource, str):
547
+ service = "unknown"
548
+ else:
549
+ service = "unknown"
550
+
504
551
  service_counts[service] = service_counts.get(service, 0) + 1
505
552
 
506
553
  progress.update(task, advance=40, description=f"Validated {len(resources)} resources...")