runbooks 1.1.5__py3-none-any.whl → 1.1.7__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 (65) hide show
  1. runbooks/__init__.py +1 -1
  2. runbooks/cli/commands/finops.py +29 -5
  3. runbooks/cli/commands/inventory.py +40 -87
  4. runbooks/common/accuracy_validator.py +6 -12
  5. runbooks/common/cli_decorators.py +61 -0
  6. runbooks/common/mcp_integration.py +38 -7
  7. runbooks/common/rich_utils.py +116 -2
  8. runbooks/inventory/CLAUDE.md +1 -1
  9. runbooks/inventory/aws_decorators.py +2 -3
  10. runbooks/inventory/check_cloudtrail_compliance.py +2 -4
  11. runbooks/inventory/check_controltower_readiness.py +152 -151
  12. runbooks/inventory/check_landingzone_readiness.py +85 -84
  13. runbooks/inventory/core/formatter.py +11 -0
  14. runbooks/inventory/draw_org_structure.py +8 -9
  15. runbooks/inventory/ec2_vpc_utils.py +2 -2
  16. runbooks/inventory/find_cfn_drift_detection.py +5 -7
  17. runbooks/inventory/find_cfn_orphaned_stacks.py +7 -9
  18. runbooks/inventory/find_cfn_stackset_drift.py +5 -6
  19. runbooks/inventory/find_ec2_security_groups.py +48 -42
  20. runbooks/inventory/find_landingzone_versions.py +4 -6
  21. runbooks/inventory/find_vpc_flow_logs.py +7 -9
  22. runbooks/inventory/inventory_modules.py +103 -91
  23. runbooks/inventory/list_cfn_stacks.py +9 -10
  24. runbooks/inventory/list_cfn_stackset_operation_results.py +1 -3
  25. runbooks/inventory/list_cfn_stackset_operations.py +79 -57
  26. runbooks/inventory/list_cfn_stacksets.py +8 -10
  27. runbooks/inventory/list_config_recorders_delivery_channels.py +49 -39
  28. runbooks/inventory/list_ds_directories.py +65 -53
  29. runbooks/inventory/list_ec2_availability_zones.py +2 -4
  30. runbooks/inventory/list_ec2_ebs_volumes.py +32 -35
  31. runbooks/inventory/list_ec2_instances.py +23 -28
  32. runbooks/inventory/list_ecs_clusters_and_tasks.py +26 -34
  33. runbooks/inventory/list_elbs_load_balancers.py +22 -20
  34. runbooks/inventory/list_enis_network_interfaces.py +26 -33
  35. runbooks/inventory/list_guardduty_detectors.py +2 -4
  36. runbooks/inventory/list_iam_policies.py +2 -4
  37. runbooks/inventory/list_iam_roles.py +5 -7
  38. runbooks/inventory/list_iam_saml_providers.py +4 -6
  39. runbooks/inventory/list_lambda_functions.py +38 -38
  40. runbooks/inventory/list_org_accounts.py +6 -8
  41. runbooks/inventory/list_org_accounts_users.py +55 -44
  42. runbooks/inventory/list_rds_db_instances.py +31 -33
  43. runbooks/inventory/list_route53_hosted_zones.py +3 -5
  44. runbooks/inventory/list_servicecatalog_provisioned_products.py +37 -41
  45. runbooks/inventory/list_sns_topics.py +2 -4
  46. runbooks/inventory/list_ssm_parameters.py +4 -7
  47. runbooks/inventory/list_vpc_subnets.py +2 -4
  48. runbooks/inventory/list_vpcs.py +7 -10
  49. runbooks/inventory/mcp_inventory_validator.py +5 -3
  50. runbooks/inventory/organizations_discovery.py +8 -4
  51. runbooks/inventory/recover_cfn_stack_ids.py +7 -8
  52. runbooks/inventory/requirements.txt +0 -1
  53. runbooks/inventory/rich_inventory_display.py +2 -2
  54. runbooks/inventory/run_on_multi_accounts.py +3 -5
  55. runbooks/inventory/unified_validation_engine.py +3 -2
  56. runbooks/inventory/verify_ec2_security_groups.py +1 -1
  57. runbooks/inventory/vpc_analyzer.py +3 -2
  58. runbooks/inventory/vpc_dependency_analyzer.py +2 -2
  59. runbooks/validation/terraform_drift_detector.py +16 -5
  60. {runbooks-1.1.5.dist-info → runbooks-1.1.7.dist-info}/METADATA +3 -4
  61. {runbooks-1.1.5.dist-info → runbooks-1.1.7.dist-info}/RECORD +65 -65
  62. {runbooks-1.1.5.dist-info → runbooks-1.1.7.dist-info}/WHEEL +0 -0
  63. {runbooks-1.1.5.dist-info → runbooks-1.1.7.dist-info}/entry_points.txt +0 -0
  64. {runbooks-1.1.5.dist-info → runbooks-1.1.7.dist-info}/licenses/LICENSE +0 -0
  65. {runbooks-1.1.5.dist-info → runbooks-1.1.7.dist-info}/top_level.txt +0 -0
