runbooks 1.0.0__py3-none-any.whl → 1.0.2__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 (99) hide show
  1. runbooks/__init__.py +1 -1
  2. runbooks/cfat/WEIGHT_CONFIG_README.md +368 -0
  3. runbooks/cfat/app.ts +27 -19
  4. runbooks/cfat/assessment/runner.py +6 -5
  5. runbooks/cfat/tests/test_weight_configuration.ts +449 -0
  6. runbooks/cfat/weight_config.ts +574 -0
  7. runbooks/cloudops/models.py +20 -14
  8. runbooks/common/__init__.py +26 -9
  9. runbooks/common/aws_pricing.py +1070 -105
  10. runbooks/common/aws_pricing_api.py +276 -44
  11. runbooks/common/date_utils.py +115 -0
  12. runbooks/common/dry_run_examples.py +587 -0
  13. runbooks/common/dry_run_framework.py +520 -0
  14. runbooks/common/enhanced_exception_handler.py +10 -7
  15. runbooks/common/mcp_cost_explorer_integration.py +5 -4
  16. runbooks/common/memory_optimization.py +533 -0
  17. runbooks/common/performance_optimization_engine.py +1153 -0
  18. runbooks/common/profile_utils.py +86 -118
  19. runbooks/common/rich_utils.py +3 -3
  20. runbooks/common/sre_performance_suite.py +574 -0
  21. runbooks/finops/business_case_config.py +314 -0
  22. runbooks/finops/cost_processor.py +19 -4
  23. runbooks/finops/dashboard_runner.py +47 -28
  24. runbooks/finops/ebs_cost_optimizer.py +1 -1
  25. runbooks/finops/ebs_optimizer.py +56 -9
  26. runbooks/finops/embedded_mcp_validator.py +642 -36
  27. runbooks/finops/enhanced_trend_visualization.py +7 -2
  28. runbooks/finops/executive_export.py +789 -0
  29. runbooks/finops/finops_dashboard.py +6 -5
  30. runbooks/finops/finops_scenarios.py +34 -27
  31. runbooks/finops/iam_guidance.py +6 -1
  32. runbooks/finops/nat_gateway_optimizer.py +46 -27
  33. runbooks/finops/notebook_utils.py +1 -1
  34. runbooks/finops/schemas.py +73 -58
  35. runbooks/finops/single_dashboard.py +20 -4
  36. runbooks/finops/tests/test_integration.py +3 -1
  37. runbooks/finops/vpc_cleanup_exporter.py +2 -1
  38. runbooks/finops/vpc_cleanup_optimizer.py +22 -29
  39. runbooks/inventory/core/collector.py +51 -28
  40. runbooks/inventory/discovery.md +197 -247
  41. runbooks/inventory/inventory_modules.py +2 -2
  42. runbooks/inventory/list_ec2_instances.py +3 -3
  43. runbooks/inventory/models/account.py +5 -3
  44. runbooks/inventory/models/inventory.py +1 -1
  45. runbooks/inventory/models/resource.py +5 -3
  46. runbooks/inventory/organizations_discovery.py +102 -13
  47. runbooks/inventory/unified_validation_engine.py +2 -15
  48. runbooks/main.py +255 -92
  49. runbooks/operate/base.py +9 -6
  50. runbooks/operate/deployment_framework.py +5 -4
  51. runbooks/operate/deployment_validator.py +6 -5
  52. runbooks/operate/mcp_integration.py +6 -5
  53. runbooks/operate/networking_cost_heatmap.py +17 -13
  54. runbooks/operate/vpc_operations.py +82 -13
  55. runbooks/remediation/base.py +3 -1
  56. runbooks/remediation/commons.py +5 -5
  57. runbooks/remediation/commvault_ec2_analysis.py +66 -18
  58. runbooks/remediation/config/accounts_example.json +31 -0
  59. runbooks/remediation/multi_account.py +120 -7
  60. runbooks/remediation/remediation_cli.py +710 -0
  61. runbooks/remediation/universal_account_discovery.py +377 -0
  62. runbooks/remediation/workspaces_list.py +2 -2
  63. runbooks/security/compliance_automation_engine.py +99 -20
  64. runbooks/security/config/__init__.py +24 -0
  65. runbooks/security/config/compliance_config.py +255 -0
  66. runbooks/security/config/compliance_weights_example.json +22 -0
  67. runbooks/security/config_template_generator.py +500 -0
  68. runbooks/security/security_cli.py +377 -0
  69. runbooks/validation/cli.py +8 -7
  70. runbooks/validation/comprehensive_2way_validator.py +26 -15
  71. runbooks/validation/mcp_validator.py +62 -8
  72. runbooks/vpc/config.py +49 -15
  73. runbooks/vpc/cross_account_session.py +5 -1
  74. runbooks/vpc/heatmap_engine.py +438 -59
  75. runbooks/vpc/mcp_no_eni_validator.py +115 -36
  76. runbooks/vpc/performance_optimized_analyzer.py +546 -0
  77. runbooks/vpc/runbooks_adapter.py +33 -12
  78. runbooks/vpc/tests/conftest.py +4 -2
  79. runbooks/vpc/tests/test_cost_engine.py +3 -1
  80. {runbooks-1.0.0.dist-info → runbooks-1.0.2.dist-info}/METADATA +1 -1
  81. {runbooks-1.0.0.dist-info → runbooks-1.0.2.dist-info}/RECORD +85 -79
  82. runbooks/finops/runbooks.inventory.organizations_discovery.log +0 -0
  83. runbooks/finops/runbooks.security.report_generator.log +0 -0
  84. runbooks/finops/runbooks.security.run_script.log +0 -0
  85. runbooks/finops/runbooks.security.security_export.log +0 -0
  86. runbooks/finops/tests/results_test_finops_dashboard.xml +0 -1
  87. runbooks/inventory/artifacts/scale-optimize-status.txt +0 -12
  88. runbooks/inventory/runbooks.inventory.organizations_discovery.log +0 -0
  89. runbooks/inventory/runbooks.security.report_generator.log +0 -0
  90. runbooks/inventory/runbooks.security.run_script.log +0 -0
  91. runbooks/inventory/runbooks.security.security_export.log +0 -0
  92. runbooks/vpc/runbooks.inventory.organizations_discovery.log +0 -0
  93. runbooks/vpc/runbooks.security.report_generator.log +0 -0
  94. runbooks/vpc/runbooks.security.run_script.log +0 -0
  95. runbooks/vpc/runbooks.security.security_export.log +0 -0
  96. {runbooks-1.0.0.dist-info → runbooks-1.0.2.dist-info}/WHEEL +0 -0
  97. {runbooks-1.0.0.dist-info → runbooks-1.0.2.dist-info}/entry_points.txt +0 -0
  98. {runbooks-1.0.0.dist-info → runbooks-1.0.2.dist-info}/licenses/LICENSE +0 -0
  99. {runbooks-1.0.0.dist-info → runbooks-1.0.2.dist-info}/top_level.txt +0 -0
