runbooks 0.7.9__py3-none-any.whl → 0.9.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/README.md +12 -1
- runbooks/cfat/__init__.py +1 -1
- runbooks/cfat/assessment/runner.py +42 -34
- runbooks/cfat/models.py +1 -1
- runbooks/common/__init__.py +152 -0
- runbooks/common/accuracy_validator.py +1039 -0
- runbooks/common/context_logger.py +440 -0
- runbooks/common/cross_module_integration.py +594 -0
- runbooks/common/enhanced_exception_handler.py +1108 -0
- runbooks/common/enterprise_audit_integration.py +634 -0
- runbooks/common/mcp_integration.py +539 -0
- runbooks/common/performance_monitor.py +387 -0
- runbooks/common/profile_utils.py +216 -0
- runbooks/common/rich_utils.py +171 -0
- runbooks/feedback/user_feedback_collector.py +440 -0
- runbooks/finops/README.md +339 -451
- runbooks/finops/__init__.py +4 -21
- runbooks/finops/account_resolver.py +279 -0
- runbooks/finops/accuracy_cross_validator.py +638 -0
- runbooks/finops/aws_client.py +721 -36
- runbooks/finops/budget_integration.py +313 -0
- runbooks/finops/cli.py +59 -5
- runbooks/finops/cost_processor.py +211 -37
- runbooks/finops/dashboard_router.py +900 -0
- runbooks/finops/dashboard_runner.py +990 -232
- runbooks/finops/embedded_mcp_validator.py +288 -0
- runbooks/finops/enhanced_dashboard_runner.py +8 -7
- runbooks/finops/enhanced_progress.py +327 -0
- runbooks/finops/enhanced_trend_visualization.py +423 -0
- runbooks/finops/finops_dashboard.py +29 -1880
- runbooks/finops/helpers.py +509 -196
- runbooks/finops/iam_guidance.py +400 -0
- runbooks/finops/markdown_exporter.py +466 -0
- runbooks/finops/multi_dashboard.py +1502 -0
- runbooks/finops/optimizer.py +15 -15
- runbooks/finops/profile_processor.py +2 -2
- runbooks/finops/runbooks.inventory.organizations_discovery.log +0 -0
- runbooks/finops/runbooks.security.report_generator.log +0 -0
- runbooks/finops/runbooks.security.run_script.log +0 -0
- runbooks/finops/runbooks.security.security_export.log +0 -0
- runbooks/finops/service_mapping.py +195 -0
- runbooks/finops/single_dashboard.py +710 -0
- runbooks/finops/tests/test_reference_images_validation.py +1 -1
- runbooks/inventory/README.md +12 -1
- runbooks/inventory/core/collector.py +157 -29
- runbooks/inventory/list_ec2_instances.py +9 -6
- runbooks/inventory/list_ssm_parameters.py +10 -10
- runbooks/inventory/organizations_discovery.py +210 -164
- runbooks/inventory/rich_inventory_display.py +74 -107
- runbooks/inventory/run_on_multi_accounts.py +13 -13
- runbooks/main.py +740 -134
- runbooks/metrics/dora_metrics_engine.py +711 -17
- runbooks/monitoring/performance_monitor.py +433 -0
- runbooks/operate/README.md +394 -0
- runbooks/operate/base.py +215 -47
- runbooks/operate/ec2_operations.py +7 -5
- runbooks/operate/privatelink_operations.py +1 -1
- runbooks/operate/vpc_endpoints.py +1 -1
- runbooks/remediation/README.md +489 -13
- runbooks/remediation/commons.py +8 -4
- runbooks/security/ENTERPRISE_SECURITY_FRAMEWORK.md +506 -0
- runbooks/security/README.md +12 -1
- runbooks/security/__init__.py +164 -33
- runbooks/security/compliance_automation.py +12 -10
- runbooks/security/compliance_automation_engine.py +1021 -0
- runbooks/security/enterprise_security_framework.py +931 -0
- runbooks/security/enterprise_security_policies.json +293 -0
- runbooks/security/integration_test_enterprise_security.py +879 -0
- runbooks/security/module_security_integrator.py +641 -0
- runbooks/security/report_generator.py +1 -1
- runbooks/security/run_script.py +4 -8
- runbooks/security/security_baseline_tester.py +36 -49
- runbooks/security/security_export.py +99 -120
- runbooks/sre/README.md +472 -0
- runbooks/sre/__init__.py +33 -0
- runbooks/sre/mcp_reliability_engine.py +1049 -0
- runbooks/sre/performance_optimization_engine.py +1032 -0
- runbooks/sre/reliability_monitoring_framework.py +1011 -0
- runbooks/validation/__init__.py +2 -2
- runbooks/validation/benchmark.py +154 -149
- runbooks/validation/cli.py +159 -147
- runbooks/validation/mcp_validator.py +265 -236
- runbooks/vpc/README.md +478 -0
- runbooks/vpc/__init__.py +2 -2
- runbooks/vpc/manager_interface.py +366 -351
- runbooks/vpc/networking_wrapper.py +62 -33
- runbooks/vpc/rich_formatters.py +22 -8
- {runbooks-0.7.9.dist-info → runbooks-0.9.0.dist-info}/METADATA +136 -54
- {runbooks-0.7.9.dist-info → runbooks-0.9.0.dist-info}/RECORD +94 -55
- {runbooks-0.7.9.dist-info → runbooks-0.9.0.dist-info}/entry_points.txt +1 -1
- runbooks/finops/cross_validation.py +0 -375
- {runbooks-0.7.9.dist-info → runbooks-0.9.0.dist-info}/WHEEL +0 -0
- {runbooks-0.7.9.dist-info → runbooks-0.9.0.dist-info}/licenses/LICENSE +0 -0
- {runbooks-0.7.9.dist-info → runbooks-0.9.0.dist-info}/top_level.txt +0 -0
runbooks/finops/__init__.py
CHANGED
@@ -39,18 +39,9 @@ from runbooks.finops.dashboard_runner import (
|
|
39
39
|
run_dashboard,
|
40
40
|
)
|
41
41
|
|
42
|
-
# Enterprise FinOps Dashboard Components
|
43
|
-
|
44
|
-
|
45
|
-
EnterpriseExecutiveDashboard,
|
46
|
-
EnterpriseExportEngine,
|
47
|
-
EnterpriseResourceAuditor,
|
48
|
-
FinOpsConfig,
|
49
|
-
MultiAccountCostTrendAnalyzer,
|
50
|
-
ResourceUtilizationHeatmapAnalyzer,
|
51
|
-
create_finops_dashboard,
|
52
|
-
run_complete_finops_analysis,
|
53
|
-
)
|
42
|
+
# Enterprise FinOps Dashboard Components - Using existing dashboard_runner.py
|
43
|
+
# Backward compatibility module for legacy tests and components
|
44
|
+
from runbooks.finops.finops_dashboard import FinOpsConfig
|
54
45
|
from runbooks.finops.helpers import (
|
55
46
|
export_audit_report_to_csv,
|
56
47
|
export_audit_report_to_json,
|
@@ -76,16 +67,8 @@ __all__ = [
|
|
76
67
|
"_run_cost_trend_analysis",
|
77
68
|
"_run_resource_heatmap_analysis",
|
78
69
|
"_run_executive_dashboard",
|
79
|
-
#
|
70
|
+
# Enterprise Dashboard Classes - backward compatibility
|
80
71
|
"FinOpsConfig",
|
81
|
-
"EnterpriseDiscovery",
|
82
|
-
"MultiAccountCostTrendAnalyzer",
|
83
|
-
"ResourceUtilizationHeatmapAnalyzer",
|
84
|
-
"EnterpriseResourceAuditor",
|
85
|
-
"EnterpriseExecutiveDashboard",
|
86
|
-
"EnterpriseExportEngine",
|
87
|
-
"create_finops_dashboard",
|
88
|
-
"run_complete_finops_analysis",
|
89
72
|
# Processors
|
90
73
|
"get_cost_data",
|
91
74
|
"get_trend",
|
@@ -0,0 +1,279 @@
|
|
1
|
+
#!/usr/bin/env python3
|
2
|
+
"""
|
3
|
+
AWS Account Name Resolution Module for CloudOps Runbooks FinOps
|
4
|
+
|
5
|
+
This module provides account ID to account name mapping functionality using
|
6
|
+
the AWS Organizations API for readable account display in FinOps dashboards.
|
7
|
+
|
8
|
+
Features:
|
9
|
+
- Organizations API integration for account discovery
|
10
|
+
- Fallback to account ID when Organizations access unavailable
|
11
|
+
- Caching for performance optimization
|
12
|
+
- Profile-aware session management
|
13
|
+
- Error handling with graceful degradation
|
14
|
+
|
15
|
+
Author: CloudOps Runbooks Team
|
16
|
+
Version: 0.7.8
|
17
|
+
"""
|
18
|
+
|
19
|
+
import functools
|
20
|
+
import time
|
21
|
+
from typing import Dict, Optional
|
22
|
+
|
23
|
+
import boto3
|
24
|
+
from botocore.exceptions import ClientError, NoCredentialsError
|
25
|
+
|
26
|
+
from runbooks.common.rich_utils import console, print_error, print_info, print_warning
|
27
|
+
|
28
|
+
|
29
|
+
class AccountResolver:
|
30
|
+
"""AWS account name resolution using Organizations API."""
|
31
|
+
|
32
|
+
def __init__(self, management_profile: Optional[str] = None):
|
33
|
+
"""
|
34
|
+
Initialize account resolver with management profile.
|
35
|
+
|
36
|
+
Args:
|
37
|
+
management_profile: AWS profile with Organizations read access
|
38
|
+
"""
|
39
|
+
self.management_profile = management_profile
|
40
|
+
self._account_cache: Dict[str, str] = {}
|
41
|
+
self._cache_timestamp = 0
|
42
|
+
self._cache_ttl = 300 # 5 minutes cache TTL
|
43
|
+
|
44
|
+
@functools.lru_cache(maxsize=1)
|
45
|
+
def _get_organizations_client(self) -> Optional[boto3.client]:
|
46
|
+
"""
|
47
|
+
Get Organizations client with error handling.
|
48
|
+
|
49
|
+
Returns:
|
50
|
+
Boto3 Organizations client or None if unavailable
|
51
|
+
"""
|
52
|
+
try:
|
53
|
+
if self.management_profile:
|
54
|
+
session = boto3.Session(profile_name=self.management_profile)
|
55
|
+
else:
|
56
|
+
session = boto3.Session()
|
57
|
+
|
58
|
+
# Use us-east-1 for Organizations API (global service)
|
59
|
+
client = session.client("organizations", region_name="us-east-1")
|
60
|
+
|
61
|
+
# Test connectivity with a simple API call
|
62
|
+
client.describe_organization()
|
63
|
+
return client
|
64
|
+
|
65
|
+
except ClientError as e:
|
66
|
+
error_code = e.response.get("Error", {}).get("Code", "")
|
67
|
+
if error_code == "AccessDeniedException":
|
68
|
+
print_warning(f"Organizations access denied for profile '{self.management_profile}'")
|
69
|
+
elif error_code == "AWSOrganizationsNotInUseException":
|
70
|
+
print_info("AWS Organizations not enabled for this account")
|
71
|
+
else:
|
72
|
+
print_warning(f"Organizations API error: {error_code}")
|
73
|
+
return None
|
74
|
+
|
75
|
+
except NoCredentialsError:
|
76
|
+
print_warning("AWS credentials not available for Organizations API")
|
77
|
+
return None
|
78
|
+
|
79
|
+
except Exception as e:
|
80
|
+
print_warning(f"Unexpected error accessing Organizations API: {e}")
|
81
|
+
return None
|
82
|
+
|
83
|
+
def _refresh_account_cache(self) -> bool:
|
84
|
+
"""
|
85
|
+
Refresh account cache from Organizations API.
|
86
|
+
|
87
|
+
Returns:
|
88
|
+
True if cache was refreshed successfully
|
89
|
+
"""
|
90
|
+
current_time = time.time()
|
91
|
+
|
92
|
+
# Check if cache is still valid
|
93
|
+
if self._account_cache and (current_time - self._cache_timestamp) < self._cache_ttl:
|
94
|
+
return True
|
95
|
+
|
96
|
+
client = self._get_organizations_client()
|
97
|
+
if not client:
|
98
|
+
return False
|
99
|
+
|
100
|
+
try:
|
101
|
+
print_info("Refreshing account names from Organizations API...")
|
102
|
+
|
103
|
+
# List all accounts in the organization
|
104
|
+
paginator = client.get_paginator("list_accounts")
|
105
|
+
accounts = {}
|
106
|
+
|
107
|
+
for page in paginator.paginate():
|
108
|
+
for account in page.get("Accounts", []):
|
109
|
+
account_id = account.get("Id", "")
|
110
|
+
account_name = account.get("Name", account_id)
|
111
|
+
|
112
|
+
# Clean up account name for display
|
113
|
+
display_name = self._clean_account_name(account_name)
|
114
|
+
accounts[account_id] = display_name
|
115
|
+
|
116
|
+
# Update cache
|
117
|
+
self._account_cache = accounts
|
118
|
+
self._cache_timestamp = current_time
|
119
|
+
|
120
|
+
print_info(f"✅ Loaded {len(accounts)} account names from Organizations API")
|
121
|
+
return True
|
122
|
+
|
123
|
+
except ClientError as e:
|
124
|
+
error_code = e.response.get("Error", {}).get("Code", "")
|
125
|
+
print_warning(f"Failed to refresh account cache: {error_code}")
|
126
|
+
return False
|
127
|
+
|
128
|
+
except Exception as e:
|
129
|
+
print_error(f"Unexpected error refreshing account cache: {e}")
|
130
|
+
return False
|
131
|
+
|
132
|
+
def _clean_account_name(self, account_name: str, max_length: int = 40) -> str:
|
133
|
+
"""
|
134
|
+
Clean up account name for display with intelligent truncation.
|
135
|
+
|
136
|
+
Args:
|
137
|
+
account_name: Raw account name from Organizations API
|
138
|
+
max_length: Maximum display length (default 40 for better readability)
|
139
|
+
|
140
|
+
Returns:
|
141
|
+
Cleaned account name suitable for enterprise display
|
142
|
+
"""
|
143
|
+
if not account_name:
|
144
|
+
return "Unknown"
|
145
|
+
|
146
|
+
# Remove common prefixes and suffixes for cleaner display
|
147
|
+
cleaned = account_name
|
148
|
+
|
149
|
+
# Remove common organizational prefixes (be more selective)
|
150
|
+
prefixes_to_remove = ["ams-", "aws-", "org-", "account-"]
|
151
|
+
for prefix in prefixes_to_remove:
|
152
|
+
if cleaned.lower().startswith(prefix):
|
153
|
+
cleaned = cleaned[len(prefix) :]
|
154
|
+
break
|
155
|
+
|
156
|
+
# Remove common suffixes (be more selective to preserve meaningful info)
|
157
|
+
suffixes_to_remove = ["-account"] # Only remove truly redundant suffixes
|
158
|
+
for suffix in suffixes_to_remove:
|
159
|
+
if cleaned.lower().endswith(suffix):
|
160
|
+
cleaned = cleaned[: -len(suffix)]
|
161
|
+
break
|
162
|
+
|
163
|
+
# Capitalize first letter of each word for professional presentation
|
164
|
+
cleaned = " ".join(word.capitalize() for word in cleaned.replace("-", " ").replace("_", " ").split())
|
165
|
+
|
166
|
+
# Smart truncation - preserve meaningful parts
|
167
|
+
if len(cleaned) > max_length:
|
168
|
+
# Strategy 1: Try to truncate at word boundaries
|
169
|
+
words = cleaned.split()
|
170
|
+
if len(words) > 2:
|
171
|
+
# Keep first two meaningful words and truncate
|
172
|
+
truncated = f"{words[0]} {words[1]}..."
|
173
|
+
if len(truncated) <= max_length:
|
174
|
+
return truncated
|
175
|
+
|
176
|
+
# Strategy 2: Truncate with ellipsis, preserving more characters
|
177
|
+
cleaned = cleaned[: max_length - 3] + "..."
|
178
|
+
|
179
|
+
return cleaned or "Unknown"
|
180
|
+
|
181
|
+
def get_account_name(self, account_id: str, max_length: int = 40) -> str:
|
182
|
+
"""
|
183
|
+
Get readable account name for account ID with configurable length.
|
184
|
+
|
185
|
+
Args:
|
186
|
+
account_id: AWS account ID
|
187
|
+
max_length: Maximum display length for the name
|
188
|
+
|
189
|
+
Returns:
|
190
|
+
Readable account name or account ID if resolution fails
|
191
|
+
"""
|
192
|
+
if not account_id:
|
193
|
+
return "Unknown"
|
194
|
+
|
195
|
+
# Try to refresh cache if needed
|
196
|
+
self._refresh_account_cache()
|
197
|
+
|
198
|
+
# Get cached name or fallback to account ID
|
199
|
+
cached_name = self._account_cache.get(account_id, account_id)
|
200
|
+
|
201
|
+
# If we have a resolved name and it's different from account ID, apply length constraint
|
202
|
+
if cached_name != account_id:
|
203
|
+
return self._clean_account_name(cached_name, max_length)
|
204
|
+
|
205
|
+
return cached_name
|
206
|
+
|
207
|
+
def get_full_account_name(self, account_id: str) -> str:
|
208
|
+
"""
|
209
|
+
Get full, untruncated account name for account ID.
|
210
|
+
|
211
|
+
Args:
|
212
|
+
account_id: AWS account ID
|
213
|
+
|
214
|
+
Returns:
|
215
|
+
Full readable account name or account ID if resolution fails
|
216
|
+
"""
|
217
|
+
if not account_id:
|
218
|
+
return "Unknown"
|
219
|
+
|
220
|
+
# Try to refresh cache if needed
|
221
|
+
self._refresh_account_cache()
|
222
|
+
|
223
|
+
# Return raw cached name without truncation
|
224
|
+
return self._account_cache.get(account_id, account_id)
|
225
|
+
|
226
|
+
def get_account_names(self, account_ids: list) -> Dict[str, str]:
|
227
|
+
"""
|
228
|
+
Get readable account names for multiple account IDs.
|
229
|
+
|
230
|
+
Args:
|
231
|
+
account_ids: List of AWS account IDs
|
232
|
+
|
233
|
+
Returns:
|
234
|
+
Dictionary mapping account IDs to readable names
|
235
|
+
"""
|
236
|
+
if not account_ids:
|
237
|
+
return {}
|
238
|
+
|
239
|
+
# Refresh cache once for all accounts
|
240
|
+
self._refresh_account_cache()
|
241
|
+
|
242
|
+
# Build result dictionary
|
243
|
+
result = {}
|
244
|
+
for account_id in account_ids:
|
245
|
+
result[account_id] = self._account_cache.get(account_id, account_id)
|
246
|
+
|
247
|
+
return result
|
248
|
+
|
249
|
+
|
250
|
+
def get_account_resolver(management_profile: Optional[str] = None) -> AccountResolver:
|
251
|
+
"""
|
252
|
+
Get account resolver instance with specified profile.
|
253
|
+
|
254
|
+
Args:
|
255
|
+
management_profile: AWS profile with Organizations read access
|
256
|
+
|
257
|
+
Returns:
|
258
|
+
AccountResolver instance
|
259
|
+
"""
|
260
|
+
return AccountResolver(management_profile)
|
261
|
+
|
262
|
+
|
263
|
+
def resolve_account_name(account_id: str, management_profile: Optional[str] = None) -> str:
|
264
|
+
"""
|
265
|
+
Convenience function to resolve single account name.
|
266
|
+
|
267
|
+
Args:
|
268
|
+
account_id: AWS account ID to resolve
|
269
|
+
management_profile: AWS profile with Organizations read access
|
270
|
+
|
271
|
+
Returns:
|
272
|
+
Readable account name or account ID if resolution fails
|
273
|
+
"""
|
274
|
+
resolver = get_account_resolver(management_profile)
|
275
|
+
return resolver.get_account_name(account_id)
|
276
|
+
|
277
|
+
|
278
|
+
# Export public interface
|
279
|
+
__all__ = ["AccountResolver", "get_account_resolver", "resolve_account_name"]
|