runbooks 0.2.5__py3-none-any.whl → 0.6.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (221) hide show
  1. conftest.py +26 -0
  2. jupyter-agent/.env.template +2 -0
  3. jupyter-agent/.gitattributes +35 -0
  4. jupyter-agent/README.md +16 -0
  5. jupyter-agent/app.py +256 -0
  6. jupyter-agent/cloudops-agent.png +0 -0
  7. jupyter-agent/ds-system-prompt.txt +154 -0
  8. jupyter-agent/jupyter-agent.png +0 -0
  9. jupyter-agent/llama3_template.jinja +123 -0
  10. jupyter-agent/requirements.txt +9 -0
  11. jupyter-agent/utils.py +409 -0
  12. runbooks/__init__.py +71 -3
  13. runbooks/__main__.py +13 -0
  14. runbooks/aws/ec2_describe_instances.py +1 -1
  15. runbooks/aws/ec2_run_instances.py +8 -2
  16. runbooks/aws/ec2_start_stop_instances.py +17 -4
  17. runbooks/aws/ec2_unused_volumes.py +5 -1
  18. runbooks/aws/s3_create_bucket.py +4 -2
  19. runbooks/aws/s3_list_objects.py +6 -1
  20. runbooks/aws/tagging_lambda_handler.py +13 -2
  21. runbooks/aws/tags.json +12 -0
  22. runbooks/base.py +353 -0
  23. runbooks/cfat/README.md +49 -0
  24. runbooks/cfat/__init__.py +74 -0
  25. runbooks/cfat/app.ts +644 -0
  26. runbooks/cfat/assessment/__init__.py +40 -0
  27. runbooks/cfat/assessment/asana-import.csv +39 -0
  28. runbooks/cfat/assessment/cfat-checks.csv +31 -0
  29. runbooks/cfat/assessment/cfat.txt +520 -0
  30. runbooks/cfat/assessment/collectors.py +200 -0
  31. runbooks/cfat/assessment/jira-import.csv +39 -0
  32. runbooks/cfat/assessment/runner.py +387 -0
  33. runbooks/cfat/assessment/validators.py +290 -0
  34. runbooks/cfat/cli.py +103 -0
  35. runbooks/cfat/docs/asana-import.csv +24 -0
  36. runbooks/cfat/docs/cfat-checks.csv +31 -0
  37. runbooks/cfat/docs/cfat.txt +335 -0
  38. runbooks/cfat/docs/checks-output.png +0 -0
  39. runbooks/cfat/docs/cloudshell-console-run.png +0 -0
  40. runbooks/cfat/docs/cloudshell-download.png +0 -0
  41. runbooks/cfat/docs/cloudshell-output.png +0 -0
  42. runbooks/cfat/docs/downloadfile.png +0 -0
  43. runbooks/cfat/docs/jira-import.csv +24 -0
  44. runbooks/cfat/docs/open-cloudshell.png +0 -0
  45. runbooks/cfat/docs/report-header.png +0 -0
  46. runbooks/cfat/models.py +1026 -0
  47. runbooks/cfat/package-lock.json +5116 -0
  48. runbooks/cfat/package.json +38 -0
  49. runbooks/cfat/report.py +496 -0
  50. runbooks/cfat/reporting/__init__.py +46 -0
  51. runbooks/cfat/reporting/exporters.py +337 -0
  52. runbooks/cfat/reporting/formatters.py +496 -0
  53. runbooks/cfat/reporting/templates.py +135 -0
  54. runbooks/cfat/run-assessment.sh +23 -0
  55. runbooks/cfat/runner.py +69 -0
  56. runbooks/cfat/src/actions/check-cloudtrail-existence.ts +43 -0
  57. runbooks/cfat/src/actions/check-config-existence.ts +37 -0
  58. runbooks/cfat/src/actions/check-control-tower.ts +37 -0
  59. runbooks/cfat/src/actions/check-ec2-existence.ts +46 -0
  60. runbooks/cfat/src/actions/check-iam-users.ts +50 -0
  61. runbooks/cfat/src/actions/check-legacy-cur.ts +30 -0
  62. runbooks/cfat/src/actions/check-org-cloudformation.ts +30 -0
  63. runbooks/cfat/src/actions/check-vpc-existence.ts +43 -0
  64. runbooks/cfat/src/actions/create-asanaimport.ts +14 -0
  65. runbooks/cfat/src/actions/create-backlog.ts +372 -0
  66. runbooks/cfat/src/actions/create-jiraimport.ts +15 -0
  67. runbooks/cfat/src/actions/create-report.ts +616 -0
  68. runbooks/cfat/src/actions/define-account-type.ts +51 -0
  69. runbooks/cfat/src/actions/get-enabled-org-policy-types.ts +40 -0
  70. runbooks/cfat/src/actions/get-enabled-org-services.ts +26 -0
  71. runbooks/cfat/src/actions/get-idc-info.ts +34 -0
  72. runbooks/cfat/src/actions/get-org-da-accounts.ts +34 -0
  73. runbooks/cfat/src/actions/get-org-details.ts +35 -0
  74. runbooks/cfat/src/actions/get-org-member-accounts.ts +44 -0
  75. runbooks/cfat/src/actions/get-org-ous.ts +35 -0
  76. runbooks/cfat/src/actions/get-regions.ts +22 -0
  77. runbooks/cfat/src/actions/zip-assessment.ts +27 -0
  78. runbooks/cfat/src/types/index.d.ts +147 -0
  79. runbooks/cfat/tests/__init__.py +141 -0
  80. runbooks/cfat/tests/test_cli.py +340 -0
  81. runbooks/cfat/tests/test_integration.py +290 -0
  82. runbooks/cfat/tests/test_models.py +505 -0
  83. runbooks/cfat/tests/test_reporting.py +354 -0
  84. runbooks/cfat/tsconfig.json +16 -0
  85. runbooks/cfat/webpack.config.cjs +27 -0
  86. runbooks/config.py +260 -0
  87. runbooks/finops/__init__.py +88 -0
  88. runbooks/finops/aws_client.py +245 -0
  89. runbooks/finops/cli.py +151 -0
  90. runbooks/finops/cost_processor.py +410 -0
  91. runbooks/finops/dashboard_runner.py +448 -0
  92. runbooks/finops/helpers.py +355 -0
  93. runbooks/finops/main.py +14 -0
  94. runbooks/finops/profile_processor.py +174 -0
  95. runbooks/finops/types.py +66 -0
  96. runbooks/finops/visualisations.py +80 -0
  97. runbooks/inventory/.gitignore +354 -0
  98. runbooks/inventory/ArgumentsClass.py +261 -0
  99. runbooks/inventory/Inventory_Modules.py +6130 -0
  100. runbooks/inventory/LandingZone/delete_lz.py +1075 -0
  101. runbooks/inventory/README.md +1320 -0
  102. runbooks/inventory/__init__.py +62 -0
  103. runbooks/inventory/account_class.py +532 -0
  104. runbooks/inventory/all_my_instances_wrapper.py +123 -0
  105. runbooks/inventory/aws_decorators.py +201 -0
  106. runbooks/inventory/cfn_move_stack_instances.py +1526 -0
  107. runbooks/inventory/check_cloudtrail_compliance.py +614 -0
  108. runbooks/inventory/check_controltower_readiness.py +1107 -0
  109. runbooks/inventory/check_landingzone_readiness.py +711 -0
  110. runbooks/inventory/cloudtrail.md +727 -0
  111. runbooks/inventory/collectors/__init__.py +20 -0
  112. runbooks/inventory/collectors/aws_compute.py +518 -0
  113. runbooks/inventory/collectors/aws_networking.py +275 -0
  114. runbooks/inventory/collectors/base.py +222 -0
  115. runbooks/inventory/core/__init__.py +19 -0
  116. runbooks/inventory/core/collector.py +303 -0
  117. runbooks/inventory/core/formatter.py +296 -0
  118. runbooks/inventory/delete_s3_buckets_objects.py +169 -0
  119. runbooks/inventory/discovery.md +81 -0
  120. runbooks/inventory/draw_org_structure.py +748 -0
  121. runbooks/inventory/ec2_vpc_utils.py +341 -0
  122. runbooks/inventory/find_cfn_drift_detection.py +272 -0
  123. runbooks/inventory/find_cfn_orphaned_stacks.py +719 -0
  124. runbooks/inventory/find_cfn_stackset_drift.py +733 -0
  125. runbooks/inventory/find_ec2_security_groups.py +669 -0
  126. runbooks/inventory/find_landingzone_versions.py +201 -0
  127. runbooks/inventory/find_vpc_flow_logs.py +1221 -0
  128. runbooks/inventory/inventory.sh +659 -0
  129. runbooks/inventory/list_cfn_stacks.py +558 -0
  130. runbooks/inventory/list_cfn_stackset_operation_results.py +252 -0
  131. runbooks/inventory/list_cfn_stackset_operations.py +734 -0
  132. runbooks/inventory/list_cfn_stacksets.py +453 -0
  133. runbooks/inventory/list_config_recorders_delivery_channels.py +681 -0
  134. runbooks/inventory/list_ds_directories.py +354 -0
  135. runbooks/inventory/list_ec2_availability_zones.py +286 -0
  136. runbooks/inventory/list_ec2_ebs_volumes.py +244 -0
  137. runbooks/inventory/list_ec2_instances.py +425 -0
  138. runbooks/inventory/list_ecs_clusters_and_tasks.py +562 -0
  139. runbooks/inventory/list_elbs_load_balancers.py +411 -0
  140. runbooks/inventory/list_enis_network_interfaces.py +526 -0
  141. runbooks/inventory/list_guardduty_detectors.py +568 -0
  142. runbooks/inventory/list_iam_policies.py +404 -0
  143. runbooks/inventory/list_iam_roles.py +518 -0
  144. runbooks/inventory/list_iam_saml_providers.py +359 -0
  145. runbooks/inventory/list_lambda_functions.py +882 -0
  146. runbooks/inventory/list_org_accounts.py +446 -0
  147. runbooks/inventory/list_org_accounts_users.py +354 -0
  148. runbooks/inventory/list_rds_db_instances.py +406 -0
  149. runbooks/inventory/list_route53_hosted_zones.py +318 -0
  150. runbooks/inventory/list_servicecatalog_provisioned_products.py +575 -0
  151. runbooks/inventory/list_sns_topics.py +360 -0
  152. runbooks/inventory/list_ssm_parameters.py +402 -0
  153. runbooks/inventory/list_vpc_subnets.py +433 -0
  154. runbooks/inventory/list_vpcs.py +422 -0
  155. runbooks/inventory/lockdown_cfn_stackset_role.py +224 -0
  156. runbooks/inventory/models/__init__.py +24 -0
  157. runbooks/inventory/models/account.py +192 -0
  158. runbooks/inventory/models/inventory.py +309 -0
  159. runbooks/inventory/models/resource.py +247 -0
  160. runbooks/inventory/recover_cfn_stack_ids.py +205 -0
  161. runbooks/inventory/requirements.txt +12 -0
  162. runbooks/inventory/run_on_multi_accounts.py +211 -0
  163. runbooks/inventory/tests/common_test_data.py +3661 -0
  164. runbooks/inventory/tests/common_test_functions.py +204 -0
  165. runbooks/inventory/tests/script_test_data.py +0 -0
  166. runbooks/inventory/tests/setup.py +24 -0
  167. runbooks/inventory/tests/src.py +18 -0
  168. runbooks/inventory/tests/test_cfn_describe_stacks.py +208 -0
  169. runbooks/inventory/tests/test_ec2_describe_instances.py +162 -0
  170. runbooks/inventory/tests/test_inventory_modules.py +55 -0
  171. runbooks/inventory/tests/test_lambda_list_functions.py +86 -0
  172. runbooks/inventory/tests/test_moto_integration_example.py +273 -0
  173. runbooks/inventory/tests/test_org_list_accounts.py +49 -0
  174. runbooks/inventory/update_aws_actions.py +173 -0
  175. runbooks/inventory/update_cfn_stacksets.py +1215 -0
  176. runbooks/inventory/update_cloudwatch_logs_retention_policy.py +294 -0
  177. runbooks/inventory/update_iam_roles_cross_accounts.py +478 -0
  178. runbooks/inventory/update_s3_public_access_block.py +539 -0
  179. runbooks/inventory/utils/__init__.py +23 -0
  180. runbooks/inventory/utils/aws_helpers.py +510 -0
  181. runbooks/inventory/utils/threading_utils.py +493 -0
  182. runbooks/inventory/utils/validation.py +682 -0
  183. runbooks/inventory/verify_ec2_security_groups.py +1430 -0
  184. runbooks/main.py +785 -0
  185. runbooks/organizations/__init__.py +12 -0
  186. runbooks/organizations/manager.py +374 -0
  187. runbooks/security_baseline/README.md +324 -0
  188. runbooks/security_baseline/checklist/alternate_contacts.py +8 -1
  189. runbooks/security_baseline/checklist/bucket_public_access.py +4 -1
  190. runbooks/security_baseline/checklist/cloudwatch_alarm_configuration.py +9 -2
  191. runbooks/security_baseline/checklist/guardduty_enabled.py +9 -2
  192. runbooks/security_baseline/checklist/multi_region_instance_usage.py +5 -1
  193. runbooks/security_baseline/checklist/root_access_key.py +6 -1
  194. runbooks/security_baseline/config-origin.json +1 -1
  195. runbooks/security_baseline/config.json +1 -1
  196. runbooks/security_baseline/permission.json +1 -1
  197. runbooks/security_baseline/report_generator.py +10 -2
  198. runbooks/security_baseline/report_template_en.html +7 -7
  199. runbooks/security_baseline/report_template_jp.html +7 -7
  200. runbooks/security_baseline/report_template_kr.html +12 -12
  201. runbooks/security_baseline/report_template_vn.html +7 -7
  202. runbooks/security_baseline/requirements.txt +7 -0
  203. runbooks/security_baseline/run_script.py +8 -2
  204. runbooks/security_baseline/security_baseline_tester.py +10 -2
  205. runbooks/security_baseline/utils/common.py +5 -1
  206. runbooks/utils/__init__.py +204 -0
  207. runbooks-0.6.1.dist-info/METADATA +373 -0
  208. runbooks-0.6.1.dist-info/RECORD +237 -0
  209. {runbooks-0.2.5.dist-info → runbooks-0.6.1.dist-info}/WHEEL +1 -1
  210. runbooks-0.6.1.dist-info/entry_points.txt +7 -0
  211. runbooks-0.6.1.dist-info/licenses/LICENSE +201 -0
  212. runbooks-0.6.1.dist-info/top_level.txt +3 -0
  213. runbooks/python101/calculator.py +0 -34
  214. runbooks/python101/config.py +0 -1
  215. runbooks/python101/exceptions.py +0 -16
  216. runbooks/python101/file_manager.py +0 -218
  217. runbooks/python101/toolkit.py +0 -153
  218. runbooks-0.2.5.dist-info/METADATA +0 -439
  219. runbooks-0.2.5.dist-info/RECORD +0 -61
  220. runbooks-0.2.5.dist-info/entry_points.txt +0 -3
  221. runbooks-0.2.5.dist-info/top_level.txt +0 -1