runbooks/main.py CHANGED
@@ -104,9 +104,31 @@ from runbooks.common.rich_utils import console, create_table, print_banner, prin
104
104
  from runbooks.config import load_config, save_config
105
105
  from runbooks.inventory.core.collector import InventoryCollector
106
106
  from runbooks.utils import setup_logging, setup_enhanced_logging
107
+ from runbooks.finops.business_case_config import get_business_case_config, format_business_achievement
107
108
 
108
109
  console = Console()
109
110
 
111
+ # ============================================================================
112
+ # CLI ARGUMENT FIXES - Handle Profile Tuples and Export Format Issues
113
+ # ============================================================================
114
+
115
+ def normalize_profile_parameter(profile_param):
116
+ """
117
+ Normalize profile parameter from Click multiple=True tuple to string.
118
+
119
+ Args:
120
+ profile_param: Profile parameter from Click (could be tuple, list, or string)
121
+
122
+ Returns:
123
+ str: Single profile name for AWS operations
124
+ """
125
+ if isinstance(profile_param, (tuple, list)) and profile_param:
126
+ return profile_param[0] # Take first profile from tuple/list
127
+ elif isinstance(profile_param, str):
128
+ return profile_param
129
+ else:
130
+ return "default"
131
+
110
132
  # ============================================================================
111
133
  # ACCOUNT ID RESOLUTION HELPER
112
134
  # ============================================================================
@@ -219,7 +241,6 @@ def common_aws_options(f):
219
241
  f = click.option(
220
242
  "--profile",
221
243
  multiple=True,
222
- default=("default",), # Tuple default for multiple=True
223
244
  help="AWS profile(s) - supports: --profile prof1 prof2 OR --profile prof1 --profile prof2 OR --profile prof1,prof2",
224
245
  )(f)
225
246
  f = click.option("--region", default="ap-southeast-2", help="AWS region (default: 'ap-southeast-2')")(f)
@@ -283,7 +304,7 @@ def common_filter_options(f):
283
304
  Examples:
284
305
  ```bash
285
306
  runbooks inventory ec2 --tags Environment=production Team=platform
286
- runbooks inventory s3 --accounts 123456789012 987654321098
307
+ runbooks inventory s3 --accounts 111111111111 222222222222
287
308
  runbooks inventory vpc --regions us-east-1 us-west-2 --tags CostCenter=engineering
288
309
  ```
289
310
  """
@@ -376,15 +397,23 @@ def main(ctx, debug, log_level, json_output, profile, region, dry_run, config):
376
397
  @click.pass_context
377
398
  def inventory(ctx, profile, region, dry_run, output, output_file, tags, accounts, regions):
378
399
  """
379
- Multi-account AWS resource discovery and inventory.
400
+ Universal AWS resource discovery and inventory - works with ANY AWS environment.
380
401
 
381
- Read-only operations for comprehensive resource discovery across
382
- AWS services, accounts, and regions with advanced filtering.
402
+ Universal Compatibility: Works with single accounts, Organizations, and any profile setup
403
+ 🔍 Read-only operations for safe resource discovery across AWS services
404
+ 🚀 Intelligent fallback: Organizations → standalone account detection
405
+
406
+ Profile Options:
407
+ --profile PROFILE Use specific AWS profile (highest priority)
408
+ No --profile Uses AWS_PROFILE environment variable
409
+ No configuration Uses 'default' profile (universal AWS CLI compatibility)
383
410
 
384
411
  Examples:
385
- runbooks inventory collect --resources ec2,rds
386
- runbooks inventory collect --accounts 123456789012 --regions us-east-1
387
- runbooks inventory collect --tags Environment=prod --output json
412
+ runbooks inventory collect # Use default profile
413
+ runbooks inventory collect --profile my-profile # Use specific profile
414
+ runbooks inventory collect --resources ec2,rds # Specific resources
415
+ runbooks inventory collect --all-accounts # Multi-account (if Organizations access)
416
+ runbooks inventory collect --tags Environment=prod # Filtered discovery
388
417
  """
389
418
  # Update context with inventory-specific options
390
419
  ctx.obj.update(
@@ -427,29 +456,35 @@ def inventory(ctx, profile, region, dry_run, output, output_file, tags, accounts
427
456
  def collect(ctx, profile, region, dry_run, resources, all_resources, all_accounts, include_costs, parallel, validate, validate_all,
428
457
  all, combine, csv, json, pdf, markdown, export_format, output_dir, report_name):
429
458
  """
430
- 🔍 Comprehensive AWS resource inventory collection with enhanced exports and validation.
459
+ 🔍 Universal AWS resource inventory collection - works with ANY AWS environment.
431
460
 
432
- Upgraded to match proven finops/vpc best practices:
433
- - Profile override priority (User > Environment > Default)
434
- - Multi-format exports (CSV, JSON, PDF, Markdown, YAML)
435
- - Multi-account support with --all pattern
436
- - Rich CLI integration with enterprise UX standards
461
+ Universal Compatibility Features:
462
+ - Works with single accounts, AWS Organizations, and standalone setups
463
+ - Profile override priority: User > Environment > Default ('default' profile fallback)
464
+ - Intelligent Organizations detection with graceful standalone fallback
465
+ - 50+ AWS services discovery across any account configuration
466
+ - Multi-format exports: CSV, JSON, PDF, Markdown, YAML
437
467
  - MCP validation for ≥99.5% accuracy