@@ -62,18 +62,17 @@ from time import time
62
62
 
63
63
  from ArgumentsClass import CommonArguments
64
64
  from botocore.exceptions import ClientError
65
- from colorama import Fore, init
65
+ from runbooks.common.rich_utils import console
66
66
  from Inventory_Modules import (
67
67
  display_results,
68
68
  find_references_to_security_groups2,
69
69
  find_security_groups2,
70
70
  get_all_credentials,
71
71
  )
72
- from tqdm.auto import tqdm
72
+ # Migrated to Rich.Progress - see rich_utils.py for enterprise UX standards
73
+ # from tqdm.auto import tqdm
73
74
 
74
- init()
75
75
  __version__ = "2024.09.24"
76
- ERASE_LINE = "\x1b[2K"
77
76
  begin_time = time()
78
77
 
79
78
 
@@ -387,7 +386,7 @@ def check_accounts_for_security_groups(
387
386
  logging.info(
388
387
  f"{ERASE_LINE}Finished finding security groups in account {c_account_credentials['AccountId']} in region {c_account_credentials['Region']}"
389
388
  )
390
- pbar.update()
389
+ pbar.update(pbar_task, advance=1) # Update Rich progress bar
391
390
  self.queue.task_done()
392
391
 
393
392
  ###########
@@ -406,43 +405,50 @@ def check_accounts_for_security_groups(
406
405
  # Initialize thread-safe work queue
407
406
  checkqueue = Queue()
408
407
 
409
- # Initialize progress bar for user feedback during long-running analysis
410
- pbar = tqdm(
411
- desc=f"Finding security groups from {len(fCredentialList)} locations",
412
- total=len(fCredentialList),
413
- unit=" locations",
414
- )
408
+ # Import Rich display utilities for professional progress tracking
409
+ from runbooks.common.rich_utils import create_progress_bar
410
+
411
+ # Initialize progress tracking for user feedback during long-running analysis
412
+ with create_progress_bar() as progress:
413
+ task = progress.add_task(
414
+ f"[cyan]Finding security groups from {len(fCredentialList)} locations...",
415
+ total=len(fCredentialList)
416
+ )
417
+
418
+ # Make progress object available to worker threads via global (multi-threaded pattern)
419
+ global pbar
420
+ pbar = progress
421
+ global pbar_task
422
+ pbar_task = task
423
+
424
+ # Start worker thread pool for concurrent security group analysis
425
+ for x in range(WorkerThreads):
426
+ worker = FindSecurityGroups(checkqueue)
427
+ # Daemon threads will terminate when main thread exits
428
+ # This prevents hanging if an exception occurs in main thread
429
+ worker.daemon = True
430
+ worker.start()
431
+
432
+ # Queue all credential work items for concurrent processing
433
+ for credential in fCredentialList:
434
+ logging.info(f"Connecting to account {credential['AccountId']}")
435
+ try:
436
+ # Add credential set and parameters to work queue for worker thread processing
437
+ checkqueue.put((credential, fFragment, fExact, fDefault))
438
+ except ClientError as my_Error:
439
+ # Handle credential validation errors during queue population
440
+ if "AuthFailure" in str(my_Error):
441
+ logging.error(
442
+ f"Authorization Failure accessing account {credential['AccountId']} in '{credential['Region']}' region"
443
+ )
444
+ logging.warning(f"It's possible that the region '{credential['Region']}' hasn't been opted-into")
445
+ # Continue queuing other credentials despite this failure
446
+ pass
415
447
 
416
- # Start worker thread pool for concurrent security group analysis
417
- for x in range(WorkerThreads):
418
- worker = FindSecurityGroups(checkqueue)
419
- # Daemon threads will terminate when main thread exits
420
- # This prevents hanging if an exception occurs in main thread
421
- worker.daemon = True
422
- worker.start()
423
-
424
- # Queue all credential work items for concurrent processing
425
- for credential in fCredentialList:
426
- logging.info(f"Connecting to account {credential['AccountId']}")
427
- try:
428
- # Add credential set and parameters to work queue for worker thread processing
429
- checkqueue.put((credential, fFragment, fExact, fDefault))
430
- except ClientError as my_Error:
431
- # Handle credential validation errors during queue population
432
- if "AuthFailure" in str(my_Error):
433
- logging.error(
434
- f"Authorization Failure accessing account {credential['AccountId']} in '{credential['Region']}' region"
435
- )
436
- logging.warning(f"It's possible that the region '{credential['Region']}' hasn't been opted-into")
437
- # Continue queuing other credentials despite this failure
438
- pass
439
-
440
- # Wait for all queued work items to be processed by worker threads
441
- # This blocks until all worker threads call task_done()
442
- checkqueue.join()
443
-
444
- # Clean up progress bar
445
- pbar.close()
448
+ # Wait for all queued work items to be processed by worker threads
449
+ # This blocks until all worker threads call task_done()
450
+ checkqueue.join()
451
+ # Progress bar auto-closes when exiting context manager
446
452
 
447
453
  return AllSecurityGroups
448
454
 
@@ -659,7 +665,7 @@ if __name__ == "__main__":
659
665
  print(f"Data has been saved to {saved_filename}")
660
666
  if pTiming:
661
667
  print(ERASE_LINE)
662
- print(f"{Fore.GREEN}This script took {time() - begin_time:.2f} seconds{Fore.RESET}")
668
+ print(f"[green]This script took {time() - begin_time:.2f} seconds")
663
669
 
664
670
  print(
665
671
  f"We found {len(AllSecurityGroups)} {'default ' if pDefault else ''}security group{'' if len(AllSecurityGroups) == 1 else 's'} across {len(AccountList)} accounts and {len(RegionList)} regions"
@@ -76,10 +76,9 @@ import boto3
76
76
  import Inventory_Modules
77
77
  from ArgumentsClass import CommonArguments
78
78
  from botocore.exceptions import ClientError, CredentialRetrievalError, InvalidConfigError
79
- from colorama import init
79
+ # colorama removed - migrated to Rich
80
80
 
81
81
  # Initialize colorama for cross-platform colored terminal output
82
- init()
83
82
 
84
83
  __version__ = "2023.05.31"
85
84
 
@@ -101,7 +100,6 @@ logging.basicConfig(
101
100
 
102
101
  ##########################
103
102
  # Terminal control and operational configuration constants
104
- ERASE_LINE = "\x1b[2K" # ANSI escape sequence for clearing terminal line during progress display
105
103
  SkipProfiles = ["default"] # Profile exclusion list for organizational Landing Zone discovery
106
104
 
107
105
  # Configure AWS profile discovery strategy based on user input
@@ -129,7 +127,7 @@ ALZProfiles = [] # Initialize Landing Zone profile collection for discovered Ma
129
127
 
130
128
  for profile in AllProfiles:
131
129
  # Display real-time progress during profile analysis with terminal line clearing
132
- print(f"{ERASE_LINE}Checking profile: {profile}", end="\r")
130
+ print(f"Checking profile: {profile}", end="\r")
133
131
 
134
132
  try:
135
133
  # Analyze current profile to determine if it represents an AWS Landing Zone Management Account
@@ -164,7 +162,7 @@ for profile in AllProfiles:
164
162
  pass
165
163
 
166
164
  # Clear progress display and initialize tabular output formatting for Landing Zone inventory
167
- print(ERASE_LINE)
165
+ console.print()
168
166
  fmt = "%-20s %-13s %-15s %-35s %-21s" # Column formatting for structured Landing Zone data display
169
167
  print(fmt % ("Profile", "Account", "Region", "ALZ Stack Name", "ALZ Version"))
170
168
  print(fmt % ("-------", "-------", "------", "--------------", "-----------"))
@@ -194,7 +192,7 @@ for item in ALZProfiles:
194
192
  )
195
193
 
196
194
  # Display comprehensive operational summary with discovery metrics
197
- print(ERASE_LINE)
195
+ console.print()
198
196
  print(f"Checked {len(AllProfiles)} accounts/ Orgs. Found {len(ALZProfiles)} ALZs")
199
197
  print()
200
198
  print("Thank you for using this script.")
@@ -93,13 +93,11 @@ from account_class import aws_acct_access
93
93
  from ArgumentsClass import CommonArguments
94
94
  from botocore.config import Config
95
95
  from botocore.exceptions import ClientError
96
- from colorama import Fore, init
96
+ from runbooks.common.rich_utils import console
97
97
  from Inventory_Modules import RemoveCoreAccounts, display_results, get_all_credentials, get_regions3
98
98
 
99
99
  # Initialize colorama for cross-platform colored terminal output
100
- init()
101
100
  __version__ = "2024.03.10"
102
- ERASE_LINE = "\x1b[2K" # ANSI escape sequence for clearing terminal line during progress display
103
101
  begin_time = time() # Script execution timing for performance monitoring
104
102
  sleep_interval = 5 # Default wait interval for CloudWatch Logs query processing
105
103
 
@@ -305,13 +303,13 @@ def setup_auth_accounts_and_regions(fProfile: str) -> (aws_acct_access, list, li
305
303
 
306
304
  # Display comprehensive operational context for user confirmation and audit logging
307
305
  print(f"You asked to sum flow log data")
308
- print(f"\tin these accounts: {Fore.RED}{AccountList}{Fore.RESET}")
309
- print(f"\tin these regions: {Fore.RED}{RegionList}{Fore.RESET}")
306
+ print(f"\tin these accounts: [red]{AccountList}")
307
+ print(f"\tin these regions: [red]{RegionList}")
310
308
  print(f"\tFrom: {pStartDate} until {pEndDate}")
311
309
 
312
310
  # Display account exclusion information for operational transparency
313
311
  if pSkipAccounts is not None:
314
- print(f"\tWhile skipping these accounts: {Fore.RED}{pSkipAccounts}{Fore.RESET}")
312
+ print(f"\tWhile skipping these accounts: [red]{pSkipAccounts}")
315
313
 
316
314
  return aws_acct, AccountList, RegionList
317
315
 
@@ -1040,13 +1038,13 @@ def get_cw_query_results(fquery_requests: list) -> list[dict]:
1040
1038
  # Implement timeout protection for long-running queries
1041
1039
  if waited_seconds_total > (SpannedDaysChecked * 5):
1042
1040
  print(
1043
- f"{ERASE_LINE}Query is still running... Waited {waited_seconds_total} seconds already, we'll have to check manually later. "
1041
+ f"Query is still running... Waited {waited_seconds_total} seconds already, we'll have to check manually later. "
1044
1042
  )
1045
1043
  break
1046
1044
 
1047
1045
  # Display real-time progress for query execution monitoring
1048
1046
  print(
1049
- f"{ERASE_LINE}Query for vpc {query['VPCId']} in account {query['AccountId']} in region {query['Region']} is still running... It's been {waited_seconds_total} seconds so far",
1047
+ f"Query for vpc {query['VPCId']} in account {query['AccountId']} in region {query['Region']} is still running... It's been {waited_seconds_total} seconds so far",
1050
1048
  end="\r",
1051
1049
  )
1052
1050
 
@@ -1181,7 +1179,7 @@ if __name__ == "__main__":
1181
1179
  f"Account {credential['AccountId']} was successfully connected via role {credential.get('Role', pAccessRole)} from {aws_acct.acct_number}"
1182
1180
  )
1183
1181
  print(
1184
- f"{ERASE_LINE}Checking account {Fore.BLUE}{credential['AccountId']}{Fore.RESET} in region {Fore.BLUE}{credential['Region']}{Fore.RESET}...",
1182
+ f"Checking account [blue]{credential['AccountId']} in region [blue]{credential['Region']}...",
1185
1183
  end="\r",
1186
1184
  )
1187
1185
  """
@@ -455,12 +455,12 @@ def print_timings(fTiming: bool = False, fverbose: int = 50, fbegin_time=None, f
455
455
  """
456
456
  from time import time
457
457
 
458
- from colorama import Fore, init
458
+ from runbooks.common.rich_utils import console
459
459
 
460
460
  init()
461
461
 
462
462
  if fTiming and fverbose < 50 and fbegin_time is not None:
463
- print(f"{Fore.GREEN}{fmessage}\nThis script has taken {time() - fbegin_time:.6f} seconds so far{Fore.RESET}")
463
+ print(f"[green]{fmessage}\nThis script has taken {time() - fbegin_time:.6f} seconds so far")
464
464
 
465
465
 
466
466
  def make_creds(faws_acct):
@@ -4241,12 +4241,14 @@ def find_stacksets3(
4241
4241
  from queue import Queue
4242
4242
  from threading import Thread
4243
4243
 
4244
- from tqdm.auto import tqdm
4244
+ from runbooks.common.rich_utils import create_progress_bar
4245
4245
 
4246
4246
  class GetStackSetStatus(Thread):
4247
- def __init__(self, queue):
4247
+ def __init__(self, queue, progress, task_id):
4248
4248
  Thread.__init__(self)
4249
4249
  self.queue = queue
4250
+ self.progress = progress
4251
+ self.task_id = task_id
4250
4252
 
4251
4253
  def run(self):
4252
4254
  while True:
@@ -4317,7 +4319,7 @@ def find_stacksets3(
4317
4319
  )
4318
4320
  continue
4319
4321
  finally:
4320
- pbar.update()
4322
+ self.progress.update(self.task_id, advance=1)
4321
4323
  self.queue.task_done()
4322
4324
 
4323
4325
  ###########
@@ -4328,35 +4330,35 @@ def find_stacksets3(
4328
4330
  WorkerThreads = min(len(fStackSetsCopy), MaxWorkerThreads)
4329
4331
  logging.info(f"Using {WorkerThreads} threads")
4330
4332
 
4331
- pbar = tqdm(
4332
- desc=f"Finding all Stacksets from {len(fStackSetsCopy)} stacksets",
4333
- total=len(fStackSetsCopy),
4334
- unit=" stacksets",
4335
- )
4333
+ with create_progress_bar() as progress:
4334
+ task = progress.add_task(
4335
+ "[cyan]Finding all Stacksets...",
4336
+ total=len(fStackSetsCopy)
4337
+ )
4336
4338
 
4337
- for x in range(WorkerThreads):
4338
- worker = GetStackSetStatus(checkqueue)
4339
- # Setting daemon to True will let the main thread exit even though the workers are blocking
4340
- worker.daemon = True
4341
- worker.start()
4339
+ for x in range(WorkerThreads):
4340
+ worker = GetStackSetStatus(checkqueue, progress, task)
4341
+ # Setting daemon to True will let the main thread exit even though the workers are blocking
4342
+ worker.daemon = True
4343
+ worker.start()
4344
+
4345
+ for stackset in fStackSetsCopy:
4346
+ logging.debug(f"Beginning to queue data - starting with {stackset['StackSetName']}")
4347
+ try:
4348
+ # I don't know why - but double parens are necessary below. If you remove them, only the first parameter is queued.
4349
+ PlaceCount += 1
4350
+ # print(".", end='')
4351
+ checkqueue.put((stackset, fRegion, PlaceCount))
4352
+ except ClientError as my_Error:
4353
+ if "AuthFailure" in str(my_Error):
4354
+ logging.error(
4355
+ f"Authorization Failure accessing stack set {stackset['StackSetName']} in {fRegion} region"
4356
+ )
4357
+ logging.warning(f"It's possible that the region {fRegion} hasn't been opted-into")
4358
+ pass
4359
+ checkqueue.join()
4360
+ logging.info(f"Getting the stackset operation data took {time() - begin_time:.2f} seconds")
4342
4361
 
4343
- for stackset in fStackSetsCopy:
4344
- logging.debug(f"Beginning to queue data - starting with {stackset['StackSetName']}")
4345
- try:
4346
- # I don't know why - but double parens are necessary below. If you remove them, only the first parameter is queued.
4347
- PlaceCount += 1
4348
- # print(".", end='')
4349
- checkqueue.put((stackset, fRegion, PlaceCount))
4350
- except ClientError as my_Error:
4351
- if "AuthFailure" in str(my_Error):
4352
- logging.error(
4353
- f"Authorization Failure accessing stack set {stackset['StackSetName']} in {fRegion} region"
4354
- )
4355
- logging.warning(f"It's possible that the region {fRegion} hasn't been opted-into")
4356
- pass
4357
- checkqueue.join()
4358
- logging.info(f"Getting the stackset operation data took {time() - begin_time:.2f} seconds")
4359
- pbar.close()
4360
4362
  return fStackSetsCopy
4361
4363
 
4362
4364
  # Logging Settings
@@ -5341,7 +5343,7 @@ def display_results(
5341
5343
  ):
5342
5344
  from datetime import datetime
5343
5345
 
5344
- from colorama import Fore, init
5346
+ from runbooks.common.rich_utils import console
5345
5347
 
5346
5348
  init()
5347
5349
  """
@@ -5757,12 +5759,12 @@ def get_all_credentials(
5757
5759
  from .account_class import aws_acct_access
5758
5760
 
5759
5761
  # from time import time
5760
- from colorama import Fore, init
5762
+ from runbooks.common.rich_utils import console
5761
5763
 
5762
5764
  init()
5763
5765
  # ERASE_LINE = '\x1b[2K'
5764
5766
  # begin_time = time()
5765
- print(f"{Fore.GREEN}Timing is enabled{Fore.RESET}") if fTiming else None
5767
+ print(f"[green]Timing is enabled") if fTiming else None
5766
5768
 
5767
5769
  AllCredentials = []
5768
5770
  if fSkipProfiles is None:
@@ -5848,16 +5850,18 @@ def get_credentials_for_accounts_in_org(
5848
5850
  from time import time
5849
5851
 
5850
5852
  from botocore.exceptions import ClientError
5851
- from colorama import Fore, init
5852
- from tqdm.auto import tqdm
5853
+ from runbooks.common.rich_utils import console
5854
+ from runbooks.common.rich_utils import create_progress_bar
5853
5855
 
5854
5856
  init()
5855
5857
  begin_time = time()
5856
5858
 
5857
5859
  class AssembleCredentials(Thread):
5858
- def __init__(self, queue):
5860
+ def __init__(self, queue, progress, task_id):
5859
5861
  Thread.__init__(self)
5860
5862
  self.queue = queue
5863
+ self.progress = progress
5864
+ self.task_id = task_id
5861
5865
 
5862
5866
  def run(self):
5863
5867
  while True:
@@ -5914,7 +5918,7 @@ def get_credentials_for_accounts_in_org(
5914
5918
  logging.error(f"Error: Likely that one of the supplied profiles was wrong\nError: {my_Error}")
5915
5919
  continue
5916
5920
  finally:
5917
- pbar.update()
5921
+ self.progress.update(self.task_id, advance=1)
5918
5922
  self.queue.task_done()
5919
5923
 
5920
5924
  if fSkipAccounts is None:
@@ -5959,47 +5963,48 @@ def get_credentials_for_accounts_in_org(
5959
5963
  # Defaults to 50, unless something more was passed in - which is only done for time testing.
5960
5964
  WorkerThreads = min(len(ChildAccounts) * len(fregions), MaxThreads)
5961
5965
 
5962
- # Create x worker threads
5963
- for x in range(WorkerThreads):
5964
- worker = AssembleCredentials(credqueue)
5965
- # Setting daemon to True will let the main thread exit even though the workers are blocking
5966
- worker.daemon = True
5967
- worker.start()
5968
-
5969
- pbar = tqdm(
5970
- desc=f"Getting credentials for profile: {fprofile} with {len(ChildAccounts)} accounts in {len(fregions)} regions",
5971
- total=len(ChildAccounts) * len(fregions),
5972
- unit=" credentials",
5973
- )
5974
-
5975
5966
  logging.info(
5976
5967
  f"You asked to check {len(ChildAccounts) * len(fregions)} place{'s' if len(ChildAccounts) * len(fregions) > 1 else ''}... It's going to take a moment"
5977
5968
  )
5978
- logging.info(
5979
- f"{Fore.GREEN}It's taken {time() - begin_time:.2f} seconds to prep WorkerThreads and such{Fore.RESET}"
5980
- ) if fTiming else None
5981
- for account in ChildAccounts:
5982
- if account["AccountId"] in fSkipAccounts:
5983
- continue
5984
- elif fRootOnly and not account["AccountId"] == account["MgmtAccount"]:
5985
- continue
5986
- elif accountlist and account["AccountId"] not in accountlist:
5987
- continue
5988
- AccountNum += 1
5989
- logging.info(f"Queuing account info for {AccountNum} / {len(ChildAccounts)} accounts in profile {fprofile}")
5990
- RegionNum = 0
5991
- for region in fregions:
5992
- RegionNum += 1
5993
- logging.info(f"\t\tRegion {RegionNum} of {len(fregions)}")
5994
- credqueue.put((account, fprofile, region))
5995
- logging.info(f"Account / Region: {account} / {region} | {datetime.now()}")
5996
- logging.info(f"Queue Size: {credqueue.qsize()}")
5997
- print(
5998
- f"{Fore.GREEN}Enumerating {AccountNum} account{'s' if len(ChildAccounts) * len(fregions) > 1 else ''} and {len(fregions)} regions "
5999
- f"took {time() - begin_time:.3f} seconds {Fore.RESET}"
6000
- ) if fTiming else None
6001
- credqueue.join()
6002
- pbar.close()
5969
+
5970
+ with create_progress_bar() as progress:
5971
+ task = progress.add_task(
5972
+ "[cyan]Getting credentials...",
5973
+ total=len(ChildAccounts) * len(fregions)
5974
+ )
5975
+
5976
+ # Create x worker threads
5977
+ for x in range(WorkerThreads):
5978
+ worker = AssembleCredentials(credqueue, progress, task)
5979
+ # Setting daemon to True will let the main thread exit even though the workers are blocking
5980
+ worker.daemon = True
5981
+ worker.start()
5982
+
5983
+ logging.info(
5984
+ f"[green]It's taken {time() - begin_time:.2f} seconds to prep WorkerThreads and such"
5985
+ ) if fTiming else None
5986
+ for account in ChildAccounts:
5987
+ if account["AccountId"] in fSkipAccounts:
5988
+ continue
5989
+ elif fRootOnly and not account["AccountId"] == account["MgmtAccount"]:
5990
+ continue
5991
+ elif accountlist and account["AccountId"] not in accountlist:
5992
+ continue
5993
+ AccountNum += 1
5994
+ logging.info(f"Queuing account info for {AccountNum} / {len(ChildAccounts)} accounts in profile {fprofile}")
5995
+ RegionNum = 0
5996
+ for region in fregions:
5997
+ RegionNum += 1
5998
+ logging.info(f"\t\tRegion {RegionNum} of {len(fregions)}")
5999
+ credqueue.put((account, fprofile, region))
6000
+ logging.info(f"Account / Region: {account} / {region} | {datetime.now()}")
6001
+ logging.info(f"Queue Size: {credqueue.qsize()}")
6002
+ print(
6003
+ f"[green]Enumerating {AccountNum} account{'s' if len(ChildAccounts) * len(fregions) > 1 else ''} and {len(fregions)} regions "
6004
+ f"took {time() - begin_time:.3f} seconds "
6005
+ ) if fTiming else None
6006
+ credqueue.join()
6007
+
6003
6008
  return AllCreds
6004
6009
 
6005
6010
 
@@ -6015,19 +6020,21 @@ def get_org_accounts_from_profiles(fProfileList):
6015
6020
 
6016
6021
  from .account_class import aws_acct_access
6017
6022
  from botocore.exceptions import ClientError, InvalidConfigError, NoCredentialsError
6018
- from tqdm.auto import tqdm
6023
+ from runbooks.common.rich_utils import create_progress_bar
6019
6024
 
6020
6025
  class AssembleCredentials(Thread):
6021
- def __init__(self, queue):
6026
+ def __init__(self, queue, progress, task_id):
6022
6027
  Thread.__init__(self)
6023
6028
  self.queue = queue
6029
+ self.progress = progress
6030
+ self.task_id = task_id
6024
6031
 
6025
6032
  def run(self):
6026
6033
  # Account = dict()
6027
6034
  while True:
6028
6035
  # Get the work from the queue and expand the tuple
6029
6036
  profile = self.queue.get()
6030
- pbar.update()
6037
+ self.progress.update(self.task_id, advance=1)
6031
6038
  Account = {
6032
6039
  "ErrorFlag": False,
6033
6040
  "Success": False,
@@ -6111,17 +6118,22 @@ def get_org_accounts_from_profiles(fProfileList):
6111
6118
  # WorkerThreads = len(fProfileList)
6112
6119
  WorkerThreads = 2
6113
6120
 
6114
- # Create x worker threads
6115
- for x in range(WorkerThreads):
6116
- worker = AssembleCredentials(profilequeue)
6117
- # Setting daemon to True will let the main thread exit even though the workers are blocking
6118
- worker.daemon = True
6119
- worker.start()
6121
+ with create_progress_bar() as progress:
6122
+ task = progress.add_task(
6123
+ f"[cyan]Getting accounts from {len(fProfileList)} profiles...",
6124
+ total=len(fProfileList)
6125
+ )
6126
+
6127
+ # Create x worker threads
6128
+ for x in range(WorkerThreads):
6129
+ worker = AssembleCredentials(profilequeue, progress, task)
6130
+ # Setting daemon to True will let the main thread exit even though the workers are blocking
6131
+ worker.daemon = True
6132
+ worker.start()
6120
6133
 
6121
- pbar = tqdm(desc=f"Getting accounts from {len(fProfileList)} profiles", total=len(fProfileList))
6134
+ for profile_item in fProfileList:
6135
+ logging.info(f"Queuing profile {profile_item} / {len(fProfileList)} profiles")
6136
+ profilequeue.put(profile_item)
6137
+ profilequeue.join()
6122
6138
 
6123
- for profile_item in fProfileList:
6124
- logging.info(f"Queuing profile {profile_item} / {len(fProfileList)} profiles")
6125
- profilequeue.put(profile_item)
6126
- profilequeue.join()
6127
6139
  return AllAccounts
@@ -67,10 +67,9 @@ import Inventory_Modules
67
67
  from account_class import aws_acct_access
68
68
  from ArgumentsClass import CommonArguments
69
69
  from botocore.exceptions import ClientError
70
- from colorama import Fore, init
70
+ from runbooks.common.rich_utils import console
71
71
  from Inventory_Modules import display_results, get_all_credentials
72
72
 
73
- init()
74
73
 
75
74
  __version__ = "2024.05.31"
76
75
 
@@ -230,16 +229,16 @@ def setup_auth_accounts_and_regions(
230
229
  else:
231
230
  AccountList = [account["AccountId"] for account in ChildAccounts if account["AccountId"] in fAccountList]
232
231
 
233
- print(f"You asked to find stacks with this fragment {Fore.RED}'{fStackFrag}'{Fore.RESET}")
234
- print(f"in these accounts:\n{Fore.RED}{AccountList}{Fore.RESET}")
235
- print(f"in these regions:\n{Fore.RED}{RegionList}{Fore.RESET}")
236
- print(f"While skipping these accounts:\n{Fore.RED}{fSkipAccounts}{Fore.RESET}") if fSkipAccounts is not None else ""
232
+ print(f"You asked to find stacks with this fragment [red]'{fStackFrag}'")
233
+ print(f"in these accounts:\n[red]{AccountList}")
234
+ print(f"in these regions:\n[red]{RegionList}")
235
+ print(f"While skipping these accounts:\n[red]{fSkipAccounts}") if fSkipAccounts is not None else ""
237
236
  if fDeletionRun:
238
237
  print()
239
238
  print("And delete the stacks that are found...")
240
239
 
241
240
  if fExact:
242
- print(f"\t\tFor stacks that {Fore.RED}exactly match{Fore.RESET} these fragments: {fStackFrag}")
241
+ print(f"\t\tFor stacks that [red]exactly match these fragments: {fStackFrag}")
243
242
  else:
244
243
  print(f"\t\tFor stacks that contains these fragments: {fStackFrag}")
245
244
 
@@ -330,7 +329,7 @@ def collect_cfnstacks(fCredentialList: list) -> list:
330
329
 
331
330
  # Display real-time progress with colored output
332
331
  print(
333
- f"{ERASE_LINE}{Fore.RED}Account: {credential['AccountId']} Region: {credential['Region']} Found {len(Stacks)} Stacks{Fore.RESET}",
332
+ f"{ERASE_LINE}[red]Account: {credential['AccountId']} Region: {credential['Region']} Found {len(Stacks)} Stacks",
334
333
  end="\r",
335
334
  )
336
335
 
@@ -410,7 +409,7 @@ def display_stacks(fAllStacks: list):
410
409
  )
411
410
  print(ERASE_LINE)
412
411
  print(
413
- f"{Fore.RED}Found {len(fAllStacks)} stacks across {len(AccountList)} accounts across {len(RegionList)} regions{Fore.RESET}"
412
+ f"[red]Found {len(fAllStacks)} stacks across {len(AccountList)} accounts across {len(RegionList)} regions"
414
413
  )
415
414
  print()
416
415
  if args.loglevel < 21: # INFO level
@@ -551,7 +550,7 @@ if __name__ == "__main__":
551
550
 
552
551
  if pTiming:
553
552
  print(ERASE_LINE)
554
- print(f"{Fore.GREEN}This script took {time() - begin_time:.2f} seconds{Fore.RESET}")
553
+ print(f"[green]This script took {time() - begin_time:.2f} seconds")
555
554
 
556
555
  print()
557
556
  print("Thanks for using this script...")
@@ -76,9 +76,8 @@ import logging
76
76
  import re
77
77
 
78
78
  from ArgumentsClass import CommonArguments
79
- from colorama import Fore, init
79
+ from runbooks.common.rich_utils import console
80
80
 
81
- init()
82
81
  __version__ = "2024.06.20"
83
82
 
84
83
  # Configure CLI argument parsing for StackSet results analysis and correlation
@@ -123,7 +122,6 @@ logging.getLogger("urllib3").setLevel(logging.CRITICAL) # Suppress HTTP client
123
122
  # Analysis and Data Processing
124
123
  ##########################
125
124
 
126
- ERASE_LINE = "\x1b[2K" # Terminal line clearing for dynamic output updates
127
125
 
128
126
  # Initialize StackSets data structure for comprehensive deployment analysis
129
127
  StackSets = {}