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.
Files changed (46) hide show
  1. runbooks/__init__.py +1 -1
  2. runbooks/cfat/assessment/compliance.py +4 -1
  3. runbooks/cloudops/__init__.py +123 -0
  4. runbooks/cloudops/base.py +385 -0
  5. runbooks/cloudops/cost_optimizer.py +811 -0
  6. runbooks/cloudops/infrastructure_optimizer.py +29 -0
  7. runbooks/cloudops/interfaces.py +828 -0
  8. runbooks/cloudops/lifecycle_manager.py +29 -0
  9. runbooks/cloudops/mcp_cost_validation.py +678 -0
  10. runbooks/cloudops/models.py +251 -0
  11. runbooks/cloudops/monitoring_automation.py +29 -0
  12. runbooks/cloudops/notebook_framework.py +676 -0
  13. runbooks/cloudops/security_enforcer.py +449 -0
  14. runbooks/common/mcp_cost_explorer_integration.py +900 -0
  15. runbooks/common/mcp_integration.py +19 -10
  16. runbooks/common/rich_utils.py +1 -1
  17. runbooks/finops/README.md +31 -0
  18. runbooks/finops/cost_optimizer.py +1340 -0
  19. runbooks/finops/finops_dashboard.py +211 -5
  20. runbooks/finops/schemas.py +589 -0
  21. runbooks/inventory/runbooks.inventory.organizations_discovery.log +0 -0
  22. runbooks/inventory/runbooks.security.security_export.log +0 -0
  23. runbooks/main.py +525 -0
  24. runbooks/operate/ec2_operations.py +428 -0
  25. runbooks/operate/iam_operations.py +598 -3
  26. runbooks/operate/rds_operations.py +508 -0
  27. runbooks/operate/s3_operations.py +508 -0
  28. runbooks/remediation/base.py +5 -3
  29. runbooks/security/__init__.py +101 -0
  30. runbooks/security/cloudops_automation_security_validator.py +1164 -0
  31. runbooks/security/compliance_automation_engine.py +4 -4
  32. runbooks/security/enterprise_security_framework.py +4 -5
  33. runbooks/security/executive_security_dashboard.py +1247 -0
  34. runbooks/security/multi_account_security_controls.py +2254 -0
  35. runbooks/security/real_time_security_monitor.py +1196 -0
  36. runbooks/security/security_baseline_tester.py +3 -3
  37. runbooks/sre/production_monitoring_framework.py +584 -0
  38. runbooks/validation/mcp_validator.py +29 -15
  39. runbooks/vpc/networking_wrapper.py +6 -3
  40. runbooks-0.9.2.dist-info/METADATA +525 -0
  41. {runbooks-0.9.0.dist-info → runbooks-0.9.2.dist-info}/RECORD +45 -23
  42. runbooks-0.9.0.dist-info/METADATA +0 -718
  43. {runbooks-0.9.0.dist-info → runbooks-0.9.2.dist-info}/WHEEL +0 -0
  44. {runbooks-0.9.0.dist-info → runbooks-0.9.2.dist-info}/entry_points.txt +0 -0
  45. {runbooks-0.9.0.dist-info → runbooks-0.9.2.dist-info}/licenses/LICENSE +0 -0
  46. {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
+ ]