runbooks 0.9.0__py3-none-any.whl → 0.9.2__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/assessment/compliance.py +4 -1
- runbooks/cloudops/__init__.py +123 -0
- runbooks/cloudops/base.py +385 -0
- runbooks/cloudops/cost_optimizer.py +811 -0
- runbooks/cloudops/infrastructure_optimizer.py +29 -0
- runbooks/cloudops/interfaces.py +828 -0
- runbooks/cloudops/lifecycle_manager.py +29 -0
- runbooks/cloudops/mcp_cost_validation.py +678 -0
- runbooks/cloudops/models.py +251 -0
- runbooks/cloudops/monitoring_automation.py +29 -0
- runbooks/cloudops/notebook_framework.py +676 -0
- runbooks/cloudops/security_enforcer.py +449 -0
- runbooks/common/mcp_cost_explorer_integration.py +900 -0
- runbooks/common/mcp_integration.py +19 -10
- runbooks/common/rich_utils.py +1 -1
- runbooks/finops/README.md +31 -0
- runbooks/finops/cost_optimizer.py +1340 -0
- runbooks/finops/finops_dashboard.py +211 -5
- runbooks/finops/schemas.py +589 -0
- runbooks/inventory/runbooks.inventory.organizations_discovery.log +0 -0
- runbooks/inventory/runbooks.security.security_export.log +0 -0
- runbooks/main.py +525 -0
- runbooks/operate/ec2_operations.py +428 -0
- runbooks/operate/iam_operations.py +598 -3
- runbooks/operate/rds_operations.py +508 -0
- runbooks/operate/s3_operations.py +508 -0
- runbooks/remediation/base.py +5 -3
- runbooks/security/__init__.py +101 -0
- runbooks/security/cloudops_automation_security_validator.py +1164 -0
- runbooks/security/compliance_automation_engine.py +4 -4
- runbooks/security/enterprise_security_framework.py +4 -5
- runbooks/security/executive_security_dashboard.py +1247 -0
- runbooks/security/multi_account_security_controls.py +2254 -0
- runbooks/security/real_time_security_monitor.py +1196 -0
- runbooks/security/security_baseline_tester.py +3 -3
- runbooks/sre/production_monitoring_framework.py +584 -0
- runbooks/validation/mcp_validator.py +29 -15
- runbooks/vpc/networking_wrapper.py +6 -3
- runbooks-0.9.2.dist-info/METADATA +525 -0
- {runbooks-0.9.0.dist-info → runbooks-0.9.2.dist-info}/RECORD +45 -23
- runbooks-0.9.0.dist-info/METADATA +0 -718
- {runbooks-0.9.0.dist-info → runbooks-0.9.2.dist-info}/WHEEL +0 -0
- {runbooks-0.9.0.dist-info → runbooks-0.9.2.dist-info}/entry_points.txt +0 -0
- {runbooks-0.9.0.dist-info → runbooks-0.9.2.dist-info}/licenses/LICENSE +0 -0
- {runbooks-0.9.0.dist-info → runbooks-0.9.2.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,676 @@
|
|
1
|
+
#!/usr/bin/env python3
|
2
|
+
"""
|
3
|
+
CloudOps Notebook Framework - Enterprise Consolidation Infrastructure
|
4
|
+
|
5
|
+
Provides reusable components for consolidating 64 individual notebooks into
|
6
|
+
12-15 production scenarios with enterprise-grade functionality.
|
7
|
+
|
8
|
+
Strategic Alignment:
|
9
|
+
- Follows Rich CLI standards from rich_utils.py
|
10
|
+
- Handles authentication failures gracefully (no hardcoding/assumptions)
|
11
|
+
- Dual-purpose interface: executive summary + technical details
|
12
|
+
- Type-safe validation with Pydantic v2
|
13
|
+
|
14
|
+
Key Features:
|
15
|
+
- Authentication flow management with comprehensive error handling
|
16
|
+
- Executive and technical reporting modes
|
17
|
+
- Multi-scenario consolidation support
|
18
|
+
- MCP integration readiness
|
19
|
+
- Performance monitoring and benchmarking
|
20
|
+
"""
|
21
|
+
|
22
|
+
import asyncio
|
23
|
+
import json
|
24
|
+
import time
|
25
|
+
from datetime import datetime
|
26
|
+
from typing import Any, Dict, List, Optional, Union, Callable, Tuple
|
27
|
+
from pathlib import Path
|
28
|
+
from dataclasses import dataclass
|
29
|
+
from enum import Enum
|
30
|
+
|
31
|
+
import boto3
|
32
|
+
import pandas as pd
|
33
|
+
from botocore.exceptions import (
|
34
|
+
ClientError, NoCredentialsError, ProfileNotFound,
|
35
|
+
TokenRetrievalError, UnauthorizedSSOTokenError
|
36
|
+
)
|
37
|
+
from pydantic import BaseModel, Field, ValidationError
|
38
|
+
|
39
|
+
from runbooks.common.rich_utils import (
|
40
|
+
console, print_header, print_success, print_error, print_warning, print_info,
|
41
|
+
create_table, create_progress_bar, format_cost, create_panel, STATUS_INDICATORS,
|
42
|
+
print_json, create_columns, confirm_action
|
43
|
+
)
|
44
|
+
from runbooks.common.profile_utils import (
|
45
|
+
get_profile_for_operation, create_cost_session, create_management_session,
|
46
|
+
create_operational_session
|
47
|
+
)
|
48
|
+
from .models import (
|
49
|
+
BusinessScenario, ExecutionMode, RiskLevel, ProfileConfiguration,
|
50
|
+
CloudOpsExecutionResult, BusinessMetrics, ResourceImpact, CostOptimizationResult
|
51
|
+
)
|
52
|
+
from .base import CloudOpsBase, PerformanceBenchmark
|
53
|
+
|
54
|
+
|
55
|
+
class NotebookMode(str, Enum):
|
56
|
+
"""Execution modes for notebook interface."""
|
57
|
+
EXECUTIVE = "executive" # Executive summary for business stakeholders
|
58
|
+
TECHNICAL = "technical" # Technical details for engineering teams
|
59
|
+
COMPREHENSIVE = "comprehensive" # Both executive and technical views
|
60
|
+
|
61
|
+
|
62
|
+
class AuthenticationStatus(str, Enum):
|
63
|
+
"""AWS authentication status tracking."""
|
64
|
+
SUCCESS = "success"
|
65
|
+
EXPIRED_TOKEN = "expired_token"
|
66
|
+
INVALID_PROFILE = "invalid_profile"
|
67
|
+
NO_CREDENTIALS = "no_credentials"
|
68
|
+
PERMISSION_DENIED = "permission_denied"
|
69
|
+
UNKNOWN_ERROR = "unknown_error"
|
70
|
+
|
71
|
+
|
72
|
+
@dataclass
|
73
|
+
class AuthenticationResult:
|
74
|
+
"""Results of AWS authentication validation."""
|
75
|
+
status: AuthenticationStatus
|
76
|
+
profile_name: str
|
77
|
+
account_id: Optional[str] = None
|
78
|
+
error_message: Optional[str] = None
|
79
|
+
remediation_steps: List[str] = None
|
80
|
+
|
81
|
+
def __post_init__(self):
|
82
|
+
if self.remediation_steps is None:
|
83
|
+
self.remediation_steps = []
|
84
|
+
|
85
|
+
|
86
|
+
class ScenarioMetadata(BaseModel):
|
87
|
+
"""Metadata for consolidated notebook scenarios."""
|
88
|
+
scenario_id: str = Field(description="Unique scenario identifier")
|
89
|
+
scenario_name: str = Field(description="Human-readable scenario name")
|
90
|
+
scenario_type: BusinessScenario = Field(description="Business scenario category")
|
91
|
+
consolidated_notebooks: List[str] = Field(description="List of individual notebooks consolidated")
|
92
|
+
|
93
|
+
# Executive Information
|
94
|
+
business_objective: str = Field(description="High-level business objective")
|
95
|
+
expected_outcomes: List[str] = Field(description="Expected business outcomes")
|
96
|
+
stakeholders: List[str] = Field(description="Key stakeholders", default=[])
|
97
|
+
|
98
|
+
# Technical Information
|
99
|
+
aws_services: List[str] = Field(description="AWS services utilized")
|
100
|
+
estimated_execution_time: int = Field(description="Estimated execution time in minutes")
|
101
|
+
prerequisites: List[str] = Field(description="Technical prerequisites", default=[])
|
102
|
+
|
103
|
+
|
104
|
+
class NotebookFramework(CloudOpsBase):
|
105
|
+
"""
|
106
|
+
Enterprise notebook framework for consolidated CloudOps scenarios.
|
107
|
+
|
108
|
+
Provides comprehensive infrastructure for transforming individual notebooks
|
109
|
+
into enterprise-grade consolidated scenarios with dual executive/technical interfaces.
|
110
|
+
"""
|
111
|
+
|
112
|
+
def __init__(
|
113
|
+
self,
|
114
|
+
profile: str = "default",
|
115
|
+
mode: NotebookMode = NotebookMode.COMPREHENSIVE,
|
116
|
+
dry_run: bool = True,
|
117
|
+
validate_auth: bool = True
|
118
|
+
):
|
119
|
+
"""
|
120
|
+
Initialize notebook framework.
|
121
|
+
|
122
|
+
Args:
|
123
|
+
profile: AWS profile for authentication
|
124
|
+
mode: Notebook execution mode (executive/technical/comprehensive)
|
125
|
+
dry_run: Enable dry-run mode for safe analysis
|
126
|
+
validate_auth: Validate authentication before proceeding
|
127
|
+
"""
|
128
|
+
self.mode = mode
|
129
|
+
self.validate_auth = validate_auth
|
130
|
+
self.auth_status: Optional[AuthenticationResult] = None
|
131
|
+
|
132
|
+
# Initialize base class (handles AWS session setup)
|
133
|
+
# Note: This may raise exceptions for authentication issues
|
134
|
+
try:
|
135
|
+
super().__init__(profile=profile, dry_run=dry_run)
|
136
|
+
if self.validate_auth:
|
137
|
+
self.auth_status = self._validate_authentication()
|
138
|
+
except Exception as e:
|
139
|
+
# Handle authentication failures gracefully
|
140
|
+
self.auth_status = AuthenticationResult(
|
141
|
+
status=AuthenticationStatus.UNKNOWN_ERROR,
|
142
|
+
profile_name=profile,
|
143
|
+
error_message=str(e),
|
144
|
+
remediation_steps=self._get_generic_remediation_steps()
|
145
|
+
)
|
146
|
+
self.session = None # Ensure session is None on failure
|
147
|
+
|
148
|
+
def _validate_authentication(self) -> AuthenticationResult:
|
149
|
+
"""
|
150
|
+
Comprehensive AWS authentication validation with detailed error handling.
|
151
|
+
|
152
|
+
Returns:
|
153
|
+
AuthenticationResult with status and remediation guidance
|
154
|
+
"""
|
155
|
+
try:
|
156
|
+
if not self.session:
|
157
|
+
return AuthenticationResult(
|
158
|
+
status=AuthenticationStatus.NO_CREDENTIALS,
|
159
|
+
profile_name=self.profile,
|
160
|
+
error_message="No AWS session available",
|
161
|
+
remediation_steps=self._get_generic_remediation_steps()
|
162
|
+
)
|
163
|
+
|
164
|
+
# Test authentication by calling STS
|
165
|
+
sts_client = self.session.client('sts')
|
166
|
+
identity = sts_client.get_caller_identity()
|
167
|
+
|
168
|
+
account_id = identity.get('Account')
|
169
|
+
user_arn = identity.get('Arn')
|
170
|
+
|
171
|
+
print_success(f"Authentication successful for profile: {self.profile}")
|
172
|
+
print_info(f"Account: {account_id}, Identity: {user_arn}")
|
173
|
+
|
174
|
+
return AuthenticationResult(
|
175
|
+
status=AuthenticationStatus.SUCCESS,
|
176
|
+
profile_name=self.profile,
|
177
|
+
account_id=account_id
|
178
|
+
)
|
179
|
+
|
180
|
+
except UnauthorizedSSOTokenError:
|
181
|
+
return AuthenticationResult(
|
182
|
+
status=AuthenticationStatus.EXPIRED_TOKEN,
|
183
|
+
profile_name=self.profile,
|
184
|
+
error_message="AWS SSO token has expired",
|
185
|
+
remediation_steps=[
|
186
|
+
"Run: aws sso login",
|
187
|
+
"Ensure your AWS SSO session is active",
|
188
|
+
f"Verify profile '{self.profile}' is configured for SSO"
|
189
|
+
]
|
190
|
+
)
|
191
|
+
|
192
|
+
except TokenRetrievalError as e:
|
193
|
+
return AuthenticationResult(
|
194
|
+
status=AuthenticationStatus.EXPIRED_TOKEN,
|
195
|
+
profile_name=self.profile,
|
196
|
+
error_message=f"Token retrieval failed: {str(e)}",
|
197
|
+
remediation_steps=[
|
198
|
+
"Run: aws sso login",
|
199
|
+
"Check your internet connection",
|
200
|
+
"Verify AWS SSO configuration"
|
201
|
+
]
|
202
|
+
)
|
203
|
+
|
204
|
+
except ProfileNotFound:
|
205
|
+
return AuthenticationResult(
|
206
|
+
status=AuthenticationStatus.INVALID_PROFILE,
|
207
|
+
profile_name=self.profile,
|
208
|
+
error_message=f"AWS profile '{self.profile}' not found",
|
209
|
+
remediation_steps=[
|
210
|
+
f"Check if profile '{self.profile}' exists in ~/.aws/config",
|
211
|
+
"Run: aws configure list-profiles",
|
212
|
+
"Configure the profile using: aws configure sso"
|
213
|
+
]
|
214
|
+
)
|
215
|
+
|
216
|
+
except NoCredentialsError:
|
217
|
+
return AuthenticationResult(
|
218
|
+
status=AuthenticationStatus.NO_CREDENTIALS,
|
219
|
+
profile_name=self.profile,
|
220
|
+
error_message="No valid AWS credentials found",
|
221
|
+
remediation_steps=[
|
222
|
+
"Configure AWS credentials using: aws configure",
|
223
|
+
"Or set up SSO using: aws configure sso",
|
224
|
+
"Verify AWS credentials are properly configured"
|
225
|
+
]
|
226
|
+
)
|
227
|
+
|
228
|
+
except ClientError as e:
|
229
|
+
error_code = e.response.get('Error', {}).get('Code', 'Unknown')
|
230
|
+
|
231
|
+
if error_code in ['AccessDenied', 'UnauthorizedOperation']:
|
232
|
+
return AuthenticationResult(
|
233
|
+
status=AuthenticationStatus.PERMISSION_DENIED,
|
234
|
+
profile_name=self.profile,
|
235
|
+
error_message=f"Permission denied: {str(e)}",
|
236
|
+
remediation_steps=[
|
237
|
+
"Verify your AWS profile has sufficient permissions",
|
238
|
+
"Contact your AWS administrator for access",
|
239
|
+
"Check IAM policies attached to your role/user"
|
240
|
+
]
|
241
|
+
)
|
242
|
+
else:
|
243
|
+
return AuthenticationResult(
|
244
|
+
status=AuthenticationStatus.UNKNOWN_ERROR,
|
245
|
+
profile_name=self.profile,
|
246
|
+
error_message=f"AWS API error: {str(e)}",
|
247
|
+
remediation_steps=self._get_generic_remediation_steps()
|
248
|
+
)
|
249
|
+
|
250
|
+
except Exception as e:
|
251
|
+
return AuthenticationResult(
|
252
|
+
status=AuthenticationStatus.UNKNOWN_ERROR,
|
253
|
+
profile_name=self.profile,
|
254
|
+
error_message=f"Unexpected error: {str(e)}",
|
255
|
+
remediation_steps=self._get_generic_remediation_steps()
|
256
|
+
)
|
257
|
+
|
258
|
+
def _get_generic_remediation_steps(self) -> List[str]:
|
259
|
+
"""Get generic remediation steps for authentication issues."""
|
260
|
+
return [
|
261
|
+
"Check AWS profile configuration: aws configure list-profiles",
|
262
|
+
"Verify credentials: aws sts get-caller-identity",
|
263
|
+
"For SSO profiles, login again: aws sso login",
|
264
|
+
"Contact your AWS administrator if issues persist"
|
265
|
+
]
|
266
|
+
|
267
|
+
def display_authentication_status(self) -> None:
|
268
|
+
"""Display authentication status with Rich CLI formatting."""
|
269
|
+
if not self.auth_status:
|
270
|
+
print_warning("Authentication status not available")
|
271
|
+
return
|
272
|
+
|
273
|
+
status_colors = {
|
274
|
+
AuthenticationStatus.SUCCESS: "green",
|
275
|
+
AuthenticationStatus.EXPIRED_TOKEN: "yellow",
|
276
|
+
AuthenticationStatus.INVALID_PROFILE: "red",
|
277
|
+
AuthenticationStatus.NO_CREDENTIALS: "red",
|
278
|
+
AuthenticationStatus.PERMISSION_DENIED: "red",
|
279
|
+
AuthenticationStatus.UNKNOWN_ERROR: "red"
|
280
|
+
}
|
281
|
+
|
282
|
+
status_icons = {
|
283
|
+
AuthenticationStatus.SUCCESS: "✅",
|
284
|
+
AuthenticationStatus.EXPIRED_TOKEN: "⚠️",
|
285
|
+
AuthenticationStatus.INVALID_PROFILE: "❌",
|
286
|
+
AuthenticationStatus.NO_CREDENTIALS: "❌",
|
287
|
+
AuthenticationStatus.PERMISSION_DENIED: "🔒",
|
288
|
+
AuthenticationStatus.UNKNOWN_ERROR: "❓"
|
289
|
+
}
|
290
|
+
|
291
|
+
status_color = status_colors.get(self.auth_status.status, "white")
|
292
|
+
status_icon = status_icons.get(self.auth_status.status, "?")
|
293
|
+
|
294
|
+
# Authentication Status Panel
|
295
|
+
status_content = (
|
296
|
+
f"Profile: {self.auth_status.profile_name}\n"
|
297
|
+
f"Status: {status_icon} {self.auth_status.status.value.replace('_', ' ').title()}"
|
298
|
+
)
|
299
|
+
|
300
|
+
if self.auth_status.account_id:
|
301
|
+
status_content += f"\nAccount ID: {self.auth_status.account_id}"
|
302
|
+
|
303
|
+
if self.auth_status.error_message:
|
304
|
+
status_content += f"\nError: {self.auth_status.error_message}"
|
305
|
+
|
306
|
+
auth_panel = create_panel(
|
307
|
+
status_content,
|
308
|
+
title="AWS Authentication Status",
|
309
|
+
border_style=status_color
|
310
|
+
)
|
311
|
+
console.print(auth_panel)
|
312
|
+
|
313
|
+
# Remediation Steps (if authentication failed)
|
314
|
+
if (self.auth_status.status != AuthenticationStatus.SUCCESS and
|
315
|
+
self.auth_status.remediation_steps):
|
316
|
+
|
317
|
+
remediation_text = "\n".join([f"• {step}" for step in self.auth_status.remediation_steps])
|
318
|
+
remediation_panel = create_panel(
|
319
|
+
remediation_text,
|
320
|
+
title="Remediation Steps",
|
321
|
+
border_style="blue"
|
322
|
+
)
|
323
|
+
console.print(remediation_panel)
|
324
|
+
|
325
|
+
def is_authenticated(self) -> bool:
|
326
|
+
"""Check if AWS authentication is successful."""
|
327
|
+
return (self.auth_status and
|
328
|
+
self.auth_status.status == AuthenticationStatus.SUCCESS and
|
329
|
+
self.session is not None)
|
330
|
+
|
331
|
+
def create_scenario_header(
|
332
|
+
self,
|
333
|
+
metadata: ScenarioMetadata,
|
334
|
+
show_consolidated_info: bool = True
|
335
|
+
) -> None:
|
336
|
+
"""
|
337
|
+
Create rich scenario header for consolidated notebooks.
|
338
|
+
|
339
|
+
Args:
|
340
|
+
metadata: Scenario metadata with business and technical information
|
341
|
+
show_consolidated_info: Show information about consolidated notebooks
|
342
|
+
"""
|
343
|
+
# Main scenario header
|
344
|
+
print_header(f"CloudOps Scenario: {metadata.scenario_name}", "v0.9.1")
|
345
|
+
|
346
|
+
# Executive Summary (always shown)
|
347
|
+
if self.mode in [NotebookMode.EXECUTIVE, NotebookMode.COMPREHENSIVE]:
|
348
|
+
exec_content = (
|
349
|
+
f"🎯 Business Objective: {metadata.business_objective}\n"
|
350
|
+
f"📊 Scenario Type: {metadata.scenario_type.value.replace('_', ' ').title()}\n"
|
351
|
+
f"⏱️ Estimated Time: {metadata.estimated_execution_time} minutes\n"
|
352
|
+
f"🛡️ Execution Mode: {'🔍 Analysis Only' if self.dry_run else '⚡ Live Execution'}"
|
353
|
+
)
|
354
|
+
|
355
|
+
if metadata.expected_outcomes:
|
356
|
+
exec_content += f"\n\n📈 Expected Outcomes:\n"
|
357
|
+
exec_content += "\n".join([f"• {outcome}" for outcome in metadata.expected_outcomes])
|
358
|
+
|
359
|
+
exec_panel = create_panel(
|
360
|
+
exec_content,
|
361
|
+
title="Executive Summary",
|
362
|
+
border_style="cyan"
|
363
|
+
)
|
364
|
+
console.print(exec_panel)
|
365
|
+
|
366
|
+
# Technical Details (technical/comprehensive modes)
|
367
|
+
if self.mode in [NotebookMode.TECHNICAL, NotebookMode.COMPREHENSIVE]:
|
368
|
+
tech_content = (
|
369
|
+
f"🔧 AWS Services: {', '.join(metadata.aws_services)}\n"
|
370
|
+
f"📝 Scenario ID: {metadata.scenario_id}"
|
371
|
+
)
|
372
|
+
|
373
|
+
if metadata.prerequisites:
|
374
|
+
tech_content += f"\n\n✅ Prerequisites:\n"
|
375
|
+
tech_content += "\n".join([f"• {prereq}" for prereq in metadata.prerequisites])
|
376
|
+
|
377
|
+
if show_consolidated_info and metadata.consolidated_notebooks:
|
378
|
+
tech_content += f"\n\n📚 Consolidated Notebooks ({len(metadata.consolidated_notebooks)}):\n"
|
379
|
+
tech_content += "\n".join([f"• {nb}" for nb in metadata.consolidated_notebooks])
|
380
|
+
|
381
|
+
tech_panel = create_panel(
|
382
|
+
tech_content,
|
383
|
+
title="Technical Information",
|
384
|
+
border_style="blue"
|
385
|
+
)
|
386
|
+
console.print(tech_panel)
|
387
|
+
|
388
|
+
def create_results_summary(
|
389
|
+
self,
|
390
|
+
result: CloudOpsExecutionResult,
|
391
|
+
show_detailed_metrics: bool = None
|
392
|
+
) -> None:
|
393
|
+
"""
|
394
|
+
Create comprehensive results summary with mode-appropriate detail level.
|
395
|
+
|
396
|
+
Args:
|
397
|
+
result: CloudOps execution result
|
398
|
+
show_detailed_metrics: Override detail level (None = use mode default)
|
399
|
+
"""
|
400
|
+
if show_detailed_metrics is None:
|
401
|
+
show_detailed_metrics = self.mode in [NotebookMode.TECHNICAL, NotebookMode.COMPREHENSIVE]
|
402
|
+
|
403
|
+
# Executive Summary (always shown)
|
404
|
+
exec_summary = (
|
405
|
+
f"📊 Scenario: {result.scenario_name}\n"
|
406
|
+
f"✅ Success: {'Yes' if result.success else 'No'}\n"
|
407
|
+
f"🔍 Resources Analyzed: {result.resources_analyzed:,}\n"
|
408
|
+
f"🎯 Resources Impacted: {len(result.resources_impacted):,}\n"
|
409
|
+
f"💰 Monthly Savings: {format_cost(result.business_metrics.total_monthly_savings)}\n"
|
410
|
+
f"⏱️ Execution Time: {result.execution_time:.1f}s"
|
411
|
+
)
|
412
|
+
|
413
|
+
if result.business_metrics.roi_percentage:
|
414
|
+
exec_summary += f"\n📈 ROI: {result.business_metrics.roi_percentage:.1f}%"
|
415
|
+
|
416
|
+
if not result.success and result.error_message:
|
417
|
+
exec_summary += f"\n❌ Error: {result.error_message}"
|
418
|
+
|
419
|
+
exec_panel = create_panel(
|
420
|
+
exec_summary,
|
421
|
+
title="Execution Results Summary",
|
422
|
+
border_style="green" if result.success else "red"
|
423
|
+
)
|
424
|
+
console.print(exec_panel)
|
425
|
+
|
426
|
+
# Detailed Metrics (technical/comprehensive modes)
|
427
|
+
if show_detailed_metrics and result.business_metrics:
|
428
|
+
metrics_table = create_table(
|
429
|
+
title="Business Impact Metrics",
|
430
|
+
columns=[
|
431
|
+
{"name": "Metric", "style": "cyan"},
|
432
|
+
{"name": "Value", "style": "green"},
|
433
|
+
{"name": "Impact", "style": "yellow"}
|
434
|
+
]
|
435
|
+
)
|
436
|
+
|
437
|
+
# Financial Metrics
|
438
|
+
metrics_table.add_row(
|
439
|
+
"Monthly Savings",
|
440
|
+
f"${result.business_metrics.total_monthly_savings:,.2f}",
|
441
|
+
"Cost Reduction"
|
442
|
+
)
|
443
|
+
|
444
|
+
if result.business_metrics.roi_percentage:
|
445
|
+
metrics_table.add_row(
|
446
|
+
"ROI Percentage",
|
447
|
+
f"{result.business_metrics.roi_percentage:.1f}%",
|
448
|
+
"Investment Return"
|
449
|
+
)
|
450
|
+
|
451
|
+
if result.business_metrics.payback_period_months:
|
452
|
+
metrics_table.add_row(
|
453
|
+
"Payback Period",
|
454
|
+
f"{result.business_metrics.payback_period_months} months",
|
455
|
+
"Investment Recovery"
|
456
|
+
)
|
457
|
+
|
458
|
+
# Operational Metrics
|
459
|
+
if result.business_metrics.operational_efficiency_gain:
|
460
|
+
metrics_table.add_row(
|
461
|
+
"Efficiency Gain",
|
462
|
+
f"{result.business_metrics.operational_efficiency_gain:.1f}%",
|
463
|
+
"Operational Improvement"
|
464
|
+
)
|
465
|
+
|
466
|
+
metrics_table.add_row(
|
467
|
+
"Risk Level",
|
468
|
+
result.business_metrics.overall_risk_level.value.title(),
|
469
|
+
"Risk Assessment"
|
470
|
+
)
|
471
|
+
|
472
|
+
console.print(metrics_table)
|
473
|
+
|
474
|
+
# Recommendations (always shown if present)
|
475
|
+
if result.recommendations:
|
476
|
+
rec_text = "\n".join([f"• {rec}" for rec in result.recommendations])
|
477
|
+
rec_panel = create_panel(
|
478
|
+
rec_text,
|
479
|
+
title="Strategic Recommendations",
|
480
|
+
border_style="blue"
|
481
|
+
)
|
482
|
+
console.print(rec_panel)
|
483
|
+
|
484
|
+
async def execute_with_auth_handling(
|
485
|
+
self,
|
486
|
+
operation_name: str,
|
487
|
+
operation_func: Callable,
|
488
|
+
*args,
|
489
|
+
**kwargs
|
490
|
+
) -> Any:
|
491
|
+
"""
|
492
|
+
Execute operation with comprehensive authentication error handling.
|
493
|
+
|
494
|
+
Args:
|
495
|
+
operation_name: Human-readable operation name
|
496
|
+
operation_func: Function to execute (sync or async)
|
497
|
+
*args, **kwargs: Arguments for operation_func
|
498
|
+
|
499
|
+
Returns:
|
500
|
+
Operation result or None if authentication failed
|
501
|
+
"""
|
502
|
+
# Check authentication before proceeding
|
503
|
+
if not self.is_authenticated():
|
504
|
+
print_error(f"Cannot execute {operation_name}: Authentication failed")
|
505
|
+
self.display_authentication_status()
|
506
|
+
return None
|
507
|
+
|
508
|
+
# Execute with monitoring (from CloudOpsBase)
|
509
|
+
try:
|
510
|
+
return await self.execute_with_monitoring(
|
511
|
+
operation_name, operation_func, *args, **kwargs
|
512
|
+
)
|
513
|
+
except ClientError as e:
|
514
|
+
error_code = e.response.get('Error', {}).get('Code', 'Unknown')
|
515
|
+
|
516
|
+
if error_code in ['ExpiredToken', 'InvalidToken']:
|
517
|
+
print_error(f"AWS token expired during {operation_name}")
|
518
|
+
print_warning("Please refresh your AWS credentials and retry")
|
519
|
+
return None
|
520
|
+
elif error_code in ['AccessDenied', 'UnauthorizedOperation']:
|
521
|
+
print_error(f"Permission denied during {operation_name}")
|
522
|
+
print_info("Contact your AWS administrator for required permissions")
|
523
|
+
return None
|
524
|
+
else:
|
525
|
+
# Re-raise other AWS errors
|
526
|
+
raise
|
527
|
+
|
528
|
+
def export_results_to_formats(
|
529
|
+
self,
|
530
|
+
result: CloudOpsExecutionResult,
|
531
|
+
export_dir: Path = None,
|
532
|
+
formats: List[str] = None
|
533
|
+
) -> Dict[str, str]:
|
534
|
+
"""
|
535
|
+
Export results to multiple formats for enterprise reporting.
|
536
|
+
|
537
|
+
Args:
|
538
|
+
result: Execution result to export
|
539
|
+
export_dir: Directory for exported files (default: ./exports)
|
540
|
+
formats: List of formats ['json', 'csv', 'html', 'pdf'] (default: all)
|
541
|
+
|
542
|
+
Returns:
|
543
|
+
Dictionary mapping format to file path
|
544
|
+
"""
|
545
|
+
if export_dir is None:
|
546
|
+
export_dir = Path("./exports")
|
547
|
+
|
548
|
+
if formats is None:
|
549
|
+
formats = ['json', 'csv', 'html'] # PDF requires additional dependencies
|
550
|
+
|
551
|
+
export_dir.mkdir(exist_ok=True)
|
552
|
+
exported_files = {}
|
553
|
+
|
554
|
+
# Base filename
|
555
|
+
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
556
|
+
base_filename = f"{result.scenario.value}_{timestamp}"
|
557
|
+
|
558
|
+
# JSON Export
|
559
|
+
if 'json' in formats:
|
560
|
+
json_file = export_dir / f"{base_filename}.json"
|
561
|
+
with open(json_file, 'w') as f:
|
562
|
+
json.dump(result.dict(), f, indent=2, default=str)
|
563
|
+
exported_files['json'] = str(json_file)
|
564
|
+
print_success(f"JSON export: {json_file}")
|
565
|
+
|
566
|
+
# CSV Export (summary metrics)
|
567
|
+
if 'csv' in formats:
|
568
|
+
csv_file = export_dir / f"{base_filename}_summary.csv"
|
569
|
+
summary_df = pd.DataFrame([result.summary_metrics])
|
570
|
+
summary_df.to_csv(csv_file, index=False)
|
571
|
+
exported_files['csv'] = str(csv_file)
|
572
|
+
print_success(f"CSV export: {csv_file}")
|
573
|
+
|
574
|
+
# Resource impacts CSV
|
575
|
+
if result.resources_impacted:
|
576
|
+
impacts_csv = export_dir / f"{base_filename}_impacts.csv"
|
577
|
+
impacts_data = [impact.dict() for impact in result.resources_impacted]
|
578
|
+
impacts_df = pd.DataFrame(impacts_data)
|
579
|
+
impacts_df.to_csv(impacts_csv, index=False)
|
580
|
+
exported_files['csv_impacts'] = str(impacts_csv)
|
581
|
+
print_info(f"Resource impacts CSV: {impacts_csv}")
|
582
|
+
|
583
|
+
# HTML Export (Rich console output)
|
584
|
+
if 'html' in formats:
|
585
|
+
html_file = export_dir / f"{base_filename}.html"
|
586
|
+
# Create HTML version of the results
|
587
|
+
html_content = self._create_html_report(result)
|
588
|
+
with open(html_file, 'w') as f:
|
589
|
+
f.write(html_content)
|
590
|
+
exported_files['html'] = str(html_file)
|
591
|
+
print_success(f"HTML export: {html_file}")
|
592
|
+
|
593
|
+
return exported_files
|
594
|
+
|
595
|
+
def _create_html_report(self, result: CloudOpsExecutionResult) -> str:
|
596
|
+
"""
|
597
|
+
Create HTML report from execution result.
|
598
|
+
|
599
|
+
Args:
|
600
|
+
result: Execution result
|
601
|
+
|
602
|
+
Returns:
|
603
|
+
HTML content string
|
604
|
+
"""
|
605
|
+
html_template = f"""
|
606
|
+
<!DOCTYPE html>
|
607
|
+
<html>
|
608
|
+
<head>
|
609
|
+
<title>CloudOps Report - {result.scenario_name}</title>
|
610
|
+
<style>
|
611
|
+
body {{ font-family: Arial, sans-serif; margin: 40px; }}
|
612
|
+
.header {{ color: #2E86AB; border-bottom: 2px solid #2E86AB; padding-bottom: 10px; }}
|
613
|
+
.summary {{ background: #f5f5f5; padding: 20px; border-radius: 5px; margin: 20px 0; }}
|
614
|
+
.metrics {{ display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 20px; }}
|
615
|
+
.metric-box {{ background: white; padding: 15px; border-radius: 5px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }}
|
616
|
+
.success {{ color: #28a745; }}
|
617
|
+
.error {{ color: #dc3545; }}
|
618
|
+
.warning {{ color: #ffc107; }}
|
619
|
+
</style>
|
620
|
+
</head>
|
621
|
+
<body>
|
622
|
+
<h1 class="header">CloudOps Execution Report</h1>
|
623
|
+
|
624
|
+
<div class="summary">
|
625
|
+
<h2>Executive Summary</h2>
|
626
|
+
<p><strong>Scenario:</strong> {result.scenario_name}</p>
|
627
|
+
<p><strong>Execution Time:</strong> {result.execution_time:.1f} seconds</p>
|
628
|
+
<p><strong>Status:</strong>
|
629
|
+
<span class="{'success' if result.success else 'error'}">
|
630
|
+
{'✅ Success' if result.success else '❌ Failed'}
|
631
|
+
</span>
|
632
|
+
</p>
|
633
|
+
<p><strong>Resources Analyzed:</strong> {result.resources_analyzed:,}</p>
|
634
|
+
<p><strong>Resources Impacted:</strong> {len(result.resources_impacted):,}</p>
|
635
|
+
</div>
|
636
|
+
|
637
|
+
<div class="metrics">
|
638
|
+
<div class="metric-box">
|
639
|
+
<h3>Financial Impact</h3>
|
640
|
+
<p><strong>Monthly Savings:</strong> ${result.business_metrics.total_monthly_savings:,.2f}</p>
|
641
|
+
{"<p><strong>ROI:</strong> " + f"{result.business_metrics.roi_percentage:.1f}%" + "</p>" if result.business_metrics.roi_percentage else ""}
|
642
|
+
</div>
|
643
|
+
|
644
|
+
<div class="metric-box">
|
645
|
+
<h3>Risk Assessment</h3>
|
646
|
+
<p><strong>Risk Level:</strong> {result.business_metrics.overall_risk_level.value.title()}</p>
|
647
|
+
<p><strong>Business Continuity:</strong> {result.business_metrics.business_continuity_impact.title()}</p>
|
648
|
+
</div>
|
649
|
+
</div>
|
650
|
+
|
651
|
+
{"<div class='error'><h3>Error Details</h3><p>" + result.error_message + "</p></div>" if not result.success and result.error_message else ""}
|
652
|
+
|
653
|
+
<div>
|
654
|
+
<h3>Recommendations</h3>
|
655
|
+
<ul>
|
656
|
+
{"".join([f"<li>{rec}</li>" for rec in result.recommendations])}
|
657
|
+
</ul>
|
658
|
+
</div>
|
659
|
+
|
660
|
+
<footer style="margin-top: 40px; padding-top: 20px; border-top: 1px solid #ddd; color: #666;">
|
661
|
+
<p>Generated by CloudOps Runbooks v0.9.1 at {datetime.now().strftime("%Y-%m-%d %H:%M:%S")}</p>
|
662
|
+
</footer>
|
663
|
+
</body>
|
664
|
+
</html>
|
665
|
+
"""
|
666
|
+
return html_template
|
667
|
+
|
668
|
+
|
669
|
+
# Export framework components
|
670
|
+
__all__ = [
|
671
|
+
"NotebookFramework",
|
672
|
+
"NotebookMode",
|
673
|
+
"AuthenticationStatus",
|
674
|
+
"AuthenticationResult",
|
675
|
+
"ScenarioMetadata"
|
676
|
+
]
|