runbooks 1.1.3__py3-none-any.whl → 1.1.4__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 (90) hide show
  1. runbooks/cfat/WEIGHT_CONFIG_README.md +1 -1
  2. runbooks/cfat/assessment/compliance.py +7 -7
  3. runbooks/cfat/models.py +6 -2
  4. runbooks/cfat/tests/__init__.py +6 -1
  5. runbooks/cli/__init__.py +13 -0
  6. runbooks/cli/commands/cfat.py +233 -0
  7. runbooks/cli/commands/finops.py +213 -0
  8. runbooks/cli/commands/inventory.py +276 -0
  9. runbooks/cli/commands/operate.py +266 -0
  10. runbooks/cli/commands/security.py +224 -0
  11. runbooks/cli/commands/validation.py +411 -0
  12. runbooks/cli/commands/vpc.py +246 -0
  13. runbooks/cli/registry.py +95 -0
  14. runbooks/cloudops/__init__.py +3 -3
  15. runbooks/cloudops/cost_optimizer.py +6 -6
  16. runbooks/cloudops/interfaces.py +2 -2
  17. runbooks/cloudops/mcp_cost_validation.py +3 -3
  18. runbooks/cloudops/notebook_framework.py +2 -2
  19. runbooks/common/aws_profile_manager.py +337 -0
  20. runbooks/common/aws_utils.py +1 -1
  21. runbooks/common/business_logic.py +3 -3
  22. runbooks/common/comprehensive_cost_explorer_integration.py +1 -1
  23. runbooks/common/cross_account_manager.py +1 -1
  24. runbooks/common/decorators.py +225 -0
  25. runbooks/common/mcp_cost_explorer_integration.py +2 -2
  26. runbooks/common/organizations_client.py +1 -1
  27. runbooks/common/patterns.py +206 -0
  28. runbooks/common/profile_utils.py +149 -14
  29. runbooks/common/rich_utils.py +502 -11
  30. runbooks/finops/README.md +8 -8
  31. runbooks/finops/__init__.py +4 -4
  32. runbooks/finops/business_cases.py +3 -3
  33. runbooks/finops/cost_optimizer.py +4 -4
  34. runbooks/finops/dashboard_router.py +2 -2
  35. runbooks/finops/ebs_cost_optimizer.py +4 -4
  36. runbooks/finops/ebs_optimizer.py +19 -2
  37. runbooks/finops/enhanced_progress.py +8 -8
  38. runbooks/finops/enterprise_wrappers.py +7 -7
  39. runbooks/finops/finops_scenarios.py +11 -11
  40. runbooks/finops/legacy_migration.py +8 -8
  41. runbooks/finops/markdown_exporter.py +2 -2
  42. runbooks/finops/multi_dashboard.py +1 -1
  43. runbooks/finops/nat_gateway_optimizer.py +1 -1
  44. runbooks/finops/optimizer.py +6 -6
  45. runbooks/finops/rds_snapshot_optimizer.py +2 -2
  46. runbooks/finops/scenario_cli_integration.py +13 -13
  47. runbooks/finops/scenarios.py +16 -16
  48. runbooks/finops/single_dashboard.py +10 -10
  49. runbooks/finops/tests/test_finops_dashboard.py +3 -3
  50. runbooks/finops/tests/test_reference_images_validation.py +2 -2
  51. runbooks/finops/tests/test_single_account_features.py +17 -17
  52. runbooks/finops/tests/validate_test_suite.py +1 -1
  53. runbooks/finops/validation_framework.py +5 -5
  54. runbooks/finops/vpc_cleanup_exporter.py +3 -3
  55. runbooks/finops/vpc_cleanup_optimizer.py +2 -2
  56. runbooks/finops/workspaces_analyzer.py +1 -1
  57. runbooks/hitl/enhanced_workflow_engine.py +1 -1
  58. runbooks/inventory/README.md +3 -3
  59. runbooks/inventory/Tests/common_test_data.py +30 -30
  60. runbooks/inventory/collectors/aws_comprehensive.py +28 -11
  61. runbooks/inventory/collectors/aws_networking.py +2 -2
  62. runbooks/inventory/discovery.md +2 -2
  63. runbooks/inventory/find_ec2_security_groups.py +1 -1
  64. runbooks/inventory/organizations_discovery.py +1 -1
  65. runbooks/inventory/vpc_analyzer.py +1 -1
  66. runbooks/inventory/vpc_flow_analyzer.py +2 -2
  67. runbooks/main.py +143 -9153
  68. runbooks/metrics/dora_metrics_engine.py +2 -2
  69. runbooks/operate/mcp_integration.py +1 -1
  70. runbooks/operate/networking_cost_heatmap.py +4 -2
  71. runbooks/operate/privatelink_operations.py +1 -1
  72. runbooks/operate/vpc_endpoints.py +1 -1
  73. runbooks/operate/vpc_operations.py +2 -2
  74. runbooks/remediation/commvault_ec2_analysis.py +1 -1
  75. runbooks/remediation/rds_snapshot_list.py +5 -5
  76. runbooks/remediation/workspaces_list.py +5 -5
  77. runbooks/security/integration_test_enterprise_security.py +5 -3
  78. runbooks/security/run_script.py +1 -1
  79. runbooks/sre/mcp_reliability_engine.py +6 -6
  80. runbooks/utils/version_validator.py +1 -1
  81. runbooks/validation/comprehensive_2way_validator.py +9 -4
  82. runbooks/vpc/heatmap_engine.py +7 -4
  83. runbooks/vpc/mcp_no_eni_validator.py +1 -1
  84. runbooks/vpc/unified_scenarios.py +7 -7
  85. {runbooks-1.1.3.dist-info → runbooks-1.1.4.dist-info}/METADATA +53 -52
  86. {runbooks-1.1.3.dist-info → runbooks-1.1.4.dist-info}/RECORD +90 -78
  87. {runbooks-1.1.3.dist-info → runbooks-1.1.4.dist-info}/WHEEL +0 -0
  88. {runbooks-1.1.3.dist-info → runbooks-1.1.4.dist-info}/entry_points.txt +0 -0
  89. {runbooks-1.1.3.dist-info → runbooks-1.1.4.dist-info}/licenses/LICENSE +0 -0
  90. {runbooks-1.1.3.dist-info → runbooks-1.1.4.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,337 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ AWS Profile Manager - Universal v1.1.x Compatibility
4
+
5
+ Centralized AWS profile and account management for CloudOps Runbooks platform.
6
+ Eliminates ALL hardcoded account IDs and provides universal --profile support.
7
+
8
+ Features:
9
+ - 3-tier profile priority: User > Environment > Default
10
+ - Dynamic account ID resolution
11
+ - Multi-account discovery and validation
12
+ - Profile existence validation with helpful error messages
13
+ - Integration with Rich CLI for beautiful output
14
+
15
+ Author: CloudOps Runbooks Team
16
+ Version: 1.1.0
17
+ """
18
+
19
+ import os
20
+ import boto3
21
+ from typing import Dict, List, Optional, Any
22
+ from botocore.exceptions import ClientError, ProfileNotFound, NoCredentialsError
23
+ from rich.console import Console
24
+
25
+ from runbooks.common.rich_utils import console, print_error, print_success, print_warning, print_info
26
+
27
+ class AWSProfileManager:
28
+ """
29
+ Universal AWS Profile Manager for CloudOps v1.1.x compatibility.
30
+
31
+ Provides centralized profile management, account resolution, and
32
+ multi-account discovery for all CloudOps modules.
33
+ """
34
+
35
+ def __init__(self, profile: Optional[str] = None):
36
+ """
37
+ Initialize ProfileManager with 3-tier priority.
38
+
39
+ Args:
40
+ profile: User-specified profile (highest priority)
41
+ """
42
+ self.profile = self._resolve_profile(profile)
43
+ self.session = None
44
+ self._account_cache: Dict[str, str] = {}
45
+
46
+ def _resolve_profile(self, user_profile: Optional[str]) -> Optional[str]:
47
+ """
48
+ Resolve profile using 3-tier priority: User > Environment > Default
49
+
50
+ Args:
51
+ user_profile: User-specified profile
52
+
53
+ Returns:
54
+ Resolved profile name or None for default
55
+ """
56
+ # Tier 1: User-specified profile (highest priority)
57
+ if user_profile:
58
+ return user_profile
59
+
60
+ # Tier 2: Environment variable
61
+ env_profile = os.getenv('AWS_PROFILE')
62
+ if env_profile:
63
+ return env_profile
64
+
65
+ # Tier 3: Default (no explicit profile)
66
+ return None
67
+
68
+ def get_session(self, region: str = 'us-east-1') -> boto3.Session:
69
+ """
70
+ Get boto3 session with resolved profile.
71
+
72
+ Args:
73
+ region: AWS region (default: us-east-1)
74
+
75
+ Returns:
76
+ Configured boto3 session
77
+
78
+ Raises:
79
+ ProfileNotFound: If specified profile doesn't exist
80
+ NoCredentialsError: If no valid credentials found
81
+ """
82
+ if not self.session:
83
+ try:
84
+ if self.profile:
85
+ self.session = boto3.Session(profile_name=self.profile, region_name=region)
86
+ print_info(f"Using AWS profile: {self.profile}")
87
+ else:
88
+ self.session = boto3.Session(region_name=region)
89
+ print_info("Using default AWS credentials")
90
+
91
+ # Validate credentials by getting caller identity
92
+ sts = self.session.client('sts')
93
+ identity = sts.get_caller_identity()
94
+ print_success(f"✅ Authenticated as: {identity.get('Arn', 'Unknown')}")
95
+
96
+ except ProfileNotFound as e:
97
+ print_error(f"❌ AWS profile '{self.profile}' not found")
98
+ print_info("Available profiles:")
99
+ self._list_available_profiles()
100
+ raise e
101
+ except NoCredentialsError as e:
102
+ print_error("❌ No AWS credentials found")
103
+ print_info("Setup options:")
104
+ print_info("1. Configure AWS CLI: aws configure")
105
+ print_info("2. Set AWS_PROFILE environment variable")
106
+ print_info("3. Use --profile flag with valid profile name")
107
+ raise e
108
+
109
+ return self.session
110
+
111
+ def get_account_id(self, region: str = 'us-east-1') -> str:
112
+ """
113
+ Get current AWS account ID dynamically.
114
+
115
+ Args:
116
+ region: AWS region
117
+
118
+ Returns:
119
+ Current AWS account ID
120
+ """
121
+ cache_key = f"{self.profile or 'default'}:{region}"
122
+
123
+ if cache_key not in self._account_cache:
124
+ try:
125
+ session = self.get_session(region)
126
+ sts = session.client('sts')
127
+ identity = sts.get_caller_identity()
128
+ account_id = identity['Account']
129
+ self._account_cache[cache_key] = account_id
130
+ print_info(f"Current account ID: {account_id}")
131
+
132
+ except ClientError as e:
133
+ print_error(f"❌ Failed to get account ID: {e}")
134
+ # Return generic account ID for testing/mock scenarios
135
+ return "123456789012"
136
+
137
+ return self._account_cache[cache_key]
138
+
139
+ def discover_organization_accounts(self, region: str = 'us-east-1') -> List[Dict[str, Any]]:
140
+ """
141
+ Discover all accounts in AWS Organizations (if available).
142
+
143
+ Args:
144
+ region: AWS region
145
+
146
+ Returns:
147
+ List of organization accounts with metadata
148
+ """
149
+ try:
150
+ session = self.get_session(region)
151
+ org_client = session.client('organizations')
152
+
153
+ # Get organization information
154
+ try:
155
+ org_info = org_client.describe_organization()
156
+ print_success(f"✅ Organization: {org_info['Organization']['Id']}")
157
+ except ClientError:
158
+ print_warning("⚠️ Not connected to AWS Organizations")
159
+ return []
160
+
161
+ # List all accounts
162
+ accounts = []
163
+ paginator = org_client.get_paginator('list_accounts')
164
+
165
+ for page in paginator.paginate():
166
+ for account in page['Accounts']:
167
+ accounts.append({
168
+ 'Id': account['Id'],
169
+ 'Name': account['Name'],
170
+ 'Email': account['Email'],
171
+ 'Status': account['Status'],
172
+ 'JoinedMethod': account.get('JoinedMethod', 'UNKNOWN')
173
+ })
174
+
175
+ print_success(f"✅ Discovered {len(accounts)} organization accounts")
176
+ return accounts
177
+
178
+ except ClientError as e:
179
+ print_warning(f"⚠️ Unable to discover organization accounts: {e}")
180
+ return []
181
+
182
+ def validate_profile_access(self, required_services: List[str] = None) -> Dict[str, bool]:
183
+ """
184
+ Validate profile has access to required AWS services.
185
+
186
+ Args:
187
+ required_services: List of AWS service names to validate
188
+
189
+ Returns:
190
+ Dict mapping service names to access status
191
+ """
192
+ if not required_services:
193
+ required_services = ['sts', 'ce', 'ec2', 's3']
194
+
195
+ access_status = {}
196
+ session = self.get_session()
197
+
198
+ for service in required_services:
199
+ try:
200
+ client = session.client(service)
201
+
202
+ # Service-specific health checks
203
+ if service == 'sts':
204
+ client.get_caller_identity()
205
+ elif service == 'ce':
206
+ # Test Cost Explorer access
207
+ from datetime import datetime, timedelta
208
+ end_date = datetime.now().strftime('%Y-%m-%d')
209
+ start_date = (datetime.now() - timedelta(days=7)).strftime('%Y-%m-%d')
210
+ client.get_cost_and_usage(
211
+ TimePeriod={'Start': start_date, 'End': end_date},
212
+ Granularity='MONTHLY',
213
+ Metrics=['UnblendedCost']
214
+ )
215
+ elif service == 'ec2':
216
+ client.describe_regions(MaxResults=1)
217
+ elif service == 's3':
218
+ client.list_buckets()
219
+
220
+ access_status[service] = True
221
+ print_success(f"✅ {service.upper()} access validated")
222
+
223
+ except ClientError as e:
224
+ access_status[service] = False
225
+ print_warning(f"⚠️ {service.upper()} access limited: {e}")
226
+
227
+ return access_status
228
+
229
+ def _list_available_profiles(self) -> None:
230
+ """List available AWS profiles from credentials file."""
231
+ try:
232
+ import configparser
233
+ import os.path
234
+
235
+ credentials_path = os.path.expanduser('~/.aws/credentials')
236
+ config_path = os.path.expanduser('~/.aws/config')
237
+
238
+ profiles = set()
239
+
240
+ # Check credentials file
241
+ if os.path.exists(credentials_path):
242
+ cred_config = configparser.ConfigParser()
243
+ cred_config.read(credentials_path)
244
+ profiles.update(cred_config.sections())
245
+
246
+ # Check config file (profiles prefixed with 'profile ')
247
+ if os.path.exists(config_path):
248
+ config_config = configparser.ConfigParser()
249
+ config_config.read(config_path)
250
+ for section in config_config.sections():
251
+ if section.startswith('profile '):
252
+ profiles.add(section[8:]) # Remove 'profile ' prefix
253
+ elif section == 'default':
254
+ profiles.add('default')
255
+
256
+ if profiles:
257
+ for profile in sorted(profiles):
258
+ print_info(f" - {profile}")
259
+ else:
260
+ print_info(" No profiles found in ~/.aws/credentials or ~/.aws/config")
261
+
262
+ except Exception as e:
263
+ print_warning(f"⚠️ Could not list profiles: {e}")
264
+
265
+ @classmethod
266
+ def create_mock_account_context(cls, mock_account_id: str = "123456789012") -> 'AWSProfileManager':
267
+ """
268
+ Create mock profile manager for testing scenarios.
269
+
270
+ Args:
271
+ mock_account_id: Mock account ID to use
272
+
273
+ Returns:
274
+ ProfileManager configured for testing
275
+ """
276
+ manager = cls()
277
+ manager._account_cache['mock'] = mock_account_id
278
+ return manager
279
+
280
+ def get_profile_display_name(self) -> str:
281
+ """
282
+ Get human-friendly profile display name.
283
+
284
+ Returns:
285
+ Profile display name for CLI output
286
+ """
287
+ if self.profile:
288
+ return f"Profile: {self.profile}"
289
+ else:
290
+ return "Profile: default"
291
+
292
+ def __repr__(self) -> str:
293
+ return f"AWSProfileManager(profile={self.profile})"
294
+
295
+
296
+ # Global convenience functions for backward compatibility
297
+ def get_current_account_id(profile: Optional[str] = None, region: str = 'us-east-1') -> str:
298
+ """
299
+ Convenience function to get current account ID.
300
+
301
+ Args:
302
+ profile: AWS profile name
303
+ region: AWS region
304
+
305
+ Returns:
306
+ Current AWS account ID
307
+ """
308
+ manager = AWSProfileManager(profile)
309
+ return manager.get_account_id(region)
310
+
311
+
312
+ def validate_profile_or_exit(profile: Optional[str] = None, required_services: List[str] = None) -> AWSProfileManager:
313
+ """
314
+ Validate profile exists and has required access, exit gracefully if not.
315
+
316
+ Args:
317
+ profile: AWS profile name
318
+ required_services: Required AWS services for validation
319
+
320
+ Returns:
321
+ Validated AWSProfileManager instance
322
+ """
323
+ try:
324
+ manager = AWSProfileManager(profile)
325
+ access_status = manager.validate_profile_access(required_services)
326
+
327
+ failed_services = [svc for svc, status in access_status.items() if not status]
328
+ if failed_services:
329
+ print_warning(f"⚠️ Limited access to services: {', '.join(failed_services)}")
330
+ print_info("Continuing with available services...")
331
+
332
+ return manager
333
+
334
+ except (ProfileNotFound, NoCredentialsError):
335
+ print_error("❌ Cannot proceed without valid AWS credentials")
336
+ print_info("Please configure AWS credentials and try again.")
337
+ exit(1)
@@ -13,7 +13,7 @@ Features:
13
13
  - Token expiration prediction and silent refresh
14
14
 
15
15
  Author: DevSecOps Security Engineer - CloudOps Runbooks Team
16
- Version: 0.9.1
16
+ Version: latest version
17
17
  Security Focus: Enterprise AWS Account Protection
18
18
  """
19
19
 
@@ -22,8 +22,8 @@ from .profile_utils import get_profile_for_operation
22
22
  class BusinessImpactLevel(Enum):
23
23
  """Business impact classification for operations and optimizations."""
24
24
  CRITICAL = "CRITICAL" # >$100K annual impact
25
- HIGH = "HIGH" # $20K-100K annual impact
26
- MEDIUM = "MEDIUM" # $5K-20K annual impact
25
+ HIGH = "HIGH" # measurable range annual impact
26
+ MEDIUM = "MEDIUM" # measurable range annual impact
27
27
  LOW = "LOW" # <$5K annual impact
28
28
 
29
29
 
@@ -189,7 +189,7 @@ class UniversalBusinessLogic:
189
189
  # Apply proven profile management patterns
190
190
  selected_profile = get_profile_for_operation("operational", profile)
191
191
 
192
- print_header(f"{resource_type.title()} {operation.title()}", f"v1.1.2 - {self.module_name}")
192
+ print_header(f"{resource_type.title()} {operation.title()}", f"latest version - {self.module_name}")
193
193
  print_info(f"Using profile: {selected_profile}")
194
194
 
195
195
  # Standard operation tracking
@@ -749,7 +749,7 @@ class ComprehensiveCostExplorerIntegration:
749
749
 
750
750
  ---
751
751
 
752
- *Generated by Comprehensive Cost Explorer Integration v1.0.0*
752
+ *Generated by Comprehensive Cost Explorer Integration latest version*
753
753
  *Strategic Coordination: Enterprise Agile Team with systematic delegation*
754
754
  """
755
755
 
@@ -14,7 +14,7 @@ Features:
14
14
  - Comprehensive session validation and metadata tracking
15
15
 
16
16
  Author: CloudOps Runbooks Team
17
- Version: 0.9.1
17
+ Version: latest version
18
18
  """
19
19
 
20
20
  import threading
@@ -0,0 +1,225 @@
1
+ """
2
+ Common CLI Decorators for Modular Commands
3
+
4
+ KISS Principle: Simple, reusable decorators for consistent CLI patterns
5
+ DRY Principle: No duplicated decorator logic across command modules
6
+
7
+ This module provides consistent decorators used across all modular command
8
+ files, enabling the DRY principle while maintaining enterprise standards.
9
+ """
10
+
11
+ import functools
12
+ import time
13
+ from typing import Any, Callable
14
+
15
+ import click
16
+ from rich.console import Console
17
+
18
+ console = Console()
19
+
20
+
21
+ def common_aws_options(f):
22
+ """
23
+ Common AWS options for all commands.
24
+
25
+ Provides consistent AWS configuration options across all command modules:
26
+ - --profile: AWS profile selection
27
+ - --region: AWS region targeting
28
+ - --dry-run: Safety mode for testing
29
+ """
30
+ f = click.option("--profile", default="default", help="AWS profile to use")(f)
31
+ f = click.option("--region", help="AWS region (overrides profile default)")(f)
32
+ f = click.option("--dry-run", is_flag=True, help="Perform a dry run without making changes")(f)
33
+ return f
34
+
35
+
36
+ def common_output_options(f):
37
+ """
38
+ Common output options for commands that generate reports.
39
+
40
+ Provides consistent output formatting options:
41
+ - --format: Output format selection (table, csv, json, markdown, pdf)
42
+ - --output-file: File output destination
43
+ """
44
+ f = click.option("--format", "output_format", type=click.Choice(['table', 'csv', 'json', 'markdown', 'pdf']),
45
+ default='table', help="Output format")(f)
46
+ f = click.option("--output-file", type=click.Path(), help="Output file path")(f)
47
+ return f
48
+
49
+
50
+ def common_filter_options(f):
51
+ """
52
+ Common filtering options for resource discovery commands.
53
+
54
+ Provides consistent filtering capabilities:
55
+ - --tags: Resource tag filtering
56
+ - --accounts: Account ID filtering
57
+ - --regions: Region filtering
58
+ """
59
+ f = click.option("--tags", multiple=True, help="Filter by tags (key=value format)")(f)
60
+ f = click.option("--accounts", multiple=True, help="Filter by account IDs")(f)
61
+ f = click.option("--regions", multiple=True, help="Filter by regions")(f)
62
+ return f
63
+
64
+
65
+ def performance_timing(f):
66
+ """
67
+ Performance timing decorator for measuring command execution time.
68
+
69
+ Automatically tracks and reports command execution time for performance
70
+ monitoring and optimization analysis.
71
+ """
72
+ @functools.wraps(f)
73
+ def wrapper(*args, **kwargs):
74
+ start_time = time.time()
75
+ try:
76
+ result = f(*args, **kwargs)
77
+ execution_time = time.time() - start_time
78
+
79
+ # Only show timing in debug mode or for slow operations
80
+ if execution_time > 1.0: # Show for operations > 1 second
81
+ console.print(f"[dim]⏱️ Completed in {execution_time:.2f}s[/dim]")
82
+
83
+ return result
84
+ except Exception as e:
85
+ execution_time = time.time() - start_time
86
+ console.print(f"[red]❌ Failed after {execution_time:.2f}s: {e}[/red]")
87
+ raise
88
+
89
+ return wrapper
90
+
91
+
92
+ def error_handler(f):
93
+ """
94
+ Common error handling decorator for consistent error reporting.
95
+
96
+ Provides enterprise-grade error handling with:
97
+ - Rich formatting for better UX
98
+ - Consistent error message structure
99
+ - Debug information when enabled
100
+ """
101
+ @functools.wraps(f)
102
+ def wrapper(*args, **kwargs):
103
+ try:
104
+ return f(*args, **kwargs)
105
+ except click.ClickException:
106
+ # Re-raise Click exceptions as-is
107
+ raise
108
+ except ImportError as e:
109
+ console.print(f"[red]❌ Module not available: {e}[/red]")
110
+ console.print(f"[yellow]💡 This functionality may require additional dependencies[/yellow]")
111
+ raise click.ClickException("Required module not available")
112
+ except Exception as e:
113
+ console.print(f"[red]❌ Unexpected error: {e}[/red]")
114
+ console.print(f"[yellow]💡 Run with --debug for detailed error information[/yellow]")
115
+ raise click.ClickException(str(e))
116
+
117
+ return wrapper
118
+
119
+
120
+ def require_aws_profile(f):
121
+ """
122
+ Decorator to ensure AWS profile is properly configured.
123
+
124
+ Validates that the AWS profile exists and is accessible before
125
+ executing commands that require AWS API access.
126
+ """
127
+ @functools.wraps(f)
128
+ def wrapper(*args, **kwargs):
129
+ # Get profile from context or kwargs
130
+ ctx = click.get_current_context()
131
+ profile = ctx.obj.get('profile', 'default')
132
+
133
+ try:
134
+ import boto3
135
+ # Test profile access
136
+ session = boto3.Session(profile_name=profile)
137
+ session.get_credentials()
138
+
139
+ return f(*args, **kwargs)
140
+ except Exception as e:
141
+ console.print(f"[red]❌ AWS profile '{profile}' not accessible: {e}[/red]")
142
+ console.print(f"[yellow]💡 Run 'aws configure list-profiles' to see available profiles[/yellow]")
143
+ raise click.ClickException(f"AWS profile '{profile}' not accessible")
144
+
145
+ return wrapper
146
+
147
+
148
+ def enterprise_audit_trail(f):
149
+ """
150
+ Enterprise audit trail decorator for compliance and governance.
151
+
152
+ Automatically logs command execution for audit purposes with:
153
+ - Command name and parameters
154
+ - User context and timestamp
155
+ - Execution results and duration
156
+ """
157
+ @functools.wraps(f)
158
+ def wrapper(*args, **kwargs):
159
+ ctx = click.get_current_context()
160
+
161
+ # Log command execution start
162
+ audit_data = {
163
+ 'command': ctx.command.name,
164
+ 'profile': ctx.obj.get('profile', 'default'),
165
+ 'region': ctx.obj.get('region', 'default'),
166
+ 'dry_run': ctx.obj.get('dry_run', False),
167
+ 'timestamp': time.time()
168
+ }
169
+
170
+ try:
171
+ result = f(*args, **kwargs)
172
+ audit_data['status'] = 'success'
173
+ audit_data['duration'] = time.time() - audit_data['timestamp']
174
+
175
+ # Log successful execution
176
+ if ctx.obj.get('debug'):
177
+ console.print(f"[dim]📋 Audit: {audit_data}[/dim]")
178
+
179
+ return result
180
+ except Exception as e:
181
+ audit_data['status'] = 'error'
182
+ audit_data['error'] = str(e)
183
+ audit_data['duration'] = time.time() - audit_data['timestamp']
184
+
185
+ # Log failed execution
186
+ if ctx.obj.get('debug'):
187
+ console.print(f"[dim]📋 Audit: {audit_data}[/dim]")
188
+
189
+ raise
190
+
191
+ return wrapper
192
+
193
+
194
+ def rich_progress(description: str = "Processing"):
195
+ """
196
+ Rich progress indicator decorator for long-running operations.
197
+
198
+ Args:
199
+ description: Description text for the progress indicator
200
+
201
+ Automatically shows a progress spinner for operations that take time,
202
+ improving user experience for long-running commands.
203
+ """
204
+ def decorator(f):
205
+ @functools.wraps(f)
206
+ def wrapper(*args, **kwargs):
207
+ from rich.progress import Progress, SpinnerColumn, TextColumn
208
+
209
+ with Progress(
210
+ SpinnerColumn(),
211
+ TextColumn("[progress.description]{task.description}"),
212
+ console=console
213
+ ) as progress:
214
+ task = progress.add_task(description, total=None)
215
+
216
+ try:
217
+ result = f(*args, **kwargs)
218
+ progress.update(task, description=f"✅ {description} completed")
219
+ return result
220
+ except Exception as e:
221
+ progress.update(task, description=f"❌ {description} failed")
222
+ raise
223
+
224
+ return wrapper
225
+ return decorator
@@ -703,7 +703,7 @@ class MCPCostExplorerIntegration:
703
703
  # Generate recommendations
704
704
  if priorities_assessment['priorities']['workspaces_cleanup']['status'] == 'needs_expansion':
705
705
  priorities_assessment['recommendations'].append(
706
- "Expand WorkSpaces analysis scope to achieve $12,518 annual target"
706
+ "Expand WorkSpaces analysis scope to achieve significant annual savings target"
707
707
  )
708
708
 
709
709
  if priorities_assessment['priorities']['nat_gateway_optimization']['status'] == 'limited_opportunities':
@@ -713,7 +713,7 @@ class MCPCostExplorerIntegration:
713
713
 
714
714
  if priorities_assessment['priorities']['rds_optimization']['status'] == 'outside_range':
715
715
  priorities_assessment['recommendations'].append(
716
- "RDS optimization potential outside $5K-24K range - review Multi-AZ configurations"
716
+ "RDS optimization potential outside measurable range range - review Multi-AZ configurations"
717
717
  )
718
718
 
719
719
  return priorities_assessment
@@ -14,7 +14,7 @@ Features:
14
14
  - Thread-safe operations with concurrent access support
15
15
 
16
16
  Author: CloudOps Runbooks Team
17
- Version: 0.9.1
17
+ Version: latest version
18
18
  """
19
19
 
20
20
  import asyncio