438
- - 3-way comprehensive validation: runbooks + MCP + terraform
439
468
 
440
- Export Format Precedence (following finops/vpc patterns):
441
- 1. PRIORITY: Convenience flags (--csv, --json, --pdf, --markdown)
442
- 2. FALLBACK: --export-format option (when convenience flags not used)
443
- 3. Multiple formats supported simultaneously
469
+ Universal Profile Usage:
470
+ - ANY AWS profile works (no hardcoded assumptions)
471
+ - Organizations permissions auto-detected (graceful fallback to single account)
472
+ - AWS_PROFILE environment variable used when available
473
+ - 'default' profile used as universal fallback
444
474
 
445
475
  Examples:
446
- runbooks inventory collect --csv --json
447
- runbooks inventory collect --all --markdown
448
- runbooks inventory collect --profile prod --resources ec2 s3 --pdf
449
- runbooks inventory collect --all-accounts --validate --export-format csv
450
- runbooks inventory collect --resources ec2,rds,lambda --combine --pdf
451
- runbooks inventory collect --validate-all --csv --json --markdown
452
- runbooks inventory collect --profile enterprise --validate-all --export-format json
476
+ # Universal compatibility - works with any AWS setup
477
+ runbooks inventory collect # Default profile
478
+ runbooks inventory collect --profile my-aws-profile # Any profile
479
+ runbooks inventory collect --all-accounts # Auto-detects Organizations
480
+
481
+ # Resource-specific discovery
482
+ runbooks inventory collect --resources ec2,rds,s3 # Specific services
483
+ runbooks inventory collect --all-resources # All 50+ services
484
+
485
+ # Multi-format exports
486
+ runbooks inventory collect --csv --json --pdf # Multiple formats
487
+ runbooks inventory collect --profile prod --validate --markdown
453
488
  """
454
489
  try:
455
490
  console.print(f"[blue]📊 Starting AWS Resource Inventory Collection[/blue]")
@@ -466,9 +501,7 @@ def collect(ctx, profile, region, dry_run, resources, all_resources, all_account
466
501
  from runbooks.common.profile_utils import get_profile_for_operation
467
502
 
468
503
  # Handle profile tuple (multiple=True in common_aws_options)
469
- profile_value = profile
470
- if isinstance(profile_value, tuple) and profile_value:
471
- profile_value = profile_value[0] # Take first profile from tuple
504
+ profile_value = normalize_profile_parameter(profile)
472
505
 
473
506
  resolved_profile = get_profile_for_operation("management", profile_value)
474
507
 
@@ -3674,30 +3707,58 @@ def security(ctx, profile, region, dry_run, language, output, output_file):
3674
3707
 
3675
3708
  @security.command()
3676
3709
  @common_aws_options
3710
+ @click.option(
3711
+ "--frameworks",
3712
+ multiple=True,
3713
+ type=click.Choice([
3714
+ "aws-well-architected",
3715
+ "soc2-type-ii",
3716
+ "pci-dss",
3717
+ "hipaa",
3718
+ "iso27001",
3719
+ "nist-cybersecurity",
3720
+ "cis-benchmarks"
3721
+ ]),
3722
+ help="Compliance frameworks to assess (supports multiple)"
3723
+ )
3677
3724
  @click.option("--checks", multiple=True, help="Specific security checks to run")
3678
- @click.option("--export-formats", multiple=True, default=["json", "csv"], help="Export formats (json, csv, pdf)")
3725
+ @click.option("--export-formats", multiple=True, help="Export formats (json, csv, pdf)")
3679
3726
  @click.pass_context
3680
- def assess(ctx, profile, region, dry_run, checks, export_formats):
3727
+ def assess(ctx, profile, region, dry_run, frameworks, checks, export_formats):
3681
3728
  """Run comprehensive security baseline assessment with Rich CLI output."""
3682
3729
  try:
3683
3730
  from runbooks.security.security_baseline_tester import SecurityBaselineTester
3684
3731
 
3685
3732
  # Use command-level profile with fallback to context profile
3686
- resolved_profile = profile or ctx.obj.get('profile', 'default')
3733
+ # Handle profile tuple (multiple=True in common_aws_options) - CRITICAL CLI FIX
3734
+ profile_str = normalize_profile_parameter(profile)
3735
+ resolved_profile = profile_str or ctx.obj.get('profile', 'default')
3687
3736
  resolved_region = region or ctx.obj.get('region', 'us-east-1')
3737
+
3738
+ # CRITICAL FIX: Handle empty export_formats after removing default value
3739
+ if not export_formats:
3740
+ export_formats = ["json", "csv"]
3741
+
3742
+ # CRITICAL FIX: Handle empty frameworks after removing default value
3743
+ if not frameworks:
3744
+ frameworks = ["aws-well-architected"]
3688
3745
 
3689
3746
  console.print(f"[blue]🔒 Starting Security Assessment[/blue]")
3690
3747
  console.print(
3691
- f"[dim]Profile: {resolved_profile} | Language: {ctx.obj['language']} | Export: {', '.join(export_formats)}[/dim]"
3748
+ f"[dim]Profile: {resolved_profile} | Language: {ctx.obj['language']} | Frameworks: {', '.join(frameworks)} | Export: {', '.join(export_formats)}[/dim]"
3692
3749
  )
3693
3750
 
3694
3751
  # Initialize tester with export formats
3752
+ # TODO: Add frameworks support to SecurityBaselineTester for SOC2, PCI-DSS, HIPAA compliance
3695
3753
  tester = SecurityBaselineTester(
3696
3754
  profile=resolved_profile,
3697
3755
  lang_code=ctx.obj["language"],
3698
3756
  output_dir=ctx.obj.get("output_file"),
3699
3757
  export_formats=list(export_formats),
3700
3758
  )
3759
+
3760
+ # Store frameworks for future enhancement (currently using default aws-well-architected)
3761
+ console.print(f"[dim]Note: Using AWS Well-Architected baseline checks (frameworks parameter accepted for future enhancement)[/dim]")
3701
3762
 
3702
3763
  # Run assessment with Rich CLI
3703
3764
  tester.run()
@@ -5488,8 +5549,8 @@ def cost():
5488
5549
  pass
5489
5550
 
5490
5551
  @cost.command()
5491
- @click.option('--billing-profile', default=None, help='AWS billing profile with Cost Explorer access (uses AWS_BILLING_PROFILE env var if not specified)')
5492
- @click.option('--management-profile', default=None, help='AWS management profile with Organizations access (uses AWS_MANAGEMENT_PROFILE env var if not specified)')
5552
+ @click.option('--billing-profile', default=None, help='AWS profile for Cost Explorer access (uses universal profile selection if not specified)')
5553
+ @click.option('--management-profile', default=None, help='AWS profile for Organizations access (uses universal profile selection if not specified)')
5493
5554
  @click.option('--tolerance-percent', default=5.0, help='MCP cross-validation tolerance percentage')
5494
5555
  @click.option('--performance-target-ms', default=30000.0, help='Performance target in milliseconds')
5495
5556
  @click.option('--export-evidence/--no-export', default=True, help='Export DoD validation evidence')
@@ -5857,7 +5918,7 @@ def workspaces(profile, region, dry_run, analyze, calculate_savings, unused_days
5857
5918
 
5858
5919
  try:
5859
5920
  # Handle profile tuple (multiple=True in common_aws_options)
5860
- active_profile = profile[0] if isinstance(profile, tuple) and profile else "default"
5921
+ active_profile = normalize_profile_parameter(profile)
5861
5922
 
5862
5923
  # Call enhanced workspaces analysis
5863
5924
  ctx = click.Context(get_workspaces)
@@ -5906,7 +5967,7 @@ def rds_snapshots(profile, region, dry_run, manual_only, older_than, calculate_s
5906
5967
 
5907
5968
  try:
5908
5969
  # Handle profile tuple (multiple=True in common_aws_options)
5909
- active_profile = profile[0] if isinstance(profile, tuple) and profile else "default"
5970
+ active_profile = normalize_profile_parameter(profile)
5910
5971
 
5911
5972
  # Call enhanced RDS snapshot analysis
5912
5973
  ctx = click.Context(get_rds_snapshot_details)
@@ -5935,25 +5996,34 @@ def rds_snapshots(profile, region, dry_run, manual_only, older_than, calculate_s
5935
5996
 
5936
5997
  @cost_optimization.command()
5937
5998
  @common_aws_options
5938
- @click.option("--account", default="637423383469", help="Commvault backup account ID (JIRA FinOps-25)")
5999
+ @click.option("--account", help="Commvault backup account ID (JIRA FinOps-25). If not specified, uses current AWS account.")
5939
6000
  @click.option("--investigate-utilization", is_flag=True, help="Investigate EC2 utilization patterns")
5940
6001
  @click.option("--output-file", default="./tmp/commvault_ec2_analysis.csv", help="Output CSV file path")
5941
6002
  def commvault_ec2(profile, region, dry_run, account, investigate_utilization, output_file):
5942
6003
  """
