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.
- runbooks/__init__.py +1 -1
- runbooks/cfat/WEIGHT_CONFIG_README.md +368 -0
- runbooks/cfat/app.ts +27 -19
- runbooks/cfat/assessment/runner.py +6 -5
- runbooks/cfat/cloud_foundations_assessment.py +626 -0
- runbooks/cfat/tests/test_weight_configuration.ts +449 -0
- runbooks/cfat/weight_config.ts +574 -0
- runbooks/cloudops/cost_optimizer.py +95 -33
- runbooks/common/__init__.py +26 -9
- runbooks/common/aws_pricing.py +1353 -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/date_utils.py +115 -0
- runbooks/common/enhanced_exception_handler.py +14 -7
- runbooks/common/env_utils.py +96 -0
- runbooks/common/mcp_cost_explorer_integration.py +5 -4
- runbooks/common/mcp_integration.py +49 -2
- runbooks/common/organizations_client.py +579 -0
- runbooks/common/profile_utils.py +127 -72
- runbooks/common/rich_utils.py +3 -3
- runbooks/finops/cost_optimizer.py +2 -1
- runbooks/finops/dashboard_runner.py +47 -28
- runbooks/finops/ebs_optimizer.py +56 -9
- runbooks/finops/elastic_ip_optimizer.py +13 -9
- runbooks/finops/embedded_mcp_validator.py +31 -0
- runbooks/finops/enhanced_trend_visualization.py +10 -4
- runbooks/finops/finops_dashboard.py +6 -5
- runbooks/finops/iam_guidance.py +6 -1
- runbooks/finops/markdown_exporter.py +217 -2
- runbooks/finops/nat_gateway_optimizer.py +76 -20
- runbooks/finops/tests/test_integration.py +3 -1
- runbooks/finops/vpc_cleanup_exporter.py +28 -26
- runbooks/finops/vpc_cleanup_optimizer.py +363 -16
- runbooks/inventory/__init__.py +10 -1
- runbooks/inventory/cloud_foundations_integration.py +409 -0
- runbooks/inventory/core/collector.py +1177 -94
- runbooks/inventory/discovery.md +339 -0
- runbooks/inventory/drift_detection_cli.py +327 -0
- runbooks/inventory/inventory_mcp_cli.py +171 -0
- runbooks/inventory/inventory_modules.py +6 -9
- runbooks/inventory/list_ec2_instances.py +3 -3
- runbooks/inventory/mcp_inventory_validator.py +2149 -0
- runbooks/inventory/mcp_vpc_validator.py +23 -6
- runbooks/inventory/organizations_discovery.py +104 -9
- runbooks/inventory/rich_inventory_display.py +129 -1
- runbooks/inventory/unified_validation_engine.py +1279 -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 +708 -47
- runbooks/monitoring/performance_monitor.py +11 -7
- runbooks/operate/base.py +9 -6
- runbooks/operate/deployment_framework.py +5 -4
- runbooks/operate/deployment_validator.py +6 -5
- runbooks/operate/dynamodb_operations.py +6 -5
- runbooks/operate/ec2_operations.py +3 -2
- runbooks/operate/mcp_integration.py +6 -5
- runbooks/operate/networking_cost_heatmap.py +21 -16
- runbooks/operate/s3_operations.py +13 -12
- runbooks/operate/vpc_operations.py +100 -12
- runbooks/remediation/base.py +4 -2
- runbooks/remediation/commons.py +5 -5
- runbooks/remediation/commvault_ec2_analysis.py +68 -15
- runbooks/remediation/config/accounts_example.json +31 -0
- runbooks/remediation/ec2_unattached_ebs_volumes.py +6 -3
- runbooks/remediation/multi_account.py +120 -7
- runbooks/remediation/rds_snapshot_list.py +5 -3
- runbooks/remediation/remediation_cli.py +710 -0
- runbooks/remediation/universal_account_discovery.py +377 -0
- runbooks/security/compliance_automation_engine.py +99 -20
- runbooks/security/config/__init__.py +24 -0
- runbooks/security/config/compliance_config.py +255 -0
- runbooks/security/config/compliance_weights_example.json +22 -0
- runbooks/security/config_template_generator.py +500 -0
- runbooks/security/security_cli.py +377 -0
- runbooks/validation/__init__.py +21 -1
- runbooks/validation/cli.py +8 -7
- runbooks/validation/comprehensive_2way_validator.py +2007 -0
- runbooks/validation/mcp_validator.py +965 -101
- 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 +346 -73
- runbooks/vpc/cross_account_session.py +312 -0
- runbooks/vpc/heatmap_engine.py +115 -41
- runbooks/vpc/manager_interface.py +9 -9
- runbooks/vpc/mcp_no_eni_validator.py +1630 -0
- runbooks/vpc/networking_wrapper.py +14 -8
- runbooks/vpc/runbooks_adapter.py +33 -12
- runbooks/vpc/tests/conftest.py +4 -2
- runbooks/vpc/tests/test_cost_engine.py +4 -2
- runbooks/vpc/unified_scenarios.py +73 -3
- runbooks/vpc/vpc_cleanup_integration.py +512 -78
- {runbooks-0.9.9.dist-info → runbooks-1.0.1.dist-info}/METADATA +94 -52
- {runbooks-0.9.9.dist-info → runbooks-1.0.1.dist-info}/RECORD +101 -81
- 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/tests/results_test_finops_dashboard.xml +0 -1
- runbooks/inventory/artifacts/scale-optimize-status.txt +0 -12
- runbooks/inventory/runbooks.inventory.organizations_discovery.log +0 -0
- runbooks/inventory/runbooks.security.report_generator.log +0 -0
- runbooks/inventory/runbooks.security.run_script.log +0 -0
- runbooks/inventory/runbooks.security.security_export.log +0 -0
- {runbooks-0.9.9.dist-info → runbooks-1.0.1.dist-info}/WHEEL +0 -0
- {runbooks-0.9.9.dist-info → runbooks-1.0.1.dist-info}/entry_points.txt +0 -0
- {runbooks-0.9.9.dist-info → runbooks-1.0.1.dist-info}/licenses/LICENSE +0 -0
- {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
|
-
|
786
|
-
|
785
|
+
# Perfect match
|
786
|
+
if runbooks_value == mcp_value:
|
787
|
+
return 100.0
|
787
788
|
|
788
|
-
|
789
|
-
|
790
|
-
|
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
|
-
#
|
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": "
|
43
|
-
"MANAGEMENT_PROFILE": "
|
44
|
-
"CENTRALISED_OPS_PROFILE": "
|
45
|
-
"SINGLE_ACCOUNT_PROFILE": "
|
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
|
-
|
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 =
|
1210
|
-
billing_profile: str =
|
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
|
]
|