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
runbooks/main.py ADDED
@@ -0,0 +1,785 @@
1
+ """
2
+ CloudOps Runbooks - Main CLI entry point for all runbook commands.
3
+
4
+ This module provides the command-line interface for CloudOps automation,
5
+ integrating AWS Cloud Foundations best practices with operational runbooks.
6
+
7
+ Following KISS principle: this is the main entry point combining both CLI logic and main execution.
8
+ """
9
+
10
+ import sys
11
+ from datetime import datetime
12
+ from pathlib import Path
13
+ from typing import Optional
14
+
15
+ import click
16
+ from loguru import logger
17
+
18
+ try:
19
+ from rich.console import Console
20
+ from rich.table import Table
21
+
22
+ _HAS_RICH = True
23
+ except ImportError:
24
+ _HAS_RICH = False
25
+
26
+ # Simple fallback console
27
+ class Console:
28
+ def print(self, *args, **kwargs):
29
+ print(*args)
30
+
31
+ def status(self, message):
32
+ print(f"Status: {message}")
33
+ return self
34
+
35
+ def __enter__(self):
36
+ return self
37
+
38
+ def __exit__(self, *args):
39
+ pass
40
+
41
+ class Table:
42
+ def __init__(self, title=""):
43
+ self.title = title
44
+ self.columns = []
45
+ self.rows = []
46
+
47
+ def add_column(self, name, style=""):
48
+ self.columns.append(name)
49
+
50
+ def add_row(self, *args):
51
+ self.rows.append(args)
52
+
53
+ def __str__(self):
54
+ if not self.columns:
55
+ return ""
56
+
57
+ # Simple text table
58
+ output = f"\n{self.title}\n" + "=" * len(self.title) + "\n"
59
+ output += " | ".join(self.columns) + "\n"
60
+ output += "-" * (len(" | ".join(self.columns))) + "\n"
61
+
62
+ for row in self.rows:
63
+ output += " | ".join(str(cell) for cell in row) + "\n"
64
+
65
+ return output
66
+
67
+
68
+ from runbooks import __version__
69
+ from runbooks.cfat.runner import AssessmentRunner
70
+ from runbooks.config import load_config, save_config
71
+ from runbooks.inventory.core.collector import InventoryCollector
72
+ from runbooks.organizations.manager import OUManager
73
+ from runbooks.utils import setup_logging
74
+
75
+ console = Console()
76
+
77
+
78
+ @click.group(invoke_without_command=True)
79
+ @click.version_option(version=__version__)
80
+ @click.option("--debug", is_flag=True, help="Enable debug logging")
81
+ @click.option("--profile", default="default", help="AWS profile to use")
82
+ @click.option("--region", help="AWS region (overrides profile region)")
83
+ @click.option("--config", type=click.Path(), help="Configuration file path")
84
+ @click.pass_context
85
+ def main(ctx, debug, profile, region, config):
86
+ """
87
+ CloudOps Runbooks - Enterprise CloudOps Automation Toolkit.
88
+
89
+ This tool provides comprehensive AWS automation capabilities including:
90
+ - Cloud Foundations Assessment Tool (CFAT)
91
+ - Multi-account resource inventory
92
+ - Organization management
93
+ - Control Tower automation
94
+ - Identity and access management
95
+ - Centralized logging setup
96
+
97
+ Use 'runbooks COMMAND --help' for more information on specific commands.
98
+ """
99
+ # Initialize context
100
+ ctx.ensure_object(dict)
101
+ ctx.obj["debug"] = debug
102
+ ctx.obj["profile"] = profile
103
+ ctx.obj["region"] = region
104
+
105
+ # Setup logging
106
+ setup_logging(debug=debug)
107
+
108
+ # Load configuration
109
+ config_path = Path(config) if config else Path.home() / ".runbooks" / "config.yaml"
110
+ ctx.obj["config"] = load_config(config_path)
111
+
112
+ # Show help if no command provided
113
+ if ctx.invoked_subcommand is None:
114
+ click.echo(ctx.get_help())
115
+
116
+
117
+ # ============================================================================
118
+ # CFAT Commands
119
+ # ============================================================================
120
+
121
+
122
+ @main.group()
123
+ @click.pass_context
124
+ def cfat(ctx):
125
+ """Cloud Foundations Assessment Tool - Assess AWS account configuration."""
126
+ pass
127
+
128
+
129
+ @cfat.command()
130
+ @click.option(
131
+ "--output",
132
+ type=click.Choice(["console", "html", "csv", "json", "markdown", "all"]),
133
+ default="console",
134
+ help="Output format (use 'all' for multiple formats)",
135
+ )
136
+ @click.option("--output-file", type=click.Path(), help="Output file path (auto-generated if not specified)")
137
+ @click.option("--checks", multiple=True, help="Specific checks to run")
138
+ @click.option("--skip-checks", multiple=True, help="Checks to skip")
139
+ @click.option("--categories", multiple=True, help="Assessment categories to include")
140
+ @click.option("--skip-categories", multiple=True, help="Assessment categories to exclude")
141
+ @click.option("--severity", type=click.Choice(["INFO", "WARNING", "CRITICAL"]), help="Minimum severity level to report")
142
+ @click.option("--parallel/--sequential", default=True, help="Enable/disable parallel execution")
143
+ @click.option("--max-workers", type=int, default=10, help="Maximum parallel workers")
144
+ @click.option("--compliance-framework", help="Target compliance framework (SOC2, PCI-DSS, HIPAA)")
145
+ @click.option("--export-jira", type=click.Path(), help="Export findings to Jira CSV format")
146
+ @click.option("--export-asana", type=click.Path(), help="Export findings to Asana CSV format")
147
+ @click.option("--export-servicenow", type=click.Path(), help="Export findings to ServiceNow JSON format")
148
+ @click.option("--serve-web", is_flag=True, help="Start web server for interactive reports")
149
+ @click.option("--web-port", type=int, default=8080, help="Port for web server")
150
+ @click.pass_context
151
+ def assess(
152
+ ctx,
153
+ output,
154
+ output_file,
155
+ checks,
156
+ skip_checks,
157
+ categories,
158
+ skip_categories,
159
+ severity,
160
+ parallel,
161
+ max_workers,
162
+ compliance_framework,
163
+ export_jira,
164
+ export_asana,
165
+ export_servicenow,
166
+ serve_web,
167
+ web_port,
168
+ ):
169
+ """
170
+ Run enhanced Cloud Foundations assessment with enterprise features.
171
+
172
+ This command performs a comprehensive assessment of your AWS account
173
+ configuration against Cloud Foundations best practices with advanced
174
+ features including multi-format reporting, parallel execution,
175
+ compliance framework alignment, and project management integration.
176
+
177
+ Examples:
178
+ # Basic assessment with HTML report
179
+ runbooks cfat assess --output html --output-file report.html
180
+
181
+ # Target specific categories and severity
182
+ runbooks cfat assess --categories iam,cloudtrail --severity CRITICAL
183
+
184
+ # Parallel execution with custom workers
185
+ runbooks cfat assess --parallel --max-workers 5
186
+
187
+ # Compliance framework assessment
188
+ runbooks cfat assess --compliance-framework SOC2 --output all
189
+
190
+ # Export to project management tools
191
+ runbooks cfat assess --export-jira jira_tasks.csv --export-asana asana_tasks.csv
192
+
193
+ # Interactive web report
194
+ runbooks cfat assess --serve-web --web-port 8080
195
+ """
196
+ logger.info(f"Starting enhanced Cloud Foundations assessment for profile: {ctx.obj['profile']}")
197
+
198
+ with console.status("[bold green]Running enhanced assessment checks...") as status:
199
+ try:
200
+ # Initialize enhanced assessment runner
201
+ runner = AssessmentRunner(profile=ctx.obj["profile"], region=ctx.obj["region"])
202
+
203
+ # Configure assessment parameters
204
+ if checks:
205
+ runner.set_checks(list(checks))
206
+ if skip_checks:
207
+ runner.skip_checks(list(skip_checks))
208
+ if severity:
209
+ runner.set_min_severity(severity)
210
+
211
+ # Configure categories
212
+ if categories:
213
+ runner.assessment_config.included_categories = list(categories)
214
+ if skip_categories:
215
+ runner.assessment_config.excluded_categories = list(skip_categories)
216
+
217
+ # Configure execution settings
218
+ runner.assessment_config.parallel_execution = parallel
219
+ runner.assessment_config.max_workers = max_workers
220
+
221
+ # Set compliance framework
222
+ if compliance_framework:
223
+ runner.assessment_config.compliance_framework = compliance_framework
224
+
225
+ status.update("[bold green]Executing assessment checks...")
226
+
227
+ # Run assessment
228
+ report = runner.run_assessment()
229
+
230
+ status.update("[bold green]Generating reports...")
231
+
232
+ # Display console summary
233
+ display_assessment_results(report)
234
+
235
+ # Generate output files
236
+ generated_files = []
237
+
238
+ if output == "all":
239
+ # Generate all formats
240
+ timestamp = report.timestamp.strftime("%Y%m%d_%H%M%S")
241
+ base_name = f"cfat_report_{timestamp}"
242
+
243
+ report.to_html(f"{base_name}.html")
244
+ generated_files.append(f"{base_name}.html")
245
+
246
+ report.to_json(f"{base_name}.json")
247
+ generated_files.append(f"{base_name}.json")
248
+
249
+ report.to_csv(f"{base_name}.csv")
250
+ generated_files.append(f"{base_name}.csv")
251
+
252
+ report.to_markdown(f"{base_name}.md")
253
+ generated_files.append(f"{base_name}.md")
254
+
255
+ elif output != "console":
256
+ # Generate specific format
257
+ if not output_file:
258
+ timestamp = report.timestamp.strftime("%Y%m%d_%H%M%S")
259
+ output_file = f"cfat_report_{timestamp}.{output}"
260
+
261
+ if output == "html":
262
+ report.to_html(output_file)
263
+ elif output == "csv":
264
+ report.to_csv(output_file)
265
+ elif output == "json":
266
+ report.to_json(output_file)
267
+ elif output == "markdown":
268
+ report.to_markdown(output_file)
269
+
270
+ generated_files.append(output_file)
271
+
272
+ # Export to project management tools
273
+ if export_jira:
274
+ from runbooks.cfat.reporting.exporters import JiraExporter
275
+
276
+ exporter = JiraExporter()
277
+ exporter.export(report, export_jira)
278
+ generated_files.append(export_jira)
279
+
280
+ if export_asana:
281
+ from runbooks.cfat.reporting.exporters import AsanaExporter
282
+
283
+ exporter = AsanaExporter()
284
+ exporter.export(report, export_asana)
285
+ generated_files.append(export_asana)
286
+
287
+ if export_servicenow:
288
+ from runbooks.cfat.reporting.exporters import ServiceNowExporter
289
+
290
+ exporter = ServiceNowExporter()
291
+ exporter.export(report, export_servicenow)
292
+ generated_files.append(export_servicenow)
293
+
294
+ # Start web server if requested
295
+ if serve_web:
296
+ start_web_server(report, web_port)
297
+
298
+ # Display success message
299
+ if generated_files:
300
+ console.print(f"\n[green]✓ Assessment completed successfully![/green]")
301
+ console.print(f"[green]✓ Generated files:[/green]")
302
+ for file in generated_files:
303
+ console.print(f" • {file}")
304
+
305
+ # Display summary statistics
306
+ console.print(f"\n[bold]Assessment Summary:[/bold]")
307
+ console.print(f"• Compliance Score: [bold]{report.summary.compliance_score}/100[/bold]")
308
+ console.print(f"• Risk Level: [bold]{report.summary.risk_level}[/bold]")
309
+ console.print(f"• Critical Issues: [bold red]{report.summary.critical_issues}[/bold red]")
310
+ console.print(f"• Total Checks: {report.summary.total_checks}")
311
+ console.print(f"• Pass Rate: {report.summary.pass_rate:.1f}%")
312
+
313
+ except Exception as e:
314
+ logger.error(f"Assessment failed: {e}")
315
+ console.print(f"[red]✗ Assessment failed: {e}[/red]")
316
+ sys.exit(1)
317
+
318
+
319
+ def start_web_server(report, port: int = 8080):
320
+ """
321
+ Start interactive web server for assessment results.
322
+
323
+ Args:
324
+ report: Assessment report to serve
325
+ port: Port number for web server
326
+ """
327
+ try:
328
+ import os
329
+ import tempfile
330
+ import threading
331
+ import webbrowser
332
+ from http.server import HTTPServer, SimpleHTTPRequestHandler
333
+
334
+ # Generate HTML report in temporary directory
335
+ temp_dir = tempfile.mkdtemp()
336
+ html_file = os.path.join(temp_dir, "assessment_report.html")
337
+ report.to_html(html_file)
338
+
339
+ # Change to temp directory for serving
340
+ os.chdir(temp_dir)
341
+
342
+ # Start web server in background thread
343
+ def serve():
344
+ httpd = HTTPServer(("localhost", port), SimpleHTTPRequestHandler)
345
+ console.print(f"[green]🌐 Web server started at http://localhost:{port}[/green]")
346
+ console.print(f"[yellow]Press Ctrl+C to stop the server[/yellow]")
347
+ httpd.serve_forever()
348
+
349
+ server_thread = threading.Thread(target=serve, daemon=True)
350
+ server_thread.start()
351
+
352
+ # Open browser
353
+ webbrowser.open(f"http://localhost:{port}/assessment_report.html")
354
+
355
+ # Keep main thread alive
356
+ try:
357
+ server_thread.join()
358
+ except KeyboardInterrupt:
359
+ console.print(f"\n[yellow]Web server stopped[/yellow]")
360
+
361
+ except ImportError:
362
+ console.print(f"[red]Web server functionality requires additional dependencies[/red]")
363
+ except Exception as e:
364
+ logger.error(f"Failed to start web server: {e}")
365
+ console.print(f"[red]Failed to start web server: {e}[/red]")
366
+
367
+
368
+ def display_assessment_results(report):
369
+ """
370
+ Display enhanced assessment results in formatted tables.
371
+
372
+ Args:
373
+ report: Assessment report to display
374
+ """
375
+ # Display executive summary first
376
+ console.print(f"\n[bold blue]📊 Cloud Foundations Assessment Results[/bold blue]")
377
+ console.print(f"[dim]Account: {report.account_id} | Region: {report.region} | Profile: {report.profile}[/dim]")
378
+
379
+ # Summary metrics table
380
+ summary_table = Table(title="Assessment Summary", show_header=True, header_style="bold magenta")
381
+ summary_table.add_column("Metric", style="cyan", width=20)
382
+ summary_table.add_column("Value", style="bold", width=15)
383
+ summary_table.add_column("Status", width=15)
384
+
385
+ # Add summary rows with enhanced formatting
386
+ summary_table.add_row(
387
+ "Compliance Score",
388
+ f"{report.summary.compliance_score}/100",
389
+ f"[{'green' if report.summary.compliance_score >= 80 else 'yellow' if report.summary.compliance_score >= 60 else 'red'}]{report.summary.risk_level}[/]",
390
+ )
391
+ summary_table.add_row("Total Checks", str(report.summary.total_checks), "✓ Completed")
392
+ summary_table.add_row(
393
+ "Pass Rate",
394
+ f"{report.summary.pass_rate:.1f}%",
395
+ f"[{'green' if report.summary.pass_rate >= 80 else 'yellow' if report.summary.pass_rate >= 60 else 'red'}]{'Good' if report.summary.pass_rate >= 80 else 'Fair' if report.summary.pass_rate >= 60 else 'Poor'}[/]",
396
+ )
397
+ summary_table.add_row(
398
+ "Critical Issues",
399
+ str(report.summary.critical_issues),
400
+ f"[{'red' if report.summary.critical_issues > 0 else 'green'}]{'Action Required' if report.summary.critical_issues > 0 else 'None'}[/]",
401
+ )
402
+ summary_table.add_row(
403
+ "Execution Time",
404
+ f"{report.summary.total_execution_time:.1f}s",
405
+ f"[dim]{report.summary.avg_execution_time:.2f}s avg[/dim]",
406
+ )
407
+
408
+ console.print(summary_table)
409
+
410
+ # Category breakdown
411
+ if report.results:
412
+ console.print(f"\n[bold]📋 Results by Category[/bold]")
413
+ category_summary = report.get_category_summary()
414
+
415
+ category_table = Table(show_header=True, header_style="bold magenta")
416
+ category_table.add_column("Category", style="cyan")
417
+ category_table.add_column("Total", justify="center")
418
+ category_table.add_column("Passed", justify="center", style="green")
419
+ category_table.add_column("Failed", justify="center", style="red")
420
+ category_table.add_column("Critical", justify="center", style="bold red")
421
+ category_table.add_column("Pass Rate", justify="center")
422
+
423
+ for category, stats in category_summary.items():
424
+ pass_rate = (stats["passed"] / stats["total"] * 100) if stats["total"] > 0 else 0
425
+ pass_rate_color = "green" if pass_rate >= 80 else "yellow" if pass_rate >= 60 else "red"
426
+
427
+ category_table.add_row(
428
+ category.upper(),
429
+ str(stats["total"]),
430
+ str(stats["passed"]),
431
+ str(stats["failed"]),
432
+ str(stats["critical"]),
433
+ f"[{pass_rate_color}]{pass_rate:.1f}%[/{pass_rate_color}]",
434
+ )
435
+
436
+ console.print(category_table)
437
+
438
+ # Show failed checks if any
439
+ failed_results = report.get_failed_results()
440
+ if failed_results:
441
+ console.print(f"\n[bold red]🚨 Failed Checks ({len(failed_results)})[/bold red]")
442
+
443
+ failed_table = Table(show_header=True, header_style="bold magenta")
444
+ failed_table.add_column("Finding ID", style="cyan", width=12)
445
+ failed_table.add_column("Check", style="bold", width=25)
446
+ failed_table.add_column("Severity", width=10)
447
+ failed_table.add_column("Message", style="dim", width=50)
448
+
449
+ for result in failed_results[:10]: # Show first 10 failed checks
450
+ severity_color = {"INFO": "blue", "WARNING": "yellow", "CRITICAL": "red"}.get(
451
+ result.severity.value, "white"
452
+ )
453
+
454
+ failed_table.add_row(
455
+ result.finding_id,
456
+ result.check_name,
457
+ f"[{severity_color}]{result.severity.value}[/{severity_color}]",
458
+ result.message[:47] + "..." if len(result.message) > 50 else result.message,
459
+ )
460
+
461
+ if len(failed_results) > 10:
462
+ console.print(f"[dim]... and {len(failed_results) - 10} more failed checks[/dim]")
463
+
464
+ console.print(failed_table)
465
+
466
+ # Show critical findings if any
467
+ critical_results = report.get_critical_results()
468
+ if critical_results:
469
+ console.print(f"\n[bold red]⚠️ Critical Findings Requiring Immediate Action[/bold red]")
470
+ for i, result in enumerate(critical_results[:3], 1): # Show first 3 critical
471
+ console.print(f"[red]{i}. {result.finding_id}[/red]: {result.message}")
472
+ if result.recommendations:
473
+ console.print(f" [yellow]→ {result.recommendations[0]}[/yellow]")
474
+
475
+ if len(critical_results) > 3:
476
+ console.print(f" [dim]... and {len(critical_results) - 3} more critical findings[/dim]")
477
+
478
+ # Final recommendations
479
+ console.print(f"\n[bold]📝 Next Steps[/bold]")
480
+ if report.summary.critical_issues > 0:
481
+ console.print(f"[red]• Address {report.summary.critical_issues} critical security issues immediately[/red]")
482
+ if report.summary.failed_checks > 0:
483
+ console.print(f"[yellow]• Review and remediate {report.summary.failed_checks} failed checks[/yellow]")
484
+ if report.summary.pass_rate < 80:
485
+ console.print(
486
+ f"[yellow]• Improve overall compliance score (currently {report.summary.compliance_score}/100)[/yellow]"
487
+ )
488
+ else:
489
+ console.print(f"[green]• Maintain current security posture and continue monitoring[/green]")
490
+
491
+
492
+ # ============================================================================
493
+ # Inventory Commands
494
+ # ============================================================================
495
+
496
+
497
+ @main.group()
498
+ @click.pass_context
499
+ def inventory(ctx):
500
+ """Multi-account resource inventory and discovery."""
501
+ pass
502
+
503
+
504
+ @inventory.command()
505
+ @click.option("--resources", "-r", multiple=True, help="Resource types to inventory (ec2, rds, lambda, etc.)")
506
+ @click.option("--all-resources", is_flag=True, help="Inventory all resource types")
507
+ @click.option("--accounts", "-a", multiple=True, help="Account IDs to inventory")
508
+ @click.option("--all-accounts", is_flag=True, help="Inventory all organization accounts")
509
+ @click.option("--output", type=click.Choice(["table", "csv", "json", "excel"]), default="table", help="Output format")
510
+ @click.option("--output-file", type=click.Path(), help="Output file path")
511
+ @click.option("--include-costs", is_flag=True, help="Include cost information")
512
+ @click.option("--parallel", is_flag=True, default=True, help="Run in parallel")
513
+ @click.pass_context
514
+ def collect(ctx, resources, all_resources, accounts, all_accounts, output, output_file, include_costs, parallel):
515
+ """
516
+ Collect inventory of AWS resources across accounts.
517
+
518
+ Examples:
519
+ runbooks inventory collect --all-resources --output excel
520
+ runbooks inventory collect -r ec2 -r rds --accounts 123456789012
521
+ runbooks inventory collect --all-accounts --include-costs
522
+ """
523
+ logger.info("Starting resource inventory collection")
524
+
525
+ with console.status("[bold green]Collecting inventory...") as status:
526
+ try:
527
+ # Initialize inventory collector
528
+ collector = InventoryCollector(profile=ctx.obj["profile"], region=ctx.obj["region"], parallel=parallel)
529
+
530
+ # Configure resources
531
+ if all_resources:
532
+ resource_types = collector.get_all_resource_types()
533
+ elif resources:
534
+ resource_types = list(resources)
535
+ else:
536
+ resource_types = ["ec2", "rds", "s3", "lambda"] # Default set
537
+
538
+ # Configure accounts
539
+ if all_accounts:
540
+ account_ids = collector.get_organization_accounts()
541
+ elif accounts:
542
+ account_ids = list(accounts)
543
+ else:
544
+ account_ids = [collector.get_current_account_id()]
545
+
546
+ # Collect inventory
547
+ results = collector.collect_inventory(
548
+ resource_types=resource_types, account_ids=account_ids, include_costs=include_costs
549
+ )
550
+
551
+ # Generate output
552
+ if output == "table":
553
+ display_inventory_results(results)
554
+ else:
555
+ from runbooks.inventory.core.formatter import InventoryFormatter
556
+
557
+ formatter = InventoryFormatter(results)
558
+
559
+ if not output_file:
560
+ timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
561
+ output_file = f"inventory_{timestamp}.{output}"
562
+
563
+ if output == "csv":
564
+ formatter.to_csv(output_file)
565
+ elif output == "json":
566
+ formatter.to_json(output_file)
567
+ elif output == "excel":
568
+ formatter.to_excel(output_file)
569
+
570
+ console.print(f"[green]✓ Inventory saved to: {output_file}[/green]")
571
+
572
+ except Exception as e:
573
+ logger.error(f"Inventory collection failed: {e}")
574
+ console.print(f"[red]✗ Collection failed: {e}[/red]")
575
+ sys.exit(1)
576
+
577
+
578
+ # ============================================================================
579
+ # Organizations Commands
580
+ # ============================================================================
581
+
582
+
583
+ @main.group()
584
+ @click.pass_context
585
+ def org(ctx):
586
+ """AWS Organizations management and automation."""
587
+ pass
588
+
589
+
590
+ @org.command()
591
+ @click.option(
592
+ "--template",
593
+ type=click.Choice(["standard", "security", "custom"]),
594
+ default="standard",
595
+ help="OU structure template",
596
+ )
597
+ @click.option("--config-file", type=click.Path(exists=True), help="Custom OU structure configuration file")
598
+ @click.option("--dry-run", is_flag=True, help="Show what would be created")
599
+ @click.pass_context
600
+ def setup_ous(ctx, template, config_file, dry_run):
601
+ """
602
+ Set up organizational unit structure.
603
+
604
+ Creates a best-practice OU structure for AWS Organizations
605
+ based on Cloud Foundations recommendations.
606
+
607
+ Examples:
608
+ runbooks org setup-ous --template security
609
+ runbooks org setup-ous --config-file ou_structure.yaml --dry-run
610
+ """
611
+ logger.info(f"Setting up OU structure with template: {template}")
612
+
613
+ try:
614
+ manager = OUManager(profile=ctx.obj["profile"], region=ctx.obj["region"])
615
+
616
+ if config_file:
617
+ structure = manager.load_structure_from_file(config_file)
618
+ else:
619
+ structure = manager.get_template_structure(template)
620
+
621
+ if dry_run:
622
+ console.print("[yellow]DRY RUN - No changes will be made[/yellow]\n")
623
+ display_ou_structure(structure)
624
+ else:
625
+ with console.status("[bold green]Creating OU structure..."):
626
+ results = manager.create_ou_structure(structure)
627
+ display_creation_results(results)
628
+
629
+ except Exception as e:
630
+ logger.error(f"OU setup failed: {e}")
631
+ console.print(f"[red]✗ Setup failed: {e}[/red]")
632
+ sys.exit(1)
633
+
634
+
635
+ def display_inventory_results(results):
636
+ """Display inventory results in a formatted table."""
637
+ from runbooks.inventory.core.formatter import InventoryFormatter
638
+
639
+ formatter = InventoryFormatter(results)
640
+ console_output = formatter.format_console_table()
641
+ console.print(console_output)
642
+
643
+
644
+ def display_ou_structure(structure):
645
+ """Display OU structure in a formatted view."""
646
+ table = Table(title=f"OU Structure: {structure.get('name', 'Unnamed')}")
647
+ table.add_column("OU Name", style="cyan")
648
+ table.add_column("Description", style="dim")
649
+ table.add_column("Level", style="bold")
650
+
651
+ def add_ou_to_table(ou_def, level=0):
652
+ indent = " " * level
653
+ table.add_row(f"{indent}{ou_def['name']}", ou_def.get("description", ""), str(level))
654
+
655
+ for child in ou_def.get("children", []):
656
+ add_ou_to_table(child, level + 1)
657
+
658
+ for ou in structure.get("organizational_units", []):
659
+ add_ou_to_table(ou)
660
+
661
+ console.print(table)
662
+
663
+
664
+ def display_creation_results(results):
665
+ """Display OU creation results."""
666
+ table = Table(title="OU Creation Results")
667
+ table.add_column("OU Name", style="cyan")
668
+ table.add_column("OU ID", style="bold")
669
+ table.add_column("Parent ID", style="dim")
670
+ table.add_column("Status", style="green")
671
+
672
+ def add_results_to_table(ou_result, level=0):
673
+ indent = " " * level
674
+ table.add_row(f"{indent}{ou_result['name']}", ou_result["id"], ou_result["parent_id"], "✓ Created")
675
+
676
+ for child in ou_result.get("children", []):
677
+ add_results_to_table(child, level + 1)
678
+
679
+ for ou_result in results.get("created_ous", []):
680
+ add_results_to_table(ou_result)
681
+
682
+ console.print(table)
683
+
684
+ if results.get("errors"):
685
+ console.print("\n[red]Errors:[/red]")
686
+ for error in results["errors"]:
687
+ console.print(f" [red]✗ {error}[/red]")
688
+
689
+
690
+ # ============================================================================
691
+ # FinOps Commands
692
+ # ============================================================================
693
+
694
+
695
+ @main.group(invoke_without_command=True)
696
+ @click.option(
697
+ "--config-file",
698
+ "-C",
699
+ help="Path to a TOML, YAML, or JSON configuration file.",
700
+ type=str,
701
+ )
702
+ @click.option(
703
+ "--profiles",
704
+ "-p",
705
+ nargs="+",
706
+ help="Specific AWS profiles to use (space-separated)",
707
+ type=str,
708
+ )
709
+ @click.option(
710
+ "--regions",
711
+ "-r",
712
+ nargs="+",
713
+ help="AWS regions to check for EC2 instances (space-separated)",
714
+ type=str,
715
+ )
716
+ @click.option("--all", "-a", is_flag=True, help="Use all available AWS profiles")
717
+ @click.option(
718
+ "--combine",
719
+ "-c",
720
+ is_flag=True,
721
+ help="Combine profiles from the same AWS account",
722
+ )
723
+ @click.option(
724
+ "--report-name",
725
+ "-n",
726
+ help="Specify the base name for the report file (without extension)",
727
+ default=None,
728
+ type=str,
729
+ )
730
+ @click.option(
731
+ "--report-type",
732
+ "-y",
733
+ nargs="+",
734
+ choices=["csv", "json", "pdf"],
735
+ help="Specify one or more report types: csv and/or json and/or pdf (space-separated)",
736
+ type=str,
737
+ default=["csv"],
738
+ )
739
+ @click.option(
740
+ "--dir",
741
+ "-d",
742
+ help="Directory to save the report files (default: current directory)",
743
+ type=str,
744
+ )
745
+ @click.option(
746
+ "--time-range",
747
+ "-t",
748
+ help="Time range for cost data in days (default: current month). Examples: 7, 30, 90",
749
+ type=int,
750
+ )
751
+ @click.option(
752
+ "--tag",
753
+ "-g",
754
+ nargs="+",
755
+ help="Cost allocation tag to filter resources, e.g., --tag Team=DevOps",
756
+ type=str,
757
+ )
758
+ @click.option(
759
+ "--trend",
760
+ is_flag=True,
761
+ help="Display a trend report as bars for the past 6 months time range",
762
+ )
763
+ @click.option(
764
+ "--audit",
765
+ is_flag=True,
766
+ help="Display an audit report with cost anomalies, stopped EC2 instances, unused EBS volumes, budget alerts, and more",
767
+ )
768
+ @click.pass_context
769
+ def finops(ctx, **kwargs):
770
+ """AWS FinOps Dashboard - Cost and Resource Monitoring."""
771
+ if ctx.invoked_subcommand is None:
772
+ import argparse
773
+
774
+ from runbooks.finops.dashboard_runner import run_dashboard
775
+
776
+ args = argparse.Namespace(**kwargs)
777
+ run_dashboard(args)
778
+
779
+
780
+ # ============================================================================
781
+ # Main entry point - KISS principle: everything in one file
782
+ # ============================================================================
783
+
784
+ if __name__ == "__main__":
785
+ main()