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
@@ -0,0 +1,439 @@
1
+ """
2
+ Enterprise-grade structured logging for CloudOps-Runbooks.
3
+
4
+ This module provides structured logging capabilities for enterprise environments,
5
+ including audit trails, performance monitoring, and compliance logging.
6
+ """
7
+
8
+ import json
9
+ import sys
10
+ import time
11
+ from contextlib import contextmanager
12
+ from datetime import datetime
13
+ from functools import wraps
14
+ from pathlib import Path
15
+ from typing import Any, Dict, Optional, Union
16
+
17
+ try:
18
+ from loguru import logger as loguru_logger
19
+
20
+ _HAS_LOGURU = True
21
+ except ImportError:
22
+ import logging
23
+
24
+ loguru_logger = logging.getLogger(__name__)
25
+ _HAS_LOGURU = False
26
+
27
+
28
+ class EnterpriseLogger:
29
+ """Enterprise-grade logger with structured logging capabilities."""
30
+
31
+ def __init__(
32
+ self,
33
+ name: str = "runbooks",
34
+ level: str = "INFO",
35
+ log_dir: Optional[Path] = None,
36
+ enable_console: bool = True,
37
+ enable_file: bool = True,
38
+ enable_audit: bool = True,
39
+ correlation_id: Optional[str] = None,
40
+ ):
41
+ """
42
+ Initialize enterprise logger.
43
+
44
+ Args:
45
+ name: Logger name
46
+ level: Log level (DEBUG, INFO, WARNING, ERROR, CRITICAL)
47
+ log_dir: Directory for log files
48
+ enable_console: Enable console logging
49
+ enable_file: Enable file logging
50
+ enable_audit: Enable audit logging
51
+ correlation_id: Correlation ID for tracking operations
52
+ """
53
+ self.name = name
54
+ self.level = level
55
+ self.log_dir = log_dir or Path.home() / ".runbooks" / "logs"
56
+ self.correlation_id = correlation_id or self._generate_correlation_id()
57
+
58
+ # Ensure log directory exists
59
+ self.log_dir.mkdir(parents=True, exist_ok=True)
60
+
61
+ # Setup logging handlers
62
+ if _HAS_LOGURU:
63
+ self._setup_loguru_logging(enable_console, enable_file, enable_audit)
64
+ else:
65
+ self._setup_standard_logging(enable_console, enable_file)
66
+
67
+ def _generate_correlation_id(self) -> str:
68
+ """Generate unique correlation ID for tracking operations."""
69
+ import uuid
70
+
71
+ return f"runbooks-{int(time.time())}-{str(uuid.uuid4())[:8]}"
72
+
73
+ def _setup_loguru_logging(self, enable_console: bool, enable_file: bool, enable_audit: bool) -> None:
74
+ """Setup Loguru-based logging."""
75
+ # Remove default handler
76
+ loguru_logger.remove()
77
+
78
+ # Console handler
79
+ if enable_console:
80
+ loguru_logger.add(
81
+ sys.stderr,
82
+ level=self.level,
83
+ format="<green>{time:YYYY-MM-DD HH:mm:ss.SSS}</green> | "
84
+ "<level>{level: <8}</level> | "
85
+ "<cyan>{extra[correlation_id]}</cyan> | "
86
+ "<cyan>{name}</cyan>:<cyan>{function}</cyan>:<cyan>{line}</cyan> - "
87
+ "<level>{message}</level>",
88
+ colorize=True,
89
+ filter=lambda record: record["extra"].setdefault("correlation_id", self.correlation_id),
90
+ )
91
+
92
+ # Application log file
93
+ if enable_file:
94
+ app_log_file = self.log_dir / "runbooks.log"
95
+ loguru_logger.add(
96
+ app_log_file,
97
+ level="DEBUG",
98
+ format="{time:YYYY-MM-DD HH:mm:ss.SSS} | {level: <8} | {extra[correlation_id]} | "
99
+ "{name}:{function}:{line} - {message}",
100
+ rotation="10 MB",
101
+ retention="30 days",
102
+ compression="zip",
103
+ filter=lambda record: record["extra"].setdefault("correlation_id", self.correlation_id),
104
+ )
105
+
106
+ # Audit log file
107
+ if enable_audit:
108
+ audit_log_file = self.log_dir / "audit.log"
109
+ loguru_logger.add(
110
+ audit_log_file,
111
+ level="INFO",
112
+ format="{time:YYYY-MM-DD HH:mm:ss.SSS} | {extra[correlation_id]} | {message}",
113
+ rotation="50 MB",
114
+ retention="365 days",
115
+ compression="zip",
116
+ filter=lambda record: (
117
+ record["extra"].setdefault("correlation_id", self.correlation_id)
118
+ or record.get("extra", {}).get("audit", False)
119
+ ),
120
+ )
121
+
122
+ # Performance log file
123
+ performance_log_file = self.log_dir / "performance.log"
124
+ loguru_logger.add(
125
+ performance_log_file,
126
+ level="INFO",
127
+ format="{time:YYYY-MM-DD HH:mm:ss.SSS} | {extra[correlation_id]} | {message}",
128
+ rotation="20 MB",
129
+ retention="7 days",
130
+ filter=lambda record: (
131
+ record["extra"].setdefault("correlation_id", self.correlation_id)
132
+ or record.get("extra", {}).get("performance", False)
133
+ ),
134
+ )
135
+
136
+ # Bind correlation ID
137
+ loguru_logger = loguru_logger.bind(correlation_id=self.correlation_id)
138
+
139
+ def _setup_standard_logging(self, enable_console: bool, enable_file: bool) -> None:
140
+ """Setup standard logging as fallback."""
141
+ logger = logging.getLogger(self.name)
142
+ logger.setLevel(getattr(logging, self.level))
143
+
144
+ # Console handler
145
+ if enable_console:
146
+ console_handler = logging.StreamHandler(sys.stderr)
147
+ console_formatter = logging.Formatter(
148
+ "%(asctime)s | %(levelname)-8s | %(name)s:%(funcName)s:%(lineno)d - %(message)s"
149
+ )
150
+ console_handler.setFormatter(console_formatter)
151
+ logger.addHandler(console_handler)
152
+
153
+ # File handler
154
+ if enable_file:
155
+ file_handler = logging.FileHandler(self.log_dir / "runbooks.log")
156
+ file_formatter = logging.Formatter(
157
+ "%(asctime)s | %(levelname)-8s | %(name)s:%(funcName)s:%(lineno)d - %(message)s"
158
+ )
159
+ file_handler.setFormatter(file_formatter)
160
+ logger.addHandler(file_handler)
161
+
162
+ def info(self, message: str, **kwargs) -> None:
163
+ """Log info message."""
164
+ if _HAS_LOGURU:
165
+ loguru_logger.bind(**kwargs).info(message)
166
+ else:
167
+ logging.getLogger(self.name).info(message)
168
+
169
+ def debug(self, message: str, **kwargs) -> None:
170
+ """Log debug message."""
171
+ if _HAS_LOGURU:
172
+ loguru_logger.bind(**kwargs).debug(message)
173
+ else:
174
+ logging.getLogger(self.name).debug(message)
175
+
176
+ def warning(self, message: str, **kwargs) -> None:
177
+ """Log warning message."""
178
+ if _HAS_LOGURU:
179
+ loguru_logger.bind(**kwargs).warning(message)
180
+ else:
181
+ logging.getLogger(self.name).warning(message)
182
+
183
+ def error(self, message: str, **kwargs) -> None:
184
+ """Log error message."""
185
+ if _HAS_LOGURU:
186
+ loguru_logger.bind(**kwargs).error(message)
187
+ else:
188
+ logging.getLogger(self.name).error(message)
189
+
190
+ def critical(self, message: str, **kwargs) -> None:
191
+ """Log critical message."""
192
+ if _HAS_LOGURU:
193
+ loguru_logger.bind(**kwargs).critical(message)
194
+ else:
195
+ logging.getLogger(self.name).critical(message)
196
+
197
+
198
+ class AuditLogger:
199
+ """Specialized logger for audit trails and compliance."""
200
+
201
+ def __init__(self, log_dir: Optional[Path] = None):
202
+ """Initialize audit logger."""
203
+ self.log_dir = log_dir or Path.home() / ".runbooks" / "logs"
204
+ self.log_dir.mkdir(parents=True, exist_ok=True)
205
+ self.audit_file = self.log_dir / "audit.log"
206
+
207
+ def log_operation(
208
+ self,
209
+ operation: str,
210
+ user: Optional[str] = None,
211
+ resource: Optional[str] = None,
212
+ success: bool = True,
213
+ details: Optional[Dict[str, Any]] = None,
214
+ correlation_id: Optional[str] = None,
215
+ ) -> None:
216
+ """
217
+ Log audit operation.
218
+
219
+ Args:
220
+ operation: Operation performed
221
+ user: User who performed the operation
222
+ resource: Resource affected
223
+ success: Whether operation was successful
224
+ details: Additional operation details
225
+ correlation_id: Correlation ID for tracking
226
+ """
227
+ audit_entry = {
228
+ "timestamp": datetime.utcnow().isoformat(),
229
+ "correlation_id": correlation_id,
230
+ "operation": operation,
231
+ "user": user or "system",
232
+ "resource": resource,
233
+ "success": success,
234
+ "details": details or {},
235
+ }
236
+
237
+ if _HAS_LOGURU:
238
+ loguru_logger.bind(audit=True, **audit_entry).info(
239
+ f"AUDIT: {operation} - {'SUCCESS' if success else 'FAILED'}"
240
+ )
241
+ else:
242
+ # Fallback to direct file writing
243
+ with open(self.audit_file, "a") as f:
244
+ f.write(json.dumps(audit_entry) + "\n")
245
+
246
+
247
+ class PerformanceLogger:
248
+ """Specialized logger for performance monitoring."""
249
+
250
+ def __init__(self, log_dir: Optional[Path] = None):
251
+ """Initialize performance logger."""
252
+ self.log_dir = log_dir or Path.home() / ".runbooks" / "logs"
253
+ self.log_dir.mkdir(parents=True, exist_ok=True)
254
+ self.performance_file = self.log_dir / "performance.log"
255
+
256
+ def log_performance(
257
+ self,
258
+ operation: str,
259
+ duration: float,
260
+ success: bool = True,
261
+ details: Optional[Dict[str, Any]] = None,
262
+ correlation_id: Optional[str] = None,
263
+ ) -> None:
264
+ """
265
+ Log performance metrics.
266
+
267
+ Args:
268
+ operation: Operation name
269
+ duration: Duration in seconds
270
+ success: Whether operation was successful
271
+ details: Additional performance details
272
+ correlation_id: Correlation ID for tracking
273
+ """
274
+ perf_entry = {
275
+ "timestamp": datetime.utcnow().isoformat(),
276
+ "correlation_id": correlation_id,
277
+ "operation": operation,
278
+ "duration_seconds": round(duration, 3),
279
+ "success": success,
280
+ "details": details or {},
281
+ }
282
+
283
+ if _HAS_LOGURU:
284
+ loguru_logger.bind(performance=True, **perf_entry).info(
285
+ f"PERFORMANCE: {operation} completed in {duration:.3f}s"
286
+ )
287
+ else:
288
+ # Fallback to direct file writing
289
+ with open(self.performance_file, "a") as f:
290
+ f.write(json.dumps(perf_entry) + "\n")
291
+
292
+ @contextmanager
293
+ def measure_operation(
294
+ self,
295
+ operation: str,
296
+ details: Optional[Dict[str, Any]] = None,
297
+ correlation_id: Optional[str] = None,
298
+ ):
299
+ """
300
+ Context manager for measuring operation performance.
301
+
302
+ Args:
303
+ operation: Operation name
304
+ details: Additional performance details
305
+ correlation_id: Correlation ID for tracking
306
+ """
307
+ start_time = time.time()
308
+ success = True
309
+ try:
310
+ yield
311
+ except Exception:
312
+ success = False
313
+ raise
314
+ finally:
315
+ duration = time.time() - start_time
316
+ self.log_performance(
317
+ operation=operation,
318
+ duration=duration,
319
+ success=success,
320
+ details=details,
321
+ correlation_id=correlation_id,
322
+ )
323
+
324
+
325
+ def configure_enterprise_logging(
326
+ level: str = "INFO",
327
+ log_dir: Optional[Union[str, Path]] = None,
328
+ correlation_id: Optional[str] = None,
329
+ enable_audit: bool = True,
330
+ enable_performance: bool = True,
331
+ ) -> EnterpriseLogger:
332
+ """
333
+ Configure enterprise logging for the application.
334
+
335
+ Args:
336
+ level: Log level
337
+ log_dir: Log directory path
338
+ correlation_id: Correlation ID for tracking
339
+ enable_audit: Enable audit logging
340
+ enable_performance: Enable performance logging
341
+
342
+ Returns:
343
+ Configured enterprise logger
344
+ """
345
+ if isinstance(log_dir, str):
346
+ log_dir = Path(log_dir)
347
+
348
+ return EnterpriseLogger(
349
+ level=level,
350
+ log_dir=log_dir,
351
+ correlation_id=correlation_id,
352
+ enable_audit=enable_audit,
353
+ )
354
+
355
+
356
+ def log_operation_performance(
357
+ operation_name: Optional[str] = None,
358
+ details: Optional[Dict[str, Any]] = None,
359
+ ):
360
+ """
361
+ Decorator for logging operation performance.
362
+
363
+ Args:
364
+ operation_name: Name of operation (defaults to function name)
365
+ details: Additional details to log
366
+ """
367
+
368
+ def decorator(func):
369
+ @wraps(func)
370
+ def wrapper(*args, **kwargs):
371
+ op_name = operation_name or func.__name__
372
+ perf_logger = PerformanceLogger()
373
+
374
+ with perf_logger.measure_operation(
375
+ operation=op_name,
376
+ details=details,
377
+ ):
378
+ return func(*args, **kwargs)
379
+
380
+ return wrapper
381
+
382
+ return decorator
383
+
384
+
385
+ def log_audit_operation(
386
+ operation_name: Optional[str] = None,
387
+ resource_extractor: Optional[callable] = None,
388
+ ):
389
+ """
390
+ Decorator for logging audit operations.
391
+
392
+ Args:
393
+ operation_name: Name of operation (defaults to function name)
394
+ resource_extractor: Function to extract resource from arguments
395
+ """
396
+
397
+ def decorator(func):
398
+ @wraps(func)
399
+ def wrapper(*args, **kwargs):
400
+ op_name = operation_name or func.__name__
401
+ resource = None
402
+
403
+ if resource_extractor:
404
+ try:
405
+ resource = resource_extractor(*args, **kwargs)
406
+ except Exception:
407
+ pass
408
+
409
+ audit_logger = AuditLogger()
410
+ success = True
411
+
412
+ try:
413
+ result = func(*args, **kwargs)
414
+ return result
415
+ except Exception:
416
+ success = False
417
+ raise
418
+ finally:
419
+ audit_logger.log_operation(
420
+ operation=op_name,
421
+ resource=resource,
422
+ success=success,
423
+ )
424
+
425
+ return wrapper
426
+
427
+ return decorator
428
+
429
+
430
+ # Global logger instance
431
+ _global_logger: Optional[EnterpriseLogger] = None
432
+
433
+
434
+ def get_logger() -> EnterpriseLogger:
435
+ """Get global enterprise logger instance."""
436
+ global _global_logger
437
+ if _global_logger is None:
438
+ _global_logger = configure_enterprise_logging()
439
+ return _global_logger