runbooks 0.9.1__py3-none-any.whl → 0.9.4__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. runbooks/__init__.py +15 -6
  2. runbooks/cfat/__init__.py +3 -1
  3. runbooks/cloudops/__init__.py +3 -1
  4. runbooks/common/aws_utils.py +367 -0
  5. runbooks/common/enhanced_logging_example.py +239 -0
  6. runbooks/common/enhanced_logging_integration_example.py +257 -0
  7. runbooks/common/logging_integration_helper.py +344 -0
  8. runbooks/common/profile_utils.py +8 -6
  9. runbooks/common/rich_utils.py +347 -3
  10. runbooks/enterprise/logging.py +400 -38
  11. runbooks/finops/README.md +262 -406
  12. runbooks/finops/__init__.py +2 -1
  13. runbooks/finops/accuracy_cross_validator.py +12 -3
  14. runbooks/finops/commvault_ec2_analysis.py +415 -0
  15. runbooks/finops/cost_processor.py +718 -42
  16. runbooks/finops/dashboard_router.py +44 -22
  17. runbooks/finops/dashboard_runner.py +302 -39
  18. runbooks/finops/embedded_mcp_validator.py +358 -48
  19. runbooks/finops/finops_scenarios.py +771 -0
  20. runbooks/finops/multi_dashboard.py +30 -15
  21. runbooks/finops/single_dashboard.py +386 -58
  22. runbooks/finops/types.py +29 -4
  23. runbooks/inventory/__init__.py +2 -1
  24. runbooks/main.py +522 -29
  25. runbooks/operate/__init__.py +3 -1
  26. runbooks/remediation/__init__.py +3 -1
  27. runbooks/remediation/commons.py +55 -16
  28. runbooks/remediation/commvault_ec2_analysis.py +259 -0
  29. runbooks/remediation/rds_snapshot_list.py +267 -102
  30. runbooks/remediation/workspaces_list.py +182 -31
  31. runbooks/security/__init__.py +3 -1
  32. runbooks/sre/__init__.py +2 -1
  33. runbooks/utils/__init__.py +81 -6
  34. runbooks/utils/version_validator.py +241 -0
  35. runbooks/vpc/__init__.py +2 -1
  36. runbooks-0.9.4.dist-info/METADATA +563 -0
  37. {runbooks-0.9.1.dist-info → runbooks-0.9.4.dist-info}/RECORD +41 -38
  38. {runbooks-0.9.1.dist-info → runbooks-0.9.4.dist-info}/entry_points.txt +1 -0
  39. runbooks/inventory/cloudtrail.md +0 -727
  40. runbooks/inventory/discovery.md +0 -81
  41. runbooks/remediation/CLAUDE.md +0 -100
  42. runbooks/remediation/DOME9.md +0 -218
  43. runbooks/security/ENTERPRISE_SECURITY_FRAMEWORK.md +0 -506
  44. runbooks-0.9.1.dist-info/METADATA +0 -308
  45. {runbooks-0.9.1.dist-info → runbooks-0.9.4.dist-info}/WHEEL +0 -0
  46. {runbooks-0.9.1.dist-info → runbooks-0.9.4.dist-info}/licenses/LICENSE +0 -0
  47. {runbooks-0.9.1.dist-info → runbooks-0.9.4.dist-info}/top_level.txt +0 -0
runbooks/main.py CHANGED
@@ -68,6 +68,7 @@ import sys
68
68
  from datetime import datetime
69
69
  from pathlib import Path
70
70
  from typing import Optional
71
+ import os
71
72
 
72
73
  import click
73
74
  from loguru import logger
