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.
Files changed (111) hide show
  1. runbooks/__init__.py +1 -1
  2. runbooks/base.py +5 -1
  3. runbooks/cfat/__init__.py +8 -4
  4. runbooks/cfat/assessment/collectors.py +171 -14
  5. runbooks/cfat/assessment/compliance.py +871 -0
  6. runbooks/cfat/assessment/runner.py +122 -11
  7. runbooks/cfat/models.py +6 -2
  8. runbooks/common/logger.py +14 -0
  9. runbooks/common/rich_utils.py +451 -0
  10. runbooks/enterprise/__init__.py +68 -0
  11. runbooks/enterprise/error_handling.py +411 -0
  12. runbooks/enterprise/logging.py +439 -0
  13. runbooks/enterprise/multi_tenant.py +583 -0
  14. runbooks/finops/README.md +468 -241
  15. runbooks/finops/__init__.py +39 -3
  16. runbooks/finops/cli.py +83 -18
  17. runbooks/finops/cross_validation.py +375 -0
  18. runbooks/finops/dashboard_runner.py +812 -164
  19. runbooks/finops/enhanced_dashboard_runner.py +525 -0
  20. runbooks/finops/finops_dashboard.py +1892 -0
  21. runbooks/finops/helpers.py +485 -51
  22. runbooks/finops/optimizer.py +823 -0
  23. runbooks/finops/tests/__init__.py +19 -0
  24. runbooks/finops/tests/results_test_finops_dashboard.xml +1 -0
  25. runbooks/finops/tests/run_comprehensive_tests.py +421 -0
  26. runbooks/finops/tests/run_tests.py +305 -0
  27. runbooks/finops/tests/test_finops_dashboard.py +705 -0
  28. runbooks/finops/tests/test_integration.py +477 -0
  29. runbooks/finops/tests/test_performance.py +380 -0
  30. runbooks/finops/tests/test_performance_benchmarks.py +500 -0
  31. runbooks/finops/tests/test_reference_images_validation.py +867 -0
  32. runbooks/finops/tests/test_single_account_features.py +715 -0
  33. runbooks/finops/tests/validate_test_suite.py +220 -0
  34. runbooks/finops/types.py +1 -1
  35. runbooks/hitl/enhanced_workflow_engine.py +725 -0
  36. runbooks/inventory/artifacts/scale-optimize-status.txt +12 -0
  37. runbooks/inventory/collectors/aws_comprehensive.py +442 -0
  38. runbooks/inventory/collectors/enterprise_scale.py +281 -0
  39. runbooks/inventory/core/collector.py +172 -13
  40. runbooks/inventory/discovery.md +1 -1
  41. runbooks/inventory/list_ec2_instances.py +18 -20
  42. runbooks/inventory/list_ssm_parameters.py +31 -3
  43. runbooks/inventory/organizations_discovery.py +1269 -0
  44. runbooks/inventory/rich_inventory_display.py +393 -0
  45. runbooks/inventory/run_on_multi_accounts.py +35 -19
  46. runbooks/inventory/runbooks.security.report_generator.log +0 -0
  47. runbooks/inventory/runbooks.security.run_script.log +0 -0
  48. runbooks/inventory/vpc_flow_analyzer.py +1030 -0
  49. runbooks/main.py +2215 -119
  50. runbooks/metrics/dora_metrics_engine.py +599 -0
  51. runbooks/operate/__init__.py +2 -2
  52. runbooks/operate/base.py +122 -10
  53. runbooks/operate/deployment_framework.py +1032 -0
  54. runbooks/operate/deployment_validator.py +853 -0
  55. runbooks/operate/dynamodb_operations.py +10 -6
  56. runbooks/operate/ec2_operations.py +319 -11
  57. runbooks/operate/executive_dashboard.py +779 -0
  58. runbooks/operate/mcp_integration.py +750 -0
  59. runbooks/operate/nat_gateway_operations.py +1120 -0
  60. runbooks/operate/networking_cost_heatmap.py +685 -0
  61. runbooks/operate/privatelink_operations.py +940 -0
  62. runbooks/operate/s3_operations.py +10 -6
  63. runbooks/operate/vpc_endpoints.py +644 -0
  64. runbooks/operate/vpc_operations.py +1038 -0
  65. runbooks/remediation/__init__.py +2 -2
  66. runbooks/remediation/acm_remediation.py +1 -1
  67. runbooks/remediation/base.py +1 -1
  68. runbooks/remediation/cloudtrail_remediation.py +1 -1
  69. runbooks/remediation/cognito_remediation.py +1 -1
  70. runbooks/remediation/dynamodb_remediation.py +1 -1
  71. runbooks/remediation/ec2_remediation.py +1 -1
  72. runbooks/remediation/ec2_unattached_ebs_volumes.py +1 -1
  73. runbooks/remediation/kms_enable_key_rotation.py +1 -1
  74. runbooks/remediation/kms_remediation.py +1 -1
  75. runbooks/remediation/lambda_remediation.py +1 -1
  76. runbooks/remediation/multi_account.py +1 -1
  77. runbooks/remediation/rds_remediation.py +1 -1
  78. runbooks/remediation/s3_block_public_access.py +1 -1
  79. runbooks/remediation/s3_enable_access_logging.py +1 -1
  80. runbooks/remediation/s3_encryption.py +1 -1
  81. runbooks/remediation/s3_remediation.py +1 -1
  82. runbooks/remediation/vpc_remediation.py +475 -0
  83. runbooks/security/__init__.py +3 -1
  84. runbooks/security/compliance_automation.py +632 -0
  85. runbooks/security/report_generator.py +10 -0
  86. runbooks/security/run_script.py +31 -5
  87. runbooks/security/security_baseline_tester.py +169 -30
  88. runbooks/security/security_export.py +477 -0
  89. runbooks/validation/__init__.py +10 -0
  90. runbooks/validation/benchmark.py +484 -0
  91. runbooks/validation/cli.py +356 -0
  92. runbooks/validation/mcp_validator.py +768 -0
  93. runbooks/vpc/__init__.py +38 -0
  94. runbooks/vpc/config.py +212 -0
  95. runbooks/vpc/cost_engine.py +347 -0
  96. runbooks/vpc/heatmap_engine.py +605 -0
  97. runbooks/vpc/manager_interface.py +634 -0
  98. runbooks/vpc/networking_wrapper.py +1260 -0
  99. runbooks/vpc/rich_formatters.py +679 -0
  100. runbooks/vpc/tests/__init__.py +5 -0
  101. runbooks/vpc/tests/conftest.py +356 -0
  102. runbooks/vpc/tests/test_cli_integration.py +530 -0
  103. runbooks/vpc/tests/test_config.py +458 -0
  104. runbooks/vpc/tests/test_cost_engine.py +479 -0
  105. runbooks/vpc/tests/test_networking_wrapper.py +512 -0
  106. {runbooks-0.7.6.dist-info → runbooks-0.7.9.dist-info}/METADATA +40 -12
  107. {runbooks-0.7.6.dist-info → runbooks-0.7.9.dist-info}/RECORD +111 -50
  108. {runbooks-0.7.6.dist-info → runbooks-0.7.9.dist-info}/WHEEL +0 -0
  109. {runbooks-0.7.6.dist-info → runbooks-0.7.9.dist-info}/entry_points.txt +0 -0
  110. {runbooks-0.7.6.dist-info → runbooks-0.7.9.dist-info}/licenses/LICENSE +0 -0
  111. {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
- super().__init__(profile, region, config)
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
- logger.info("Starting Cloud Foundations assessment")
157
- start_time = time.time()
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
- logger.info(f"Running {len(checks_to_run)} assessment checks")
206
+ console.print(f"[green]🔍 Running {len(checks_to_run)} assessment checks[/green]")
167
207
 
168
- # Execute checks
169
- results = self._execute_checks(checks_to_run)
208
+ # Execute checks with Rich CLI progress
209
+ results = self._execute_checks_enhanced(checks_to_run)
170
210
 
171
- # Generate summary
172
- summary = self._generate_summary(results, time.time() - start_time)
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
- print(f"Pass rate: {summary.pass_rate:.1f}%")
244
- print(f"Compliance score: {summary.compliance_score}")
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
+ ]