runbooks 1.1.2__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.
- runbooks/__init__.py +1 -1
- runbooks/cfat/WEIGHT_CONFIG_README.md +1 -1
- runbooks/cfat/assessment/compliance.py +7 -7
- runbooks/cfat/models.py +6 -2
- runbooks/cfat/tests/__init__.py +6 -1
- runbooks/cli/__init__.py +13 -0
- runbooks/cli/commands/cfat.py +233 -0
- runbooks/cli/commands/finops.py +213 -0
- runbooks/cli/commands/inventory.py +276 -0
- runbooks/cli/commands/operate.py +266 -0
- runbooks/cli/commands/security.py +224 -0
- runbooks/cli/commands/validation.py +411 -0
- runbooks/cli/commands/vpc.py +246 -0
- runbooks/cli/registry.py +95 -0
- runbooks/cloudops/__init__.py +3 -3
- runbooks/cloudops/cost_optimizer.py +164 -28
- runbooks/cloudops/interfaces.py +2 -2
- runbooks/cloudops/mcp_cost_validation.py +3 -3
- runbooks/cloudops/notebook_framework.py +2 -2
- runbooks/common/aws_profile_manager.py +337 -0
- runbooks/common/aws_utils.py +1 -1
- runbooks/common/business_logic.py +3 -3
- runbooks/common/comprehensive_cost_explorer_integration.py +1 -1
- runbooks/common/cross_account_manager.py +1 -1
- runbooks/common/decorators.py +225 -0
- runbooks/common/mcp_cost_explorer_integration.py +2 -2
- runbooks/common/organizations_client.py +1 -1
- runbooks/common/patterns.py +206 -0
- runbooks/common/profile_utils.py +149 -14
- runbooks/common/rich_utils.py +507 -16
- runbooks/finops/README.md +11 -11
- runbooks/finops/__init__.py +4 -4
- runbooks/finops/business_cases.py +3 -3
- runbooks/finops/cli.py +169 -103
- runbooks/finops/cost_optimizer.py +4 -4
- runbooks/finops/dashboard_router.py +2 -2
- runbooks/finops/ebs_cost_optimizer.py +4 -4
- runbooks/finops/ebs_optimizer.py +19 -2
- runbooks/finops/embedded_mcp_validator.py +101 -23
- runbooks/finops/enhanced_progress.py +8 -8
- runbooks/finops/enterprise_wrappers.py +7 -7
- runbooks/finops/finops_scenarios.py +101 -27
- runbooks/finops/legacy_migration.py +8 -8
- runbooks/finops/markdown_exporter.py +2 -2
- runbooks/finops/multi_dashboard.py +1 -1
- runbooks/finops/nat_gateway_optimizer.py +1 -1
- runbooks/finops/optimizer.py +6 -6
- runbooks/finops/rds_snapshot_optimizer.py +1389 -0
- runbooks/finops/scenario_cli_integration.py +13 -13
- runbooks/finops/scenarios.py +16 -16
- runbooks/finops/single_dashboard.py +10 -10
- 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/validation_framework.py +5 -5
- runbooks/finops/vpc_cleanup_exporter.py +3 -3
- runbooks/finops/vpc_cleanup_optimizer.py +3 -3
- runbooks/finops/workspaces_analyzer.py +31 -13
- runbooks/hitl/enhanced_workflow_engine.py +1 -1
- runbooks/inventory/README.md +3 -3
- runbooks/inventory/Tests/common_test_data.py +30 -30
- runbooks/inventory/collectors/aws_comprehensive.py +28 -11
- runbooks/inventory/collectors/aws_networking.py +2 -2
- runbooks/inventory/discovery.md +2 -2
- runbooks/inventory/find_ec2_security_groups.py +1 -1
- runbooks/inventory/list_rds_snapshots_aggregator.py +745 -0
- runbooks/inventory/organizations_discovery.py +1 -1
- runbooks/inventory/vpc_analyzer.py +1 -1
- runbooks/inventory/vpc_flow_analyzer.py +2 -2
- runbooks/main.py +143 -8882
- runbooks/metrics/dora_metrics_engine.py +2 -2
- runbooks/operate/mcp_integration.py +1 -1
- runbooks/operate/networking_cost_heatmap.py +4 -2
- runbooks/operate/privatelink_operations.py +1 -1
- runbooks/operate/vpc_endpoints.py +1 -1
- runbooks/operate/vpc_operations.py +2 -2
- runbooks/remediation/commvault_ec2_analysis.py +1 -1
- runbooks/remediation/rds_snapshot_list.py +5 -5
- runbooks/remediation/workspaces_list.py +5 -5
- runbooks/security/integration_test_enterprise_security.py +5 -3
- runbooks/security/run_script.py +1 -1
- runbooks/sre/mcp_reliability_engine.py +6 -6
- runbooks/utils/version_validator.py +1 -1
- runbooks/validation/comprehensive_2way_validator.py +9 -4
- runbooks/vpc/heatmap_engine.py +7 -4
- runbooks/vpc/mcp_no_eni_validator.py +1 -1
- runbooks/vpc/unified_scenarios.py +7 -7
- {runbooks-1.1.2.dist-info → runbooks-1.1.4.dist-info}/METADATA +53 -52
- {runbooks-1.1.2.dist-info → runbooks-1.1.4.dist-info}/RECORD +94 -80
- {runbooks-1.1.2.dist-info → runbooks-1.1.4.dist-info}/WHEEL +0 -0
- {runbooks-1.1.2.dist-info → runbooks-1.1.4.dist-info}/entry_points.txt +0 -0
- {runbooks-1.1.2.dist-info → runbooks-1.1.4.dist-info}/licenses/LICENSE +0 -0
- {runbooks-1.1.2.dist-info → runbooks-1.1.4.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,206 @@
|
|
1
|
+
"""
|
2
|
+
DRY Pattern Manager - Eliminate Pattern Duplication
|
3
|
+
|
4
|
+
Following Claude Code best practices for memory efficiency and enterprise
|
5
|
+
architecture patterns for systematic deduplication.
|
6
|
+
|
7
|
+
Official Claude Code Limitation: Context window optimization requires eliminating
|
8
|
+
redundant patterns to maximize available context for complex operations.
|
9
|
+
|
10
|
+
Enterprise Best Practice: Single source of truth for all reusable patterns
|
11
|
+
prevents inconsistencies and reduces maintenance overhead.
|
12
|
+
"""
|
13
|
+
|
14
|
+
from typing import Any, Dict, Optional, Callable
|
15
|
+
from functools import lru_cache
|
16
|
+
import click
|
17
|
+
from rich.console import Console
|
18
|
+
from rich.markup import escape
|
19
|
+
|
20
|
+
|
21
|
+
class DRYPatternManager:
|
22
|
+
"""
|
23
|
+
Don't Repeat Yourself - Load patterns once, reference everywhere.
|
24
|
+
|
25
|
+
Following Claude Code optimization principles:
|
26
|
+
- Single pattern registry (no duplicates)
|
27
|
+
- Reference-based access (@ notation concept)
|
28
|
+
- Memory efficiency through lazy loading
|
29
|
+
- Consistent patterns across all modules
|
30
|
+
|
31
|
+
Enterprise Architecture:
|
32
|
+
- Centralized pattern management
|
33
|
+
- Type-safe pattern access
|
34
|
+
- Cached pattern instances
|
35
|
+
- Extensible pattern registry
|
36
|
+
"""
|
37
|
+
|
38
|
+
_patterns: Dict[str, Any] = {}
|
39
|
+
_console: Optional[Console] = None
|
40
|
+
_loaded: bool = False
|
41
|
+
|
42
|
+
@classmethod
|
43
|
+
@lru_cache(maxsize=None)
|
44
|
+
def get_console(cls) -> Console:
|
45
|
+
"""
|
46
|
+
Single console instance for all modules.
|
47
|
+
|
48
|
+
Claude Code Best Practice: Reuse console objects to reduce memory overhead
|
49
|
+
and ensure consistent formatting across all CLI operations.
|
50
|
+
"""
|
51
|
+
if cls._console is None:
|
52
|
+
cls._console = Console()
|
53
|
+
return cls._console
|
54
|
+
|
55
|
+
@classmethod
|
56
|
+
def get_pattern(cls, name: str) -> Any:
|
57
|
+
"""
|
58
|
+
Get pattern by name with lazy loading.
|
59
|
+
|
60
|
+
Args:
|
61
|
+
name: Pattern identifier (e.g., 'error_handlers', 'click_group')
|
62
|
+
|
63
|
+
Returns:
|
64
|
+
Cached pattern instance
|
65
|
+
|
66
|
+
Raises:
|
67
|
+
KeyError: If pattern not found
|
68
|
+
"""
|
69
|
+
if not cls._loaded:
|
70
|
+
cls._load_all_patterns()
|
71
|
+
|
72
|
+
if name not in cls._patterns:
|
73
|
+
raise KeyError(f"Pattern '{name}' not found. Available: {list(cls._patterns.keys())}")
|
74
|
+
|
75
|
+
return cls._patterns[name]
|
76
|
+
|
77
|
+
@classmethod
|
78
|
+
def _load_all_patterns(cls):
|
79
|
+
"""Load all patterns once - DRY principle implementation."""
|
80
|
+
if cls._loaded:
|
81
|
+
return
|
82
|
+
|
83
|
+
# Common import patterns
|
84
|
+
cls._patterns['click'] = click
|
85
|
+
cls._patterns['console'] = cls.get_console()
|
86
|
+
|
87
|
+
# Error handling patterns
|
88
|
+
cls._patterns['error_handlers'] = cls._create_error_handlers()
|
89
|
+
|
90
|
+
# Click group patterns
|
91
|
+
cls._patterns['click_group'] = cls._create_click_group_pattern()
|
92
|
+
|
93
|
+
# Common CLI decorators reference
|
94
|
+
cls._patterns['common_decorators'] = cls._get_common_decorators()
|
95
|
+
|
96
|
+
cls._loaded = True
|
97
|
+
|
98
|
+
@classmethod
|
99
|
+
def _create_error_handlers(cls) -> Dict[str, Callable]:
|
100
|
+
"""
|
101
|
+
Centralized error handling patterns.
|
102
|
+
|
103
|
+
Eliminates 19 instances of duplicated error messages across CLI modules.
|
104
|
+
"""
|
105
|
+
console = cls.get_console()
|
106
|
+
|
107
|
+
def module_not_available_error(module_name: str, error: Exception):
|
108
|
+
"""Standardized 'module not available' error handler."""
|
109
|
+
console.print(f"[red]❌ {module_name} module not available: {error}[/red]")
|
110
|
+
|
111
|
+
def operation_failed_error(operation_name: str, error: Exception):
|
112
|
+
"""Standardized 'operation failed' error handler."""
|
113
|
+
console.print(f"[red]❌ {operation_name} failed: {error}[/red]")
|
114
|
+
|
115
|
+
def success_message(message: str, details: Optional[str] = None):
|
116
|
+
"""Standardized success message."""
|
117
|
+
console.print(f"[green]✅ {message}[/green]")
|
118
|
+
if details:
|
119
|
+
console.print(f"[dim]{details}[/dim]")
|
120
|
+
|
121
|
+
return {
|
122
|
+
'module_not_available': module_not_available_error,
|
123
|
+
'operation_failed': operation_failed_error,
|
124
|
+
'success': success_message
|
125
|
+
}
|
126
|
+
|
127
|
+
@classmethod
|
128
|
+
def _create_click_group_pattern(cls) -> Callable:
|
129
|
+
"""
|
130
|
+
Standardized Click group creation pattern.
|
131
|
+
|
132
|
+
Eliminates 6 instances of identical @click.group patterns.
|
133
|
+
"""
|
134
|
+
def create_group(name: str, help_text: str, invoke_without_command: bool = True):
|
135
|
+
"""Create standardized Click group with common options."""
|
136
|
+
|
137
|
+
@click.group(invoke_without_command=invoke_without_command)
|
138
|
+
@click.pass_context
|
139
|
+
def group(ctx):
|
140
|
+
if ctx.invoked_subcommand is None:
|
141
|
+
click.echo(f"{name.title()} Commands:")
|
142
|
+
click.echo(help_text)
|
143
|
+
|
144
|
+
# Apply consistent group naming
|
145
|
+
group.name = name
|
146
|
+
group.__doc__ = help_text
|
147
|
+
|
148
|
+
return group
|
149
|
+
|
150
|
+
return create_group
|
151
|
+
|
152
|
+
@classmethod
|
153
|
+
def _get_common_decorators(cls):
|
154
|
+
"""Reference to common decorators - avoid importing in every module."""
|
155
|
+
try:
|
156
|
+
from runbooks.common.decorators import (
|
157
|
+
common_aws_options,
|
158
|
+
common_output_options,
|
159
|
+
common_filter_options
|
160
|
+
)
|
161
|
+
return {
|
162
|
+
'aws_options': common_aws_options,
|
163
|
+
'output_options': common_output_options,
|
164
|
+
'filter_options': common_filter_options
|
165
|
+
}
|
166
|
+
except ImportError:
|
167
|
+
# Graceful degradation if decorators not available
|
168
|
+
return {}
|
169
|
+
|
170
|
+
|
171
|
+
# Convenience functions for direct pattern access
|
172
|
+
def get_console() -> Console:
|
173
|
+
"""Get shared console instance - replaces individual console = Console() calls."""
|
174
|
+
return DRYPatternManager.get_console()
|
175
|
+
|
176
|
+
|
177
|
+
def get_error_handlers() -> Dict[str, Callable]:
|
178
|
+
"""Get standardized error handlers."""
|
179
|
+
return DRYPatternManager.get_pattern('error_handlers')
|
180
|
+
|
181
|
+
|
182
|
+
def get_click_group_creator() -> Callable:
|
183
|
+
"""Get standardized Click group creator."""
|
184
|
+
return DRYPatternManager.get_pattern('click_group')
|
185
|
+
|
186
|
+
|
187
|
+
def get_common_decorators() -> Dict[str, Any]:
|
188
|
+
"""Get common CLI decorators."""
|
189
|
+
return DRYPatternManager.get_pattern('common_decorators')
|
190
|
+
|
191
|
+
|
192
|
+
# Pattern registry status for monitoring
|
193
|
+
def get_pattern_registry_status() -> Dict[str, Any]:
|
194
|
+
"""
|
195
|
+
Get DRY pattern registry status for monitoring and optimization.
|
196
|
+
|
197
|
+
Returns:
|
198
|
+
Dictionary containing registry status, loaded patterns, and memory efficiency metrics
|
199
|
+
"""
|
200
|
+
return {
|
201
|
+
'loaded': DRYPatternManager._loaded,
|
202
|
+
'pattern_count': len(DRYPatternManager._patterns),
|
203
|
+
'available_patterns': list(DRYPatternManager._patterns.keys()),
|
204
|
+
'console_shared': DRYPatternManager._console is not None,
|
205
|
+
'memory_efficiency': 'Single instance sharing active'
|
206
|
+
}
|
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,16 @@ _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={
|
23
|
+
'max_attempts': 3,
|
24
|
+
'mode': 'adaptive'
|
25
|
+
}
|
26
|
+
)
|
27
|
+
|
17
28
|
def _get_session_id() -> str:
|
18
29
|
"""Generate consistent session ID for cache scoping"""
|
19
30
|
global _session_id
|
@@ -169,45 +180,147 @@ def get_account_id_from_profile(profile_name: str) -> Optional[str]:
|
|
169
180
|
|
170
181
|
def create_cost_session(profile_name: Optional[str] = None) -> boto3.Session:
|
171
182
|
"""
|
172
|
-
Create AWS session optimized for cost operations (Cost Explorer).
|
183
|
+
Create AWS session optimized for cost operations (Cost Explorer) with token error handling.
|
173
184
|
|
174
185
|
Args:
|
175
186
|
profile_name: AWS profile name for cost operations
|
176
187
|
|
177
188
|
Returns:
|
178
189
|
Configured boto3 Session for cost operations
|
190
|
+
|
191
|
+
Raises:
|
192
|
+
SystemExit: When authentication fails with clear user guidance
|
179
193
|
"""
|
194
|
+
from botocore.exceptions import TokenRetrievalError, NoCredentialsError
|
195
|
+
from .rich_utils import console, print_error, print_info
|
196
|
+
|
180
197
|
cost_profile = get_profile_for_operation("billing", profile_name)
|
181
198
|
|
182
|
-
# Use cached session if available
|
199
|
+
# Use cached session if available and validate it's still working
|
183
200
|
session_key = f"cost:{cost_profile}"
|
184
201
|
if session_key in _session_cache:
|
185
|
-
|
202
|
+
cached_session = _session_cache[session_key]
|
203
|
+
# Quick validation that the cached session still works
|
204
|
+
try:
|
205
|
+
# Test with a minimal STS call to check if credentials are valid (with timeout)
|
206
|
+
sts_client = cached_session.client('sts', config=_AWS_CLIENT_CONFIG)
|
207
|
+
sts_client.get_caller_identity()
|
208
|
+
return cached_session
|
209
|
+
except (TokenRetrievalError, NoCredentialsError):
|
210
|
+
# Remove invalid cached session
|
211
|
+
del _session_cache[session_key]
|
212
|
+
console.log("[yellow]⚠️ Cached session expired, creating new session[/]")
|
186
213
|
|
187
|
-
|
188
|
-
|
189
|
-
|
214
|
+
try:
|
215
|
+
session = boto3.Session(profile_name=cost_profile)
|
216
|
+
# Test the session to ensure credentials are valid (with timeout)
|
217
|
+
sts_client = session.client('sts', config=_AWS_CLIENT_CONFIG)
|
218
|
+
sts_client.get_caller_identity()
|
219
|
+
|
220
|
+
# Cache only if session works
|
221
|
+
_session_cache[session_key] = session
|
222
|
+
return session
|
223
|
+
|
224
|
+
except TokenRetrievalError as e:
|
225
|
+
print_error("🔐 AWS SSO token has expired")
|
226
|
+
print_info("💡 To fix this issue:")
|
227
|
+
print_info(f" 1. Run: [cyan]aws sso login --profile {cost_profile}[/]")
|
228
|
+
print_info(" 2. Or try: [cyan]aws sso login[/] (if using default profile)")
|
229
|
+
print_info(" 3. Verify your internet connection")
|
230
|
+
print_info(" 4. Check if your AWS SSO session has expired")
|
231
|
+
console.log(f"[dim]Profile used: {cost_profile}[/]")
|
232
|
+
console.log(f"[dim]Error details: {str(e)}[/]")
|
233
|
+
raise SystemExit(1)
|
234
|
+
|
235
|
+
except NoCredentialsError as e:
|
236
|
+
print_error("🔐 No AWS credentials configured")
|
237
|
+
print_info("💡 To fix this issue:")
|
238
|
+
print_info(" 1. Configure AWS CLI: [cyan]aws configure[/]")
|
239
|
+
print_info(" 2. Or setup SSO: [cyan]aws configure sso[/]")
|
240
|
+
print_info(f" 3. Or set profile: [cyan]export AWS_PROFILE={cost_profile}[/]")
|
241
|
+
console.log(f"[dim]Profile attempted: {cost_profile}[/]")
|
242
|
+
raise SystemExit(1)
|
243
|
+
|
244
|
+
except Exception as e:
|
245
|
+
print_error(f"🔐 Authentication failed for profile: {cost_profile}")
|
246
|
+
print_info("💡 To fix this issue:")
|
247
|
+
print_info(" 1. Verify the profile exists: [cyan]aws configure list-profiles[/]")
|
248
|
+
print_info(" 2. Check profile permissions for cost analysis")
|
249
|
+
print_info(" 3. Ensure profile has Cost Explorer access")
|
250
|
+
console.log(f"[dim]Error details: {str(e)}[/]")
|
251
|
+
raise SystemExit(1)
|
190
252
|
|
191
253
|
def create_management_session(profile_name: Optional[str] = None) -> boto3.Session:
|
192
254
|
"""
|
193
|
-
Create AWS session optimized for management operations (Organizations).
|
255
|
+
Create AWS session optimized for management operations (Organizations) with token error handling.
|
194
256
|
|
195
257
|
Args:
|
196
258
|
profile_name: AWS profile name for management operations
|
197
259
|
|
198
260
|
Returns:
|
199
261
|
Configured boto3 Session for management operations
|
262
|
+
|
263
|
+
Raises:
|
264
|
+
SystemExit: When authentication fails with clear user guidance
|
200
265
|
"""
|
266
|
+
from botocore.exceptions import TokenRetrievalError, NoCredentialsError
|
267
|
+
from .rich_utils import console, print_error, print_info
|
268
|
+
|
201
269
|
mgmt_profile = get_profile_for_operation("management", profile_name)
|
202
270
|
|
203
|
-
# Use cached session if available
|
271
|
+
# Use cached session if available and validate it's still working
|
204
272
|
session_key = f"management:{mgmt_profile}"
|
205
273
|
if session_key in _session_cache:
|
206
|
-
|
274
|
+
cached_session = _session_cache[session_key]
|
275
|
+
# Quick validation that the cached session still works
|
276
|
+
try:
|
277
|
+
# Test with a minimal STS call to check if credentials are valid (with timeout)
|
278
|
+
sts_client = cached_session.client('sts', config=_AWS_CLIENT_CONFIG)
|
279
|
+
sts_client.get_caller_identity()
|
280
|
+
return cached_session
|
281
|
+
except (TokenRetrievalError, NoCredentialsError):
|
282
|
+
# Remove invalid cached session
|
283
|
+
del _session_cache[session_key]
|
284
|
+
console.log("[yellow]⚠️ Cached session expired, creating new session[/]")
|
207
285
|
|
208
|
-
|
209
|
-
|
210
|
-
|
286
|
+
try:
|
287
|
+
session = boto3.Session(profile_name=mgmt_profile)
|
288
|
+
# Test the session to ensure credentials are valid (with timeout)
|
289
|
+
sts_client = session.client('sts', config=_AWS_CLIENT_CONFIG)
|
290
|
+
sts_client.get_caller_identity()
|
291
|
+
|
292
|
+
# Cache only if session works
|
293
|
+
_session_cache[session_key] = session
|
294
|
+
return session
|
295
|
+
|
296
|
+
except TokenRetrievalError as e:
|
297
|
+
print_error("🔐 AWS SSO token has expired")
|
298
|
+
print_info("💡 To fix this issue:")
|
299
|
+
print_info(f" 1. Run: [cyan]aws sso login --profile {mgmt_profile}[/]")
|
300
|
+
print_info(" 2. Or try: [cyan]aws sso login[/] (if using default profile)")
|
301
|
+
print_info(" 3. Verify your internet connection")
|
302
|
+
print_info(" 4. Check if your AWS SSO session has expired")
|
303
|
+
console.log(f"[dim]Profile used: {mgmt_profile}[/]")
|
304
|
+
console.log(f"[dim]Error details: {str(e)}[/]")
|
305
|
+
raise SystemExit(1)
|
306
|
+
|
307
|
+
except NoCredentialsError as e:
|
308
|
+
print_error("🔐 No AWS credentials configured")
|
309
|
+
print_info("💡 To fix this issue:")
|
310
|
+
print_info(" 1. Configure AWS CLI: [cyan]aws configure[/]")
|
311
|
+
print_info(" 2. Or setup SSO: [cyan]aws configure sso[/]")
|
312
|
+
print_info(f" 3. Or set profile: [cyan]export AWS_PROFILE={mgmt_profile}[/]")
|
313
|
+
console.log(f"[dim]Profile attempted: {mgmt_profile}[/]")
|
314
|
+
raise SystemExit(1)
|
315
|
+
|
316
|
+
except Exception as e:
|
317
|
+
print_error(f"🔐 Authentication failed for profile: {mgmt_profile}")
|
318
|
+
print_info("💡 To fix this issue:")
|
319
|
+
print_info(" 1. Verify the profile exists: [cyan]aws configure list-profiles[/]")
|
320
|
+
print_info(" 2. Check profile permissions for management operations")
|
321
|
+
print_info(" 3. Ensure profile has Organizations access")
|
322
|
+
console.log(f"[dim]Error details: {str(e)}[/]")
|
323
|
+
raise SystemExit(1)
|
211
324
|
|
212
325
|
def create_operational_session(profile_name: Optional[str] = None) -> boto3.Session:
|
213
326
|
"""
|
@@ -242,7 +355,7 @@ def get_current_profile_info(profile_name: Optional[str] = None) -> Dict[str, An
|
|
242
355
|
"""
|
243
356
|
try:
|
244
357
|
session = boto3.Session(profile_name=profile_name)
|
245
|
-
sts_client = session.client('sts')
|
358
|
+
sts_client = session.client('sts', config=_AWS_CLIENT_CONFIG)
|
246
359
|
identity = sts_client.get_caller_identity()
|
247
360
|
|
248
361
|
return {
|
@@ -307,4 +420,26 @@ def clear_profile_cache() -> None:
|
|
307
420
|
_validation_cache.clear()
|
308
421
|
_session_cache.clear()
|
309
422
|
_cache_timestamp = None
|
310
|
-
_session_id = None
|
423
|
+
_session_id = None
|
424
|
+
|
425
|
+
def create_timeout_protected_client(session: boto3.Session, service_name: str, region_name: Optional[str] = None):
|
426
|
+
"""
|
427
|
+
Create AWS service client with timeout protection to prevent execution flow hangs.
|
428
|
+
|
429
|
+
This function should be used by all FinOps modules to create AWS clients with
|
430
|
+
enterprise-grade timeout protection and retry configuration.
|
431
|
+
|
432
|
+
Args:
|
433
|
+
session: boto3 Session to use for client creation
|
434
|
+
service_name: AWS service name (e.g., 'ec2', 'ce', 'workspaces', 'rds')
|
435
|
+
region_name: AWS region name (optional)
|
436
|
+
|
437
|
+
Returns:
|
438
|
+
AWS service client with timeout protection
|
439
|
+
|
440
|
+
Example:
|
441
|
+
session = create_cost_session()
|
442
|
+
ce_client = create_timeout_protected_client(session, 'ce', 'us-east-1')
|
443
|
+
ec2_client = create_timeout_protected_client(session, 'ec2', region_name)
|
444
|
+
"""
|
445
|
+
return session.client(service_name, region_name=region_name, config=_AWS_CLIENT_CONFIG)
|