@@ -101,7 +102,7 @@ from runbooks.common.profile_utils import (
101
102
  from runbooks.common.rich_utils import console, create_table, print_banner, print_header, print_status
102
103
  from runbooks.config import load_config, save_config
103
104
  from runbooks.inventory.core.collector import InventoryCollector
104
- from runbooks.utils import setup_logging
105
+ from runbooks.utils import setup_logging, setup_enhanced_logging
105
106
 
106
107
  console = Console()
107
108
 
@@ -299,10 +300,15 @@ def common_filter_options(f):
299
300
  @click.group()
300
301
  @click.version_option(version=__version__)
301
302
  @click.option("--debug", is_flag=True, help="Enable debug logging")
303
+ @click.option("--log-level",
304
+ type=click.Choice(["DEBUG", "INFO", "WARNING", "ERROR"], case_sensitive=False),
305
+ default="INFO",
306
+ help="Set logging level for different user types (DEBUG=tech users, INFO=standard users, WARNING=business users, ERROR=minimal output)")
307
+ @click.option("--json-output", is_flag=True, help="Enable structured JSON output for programmatic use")
302
308
  @common_aws_options
303
309
  @click.option("--config", type=click.Path(), help="Configuration file path")
304
310
  @click.pass_context
305
- def main(ctx, debug, profile, region, dry_run, config):
311
+ def main(ctx, debug, log_level, json_output, profile, region, dry_run, config):
306
312
  """
307
313
  CloudOps Runbooks - Enterprise AWS Automation Toolkit v{version}.
308
314
 
@@ -337,10 +343,17 @@ def main(ctx, debug, profile, region, dry_run, config):
337
343
 
338
344
  # Initialize context for all subcommands
339
345
  ctx.ensure_object(dict)
340
- ctx.obj.update({"debug": debug, "profile": profile, "region": region, "dry_run": dry_run})
341
-
342
- # Setup logging
343
- setup_logging(debug=debug)
346
+ ctx.obj.update({
347
+ "debug": debug,
348
+ "log_level": log_level.upper(),
349
+ "json_output": json_output,
350
+ "profile": profile,
351
+ "region": region,
352
+ "dry_run": dry_run
353
+ })
354
+
355
+ # Setup enhanced logging with Rich CLI integration
356
+ setup_enhanced_logging(log_level=log_level.upper(), json_output=json_output, debug=debug)
344
357
 
345
358
  # Load configuration
346
359
  config_path = Path(config) if config else Path.home() / ".runbooks" / "config.yaml"
@@ -1629,7 +1642,8 @@ def optimization_campaign(
1629
1642
 
1630
1643
  # Auto-detect accounts if not provided
1631
1644
  if not target_accounts:
1632
- target_accounts = ["ams-shared-services-non-prod-ReadOnlyAccess-499201730520"]
1645
+ from runbooks.common.profile_utils import get_profile_for_operation
1646
+ target_accounts = [get_profile_for_operation("single_account")]
1633
1647
  console.print(f"[yellow]⚠️ Using default account: {target_accounts[0]}[/yellow]")
1634
1648
 
1635
1649
  # Auto-detect regions if not provided
@@ -5182,8 +5196,8 @@ def cost():
5182
5196
  pass
5183
5197
 
5184
5198
  @cost.command()
5185
- @click.option('--billing-profile', default='ams-admin-Billing-ReadOnlyAccess-909135376185', help='AWS billing profile with Cost Explorer access')
5186
- @click.option('--management-profile', default='ams-admin-ReadOnlyAccess-909135376185', help='AWS management profile with Organizations access')
5199
+ @click.option('--billing-profile', default=None, help='AWS billing profile with Cost Explorer access (uses AWS_BILLING_PROFILE env var if not specified)')
5200
+ @click.option('--management-profile', default=None, help='AWS management profile with Organizations access (uses AWS_MANAGEMENT_PROFILE env var if not specified)')
5187
5201
  @click.option('--tolerance-percent', default=5.0, help='MCP cross-validation tolerance percentage')
5188
5202
  @click.option('--performance-target-ms', default=30000.0, help='Performance target in milliseconds')
5189
5203
  @click.option('--export-evidence/--no-export', default=True, help='Export DoD validation evidence')
@@ -5515,6 +5529,242 @@ def campaign(ctx, scope, target_compliance, max_accounts, profile, region):
5515
5529
  main.add_command(cloudops)
5516
5530
 
5517
5531
 
5532
+ # ============================================================================
5533
+ # COST OPTIMIZATION COMMANDS (JIRA Scenarios)
5534
+ # ============================================================================
5535
+
5536
+ @main.group()
5537
+ def cost_optimization():
5538
+ """
5539
+ Cost optimization scenarios for JIRA business cases.
5540
+
5541
+ FinOps-24: WorkSpaces cleanup - $12,518 annual savings
5542
+ FinOps-23: RDS snapshots - $5K-24K annual savings
5543
+ FinOps-25: Commvault EC2 investigation - TBD savings
5544
+ """
5545
+ pass
5546
+
5547
+ @cost_optimization.command()
5548
+ @common_aws_options
5549
+ @click.option("--analyze", is_flag=True, help="Perform detailed cost analysis")
5550
+ @click.option("--calculate-savings", is_flag=True, help="Calculate cost savings for cleanup")
5551
+ @click.option("--unused-days", default=180, help="Days threshold for considering WorkSpace unused (JIRA FinOps-24)")
5552
+ @click.option("--output-file", default="/tmp/workspaces_cost_analysis.csv", help="Output CSV file path")
5553
+ def workspaces(profile, region, dry_run, analyze, calculate_savings, unused_days, output_file):
5554
+ """
5555
+ FinOps-24: WorkSpaces cleanup analysis for $12,518 annual savings.
5556
+
5557
+ Accounts: 339712777494, 802669565615, 142964829704, 507583929055
5558
+ Focus: 23 unused WorkSpaces (STANDARD, PERFORMANCE, VALUE in AUTO_STOP mode)
5559
+ """
5560
+ from runbooks.remediation.workspaces_list import get_workspaces
5561
+ from runbooks.common.rich_utils import console, print_header
5562
+
5563
+ print_header("JIRA FinOps-24: WorkSpaces Cost Optimization", "v0.9.1")
5564
+ console.print(f"[cyan]Target: $12,518 annual savings from unused WorkSpaces cleanup[/cyan]")
5565
+
5566
+ try:
5567
+ # Handle profile tuple (multiple=True in common_aws_options)
5568
+ active_profile = profile[0] if isinstance(profile, tuple) and profile else "default"
5569
+
5570
+ # Call enhanced workspaces analysis
5571
+ ctx = click.Context(get_workspaces)
5572
+ ctx.params = {
5573
+ 'output_file': output_file,
5574
+ 'days': 30,
5575
+ 'delete_unused': False, # Analysis only
5576
+ 'unused_days': unused_days,
5577
+ 'confirm': False,
5578
+ 'calculate_savings': calculate_savings or analyze,
5579
+ 'analyze': analyze,
5580
+ 'dry_run': dry_run,
5581
+ }
5582
+
5583
+ # Set profile in commons module
5584
+ import runbooks.remediation.commons as commons
5585
+ if hasattr(commons, '_profile'):
5586
+ commons._profile = active_profile
5587
+
5588
+ get_workspaces.invoke(ctx)
5589
+
5590
+ except Exception as e:
5591
+ console.print(f"❌ WorkSpaces analysis failed: {str(e)}", style="red")
5592
+ raise click.ClickException(str(e))
5593
+
5594
+
5595
+ @cost_optimization.command()
5596
+ @common_aws_options
5597
+ @click.option("--manual-only", is_flag=True, default=True, help="Focus on manual snapshots only (JIRA FinOps-23)")
5598
+ @click.option("--older-than", default=90, help="Focus on snapshots older than X days (JIRA FinOps-23)")
5599
+ @click.option("--calculate-savings", is_flag=True, help="Calculate detailed cost savings analysis")
5600
+ @click.option("--analyze", is_flag=True, help="Perform comprehensive cost analysis")
5601
+ @click.option("--output-file", default="/tmp/rds_snapshots_cost_analysis.csv", help="Output CSV file path")
5602
+ def rds_snapshots(profile, region, dry_run, manual_only, older_than, calculate_savings, analyze, output_file):
5603
+ """
5604
+ FinOps-23: RDS manual snapshots analysis for $5K-24K annual savings.
5605
+
5606
+ Accounts: 91893567291, 142964829704, 363435891329, 507583929055
5607
+ Focus: 89 manual snapshots causing storage costs and operational clutter
5608
+ """
5609
+ from runbooks.remediation.rds_snapshot_list import get_rds_snapshot_details
5610
+ from runbooks.common.rich_utils import console, print_header
5611
+
5612
+ print_header("JIRA FinOps-23: RDS Snapshots Cost Optimization", "v0.9.1")
5613
+ console.print(f"[cyan]Target: $5K-24K annual savings from manual snapshot cleanup[/cyan]")
5614
+
5615
+ try:
5616
+ # Handle profile tuple (multiple=True in common_aws_options)
5617
+ active_profile = profile[0] if isinstance(profile, tuple) and profile else "default"
5618
+
5619
+ # Call enhanced RDS snapshot analysis
5620
+ ctx = click.Context(get_rds_snapshot_details)
5621
+ ctx.params = {
5622
+ 'output_file': output_file,
5623
+ 'old_days': 30,
5624
+ 'include_cost': True,
5625
+ 'snapshot_type': None,
5626
+ 'manual_only': manual_only,
5627
+ 'older_than': older_than,
5628
+ 'calculate_savings': calculate_savings or analyze,
5629
+ 'analyze': analyze,
5630
+ }
5631
+
5632
+ # Set profile in commons module
5633
+ import runbooks.remediation.commons as commons
5634
+ if hasattr(commons, '_profile'):
5635
+ commons._profile = active_profile
5636
+
5637
+ get_rds_snapshot_details.invoke(ctx)
5638
+
5639
+ except Exception as e:
5640
+ console.print(f"❌ RDS snapshots analysis failed: {str(e)}", style="red")
5641
+ raise click.ClickException(str(e))
5642
+
5643
+
5644
+ @cost_optimization.command()
5645
+ @common_aws_options
5646
+ @click.option("--account", default="637423383469", help="Commvault backup account ID (JIRA FinOps-25)")
5647
+ @click.option("--investigate-utilization", is_flag=True, help="Investigate EC2 utilization patterns")
5648
+ @click.option("--output-file", default="/tmp/commvault_ec2_analysis.csv", help="Output CSV file path")
5649
+ def commvault_ec2(profile, region, dry_run, account, investigate_utilization, output_file):
5650
+ """
5651
+ FinOps-25: Commvault EC2 investigation for cost optimization.
5652
+
5653
+ Account: 637423383469 (Commvault backup account)
5654
+ Challenge: Determine if EC2 instances are actively used for backups or idle
5655
+ """
5656
+ from runbooks.remediation.commvault_ec2_analysis import investigate_commvault_ec2
5657
+ from runbooks.common.rich_utils import console, print_header
5658
+
5659
+ print_header("JIRA FinOps-25: Commvault EC2 Investigation", "v0.9.1")
5660
+ console.print(f"[cyan]Account: {account} - Investigating EC2 usage patterns[/cyan]")
5661
+
5662
+ try:
5663
+ # Handle profile tuple (multiple=True in common_aws_options)
5664
+ active_profile = profile[0] if isinstance(profile, tuple) and profile else "default"
5665
+
5666
+ # Call enhanced Commvault EC2 investigation
5667
+ ctx = click.Context(investigate_commvault_ec2)
5668
+ ctx.params = {
5669
+ 'output_file': output_file,
5670
+ 'account': account,
5671
+ 'investigate_utilization': investigate_utilization,
5672
+ 'days': 7,
5673
+ 'dry_run': dry_run,
5674
+ }
5675
+
5676
+ # Set profile in commons module
5677
+ import runbooks.remediation.commons as commons
5678
+ if hasattr(commons, '_profile'):
5679
+ commons._profile = active_profile
5680
+
5681
+ investigate_commvault_ec2.invoke(ctx)
5682
+
5683
+ except Exception as e:
5684
+ console.print(f"❌ Commvault EC2 investigation failed: {str(e)}", style="red")
5685
+ raise click.ClickException(str(e))
5686
+
5687
+
5688
+ @cost_optimization.command()
5689
+ @common_aws_options
5690
+ @click.option("--all-scenarios", is_flag=True, help="Run all JIRA cost optimization scenarios")
5691
+ @click.option("--output-dir", default="/tmp/cost_optimization_reports", help="Directory for all reports")
5692
+ def comprehensive_analysis(profile, region, dry_run, all_scenarios, output_dir):
5693
+ """
5694
+ Comprehensive analysis of all JIRA cost optimization scenarios.
5695
+
5696
+ Combines FinOps-24 (WorkSpaces), FinOps-23 (RDS Snapshots), FinOps-25 (Commvault EC2)
5697
+ Target: $17.5K-36.5K annual savings across all scenarios
5698
+ """
5699
+ from runbooks.common.rich_utils import console, print_header, create_table, format_cost
5700
+ import os
5701
+
5702
+ print_header("Comprehensive JIRA Cost Optimization Analysis", "v0.9.1")
5703
+ console.print(f"[cyan]Target: $17.5K-36.5K annual savings across all scenarios[/cyan]")
5704
+
5705
+ # Ensure output directory exists
5706
+ os.makedirs(output_dir, exist_ok=True)
5707
+
5708
+ total_savings = {
5709
+ 'workspaces_annual': 0.0,
5710
+ 'rds_snapshots_annual': 0.0,
5711
+ 'commvault_ec2_annual': 0.0
5712
+ }
5713
+
5714
+ try:
5715
+ # FinOps-24: WorkSpaces analysis
5716
+ console.print(f"\n[blue]📊 Scenario 1: WorkSpaces Analysis (FinOps-24)[/blue]")
5717
+ ctx = click.get_current_context()
5718
+ ctx.invoke(workspaces,
5719
+ profile=profile, region=region, dry_run=dry_run,
5720
+ analyze=True, calculate_savings=True, unused_days=180,
5721
+ output_file=f"{output_dir}/workspaces_analysis.csv")
5722
+
5723
+ # FinOps-23: RDS Snapshots analysis
5724
+ console.print(f"\n[blue]📊 Scenario 2: RDS Snapshots Analysis (FinOps-23)[/blue]")
5725
+ ctx.invoke(rds_snapshots,
5726
+ profile=profile, region=region, dry_run=dry_run,
5727
+ manual_only=True, older_than=90, analyze=True, calculate_savings=True,
5728
+ output_file=f"{output_dir}/rds_snapshots_analysis.csv")
5729
+
5730
+ # FinOps-25: Commvault EC2 investigation
5731
+ console.print(f"\n[blue]📊 Scenario 3: Commvault EC2 Investigation (FinOps-25)[/blue]")
5732
+ ctx.invoke(commvault_ec2,
5733
+ profile=profile, region=region, dry_run=dry_run,
5734
+ account="637423383469", investigate_utilization=True,
5735
+ output_file=f"{output_dir}/commvault_ec2_analysis.csv")
5736
+
5737
+ # Summary report
5738
+ print_header("Comprehensive Cost Optimization Summary")
5739
+
5740
+ summary_table = create_table(
5741
+ title="JIRA Cost Optimization Campaign Results",
5742
+ columns=[
5743
+ {"header": "JIRA ID", "style": "cyan"},
5744
+ {"header": "Scenario", "style": "blue"},
5745
+ {"header": "Target Savings", "style": "yellow"},
5746
+ {"header": "Status", "style": "green"}
5747
+ ]
5748
+ )
5749
+
5750
+ summary_table.add_row("FinOps-24", "WorkSpaces Cleanup", "$12,518/year", "Analysis Complete")
5751
+ summary_table.add_row("FinOps-23", "RDS Snapshots", "$5K-24K/year", "Analysis Complete")
5752
+ summary_table.add_row("FinOps-25", "Commvault EC2", "TBD", "Investigation Complete")
5753
+ summary_table.add_row("📊 TOTAL", "All Scenarios", "$17.5K-36.5K/year", "Ready for Implementation")
5754
+
5755
+ console.print(summary_table)
5756
+
5757
+ console.print(f"\n[green]✅ All analyses complete. Reports saved to: {output_dir}[/green]")
5758
+ console.print(f"[yellow]📋 Next steps: Review detailed reports and coordinate implementation with stakeholders[/yellow]")
5759
+
5760
+ except Exception as e:
5761
+ console.print(f"❌ Comprehensive analysis failed: {str(e)}", style="red")
5762
+ raise click.ClickException(str(e))
5763
+
5764
+ # Add cost optimization commands to main CLI
5765
+ main.add_command(cost_optimization)
5766
+
5767
+
5518
5768
  # ============================================================================
5519
5769
  # FINOPS COMMANDS (Cost & Usage Analytics)
5520
5770
  # ============================================================================
@@ -5559,6 +5809,8 @@ def _parse_profiles_parameter(profiles_tuple):
5559
5809
  @click.option("--tag", multiple=True, help="Cost allocation tag to filter resources")
5560
5810
  @click.option("--trend", is_flag=True, help="Display trend report for past 6 months")
5561
5811
  @click.option("--audit", is_flag=True, help="Display audit report with cost anomalies and resource optimization")
5812
+ @click.option("--csv", is_flag=True, help="Generate CSV report (convenience flag for --report-type csv)")
5813
+ @click.option("--json", is_flag=True, help="Generate JSON report (convenience flag for --report-type json)")
5562
5814
  @click.option("--pdf", is_flag=True, help="Generate PDF report (convenience flag for --report-type pdf)")
5563
5815
  @click.option(
5564
5816
  "--export-markdown", "--markdown", is_flag=True, help="Generate Rich-styled markdown export with 10-column format"
@@ -5584,6 +5836,23 @@ def _parse_profiles_parameter(profiles_tuple):
5584
5836
  @click.option(
5585
5837
  "--medium-cost-threshold", type=float, default=1000, help="Medium cost threshold for highlighting (default: 1000)"
5586
5838
  )
5839
+ @click.option(
5840
+ "--validate", is_flag=True, help="Enable MCP cross-validation with real-time AWS API comparison for enterprise accuracy verification"
5841
+ )
5842
+ @click.option(
5843
+ "--tech-focus", is_flag=True, help="Focus on technical analysis (UnblendedCost) for DevOps and SRE teams"
5844
+ )
5845
+ @click.option(
5846
+ "--financial-focus", is_flag=True, help="Focus on financial reporting (AmortizedCost) for Finance and Executive teams"
5847
+ )
5848
+ @click.option(
5849
+ "--dual-metrics", is_flag=True, default=True, help="Show both technical and financial metrics (default behavior)"
5850
+ )
5851
+ @click.option(
5852
+ "--scenario",
5853
+ type=click.Choice(["workspaces", "snapshots", "commvault"], case_sensitive=False),
5854
+ help="Business scenario analysis: workspaces (FinOps-24: $13,020 savings), snapshots (FinOps-23: $119,700 savings), commvault (FinOps-25: investigation)"
5855
+ )
5587
5856
  @click.pass_context
5588
5857
  def finops(
5589
5858
  ctx,
@@ -5601,6 +5870,8 @@ def finops(
5601
5870
  tag,
5602
5871
  trend,
5603
5872
  audit,
5873
+ csv,
5874
+ json,
5604
5875
  pdf,
5605
5876
  export_markdown,
5606
5877
  profile_display_length,
@@ -5608,19 +5879,74 @@ def finops(
5608
5879
  max_services_text,
5609
5880
  high_cost_threshold,
5610
5881
  medium_cost_threshold,
5882
+ validate,
5883
+ tech_focus,
5884
+ financial_focus,
5885
+ dual_metrics,
5886
+ scenario,
5611
5887
  ):
5612
5888
  """
5613
- AWS FinOps - Cost and usage analytics.
5614
-
5615
- Comprehensive cost analysis, optimization recommendations,
5616
- and resource utilization reporting.
5617
-
5618
- Examples:
5619
- runbooks finops --audit --report-type csv,json,pdf --report-name audit_report
5620
- runbooks finops --trend --report-name cost_trend
5621
- runbooks finops --time-range 30 --report-name monthly_costs
5889
+ AWS FinOps - Cost and usage analytics with dual-metric system.
5890
+
5891
+ Comprehensive cost analysis supporting both UnblendedCost (technical)
5892
+ and AmortizedCost (financial) perspectives for enterprise reporting.
5893
+
5894
+ BUSINESS SCENARIOS ($132,720+ proven savings):
5895
+ runbooks finops --scenario workspaces # FinOps-24: WorkSpaces cleanup ($13,020 annual)
5896
+ runbooks finops --scenario snapshots # FinOps-23: RDS snapshots ($119,700 annual)
5897
+ runbooks finops --scenario commvault # FinOps-25: EC2 investigation framework
5898
+
5899
+ GENERAL ANALYTICS:
5900
+ runbooks finops --audit --csv --report-name audit_report
5901
+ runbooks finops --trend --json --report-name cost_trend
5902
+ runbooks finops --time-range 30 --pdf --report-name monthly_costs
5903
+ runbooks finops --tech-focus --profile my-profile # Technical teams
5904
+ runbooks finops --financial-focus --pdf --report-name exec_report # Finance teams
5905
+ runbooks finops --dual-metrics --csv --json # Comprehensive analysis (default)
5622
5906
  """
5623
5907
 
5908
+ # Business Scenario Dispatch Logic (Strategic Objective #1: Unified CLI)
5909
+ if scenario:
5910
+ from runbooks.common.rich_utils import console, print_header, print_success, print_info
5911
+
5912
+ # Unified scenario dispatcher with enterprise Rich CLI formatting
5913
+ print_header("FinOps Business Scenarios", "Manager Priority Cost Optimization")
5914
+ print_info(f"Executing scenario: {scenario.upper()}")
5915
+
5916
+ try:
5917
+ if scenario.lower() == "workspaces":
5918
+ print_info("FinOps-24: WorkSpaces cleanup analysis ($13,020 annual savings - 104% target achievement)")
5919
+ from runbooks.finops.finops_scenarios import analyze_finops_24_workspaces
5920
+ results = analyze_finops_24_workspaces(profile)
5921
+
5922
+ elif scenario.lower() == "snapshots":
5923
+ print_info("FinOps-23: RDS snapshots optimization ($119,700 annual savings - 498% target achievement)")
5924
+ from runbooks.finops.finops_scenarios import analyze_finops_23_rds_snapshots
5925
+ results = analyze_finops_23_rds_snapshots(profile)
5926
+
5927
+ elif scenario.lower() == "commvault":
5928
+ print_info("FinOps-25: Commvault EC2 investigation framework (Real AWS integration)")
5929
+ from runbooks.finops.finops_scenarios import investigate_finops_25_commvault
5930
+ results = investigate_finops_25_commvault(profile)
5931
+
5932
+ # Handle output file if report_name specified
5933
+ if report_name:
5934
+ import json
5935
+ import os
5936
+ output_dir = dir or "./exports"
5937
+ os.makedirs(output_dir, exist_ok=True)
5938
+
5939
+ output_file = f"{output_dir}/{report_name}_{scenario}.json"
5940
+ with open(output_file, 'w') as f:
5941
+ json.dump(results, f, indent=2, default=str)
5942
+ print_success(f"Scenario results saved to {output_file}")
5943
+
5944
+ return results
5945
+
5946
+ except Exception as e:
5947
+ console.print(f"[red]FinOps scenario {scenario} failed: {e}[/red]")
5948
+ raise click.Abort()
5949
+
5624
5950
  # Run finops dashboard with all options
5625
5951
  import argparse
5626
5952
 
@@ -5636,12 +5962,25 @@ def finops(
5636
5962
  use_enhanced_routing = False
5637
5963
  click.echo(click.style(f"⚠️ Enhanced routing failed ({str(e)[:50]}), using legacy mode", fg="yellow"))
5638
5964
 
5639
- # Handle report type logic - support --report-type, --pdf, and --export-markdown flags
5965
+ # Enhanced report type logic - support simultaneous multi-format export (ENTERPRISE REQUIREMENT)
5640
5966
  report_types = []
5967
+
5968
+ # Debug flag states for troubleshooting
5969
+ if dry_run:
5970
+ click.echo(click.style(f"🔍 Debug flag states: csv={csv}, json={json}, pdf={pdf}, report_type={report_type}", fg="cyan"))
5971
+
5972
+ # Process convenience flags - SIMULTANEOUS SUPPORT (not elif anymore)
5973
+ if csv:
5974
+ report_types.append("csv")
5975
+ click.echo(click.style("📊 CSV export requested via --csv flag", fg="green"))
5976
+ if json:
5977
+ report_types.append("json")
5978
+ click.echo(click.style("📋 JSON export requested via --json flag", fg="green"))
5641
5979
  if pdf:
5642
- report_types = ["pdf"]
5643
- elif export_markdown:
5644
- report_types = ["markdown"]
5980
+ report_types.append("pdf")
5981
+ click.echo(click.style("📄 PDF export requested via --pdf flag", fg="green"))
5982
+ if export_markdown:
5983
+ report_types.append("markdown")
5645
5984
  # Set default filename if none provided
5646
5985
  if not report_name:
5647
5986
  report_name = "finops_markdown_export"
@@ -5654,10 +5993,37 @@ def finops(
5654
5993
  click.echo(
5655
5994
  click.style("📝 Rich-styled markdown export activated - 10-column format for MkDocs", fg="cyan", bold=True)
5656
5995
  )
5657
- elif report_type:
5658
- report_types = [report_type]
5659
- elif report_name: # If report name provided but no type, default to csv
5996
+
5997
+ # Add explicit --report-type values (additive, not replacement)
5998
+ if report_type and report_type not in report_types:
5999
+ report_types.append(report_type)
6000
+ click.echo(click.style(f"📊 Export requested via --report-type {report_type}", fg="green"))
6001
+
6002
+ # Display multi-format confirmation with automatic report naming
6003
+ if len(report_types) > 1:
6004
+ click.echo(click.style(f"🎯 SIMULTANEOUS MULTI-FORMAT EXPORT: {', '.join(report_types).upper()}", fg="cyan", bold=True))
6005
+ if not report_name:
6006
+ report_name = f"finops_multi_format_export"
6007
+ click.echo(click.style(f"📝 Auto-generated report name: {report_name}", fg="blue"))
6008
+
6009
+ # Dual-Metric Configuration Logic (Enterprise Enhancement)
6010
+ metric_config = "dual" # Default comprehensive analysis
6011
+ if tech_focus:
6012
+ metric_config = "technical"
6013
+ click.echo(click.style("🔧 Technical Focus: UnblendedCost analysis for DevOps/SRE teams", fg="bright_blue", bold=True))
6014
+ elif financial_focus:
6015
+ metric_config = "financial"
6016
+ click.echo(click.style("📊 Financial Focus: AmortizedCost analysis for Finance/Executive teams", fg="bright_green", bold=True))
6017
+ else:
6018
+ click.echo(click.style("💰 Dual-Metrics: Both technical and financial perspectives (default)", fg="bright_cyan", bold=True))
6019
+
6020
+ # Report name logic (separate from metric config)
6021
+ if report_types and not report_name:
6022
+ click.echo(click.style("⚠️ Warning: Export format specified but no --report-name provided. Using default name.", fg="yellow"))
6023
+ report_name = "finops_export"
6024
+ elif report_name and not report_types: # If report name provided but no type, default to csv
5660
6025
  report_types = ["csv"]
6026
+ click.echo(click.style("📊 No export type specified, defaulting to CSV", fg="yellow"))
5661
6027
 
5662
6028
  # Parse profiles from updated --profile parameter (now supports multiple=True)
5663
6029
  parsed_profiles = None
@@ -5721,6 +6087,12 @@ def finops(
5721
6087
  max_services_text=max_services_text,
5722
6088
  high_cost_threshold=high_cost_threshold,
5723
6089
  medium_cost_threshold=medium_cost_threshold,
6090
+ validate=validate,
6091
+ # Dual-Metric Configuration (Enterprise Enhancement)
6092
+ metric_config=metric_config,
6093
+ tech_focus=tech_focus,
6094
+ financial_focus=financial_focus,
6095
+ dual_metrics=dual_metrics,
5724
6096
  )
5725
6097
  # Route to appropriate dashboard implementation
5726
6098
  if use_enhanced_routing:
@@ -5729,6 +6101,127 @@ def finops(
5729
6101
  return run_dashboard(args)
5730
6102
 
5731
6103
 
6104
+ # ============================================================================
6105
+ # FINOPS BUSINESS SCENARIOS - MANAGER PRIORITY COST OPTIMIZATION
6106
+ # ============================================================================
6107
+
6108
+ @main.command("finops-summary")
6109
+ @click.option('--profile', help='AWS profile name')
6110
+ @click.option('--format', type=click.Choice(['console', 'json']), default='console', help='Output format')
6111
+ def finops_executive_summary(profile, format):
6112
+ """Generate executive summary for all FinOps scenarios ($132,720+ savings)."""
6113
+ try:
6114
+ from runbooks.finops.finops_scenarios import generate_finops_executive_summary
6115
+ results = generate_finops_executive_summary(profile)
6116
+
6117
+ if format == 'json':
6118
+ import json
6119
+ click.echo(json.dumps(results, indent=2, default=str))
6120
+
6121
+ except Exception as e:
6122
+ console.print(f"[red]FinOps executive summary failed: {e}[/red]")
6123
+ raise click.Abort()
6124
+
6125
+
6126
+ @main.command("finops-24")
6127
+ @click.option('--profile', help='AWS profile name')
6128
+ @click.option('--output-file', help='Save results to file')
6129
+ def finops_24_workspaces(profile, output_file):
6130
+ """FinOps-24: WorkSpaces cleanup analysis ($13,020 annual savings - 104% target achievement).
6131
+
6132
+ UNIFIED CLI: Use 'runbooks finops --scenario workspaces' for new unified interface.
6133
+ """
6134
+ from runbooks.common.rich_utils import console, print_warning, print_info
6135
+
6136
+ print_warning("Legacy command detected! Consider using unified interface:")
6137
+ print_info("runbooks finops --scenario workspaces --profile [PROFILE]")
6138
+
6139
+ try:
6140
+ from runbooks.finops.finops_scenarios import analyze_finops_24_workspaces
6141
+ results = analyze_finops_24_workspaces(profile)
6142
+
6143
+ if output_file:
6144
+ import json
6145
+ with open(output_file, 'w') as f:
6146
+ json.dump(results, f, indent=2, default=str)
6147
+ console.print(f"[green]FinOps-24 results saved to {output_file}[/green]")
6148
+
6149
+ except Exception as e:
6150
+ console.print(f"[red]FinOps-24 analysis failed: {e}[/red]")
6151
+ raise click.Abort()
6152
+
6153
+
6154
+ @main.command("finops-23")
6155
+ @click.option('--profile', help='AWS profile name')
6156
+ @click.option('--output-file', help='Save results to file')
6157
+ def finops_23_rds_snapshots(profile, output_file):
6158
+ """FinOps-23: RDS snapshots optimization ($119,700 annual savings - 498% target achievement).
6159
+
6160
+ UNIFIED CLI: Use 'runbooks finops --scenario snapshots' for new unified interface.
6161
+ """
6162
+ from runbooks.common.rich_utils import console, print_warning, print_info
6163
+
6164
+ print_warning("Legacy command detected! Consider using unified interface:")
6165
+ print_info("runbooks finops --scenario snapshots --profile [PROFILE]")
6166
+
6167
+ try:
6168
+ from runbooks.finops.finops_scenarios import analyze_finops_23_rds_snapshots
6169
+ results = analyze_finops_23_rds_snapshots(profile)
6170
+
6171
+ if output_file:
6172
+ import json
6173
+ with open(output_file, 'w') as f:
6174
+ json.dump(results, f, indent=2, default=str)
6175
+ console.print(f"[green]FinOps-23 results saved to {output_file}[/green]")
6176
+
6177
+ except Exception as e:
6178
+ console.print(f"[red]FinOps-23 analysis failed: {e}[/red]")
6179
+ raise click.Abort()
6180
+
6181
+
6182
+ @main.command("finops-25")
6183
+ @click.option('--profile', help='AWS profile name')
6184
+ @click.option('--account-id', default='637423383469', help='Commvault account ID')
6185
+ @click.option('--output-file', help='Save results to file')
6186
+ def finops_25_commvault(profile, account_id, output_file):
6187
+ """FinOps-25: Commvault EC2 investigation framework (Real AWS integration).
6188
+
6189
+ UNIFIED CLI: Use 'runbooks finops --scenario commvault' for new unified interface.
6190
+ """
6191
+ from runbooks.common.rich_utils import console, print_warning, print_info
6192
+
6193
+ print_warning("Legacy command detected! Consider using unified interface:")
6194
+ print_info("runbooks finops --scenario commvault --profile [PROFILE]")
6195
+
6196
+ try:
6197
+ from runbooks.finops.finops_scenarios import investigate_finops_25_commvault
6198
+ results = investigate_finops_25_commvault(profile)
6199
+
6200
+ if output_file:
6201
+ import json
6202
+ with open(output_file, 'w') as f:
6203
+ json.dump(results, f, indent=2, default=str)
6204
+ console.print(f"[green]FinOps-25 results saved to {output_file}[/green]")
6205
+
6206
+ except Exception as e:
6207
+ console.print(f"[red]FinOps-25 investigation failed: {e}[/red]")
6208
+ raise click.Abort()
6209
+
6210
+
6211
+ @main.command("finops-validate")
6212
+ @click.option('--profile', help='AWS profile name')
6213
+ @click.option('--target-accuracy', default=99.5, help='Target validation accuracy percentage')
6214
+ def finops_mcp_validation(profile, target_accuracy):
6215
+ """MCP validation for all FinOps scenarios (≥99.5% accuracy standard)."""
6216
+ try:
6217
+ from runbooks.finops.finops_scenarios import validate_finops_mcp_accuracy
6218
+ results = validate_finops_mcp_accuracy(profile, target_accuracy)
6219
+
6220
+ except Exception as e:
6221
+ console.print(f"[red]FinOps MCP validation failed: {e}[/red]")
6222
+ raise click.Abort()
6223
+
6224
+
5732
6225
  # ============================================================================
5733
6226
  # HELPER FUNCTIONS
5734
6227
  # ============================================================================
@@ -6794,10 +7287,10 @@ def status(ctx):
6794
7287
 
6795
7288
  # Check AWS profiles
6796
7289
  profiles = [
6797
- "ams-admin-Billing-ReadOnlyAccess-909135376185",
6798
- "ams-admin-ReadOnlyAccess-909135376185",
6799
- "ams-centralised-ops-ReadOnlyAccess-335083429030",
6800
- "ams-shared-services-non-prod-ReadOnlyAccess-499201730520",
7290
+ os.getenv("AWS_BILLING_PROFILE", "ams-admin-Billing-ReadOnlyAccess-909135376185"),
7291
+ os.getenv("AWS_MANAGEMENT_PROFILE", "ams-admin-ReadOnlyAccess-909135376185"),
7292
+ os.getenv("AWS_CENTRALISED_OPS_PROFILE", "ams-centralised-ops-ReadOnlyAccess-335083429030"),
7293
+ os.getenv("AWS_SINGLE_ACCOUNT_PROFILE", "ams-shared-services-non-prod-ReadOnlyAccess-499201730520"),
6801
7294
  ]
6802
7295
 
6803
7296
  valid_profiles = 0