runbooks 1.1.3__py3-none-any.whl → 1.1.5__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- runbooks/__init__.py +31 -2
- runbooks/__init___optimized.py +18 -4
- runbooks/_platform/__init__.py +1 -5
- runbooks/_platform/core/runbooks_wrapper.py +141 -138
- runbooks/aws2/accuracy_validator.py +812 -0
- runbooks/base.py +7 -0
- runbooks/cfat/WEIGHT_CONFIG_README.md +1 -1
- runbooks/cfat/assessment/compliance.py +8 -8
- runbooks/cfat/assessment/runner.py +1 -0
- runbooks/cfat/cloud_foundations_assessment.py +227 -239
- runbooks/cfat/models.py +6 -2
- runbooks/cfat/tests/__init__.py +6 -1
- runbooks/cli/__init__.py +13 -0
- runbooks/cli/commands/cfat.py +274 -0
- runbooks/cli/commands/finops.py +1164 -0
- runbooks/cli/commands/inventory.py +379 -0
- runbooks/cli/commands/operate.py +239 -0
- runbooks/cli/commands/security.py +248 -0
- runbooks/cli/commands/validation.py +825 -0
- runbooks/cli/commands/vpc.py +310 -0
- runbooks/cli/registry.py +107 -0
- runbooks/cloudops/__init__.py +23 -30
- runbooks/cloudops/base.py +96 -107
- runbooks/cloudops/cost_optimizer.py +549 -547
- runbooks/cloudops/infrastructure_optimizer.py +5 -4
- runbooks/cloudops/interfaces.py +226 -227
- runbooks/cloudops/lifecycle_manager.py +5 -4
- runbooks/cloudops/mcp_cost_validation.py +252 -235
- runbooks/cloudops/models.py +78 -53
- runbooks/cloudops/monitoring_automation.py +5 -4
- runbooks/cloudops/notebook_framework.py +179 -215
- runbooks/cloudops/security_enforcer.py +125 -159
- runbooks/common/accuracy_validator.py +11 -0
- runbooks/common/aws_pricing.py +349 -326
- runbooks/common/aws_pricing_api.py +211 -212
- runbooks/common/aws_profile_manager.py +341 -0
- runbooks/common/aws_utils.py +75 -80
- runbooks/common/business_logic.py +127 -105
- runbooks/common/cli_decorators.py +36 -60
- runbooks/common/comprehensive_cost_explorer_integration.py +456 -464
- runbooks/common/cross_account_manager.py +198 -205
- runbooks/common/date_utils.py +27 -39
- runbooks/common/decorators.py +235 -0
- runbooks/common/dry_run_examples.py +173 -208
- runbooks/common/dry_run_framework.py +157 -155
- runbooks/common/enhanced_exception_handler.py +15 -4
- runbooks/common/enhanced_logging_example.py +50 -64
- runbooks/common/enhanced_logging_integration_example.py +65 -37
- runbooks/common/env_utils.py +16 -16
- runbooks/common/error_handling.py +40 -38
- runbooks/common/lazy_loader.py +41 -23
- runbooks/common/logging_integration_helper.py +79 -86
- runbooks/common/mcp_cost_explorer_integration.py +478 -495
- runbooks/common/mcp_integration.py +63 -74
- runbooks/common/memory_optimization.py +140 -118
- runbooks/common/module_cli_base.py +37 -58
- runbooks/common/organizations_client.py +176 -194
- runbooks/common/patterns.py +204 -0
- runbooks/common/performance_monitoring.py +67 -71
- runbooks/common/performance_optimization_engine.py +283 -274
- runbooks/common/profile_utils.py +248 -39
- runbooks/common/rich_utils.py +643 -92
- runbooks/common/sre_performance_suite.py +177 -186
- runbooks/enterprise/__init__.py +1 -1
- runbooks/enterprise/logging.py +144 -106
- runbooks/enterprise/security.py +187 -204
- runbooks/enterprise/validation.py +43 -56
- runbooks/finops/__init__.py +29 -33
- runbooks/finops/account_resolver.py +1 -1
- runbooks/finops/advanced_optimization_engine.py +980 -0
- runbooks/finops/automation_core.py +268 -231
- runbooks/finops/business_case_config.py +184 -179
- runbooks/finops/cli.py +660 -139
- runbooks/finops/commvault_ec2_analysis.py +157 -164
- runbooks/finops/compute_cost_optimizer.py +336 -320
- runbooks/finops/config.py +20 -20
- runbooks/finops/cost_optimizer.py +488 -622
- runbooks/finops/cost_processor.py +332 -214
- runbooks/finops/dashboard_runner.py +1006 -172
- runbooks/finops/ebs_cost_optimizer.py +991 -657
- runbooks/finops/elastic_ip_optimizer.py +317 -257
- runbooks/finops/enhanced_mcp_integration.py +340 -0
- runbooks/finops/enhanced_progress.py +40 -37
- runbooks/finops/enhanced_trend_visualization.py +3 -2
- runbooks/finops/enterprise_wrappers.py +230 -292
- runbooks/finops/executive_export.py +203 -160
- runbooks/finops/helpers.py +130 -288
- runbooks/finops/iam_guidance.py +1 -1
- runbooks/finops/infrastructure/__init__.py +80 -0
- runbooks/finops/infrastructure/commands.py +506 -0
- runbooks/finops/infrastructure/load_balancer_optimizer.py +866 -0
- runbooks/finops/infrastructure/vpc_endpoint_optimizer.py +832 -0
- runbooks/finops/markdown_exporter.py +338 -175
- runbooks/finops/mcp_validator.py +1952 -0
- runbooks/finops/nat_gateway_optimizer.py +1513 -482
- runbooks/finops/network_cost_optimizer.py +657 -587
- runbooks/finops/notebook_utils.py +226 -188
- runbooks/finops/optimization_engine.py +1136 -0
- runbooks/finops/optimizer.py +25 -29
- runbooks/finops/rds_snapshot_optimizer.py +367 -411
- runbooks/finops/reservation_optimizer.py +427 -363
- runbooks/finops/scenario_cli_integration.py +77 -78
- runbooks/finops/scenarios.py +1278 -439
- runbooks/finops/schemas.py +218 -182
- runbooks/finops/snapshot_manager.py +2289 -0
- runbooks/finops/tests/test_finops_dashboard.py +3 -3
- runbooks/finops/tests/test_reference_images_validation.py +2 -2
- runbooks/finops/tests/test_single_account_features.py +17 -17
- runbooks/finops/tests/validate_test_suite.py +1 -1
- runbooks/finops/types.py +3 -3
- runbooks/finops/validation_framework.py +263 -269
- runbooks/finops/vpc_cleanup_exporter.py +191 -146
- runbooks/finops/vpc_cleanup_optimizer.py +593 -575
- runbooks/finops/workspaces_analyzer.py +171 -182
- runbooks/hitl/enhanced_workflow_engine.py +1 -1
- runbooks/integration/__init__.py +89 -0
- runbooks/integration/mcp_integration.py +1920 -0
- runbooks/inventory/CLAUDE.md +816 -0
- runbooks/inventory/README.md +3 -3
- runbooks/inventory/Tests/common_test_data.py +30 -30
- runbooks/inventory/__init__.py +2 -2
- runbooks/inventory/cloud_foundations_integration.py +144 -149
- runbooks/inventory/collectors/aws_comprehensive.py +28 -11
- runbooks/inventory/collectors/aws_networking.py +111 -101
- runbooks/inventory/collectors/base.py +4 -0
- runbooks/inventory/core/collector.py +495 -313
- runbooks/inventory/discovery.md +2 -2
- runbooks/inventory/drift_detection_cli.py +69 -96
- runbooks/inventory/find_ec2_security_groups.py +1 -1
- runbooks/inventory/inventory_mcp_cli.py +48 -46
- runbooks/inventory/list_rds_snapshots_aggregator.py +192 -208
- runbooks/inventory/mcp_inventory_validator.py +549 -465
- runbooks/inventory/mcp_vpc_validator.py +359 -442
- runbooks/inventory/organizations_discovery.py +56 -52
- runbooks/inventory/rich_inventory_display.py +33 -32
- runbooks/inventory/unified_validation_engine.py +278 -251
- runbooks/inventory/vpc_analyzer.py +733 -696
- runbooks/inventory/vpc_architecture_validator.py +293 -348
- runbooks/inventory/vpc_dependency_analyzer.py +382 -378
- runbooks/inventory/vpc_flow_analyzer.py +3 -3
- runbooks/main.py +152 -9147
- runbooks/main_final.py +91 -60
- runbooks/main_minimal.py +22 -10
- runbooks/main_optimized.py +131 -100
- runbooks/main_ultra_minimal.py +7 -2
- runbooks/mcp/__init__.py +36 -0
- runbooks/mcp/integration.py +679 -0
- runbooks/metrics/dora_metrics_engine.py +2 -2
- runbooks/monitoring/performance_monitor.py +9 -4
- runbooks/operate/dynamodb_operations.py +3 -1
- runbooks/operate/ec2_operations.py +145 -137
- runbooks/operate/iam_operations.py +146 -152
- runbooks/operate/mcp_integration.py +1 -1
- runbooks/operate/networking_cost_heatmap.py +33 -10
- runbooks/operate/privatelink_operations.py +1 -1
- runbooks/operate/rds_operations.py +223 -254
- runbooks/operate/s3_operations.py +107 -118
- runbooks/operate/vpc_endpoints.py +1 -1
- runbooks/operate/vpc_operations.py +648 -618
- runbooks/remediation/base.py +1 -1
- runbooks/remediation/commons.py +10 -7
- runbooks/remediation/commvault_ec2_analysis.py +71 -67
- runbooks/remediation/ec2_unattached_ebs_volumes.py +1 -0
- runbooks/remediation/multi_account.py +24 -21
- runbooks/remediation/rds_snapshot_list.py +91 -65
- runbooks/remediation/remediation_cli.py +92 -146
- runbooks/remediation/universal_account_discovery.py +83 -79
- runbooks/remediation/workspaces_list.py +49 -44
- runbooks/security/__init__.py +19 -0
- runbooks/security/assessment_runner.py +1150 -0
- runbooks/security/baseline_checker.py +812 -0
- runbooks/security/cloudops_automation_security_validator.py +509 -535
- runbooks/security/compliance_automation_engine.py +17 -17
- runbooks/security/config/__init__.py +2 -2
- runbooks/security/config/compliance_config.py +50 -50
- runbooks/security/config_template_generator.py +63 -76
- runbooks/security/enterprise_security_framework.py +1 -1
- runbooks/security/executive_security_dashboard.py +519 -508
- runbooks/security/integration_test_enterprise_security.py +5 -3
- runbooks/security/multi_account_security_controls.py +959 -1210
- runbooks/security/real_time_security_monitor.py +422 -444
- runbooks/security/run_script.py +1 -1
- runbooks/security/security_baseline_tester.py +1 -1
- runbooks/security/security_cli.py +143 -112
- runbooks/security/test_2way_validation.py +439 -0
- runbooks/security/two_way_validation_framework.py +852 -0
- runbooks/sre/mcp_reliability_engine.py +6 -6
- runbooks/sre/production_monitoring_framework.py +167 -177
- runbooks/tdd/__init__.py +15 -0
- runbooks/tdd/cli.py +1071 -0
- runbooks/utils/__init__.py +14 -17
- runbooks/utils/logger.py +7 -2
- runbooks/utils/version_validator.py +51 -48
- runbooks/validation/__init__.py +6 -6
- runbooks/validation/cli.py +9 -3
- runbooks/validation/comprehensive_2way_validator.py +754 -708
- runbooks/validation/mcp_validator.py +906 -228
- runbooks/validation/terraform_citations_validator.py +104 -115
- runbooks/validation/terraform_drift_detector.py +447 -451
- runbooks/vpc/README.md +617 -0
- runbooks/vpc/__init__.py +8 -1
- runbooks/vpc/analyzer.py +577 -0
- runbooks/vpc/cleanup_wrapper.py +476 -413
- runbooks/vpc/cli_cloudtrail_commands.py +339 -0
- runbooks/vpc/cli_mcp_validation_commands.py +480 -0
- runbooks/vpc/cloudtrail_audit_integration.py +717 -0
- runbooks/vpc/config.py +92 -97
- runbooks/vpc/cost_engine.py +411 -148
- runbooks/vpc/cost_explorer_integration.py +553 -0
- runbooks/vpc/cross_account_session.py +101 -106
- runbooks/vpc/enhanced_mcp_validation.py +917 -0
- runbooks/vpc/eni_gate_validator.py +961 -0
- runbooks/vpc/heatmap_engine.py +190 -162
- runbooks/vpc/mcp_no_eni_validator.py +681 -640
- runbooks/vpc/nat_gateway_optimizer.py +358 -0
- runbooks/vpc/networking_wrapper.py +15 -8
- runbooks/vpc/pdca_remediation_planner.py +528 -0
- runbooks/vpc/performance_optimized_analyzer.py +219 -231
- runbooks/vpc/runbooks_adapter.py +1167 -241
- runbooks/vpc/tdd_red_phase_stubs.py +601 -0
- runbooks/vpc/test_data_loader.py +358 -0
- runbooks/vpc/tests/conftest.py +314 -4
- runbooks/vpc/tests/test_cleanup_framework.py +1022 -0
- runbooks/vpc/tests/test_cost_engine.py +0 -2
- runbooks/vpc/topology_generator.py +326 -0
- runbooks/vpc/unified_scenarios.py +1302 -1129
- runbooks/vpc/vpc_cleanup_integration.py +1943 -1115
- runbooks-1.1.5.dist-info/METADATA +328 -0
- {runbooks-1.1.3.dist-info → runbooks-1.1.5.dist-info}/RECORD +233 -200
- runbooks/finops/README.md +0 -414
- runbooks/finops/accuracy_cross_validator.py +0 -647
- runbooks/finops/business_cases.py +0 -950
- runbooks/finops/dashboard_router.py +0 -922
- runbooks/finops/ebs_optimizer.py +0 -956
- runbooks/finops/embedded_mcp_validator.py +0 -1629
- runbooks/finops/enhanced_dashboard_runner.py +0 -527
- runbooks/finops/finops_dashboard.py +0 -584
- runbooks/finops/finops_scenarios.py +0 -1218
- runbooks/finops/legacy_migration.py +0 -730
- runbooks/finops/multi_dashboard.py +0 -1519
- runbooks/finops/single_dashboard.py +0 -1113
- runbooks/finops/unlimited_scenarios.py +0 -393
- runbooks-1.1.3.dist-info/METADATA +0 -799
- {runbooks-1.1.3.dist-info → runbooks-1.1.5.dist-info}/WHEEL +0 -0
- {runbooks-1.1.3.dist-info → runbooks-1.1.5.dist-info}/entry_points.txt +0 -0
- {runbooks-1.1.3.dist-info → runbooks-1.1.5.dist-info}/licenses/LICENSE +0 -0
- {runbooks-1.1.3.dist-info → runbooks-1.1.5.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,1164 @@
|
|
1
|
+
"""
|
2
|
+
FinOps Commands Module - Financial Operations & Cost Optimization
|
3
|
+
|
4
|
+
KISS Principle: Focused on financial operations and cost optimization
|
5
|
+
DRY Principle: Uses centralized patterns from DRYPatternManager
|
6
|
+
|
7
|
+
Phase 2 Enhancement: Eliminates pattern duplication through reference-based access.
|
8
|
+
Context Efficiency: Reduced imports and shared instances for memory optimization.
|
9
|
+
"""
|
10
|
+
|
11
|
+
# Essential imports that can't be centralized due to decorator usage
|
12
|
+
import click
|
13
|
+
|
14
|
+
# DRY Pattern Manager - eliminates duplication across CLI modules
|
15
|
+
from runbooks.common.patterns import get_console, get_error_handlers, get_click_group_creator, get_common_decorators
|
16
|
+
|
17
|
+
# Import common utilities and decorators
|
18
|
+
from runbooks.common.decorators import common_aws_options
|
19
|
+
|
20
|
+
# Single console instance shared across all modules (DRY principle)
|
21
|
+
console = get_console()
|
22
|
+
|
23
|
+
# Import additional modules for enhanced functionality
|
24
|
+
from runbooks.common.rich_utils import print_header, print_success, print_error, print_info
|
25
|
+
|
26
|
+
# Centralized error handlers - replaces 6 duplicate patterns in this module
|
27
|
+
error_handlers = get_error_handlers()
|
28
|
+
|
29
|
+
|
30
|
+
def _get_cost_metric_display(cost_metrics):
|
31
|
+
"""Get display string for cost metrics."""
|
32
|
+
if len(cost_metrics) == 1:
|
33
|
+
return cost_metrics[0]
|
34
|
+
else:
|
35
|
+
return " + ".join(cost_metrics)
|
36
|
+
|
37
|
+
|
38
|
+
def create_finops_group():
|
39
|
+
"""
|
40
|
+
Create the finops command group with all subcommands.
|
41
|
+
|
42
|
+
Returns:
|
43
|
+
Click Group object with all finops commands
|
44
|
+
|
45
|
+
Performance: Lazy creation only when needed by DRYCommandRegistry
|
46
|
+
Context Reduction: ~800 lines extracted from main.py
|
47
|
+
"""
|
48
|
+
|
49
|
+
@click.group(invoke_without_command=True)
|
50
|
+
@common_aws_options
|
51
|
+
@click.pass_context
|
52
|
+
def finops(ctx, profile, region, dry_run):
|
53
|
+
"""
|
54
|
+
Financial operations and cost optimization for AWS resources.
|
55
|
+
|
56
|
+
Comprehensive cost analysis, budget management, and financial reporting
|
57
|
+
with enterprise-grade accuracy and multi-format export capabilities.
|
58
|
+
|
59
|
+
Features:
|
60
|
+
• Real-time cost analysis with MCP validation (≥99.5% accuracy)
|
61
|
+
• Multi-format exports: CSV, JSON, PDF, Markdown
|
62
|
+
• Quarterly intelligence with strategic financial reporting
|
63
|
+
• Enterprise AWS profile support with multi-account capabilities
|
64
|
+
|
65
|
+
Examples:
|
66
|
+
runbooks finops dashboard --profile billing-profile
|
67
|
+
runbooks finops analyze --service ec2 --timeframe monthly
|
68
|
+
runbooks finops export --format pdf --output-dir ./reports
|
69
|
+
"""
|
70
|
+
# Ensure context object exists
|
71
|
+
if ctx.obj is None:
|
72
|
+
ctx.obj = {}
|
73
|
+
ctx.obj.update({"profile": profile, "region": region, "dry_run": dry_run})
|
74
|
+
|
75
|
+
if ctx.invoked_subcommand is None:
|
76
|
+
click.echo(ctx.get_help())
|
77
|
+
|
78
|
+
@finops.command()
|
79
|
+
@click.option("--profile", help="AWS profile to use for authentication")
|
80
|
+
@click.option(
|
81
|
+
"--all-profiles", help="Enable multi-account Landing Zone analysis using specified management profile"
|
82
|
+
)
|
83
|
+
@click.option(
|
84
|
+
"--timeframe",
|
85
|
+
type=click.Choice(["daily", "weekly", "monthly", "quarterly"]),
|
86
|
+
default="monthly",
|
87
|
+
help="Analysis timeframe",
|
88
|
+
)
|
89
|
+
@click.option("--services", multiple=True, help="Specific AWS services to analyze")
|
90
|
+
@click.option("--accounts", multiple=True, help="Specific AWS accounts to analyze")
|
91
|
+
@click.option("--validate", is_flag=True, help="Enable MCP validation for accuracy")
|
92
|
+
@click.option("--validate-mcp", is_flag=True, help="Run standalone MCP validation framework (AWS-2 implementation)")
|
93
|
+
@click.option("--mcp-validate", is_flag=True, help="Enable MCP validation for ≥99.5% accuracy cross-validation")
|
94
|
+
@click.option("--csv", is_flag=True, help="Export results to CSV format")
|
95
|
+
@click.option("--markdown", is_flag=True, help="Export results to Markdown format")
|
96
|
+
@click.option("--pdf", is_flag=True, help="Export results to PDF format")
|
97
|
+
@click.option("--json", is_flag=True, help="Export results to JSON format")
|
98
|
+
@click.option(
|
99
|
+
"--export-format",
|
100
|
+
type=click.Choice(["json", "csv", "pdf", "markdown"]),
|
101
|
+
help="Export format for results (legacy option - use individual flags)",
|
102
|
+
)
|
103
|
+
@click.option("--unblended", is_flag=True, help="Use unblended cost metrics (default: BlendedCost)")
|
104
|
+
@click.option("--amortized", is_flag=True, help="Use amortized cost metrics for Reserved Instances")
|
105
|
+
@click.option("--dual-metrics", is_flag=True, help="Show both BlendedCost and AmortizedCost")
|
106
|
+
@click.option("--dry-run", is_flag=True, default=True, help="Execute in dry-run mode")
|
107
|
+
@click.pass_context
|
108
|
+
def dashboard(
|
109
|
+
ctx,
|
110
|
+
profile,
|
111
|
+
all_profiles,
|
112
|
+
timeframe,
|
113
|
+
services,
|
114
|
+
accounts,
|
115
|
+
validate,
|
116
|
+
validate_mcp,
|
117
|
+
mcp_validate,
|
118
|
+
csv,
|
119
|
+
markdown,
|
120
|
+
pdf,
|
121
|
+
json,
|
122
|
+
export_format,
|
123
|
+
unblended,
|
124
|
+
amortized,
|
125
|
+
dual_metrics,
|
126
|
+
dry_run,
|
127
|
+
):
|
128
|
+
"""
|
129
|
+
Generate comprehensive cost analysis dashboard.
|
130
|
+
|
131
|
+
Enterprise Features:
|
132
|
+
• MCP validation with ≥99.5% accuracy
|
133
|
+
• Multi-account Landing Zone consolidated billing analysis
|
134
|
+
• Organizational unit hierarchy and cost allocation
|
135
|
+
• Rich CLI formatting for executive presentations
|
136
|
+
• Multi-format exports for stakeholder consumption
|
137
|
+
|
138
|
+
Examples:
|
139
|
+
# Single account analysis
|
140
|
+
runbooks finops dashboard --profile BILLING_PROFILE --timeframe monthly --validate
|
141
|
+
|
142
|
+
# Multi-account Landing Zone analysis
|
143
|
+
runbooks finops dashboard --all-profiles MANAGEMENT_PROFILE --mcp-validate
|
144
|
+
|
145
|
+
# Service-specific analysis across all accounts
|
146
|
+
runbooks finops dashboard --all-profiles MANAGEMENT_PROFILE --services ec2,s3
|
147
|
+
|
148
|
+
# Export multi-account analysis
|
149
|
+
runbooks finops dashboard --all-profiles MANAGEMENT_PROFILE --export-format pdf
|
150
|
+
"""
|
151
|
+
# Handle multi-account Landing Zone analysis
|
152
|
+
if all_profiles:
|
153
|
+
try:
|
154
|
+
from runbooks.finops.dashboard_runner import MultiAccountDashboard, DashboardRouter
|
155
|
+
from runbooks.common.rich_utils import print_header, print_success, print_error, print_info
|
156
|
+
import argparse
|
157
|
+
|
158
|
+
print_header("Multi-Account Landing Zone Dashboard", all_profiles)
|
159
|
+
console.print("[cyan]🏢 Activating enterprise multi-account consolidated billing analysis[/cyan]")
|
160
|
+
console.print(f"[dim]Management Profile: {all_profiles}[/dim]")
|
161
|
+
console.print(f"[dim]Analysis Scope: Organization-wide with Landing Zone support[/dim]\n")
|
162
|
+
|
163
|
+
# Create mock args object for multi-dashboard compatibility
|
164
|
+
args = argparse.Namespace()
|
165
|
+
args.profile = all_profiles
|
166
|
+
args.timeframe = timeframe
|
167
|
+
args.services = services
|
168
|
+
args.accounts = accounts
|
169
|
+
args.validate = validate or mcp_validate
|
170
|
+
# CRITICAL FIX: Handle multiple export format flags
|
171
|
+
export_formats = []
|
172
|
+
if csv:
|
173
|
+
export_formats.append("csv")
|
174
|
+
if markdown:
|
175
|
+
export_formats.append("markdown")
|
176
|
+
if pdf:
|
177
|
+
export_formats.append("pdf")
|
178
|
+
if json:
|
179
|
+
export_formats.append("json")
|
180
|
+
if export_format and export_format not in export_formats:
|
181
|
+
export_formats.append(export_format)
|
182
|
+
|
183
|
+
args.export_format = export_formats[0] if export_formats else None
|
184
|
+
args.export_formats = export_formats # Store all requested formats
|
185
|
+
|
186
|
+
# CRITICAL FIX: Handle cost metric options
|
187
|
+
cost_metrics = ["BlendedCost"] # Default metric
|
188
|
+
if unblended:
|
189
|
+
cost_metrics = ["UnblendedCost"]
|
190
|
+
elif amortized:
|
191
|
+
cost_metrics = ["AmortizedCost"]
|
192
|
+
elif dual_metrics:
|
193
|
+
cost_metrics = ["BlendedCost", "AmortizedCost"]
|
194
|
+
|
195
|
+
args.cost_metrics = cost_metrics
|
196
|
+
args.cost_metric_display = _get_cost_metric_display(cost_metrics)
|
197
|
+
args.dry_run = dry_run
|
198
|
+
args.all = True # Enable all accounts mode
|
199
|
+
args.top_accounts = 50 # Show many accounts for enterprise view
|
200
|
+
args.services_per_account = 3
|
201
|
+
args.time_range = None
|
202
|
+
args.tag = None
|
203
|
+
args.regions = None
|
204
|
+
|
205
|
+
# Initialize router and dashboard
|
206
|
+
router = DashboardRouter(console=console)
|
207
|
+
routing_config = router.route_dashboard_request(args)
|
208
|
+
|
209
|
+
# Create multi-account dashboard
|
210
|
+
multi_dashboard = MultiAccountDashboard(console=console)
|
211
|
+
|
212
|
+
# Execute multi-account analysis
|
213
|
+
result = multi_dashboard.run_dashboard(args, routing_config)
|
214
|
+
|
215
|
+
if result == 0:
|
216
|
+
print_success("Multi-account Landing Zone analysis completed successfully")
|
217
|
+
else:
|
218
|
+
print_error("Multi-account analysis encountered issues")
|
219
|
+
|
220
|
+
return result
|
221
|
+
|
222
|
+
except ImportError as e:
|
223
|
+
console.print(f"[red]❌ Multi-account dashboard not available: {e}[/red]")
|
224
|
+
console.print("[yellow]💡 Falling back to single-account mode with specified profile[/yellow]")
|
225
|
+
# Fallback to single account with the specified profile
|
226
|
+
resolved_profile = all_profiles
|
227
|
+
except Exception as e:
|
228
|
+
console.print(f"[red]❌ Multi-account analysis failed: {e}[/red]")
|
229
|
+
console.print("[yellow]💡 Falling back to single-account mode[/yellow]")
|
230
|
+
resolved_profile = all_profiles
|
231
|
+
else:
|
232
|
+
resolved_profile = profile or ctx.obj.get("profile", "default")
|
233
|
+
|
234
|
+
# Handle standalone MCP validation (AWS-2 implementation)
|
235
|
+
if validate_mcp:
|
236
|
+
try:
|
237
|
+
from runbooks.common.rich_utils import print_header, print_success, print_error, print_info
|
238
|
+
import asyncio
|
239
|
+
|
240
|
+
print_header("MCP Validation Framework", "AWS-2 Implementation")
|
241
|
+
console.print("[cyan]🔍 Running comprehensive MCP validation for ≥99.5% accuracy[/cyan]")
|
242
|
+
|
243
|
+
# Import and initialize MCP validator
|
244
|
+
from runbooks.validation.mcp_validator import MCPValidator
|
245
|
+
|
246
|
+
# Enterprise profiles configuration
|
247
|
+
validation_profiles = {
|
248
|
+
"billing": "ams-admin-Billing-ReadOnlyAccess-909135376185",
|
249
|
+
"management": "ams-admin-ReadOnlyAccess-909135376185",
|
250
|
+
"centralised_ops": "ams-centralised-ops-ReadOnlyAccess-335083429030",
|
251
|
+
"single_aws": "ams-shared-services-non-prod-ReadOnlyAccess-499201730520",
|
252
|
+
}
|
253
|
+
|
254
|
+
# Initialize validator with configured profiles
|
255
|
+
validator = MCPValidator(
|
256
|
+
profiles=validation_profiles, tolerance_percentage=5.0, performance_target_seconds=30.0
|
257
|
+
)
|
258
|
+
|
259
|
+
# Run comprehensive validation
|
260
|
+
validation_report = asyncio.run(validator.validate_all_operations())
|
261
|
+
|
262
|
+
# Success criteria for AWS-2
|
263
|
+
if validation_report.overall_accuracy >= 99.5:
|
264
|
+
print_success(
|
265
|
+
f"✅ AWS-2 SUCCESS: {validation_report.overall_accuracy:.1f}% ≥ 99.5% target achieved"
|
266
|
+
)
|
267
|
+
return 0
|
268
|
+
else:
|
269
|
+
print_error(f"❌ AWS-2 FAILED: {validation_report.overall_accuracy:.1f}% < 99.5% target")
|
270
|
+
return 1
|
271
|
+
|
272
|
+
except Exception as e:
|
273
|
+
print_error(f"❌ AWS-2 MCP validation failed: {e}")
|
274
|
+
return 1
|
275
|
+
|
276
|
+
try:
|
277
|
+
from runbooks.common.rich_utils import print_header, print_success, print_error, create_table, format_cost
|
278
|
+
from runbooks.common.profile_utils import create_cost_session
|
279
|
+
from runbooks.finops.cost_processor import get_cost_data
|
280
|
+
from runbooks.finops.aws_client import get_account_id, ec2_summary, get_accessible_regions
|
281
|
+
import boto3
|
282
|
+
from datetime import datetime, timedelta
|
283
|
+
from rich.table import Table
|
284
|
+
from rich.panel import Panel
|
285
|
+
|
286
|
+
# Resolve profile with priority: command --profile > parent context > default
|
287
|
+
# Note: resolved_profile already set above for multi-account vs single-account mode
|
288
|
+
if "resolved_profile" not in locals():
|
289
|
+
resolved_profile = profile or ctx.obj.get("profile", "default")
|
290
|
+
resolved_dry_run = dry_run if dry_run is not None else ctx.obj.get("dry_run", True)
|
291
|
+
|
292
|
+
# MCP validation integration
|
293
|
+
mcp_results = None
|
294
|
+
if mcp_validate or validate:
|
295
|
+
try:
|
296
|
+
from runbooks.validation.mcp_validator import MCPValidator
|
297
|
+
import asyncio
|
298
|
+
|
299
|
+
console.print("[cyan]🔍 Running MCP validation for dashboard data accuracy[/cyan]")
|
300
|
+
|
301
|
+
# Configure validation profiles using resolved profile
|
302
|
+
validation_profiles = {
|
303
|
+
"billing": resolved_profile,
|
304
|
+
"management": resolved_profile,
|
305
|
+
"centralised_ops": resolved_profile,
|
306
|
+
"single_aws": resolved_profile,
|
307
|
+
}
|
308
|
+
|
309
|
+
# Initialize validator
|
310
|
+
validator = MCPValidator(
|
311
|
+
profiles=validation_profiles, tolerance_percentage=5.0, performance_target_seconds=30.0
|
312
|
+
)
|
313
|
+
|
314
|
+
# Run validation focused on cost explorer operations (primary finops validation)
|
315
|
+
mcp_results = asyncio.run(validator.validate_cost_explorer())
|
316
|
+
|
317
|
+
# Display validation results
|
318
|
+
if mcp_results.accuracy_percentage >= 99.5:
|
319
|
+
console.print(
|
320
|
+
f"[green]✅ MCP Validation PASSED: {mcp_results.accuracy_percentage:.1f}% accuracy[/green]"
|
321
|
+
)
|
322
|
+
elif mcp_results.accuracy_percentage >= 95.0:
|
323
|
+
console.print(
|
324
|
+
f"[yellow]⚠️ MCP Validation WARNING: {mcp_results.accuracy_percentage:.1f}% accuracy (target: ≥99.5%)[/yellow]"
|
325
|
+
)
|
326
|
+
else:
|
327
|
+
console.print(
|
328
|
+
f"[red]❌ MCP Validation FAILED: {mcp_results.accuracy_percentage:.1f}% accuracy[/red]"
|
329
|
+
)
|
330
|
+
|
331
|
+
except Exception as e:
|
332
|
+
console.print(f"[yellow]⚠️ MCP validation failed: {e}[/yellow]")
|
333
|
+
console.print("[dim]Continuing with dashboard generation...[/dim]")
|
334
|
+
|
335
|
+
print_header("FinOps Cost Analysis Dashboard", resolved_profile)
|
336
|
+
|
337
|
+
# Create AWS session and get account info
|
338
|
+
session = create_cost_session(profile_name=resolved_profile)
|
339
|
+
account_id = get_account_id(session)
|
340
|
+
|
341
|
+
console.print(f"[cyan]📊 Analyzing costs for AWS Account: {account_id}[/cyan]\n")
|
342
|
+
|
343
|
+
# Get cost data for the specified timeframe
|
344
|
+
try:
|
345
|
+
# Calculate time range based on timeframe
|
346
|
+
time_range_days = {"daily": 7, "weekly": 30, "monthly": 90, "quarterly": 365}.get(timeframe, 30)
|
347
|
+
|
348
|
+
# Get comprehensive cost data
|
349
|
+
cost_data = get_cost_data(
|
350
|
+
session,
|
351
|
+
time_range=time_range_days,
|
352
|
+
get_trend=True,
|
353
|
+
profile_name=resolved_profile,
|
354
|
+
account_id=account_id,
|
355
|
+
)
|
356
|
+
|
357
|
+
# Display Cost Summary Table
|
358
|
+
cost_table = create_table(title="💰 Cost Analysis Summary")
|
359
|
+
cost_table.add_column("Metric", style="bold")
|
360
|
+
cost_table.add_column("Value", style="cyan")
|
361
|
+
|
362
|
+
# Access cost data using correct field names from CostData TypedDict
|
363
|
+
current_cost = cost_data.get("current_month", 0)
|
364
|
+
previous_cost = cost_data.get("last_month", 0)
|
365
|
+
|
366
|
+
cost_table.add_row("Current Monthly Spend", f"${current_cost:,.2f}")
|
367
|
+
cost_table.add_row("Previous Month", f"${previous_cost:,.2f}")
|
368
|
+
|
369
|
+
# CRITICAL FIX: Enhanced month-over-month calculation for partial months
|
370
|
+
if previous_cost > 0:
|
371
|
+
change_pct = ((current_cost - previous_cost) / previous_cost) * 100
|
372
|
+
|
373
|
+
# Check if current month is partial
|
374
|
+
from datetime import datetime
|
375
|
+
|
376
|
+
today = datetime.now()
|
377
|
+
current_day = today.day
|
378
|
+
|
379
|
+
# Add context for partial month comparisons
|
380
|
+
if current_day < 28: # Likely partial month
|
381
|
+
# Calculate daily average for comparison
|
382
|
+
daily_current = current_cost / current_day
|
383
|
+
daily_previous = previous_cost / 30 # Assume 30-day month
|
384
|
+
daily_change_pct = ((daily_current - daily_previous) / daily_previous) * 100
|
385
|
+
|
386
|
+
change_str = f"{change_pct:+.1f}% (Daily Avg: {daily_change_pct:+.1f}%)"
|
387
|
+
cost_table.add_row("Month-over-Month Change", f"[yellow]{change_str}[/yellow]")
|
388
|
+
cost_table.add_row(
|
389
|
+
"Comparison Note", f"[dim]Partial month ({current_day} days vs full previous month)[/dim]"
|
390
|
+
)
|
391
|
+
else:
|
392
|
+
# Full month comparison
|
393
|
+
change_str = f"{change_pct:+.1f}%"
|
394
|
+
if change_pct > 10:
|
395
|
+
change_str = f"[red]{change_str} ⚠️[/red]"
|
396
|
+
elif change_pct < -5:
|
397
|
+
change_str = f"[green]{change_str} ✅[/green]"
|
398
|
+
else:
|
399
|
+
change_str = f"[yellow]{change_str}[/yellow]"
|
400
|
+
cost_table.add_row("Month-over-Month Change", change_str)
|
401
|
+
|
402
|
+
cost_table.add_row("Account ID", account_id)
|
403
|
+
cost_table.add_row("Analysis Period", f"{timeframe.title()} ({time_range_days} days)")
|
404
|
+
console.print(cost_table)
|
405
|
+
console.print()
|
406
|
+
|
407
|
+
# Display Top Services by Cost
|
408
|
+
services_data = cost_data.get("costs_by_service", {})
|
409
|
+
if services_data:
|
410
|
+
services_table = create_table(title="🏗️ Top AWS Services by Cost")
|
411
|
+
services_table.add_column("Service", style="bold")
|
412
|
+
services_table.add_column("Cost", style="green")
|
413
|
+
services_table.add_column("% of Total", style="yellow")
|
414
|
+
|
415
|
+
# Sort services by cost and show top 10
|
416
|
+
sorted_services = sorted(services_data.items(), key=lambda x: x[1], reverse=True)[:10]
|
417
|
+
|
418
|
+
for service, cost in sorted_services:
|
419
|
+
percentage = (cost / current_cost * 100) if current_cost > 0 else 0
|
420
|
+
services_table.add_row(service, f"${cost:,.2f}", f"{percentage:.1f}%")
|
421
|
+
|
422
|
+
console.print(services_table)
|
423
|
+
console.print()
|
424
|
+
|
425
|
+
# Get EC2 resource summary for optimization opportunities
|
426
|
+
try:
|
427
|
+
ec2_data = ec2_summary(session, profile_name=resolved_profile)
|
428
|
+
|
429
|
+
resources_table = create_table(title="💡 Optimization Opportunities")
|
430
|
+
resources_table.add_column("Resource Type", style="bold")
|
431
|
+
resources_table.add_column("Count", style="cyan")
|
432
|
+
resources_table.add_column("Potential Action", style="yellow")
|
433
|
+
|
434
|
+
# EC2Summary is a Dict[str, int], so access it accordingly
|
435
|
+
total_instances = ec2_data.get("total_instances", 0)
|
436
|
+
running_instances = ec2_data.get("running_instances", 0)
|
437
|
+
stopped_instances = ec2_data.get("stopped_instances", 0)
|
438
|
+
|
439
|
+
# Add EC2 optimization opportunities
|
440
|
+
if total_instances > 0:
|
441
|
+
resources_table.add_row("Total EC2 Instances", str(total_instances), "Review rightsizing")
|
442
|
+
resources_table.add_row("Running Instances", str(running_instances), "Monitor utilization")
|
443
|
+
resources_table.add_row("Stopped Instances", str(stopped_instances), "Consider termination")
|
444
|
+
|
445
|
+
# Calculate potential savings estimates
|
446
|
+
if stopped_instances > 0:
|
447
|
+
# Estimate $100/month per stopped instance for EBS storage costs
|
448
|
+
stopped_savings = stopped_instances * 100
|
449
|
+
resources_table.add_row(
|
450
|
+
"Stopped Instance Savings", f"~${stopped_savings}/month", "Terminate unused instances"
|
451
|
+
)
|
452
|
+
|
453
|
+
if running_instances > 5:
|
454
|
+
# Estimate 20% rightsizing opportunity
|
455
|
+
ec2_cost_estimate = services_data.get("Amazon Elastic Compute Cloud - Compute", 0)
|
456
|
+
rightsizing_savings = ec2_cost_estimate * 0.20
|
457
|
+
if rightsizing_savings > 100:
|
458
|
+
resources_table.add_row(
|
459
|
+
"EC2 Rightsizing Potential",
|
460
|
+
f"~${rightsizing_savings:.0f}/month",
|
461
|
+
"Rightsize overprovisioned instances",
|
462
|
+
)
|
463
|
+
|
464
|
+
else:
|
465
|
+
resources_table.add_row("EC2 Instances", "0", "No instances found in accessible regions")
|
466
|
+
|
467
|
+
console.print(resources_table)
|
468
|
+
console.print()
|
469
|
+
|
470
|
+
except Exception as e:
|
471
|
+
console.print(f"[yellow]⚠️ Could not fetch EC2 optimization data: {e}[/yellow]\n")
|
472
|
+
|
473
|
+
# Display Business Impact Summary
|
474
|
+
total_annual = current_cost * 12
|
475
|
+
optimization_potential = total_annual * 0.15 # Conservative 15% optimization target
|
476
|
+
|
477
|
+
business_panel = Panel(
|
478
|
+
f"""[bold]Annual Spend Projection:[/bold] ${total_annual:,.2f}
|
479
|
+
[bold]Conservative Optimization Target (15%):[/bold] ${optimization_potential:,.2f}
|
480
|
+
[bold]Recommended Actions:[/bold]
|
481
|
+
• Review EC2 instance rightsizing opportunities
|
482
|
+
• Analyze unused resources and storage
|
483
|
+
• Implement automated cost monitoring
|
484
|
+
• Set up budget alerts for cost control
|
485
|
+
|
486
|
+
[dim]Analysis Period: {timeframe.title()} view | Account: {account_id}[/dim]""",
|
487
|
+
title="💼 Business Impact Summary",
|
488
|
+
border_style="blue",
|
489
|
+
)
|
490
|
+
console.print(business_panel)
|
491
|
+
|
492
|
+
# Prepare results dictionary
|
493
|
+
results = {
|
494
|
+
"status": "completed",
|
495
|
+
"account_id": account_id,
|
496
|
+
"timeframe": timeframe,
|
497
|
+
"cost_analysis": {
|
498
|
+
"current_monthly_spend": current_cost,
|
499
|
+
"previous_monthly_spend": previous_cost,
|
500
|
+
"annual_projection": total_annual,
|
501
|
+
"optimization_potential": optimization_potential,
|
502
|
+
"top_services": dict(sorted_services[:5]) if services_data else {},
|
503
|
+
"ec2_summary": {
|
504
|
+
"total_instances": total_instances if "total_instances" in locals() else 0,
|
505
|
+
"running_instances": running_instances if "running_instances" in locals() else 0,
|
506
|
+
"stopped_instances": stopped_instances if "stopped_instances" in locals() else 0,
|
507
|
+
},
|
508
|
+
},
|
509
|
+
}
|
510
|
+
|
511
|
+
# Attach MCP validation results if available
|
512
|
+
if mcp_results:
|
513
|
+
results["mcp_validation"] = {
|
514
|
+
"accuracy_percentage": mcp_results.accuracy_percentage,
|
515
|
+
"validation_passed": mcp_results.accuracy_percentage >= 99.5,
|
516
|
+
"operation_name": mcp_results.operation_name,
|
517
|
+
"status": mcp_results.status.value,
|
518
|
+
"detailed_results": mcp_results,
|
519
|
+
}
|
520
|
+
|
521
|
+
return results
|
522
|
+
|
523
|
+
except Exception as e:
|
524
|
+
print_error(f"Failed to retrieve cost data: {e}")
|
525
|
+
console.print(
|
526
|
+
f"[yellow]💡 Tip: Ensure your AWS profile '{resolved_profile}' has Cost Explorer permissions[/yellow]"
|
527
|
+
)
|
528
|
+
console.print(f"[dim]Required permissions: ce:GetCostAndUsage, ce:GetDimensionValues[/dim]")
|
529
|
+
raise
|
530
|
+
|
531
|
+
except ImportError as e:
|
532
|
+
error_handlers["module_not_available"]("FinOps dashboard", e)
|
533
|
+
raise click.ClickException("FinOps dashboard functionality not available")
|
534
|
+
except Exception as e:
|
535
|
+
error_handlers["operation_failed"]("FinOps dashboard generation", e)
|
536
|
+
raise click.ClickException(str(e))
|
537
|
+
|
538
|
+
@finops.command()
|
539
|
+
@click.option(
|
540
|
+
"--resource-type",
|
541
|
+
type=click.Choice(["ec2", "s3", "rds", "lambda", "vpc"]),
|
542
|
+
required=True,
|
543
|
+
help="Resource type for optimization analysis",
|
544
|
+
)
|
545
|
+
@click.option(
|
546
|
+
"--savings-target", type=click.FloatRange(0.1, 0.8), default=0.3, help="Target savings percentage (0.1-0.8)"
|
547
|
+
)
|
548
|
+
@click.option(
|
549
|
+
"--analysis-depth",
|
550
|
+
type=click.Choice(["basic", "comprehensive", "enterprise"]),
|
551
|
+
default="comprehensive",
|
552
|
+
help="Analysis depth level",
|
553
|
+
)
|
554
|
+
@click.option("--mcp-validate", is_flag=True, help="Enable MCP validation for ≥99.5% accuracy cross-validation")
|
555
|
+
@click.pass_context
|
556
|
+
def optimize(ctx, resource_type, savings_target, analysis_depth, mcp_validate):
|
557
|
+
"""
|
558
|
+
Generate cost optimization recommendations for specific resource types.
|
559
|
+
|
560
|
+
Enterprise Optimization Features:
|
561
|
+
• Safety-first analysis with READ-ONLY operations
|
562
|
+
• Quantified savings projections with ROI analysis
|
563
|
+
• Risk assessment and business impact evaluation
|
564
|
+
• Implementation timeline and priority recommendations
|
565
|
+
|
566
|
+
Examples:
|
567
|
+
runbooks finops optimize --resource-type ec2 --savings-target 0.25
|
568
|
+
runbooks finops optimize --resource-type s3 --analysis-depth enterprise
|
569
|
+
"""
|
570
|
+
try:
|
571
|
+
from runbooks.finops.optimization_engine import ResourceOptimizer
|
572
|
+
|
573
|
+
# MCP validation integration for optimization accuracy
|
574
|
+
mcp_results = None
|
575
|
+
if mcp_validate:
|
576
|
+
try:
|
577
|
+
from runbooks.validation.mcp_validator import MCPValidator
|
578
|
+
import asyncio
|
579
|
+
|
580
|
+
console.print(f"[cyan]🔍 Running MCP validation for {resource_type} optimization accuracy[/cyan]")
|
581
|
+
|
582
|
+
# Configure validation profiles
|
583
|
+
validation_profiles = {
|
584
|
+
"billing": ctx.obj.get("profile", "default"),
|
585
|
+
"management": ctx.obj.get("profile", "default"),
|
586
|
+
"centralised_ops": ctx.obj.get("profile", "default"),
|
587
|
+
"single_aws": ctx.obj.get("profile", "default"),
|
588
|
+
}
|
589
|
+
|
590
|
+
# Initialize validator
|
591
|
+
validator = MCPValidator(
|
592
|
+
profiles=validation_profiles, tolerance_percentage=5.0, performance_target_seconds=30.0
|
593
|
+
)
|
594
|
+
|
595
|
+
# Run validation based on resource type
|
596
|
+
if resource_type in ["ec2"]:
|
597
|
+
mcp_results = asyncio.run(validator.validate_ec2_inventory())
|
598
|
+
elif resource_type in ["vpc"]:
|
599
|
+
mcp_results = asyncio.run(validator.validate_vpc_analysis())
|
600
|
+
elif resource_type in ["s3", "rds", "lambda"]:
|
601
|
+
# For these resource types, use cost explorer validation
|
602
|
+
mcp_results = asyncio.run(validator.validate_cost_explorer())
|
603
|
+
else:
|
604
|
+
# Default to cost explorer validation
|
605
|
+
mcp_results = asyncio.run(validator.validate_cost_explorer())
|
606
|
+
|
607
|
+
# Display validation results
|
608
|
+
if mcp_results.accuracy_percentage >= 99.5:
|
609
|
+
console.print(
|
610
|
+
f"[green]✅ MCP Validation PASSED: {mcp_results.accuracy_percentage:.1f}% accuracy for {resource_type}[/green]"
|
611
|
+
)
|
612
|
+
elif mcp_results.accuracy_percentage >= 95.0:
|
613
|
+
console.print(
|
614
|
+
f"[yellow]⚠️ MCP Validation WARNING: {mcp_results.accuracy_percentage:.1f}% accuracy (target: ≥99.5%)[/yellow]"
|
615
|
+
)
|
616
|
+
else:
|
617
|
+
console.print(
|
618
|
+
f"[red]❌ MCP Validation FAILED: {mcp_results.accuracy_percentage:.1f}% accuracy[/red]"
|
619
|
+
)
|
620
|
+
|
621
|
+
except Exception as e:
|
622
|
+
console.print(f"[yellow]⚠️ MCP validation failed: {e}[/yellow]")
|
623
|
+
console.print("[dim]Continuing with optimization analysis...[/dim]")
|
624
|
+
|
625
|
+
optimizer = ResourceOptimizer(
|
626
|
+
profile=ctx.obj["profile"],
|
627
|
+
region=ctx.obj["region"],
|
628
|
+
resource_type=resource_type,
|
629
|
+
savings_target=savings_target,
|
630
|
+
analysis_depth=analysis_depth,
|
631
|
+
mcp_validate=mcp_validate,
|
632
|
+
)
|
633
|
+
|
634
|
+
optimization_results = optimizer.analyze_optimization_opportunities()
|
635
|
+
|
636
|
+
# Attach MCP validation results if available
|
637
|
+
if mcp_results and isinstance(optimization_results, dict):
|
638
|
+
optimization_results["mcp_validation"] = {
|
639
|
+
"accuracy_percentage": mcp_results.accuracy_percentage,
|
640
|
+
"validation_passed": mcp_results.accuracy_percentage >= 99.5,
|
641
|
+
"resource_type": resource_type,
|
642
|
+
"operation_name": mcp_results.operation_name,
|
643
|
+
"status": mcp_results.status.value,
|
644
|
+
"detailed_results": mcp_results,
|
645
|
+
}
|
646
|
+
|
647
|
+
return optimization_results
|
648
|
+
|
649
|
+
except ImportError as e:
|
650
|
+
error_handlers["module_not_available"]("FinOps optimization", e)
|
651
|
+
raise click.ClickException("FinOps optimization functionality not available")
|
652
|
+
except Exception as e:
|
653
|
+
error_handlers["operation_failed"]("FinOps optimization analysis", e)
|
654
|
+
raise click.ClickException(str(e))
|
655
|
+
|
656
|
+
@finops.command()
|
657
|
+
@click.option(
|
658
|
+
"--format",
|
659
|
+
"export_format",
|
660
|
+
type=click.Choice(["csv", "json", "pdf", "markdown"]),
|
661
|
+
multiple=True,
|
662
|
+
default=["json"],
|
663
|
+
help="Export formats",
|
664
|
+
)
|
665
|
+
@click.option("--output-dir", default="./finops_reports", help="Output directory for exports")
|
666
|
+
@click.option("--include-quarterly", is_flag=True, help="Include quarterly intelligence data")
|
667
|
+
@click.option("--executive-summary", is_flag=True, help="Generate executive summary format")
|
668
|
+
@click.option("--mcp-validate", is_flag=True, help="Enable MCP validation for ≥99.5% accuracy cross-validation")
|
669
|
+
@click.pass_context
|
670
|
+
def export(ctx, export_format, output_dir, include_quarterly, executive_summary, mcp_validate):
|
671
|
+
"""
|
672
|
+
Export financial analysis results in multiple formats.
|
673
|
+
|
674
|
+
Enterprise Export Features:
|
675
|
+
• Multi-format simultaneous export
|
676
|
+
• Executive-ready formatting and presentation
|
677
|
+
• Quarterly intelligence integration
|
678
|
+
• Complete audit trail documentation
|
679
|
+
|
680
|
+
Examples:
|
681
|
+
runbooks finops export --format csv,pdf --executive-summary
|
682
|
+
runbooks finops export --include-quarterly --output-dir ./executive_reports
|
683
|
+
"""
|
684
|
+
try:
|
685
|
+
from runbooks.finops.export_manager import FinOpsExportManager
|
686
|
+
|
687
|
+
# MCP validation integration for export accuracy
|
688
|
+
mcp_results = None
|
689
|
+
if mcp_validate:
|
690
|
+
try:
|
691
|
+
from runbooks.validation.mcp_validator import MCPValidator
|
692
|
+
import asyncio
|
693
|
+
|
694
|
+
console.print("[cyan]🔍 Running MCP validation for export data accuracy[/cyan]")
|
695
|
+
|
696
|
+
# Configure validation profiles
|
697
|
+
validation_profiles = {
|
698
|
+
"billing": ctx.obj.get("profile", "default"),
|
699
|
+
"management": ctx.obj.get("profile", "default"),
|
700
|
+
"centralised_ops": ctx.obj.get("profile", "default"),
|
701
|
+
"single_aws": ctx.obj.get("profile", "default"),
|
702
|
+
}
|
703
|
+
|
704
|
+
# Initialize validator
|
705
|
+
validator = MCPValidator(
|
706
|
+
profiles=validation_profiles, tolerance_percentage=5.0, performance_target_seconds=30.0
|
707
|
+
)
|
708
|
+
|
709
|
+
# Run validation for export data accuracy using cost explorer validation
|
710
|
+
mcp_results = asyncio.run(validator.validate_cost_explorer())
|
711
|
+
|
712
|
+
# Display validation results
|
713
|
+
if mcp_results.accuracy_percentage >= 99.5:
|
714
|
+
console.print(
|
715
|
+
f"[green]✅ MCP Validation PASSED: {mcp_results.accuracy_percentage:.1f}% accuracy for exports[/green]"
|
716
|
+
)
|
717
|
+
elif mcp_results.accuracy_percentage >= 95.0:
|
718
|
+
console.print(
|
719
|
+
f"[yellow]⚠️ MCP Validation WARNING: {mcp_results.accuracy_percentage:.1f}% accuracy (target: ≥99.5%)[/yellow]"
|
720
|
+
)
|
721
|
+
else:
|
722
|
+
console.print(
|
723
|
+
f"[red]❌ MCP Validation FAILED: {mcp_results.accuracy_percentage:.1f}% accuracy[/red]"
|
724
|
+
)
|
725
|
+
|
726
|
+
except Exception as e:
|
727
|
+
console.print(f"[yellow]⚠️ MCP validation failed: {e}[/yellow]")
|
728
|
+
console.print("[dim]Continuing with export operation...[/dim]")
|
729
|
+
|
730
|
+
export_manager = FinOpsExportManager(
|
731
|
+
profile=ctx.obj["profile"],
|
732
|
+
output_dir=output_dir,
|
733
|
+
include_quarterly=include_quarterly,
|
734
|
+
executive_summary=executive_summary,
|
735
|
+
mcp_validate=mcp_validate,
|
736
|
+
)
|
737
|
+
|
738
|
+
export_results = {}
|
739
|
+
for format_type in export_format:
|
740
|
+
result = export_manager.export_analysis(format=format_type)
|
741
|
+
export_results[format_type] = result
|
742
|
+
|
743
|
+
# Attach MCP validation results if available
|
744
|
+
if mcp_results:
|
745
|
+
export_results["mcp_validation"] = {
|
746
|
+
"accuracy_percentage": mcp_results.accuracy_percentage,
|
747
|
+
"validation_passed": mcp_results.accuracy_percentage >= 99.5,
|
748
|
+
"export_formats": list(export_format),
|
749
|
+
"operation_name": mcp_results.operation_name,
|
750
|
+
"status": mcp_results.status.value,
|
751
|
+
"detailed_results": mcp_results,
|
752
|
+
}
|
753
|
+
|
754
|
+
error_handlers["success"](
|
755
|
+
f"Successfully exported to {len(export_format)} format(s)", f"Output directory: {output_dir}"
|
756
|
+
)
|
757
|
+
|
758
|
+
return export_results
|
759
|
+
|
760
|
+
except ImportError as e:
|
761
|
+
error_handlers["module_not_available"]("FinOps export", e)
|
762
|
+
raise click.ClickException("FinOps export functionality not available")
|
763
|
+
except Exception as e:
|
764
|
+
error_handlers["operation_failed"]("FinOps export operation", e)
|
765
|
+
raise click.ClickException(str(e))
|
766
|
+
|
767
|
+
@finops.command()
|
768
|
+
@click.option(
|
769
|
+
"--older-than-days", type=int, default=90, help="Minimum age in days for cleanup consideration (default: 90)"
|
770
|
+
)
|
771
|
+
@click.option(
|
772
|
+
"--validate", is_flag=True, default=True, help="Enable MCP validation for ≥99.5% accuracy (default: enabled)"
|
773
|
+
)
|
774
|
+
@click.option("--cleanup", is_flag=True, help="Enable cleanup recommendations (READ-ONLY analysis only)")
|
775
|
+
@click.option("--export-results", is_flag=True, help="Export analysis results to JSON file")
|
776
|
+
@click.option(
|
777
|
+
"--safety-checks/--no-safety-checks",
|
778
|
+
default=True,
|
779
|
+
help="Enable comprehensive safety validations (default: enabled)",
|
780
|
+
)
|
781
|
+
@click.option("--all-profiles", help="Use specified profile for all operations (overrides parent --profile)")
|
782
|
+
@click.pass_context
|
783
|
+
def ec2_snapshots(ctx, older_than_days, validate, cleanup, export_results, safety_checks, all_profiles):
|
784
|
+
"""
|
785
|
+
EC2 snapshot cost optimization and cleanup analysis.
|
786
|
+
|
787
|
+
Sprint 1, Task 1: Analyze EC2 snapshots for cost optimization opportunities
|
788
|
+
targeting $50K+ annual savings through systematic age-based cleanup with
|
789
|
+
enterprise safety validations and MCP accuracy frameworks.
|
790
|
+
|
791
|
+
Enterprise Features:
|
792
|
+
• Multi-account snapshot discovery via AWS Config aggregator
|
793
|
+
• Dynamic pricing via AWS Pricing API for accurate cost calculations
|
794
|
+
• MCP validation framework with ≥99.5% accuracy cross-validation
|
795
|
+
• Comprehensive safety checks (volume attachment, AMI association, age)
|
796
|
+
• Executive reporting with Sprint 1 business impact metrics
|
797
|
+
|
798
|
+
Safety Features:
|
799
|
+
• READ-ONLY analysis by default (no actual cleanup performed)
|
800
|
+
• Volume attachment verification before recommendations
|
801
|
+
• AMI association checking to prevent data loss
|
802
|
+
• Configurable age thresholds with safety validations
|
803
|
+
|
804
|
+
Examples:
|
805
|
+
# Basic analysis with MCP validation using parent profile
|
806
|
+
runbooks finops --profile BILLING_PROFILE ec2-snapshots --validate
|
807
|
+
|
808
|
+
# Override parent profile with command-specific profile
|
809
|
+
runbooks finops ec2-snapshots --all-profiles BILLING_PROFILE --validate
|
810
|
+
|
811
|
+
# Custom age threshold with export
|
812
|
+
runbooks finops --profile BILLING_PROFILE ec2-snapshots --older-than-days 120 --export-results
|
813
|
+
|
814
|
+
# Comprehensive analysis for Sprint 1
|
815
|
+
runbooks finops --profile BILLING_PROFILE ec2-snapshots --cleanup --validate --export-results
|
816
|
+
|
817
|
+
# Quick analysis without safety checks (not recommended)
|
818
|
+
runbooks finops ec2-snapshots --all-profiles BILLING_PROFILE --no-safety-checks --older-than-days 30
|
819
|
+
|
820
|
+
Sprint 1 Context:
|
821
|
+
Task 1 targeting $50K+ annual savings through systematic snapshot cleanup
|
822
|
+
with enterprise coordination and MCP validation accuracy ≥99.5%
|
823
|
+
"""
|
824
|
+
try:
|
825
|
+
import asyncio
|
826
|
+
from runbooks.finops.snapshot_manager import EC2SnapshotManager
|
827
|
+
|
828
|
+
console.print("\n[bold blue]🎯 Sprint 1, Task 1: EC2 Snapshot Cost Optimization[/bold blue]")
|
829
|
+
|
830
|
+
# Resolve profile with priority: --all-profiles > ctx.obj['profile'] > 'default'
|
831
|
+
resolved_profile = all_profiles or ctx.obj.get("profile", "default")
|
832
|
+
resolved_region = ctx.obj.get("region", "all")
|
833
|
+
resolved_dry_run = ctx.obj.get("dry_run", True)
|
834
|
+
|
835
|
+
# Validate profile resolution
|
836
|
+
if not resolved_profile:
|
837
|
+
console.print("[red]❌ Error: No AWS profile specified or found[/red]")
|
838
|
+
console.print("[yellow]💡 Use --all-profiles PROFILE_NAME or set parent --profile option[/yellow]")
|
839
|
+
raise click.ClickException("AWS profile required for ec2-snapshots command")
|
840
|
+
|
841
|
+
console.print(
|
842
|
+
f"[dim]Profile: {resolved_profile} | Region: {resolved_region} | Dry-run: {resolved_dry_run}[/dim]\n"
|
843
|
+
)
|
844
|
+
|
845
|
+
# Initialize snapshot manager with enterprise configuration
|
846
|
+
manager = EC2SnapshotManager(profile=resolved_profile, dry_run=resolved_dry_run)
|
847
|
+
|
848
|
+
# Configure safety checks based on user preference
|
849
|
+
if not safety_checks:
|
850
|
+
console.print("[yellow]⚠️ Safety checks disabled - use with caution[/yellow]")
|
851
|
+
manager.safety_checks = {
|
852
|
+
"volume_attachment_check": False,
|
853
|
+
"ami_association_check": False,
|
854
|
+
"minimum_age_check": True, # Always keep age check for safety
|
855
|
+
"cross_account_validation": False,
|
856
|
+
}
|
857
|
+
|
858
|
+
# Run the main analysis using the enhanced method
|
859
|
+
async def run_analysis():
|
860
|
+
return await manager.analyze_snapshot_opportunities(
|
861
|
+
profile=resolved_profile,
|
862
|
+
older_than_days=older_than_days,
|
863
|
+
enable_mcp_validation=validate,
|
864
|
+
export_results=export_results,
|
865
|
+
)
|
866
|
+
|
867
|
+
# Execute analysis
|
868
|
+
results = asyncio.run(run_analysis())
|
869
|
+
|
870
|
+
# Sprint 1 success validation
|
871
|
+
annual_savings = results["cost_analysis"]["annual_savings"]
|
872
|
+
sprint_target = 50000 # $50K Sprint 1 target
|
873
|
+
|
874
|
+
if annual_savings >= sprint_target:
|
875
|
+
console.print(f"\n[bold green]✅ Sprint 1 Task 1 SUCCESS![/bold green]")
|
876
|
+
console.print(f"[green]Target: ${sprint_target:,} | Achieved: ${annual_savings:,.2f}[/green]")
|
877
|
+
else:
|
878
|
+
console.print(f"\n[bold yellow]⚠️ Sprint 1 Task 1 - Below Target[/bold yellow]")
|
879
|
+
console.print(f"[yellow]Target: ${sprint_target:,} | Achieved: ${annual_savings:,.2f}[/yellow]")
|
880
|
+
|
881
|
+
# MCP validation status for Sprint 1
|
882
|
+
if validate and results.get("mcp_validation"):
|
883
|
+
mcp_results = results["mcp_validation"]
|
884
|
+
if mcp_results["validation_passed"]:
|
885
|
+
console.print(
|
886
|
+
f"[green]✅ MCP Validation: {mcp_results['accuracy_percentage']:.2f}% accuracy[/green]"
|
887
|
+
)
|
888
|
+
else:
|
889
|
+
console.print(
|
890
|
+
f"[red]❌ MCP Validation: {mcp_results['accuracy_percentage']:.2f}% accuracy (Required: ≥99.5%)[/red]"
|
891
|
+
)
|
892
|
+
|
893
|
+
# Enterprise coordination confirmation
|
894
|
+
console.print(f"\n[dim]🏢 Enterprise coordination: python-runbooks-engineer [1] (Primary)[/dim]")
|
895
|
+
console.print(f"[dim]🎯 Sprint coordination: Systematic delegation activated[/dim]")
|
896
|
+
|
897
|
+
return results
|
898
|
+
|
899
|
+
except ImportError as e:
|
900
|
+
error_handlers["module_not_available"]("EC2 Snapshot Manager", e)
|
901
|
+
raise click.ClickException("EC2 snapshot analysis functionality not available")
|
902
|
+
except Exception as e:
|
903
|
+
error_handlers["operation_failed"]("EC2 snapshot analysis", e)
|
904
|
+
raise click.ClickException(str(e))
|
905
|
+
|
906
|
+
# Epic 2 Infrastructure Optimization Commands
|
907
|
+
@finops.group()
|
908
|
+
def infrastructure():
|
909
|
+
"""Epic 2 Infrastructure Optimization - $210,147 annual savings target"""
|
910
|
+
pass
|
911
|
+
|
912
|
+
@infrastructure.command()
|
913
|
+
@click.option(
|
914
|
+
"--components",
|
915
|
+
multiple=True,
|
916
|
+
type=click.Choice(["nat-gateway", "elastic-ip", "load-balancer", "vpc-endpoint"]),
|
917
|
+
help="Infrastructure components to analyze (default: all)",
|
918
|
+
)
|
919
|
+
@click.option(
|
920
|
+
"--export-format",
|
921
|
+
type=click.Choice(["json", "csv", "markdown"]),
|
922
|
+
default="json",
|
923
|
+
help="Export format for results",
|
924
|
+
)
|
925
|
+
@click.option("--output-file", help="Output file path for results export")
|
926
|
+
@click.option("--mcp-validate", is_flag=True, help="Enable MCP validation for ≥99.5% accuracy cross-validation")
|
927
|
+
@click.pass_context
|
928
|
+
def analyze(ctx, components, export_format, output_file, mcp_validate):
|
929
|
+
"""
|
930
|
+
Comprehensive Infrastructure Optimization Analysis - Epic 2
|
931
|
+
|
932
|
+
Analyze all infrastructure components to achieve $210,147 Epic 2 annual savings target:
|
933
|
+
• NAT Gateway optimization: $147,420 target
|
934
|
+
• Elastic IP optimization: $21,593 target
|
935
|
+
• Load Balancer optimization: $35,280 target
|
936
|
+
• VPC Endpoint optimization: $5,854 target
|
937
|
+
|
938
|
+
SAFETY: READ-ONLY analysis only - no resource modifications.
|
939
|
+
|
940
|
+
Examples:
|
941
|
+
runbooks finops infrastructure analyze
|
942
|
+
runbooks finops infrastructure analyze --components nat-gateway load-balancer
|
943
|
+
"""
|
944
|
+
try:
|
945
|
+
import asyncio
|
946
|
+
from runbooks.finops.infrastructure.commands import InfrastructureOptimizer
|
947
|
+
|
948
|
+
# MCP validation integration for infrastructure analysis
|
949
|
+
mcp_results = None
|
950
|
+
if mcp_validate:
|
951
|
+
try:
|
952
|
+
from runbooks.validation.mcp_validator import MCPValidator
|
953
|
+
|
954
|
+
console.print("[cyan]🔍 Running MCP validation for infrastructure optimization accuracy[/cyan]")
|
955
|
+
|
956
|
+
# Configure validation profiles
|
957
|
+
validation_profiles = {
|
958
|
+
"billing": ctx.obj.get("profile", "default"),
|
959
|
+
"management": ctx.obj.get("profile", "default"),
|
960
|
+
"centralised_ops": ctx.obj.get("profile", "default"),
|
961
|
+
"single_aws": ctx.obj.get("profile", "default"),
|
962
|
+
}
|
963
|
+
|
964
|
+
# Initialize validator
|
965
|
+
validator = MCPValidator(
|
966
|
+
profiles=validation_profiles, tolerance_percentage=5.0, performance_target_seconds=30.0
|
967
|
+
)
|
968
|
+
|
969
|
+
# Run validation for infrastructure operations using VPC validation for networking components
|
970
|
+
component_types = (
|
971
|
+
list(components)
|
972
|
+
if components
|
973
|
+
else ["nat-gateway", "elastic-ip", "load-balancer", "vpc-endpoint"]
|
974
|
+
)
|
975
|
+
if any(comp in ["nat-gateway", "vpc-endpoint"] for comp in component_types):
|
976
|
+
mcp_results = asyncio.run(validator.validate_vpc_analysis())
|
977
|
+
elif any(comp in ["elastic-ip"] for comp in component_types):
|
978
|
+
mcp_results = asyncio.run(validator.validate_ec2_inventory())
|
979
|
+
else:
|
980
|
+
# Default to cost explorer for load balancer cost analysis
|
981
|
+
mcp_results = asyncio.run(validator.validate_cost_explorer())
|
982
|
+
|
983
|
+
# Display validation results
|
984
|
+
if mcp_results.accuracy_percentage >= 99.5:
|
985
|
+
console.print(
|
986
|
+
f"[green]✅ MCP Validation PASSED: {mcp_results.accuracy_percentage:.1f}% accuracy for infrastructure[/green]"
|
987
|
+
)
|
988
|
+
elif mcp_results.accuracy_percentage >= 95.0:
|
989
|
+
console.print(
|
990
|
+
f"[yellow]⚠️ MCP Validation WARNING: {mcp_results.accuracy_percentage:.1f}% accuracy (target: ≥99.5%)[/yellow]"
|
991
|
+
)
|
992
|
+
else:
|
993
|
+
console.print(
|
994
|
+
f"[red]❌ MCP Validation FAILED: {mcp_results.accuracy_percentage:.1f}% accuracy[/red]"
|
995
|
+
)
|
996
|
+
|
997
|
+
except Exception as e:
|
998
|
+
console.print(f"[yellow]⚠️ MCP validation failed: {e}[/yellow]")
|
999
|
+
console.print("[dim]Continuing with infrastructure analysis...[/dim]")
|
1000
|
+
|
1001
|
+
# Initialize comprehensive optimizer
|
1002
|
+
optimizer = InfrastructureOptimizer(
|
1003
|
+
profile_name=ctx.obj.get("profile"),
|
1004
|
+
regions=[ctx.obj.get("region")] if ctx.obj.get("region") else None,
|
1005
|
+
mcp_validate=mcp_validate,
|
1006
|
+
)
|
1007
|
+
|
1008
|
+
# Execute comprehensive analysis
|
1009
|
+
results = asyncio.run(
|
1010
|
+
optimizer.analyze_comprehensive_infrastructure(
|
1011
|
+
components=list(components) if components else None, dry_run=ctx.obj.get("dry_run", True)
|
1012
|
+
)
|
1013
|
+
)
|
1014
|
+
|
1015
|
+
# Attach MCP validation results if available
|
1016
|
+
if mcp_results and hasattr(results, "__dict__"):
|
1017
|
+
results.mcp_validation = {
|
1018
|
+
"accuracy_percentage": mcp_results.accuracy_percentage,
|
1019
|
+
"validation_passed": mcp_results.accuracy_percentage >= 99.5,
|
1020
|
+
"components_validated": list(components) if components else "all",
|
1021
|
+
"operation_name": mcp_results.operation_name,
|
1022
|
+
"status": mcp_results.status.value,
|
1023
|
+
"detailed_results": mcp_results,
|
1024
|
+
}
|
1025
|
+
|
1026
|
+
# Display Epic 2 progress
|
1027
|
+
if results.epic_2_target_achieved:
|
1028
|
+
console.print(f"\n[bold green]✅ Epic 2 Infrastructure Target Achieved![/bold green]")
|
1029
|
+
console.print(
|
1030
|
+
f"[green]Target: ${results.epic_2_target_savings:,.0f} | Achieved: ${results.total_potential_savings:,.0f}[/green]"
|
1031
|
+
)
|
1032
|
+
else:
|
1033
|
+
progress_pct = results.epic_2_progress_percentage
|
1034
|
+
console.print(f"\n[bold yellow]📊 Epic 2 Infrastructure Progress: {progress_pct:.1f}%[/bold yellow]")
|
1035
|
+
console.print(
|
1036
|
+
f"[yellow]Target: ${results.epic_2_target_savings:,.0f} | Achieved: ${results.total_potential_savings:,.0f}[/yellow]"
|
1037
|
+
)
|
1038
|
+
|
1039
|
+
# Export results if requested
|
1040
|
+
if output_file or export_format != "json":
|
1041
|
+
console.print(f"[dim]Export functionality available - results ready for {export_format} export[/dim]")
|
1042
|
+
|
1043
|
+
return results
|
1044
|
+
|
1045
|
+
except ImportError as e:
|
1046
|
+
error_handlers["module_not_available"]("Infrastructure Optimizer", e)
|
1047
|
+
raise click.ClickException("Infrastructure optimization functionality not available")
|
1048
|
+
except Exception as e:
|
1049
|
+
error_handlers["operation_failed"]("Infrastructure optimization analysis", e)
|
1050
|
+
raise click.ClickException(str(e))
|
1051
|
+
|
1052
|
+
@infrastructure.command()
|
1053
|
+
@click.pass_context
|
1054
|
+
def nat_gateway(ctx):
|
1055
|
+
"""NAT Gateway optimization analysis - $147,420 Epic 2 target"""
|
1056
|
+
try:
|
1057
|
+
import asyncio
|
1058
|
+
from runbooks.finops.nat_gateway_optimizer import NATGatewayOptimizer
|
1059
|
+
|
1060
|
+
optimizer = NATGatewayOptimizer(
|
1061
|
+
profile_name=ctx.obj.get("profile"), regions=[ctx.obj.get("region")] if ctx.obj.get("region") else None
|
1062
|
+
)
|
1063
|
+
|
1064
|
+
results = asyncio.run(optimizer.analyze_nat_gateways(dry_run=ctx.obj.get("dry_run", True)))
|
1065
|
+
|
1066
|
+
# Display Epic 2 component progress
|
1067
|
+
target = 147420.0
|
1068
|
+
if results.potential_annual_savings >= target:
|
1069
|
+
console.print(f"\n[bold green]✅ NAT Gateway Epic 2 Target Achieved![/bold green]")
|
1070
|
+
else:
|
1071
|
+
progress = (results.potential_annual_savings / target) * 100
|
1072
|
+
console.print(f"\n[yellow]📊 NAT Gateway Progress: {progress:.1f}% of Epic 2 target[/yellow]")
|
1073
|
+
|
1074
|
+
return results
|
1075
|
+
|
1076
|
+
except Exception as e:
|
1077
|
+
error_handlers["operation_failed"]("NAT Gateway optimization", e)
|
1078
|
+
raise click.ClickException(str(e))
|
1079
|
+
|
1080
|
+
@infrastructure.command()
|
1081
|
+
@click.pass_context
|
1082
|
+
def elastic_ip(ctx):
|
1083
|
+
"""Elastic IP optimization analysis - $21,593 Epic 2 target"""
|
1084
|
+
try:
|
1085
|
+
import asyncio
|
1086
|
+
from runbooks.finops.elastic_ip_optimizer import ElasticIPOptimizer
|
1087
|
+
|
1088
|
+
optimizer = ElasticIPOptimizer(
|
1089
|
+
profile_name=ctx.obj.get("profile"), regions=[ctx.obj.get("region")] if ctx.obj.get("region") else None
|
1090
|
+
)
|
1091
|
+
|
1092
|
+
results = asyncio.run(optimizer.analyze_elastic_ips(dry_run=ctx.obj.get("dry_run", True)))
|
1093
|
+
|
1094
|
+
# Display Epic 2 component progress
|
1095
|
+
target = 21593.0
|
1096
|
+
if results.potential_annual_savings >= target:
|
1097
|
+
console.print(f"\n[bold green]✅ Elastic IP Epic 2 Target Achieved![/bold green]")
|
1098
|
+
else:
|
1099
|
+
progress = (results.potential_annual_savings / target) * 100
|
1100
|
+
console.print(f"\n[yellow]📊 Elastic IP Progress: {progress:.1f}% of Epic 2 target[/yellow]")
|
1101
|
+
|
1102
|
+
return results
|
1103
|
+
|
1104
|
+
except Exception as e:
|
1105
|
+
error_handlers["operation_failed"]("Elastic IP optimization", e)
|
1106
|
+
raise click.ClickException(str(e))
|
1107
|
+
|
1108
|
+
@infrastructure.command()
|
1109
|
+
@click.pass_context
|
1110
|
+
def load_balancer(ctx):
|
1111
|
+
"""Load Balancer optimization analysis - $35,280 Epic 2 target"""
|
1112
|
+
try:
|
1113
|
+
import asyncio
|
1114
|
+
from runbooks.finops.infrastructure.load_balancer_optimizer import LoadBalancerOptimizer
|
1115
|
+
|
1116
|
+
optimizer = LoadBalancerOptimizer(
|
1117
|
+
profile_name=ctx.obj.get("profile"), regions=[ctx.obj.get("region")] if ctx.obj.get("region") else None
|
1118
|
+
)
|
1119
|
+
|
1120
|
+
results = asyncio.run(optimizer.analyze_load_balancers(dry_run=ctx.obj.get("dry_run", True)))
|
1121
|
+
|
1122
|
+
# Display Epic 2 component progress
|
1123
|
+
target = 35280.0
|
1124
|
+
if results.potential_annual_savings >= target:
|
1125
|
+
console.print(f"\n[bold green]✅ Load Balancer Epic 2 Target Achieved![/bold green]")
|
1126
|
+
else:
|
1127
|
+
progress = (results.potential_annual_savings / target) * 100
|
1128
|
+
console.print(f"\n[yellow]📊 Load Balancer Progress: {progress:.1f}% of Epic 2 target[/yellow]")
|
1129
|
+
|
1130
|
+
return results
|
1131
|
+
|
1132
|
+
except Exception as e:
|
1133
|
+
error_handlers["operation_failed"]("Load Balancer optimization", e)
|
1134
|
+
raise click.ClickException(str(e))
|
1135
|
+
|
1136
|
+
@infrastructure.command()
|
1137
|
+
@click.pass_context
|
1138
|
+
def vpc_endpoint(ctx):
|
1139
|
+
"""VPC Endpoint optimization analysis - $5,854 Epic 2 target"""
|
1140
|
+
try:
|
1141
|
+
import asyncio
|
1142
|
+
from runbooks.finops.infrastructure.vpc_endpoint_optimizer import VPCEndpointOptimizer
|
1143
|
+
|
1144
|
+
optimizer = VPCEndpointOptimizer(
|
1145
|
+
profile_name=ctx.obj.get("profile"), regions=[ctx.obj.get("region")] if ctx.obj.get("region") else None
|
1146
|
+
)
|
1147
|
+
|
1148
|
+
results = asyncio.run(optimizer.analyze_vpc_endpoints(dry_run=ctx.obj.get("dry_run", True)))
|
1149
|
+
|
1150
|
+
# Display Epic 2 component progress
|
1151
|
+
target = 5854.0
|
1152
|
+
if results.potential_annual_savings >= target:
|
1153
|
+
console.print(f"\n[bold green]✅ VPC Endpoint Epic 2 Target Achieved![/bold green]")
|
1154
|
+
else:
|
1155
|
+
progress = (results.potential_annual_savings / target) * 100
|
1156
|
+
console.print(f"\n[yellow]📊 VPC Endpoint Progress: {progress:.1f}% of Epic 2 target[/yellow]")
|
1157
|
+
|
1158
|
+
return results
|
1159
|
+
|
1160
|
+
except Exception as e:
|
1161
|
+
error_handlers["operation_failed"]("VPC Endpoint optimization", e)
|
1162
|
+
raise click.ClickException(str(e))
|
1163
|
+
|
1164
|
+
return finops
|