@@ -0,0 +1,558 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ AWS CloudFormation Stack Inventory Collection
4
+
5
+ A comprehensive CloudFormation stack discovery and management tool for multi-account
6
+ AWS Organizations. Provides detailed stack inventory with advanced filtering capabilities
7
+ and optional stack deletion functionality.
8
+
9
+ **AWS API Mapping**: `cloudformation.describe_stacks()`, `cloudformation.list_stacks()`
10
+
11
+ Features:
12
+ - Multi-account CloudFormation stack discovery
13
+ - Advanced status filtering (CREATE_COMPLETE, UPDATE_FAILED, etc.)
14
+ - Stack fragment matching for partial name searches
15
+ - Stack ID display and metadata collection
16
+ - Conditional stack deletion with safety controls
17
+ - Cross-region stack inventory aggregation
18
+ - Specialized GuardDuty stack handling
19
+
20
+ Compatibility:
21
+ - AWS Organizations with cross-account roles
22
+ - AWS Control Tower managed accounts
23
+ - Standalone AWS accounts
24
+ - All AWS regions including opt-in regions
25
+
26
+ Example:
27
+ Discover all active stacks:
28
+ ```bash
29
+ python cfn_describe_stacks.py --profile my-org-profile
30
+ ```
31
+
32
+ Find stacks by name fragment:
33
+ ```bash
34
+ python cfn_describe_stacks.py --profile my-profile \\
35
+ --fragment my-app --status CREATE_COMPLETE
36
+ ```
37
+
38
+ Display stack IDs for troubleshooting:
39
+ ```bash
40
+ python cfn_describe_stacks.py --profile my-profile \\
41
+ --stackid --fragment problematic-stack
42
+ ```
43
+
44
+ Warning:
45
+ The `+delete` parameter will **DELETE** matching stacks WITHOUT additional
46
+ confirmation. Use with extreme caution in production environments.
47
+
48
+ Requirements:
49
+ - IAM permissions: `cloudformation:DescribeStacks`, `cloudformation:DeleteStack`
50
+ - AWS Organizations access (for multi-account scanning)
51
+ - Python 3.8+ with required dependencies
52
+
53
+ Author:
54
+ AWS Cloud Foundations Team
55
+
56
+ Version:
57
+ 2024.05.31
58
+ """
59
+
60
+ import logging
61
+ import sys
62
+ from os.path import split
63
+ from pprint import pprint
64
+ from time import time
65
+
66
+ import Inventory_Modules
67
+ from account_class import aws_acct_access
68
+ from ArgumentsClass import CommonArguments
69
+ from botocore.exceptions import ClientError
70
+ from colorama import Fore, init
71
+ from Inventory_Modules import display_results, get_all_credentials
72
+
73
+ init()
74
+
75
+ __version__ = "2024.05.31"
76
+
77
+ ###########################
78
+
79
+
80
+ def parse_args(f_arguments):
81
+ """
82
+ Parse and validate command-line arguments for CloudFormation stack inventory.
83
+
84
+ Configures the argument parser with CloudFormation-specific options including
85
+ status filtering, stack ID display, and optional deletion capabilities.
86
+ Uses the standardized CommonArguments framework for consistency.
87
+
88
+ Args:
89
+ f_arguments (list): Command-line arguments to parse (typically sys.argv[1:])
90
+
91
+ Returns:
92
+ argparse.Namespace: Parsed arguments containing:
93
+ - Profile: AWS profile for authentication
94
+ - Regions: Target AWS regions for stack discovery
95
+ - Fragments: Stack name fragments for filtering
96
+ - status: CloudFormation stack status filters
97
+ - stackid: Flag to display full stack ARNs
98
+ - DeletionRun: DANGEROUS flag to delete matched stacks
99
+ - Other standard framework arguments
100
+
101
+ Note:
102
+ The DeletionRun parameter enables destructive operations.
103
+ Use with extreme caution in production environments.
104
+ """
105
+ script_path, script_name = split(sys.argv[0])
106
+ parser = CommonArguments()
107
+ parser.singleprofile() # Allows for a single profile to be specified
108
+ parser.multiregion() # Allows for multiple regions to be specified at the command line
109
+ parser.extendedargs() # Allows for extended arguments like which accounts to skip, and whether Force is enabled.
110
+ parser.rootOnly()
111
+ # TODO: Add parameter for access_roles
112
+ # parser.rolestouse() # Allows for the roles to be specified at the command line.
113
+ parser.fragment()
114
+ parser.timing()
115
+ parser.verbosity() # Allows for the verbosity to be handled.
116
+ parser.version(__version__)
117
+ local = parser.my_parser.add_argument_group(script_name, "Parameters specific to this script")
118
+ local.add_argument(
119
+ "-s",
120
+ "--status",
121
+ dest="status",
122
+ nargs="*",
123
+ choices=[
124
+ "CREATE_IN_PROGRESS",
125
+ "CREATE_FAILED",
126
+ "CREATE_COMPLETE",
127
+ "ROLLBACK_IN_PROGRESS",
128
+ "ROLLBACK_FAILED",
129
+ "ROLLBACK_COMPLETE",
130
+ "DELETE_IN_PROGRESS",
131
+ "DELETE_FAILED",
132
+ "DELETE_COMPLETE",
133
+ "UPDATE_IN_PROGRESS",
134
+ "UPDATE_COMPLETE_CLEANUP_IN_PROGRESS",
135
+ "UPDATE_COMPLETE",
136
+ "UPDATE_FAILED",
137
+ "UPDATE_ROLLBACK_IN_PROGRESS",
138
+ "UPDATE_ROLLBACK_FAILED",
139
+ "UPDATE_ROLLBACK_COMPLETE_CLEANUP_IN_PROGRESS",
140
+ "UPDATE_ROLLBACK_COMPLETE",
141
+ "REVIEW_IN_PROGRESS",
142
+ "IMPORT_IN_PROGRESS",
143
+ "IMPORT_COMPLETE",
144
+ "IMPORT_ROLLBACK_IN_PROGRESS",
145
+ "IMPORT_ROLLBACK_FAILED",
146
+ "IMPORT_ROLLBACK_COMPLETE",
147
+ "all",
148
+ "All",
149
+ "ALL",
150
+ ],
151
+ metavar="CloudFormation status",
152
+ default=None,
153
+ help="List of statuses that determines which statuses we see. Default is all ACTIVE statuses. 'All' will capture all statuses",
154
+ )
155
+ local.add_argument(
156
+ "--stackid",
157
+ dest="stackid",
158
+ action="store_true",
159
+ help="Flag that determines whether we display the Stack IDs as well",
160
+ )
161
+ local.add_argument(
162
+ "+delete",
163
+ "+forreal",
164
+ dest="DeletionRun",
165
+ action="store_true",
166
+ help="This will delete the stacks found - without any opportunity to confirm. Be careful!!",
167
+ )
168
+ return parser.my_parser.parse_args(f_arguments)
169
+
170
+
171
+ def setup_auth_accounts_and_regions(
172
+ fProfile: str,
173
+ fRegionList: list = None,
174
+ fAccountList: list = None,
175
+ fSkipAccounts: list = None,
176
+ fStackFrag: list = None,
177
+ fExact: bool = False,
178
+ fDeletionRun: bool = False,
179
+ ) -> (aws_acct_access, list, list):
180
+ """
181
+ Initialize AWS account access and determine target accounts/regions for stack discovery.
182
+
183
+ This function establishes the AWS Organizations context, resolves account access
184
+ permissions, and prepares the execution scope for CloudFormation stack operations.
185
+ Includes safety warnings and user confirmation for destructive operations.
186
+
187
+ Args:
188
+ fProfile (str): AWS profile name for authentication and organization access
189
+ fRegionList (list, optional): Target regions for stack discovery.
190
+ Defaults to all accessible regions if None.
191
+ fAccountList (list, optional): Specific account IDs to include.
192
+ Defaults to all organization accounts if None.
193
+ fSkipAccounts (list, optional): Account IDs to exclude from discovery
194
+ fStackFrag (list, optional): Stack name fragments for filtering operations
195
+ fExact (bool, optional): Use exact matching for stack fragments.
196
+ Defaults to False (partial matching).
197
+ fDeletionRun (bool, optional): Flag indicating destructive operations.
198
+ Triggers additional warnings and confirmations.
199
+
200
+ Returns:
201
+ tuple: Three-element tuple containing:
202
+ - aws_acct_access: Authenticated AWS account access object
203
+ - list: Resolved list of target account IDs
204
+ - list: Resolved list of target AWS regions
205
+
206
+ Raises:
207
+ ConnectionError: When AWS profile authentication fails
208
+
209
+ Side Effects:
210
+ - Displays execution parameters and warnings to stdout
211
+ - Shows colored output for operation confirmation
212
+ - Exits process (sys.exit(8)) on authentication failure
213
+
214
+ Security Note:
215
+ When fDeletionRun=True, displays prominent warnings about
216
+ destructive operations to prevent accidental stack deletion.
217
+ """
218
+ try:
219
+ aws_acct = aws_acct_access(fProfile)
220
+ except ConnectionError as my_Error:
221
+ logging.error(f"Exiting due to error: {my_Error}")
222
+ sys.exit(8)
223
+
224
+ ChildAccounts = aws_acct.ChildAccounts
225
+ RegionList = Inventory_Modules.get_regions3(aws_acct, fRegionList)
226
+
227
+ ChildAccounts = Inventory_Modules.RemoveCoreAccounts(ChildAccounts, fSkipAccounts)
228
+ if fAccountList is None:
229
+ AccountList = [account["AccountId"] for account in ChildAccounts]
230
+ else:
231
+ AccountList = [account["AccountId"] for account in ChildAccounts if account["AccountId"] in fAccountList]
232
+
233
+ print(f"You asked to find stacks with this fragment {Fore.RED}'{fStackFrag}'{Fore.RESET}")
234
+ print(f"in these accounts:\n{Fore.RED}{AccountList}{Fore.RESET}")
235
+ print(f"in these regions:\n{Fore.RED}{RegionList}{Fore.RESET}")
236
+ print(f"While skipping these accounts:\n{Fore.RED}{fSkipAccounts}{Fore.RESET}") if fSkipAccounts is not None else ""
237
+ if fDeletionRun:
238
+ print()
239
+ print("And delete the stacks that are found...")
240
+
241
+ if fExact:
242
+ print(f"\t\tFor stacks that {Fore.RED}exactly match{Fore.RESET} these fragments: {fStackFrag}")
243
+ else:
244
+ print(f"\t\tFor stacks that contains these fragments: {fStackFrag}")
245
+
246
+ return aws_acct, AccountList, RegionList
247
+
248
+
249
+ def collect_cfnstacks(fCredentialList: list) -> list:
250
+ """
251
+ Execute multi-threaded CloudFormation stack discovery across AWS accounts and regions.
252
+
253
+ This is the core discovery engine that performs concurrent CloudFormation API calls
254
+ across all provided credentials. Uses a worker thread pool pattern for optimal
255
+ performance while managing API rate limits and error handling.
256
+
257
+ Args:
258
+ fCredentialList (list): List of credential dictionaries containing:
259
+ - AccountId: AWS account identifier
260
+ - Region: AWS region name
261
+ - AccessKeyId, SecretAccessKey, SessionToken: AWS credentials
262
+ - Success: Boolean flag indicating credential validation status
263
+
264
+ Returns:
265
+ list: Sorted list of CloudFormation stack dictionaries with metadata:
266
+ - Account: AWS account ID where stack exists
267
+ - Region: AWS region of the stack
268
+ - StackName: CloudFormation stack name
269
+ - StackStatus: Current stack status (CREATE_COMPLETE, etc.)
270
+ - StackCreate: Stack creation date (formatted YYYY-MM-DD)
271
+ - StackArn: Full stack ARN (if stackid flag enabled)
272
+ - Credential fields for potential stack operations
273
+
274
+ Threading Architecture:
275
+ - Uses Queue for thread-safe work distribution
276
+ - 4 concurrent worker threads for balanced throughput
277
+ - Real-time progress display with colored output
278
+ - Graceful handling of authentication failures
279
+
280
+ Error Handling:
281
+ - AuthFailure: Logs and continues with other accounts
282
+ - ClientError: Handles AWS API throttling and service errors
283
+ - Credential validation via Success flag checking
284
+
285
+ Performance:
286
+ Results are automatically sorted by Account -> Region -> StackName
287
+ for consistent output presentation across executions.
288
+ """
289
+ from queue import Queue
290
+ from threading import Thread
291
+
292
+ StacksFound = []
293
+
294
+ def worker(q, pStackfrag, pstatus):
295
+ """
296
+ Worker thread function for concurrent CloudFormation stack discovery.
297
+
298
+ Each worker processes credentials from the shared queue, calls the
299
+ CloudFormation API to discover stacks, and aggregates results.
300
+ Implements comprehensive error handling and real-time progress display.
301
+
302
+ Args:
303
+ q (Queue): Thread-safe queue containing credential work items
304
+ pStackfrag (list): Stack name fragments for filtering
305
+ pstatus (list): Stack status filters to apply
306
+
307
+ Side Effects:
308
+ - Updates shared StacksFound list with discoveries
309
+ - Displays real-time progress to stdout
310
+ - Logs detailed account/region processing information
311
+ """
312
+ while True:
313
+ # Get work item from thread-safe queue
314
+ credential = q.get()
315
+ if credential is None:
316
+ break # Shutdown signal received
317
+
318
+ Stacks = False
319
+
320
+ # Only process credentials that passed validation
321
+ if credential["Success"]:
322
+ try:
323
+ # Call CloudFormation API to discover stacks
324
+ Stacks = Inventory_Modules.find_stacks2(credential, credential["Region"], pStackfrag, pstatus)
325
+
326
+ # Log discovery results for debugging
327
+ logging.warning(
328
+ f"Account: {credential['AccountId']} | Region: {credential['Region']} | Found {len(Stacks)} Stacks"
329
+ )
330
+
331
+ # Display real-time progress with colored output
332
+ print(
333
+ f"{ERASE_LINE}{Fore.RED}Account: {credential['AccountId']} Region: {credential['Region']} Found {len(Stacks)} Stacks{Fore.RESET}",
334
+ end="\r",
335
+ )
336
+
337
+ except ClientError as my_Error:
338
+ # Handle AWS authentication and authorization errors
339
+ if "AuthFailure" in str(my_Error):
340
+ print(f"{credential['AccountId']}: Authorization Failure")
341
+ else:
342
+ # Skip credentials that failed validation
343
+ continue
344
+
345
+ # Process discovered stacks and aggregate results
346
+ if Stacks and len(Stacks) > 0:
347
+ for y in range(len(Stacks)):
348
+ # Extract stack metadata from AWS API response
349
+ StackName = Stacks[y]["StackName"]
350
+ StackStatus = Stacks[y]["StackStatus"]
351
+ StackID = Stacks[y]["StackId"]
352
+ StackCreate = Stacks[y]["CreationTime"]
353
+
354
+ # Create standardized stack record for aggregation
355
+ # Includes both metadata and credentials for potential operations
356
+ StacksFound.append(
357
+ {
358
+ # Identity and location information
359
+ "Account": credential["AccountId"],
360
+ "Region": credential["Region"],
361
+ # AWS credentials for stack operations (deletion, etc.)
362
+ "AccessKeyId": credential["AccessKeyId"],
363
+ "SecretAccessKey": credential["SecretAccessKey"],
364
+ "SessionToken": credential["SessionToken"],
365
+ "AccountNumber": credential["AccountNumber"],
366
+ # CloudFormation stack metadata
367
+ "StackName": StackName,
368
+ "StackCreate": StackCreate.strftime("%Y-%m-%d"),
369
+ "StackStatus": StackStatus,
370
+ # Conditional stack ARN display (based on CLI flag)
371
+ "StackArn": StackID if pStackIdFlag else "None",
372
+ }
373
+ )
374
+
375
+ # Mark work item as complete for queue management
376
+ q.task_done()
377
+
378
+ stacks_queue = Queue()
379
+ for credential in fCredentialList:
380
+ stacks_queue.put(credential)
381
+
382
+ num_threads = 4 # Number of worker threads
383
+ for i in range(num_threads):
384
+ t = Thread(target=worker, args=(stacks_queue, pStackfrag, pstatus))
385
+ t.start()
386
+
387
+ stacks_queue.join()
388
+
389
+ for i in range(num_threads):
390
+ stacks_queue.put(None)
391
+
392
+ sortedStacksFound = sorted(StacksFound, key=lambda x: (x["Account"], x["Region"], x["StackName"]))
393
+ return sortedStacksFound
394
+
395
+
396
+ def display_stacks(fAllStacks: list):
397
+ display_dict = {
398
+ "Account": {"DisplayOrder": 1, "Heading": "Account"},
399
+ "Region": {"DisplayOrder": 2, "Heading": "Region"},
400
+ "StackStatus": {"DisplayOrder": 3, "Heading": "Stack Status"},
401
+ "StackCreate": {"DisplayOrder": 4, "Heading": "Create Date"},
402
+ "StackName": {"DisplayOrder": 5, "Heading": "Stack Name"},
403
+ "StackArn": {"DisplayOrder": 6, "Heading": "Stack ID"},
404
+ }
405
+
406
+ display_results(
407
+ fAllStacks,
408
+ display_dict,
409
+ None,
410
+ )
411
+ print(ERASE_LINE)
412
+ print(
413
+ f"{Fore.RED}Found {len(fAllStacks)} stacks across {len(AccountList)} accounts across {len(RegionList)} regions{Fore.RESET}"
414
+ )
415
+ print()
416
+ if args.loglevel < 21: # INFO level
417
+ lAccounts = lRegions = lAccountsAndRegions = []
418
+ for i in range(len(fAllStacks)):
419
+ lAccounts.append(fAllStacks[i]["Account"])
420
+ lRegions.append(fAllStacks[i]["Region"])
421
+ lAccountsAndRegions.append((fAllStacks[i]["Account"], fAllStacks[i]["Region"]))
422
+ print("The list of accounts and regions:")
423
+ pprint(list(sorted(set(lAccountsAndRegions))))
424
+
425
+
426
+ def modify_stacks(fStacksFound: list):
427
+ """
428
+ Execute CloudFormation stack deletion operations with specialized error handling.
429
+
430
+ This function implements the destructive stack deletion workflow with multiple
431
+ safety confirmations and specialized handling for problematic stack types like
432
+ GuardDuty stacks that may have resource retention requirements.
433
+
434
+ Args:
435
+ fStacksFound (list): List of stack dictionaries to delete, each containing:
436
+ - StackName: CloudFormation stack identifier
437
+ - Account: AWS account ID
438
+ - Region: AWS region
439
+ - StackStatus: Current stack status
440
+ - Credential information for API calls
441
+
442
+ Returns:
443
+ list: Collection of AWS API responses from deletion operations.
444
+ May contain success confirmations or error details.
445
+
446
+ Safety Features:
447
+ - Interactive confirmation prompt before execution
448
+ - Special handling for DELETE_FAILED stacks
449
+ - GuardDuty-specific resource retention logic
450
+ - Progress display during batch deletions
451
+
452
+ Special Cases:
453
+ - GuardDuty stacks: Uses RetainResources=['MasterDetector'] due to
454
+ common deletion failures with GuardDuty master detectors
455
+ - DELETE_FAILED stacks: Automatically retains problematic resources
456
+ that may be blocking normal deletion
457
+
458
+ Warning:
459
+ This function performs IRREVERSIBLE CloudFormation stack deletions.
460
+ Ensure proper backups and confirmations before execution.
461
+
462
+ Example:
463
+ >>> stacks_to_delete = [{'StackName': 'test-stack', 'Account': '123456789'}]
464
+ >>> responses = modify_stacks(stacks_to_delete)
465
+ >>> print(f"Deleted {len(responses)} stacks")
466
+ """
467
+ ReallyDelete = (
468
+ (input(f"Deletion of stacks has been requested, are you still sure? (y/n): ") in ["y", "Y"])
469
+ if DeletionRun
470
+ else False
471
+ )
472
+ response2 = []
473
+ if DeletionRun and ReallyDelete and ("GuardDuty" in pStackfrag or "guardduty" in pStackfrag):
474
+ logging.warning(f"Deleting {len(fStacksFound)} stacks")
475
+ for stack_found in fStacksFound:
476
+ print(
477
+ f"Deleting stack {stack_found['StackName']} from Account {stack_found['Account']} in region {stack_found['Region']}"
478
+ )
479
+ if stack_found["StackStatus"] == "DELETE_FAILED":
480
+ # This deletion generally fails because the Master Detector doesn't properly delete (and it's usually already deleted due to some other script) - so we just need to delete the stack anyway - and ignore the actual resource.
481
+ response = Inventory_Modules.delete_stack2(
482
+ stack_found,
483
+ stack_found["Region"],
484
+ stack_found["StackName"],
485
+ RetainResources=True,
486
+ ResourcesToRetain=["MasterDetector"],
487
+ )
488
+ else:
489
+ response = Inventory_Modules.delete_stack2(stack_found, stack_found["Region"], stack_found["StackName"])
490
+ response2.append(response)
491
+ elif DeletionRun and ReallyDelete:
492
+ logging.warning(f"Deleting {len(fStacksFound)} stacks")
493
+ for stack_found in fStacksFound:
494
+ print(
495
+ f"Deleting stack {stack_found['StackName']} from account {stack_found['Account']} in region {stack_found['Region']} with status: {stack_found['StackStatus']}"
496
+ )
497
+ # print(f"Finished {y + 1} of {len(fStacksFound)}")
498
+ response = Inventory_Modules.delete_stack2(stack_found, stack_found["Region"], stack_found["StackName"])
499
+ response2.append(response)
500
+ # pprint(response)
501
+ return response2
502
+
503
+
504
+ ###########################
505
+
506
+ if __name__ == "__main__":
507
+ args = parse_args(sys.argv[1:])
508
+
509
+ pProfile = args.Profile
510
+ pRegionList = args.Regions
511
+ pStackfrag = args.Fragments
512
+ pExact = args.Exact
513
+ pTiming = args.Time
514
+ verbose = args.loglevel
515
+ pSkipProfiles = args.SkipProfiles
516
+ pSkipAccounts = args.SkipAccounts
517
+ pRootOnly = args.RootOnly
518
+ pAccountList = args.Accounts
519
+ pstatus = args.status
520
+ pStackIdFlag = args.stackid
521
+ DeletionRun = args.DeletionRun
522
+ # Setup logging levels
523
+ logging.basicConfig(level=verbose, format="[%(filename)s:%(lineno)s - %(funcName)20s() ] %(message)s")
524
+ logging.getLogger("boto3").setLevel(logging.CRITICAL)
525
+ logging.getLogger("botocore").setLevel(logging.CRITICAL)
526
+ logging.getLogger("s3transfer").setLevel(logging.CRITICAL)
527
+ logging.getLogger("urllib3").setLevel(logging.CRITICAL)
528
+ logging.getLogger("botocore").setLevel(logging.CRITICAL)
529
+
530
+ begin_time = time()
531
+
532
+ ##########################
533
+ ERASE_LINE = "\x1b[2K"
534
+
535
+ print()
536
+ # Setup the aws_acct object
537
+ aws_acct, AccountList, RegionList = setup_auth_accounts_and_regions(
538
+ pProfile, pRegionList, pAccountList, pSkipAccounts, pStackfrag, pExact, DeletionRun
539
+ )
540
+ # Get credentials for all Child Accounts
541
+ CredentialList = get_all_credentials(
542
+ pProfile, pTiming, pSkipProfiles, pSkipAccounts, pRootOnly, AccountList, RegionList
543
+ )
544
+ # Collect the stacksets, AccountList and RegionList involved
545
+ AllStacks = collect_cfnstacks(CredentialList)
546
+ # Display the information we've found this far
547
+ display_stacks(AllStacks)
548
+ # Modify stacks, if requested
549
+ if DeletionRun:
550
+ modify_result = modify_stacks(AllStacks)
551
+
552
+ if pTiming:
553
+ print(ERASE_LINE)
554
+ print(f"{Fore.GREEN}This script took {time() - begin_time:.2f} seconds{Fore.RESET}")
555
+
556
+ print()
557
+ print("Thanks for using this script...")
558
+ print()