runbooks 0.7.9__py3-none-any.whl → 0.9.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (95) hide show
  1. runbooks/__init__.py +1 -1
  2. runbooks/cfat/README.md +12 -1
  3. runbooks/cfat/__init__.py +1 -1
  4. runbooks/cfat/assessment/runner.py +42 -34
  5. runbooks/cfat/models.py +1 -1
  6. runbooks/common/__init__.py +152 -0
  7. runbooks/common/accuracy_validator.py +1039 -0
  8. runbooks/common/context_logger.py +440 -0
  9. runbooks/common/cross_module_integration.py +594 -0
  10. runbooks/common/enhanced_exception_handler.py +1108 -0
  11. runbooks/common/enterprise_audit_integration.py +634 -0
  12. runbooks/common/mcp_integration.py +539 -0
  13. runbooks/common/performance_monitor.py +387 -0
  14. runbooks/common/profile_utils.py +216 -0
  15. runbooks/common/rich_utils.py +171 -0
  16. runbooks/feedback/user_feedback_collector.py +440 -0
  17. runbooks/finops/README.md +339 -451
  18. runbooks/finops/__init__.py +4 -21
  19. runbooks/finops/account_resolver.py +279 -0
  20. runbooks/finops/accuracy_cross_validator.py +638 -0
  21. runbooks/finops/aws_client.py +721 -36
  22. runbooks/finops/budget_integration.py +313 -0
  23. runbooks/finops/cli.py +59 -5
  24. runbooks/finops/cost_processor.py +211 -37
  25. runbooks/finops/dashboard_router.py +900 -0
  26. runbooks/finops/dashboard_runner.py +990 -232
  27. runbooks/finops/embedded_mcp_validator.py +288 -0
  28. runbooks/finops/enhanced_dashboard_runner.py +8 -7
  29. runbooks/finops/enhanced_progress.py +327 -0
  30. runbooks/finops/enhanced_trend_visualization.py +423 -0
  31. runbooks/finops/finops_dashboard.py +29 -1880
  32. runbooks/finops/helpers.py +509 -196
  33. runbooks/finops/iam_guidance.py +400 -0
  34. runbooks/finops/markdown_exporter.py +466 -0
  35. runbooks/finops/multi_dashboard.py +1502 -0
  36. runbooks/finops/optimizer.py +15 -15
  37. runbooks/finops/profile_processor.py +2 -2
  38. runbooks/finops/runbooks.inventory.organizations_discovery.log +0 -0
  39. runbooks/finops/runbooks.security.report_generator.log +0 -0
  40. runbooks/finops/runbooks.security.run_script.log +0 -0
  41. runbooks/finops/runbooks.security.security_export.log +0 -0
  42. runbooks/finops/service_mapping.py +195 -0
  43. runbooks/finops/single_dashboard.py +710 -0
  44. runbooks/finops/tests/test_reference_images_validation.py +1 -1
  45. runbooks/inventory/README.md +12 -1
  46. runbooks/inventory/core/collector.py +157 -29
  47. runbooks/inventory/list_ec2_instances.py +9 -6
  48. runbooks/inventory/list_ssm_parameters.py +10 -10
  49. runbooks/inventory/organizations_discovery.py +210 -164
  50. runbooks/inventory/rich_inventory_display.py +74 -107
  51. runbooks/inventory/run_on_multi_accounts.py +13 -13
  52. runbooks/main.py +740 -134
  53. runbooks/metrics/dora_metrics_engine.py +711 -17
  54. runbooks/monitoring/performance_monitor.py +433 -0
  55. runbooks/operate/README.md +394 -0
  56. runbooks/operate/base.py +215 -47
  57. runbooks/operate/ec2_operations.py +7 -5
  58. runbooks/operate/privatelink_operations.py +1 -1
  59. runbooks/operate/vpc_endpoints.py +1 -1
  60. runbooks/remediation/README.md +489 -13
  61. runbooks/remediation/commons.py +8 -4
  62. runbooks/security/ENTERPRISE_SECURITY_FRAMEWORK.md +506 -0
  63. runbooks/security/README.md +12 -1
  64. runbooks/security/__init__.py +164 -33
  65. runbooks/security/compliance_automation.py +12 -10
  66. runbooks/security/compliance_automation_engine.py +1021 -0
  67. runbooks/security/enterprise_security_framework.py +931 -0
  68. runbooks/security/enterprise_security_policies.json +293 -0
  69. runbooks/security/integration_test_enterprise_security.py +879 -0
  70. runbooks/security/module_security_integrator.py +641 -0
  71. runbooks/security/report_generator.py +1 -1
  72. runbooks/security/run_script.py +4 -8
  73. runbooks/security/security_baseline_tester.py +36 -49
  74. runbooks/security/security_export.py +99 -120
  75. runbooks/sre/README.md +472 -0
  76. runbooks/sre/__init__.py +33 -0
  77. runbooks/sre/mcp_reliability_engine.py +1049 -0
  78. runbooks/sre/performance_optimization_engine.py +1032 -0
  79. runbooks/sre/reliability_monitoring_framework.py +1011 -0
  80. runbooks/validation/__init__.py +2 -2
  81. runbooks/validation/benchmark.py +154 -149
  82. runbooks/validation/cli.py +159 -147
  83. runbooks/validation/mcp_validator.py +265 -236
  84. runbooks/vpc/README.md +478 -0
  85. runbooks/vpc/__init__.py +2 -2
  86. runbooks/vpc/manager_interface.py +366 -351
  87. runbooks/vpc/networking_wrapper.py +62 -33
  88. runbooks/vpc/rich_formatters.py +22 -8
  89. {runbooks-0.7.9.dist-info → runbooks-0.9.0.dist-info}/METADATA +136 -54
  90. {runbooks-0.7.9.dist-info → runbooks-0.9.0.dist-info}/RECORD +94 -55
  91. {runbooks-0.7.9.dist-info → runbooks-0.9.0.dist-info}/entry_points.txt +1 -1
  92. runbooks/finops/cross_validation.py +0 -375
  93. {runbooks-0.7.9.dist-info → runbooks-0.9.0.dist-info}/WHEEL +0 -0
  94. {runbooks-0.7.9.dist-info → runbooks-0.9.0.dist-info}/licenses/LICENSE +0 -0
  95. {runbooks-0.7.9.dist-info → runbooks-0.9.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,440 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Context-Aware Logging System for CloudOps Runbooks
4
+
5
+ This module provides adaptive logging that detects execution context (CLI vs Jupyter)
6
+ and adjusts verbosity, formatting, and output style accordingly.
7
+
8
+ Features:
9
+ - Automatic CLI vs Jupyter environment detection
10
+ - Adaptive logging levels (technical for CLI, clean for Jupyter)
11
+ - Smart Rich console output based on context
12
+ - Performance metrics display optimization
13
+ - Context-aware error handling and stack traces
14
+
15
+ Usage:
16
+ from runbooks.common.context_logger import ContextLogger, get_context_console
17
+
18
+ logger = ContextLogger("finops.dashboard")
19
+ console = get_context_console()
20
+
21
+ logger.info("Starting analysis") # Detailed in CLI, simple in Jupyter
22
+ logger.technical_detail("AWS API call details") # Only shown in CLI
23
+
24
+ Author: CloudOps Runbooks Team
25
+ Version: 0.8.0
26
+ """
27
+
28
+ import os
29
+ import sys
30
+ import time
31
+ from dataclasses import dataclass
32
+ from enum import Enum
33
+ from typing import Any, Dict, List, Optional, Union
34
+
35
+ from rich.console import Console
36
+ from rich.panel import Panel
37
+ from rich.progress import BarColumn, Progress, SpinnerColumn, TaskProgressColumn, TextColumn, TimeElapsedColumn
38
+ from rich.table import Table
39
+ from rich.text import Text
40
+ from rich.traceback import install as install_rich_traceback
41
+
42
+ from runbooks.common.rich_utils import (
43
+ CLOUDOPS_THEME,
44
+ STATUS_INDICATORS,
45
+ create_panel,
46
+ format_cost,
47
+ print_error,
48
+ print_info,
49
+ print_status,
50
+ print_success,
51
+ print_warning,
52
+ )
53
+ from runbooks.common.rich_utils import (
54
+ console as rich_console,
55
+ )
56
+
57
+
58
+ class ExecutionContext(Enum):
59
+ """Execution context types."""
60
+
61
+ CLI = "cli"
62
+ JUPYTER = "jupyter"
63
+ UNKNOWN = "unknown"
64
+
65
+
66
+ @dataclass
67
+ class ContextConfig:
68
+ """Configuration for context-aware logging."""
69
+
70
+ context: ExecutionContext
71
+ show_technical_details: bool
72
+ show_performance_metrics: bool
73
+ show_progress_bars: bool
74
+ show_full_stack_traces: bool
75
+ console_width: Optional[int] = None
76
+
77
+
78
+ class ContextDetector:
79
+ """Detect execution environment context."""
80
+
81
+ @staticmethod
82
+ def detect_context() -> ExecutionContext:
83
+ """
84
+ Detect if running in CLI vs Jupyter environment.
85
+
86
+ Returns:
87
+ ExecutionContext: Detected execution context
88
+ """
89
+ # Check for IPython/Jupyter kernel
90
+ try:
91
+ # Check if we're in IPython/Jupyter
92
+ from IPython import get_ipython
93
+
94
+ ipython = get_ipython()
95
+
96
+ if ipython is not None:
97
+ # Check if it's a Jupyter kernel
98
+ if hasattr(ipython, "kernel"):
99
+ return ExecutionContext.JUPYTER
100
+
101
+ # Check for Jupyter-specific modules
102
+ if "zmq" in str(type(ipython)).lower():
103
+ return ExecutionContext.JUPYTER
104
+
105
+ # Check for notebook environment variables
106
+ if any(key in os.environ for key in ["JUPYTER_SERVER_ROOT", "JPY_SESSION_NAME", "KERNEL_ID"]):
107
+ return ExecutionContext.JUPYTER
108
+
109
+ except ImportError:
110
+ # IPython not available, definitely not Jupyter
111
+ pass
112
+
113
+ # Check for terminal environment
114
+ if hasattr(sys.stdout, "isatty") and sys.stdout.isatty():
115
+ return ExecutionContext.CLI
116
+
117
+ # Check for common CLI indicators
118
+ if any(arg in sys.argv for arg in ["--help", "-h", "runbooks"]):
119
+ return ExecutionContext.CLI
120
+
121
+ # Default fallback
122
+ return ExecutionContext.UNKNOWN
123
+
124
+ @staticmethod
125
+ def get_context_config() -> ContextConfig:
126
+ """
127
+ Get context configuration based on detected environment.
128
+
129
+ Returns:
130
+ ContextConfig: Configuration optimized for detected context
131
+ """
132
+ context = ContextDetector.detect_context()
133
+
134
+ if context == ExecutionContext.CLI:
135
+ return ContextConfig(
136
+ context=context,
137
+ show_technical_details=True,
138
+ show_performance_metrics=True,
139
+ show_progress_bars=True,
140
+ show_full_stack_traces=True,
141
+ console_width=None, # Auto-detect
142
+ )
143
+ elif context == ExecutionContext.JUPYTER:
144
+ return ContextConfig(
145
+ context=context,
146
+ show_technical_details=False,
147
+ show_performance_metrics=False,
148
+ show_progress_bars=True, # Still useful in notebooks
149
+ show_full_stack_traces=False,
150
+ console_width=100, # Fixed width for notebook display
151
+ )
152
+ else:
153
+ # Conservative defaults for unknown context
154
+ return ContextConfig(
155
+ context=context,
156
+ show_technical_details=True,
157
+ show_performance_metrics=True,
158
+ show_progress_bars=True,
159
+ show_full_stack_traces=True,
160
+ console_width=None,
161
+ )
162
+
163
+
164
+ class ContextAwareConsole:
165
+ """Console wrapper that adapts output based on execution context."""
166
+
167
+ def __init__(self, config: Optional[ContextConfig] = None):
168
+ """
169
+ Initialize context-aware console.
170
+
171
+ Args:
172
+ config: Optional context configuration (auto-detected if None)
173
+ """
174
+ self.config = config or ContextDetector.get_context_config()
175
+
176
+ # Create console with appropriate configuration
177
+ console_kwargs = {
178
+ "theme": CLOUDOPS_THEME,
179
+ "force_terminal": self.config.context == ExecutionContext.CLI,
180
+ }
181
+
182
+ if self.config.console_width:
183
+ console_kwargs["width"] = self.config.console_width
184
+
185
+ self.console = Console(**console_kwargs)
186
+
187
+ # Install rich tracebacks if configured
188
+ if self.config.show_full_stack_traces:
189
+ install_rich_traceback(show_locals=True, console=self.console)
190
+
191
+ def print(self, *args, **kwargs) -> None:
192
+ """Print with context awareness."""
193
+ self.console.print(*args, **kwargs)
194
+
195
+ def log(self, *args, **kwargs) -> None:
196
+ """Log with context awareness."""
197
+ if self.config.show_technical_details:
198
+ self.console.log(*args, **kwargs)
199
+
200
+ def print_technical_detail(self, message: str, style: str = "dim") -> None:
201
+ """Print technical detail only in appropriate contexts."""
202
+ if self.config.show_technical_details:
203
+ self.console.print(f"🔧 {message}", style=style)
204
+
205
+ def print_performance_metric(self, metric_name: str, value: Union[str, float], unit: str = "") -> None:
206
+ """Print performance metrics only in appropriate contexts."""
207
+ if self.config.show_performance_metrics:
208
+ if isinstance(value, float):
209
+ formatted_value = f"{value:.2f}{unit}"
210
+ else:
211
+ formatted_value = f"{value}{unit}"
212
+ self.console.print(f"⚡ {metric_name}: [highlight]{formatted_value}[/]", style="info")
213
+
214
+ def create_progress(self, description: str = "Processing") -> Progress:
215
+ """Create progress bar appropriate for context."""
216
+ if self.config.show_progress_bars:
217
+ if self.config.context == ExecutionContext.JUPYTER:
218
+ # Simpler progress for Jupyter
219
+ return Progress(
220
+ TextColumn("[progress.description]{task.description}"),
221
+ BarColumn(bar_width=40),
222
+ TaskProgressColumn(),
223
+ console=self.console,
224
+ transient=False, # Don't hide in notebooks
225
+ )
226
+ else:
227
+ # Full progress for CLI
228
+ return Progress(
229
+ SpinnerColumn(spinner_name="dots", style="cyan"),
230
+ TextColumn("[progress.description]{task.description}"),
231
+ BarColumn(bar_width=40, style="cyan", complete_style="green"),
232
+ TaskProgressColumn(),
233
+ TimeElapsedColumn(),
234
+ console=self.console,
235
+ transient=True,
236
+ )
237
+ else:
238
+ # Minimal progress fallback
239
+ return Progress(
240
+ TextColumn("[progress.description]{task.description}"),
241
+ console=self.console,
242
+ )
243
+
244
+
245
+ class ContextLogger:
246
+ """Context-aware logger for CloudOps operations."""
247
+
248
+ def __init__(self, name: str, console: Optional[ContextAwareConsole] = None):
249
+ """
250
+ Initialize context logger.
251
+
252
+ Args:
253
+ name: Logger name (typically module.operation)
254
+ console: Optional context-aware console (auto-created if None)
255
+ """
256
+ self.name = name
257
+ self.console = console or ContextAwareConsole()
258
+ self.start_time = time.time()
259
+ self._operation_start_times: Dict[str, float] = {}
260
+
261
+ def info(self, message: str, technical_detail: Optional[str] = None) -> None:
262
+ """
263
+ Log info message with optional technical detail.
264
+
265
+ Args:
266
+ message: Main message (always shown)
267
+ technical_detail: Technical detail (CLI only)
268
+ """
269
+ # Always show main message
270
+ print_info(message)
271
+
272
+ # Show technical detail only in appropriate context
273
+ if technical_detail and self.console.config.show_technical_details:
274
+ self.console.print_technical_detail(technical_detail)
275
+
276
+ def success(self, message: str, metrics: Optional[Dict[str, Any]] = None) -> None:
277
+ """
278
+ Log success message with optional performance metrics.
279
+
280
+ Args:
281
+ message: Success message
282
+ metrics: Optional performance metrics (CLI only)
283
+ """
284
+ print_success(message)
285
+
286
+ if metrics and self.console.config.show_performance_metrics:
287
+ for metric_name, metric_value in metrics.items():
288
+ if isinstance(metric_value, dict) and "value" in metric_value:
289
+ self.console.print_performance_metric(
290
+ metric_name, metric_value["value"], metric_value.get("unit", "")
291
+ )
292
+ else:
293
+ self.console.print_performance_metric(metric_name, metric_value)
294
+
295
+ def warning(self, message: str, technical_detail: Optional[str] = None) -> None:
296
+ """
297
+ Log warning message with optional technical detail.
298
+
299
+ Args:
300
+ message: Warning message
301
+ technical_detail: Technical detail (CLI only)
302
+ """
303
+ print_warning(message)
304
+
305
+ if technical_detail and self.console.config.show_technical_details:
306
+ self.console.print_technical_detail(f"Details: {technical_detail}", style="yellow")
307
+
308
+ def error(self, message: str, exception: Optional[Exception] = None, show_traceback: bool = True) -> None:
309
+ """
310
+ Log error message with context-appropriate error handling.
311
+
312
+ Args:
313
+ message: Error message
314
+ exception: Optional exception object
315
+ show_traceback: Whether to show full traceback
316
+ """
317
+ if exception:
318
+ if self.console.config.show_full_stack_traces and show_traceback:
319
+ # Full error details for CLI
320
+ print_error(message, exception)
321
+ self.console.console.print_exception(show_locals=True)
322
+ else:
323
+ # Simple error for Jupyter
324
+ error_detail = f"{type(exception).__name__}: {str(exception)}"
325
+ print_error(f"{message} - {error_detail}")
326
+ else:
327
+ print_error(message)
328
+
329
+ def start_operation(self, operation: str, description: Optional[str] = None) -> None:
330
+ """
331
+ Start timing an operation.
332
+
333
+ Args:
334
+ operation: Operation name
335
+ description: Optional description
336
+ """
337
+ self._operation_start_times[operation] = time.time()
338
+
339
+ if description:
340
+ display_msg = f"{description}"
341
+ else:
342
+ display_msg = f"Starting {operation}"
343
+
344
+ if self.console.config.show_technical_details:
345
+ self.console.print_technical_detail(f"Operation: {operation}")
346
+
347
+ self.info(display_msg)
348
+
349
+ def complete_operation(self, operation: str, result_summary: Optional[str] = None) -> float:
350
+ """
351
+ Complete timing an operation and log results.
352
+
353
+ Args:
354
+ operation: Operation name
355
+ result_summary: Optional result summary
356
+
357
+ Returns:
358
+ Operation duration in seconds
359
+ """
360
+ if operation not in self._operation_start_times:
361
+ self.warning(f"Operation '{operation}' was not started with start_operation()")
362
+ return 0.0
363
+
364
+ duration = time.time() - self._operation_start_times[operation]
365
+ del self._operation_start_times[operation]
366
+
367
+ # Build success message
368
+ success_msg = f"Completed {operation}"
369
+ if result_summary:
370
+ success_msg = f"{success_msg}: {result_summary}"
371
+
372
+ # Include metrics if configured
373
+ metrics = None
374
+ if self.console.config.show_performance_metrics:
375
+ metrics = {
376
+ "Duration": {"value": duration, "unit": "s"},
377
+ "Total Runtime": {"value": time.time() - self.start_time, "unit": "s"},
378
+ }
379
+
380
+ self.success(success_msg, metrics)
381
+ return duration
382
+
383
+ def create_progress_context(self, description: str = "Processing"):
384
+ """
385
+ Create a progress context manager.
386
+
387
+ Args:
388
+ description: Progress description
389
+
390
+ Returns:
391
+ Progress context manager
392
+ """
393
+ return self.console.create_progress(description)
394
+
395
+
396
+ # Global instances for easy access
397
+ _global_config: Optional[ContextConfig] = None
398
+ _global_console: Optional[ContextAwareConsole] = None
399
+
400
+
401
+ def get_context_config() -> ContextConfig:
402
+ """Get global context configuration."""
403
+ global _global_config
404
+ if _global_config is None:
405
+ _global_config = ContextDetector.get_context_config()
406
+ return _global_config
407
+
408
+
409
+ def get_context_console() -> ContextAwareConsole:
410
+ """Get global context-aware console."""
411
+ global _global_console
412
+ if _global_console is None:
413
+ _global_console = ContextAwareConsole(get_context_config())
414
+ return _global_console
415
+
416
+
417
+ def create_context_logger(name: str) -> ContextLogger:
418
+ """
419
+ Create a context logger for a module.
420
+
421
+ Args:
422
+ name: Logger name (typically module.operation)
423
+
424
+ Returns:
425
+ ContextLogger instance
426
+ """
427
+ return ContextLogger(name, get_context_console())
428
+
429
+
430
+ # Export public API
431
+ __all__ = [
432
+ "ExecutionContext",
433
+ "ContextConfig",
434
+ "ContextDetector",
435
+ "ContextAwareConsole",
436
+ "ContextLogger",
437
+ "get_context_config",
438
+ "get_context_console",
439
+ "create_context_logger",
440
+ ]