5943
6004
  FinOps-25: Commvault EC2 investigation for cost optimization.
5944
6005
 
5945
- Account: 637423383469 (Commvault backup account)
5946
6006
  Challenge: Determine if EC2 instances are actively used for backups or idle
5947
6007
  """
5948
6008
  from runbooks.remediation.commvault_ec2_analysis import investigate_commvault_ec2
5949
6009
  from runbooks.common.rich_utils import console, print_header
5950
6010
 
5951
6011
  print_header("JIRA FinOps-25: Commvault EC2 Investigation", "v0.9.1")
5952
- console.print(f"[cyan]Account: {account} - Investigating EC2 usage patterns[/cyan]")
5953
6012
 
5954
6013
  try:
5955
6014
  # Handle profile tuple (multiple=True in common_aws_options)
5956
- active_profile = profile[0] if isinstance(profile, tuple) and profile else "default"
6015
+ active_profile = normalize_profile_parameter(profile)
6016
+
6017
+ # If no account specified, detect current account from profile
6018
+ if not account:
6019
+ from runbooks.common.profile_utils import create_operational_session
6020
+ session = create_operational_session(active_profile)
6021
+ sts = session.client('sts')
6022
+ identity = sts.get_caller_identity()
6023
+ account = identity['Account']
6024
+ console.print(f"[dim cyan]Auto-detected account: {account}[/dim cyan]")
6025
+
6026
+ console.print(f"[cyan]Account: {account} - Investigating EC2 usage patterns[/cyan]")
5957
6027
 
5958
6028
  # Call enhanced Commvault EC2 investigation
5959
6029
  ctx = click.Context(investigate_commvault_ec2)
@@ -6039,10 +6109,31 @@ def comprehensive_analysis(profile, region, dry_run, all_scenarios, output_dir):
6039
6109
  ]
6040
6110
  )
6041
6111
 
6042
- summary_table.add_row("FinOps-24", "WorkSpaces Cleanup", "$12,518/year", "Analysis Complete")
6043
- summary_table.add_row("FinOps-23", "RDS Snapshots", "$5K-24K/year", "Analysis Complete")
6044
- summary_table.add_row("FinOps-25", "Commvault EC2", "TBD", "Investigation Complete")
6045
- summary_table.add_row("📊 TOTAL", "All Scenarios", "$17.5K-36.5K/year", "Ready for Implementation")
6112
+ # Use dynamic configuration for summary table
6113
+ config = get_business_case_config()
6114
+ workspaces_scenario = config.get_scenario('workspaces')
6115
+ rds_scenario = config.get_scenario('rds-snapshots')
6116
+ backup_scenario = config.get_scenario('backup-investigation')
6117
+
6118
+ summary_table.add_row(
6119
+ workspaces_scenario.scenario_id if workspaces_scenario else "workspaces",
6120
+ workspaces_scenario.display_name if workspaces_scenario else "WorkSpaces Resource Optimization",
6121
+ workspaces_scenario.savings_range_display if workspaces_scenario else "$12K-15K/year",
6122
+ "Analysis Complete"
6123
+ )
6124
+ summary_table.add_row(
6125
+ rds_scenario.scenario_id if rds_scenario else "rds-snapshots",
6126
+ rds_scenario.display_name if rds_scenario else "RDS Storage Optimization",
6127
+ rds_scenario.savings_range_display if rds_scenario else "$5K-24K/year",
6128
+ "Analysis Complete"
6129
+ )
6130
+ summary_table.add_row(
6131
+ backup_scenario.scenario_id if backup_scenario else "backup-investigation",
6132
+ backup_scenario.display_name if backup_scenario else "Backup Infrastructure Analysis",
6133
+ "Framework Ready",
6134
+ "Investigation Complete"
6135
+ )
6136
+ summary_table.add_row("📊 TOTAL", "All Scenarios", "Dynamic Configuration", "Ready for Implementation")
6046
6137
 
6047
6138
  console.print(summary_table)
6048
6139
 
@@ -6084,7 +6175,7 @@ def _parse_profiles_parameter(profiles_tuple):
6084
6175
  return [p for p in all_profiles if p] # Remove empty strings
6085
6176
 
6086
6177
 
6087
- @main.command()
6178
+ @main.group(invoke_without_command=True)
6088
6179
  @common_aws_options
6089
6180
  @click.option("--time-range", type=int, help="Time range in days (default: current month)")
6090
6181
  @click.option("--report-type", type=click.Choice(["csv", "json", "pdf", "markdown"]), help="Report type for export")
@@ -6143,7 +6234,7 @@ def _parse_profiles_parameter(profiles_tuple):
6143
6234
  @click.option(
6144
6235
  "--scenario",
6145
6236
  type=click.Choice(["workspaces", "snapshots", "commvault", "nat-gateway", "elastic-ip", "ebs", "vpc-cleanup"], case_sensitive=False),
6146
- help="Business scenario analysis: workspaces (FinOps-24: $13,020 savings), snapshots (FinOps-23: $119,700 savings), commvault (FinOps-25: investigation), nat-gateway (FinOps-26: $8K-$12K potential), elastic-ip (FinOps-EIP: $3.65/month direct savings), ebs (FinOps-EBS: 15-20% storage optimization), vpc-cleanup (AWSO-05: $5,869.20 VPC cleanup savings)"
6237
+ help="Business scenario analysis with dynamic configuration - use 'runbooks finops --help-scenarios' for current scenario details"
6147
6238
  )
6148
6239
  @click.pass_context
6149
6240
  def finops(
@@ -6183,14 +6274,16 @@ def finops(
6183
6274
  Comprehensive cost analysis supporting both UnblendedCost (technical)
6184
6275
  and AmortizedCost (financial) perspectives for enterprise reporting.
6185
6276
 
6186
- BUSINESS SCENARIOS ($138,589+ proven savings):
6187
- runbooks finops --scenario workspaces # FinOps-24: WorkSpaces cleanup ($13,020 annual)
6188
- runbooks finops --scenario snapshots # FinOps-23: RDS snapshots ($119,700 annual)
6189
- runbooks finops --scenario commvault # FinOps-25: EC2 investigation framework
6190
- runbooks finops --scenario nat-gateway # FinOps-26: NAT Gateway optimization ($8K-$12K potential)
6191
- runbooks finops --scenario elastic-ip # FinOps-EIP: Elastic IP cleanup ($3.65/month per EIP)
6192
- runbooks finops --scenario ebs # FinOps-EBS: Storage optimization (15-20% cost reduction)
6193
- runbooks finops --scenario vpc-cleanup # AWSO-05: VPC cleanup ($5,869.20 annual savings)
6277
+ BUSINESS SCENARIOS (Dynamic Configuration):
6278
+ runbooks finops --scenario workspaces # WorkSpaces Resource Optimization
6279
+ runbooks finops --scenario snapshots # RDS Storage Optimization
6280
+ runbooks finops --scenario commvault # Backup Infrastructure Analysis
6281
+ runbooks finops --scenario nat-gateway # Network Gateway Optimization
6282
+ runbooks finops --scenario elastic-ip # IP Address Resource Management
6283
+ runbooks finops --scenario ebs # Storage Volume Optimization
6284
+ runbooks finops --scenario vpc-cleanup # Network Infrastructure Cleanup
6285
+
6286
+ Use --help-scenarios to see current configured targets and savings ranges.
6194
6287
 
6195
6288
  GENERAL ANALYTICS:
6196
6289
  runbooks finops --audit --csv --report-name audit_report
@@ -6201,6 +6294,14 @@ def finops(
6201
6294
  runbooks finops --dual-metrics --csv --json # Comprehensive analysis (default)
6202
6295
  """
