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
@@ -77,14 +77,13 @@ import Inventory_Modules
77
77
  from account_class import aws_acct_access
78
78
  from ArgumentsClass import CommonArguments
79
79
  from botocore.exceptions import ClientError
80
- from colorama import Fore, init
80
+ from runbooks.common.rich_utils import console
81
81
  from Inventory_Modules import display_results, find_stacksets3, get_regions3
82
- from tqdm.auto import tqdm
82
+ # Migrated to Rich.Progress - see rich_utils.py for enterprise UX standards
83
+ # from tqdm.auto import tqdm
83
84
 
84
- init()
85
85
 
86
86
  __version__ = "2024.05.18"
87
- ERASE_LINE = "\x1b[2K"
88
87
  begin_time = time()
89
88
  DefaultMaxWorkerThreads = 5
90
89
 
@@ -268,8 +267,8 @@ def setup_auth_and_regions(
268
267
  if fRegion.lower() not in RegionList:
269
268
  print()
270
269
  print(
271
- f"{Fore.RED}You specified '{fRegion}' as the region, but this script only works with a single region.\n"
272
- f"Please run the command again and specify only a single, valid region{Fore.RESET}"
270
+ f"[red]You specified '{fRegion}' as the region, but this script only works with a single region.\n"
271
+ f"Please run the command again and specify only a single, valid region"
273
272
  )
274
273
  print()
275
274
  raise ValueError(f"You specified '{fRegion}' as the region, but this script only works with a single region.")
@@ -283,7 +282,7 @@ def setup_auth_and_regions(
283
282
 
284
283
  # Display fragment matching configuration for search transparency
285
284
  if fExact:
286
- print(f"\t\tFor stacksets that {Fore.RED}exactly match{Fore.RESET} these fragments: {fStackfrag}")
285
+ print(f"\t\tFor stacksets that [red]exactly match these fragments: {fStackfrag}")
287
286
  else:
288
287
  print(f"\t\tFor stacksets that contains these fragments: {fStackfrag}")
289
288
 
@@ -557,7 +556,7 @@ def find_stack_set_instances(fStackSetNames: list, fRegion: str) -> list:
557
556
  logging.info(
558
557
  f"{ERASE_LINE}Finished finding stack instances in stackset {c_stacksetname} in region {c_region} - {c_PlaceCount} / {len(fStackSetNames)}"
559
558
  )
560
- pbar.update() # Update progress bar for operational visibility
559
+ pbar.update(pbar_task, advance=1) # Update Rich progress bar for operational visibility
561
560
  self.queue.task_done() # Mark queue item as completed
562
561
 
563
562
  ###########
@@ -573,40 +572,50 @@ def find_stack_set_instances(fStackSetNames: list, fRegion: str) -> list:
573
572
  # Configure optimal worker thread count based on StackSet count and system limits
574
573
  WorkerThreads = min(len(fStackSetNames), DefaultMaxWorkerThreads)
575
574
 
575
+ # Import Rich display utilities for professional progress tracking
576
+ from runbooks.common.rich_utils import create_progress_bar
577
+
576
578
  # Initialize progress tracking for operational visibility during discovery
577
- pbar = tqdm(
578
- desc=f"Finding Stackset instances from {len(fStackSetNames)} stacksets",
579
- total=len(fStackSetNames),
580
- unit=" stacksets",
581
- )
579
+ with create_progress_bar() as progress:
580
+ task = progress.add_task(
581
+ f"[cyan]Finding Stackset instances from {len(fStackSetNames)} stacksets...",
582
+ total=len(fStackSetNames)
583
+ )
584
+
585
+ # Make progress object available to worker threads via global (multi-threaded pattern)
586
+ global pbar
587
+ pbar = progress
588
+ global pbar_task
589
+ pbar_task = task
590
+
591
+ # Create and start worker thread pool for concurrent StackSet instance discovery
592
+ for x in range(WorkerThreads):
593
+ worker = FindStackSets(checkqueue)
594
+ # Daemon threads allow main thread exit even if workers are still processing
595
+ worker.daemon = True
596
+ worker.start()
597
+
598
+ # Queue StackSet discovery work items for worker thread processing
599
+ for stacksetname in fStackSetNames:
600
+ logging.debug(f"Beginning to queue data - starting with {stacksetname}")
601
+ try:
602
+ # Queue StackSet information for worker thread processing
603
+ # Note: Tuple structure is critical for proper parameter expansion in worker threads
604
+ PlaceCount += 1
605
+ checkqueue.put((stacksetname, fRegion, stacksetname, PlaceCount))
606
+ except ClientError as my_Error:
607
+ # Handle authorization failures with informative error messaging
608
+ if "AuthFailure" in str(my_Error):
609
+ logging.error(
610
+ f"Authorization Failure accessing stack set {stacksetname['StackSetName']} in {fRegion} region"
611
+ )
612
+ logging.warning(f"It's possible that the region {fRegion} hasn't been opted-into")
613
+ pass
614
+
615
+ # Wait for all worker threads to complete processing
616
+ checkqueue.join()
617
+ # Progress bar auto-closes when exiting context manager
582
618
 
583
- # Create and start worker thread pool for concurrent StackSet instance discovery
584
- for x in range(WorkerThreads):
585
- worker = FindStackSets(checkqueue)
586
- # Daemon threads allow main thread exit even if workers are still processing
587
- worker.daemon = True
588
- worker.start()
589
-
590
- # Queue StackSet discovery work items for worker thread processing
591
- for stacksetname in fStackSetNames:
592
- logging.debug(f"Beginning to queue data - starting with {stacksetname}")
593
- try:
594
- # Queue StackSet information for worker thread processing
595
- # Note: Tuple structure is critical for proper parameter expansion in worker threads
596
- PlaceCount += 1
597
- checkqueue.put((stacksetname, fRegion, stacksetname, PlaceCount))
598
- except ClientError as my_Error:
599
- # Handle authorization failures with informative error messaging
600
- if "AuthFailure" in str(my_Error):
601
- logging.error(
602
- f"Authorization Failure accessing stack set {stacksetname['StackSetName']} in {fRegion} region"
603
- )
604
- logging.warning(f"It's possible that the region {fRegion} hasn't been opted-into")
605
- pass
606
-
607
- # Wait for all worker threads to complete processing
608
- checkqueue.join()
609
- pbar.close() # Close progress bar after completion
610
619
  return f_combined_stack_set_instances
611
620
 
612
621
 
@@ -659,23 +668,36 @@ def find_last_operations(faws_acct: aws_acct_access, fStackSetNames: list):
659
668
  StackSetOps_client = faws_acct.session.client("cloudformation")
660
669
  AllStackSetOps = []
661
670
 
671
+ # Import Rich display utilities for professional progress tracking
672
+ from runbooks.common.rich_utils import create_progress_bar
673
+
662
674
  # Discover last operation for each StackSet with progress tracking
663
- for stacksetname in tqdm(fStackSetNames, desc="Checking stackset operations"):
664
- # Retrieve most recent operation for the current StackSet
665
- StackSetOps = StackSetOps_client.list_stack_set_operations(
666
- StackSetName=stacksetname, MaxResults=1, CallAs="SELF"
667
- )["Summaries"]
668
-
669
- # Extract and aggregate operation metadata for analysis
670
- AllStackSetOps.append(
671
- {
672
- "StackSetName": stacksetname, # StackSet identifier for correlation
673
- "Operation": StackSetOps[0]["Action"], # Operation type for lifecycle tracking
674
- "LatestStatus": StackSetOps[0]["Status"], # Current operation status
675
- "LatestDate": StackSetOps[0]["EndTimestamp"], # Completion timestamp
676
- "Details": StackSetOps[0]["StatusDetails"]["FailedStackInstancesCount"], # Failure count for analysis
677
- }
675
+ with create_progress_bar() as progress:
676
+ task = progress.add_task(
677
+ "[cyan]Checking stackset operations...",
678
+ total=len(fStackSetNames)
678
679
  )
680
+
681
+ for stacksetname in fStackSetNames:
682
+ # Retrieve most recent operation for the current StackSet
683
+ StackSetOps = StackSetOps_client.list_stack_set_operations(
684
+ StackSetName=stacksetname, MaxResults=1, CallAs="SELF"
685
+ )["Summaries"]
686
+
687
+ # Extract and aggregate operation metadata for analysis
688
+ AllStackSetOps.append(
689
+ {
690
+ "StackSetName": stacksetname, # StackSet identifier for correlation
691
+ "Operation": StackSetOps[0]["Action"], # Operation type for lifecycle tracking
692
+ "LatestStatus": StackSetOps[0]["Status"], # Current operation status
693
+ "LatestDate": StackSetOps[0]["EndTimestamp"], # Completion timestamp
694
+ "Details": StackSetOps[0]["StatusDetails"]["FailedStackInstancesCount"], # Failure count for analysis
695
+ }
696
+ )
697
+
698
+ # Update progress after processing each StackSet
699
+ progress.update(task, advance=1)
700
+
679
701
  return AllStackSetOps
680
702
 
681
703
 
@@ -724,11 +746,11 @@ if __name__ == "__main__":
724
746
  print()
725
747
  print(ERASE_LINE)
726
748
  print(
727
- f"{Fore.RED}Found {len(StackSets['StackSetsList'])} Stacksets across {len(Accounts)} accounts across {len(Regions)} regions{Fore.RESET}"
749
+ f"[red]Found {len(StackSets['StackSetsList'])} Stacksets across {len(Accounts)} accounts across {len(Regions)} regions"
728
750
  )
729
751
  print()
730
752
  if pTiming:
731
753
  print(ERASE_LINE)
732
- print(f"{Fore.GREEN}This script took {time() - begin_time:.2f} seconds{Fore.RESET}")
754
+ print(f"[green]This script took {time() - begin_time:.2f} seconds")
733
755
  print("Thanks for using this script...")
734
756
  print()
@@ -88,7 +88,7 @@ from time import time
88
88
 
89
89
  from account_class import aws_acct_access
90
90
  from ArgumentsClass import CommonArguments
91
- from colorama import Fore, init
91
+ from runbooks.common.rich_utils import console
92
92
  from Inventory_Modules import (
93
93
  RemoveCoreAccounts,
94
94
  display_results,
@@ -98,11 +98,9 @@ from Inventory_Modules import (
98
98
  get_regions3,
99
99
  )
100
100
 
101
- init()
102
101
 
103
102
  __version__ = "2024.06.20"
104
103
  begin_time = time()
105
- ERASE_LINE = "\x1b[2K"
106
104
 
107
105
  #####################
108
106
  # Functions
@@ -270,13 +268,13 @@ def setup_auth_accounts_and_regions(fProfile: str) -> (aws_acct_access, list, li
270
268
  if pRootOnly:
271
269
  print(f"\tIn only the root account: {aws_acct.acct_number}")
272
270
  else:
273
- print(f"\tin these accounts: {Fore.RED}{AccountList}{Fore.RESET}")
274
- print(f"\tin these regions: {Fore.RED}{RegionList}{Fore.RESET}")
271
+ print(f"\tin these accounts: [red]{AccountList}")
272
+ print(f"\tin these regions: [red]{RegionList}")
275
273
  print(
276
274
  f"\tContaining {'this ' + Fore.RED + 'exact fragment' + Fore.RESET if pExact else 'one of these fragments'}: {pFragments}"
277
275
  )
278
276
  if pSkipAccounts is not None:
279
- print(f"\tWhile skipping these accounts: {Fore.RED}{pSkipAccounts}{Fore.RESET}")
277
+ print(f"\tWhile skipping these accounts: [red]{pSkipAccounts}")
280
278
 
281
279
  return aws_acct, AccountList, RegionList
282
280
 
@@ -343,7 +341,7 @@ def find_all_cfnstacksets(f_All_Credentials: list, f_Fragments: list, f_Status)
343
341
  # logging.info(f"Account Creds: {account_credentials}")
344
342
  # Display progress for operational visibility during StackSet discovery
345
343
  print(
346
- f"{ERASE_LINE}{Fore.RED}Checking Account: {credential['AccountId']} Region: {credential['Region']} for stacksets matching {f_Fragments} with status: {f_Status}{Fore.RESET}",
344
+ f"{ERASE_LINE}[red]Checking Account: {credential['AccountId']} Region: {credential['Region']} for stacksets matching {f_Fragments} with status: {f_Status}",
347
345
  end="\r",
348
346
  )
349
347
 
@@ -361,7 +359,7 @@ def find_all_cfnstacksets(f_All_Credentials: list, f_Fragments: list, f_Status)
361
359
  ) if verbose < 50 else ""
362
360
  else:
363
361
  print(
364
- f"{ERASE_LINE}{Fore.RED}Account: {credential['AccountId']} Region: {credential['Region']} Found {len(StackSets)} Stacksets{Fore.RESET}",
362
+ f"{ERASE_LINE}[red]Account: {credential['AccountId']} Region: {credential['Region']} Found {len(StackSets)} Stacksets",
365
363
  end="\r",
366
364
  ) if verbose < 50 else ""
367
365
 
@@ -443,11 +441,11 @@ if __name__ == "__main__":
443
441
 
444
442
  print(ERASE_LINE)
445
443
  print(
446
- f"{Fore.RED}Found {len(All_Results)} Stacksets across {len(AccountList)} accounts across {len(RegionList)} regions{Fore.RESET}"
444
+ f"[red]Found {len(All_Results)} Stacksets across {len(AccountList)} accounts across {len(RegionList)} regions"
447
445
  )
448
446
  print()
449
447
  if pTiming:
450
448
  print(ERASE_LINE)
451
- print(f"{Fore.GREEN}This script took {time() - begin_time:.2f} seconds{Fore.RESET}")
449
+ print(f"[green]This script took {time() - begin_time:.2f} seconds")
452
450
  print("Thanks for using this script...")
453
451
  print()
@@ -93,11 +93,11 @@ from time import time
93
93
  import Inventory_Modules
94
94
  from ArgumentsClass import CommonArguments
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 del_config_recorder_or_delivery_channel2, display_results, get_all_credentials
98
- from tqdm.auto import tqdm
98
+ # Migrated to Rich.Progress - see rich_utils.py for enterprise UX standards
99
+ # from tqdm.auto import tqdm
99
100
 
100
- init()
101
101
  __version__ = "2024.05.31"
102
102
 
103
103
 
@@ -409,7 +409,7 @@ def check_accounts_for_delivery_channels_and_config_recorders(CredentialList, fF
409
409
  logging.info(
410
410
  f"{ERASE_LINE}Finished finding items in account {c_account_credentials['AccountId']} in region {c_account_credentials['Region']} - {c_PlaceCount} / {c_PlacesToLook}"
411
411
  )
412
- pbar.update() # Update progress bar for operational visibility
412
+ pbar.update(pbar_task, advance=1) # Update Rich progress bar for operational visibility
413
413
  self.queue.task_done() # Mark queue item as completed
414
414
 
415
415
  # Initialize processing context and data structures for Config discovery
@@ -421,40 +421,50 @@ def check_accounts_for_delivery_channels_and_config_recorders(CredentialList, fF
421
421
 
422
422
  checkqueue = Queue() # Queue for work distribution across worker threads
423
423
 
424
+ # Import Rich display utilities for professional progress tracking
425
+ from runbooks.common.rich_utils import create_progress_bar
426
+
424
427
  # Initialize progress tracking for operational visibility during Config discovery
425
- pbar = tqdm(
426
- desc=f"Finding config recorders / delivery channels from {len(AllCredentials)} accounts and regions",
427
- total=len(AllCredentials),
428
- unit=" accounts & regions",
429
- )
428
+ with create_progress_bar() as progress:
429
+ task = progress.add_task(
430
+ f"[cyan]Finding config recorders / delivery channels from {len(AllCredentials)} accounts and regions...",
431
+ total=len(AllCredentials)
432
+ )
430
433
 
431
- # Create and start worker thread pool for concurrent Config component discovery
432
- for x in range(WorkerThreads):
433
- worker = Find_Config_Recorders_and_Delivery_Channels(checkqueue)
434
- # Daemon threads allow main thread exit even if workers are still processing
435
- worker.daemon = True
436
- worker.start()
437
-
438
- # Queue Config discovery work items for worker thread processing
439
- # Note: Credential list already includes regional context, eliminating need for nested region iteration
440
- for credential in CredentialList:
441
- logging.info(f"Connecting to account {credential['AccountId']} in region {credential['Region']}")
442
- try:
443
- # Queue account and region combination for worker thread processing
444
- # Note: Tuple structure is critical for proper parameter expansion in worker threads
445
- checkqueue.put((credential, fFixRun, fFragments, len(CredentialList), PlaceCount))
446
- except ClientError as my_Error:
447
- # Handle authorization failures with informative error messaging
448
- if "AuthFailure" in str(my_Error):
449
- logging.error(
450
- f"Authorization Failure accessing account {credential['AccountId']} in {credential['Region']} region"
451
- )
452
- logging.warning(f"It's possible that the region {credential['Region']} hasn't been opted-into")
453
- pass
434
+ # Make progress object available to worker threads via global (multi-threaded pattern)
435
+ global pbar
436
+ pbar = progress
437
+ global pbar_task
438
+ pbar_task = task
439
+
440
+ # Create and start worker thread pool for concurrent Config component discovery
441
+ for x in range(WorkerThreads):
442
+ worker = Find_Config_Recorders_and_Delivery_Channels(checkqueue)
443
+ # Daemon threads allow main thread exit even if workers are still processing
444
+ worker.daemon = True
445
+ worker.start()
446
+
447
+ # Queue Config discovery work items for worker thread processing
448
+ # Note: Credential list already includes regional context, eliminating need for nested region iteration
449
+ for credential in CredentialList:
450
+ logging.info(f"Connecting to account {credential['AccountId']} in region {credential['Region']}")
451
+ try:
452
+ # Queue account and region combination for worker thread processing
453
+ # Note: Tuple structure is critical for proper parameter expansion in worker threads
454
+ checkqueue.put((credential, fFixRun, fFragments, len(CredentialList), PlaceCount))
455
+ except ClientError as my_Error:
456
+ # Handle authorization failures with informative error messaging
457
+ if "AuthFailure" in str(my_Error):
458
+ logging.error(
459
+ f"Authorization Failure accessing account {credential['AccountId']} in {credential['Region']} region"
460
+ )
461
+ logging.warning(f"It's possible that the region {credential['Region']} hasn't been opted-into")
462
+ pass
463
+
464
+ # Wait for all worker threads to complete processing
465
+ checkqueue.join()
466
+ # Progress bar auto-closes when exiting context manager
454
467
 
455
- # Wait for all worker threads to complete processing
456
- checkqueue.join()
457
- pbar.close() # Close progress bar after completion
458
468
  return account_crs_and_dcs
459
469
 
460
470
 
@@ -619,7 +629,7 @@ if __name__ == "__main__":
619
629
  print()
620
630
  milestone_time1 = time()
621
631
  print(
622
- f"{Fore.GREEN}\t\tFiguring out what regions are available to your accounts, and capturing credentials for all accounts in those regions took: {(milestone_time1 - begin_time):.3f} seconds{Fore.RESET}"
632
+ f"[green]\t\tFiguring out what regions are available to your accounts, and capturing credentials for all accounts in those regions took: {(milestone_time1 - begin_time):.3f} seconds"
623
633
  )
624
634
  print()
625
635
  print(f"Now running through all accounts and regions identified to find resources...")
@@ -631,7 +641,7 @@ if __name__ == "__main__":
631
641
  print()
632
642
  milestone_time2 = time()
633
643
  print(
634
- f"{Fore.GREEN}\t\tChecking {len(AllCredentials)} places took: {(milestone_time2 - milestone_time1):.3f} seconds{Fore.RESET}"
644
+ f"[green]\t\tChecking {len(AllCredentials)} places took: {(milestone_time2 - milestone_time1):.3f} seconds"
635
645
  )
636
646
  print()
637
647
  cr = 0
@@ -650,7 +660,7 @@ if __name__ == "__main__":
650
660
  print()
651
661
  milestone_time3 = time()
652
662
  print(
653
- f"{Fore.GREEN}\t\tSorting the list of places took: {(milestone_time3 - milestone_time2):.3f} seconds{Fore.RESET}"
663
+ f"[green]\t\tSorting the list of places took: {(milestone_time3 - milestone_time2):.3f} seconds"
654
664
  )
655
665
  print()
656
666
  display_results(all_sorted_config_recorders_and_delivery_channels, display_dict, None, pFilename)
@@ -675,7 +685,7 @@ if __name__ == "__main__":
675
685
 
676
686
  if pTiming:
677
687
  print(ERASE_LINE)
678
- print(f"{Fore.GREEN}This whole script took {time() - begin_time:.3f} seconds{Fore.RESET}")
688
+ print(f"[green]This whole script took {time() - begin_time:.3f} seconds")
679
689
  print()
680
690
  print("Thank you for using this tool")
681
691
  print()
@@ -60,11 +60,11 @@ from time import time
60
60
 
61
61
  from ArgumentsClass import CommonArguments
62
62
  from botocore.exceptions import ClientError
63
- from colorama import Fore, init
63
+ from runbooks.common.rich_utils import console
64
64
  from Inventory_Modules import display_results, find_directories2, get_all_credentials
65
- from tqdm.auto import tqdm
65
+ # Migrated to Rich.Progress - see rich_utils.py for enterprise UX standards
66
+ # from tqdm.auto import tqdm
66
67
 
67
- init()
68
68
  __version__ = "2024.05.31"
69
69
 
70
70
 
@@ -193,54 +193,66 @@ def find_all_directories(f_credentials, f_fragments, f_exact):
193
193
  """
194
194
  AllDirectories = [] # Aggregated list for all discovered directories
195
195
 
196
+ # Import Rich display utilities for professional progress tracking
197
+ from runbooks.common.rich_utils import create_progress_bar
198
+
196
199
  # TODO: Need to use multi-threading here for improved performance
197
200
  # Sequential processing with progress tracking for operational visibility
198
- for credential in tqdm(
199
- f_credentials, desc=f"Looking through {len(f_credentials)} accounts and regions", unit="credentials"
200
- ):
201
- logging.info(f"{ERASE_LINE}Looking in account: {credential['AccountId']} in region {credential['Region']}")
202
-
203
- # Skip failed credentials to avoid API errors
204
- if not credential["Success"]:
205
- continue
206
-
207
- try:
208
- # Discover directories using Directory Service API with fragment filtering
209
- directories = find_directories2(credential, credential["Region"], f_fragments, f_exact)
210
- logging.info(f"directories: {directories}")
211
- logging.info(
212
- f"{ERASE_LINE}Account: {credential['AccountId']} Region: {credential['Region']} Found {len(directories)} directories"
213
- )
214
-
215
- # Process and aggregate discovered directories with organizational context
216
- if directories:
217
- for directory in directories:
218
- # Enhance directory metadata with organizational and regional context
219
- # Available directory metadata includes:
220
- # - DirectoryName: Human-readable directory identifier
221
- # - DirectoryId: Unique AWS Directory Service identifier
222
- # - HomeRegion: Primary directory service region
223
- # - Status: Operational status (Active, Creating, etc.)
224
- # - Type: Directory type (SimpleAD, MicrosoftAD, etc.)
225
- # - Owner: Directory ownership context
226
-
227
- directory.update(
228
- {
229
- "MgmtAccount": credential["MgmtAccount"], # Management account context
230
- "Region": credential["Region"], # Regional deployment information
231
- "AccountId": credential["AccountId"], # Account ownership details
232
- }
233
- )
234
- AllDirectories.append(directory)
235
-
236
- except TypeError as my_Error:
237
- # Handle type errors from malformed Directory Service API responses
238
- logging.info(f"Error: {my_Error}")
239
- continue
240
- except ClientError as my_Error:
241
- # Handle AWS API authorization failures with informative logging
242
- if "AuthFailure" in str(my_Error):
243
- logging.error(f"{ERASE_LINE} Account {credential['AccountId']} : Authorization Failure")
201
+ with create_progress_bar() as progress:
202
+ task = progress.add_task(
203
+ f"[cyan]Looking through {len(f_credentials)} accounts and regions...",
204
+ total=len(f_credentials)
205
+ )
206
+
207
+ for credential in f_credentials:
208
+ logging.info(f"Looking in account: {credential['AccountId']} in region {credential['Region']}")
209
+
210
+ # Skip failed credentials to avoid API errors
211
+ if not credential["Success"]:
212
+ progress.update(task, advance=1)
213
+ continue
214
+
215
+ try:
216
+ # Discover directories using Directory Service API with fragment filtering
217
+ directories = find_directories2(credential, credential["Region"], f_fragments, f_exact)
218
+ logging.info(f"directories: {directories}")
219
+ logging.info(
220
+ f"Account: {credential['AccountId']} Region: {credential['Region']} Found {len(directories)} directories"
221
+ )
222
+
223
+ # Process and aggregate discovered directories with organizational context
224
+ if directories:
225
+ for directory in directories:
226
+ # Enhance directory metadata with organizational and regional context
227
+ # Available directory metadata includes:
228
+ # - DirectoryName: Human-readable directory identifier
229
+ # - DirectoryId: Unique AWS Directory Service identifier
230
+ # - HomeRegion: Primary directory service region
231
+ # - Status: Operational status (Active, Creating, etc.)
232
+ # - Type: Directory type (SimpleAD, MicrosoftAD, etc.)
233
+ # - Owner: Directory ownership context
234
+
235
+ directory.update(
236
+ {
237
+ "MgmtAccount": credential["MgmtAccount"], # Management account context
238
+ "Region": credential["Region"], # Regional deployment information
239
+ "AccountId": credential["AccountId"], # Account ownership details
240
+ }
241
+ )
242
+ AllDirectories.append(directory)
243
+
244
+ except TypeError as my_Error:
245
+ # Handle type errors from malformed Directory Service API responses
246
+ logging.info(f"Error: {my_Error}")
247
+ progress.update(task, advance=1)
248
+ continue
249
+ except ClientError as my_Error:
250
+ # Handle AWS API authorization failures with informative logging
251
+ if "AuthFailure" in str(my_Error):
252
+ logging.error(f" Account {credential['AccountId']} : Authorization Failure")
253
+
254
+ # Update progress after processing each credential
255
+ progress.update(task, advance=1)
244
256
 
245
257
  return AllDirectories
246
258
 
@@ -293,7 +305,7 @@ if __name__ == "__main__":
293
305
  # Display credential retrieval timing for performance optimization
294
306
  if pTiming:
295
307
  print(
296
- f"{Fore.GREEN}\tAfter getting credentials, this script took {time() - begin_time:.3f} seconds{Fore.RESET}"
308
+ f"[green]\tAfter getting credentials, this script took {time() - begin_time:.3f} seconds"
297
309
  )
298
310
  print()
299
311
 
@@ -304,7 +316,7 @@ if __name__ == "__main__":
304
316
  # Display credential parsing timing for operational metrics
305
317
  if pTiming:
306
318
  print(
307
- f"{Fore.GREEN}\tAfter parsing out all Regions, Account and Profiles, this script took {time() - begin_time:.3f} seconds{Fore.RESET}"
319
+ f"[green]\tAfter parsing out all Regions, Account and Profiles, this script took {time() - begin_time:.3f} seconds"
308
320
  )
309
321
  print()
310
322
 
@@ -340,7 +352,7 @@ if __name__ == "__main__":
340
352
  display_results(sorted_Results, display_dict, "None")
341
353
 
342
354
  # Provide operational summary with discovery metrics and performance timing
343
- print(ERASE_LINE)
355
+ console.print()
344
356
  print(
345
357
  f"Found {len(all_directories)} directories across {len(AccountList)} accounts across {len(RegionList)} regions"
346
358
  )
@@ -348,7 +360,7 @@ if __name__ == "__main__":
348
360
 
349
361
  # Display total execution timing for performance analysis and optimization
350
362
  if pTiming:
351
- print(f"{Fore.GREEN}\tThis script took {time() - begin_time:.3f} seconds{Fore.RESET}")
363
+ print(f"[green]\tThis script took {time() - begin_time:.3f} seconds")
352
364
  print()
353
365
  print("Thank you for using this script")
354
366
  print()
@@ -67,12 +67,10 @@ import sys
67
67
  from time import time
68
68
 
69
69
  from ArgumentsClass import CommonArguments
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, get_region_azs2
72
72
 
73
- init()
74
73
  __version__ = "2024.03.06"
75
- ERASE_LINE = "\x1b[2K"
76
74
  begin_time = time()
77
75
 
78
76
 
@@ -281,6 +279,6 @@ if __name__ == "__main__":
281
279
 
282
280
  print()
283
281
  if pTiming:
284
- print(f"{Fore.GREEN}This script took {time() - begin_time:.2f} seconds{Fore.RESET}")
282
+ print(f"[green]This script took {time() - begin_time:.2f} seconds")
285
283
  print("Thanks for using this script")
286
284
  print()