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
@@ -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
|
80
|
+
from runbooks.common.rich_utils import console
|
81
81
|
from Inventory_Modules import display_results, find_stacksets3, get_regions3
|
82
|
-
|
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"
|
272
|
-
f"Please run the command again and specify only a single, valid region
|
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
|
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
|
-
|
578
|
-
|
579
|
-
|
580
|
-
|
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
|
-
|
664
|
-
|
665
|
-
|
666
|
-
|
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"
|
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"
|
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
|
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: {
|
274
|
-
print(f"\tin these regions: {
|
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: {
|
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}
|
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}
|
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"
|
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"
|
449
|
+
print(f"[green]This script took {time() - begin_time:.2f} seconds")
|
452
450
|
print("Thanks for using this script...")
|
453
451
|
print()
|
@@ -93,11 +93,11 @@ from time import time
|
|
93
93
|
import Inventory_Modules
|
94
94
|
from ArgumentsClass import CommonArguments
|
95
95
|
from botocore.exceptions import ClientError
|
96
|
-
from
|
96
|
+
from runbooks.common.rich_utils import console
|
97
97
|
from Inventory_Modules import del_config_recorder_or_delivery_channel2, display_results, get_all_credentials
|
98
|
-
|
98
|
+
# Migrated to Rich.Progress - see rich_utils.py for enterprise UX standards
|
99
|
+
# from tqdm.auto import tqdm
|
99
100
|
|
100
|
-
init()
|
101
101
|
__version__ = "2024.05.31"
|
102
102
|
|
103
103
|
|
@@ -409,7 +409,7 @@ def check_accounts_for_delivery_channels_and_config_recorders(CredentialList, fF
|
|
409
409
|
logging.info(
|
410
410
|
f"{ERASE_LINE}Finished finding items in account {c_account_credentials['AccountId']} in region {c_account_credentials['Region']} - {c_PlaceCount} / {c_PlacesToLook}"
|
411
411
|
)
|
412
|
-
pbar.update() # Update progress bar for operational visibility
|
412
|
+
pbar.update(pbar_task, advance=1) # Update Rich progress bar for operational visibility
|
413
413
|
self.queue.task_done() # Mark queue item as completed
|
414
414
|
|
415
415
|
# Initialize processing context and data structures for Config discovery
|
@@ -421,40 +421,50 @@ def check_accounts_for_delivery_channels_and_config_recorders(CredentialList, fF
|
|
421
421
|
|
422
422
|
checkqueue = Queue() # Queue for work distribution across worker threads
|
423
423
|
|
424
|
+
# Import Rich display utilities for professional progress tracking
|
425
|
+
from runbooks.common.rich_utils import create_progress_bar
|
426
|
+
|
424
427
|
# Initialize progress tracking for operational visibility during Config discovery
|
425
|
-
|
426
|
-
|
427
|
-
|
428
|
-
|
429
|
-
|
428
|
+
with create_progress_bar() as progress:
|
429
|
+
task = progress.add_task(
|
430
|
+
f"[cyan]Finding config recorders / delivery channels from {len(AllCredentials)} accounts and regions...",
|
431
|
+
total=len(AllCredentials)
|
432
|
+
)
|
430
433
|
|
431
|
-
|
432
|
-
|
433
|
-
|
434
|
-
|
435
|
-
|
436
|
-
|
437
|
-
|
438
|
-
|
439
|
-
|
440
|
-
|
441
|
-
|
442
|
-
|
443
|
-
|
444
|
-
|
445
|
-
|
446
|
-
|
447
|
-
|
448
|
-
|
449
|
-
|
450
|
-
|
451
|
-
)
|
452
|
-
|
453
|
-
|
434
|
+
# Make progress object available to worker threads via global (multi-threaded pattern)
|
435
|
+
global pbar
|
436
|
+
pbar = progress
|
437
|
+
global pbar_task
|
438
|
+
pbar_task = task
|
439
|
+
|
440
|
+
# Create and start worker thread pool for concurrent Config component discovery
|
441
|
+
for x in range(WorkerThreads):
|
442
|
+
worker = Find_Config_Recorders_and_Delivery_Channels(checkqueue)
|
443
|
+
# Daemon threads allow main thread exit even if workers are still processing
|
444
|
+
worker.daemon = True
|
445
|
+
worker.start()
|
446
|
+
|
447
|
+
# Queue Config discovery work items for worker thread processing
|
448
|
+
# Note: Credential list already includes regional context, eliminating need for nested region iteration
|
449
|
+
for credential in CredentialList:
|
450
|
+
logging.info(f"Connecting to account {credential['AccountId']} in region {credential['Region']}")
|
451
|
+
try:
|
452
|
+
# Queue account and region combination for worker thread processing
|
453
|
+
# Note: Tuple structure is critical for proper parameter expansion in worker threads
|
454
|
+
checkqueue.put((credential, fFixRun, fFragments, len(CredentialList), PlaceCount))
|
455
|
+
except ClientError as my_Error:
|
456
|
+
# Handle authorization failures with informative error messaging
|
457
|
+
if "AuthFailure" in str(my_Error):
|
458
|
+
logging.error(
|
459
|
+
f"Authorization Failure accessing account {credential['AccountId']} in {credential['Region']} region"
|
460
|
+
)
|
461
|
+
logging.warning(f"It's possible that the region {credential['Region']} hasn't been opted-into")
|
462
|
+
pass
|
463
|
+
|
464
|
+
# Wait for all worker threads to complete processing
|
465
|
+
checkqueue.join()
|
466
|
+
# Progress bar auto-closes when exiting context manager
|
454
467
|
|
455
|
-
# Wait for all worker threads to complete processing
|
456
|
-
checkqueue.join()
|
457
|
-
pbar.close() # Close progress bar after completion
|
458
468
|
return account_crs_and_dcs
|
459
469
|
|
460
470
|
|
@@ -619,7 +629,7 @@ if __name__ == "__main__":
|
|
619
629
|
print()
|
620
630
|
milestone_time1 = time()
|
621
631
|
print(
|
622
|
-
f"
|
632
|
+
f"[green]\t\tFiguring out what regions are available to your accounts, and capturing credentials for all accounts in those regions took: {(milestone_time1 - begin_time):.3f} seconds"
|
623
633
|
)
|
624
634
|
print()
|
625
635
|
print(f"Now running through all accounts and regions identified to find resources...")
|
@@ -631,7 +641,7 @@ if __name__ == "__main__":
|
|
631
641
|
print()
|
632
642
|
milestone_time2 = time()
|
633
643
|
print(
|
634
|
-
f"
|
644
|
+
f"[green]\t\tChecking {len(AllCredentials)} places took: {(milestone_time2 - milestone_time1):.3f} seconds"
|
635
645
|
)
|
636
646
|
print()
|
637
647
|
cr = 0
|
@@ -650,7 +660,7 @@ if __name__ == "__main__":
|
|
650
660
|
print()
|
651
661
|
milestone_time3 = time()
|
652
662
|
print(
|
653
|
-
f"
|
663
|
+
f"[green]\t\tSorting the list of places took: {(milestone_time3 - milestone_time2):.3f} seconds"
|
654
664
|
)
|
655
665
|
print()
|
656
666
|
display_results(all_sorted_config_recorders_and_delivery_channels, display_dict, None, pFilename)
|
@@ -675,7 +685,7 @@ if __name__ == "__main__":
|
|
675
685
|
|
676
686
|
if pTiming:
|
677
687
|
print(ERASE_LINE)
|
678
|
-
print(f"
|
688
|
+
print(f"[green]This whole script took {time() - begin_time:.3f} seconds")
|
679
689
|
print()
|
680
690
|
print("Thank you for using this tool")
|
681
691
|
print()
|
@@ -60,11 +60,11 @@ from time import time
|
|
60
60
|
|
61
61
|
from ArgumentsClass import CommonArguments
|
62
62
|
from botocore.exceptions import ClientError
|
63
|
-
from
|
63
|
+
from runbooks.common.rich_utils import console
|
64
64
|
from Inventory_Modules import display_results, find_directories2, get_all_credentials
|
65
|
-
|
65
|
+
# Migrated to Rich.Progress - see rich_utils.py for enterprise UX standards
|
66
|
+
# from tqdm.auto import tqdm
|
66
67
|
|
67
|
-
init()
|
68
68
|
__version__ = "2024.05.31"
|
69
69
|
|
70
70
|
|
@@ -193,54 +193,66 @@ def find_all_directories(f_credentials, f_fragments, f_exact):
|
|
193
193
|
"""
|
194
194
|
AllDirectories = [] # Aggregated list for all discovered directories
|
195
195
|
|
196
|
+
# Import Rich display utilities for professional progress tracking
|
197
|
+
from runbooks.common.rich_utils import create_progress_bar
|
198
|
+
|
196
199
|
# TODO: Need to use multi-threading here for improved performance
|
197
200
|
# Sequential processing with progress tracking for operational visibility
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
logging.
|
201
|
+
with create_progress_bar() as progress:
|
202
|
+
task = progress.add_task(
|
203
|
+
f"[cyan]Looking through {len(f_credentials)} accounts and regions...",
|
204
|
+
total=len(f_credentials)
|
205
|
+
)
|
206
|
+
|
207
|
+
for credential in f_credentials:
|
208
|
+
logging.info(f"Looking in account: {credential['AccountId']} in region {credential['Region']}")
|
209
|
+
|
210
|
+
# Skip failed credentials to avoid API errors
|
211
|
+
if not credential["Success"]:
|
212
|
+
progress.update(task, advance=1)
|
213
|
+
continue
|
214
|
+
|
215
|
+
try:
|
216
|
+
# Discover directories using Directory Service API with fragment filtering
|
217
|
+
directories = find_directories2(credential, credential["Region"], f_fragments, f_exact)
|
218
|
+
logging.info(f"directories: {directories}")
|
219
|
+
logging.info(
|
220
|
+
f"Account: {credential['AccountId']} Region: {credential['Region']} Found {len(directories)} directories"
|
221
|
+
)
|
222
|
+
|
223
|
+
# Process and aggregate discovered directories with organizational context
|
224
|
+
if directories:
|
225
|
+
for directory in directories:
|
226
|
+
# Enhance directory metadata with organizational and regional context
|
227
|
+
# Available directory metadata includes:
|
228
|
+
# - DirectoryName: Human-readable directory identifier
|
229
|
+
# - DirectoryId: Unique AWS Directory Service identifier
|
230
|
+
# - HomeRegion: Primary directory service region
|
231
|
+
# - Status: Operational status (Active, Creating, etc.)
|
232
|
+
# - Type: Directory type (SimpleAD, MicrosoftAD, etc.)
|
233
|
+
# - Owner: Directory ownership context
|
234
|
+
|
235
|
+
directory.update(
|
236
|
+
{
|
237
|
+
"MgmtAccount": credential["MgmtAccount"], # Management account context
|
238
|
+
"Region": credential["Region"], # Regional deployment information
|
239
|
+
"AccountId": credential["AccountId"], # Account ownership details
|
240
|
+
}
|
241
|
+
)
|
242
|
+
AllDirectories.append(directory)
|
243
|
+
|
244
|
+
except TypeError as my_Error:
|
245
|
+
# Handle type errors from malformed Directory Service API responses
|
246
|
+
logging.info(f"Error: {my_Error}")
|
247
|
+
progress.update(task, advance=1)
|
248
|
+
continue
|
249
|
+
except ClientError as my_Error:
|
250
|
+
# Handle AWS API authorization failures with informative logging
|
251
|
+
if "AuthFailure" in str(my_Error):
|
252
|
+
logging.error(f" Account {credential['AccountId']} : Authorization Failure")
|
253
|
+
|
254
|
+
# Update progress after processing each credential
|
255
|
+
progress.update(task, advance=1)
|
244
256
|
|
245
257
|
return AllDirectories
|
246
258
|
|
@@ -293,7 +305,7 @@ if __name__ == "__main__":
|
|
293
305
|
# Display credential retrieval timing for performance optimization
|
294
306
|
if pTiming:
|
295
307
|
print(
|
296
|
-
f"
|
308
|
+
f"[green]\tAfter getting credentials, this script took {time() - begin_time:.3f} seconds"
|
297
309
|
)
|
298
310
|
print()
|
299
311
|
|
@@ -304,7 +316,7 @@ if __name__ == "__main__":
|
|
304
316
|
# Display credential parsing timing for operational metrics
|
305
317
|
if pTiming:
|
306
318
|
print(
|
307
|
-
f"
|
319
|
+
f"[green]\tAfter parsing out all Regions, Account and Profiles, this script took {time() - begin_time:.3f} seconds"
|
308
320
|
)
|
309
321
|
print()
|
310
322
|
|
@@ -340,7 +352,7 @@ if __name__ == "__main__":
|
|
340
352
|
display_results(sorted_Results, display_dict, "None")
|
341
353
|
|
342
354
|
# Provide operational summary with discovery metrics and performance timing
|
343
|
-
print(
|
355
|
+
console.print()
|
344
356
|
print(
|
345
357
|
f"Found {len(all_directories)} directories across {len(AccountList)} accounts across {len(RegionList)} regions"
|
346
358
|
)
|
@@ -348,7 +360,7 @@ if __name__ == "__main__":
|
|
348
360
|
|
349
361
|
# Display total execution timing for performance analysis and optimization
|
350
362
|
if pTiming:
|
351
|
-
print(f"
|
363
|
+
print(f"[green]\tThis script took {time() - begin_time:.3f} seconds")
|
352
364
|
print()
|
353
365
|
print("Thank you for using this script")
|
354
366
|
print()
|
@@ -67,12 +67,10 @@ import sys
|
|
67
67
|
from time import time
|
68
68
|
|
69
69
|
from ArgumentsClass import CommonArguments
|
70
|
-
from
|
70
|
+
from runbooks.common.rich_utils import console
|
71
71
|
from Inventory_Modules import display_results, get_all_credentials, get_region_azs2
|
72
72
|
|
73
|
-
init()
|
74
73
|
__version__ = "2024.03.06"
|
75
|
-
ERASE_LINE = "\x1b[2K"
|
76
74
|
begin_time = time()
|
77
75
|
|
78
76
|
|
@@ -281,6 +279,6 @@ if __name__ == "__main__":
|
|
281
279
|
|
282
280
|
print()
|
283
281
|
if pTiming:
|
284
|
-
print(f"
|
282
|
+
print(f"[green]This script took {time() - begin_time:.2f} seconds")
|
285
283
|
print("Thanks for using this script")
|
286
284
|
print()
|