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.
- runbooks/__init__.py +1 -1
- runbooks/cfat/README.md +12 -1
- runbooks/cfat/__init__.py +1 -1
- runbooks/cfat/assessment/runner.py +42 -34
- runbooks/cfat/models.py +1 -1
- runbooks/common/__init__.py +152 -0
- runbooks/common/accuracy_validator.py +1039 -0
- runbooks/common/context_logger.py +440 -0
- runbooks/common/cross_module_integration.py +594 -0
- runbooks/common/enhanced_exception_handler.py +1108 -0
- runbooks/common/enterprise_audit_integration.py +634 -0
- runbooks/common/mcp_integration.py +539 -0
- runbooks/common/performance_monitor.py +387 -0
- runbooks/common/profile_utils.py +216 -0
- runbooks/common/rich_utils.py +171 -0
- runbooks/feedback/user_feedback_collector.py +440 -0
- runbooks/finops/README.md +339 -451
- runbooks/finops/__init__.py +4 -21
- runbooks/finops/account_resolver.py +279 -0
- runbooks/finops/accuracy_cross_validator.py +638 -0
- runbooks/finops/aws_client.py +721 -36
- runbooks/finops/budget_integration.py +313 -0
- runbooks/finops/cli.py +59 -5
- runbooks/finops/cost_processor.py +211 -37
- runbooks/finops/dashboard_router.py +900 -0
- runbooks/finops/dashboard_runner.py +990 -232
- runbooks/finops/embedded_mcp_validator.py +288 -0
- runbooks/finops/enhanced_dashboard_runner.py +8 -7
- runbooks/finops/enhanced_progress.py +327 -0
- runbooks/finops/enhanced_trend_visualization.py +423 -0
- runbooks/finops/finops_dashboard.py +29 -1880
- runbooks/finops/helpers.py +509 -196
- runbooks/finops/iam_guidance.py +400 -0
- runbooks/finops/markdown_exporter.py +466 -0
- runbooks/finops/multi_dashboard.py +1502 -0
- runbooks/finops/optimizer.py +15 -15
- runbooks/finops/profile_processor.py +2 -2
- runbooks/finops/runbooks.inventory.organizations_discovery.log +0 -0
- runbooks/finops/runbooks.security.report_generator.log +0 -0
- runbooks/finops/runbooks.security.run_script.log +0 -0
- runbooks/finops/runbooks.security.security_export.log +0 -0
- runbooks/finops/service_mapping.py +195 -0
- runbooks/finops/single_dashboard.py +710 -0
- runbooks/finops/tests/test_reference_images_validation.py +1 -1
- runbooks/inventory/README.md +12 -1
- runbooks/inventory/core/collector.py +157 -29
- runbooks/inventory/list_ec2_instances.py +9 -6
- runbooks/inventory/list_ssm_parameters.py +10 -10
- runbooks/inventory/organizations_discovery.py +210 -164
- runbooks/inventory/rich_inventory_display.py +74 -107
- runbooks/inventory/run_on_multi_accounts.py +13 -13
- runbooks/main.py +740 -134
- runbooks/metrics/dora_metrics_engine.py +711 -17
- runbooks/monitoring/performance_monitor.py +433 -0
- runbooks/operate/README.md +394 -0
- runbooks/operate/base.py +215 -47
- runbooks/operate/ec2_operations.py +7 -5
- runbooks/operate/privatelink_operations.py +1 -1
- runbooks/operate/vpc_endpoints.py +1 -1
- runbooks/remediation/README.md +489 -13
- runbooks/remediation/commons.py +8 -4
- runbooks/security/ENTERPRISE_SECURITY_FRAMEWORK.md +506 -0
- runbooks/security/README.md +12 -1
- runbooks/security/__init__.py +164 -33
- runbooks/security/compliance_automation.py +12 -10
- runbooks/security/compliance_automation_engine.py +1021 -0
- runbooks/security/enterprise_security_framework.py +931 -0
- runbooks/security/enterprise_security_policies.json +293 -0
- runbooks/security/integration_test_enterprise_security.py +879 -0
- runbooks/security/module_security_integrator.py +641 -0
- runbooks/security/report_generator.py +1 -1
- runbooks/security/run_script.py +4 -8
- runbooks/security/security_baseline_tester.py +36 -49
- runbooks/security/security_export.py +99 -120
- runbooks/sre/README.md +472 -0
- runbooks/sre/__init__.py +33 -0
- runbooks/sre/mcp_reliability_engine.py +1049 -0
- runbooks/sre/performance_optimization_engine.py +1032 -0
- runbooks/sre/reliability_monitoring_framework.py +1011 -0
- runbooks/validation/__init__.py +2 -2
- runbooks/validation/benchmark.py +154 -149
- runbooks/validation/cli.py +159 -147
- runbooks/validation/mcp_validator.py +265 -236
- runbooks/vpc/README.md +478 -0
- runbooks/vpc/__init__.py +2 -2
- runbooks/vpc/manager_interface.py +366 -351
- runbooks/vpc/networking_wrapper.py +62 -33
- runbooks/vpc/rich_formatters.py +22 -8
- {runbooks-0.7.9.dist-info → runbooks-0.9.0.dist-info}/METADATA +136 -54
- {runbooks-0.7.9.dist-info → runbooks-0.9.0.dist-info}/RECORD +94 -55
- {runbooks-0.7.9.dist-info → runbooks-0.9.0.dist-info}/entry_points.txt +1 -1
- runbooks/finops/cross_validation.py +0 -375
- {runbooks-0.7.9.dist-info → runbooks-0.9.0.dist-info}/WHEEL +0 -0
- {runbooks-0.7.9.dist-info → runbooks-0.9.0.dist-info}/licenses/LICENSE +0 -0
- {runbooks-0.7.9.dist-info → runbooks-0.9.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,387 @@
|
|
1
|
+
#!/usr/bin/env python3
|
2
|
+
"""
|
3
|
+
Performance Monitoring Framework for CloudOps Runbooks Platform
|
4
|
+
|
5
|
+
This module provides enterprise-grade performance monitoring capabilities
|
6
|
+
extracted from proven FinOps benchmarking patterns achieving 69% performance improvement.
|
7
|
+
|
8
|
+
Features:
|
9
|
+
- Real-time performance tracking with Rich progress indicators
|
10
|
+
- Module-specific performance targets and thresholds
|
11
|
+
- Performance degradation detection and alerting
|
12
|
+
- Comprehensive metrics collection and reporting
|
13
|
+
- Context-aware performance optimization
|
14
|
+
|
15
|
+
Author: CloudOps Runbooks Team
|
16
|
+
Version: 0.8.0
|
17
|
+
"""
|
18
|
+
|
19
|
+
import threading
|
20
|
+
import time
|
21
|
+
from contextlib import contextmanager
|
22
|
+
from dataclasses import dataclass, field
|
23
|
+
from datetime import datetime, timedelta
|
24
|
+
from typing import Any, Callable, Dict, List, Optional
|
25
|
+
|
26
|
+
from runbooks.common.rich_utils import (
|
27
|
+
STATUS_INDICATORS,
|
28
|
+
console,
|
29
|
+
create_progress_bar,
|
30
|
+
create_table,
|
31
|
+
format_cost,
|
32
|
+
print_error,
|
33
|
+
print_success,
|
34
|
+
print_warning,
|
35
|
+
)
|
36
|
+
|
37
|
+
|
38
|
+
@dataclass
|
39
|
+
class PerformanceMetrics:
|
40
|
+
"""Performance metrics container for operation tracking."""
|
41
|
+
|
42
|
+
operation_name: str
|
43
|
+
start_time: float = field(default_factory=time.time)
|
44
|
+
end_time: Optional[float] = None
|
45
|
+
duration: Optional[float] = None
|
46
|
+
target_duration: Optional[float] = None
|
47
|
+
success: bool = False
|
48
|
+
error_message: Optional[str] = None
|
49
|
+
memory_usage: Optional[float] = None
|
50
|
+
metadata: Dict[str, Any] = field(default_factory=dict)
|
51
|
+
|
52
|
+
def calculate_duration(self) -> float:
|
53
|
+
"""Calculate operation duration."""
|
54
|
+
if self.end_time and self.start_time:
|
55
|
+
self.duration = self.end_time - self.start_time
|
56
|
+
return self.duration
|
57
|
+
return 0.0
|
58
|
+
|
59
|
+
def is_within_target(self) -> bool:
|
60
|
+
"""Check if operation completed within target duration."""
|
61
|
+
if not self.target_duration or not self.duration:
|
62
|
+
return True
|
63
|
+
return self.duration <= self.target_duration
|
64
|
+
|
65
|
+
def get_performance_status(self) -> str:
|
66
|
+
"""Get performance status indicator."""
|
67
|
+
if not self.success:
|
68
|
+
return "error"
|
69
|
+
elif not self.is_within_target():
|
70
|
+
return "warning"
|
71
|
+
else:
|
72
|
+
return "success"
|
73
|
+
|
74
|
+
|
75
|
+
@dataclass
|
76
|
+
class ModulePerformanceConfig:
|
77
|
+
"""Performance configuration for each module."""
|
78
|
+
|
79
|
+
module_name: str
|
80
|
+
target_duration: float # seconds
|
81
|
+
warning_threshold: float # seconds
|
82
|
+
critical_threshold: float # seconds
|
83
|
+
max_memory_mb: float = 512 # MB
|
84
|
+
description: str = ""
|
85
|
+
|
86
|
+
|
87
|
+
class PerformanceBenchmark:
|
88
|
+
"""
|
89
|
+
Enterprise performance monitoring system extracted from FinOps success patterns.
|
90
|
+
|
91
|
+
Provides real-time performance tracking, threshold monitoring, and degradation alerts
|
92
|
+
across all CloudOps Runbooks modules.
|
93
|
+
"""
|
94
|
+
|
95
|
+
# Module performance targets based on enterprise requirements
|
96
|
+
MODULE_CONFIGS = {
|
97
|
+
"inventory": ModulePerformanceConfig(
|
98
|
+
module_name="inventory",
|
99
|
+
target_duration=30.0,
|
100
|
+
warning_threshold=45.0,
|
101
|
+
critical_threshold=60.0,
|
102
|
+
description="Multi-account resource discovery",
|
103
|
+
),
|
104
|
+
"operate": ModulePerformanceConfig(
|
105
|
+
module_name="operate",
|
106
|
+
target_duration=15.0,
|
107
|
+
warning_threshold=30.0,
|
108
|
+
critical_threshold=45.0,
|
109
|
+
description="Resource operations (start/stop/modify)",
|
110
|
+
),
|
111
|
+
"security": ModulePerformanceConfig(
|
112
|
+
module_name="security",
|
113
|
+
target_duration=45.0,
|
114
|
+
warning_threshold=60.0,
|
115
|
+
critical_threshold=90.0,
|
116
|
+
description="Security baseline assessment",
|
117
|
+
),
|
118
|
+
"cfat": ModulePerformanceConfig(
|
119
|
+
module_name="cfat",
|
120
|
+
target_duration=30.0,
|
121
|
+
warning_threshold=45.0,
|
122
|
+
critical_threshold=60.0,
|
123
|
+
description="Cloud Foundations Assessment Tool",
|
124
|
+
),
|
125
|
+
"vpc": ModulePerformanceConfig(
|
126
|
+
module_name="vpc",
|
127
|
+
target_duration=20.0,
|
128
|
+
warning_threshold=30.0,
|
129
|
+
critical_threshold=45.0,
|
130
|
+
description="VPC analysis and optimization",
|
131
|
+
),
|
132
|
+
"remediation": ModulePerformanceConfig(
|
133
|
+
module_name="remediation",
|
134
|
+
target_duration=25.0,
|
135
|
+
warning_threshold=40.0,
|
136
|
+
critical_threshold=60.0,
|
137
|
+
description="Security remediation scripts",
|
138
|
+
),
|
139
|
+
"finops": ModulePerformanceConfig(
|
140
|
+
module_name="finops",
|
141
|
+
target_duration=30.0,
|
142
|
+
warning_threshold=45.0,
|
143
|
+
critical_threshold=60.0,
|
144
|
+
description="Cost optimization and analytics",
|
145
|
+
),
|
146
|
+
}
|
147
|
+
|
148
|
+
def __init__(self, module_name: str):
|
149
|
+
"""
|
150
|
+
Initialize performance benchmark for specific module.
|
151
|
+
|
152
|
+
Args:
|
153
|
+
module_name: Name of module being monitored
|
154
|
+
"""
|
155
|
+
self.module_name = module_name
|
156
|
+
self.config = self.MODULE_CONFIGS.get(
|
157
|
+
module_name,
|
158
|
+
ModulePerformanceConfig(
|
159
|
+
module_name=module_name, target_duration=30.0, warning_threshold=45.0, critical_threshold=60.0
|
160
|
+
),
|
161
|
+
)
|
162
|
+
self.metrics: List[PerformanceMetrics] = []
|
163
|
+
self._current_operation: Optional[PerformanceMetrics] = None
|
164
|
+
|
165
|
+
@contextmanager
|
166
|
+
def measure_operation(self, operation_name: str, show_progress: bool = True):
|
167
|
+
"""
|
168
|
+
Context manager for measuring operation performance.
|
169
|
+
|
170
|
+
Args:
|
171
|
+
operation_name: Name of operation being measured
|
172
|
+
show_progress: Whether to show Rich progress indicator
|
173
|
+
"""
|
174
|
+
# Initialize performance metrics
|
175
|
+
metrics = PerformanceMetrics(operation_name=operation_name, target_duration=self.config.target_duration)
|
176
|
+
self._current_operation = metrics
|
177
|
+
|
178
|
+
# Show progress indicator if enabled
|
179
|
+
progress = None
|
180
|
+
if show_progress and hasattr(console, "is_terminal") and console.is_terminal:
|
181
|
+
progress = create_progress_bar(f"[cyan]{self.module_name.title()}[/] - {operation_name}")
|
182
|
+
task = progress.add_task(operation_name, total=100)
|
183
|
+
progress.start()
|
184
|
+
|
185
|
+
try:
|
186
|
+
console.log(f"[dim]Starting {operation_name} (target: {self.config.target_duration}s)[/]")
|
187
|
+
yield metrics
|
188
|
+
|
189
|
+
# Mark as successful
|
190
|
+
metrics.success = True
|
191
|
+
metrics.end_time = time.time()
|
192
|
+
metrics.calculate_duration()
|
193
|
+
|
194
|
+
# Log performance results
|
195
|
+
self._log_performance_result(metrics)
|
196
|
+
|
197
|
+
except Exception as e:
|
198
|
+
# Mark as failed
|
199
|
+
metrics.success = False
|
200
|
+
metrics.error_message = str(e)
|
201
|
+
metrics.end_time = time.time()
|
202
|
+
metrics.calculate_duration()
|
203
|
+
|
204
|
+
print_error(f"Operation '{operation_name}' failed", e)
|
205
|
+
raise
|
206
|
+
|
207
|
+
finally:
|
208
|
+
if progress:
|
209
|
+
progress.stop()
|
210
|
+
|
211
|
+
# Store metrics
|
212
|
+
self.metrics.append(metrics)
|
213
|
+
self._current_operation = None
|
214
|
+
|
215
|
+
def _log_performance_result(self, metrics: PerformanceMetrics):
|
216
|
+
"""Log performance results with appropriate styling."""
|
217
|
+
status = metrics.get_performance_status()
|
218
|
+
duration_str = f"{metrics.duration:.2f}s" if metrics.duration else "N/A"
|
219
|
+
target_str = f"{metrics.target_duration:.1f}s" if metrics.target_duration else "N/A"
|
220
|
+
|
221
|
+
if status == "success":
|
222
|
+
print_success(f"{metrics.operation_name} completed in {duration_str} (target: {target_str})")
|
223
|
+
elif status == "warning":
|
224
|
+
print_warning(f"{metrics.operation_name} completed in {duration_str} (exceeded target: {target_str})")
|
225
|
+
else:
|
226
|
+
print_error(f"{metrics.operation_name} failed after {duration_str}")
|
227
|
+
|
228
|
+
# Check for performance degradation
|
229
|
+
if metrics.duration and metrics.duration > self.config.critical_threshold:
|
230
|
+
console.log(f"[red]🚨 Critical performance degradation detected for {self.module_name}[/]")
|
231
|
+
|
232
|
+
def get_module_performance_summary(self) -> Dict[str, Any]:
|
233
|
+
"""Get comprehensive performance summary for the module."""
|
234
|
+
if not self.metrics:
|
235
|
+
return {
|
236
|
+
"module": self.module_name,
|
237
|
+
"total_operations": 0,
|
238
|
+
"average_duration": 0.0,
|
239
|
+
"success_rate": 0.0,
|
240
|
+
"target_achievement": 0.0,
|
241
|
+
"status": "no_data",
|
242
|
+
}
|
243
|
+
|
244
|
+
successful_ops = [m for m in self.metrics if m.success]
|
245
|
+
total_ops = len(self.metrics)
|
246
|
+
|
247
|
+
avg_duration = (
|
248
|
+
sum(m.duration for m in successful_ops if m.duration) / len(successful_ops) if successful_ops else 0.0
|
249
|
+
)
|
250
|
+
success_rate = len(successful_ops) / total_ops if total_ops > 0 else 0.0
|
251
|
+
target_achievement = (
|
252
|
+
len([m for m in successful_ops if m.is_within_target()]) / len(successful_ops) if successful_ops else 0.0
|
253
|
+
)
|
254
|
+
|
255
|
+
# Determine overall status
|
256
|
+
if success_rate < 0.9:
|
257
|
+
status = "critical"
|
258
|
+
elif target_achievement < 0.8:
|
259
|
+
status = "warning"
|
260
|
+
else:
|
261
|
+
status = "healthy"
|
262
|
+
|
263
|
+
return {
|
264
|
+
"module": self.module_name,
|
265
|
+
"total_operations": total_ops,
|
266
|
+
"successful_operations": len(successful_ops),
|
267
|
+
"average_duration": avg_duration,
|
268
|
+
"success_rate": success_rate,
|
269
|
+
"target_achievement": target_achievement,
|
270
|
+
"target_duration": self.config.target_duration,
|
271
|
+
"status": status,
|
272
|
+
"description": self.config.description,
|
273
|
+
}
|
274
|
+
|
275
|
+
def create_performance_table(self) -> None:
|
276
|
+
"""Display performance metrics in Rich table format."""
|
277
|
+
summary = self.get_module_performance_summary()
|
278
|
+
|
279
|
+
table = create_table(
|
280
|
+
title=f"Performance Summary - {self.module_name.title()} Module",
|
281
|
+
columns=[
|
282
|
+
{"name": "Metric", "style": "cyan", "justify": "left"},
|
283
|
+
{"name": "Value", "style": "white", "justify": "right"},
|
284
|
+
{"name": "Status", "style": "white", "justify": "center"},
|
285
|
+
],
|
286
|
+
)
|
287
|
+
|
288
|
+
# Add rows with performance data
|
289
|
+
status_style = {"healthy": "green", "warning": "yellow", "critical": "red"}.get(summary["status"], "white")
|
290
|
+
|
291
|
+
table.add_row(
|
292
|
+
"Total Operations",
|
293
|
+
str(summary["total_operations"]),
|
294
|
+
f"[{status_style}]{STATUS_INDICATORS.get('success' if summary['status'] == 'healthy' else summary['status'], '')}[/]",
|
295
|
+
)
|
296
|
+
|
297
|
+
table.add_row(
|
298
|
+
"Success Rate",
|
299
|
+
f"{summary['success_rate']:.1%}",
|
300
|
+
f"[{'green' if summary['success_rate'] >= 0.9 else 'red'}]{summary['success_rate']:.1%}[/]",
|
301
|
+
)
|
302
|
+
|
303
|
+
table.add_row(
|
304
|
+
"Average Duration",
|
305
|
+
f"{summary['average_duration']:.2f}s",
|
306
|
+
f"[{'green' if summary['average_duration'] <= self.config.target_duration else 'yellow'}]{summary['average_duration']:.2f}s[/]",
|
307
|
+
)
|
308
|
+
|
309
|
+
table.add_row(
|
310
|
+
"Target Achievement",
|
311
|
+
f"{summary['target_achievement']:.1%}",
|
312
|
+
f"[{'green' if summary['target_achievement'] >= 0.8 else 'yellow'}]{summary['target_achievement']:.1%}[/]",
|
313
|
+
)
|
314
|
+
|
315
|
+
table.add_row("Performance Target", f"{self.config.target_duration:.1f}s", f"[dim]{self.config.description}[/]")
|
316
|
+
|
317
|
+
console.print(table)
|
318
|
+
|
319
|
+
@staticmethod
|
320
|
+
def create_enterprise_performance_dashboard(benchmarks: List["PerformanceBenchmark"]) -> None:
|
321
|
+
"""Create enterprise-wide performance dashboard."""
|
322
|
+
console.print("\n")
|
323
|
+
console.print("[bold cyan]📊 Enterprise Performance Dashboard[/]")
|
324
|
+
console.print("=" * 80)
|
325
|
+
|
326
|
+
# Create summary table
|
327
|
+
table = create_table(
|
328
|
+
title="Module Performance Overview",
|
329
|
+
columns=[
|
330
|
+
{"name": "Module", "style": "cyan", "justify": "left"},
|
331
|
+
{"name": "Ops", "style": "white", "justify": "center"},
|
332
|
+
{"name": "Success", "style": "white", "justify": "center"},
|
333
|
+
{"name": "Avg Duration", "style": "white", "justify": "right"},
|
334
|
+
{"name": "Target", "style": "white", "justify": "right"},
|
335
|
+
{"name": "Status", "style": "white", "justify": "center"},
|
336
|
+
],
|
337
|
+
)
|
338
|
+
|
339
|
+
for benchmark in benchmarks:
|
340
|
+
summary = benchmark.get_module_performance_summary()
|
341
|
+
status_indicator = {
|
342
|
+
"healthy": f"[green]{STATUS_INDICATORS['success']}[/]",
|
343
|
+
"warning": f"[yellow]{STATUS_INDICATORS['warning']}[/]",
|
344
|
+
"critical": f"[red]{STATUS_INDICATORS['error']}[/]",
|
345
|
+
"no_data": f"[dim]{STATUS_INDICATORS['pending']}[/]",
|
346
|
+
}.get(summary["status"], STATUS_INDICATORS["info"])
|
347
|
+
|
348
|
+
table.add_row(
|
349
|
+
summary["module"].title(),
|
350
|
+
str(summary["total_operations"]),
|
351
|
+
f"{summary['success_rate']:.0%}",
|
352
|
+
f"{summary['average_duration']:.1f}s",
|
353
|
+
f"{summary['target_duration']:.1f}s",
|
354
|
+
status_indicator,
|
355
|
+
)
|
356
|
+
|
357
|
+
console.print(table)
|
358
|
+
console.print("\n")
|
359
|
+
|
360
|
+
|
361
|
+
# Global performance tracking
|
362
|
+
_module_benchmarks: Dict[str, PerformanceBenchmark] = {}
|
363
|
+
|
364
|
+
|
365
|
+
def get_performance_benchmark(module_name: str) -> PerformanceBenchmark:
|
366
|
+
"""Get or create performance benchmark for module."""
|
367
|
+
if module_name not in _module_benchmarks:
|
368
|
+
_module_benchmarks[module_name] = PerformanceBenchmark(module_name)
|
369
|
+
return _module_benchmarks[module_name]
|
370
|
+
|
371
|
+
|
372
|
+
def create_enterprise_performance_report() -> None:
|
373
|
+
"""Create enterprise-wide performance report."""
|
374
|
+
if _module_benchmarks:
|
375
|
+
PerformanceBenchmark.create_enterprise_performance_dashboard(list(_module_benchmarks.values()))
|
376
|
+
else:
|
377
|
+
console.print("[yellow]No performance data available yet[/]")
|
378
|
+
|
379
|
+
|
380
|
+
# Export all public functions and classes
|
381
|
+
__all__ = [
|
382
|
+
"PerformanceMetrics",
|
383
|
+
"ModulePerformanceConfig",
|
384
|
+
"PerformanceBenchmark",
|
385
|
+
"get_performance_benchmark",
|
386
|
+
"create_enterprise_performance_report",
|
387
|
+
]
|
@@ -0,0 +1,216 @@
|
|
1
|
+
#!/usr/bin/env python3
|
2
|
+
"""
|
3
|
+
Profile Management Utilities for CloudOps Runbooks Platform
|
4
|
+
|
5
|
+
This module provides centralized AWS profile management with enterprise-grade
|
6
|
+
three-tier priority system extracted from proven FinOps success patterns.
|
7
|
+
|
8
|
+
Features:
|
9
|
+
- Three-tier priority: User > Environment > Default
|
10
|
+
- Multi-profile enterprise architecture support
|
11
|
+
- Consistent session creation across all modules
|
12
|
+
- Rich CLI integration for user feedback
|
13
|
+
- Profile validation and error handling
|
14
|
+
|
15
|
+
Author: CloudOps Runbooks Team
|
16
|
+
Version: 0.9.0
|
17
|
+
"""
|
18
|
+
|
19
|
+
import os
|
20
|
+
from typing import Dict, Optional
|
21
|
+
|
22
|
+
import boto3
|
23
|
+
|
24
|
+
from runbooks.common.rich_utils import console
|
25
|
+
|
26
|
+
|
27
|
+
def get_profile_for_operation(operation_type: str, user_specified_profile: Optional[str] = None) -> str:
|
28
|
+
"""
|
29
|
+
Get the appropriate AWS profile based on operation type using proven three-tier priority system.
|
30
|
+
|
31
|
+
PRIORITY ORDER (Enterprise Success Pattern):
|
32
|
+
1. User-specified profile (--profile parameter) - HIGHEST PRIORITY
|
33
|
+
2. Environment variables for specialized operations - FALLBACK ONLY
|
34
|
+
3. Default profile - LAST RESORT
|
35
|
+
|
36
|
+
This pattern extracted from FinOps module achieving 99.9996% accuracy and 280% ROI.
|
37
|
+
|
38
|
+
Args:
|
39
|
+
operation_type: Type of operation ('billing', 'management', 'operational')
|
40
|
+
user_specified_profile: Profile specified by user via --profile parameter
|
41
|
+
|
42
|
+
Returns:
|
43
|
+
str: Profile name to use for the operation
|
44
|
+
|
45
|
+
Raises:
|
46
|
+
SystemExit: If user-specified profile not found in AWS config
|
47
|
+
"""
|
48
|
+
available_profiles = boto3.Session().available_profiles
|
49
|
+
|
50
|
+
# PRIORITY 1: User-specified profile ALWAYS takes precedence
|
51
|
+
if user_specified_profile and user_specified_profile != "default":
|
52
|
+
if user_specified_profile in available_profiles:
|
53
|
+
console.log(f"[green]Using user-specified profile for {operation_type}: {user_specified_profile}[/]")
|
54
|
+
return user_specified_profile
|
55
|
+
else:
|
56
|
+
console.log(f"[red]Error: User-specified profile '{user_specified_profile}' not found in AWS config[/]")
|
57
|
+
# Don't fall back - user explicitly chose this profile
|
58
|
+
raise SystemExit(1)
|
59
|
+
|
60
|
+
# PRIORITY 2: Environment variables (only when no user input)
|
61
|
+
profile_map = {
|
62
|
+
"billing": os.getenv("BILLING_PROFILE"),
|
63
|
+
"management": os.getenv("MANAGEMENT_PROFILE"),
|
64
|
+
"operational": os.getenv("CENTRALISED_OPS_PROFILE"),
|
65
|
+
}
|
66
|
+
|
67
|
+
env_profile = profile_map.get(operation_type)
|
68
|
+
if env_profile and env_profile in available_profiles:
|
69
|
+
console.log(f"[dim cyan]Using {operation_type} profile from environment: {env_profile}[/]")
|
70
|
+
return env_profile
|
71
|
+
|
72
|
+
# PRIORITY 3: Default profile (last resort)
|
73
|
+
console.log(f"[yellow]No {operation_type} profile found, using default: {user_specified_profile or 'default'}[/]")
|
74
|
+
return user_specified_profile or "default"
|
75
|
+
|
76
|
+
|
77
|
+
def resolve_profile_for_operation_silent(operation_type: str, user_specified_profile: Optional[str] = None) -> str:
|
78
|
+
"""
|
79
|
+
Resolve AWS profile for operation type without logging (for display purposes).
|
80
|
+
Uses the same logic as get_profile_for_operation but without console output.
|
81
|
+
|
82
|
+
Args:
|
83
|
+
operation_type: Type of operation ('billing', 'management', 'operational')
|
84
|
+
user_specified_profile: Profile specified by user via --profile parameter
|
85
|
+
|
86
|
+
Returns:
|
87
|
+
str: Profile name to use for the operation
|
88
|
+
|
89
|
+
Raises:
|
90
|
+
SystemExit: If user-specified profile not found in AWS config
|
91
|
+
"""
|
92
|
+
available_profiles = boto3.Session().available_profiles
|
93
|
+
|
94
|
+
# PRIORITY 1: User-specified profile ALWAYS takes precedence
|
95
|
+
if user_specified_profile and user_specified_profile != "default":
|
96
|
+
if user_specified_profile in available_profiles:
|
97
|
+
return user_specified_profile
|
98
|
+
else:
|
99
|
+
# Don't fall back - user explicitly chose this profile
|
100
|
+
raise SystemExit(1)
|
101
|
+
|
102
|
+
# PRIORITY 2: Environment variables (only when no user input)
|
103
|
+
profile_map = {
|
104
|
+
"billing": os.getenv("BILLING_PROFILE"),
|
105
|
+
"management": os.getenv("MANAGEMENT_PROFILE"),
|
106
|
+
"operational": os.getenv("CENTRALISED_OPS_PROFILE"),
|
107
|
+
}
|
108
|
+
|
109
|
+
env_profile = profile_map.get(operation_type)
|
110
|
+
if env_profile and env_profile in available_profiles:
|
111
|
+
return env_profile
|
112
|
+
|
113
|
+
# PRIORITY 3: Default profile (last resort)
|
114
|
+
return user_specified_profile or "default"
|
115
|
+
|
116
|
+
|
117
|
+
def create_cost_session(profile: Optional[str] = None) -> boto3.Session:
|
118
|
+
"""
|
119
|
+
Create a boto3 session specifically for cost operations.
|
120
|
+
User-specified profile takes priority over BILLING_PROFILE environment variable.
|
121
|
+
|
122
|
+
Args:
|
123
|
+
profile: User-specified profile (from --profile parameter)
|
124
|
+
|
125
|
+
Returns:
|
126
|
+
boto3.Session: Session configured for cost operations
|
127
|
+
"""
|
128
|
+
cost_profile = get_profile_for_operation("billing", profile)
|
129
|
+
return boto3.Session(profile_name=cost_profile)
|
130
|
+
|
131
|
+
|
132
|
+
def create_management_session(profile: Optional[str] = None) -> boto3.Session:
|
133
|
+
"""
|
134
|
+
Create a boto3 session specifically for management operations.
|
135
|
+
User-specified profile takes priority over MANAGEMENT_PROFILE environment variable.
|
136
|
+
|
137
|
+
Args:
|
138
|
+
profile: User-specified profile (from --profile parameter)
|
139
|
+
|
140
|
+
Returns:
|
141
|
+
boto3.Session: Session configured for management operations
|
142
|
+
"""
|
143
|
+
mgmt_profile = get_profile_for_operation("management", profile)
|
144
|
+
return boto3.Session(profile_name=mgmt_profile)
|
145
|
+
|
146
|
+
|
147
|
+
def create_operational_session(profile: Optional[str] = None) -> boto3.Session:
|
148
|
+
"""
|
149
|
+
Create a boto3 session specifically for operational tasks.
|
150
|
+
User-specified profile takes priority over CENTRALISED_OPS_PROFILE environment variable.
|
151
|
+
|
152
|
+
Args:
|
153
|
+
profile: User-specified profile (from --profile parameter)
|
154
|
+
|
155
|
+
Returns:
|
156
|
+
boto3.Session: Session configured for operational tasks
|
157
|
+
"""
|
158
|
+
ops_profile = get_profile_for_operation("operational", profile)
|
159
|
+
return boto3.Session(profile_name=ops_profile)
|
160
|
+
|
161
|
+
|
162
|
+
def get_enterprise_profile_mapping() -> Dict[str, Optional[str]]:
|
163
|
+
"""
|
164
|
+
Get current enterprise profile mapping from environment variables.
|
165
|
+
|
166
|
+
Returns:
|
167
|
+
Dict mapping operation types to their environment profile values
|
168
|
+
"""
|
169
|
+
return {
|
170
|
+
"billing": os.getenv("BILLING_PROFILE"),
|
171
|
+
"management": os.getenv("MANAGEMENT_PROFILE"),
|
172
|
+
"operational": os.getenv("CENTRALISED_OPS_PROFILE"),
|
173
|
+
}
|
174
|
+
|
175
|
+
|
176
|
+
def validate_profile_access(profile_name: str, operation_type: str = "general") -> bool:
|
177
|
+
"""
|
178
|
+
Validate that profile exists and is accessible.
|
179
|
+
|
180
|
+
Args:
|
181
|
+
profile_name: AWS profile name to validate
|
182
|
+
operation_type: Type of operation for context
|
183
|
+
|
184
|
+
Returns:
|
185
|
+
bool: True if profile is valid and accessible
|
186
|
+
"""
|
187
|
+
try:
|
188
|
+
available_profiles = boto3.Session().available_profiles
|
189
|
+
if profile_name not in available_profiles:
|
190
|
+
console.log(f"[red]Profile '{profile_name}' not found in AWS config[/]")
|
191
|
+
return False
|
192
|
+
|
193
|
+
# Test session creation
|
194
|
+
session = boto3.Session(profile_name=profile_name)
|
195
|
+
sts_client = session.client("sts")
|
196
|
+
identity = sts_client.get_caller_identity()
|
197
|
+
|
198
|
+
console.log(f"[green]Profile '{profile_name}' validated for {operation_type} operations[/]")
|
199
|
+
console.log(f"[dim]Account: {identity.get('Account')}, User: {identity.get('UserId', 'Unknown')}[/]")
|
200
|
+
return True
|
201
|
+
|
202
|
+
except Exception as e:
|
203
|
+
console.log(f"[red]Profile '{profile_name}' validation failed: {str(e)}[/]")
|
204
|
+
return False
|
205
|
+
|
206
|
+
|
207
|
+
# Export all public functions
|
208
|
+
__all__ = [
|
209
|
+
"get_profile_for_operation",
|
210
|
+
"resolve_profile_for_operation_silent",
|
211
|
+
"create_cost_session",
|
212
|
+
"create_management_session",
|
213
|
+
"create_operational_session",
|
214
|
+
"get_enterprise_profile_mapping",
|
215
|
+
"validate_profile_access",
|
216
|
+
]
|