runbooks 1.1.5__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.
- runbooks/cli/commands/inventory.py +21 -80
- runbooks/common/accuracy_validator.py +6 -12
- runbooks/common/mcp_integration.py +38 -7
- runbooks/common/rich_utils.py +116 -2
- runbooks/inventory/CLAUDE.md +1 -1
- runbooks/inventory/aws_decorators.py +2 -3
- runbooks/inventory/check_cloudtrail_compliance.py +2 -4
- runbooks/inventory/check_controltower_readiness.py +152 -151
- runbooks/inventory/check_landingzone_readiness.py +85 -84
- runbooks/inventory/core/formatter.py +11 -0
- runbooks/inventory/draw_org_structure.py +8 -9
- runbooks/inventory/ec2_vpc_utils.py +2 -2
- runbooks/inventory/find_cfn_drift_detection.py +5 -7
- runbooks/inventory/find_cfn_orphaned_stacks.py +7 -9
- runbooks/inventory/find_cfn_stackset_drift.py +5 -6
- runbooks/inventory/find_ec2_security_groups.py +48 -42
- runbooks/inventory/find_landingzone_versions.py +4 -6
- runbooks/inventory/find_vpc_flow_logs.py +7 -9
- runbooks/inventory/inventory_modules.py +103 -91
- runbooks/inventory/list_cfn_stacks.py +9 -10
- runbooks/inventory/list_cfn_stackset_operation_results.py +1 -3
- runbooks/inventory/list_cfn_stackset_operations.py +79 -57
- runbooks/inventory/list_cfn_stacksets.py +8 -10
- runbooks/inventory/list_config_recorders_delivery_channels.py +49 -39
- runbooks/inventory/list_ds_directories.py +65 -53
- runbooks/inventory/list_ec2_availability_zones.py +2 -4
- runbooks/inventory/list_ec2_ebs_volumes.py +32 -35
- runbooks/inventory/list_ec2_instances.py +23 -28
- runbooks/inventory/list_ecs_clusters_and_tasks.py +26 -34
- runbooks/inventory/list_elbs_load_balancers.py +22 -20
- runbooks/inventory/list_enis_network_interfaces.py +26 -33
- runbooks/inventory/list_guardduty_detectors.py +2 -4
- runbooks/inventory/list_iam_policies.py +2 -4
- runbooks/inventory/list_iam_roles.py +5 -7
- runbooks/inventory/list_iam_saml_providers.py +4 -6
- runbooks/inventory/list_lambda_functions.py +38 -38
- runbooks/inventory/list_org_accounts.py +6 -8
- runbooks/inventory/list_org_accounts_users.py +55 -44
- runbooks/inventory/list_rds_db_instances.py +31 -33
- runbooks/inventory/list_route53_hosted_zones.py +3 -5
- runbooks/inventory/list_servicecatalog_provisioned_products.py +37 -41
- runbooks/inventory/list_sns_topics.py +2 -4
- runbooks/inventory/list_ssm_parameters.py +4 -7
- runbooks/inventory/list_vpc_subnets.py +2 -4
- runbooks/inventory/list_vpcs.py +7 -10
- runbooks/inventory/mcp_inventory_validator.py +5 -3
- runbooks/inventory/organizations_discovery.py +8 -4
- runbooks/inventory/recover_cfn_stack_ids.py +7 -8
- runbooks/inventory/requirements.txt +0 -1
- runbooks/inventory/rich_inventory_display.py +2 -2
- runbooks/inventory/run_on_multi_accounts.py +3 -5
- runbooks/inventory/unified_validation_engine.py +3 -2
- runbooks/inventory/verify_ec2_security_groups.py +1 -1
- runbooks/inventory/vpc_analyzer.py +3 -2
- runbooks/inventory/vpc_dependency_analyzer.py +2 -2
- runbooks/validation/terraform_drift_detector.py +16 -5
- {runbooks-1.1.5.dist-info → runbooks-1.1.6.dist-info}/METADATA +3 -4
- {runbooks-1.1.5.dist-info → runbooks-1.1.6.dist-info}/RECORD +62 -62
- {runbooks-1.1.5.dist-info → runbooks-1.1.6.dist-info}/WHEEL +0 -0
- {runbooks-1.1.5.dist-info → runbooks-1.1.6.dist-info}/entry_points.txt +0 -0
- {runbooks-1.1.5.dist-info → runbooks-1.1.6.dist-info}/licenses/LICENSE +0 -0
- {runbooks-1.1.5.dist-info → runbooks-1.1.6.dist-info}/top_level.txt +0 -0
@@ -62,18 +62,17 @@ from time import time
|
|
62
62
|
|
63
63
|
from ArgumentsClass import CommonArguments
|
64
64
|
from botocore.exceptions import ClientError
|
65
|
-
from
|
65
|
+
from runbooks.common.rich_utils import console
|
66
66
|
from Inventory_Modules import (
|
67
67
|
display_results,
|
68
68
|
find_references_to_security_groups2,
|
69
69
|
find_security_groups2,
|
70
70
|
get_all_credentials,
|
71
71
|
)
|
72
|
-
|
72
|
+
# Migrated to Rich.Progress - see rich_utils.py for enterprise UX standards
|
73
|
+
# from tqdm.auto import tqdm
|
73
74
|
|
74
|
-
init()
|
75
75
|
__version__ = "2024.09.24"
|
76
|
-
ERASE_LINE = "\x1b[2K"
|
77
76
|
begin_time = time()
|
78
77
|
|
79
78
|
|
@@ -387,7 +386,7 @@ def check_accounts_for_security_groups(
|
|
387
386
|
logging.info(
|
388
387
|
f"{ERASE_LINE}Finished finding security groups in account {c_account_credentials['AccountId']} in region {c_account_credentials['Region']}"
|
389
388
|
)
|
390
|
-
pbar.update()
|
389
|
+
pbar.update(pbar_task, advance=1) # Update Rich progress bar
|
391
390
|
self.queue.task_done()
|
392
391
|
|
393
392
|
###########
|
@@ -406,43 +405,50 @@ def check_accounts_for_security_groups(
|
|
406
405
|
# Initialize thread-safe work queue
|
407
406
|
checkqueue = Queue()
|
408
407
|
|
409
|
-
#
|
410
|
-
|
411
|
-
|
412
|
-
|
413
|
-
|
414
|
-
|
408
|
+
# Import Rich display utilities for professional progress tracking
|
409
|
+
from runbooks.common.rich_utils import create_progress_bar
|
410
|
+
|
411
|
+
# Initialize progress tracking for user feedback during long-running analysis
|
412
|
+
with create_progress_bar() as progress:
|
413
|
+
task = progress.add_task(
|
414
|
+
f"[cyan]Finding security groups from {len(fCredentialList)} locations...",
|
415
|
+
total=len(fCredentialList)
|
416
|
+
)
|
417
|
+
|
418
|
+
# Make progress object available to worker threads via global (multi-threaded pattern)
|
419
|
+
global pbar
|
420
|
+
pbar = progress
|
421
|
+
global pbar_task
|
422
|
+
pbar_task = task
|
423
|
+
|
424
|
+
# Start worker thread pool for concurrent security group analysis
|
425
|
+
for x in range(WorkerThreads):
|
426
|
+
worker = FindSecurityGroups(checkqueue)
|
427
|
+
# Daemon threads will terminate when main thread exits
|
428
|
+
# This prevents hanging if an exception occurs in main thread
|
429
|
+
worker.daemon = True
|
430
|
+
worker.start()
|
431
|
+
|
432
|
+
# Queue all credential work items for concurrent processing
|
433
|
+
for credential in fCredentialList:
|
434
|
+
logging.info(f"Connecting to account {credential['AccountId']}")
|
435
|
+
try:
|
436
|
+
# Add credential set and parameters to work queue for worker thread processing
|
437
|
+
checkqueue.put((credential, fFragment, fExact, fDefault))
|
438
|
+
except ClientError as my_Error:
|
439
|
+
# Handle credential validation errors during queue population
|
440
|
+
if "AuthFailure" in str(my_Error):
|
441
|
+
logging.error(
|
442
|
+
f"Authorization Failure accessing account {credential['AccountId']} in '{credential['Region']}' region"
|
443
|
+
)
|
444
|
+
logging.warning(f"It's possible that the region '{credential['Region']}' hasn't been opted-into")
|
445
|
+
# Continue queuing other credentials despite this failure
|
446
|
+
pass
|
415
447
|
|
416
|
-
|
417
|
-
|
418
|
-
|
419
|
-
#
|
420
|
-
# This prevents hanging if an exception occurs in main thread
|
421
|
-
worker.daemon = True
|
422
|
-
worker.start()
|
423
|
-
|
424
|
-
# Queue all credential work items for concurrent processing
|
425
|
-
for credential in fCredentialList:
|
426
|
-
logging.info(f"Connecting to account {credential['AccountId']}")
|
427
|
-
try:
|
428
|
-
# Add credential set and parameters to work queue for worker thread processing
|
429
|
-
checkqueue.put((credential, fFragment, fExact, fDefault))
|
430
|
-
except ClientError as my_Error:
|
431
|
-
# Handle credential validation errors during queue population
|
432
|
-
if "AuthFailure" in str(my_Error):
|
433
|
-
logging.error(
|
434
|
-
f"Authorization Failure accessing account {credential['AccountId']} in '{credential['Region']}' region"
|
435
|
-
)
|
436
|
-
logging.warning(f"It's possible that the region '{credential['Region']}' hasn't been opted-into")
|
437
|
-
# Continue queuing other credentials despite this failure
|
438
|
-
pass
|
439
|
-
|
440
|
-
# Wait for all queued work items to be processed by worker threads
|
441
|
-
# This blocks until all worker threads call task_done()
|
442
|
-
checkqueue.join()
|
443
|
-
|
444
|
-
# Clean up progress bar
|
445
|
-
pbar.close()
|
448
|
+
# Wait for all queued work items to be processed by worker threads
|
449
|
+
# This blocks until all worker threads call task_done()
|
450
|
+
checkqueue.join()
|
451
|
+
# Progress bar auto-closes when exiting context manager
|
446
452
|
|
447
453
|
return AllSecurityGroups
|
448
454
|
|
@@ -659,7 +665,7 @@ if __name__ == "__main__":
|
|
659
665
|
print(f"Data has been saved to {saved_filename}")
|
660
666
|
if pTiming:
|
661
667
|
print(ERASE_LINE)
|
662
|
-
print(f"
|
668
|
+
print(f"[green]This script took {time() - begin_time:.2f} seconds")
|
663
669
|
|
664
670
|
print(
|
665
671
|
f"We found {len(AllSecurityGroups)} {'default ' if pDefault else ''}security group{'' if len(AllSecurityGroups) == 1 else 's'} across {len(AccountList)} accounts and {len(RegionList)} regions"
|
@@ -76,10 +76,9 @@ import boto3
|
|
76
76
|
import Inventory_Modules
|
77
77
|
from ArgumentsClass import CommonArguments
|
78
78
|
from botocore.exceptions import ClientError, CredentialRetrievalError, InvalidConfigError
|
79
|
-
|
79
|
+
# colorama removed - migrated to Rich
|
80
80
|
|
81
81
|
# Initialize colorama for cross-platform colored terminal output
|
82
|
-
init()
|
83
82
|
|
84
83
|
__version__ = "2023.05.31"
|
85
84
|
|
@@ -101,7 +100,6 @@ logging.basicConfig(
|
|
101
100
|
|
102
101
|
##########################
|
103
102
|
# Terminal control and operational configuration constants
|
104
|
-
ERASE_LINE = "\x1b[2K" # ANSI escape sequence for clearing terminal line during progress display
|
105
103
|
SkipProfiles = ["default"] # Profile exclusion list for organizational Landing Zone discovery
|
106
104
|
|
107
105
|
# Configure AWS profile discovery strategy based on user input
|
@@ -129,7 +127,7 @@ ALZProfiles = [] # Initialize Landing Zone profile collection for discovered Ma
|
|
129
127
|
|
130
128
|
for profile in AllProfiles:
|
131
129
|
# Display real-time progress during profile analysis with terminal line clearing
|
132
|
-
print(f"
|
130
|
+
print(f"Checking profile: {profile}", end="\r")
|
133
131
|
|
134
132
|
try:
|
135
133
|
# Analyze current profile to determine if it represents an AWS Landing Zone Management Account
|
@@ -164,7 +162,7 @@ for profile in AllProfiles:
|
|
164
162
|
pass
|
165
163
|
|
166
164
|
# Clear progress display and initialize tabular output formatting for Landing Zone inventory
|
167
|
-
print(
|
165
|
+
console.print()
|
168
166
|
fmt = "%-20s %-13s %-15s %-35s %-21s" # Column formatting for structured Landing Zone data display
|
169
167
|
print(fmt % ("Profile", "Account", "Region", "ALZ Stack Name", "ALZ Version"))
|
170
168
|
print(fmt % ("-------", "-------", "------", "--------------", "-----------"))
|
@@ -194,7 +192,7 @@ for item in ALZProfiles:
|
|
194
192
|
)
|
195
193
|
|
196
194
|
# Display comprehensive operational summary with discovery metrics
|
197
|
-
print(
|
195
|
+
console.print()
|
198
196
|
print(f"Checked {len(AllProfiles)} accounts/ Orgs. Found {len(ALZProfiles)} ALZs")
|
199
197
|
print()
|
200
198
|
print("Thank you for using this script.")
|
@@ -93,13 +93,11 @@ from account_class import aws_acct_access
|
|
93
93
|
from ArgumentsClass import CommonArguments
|
94
94
|
from botocore.config import Config
|
95
95
|
from botocore.exceptions import ClientError
|
96
|
-
from
|
96
|
+
from runbooks.common.rich_utils import console
|
97
97
|
from Inventory_Modules import RemoveCoreAccounts, display_results, get_all_credentials, get_regions3
|
98
98
|
|
99
99
|
# Initialize colorama for cross-platform colored terminal output
|
100
|
-
init()
|
101
100
|
__version__ = "2024.03.10"
|
102
|
-
ERASE_LINE = "\x1b[2K" # ANSI escape sequence for clearing terminal line during progress display
|
103
101
|
begin_time = time() # Script execution timing for performance monitoring
|
104
102
|
sleep_interval = 5 # Default wait interval for CloudWatch Logs query processing
|
105
103
|
|
@@ -305,13 +303,13 @@ def setup_auth_accounts_and_regions(fProfile: str) -> (aws_acct_access, list, li
|
|
305
303
|
|
306
304
|
# Display comprehensive operational context for user confirmation and audit logging
|
307
305
|
print(f"You asked to sum flow log data")
|
308
|
-
print(f"\tin these accounts: {
|
309
|
-
print(f"\tin these regions: {
|
306
|
+
print(f"\tin these accounts: [red]{AccountList}")
|
307
|
+
print(f"\tin these regions: [red]{RegionList}")
|
310
308
|
print(f"\tFrom: {pStartDate} until {pEndDate}")
|
311
309
|
|
312
310
|
# Display account exclusion information for operational transparency
|
313
311
|
if pSkipAccounts is not None:
|
314
|
-
print(f"\tWhile skipping these accounts: {
|
312
|
+
print(f"\tWhile skipping these accounts: [red]{pSkipAccounts}")
|
315
313
|
|
316
314
|
return aws_acct, AccountList, RegionList
|
317
315
|
|
@@ -1040,13 +1038,13 @@ def get_cw_query_results(fquery_requests: list) -> list[dict]:
|
|
1040
1038
|
# Implement timeout protection for long-running queries
|
1041
1039
|
if waited_seconds_total > (SpannedDaysChecked * 5):
|
1042
1040
|
print(
|
1043
|
-
f"
|
1041
|
+
f"Query is still running... Waited {waited_seconds_total} seconds already, we'll have to check manually later. "
|
1044
1042
|
)
|
1045
1043
|
break
|
1046
1044
|
|
1047
1045
|
# Display real-time progress for query execution monitoring
|
1048
1046
|
print(
|
1049
|
-
f"
|
1047
|
+
f"Query for vpc {query['VPCId']} in account {query['AccountId']} in region {query['Region']} is still running... It's been {waited_seconds_total} seconds so far",
|
1050
1048
|
end="\r",
|
1051
1049
|
)
|
1052
1050
|
|
@@ -1181,7 +1179,7 @@ if __name__ == "__main__":
|
|
1181
1179
|
f"Account {credential['AccountId']} was successfully connected via role {credential.get('Role', pAccessRole)} from {aws_acct.acct_number}"
|
1182
1180
|
)
|
1183
1181
|
print(
|
1184
|
-
f"
|
1182
|
+
f"Checking account [blue]{credential['AccountId']} in region [blue]{credential['Region']}...",
|
1185
1183
|
end="\r",
|
1186
1184
|
)
|
1187
1185
|
"""
|
@@ -455,12 +455,12 @@ def print_timings(fTiming: bool = False, fverbose: int = 50, fbegin_time=None, f
|
|
455
455
|
"""
|
456
456
|
from time import time
|
457
457
|
|
458
|
-
from
|
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"{
|
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
|
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
|
-
|
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
|
-
|
4332
|
-
|
4333
|
-
|
4334
|
-
|
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
|
-
|
4338
|
-
|
4339
|
-
|
4340
|
-
|
4341
|
-
|
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
|
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
|
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"
|
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
|
5852
|
-
from
|
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
|
-
|
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
|
-
|
5979
|
-
|
5980
|
-
|
5981
|
-
|
5982
|
-
|
5983
|
-
|
5984
|
-
|
5985
|
-
|
5986
|
-
|
5987
|
-
|
5988
|
-
|
5989
|
-
|
5990
|
-
|
5991
|
-
|
5992
|
-
|
5993
|
-
|
5994
|
-
|
5995
|
-
|
5996
|
-
|
5997
|
-
|
5998
|
-
|
5999
|
-
|
6000
|
-
|
6001
|
-
|
6002
|
-
|
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
|
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
|
-
|
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
|
-
|
6115
|
-
|
6116
|
-
|
6117
|
-
|
6118
|
-
|
6119
|
-
|
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
|
-
|
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
|
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
|
234
|
-
print(f"in these accounts:\n{
|
235
|
-
print(f"in these regions:\n{
|
236
|
-
print(f"While skipping these accounts:\n{
|
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
|
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}
|
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"
|
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"
|
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
|
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 = {}
|