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
@@ -8,10 +8,9 @@ import Inventory_Modules
|
|
8
8
|
from account_class import aws_acct_access
|
9
9
|
from ArgumentsClass import CommonArguments
|
10
10
|
from botocore.exceptions import ClientError
|
11
|
-
from
|
12
|
-
from
|
11
|
+
from runbooks.common.rich_utils import console, print_success, print_error, print_info
|
12
|
+
from runbooks.common.rich_utils import console, create_table
|
13
13
|
|
14
|
-
init()
|
15
14
|
__version__ = "2023.05.04"
|
16
15
|
|
17
16
|
parser = CommonArguments()
|
@@ -109,7 +108,6 @@ else:
|
|
109
108
|
"us-west-2",
|
110
109
|
]
|
111
110
|
|
112
|
-
ERASE_LINE = "\x1b[2K"
|
113
111
|
|
114
112
|
ExplainMessage = """
|
115
113
|
|
@@ -175,37 +173,37 @@ but that's sometime in the future.
|
|
175
173
|
|
176
174
|
print("This script does 6 things... ")
|
177
175
|
print(
|
178
|
-
f"
|
176
|
+
f"[blue] 0. Checks to ensure you have the necessary cross-account role access to the child account."
|
179
177
|
)
|
180
|
-
print(f"
|
178
|
+
print(f"[blue] 1. Checks to ensure the [red]Default VPCs in each region are deleted")
|
181
179
|
if FixRun and not pVPCConfirm:
|
182
180
|
print(
|
183
|
-
f"
|
181
|
+
f"[blue] You've asked to delete any default VPCs we find - with confirmation on each one."
|
184
182
|
)
|
185
183
|
elif FixRun and pVPCConfirm:
|
186
184
|
print()
|
187
185
|
print(
|
188
|
-
f"
|
186
|
+
f"[red] You've asked to delete any default VPCs we find - WITH NO CONFIRMATION on each one."
|
189
187
|
)
|
190
188
|
print()
|
191
189
|
elif pVPCConfirm and not FixRun:
|
192
190
|
print()
|
193
191
|
print(
|
194
|
-
f"
|
192
|
+
f"[blue] You asked us to delete the default VPCs with no confirmation, but didn't provide the '+fixrun' parameter, so we're proceeding with NOT deleting. You can safely interupt this script and run it again with the necessary parameters."
|
195
193
|
)
|
196
194
|
print()
|
197
|
-
print(f"
|
198
|
-
print(f" to see if there's already a
|
195
|
+
print(f"[blue] 2. Checks the child account in each of the regions")
|
196
|
+
print(f" to see if there's already a [red]Config Recorder and Delivery Channel enabled...")
|
199
197
|
print(
|
200
|
-
f"
|
198
|
+
f"[blue] 3. Checks that there isn't a duplicate [red]CloudTrail trail in the account."
|
201
199
|
)
|
202
200
|
print(
|
203
|
-
f"
|
201
|
+
f"[blue] 4. Checks to see if [red]GuardDuty has been enabled for this child account."
|
204
202
|
)
|
205
203
|
print(" If it has been, it needs to be deleted before we can adopt this new account")
|
206
204
|
print(" into the Org's Automated Landing Zone.")
|
207
205
|
print(
|
208
|
-
f"
|
206
|
+
f"[blue] 5. This child account [red]must exist within the Parent Organization."
|
209
207
|
)
|
210
208
|
print(" If it doesn't - then you must move it into this Org")
|
211
209
|
print(" (this script can't do that for you).")
|
@@ -277,10 +275,10 @@ for childaccount in ChildAccountList:
|
|
277
275
|
finally:
|
278
276
|
if not account_credentials.get("Success", False):
|
279
277
|
logging.error(
|
280
|
-
f"Was
|
278
|
+
f"Was [red]not able to successfully connect to account {childaccount} using credentials from account {aws_account.acct_number}... "
|
281
279
|
)
|
282
280
|
print()
|
283
|
-
print(f"
|
281
|
+
print(f"[red]** Step 0 failed for account {childaccount}")
|
284
282
|
print()
|
285
283
|
ProcessStatus[childaccount]["Step0"]["Success"] = False
|
286
284
|
ProcessStatus[childaccount]["Step0"]["IssuesFound"] += 1
|
@@ -290,7 +288,7 @@ for childaccount in ChildAccountList:
|
|
290
288
|
f"Was able to successfully connect to account {childaccount} using credentials from account {aws_account.acct_number}... "
|
291
289
|
)
|
292
290
|
print()
|
293
|
-
print(f"
|
291
|
+
print(f"[green]** Step 0 completed without issues")
|
294
292
|
print()
|
295
293
|
ProcessStatus[childaccount]["Step0"]["Success"] = True
|
296
294
|
|
@@ -305,7 +303,7 @@ for childaccount in ChildAccountList:
|
|
305
303
|
for region in RegionList:
|
306
304
|
print(
|
307
305
|
ERASE_LINE,
|
308
|
-
f"Checking account {childaccount} in region {region} for
|
306
|
+
f"Checking account {childaccount} in region {region} for [red]default VPCs",
|
309
307
|
end="\r",
|
310
308
|
)
|
311
309
|
logging.info("Looking for Default VPCs in account {} from Region {}}", childaccount, region)
|
@@ -364,18 +362,18 @@ for childaccount in ChildAccountList:
|
|
364
362
|
|
365
363
|
print()
|
366
364
|
if ProcessStatus[childaccount]["Step1"]["Success"]:
|
367
|
-
print(f"{ERASE_LINE + Fore.GREEN}** Step 1 completed with no issues
|
365
|
+
print(f"{ERASE_LINE + Fore.GREEN}** Step 1 completed with no issues")
|
368
366
|
elif ProcessStatus[childaccount]["Step1"]["IssuesFound"] - ProcessStatus[childaccount]["Step1"]["IssuesFixed"] == 0:
|
369
367
|
print(
|
370
|
-
f"{ERASE_LINE + Fore.GREEN}** Step 1 found {ProcessStatus[childaccount]['Step1']['IssuesFound']} issues, but they were fixed by deleting the default vpcs
|
368
|
+
f"{ERASE_LINE + Fore.GREEN}** Step 1 found {ProcessStatus[childaccount]['Step1']['IssuesFound']} issues, but they were fixed by deleting the default vpcs"
|
371
369
|
)
|
372
370
|
ProcessStatus[childaccount]["Step1"]["Success"] = True
|
373
371
|
elif ProcessStatus[childaccount]["Step1"]["IssuesFound"] > ProcessStatus[childaccount]["Step1"]["IssuesFixed"]:
|
374
372
|
print(
|
375
|
-
f"{ERASE_LINE + Fore.RED}** Step 1 completed, but there were {ProcessStatus[childaccount]['Step1']['IssuesFound'] - ProcessStatus[childaccount]['Step1']['IssuesFixed']} vpcs that couldn't be fixed
|
373
|
+
f"{ERASE_LINE + Fore.RED}** Step 1 completed, but there were {ProcessStatus[childaccount]['Step1']['IssuesFound'] - ProcessStatus[childaccount]['Step1']['IssuesFixed']} vpcs that couldn't be fixed"
|
376
374
|
)
|
377
375
|
else:
|
378
|
-
print(f"{ERASE_LINE + Fore.RED}** Step 1 completed with blockers found
|
376
|
+
print(f"{ERASE_LINE + Fore.RED}** Step 1 completed with blockers found")
|
379
377
|
|
380
378
|
# Step 2
|
381
379
|
# This part will check the Config Recorder and Delivery Channel. If they have one, we need to delete it, so we can create another. We'll ask whether this is ok before we delete.
|
@@ -422,7 +420,7 @@ for childaccount in ChildAccountList:
|
|
422
420
|
|
423
421
|
for i in range(len(ConfigList)):
|
424
422
|
logging.error(
|
425
|
-
f"
|
423
|
+
f"[red]Found a config recorder for account %s in region %s",
|
426
424
|
ConfigList[i]["AccountID"],
|
427
425
|
ConfigList[i]["Region"] + Fore.RESET,
|
428
426
|
)
|
@@ -442,7 +440,7 @@ for childaccount in ChildAccountList:
|
|
442
440
|
ProcessStatus[childaccount]["Step2"]["IssuesFixed"] += 1
|
443
441
|
for i in range(len(DeliveryChanList)):
|
444
442
|
logging.error(
|
445
|
-
f"
|
443
|
+
f"[red]I found a delivery channel for account %s in region %s",
|
446
444
|
DeliveryChanList[i]["AccountID"],
|
447
445
|
DeliveryChanList[i]["Region"] + Fore.RESET,
|
448
446
|
)
|
@@ -466,18 +464,18 @@ for childaccount in ChildAccountList:
|
|
466
464
|
ProcessStatus[childaccount]["Step2"]["Success"] = True
|
467
465
|
|
468
466
|
if ProcessStatus[childaccount]["Step2"]["Success"]:
|
469
|
-
print(f"{ERASE_LINE + Fore.GREEN}** Step 2 completed with no issues
|
467
|
+
print(f"{ERASE_LINE + Fore.GREEN}** Step 2 completed with no issues")
|
470
468
|
elif ProcessStatus[childaccount]["Step2"]["IssuesFound"] - ProcessStatus[childaccount]["Step2"]["IssuesFixed"] == 0:
|
471
469
|
print(
|
472
|
-
f"{ERASE_LINE + Fore.GREEN}** Step 2 found {ProcessStatus[childaccount]['Step2']['IssuesFound']} issues, but they were fixed by deleting the existing Config Recorders and Delivery Channels
|
470
|
+
f"{ERASE_LINE + Fore.GREEN}** Step 2 found {ProcessStatus[childaccount]['Step2']['IssuesFound']} issues, but they were fixed by deleting the existing Config Recorders and Delivery Channels"
|
473
471
|
)
|
474
472
|
ProcessStatus[childaccount]["Step2"]["Success"] = True
|
475
473
|
elif ProcessStatus[childaccount]["Step2"]["IssuesFound"] > ProcessStatus[childaccount]["Step2"]["IssuesFixed"]:
|
476
474
|
print(
|
477
|
-
f"{ERASE_LINE + Fore.RED}** Step 2 completed, but there were {ProcessStatus[childaccount]['Step2']['IssuesFound'] - ProcessStatus[childaccount]['Step2']['IssuesFixed']} items found that couldn't be deleted
|
475
|
+
f"{ERASE_LINE + Fore.RED}** Step 2 completed, but there were {ProcessStatus[childaccount]['Step2']['IssuesFound'] - ProcessStatus[childaccount]['Step2']['IssuesFixed']} items found that couldn't be deleted"
|
478
476
|
)
|
479
477
|
else:
|
480
|
-
print(f"{ERASE_LINE + Fore.RED}** Step 2 completed with blockers found
|
478
|
+
print(f"{ERASE_LINE + Fore.RED}** Step 2 completed with blockers found")
|
481
479
|
print()
|
482
480
|
|
483
481
|
# Step 3
|
@@ -505,7 +503,7 @@ for childaccount in ChildAccountList:
|
|
505
503
|
|
506
504
|
for i in range(len(CTtrails2)):
|
507
505
|
logging.error(
|
508
|
-
f"
|
506
|
+
f"[red]Found a CloudTrail trail for account {childaccount} in region {CTtrails2[i]['HomeRegion']} named {CTtrails2[i]['Name']}"
|
509
507
|
)
|
510
508
|
ProcessStatus[childaccount]["Step3"]["IssuesFound"] += 1
|
511
509
|
if FixRun:
|
@@ -523,18 +521,18 @@ for childaccount in ChildAccountList:
|
|
523
521
|
ProcessStatus[childaccount]["Step3"]["Success"] = True
|
524
522
|
|
525
523
|
if ProcessStatus[childaccount]["Step3"]["Success"]:
|
526
|
-
print(f"{ERASE_LINE + Fore.GREEN}** Step 3 completed with no issues
|
524
|
+
print(f"{ERASE_LINE + Fore.GREEN}** Step 3 completed with no issues")
|
527
525
|
elif ProcessStatus[childaccount]["Step3"]["IssuesFound"] - ProcessStatus[childaccount]["Step3"]["IssuesFixed"] == 0:
|
528
526
|
print(
|
529
|
-
f"{ERASE_LINE + Fore.GREEN}** Step 3 found {ProcessStatus[childaccount]['Step3']['IssuesFound']} issues, but they were fixed by deleting the existing CloudTrail trail names
|
527
|
+
f"{ERASE_LINE + Fore.GREEN}** Step 3 found {ProcessStatus[childaccount]['Step3']['IssuesFound']} issues, but they were fixed by deleting the existing CloudTrail trail names"
|
530
528
|
)
|
531
529
|
ProcessStatus[childaccount]["Step3"]["Success"] = True
|
532
530
|
elif ProcessStatus[childaccount]["Step3"]["IssuesFound"] > ProcessStatus[childaccount]["Step3"]["IssuesFixed"]:
|
533
531
|
print(
|
534
|
-
f"{ERASE_LINE + Fore.RED}** Step 3 completed, but there were {ProcessStatus[childaccount]['Step3']['IssuesFound'] - ProcessStatus[childaccount]['Step3']['IssuesFixed']} trail names found that couldn't be deleted
|
532
|
+
f"{ERASE_LINE + Fore.RED}** Step 3 completed, but there were {ProcessStatus[childaccount]['Step3']['IssuesFound'] - ProcessStatus[childaccount]['Step3']['IssuesFixed']} trail names found that couldn't be deleted"
|
535
533
|
)
|
536
534
|
else:
|
537
|
-
print(f"{ERASE_LINE + Fore.RED}** Step 3 completed with blockers found
|
535
|
+
print(f"{ERASE_LINE + Fore.RED}** Step 3 completed with blockers found")
|
538
536
|
print()
|
539
537
|
|
540
538
|
# Step 4
|
@@ -545,7 +543,7 @@ for childaccount in ChildAccountList:
|
|
545
543
|
print(f"Checking account {childaccount} for any GuardDuty invites")
|
546
544
|
for region in RegionList:
|
547
545
|
print(
|
548
|
-
f"{ERASE_LINE}Checking account {childaccount} in region {region} for
|
546
|
+
f"{ERASE_LINE}Checking account {childaccount} in region {region} for [red]GuardDutyinvitations",
|
549
547
|
end="\r",
|
550
548
|
)
|
551
549
|
GDinvites = Inventory_Modules.find_gd_invites2(account_credentials, region)
|
@@ -570,7 +568,7 @@ for childaccount in ChildAccountList:
|
|
570
568
|
|
571
569
|
for i in range(len(GDinvites2)):
|
572
570
|
logging.error(
|
573
|
-
f"
|
571
|
+
f"[red]I found a GuardDuty invitation for account %s in region %s from account %s ",
|
574
572
|
childaccount,
|
575
573
|
GDinvites2[i]["Region"],
|
576
574
|
GDinvites2[i]["AccountId"] + Fore.RESET,
|
@@ -593,18 +591,18 @@ for childaccount in ChildAccountList:
|
|
593
591
|
ProcessStatus[childaccount]["Step4"]["Success"] = True
|
594
592
|
|
595
593
|
if ProcessStatus[childaccount]["Step4"]["Success"]:
|
596
|
-
print(f"{ERASE_LINE + Fore.GREEN}** Step 4 completed with no issues
|
594
|
+
print(f"{ERASE_LINE + Fore.GREEN}** Step 4 completed with no issues")
|
597
595
|
elif ProcessStatus[childaccount]["Step4"]["IssuesFound"] - ProcessStatus[childaccount]["Step4"]["IssuesFixed"] == 0:
|
598
596
|
print(
|
599
|
-
f"{ERASE_LINE + Fore.GREEN}** Step 4 found {ProcessStatus[childaccount]['Step4']['IssuesFound']} guardduty invites, but they were deleted
|
597
|
+
f"{ERASE_LINE + Fore.GREEN}** Step 4 found {ProcessStatus[childaccount]['Step4']['IssuesFound']} guardduty invites, but they were deleted"
|
600
598
|
)
|
601
599
|
ProcessStatus[childaccount]["Step4"]["Success"] = True
|
602
600
|
elif ProcessStatus[childaccount]["Step4"]["IssuesFound"] > ProcessStatus[childaccount]["Step4"]["IssuesFixed"]:
|
603
601
|
print(
|
604
|
-
f"{ERASE_LINE + Fore.RED}** Step 4 completed, but there were {ProcessStatus[childaccount]['Step4']['IssuesFound'] - ProcessStatus[childaccount]['Step4']['IssuesFixed']} guardduty invites found that couldn't be deleted
|
602
|
+
f"{ERASE_LINE + Fore.RED}** Step 4 completed, but there were {ProcessStatus[childaccount]['Step4']['IssuesFound'] - ProcessStatus[childaccount]['Step4']['IssuesFixed']} guardduty invites found that couldn't be deleted"
|
605
603
|
)
|
606
604
|
else:
|
607
|
-
print(f"{ERASE_LINE + Fore.RED}** Step 4 completed with blockers found
|
605
|
+
print(f"{ERASE_LINE + Fore.RED}** Step 4 completed with blockers found")
|
608
606
|
print()
|
609
607
|
|
610
608
|
"""
|
@@ -631,21 +629,21 @@ for childaccount in ChildAccountList:
|
|
631
629
|
ProcessStatus[childaccount]["Step5"]["IssuesFound"] += 1
|
632
630
|
|
633
631
|
if ProcessStatus[childaccount]["Step5"]["Success"]:
|
634
|
-
print(f"{ERASE_LINE + Fore.GREEN}** Step 5 completed with no issues
|
632
|
+
print(f"{ERASE_LINE + Fore.GREEN}** Step 5 completed with no issues")
|
635
633
|
elif ProcessStatus[childaccount]["Step5"]["IssuesFound"] - ProcessStatus[childaccount]["Step5"]["IssuesFixed"] == 0:
|
636
634
|
print(
|
637
|
-
f"{ERASE_LINE + Fore.GREEN}** Step 5 found {ProcessStatus[childaccount]['Step5']['IssuesFound']} issues, but we were able to move the account into the they were able to be fixed
|
635
|
+
f"{ERASE_LINE + Fore.GREEN}** Step 5 found {ProcessStatus[childaccount]['Step5']['IssuesFound']} issues, but we were able to move the account into the they were able to be fixed"
|
638
636
|
)
|
639
637
|
ProcessStatus[childaccount]["Step5"]["Success"] = True
|
640
638
|
elif ProcessStatus[childaccount]["Step5"]["IssuesFound"] > ProcessStatus[childaccount]["Step5"]["IssuesFixed"]:
|
641
639
|
print(
|
642
|
-
f"{ERASE_LINE + Fore.RED}** Step 5 completed, but there were {ProcessStatus[childaccount]['Step5']['IssuesFound'] - ProcessStatus[childaccount]['Step5']['IssuesFixed']} blockers found that couldn't be fixed
|
640
|
+
f"{ERASE_LINE + Fore.RED}** Step 5 completed, but there were {ProcessStatus[childaccount]['Step5']['IssuesFound'] - ProcessStatus[childaccount]['Step5']['IssuesFixed']} blockers found that couldn't be fixed"
|
643
641
|
)
|
644
642
|
else:
|
645
|
-
print(f"{ERASE_LINE + Fore.RED}** Step 5 completed with blockers found
|
643
|
+
print(f"{ERASE_LINE + Fore.RED}** Step 5 completed with blockers found")
|
646
644
|
print()
|
647
645
|
|
648
|
-
print(f"
|
646
|
+
print(f"[cyan]Account {childaccount} is complete. {accountsleft} more to go!!")
|
649
647
|
|
650
648
|
"""
|
651
649
|
# Step 6
|
@@ -653,59 +651,62 @@ for childaccount in ChildAccountList:
|
|
653
651
|
So we'll need to verify that the parent OU of the account is the root of the organization.
|
654
652
|
"""
|
655
653
|
|
656
|
-
x =
|
657
|
-
|
658
|
-
|
659
|
-
|
660
|
-
|
661
|
-
|
662
|
-
|
663
|
-
|
664
|
-
|
665
|
-
|
666
|
-
"
|
667
|
-
|
668
|
-
|
669
|
-
|
670
|
-
|
654
|
+
x = create_table(
|
655
|
+
title="Account Readiness Summary",
|
656
|
+
columns=[
|
657
|
+
{"header": "Account", "justify": "left"},
|
658
|
+
{"header": "Issues Found", "justify": "center"},
|
659
|
+
{"header": "Issues Fixed", "justify": "center"},
|
660
|
+
{"header": "Ready?", "justify": "center"},
|
661
|
+
]
|
662
|
+
)
|
663
|
+
y = create_table(
|
664
|
+
title="Account Issue Details",
|
665
|
+
columns=[
|
666
|
+
{"header": "Account", "justify": "left"},
|
667
|
+
{"header": "Account Access", "justify": "center"},
|
668
|
+
{"header": "Default VPCs", "justify": "center"},
|
669
|
+
{"header": "Recorders", "justify": "center"},
|
670
|
+
{"header": "CloudTrail", "justify": "center"},
|
671
|
+
{"header": "GuardDuty", "justify": "center"},
|
672
|
+
{"header": "Org Member", "justify": "center"},
|
673
|
+
{"header": "Ready?", "justify": "center"},
|
674
|
+
]
|
675
|
+
)
|
671
676
|
for item in ProcessStatus:
|
672
677
|
for _ in range(Steps):
|
673
678
|
Step = f"Step{str(_)}"
|
674
679
|
ProcessStatus[item]["IssuesFound"] += ProcessStatus[item][Step]["IssuesFound"]
|
675
680
|
ProcessStatus[item]["IssuesFound"] += ProcessStatus[item][Step]["IssuesFixed"]
|
676
681
|
x.add_row(
|
677
|
-
|
678
|
-
|
679
|
-
|
680
|
-
|
681
|
-
ProcessStatus[item]["ChildIsReady"],
|
682
|
-
]
|
682
|
+
item,
|
683
|
+
str(ProcessStatus[item]["IssuesFound"]),
|
684
|
+
str(ProcessStatus[item]["IssuesFixed"]),
|
685
|
+
str(ProcessStatus[item]["ChildIsReady"]),
|
683
686
|
)
|
684
687
|
y.add_row(
|
685
|
-
|
686
|
-
|
687
|
-
|
688
|
-
|
689
|
-
|
690
|
-
|
691
|
-
|
692
|
-
|
693
|
-
ProcessStatus[item]["Step0"]["Success"]
|
688
|
+
item,
|
689
|
+
str(ProcessStatus[item]["Step0"]["IssuesFound"] - ProcessStatus[item]["Step0"]["IssuesFixed"]),
|
690
|
+
str(ProcessStatus[item]["Step1"]["IssuesFound"] - ProcessStatus[item]["Step1"]["IssuesFixed"]),
|
691
|
+
str(ProcessStatus[item]["Step2"]["IssuesFound"] - ProcessStatus[item]["Step2"]["IssuesFixed"]),
|
692
|
+
str(ProcessStatus[item]["Step3"]["IssuesFound"] - ProcessStatus[item]["Step3"]["IssuesFixed"]),
|
693
|
+
str(ProcessStatus[item]["Step4"]["IssuesFound"] - ProcessStatus[item]["Step4"]["IssuesFixed"]),
|
694
|
+
str(ProcessStatus[item]["Step5"]["IssuesFound"] - ProcessStatus[item]["Step5"]["IssuesFixed"]),
|
695
|
+
str(ProcessStatus[item]["Step0"]["Success"]
|
694
696
|
and ProcessStatus[item]["Step1"]["Success"]
|
695
697
|
and ProcessStatus[item]["Step2"]["Success"]
|
696
698
|
and ProcessStatus[item]["Step3"]["Success"]
|
697
699
|
and ProcessStatus[item]["Step4"]["Success"]
|
698
|
-
and ProcessStatus[item]["Step5"]["Success"],
|
699
|
-
]
|
700
|
+
and ProcessStatus[item]["Step5"]["Success"]),
|
700
701
|
)
|
701
|
-
print(
|
702
|
-
"The following table represents the accounts looked at, and whether they are ready to be incorporated into an ALZ environment."
|
702
|
+
console.print(
|
703
|
+
"\n[bold cyan]The following table represents the accounts looked at, and whether they are ready to be incorporated into an ALZ environment.[/bold cyan]"
|
703
704
|
)
|
704
|
-
print(x)
|
705
|
-
print()
|
706
|
-
print(
|
707
|
-
"The following table represents the accounts looked at, and gives details under each type of issue as to what might prevent a successful migration of this account into an ALZ environment."
|
705
|
+
console.print(x)
|
706
|
+
console.print()
|
707
|
+
console.print(
|
708
|
+
"[bold cyan]The following table represents the accounts looked at, and gives details under each type of issue as to what might prevent a successful migration of this account into an ALZ environment.[/bold cyan]"
|
708
709
|
)
|
709
|
-
print(y)
|
710
|
+
console.print(y)
|
710
711
|
|
711
|
-
print("Thanks for using this script...")
|
712
|
+
console.print("\n[green]Thanks for using this script...[/green]")
|
@@ -203,6 +203,17 @@ class InventoryFormatter(CloudFoundationsFormatter):
|
|
203
203
|
|
204
204
|
def format_console_table(self) -> str:
|
205
205
|
"""Format inventory data for console display."""
|
206
|
+
import os
|
207
|
+
import sys
|
208
|
+
import re
|
209
|
+
|
210
|
+
# Test Mode Support: Disable Rich Console in test environments to prevent I/O conflicts
|
211
|
+
USE_RICH = os.getenv("RUNBOOKS_TEST_MODE") != "1"
|
212
|
+
|
213
|
+
if not USE_RICH:
|
214
|
+
# Test mode: Use simple text formatting to prevent Click CliRunner I/O conflicts
|
215
|
+
return self._format_simple_text_table()
|
216
|
+
|
206
217
|
try:
|
207
218
|
from rich.console import Console
|
208
219
|
from rich.table import Table
|
@@ -58,7 +58,7 @@ from typing import Any, Dict, List, Optional
|
|
58
58
|
|
59
59
|
import boto3
|
60
60
|
from ArgumentsClass import CommonArguments
|
61
|
-
from
|
61
|
+
from runbooks.common.rich_utils import console
|
62
62
|
from graphviz import Digraph
|
63
63
|
|
64
64
|
# Optional imports for enhanced features
|
@@ -74,7 +74,6 @@ except ImportError:
|
|
74
74
|
|
75
75
|
__version__ = "2025.04.09"
|
76
76
|
|
77
|
-
init()
|
78
77
|
|
79
78
|
# Visual styling constants
|
80
79
|
account_fillcolor = "orange"
|
@@ -379,7 +378,7 @@ def generate_mermaid(org_structure: Any, filename: str) -> None:
|
|
379
378
|
try:
|
380
379
|
with open(filename, "w", encoding="utf-8") as f:
|
381
380
|
f.write("\n".join(lines))
|
382
|
-
print(f"Mermaid diagram successfully saved to '{
|
381
|
+
print(f"Mermaid diagram successfully saved to '[red]{filename}'")
|
383
382
|
logging.info(f"Mermaid diagram successfully saved to {filename}")
|
384
383
|
except Exception as e:
|
385
384
|
logging.error(f"Failed to write Mermaid diagram to {filename}: {e}")
|
@@ -412,7 +411,7 @@ def generate_diagrams(org_structure: Any, filename: str) -> None:
|
|
412
411
|
)
|
413
412
|
except ImportError as imp_err:
|
414
413
|
logging.error("Please install the 'diagrams' package to use generate_diagrams: pip install diagrams")
|
415
|
-
print(f"
|
414
|
+
print(f"[yellow]Warning: diagrams library not available. Install with: pip install diagrams")
|
416
415
|
return
|
417
416
|
|
418
417
|
def build_diagram(node: Dict[str, Any]):
|
@@ -434,7 +433,7 @@ def generate_diagrams(org_structure: Any, filename: str) -> None:
|
|
434
433
|
try:
|
435
434
|
with Diagram("AWS Organization Diagram", filename=filename, show=False, direction="LR"):
|
436
435
|
build_diagram(org_structure)
|
437
|
-
print(f"Diagrams image successfully generated as '{
|
436
|
+
print(f"Diagrams image successfully generated as '[red]{filename}'")
|
438
437
|
logging.info(f"Diagrams image successfully generated as {filename}")
|
439
438
|
except Exception as e:
|
440
439
|
logging.error(f"Failed to generate diagrams image: {e}")
|
@@ -636,7 +635,7 @@ def draw_org(froot: str, filename: str):
|
|
636
635
|
|
637
636
|
# Save the diagram to a PNG file
|
638
637
|
dot_unflat.render(filename, format="png", view=False)
|
639
|
-
print(f"Diagram saved to '{
|
638
|
+
print(f"Diagram saved to '[red]{filename}.png'")
|
640
639
|
|
641
640
|
|
642
641
|
#####################
|
@@ -708,7 +707,7 @@ if __name__ == "__main__":
|
|
708
707
|
if anticipated_time > 30:
|
709
708
|
print()
|
710
709
|
print(
|
711
|
-
f"
|
710
|
+
f"[red]Since this may take a while, you could re-run this script for only a specific OU by using the '--ou <OU ID>' parameter "
|
712
711
|
)
|
713
712
|
print()
|
714
713
|
else:
|
@@ -737,11 +736,11 @@ if __name__ == "__main__":
|
|
737
736
|
# Display timing information
|
738
737
|
if pTiming and pPolicy:
|
739
738
|
print(
|
740
|
-
f"
|
739
|
+
f"[green]Drawing the Org structure when policies are included took {time() - begin_time:.2f} seconds"
|
741
740
|
)
|
742
741
|
elif pTiming:
|
743
742
|
print(
|
744
|
-
f"
|
743
|
+
f"[green]Drawing the Org structure without policies took {time() - begin_time:.2f} seconds"
|
745
744
|
)
|
746
745
|
|
747
746
|
print("Thank you for using this script")
|
@@ -14,7 +14,7 @@ def del_vpc(ocredentials, fVPCId, fRegion):
|
|
14
14
|
|
15
15
|
import boto3
|
16
16
|
from botocore.exceptions import ClientError
|
17
|
-
from
|
17
|
+
from runbooks.common.rich_utils import console
|
18
18
|
|
19
19
|
# ERASE_LINE = '\x1b[2K'
|
20
20
|
|
@@ -335,7 +335,7 @@ def del_vpc(ocredentials, fVPCId, fRegion):
|
|
335
335
|
return 1 # out of the try
|
336
336
|
except ClientError as my_Error:
|
337
337
|
print(my_Error)
|
338
|
-
print(f"
|
338
|
+
print(f"[red]What to do now?")
|
339
339
|
return 1
|
340
340
|
|
341
341
|
return 0
|
@@ -70,9 +70,8 @@ import Inventory_Modules
|
|
70
70
|
from account_class import aws_acct_access
|
71
71
|
from ArgumentsClass import CommonArguments
|
72
72
|
from botocore.exceptions import ClientError
|
73
|
-
from
|
73
|
+
from runbooks.common.rich_utils import console
|
74
74
|
|
75
|
-
init()
|
76
75
|
__version__ = "2023.05.04"
|
77
76
|
|
78
77
|
# Configure comprehensive argument parsing for CloudFormation drift detection operations
|
@@ -142,7 +141,6 @@ This would enable:
|
|
142
141
|
"""
|
143
142
|
|
144
143
|
##########################
|
145
|
-
ERASE_LINE = "\x1b[2K" # Terminal control for dynamic output updates
|
146
144
|
|
147
145
|
# Initialize AWS account access and organizational context for drift detection
|
148
146
|
aws_acct = aws_acct_access(pProfile)
|
@@ -235,7 +233,7 @@ for account in ChildAccounts:
|
|
235
233
|
# Log regional stack discovery progress for operational visibility
|
236
234
|
logging.warning(f"Account: {account['AccountId']} | Region: {region} | Found {StackNum} Stacks")
|
237
235
|
logging.info(
|
238
|
-
f"
|
236
|
+
f"[red]Account: {account['AccountId']} Region: {region} Found {StackNum} Stacks"
|
239
237
|
)
|
240
238
|
|
241
239
|
except ClientError as my_Error:
|
@@ -260,10 +258,10 @@ for account in ChildAccounts:
|
|
260
258
|
NumStacksFound += 1 # Increment total drift detection counter
|
261
259
|
|
262
260
|
# Provide comprehensive operational summary with drift detection metrics
|
263
|
-
print(
|
261
|
+
console.print()
|
264
262
|
print(
|
265
|
-
f"
|
266
|
-
f"{len(RegionList)} regions
|
263
|
+
f"[red]Looked through {NumStacksFound} Stacks across {len(ChildAccounts)} accounts across "
|
264
|
+
f"{len(RegionList)} regions"
|
267
265
|
)
|
268
266
|
print()
|
269
267
|
|
@@ -76,7 +76,7 @@ from ArgumentsClass import CommonArguments
|
|
76
76
|
|
77
77
|
# import simplejson as json
|
78
78
|
from botocore.exceptions import ClientError
|
79
|
-
from
|
79
|
+
from runbooks.common.rich_utils import console
|
80
80
|
from Inventory_Modules import (
|
81
81
|
display_results,
|
82
82
|
find_stack_instances3,
|
@@ -87,9 +87,7 @@ from Inventory_Modules import (
|
|
87
87
|
print_timings,
|
88
88
|
)
|
89
89
|
|
90
|
-
init()
|
91
90
|
__version__ = "2024.05.18"
|
92
|
-
ERASE_LINE = "\x1b[2K"
|
93
91
|
begin_time = time()
|
94
92
|
|
95
93
|
|
@@ -250,7 +248,7 @@ def setup_auth_and_regions(fProfile: str, f_AccountList: list, f_Region: str, f_
|
|
250
248
|
pass # Valid profile specification (string or None for default)
|
251
249
|
else: # Invalid profile type (list, integer, etc.) - should be caught at argparse level
|
252
250
|
print(
|
253
|
-
f"
|
251
|
+
f"[red]You specified an invalid profile name. This script only allows for one profile at a time. Please try again."
|
254
252
|
)
|
255
253
|
sys.exit(7)
|
256
254
|
|
@@ -268,8 +266,8 @@ def setup_auth_and_regions(fProfile: str, f_AccountList: list, f_Region: str, f_
|
|
268
266
|
# Validate home region specification ensuring single region for StackSet control plane
|
269
267
|
if f_Region.lower() not in AllRegions:
|
270
268
|
print(
|
271
|
-
f"
|
272
|
-
f"Please run the command again and specify only a single, valid region
|
269
|
+
f"[red]You specified '{f_Region}' as the region, but this script only works with a single region.\n"
|
270
|
+
f"Please run the command again and specify only a single, valid region"
|
273
271
|
)
|
274
272
|
sys.exit(9)
|
275
273
|
|
@@ -309,12 +307,12 @@ def setup_auth_and_regions(fProfile: str, f_AccountList: list, f_Region: str, f_
|
|
309
307
|
|
310
308
|
# Display account exclusion list if specified
|
311
309
|
print(
|
312
|
-
f"While skipping these accounts:\n{
|
310
|
+
f"While skipping these accounts:\n[red]{f_args.SkipAccounts}"
|
313
311
|
) if f_args.SkipAccounts is not None else ""
|
314
312
|
|
315
313
|
# Display fragment filtering configuration with exact vs substring matching
|
316
314
|
if f_args.Exact:
|
317
|
-
print(f"\t\tFor stacksets that
|
315
|
+
print(f"\t\tFor stacksets that [red]exactly match: {f_args.Fragments}")
|
318
316
|
else:
|
319
317
|
print(
|
320
318
|
f"\t\tFor stacksets that contain th{'is fragment' if len(f_args.Fragments) == 1 else 'ese fragments'}: {f_args.Fragments}"
|
@@ -712,7 +710,7 @@ if __name__ == "__main__":
|
|
712
710
|
|
713
711
|
if pTiming:
|
714
712
|
print(ERASE_LINE)
|
715
|
-
print(f"
|
713
|
+
print(f"[green]This script took {time() - begin_time:.2f} seconds")
|
716
714
|
|
717
715
|
print()
|
718
716
|
print("Thanks for using this script...")
|
@@ -70,11 +70,10 @@ import Inventory_Modules
|
|
70
70
|
from account_class import aws_acct_access
|
71
71
|
from ArgumentsClass import CommonArguments
|
72
72
|
from botocore.exceptions import ClientError
|
73
|
-
from
|
73
|
+
from runbooks.common.rich_utils import console
|
74
74
|
from Inventory_Modules import display_results
|
75
75
|
|
76
76
|
# Initialize colorama for cross-platform colored terminal output
|
77
|
-
init()
|
78
77
|
__version__ = "2024.03.06"
|
79
78
|
begin_time = time() # Script execution timing for performance monitoring
|
80
79
|
sleep_interval = 5 # Default wait interval for drift detection operations
|
@@ -248,7 +247,7 @@ def setup_auth(fProfile: str) -> aws_acct_access:
|
|
248
247
|
|
249
248
|
# Validate successful authentication and account access
|
250
249
|
if not aws_acct.Success:
|
251
|
-
print(f"
|
250
|
+
print(f"[red]Profile {pProfile} failed to access an account. Check credentials and try again")
|
252
251
|
sys.exit(99)
|
253
252
|
|
254
253
|
# Display comprehensive operational context for user confirmation and audit logging
|
@@ -259,7 +258,7 @@ def setup_auth(fProfile: str) -> aws_acct_access:
|
|
259
258
|
|
260
259
|
# Display fragment filtering criteria with exact vs substring matching context
|
261
260
|
if pExact:
|
262
|
-
print(f"\t\tFor stacksets that
|
261
|
+
print(f"\t\tFor stacksets that [red]exactly match: {pFragments}")
|
263
262
|
else:
|
264
263
|
print(
|
265
264
|
f"\t\tFor stacksets that contain th{'is fragment' if len(pFragments) == 1 else 'ese fragments'}: {pFragments}"
|
@@ -723,11 +722,11 @@ if __name__ == "__main__":
|
|
723
722
|
display_results(sorted_all_stacksets, display_dict_stacksets, None, pSaveFilename)
|
724
723
|
|
725
724
|
print(ERASE_LINE)
|
726
|
-
print(f"
|
725
|
+
print(f"[red]Looked through {len(sorted_all_stacksets)} StackSets across the {pRegion} region")
|
727
726
|
print()
|
728
727
|
|
729
728
|
if pTiming:
|
730
729
|
print(ERASE_LINE)
|
731
|
-
print(f"
|
730
|
+
print(f"[green]This script took {time() - begin_time:.3f} seconds")
|
732
731
|
print("Thanks for using this script...")
|
733
732
|
print()
|