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.
- runbooks/__init__.py +1 -1
- runbooks/cfat/cloud_foundations_assessment.py +626 -0
- runbooks/cloudops/cost_optimizer.py +95 -33
- runbooks/common/aws_pricing.py +388 -0
- runbooks/common/aws_pricing_api.py +205 -0
- runbooks/common/aws_utils.py +2 -2
- runbooks/common/comprehensive_cost_explorer_integration.py +979 -0
- runbooks/common/cross_account_manager.py +606 -0
- runbooks/common/enhanced_exception_handler.py +4 -0
- runbooks/common/env_utils.py +96 -0
- runbooks/common/mcp_integration.py +49 -2
- runbooks/common/organizations_client.py +579 -0
- runbooks/common/profile_utils.py +96 -2
- runbooks/common/rich_utils.py +3 -0
- runbooks/finops/cost_optimizer.py +2 -1
- runbooks/finops/elastic_ip_optimizer.py +13 -9
- runbooks/finops/embedded_mcp_validator.py +31 -0
- runbooks/finops/enhanced_trend_visualization.py +3 -2
- runbooks/finops/markdown_exporter.py +441 -0
- runbooks/finops/nat_gateway_optimizer.py +57 -20
- runbooks/finops/optimizer.py +2 -0
- runbooks/finops/single_dashboard.py +2 -2
- runbooks/finops/vpc_cleanup_exporter.py +330 -0
- runbooks/finops/vpc_cleanup_optimizer.py +895 -40
- runbooks/inventory/__init__.py +10 -1
- runbooks/inventory/cloud_foundations_integration.py +409 -0
- runbooks/inventory/core/collector.py +1148 -88
- runbooks/inventory/discovery.md +389 -0
- runbooks/inventory/drift_detection_cli.py +327 -0
- runbooks/inventory/inventory_mcp_cli.py +171 -0
- runbooks/inventory/inventory_modules.py +4 -7
- runbooks/inventory/mcp_inventory_validator.py +2149 -0
- runbooks/inventory/mcp_vpc_validator.py +23 -6
- runbooks/inventory/organizations_discovery.py +91 -1
- runbooks/inventory/rich_inventory_display.py +129 -1
- runbooks/inventory/unified_validation_engine.py +1292 -0
- runbooks/inventory/verify_ec2_security_groups.py +3 -1
- runbooks/inventory/vpc_analyzer.py +825 -7
- runbooks/inventory/vpc_flow_analyzer.py +36 -42
- runbooks/main.py +969 -42
- runbooks/monitoring/performance_monitor.py +11 -7
- runbooks/operate/dynamodb_operations.py +6 -5
- runbooks/operate/ec2_operations.py +3 -2
- runbooks/operate/networking_cost_heatmap.py +4 -3
- runbooks/operate/s3_operations.py +13 -12
- runbooks/operate/vpc_operations.py +50 -2
- runbooks/remediation/base.py +1 -1
- runbooks/remediation/commvault_ec2_analysis.py +6 -1
- runbooks/remediation/ec2_unattached_ebs_volumes.py +6 -3
- runbooks/remediation/rds_snapshot_list.py +5 -3
- runbooks/validation/__init__.py +21 -1
- runbooks/validation/comprehensive_2way_validator.py +1996 -0
- runbooks/validation/mcp_validator.py +904 -94
- runbooks/validation/terraform_citations_validator.py +363 -0
- runbooks/validation/terraform_drift_detector.py +1098 -0
- runbooks/vpc/cleanup_wrapper.py +231 -10
- runbooks/vpc/config.py +310 -62
- runbooks/vpc/cross_account_session.py +308 -0
- runbooks/vpc/heatmap_engine.py +96 -29
- runbooks/vpc/manager_interface.py +9 -9
- runbooks/vpc/mcp_no_eni_validator.py +1551 -0
- runbooks/vpc/networking_wrapper.py +14 -8
- runbooks/vpc/runbooks.inventory.organizations_discovery.log +0 -0
- runbooks/vpc/runbooks.security.report_generator.log +0 -0
- runbooks/vpc/runbooks.security.run_script.log +0 -0
- runbooks/vpc/runbooks.security.security_export.log +0 -0
- runbooks/vpc/tests/test_cost_engine.py +1 -1
- runbooks/vpc/unified_scenarios.py +3269 -0
- runbooks/vpc/vpc_cleanup_integration.py +516 -82
- {runbooks-0.9.8.dist-info → runbooks-1.0.0.dist-info}/METADATA +94 -52
- {runbooks-0.9.8.dist-info → runbooks-1.0.0.dist-info}/RECORD +75 -51
- {runbooks-0.9.8.dist-info → runbooks-1.0.0.dist-info}/WHEEL +0 -0
- {runbooks-0.9.8.dist-info → runbooks-1.0.0.dist-info}/entry_points.txt +0 -0
- {runbooks-0.9.8.dist-info → runbooks-1.0.0.dist-info}/licenses/LICENSE +0 -0
- {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
|
-
|
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
|
-
|
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...")
|