runbooks 0.7.6__py3-none-any.whl → 0.7.9__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 (111) hide show
  1. runbooks/__init__.py +1 -1
  2. runbooks/base.py +5 -1
  3. runbooks/cfat/__init__.py +8 -4
  4. runbooks/cfat/assessment/collectors.py +171 -14
  5. runbooks/cfat/assessment/compliance.py +871 -0
  6. runbooks/cfat/assessment/runner.py +122 -11
  7. runbooks/cfat/models.py +6 -2
  8. runbooks/common/logger.py +14 -0
  9. runbooks/common/rich_utils.py +451 -0
  10. runbooks/enterprise/__init__.py +68 -0
  11. runbooks/enterprise/error_handling.py +411 -0
  12. runbooks/enterprise/logging.py +439 -0
  13. runbooks/enterprise/multi_tenant.py +583 -0
  14. runbooks/finops/README.md +468 -241
  15. runbooks/finops/__init__.py +39 -3
  16. runbooks/finops/cli.py +83 -18
  17. runbooks/finops/cross_validation.py +375 -0
  18. runbooks/finops/dashboard_runner.py +812 -164
  19. runbooks/finops/enhanced_dashboard_runner.py +525 -0
  20. runbooks/finops/finops_dashboard.py +1892 -0
  21. runbooks/finops/helpers.py +485 -51
  22. runbooks/finops/optimizer.py +823 -0
  23. runbooks/finops/tests/__init__.py +19 -0
  24. runbooks/finops/tests/results_test_finops_dashboard.xml +1 -0
  25. runbooks/finops/tests/run_comprehensive_tests.py +421 -0
  26. runbooks/finops/tests/run_tests.py +305 -0
  27. runbooks/finops/tests/test_finops_dashboard.py +705 -0
  28. runbooks/finops/tests/test_integration.py +477 -0
  29. runbooks/finops/tests/test_performance.py +380 -0
  30. runbooks/finops/tests/test_performance_benchmarks.py +500 -0
  31. runbooks/finops/tests/test_reference_images_validation.py +867 -0
  32. runbooks/finops/tests/test_single_account_features.py +715 -0
  33. runbooks/finops/tests/validate_test_suite.py +220 -0
  34. runbooks/finops/types.py +1 -1
  35. runbooks/hitl/enhanced_workflow_engine.py +725 -0
  36. runbooks/inventory/artifacts/scale-optimize-status.txt +12 -0
  37. runbooks/inventory/collectors/aws_comprehensive.py +442 -0
  38. runbooks/inventory/collectors/enterprise_scale.py +281 -0
  39. runbooks/inventory/core/collector.py +172 -13
  40. runbooks/inventory/discovery.md +1 -1
  41. runbooks/inventory/list_ec2_instances.py +18 -20
  42. runbooks/inventory/list_ssm_parameters.py +31 -3
  43. runbooks/inventory/organizations_discovery.py +1269 -0
  44. runbooks/inventory/rich_inventory_display.py +393 -0
  45. runbooks/inventory/run_on_multi_accounts.py +35 -19
  46. runbooks/inventory/runbooks.security.report_generator.log +0 -0
  47. runbooks/inventory/runbooks.security.run_script.log +0 -0
  48. runbooks/inventory/vpc_flow_analyzer.py +1030 -0
  49. runbooks/main.py +2215 -119
  50. runbooks/metrics/dora_metrics_engine.py +599 -0
  51. runbooks/operate/__init__.py +2 -2
  52. runbooks/operate/base.py +122 -10
  53. runbooks/operate/deployment_framework.py +1032 -0
  54. runbooks/operate/deployment_validator.py +853 -0
  55. runbooks/operate/dynamodb_operations.py +10 -6
  56. runbooks/operate/ec2_operations.py +319 -11
  57. runbooks/operate/executive_dashboard.py +779 -0
  58. runbooks/operate/mcp_integration.py +750 -0
  59. runbooks/operate/nat_gateway_operations.py +1120 -0
  60. runbooks/operate/networking_cost_heatmap.py +685 -0
  61. runbooks/operate/privatelink_operations.py +940 -0
  62. runbooks/operate/s3_operations.py +10 -6
  63. runbooks/operate/vpc_endpoints.py +644 -0
  64. runbooks/operate/vpc_operations.py +1038 -0
  65. runbooks/remediation/__init__.py +2 -2
  66. runbooks/remediation/acm_remediation.py +1 -1
  67. runbooks/remediation/base.py +1 -1
  68. runbooks/remediation/cloudtrail_remediation.py +1 -1
  69. runbooks/remediation/cognito_remediation.py +1 -1
  70. runbooks/remediation/dynamodb_remediation.py +1 -1
  71. runbooks/remediation/ec2_remediation.py +1 -1
  72. runbooks/remediation/ec2_unattached_ebs_volumes.py +1 -1
  73. runbooks/remediation/kms_enable_key_rotation.py +1 -1
  74. runbooks/remediation/kms_remediation.py +1 -1
  75. runbooks/remediation/lambda_remediation.py +1 -1
  76. runbooks/remediation/multi_account.py +1 -1
  77. runbooks/remediation/rds_remediation.py +1 -1
  78. runbooks/remediation/s3_block_public_access.py +1 -1
  79. runbooks/remediation/s3_enable_access_logging.py +1 -1
  80. runbooks/remediation/s3_encryption.py +1 -1
  81. runbooks/remediation/s3_remediation.py +1 -1
  82. runbooks/remediation/vpc_remediation.py +475 -0
  83. runbooks/security/__init__.py +3 -1
  84. runbooks/security/compliance_automation.py +632 -0
  85. runbooks/security/report_generator.py +10 -0
  86. runbooks/security/run_script.py +31 -5
  87. runbooks/security/security_baseline_tester.py +169 -30
  88. runbooks/security/security_export.py +477 -0
  89. runbooks/validation/__init__.py +10 -0
  90. runbooks/validation/benchmark.py +484 -0
  91. runbooks/validation/cli.py +356 -0
  92. runbooks/validation/mcp_validator.py +768 -0
  93. runbooks/vpc/__init__.py +38 -0
  94. runbooks/vpc/config.py +212 -0
  95. runbooks/vpc/cost_engine.py +347 -0
  96. runbooks/vpc/heatmap_engine.py +605 -0
  97. runbooks/vpc/manager_interface.py +634 -0
  98. runbooks/vpc/networking_wrapper.py +1260 -0
  99. runbooks/vpc/rich_formatters.py +679 -0
  100. runbooks/vpc/tests/__init__.py +5 -0
  101. runbooks/vpc/tests/conftest.py +356 -0
  102. runbooks/vpc/tests/test_cli_integration.py +530 -0
  103. runbooks/vpc/tests/test_config.py +458 -0
  104. runbooks/vpc/tests/test_cost_engine.py +479 -0
  105. runbooks/vpc/tests/test_networking_wrapper.py +512 -0
  106. {runbooks-0.7.6.dist-info → runbooks-0.7.9.dist-info}/METADATA +40 -12
  107. {runbooks-0.7.6.dist-info → runbooks-0.7.9.dist-info}/RECORD +111 -50
  108. {runbooks-0.7.6.dist-info → runbooks-0.7.9.dist-info}/WHEEL +0 -0
  109. {runbooks-0.7.6.dist-info → runbooks-0.7.9.dist-info}/entry_points.txt +0 -0
  110. {runbooks-0.7.6.dist-info → runbooks-0.7.9.dist-info}/licenses/LICENSE +0 -0
  111. {runbooks-0.7.6.dist-info → runbooks-0.7.9.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,393 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Rich Inventory Display - Enhanced inventory presentation with Rich library
4
+
5
+ This module provides enterprise-grade inventory display functionality using
6
+ the Rich library for beautiful, consistent CLI output that works in both
7
+ terminal and Jupyter environments.
8
+
9
+ Features:
10
+ - Rich progress bars for long-running operations
11
+ - Professional table formatting for results
12
+ - Status indicators and color coding
13
+ - Performance timing with visual feedback
14
+ - Consistent branding with CloudOps theme
15
+
16
+ Author: CloudOps Runbooks Team
17
+ Version: 0.7.8
18
+ """
19
+
20
+ from datetime import datetime
21
+ from typing import Any, Dict, List, Optional
22
+
23
+ from rich.console import Console
24
+ from rich.progress import Progress, SpinnerColumn, TextColumn, BarColumn, TaskProgressColumn
25
+ from rich.table import Table
26
+ from rich import box
27
+ from rich.panel import Panel
28
+ from rich.text import Text
29
+ from rich.tree import Tree
30
+
31
+ from runbooks.common.rich_utils import (
32
+ console,
33
+ create_table,
34
+ create_progress_bar,
35
+ print_status,
36
+ print_success,
37
+ print_info,
38
+ create_panel,
39
+ STATUS_INDICATORS
40
+ )
41
+
42
+
43
+ def display_inventory_header(operation: str, profile: str, accounts: int, regions: int) -> None:
44
+ """
45
+ Display inventory operation header with operation context.
46
+
47
+ Args:
48
+ operation: Type of inventory operation (EC2, RDS, S3, etc.)
49
+ profile: AWS profile being used
50
+ accounts: Number of accounts to scan
51
+ regions: Number of regions to scan
52
+ """
53
+ header_text = f"""
54
+ [bold cyan]🔍 AWS {operation} Inventory Discovery[/bold cyan]
55
+
56
+ [yellow]Profile:[/yellow] {profile}
57
+ [yellow]Scope:[/yellow] {accounts} accounts × {regions} regions = {accounts * regions} total operations
58
+ [yellow]Started:[/yellow] {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}
59
+ """
60
+
61
+ console.print(create_panel(
62
+ header_text.strip(),
63
+ title="📊 Inventory Operation",
64
+ border_style="cyan"
65
+ ))
66
+
67
+
68
+ def create_inventory_progress(total_operations: int, operation_name: str = "Scanning Resources") -> Progress:
69
+ """
70
+ Create a Rich progress bar for inventory operations.
71
+
72
+ Args:
73
+ total_operations: Total number of operations to perform
74
+ operation_name: Name of the operation being performed
75
+
76
+ Returns:
77
+ Progress instance for tracking
78
+ """
79
+ return Progress(
80
+ SpinnerColumn(spinner_name="dots", style="cyan"),
81
+ TextColumn("[progress.description]{task.description}"),
82
+ BarColumn(bar_width=40, style="cyan", complete_style="green"),
83
+ TaskProgressColumn(),
84
+ TextColumn("•"),
85
+ TextColumn("[blue]{task.completed}/{task.total} operations"),
86
+ console=console,
87
+ transient=False
88
+ )
89
+
90
+
91
+ def display_ec2_inventory_results(instances: List[Dict[str, Any]],
92
+ accounts: int,
93
+ regions: int,
94
+ timing_info: Optional[Dict] = None) -> None:
95
+ """
96
+ Display EC2 inventory results in a professional Rich table format.
97
+
98
+ Args:
99
+ instances: List of EC2 instance data
100
+ accounts: Number of accounts scanned
101
+ regions: Number of regions scanned
102
+ timing_info: Optional timing information
103
+ """
104
+ # Summary panel first
105
+ total_instances = len(instances)
106
+
107
+ # Count by state
108
+ state_counts = {}
109
+ for instance in instances:
110
+ state = instance.get("State", {}).get("Name", "unknown")
111
+ state_counts[state] = state_counts.get(state, 0) + 1
112
+
113
+ # Create status breakdown
114
+ status_text = ""
115
+ for state, count in sorted(state_counts.items()):
116
+ status_indicator = {
117
+ "running": "🟢",
118
+ "stopped": "🔴",
119
+ "stopping": "🟡",
120
+ "starting": "🟡",
121
+ "terminated": "⚫",
122
+ "terminating": "🟡"
123
+ }.get(state, "⚪")
124
+
125
+ status_text += f"{status_indicator} {state.title()}: {count}\n"
126
+
127
+ summary_content = f"""
128
+ [bold cyan]EC2 Inventory Summary[/bold cyan]
129
+
130
+ [green]Total Instances Found:[/green] {total_instances}
131
+ [green]Accounts Scanned:[/green] {accounts}
132
+ [green]Regions Scanned:[/green] {regions}
133
+
134
+ [bold yellow]Instance States:[/bold yellow]
135
+ {status_text.strip()}
136
+ """
137
+
138
+ console.print(create_panel(
139
+ summary_content.strip(),
140
+ title="📊 Discovery Results",
141
+ border_style="green"
142
+ ))
143
+
144
+ # Detailed results table if instances found
145
+ if instances:
146
+ # Group instances by account for better organization
147
+ instances_by_account = {}
148
+ for instance in instances:
149
+ account_id = instance.get("AccountId", "Unknown")
150
+ if account_id not in instances_by_account:
151
+ instances_by_account[account_id] = []
152
+ instances_by_account[account_id].append(instance)
153
+
154
+ # Create detailed table
155
+ table = create_table(
156
+ title="🖥️ EC2 Instance Details",
157
+ columns=[
158
+ {"name": "Account", "style": "cyan"},
159
+ {"name": "Region", "style": "yellow"},
160
+ {"name": "Instance ID", "style": "magenta"},
161
+ {"name": "Type", "style": "blue"},
162
+ {"name": "State", "style": "green"},
163
+ {"name": "Name", "style": "white"}
164
+ ],
165
+ box_style=box.ROUNDED
166
+ )
167
+
168
+ # Add rows (show first 50 instances to avoid overwhelming output)
169
+ displayed_count = 0
170
+ for account_id in sorted(instances_by_account.keys()):
171
+ account_instances = instances_by_account[account_id][:10] # Max 10 per account
172
+
173
+ for instance in account_instances:
174
+ if displayed_count >= 50: # Overall limit
175
+ break
176
+
177
+ # Extract instance information
178
+ instance_id = instance.get("InstanceId", "N/A")
179
+ instance_type = instance.get("InstanceType", "N/A")
180
+ state = instance.get("State", {}).get("Name", "unknown")
181
+ region = instance.get("Region", "N/A")
182
+
183
+ # Get instance name from tags
184
+ instance_name = "N/A"
185
+ tags = instance.get("Tags", [])
186
+ for tag in tags:
187
+ if tag.get("Key") == "Name":
188
+ instance_name = tag.get("Value", "N/A")
189
+ break
190
+
191
+ # Style state with appropriate color
192
+ state_styled = {
193
+ "running": "[green]🟢 Running[/green]",
194
+ "stopped": "[red]🔴 Stopped[/red]",
195
+ "stopping": "[yellow]🟡 Stopping[/yellow]",
196
+ "starting": "[yellow]🟡 Starting[/yellow]",
197
+ "terminated": "[dim]⚫ Terminated[/dim]",
198
+ "terminating": "[yellow]🟡 Terminating[/yellow]"
199
+ }.get(state, f"[white]⚪ {state.title()}[/white]")
200
+
201
+ table.add_row(
202
+ account_id[:12], # Truncate account ID
203
+ region,
204
+ instance_id,
205
+ instance_type,
206
+ state_styled,
207
+ instance_name[:20] if instance_name != "N/A" else "N/A" # Truncate long names
208
+ )
209
+
210
+ displayed_count += 1
211
+
212
+ console.print(table)
213
+
214
+ # Show truncation message if needed
215
+ if total_instances > 50:
216
+ console.print(f"\n[dim]Showing first 50 instances. Total found: {total_instances}[/dim]")
217
+
218
+ # Timing information
219
+ if timing_info:
220
+ execution_time = timing_info.get("total_time", 0)
221
+ print_success(f"✅ Inventory scan completed in {execution_time:.2f} seconds")
222
+
223
+ print_info("💡 Use --output json or --output csv to export complete results")
224
+
225
+
226
+ def display_generic_inventory_results(resource_type: str,
227
+ resources: List[Dict[str, Any]],
228
+ accounts: int,
229
+ regions: int) -> None:
230
+ """
231
+ Display generic inventory results for any resource type.
232
+
233
+ Args:
234
+ resource_type: Type of AWS resource (RDS, S3, Lambda, etc.)
235
+ resources: List of resource data
236
+ accounts: Number of accounts scanned
237
+ regions: Number of regions scanned
238
+ """
239
+ total_resources = len(resources)
240
+
241
+ # Resource type icons
242
+ resource_icons = {
243
+ "rds": "🗄️",
244
+ "s3": "🪣",
245
+ "lambda": "⚡",
246
+ "vpc": "🌐",
247
+ "iam": "👤",
248
+ "cloudformation": "📚",
249
+ "ssm": "🔑",
250
+ "route53": "🌍"
251
+ }
252
+
253
+ icon = resource_icons.get(resource_type.lower(), "📦")
254
+
255
+ summary_content = f"""
256
+ [bold cyan]{resource_type.upper()} Inventory Summary[/bold cyan]
257
+
258
+ [green]Total Resources Found:[/green] {total_resources}
259
+ [green]Accounts Scanned:[/green] {accounts}
260
+ [green]Regions Scanned:[/green] {regions}
261
+ """
262
+
263
+ console.print(create_panel(
264
+ summary_content.strip(),
265
+ title=f"{icon} Discovery Results",
266
+ border_style="green"
267
+ ))
268
+
269
+ if total_resources > 0:
270
+ print_success(f"✅ Found {total_resources} {resource_type} resources across {accounts} accounts")
271
+ else:
272
+ print_info(f"ℹ️ No {resource_type} resources found in the specified scope")
273
+
274
+
275
+ def display_inventory_error(error_message: str, suggestions: Optional[List[str]] = None) -> None:
276
+ """
277
+ Display inventory operation error with helpful suggestions.
278
+
279
+ Args:
280
+ error_message: Error message to display
281
+ suggestions: Optional list of suggestions for resolution
282
+ """
283
+ error_content = f"[bold red]❌ Inventory Operation Failed[/bold red]\n\n{error_message}"
284
+
285
+ if suggestions:
286
+ error_content += "\n\n[yellow]💡 Suggestions:[/yellow]\n"
287
+ for suggestion in suggestions:
288
+ error_content += f" • {suggestion}\n"
289
+
290
+ console.print(create_panel(
291
+ error_content.strip(),
292
+ title="🚨 Error",
293
+ border_style="red",
294
+ padding=1
295
+ ))
296
+
297
+
298
+ def display_multi_resource_summary(resource_counts: Dict[str, int],
299
+ accounts: int,
300
+ regions: int,
301
+ execution_time: float) -> None:
302
+ """
303
+ Display summary for multi-resource inventory operations.
304
+
305
+ Args:
306
+ resource_counts: Dictionary of resource type to count
307
+ accounts: Number of accounts scanned
308
+ regions: Number of regions scanned
309
+ execution_time: Total execution time in seconds
310
+ """
311
+ # Create summary table
312
+ table = create_table(
313
+ title="📊 Multi-Resource Inventory Summary",
314
+ columns=[
315
+ {"name": "Resource Type", "style": "cyan"},
316
+ {"name": "Count", "style": "green", "justify": "right"},
317
+ {"name": "Status", "style": "yellow"}
318
+ ]
319
+ )
320
+
321
+ total_resources = 0
322
+ for resource_type, count in sorted(resource_counts.items()):
323
+ status = "✅ Found" if count > 0 else "⚪ None"
324
+ table.add_row(
325
+ resource_type.title(),
326
+ str(count),
327
+ status
328
+ )
329
+ total_resources += count
330
+
331
+ console.print(table)
332
+
333
+ # Overall summary
334
+ summary_text = f"""
335
+ [bold green]Overall Summary[/bold green]
336
+
337
+ [cyan]Total Resources:[/cyan] {total_resources}
338
+ [cyan]Accounts Scanned:[/cyan] {accounts}
339
+ [cyan]Regions Scanned:[/cyan] {regions}
340
+ [cyan]Execution Time:[/cyan] {execution_time:.2f} seconds
341
+ [cyan]Performance:[/cyan] {(accounts * regions) / execution_time:.1f} operations/second
342
+ """
343
+
344
+ console.print(create_panel(
345
+ summary_text.strip(),
346
+ title="🎯 Inventory Complete",
347
+ border_style="bright_blue"
348
+ ))
349
+
350
+
351
+ def display_account_tree(accounts_data: Dict[str, Dict]) -> None:
352
+ """
353
+ Display account and resource hierarchy as a Rich tree.
354
+
355
+ Args:
356
+ accounts_data: Nested dictionary of account -> resource data
357
+ """
358
+ tree = Tree("🏢 [bold cyan]AWS Organization Structure[/bold cyan]")
359
+
360
+ for account_id, account_data in accounts_data.items():
361
+ account_name = account_data.get("account_name", "Unknown")
362
+ account_branch = tree.add(f"📊 [yellow]Account: {account_id}[/yellow] ({account_name})")
363
+
364
+ # Add regions
365
+ regions = account_data.get("regions", {})
366
+ for region, region_data in regions.items():
367
+ region_branch = account_branch.add(f"🌍 [green]Region: {region}[/green]")
368
+
369
+ # Add resource counts
370
+ for resource_type, count in region_data.get("resource_counts", {}).items():
371
+ if count > 0:
372
+ icon = {
373
+ "ec2": "🖥️",
374
+ "rds": "🗄️",
375
+ "s3": "🪣",
376
+ "lambda": "⚡"
377
+ }.get(resource_type, "📦")
378
+
379
+ region_branch.add(f"{icon} {resource_type.upper()}: [bold]{count}[/bold] resources")
380
+
381
+ console.print(tree)
382
+
383
+
384
+ # Export public functions
385
+ __all__ = [
386
+ "display_inventory_header",
387
+ "create_inventory_progress",
388
+ "display_ec2_inventory_results",
389
+ "display_generic_inventory_results",
390
+ "display_inventory_error",
391
+ "display_multi_resource_summary",
392
+ "display_account_tree"
393
+ ]
@@ -8,6 +8,11 @@ from account_class import aws_acct_access
8
8
  from ArgumentsClass import CommonArguments
9
9
  from botocore.exceptions import ClientError
10
10
  from colorama import Fore, init
11
+ from rich.console import Console
12
+ from rich.panel import Panel
13
+
14
+ # Initialize Rich console
15
+ console = Console()
11
16
 
12
17
  init()
13
18
  __version__ = "2023.05.04"
@@ -57,23 +62,23 @@ def check_account_access(faws_acct, faccount_num, fAccessRole=None):
57
62
  return_response = {"Credentials": credentials, "Success": True, "ErrorMessage": ""}
58
63
  return return_response
59
64
  except ClientError as my_Error:
60
- print(f"Client Error: {my_Error}")
65
+ console.print(f"[red]❌ Client Error: {my_Error}[/red]")
61
66
  return_response = {"Success": False, "ErrorMessage": "Client Error"}
62
67
  return return_response
63
68
  except sts_client.exceptions.MalformedPolicyDocumentException as my_Error:
64
- print(f"MalformedPolicy: {my_Error}")
69
+ console.print(f"[red]❌ MalformedPolicy: {my_Error}[/red]")
65
70
  return_response = {"Success": False, "ErrorMessage": "Malformed Policy"}
66
71
  return return_response
67
72
  except sts_client.exceptions.PackedPolicyTooLargeException as my_Error:
68
- print(f"Policy is too large: {my_Error}")
73
+ console.print(f"[red]❌ Policy is too large: {my_Error}[/red]")
69
74
  return_response = {"Success": False, "ErrorMessage": "Policy is too large"}
70
75
  return return_response
71
76
  except sts_client.exceptions.RegionDisabledException as my_Error:
72
- print(f"Region is disabled: {my_Error}")
77
+ console.print(f"[red]❌ Region is disabled: {my_Error}[/red]")
73
78
  return_response = {"Success": False, "ErrorMessage": "Region Disabled"}
74
79
  return return_response
75
80
  except sts_client.exceptions.ExpiredTokenException as my_Error:
76
- print(f"Expired Token: {my_Error}")
81
+ console.print(f"[red]❌ Expired Token: {my_Error}[/red]")
77
82
  return_response = {"Success": False, "ErrorMessage": "Expired Token"}
78
83
  return return_response
79
84
 
@@ -187,7 +192,7 @@ for account_num in Accounts:
187
192
  )
188
193
  response = check_account_access(aws_acct, account_num, pAccessRole)
189
194
  if response["Success"]:
190
- print(f"Account {account_num} was successfully connected via role {pAccessRole} from {aws_acct.acct_number}")
195
+ console.print(f"[green]✅ Account {account_num} was successfully connected via role {pAccessRole} from {aws_acct.acct_number}[/green]")
191
196
  """
192
197
  Put more commands here... Or you can write functions that represent your commands and call them from here.
193
198
  """
@@ -196,16 +201,27 @@ for account_num in Accounts:
196
201
  username = "Paul"
197
202
  user_response = participant_user(tgt_aws_access, username=username)
198
203
  else:
199
- print(
200
- f"Access Role {pAccessRole} failed to connect to {account_num} from {aws_acct.acct_number} with error: {response['ErrorMessage']}"
201
- )
202
-
203
- # Display access keys
204
- print(f"Credentials for account {tgt_aws_access.acct_number}")
205
- print(f"User {username} has been created (or confirmed) in account {user_response['AccountId']}")
206
- print(f"Password for {user_response['User']} is {user_response['Password']}")
207
- print(f"Access Keys are:\n{user_response['AccessKeyId']}\n{user_response['SecretAccessKey']}")
208
-
209
- print()
210
- print("Thanks for using this script...")
211
- print()
204
+ console.print(Panel(
205
+ f"Access Role {pAccessRole} failed to connect to {account_num} from {aws_acct.acct_number}",
206
+ title=f"[red]❌ Connection Error: {response['ErrorMessage']}[/red]",
207
+ border_style="red"
208
+ ))
209
+
210
+ # Display access keys with Rich formatting
211
+ credentials_info = [
212
+ f"[cyan]Account:[/cyan] {tgt_aws_access.acct_number}",
213
+ f"[cyan]User:[/cyan] {user_response['User']} (created/confirmed in account {user_response['AccountId']})",
214
+ f"[cyan]Password:[/cyan] {user_response['Password']}",
215
+ f"[cyan]Access Key ID:[/cyan] {user_response['AccessKeyId']}",
216
+ f"[cyan]Secret Access Key:[/cyan] {user_response['SecretAccessKey']}"
217
+ ]
218
+
219
+ console.print(Panel(
220
+ "\n".join(credentials_info),
221
+ title="[blue]🔑 Account Credentials[/blue]",
222
+ border_style="blue"
223
+ ))
224
+
225
+ console.print("")
226
+ console.print("[green]✅ Thanks for using this script...[/green]")
227
+ console.print("")
File without changes