runbooks 1.0.2__py3-none-any.whl → 1.1.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 +9 -4
- runbooks/__init__.py.backup +134 -0
- runbooks/__init___optimized.py +110 -0
- runbooks/cloudops/base.py +56 -3
- runbooks/cloudops/cost_optimizer.py +496 -42
- runbooks/common/aws_pricing.py +236 -80
- runbooks/common/business_logic.py +485 -0
- runbooks/common/cli_decorators.py +219 -0
- runbooks/common/error_handling.py +424 -0
- runbooks/common/lazy_loader.py +186 -0
- runbooks/common/module_cli_base.py +378 -0
- runbooks/common/performance_monitoring.py +512 -0
- runbooks/common/profile_utils.py +133 -6
- runbooks/enterprise/logging.py +30 -2
- runbooks/enterprise/validation.py +177 -0
- runbooks/finops/README.md +311 -236
- runbooks/finops/aws_client.py +1 -1
- runbooks/finops/business_case_config.py +723 -19
- runbooks/finops/cli.py +136 -0
- runbooks/finops/commvault_ec2_analysis.py +25 -9
- runbooks/finops/config.py +272 -0
- runbooks/finops/dashboard_runner.py +136 -23
- runbooks/finops/ebs_cost_optimizer.py +39 -40
- runbooks/finops/enhanced_trend_visualization.py +7 -2
- runbooks/finops/enterprise_wrappers.py +45 -18
- runbooks/finops/finops_dashboard.py +50 -25
- runbooks/finops/finops_scenarios.py +22 -7
- runbooks/finops/helpers.py +115 -2
- runbooks/finops/multi_dashboard.py +7 -5
- runbooks/finops/optimizer.py +97 -6
- runbooks/finops/scenario_cli_integration.py +247 -0
- runbooks/finops/scenarios.py +12 -1
- runbooks/finops/unlimited_scenarios.py +393 -0
- runbooks/finops/validation_framework.py +19 -7
- runbooks/finops/workspaces_analyzer.py +1 -5
- runbooks/inventory/mcp_inventory_validator.py +2 -1
- runbooks/main.py +132 -94
- runbooks/main_final.py +358 -0
- runbooks/main_minimal.py +84 -0
- runbooks/main_optimized.py +493 -0
- runbooks/main_ultra_minimal.py +47 -0
- {runbooks-1.0.2.dist-info → runbooks-1.1.0.dist-info}/METADATA +15 -15
- {runbooks-1.0.2.dist-info → runbooks-1.1.0.dist-info}/RECORD +47 -31
- {runbooks-1.0.2.dist-info → runbooks-1.1.0.dist-info}/WHEEL +0 -0
- {runbooks-1.0.2.dist-info → runbooks-1.1.0.dist-info}/entry_points.txt +0 -0
- {runbooks-1.0.2.dist-info → runbooks-1.1.0.dist-info}/licenses/LICENSE +0 -0
- {runbooks-1.0.2.dist-info → runbooks-1.1.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,219 @@
|
|
1
|
+
"""
|
2
|
+
CLI Decorators for runbooks package - Enterprise CLI Standardization
|
3
|
+
|
4
|
+
Provides standard decorators for common CLI options, eliminating code duplication
|
5
|
+
and ensuring consistent user experience across all runbooks modules.
|
6
|
+
|
7
|
+
Following KISS & DRY principles - enhance existing structure without creating complexity.
|
8
|
+
"""
|
9
|
+
|
10
|
+
import click
|
11
|
+
from functools import wraps
|
12
|
+
from typing import Callable, Any
|
13
|
+
|
14
|
+
|
15
|
+
def common_aws_options(f: Callable) -> Callable:
|
16
|
+
"""
|
17
|
+
Standard AWS options for all runbooks commands.
|
18
|
+
|
19
|
+
Provides consistent AWS configuration options across all modules:
|
20
|
+
- --profile: AWS profile override (highest priority in 3-tier system)
|
21
|
+
- --region: AWS region override
|
22
|
+
- --dry-run: Safe analysis mode (enterprise default)
|
23
|
+
|
24
|
+
Usage:
|
25
|
+
@common_aws_options
|
26
|
+
@click.command()
|
27
|
+
def my_command(profile, region, dry_run, **kwargs):
|
28
|
+
# Your command logic here
|
29
|
+
"""
|
30
|
+
@click.option(
|
31
|
+
'--profile',
|
32
|
+
help='AWS profile override (highest priority over environment variables)',
|
33
|
+
type=str
|
34
|
+
)
|
35
|
+
@click.option(
|
36
|
+
'--region',
|
37
|
+
help='AWS region override (default: us-east-1)',
|
38
|
+
type=str,
|
39
|
+
default='us-east-1'
|
40
|
+
)
|
41
|
+
@click.option(
|
42
|
+
'--dry-run',
|
43
|
+
is_flag=True,
|
44
|
+
default=True,
|
45
|
+
help='Safe analysis mode - no resource modifications (enterprise default)'
|
46
|
+
)
|
47
|
+
@wraps(f)
|
48
|
+
def wrapper(*args, **kwargs):
|
49
|
+
return f(*args, **kwargs)
|
50
|
+
return wrapper
|
51
|
+
|
52
|
+
|
53
|
+
def common_output_options(f: Callable) -> Callable:
|
54
|
+
"""
|
55
|
+
Standard output options for all runbooks commands.
|
56
|
+
|
57
|
+
Provides consistent output formatting and export options:
|
58
|
+
- --output-format: JSON, CSV, table, PDF output formats
|
59
|
+
- --output-dir: Output directory for generated files
|
60
|
+
- --export: Enable multi-format export capability
|
61
|
+
|
62
|
+
Usage:
|
63
|
+
@common_output_options
|
64
|
+
@click.command()
|
65
|
+
def my_command(output_format, output_dir, export, **kwargs):
|
66
|
+
# Your command logic here
|
67
|
+
"""
|
68
|
+
@click.option(
|
69
|
+
'--output-format',
|
70
|
+
type=click.Choice(['json', 'csv', 'table', 'pdf', 'markdown'], case_sensitive=False),
|
71
|
+
default='table',
|
72
|
+
help='Output format for results display'
|
73
|
+
)
|
74
|
+
@click.option(
|
75
|
+
'--output-dir',
|
76
|
+
default='./awso_evidence',
|
77
|
+
help='Directory for generated files and evidence packages',
|
78
|
+
type=click.Path()
|
79
|
+
)
|
80
|
+
@click.option(
|
81
|
+
'--export',
|
82
|
+
is_flag=True,
|
83
|
+
help='Enable multi-format export (CSV, JSON, PDF, HTML)'
|
84
|
+
)
|
85
|
+
@wraps(f)
|
86
|
+
def wrapper(*args, **kwargs):
|
87
|
+
return f(*args, **kwargs)
|
88
|
+
return wrapper
|
89
|
+
|
90
|
+
|
91
|
+
def mcp_validation_option(f: Callable) -> Callable:
|
92
|
+
"""
|
93
|
+
MCP validation option for cost optimization and inventory modules.
|
94
|
+
|
95
|
+
Provides MCP (Model Context Protocol) validation capability for enhanced accuracy:
|
96
|
+
- --validate: Enable MCP validation with ≥99.5% accuracy target
|
97
|
+
- --confidence-threshold: Minimum confidence threshold (default: 99.5%)
|
98
|
+
|
99
|
+
Usage:
|
100
|
+
@mcp_validation_option
|
101
|
+
@click.command()
|
102
|
+
def my_command(validate, confidence_threshold, **kwargs):
|
103
|
+
# Your command logic with MCP validation
|
104
|
+
"""
|
105
|
+
@click.option(
|
106
|
+
'--validate',
|
107
|
+
is_flag=True,
|
108
|
+
help='Enable MCP validation for enhanced accuracy (≥99.5% target)'
|
109
|
+
)
|
110
|
+
@click.option(
|
111
|
+
'--confidence-threshold',
|
112
|
+
type=float,
|
113
|
+
default=99.5,
|
114
|
+
help='Minimum confidence threshold for validation (default: 99.5%%)'
|
115
|
+
)
|
116
|
+
@wraps(f)
|
117
|
+
def wrapper(*args, **kwargs):
|
118
|
+
return f(*args, **kwargs)
|
119
|
+
return wrapper
|
120
|
+
|
121
|
+
|
122
|
+
def enterprise_options(f: Callable) -> Callable:
|
123
|
+
"""
|
124
|
+
Enterprise-specific options for multi-account operations.
|
125
|
+
|
126
|
+
Provides enterprise deployment options:
|
127
|
+
- --all: Use all available AWS profiles
|
128
|
+
- --combine: Combine profiles from same AWS account
|
129
|
+
- --approval-required: Require human approval for operations
|
130
|
+
|
131
|
+
Usage:
|
132
|
+
@enterprise_options
|
133
|
+
@click.command()
|
134
|
+
def my_command(all_profiles, combine, approval_required, **kwargs):
|
135
|
+
# Your enterprise command logic
|
136
|
+
"""
|
137
|
+
@click.option(
|
138
|
+
'--all',
|
139
|
+
'all_profiles',
|
140
|
+
is_flag=True,
|
141
|
+
help='Use all available AWS profiles for multi-account operations'
|
142
|
+
)
|
143
|
+
@click.option(
|
144
|
+
'--combine',
|
145
|
+
is_flag=True,
|
146
|
+
help='Combine profiles from the same AWS account for unified reporting'
|
147
|
+
)
|
148
|
+
@click.option(
|
149
|
+
'--approval-required',
|
150
|
+
is_flag=True,
|
151
|
+
default=True,
|
152
|
+
help='Require human approval for state-changing operations (enterprise default)'
|
153
|
+
)
|
154
|
+
@wraps(f)
|
155
|
+
def wrapper(*args, **kwargs):
|
156
|
+
return f(*args, **kwargs)
|
157
|
+
return wrapper
|
158
|
+
|
159
|
+
|
160
|
+
def performance_options(f: Callable) -> Callable:
|
161
|
+
"""
|
162
|
+
Performance monitoring options for enterprise operations.
|
163
|
+
|
164
|
+
Provides performance monitoring and optimization options:
|
165
|
+
- --performance-target: Target execution time in seconds
|
166
|
+
- --timeout: Maximum execution timeout
|
167
|
+
- --parallel: Enable parallel processing where supported
|
168
|
+
|
169
|
+
Usage:
|
170
|
+
@performance_options
|
171
|
+
@click.command()
|
172
|
+
def my_command(performance_target, timeout, parallel, **kwargs):
|
173
|
+
# Your performance-monitored command
|
174
|
+
"""
|
175
|
+
@click.option(
|
176
|
+
'--performance-target',
|
177
|
+
type=int,
|
178
|
+
default=30,
|
179
|
+
help='Target execution time in seconds (enterprise default: 30s)'
|
180
|
+
)
|
181
|
+
@click.option(
|
182
|
+
'--timeout',
|
183
|
+
type=int,
|
184
|
+
default=300,
|
185
|
+
help='Maximum execution timeout in seconds (default: 5 minutes)'
|
186
|
+
)
|
187
|
+
@click.option(
|
188
|
+
'--parallel',
|
189
|
+
is_flag=True,
|
190
|
+
help='Enable parallel processing for multi-resource operations'
|
191
|
+
)
|
192
|
+
@wraps(f)
|
193
|
+
def wrapper(*args, **kwargs):
|
194
|
+
return f(*args, **kwargs)
|
195
|
+
return wrapper
|
196
|
+
|
197
|
+
|
198
|
+
def all_standard_options(f: Callable) -> Callable:
|
199
|
+
"""
|
200
|
+
Convenience decorator applying all standard options.
|
201
|
+
|
202
|
+
Combines common_aws_options, common_output_options, mcp_validation_option,
|
203
|
+
enterprise_options, and performance_options for comprehensive CLI commands.
|
204
|
+
|
205
|
+
Usage:
|
206
|
+
@all_standard_options
|
207
|
+
@click.command()
|
208
|
+
def comprehensive_command(**kwargs):
|
209
|
+
# Command with all standard options available
|
210
|
+
"""
|
211
|
+
@performance_options
|
212
|
+
@enterprise_options
|
213
|
+
@mcp_validation_option
|
214
|
+
@common_output_options
|
215
|
+
@common_aws_options
|
216
|
+
@wraps(f)
|
217
|
+
def wrapper(*args, **kwargs):
|
218
|
+
return f(*args, **kwargs)
|
219
|
+
return wrapper
|
@@ -0,0 +1,424 @@
|
|
1
|
+
"""
|
2
|
+
Error Handling Enhancement for runbooks package - Enterprise Error Management
|
3
|
+
|
4
|
+
Provides standardized error handling decorators and utilities that build upon the existing
|
5
|
+
enhanced_exception_handler.py infrastructure while adding commonly needed patterns.
|
6
|
+
|
7
|
+
Following KISS & DRY principles - enhance existing structure with practical decorators.
|
8
|
+
"""
|
9
|
+
|
10
|
+
import sys
|
11
|
+
import time
|
12
|
+
from functools import wraps
|
13
|
+
from typing import Callable, Dict, Any, Optional
|
14
|
+
|
15
|
+
from botocore.exceptions import ClientError, NoCredentialsError, PartialCredentialsError, ProfileNotFound
|
16
|
+
|
17
|
+
from .rich_utils import print_error, print_warning, print_info, print_success, console
|
18
|
+
from .enhanced_exception_handler import (
|
19
|
+
EnterpriseExceptionHandler,
|
20
|
+
ErrorContext,
|
21
|
+
create_exception_handler,
|
22
|
+
enhanced_error_handling
|
23
|
+
)
|
24
|
+
|
25
|
+
|
26
|
+
def handle_aws_errors(module_name: str = "runbooks", enable_recovery: bool = True):
|
27
|
+
"""
|
28
|
+
Decorator for standardized AWS error handling with Rich CLI formatting.
|
29
|
+
|
30
|
+
Provides consistent error handling across all runbooks modules with:
|
31
|
+
- AWS-specific error classification and guidance
|
32
|
+
- Profile override recommendations
|
33
|
+
- Rich CLI formatted error messages
|
34
|
+
- Automatic recovery suggestions
|
35
|
+
|
36
|
+
Args:
|
37
|
+
module_name: Name of the module using this decorator
|
38
|
+
enable_recovery: Enable interactive error recovery workflows
|
39
|
+
|
40
|
+
Usage:
|
41
|
+
@handle_aws_errors(module_name="finops")
|
42
|
+
def my_aws_operation(profile=None, region=None, **kwargs):
|
43
|
+
# Your AWS operation code here
|
44
|
+
"""
|
45
|
+
def decorator(f: Callable) -> Callable:
|
46
|
+
@wraps(f)
|
47
|
+
def wrapper(*args, **kwargs):
|
48
|
+
# Extract common parameters for error context
|
49
|
+
profile = kwargs.get('profile')
|
50
|
+
region = kwargs.get('region', 'us-east-1')
|
51
|
+
operation = kwargs.get('operation', f.__name__)
|
52
|
+
|
53
|
+
# Create error context
|
54
|
+
context = ErrorContext(
|
55
|
+
module_name=module_name,
|
56
|
+
operation=operation,
|
57
|
+
aws_profile=profile,
|
58
|
+
aws_region=region,
|
59
|
+
user_context=kwargs
|
60
|
+
)
|
61
|
+
|
62
|
+
# Create exception handler
|
63
|
+
handler = create_exception_handler(module_name, enable_rich_output=True)
|
64
|
+
|
65
|
+
try:
|
66
|
+
return f(*args, **kwargs)
|
67
|
+
|
68
|
+
except ClientError as e:
|
69
|
+
error_code = e.response.get('Error', {}).get('Code', 'Unknown')
|
70
|
+
service = e.operation_name if hasattr(e, 'operation_name') else 'AWS'
|
71
|
+
|
72
|
+
# Handle specific AWS errors with targeted guidance
|
73
|
+
if error_code == 'ExpiredToken':
|
74
|
+
print_error("AWS SSO token expired")
|
75
|
+
profile_name = profile or "your-profile"
|
76
|
+
print_info(f"Run: [bold green]aws sso login --profile {profile_name}[/]")
|
77
|
+
sys.exit(1)
|
78
|
+
|
79
|
+
elif error_code in ['AccessDenied', 'UnauthorizedOperation', 'Forbidden']:
|
80
|
+
print_error(f"Access denied: {e.response['Error']['Message']}")
|
81
|
+
print_warning("Check IAM permissions for this operation")
|
82
|
+
|
83
|
+
# Provide profile recommendations
|
84
|
+
if operation in ['finops', 'cost-analysis']:
|
85
|
+
print_info("Try using billing profile: [bold green]--profile BILLING_PROFILE[/]")
|
86
|
+
elif operation in ['inventory', 'organizations']:
|
87
|
+
print_info("Try using management profile: [bold green]--profile MANAGEMENT_PROFILE[/]")
|
88
|
+
elif operation in ['operate', 'resource-management']:
|
89
|
+
print_info("Try using ops profile: [bold green]--profile CENTRALISED_OPS_PROFILE[/]")
|
90
|
+
|
91
|
+
sys.exit(1)
|
92
|
+
|
93
|
+
elif error_code in ['Throttling', 'ThrottlingException', 'RequestLimitExceeded']:
|
94
|
+
print_warning(f"AWS API throttling detected: {error_code}")
|
95
|
+
print_info("Implementing automatic retry with backoff...")
|
96
|
+
time.sleep(2) # Basic backoff
|
97
|
+
return f(*args, **kwargs) # Retry once
|
98
|
+
|
99
|
+
else:
|
100
|
+
# Use enterprise exception handler for complex cases
|
101
|
+
enhanced_error = handler.handle_aws_error(e, context, operation)
|
102
|
+
if enable_recovery and enhanced_error.retry_possible:
|
103
|
+
handler.create_error_recovery_workflow(enhanced_error, interactive=False)
|
104
|
+
sys.exit(1)
|
105
|
+
|
106
|
+
except (NoCredentialsError, PartialCredentialsError, ProfileNotFound) as e:
|
107
|
+
enhanced_error = handler.handle_credentials_error(e, context)
|
108
|
+
if enable_recovery:
|
109
|
+
handler.create_error_recovery_workflow(enhanced_error, interactive=True)
|
110
|
+
sys.exit(1)
|
111
|
+
|
112
|
+
except ConnectionError as e:
|
113
|
+
print_error(f"Network connection failed: {str(e)}")
|
114
|
+
print_info("Check your internet connection and try again")
|
115
|
+
sys.exit(1)
|
116
|
+
|
117
|
+
except Exception as e:
|
118
|
+
print_error(f"Unexpected error in {operation}: {str(e)}")
|
119
|
+
if kwargs.get('debug') or kwargs.get('verbose'):
|
120
|
+
console.print_exception()
|
121
|
+
sys.exit(1)
|
122
|
+
|
123
|
+
return wrapper
|
124
|
+
return decorator
|
125
|
+
|
126
|
+
|
127
|
+
def handle_performance_errors(target_seconds: int = 30, module_name: str = "runbooks"):
|
128
|
+
"""
|
129
|
+
Decorator for performance monitoring and error handling.
|
130
|
+
|
131
|
+
Monitors operation execution time and provides performance guidance
|
132
|
+
when operations exceed enterprise targets.
|
133
|
+
|
134
|
+
Args:
|
135
|
+
target_seconds: Target execution time in seconds
|
136
|
+
module_name: Name of the module for error context
|
137
|
+
|
138
|
+
Usage:
|
139
|
+
@handle_performance_errors(target_seconds=15, module_name="finops")
|
140
|
+
def my_operation(**kwargs):
|
141
|
+
# Your operation code here
|
142
|
+
"""
|
143
|
+
def decorator(f: Callable) -> Callable:
|
144
|
+
@wraps(f)
|
145
|
+
def wrapper(*args, **kwargs):
|
146
|
+
start_time = time.time()
|
147
|
+
operation = kwargs.get('operation', f.__name__)
|
148
|
+
|
149
|
+
try:
|
150
|
+
result = f(*args, **kwargs)
|
151
|
+
execution_time = time.time() - start_time
|
152
|
+
|
153
|
+
# Performance feedback
|
154
|
+
if execution_time <= target_seconds:
|
155
|
+
print_success(f"⚡ Performance: {execution_time:.1f}s (target: <{target_seconds}s)")
|
156
|
+
else:
|
157
|
+
print_warning(f"⚠️ Performance: {execution_time:.1f}s (exceeded {target_seconds}s target)")
|
158
|
+
|
159
|
+
# Provide optimization suggestions
|
160
|
+
if execution_time > target_seconds * 2: # Significantly exceeded
|
161
|
+
print_info("Performance optimization suggestions:")
|
162
|
+
print_info(f" • Consider using --parallel for {operation}")
|
163
|
+
print_info(" • Try a different AWS region for better performance")
|
164
|
+
print_info(" • Check for API throttling or network issues")
|
165
|
+
|
166
|
+
# Create performance error for enterprise tracking
|
167
|
+
context = ErrorContext(
|
168
|
+
module_name=module_name,
|
169
|
+
operation=operation,
|
170
|
+
performance_context={
|
171
|
+
'execution_time': execution_time,
|
172
|
+
'target_seconds': target_seconds,
|
173
|
+
'performance_ratio': execution_time / target_seconds
|
174
|
+
}
|
175
|
+
)
|
176
|
+
|
177
|
+
handler = create_exception_handler(module_name)
|
178
|
+
handler.handle_performance_error(operation, execution_time, target_seconds, context)
|
179
|
+
|
180
|
+
return result
|
181
|
+
|
182
|
+
except Exception as e:
|
183
|
+
execution_time = time.time() - start_time
|
184
|
+
print_error(f"❌ Operation failed after {execution_time:.1f}s: {str(e)}")
|
185
|
+
raise
|
186
|
+
|
187
|
+
return wrapper
|
188
|
+
return decorator
|
189
|
+
|
190
|
+
|
191
|
+
def handle_validation_errors(f: Callable) -> Callable:
|
192
|
+
"""
|
193
|
+
Decorator for data validation error handling.
|
194
|
+
|
195
|
+
Provides clear guidance for data validation failures with
|
196
|
+
suggestions for correction.
|
197
|
+
|
198
|
+
Usage:
|
199
|
+
@handle_validation_errors
|
200
|
+
def my_validation_function(data, **kwargs):
|
201
|
+
# Your validation code here
|
202
|
+
"""
|
203
|
+
@wraps(f)
|
204
|
+
def wrapper(*args, **kwargs):
|
205
|
+
try:
|
206
|
+
return f(*args, **kwargs)
|
207
|
+
|
208
|
+
except (ValueError, TypeError) as e:
|
209
|
+
error_msg = str(e)
|
210
|
+
|
211
|
+
if 'profile' in error_msg.lower():
|
212
|
+
print_error(f"Profile validation error: {error_msg}")
|
213
|
+
print_info("Check available profiles: [bold green]aws configure list-profiles[/]")
|
214
|
+
|
215
|
+
elif 'region' in error_msg.lower():
|
216
|
+
print_error(f"Region validation error: {error_msg}")
|
217
|
+
print_info("Use valid AWS region like: [bold green]us-east-1, us-west-2, eu-west-1[/]")
|
218
|
+
|
219
|
+
elif 'format' in error_msg.lower():
|
220
|
+
print_error(f"Format validation error: {error_msg}")
|
221
|
+
print_info("Supported formats: [bold green]json, csv, table, pdf, markdown[/]")
|
222
|
+
|
223
|
+
else:
|
224
|
+
print_error(f"Data validation error: {error_msg}")
|
225
|
+
print_info("Review input parameters and try again")
|
226
|
+
|
227
|
+
sys.exit(1)
|
228
|
+
|
229
|
+
except Exception as e:
|
230
|
+
print_error(f"Validation failed: {str(e)}")
|
231
|
+
raise
|
232
|
+
|
233
|
+
return wrapper
|
234
|
+
|
235
|
+
|
236
|
+
def graceful_degradation(fallback_function: Optional[Callable] = None,
|
237
|
+
enable_fallback: bool = True):
|
238
|
+
"""
|
239
|
+
Decorator for graceful degradation with fallback operations.
|
240
|
+
|
241
|
+
Automatically attempts fallback operations when primary operation fails,
|
242
|
+
providing seamless user experience with transparent recovery.
|
243
|
+
|
244
|
+
Args:
|
245
|
+
fallback_function: Optional fallback function to use
|
246
|
+
enable_fallback: Enable automatic fallback attempts
|
247
|
+
|
248
|
+
Usage:
|
249
|
+
@graceful_degradation(fallback_function=simple_analysis)
|
250
|
+
def complex_analysis(**kwargs):
|
251
|
+
# Complex operation that might fail
|
252
|
+
|
253
|
+
# Or with automatic fallback detection
|
254
|
+
@graceful_degradation()
|
255
|
+
def main_operation(**kwargs):
|
256
|
+
# Will attempt fallback_operation if this fails
|
257
|
+
|
258
|
+
def fallback_operation(**kwargs):
|
259
|
+
# Simpler fallback version
|
260
|
+
"""
|
261
|
+
def decorator(f: Callable) -> Callable:
|
262
|
+
@wraps(f)
|
263
|
+
def wrapper(*args, **kwargs):
|
264
|
+
operation = kwargs.get('operation', f.__name__)
|
265
|
+
|
266
|
+
try:
|
267
|
+
print_info(f"🚀 Attempting primary operation: {operation}")
|
268
|
+
return f(*args, **kwargs)
|
269
|
+
|
270
|
+
except Exception as primary_error:
|
271
|
+
print_warning(f"⚠️ Primary operation failed: {operation}")
|
272
|
+
print_info(f"Error: {str(primary_error)}")
|
273
|
+
|
274
|
+
if not enable_fallback:
|
275
|
+
raise primary_error
|
276
|
+
|
277
|
+
# Try fallback function if provided
|
278
|
+
if fallback_function:
|
279
|
+
try:
|
280
|
+
print_info(f"🔄 Attempting fallback: {fallback_function.__name__}")
|
281
|
+
result = fallback_function(*args, **kwargs)
|
282
|
+
print_success(f"✅ Fallback operation succeeded: {fallback_function.__name__}")
|
283
|
+
return result
|
284
|
+
|
285
|
+
except Exception as fallback_error:
|
286
|
+
print_error(f"❌ Fallback operation failed: {fallback_function.__name__}")
|
287
|
+
print_error(f"Primary error: {str(primary_error)}")
|
288
|
+
print_error(f"Fallback error: {str(fallback_error)}")
|
289
|
+
raise primary_error
|
290
|
+
|
291
|
+
# Try to find automatic fallback based on naming convention
|
292
|
+
fallback_name = f"fallback_{f.__name__}"
|
293
|
+
if hasattr(f.__module__, fallback_name):
|
294
|
+
try:
|
295
|
+
fallback_func = getattr(f.__module__, fallback_name)
|
296
|
+
print_info(f"🔄 Attempting automatic fallback: {fallback_name}")
|
297
|
+
result = fallback_func(*args, **kwargs)
|
298
|
+
print_success(f"✅ Automatic fallback succeeded: {fallback_name}")
|
299
|
+
return result
|
300
|
+
|
301
|
+
except Exception:
|
302
|
+
pass
|
303
|
+
|
304
|
+
# No fallback available, raise original error
|
305
|
+
print_error("❌ No fallback available, operation failed")
|
306
|
+
raise primary_error
|
307
|
+
|
308
|
+
return wrapper
|
309
|
+
return decorator
|
310
|
+
|
311
|
+
|
312
|
+
def enterprise_error_context(module_name: str):
|
313
|
+
"""
|
314
|
+
Context manager for enterprise error handling with comprehensive logging.
|
315
|
+
|
316
|
+
Provides enterprise-grade error handling with audit trails, performance
|
317
|
+
monitoring, and comprehensive error analysis.
|
318
|
+
|
319
|
+
Usage:
|
320
|
+
with enterprise_error_context("finops") as ctx:
|
321
|
+
ctx.set_operation("cost_analysis")
|
322
|
+
ctx.set_profile("BILLING_PROFILE")
|
323
|
+
# Your operation code here
|
324
|
+
"""
|
325
|
+
class EnterpriseErrorContext:
|
326
|
+
def __init__(self, module_name: str):
|
327
|
+
self.module_name = module_name
|
328
|
+
self.handler = create_exception_handler(module_name)
|
329
|
+
self.context = ErrorContext(module_name=module_name, operation="unknown")
|
330
|
+
|
331
|
+
def set_operation(self, operation: str):
|
332
|
+
self.context.operation = operation
|
333
|
+
|
334
|
+
def set_profile(self, profile: str):
|
335
|
+
self.context.aws_profile = profile
|
336
|
+
|
337
|
+
def set_region(self, region: str):
|
338
|
+
self.context.aws_region = region
|
339
|
+
|
340
|
+
def add_user_context(self, **kwargs):
|
341
|
+
self.context.user_context.update(kwargs)
|
342
|
+
|
343
|
+
def __enter__(self):
|
344
|
+
return self
|
345
|
+
|
346
|
+
def __exit__(self, exc_type, exc_value, traceback):
|
347
|
+
if exc_value is not None:
|
348
|
+
enhanced_error = self.handler.handle_exception(
|
349
|
+
exc_value,
|
350
|
+
self.context,
|
351
|
+
{'context_manager': True}
|
352
|
+
)
|
353
|
+
|
354
|
+
# Try recovery for retryable errors
|
355
|
+
if enhanced_error.retry_possible:
|
356
|
+
recovery_success = self.handler.create_error_recovery_workflow(
|
357
|
+
enhanced_error,
|
358
|
+
interactive=False
|
359
|
+
)
|
360
|
+
if recovery_success:
|
361
|
+
return True # Suppress exception
|
362
|
+
|
363
|
+
return False # Let exception propagate
|
364
|
+
|
365
|
+
return EnterpriseErrorContext(module_name)
|
366
|
+
|
367
|
+
|
368
|
+
# Utility functions for common error scenarios
|
369
|
+
def validate_aws_profile(profile: str) -> bool:
|
370
|
+
"""
|
371
|
+
Validate AWS profile exists and is accessible.
|
372
|
+
|
373
|
+
Args:
|
374
|
+
profile: AWS profile name to validate
|
375
|
+
|
376
|
+
Returns:
|
377
|
+
True if profile is valid and accessible
|
378
|
+
|
379
|
+
Raises:
|
380
|
+
SystemExit if profile validation fails
|
381
|
+
"""
|
382
|
+
try:
|
383
|
+
import boto3
|
384
|
+
session = boto3.Session(profile_name=profile)
|
385
|
+
sts = session.client('sts')
|
386
|
+
identity = sts.get_caller_identity()
|
387
|
+
print_success(f"Profile validation successful: {profile}")
|
388
|
+
print_info(f"Account: {identity.get('Account', 'Unknown')}")
|
389
|
+
print_info(f"User: {identity.get('Arn', 'Unknown')}")
|
390
|
+
return True
|
391
|
+
|
392
|
+
except ProfileNotFound:
|
393
|
+
print_error(f"AWS profile not found: {profile}")
|
394
|
+
print_info("Check available profiles: [bold green]aws configure list-profiles[/]")
|
395
|
+
sys.exit(1)
|
396
|
+
|
397
|
+
except Exception as e:
|
398
|
+
print_error(f"Profile validation failed: {profile}")
|
399
|
+
print_error(f"Error: {str(e)}")
|
400
|
+
sys.exit(1)
|
401
|
+
|
402
|
+
|
403
|
+
def check_aws_connectivity(region: str = 'us-east-1') -> bool:
|
404
|
+
"""
|
405
|
+
Check basic AWS connectivity and service availability.
|
406
|
+
|
407
|
+
Args:
|
408
|
+
region: AWS region to test connectivity
|
409
|
+
|
410
|
+
Returns:
|
411
|
+
True if connectivity is successful
|
412
|
+
"""
|
413
|
+
try:
|
414
|
+
import boto3
|
415
|
+
session = boto3.Session()
|
416
|
+
sts = session.client('sts', region_name=region)
|
417
|
+
sts.get_caller_identity()
|
418
|
+
print_success(f"AWS connectivity verified: {region}")
|
419
|
+
return True
|
420
|
+
|
421
|
+
except Exception as e:
|
422
|
+
print_warning(f"AWS connectivity issue: {str(e)}")
|
423
|
+
print_info("Check internet connection and AWS service status")
|
424
|
+
return False
|