runbooks 0.2.3__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.
- conftest.py +26 -0
- jupyter-agent/.env.template +2 -0
- jupyter-agent/.gitattributes +35 -0
- jupyter-agent/README.md +16 -0
- jupyter-agent/app.py +256 -0
- jupyter-agent/cloudops-agent.png +0 -0
- jupyter-agent/ds-system-prompt.txt +154 -0
- jupyter-agent/jupyter-agent.png +0 -0
- jupyter-agent/llama3_template.jinja +123 -0
- jupyter-agent/requirements.txt +9 -0
- jupyter-agent/utils.py +409 -0
- runbooks/__init__.py +71 -3
- runbooks/__main__.py +13 -0
- runbooks/aws/ec2_describe_instances.py +1 -1
- runbooks/aws/ec2_run_instances.py +8 -2
- runbooks/aws/ec2_start_stop_instances.py +17 -4
- runbooks/aws/ec2_unused_volumes.py +5 -1
- runbooks/aws/s3_create_bucket.py +4 -2
- runbooks/aws/s3_list_objects.py +6 -1
- runbooks/aws/tagging_lambda_handler.py +13 -2
- runbooks/aws/tags.json +12 -0
- runbooks/base.py +353 -0
- runbooks/cfat/README.md +49 -0
- runbooks/cfat/__init__.py +74 -0
- runbooks/cfat/app.ts +644 -0
- runbooks/cfat/assessment/__init__.py +40 -0
- runbooks/cfat/assessment/asana-import.csv +39 -0
- runbooks/cfat/assessment/cfat-checks.csv +31 -0
- runbooks/cfat/assessment/cfat.txt +520 -0
- runbooks/cfat/assessment/collectors.py +200 -0
- runbooks/cfat/assessment/jira-import.csv +39 -0
- runbooks/cfat/assessment/runner.py +387 -0
- runbooks/cfat/assessment/validators.py +290 -0
- runbooks/cfat/cli.py +103 -0
- runbooks/cfat/docs/asana-import.csv +24 -0
- runbooks/cfat/docs/cfat-checks.csv +31 -0
- runbooks/cfat/docs/cfat.txt +335 -0
- runbooks/cfat/docs/checks-output.png +0 -0
- runbooks/cfat/docs/cloudshell-console-run.png +0 -0
- runbooks/cfat/docs/cloudshell-download.png +0 -0
- runbooks/cfat/docs/cloudshell-output.png +0 -0
- runbooks/cfat/docs/downloadfile.png +0 -0
- runbooks/cfat/docs/jira-import.csv +24 -0
- runbooks/cfat/docs/open-cloudshell.png +0 -0
- runbooks/cfat/docs/report-header.png +0 -0
- runbooks/cfat/models.py +1026 -0
- runbooks/cfat/package-lock.json +5116 -0
- runbooks/cfat/package.json +38 -0
- runbooks/cfat/report.py +496 -0
- runbooks/cfat/reporting/__init__.py +46 -0
- runbooks/cfat/reporting/exporters.py +337 -0
- runbooks/cfat/reporting/formatters.py +496 -0
- runbooks/cfat/reporting/templates.py +135 -0
- runbooks/cfat/run-assessment.sh +23 -0
- runbooks/cfat/runner.py +69 -0
- runbooks/cfat/src/actions/check-cloudtrail-existence.ts +43 -0
- runbooks/cfat/src/actions/check-config-existence.ts +37 -0
- runbooks/cfat/src/actions/check-control-tower.ts +37 -0
- runbooks/cfat/src/actions/check-ec2-existence.ts +46 -0
- runbooks/cfat/src/actions/check-iam-users.ts +50 -0
- runbooks/cfat/src/actions/check-legacy-cur.ts +30 -0
- runbooks/cfat/src/actions/check-org-cloudformation.ts +30 -0
- runbooks/cfat/src/actions/check-vpc-existence.ts +43 -0
- runbooks/cfat/src/actions/create-asanaimport.ts +14 -0
- runbooks/cfat/src/actions/create-backlog.ts +372 -0
- runbooks/cfat/src/actions/create-jiraimport.ts +15 -0
- runbooks/cfat/src/actions/create-report.ts +616 -0
- runbooks/cfat/src/actions/define-account-type.ts +51 -0
- runbooks/cfat/src/actions/get-enabled-org-policy-types.ts +40 -0
- runbooks/cfat/src/actions/get-enabled-org-services.ts +26 -0
- runbooks/cfat/src/actions/get-idc-info.ts +34 -0
- runbooks/cfat/src/actions/get-org-da-accounts.ts +34 -0
- runbooks/cfat/src/actions/get-org-details.ts +35 -0
- runbooks/cfat/src/actions/get-org-member-accounts.ts +44 -0
- runbooks/cfat/src/actions/get-org-ous.ts +35 -0
- runbooks/cfat/src/actions/get-regions.ts +22 -0
- runbooks/cfat/src/actions/zip-assessment.ts +27 -0
- runbooks/cfat/src/types/index.d.ts +147 -0
- runbooks/cfat/tests/__init__.py +141 -0
- runbooks/cfat/tests/test_cli.py +340 -0
- runbooks/cfat/tests/test_integration.py +290 -0
- runbooks/cfat/tests/test_models.py +505 -0
- runbooks/cfat/tests/test_reporting.py +354 -0
- runbooks/cfat/tsconfig.json +16 -0
- runbooks/cfat/webpack.config.cjs +27 -0
- runbooks/config.py +260 -0
- runbooks/finops/__init__.py +88 -0
- runbooks/finops/aws_client.py +245 -0
- runbooks/finops/cli.py +151 -0
- runbooks/finops/cost_processor.py +410 -0
- runbooks/finops/dashboard_runner.py +448 -0
- runbooks/finops/helpers.py +355 -0
- runbooks/finops/main.py +14 -0
- runbooks/finops/profile_processor.py +174 -0
- runbooks/finops/types.py +66 -0
- runbooks/finops/visualisations.py +80 -0
- runbooks/inventory/.gitignore +354 -0
- runbooks/inventory/ArgumentsClass.py +261 -0
- runbooks/inventory/Inventory_Modules.py +6130 -0
- runbooks/inventory/LandingZone/delete_lz.py +1075 -0
- runbooks/inventory/README.md +1320 -0
- runbooks/inventory/__init__.py +62 -0
- runbooks/inventory/account_class.py +532 -0
- runbooks/inventory/all_my_instances_wrapper.py +123 -0
- runbooks/inventory/aws_decorators.py +201 -0
- runbooks/inventory/cfn_move_stack_instances.py +1526 -0
- runbooks/inventory/check_cloudtrail_compliance.py +614 -0
- runbooks/inventory/check_controltower_readiness.py +1107 -0
- runbooks/inventory/check_landingzone_readiness.py +711 -0
- runbooks/inventory/cloudtrail.md +727 -0
- runbooks/inventory/collectors/__init__.py +20 -0
- runbooks/inventory/collectors/aws_compute.py +518 -0
- runbooks/inventory/collectors/aws_networking.py +275 -0
- runbooks/inventory/collectors/base.py +222 -0
- runbooks/inventory/core/__init__.py +19 -0
- runbooks/inventory/core/collector.py +303 -0
- runbooks/inventory/core/formatter.py +296 -0
- runbooks/inventory/delete_s3_buckets_objects.py +169 -0
- runbooks/inventory/discovery.md +81 -0
- runbooks/inventory/draw_org_structure.py +748 -0
- runbooks/inventory/ec2_vpc_utils.py +341 -0
- runbooks/inventory/find_cfn_drift_detection.py +272 -0
- runbooks/inventory/find_cfn_orphaned_stacks.py +719 -0
- runbooks/inventory/find_cfn_stackset_drift.py +733 -0
- runbooks/inventory/find_ec2_security_groups.py +669 -0
- runbooks/inventory/find_landingzone_versions.py +201 -0
- runbooks/inventory/find_vpc_flow_logs.py +1221 -0
- runbooks/inventory/inventory.sh +659 -0
- runbooks/inventory/list_cfn_stacks.py +558 -0
- runbooks/inventory/list_cfn_stackset_operation_results.py +252 -0
- runbooks/inventory/list_cfn_stackset_operations.py +734 -0
- runbooks/inventory/list_cfn_stacksets.py +453 -0
- runbooks/inventory/list_config_recorders_delivery_channels.py +681 -0
- runbooks/inventory/list_ds_directories.py +354 -0
- runbooks/inventory/list_ec2_availability_zones.py +286 -0
- runbooks/inventory/list_ec2_ebs_volumes.py +244 -0
- runbooks/inventory/list_ec2_instances.py +425 -0
- runbooks/inventory/list_ecs_clusters_and_tasks.py +562 -0
- runbooks/inventory/list_elbs_load_balancers.py +411 -0
- runbooks/inventory/list_enis_network_interfaces.py +526 -0
- runbooks/inventory/list_guardduty_detectors.py +568 -0
- runbooks/inventory/list_iam_policies.py +404 -0
- runbooks/inventory/list_iam_roles.py +518 -0
- runbooks/inventory/list_iam_saml_providers.py +359 -0
- runbooks/inventory/list_lambda_functions.py +882 -0
- runbooks/inventory/list_org_accounts.py +446 -0
- runbooks/inventory/list_org_accounts_users.py +354 -0
- runbooks/inventory/list_rds_db_instances.py +406 -0
- runbooks/inventory/list_route53_hosted_zones.py +318 -0
- runbooks/inventory/list_servicecatalog_provisioned_products.py +575 -0
- runbooks/inventory/list_sns_topics.py +360 -0
- runbooks/inventory/list_ssm_parameters.py +402 -0
- runbooks/inventory/list_vpc_subnets.py +433 -0
- runbooks/inventory/list_vpcs.py +422 -0
- runbooks/inventory/lockdown_cfn_stackset_role.py +224 -0
- runbooks/inventory/models/__init__.py +24 -0
- runbooks/inventory/models/account.py +192 -0
- runbooks/inventory/models/inventory.py +309 -0
- runbooks/inventory/models/resource.py +247 -0
- runbooks/inventory/recover_cfn_stack_ids.py +205 -0
- runbooks/inventory/requirements.txt +12 -0
- runbooks/inventory/run_on_multi_accounts.py +211 -0
- runbooks/inventory/tests/common_test_data.py +3661 -0
- runbooks/inventory/tests/common_test_functions.py +204 -0
- runbooks/inventory/tests/script_test_data.py +0 -0
- runbooks/inventory/tests/setup.py +24 -0
- runbooks/inventory/tests/src.py +18 -0
- runbooks/inventory/tests/test_cfn_describe_stacks.py +208 -0
- runbooks/inventory/tests/test_ec2_describe_instances.py +162 -0
- runbooks/inventory/tests/test_inventory_modules.py +55 -0
- runbooks/inventory/tests/test_lambda_list_functions.py +86 -0
- runbooks/inventory/tests/test_moto_integration_example.py +273 -0
- runbooks/inventory/tests/test_org_list_accounts.py +49 -0
- runbooks/inventory/update_aws_actions.py +173 -0
- runbooks/inventory/update_cfn_stacksets.py +1215 -0
- runbooks/inventory/update_cloudwatch_logs_retention_policy.py +294 -0
- runbooks/inventory/update_iam_roles_cross_accounts.py +478 -0
- runbooks/inventory/update_s3_public_access_block.py +539 -0
- runbooks/inventory/utils/__init__.py +23 -0
- runbooks/inventory/utils/aws_helpers.py +510 -0
- runbooks/inventory/utils/threading_utils.py +493 -0
- runbooks/inventory/utils/validation.py +682 -0
- runbooks/inventory/verify_ec2_security_groups.py +1430 -0
- runbooks/main.py +785 -0
- runbooks/organizations/__init__.py +12 -0
- runbooks/organizations/manager.py +374 -0
- runbooks/security_baseline/README.md +324 -0
- runbooks/security_baseline/checklist/alternate_contacts.py +8 -1
- runbooks/security_baseline/checklist/bucket_public_access.py +4 -1
- runbooks/security_baseline/checklist/cloudwatch_alarm_configuration.py +9 -2
- runbooks/security_baseline/checklist/guardduty_enabled.py +9 -2
- runbooks/security_baseline/checklist/multi_region_instance_usage.py +5 -1
- runbooks/security_baseline/checklist/root_access_key.py +6 -1
- runbooks/security_baseline/config-origin.json +1 -1
- runbooks/security_baseline/config.json +1 -1
- runbooks/security_baseline/permission.json +1 -1
- runbooks/security_baseline/report_generator.py +10 -2
- runbooks/security_baseline/report_template_en.html +8 -8
- runbooks/security_baseline/report_template_jp.html +8 -8
- runbooks/security_baseline/report_template_kr.html +13 -13
- runbooks/security_baseline/report_template_vn.html +8 -8
- runbooks/security_baseline/requirements.txt +7 -0
- runbooks/security_baseline/run_script.py +8 -2
- runbooks/security_baseline/security_baseline_tester.py +10 -2
- runbooks/security_baseline/utils/common.py +5 -1
- runbooks/utils/__init__.py +204 -0
- runbooks-0.6.1.dist-info/METADATA +373 -0
- runbooks-0.6.1.dist-info/RECORD +237 -0
- {runbooks-0.2.3.dist-info → runbooks-0.6.1.dist-info}/WHEEL +1 -1
- runbooks-0.6.1.dist-info/entry_points.txt +7 -0
- runbooks-0.6.1.dist-info/licenses/LICENSE +201 -0
- runbooks-0.6.1.dist-info/top_level.txt +3 -0
- runbooks/python101/calculator.py +0 -34
- runbooks/python101/config.py +0 -1
- runbooks/python101/exceptions.py +0 -16
- runbooks/python101/file_manager.py +0 -218
- runbooks/python101/toolkit.py +0 -153
- runbooks-0.2.3.dist-info/METADATA +0 -435
- runbooks-0.2.3.dist-info/RECORD +0 -61
- runbooks-0.2.3.dist-info/entry_points.txt +0 -3
- runbooks-0.2.3.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()
|