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
@@ -82,14 +82,12 @@ import boto3
82
82
  import Inventory_Modules
83
83
  from ArgumentsClass import CommonArguments
84
84
  from botocore.exceptions import ClientError
85
- from colorama import Fore, init
85
+ from runbooks.common.rich_utils import console
86
86
  from Inventory_Modules import display_results, get_all_credentials
87
- from tqdm.auto import tqdm
87
+ from runbooks.common.rich_utils import create_progress_bar
88
88
 
89
- init()
90
89
  __version__ = "2024.06.05"
91
90
 
92
- ERASE_LINE = "\x1b[2K"
93
91
  begin_time = time()
94
92
 
95
93
 
@@ -497,7 +495,6 @@ def check_accounts_for_functions(CredentialList, fFragments=None):
497
495
  while True:
498
496
  # Retrieve account credentials and fragment filters from thread-safe work queue
499
497
  c_account_credentials, c_fragment_list = self.queue.get()
500
- pbar.update() # Update progress bar for operational visibility
501
498
  Functions = []
502
499
  logging.info(f"De-queued info for account {c_account_credentials['AccountId']}")
503
500
  try:
@@ -543,6 +540,9 @@ def check_accounts_for_functions(CredentialList, fFragments=None):
543
540
  # Thread-safe aggregation of discovered Lambda functions
544
541
  AllFuncs.extend(Functions)
545
542
 
543
+ # Update progress bar for operational visibility
544
+ progress.update(task, advance=1)
545
+
546
546
  # Signal task completion for thread-safe work queue management
547
547
  self.queue.task_done()
548
548
 
@@ -556,40 +556,40 @@ def check_accounts_for_functions(CredentialList, fFragments=None):
556
556
  checkqueue = Queue()
557
557
 
558
558
  # Initialize progress tracking for operational visibility during discovery
559
- pbar = tqdm(
560
- desc=f"Finding instances from {len(CredentialList)} accounts / regions",
561
- total=len(CredentialList),
562
- unit=" locations",
563
- )
564
-
565
- # Initialize multi-threaded Lambda function discovery worker pool
566
- for x in range(WorkerThreads):
567
- worker = FindFunctions(checkqueue)
568
- # Enable graceful shutdown with main thread termination for enterprise operational safety
569
- worker.daemon = True
570
- worker.start() # Begin concurrent Lambda function discovery processing
559
+ with create_progress_bar() as progress:
560
+ task = progress.add_task(
561
+ f"Finding instances from {len(CredentialList)} accounts / regions",
562
+ total=len(CredentialList)
563
+ )
571
564
 