6203
6296
 
6297
+ # Handle group behavior: if no subcommand invoked, execute main functionality
6298
+ if ctx.invoked_subcommand is None:
6299
+ # Continue with original finops functionality
6300
+ pass
6301
+ else:
6302
+ # Subcommand will handle execution
6303
+ return
6304
+
6204
6305
  # Business Scenario Dispatch Logic (Strategic Objective #1: Unified CLI)
6205
6306
  if scenario:
6206
6307
  from runbooks.common.rich_utils import console, print_header, print_success, print_info
@@ -6218,7 +6319,7 @@ def finops(
6218
6319
  # Initialize CloudOps cost optimizer with enterprise patterns
6219
6320
  execution_mode = ExecutionMode.DRY_RUN if dry_run else ExecutionMode.EXECUTE
6220
6321
  # Ensure profile is a string, not a tuple
6221
- profile_str = profile[0] if isinstance(profile, (tuple, list)) and profile else profile or "default"
6322
+ profile_str = normalize_profile_parameter(profile) or "default"
6222
6323
  cost_optimizer = CostOptimizer(
6223
6324
  profile=profile_str,
6224
6325
  dry_run=dry_run,
@@ -6226,7 +6327,10 @@ def finops(
6226
6327
  )
6227
6328
 
6228
6329
  if scenario.lower() == "workspaces":
6229
- print_info("FinOps-24: WorkSpaces cleanup analysis ($13,020 annual savings - 104% target achievement)")
6330
+ config = get_business_case_config()
6331
+ workspaces_scenario = config.get_scenario('workspaces')
6332
+ scenario_info = f"{workspaces_scenario.display_name} ({workspaces_scenario.savings_range_display})" if workspaces_scenario else "WorkSpaces Resource Optimization"
6333
+ print_info(f"{scenario_info}")
6230
6334
  print_info("🚀 Enhanced with CloudOps enterprise integration")
6231
6335
 
6232
6336
  # Use CloudOps cost optimizer for enterprise-grade analysis
@@ -6235,10 +6339,10 @@ def finops(
6235
6339
  dry_run=dry_run
6236
6340
  ))
6237
6341
 
6238
- # Convert to legacy format for backward compatibility
6342
+ # Convert to dynamic format using business case configuration
6239
6343
  results = {
6240
- "scenario": "FinOps-24",
6241
- "business_case": "WorkSpaces Cleanup",
6344
+ "scenario": workspaces_scenario.scenario_id if workspaces_scenario else "workspaces",
6345
+ "business_case": workspaces_scenario.display_name if workspaces_scenario else "WorkSpaces Resource Optimization",
6242
6346
  "annual_savings": workspaces_result.annual_savings,
6243
6347
  "monthly_savings": workspaces_result.total_monthly_savings,
6244
6348
  "affected_resources": workspaces_result.affected_resources,
@@ -6248,7 +6352,10 @@ def finops(
6248
6352
  }
6249
6353
 
6250
6354
  elif scenario.lower() == "snapshots":
6251
- print_info("FinOps-23: RDS snapshots optimization ($119,700 annual savings - 498% target achievement)")
6355
+ config = get_business_case_config()
6356
+ rds_scenario = config.get_scenario('rds-snapshots')
6357
+ scenario_info = f"{rds_scenario.display_name} ({rds_scenario.savings_range_display})" if rds_scenario else "RDS Storage Optimization"
6358
+ print_info(f"{scenario_info}")
6252
6359
  print_info("🚀 Enhanced with CloudOps enterprise integration")
6253
6360
 
6254
6361
  # Use CloudOps cost optimizer for enterprise-grade analysis
@@ -6257,10 +6364,10 @@ def finops(
6257
6364
  dry_run=dry_run
6258
6365
  ))
6259
6366
 
6260
- # Convert to legacy format for backward compatibility
6367
+ # Convert to dynamic format using business case configuration
6261
6368
  results = {
6262
- "scenario": "FinOps-23",
6263
- "business_case": "RDS Snapshots Cleanup",
6369
+ "scenario": rds_scenario.scenario_id if rds_scenario else "rds-snapshots",
6370
+ "business_case": rds_scenario.display_name if rds_scenario else "RDS Storage Optimization",
6264
6371
  "annual_savings": snapshots_result.annual_savings,
6265
6372
  "monthly_savings": snapshots_result.total_monthly_savings,
6266
6373
  "affected_resources": snapshots_result.affected_resources,
@@ -6270,7 +6377,10 @@ def finops(
6270
6377
  }
6271
6378
 
6272
6379
  elif scenario.lower() == "commvault":
6273
- print_info("FinOps-25: Commvault EC2 investigation framework (Real AWS integration)")
6380
+ config = get_business_case_config()
6381
+ backup_scenario = config.get_scenario('backup-investigation')
6382
+ scenario_info = f"{backup_scenario.display_name}" if backup_scenario else "Backup Infrastructure Analysis"
6383
+ print_info(f"{scenario_info} (Real AWS integration)")
6274
6384
  print_info("🚀 Enhanced with CloudOps enterprise integration")
6275
6385
 
6276
6386
  # Use CloudOps cost optimizer for enterprise-grade investigation
@@ -6279,10 +6389,10 @@ def finops(
6279
6389
  dry_run=True # Always dry-run for investigations
6280
6390
  ))
6281
6391
 
6282
- # Convert to legacy format for backward compatibility
6392
+ # Convert to dynamic format using business case configuration
6283
6393
  results = {
6284
- "scenario": "FinOps-25",
6285
- "business_case": "Commvault EC2 Investigation",
6394
+ "scenario": backup_scenario.scenario_id if backup_scenario else "backup-investigation",
6395
+ "business_case": backup_scenario.display_name if backup_scenario else "Backup Infrastructure Analysis",
6286
6396
  "annual_savings": commvault_result.annual_savings,
6287
6397
  "monthly_savings": commvault_result.total_monthly_savings,
6288
6398
  "affected_resources": commvault_result.affected_resources,
@@ -6293,13 +6403,16 @@ def finops(
6293
6403
  }
6294
6404
 
6295
6405
  elif scenario.lower() == "nat-gateway":
6296
- print_info("FinOps-26: NAT Gateway cost optimization ($8K-$12K potential annual savings)")
6406
+ config = get_business_case_config()
6407
+ nat_scenario = config.get_scenario('nat-gateway')
6408
+ scenario_info = f"{nat_scenario.display_name} ({nat_scenario.savings_range_display})" if nat_scenario else "Network Gateway Optimization"
6409
+ print_info(f"{scenario_info}")
6297
6410
  print_info("🚀 Enterprise multi-region analysis with network dependency validation")
6298
6411
 
6299
6412
  # Use dedicated NAT Gateway optimizer for specialized analysis
6300
6413
  from runbooks.finops.nat_gateway_optimizer import NATGatewayOptimizer
6301
6414
 
6302
- profile_str = profile[0] if isinstance(profile, (tuple, list)) and profile else profile or "default"
6415
+ profile_str = normalize_profile_parameter(profile) or "default"
6303
6416
  nat_optimizer = NATGatewayOptimizer(
6304
6417
  profile_name=profile_str,
6305
6418
  regions=regions or ["us-east-1", "us-west-2", "eu-west-1"]
@@ -6307,10 +6420,10 @@ def finops(
6307
6420
 
6308
6421
  nat_result = asyncio.run(nat_optimizer.analyze_nat_gateways(dry_run=dry_run))
6309
6422
 
6310
- # Convert to legacy format for backward compatibility
6423
+ # Convert to dynamic format using business case configuration
6311
6424
  results = {
6312
- "scenario": "FinOps-26",
6313
- "business_case": "NAT Gateway Cost Optimization",
6425
+ "scenario": nat_scenario.scenario_id if nat_scenario else "nat-gateway",
6426
+ "business_case": nat_scenario.display_name if nat_scenario else "Network Gateway Optimization",
6314
6427
  "annual_savings": nat_result.potential_annual_savings,
6315
6428
  "monthly_savings": nat_result.potential_monthly_savings,
6316
6429
  "total_nat_gateways": nat_result.total_nat_gateways,
@@ -6328,13 +6441,16 @@ def finops(
6328
6441
  }
6329
6442
 
6330
6443
  elif scenario.lower() == "elastic-ip":
6331
- print_info("FinOps-EIP: Elastic IP cost optimization ($3.65/month per unattached EIP)")
6444
+ config = get_business_case_config()
6445
+ eip_scenario = config.get_scenario('elastic-ip')
6446
+ scenario_info = f"{eip_scenario.display_name} ({eip_scenario.savings_range_display})" if eip_scenario else "IP Address Resource Management"
6447
+ print_info(f"{scenario_info}")
6332
6448
  print_info("🚀 Enterprise multi-region analysis with DNS dependency validation")
6333
6449
 
6334
6450
  # Use dedicated Elastic IP optimizer for specialized analysis
6335
6451
  from runbooks.finops.elastic_ip_optimizer import ElasticIPOptimizer
6336
6452
 
6337
- profile_str = profile[0] if isinstance(profile, (tuple, list)) and profile else profile or "default"
6453
+ profile_str = normalize_profile_parameter(profile) or "default"
6338
6454
  eip_optimizer = ElasticIPOptimizer(
6339
6455
  profile_name=profile_str,
6340
6456
  regions=regions or ["us-east-1", "us-west-2", "eu-west-1", "us-east-2"]
@@ -6342,10 +6458,10 @@ def finops(
6342
6458
 
6343
6459
  eip_result = asyncio.run(eip_optimizer.analyze_elastic_ips(dry_run=dry_run))
6344
6460
 
6345
- # Convert to legacy format for backward compatibility
6461
+ # Convert to dynamic format using business case configuration
6346
6462
  results = {
6347
- "scenario": "FinOps-EIP",
6348
- "business_case": "Elastic IP Cost Optimization",
6463
+ "scenario": eip_scenario.scenario_id if eip_scenario else "elastic-ip",
6464
+ "business_case": eip_scenario.display_name if eip_scenario else "IP Address Resource Management",
6349
6465
  "annual_savings": eip_result.potential_annual_savings,
6350
6466
  "monthly_savings": eip_result.potential_monthly_savings,
6351
6467
  "total_elastic_ips": eip_result.total_elastic_ips,
@@ -6365,13 +6481,16 @@ def finops(
6365
6481
  }
6366
6482
 
6367
6483
  elif scenario.lower() == "ebs":
6368
- print_info("FinOps-EBS: EBS Volume storage optimization (15-20% cost reduction potential)")
6484
+ config = get_business_case_config()
6485
+ ebs_scenario = config.get_scenario('ebs-optimization')
6486
+ scenario_info = f"{ebs_scenario.display_name}" if ebs_scenario else "Storage Volume Optimization (15-20% cost reduction potential)"
6487
+ print_info(f"{scenario_info}")
6369
6488
  print_info("🚀 Enterprise comprehensive analysis: GP2→GP3 + Usage + Orphaned cleanup")
6370
6489
 
6371
6490
  # Use dedicated EBS optimizer for specialized analysis
6372
6491
  from runbooks.finops.ebs_optimizer import EBSOptimizer
6373
6492
 
6374
- profile_str = profile[0] if isinstance(profile, (tuple, list)) and profile else profile or "default"
6493
+ profile_str = normalize_profile_parameter(profile) or "default"
6375
6494
  ebs_optimizer = EBSOptimizer(
6376
6495
  profile_name=profile_str,
6377
6496
  regions=regions or ["us-east-1", "us-west-2", "eu-west-1"]
@@ -6379,10 +6498,10 @@ def finops(
6379
6498
 
6380
6499
  ebs_result = asyncio.run(ebs_optimizer.analyze_ebs_volumes(dry_run=dry_run))
6381
6500
 
6382
- # Convert to legacy format for backward compatibility
6501
+ # Convert to dynamic format using business case configuration
6383
6502
  results = {
6384
- "scenario": "FinOps-EBS",
6385
- "business_case": "EBS Volume Storage Optimization",
6503
+ "scenario": ebs_scenario.scenario_id if ebs_scenario else "ebs-optimization",
6504
+ "business_case": ebs_scenario.display_name if ebs_scenario else "Storage Volume Optimization",
6386
6505
  "annual_savings": ebs_result.total_potential_annual_savings,
6387
6506
  "monthly_savings": ebs_result.total_potential_monthly_savings,
6388
6507
  "total_volumes": ebs_result.total_volumes,
@@ -6410,13 +6529,16 @@ def finops(
6410
6529
  }
6411
6530
 
6412
6531
  elif scenario.lower() == "vpc-cleanup":
6413
- print_info("AWSO-05: VPC Cleanup cost optimization ($5,869.20 annual savings)")
6532
+ config = get_business_case_config()
6533
+ vpc_scenario = config.get_scenario('vpc-cleanup')
6534
+ scenario_info = f"{vpc_scenario.display_name} ({vpc_scenario.savings_range_display})" if vpc_scenario else "Network Infrastructure Cleanup"
6535
+ print_info(f"{scenario_info}")
6414
6536
  print_info("🚀 Enterprise three-bucket strategy with dependency validation")
6415
6537
 
6416
6538
  # Use dedicated VPC Cleanup optimizer for AWSO-05 analysis
6417
6539
  from runbooks.finops.vpc_cleanup_optimizer import VPCCleanupOptimizer
6418
6540
 
6419
- profile_str = profile[0] if isinstance(profile, (tuple, list)) and profile else profile or "default"
6541
+ profile_str = normalize_profile_parameter(profile) or "default"
6420
6542
  vpc_optimizer = VPCCleanupOptimizer(
6421
6543
  profile=profile_str
6422
6544
  )
@@ -6431,10 +6553,10 @@ def finops(
6431
6553
  filter_type=filter_type
6432
6554
  )
6433
6555
 
6434
- # Convert to legacy format for backward compatibility
6556
+ # Convert to dynamic format using business case configuration
6435
6557
  results = {
6436
- "scenario": "AWSO-05",
6437
- "business_case": "VPC Cleanup Cost Optimization",
6558
+ "scenario": vpc_scenario.scenario_id if vpc_scenario else "vpc-cleanup",
6559
+ "business_case": vpc_scenario.display_name if vpc_scenario else "Network Infrastructure Cleanup",
6438
6560
  "annual_savings": vpc_result.total_annual_savings,
6439
6561
  "monthly_savings": vpc_result.total_annual_savings / 12,
6440
6562
  "total_vpcs_analyzed": vpc_result.total_vpcs_analyzed,
@@ -6604,7 +6726,7 @@ def finops(
6604
6726
  # CRITICAL FIX: Ensure single profile is correctly handled for downstream processing
6605
6727
  # When multiple profiles are provided via --profile, use the first one as primary profile
6606
6728
  primary_profile = (
6607
- parsed_profiles[0] if parsed_profiles else (profile[0] if isinstance(profile, tuple) and profile else profile)
6729
+ parsed_profiles[0] if parsed_profiles else normalize_profile_parameter(profile)
6608
6730
  )
6609
6731
 
6610
6732
  args = argparse.Namespace(
@@ -6644,6 +6766,45 @@ def finops(
6644
6766
  return run_dashboard(args)
6645
6767
 
6646
6768
 
6769
+ # ============================================================================
6770
+ # FINOPS SUBCOMMANDS - Enhanced CLI Structure
6771
+ # ============================================================================
6772
+
6773
+ @finops.command()
6774
+ @click.option("--profile", help="AWS profile to use")
6775
+ @click.option("--region", help="AWS region")
6776
+ @click.option("--dry-run", is_flag=True, help="Run in dry-run mode")
6777
+ @click.option("--output", type=click.Choice(["json", "csv", "pdf", "html"]), help="Output format")
6778
+ def dashboard(profile, region, dry_run, output):
6779
+ """
6780
+ FinOps cost analytics dashboard.
6781
+
6782
+ Interactive cost analysis with Rich CLI formatting and enterprise-grade reporting.
6783
+ """
6784
+ from runbooks.common.rich_utils import console, print_header
6785
+ from runbooks.finops.dashboard_runner import run_dashboard
6786
+ import argparse
6787
+
6788
+ print_header("FinOps Dashboard", "Cost Analytics & Optimization")
6789
+
6790
+ # Create args namespace compatible with existing dashboard runner
6791
+ args = argparse.Namespace(
6792
+ profile=profile or "default",
6793
+ region=region or "ap-southeast-2",
6794
+ dry_run=dry_run,
6795
+ time_range=30,
6796
+ report_type=output,
6797
+ csv=(output == "csv") if output else False,
6798
+ json=(output == "json") if output else False,
6799
+ pdf=(output == "pdf") if output else False,
6800
+ audit=False,
6801
+ trend=False,
6802
+ validate=False
6803
+ )
6804
+
6805
+ return run_dashboard(args)
6806
+
6807
+
6647
6808
  # ============================================================================
6648
6809
  # FINOPS BUSINESS SCENARIOS - MANAGER PRIORITY COST OPTIMIZATION
6649
6810
  # ============================================================================
@@ -7343,7 +7504,9 @@ def scan(ctx, profile, region, dry_run, resources):
7343
7504
  from runbooks.inventory.core.collector import InventoryCollector
7344
7505
 
7345
7506
  try:
7346
- collector = InventoryCollector(profile=profile, region=region)
7507
+ # Handle profile tuple (multiple=True in common_aws_options) - CRITICAL CLI FIX
7508
+ profile_str = normalize_profile_parameter(profile)
7509
+ collector = InventoryCollector(profile=profile_str, region=region)
7347
7510
 
7348
7511
  # Get current account ID
7349
7512
  account_ids = [collector.get_current_account_id()]
@@ -7675,7 +7838,7 @@ def vpc(ctx, profile, region, dry_run, all, billing_profile, management_profile,
7675
7838
  # If no subcommand is specified, run cleanup analysis by default (KISS principle)
7676
7839
  if ctx.invoked_subcommand is None:
7677
7840
  # Handle profile tuple like other commands
7678
- active_profile = profile[0] if isinstance(profile, tuple) and profile else "default"
7841
+ active_profile = normalize_profile_parameter(profile)
7679
7842
 
7680
7843
  console.print("[cyan]🧹 VPC Cleanup Analysis - Enterprise Safety Controls Enabled[/cyan]")
7681
7844
  console.print(f"[dim]Using AWS profile: {active_profile}[/dim]")
@@ -7802,7 +7965,7 @@ def analyze(ctx, profile, region, dry_run, vpc_ids, output_dir, generate_evidenc
7802
7965
  runbooks vpc analyze --vpc-ids vpc-123 vpc-456 --generate-evidence
7803
7966
  """
7804
7967
  # Fix profile tuple handling like other commands (lines 5567-5568 pattern)
7805
- active_profile = profile[0] if isinstance(profile, tuple) and profile else "default"
7968
+ active_profile = normalize_profile_parameter(profile)
7806
7969
 
7807
7970
  console.print("[cyan]🔍 VPC Analysis - Enhanced with VPC Module Integration[/cyan]")
7808
7971
  console.print(f"[dim]Using AWS profile: {active_profile}[/dim]")