runbooks 1.0.0__py3-none-any.whl → 1.0.1__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 (77) hide show
  1. runbooks/__init__.py +1 -1
  2. runbooks/cfat/WEIGHT_CONFIG_README.md +368 -0
  3. runbooks/cfat/app.ts +27 -19
  4. runbooks/cfat/assessment/runner.py +6 -5
  5. runbooks/cfat/tests/test_weight_configuration.ts +449 -0
  6. runbooks/cfat/weight_config.ts +574 -0
  7. runbooks/common/__init__.py +26 -9
  8. runbooks/common/aws_pricing.py +1070 -105
  9. runbooks/common/date_utils.py +115 -0
  10. runbooks/common/enhanced_exception_handler.py +10 -7
  11. runbooks/common/mcp_cost_explorer_integration.py +5 -4
  12. runbooks/common/profile_utils.py +76 -115
  13. runbooks/common/rich_utils.py +3 -3
  14. runbooks/finops/dashboard_runner.py +47 -28
  15. runbooks/finops/ebs_optimizer.py +56 -9
  16. runbooks/finops/enhanced_trend_visualization.py +7 -2
  17. runbooks/finops/finops_dashboard.py +6 -5
  18. runbooks/finops/iam_guidance.py +6 -1
  19. runbooks/finops/nat_gateway_optimizer.py +46 -27
  20. runbooks/finops/tests/test_integration.py +3 -1
  21. runbooks/finops/vpc_cleanup_optimizer.py +22 -29
  22. runbooks/inventory/core/collector.py +51 -28
  23. runbooks/inventory/discovery.md +197 -247
  24. runbooks/inventory/inventory_modules.py +2 -2
  25. runbooks/inventory/list_ec2_instances.py +3 -3
  26. runbooks/inventory/organizations_discovery.py +13 -8
  27. runbooks/inventory/unified_validation_engine.py +2 -15
  28. runbooks/main.py +74 -32
  29. runbooks/operate/base.py +9 -6
  30. runbooks/operate/deployment_framework.py +5 -4
  31. runbooks/operate/deployment_validator.py +6 -5
  32. runbooks/operate/mcp_integration.py +6 -5
  33. runbooks/operate/networking_cost_heatmap.py +17 -13
  34. runbooks/operate/vpc_operations.py +52 -12
  35. runbooks/remediation/base.py +3 -1
  36. runbooks/remediation/commons.py +5 -5
  37. runbooks/remediation/commvault_ec2_analysis.py +66 -18
  38. runbooks/remediation/config/accounts_example.json +31 -0
  39. runbooks/remediation/multi_account.py +120 -7
  40. runbooks/remediation/remediation_cli.py +710 -0
  41. runbooks/remediation/universal_account_discovery.py +377 -0
  42. runbooks/security/compliance_automation_engine.py +99 -20
  43. runbooks/security/config/__init__.py +24 -0
  44. runbooks/security/config/compliance_config.py +255 -0
  45. runbooks/security/config/compliance_weights_example.json +22 -0
  46. runbooks/security/config_template_generator.py +500 -0
  47. runbooks/security/security_cli.py +377 -0
  48. runbooks/validation/cli.py +8 -7
  49. runbooks/validation/comprehensive_2way_validator.py +26 -15
  50. runbooks/validation/mcp_validator.py +62 -8
  51. runbooks/vpc/config.py +32 -7
  52. runbooks/vpc/cross_account_session.py +5 -1
  53. runbooks/vpc/heatmap_engine.py +21 -14
  54. runbooks/vpc/mcp_no_eni_validator.py +115 -36
  55. runbooks/vpc/runbooks_adapter.py +33 -12
  56. runbooks/vpc/tests/conftest.py +4 -2
  57. runbooks/vpc/tests/test_cost_engine.py +3 -1
  58. {runbooks-1.0.0.dist-info → runbooks-1.0.1.dist-info}/METADATA +1 -1
  59. {runbooks-1.0.0.dist-info → runbooks-1.0.1.dist-info}/RECORD +63 -65
  60. runbooks/finops/runbooks.inventory.organizations_discovery.log +0 -0
  61. runbooks/finops/runbooks.security.report_generator.log +0 -0
  62. runbooks/finops/runbooks.security.run_script.log +0 -0
  63. runbooks/finops/runbooks.security.security_export.log +0 -0
  64. runbooks/finops/tests/results_test_finops_dashboard.xml +0 -1
  65. runbooks/inventory/artifacts/scale-optimize-status.txt +0 -12
  66. runbooks/inventory/runbooks.inventory.organizations_discovery.log +0 -0
  67. runbooks/inventory/runbooks.security.report_generator.log +0 -0
  68. runbooks/inventory/runbooks.security.run_script.log +0 -0
  69. runbooks/inventory/runbooks.security.security_export.log +0 -0
  70. runbooks/vpc/runbooks.inventory.organizations_discovery.log +0 -0
  71. runbooks/vpc/runbooks.security.report_generator.log +0 -0
  72. runbooks/vpc/runbooks.security.run_script.log +0 -0
  73. runbooks/vpc/runbooks.security.security_export.log +0 -0
  74. {runbooks-1.0.0.dist-info → runbooks-1.0.1.dist-info}/WHEEL +0 -0
  75. {runbooks-1.0.0.dist-info → runbooks-1.0.1.dist-info}/entry_points.txt +0 -0
  76. {runbooks-1.0.0.dist-info → runbooks-1.0.1.dist-info}/licenses/LICENSE +0 -0
  77. {runbooks-1.0.0.dist-info → runbooks-1.0.1.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,115 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Dynamic Date Utilities for CloudOps Runbooks Platform
4
+
5
+ Replaces hardcoded 2024 dates with dynamic date generation following manager's
6
+ "No hardcoded values" requirement. Supports current month/year calculations
7
+ for all test data and AWS API time period generation.
8
+
9
+ Strategic Alignment: "Do one thing and do it well" - Focused date utility
10
+ KISS Principle: Simple, reusable date functions for all modules
11
+ """
12
+
13
+ from datetime import datetime, timedelta
14
+ from typing import Dict, Tuple
15
+
16
+
17
+ def get_current_year() -> int:
18
+ """Get current year dynamically."""
19
+ return datetime.now().year
20
+
21
+
22
+ def get_current_month_period() -> Dict[str, str]:
23
+ """
24
+ Generate current month's start and end dates for AWS API calls.
25
+
26
+ Returns:
27
+ Dict with 'Start' and 'End' keys in YYYY-MM-DD format
28
+ """
29
+ now = datetime.now()
30
+ start_date = now.replace(day=1).strftime('%Y-%m-%d')
31
+
32
+ # Get last day of current month
33
+ if now.month == 12:
34
+ next_month = now.replace(year=now.year + 1, month=1, day=1)
35
+ else:
36
+ next_month = now.replace(month=now.month + 1, day=1)
37
+
38
+ end_date = (next_month - timedelta(days=1)).strftime('%Y-%m-%d')
39
+
40
+ return {
41
+ 'Start': start_date,
42
+ 'End': end_date
43
+ }
44
+
45
+
46
+ def get_previous_month_period() -> Dict[str, str]:
47
+ """
48
+ Generate previous month's start and end dates for AWS API calls.
49
+
50
+ Returns:
51
+ Dict with 'Start' and 'End' keys in YYYY-MM-DD format
52
+ """
53
+ now = datetime.now()
54
+
55
+ # Get first day of previous month
56
+ if now.month == 1:
57
+ prev_month = now.replace(year=now.year - 1, month=12, day=1)
58
+ else:
59
+ prev_month = now.replace(month=now.month - 1, day=1)
60
+
61
+ start_date = prev_month.strftime('%Y-%m-%d')
62
+
63
+ # Get last day of previous month
64
+ end_date = (now.replace(day=1) - timedelta(days=1)).strftime('%Y-%m-%d')
65
+
66
+ return {
67
+ 'Start': start_date,
68
+ 'End': end_date
69
+ }
70
+
71
+
72
+ def get_test_date_period(days_back: int = 30) -> Dict[str, str]:
73
+ """
74
+ Generate test date periods for consistent test data.
75
+
76
+ Args:
77
+ days_back: Number of days back from today for start date
78
+
79
+ Returns:
80
+ Dict with 'Start' and 'End' keys in YYYY-MM-DD format
81
+ """
82
+ end_date = datetime.now().strftime('%Y-%m-%d')
83
+ start_date = (datetime.now() - timedelta(days=days_back)).strftime('%Y-%m-%d')
84
+
85
+ return {
86
+ 'Start': start_date,
87
+ 'End': end_date
88
+ }
89
+
90
+
91
+ def get_aws_cli_example_period() -> Tuple[str, str]:
92
+ """
93
+ Generate example date period for AWS CLI documentation.
94
+ Uses yesterday and today to ensure valid time range.
95
+
96
+ Returns:
97
+ Tuple of (start_date, end_date) in YYYY-MM-DD format
98
+ """
99
+ today = datetime.now()
100
+ yesterday = today - timedelta(days=1)
101
+
102
+ return (
103
+ yesterday.strftime('%Y-%m-%d'),
104
+ today.strftime('%Y-%m-%d')
105
+ )
106
+
107
+
108
+ def get_collection_timestamp() -> str:
109
+ """
110
+ Generate collection timestamp for test data.
111
+
112
+ Returns:
113
+ ISO format timestamp string
114
+ """
115
+ return datetime.now().isoformat()
@@ -972,13 +972,15 @@ class EnterpriseExceptionHandler:
972
972
  }
973
973
 
974
974
  def _initialize_profile_recommendations(self) -> Dict[str, List[str]]:
975
- """Initialize profile recommendations for different operations."""
975
+ """Initialize profile recommendations for different operations with universal support."""
976
+ import os
977
+ # Environment variable-driven profile recommendations
976
978
  return {
977
- "inventory": ["ams-admin-ReadOnlyAccess-909135376185", "ams-centralised-ops-ReadOnlyAccess-335083429030"],
978
- "operate": ["ams-centralised-ops-ReadOnlyAccess-335083429030", "ams-admin-ReadOnlyAccess-909135376185"],
979
- "finops": ["ams-admin-Billing-ReadOnlyAccess-909135376185", "ams-admin-ReadOnlyAccess-909135376185"],
980
- "security": ["ams-admin-ReadOnlyAccess-909135376185"],
981
- "cfat": ["ams-admin-ReadOnlyAccess-909135376185"],
979
+ "inventory": [os.getenv("MANAGEMENT_PROFILE", "management-profile"), os.getenv("CENTRALISED_OPS_PROFILE", "ops-profile")],
980
+ "operate": [os.getenv("CENTRALISED_OPS_PROFILE", "ops-profile"), os.getenv("MANAGEMENT_PROFILE", "management-profile")],
981
+ "finops": [os.getenv("BILLING_PROFILE", "billing-profile"), os.getenv("MANAGEMENT_PROFILE", "management-profile")],
982
+ "security": [os.getenv("MANAGEMENT_PROFILE", "management-profile")],
983
+ "cfat": [os.getenv("MANAGEMENT_PROFILE", "management-profile")],
982
984
  }
983
985
 
984
986
  def _analyze_aws_error(self, error: ClientError, error_code: str, service: str, operation: str) -> Dict[str, Any]:
@@ -991,7 +993,8 @@ class EnterpriseExceptionHandler:
991
993
  # Service-specific adjustments
992
994
  if service == "ce" and error_code == "AccessDenied":
993
995
  # Cost Explorer requires special billing permissions
994
- base_analysis["recommended_profiles"] = ["ams-admin-Billing-ReadOnlyAccess-909135376185"]
996
+ import os
997
+ base_analysis["recommended_profiles"] = [os.getenv("BILLING_PROFILE", "billing-profile")]
995
998
 
996
999
  return base_analysis
997
1000
 
@@ -79,10 +79,11 @@ class MCPCostExplorerIntegration:
79
79
  performance_target_seconds: Performance target for operations
80
80
  """
81
81
 
82
- # Profile configuration with intelligent defaults
83
- self.billing_profile = billing_profile or "ams-admin-Billing-ReadOnlyAccess-909135376185"
84
- self.management_profile = management_profile or "ams-admin-ReadOnlyAccess-909135376185"
85
- self.single_account_profile = single_account_profile or "ams-shared-services-non-prod-ReadOnlyAccess-499201730520"
82
+ # Profile configuration with universal environment support
83
+ from runbooks.common.profile_utils import get_profile_for_operation
84
+ self.billing_profile = billing_profile or get_profile_for_operation("billing", None)
85
+ self.management_profile = management_profile or get_profile_for_operation("management", None)
86
+ self.single_account_profile = single_account_profile or get_profile_for_operation("single_account", None)
86
87
 
87
88
  # Validation configuration
88
89
  self.tolerance_percent = tolerance_percent
@@ -1,19 +1,21 @@
1
1
  #!/usr/bin/env python3
2
2
  """
3
- Profile Management Utilities for CloudOps Runbooks Platform
3
+ Universal AWS Profile Management for CloudOps Runbooks Platform
4
4
 
5
- This module provides centralized AWS profile management with enterprise-grade
6
- three-tier priority system extracted from proven FinOps success patterns.
5
+ This module provides truly universal AWS profile management that works with ANY AWS setup:
6
+ - Single account setups
7
+ - Multi-account setups
8
+ - Any profile naming convention
9
+ - No specific environment variable requirements
7
10
 
8
11
  Features:
9
- - Three-tier priority: User > Environment > Default
10
- - Multi-profile enterprise architecture support
11
- - Consistent session creation across all modules
12
- - Rich CLI integration for user feedback
13
- - Profile validation and error handling
12
+ - Universal compatibility: User --profile AWS_PROFILE → "default"
13
+ - Works with ANY AWS profile names (not just specific test profiles)
14
+ - No hardcoded environment variable assumptions
15
+ - Simple, reliable profile selection for all users
14
16
 
15
17
  Author: CloudOps Runbooks Team
16
- Version: 0.9.0
18
+ Version: 1.0.0 - Universal Compatibility
17
19
  """
18
20
 
19
21
  import os
@@ -24,35 +26,25 @@ import boto3
24
26
 
25
27
  from runbooks.common.rich_utils import console
26
28
 
27
- # Profile cache to reduce duplicate calls (enterprise performance optimization)
29
+ # Profile cache to reduce duplicate calls (performance optimization)
28
30
  _profile_cache = {}
29
31
  _cache_timestamp = None
30
32
  _cache_ttl = 300 # 5 minutes cache TTL
31
33
 
32
- # Enterprise AWS profile mappings with fallback defaults
33
- ENV_PROFILE_MAP = {
34
- "billing": os.getenv("BILLING_PROFILE"),
35
- "management": os.getenv("MANAGEMENT_PROFILE"),
36
- "operational": os.getenv("CENTRALISED_OPS_PROFILE"),
37
- }
38
-
39
- # Fallback defaults if environment variables are not set - NO hardcoded defaults
40
- DEFAULT_PROFILE = os.getenv("AWS_PROFILE") or "default" # "default" is AWS boto3 expected fallback
41
-
42
34
 
43
35
  def get_profile_for_operation(operation_type: str, user_specified_profile: Optional[str] = None) -> str:
44
36
  """
45
- Get the appropriate AWS profile based on operation type using proven three-tier priority system.
37
+ Universal AWS profile selection that works with ANY AWS setup.
46
38
 
47
- PRIORITY ORDER (Enterprise Success Pattern):
39
+ SIMPLE PRIORITY ORDER (Universal Compatibility):
48
40
  1. User-specified profile (--profile parameter) - HIGHEST PRIORITY
49
- 2. Environment variables for specialized operations - FALLBACK ONLY
50
- 3. Default profile - LAST RESORT
41
+ 2. AWS_PROFILE environment variable - STANDARD AWS CONVENTION
42
+ 3. "default" profile - AWS STANDARD FALLBACK
51
43
 
52
- This pattern extracted from FinOps module achieving 99.9996% accuracy and 280% ROI.
44
+ Works with ANY profile names and ANY AWS setup - no specific environment variable requirements.
53
45
 
54
46
  Args:
55
- operation_type: Type of operation ('billing', 'management', 'operational')
47
+ operation_type: Type of operation (informational only, not used for profile selection)
56
48
  user_specified_profile: Profile specified by user via --profile parameter
57
49
 
58
50
  Returns:
@@ -63,7 +55,7 @@ def get_profile_for_operation(operation_type: str, user_specified_profile: Optio
63
55
  """
64
56
  global _profile_cache, _cache_timestamp
65
57
 
66
- # Check cache first to reduce duplicate calls (enterprise performance optimization)
58
+ # Check cache first to reduce duplicate calls (performance optimization)
67
59
  cache_key = f"{operation_type}:{user_specified_profile or 'None'}"
68
60
  current_time = time.time()
69
61
 
@@ -82,33 +74,26 @@ def get_profile_for_operation(operation_type: str, user_specified_profile: Optio
82
74
  # PRIORITY 1: User-specified profile ALWAYS takes precedence
83
75
  if user_specified_profile and user_specified_profile != "default":
84
76
  if user_specified_profile in available_profiles:
85
- console.log(f"[green]Using user-specified profile for {operation_type}: {user_specified_profile}[/]")
77
+ console.log(f"[green]Using user-specified profile: {user_specified_profile}[/]")
86
78
  # Cache the result to reduce duplicate calls
87
79
  _profile_cache[cache_key] = user_specified_profile
88
80
  return user_specified_profile
89
81
  else:
90
- console.log(f"[red]Error: User-specified profile '{user_specified_profile}' not found in AWS config[/]")
91
- # Don't fall back - user explicitly chose this profile
82
+ console.log(f"[red]Error: Profile '{user_specified_profile}' not found in AWS config[/]")
83
+ console.log(f"[yellow]Available profiles: {', '.join(available_profiles)}[/]")
92
84
  raise SystemExit(1)
93
85
 
94
- # PRIORITY 2: Environment variables (only when no user input)
95
- profile_map = {
96
- "billing": os.getenv("AWS_BILLING_PROFILE") or os.getenv("BILLING_PROFILE"),
97
- "management": os.getenv("AWS_MANAGEMENT_PROFILE") or os.getenv("MANAGEMENT_PROFILE"),
98
- "operational": os.getenv("AWS_CENTRALISED_OPS_PROFILE") or os.getenv("CENTRALISED_OPS_PROFILE"),
99
- "single_account": os.getenv("AWS_SINGLE_ACCOUNT_PROFILE") or os.getenv("SINGLE_AWS_PROFILE"),
100
- }
101
-
102
- env_profile = profile_map.get(operation_type)
103
- if env_profile and env_profile in available_profiles:
104
- console.log(f"[dim cyan]Using {operation_type} profile from environment: {env_profile}[/]")
86
+ # PRIORITY 2: AWS_PROFILE environment variable (standard AWS convention)
87
+ aws_profile = os.getenv("AWS_PROFILE")
88
+ if aws_profile and aws_profile in available_profiles:
89
+ console.log(f"[dim cyan]Using AWS_PROFILE environment variable: {aws_profile}[/]")
105
90
  # Cache the result to reduce duplicate calls
106
- _profile_cache[cache_key] = env_profile
107
- return env_profile
91
+ _profile_cache[cache_key] = aws_profile
92
+ return aws_profile
108
93
 
109
- # PRIORITY 3: Default profile (last resort)
110
- default_profile = user_specified_profile or "default"
111
- console.log(f"[yellow]No {operation_type} profile found, using default: {default_profile}[/]")
94
+ # PRIORITY 3: Default profile (AWS standard fallback)
95
+ default_profile = "default"
96
+ console.log(f"[yellow]Using default AWS profile: {default_profile}[/]")
112
97
  # Cache the result to reduce duplicate calls
113
98
  _profile_cache[cache_key] = default_profile
114
99
  return default_profile
@@ -116,11 +101,11 @@ def get_profile_for_operation(operation_type: str, user_specified_profile: Optio
116
101
 
117
102
  def resolve_profile_for_operation_silent(operation_type: str, user_specified_profile: Optional[str] = None) -> str:
118
103
  """
119
- Resolve AWS profile for operation type without logging (for display purposes).
120
- Uses the same logic as get_profile_for_operation but without console output.
104
+ Universal AWS profile resolution without logging (for display purposes).
105
+ Uses the same universal logic as get_profile_for_operation but without console output.
121
106
 
122
107
  Args:
123
- operation_type: Type of operation ('billing', 'management', 'operational')
108
+ operation_type: Type of operation (informational only, not used for profile selection)
124
109
  user_specified_profile: Profile specified by user via --profile parameter
125
110
 
126
111
  Returns:
@@ -139,78 +124,72 @@ def resolve_profile_for_operation_silent(operation_type: str, user_specified_pro
139
124
  # Don't fall back - user explicitly chose this profile
140
125
  raise SystemExit(1)
141
126
 
142
- # PRIORITY 2: Environment variables (only when no user input)
143
- profile_map = {
144
- "billing": os.getenv("AWS_BILLING_PROFILE") or os.getenv("BILLING_PROFILE"),
145
- "management": os.getenv("AWS_MANAGEMENT_PROFILE") or os.getenv("MANAGEMENT_PROFILE"),
146
- "operational": os.getenv("AWS_CENTRALISED_OPS_PROFILE") or os.getenv("CENTRALISED_OPS_PROFILE"),
147
- "single_account": os.getenv("AWS_SINGLE_ACCOUNT_PROFILE") or os.getenv("SINGLE_AWS_PROFILE"),
148
- }
149
-
150
- env_profile = profile_map.get(operation_type)
151
- if env_profile and env_profile in available_profiles:
152
- return env_profile
127
+ # PRIORITY 2: AWS_PROFILE environment variable (standard AWS convention)
128
+ aws_profile = os.getenv("AWS_PROFILE")
129
+ if aws_profile and aws_profile in available_profiles:
130
+ return aws_profile
153
131
 
154
- # PRIORITY 3: Default profile (last resort)
155
- return user_specified_profile or "default"
132
+ # PRIORITY 3: Default profile (AWS standard fallback)
133
+ return "default"
156
134
 
157
135
 
158
136
  def create_cost_session(profile: Optional[str] = None) -> boto3.Session:
159
137
  """
160
- Create a boto3 session specifically for cost operations.
161
- User-specified profile takes priority over BILLING_PROFILE environment variable.
138
+ Create a boto3 session for cost operations with universal profile support.
139
+ Works with ANY AWS profile configuration.
162
140
 
163
141
  Args:
164
142
  profile: User-specified profile (from --profile parameter)
165
143
 
166
144
  Returns:
167
- boto3.Session: Session configured for cost operations
145
+ boto3.Session: Session configured for AWS operations
168
146
  """
169
- cost_profile = get_profile_for_operation("billing", profile)
170
- return boto3.Session(profile_name=cost_profile)
147
+ selected_profile = get_profile_for_operation("cost", profile)
148
+ return boto3.Session(profile_name=selected_profile)
171
149
 
172
150
 
173
151
  def create_management_session(profile: Optional[str] = None) -> boto3.Session:
174
152
  """
175
- Create a boto3 session specifically for management operations.
176
- User-specified profile takes priority over MANAGEMENT_PROFILE environment variable.
153
+ Create a boto3 session for management operations with universal profile support.
154
+ Works with ANY AWS profile configuration.
177
155
 
178
156
  Args:
179
157
  profile: User-specified profile (from --profile parameter)
180
158
 
181
159
  Returns:
182
- boto3.Session: Session configured for management operations
160
+ boto3.Session: Session configured for AWS operations
183
161
  """
184
- mgmt_profile = get_profile_for_operation("management", profile)
185
- return boto3.Session(profile_name=mgmt_profile)
162
+ selected_profile = get_profile_for_operation("management", profile)
163
+ return boto3.Session(profile_name=selected_profile)
186
164
 
187
165
 
188
166
  def create_operational_session(profile: Optional[str] = None) -> boto3.Session:
189
167
  """
190
- Create a boto3 session specifically for operational tasks.
191
- User-specified profile takes priority over CENTRALISED_OPS_PROFILE environment variable.
168
+ Create a boto3 session for operational tasks with universal profile support.
169
+ Works with ANY AWS profile configuration.
192
170
 
193
171
  Args:
194
172
  profile: User-specified profile (from --profile parameter)
195
173
 
196
174
  Returns:
197
- boto3.Session: Session configured for operational tasks
175
+ boto3.Session: Session configured for AWS operations
198
176
  """
199
- ops_profile = get_profile_for_operation("operational", profile)
200
- return boto3.Session(profile_name=ops_profile)
177
+ selected_profile = get_profile_for_operation("operational", profile)
178
+ return boto3.Session(profile_name=selected_profile)
201
179
 
202
180
 
203
- def get_enterprise_profile_mapping() -> Dict[str, Optional[str]]:
181
+ def get_current_profile_info() -> Dict[str, Optional[str]]:
204
182
  """
205
- Get current enterprise profile mapping from environment variables.
183
+ Get current AWS profile information using universal approach.
184
+ Works with ANY AWS setup without hardcoded environment variable assumptions.
206
185
 
207
186
  Returns:
208
- Dict mapping operation types to their environment profile values
187
+ Dict with current profile information
209
188
  """
210
189
  return {
211
- "billing": os.getenv("BILLING_PROFILE"),
212
- "management": os.getenv("MANAGEMENT_PROFILE"),
213
- "operational": os.getenv("CENTRALISED_OPS_PROFILE"),
190
+ "aws_profile": os.getenv("AWS_PROFILE"),
191
+ "default_profile": "default",
192
+ "available_profiles": boto3.Session().available_profiles
214
193
  }
215
194
 
216
195
 
@@ -247,10 +226,10 @@ def validate_profile_access(profile_name: str, operation_type: str = "general")
247
226
 
248
227
  def get_available_profiles_for_validation() -> list:
249
228
  """
250
- Get available AWS profiles for validation - universal compatibility approach.
229
+ Get available AWS profiles for validation - truly universal approach.
251
230
 
252
- Returns all configured AWS profiles for validation without hardcoded assumptions.
253
- Supports any AWS setup: single account, multi-account, any profile naming convention.
231
+ Returns all configured AWS profiles for validation without ANY hardcoded assumptions.
232
+ Works with any AWS setup: single account, multi-account, any profile naming convention.
254
233
 
255
234
  Returns:
256
235
  list: Available AWS profile names for validation
@@ -259,38 +238,20 @@ def get_available_profiles_for_validation() -> list:
259
238
  # Get all available profiles from AWS CLI configuration
260
239
  available_profiles = boto3.Session().available_profiles
261
240
 
262
- # Filter out common system profiles that shouldn't be tested
263
- system_profiles = {'default', 'none', 'null', ''}
264
-
265
- # Return profiles for validation, including default if it's the only one
241
+ # Start with AWS_PROFILE if set
266
242
  validation_profiles = []
243
+ aws_profile = os.getenv("AWS_PROFILE")
244
+ if aws_profile and aws_profile in available_profiles:
245
+ validation_profiles.append(aws_profile)
267
246
 
268
- # Add environment variable profiles if they exist
269
- env_profiles = [
270
- os.getenv("AWS_BILLING_PROFILE"),
271
- os.getenv("AWS_MANAGEMENT_PROFILE"),
272
- os.getenv("AWS_CENTRALISED_OPS_PROFILE"),
273
- os.getenv("AWS_SINGLE_ACCOUNT_PROFILE"),
274
- os.getenv("BILLING_PROFILE"),
275
- os.getenv("MANAGEMENT_PROFILE"),
276
- os.getenv("CENTRALISED_OPS_PROFILE"),
277
- os.getenv("SINGLE_AWS_PROFILE"),
278
- ]
279
-
280
- # Add valid environment profiles
281
- for profile in env_profiles:
282
- if profile and profile in available_profiles and profile not in validation_profiles:
247
+ # Add all other available profiles (universal approach)
248
+ for profile in available_profiles:
249
+ if profile not in validation_profiles:
283
250
  validation_profiles.append(profile)
284
251
 
285
- # If no environment profiles found, use available profiles (universal approach)
252
+ # Ensure we have at least one profile to test
286
253
  if not validation_profiles:
287
- for profile in available_profiles:
288
- if profile not in system_profiles:
289
- validation_profiles.append(profile)
290
-
291
- # Always include 'default' if available and no other profiles found
292
- if not validation_profiles and 'default' in available_profiles:
293
- validation_profiles.append('default')
254
+ validation_profiles = ['default']
294
255
 
295
256
  return validation_profiles
296
257
 
@@ -303,10 +264,10 @@ def get_available_profiles_for_validation() -> list:
303
264
  __all__ = [
304
265
  "get_profile_for_operation",
305
266
  "resolve_profile_for_operation_silent",
306
- "create_cost_session",
267
+ "create_cost_session",
307
268
  "create_management_session",
308
269
  "create_operational_session",
309
- "get_enterprise_profile_mapping",
270
+ "get_current_profile_info",
310
271
  "validate_profile_access",
311
272
  "get_available_profiles_for_validation",
312
273
  ]
@@ -358,8 +358,8 @@ def create_display_profile_name(profile_name: str, max_length: int = 25, context
358
358
  meaningful information for identification. Full names remain available for AWS API calls.
359
359
 
360
360
  Examples:
361
- 'ams-admin-Billing-ReadOnlyAccess-909135376185' → 'ams-admin-Billing-9091...'
362
- 'ams-centralised-ops-ReadOnlyAccess-335083429030' → 'ams-centralised-ops-3350...'
361
+ 'your-admin-Billing-ReadOnlyAccess-123456789012' → 'your-admin-Billing-1234...'
362
+ 'your-centralised-ops-ReadOnlyAccess-987654321098' → 'your-centralised-ops-9876...'
363
363
  'short-profile' → 'short-profile' (no truncation needed)
364
364
 
365
365
  Args:
@@ -398,7 +398,7 @@ def create_display_profile_name(profile_name: str, max_length: int = 25, context
398
398
 
399
399
  # Strategy 1: Keep meaningful prefix + account ID suffix
400
400
  if len(parts) >= 4 and parts[-1].isdigit():
401
- # Enterprise pattern: ams-admin-Billing-ReadOnlyAccess-909135376185
401
+ # Enterprise pattern: your-admin-Billing-ReadOnlyAccess-123456789012
402
402
  account_id = parts[-1]
403
403
  prefix_parts = parts[:-2] # Skip permissions part for brevity
404
404
 
@@ -132,38 +132,57 @@ def estimate_resource_costs(session: boto3.Session, regions: List[str]) -> Dict[
132
132
  }
133
133
 
134
134
  try:
135
- # EC2 Instance cost estimation with performance optimization
135
+ # EC2 Instance cost estimation using dynamic AWS pricing
136
136
  profile_name = session.profile_name if hasattr(session, "profile_name") else None
137
137
  ec2_data = ec2_summary(session, regions, profile_name)
138
+
139
+ from ..common.aws_pricing import get_ec2_monthly_cost, get_aws_pricing_engine
140
+ from ..common.rich_utils import console
141
+
138
142
  for instance_type, count in ec2_data.items():
139
143
  if count > 0:
140
- # Estimate monthly cost based on instance type
141
- # Using approximate AWS pricing (simplified model)
142
- hourly_rates = {
143
- "t3.nano": 0.0052,
144
- "t3.micro": 0.0104,
145
- "t3.small": 0.0208,
146
- "t3.medium": 0.0416,
147
- "t3.large": 0.0832,
148
- "t3.xlarge": 0.1664,
149
- "t2.nano": 0.0058,
150
- "t2.micro": 0.0116,
151
- "t2.small": 0.023,
152
- "m5.large": 0.096,
153
- "m5.xlarge": 0.192,
154
- "m5.2xlarge": 0.384,
155
- "c5.large": 0.085,
156
- "c5.xlarge": 0.17,
157
- "c5.2xlarge": 0.34,
158
- "r5.large": 0.126,
159
- "r5.xlarge": 0.252,
160
- "r5.2xlarge": 0.504,
161
- }
162
-
163
- base_type = instance_type.lower()
164
- hourly_rate = hourly_rates.get(base_type, 0.05) # Default rate
165
- monthly_cost = hourly_rate * 24 * 30 * count # Hours * days * instances
166
- estimated_costs["EC2-Instance"] += monthly_cost
144
+ try:
145
+ # Use dynamic AWS pricing - NO hardcoded values
146
+ # Assume primary region for cost estimation
147
+ primary_region = regions[0] if regions else "us-east-1"
148
+ monthly_cost_per_instance = get_ec2_monthly_cost(instance_type, primary_region)
149
+ total_monthly_cost = monthly_cost_per_instance * count
150
+ estimated_costs["EC2-Instance"] += total_monthly_cost
151
+
152
+ console.print(
153
+ f"[dim]Dynamic pricing: {count}x {instance_type} = "
154
+ f"${total_monthly_cost:.2f}/month[/]"
155
+ )
156
+
157
+ except Exception as e:
158
+ console.print(
159
+ f"[yellow]⚠ Warning: Could not get dynamic pricing for {instance_type}: {e}[/yellow]"
160
+ )
161
+
162
+ try:
163
+ # Use fallback pricing engine with AWS patterns
164
+ pricing_engine = get_aws_pricing_engine(enable_fallback=True)
165
+ primary_region = regions[0] if regions else "us-east-1"
166
+ result = pricing_engine.get_ec2_instance_pricing(instance_type, primary_region)
167
+ total_monthly_cost = result.monthly_cost * count
168
+ estimated_costs["EC2-Instance"] += total_monthly_cost
169
+
170
+ console.print(
171
+ f"[dim]Fallback pricing: {count}x {instance_type} = "
172
+ f"${total_monthly_cost:.2f}/month[/]"
173
+ )
174
+
175
+ except Exception as fallback_error:
176
+ console.print(
177
+ f"[red]⚠ ERROR: All pricing methods failed for {instance_type}: {fallback_error}[/red]"
178
+ )
179
+ console.print(
180
+ f"[red]Skipping cost estimation for {count}x {instance_type}[/red]"
181
+ )
182
+ logger.error(
183
+ f"ENTERPRISE VIOLATION: Cannot estimate cost for {instance_type} "
184
+ f"without hardcoded values. Instance type skipped."
185
+ )
167
186
 
168
187
  # Add some EC2-Other costs (EBS, snapshots, etc.)
169
188
  estimated_costs["EC2-Other"] = estimated_costs["EC2-Instance"] * 0.3