572
- # Populate work queue with account credentials for distributed serverless discovery
573
- for credential in CredentialList:
574
- logging.info(f"Connecting to account {credential['AccountId']}")
575
- try:
576
- # Log work queue population for operational audit trail
577
- logging.info(
578
- f"{ERASE_LINE}Queuing account {credential['AccountId']} in region {credential['Region']}", end="\r"
579
- )
580
- # Add credential and fragment filter to processing queue
581
- checkqueue.put((credential, fFragments))
582
- except ClientError as my_Error:
583
- # Handle AWS API authorization failures during queue population
584
- if "AuthFailure" in str(my_Error):
585
- logging.error(
586
- f"Authorization Failure accessing account {credential['AccountId']} in {credential['Region']} region"
565
+ # Initialize multi-threaded Lambda function discovery worker pool
566
+ for x in range(WorkerThreads):
567
+ worker = FindFunctions(checkqueue)
568
+ # Enable graceful shutdown with main thread termination for enterprise operational safety
569
+ worker.daemon = True
570
+ worker.start() # Begin concurrent Lambda function discovery processing
571
+
572
+ # Populate work queue with account credentials for distributed serverless discovery
573
+ for credential in CredentialList:
574
+ logging.info(f"Connecting to account {credential['AccountId']}")
575
+ try:
576
+ # Log work queue population for operational audit trail
577
+ logging.info(
578
+ f"{ERASE_LINE}Queuing account {credential['AccountId']} in region {credential['Region']}", end="\r"
587
579
  )
588
- logging.error(f"It's possible that the region {credential['Region']} hasn't been opted-into")
589
- pass # Continue processing remaining accounts despite individual failures
580
+ # Add credential and fragment filter to processing queue
581
+ checkqueue.put((credential, fFragments))
582
+ except ClientError as my_Error:
583
+ # Handle AWS API authorization failures during queue population
584
+ if "AuthFailure" in str(my_Error):
585
+ logging.error(
586
+ f"Authorization Failure accessing account {credential['AccountId']} in {credential['Region']} region"
587
+ )
588
+ logging.error(f"It's possible that the region {credential['Region']} hasn't been opted-into")
589
+ pass # Continue processing remaining accounts despite individual failures
590
590
 
591
- # Wait for all Lambda function discovery tasks to complete before result aggregation
592
- checkqueue.join()
591
+ # Wait for all Lambda function discovery tasks to complete before result aggregation
592
+ checkqueue.join()
593
593
 
594
594
  # Return comprehensive Lambda function inventory with enterprise serverless metadata
595
595
  return AllFuncs
@@ -744,7 +744,7 @@ def fix_my_functions(fAllFunctions, fRuntime, fNewRuntime, fForceDelete, fTiming
744
744
  if fTiming:
745
745
  print(ERASE_LINE)
746
746
  print(
747
- f"{Fore.GREEN}Fixing {len(return_response)} functions took {time() - begin_fix_time:.3f} seconds{Fore.RESET}"
747
+ f"[green]Fixing {len(return_response)} functions took {time() - begin_fix_time:.3f} seconds"
748
748
  )
749
749
 
750
750
  # Return operation results for enterprise reporting and audit trail
@@ -869,7 +869,7 @@ if __name__ == "__main__":
869
869
  # Display performance timing metrics for operational optimization and SLA compliance
870
870
  if pTiming:
871
871
  print(ERASE_LINE)
872
- print(f"{Fore.GREEN}This script took {time() - begin_time:.3f} seconds{Fore.RESET}")
872
+ print(f"[green]This script took {time() - begin_time:.3f} seconds")
873
873
 
874
874
  print(ERASE_LINE)
875
875
 
@@ -71,12 +71,10 @@ from time import time
71
71
  from ArgumentsClass import CommonArguments
72
72
 
73
73
  # from botocore.exceptions import ClientError, NoCredentialsError, InvalidConfigError
74
- from colorama import Fore, Style, init
74
+ from runbooks.common.rich_utils import console
75
75
  from Inventory_Modules import display_results, get_org_accounts_from_profiles, get_profiles
76
76
 
77
- init()
78
77
  __version__ = "2024.05.08"
79
- ERASE_LINE = "\x1b[2K"
80
78
  begin_time = time()
81
79
 
82
80
 
@@ -251,7 +249,7 @@ def all_my_orgs(
251
249
  # Print out the results
252
250
  if f_Timing:
253
251
  print()
254
- print(f"It's taken {Fore.GREEN}{time() - begin_time:.2f}{Fore.RESET} seconds to find profile accounts...")
252
+ print(f"It's taken [green]{time() - begin_time:.2f} seconds to find profile accounts...")
255
253
  print()
256
254
  fmt = "%-23s %-15s %-15s %-12s %-10s"
257
255
  print("<------------------------------------>")
@@ -278,7 +276,7 @@ def all_my_orgs(
278
276
  else:
279
277
  logging.info(f"{item['profile']} was successful.")
280
278
  print(
281
- f"{Fore.RED if item['RootAcct'] else ''}{item['profile']:23s} {item['aws_acct'].acct_number:15s} {item['MgmtAccount']:15s} {str(item['OrgId']):12s} {item['RootAcct']}{Fore.RESET}"
279
+ f"{Fore.RED if item['RootAcct'] else ''}{item['profile']:23s} {item['aws_acct'].acct_number:15s} {item['MgmtAccount']:15s} {str(item['OrgId']):12s} {item['RootAcct']}"
282
280
  )
283
281
  except TypeError as my_Error:
284
282
  logging.error(f"Error - {my_Error} on {item}")
@@ -339,14 +337,14 @@ def all_my_orgs(
339
337
  for item in AllProfileAccounts:
340
338
  if item["Success"] and item["RootAcct"]:
341
339
  print(
342
- f"{item['profile']:{ProfileNameLength}s} {Style.BRIGHT}{item['MgmtAccount']:15s}{Style.RESET_ALL}"
340
+ f"{item['profile']:{ProfileNameLength}s} [bold]{item['MgmtAccount']:15s}"
343
341
  )
344
342
  print(
345
343
  f"\t{'Child Account Number':{len('Child Account Number')}s} {'Child Account Status':{len('Child Account Status')}s} {'Child Email Address'}"
346
344
  )
347
345
  for child_acct in item["aws_acct"].ChildAccounts:
348
346
  print(
349
- f"\t{Fore.RED if not child_acct['AccountStatus'] == 'ACTIVE' else ''}{child_acct['AccountId']:{len('Child Account Number')}s} {child_acct['AccountStatus']:{len('Child Account Status')}s} {child_acct['AccountEmail']}{Fore.RESET}"
347
+ f"\t{Fore.RED if not child_acct['AccountStatus'] == 'ACTIVE' else ''}{child_acct['AccountId']:{len('Child Account Number')}s} {child_acct['AccountStatus']:{len('Child Account Status')}s} {child_acct['AccountEmail']}"
350
348
  )
351
349
 
352
350
  elif f_SaveFilename is not None:
@@ -440,7 +438,7 @@ if __name__ == "__main__":
440
438
 
441
439
  print()
442
440
  if pTiming:
443
- print(f"{Fore.GREEN}This script took {time() - begin_time:.2f} seconds{Fore.RESET}")
441
+ print(f"[green]This script took {time() - begin_time:.2f} seconds")
444
442
  print()
445
443
  print("Thanks for using this script")
446
444
  print()
@@ -97,7 +97,7 @@ from time import time
97
97
 
98
98
  from ArgumentsClass import CommonArguments
99
99
  from botocore.exceptions import ClientError
100
- from colorama import Fore, init
100
+ from runbooks.common.rich_utils import console
101
101
  from Inventory_Modules import (
102
102
  display_results,
103
103
  find_iam_users2,
@@ -105,11 +105,10 @@ from Inventory_Modules import (
105
105
  find_idc_users2,
106
106
  get_all_credentials,
107
107
  )
108
- from tqdm.auto import tqdm
108
+ # Migrated to Rich.Progress - see rich_utils.py for enterprise UX standards
109
+ # from tqdm.auto import tqdm
109
110
 
110
- init()
111
111
  __version__ = "2024.05.09"
112
- ERASE_LINE = "\x1b[2K"
113
112
  begin_time = time()
114
113
 
115
114
 
@@ -253,46 +252,58 @@ def find_all_org_users(f_credentials, f_IDC: bool, f_IAM: bool) -> list:
253
252
  User_List = []
254
253
  directories_seen = set()
255
254
 
255
+ # Import Rich display utilities for professional progress tracking
256
+ from runbooks.common.rich_utils import create_progress_bar
257
+
256
258
  # TODO: Enhance with multi-threading for improved performance across large organizations
257
- for credential in tqdm(
258
- f_credentials, desc=f"Looking for users across {len(f_credentials)} Accounts", unit=" accounts"
259
- ):
260
- # Skip credentials that failed validation
261
- if not credential["Success"]:
262
- logging.info(f"{credential['ErrorMessage']} with roles: {credential['RolesTried']}")
263
- continue
264
-
265
- # Discover traditional IAM users if requested
266
- if f_IAM:
267
- try:
268
- # Call inventory module to discover IAM users in this account
269
- User_List.extend(find_iam_users2(credential))
270
- # Optional verbose logging for user discovery progress (currently commented)
271
- # logging.info(f"{ERASE_LINE}Account: {credential['AccountId']} Found {len(User_List)} users")
272
- except ClientError as my_Error:
273
- # Handle IAM API authorization failures gracefully
274
- if "AuthFailure" in str(my_Error):
275
- logging.error(f"{ERASE_LINE}{credential}: Authorization Failure")
276
-
277
- # Discover AWS Identity Center users if requested
278
- if f_IDC:
279
- try:
280
- # Find out if this account hosts an Identity Center with a user directory
281
- directory_ids = find_idc_directory_id2(credential)
282
- for directory_instance_id in directory_ids:
283
- # Directory deduplication: if we've already interrogated this directory, skip it
284
- if directory_instance_id in directories_seen:
285
- continue
286
- else:
287
- # Mark this directory as processed and discover users
288
- directories_seen.update(directory_ids)
289
- User_List.extend(find_idc_users2(credential, directory_instance_id))
290
- # Optional verbose logging for user discovery progress (currently commented)
291
- # logging.info(f"{ERASE_LINE}Account: {credential['AccountId']} Found {len(User_List)} users")
292
- except ClientError as my_Error:
293
- # Handle Identity Center API authorization failures gracefully
294
- if "AuthFailure" in str(my_Error):
295
- logging.error(f"{ERASE_LINE}{credential}: Authorization Failure")
259
+ with create_progress_bar() as progress:
260
+ task = progress.add_task(
261
+ f"[cyan]Looking for users across {len(f_credentials)} Accounts...",
262
+ total=len(f_credentials)
263
+ )
264
+
265
+ for credential in f_credentials:
266
+ # Skip credentials that failed validation
267
+ if not credential["Success"]:
268
+ logging.info(f"{credential['ErrorMessage']} with roles: {credential['RolesTried']}")
269
+ progress.update(task, advance=1)
270
+ continue
271
+
272
+ # Discover traditional IAM users if requested
273
+ if f_IAM:
274
+ try:
275
+ # Call inventory module to discover IAM users in this account
276
+ User_List.extend(find_iam_users2(credential))
277
+ # Optional verbose logging for user discovery progress (currently commented)
278
+ # logging.info(f"{ERASE_LINE}Account: {credential['AccountId']} Found {len(User_List)} users")
279
+ except ClientError as my_Error:
280
+ # Handle IAM API authorization failures gracefully
281
+ if "AuthFailure" in str(my_Error):
282
+ logging.error(f"{ERASE_LINE}{credential}: Authorization Failure")
283
+
284
+ # Discover AWS Identity Center users if requested
285
+ if f_IDC:
286
+ try:
287
+ # Find out if this account hosts an Identity Center with a user directory
288
+ directory_ids = find_idc_directory_id2(credential)
289
+ for directory_instance_id in directory_ids:
290
+ # Directory deduplication: if we've already interrogated this directory, skip it
291
+ if directory_instance_id in directories_seen:
292
+ continue
293
+ else:
294
+ # Mark this directory as processed and discover users
295
+ directories_seen.update(directory_ids)
296
+ User_List.extend(find_idc_users2(credential, directory_instance_id))
297
+ # Optional verbose logging for user discovery progress (currently commented)
298
+ # logging.info(f"{ERASE_LINE}Account: {credential['AccountId']} Found {len(User_List)} users")
299
+ except ClientError as my_Error:
300
+ # Handle Identity Center API authorization failures gracefully
301
+ if "AuthFailure" in str(my_Error):
302
+ logging.error(f"{ERASE_LINE}{credential}: Authorization Failure")
303
+
304
+ # Update progress after processing each credential
305
+ progress.update(task, advance=1)
306
+
296
307
  return User_List
297
308
 
298
309
 
@@ -344,7 +355,7 @@ if __name__ == "__main__":
344
355
  display_results(sorted_UserListing, display_dict, "N/A", pFilename)
345
356
  if pTiming:
346
357
  print(ERASE_LINE)
347
- print(f"{Fore.GREEN}This script took {time() - begin_time:.2f} seconds{Fore.RESET}")
358
+ print(f"[green]This script took {time() - begin_time:.2f} seconds")
348
359
  print(ERASE_LINE)
349
360
  print(
350
361
  f"Found {len(UserListing)} users across {len(SuccessfulAccountAccesses)} account{'' if len(SuccessfulAccountAccesses) == 1 else 's'}"
@@ -65,11 +65,10 @@ from time import time
65
65
  import Inventory_Modules
66
66
  from ArgumentsClass import CommonArguments
67
67
  from botocore.exceptions import ClientError
68
- from colorama import Fore, init
68
+ from runbooks.common.rich_utils import console
69
69
  from Inventory_Modules import display_results, find_account_rds_instances2, get_all_credentials
70
- from tqdm.auto import tqdm
70
+ from runbooks.common.rich_utils import create_progress_bar
71
71
 
72
- init()
73
72
 
74
73
  __version__ = "2025.04.09"
75
74
 
@@ -289,7 +288,7 @@ def check_accounts_for_instances(fAllCredentials: list) -> list:
289
288
  logging.warning(my_Error)
290
289
  continue
291
290
  finally:
292
- pbar.update()
291
+ progress.update(task, advance=1)
293
292
  self.queue.task_done()
294
293
 
295
294
  checkqueue = Queue()
@@ -297,32 +296,31 @@ def check_accounts_for_instances(fAllCredentials: list) -> list:
297
296
  AllRDSInstances = []
298
297
  WorkerThreads = min(len(fAllCredentials), 25)
299
298
 
300
- pbar = tqdm(
301
- desc=f"Finding RDS instances from {len(fAllCredentials)} locations",
302
- total=len(fAllCredentials),
303
- unit=" location",
304
- )
305
-
306
- for x in range(WorkerThreads):
307
- worker = FindRDSInstances(checkqueue)
308
- # Setting daemon to True will let the main thread exit even though the workers are blocking
309
- worker.daemon = True
310
- worker.start()
311
-
312
- for credential in fAllCredentials:
313
- logging.info(f"Beginning to queue data - starting with {credential['AccountId']}")
314
- try:
315
- # I don't know why - but double parens are necessary below. If you remove them, only the first parameter is queued.
316
- checkqueue.put((credential))
317
- except ClientError as my_Error:
318
- if "AuthFailure" in str(my_Error):
319
- logging.error(
320
- f"Authorization Failure accessing account {credential['AccountId']} in {credential['Region']} region"
321
- )
322
- logging.warning(f"It's possible that the region {credential['Region']} hasn't been opted-into")
323
- pass
324
- checkqueue.join()
325
- pbar.close()
299
+ with create_progress_bar() as progress:
300
+ task = progress.add_task(
301
+ f"Finding RDS instances from {len(fAllCredentials)} locations",
302
+ total=len(fAllCredentials)
303
+ )
304
+
305
+ for x in range(WorkerThreads):
306
+ worker = FindRDSInstances(checkqueue)
307
+ # Setting daemon to True will let the main thread exit even though the workers are blocking
308
+ worker.daemon = True
309
+ worker.start()
310
+
311
+ for credential in fAllCredentials:
312
+ logging.info(f"Beginning to queue data - starting with {credential['AccountId']}")
313
+ try:
314
+ # I don't know why - but double parens are necessary below. If you remove them, only the first parameter is queued.
315
+ checkqueue.put((credential))
316
+ except ClientError as my_Error:
317
+ if "AuthFailure" in str(my_Error):
318
+ logging.error(
319
+ f"Authorization Failure accessing account {credential['AccountId']} in {credential['Region']} region"
320
+ )
321
+ logging.warning(f"It's possible that the region {credential['Region']} hasn't been opted-into")
322
+ pass
323
+ checkqueue.join()
326
324
  return AllRDSInstances
327
325
 
328
326
 
@@ -396,11 +394,11 @@ if __name__ == "__main__":
396
394
  # Display results
397
395
  display_results(sorted_results, display_dict, None, pFilename)
398
396
 
399
- print(ERASE_LINE)
397
+ console.print()
400
398
  print(f"Found {len(InstancesFound)} instances across {AccountNum} accounts across {RegionNum} regions")
401
399
  if pTiming:
402
- print(ERASE_LINE)
403
- print(f"{Fore.GREEN}This script took {time() - begin_time:.2f} seconds{Fore.RESET}")
400
+ console.print()
401
+ print(f"[green]This script took {time() - begin_time:.2f} seconds")
404
402
  print()
405
403
  print("Thank you for using this script")
406
404
  print()
@@ -57,12 +57,10 @@ from time import time
57
57
 
58
58
  from ArgumentsClass import CommonArguments
59
59
  from botocore.exceptions import ClientError
60
- from colorama import Fore, init
60
+ from runbooks.common.rich_utils import console
61
61
  from Inventory_Modules import display_results, find_private_hosted_zones2, get_all_credentials
62
62
 
63
- init()
64
63
  __version__ = "2023.11.08"
65
- ERASE_LINE = "\x1b[2K"
66
64
 
67
65
  ########################
68
66
 
@@ -307,12 +305,12 @@ if __name__ == "__main__":
307
305
  display_results(sorted_results, display_dict, None, pFilename)
308
306
 
309
307
  print(
310
- f"{Fore.RED}Found {len(AllHostedZones)} Hosted Zones across {len(AllAccountList)} accounts across {len(AllRegionList)} regions{Fore.RESET}"
308
+ f"[red]Found {len(AllHostedZones)} Hosted Zones across {len(AllAccountList)} accounts across {len(AllRegionList)} regions"
311
309
  )
312
310
  print()
313
311
  if pTiming:
314
312
  print(ERASE_LINE)
315
- print(f"{Fore.GREEN}This script took {time() - begin_time:.2f} seconds{Fore.RESET}")
313
+ print(f"[green]This script took {time() - begin_time:.2f} seconds")
316
314
  print(ERASE_LINE)
317
315
  print("Thanks for using this script...")
318
316
  print()
@@ -97,11 +97,10 @@ import Inventory_Modules
97
97
  from account_class import aws_acct_access
98
98
  from ArgumentsClass import CommonArguments
99
99
  from botocore.exceptions import ClientError, ProfileNotFound, UnknownCredentialError, UnknownRegionError
100
- from colorama import Fore, init
100
+ from runbooks.common.rich_utils import console
101
101
  from Inventory_Modules import display_results
102
- from tqdm.auto import tqdm
102
+ from runbooks.common.rich_utils import create_progress_bar
103
103
 
104
- init()
105
104
  __version__ = "2023.08.09"
106
105
 
107
106
  parser = CommonArguments()
@@ -175,7 +174,7 @@ def find_account_stacksets(faws_acct, f_SCProducts, fRegion=None, fstacksetname=
175
174
  # Get the work from the queue and expand the tuple
176
175
  c_sc_product, c_region, c_fstacksetname, c_PlacesToLook, c_PlaceCount = self.queue.get()
177
176
  logging.info(f"De-queued info for SC Product: {c_sc_product['SCPName']}")
178
- logging.info(f"{Fore.RED}Checking {PlaceCount} of {len(f_SCProducts)} products{Fore.RESET}")
177
+ logging.info(f"[red]Checking {PlaceCount} of {len(f_SCProducts)} products")
179
178
  CFNresponse = Inventory_Modules.find_stacks3(faws_acct, pRegion, c_sc_product["SCPId"])
180
179
  logging.info(
181
180
  f"There are {len(CFNresponse)} matches for SC Provisioned Product Name {c_sc_product['SCPName']}"
@@ -206,10 +205,10 @@ def find_account_stacksets(faws_acct, f_SCProducts, fRegion=None, fstacksetname=
206
205
  AccountStatus = AccountHistogram[AccountID]
207
206
  else:
208
207
  AccountStatus = "Closed"
209
- logging.info(f"{Fore.RED}Found the Account ID: {AccountID}{Fore.RESET}")
208
+ logging.info(f"[red]Found the Account ID: {AccountID}")
210
209
  if AccountID in SuspendedAccounts:
211
210
  logging.error(
212
- f"{Fore.RED}Account ID {AccountID} has been suspended{Fore.RESET}"
211
+ f"[red]Account ID {AccountID} has been suspended"
213
212
  )
214
213
  break
215
214
  else:
@@ -258,7 +257,7 @@ def find_account_stacksets(faws_acct, f_SCProducts, fRegion=None, fstacksetname=
258
257
  logging.info(
259
258
  f"Finished finding product {c_sc_product['SCPName']} - {c_PlaceCount} / {c_PlacesToLook}"
260
259
  )
261
- pbar.update()
260
+ progress.update(task, advance=1)
262
261
  self.queue.task_done()
263
262
 
264
263
  if fRegion is None:
@@ -269,33 +268,31 @@ def find_account_stacksets(faws_acct, f_SCProducts, fRegion=None, fstacksetname=
269
268
  PlaceCount = 0
270
269
  PlacesToLook = WorkerThreads = min(len(f_SCProducts), 10)
271
270
 
272
- pbar = tqdm(
273
- desc=f"Reconciling SC Products with CloudFormation Stacks in accounts",
274
- leave=True,
275
- total=len(f_SCProducts),
276
- unit=" products",
277
- )
271
+ with create_progress_bar() as progress:
272
+ task = progress.add_task(
273
+ f"Reconciling SC Products with CloudFormation Stacks in accounts",
274
+ total=len(f_SCProducts)
275
+ )
278
276
 
279
- # Create and start the worker threads
280
- for x in range(WorkerThreads):
281
- worker = CheckProducts(checkqueue)
282
- # Setting daemon to True will let the main thread exit even though the workers are blocking
283
- worker.daemon = True
284
- worker.start()
285
-
286
- for SCProduct in SCProducts:
287
- logging.info(f"Checking service catalog product: {SCProduct['SCPName']}")
288
- try:
289
- # print(f"{ERASE_LINE}Queuing account {credential['AccountId']} in region {region}", end='\r')
290
- checkqueue.put((SCProduct, fRegion, fstacksetname, PlacesToLook, PlaceCount))
291
- PlaceCount += 1
292
- except ClientError as my_Error:
293
- if "AuthFailure" in str(my_Error):
294
- logging.error(f"Authorization Failure accessing account {faws_acct.acct_number} in {fRegion} region")
295
- logging.warning(f"It's possible that the region {fRegion} hasn't been opted-into")
296
- pass
297
- checkqueue.join()
298
- pbar.close()
277
+ # Create and start the worker threads
278
+ for x in range(WorkerThreads):
279
+ worker = CheckProducts(checkqueue)
280
+ # Setting daemon to True will let the main thread exit even though the workers are blocking
281
+ worker.daemon = True
282
+ worker.start()
283
+
284
+ for SCProduct in SCProducts:
285
+ logging.info(f"Checking service catalog product: {SCProduct['SCPName']}")
286
+ try:
287
+ # print(f"{ERASE_LINE}Queuing account {credential['AccountId']} in region {region}", end='\r')
288
+ checkqueue.put((SCProduct, fRegion, fstacksetname, PlacesToLook, PlaceCount))
289
+ PlaceCount += 1
290
+ except ClientError as my_Error:
291
+ if "AuthFailure" in str(my_Error):
292
+ logging.error(f"Authorization Failure accessing account {faws_acct.acct_number} in {fRegion} region")
293
+ logging.warning(f"It's possible that the region {fRegion} hasn't been opted-into")
294
+ pass
295
+ checkqueue.join()
299
296
  return SCP2Stacks
300
297
 
301
298
 
@@ -320,7 +317,6 @@ Significant Variable Explanation:
320
317
  """
321
318
  begin_time = time()
322
319
 
323
- ERASE_LINE = "\x1b[2K"
324
320
 
325
321
  print()
326
322
 
@@ -437,7 +433,7 @@ def main():
437
433
 
438
434
  if pTiming:
439
435
  print(
440
- f"{Fore.GREEN}Finding stacks in your account has taken {time() - begin_time:.2f} seconds now...{Fore.RESET}"
436
+ f"[green]Finding stacks in your account has taken {time() - begin_time:.2f} seconds now..."
441
437
  )
442
438
  milestone1 = time()
443
439
 
@@ -454,7 +450,7 @@ def main():
454
450
 
455
451
  if pTiming:
456
452
  print(
457
- f"{Fore.GREEN}Reconciling products to the CloudFormation stacks took {time() - milestone1:.2f} seconds{Fore.RESET}"
453
+ f"[green]Reconciling products to the CloudFormation stacks took {time() - milestone1:.2f} seconds"
458
454
  )
459
455
 
460
456
  # TODO: This might not be a good idea, if it misses the stacks which are associated with accounts no longer within the Org.
@@ -501,21 +497,21 @@ def main():
501
497
  pass # This is the desired state, so no user output is needed.
502
498
  elif AccountHistogram[acctnum] == "SUSPENDED":
503
499
  print(
504
- f"{Fore.RED}While there is no SC Product associated, account number {acctnum} appears to be a suspended account.{Fore.RESET}"
500
+ f"[red]While there is no SC Product associated, account number {acctnum} appears to be a suspended account."
505
501
  )
506
502
  elif (
507
503
  AccountHistogram[acctnum] == "ACTIVE"
508
504
  ): # This compare needs to be separate from below, since we can't compare a string with a "<" operator
509
505
  print(
510
- f"Account Number {Fore.RED}{acctnum}{Fore.RESET} appears to have no SC Product associated with it. This can be a problem"
506
+ f"Account Number [red]{acctnum} appears to have no SC Product associated with it. This can be a problem"
511
507
  )
512
508
  elif AccountHistogram[acctnum] < 1:
513
509
  print(
514
- f"Account Number {Fore.RED}{acctnum}{Fore.RESET} appears to have no SC Product associated with it. This can be a problem"
510
+ f"Account Number [red]{acctnum} appears to have no SC Product associated with it. This can be a problem"
515
511
  )
516
512
  elif AccountHistogram[acctnum] > 1:
517
513
  print(
518
- f"Account Number {Fore.RED}{acctnum}{Fore.RESET} appears to have multiple SC Products associated with it. This can be a problem"
514
+ f"Account Number [red]{acctnum} appears to have multiple SC Products associated with it. This can be a problem"
519
515
  )
520
516
 
521
517
  if ErroredSCProductExists:
@@ -561,7 +557,7 @@ def main():
561
557
  end_time = time()
562
558
  duration = end_time - begin_time
563
559
  if pTiming:
564
- print(f"{Fore.GREEN}This script took {duration:.2f} seconds{Fore.RESET}")
560
+ print(f"[green]This script took {duration:.2f} seconds")
565
561
  print(f"We found {len(aws_acct.ChildAccounts)} accounts within the Org")
566
562
  print(f"We found {len(SCProducts)} Service Catalog Products")
567
563
  print(f"We found {len(SuspendedAccounts)} Suspended accounts")
@@ -69,10 +69,9 @@ from time import time
69
69
  import Inventory_Modules
70
70
  from ArgumentsClass import CommonArguments
71
71
  from botocore.exceptions import ClientError
72
- from colorama import Fore, init
72
+ from runbooks.common.rich_utils import console
73
73
  from Inventory_Modules import display_results, get_all_credentials
74
74
 
75
- init()
76
75
  __version__ = "2023.11.08"
77
76
  begin_time = time()
78
77
 
@@ -317,7 +316,6 @@ def present_results(f_data_found: list):
317
316
 
318
317
 
319
318
  ##########################
320
- ERASE_LINE = "\x1b[2K"
321
319
 
322
320
  if __name__ == "__main__":
323
321
  args = parse_args(sys.argv[1:])
@@ -355,6 +353,6 @@ if __name__ == "__main__":
355
353
 
356
354
  print()
357
355
  if pTiming:
358
- print(f"{Fore.GREEN}This script completed in {time() - begin_time:.2f} seconds{Fore.RESET}")
356
+ print(f"[green]This script completed in {time() - begin_time:.2f} seconds")
359
357
  print()
360
358
  print("Thank you for using this script.")