runbooks 1.1.3__py3-none-any.whl → 1.1.5__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 +31 -2
- runbooks/__init___optimized.py +18 -4
- runbooks/_platform/__init__.py +1 -5
- runbooks/_platform/core/runbooks_wrapper.py +141 -138
- runbooks/aws2/accuracy_validator.py +812 -0
- runbooks/base.py +7 -0
- runbooks/cfat/WEIGHT_CONFIG_README.md +1 -1
- runbooks/cfat/assessment/compliance.py +8 -8
- runbooks/cfat/assessment/runner.py +1 -0
- runbooks/cfat/cloud_foundations_assessment.py +227 -239
- runbooks/cfat/models.py +6 -2
- runbooks/cfat/tests/__init__.py +6 -1
- runbooks/cli/__init__.py +13 -0
- runbooks/cli/commands/cfat.py +274 -0
- runbooks/cli/commands/finops.py +1164 -0
- runbooks/cli/commands/inventory.py +379 -0
- runbooks/cli/commands/operate.py +239 -0
- runbooks/cli/commands/security.py +248 -0
- runbooks/cli/commands/validation.py +825 -0
- runbooks/cli/commands/vpc.py +310 -0
- runbooks/cli/registry.py +107 -0
- runbooks/cloudops/__init__.py +23 -30
- runbooks/cloudops/base.py +96 -107
- runbooks/cloudops/cost_optimizer.py +549 -547
- runbooks/cloudops/infrastructure_optimizer.py +5 -4
- runbooks/cloudops/interfaces.py +226 -227
- runbooks/cloudops/lifecycle_manager.py +5 -4
- runbooks/cloudops/mcp_cost_validation.py +252 -235
- runbooks/cloudops/models.py +78 -53
- runbooks/cloudops/monitoring_automation.py +5 -4
- runbooks/cloudops/notebook_framework.py +179 -215
- runbooks/cloudops/security_enforcer.py +125 -159
- runbooks/common/accuracy_validator.py +11 -0
- runbooks/common/aws_pricing.py +349 -326
- runbooks/common/aws_pricing_api.py +211 -212
- runbooks/common/aws_profile_manager.py +341 -0
- runbooks/common/aws_utils.py +75 -80
- runbooks/common/business_logic.py +127 -105
- runbooks/common/cli_decorators.py +36 -60
- runbooks/common/comprehensive_cost_explorer_integration.py +456 -464
- runbooks/common/cross_account_manager.py +198 -205
- runbooks/common/date_utils.py +27 -39
- runbooks/common/decorators.py +235 -0
- runbooks/common/dry_run_examples.py +173 -208
- runbooks/common/dry_run_framework.py +157 -155
- runbooks/common/enhanced_exception_handler.py +15 -4
- runbooks/common/enhanced_logging_example.py +50 -64
- runbooks/common/enhanced_logging_integration_example.py +65 -37
- runbooks/common/env_utils.py +16 -16
- runbooks/common/error_handling.py +40 -38
- runbooks/common/lazy_loader.py +41 -23
- runbooks/common/logging_integration_helper.py +79 -86
- runbooks/common/mcp_cost_explorer_integration.py +478 -495
- runbooks/common/mcp_integration.py +63 -74
- runbooks/common/memory_optimization.py +140 -118
- runbooks/common/module_cli_base.py +37 -58
- runbooks/common/organizations_client.py +176 -194
- runbooks/common/patterns.py +204 -0
- runbooks/common/performance_monitoring.py +67 -71
- runbooks/common/performance_optimization_engine.py +283 -274
- runbooks/common/profile_utils.py +248 -39
- runbooks/common/rich_utils.py +643 -92
- runbooks/common/sre_performance_suite.py +177 -186
- runbooks/enterprise/__init__.py +1 -1
- runbooks/enterprise/logging.py +144 -106
- runbooks/enterprise/security.py +187 -204
- runbooks/enterprise/validation.py +43 -56
- runbooks/finops/__init__.py +29 -33
- runbooks/finops/account_resolver.py +1 -1
- runbooks/finops/advanced_optimization_engine.py +980 -0
- runbooks/finops/automation_core.py +268 -231
- runbooks/finops/business_case_config.py +184 -179
- runbooks/finops/cli.py +660 -139
- runbooks/finops/commvault_ec2_analysis.py +157 -164
- runbooks/finops/compute_cost_optimizer.py +336 -320
- runbooks/finops/config.py +20 -20
- runbooks/finops/cost_optimizer.py +488 -622
- runbooks/finops/cost_processor.py +332 -214
- runbooks/finops/dashboard_runner.py +1006 -172
- runbooks/finops/ebs_cost_optimizer.py +991 -657
- runbooks/finops/elastic_ip_optimizer.py +317 -257
- runbooks/finops/enhanced_mcp_integration.py +340 -0
- runbooks/finops/enhanced_progress.py +40 -37
- runbooks/finops/enhanced_trend_visualization.py +3 -2
- runbooks/finops/enterprise_wrappers.py +230 -292
- runbooks/finops/executive_export.py +203 -160
- runbooks/finops/helpers.py +130 -288
- runbooks/finops/iam_guidance.py +1 -1
- runbooks/finops/infrastructure/__init__.py +80 -0
- runbooks/finops/infrastructure/commands.py +506 -0
- runbooks/finops/infrastructure/load_balancer_optimizer.py +866 -0
- runbooks/finops/infrastructure/vpc_endpoint_optimizer.py +832 -0
- runbooks/finops/markdown_exporter.py +338 -175
- runbooks/finops/mcp_validator.py +1952 -0
- runbooks/finops/nat_gateway_optimizer.py +1513 -482
- runbooks/finops/network_cost_optimizer.py +657 -587
- runbooks/finops/notebook_utils.py +226 -188
- runbooks/finops/optimization_engine.py +1136 -0
- runbooks/finops/optimizer.py +25 -29
- runbooks/finops/rds_snapshot_optimizer.py +367 -411
- runbooks/finops/reservation_optimizer.py +427 -363
- runbooks/finops/scenario_cli_integration.py +77 -78
- runbooks/finops/scenarios.py +1278 -439
- runbooks/finops/schemas.py +218 -182
- runbooks/finops/snapshot_manager.py +2289 -0
- runbooks/finops/tests/test_finops_dashboard.py +3 -3
- runbooks/finops/tests/test_reference_images_validation.py +2 -2
- runbooks/finops/tests/test_single_account_features.py +17 -17
- runbooks/finops/tests/validate_test_suite.py +1 -1
- runbooks/finops/types.py +3 -3
- runbooks/finops/validation_framework.py +263 -269
- runbooks/finops/vpc_cleanup_exporter.py +191 -146
- runbooks/finops/vpc_cleanup_optimizer.py +593 -575
- runbooks/finops/workspaces_analyzer.py +171 -182
- runbooks/hitl/enhanced_workflow_engine.py +1 -1
- runbooks/integration/__init__.py +89 -0
- runbooks/integration/mcp_integration.py +1920 -0
- runbooks/inventory/CLAUDE.md +816 -0
- runbooks/inventory/README.md +3 -3
- runbooks/inventory/Tests/common_test_data.py +30 -30
- runbooks/inventory/__init__.py +2 -2
- runbooks/inventory/cloud_foundations_integration.py +144 -149
- runbooks/inventory/collectors/aws_comprehensive.py +28 -11
- runbooks/inventory/collectors/aws_networking.py +111 -101
- runbooks/inventory/collectors/base.py +4 -0
- runbooks/inventory/core/collector.py +495 -313
- runbooks/inventory/discovery.md +2 -2
- runbooks/inventory/drift_detection_cli.py +69 -96
- runbooks/inventory/find_ec2_security_groups.py +1 -1
- runbooks/inventory/inventory_mcp_cli.py +48 -46
- runbooks/inventory/list_rds_snapshots_aggregator.py +192 -208
- runbooks/inventory/mcp_inventory_validator.py +549 -465
- runbooks/inventory/mcp_vpc_validator.py +359 -442
- runbooks/inventory/organizations_discovery.py +56 -52
- runbooks/inventory/rich_inventory_display.py +33 -32
- runbooks/inventory/unified_validation_engine.py +278 -251
- runbooks/inventory/vpc_analyzer.py +733 -696
- runbooks/inventory/vpc_architecture_validator.py +293 -348
- runbooks/inventory/vpc_dependency_analyzer.py +382 -378
- runbooks/inventory/vpc_flow_analyzer.py +3 -3
- runbooks/main.py +152 -9147
- runbooks/main_final.py +91 -60
- runbooks/main_minimal.py +22 -10
- runbooks/main_optimized.py +131 -100
- runbooks/main_ultra_minimal.py +7 -2
- runbooks/mcp/__init__.py +36 -0
- runbooks/mcp/integration.py +679 -0
- runbooks/metrics/dora_metrics_engine.py +2 -2
- runbooks/monitoring/performance_monitor.py +9 -4
- runbooks/operate/dynamodb_operations.py +3 -1
- runbooks/operate/ec2_operations.py +145 -137
- runbooks/operate/iam_operations.py +146 -152
- runbooks/operate/mcp_integration.py +1 -1
- runbooks/operate/networking_cost_heatmap.py +33 -10
- runbooks/operate/privatelink_operations.py +1 -1
- runbooks/operate/rds_operations.py +223 -254
- runbooks/operate/s3_operations.py +107 -118
- runbooks/operate/vpc_endpoints.py +1 -1
- runbooks/operate/vpc_operations.py +648 -618
- runbooks/remediation/base.py +1 -1
- runbooks/remediation/commons.py +10 -7
- runbooks/remediation/commvault_ec2_analysis.py +71 -67
- runbooks/remediation/ec2_unattached_ebs_volumes.py +1 -0
- runbooks/remediation/multi_account.py +24 -21
- runbooks/remediation/rds_snapshot_list.py +91 -65
- runbooks/remediation/remediation_cli.py +92 -146
- runbooks/remediation/universal_account_discovery.py +83 -79
- runbooks/remediation/workspaces_list.py +49 -44
- runbooks/security/__init__.py +19 -0
- runbooks/security/assessment_runner.py +1150 -0
- runbooks/security/baseline_checker.py +812 -0
- runbooks/security/cloudops_automation_security_validator.py +509 -535
- runbooks/security/compliance_automation_engine.py +17 -17
- runbooks/security/config/__init__.py +2 -2
- runbooks/security/config/compliance_config.py +50 -50
- runbooks/security/config_template_generator.py +63 -76
- runbooks/security/enterprise_security_framework.py +1 -1
- runbooks/security/executive_security_dashboard.py +519 -508
- runbooks/security/integration_test_enterprise_security.py +5 -3
- runbooks/security/multi_account_security_controls.py +959 -1210
- runbooks/security/real_time_security_monitor.py +422 -444
- runbooks/security/run_script.py +1 -1
- runbooks/security/security_baseline_tester.py +1 -1
- runbooks/security/security_cli.py +143 -112
- runbooks/security/test_2way_validation.py +439 -0
- runbooks/security/two_way_validation_framework.py +852 -0
- runbooks/sre/mcp_reliability_engine.py +6 -6
- runbooks/sre/production_monitoring_framework.py +167 -177
- runbooks/tdd/__init__.py +15 -0
- runbooks/tdd/cli.py +1071 -0
- runbooks/utils/__init__.py +14 -17
- runbooks/utils/logger.py +7 -2
- runbooks/utils/version_validator.py +51 -48
- runbooks/validation/__init__.py +6 -6
- runbooks/validation/cli.py +9 -3
- runbooks/validation/comprehensive_2way_validator.py +754 -708
- runbooks/validation/mcp_validator.py +906 -228
- runbooks/validation/terraform_citations_validator.py +104 -115
- runbooks/validation/terraform_drift_detector.py +447 -451
- runbooks/vpc/README.md +617 -0
- runbooks/vpc/__init__.py +8 -1
- runbooks/vpc/analyzer.py +577 -0
- runbooks/vpc/cleanup_wrapper.py +476 -413
- runbooks/vpc/cli_cloudtrail_commands.py +339 -0
- runbooks/vpc/cli_mcp_validation_commands.py +480 -0
- runbooks/vpc/cloudtrail_audit_integration.py +717 -0
- runbooks/vpc/config.py +92 -97
- runbooks/vpc/cost_engine.py +411 -148
- runbooks/vpc/cost_explorer_integration.py +553 -0
- runbooks/vpc/cross_account_session.py +101 -106
- runbooks/vpc/enhanced_mcp_validation.py +917 -0
- runbooks/vpc/eni_gate_validator.py +961 -0
- runbooks/vpc/heatmap_engine.py +190 -162
- runbooks/vpc/mcp_no_eni_validator.py +681 -640
- runbooks/vpc/nat_gateway_optimizer.py +358 -0
- runbooks/vpc/networking_wrapper.py +15 -8
- runbooks/vpc/pdca_remediation_planner.py +528 -0
- runbooks/vpc/performance_optimized_analyzer.py +219 -231
- runbooks/vpc/runbooks_adapter.py +1167 -241
- runbooks/vpc/tdd_red_phase_stubs.py +601 -0
- runbooks/vpc/test_data_loader.py +358 -0
- runbooks/vpc/tests/conftest.py +314 -4
- runbooks/vpc/tests/test_cleanup_framework.py +1022 -0
- runbooks/vpc/tests/test_cost_engine.py +0 -2
- runbooks/vpc/topology_generator.py +326 -0
- runbooks/vpc/unified_scenarios.py +1302 -1129
- runbooks/vpc/vpc_cleanup_integration.py +1943 -1115
- runbooks-1.1.5.dist-info/METADATA +328 -0
- {runbooks-1.1.3.dist-info → runbooks-1.1.5.dist-info}/RECORD +233 -200
- runbooks/finops/README.md +0 -414
- runbooks/finops/accuracy_cross_validator.py +0 -647
- runbooks/finops/business_cases.py +0 -950
- runbooks/finops/dashboard_router.py +0 -922
- runbooks/finops/ebs_optimizer.py +0 -956
- runbooks/finops/embedded_mcp_validator.py +0 -1629
- runbooks/finops/enhanced_dashboard_runner.py +0 -527
- runbooks/finops/finops_dashboard.py +0 -584
- runbooks/finops/finops_scenarios.py +0 -1218
- runbooks/finops/legacy_migration.py +0 -730
- runbooks/finops/multi_dashboard.py +0 -1519
- runbooks/finops/single_dashboard.py +0 -1113
- runbooks/finops/unlimited_scenarios.py +0 -393
- runbooks-1.1.3.dist-info/METADATA +0 -799
- {runbooks-1.1.3.dist-info → runbooks-1.1.5.dist-info}/WHEEL +0 -0
- {runbooks-1.1.3.dist-info → runbooks-1.1.5.dist-info}/entry_points.txt +0 -0
- {runbooks-1.1.3.dist-info → runbooks-1.1.5.dist-info}/licenses/LICENSE +0 -0
- {runbooks-1.1.3.dist-info → runbooks-1.1.5.dist-info}/top_level.txt +0 -0
runbooks/common/profile_utils.py
CHANGED
@@ -5,6 +5,7 @@ import boto3
|
|
5
5
|
from typing import Optional, List, Dict, Any
|
6
6
|
from runbooks.common.rich_utils import console
|
7
7
|
from datetime import datetime
|
8
|
+
from botocore.config import Config
|
8
9
|
|
9
10
|
# Enhanced caching system for enterprise performance
|
10
11
|
_profile_cache: Dict[str, str] = {}
|
@@ -14,6 +15,14 @@ _cache_ttl: int = 300 # 5 minutes TTL for enterprise session management
|
|
14
15
|
_session_id: Optional[str] = None # Track session consistency
|
15
16
|
_session_cache: Dict[str, boto3.Session] = {} # Cache AWS sessions
|
16
17
|
|
18
|
+
# Timeout configuration for AWS API calls (prevents execution flow hangs)
|
19
|
+
_AWS_CLIENT_CONFIG = Config(
|
20
|
+
connect_timeout=30, # Connection timeout: 30 seconds
|
21
|
+
read_timeout=60, # Read timeout: 60 seconds
|
22
|
+
retries={"max_attempts": 3, "mode": "adaptive"},
|
23
|
+
)
|
24
|
+
|
25
|
+
|
17
26
|
def _get_session_id() -> str:
|
18
27
|
"""Generate consistent session ID for cache scoping"""
|
19
28
|
global _session_id
|
@@ -21,10 +30,9 @@ def _get_session_id() -> str:
|
|
21
30
|
_session_id = f"session_{int(time.time())}"
|
22
31
|
return _session_id
|
23
32
|
|
33
|
+
|
24
34
|
def get_profile_for_operation(
|
25
|
-
operation_type: str,
|
26
|
-
user_specified_profile: Optional[str] = None,
|
27
|
-
profiles: Optional[List[str]] = None
|
35
|
+
operation_type: str, user_specified_profile: Optional[str] = None, profiles: Optional[List[str]] = None
|
28
36
|
) -> str:
|
29
37
|
"""
|
30
38
|
Enhanced profile resolution with intelligent caching and enterprise logging optimization.
|
@@ -54,9 +62,7 @@ def get_profile_for_operation(
|
|
54
62
|
profile_cache_key = f"{_get_session_id()}:{user_specified_profile or 'default'}"
|
55
63
|
|
56
64
|
# Return cached result if still valid and within TTL
|
57
|
-
if
|
58
|
-
_cache_timestamp and
|
59
|
-
current_time - _cache_timestamp < _cache_ttl):
|
65
|
+
if profile_cache_key in _profile_cache and _cache_timestamp and current_time - _cache_timestamp < _cache_ttl:
|
60
66
|
return _profile_cache[profile_cache_key]
|
61
67
|
|
62
68
|
# Update cache timestamp only when cache is actually refreshed
|
@@ -114,6 +120,7 @@ def get_profile_for_operation(
|
|
114
120
|
console.log("[yellow]Please run: aws configure sso or aws configure[/]")
|
115
121
|
raise SystemExit(1)
|
116
122
|
|
123
|
+
|
117
124
|
def validate_profile_access(profile_name: str, operation_description: str = "") -> bool:
|
118
125
|
"""
|
119
126
|
Validate that the specified profile has proper AWS access with caching.
|
@@ -130,14 +137,12 @@ def validate_profile_access(profile_name: str, operation_description: str = "")
|
|
130
137
|
current_time = time.time()
|
131
138
|
cache_key = f"validation:{profile_name}"
|
132
139
|
|
133
|
-
if
|
134
|
-
_cache_timestamp and
|
135
|
-
current_time - _cache_timestamp < _cache_ttl):
|
140
|
+
if cache_key in _validation_cache and _cache_timestamp and current_time - _cache_timestamp < _cache_ttl:
|
136
141
|
return _validation_cache[cache_key]
|
137
142
|
|
138
143
|
try:
|
139
144
|
session = boto3.Session(profile_name=profile_name)
|
140
|
-
sts_client = session.client(
|
145
|
+
sts_client = session.client("sts")
|
141
146
|
sts_client.get_caller_identity()
|
142
147
|
|
143
148
|
# Cache successful validation
|
@@ -149,6 +154,7 @@ def validate_profile_access(profile_name: str, operation_description: str = "")
|
|
149
154
|
console.log(f"[yellow]Profile {profile_name} validation failed: {e}[/]")
|
150
155
|
return False
|
151
156
|
|
157
|
+
|
152
158
|
def get_account_id_from_profile(profile_name: str) -> Optional[str]:
|
153
159
|
"""
|
154
160
|
Extract account ID from AWS profile.
|
@@ -161,53 +167,232 @@ def get_account_id_from_profile(profile_name: str) -> Optional[str]:
|
|
161
167
|
"""
|
162
168
|
try:
|
163
169
|
session = boto3.Session(profile_name=profile_name)
|
164
|
-
sts_client = session.client(
|
170
|
+
sts_client = session.client("sts")
|
165
171
|
response = sts_client.get_caller_identity()
|
166
|
-
return response.get(
|
172
|
+
return response.get("Account")
|
167
173
|
except Exception:
|
168
174
|
return None
|
169
175
|
|
176
|
+
|
177
|
+
def auto_discover_enterprise_profiles() -> Dict[str, Optional[str]]:
|
178
|
+
"""
|
179
|
+
Auto-discover enterprise AWS SSO profiles for streamlined initialization.
|
180
|
+
|
181
|
+
Searches for profiles matching common enterprise naming patterns:
|
182
|
+
- *Billing* or *billing* for BILLING_PROFILE
|
183
|
+
- *Management* or *management* for MANAGEMENT_PROFILE
|
184
|
+
- *Ops* or *ops* for CENTRALISED_OPS_PROFILE
|
185
|
+
- Single account profiles for SINGLE_AWS_PROFILE
|
186
|
+
|
187
|
+
Returns:
|
188
|
+
Dict mapping profile types to discovered profile names
|
189
|
+
"""
|
190
|
+
available_profiles = boto3.Session().available_profiles
|
191
|
+
discovered = {"billing": None, "management": None, "centralised_ops": None, "single_aws": None}
|
192
|
+
|
193
|
+
# Search patterns for enterprise profiles
|
194
|
+
for profile in available_profiles:
|
195
|
+
profile_lower = profile.lower()
|
196
|
+
|
197
|
+
# Billing profile detection
|
198
|
+
if ("billing" in profile_lower or "cost" in profile_lower) and not discovered["billing"]:
|
199
|
+
discovered["billing"] = profile
|
200
|
+
|
201
|
+
# Management profile detection
|
202
|
+
elif ("management" in profile_lower or "admin" in profile_lower) and not discovered["management"]:
|
203
|
+
discovered["management"] = profile
|
204
|
+
|
205
|
+
# Operations profile detection
|
206
|
+
elif (
|
207
|
+
"ops" in profile_lower or "operational" in profile_lower or "centralised" in profile_lower
|
208
|
+
) and not discovered["centralised_ops"]:
|
209
|
+
discovered["centralised_ops"] = profile
|
210
|
+
|
211
|
+
# Single account detection (typically shorter names or containing 'single')
|
212
|
+
elif ("single" in profile_lower or len(profile) < 20) and not discovered["single_aws"]:
|
213
|
+
discovered["single_aws"] = profile
|
214
|
+
|
215
|
+
# Log discovered profiles for transparency
|
216
|
+
for profile_type, profile_name in discovered.items():
|
217
|
+
if profile_name:
|
218
|
+
console.log(f"[green]✅ Auto-discovered {profile_type}: {profile_name}[/green]")
|
219
|
+
else:
|
220
|
+
console.log(f"[yellow]⚠️ No profile found for {profile_type}[/yellow]")
|
221
|
+
|
222
|
+
return discovered
|
223
|
+
|
224
|
+
|
225
|
+
def setup_enterprise_environment_variables(discovered_profiles: Optional[Dict[str, Optional[str]]] = None) -> None:
|
226
|
+
"""
|
227
|
+
Setup enterprise environment variables from discovered profiles.
|
228
|
+
|
229
|
+
Args:
|
230
|
+
discovered_profiles: Optional pre-discovered profiles dict
|
231
|
+
"""
|
232
|
+
if not discovered_profiles:
|
233
|
+
discovered_profiles = auto_discover_enterprise_profiles()
|
234
|
+
|
235
|
+
# Set environment variables if not already set
|
236
|
+
env_mappings = {
|
237
|
+
"BILLING_PROFILE": discovered_profiles.get("billing"),
|
238
|
+
"MANAGEMENT_PROFILE": discovered_profiles.get("management"),
|
239
|
+
"CENTRALISED_OPS_PROFILE": discovered_profiles.get("centralised_ops"),
|
240
|
+
"SINGLE_AWS_PROFILE": discovered_profiles.get("single_aws"),
|
241
|
+
}
|
242
|
+
|
243
|
+
for env_var, profile_name in env_mappings.items():
|
244
|
+
if profile_name and not os.getenv(env_var):
|
245
|
+
os.environ[env_var] = profile_name
|
246
|
+
console.log(f"[blue]📋 Set {env_var}={profile_name}[/blue]")
|
247
|
+
elif os.getenv(env_var):
|
248
|
+
console.log(f"[dim]Using existing {env_var}={os.getenv(env_var)}[/dim]")
|
249
|
+
|
250
|
+
|
170
251
|
def create_cost_session(profile_name: Optional[str] = None) -> boto3.Session:
|
171
252
|
"""
|
172
|
-
Create AWS session optimized for cost operations (Cost Explorer).
|
253
|
+
Create AWS session optimized for cost operations (Cost Explorer) with token error handling.
|
173
254
|
|
174
255
|
Args:
|
175
256
|
profile_name: AWS profile name for cost operations
|
176
257
|
|
177
258
|
Returns:
|
178
259
|
Configured boto3 Session for cost operations
|
260
|
+
|
261
|
+
Raises:
|
262
|
+
SystemExit: When authentication fails with clear user guidance
|
179
263
|
"""
|
264
|
+
from botocore.exceptions import TokenRetrievalError, NoCredentialsError
|
265
|
+
from .rich_utils import console, print_error, print_info
|
266
|
+
|
180
267
|
cost_profile = get_profile_for_operation("billing", profile_name)
|
181
268
|
|
182
|
-
# Use cached session if available
|
269
|
+
# Use cached session if available and validate it's still working
|
183
270
|
session_key = f"cost:{cost_profile}"
|
184
271
|
if session_key in _session_cache:
|
185
|
-
|
272
|
+
cached_session = _session_cache[session_key]
|
273
|
+
# Quick validation that the cached session still works
|
274
|
+
try:
|
275
|
+
# Test with a minimal STS call to check if credentials are valid (with timeout)
|
276
|
+
sts_client = cached_session.client("sts", config=_AWS_CLIENT_CONFIG)
|
277
|
+
sts_client.get_caller_identity()
|
278
|
+
return cached_session
|
279
|
+
except (TokenRetrievalError, NoCredentialsError):
|
280
|
+
# Remove invalid cached session
|
281
|
+
del _session_cache[session_key]
|
282
|
+
console.log("[yellow]⚠️ Cached session expired, creating new session[/]")
|
283
|
+
|
284
|
+
try:
|
285
|
+
session = boto3.Session(profile_name=cost_profile)
|
286
|
+
# Test the session to ensure credentials are valid (with timeout)
|
287
|
+
sts_client = session.client("sts", config=_AWS_CLIENT_CONFIG)
|
288
|
+
sts_client.get_caller_identity()
|
289
|
+
|
290
|
+
# Cache only if session works
|
291
|
+
_session_cache[session_key] = session
|
292
|
+
return session
|
293
|
+
|
294
|
+
except TokenRetrievalError as e:
|
295
|
+
print_error("🔐 AWS SSO token has expired")
|
296
|
+
print_info("💡 To fix this issue:")
|
297
|
+
print_info(f" 1. Run: [cyan]aws sso login --profile {cost_profile}[/]")
|
298
|
+
print_info(" 2. Or try: [cyan]aws sso login[/] (if using default profile)")
|
299
|
+
print_info(" 3. Verify your internet connection")
|
300
|
+
print_info(" 4. Check if your AWS SSO session has expired")
|
301
|
+
console.log(f"[dim]Profile used: {cost_profile}[/]")
|
302
|
+
console.log(f"[dim]Error details: {str(e)}[/]")
|
303
|
+
raise SystemExit(1)
|
304
|
+
|
305
|
+
except NoCredentialsError as e:
|
306
|
+
print_error("🔐 No AWS credentials configured")
|
307
|
+
print_info("💡 To fix this issue:")
|
308
|
+
print_info(" 1. Configure AWS CLI: [cyan]aws configure[/]")
|
309
|
+
print_info(" 2. Or setup SSO: [cyan]aws configure sso[/]")
|
310
|
+
print_info(f" 3. Or set profile: [cyan]export AWS_PROFILE={cost_profile}[/]")
|
311
|
+
console.log(f"[dim]Profile attempted: {cost_profile}[/]")
|
312
|
+
raise SystemExit(1)
|
313
|
+
|
314
|
+
except Exception as e:
|
315
|
+
print_error(f"🔐 Authentication failed for profile: {cost_profile}")
|
316
|
+
print_info("💡 To fix this issue:")
|
317
|
+
print_info(" 1. Verify the profile exists: [cyan]aws configure list-profiles[/]")
|
318
|
+
print_info(" 2. Check profile permissions for cost analysis")
|
319
|
+
print_info(" 3. Ensure profile has Cost Explorer access")
|
320
|
+
console.log(f"[dim]Error details: {str(e)}[/]")
|
321
|
+
raise SystemExit(1)
|
186
322
|
|
187
|
-
session = boto3.Session(profile_name=cost_profile)
|
188
|
-
_session_cache[session_key] = session
|
189
|
-
return session
|
190
323
|
|
191
324
|
def create_management_session(profile_name: Optional[str] = None) -> boto3.Session:
|
192
325
|
"""
|
193
|
-
Create AWS session optimized for management operations (Organizations).
|
326
|
+
Create AWS session optimized for management operations (Organizations) with token error handling.
|
194
327
|
|
195
328
|
Args:
|
196
329
|
profile_name: AWS profile name for management operations
|
197
330
|
|
198
331
|
Returns:
|
199
332
|
Configured boto3 Session for management operations
|
333
|
+
|
334
|
+
Raises:
|
335
|
+
SystemExit: When authentication fails with clear user guidance
|
200
336
|
"""
|
337
|
+
from botocore.exceptions import TokenRetrievalError, NoCredentialsError
|
338
|
+
from .rich_utils import console, print_error, print_info
|
339
|
+
|
201
340
|
mgmt_profile = get_profile_for_operation("management", profile_name)
|
202
341
|
|
203
|
-
# Use cached session if available
|
342
|
+
# Use cached session if available and validate it's still working
|
204
343
|
session_key = f"management:{mgmt_profile}"
|
205
344
|
if session_key in _session_cache:
|
206
|
-
|
345
|
+
cached_session = _session_cache[session_key]
|
346
|
+
# Quick validation that the cached session still works
|
347
|
+
try:
|
348
|
+
# Test with a minimal STS call to check if credentials are valid (with timeout)
|
349
|
+
sts_client = cached_session.client("sts", config=_AWS_CLIENT_CONFIG)
|
350
|
+
sts_client.get_caller_identity()
|
351
|
+
return cached_session
|
352
|
+
except (TokenRetrievalError, NoCredentialsError):
|
353
|
+
# Remove invalid cached session
|
354
|
+
del _session_cache[session_key]
|
355
|
+
console.log("[yellow]⚠️ Cached session expired, creating new session[/]")
|
356
|
+
|
357
|
+
try:
|
358
|
+
session = boto3.Session(profile_name=mgmt_profile)
|
359
|
+
# Test the session to ensure credentials are valid (with timeout)
|
360
|
+
sts_client = session.client("sts", config=_AWS_CLIENT_CONFIG)
|
361
|
+
sts_client.get_caller_identity()
|
362
|
+
|
363
|
+
# Cache only if session works
|
364
|
+
_session_cache[session_key] = session
|
365
|
+
return session
|
366
|
+
|
367
|
+
except TokenRetrievalError as e:
|
368
|
+
print_error("🔐 AWS SSO token has expired")
|
369
|
+
print_info("💡 To fix this issue:")
|
370
|
+
print_info(f" 1. Run: [cyan]aws sso login --profile {mgmt_profile}[/]")
|
371
|
+
print_info(" 2. Or try: [cyan]aws sso login[/] (if using default profile)")
|
372
|
+
print_info(" 3. Verify your internet connection")
|
373
|
+
print_info(" 4. Check if your AWS SSO session has expired")
|
374
|
+
console.log(f"[dim]Profile used: {mgmt_profile}[/]")
|
375
|
+
console.log(f"[dim]Error details: {str(e)}[/]")
|
376
|
+
raise SystemExit(1)
|
377
|
+
|
378
|
+
except NoCredentialsError as e:
|
379
|
+
print_error("🔐 No AWS credentials configured")
|
380
|
+
print_info("💡 To fix this issue:")
|
381
|
+
print_info(" 1. Configure AWS CLI: [cyan]aws configure[/]")
|
382
|
+
print_info(" 2. Or setup SSO: [cyan]aws configure sso[/]")
|
383
|
+
print_info(f" 3. Or set profile: [cyan]export AWS_PROFILE={mgmt_profile}[/]")
|
384
|
+
console.log(f"[dim]Profile attempted: {mgmt_profile}[/]")
|
385
|
+
raise SystemExit(1)
|
386
|
+
|
387
|
+
except Exception as e:
|
388
|
+
print_error(f"🔐 Authentication failed for profile: {mgmt_profile}")
|
389
|
+
print_info("💡 To fix this issue:")
|
390
|
+
print_info(" 1. Verify the profile exists: [cyan]aws configure list-profiles[/]")
|
391
|
+
print_info(" 2. Check profile permissions for management operations")
|
392
|
+
print_info(" 3. Ensure profile has Organizations access")
|
393
|
+
console.log(f"[dim]Error details: {str(e)}[/]")
|
394
|
+
raise SystemExit(1)
|
207
395
|
|
208
|
-
session = boto3.Session(profile_name=mgmt_profile)
|
209
|
-
_session_cache[session_key] = session
|
210
|
-
return session
|
211
396
|
|
212
397
|
def create_operational_session(profile_name: Optional[str] = None) -> boto3.Session:
|
213
398
|
"""
|
@@ -230,6 +415,7 @@ def create_operational_session(profile_name: Optional[str] = None) -> boto3.Sess
|
|
230
415
|
_session_cache[session_key] = session
|
231
416
|
return session
|
232
417
|
|
418
|
+
|
233
419
|
def get_current_profile_info(profile_name: Optional[str] = None) -> Dict[str, Any]:
|
234
420
|
"""
|
235
421
|
Get current profile information including account ID and region.
|
@@ -242,28 +428,26 @@ def get_current_profile_info(profile_name: Optional[str] = None) -> Dict[str, An
|
|
242
428
|
"""
|
243
429
|
try:
|
244
430
|
session = boto3.Session(profile_name=profile_name)
|
245
|
-
sts_client = session.client(
|
431
|
+
sts_client = session.client("sts", config=_AWS_CLIENT_CONFIG)
|
246
432
|
identity = sts_client.get_caller_identity()
|
247
433
|
|
248
434
|
return {
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
435
|
+
"profile_name": profile_name or "default",
|
436
|
+
"account_id": identity.get("Account"),
|
437
|
+
"user_arn": identity.get("Arn"),
|
438
|
+
"region": session.region_name or "us-east-1",
|
253
439
|
}
|
254
440
|
except Exception as e:
|
255
441
|
return {
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
442
|
+
"profile_name": profile_name or "default",
|
443
|
+
"error": str(e),
|
444
|
+
"account_id": None,
|
445
|
+
"user_arn": None,
|
446
|
+
"region": None,
|
261
447
|
}
|
262
448
|
|
263
|
-
|
264
|
-
|
265
|
-
user_specified_profile: Optional[str] = None
|
266
|
-
) -> str:
|
449
|
+
|
450
|
+
def resolve_profile_for_operation_silent(operation_type: str, user_specified_profile: Optional[str] = None) -> str:
|
267
451
|
"""
|
268
452
|
Silent version of profile resolution without logging.
|
269
453
|
|
@@ -291,6 +475,7 @@ def resolve_profile_for_operation_silent(
|
|
291
475
|
|
292
476
|
return user_specified_profile or "default"
|
293
477
|
|
478
|
+
|
294
479
|
def list_available_profiles() -> List[str]:
|
295
480
|
"""
|
296
481
|
Get list of all available AWS profiles.
|
@@ -300,6 +485,7 @@ def list_available_profiles() -> List[str]:
|
|
300
485
|
"""
|
301
486
|
return boto3.Session().available_profiles
|
302
487
|
|
488
|
+
|
303
489
|
def clear_profile_cache() -> None:
|
304
490
|
"""Clear the profile cache for testing or troubleshooting."""
|
305
491
|
global _profile_cache, _validation_cache, _session_cache, _cache_timestamp, _session_id
|
@@ -307,4 +493,27 @@ def clear_profile_cache() -> None:
|
|
307
493
|
_validation_cache.clear()
|
308
494
|
_session_cache.clear()
|
309
495
|
_cache_timestamp = None
|
310
|
-
_session_id = None
|
496
|
+
_session_id = None
|
497
|
+
|
498
|
+
|
499
|
+
def create_timeout_protected_client(session: boto3.Session, service_name: str, region_name: Optional[str] = None):
|
500
|
+
"""
|
501
|
+
Create AWS service client with timeout protection to prevent execution flow hangs.
|
502
|
+
|
503
|
+
This function should be used by all FinOps modules to create AWS clients with
|
504
|
+
enterprise-grade timeout protection and retry configuration.
|
505
|
+
|
506
|
+
Args:
|
507
|
+
session: boto3 Session to use for client creation
|
508
|
+
service_name: AWS service name (e.g., 'ec2', 'ce', 'workspaces', 'rds')
|
509
|
+
region_name: AWS region name (optional)
|
510
|
+
|
511
|
+
Returns:
|
512
|
+
AWS service client with timeout protection
|
513
|
+
|
514
|
+
Example:
|
515
|
+
session = create_cost_session()
|
516
|
+
ce_client = create_timeout_protected_client(session, 'ce', 'us-east-1')
|
517
|
+
ec2_client = create_timeout_protected_client(session, 'ec2', region_name)
|
518
|
+
"""
|
519
|
+
return session.client(service_name, region_name=region_name, config=_AWS_CLIENT_CONFIG)
|