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