runbooks 0.7.6__py3-none-any.whl → 0.7.9__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/base.py +5 -1
- runbooks/cfat/__init__.py +8 -4
- runbooks/cfat/assessment/collectors.py +171 -14
- runbooks/cfat/assessment/compliance.py +871 -0
- runbooks/cfat/assessment/runner.py +122 -11
- runbooks/cfat/models.py +6 -2
- runbooks/common/logger.py +14 -0
- runbooks/common/rich_utils.py +451 -0
- runbooks/enterprise/__init__.py +68 -0
- runbooks/enterprise/error_handling.py +411 -0
- runbooks/enterprise/logging.py +439 -0
- runbooks/enterprise/multi_tenant.py +583 -0
- runbooks/finops/README.md +468 -241
- runbooks/finops/__init__.py +39 -3
- runbooks/finops/cli.py +83 -18
- runbooks/finops/cross_validation.py +375 -0
- runbooks/finops/dashboard_runner.py +812 -164
- runbooks/finops/enhanced_dashboard_runner.py +525 -0
- runbooks/finops/finops_dashboard.py +1892 -0
- runbooks/finops/helpers.py +485 -51
- runbooks/finops/optimizer.py +823 -0
- runbooks/finops/tests/__init__.py +19 -0
- runbooks/finops/tests/results_test_finops_dashboard.xml +1 -0
- runbooks/finops/tests/run_comprehensive_tests.py +421 -0
- runbooks/finops/tests/run_tests.py +305 -0
- runbooks/finops/tests/test_finops_dashboard.py +705 -0
- runbooks/finops/tests/test_integration.py +477 -0
- runbooks/finops/tests/test_performance.py +380 -0
- runbooks/finops/tests/test_performance_benchmarks.py +500 -0
- runbooks/finops/tests/test_reference_images_validation.py +867 -0
- runbooks/finops/tests/test_single_account_features.py +715 -0
- runbooks/finops/tests/validate_test_suite.py +220 -0
- runbooks/finops/types.py +1 -1
- runbooks/hitl/enhanced_workflow_engine.py +725 -0
- runbooks/inventory/artifacts/scale-optimize-status.txt +12 -0
- runbooks/inventory/collectors/aws_comprehensive.py +442 -0
- runbooks/inventory/collectors/enterprise_scale.py +281 -0
- runbooks/inventory/core/collector.py +172 -13
- runbooks/inventory/discovery.md +1 -1
- runbooks/inventory/list_ec2_instances.py +18 -20
- runbooks/inventory/list_ssm_parameters.py +31 -3
- runbooks/inventory/organizations_discovery.py +1269 -0
- runbooks/inventory/rich_inventory_display.py +393 -0
- runbooks/inventory/run_on_multi_accounts.py +35 -19
- runbooks/inventory/runbooks.security.report_generator.log +0 -0
- runbooks/inventory/runbooks.security.run_script.log +0 -0
- runbooks/inventory/vpc_flow_analyzer.py +1030 -0
- runbooks/main.py +2215 -119
- runbooks/metrics/dora_metrics_engine.py +599 -0
- runbooks/operate/__init__.py +2 -2
- runbooks/operate/base.py +122 -10
- runbooks/operate/deployment_framework.py +1032 -0
- runbooks/operate/deployment_validator.py +853 -0
- runbooks/operate/dynamodb_operations.py +10 -6
- runbooks/operate/ec2_operations.py +319 -11
- runbooks/operate/executive_dashboard.py +779 -0
- runbooks/operate/mcp_integration.py +750 -0
- runbooks/operate/nat_gateway_operations.py +1120 -0
- runbooks/operate/networking_cost_heatmap.py +685 -0
- runbooks/operate/privatelink_operations.py +940 -0
- runbooks/operate/s3_operations.py +10 -6
- runbooks/operate/vpc_endpoints.py +644 -0
- runbooks/operate/vpc_operations.py +1038 -0
- runbooks/remediation/__init__.py +2 -2
- runbooks/remediation/acm_remediation.py +1 -1
- runbooks/remediation/base.py +1 -1
- runbooks/remediation/cloudtrail_remediation.py +1 -1
- runbooks/remediation/cognito_remediation.py +1 -1
- runbooks/remediation/dynamodb_remediation.py +1 -1
- runbooks/remediation/ec2_remediation.py +1 -1
- runbooks/remediation/ec2_unattached_ebs_volumes.py +1 -1
- runbooks/remediation/kms_enable_key_rotation.py +1 -1
- runbooks/remediation/kms_remediation.py +1 -1
- runbooks/remediation/lambda_remediation.py +1 -1
- runbooks/remediation/multi_account.py +1 -1
- runbooks/remediation/rds_remediation.py +1 -1
- runbooks/remediation/s3_block_public_access.py +1 -1
- runbooks/remediation/s3_enable_access_logging.py +1 -1
- runbooks/remediation/s3_encryption.py +1 -1
- runbooks/remediation/s3_remediation.py +1 -1
- runbooks/remediation/vpc_remediation.py +475 -0
- runbooks/security/__init__.py +3 -1
- runbooks/security/compliance_automation.py +632 -0
- runbooks/security/report_generator.py +10 -0
- runbooks/security/run_script.py +31 -5
- runbooks/security/security_baseline_tester.py +169 -30
- runbooks/security/security_export.py +477 -0
- runbooks/validation/__init__.py +10 -0
- runbooks/validation/benchmark.py +484 -0
- runbooks/validation/cli.py +356 -0
- runbooks/validation/mcp_validator.py +768 -0
- runbooks/vpc/__init__.py +38 -0
- runbooks/vpc/config.py +212 -0
- runbooks/vpc/cost_engine.py +347 -0
- runbooks/vpc/heatmap_engine.py +605 -0
- runbooks/vpc/manager_interface.py +634 -0
- runbooks/vpc/networking_wrapper.py +1260 -0
- runbooks/vpc/rich_formatters.py +679 -0
- runbooks/vpc/tests/__init__.py +5 -0
- runbooks/vpc/tests/conftest.py +356 -0
- runbooks/vpc/tests/test_cli_integration.py +530 -0
- runbooks/vpc/tests/test_config.py +458 -0
- runbooks/vpc/tests/test_cost_engine.py +479 -0
- runbooks/vpc/tests/test_networking_wrapper.py +512 -0
- {runbooks-0.7.6.dist-info → runbooks-0.7.9.dist-info}/METADATA +40 -12
- {runbooks-0.7.6.dist-info → runbooks-0.7.9.dist-info}/RECORD +111 -50
- {runbooks-0.7.6.dist-info → runbooks-0.7.9.dist-info}/WHEEL +0 -0
- {runbooks-0.7.6.dist-info → runbooks-0.7.9.dist-info}/entry_points.txt +0 -0
- {runbooks-0.7.6.dist-info → runbooks-0.7.9.dist-info}/licenses/LICENSE +0 -0
- {runbooks-0.7.6.dist-info → runbooks-0.7.9.dist-info}/top_level.txt +0 -0
@@ -22,6 +22,11 @@ from datetime import datetime
|
|
22
22
|
from typing import Dict, List, Optional, Set
|
23
23
|
|
24
24
|
from loguru import logger
|
25
|
+
from rich.console import Console
|
26
|
+
from rich.panel import Panel
|
27
|
+
from rich.progress import Progress, SpinnerColumn, TextColumn, TimeElapsedColumn, BarColumn
|
28
|
+
from rich.table import Table
|
29
|
+
from rich.live import Live
|
25
30
|
|
26
31
|
from runbooks import __version__
|
27
32
|
from runbooks.base import CloudFoundationsBase, ProgressTracker
|
@@ -35,6 +40,17 @@ from runbooks.cfat.models import (
|
|
35
40
|
)
|
36
41
|
from runbooks.config import RunbooksConfig
|
37
42
|
|
43
|
+
# Enterprise 4-Profile Architecture - Proven FinOps Patterns
|
44
|
+
ENTERPRISE_PROFILES = {
|
45
|
+
"BILLING_PROFILE": "ams-admin-Billing-ReadOnlyAccess-909135376185",
|
46
|
+
"MANAGEMENT_PROFILE": "ams-admin-ReadOnlyAccess-909135376185",
|
47
|
+
"CENTRALISED_OPS_PROFILE": "ams-centralised-ops-ReadOnlyAccess-335083429030",
|
48
|
+
"SINGLE_ACCOUNT_PROFILE": "ams-shared-services-non-prod-ReadOnlyAccess-499201730520"
|
49
|
+
}
|
50
|
+
|
51
|
+
# Rich console instance for consistent formatting
|
52
|
+
console = Console()
|
53
|
+
|
38
54
|
|
39
55
|
class CloudFoundationsAssessment(CloudFoundationsBase):
|
40
56
|
"""
|
@@ -80,8 +96,8 @@ class CloudFoundationsAssessment(CloudFoundationsBase):
|
|
80
96
|
report = assessment.run_assessment()
|
81
97
|
|
82
98
|
# Analyze results
|
83
|
-
print(f"Compliance Score: {report.summary.compliance_score}/100")
|
84
|
-
print(f"Critical Issues: {report.summary.critical_issues}")
|
99
|
+
console.print(f"[green]Compliance Score: {report.summary.compliance_score}/100[/green]")
|
100
|
+
console.print(f"[red]Critical Issues: {report.summary.critical_issues}[/red]")
|
85
101
|
|
86
102
|
# Export in multiple formats
|
87
103
|
report.to_html("compliance_report.html")
|
@@ -96,10 +112,28 @@ class CloudFoundationsAssessment(CloudFoundationsBase):
|
|
96
112
|
def __init__(
|
97
113
|
self, profile: Optional[str] = None, region: Optional[str] = None, config: Optional[RunbooksConfig] = None
|
98
114
|
):
|
99
|
-
"""Initialize assessment runner."""
|
100
|
-
|
115
|
+
"""Initialize assessment runner with enterprise profile support."""
|
116
|
+
# Support enterprise profile shortcuts
|
117
|
+
if profile in ENTERPRISE_PROFILES:
|
118
|
+
actual_profile = ENTERPRISE_PROFILES[profile]
|
119
|
+
console.print(f"[blue]🏢 Using enterprise profile: {profile} -> {actual_profile}[/blue]")
|
120
|
+
super().__init__(actual_profile, region, config)
|
121
|
+
else:
|
122
|
+
super().__init__(profile, region, config)
|
123
|
+
|
101
124
|
self.assessment_config = AssessmentConfig()
|
102
125
|
self._available_checks = self._discover_checks()
|
126
|
+
self._performance_target = 30.0 # <30s target for cfat assessments
|
127
|
+
self._assessment_start_time = None
|
128
|
+
|
129
|
+
console.print(Panel(
|
130
|
+
f"[green]✅ Cloud Foundations Assessment initialized[/green]\n"
|
131
|
+
f"[white]Profile: {self.profile or 'default'}[/white]\n"
|
132
|
+
f"[white]Region: {self.region}[/white]\n"
|
133
|
+
f"[white]Available checks: {len(self._available_checks)}[/white]",
|
134
|
+
title="🔍 CFAT Assessment Engine",
|
135
|
+
border_style="blue"
|
136
|
+
))
|
103
137
|
|
104
138
|
def _discover_checks(self) -> Dict[str, type]:
|
105
139
|
"""Discover available assessment checks."""
|
@@ -153,23 +187,33 @@ class CloudFoundationsAssessment(CloudFoundationsBase):
|
|
153
187
|
Returns:
|
154
188
|
Assessment report with results
|
155
189
|
"""
|
156
|
-
|
157
|
-
|
190
|
+
# Performance benchmark start
|
191
|
+
self._assessment_start_time = time.time()
|
192
|
+
console.print(Panel(
|
193
|
+
"[cyan]🚀 Starting Cloud Foundations assessment...[/cyan]",
|
194
|
+
title="🔍 CFAT Assessment",
|
195
|
+
border_style="cyan"
|
196
|
+
))
|
158
197
|
|
159
198
|
try:
|
160
199
|
# Get account information
|
161
200
|
account_id = self.get_account_id()
|
162
201
|
region = self.region or "us-east-1"
|
202
|
+
console.print(f"[blue]📋 Account: {account_id} | Region: {region}[/blue]")
|
163
203
|
|
164
204
|
# Determine which checks to run
|
165
205
|
checks_to_run = self._get_checks_to_run()
|
166
|
-
|
206
|
+
console.print(f"[green]🔍 Running {len(checks_to_run)} assessment checks[/green]")
|
167
207
|
|
168
|
-
# Execute checks
|
169
|
-
results = self.
|
208
|
+
# Execute checks with Rich CLI progress
|
209
|
+
results = self._execute_checks_enhanced(checks_to_run)
|
170
210
|
|
171
|
-
#
|
172
|
-
|
211
|
+
# Performance benchmark end
|
212
|
+
elapsed_time = time.time() - self._assessment_start_time
|
213
|
+
self._display_performance_results(elapsed_time, len(checks_to_run))
|
214
|
+
|
215
|
+
# Generate summary
|
216
|
+
summary = self._generate_summary(results, elapsed_time)
|
173
217
|
|
174
218
|
# Create report
|
175
219
|
report = AssessmentReport(
|
@@ -382,6 +426,73 @@ class CloudFoundationsAssessment(CloudFoundationsBase):
|
|
382
426
|
total_execution_time=total_time,
|
383
427
|
)
|
384
428
|
|
429
|
+
def _execute_checks_enhanced(self, checks: List[str]) -> List[AssessmentResult]:
|
430
|
+
"""Execute checks with Rich CLI progress display."""
|
431
|
+
results = []
|
432
|
+
|
433
|
+
with Progress(
|
434
|
+
SpinnerColumn(),
|
435
|
+
TextColumn("[progress.description]{task.description}"),
|
436
|
+
BarColumn(),
|
437
|
+
TextColumn("[progress.percentage]{task.percentage:>3.0f}%"),
|
438
|
+
TimeElapsedColumn(),
|
439
|
+
console=console
|
440
|
+
) as progress:
|
441
|
+
task = progress.add_task(f"[cyan]Executing assessments...", total=len(checks))
|
442
|
+
|
443
|
+
for check_name in checks:
|
444
|
+
progress.update(task, description=f"[cyan]Running check: {check_name}")
|
445
|
+
|
446
|
+
try:
|
447
|
+
result = self._execute_single_check(check_name)
|
448
|
+
results.append(result)
|
449
|
+
|
450
|
+
# Status indicator
|
451
|
+
if result.status == CheckStatus.PASS:
|
452
|
+
status_emoji = "✅"
|
453
|
+
status_color = "green"
|
454
|
+
elif result.status == CheckStatus.FAIL:
|
455
|
+
status_emoji = "❌"
|
456
|
+
status_color = "red"
|
457
|
+
elif result.status == CheckStatus.SKIP:
|
458
|
+
status_emoji = "⏭️"
|
459
|
+
status_color = "yellow"
|
460
|
+
else:
|
461
|
+
status_emoji = "⚠️"
|
462
|
+
status_color = "orange"
|
463
|
+
|
464
|
+
progress.update(task, description=f"[{status_color}]{status_emoji} {check_name}[/{status_color}]")
|
465
|
+
|
466
|
+
except Exception as e:
|
467
|
+
result = self._create_error_result(check_name, str(e))
|
468
|
+
results.append(result)
|
469
|
+
progress.update(task, description=f"[red]⚠️ Error in {check_name}[/red]")
|
470
|
+
|
471
|
+
progress.advance(task)
|
472
|
+
|
473
|
+
return results
|
474
|
+
|
475
|
+
def _display_performance_results(self, elapsed_time: float, check_count: int) -> None:
|
476
|
+
"""Display assessment performance results with Rich CLI."""
|
477
|
+
# Performance validation against target
|
478
|
+
if elapsed_time <= self._performance_target:
|
479
|
+
console.print(f"[green]⚡ Assessment completed in {elapsed_time:.2f}s (target: {self._performance_target}s) ✅[/green]")
|
480
|
+
else:
|
481
|
+
console.print(f"[yellow]⚠️ Assessment completed in {elapsed_time:.2f}s (exceeded target: {self._performance_target}s)[/yellow]")
|
482
|
+
|
483
|
+
# Performance metrics table
|
484
|
+
metrics_table = Table(title="📊 Performance Metrics")
|
485
|
+
metrics_table.add_column("Metric", style="cyan")
|
486
|
+
metrics_table.add_column("Value", style="magenta")
|
487
|
+
metrics_table.add_column("Target", style="green")
|
488
|
+
|
489
|
+
avg_check_time = elapsed_time / check_count if check_count > 0 else 0
|
490
|
+
metrics_table.add_row("Total Time", f"{elapsed_time:.2f}s", f"<{self._performance_target}s")
|
491
|
+
metrics_table.add_row("Check Count", str(check_count), "N/A")
|
492
|
+
metrics_table.add_row("Avg per Check", f"{avg_check_time:.2f}s", "<1s")
|
493
|
+
|
494
|
+
console.print(metrics_table)
|
495
|
+
|
385
496
|
def run(self):
|
386
497
|
"""Implementation of abstract base method."""
|
387
498
|
return self.run_assessment()
|
runbooks/cfat/models.py
CHANGED
@@ -240,8 +240,12 @@ class AssessmentSummary(BaseModel):
|
|
240
240
|
total_execution_time=45.5
|
241
241
|
)
|
242
242
|
|
243
|
-
|
244
|
-
|
243
|
+
# Rich console output for better formatting
|
244
|
+
from rich.console import Console
|
245
|
+
console = Console()
|
246
|
+
|
247
|
+
console.print(f"[green]Pass rate: {summary.pass_rate:.1f}%[/green]")
|
248
|
+
console.print(f"[blue]Compliance score: {summary.compliance_score}[/blue]")
|
245
249
|
```
|
246
250
|
"""
|
247
251
|
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# Enterprise Logger - Common Interface
|
2
|
+
# Re-export logger functionality for consistent imports
|
3
|
+
|
4
|
+
from runbooks.utils.logger import configure_logger
|
5
|
+
|
6
|
+
|
7
|
+
# Standard interface for enterprise logging
|
8
|
+
def get_logger(module_name: str):
|
9
|
+
"""Get configured logger for module - enterprise standard interface."""
|
10
|
+
return configure_logger(module_name)
|
11
|
+
|
12
|
+
|
13
|
+
# Backward compatibility exports
|
14
|
+
__all__ = ["get_logger", "configure_logger"]
|
@@ -0,0 +1,451 @@
|
|
1
|
+
#!/usr/bin/env python3
|
2
|
+
"""
|
3
|
+
Rich Library Utilities for CloudOps Runbooks Platform
|
4
|
+
|
5
|
+
This module provides centralized Rich components and styling for consistent,
|
6
|
+
beautiful terminal output across all CloudOps Runbooks modules.
|
7
|
+
|
8
|
+
Features:
|
9
|
+
- Custom CloudOps theme and color schemes
|
10
|
+
- Reusable UI components (headers, footers, panels)
|
11
|
+
- Standard progress bars and spinners
|
12
|
+
- Consistent table styles
|
13
|
+
- Error/warning/success message formatting
|
14
|
+
- Tree displays for hierarchical data
|
15
|
+
- Layout templates for complex displays
|
16
|
+
|
17
|
+
Author: CloudOps Runbooks Team
|
18
|
+
Version: 0.7.8
|
19
|
+
"""
|
20
|
+
|
21
|
+
from datetime import datetime
|
22
|
+
from typing import Any, Dict, List, Optional, Union
|
23
|
+
|
24
|
+
from rich import box
|
25
|
+
from rich.columns import Columns
|
26
|
+
from rich.console import Console
|
27
|
+
from rich.layout import Layout
|
28
|
+
from rich.markdown import Markdown
|
29
|
+
from rich.panel import Panel
|
30
|
+
from rich.progress import BarColumn, Progress, SpinnerColumn, TaskProgressColumn, TextColumn, TimeElapsedColumn
|
31
|
+
from rich.rule import Rule
|
32
|
+
from rich.style import Style
|
33
|
+
from rich.syntax import Syntax
|
34
|
+
from rich.table import Table
|
35
|
+
from rich.text import Text
|
36
|
+
from rich.theme import Theme
|
37
|
+
from rich.tree import Tree
|
38
|
+
|
39
|
+
# CloudOps Custom Theme
|
40
|
+
CLOUDOPS_THEME = Theme(
|
41
|
+
{
|
42
|
+
"info": "cyan",
|
43
|
+
"success": "green bold",
|
44
|
+
"warning": "yellow bold",
|
45
|
+
"error": "red bold",
|
46
|
+
"critical": "red bold reverse",
|
47
|
+
"highlight": "bright_blue bold",
|
48
|
+
"header": "bright_cyan bold",
|
49
|
+
"subheader": "cyan",
|
50
|
+
"dim": "dim white",
|
51
|
+
"resource": "bright_magenta",
|
52
|
+
"cost": "bright_green",
|
53
|
+
"security": "bright_red",
|
54
|
+
"compliance": "bright_yellow",
|
55
|
+
}
|
56
|
+
)
|
57
|
+
|
58
|
+
# Initialize console with custom theme
|
59
|
+
console = Console(theme=CLOUDOPS_THEME)
|
60
|
+
|
61
|
+
# Status indicators
|
62
|
+
STATUS_INDICATORS = {
|
63
|
+
"success": "🟢",
|
64
|
+
"warning": "🟡",
|
65
|
+
"error": "🔴",
|
66
|
+
"info": "🔵",
|
67
|
+
"pending": "⚪",
|
68
|
+
"running": "🔄",
|
69
|
+
"stopped": "⏹️",
|
70
|
+
"critical": "🚨",
|
71
|
+
}
|
72
|
+
|
73
|
+
|
74
|
+
def get_console() -> Console:
|
75
|
+
"""Get the themed console instance."""
|
76
|
+
return console
|
77
|
+
|
78
|
+
|
79
|
+
def print_header(title: str, version: str = "0.7.8") -> None:
|
80
|
+
"""
|
81
|
+
Print a consistent header for all modules.
|
82
|
+
|
83
|
+
Args:
|
84
|
+
title: Module title
|
85
|
+
version: Module version
|
86
|
+
"""
|
87
|
+
header_text = Text()
|
88
|
+
header_text.append("CloudOps Runbooks ", style="header")
|
89
|
+
header_text.append(f"| {title} ", style="subheader")
|
90
|
+
header_text.append(f"v{version}", style="dim")
|
91
|
+
|
92
|
+
console.print()
|
93
|
+
console.print(Panel(header_text, box=box.DOUBLE, style="header"))
|
94
|
+
console.print()
|
95
|
+
|
96
|
+
|
97
|
+
def print_banner() -> None:
|
98
|
+
"""Print the CloudOps Runbooks ASCII banner."""
|
99
|
+
banner = r"""
|
100
|
+
╔═══════════════════════════════════════════════════════════════╗
|
101
|
+
║ _____ _ _ ____ ____ ║
|
102
|
+
║ / ____| | | |/ __ \ | _ \ ║
|
103
|
+
║ | | | | ___ _ _ __| | | | |_ __ ___ | |_) |_ _ __ ║
|
104
|
+
║ | | | |/ _ \| | | |/ _` | | | | '_ \/ __| | _ <| | | '_ \ ║
|
105
|
+
║ | |____| | (_) | |_| | (_| | |__| | |_) \__ \ | |_) | |_| | | |║
|
106
|
+
║ \_____|_|\___/ \__,_|\__,_|\____/| .__/|___/ |____/ \__,_|_| |║
|
107
|
+
║ | | ║
|
108
|
+
║ Enterprise AWS Automation |_| Platform v0.7.8 ║
|
109
|
+
╚═══════════════════════════════════════════════════════════════╝
|
110
|
+
"""
|
111
|
+
console.print(banner, style="header")
|
112
|
+
|
113
|
+
|
114
|
+
def create_table(
|
115
|
+
title: Optional[str] = None,
|
116
|
+
columns: List[Dict[str, Any]] = None,
|
117
|
+
show_header: bool = True,
|
118
|
+
show_footer: bool = False,
|
119
|
+
box_style: Any = box.ROUNDED,
|
120
|
+
title_style: str = "header",
|
121
|
+
) -> Table:
|
122
|
+
"""
|
123
|
+
Create a consistent styled table.
|
124
|
+
|
125
|
+
Args:
|
126
|
+
title: Table title
|
127
|
+
columns: List of column definitions [{"name": "Col1", "style": "cyan", "justify": "left"}]
|
128
|
+
show_header: Show header row
|
129
|
+
show_footer: Show footer row
|
130
|
+
box_style: Rich box style
|
131
|
+
title_style: Style for title
|
132
|
+
|
133
|
+
Returns:
|
134
|
+
Configured Table object
|
135
|
+
"""
|
136
|
+
table = Table(
|
137
|
+
title=title,
|
138
|
+
show_header=show_header,
|
139
|
+
show_footer=show_footer,
|
140
|
+
box=box_style,
|
141
|
+
title_style=title_style,
|
142
|
+
header_style="bold",
|
143
|
+
row_styles=["none", "dim"], # Alternating row colors
|
144
|
+
)
|
145
|
+
|
146
|
+
if columns:
|
147
|
+
for col in columns:
|
148
|
+
table.add_column(
|
149
|
+
col.get("name", ""),
|
150
|
+
style=col.get("style", ""),
|
151
|
+
justify=col.get("justify", "left"),
|
152
|
+
no_wrap=col.get("no_wrap", False),
|
153
|
+
)
|
154
|
+
|
155
|
+
return table
|
156
|
+
|
157
|
+
|
158
|
+
def create_progress_bar(description: str = "Processing") -> Progress:
|
159
|
+
"""
|
160
|
+
Create a consistent progress bar.
|
161
|
+
|
162
|
+
Args:
|
163
|
+
description: Progress bar description
|
164
|
+
|
165
|
+
Returns:
|
166
|
+
Configured Progress object
|
167
|
+
"""
|
168
|
+
return Progress(
|
169
|
+
SpinnerColumn(spinner_name="dots", style="cyan"),
|
170
|
+
TextColumn("[progress.description]{task.description}"),
|
171
|
+
BarColumn(bar_width=40, style="cyan", complete_style="green"),
|
172
|
+
TaskProgressColumn(),
|
173
|
+
TimeElapsedColumn(),
|
174
|
+
console=console,
|
175
|
+
transient=True,
|
176
|
+
)
|
177
|
+
|
178
|
+
|
179
|
+
def print_status(message: str, status: str = "info") -> None:
|
180
|
+
"""
|
181
|
+
Print a status message with appropriate styling and indicator.
|
182
|
+
|
183
|
+
Args:
|
184
|
+
message: Status message
|
185
|
+
status: Status type (success, warning, error, info, critical)
|
186
|
+
"""
|
187
|
+
indicator = STATUS_INDICATORS.get(status, "")
|
188
|
+
style = status if status in ["success", "warning", "error", "critical", "info"] else "info"
|
189
|
+
console.print(f"{indicator} {message}", style=style)
|
190
|
+
|
191
|
+
|
192
|
+
def print_error(message: str, exception: Optional[Exception] = None) -> None:
|
193
|
+
"""
|
194
|
+
Print an error message with optional exception details.
|
195
|
+
|
196
|
+
Args:
|
197
|
+
message: Error message
|
198
|
+
exception: Optional exception object
|
199
|
+
"""
|
200
|
+
console.print(f"{STATUS_INDICATORS['error']} {message}", style="error")
|
201
|
+
if exception:
|
202
|
+
console.print(f" Details: {str(exception)}", style="dim")
|
203
|
+
|
204
|
+
|
205
|
+
def print_success(message: str) -> None:
|
206
|
+
"""
|
207
|
+
Print a success message.
|
208
|
+
|
209
|
+
Args:
|
210
|
+
message: Success message
|
211
|
+
"""
|
212
|
+
console.print(f"{STATUS_INDICATORS['success']} {message}", style="success")
|
213
|
+
|
214
|
+
|
215
|
+
def print_warning(message: str) -> None:
|
216
|
+
"""
|
217
|
+
Print a warning message.
|
218
|
+
|
219
|
+
Args:
|
220
|
+
message: Warning message
|
221
|
+
"""
|
222
|
+
console.print(f"{STATUS_INDICATORS['warning']} {message}", style="warning")
|
223
|
+
|
224
|
+
|
225
|
+
def print_info(message: str) -> None:
|
226
|
+
"""
|
227
|
+
Print an info message.
|
228
|
+
|
229
|
+
Args:
|
230
|
+
message: Info message
|
231
|
+
"""
|
232
|
+
console.print(f"{STATUS_INDICATORS['info']} {message}", style="info")
|
233
|
+
|
234
|
+
|
235
|
+
def create_tree(label: str, style: str = "cyan") -> Tree:
|
236
|
+
"""
|
237
|
+
Create a tree for hierarchical display.
|
238
|
+
|
239
|
+
Args:
|
240
|
+
label: Root label
|
241
|
+
style: Tree style
|
242
|
+
|
243
|
+
Returns:
|
244
|
+
Tree object
|
245
|
+
"""
|
246
|
+
return Tree(label, style=style, guide_style="dim")
|
247
|
+
|
248
|
+
|
249
|
+
def print_separator(label: Optional[str] = None, style: str = "dim") -> None:
|
250
|
+
"""
|
251
|
+
Print a separator line.
|
252
|
+
|
253
|
+
Args:
|
254
|
+
label: Optional label for separator
|
255
|
+
style: Separator style
|
256
|
+
"""
|
257
|
+
if label:
|
258
|
+
console.print(Rule(label, style=style))
|
259
|
+
else:
|
260
|
+
console.print(Rule(style=style))
|
261
|
+
|
262
|
+
|
263
|
+
def create_panel(
|
264
|
+
content: Any,
|
265
|
+
title: Optional[str] = None,
|
266
|
+
subtitle: Optional[str] = None,
|
267
|
+
border_style: str = "cyan",
|
268
|
+
padding: int = 1,
|
269
|
+
) -> Panel:
|
270
|
+
"""
|
271
|
+
Create a panel for highlighting content.
|
272
|
+
|
273
|
+
Args:
|
274
|
+
content: Panel content
|
275
|
+
title: Panel title
|
276
|
+
subtitle: Panel subtitle
|
277
|
+
border_style: Border color/style
|
278
|
+
padding: Internal padding
|
279
|
+
|
280
|
+
Returns:
|
281
|
+
Panel object
|
282
|
+
"""
|
283
|
+
return Panel(
|
284
|
+
content, title=title, subtitle=subtitle, border_style=border_style, padding=(padding, padding), expand=False
|
285
|
+
)
|
286
|
+
|
287
|
+
|
288
|
+
def format_cost(amount: float, currency: str = "USD") -> Text:
|
289
|
+
"""
|
290
|
+
Format a cost value with appropriate styling.
|
291
|
+
|
292
|
+
Args:
|
293
|
+
amount: Cost amount
|
294
|
+
currency: Currency code
|
295
|
+
|
296
|
+
Returns:
|
297
|
+
Formatted Text object
|
298
|
+
"""
|
299
|
+
text = Text()
|
300
|
+
symbol = "$" if currency == "USD" else currency
|
301
|
+
if amount >= 10000:
|
302
|
+
text.append(f"{symbol}{amount:,.2f}", style="cost bold")
|
303
|
+
elif amount >= 1000:
|
304
|
+
text.append(f"{symbol}{amount:,.2f}", style="cost")
|
305
|
+
else:
|
306
|
+
text.append(f"{symbol}{amount:,.2f}", style="dim")
|
307
|
+
return text
|
308
|
+
|
309
|
+
|
310
|
+
def format_resource_count(count: int, resource_type: str) -> Text:
|
311
|
+
"""
|
312
|
+
Format a resource count with appropriate styling.
|
313
|
+
|
314
|
+
Args:
|
315
|
+
count: Resource count
|
316
|
+
resource_type: Type of resource
|
317
|
+
|
318
|
+
Returns:
|
319
|
+
Formatted Text object
|
320
|
+
"""
|
321
|
+
text = Text()
|
322
|
+
if count == 0:
|
323
|
+
text.append(f"{count} {resource_type}", style="dim")
|
324
|
+
elif count > 100:
|
325
|
+
text.append(f"{count} {resource_type}", style="warning")
|
326
|
+
else:
|
327
|
+
text.append(f"{count} {resource_type}", style="resource")
|
328
|
+
return text
|
329
|
+
|
330
|
+
|
331
|
+
def create_layout(sections: Dict[str, Any]) -> Layout:
|
332
|
+
"""
|
333
|
+
Create a layout for complex displays.
|
334
|
+
|
335
|
+
Args:
|
336
|
+
sections: Dictionary of layout sections
|
337
|
+
|
338
|
+
Returns:
|
339
|
+
Layout object
|
340
|
+
"""
|
341
|
+
layout = Layout()
|
342
|
+
|
343
|
+
# Example layout structure
|
344
|
+
if "header" in sections:
|
345
|
+
layout.split_column(Layout(name="header", size=3), Layout(name="body"), Layout(name="footer", size=3))
|
346
|
+
layout["header"].update(sections["header"])
|
347
|
+
|
348
|
+
if "body" in sections:
|
349
|
+
if isinstance(sections["body"], dict):
|
350
|
+
layout["body"].split_row(*[Layout(name=k) for k in sections["body"].keys()])
|
351
|
+
for key, content in sections["body"].items():
|
352
|
+
layout["body"][key].update(content)
|
353
|
+
else:
|
354
|
+
layout["body"].update(sections["body"])
|
355
|
+
|
356
|
+
if "footer" in sections:
|
357
|
+
layout["footer"].update(sections["footer"])
|
358
|
+
|
359
|
+
return layout
|
360
|
+
|
361
|
+
|
362
|
+
def print_json(data: Dict[str, Any], title: Optional[str] = None) -> None:
|
363
|
+
"""
|
364
|
+
Print JSON data with syntax highlighting.
|
365
|
+
|
366
|
+
Args:
|
367
|
+
data: JSON data to display
|
368
|
+
title: Optional title
|
369
|
+
"""
|
370
|
+
import json
|
371
|
+
|
372
|
+
json_str = json.dumps(data, indent=2)
|
373
|
+
syntax = Syntax(json_str, "json", theme="monokai", line_numbers=False)
|
374
|
+
if title:
|
375
|
+
console.print(Panel(syntax, title=title, border_style="cyan"))
|
376
|
+
else:
|
377
|
+
console.print(syntax)
|
378
|
+
|
379
|
+
|
380
|
+
def print_markdown(text: str) -> None:
|
381
|
+
"""
|
382
|
+
Print markdown formatted text.
|
383
|
+
|
384
|
+
Args:
|
385
|
+
text: Markdown text
|
386
|
+
"""
|
387
|
+
md = Markdown(text)
|
388
|
+
console.print(md)
|
389
|
+
|
390
|
+
|
391
|
+
def confirm_action(prompt: str, default: bool = False) -> bool:
|
392
|
+
"""
|
393
|
+
Get user confirmation with styled prompt.
|
394
|
+
|
395
|
+
Args:
|
396
|
+
prompt: Confirmation prompt
|
397
|
+
default: Default value if user just presses enter
|
398
|
+
|
399
|
+
Returns:
|
400
|
+
User's confirmation choice
|
401
|
+
"""
|
402
|
+
default_text = "[Y/n]" if default else "[y/N]"
|
403
|
+
console.print(f"\n{STATUS_INDICATORS['info']} {prompt} {default_text}: ", style="info", end="")
|
404
|
+
|
405
|
+
response = input().strip().lower()
|
406
|
+
if not response:
|
407
|
+
return default
|
408
|
+
return response in ["y", "yes"]
|
409
|
+
|
410
|
+
|
411
|
+
def create_columns(items: List[Any], equal: bool = True, expand: bool = True) -> Columns:
|
412
|
+
"""
|
413
|
+
Create columns for side-by-side display.
|
414
|
+
|
415
|
+
Args:
|
416
|
+
items: List of items to display in columns
|
417
|
+
equal: Equal width columns
|
418
|
+
expand: Expand to full width
|
419
|
+
|
420
|
+
Returns:
|
421
|
+
Columns object
|
422
|
+
"""
|
423
|
+
return Columns(items, equal=equal, expand=expand, padding=(0, 2))
|
424
|
+
|
425
|
+
|
426
|
+
# Export all public functions and constants
|
427
|
+
__all__ = [
|
428
|
+
"CLOUDOPS_THEME",
|
429
|
+
"STATUS_INDICATORS",
|
430
|
+
"console",
|
431
|
+
"get_console",
|
432
|
+
"print_header",
|
433
|
+
"print_banner",
|
434
|
+
"create_table",
|
435
|
+
"create_progress_bar",
|
436
|
+
"print_status",
|
437
|
+
"print_error",
|
438
|
+
"print_success",
|
439
|
+
"print_warning",
|
440
|
+
"print_info",
|
441
|
+
"create_tree",
|
442
|
+
"print_separator",
|
443
|
+
"create_panel",
|
444
|
+
"format_cost",
|
445
|
+
"format_resource_count",
|
446
|
+
"create_layout",
|
447
|
+
"print_json",
|
448
|
+
"print_markdown",
|
449
|
+
"confirm_action",
|
450
|
+
"create_columns",
|
451
|
+
]
|