runbooks 1.0.2__py3-none-any.whl → 1.1.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.
Files changed (47) hide show
  1. runbooks/__init__.py +9 -4
  2. runbooks/__init__.py.backup +134 -0
  3. runbooks/__init___optimized.py +110 -0
  4. runbooks/cloudops/base.py +56 -3
  5. runbooks/cloudops/cost_optimizer.py +496 -42
  6. runbooks/common/aws_pricing.py +236 -80
  7. runbooks/common/business_logic.py +485 -0
  8. runbooks/common/cli_decorators.py +219 -0
  9. runbooks/common/error_handling.py +424 -0
  10. runbooks/common/lazy_loader.py +186 -0
  11. runbooks/common/module_cli_base.py +378 -0
  12. runbooks/common/performance_monitoring.py +512 -0
  13. runbooks/common/profile_utils.py +133 -6
  14. runbooks/enterprise/logging.py +30 -2
  15. runbooks/enterprise/validation.py +177 -0
  16. runbooks/finops/README.md +311 -236
  17. runbooks/finops/aws_client.py +1 -1
  18. runbooks/finops/business_case_config.py +723 -19
  19. runbooks/finops/cli.py +136 -0
  20. runbooks/finops/commvault_ec2_analysis.py +25 -9
  21. runbooks/finops/config.py +272 -0
  22. runbooks/finops/dashboard_runner.py +136 -23
  23. runbooks/finops/ebs_cost_optimizer.py +39 -40
  24. runbooks/finops/enhanced_trend_visualization.py +7 -2
  25. runbooks/finops/enterprise_wrappers.py +45 -18
  26. runbooks/finops/finops_dashboard.py +50 -25
  27. runbooks/finops/finops_scenarios.py +22 -7
  28. runbooks/finops/helpers.py +115 -2
  29. runbooks/finops/multi_dashboard.py +7 -5
  30. runbooks/finops/optimizer.py +97 -6
  31. runbooks/finops/scenario_cli_integration.py +247 -0
  32. runbooks/finops/scenarios.py +12 -1
  33. runbooks/finops/unlimited_scenarios.py +393 -0
  34. runbooks/finops/validation_framework.py +19 -7
  35. runbooks/finops/workspaces_analyzer.py +1 -5
  36. runbooks/inventory/mcp_inventory_validator.py +2 -1
  37. runbooks/main.py +132 -94
  38. runbooks/main_final.py +358 -0
  39. runbooks/main_minimal.py +84 -0
  40. runbooks/main_optimized.py +493 -0
  41. runbooks/main_ultra_minimal.py +47 -0
  42. {runbooks-1.0.2.dist-info → runbooks-1.1.0.dist-info}/METADATA +15 -15
  43. {runbooks-1.0.2.dist-info → runbooks-1.1.0.dist-info}/RECORD +47 -31
  44. {runbooks-1.0.2.dist-info → runbooks-1.1.0.dist-info}/WHEEL +0 -0
  45. {runbooks-1.0.2.dist-info → runbooks-1.1.0.dist-info}/entry_points.txt +0 -0
  46. {runbooks-1.0.2.dist-info → runbooks-1.1.0.dist-info}/licenses/LICENSE +0 -0
  47. {runbooks-1.0.2.dist-info → runbooks-1.1.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,186 @@
1
+ """
2
+ Lazy Loading Architecture for Performance Optimization
3
+
4
+ This module implements deferred initialization to eliminate startup overhead
5
+ for basic CLI operations like --help and --version.
6
+
7
+ Performance Goals:
8
+ - Basic CLI operations < 0.5s
9
+ - Defer AWS/MCP initialization until needed
10
+ - Clean startup without warning pollution
11
+ """
12
+
13
+ import threading
14
+ from typing import Any, Callable, Optional, Dict
15
+ from functools import wraps
16
+ import importlib
17
+ import sys
18
+
19
+
20
+ class LazyLoader:
21
+ """Thread-safe lazy loader for expensive imports and initializations."""
22
+
23
+ def __init__(self):
24
+ self._cache: Dict[str, Any] = {}
25
+ self._lock = threading.Lock()
26
+
27
+ def get_or_load(self, key: str, loader_func: Callable[[], Any]) -> Any:
28
+ """Get cached value or load it if not present."""
29
+ if key in self._cache:
30
+ return self._cache[key]
31
+
32
+ with self._lock:
33
+ # Double-check pattern
34
+ if key in self._cache:
35
+ return self._cache[key]
36
+
37
+ self._cache[key] = loader_func()
38
+ return self._cache[key]
39
+
40
+
41
+ # Global lazy loader instance
42
+ _lazy_loader = LazyLoader()
43
+
44
+
45
+ def lazy_aws_session():
46
+ """Lazy load AWS session creation."""
47
+ def _load_aws():
48
+ import boto3
49
+ from runbooks.common.profile_utils import create_management_session
50
+ return create_management_session()
51
+
52
+ return _lazy_loader.get_or_load("aws_session", _load_aws)
53
+
54
+
55
+ def lazy_mcp_validator():
56
+ """Lazy load MCP validator."""
57
+ def _load_mcp():
58
+ try:
59
+ from runbooks.finops.embedded_mcp_validator import FinOpsMCPValidator
60
+ return FinOpsMCPValidator()
61
+ except ImportError:
62
+ return None
63
+
64
+ return _lazy_loader.get_or_load("mcp_validator", _load_mcp)
65
+
66
+
67
+ def lazy_rich_console():
68
+ """Lazy load Rich console."""
69
+ def _load_rich():
70
+ try:
71
+ from rich.console import Console
72
+ return Console()
73
+ except ImportError:
74
+ # Fallback console
75
+ class SimpleConsole:
76
+ def print(self, *args, **kwargs):
77
+ print(*args)
78
+ return SimpleConsole()
79
+
80
+ return _lazy_loader.get_or_load("rich_console", _load_rich)
81
+
82
+
83
+ def lazy_performance_monitor():
84
+ """Lazy load performance monitoring."""
85
+ def _load_monitor():
86
+ from runbooks.common.performance_monitor import get_performance_benchmark
87
+ return get_performance_benchmark
88
+
89
+ return _lazy_loader.get_or_load("performance_monitor", _load_monitor)
90
+
91
+
92
+ def lazy_pricing_api():
93
+ """Lazy load pricing API without startup warnings."""
94
+ def _load_pricing():
95
+ try:
96
+ from runbooks.common.aws_pricing_api import PricingAPI
97
+ return PricingAPI()
98
+ except Exception:
99
+ # Return fallback pricing without warnings during basic operations
100
+ class FallbackPricing:
101
+ def get_nat_gateway_price(self, region="us-east-1"):
102
+ return 32.4 # Standard fallback rate
103
+ return FallbackPricing()
104
+
105
+ return _lazy_loader.get_or_load("pricing_api", _load_pricing)
106
+
107
+
108
+ def lazy_inventory_collector():
109
+ """Lazy load inventory collector."""
110
+ def _load_collector():
111
+ from runbooks.inventory.core.collector import InventoryCollector
112
+ return InventoryCollector
113
+
114
+ return _lazy_loader.get_or_load("inventory_collector", _load_collector)
115
+
116
+
117
+ def lazy_import(module_name: str, attribute: Optional[str] = None):
118
+ """Lazy import a module or module attribute."""
119
+ cache_key = f"{module_name}.{attribute}" if attribute else module_name
120
+
121
+ def _load_module():
122
+ module = importlib.import_module(module_name)
123
+ if attribute:
124
+ return getattr(module, attribute)
125
+ return module
126
+
127
+ return _lazy_loader.get_or_load(cache_key, _load_module)
128
+
129
+
130
+ def requires_aws(func):
131
+ """Decorator to ensure AWS session is loaded before function execution."""
132
+ @wraps(func)
133
+ def wrapper(*args, **kwargs):
134
+ lazy_aws_session() # Ensure AWS is loaded
135
+ return func(*args, **kwargs)
136
+ return wrapper
137
+
138
+
139
+ def requires_mcp(func):
140
+ """Decorator to ensure MCP validator is loaded before function execution."""
141
+ @wraps(func)
142
+ def wrapper(*args, **kwargs):
143
+ lazy_mcp_validator() # Ensure MCP is loaded
144
+ return func(*args, **kwargs)
145
+ return wrapper
146
+
147
+
148
+ def fast_startup_mode() -> bool:
149
+ """Check if we're in fast startup mode (basic operations only)."""
150
+ import sys
151
+
152
+ # Basic operations that should be fast
153
+ fast_operations = {'--help', '-h', '--version', '-V', 'help'}
154
+
155
+ # Check if any CLI args match fast operations
156
+ return any(arg in fast_operations for arg in sys.argv)
157
+
158
+
159
+ def clear_lazy_cache():
160
+ """Clear the lazy loading cache (useful for testing)."""
161
+ global _lazy_loader
162
+ _lazy_loader._cache.clear()
163
+
164
+
165
+ # Utility functions for deferred initialization
166
+ def defer_expensive_imports():
167
+ """
168
+ Replace expensive imports with lazy alternatives.
169
+ Call this early in main.py to optimize startup.
170
+ """
171
+ # Only defer if we're not in fast startup mode
172
+ if fast_startup_mode():
173
+ return
174
+
175
+ # Defer expensive imports for basic operations
176
+ modules_to_defer = [
177
+ 'runbooks.finops.embedded_mcp_validator',
178
+ 'runbooks.common.aws_pricing_api',
179
+ 'runbooks.inventory.core.collector',
180
+ 'runbooks.cfat.runner',
181
+ ]
182
+
183
+ for module in modules_to_defer:
184
+ if module in sys.modules:
185
+ # Replace with lazy loader
186
+ sys.modules[module] = lazy_import(module)
@@ -0,0 +1,378 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Module CLI Base Class for CloudOps Runbooks - Enterprise Standardization
4
+
5
+ Provides consistent CLI implementation patterns across all runbooks modules.
6
+ Eliminates code duplication and ensures uniform UX across inventory, operate,
7
+ security, cfat, vpc, remediation, and sre modules.
8
+
9
+ Author: CloudOps Runbooks Team
10
+ Version: 1.0.0 - CLI Standardization Framework
11
+ """
12
+
13
+ import abc
14
+ import sys
15
+ from typing import Dict, Any, Optional, List, Callable
16
+ from dataclasses import dataclass
17
+
18
+ import click
19
+ from rich.console import Console
20
+ from rich.panel import Panel
21
+ from rich.table import Table
22
+
23
+ from runbooks.common.rich_utils import (
24
+ console, print_header, print_success, print_error, print_warning, print_info,
25
+ create_table, create_progress_bar, format_cost, STATUS_INDICATORS
26
+ )
27
+ from runbooks.common.cli_decorators import (
28
+ common_aws_options, rich_output_options, enterprise_safety_options
29
+ )
30
+ from runbooks.common.error_handling import handle_aws_errors, handle_validation_errors
31
+ from runbooks.common.profile_utils import get_profile_for_operation, validate_profile_access_decorator
32
+ from runbooks.common.business_logic import BusinessMetrics, OptimizationResult, UniversalBusinessLogic
33
+
34
+
35
+ @dataclass
36
+ class ModuleConfig:
37
+ """Configuration for runbooks module CLI."""
38
+ name: str
39
+ version: str
40
+ description: str
41
+ primary_operation_type: str # For profile selection: "management", "operational", "cost"
42
+ performance_target_seconds: int = 30 # Module-specific performance target
43
+ supports_multi_account: bool = True
44
+ supports_export: bool = True
45
+ default_region: str = "us-east-1"
46
+
47
+
48
+ class ModuleCLIBase(abc.ABC):
49
+ """
50
+ Base class for standardized runbooks module CLI implementation.
51
+
52
+ Provides consistent patterns for:
53
+ - CLI option handling with enterprise decorators
54
+ - AWS profile management with 3-tier priority
55
+ - Rich CLI output with enterprise UX standards
56
+ - Error handling with user-friendly guidance
57
+ - Performance monitoring with module targets
58
+ - Business logic integration with standardized metrics
59
+ """
60
+
61
+ def __init__(self, config: ModuleConfig):
62
+ """Initialize module CLI with configuration."""
63
+ self.config = config
64
+ self.console = console
65
+ self.business_logic = UniversalBusinessLogic()
66
+ self._session_data = {}
67
+
68
+ def print_module_header(self):
69
+ """Print standardized module header with Rich formatting."""
70
+ print_header(self.config.name, self.config.version)
71
+ self.console.print(
72
+ Panel(
73
+ f"[cyan]{self.config.description}[/cyan]",
74
+ title=f"🚀 {self.config.name} Module",
75
+ border_style="blue"
76
+ )
77
+ )
78
+
79
+ def validate_prerequisites(self, profile: Optional[str] = None) -> bool:
80
+ """
81
+ Validate module prerequisites before execution.
82
+
83
+ Args:
84
+ profile: AWS profile to validate
85
+
86
+ Returns:
87
+ True if all prerequisites are met
88
+ """
89
+ try:
90
+ # Validate AWS profile access
91
+ selected_profile = get_profile_for_operation(
92
+ self.config.primary_operation_type,
93
+ profile
94
+ )
95
+
96
+ if not validate_profile_access(selected_profile, self.config.name):
97
+ return False
98
+
99
+ self._session_data['validated_profile'] = selected_profile
100
+ return True
101
+
102
+ except Exception as e:
103
+ print_error(f"Prerequisites validation failed: {str(e)}")
104
+ return False
105
+
106
+ @abc.abstractmethod
107
+ def execute_primary_operation(self, **kwargs) -> Dict[str, Any]:
108
+ """
109
+ Execute the primary module operation.
110
+
111
+ Must be implemented by each module to provide core functionality.
112
+
113
+ Returns:
114
+ Dictionary containing operation results
115
+ """
116
+ pass
117
+
118
+ @abc.abstractmethod
119
+ def format_results_for_display(self, results: Dict[str, Any]) -> None:
120
+ """
121
+ Format and display results using Rich CLI standards.
122
+
123
+ Args:
124
+ results: Operation results from execute_primary_operation
125
+ """
126
+ pass
127
+
128
+ def create_results_table(self, title: str, data: List[Dict[str, Any]],
129
+ columns: List[str]) -> Table:
130
+ """
131
+ Create standardized results table with Rich formatting.
132
+
133
+ Args:
134
+ title: Table title
135
+ data: List of dictionaries containing row data
136
+ columns: List of column names
137
+
138
+ Returns:
139
+ Configured Rich Table object
140
+ """
141
+ table = create_table(title=title, columns=[
142
+ {"name": col, "style": "cyan" if "name" in col.lower() else "default"}
143
+ for col in columns
144
+ ])
145
+
146
+ for row in data:
147
+ table.add_row(*[str(row.get(col, "N/A")) for col in columns])
148
+
149
+ return table
150
+
151
+ def export_results(self, results: Dict[str, Any], format_type: str = "json",
152
+ output_path: Optional[str] = None) -> bool:
153
+ """
154
+ Export results in specified format.
155
+
156
+ Args:
157
+ results: Results to export
158
+ format_type: Export format (json, csv, markdown, pdf)
159
+ output_path: Optional custom output path
160
+
161
+ Returns:
162
+ True if export successful
163
+ """
164
+ if not self.config.supports_export:
165
+ print_warning(f"{self.config.name} module does not support export")
166
+ return False
167
+
168
+ try:
169
+ export_handler = self.business_logic.create_export_handler(
170
+ format_type, output_path
171
+ )
172
+
173
+ success = export_handler.export_data(results)
174
+
175
+ if success:
176
+ print_success(f"Results exported to {export_handler.output_path}")
177
+ else:
178
+ print_error(f"Failed to export results in {format_type} format")
179
+
180
+ return success
181
+
182
+ except Exception as e:
183
+ print_error(f"Export failed: {str(e)}")
184
+ return False
185
+
186
+ def calculate_business_metrics(self, results: Dict[str, Any]) -> BusinessMetrics:
187
+ """
188
+ Calculate business metrics from operation results.
189
+
190
+ Args:
191
+ results: Operation results
192
+
193
+ Returns:
194
+ BusinessMetrics with calculated values
195
+ """
196
+ return self.business_logic.calculate_business_impact(results)
197
+
198
+ def create_standard_cli_command(self, command_name: str) -> Callable:
199
+ """
200
+ Create standardized CLI command with common options.
201
+
202
+ Args:
203
+ command_name: Name of the CLI command
204
+
205
+ Returns:
206
+ Decorated click command function
207
+ """
208
+ @click.command(name=command_name)
209
+ @common_aws_options
210
+ @rich_output_options
211
+ @enterprise_safety_options
212
+ @handle_aws_errors(module_name=self.config.name)
213
+ @handle_validation_errors
214
+ @validate_profile_access_decorator(operation_type=self.config.primary_operation_type)
215
+ def standardized_command(profile=None, region=None, dry_run=True,
216
+ export_format=None, quiet=False, **kwargs):
217
+ """Execute standardized module operation with enterprise safety."""
218
+
219
+ # Module header
220
+ if not quiet:
221
+ self.print_module_header()
222
+
223
+ # Validate prerequisites
224
+ if not self.validate_prerequisites(profile):
225
+ sys.exit(1)
226
+
227
+ # Execute with performance monitoring
228
+ try:
229
+ with create_progress_bar() as progress:
230
+ task = progress.add_task(
231
+ f"[cyan]Executing {self.config.name} operation...",
232
+ total=100
233
+ )
234
+
235
+ # Execute primary operation
236
+ results = self.execute_primary_operation(
237
+ profile=profile,
238
+ region=region,
239
+ dry_run=dry_run,
240
+ progress=progress,
241
+ task=task,
242
+ **kwargs
243
+ )
244
+
245
+ progress.update(task, completed=100)
246
+
247
+ # Display results
248
+ if not quiet:
249
+ self.format_results_for_display(results)
250
+
251
+ # Calculate business metrics
252
+ metrics = self.calculate_business_metrics(results)
253
+
254
+ if not quiet and metrics.annual_savings > 0:
255
+ print_success(
256
+ f"💰 Potential annual savings: {format_cost(metrics.annual_savings)}"
257
+ )
258
+
259
+ # Export if requested
260
+ if export_format:
261
+ self.export_results(results, export_format)
262
+
263
+ return results
264
+
265
+ except Exception as e:
266
+ print_error(f"Operation failed: {str(e)}")
267
+ if not quiet:
268
+ console.print_exception()
269
+ sys.exit(1)
270
+
271
+ return standardized_command
272
+
273
+
274
+ class AnalysisModuleCLI(ModuleCLIBase):
275
+ """
276
+ Specialized base class for analysis modules (finops, inventory, cfat).
277
+
278
+ Provides additional patterns for:
279
+ - Multi-account analysis operations
280
+ - Cost calculation and savings projections
281
+ - Executive reporting capabilities
282
+ - Trend analysis and recommendations
283
+ """
284
+
285
+ def create_executive_summary(self, results: Dict[str, Any]) -> Panel:
286
+ """Create executive summary panel for business stakeholders."""
287
+ metrics = self.calculate_business_metrics(results)
288
+
289
+ summary_content = []
290
+
291
+ if metrics.annual_savings > 0:
292
+ summary_content.append(f"💰 Annual Savings Potential: {format_cost(metrics.annual_savings)}")
293
+
294
+ if metrics.roi_percentage > 0:
295
+ summary_content.append(f"📊 ROI: {metrics.roi_percentage:.1f}%")
296
+
297
+ if hasattr(metrics, 'resources_analyzed'):
298
+ summary_content.append(f"🔍 Resources Analyzed: {metrics.resources_analyzed:,}")
299
+
300
+ summary_content.append(f"⏱️ Execution Time: <{self.config.performance_target_seconds}s target")
301
+ summary_content.append(f"✅ Confidence: {metrics.confidence_level:.1f}%")
302
+
303
+ return Panel(
304
+ "\n".join(summary_content),
305
+ title="📈 Executive Summary",
306
+ border_style="green"
307
+ )
308
+
309
+
310
+ class OperationsModuleCLI(ModuleCLIBase):
311
+ """
312
+ Specialized base class for operations modules (operate, security, remediation).
313
+
314
+ Provides additional patterns for:
315
+ - Resource modification operations with safety controls
316
+ - Multi-level approval workflows
317
+ - Rollback capabilities and audit trails
318
+ - Compliance validation and reporting
319
+ """
320
+
321
+ def validate_operation_safety(self, operation: str, resources: List[str]) -> bool:
322
+ """
323
+ Validate operation safety before execution.
324
+
325
+ Args:
326
+ operation: Operation type (start, stop, delete, modify)
327
+ resources: List of resource identifiers
328
+
329
+ Returns:
330
+ True if operation is safe to proceed
331
+ """
332
+ # Always require explicit approval for destructive operations
333
+ destructive_operations = ['delete', 'terminate', 'remove', 'destroy']
334
+
335
+ if any(op in operation.lower() for op in destructive_operations):
336
+ print_warning(f"⚠️ Destructive operation requested: {operation}")
337
+ print_info(f"Resources affected: {len(resources)}")
338
+
339
+ if not click.confirm("Are you sure you want to proceed?"):
340
+ print_info("Operation cancelled by user")
341
+ return False
342
+
343
+ return True
344
+
345
+ def create_audit_trail(self, operation: str, results: Dict[str, Any]) -> str:
346
+ """
347
+ Create audit trail entry for operations.
348
+
349
+ Args:
350
+ operation: Operation performed
351
+ results: Operation results
352
+
353
+ Returns:
354
+ Audit trail entry as JSON string
355
+ """
356
+ import json
357
+ from datetime import datetime
358
+
359
+ audit_entry = {
360
+ "timestamp": datetime.now().isoformat(),
361
+ "module": self.config.name,
362
+ "operation": operation,
363
+ "user": self._session_data.get('validated_profile', 'unknown'),
364
+ "resources_affected": results.get('resources_affected', []),
365
+ "success": results.get('success', False),
366
+ "execution_time": results.get('execution_time_seconds', 0)
367
+ }
368
+
369
+ return json.dumps(audit_entry, indent=2)
370
+
371
+
372
+ # Export standardized classes for module implementations
373
+ __all__ = [
374
+ "ModuleConfig",
375
+ "ModuleCLIBase",
376
+ "AnalysisModuleCLI",
377
+ "OperationsModuleCLI"
378
+ ]