runbooks 1.1.4__py3-none-any.whl → 1.1.6__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 (273) hide show
  1. runbooks/__init__.py +31 -2
  2. runbooks/__init___optimized.py +18 -4
  3. runbooks/_platform/__init__.py +1 -5
  4. runbooks/_platform/core/runbooks_wrapper.py +141 -138
  5. runbooks/aws2/accuracy_validator.py +812 -0
  6. runbooks/base.py +7 -0
  7. runbooks/cfat/assessment/compliance.py +1 -1
  8. runbooks/cfat/assessment/runner.py +1 -0
  9. runbooks/cfat/cloud_foundations_assessment.py +227 -239
  10. runbooks/cli/__init__.py +1 -1
  11. runbooks/cli/commands/cfat.py +64 -23
  12. runbooks/cli/commands/finops.py +1005 -54
  13. runbooks/cli/commands/inventory.py +135 -91
  14. runbooks/cli/commands/operate.py +9 -36
  15. runbooks/cli/commands/security.py +42 -18
  16. runbooks/cli/commands/validation.py +432 -18
  17. runbooks/cli/commands/vpc.py +81 -17
  18. runbooks/cli/registry.py +22 -10
  19. runbooks/cloudops/__init__.py +20 -27
  20. runbooks/cloudops/base.py +96 -107
  21. runbooks/cloudops/cost_optimizer.py +544 -542
  22. runbooks/cloudops/infrastructure_optimizer.py +5 -4
  23. runbooks/cloudops/interfaces.py +224 -225
  24. runbooks/cloudops/lifecycle_manager.py +5 -4
  25. runbooks/cloudops/mcp_cost_validation.py +252 -235
  26. runbooks/cloudops/models.py +78 -53
  27. runbooks/cloudops/monitoring_automation.py +5 -4
  28. runbooks/cloudops/notebook_framework.py +177 -213
  29. runbooks/cloudops/security_enforcer.py +125 -159
  30. runbooks/common/accuracy_validator.py +17 -12
  31. runbooks/common/aws_pricing.py +349 -326
  32. runbooks/common/aws_pricing_api.py +211 -212
  33. runbooks/common/aws_profile_manager.py +40 -36
  34. runbooks/common/aws_utils.py +74 -79
  35. runbooks/common/business_logic.py +126 -104
  36. runbooks/common/cli_decorators.py +36 -60
  37. runbooks/common/comprehensive_cost_explorer_integration.py +455 -463
  38. runbooks/common/cross_account_manager.py +197 -204
  39. runbooks/common/date_utils.py +27 -39
  40. runbooks/common/decorators.py +29 -19
  41. runbooks/common/dry_run_examples.py +173 -208
  42. runbooks/common/dry_run_framework.py +157 -155
  43. runbooks/common/enhanced_exception_handler.py +15 -4
  44. runbooks/common/enhanced_logging_example.py +50 -64
  45. runbooks/common/enhanced_logging_integration_example.py +65 -37
  46. runbooks/common/env_utils.py +16 -16
  47. runbooks/common/error_handling.py +40 -38
  48. runbooks/common/lazy_loader.py +41 -23
  49. runbooks/common/logging_integration_helper.py +79 -86
  50. runbooks/common/mcp_cost_explorer_integration.py +476 -493
  51. runbooks/common/mcp_integration.py +99 -79
  52. runbooks/common/memory_optimization.py +140 -118
  53. runbooks/common/module_cli_base.py +37 -58
  54. runbooks/common/organizations_client.py +175 -193
  55. runbooks/common/patterns.py +23 -25
  56. runbooks/common/performance_monitoring.py +67 -71
  57. runbooks/common/performance_optimization_engine.py +283 -274
  58. runbooks/common/profile_utils.py +111 -37
  59. runbooks/common/rich_utils.py +315 -141
  60. runbooks/common/sre_performance_suite.py +177 -186
  61. runbooks/enterprise/__init__.py +1 -1
  62. runbooks/enterprise/logging.py +144 -106
  63. runbooks/enterprise/security.py +187 -204
  64. runbooks/enterprise/validation.py +43 -56
  65. runbooks/finops/__init__.py +26 -30
  66. runbooks/finops/account_resolver.py +1 -1
  67. runbooks/finops/advanced_optimization_engine.py +980 -0
  68. runbooks/finops/automation_core.py +268 -231
  69. runbooks/finops/business_case_config.py +184 -179
  70. runbooks/finops/cli.py +660 -139
  71. runbooks/finops/commvault_ec2_analysis.py +157 -164
  72. runbooks/finops/compute_cost_optimizer.py +336 -320
  73. runbooks/finops/config.py +20 -20
  74. runbooks/finops/cost_optimizer.py +484 -618
  75. runbooks/finops/cost_processor.py +332 -214
  76. runbooks/finops/dashboard_runner.py +1006 -172
  77. runbooks/finops/ebs_cost_optimizer.py +991 -657
  78. runbooks/finops/elastic_ip_optimizer.py +317 -257
  79. runbooks/finops/enhanced_mcp_integration.py +340 -0
  80. runbooks/finops/enhanced_progress.py +32 -29
  81. runbooks/finops/enhanced_trend_visualization.py +3 -2
  82. runbooks/finops/enterprise_wrappers.py +223 -285
  83. runbooks/finops/executive_export.py +203 -160
  84. runbooks/finops/helpers.py +130 -288
  85. runbooks/finops/iam_guidance.py +1 -1
  86. runbooks/finops/infrastructure/__init__.py +80 -0
  87. runbooks/finops/infrastructure/commands.py +506 -0
  88. runbooks/finops/infrastructure/load_balancer_optimizer.py +866 -0
  89. runbooks/finops/infrastructure/vpc_endpoint_optimizer.py +832 -0
  90. runbooks/finops/markdown_exporter.py +337 -174
  91. runbooks/finops/mcp_validator.py +1952 -0
  92. runbooks/finops/nat_gateway_optimizer.py +1512 -481
  93. runbooks/finops/network_cost_optimizer.py +657 -587
  94. runbooks/finops/notebook_utils.py +226 -188
  95. runbooks/finops/optimization_engine.py +1136 -0
  96. runbooks/finops/optimizer.py +19 -23
  97. runbooks/finops/rds_snapshot_optimizer.py +367 -411
  98. runbooks/finops/reservation_optimizer.py +427 -363
  99. runbooks/finops/scenario_cli_integration.py +64 -65
  100. runbooks/finops/scenarios.py +1277 -438
  101. runbooks/finops/schemas.py +218 -182
  102. runbooks/finops/snapshot_manager.py +2289 -0
  103. runbooks/finops/types.py +3 -3
  104. runbooks/finops/validation_framework.py +259 -265
  105. runbooks/finops/vpc_cleanup_exporter.py +189 -144
  106. runbooks/finops/vpc_cleanup_optimizer.py +591 -573
  107. runbooks/finops/workspaces_analyzer.py +171 -182
  108. runbooks/integration/__init__.py +89 -0
  109. runbooks/integration/mcp_integration.py +1920 -0
  110. runbooks/inventory/CLAUDE.md +816 -0
  111. runbooks/inventory/__init__.py +2 -2
  112. runbooks/inventory/aws_decorators.py +2 -3
  113. runbooks/inventory/check_cloudtrail_compliance.py +2 -4
  114. runbooks/inventory/check_controltower_readiness.py +152 -151
  115. runbooks/inventory/check_landingzone_readiness.py +85 -84
  116. runbooks/inventory/cloud_foundations_integration.py +144 -149
  117. runbooks/inventory/collectors/aws_comprehensive.py +1 -1
  118. runbooks/inventory/collectors/aws_networking.py +109 -99
  119. runbooks/inventory/collectors/base.py +4 -0
  120. runbooks/inventory/core/collector.py +495 -313
  121. runbooks/inventory/core/formatter.py +11 -0
  122. runbooks/inventory/draw_org_structure.py +8 -9
  123. runbooks/inventory/drift_detection_cli.py +69 -96
  124. runbooks/inventory/ec2_vpc_utils.py +2 -2
  125. runbooks/inventory/find_cfn_drift_detection.py +5 -7
  126. runbooks/inventory/find_cfn_orphaned_stacks.py +7 -9
  127. runbooks/inventory/find_cfn_stackset_drift.py +5 -6
  128. runbooks/inventory/find_ec2_security_groups.py +48 -42
  129. runbooks/inventory/find_landingzone_versions.py +4 -6
  130. runbooks/inventory/find_vpc_flow_logs.py +7 -9
  131. runbooks/inventory/inventory_mcp_cli.py +48 -46
  132. runbooks/inventory/inventory_modules.py +103 -91
  133. runbooks/inventory/list_cfn_stacks.py +9 -10
  134. runbooks/inventory/list_cfn_stackset_operation_results.py +1 -3
  135. runbooks/inventory/list_cfn_stackset_operations.py +79 -57
  136. runbooks/inventory/list_cfn_stacksets.py +8 -10
  137. runbooks/inventory/list_config_recorders_delivery_channels.py +49 -39
  138. runbooks/inventory/list_ds_directories.py +65 -53
  139. runbooks/inventory/list_ec2_availability_zones.py +2 -4
  140. runbooks/inventory/list_ec2_ebs_volumes.py +32 -35
  141. runbooks/inventory/list_ec2_instances.py +23 -28
  142. runbooks/inventory/list_ecs_clusters_and_tasks.py +26 -34
  143. runbooks/inventory/list_elbs_load_balancers.py +22 -20
  144. runbooks/inventory/list_enis_network_interfaces.py +26 -33
  145. runbooks/inventory/list_guardduty_detectors.py +2 -4
  146. runbooks/inventory/list_iam_policies.py +2 -4
  147. runbooks/inventory/list_iam_roles.py +5 -7
  148. runbooks/inventory/list_iam_saml_providers.py +4 -6
  149. runbooks/inventory/list_lambda_functions.py +38 -38
  150. runbooks/inventory/list_org_accounts.py +6 -8
  151. runbooks/inventory/list_org_accounts_users.py +55 -44
  152. runbooks/inventory/list_rds_db_instances.py +31 -33
  153. runbooks/inventory/list_rds_snapshots_aggregator.py +192 -208
  154. runbooks/inventory/list_route53_hosted_zones.py +3 -5
  155. runbooks/inventory/list_servicecatalog_provisioned_products.py +37 -41
  156. runbooks/inventory/list_sns_topics.py +2 -4
  157. runbooks/inventory/list_ssm_parameters.py +4 -7
  158. runbooks/inventory/list_vpc_subnets.py +2 -4
  159. runbooks/inventory/list_vpcs.py +7 -10
  160. runbooks/inventory/mcp_inventory_validator.py +554 -468
  161. runbooks/inventory/mcp_vpc_validator.py +359 -442
  162. runbooks/inventory/organizations_discovery.py +63 -55
  163. runbooks/inventory/recover_cfn_stack_ids.py +7 -8
  164. runbooks/inventory/requirements.txt +0 -1
  165. runbooks/inventory/rich_inventory_display.py +35 -34
  166. runbooks/inventory/run_on_multi_accounts.py +3 -5
  167. runbooks/inventory/unified_validation_engine.py +281 -253
  168. runbooks/inventory/verify_ec2_security_groups.py +1 -1
  169. runbooks/inventory/vpc_analyzer.py +735 -697
  170. runbooks/inventory/vpc_architecture_validator.py +293 -348
  171. runbooks/inventory/vpc_dependency_analyzer.py +384 -380
  172. runbooks/inventory/vpc_flow_analyzer.py +1 -1
  173. runbooks/main.py +49 -34
  174. runbooks/main_final.py +91 -60
  175. runbooks/main_minimal.py +22 -10
  176. runbooks/main_optimized.py +131 -100
  177. runbooks/main_ultra_minimal.py +7 -2
  178. runbooks/mcp/__init__.py +36 -0
  179. runbooks/mcp/integration.py +679 -0
  180. runbooks/monitoring/performance_monitor.py +9 -4
  181. runbooks/operate/dynamodb_operations.py +3 -1
  182. runbooks/operate/ec2_operations.py +145 -137
  183. runbooks/operate/iam_operations.py +146 -152
  184. runbooks/operate/networking_cost_heatmap.py +29 -8
  185. runbooks/operate/rds_operations.py +223 -254
  186. runbooks/operate/s3_operations.py +107 -118
  187. runbooks/operate/vpc_operations.py +646 -616
  188. runbooks/remediation/base.py +1 -1
  189. runbooks/remediation/commons.py +10 -7
  190. runbooks/remediation/commvault_ec2_analysis.py +70 -66
  191. runbooks/remediation/ec2_unattached_ebs_volumes.py +1 -0
  192. runbooks/remediation/multi_account.py +24 -21
  193. runbooks/remediation/rds_snapshot_list.py +86 -60
  194. runbooks/remediation/remediation_cli.py +92 -146
  195. runbooks/remediation/universal_account_discovery.py +83 -79
  196. runbooks/remediation/workspaces_list.py +46 -41
  197. runbooks/security/__init__.py +19 -0
  198. runbooks/security/assessment_runner.py +1150 -0
  199. runbooks/security/baseline_checker.py +812 -0
  200. runbooks/security/cloudops_automation_security_validator.py +509 -535
  201. runbooks/security/compliance_automation_engine.py +17 -17
  202. runbooks/security/config/__init__.py +2 -2
  203. runbooks/security/config/compliance_config.py +50 -50
  204. runbooks/security/config_template_generator.py +63 -76
  205. runbooks/security/enterprise_security_framework.py +1 -1
  206. runbooks/security/executive_security_dashboard.py +519 -508
  207. runbooks/security/multi_account_security_controls.py +959 -1210
  208. runbooks/security/real_time_security_monitor.py +422 -444
  209. runbooks/security/security_baseline_tester.py +1 -1
  210. runbooks/security/security_cli.py +143 -112
  211. runbooks/security/test_2way_validation.py +439 -0
  212. runbooks/security/two_way_validation_framework.py +852 -0
  213. runbooks/sre/production_monitoring_framework.py +167 -177
  214. runbooks/tdd/__init__.py +15 -0
  215. runbooks/tdd/cli.py +1071 -0
  216. runbooks/utils/__init__.py +14 -17
  217. runbooks/utils/logger.py +7 -2
  218. runbooks/utils/version_validator.py +50 -47
  219. runbooks/validation/__init__.py +6 -6
  220. runbooks/validation/cli.py +9 -3
  221. runbooks/validation/comprehensive_2way_validator.py +745 -704
  222. runbooks/validation/mcp_validator.py +906 -228
  223. runbooks/validation/terraform_citations_validator.py +104 -115
  224. runbooks/validation/terraform_drift_detector.py +461 -454
  225. runbooks/vpc/README.md +617 -0
  226. runbooks/vpc/__init__.py +8 -1
  227. runbooks/vpc/analyzer.py +577 -0
  228. runbooks/vpc/cleanup_wrapper.py +476 -413
  229. runbooks/vpc/cli_cloudtrail_commands.py +339 -0
  230. runbooks/vpc/cli_mcp_validation_commands.py +480 -0
  231. runbooks/vpc/cloudtrail_audit_integration.py +717 -0
  232. runbooks/vpc/config.py +92 -97
  233. runbooks/vpc/cost_engine.py +411 -148
  234. runbooks/vpc/cost_explorer_integration.py +553 -0
  235. runbooks/vpc/cross_account_session.py +101 -106
  236. runbooks/vpc/enhanced_mcp_validation.py +917 -0
  237. runbooks/vpc/eni_gate_validator.py +961 -0
  238. runbooks/vpc/heatmap_engine.py +185 -160
  239. runbooks/vpc/mcp_no_eni_validator.py +680 -639
  240. runbooks/vpc/nat_gateway_optimizer.py +358 -0
  241. runbooks/vpc/networking_wrapper.py +15 -8
  242. runbooks/vpc/pdca_remediation_planner.py +528 -0
  243. runbooks/vpc/performance_optimized_analyzer.py +219 -231
  244. runbooks/vpc/runbooks_adapter.py +1167 -241
  245. runbooks/vpc/tdd_red_phase_stubs.py +601 -0
  246. runbooks/vpc/test_data_loader.py +358 -0
  247. runbooks/vpc/tests/conftest.py +314 -4
  248. runbooks/vpc/tests/test_cleanup_framework.py +1022 -0
  249. runbooks/vpc/tests/test_cost_engine.py +0 -2
  250. runbooks/vpc/topology_generator.py +326 -0
  251. runbooks/vpc/unified_scenarios.py +1297 -1124
  252. runbooks/vpc/vpc_cleanup_integration.py +1943 -1115
  253. runbooks-1.1.6.dist-info/METADATA +327 -0
  254. runbooks-1.1.6.dist-info/RECORD +489 -0
  255. runbooks/finops/README.md +0 -414
  256. runbooks/finops/accuracy_cross_validator.py +0 -647
  257. runbooks/finops/business_cases.py +0 -950
  258. runbooks/finops/dashboard_router.py +0 -922
  259. runbooks/finops/ebs_optimizer.py +0 -973
  260. runbooks/finops/embedded_mcp_validator.py +0 -1629
  261. runbooks/finops/enhanced_dashboard_runner.py +0 -527
  262. runbooks/finops/finops_dashboard.py +0 -584
  263. runbooks/finops/finops_scenarios.py +0 -1218
  264. runbooks/finops/legacy_migration.py +0 -730
  265. runbooks/finops/multi_dashboard.py +0 -1519
  266. runbooks/finops/single_dashboard.py +0 -1113
  267. runbooks/finops/unlimited_scenarios.py +0 -393
  268. runbooks-1.1.4.dist-info/METADATA +0 -800
  269. runbooks-1.1.4.dist-info/RECORD +0 -468
  270. {runbooks-1.1.4.dist-info → runbooks-1.1.6.dist-info}/WHEEL +0 -0
  271. {runbooks-1.1.4.dist-info → runbooks-1.1.6.dist-info}/entry_points.txt +0 -0
  272. {runbooks-1.1.4.dist-info → runbooks-1.1.6.dist-info}/licenses/LICENSE +0 -0
  273. {runbooks-1.1.4.dist-info → runbooks-1.1.6.dist-info}/top_level.txt +0 -0
@@ -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 = {}
@@ -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()