runbooks 1.1.4__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.
Files changed (228) hide show
  1. runbooks/__init__.py +31 -2
  2. runbooks/__init___optimized.py +18 -4
  3. runbooks/_platform/__init__.py +1 -5
  4. runbooks/_platform/core/runbooks_wrapper.py +141 -138
  5. runbooks/aws2/accuracy_validator.py +812 -0
  6. runbooks/base.py +7 -0
  7. runbooks/cfat/assessment/compliance.py +1 -1
  8. runbooks/cfat/assessment/runner.py +1 -0
  9. runbooks/cfat/cloud_foundations_assessment.py +227 -239
  10. runbooks/cli/__init__.py +1 -1
  11. runbooks/cli/commands/cfat.py +64 -23
  12. runbooks/cli/commands/finops.py +1005 -54
  13. runbooks/cli/commands/inventory.py +138 -35
  14. runbooks/cli/commands/operate.py +9 -36
  15. runbooks/cli/commands/security.py +42 -18
  16. runbooks/cli/commands/validation.py +432 -18
  17. runbooks/cli/commands/vpc.py +81 -17
  18. runbooks/cli/registry.py +22 -10
  19. runbooks/cloudops/__init__.py +20 -27
  20. runbooks/cloudops/base.py +96 -107
  21. runbooks/cloudops/cost_optimizer.py +544 -542
  22. runbooks/cloudops/infrastructure_optimizer.py +5 -4
  23. runbooks/cloudops/interfaces.py +224 -225
  24. runbooks/cloudops/lifecycle_manager.py +5 -4
  25. runbooks/cloudops/mcp_cost_validation.py +252 -235
  26. runbooks/cloudops/models.py +78 -53
  27. runbooks/cloudops/monitoring_automation.py +5 -4
  28. runbooks/cloudops/notebook_framework.py +177 -213
  29. runbooks/cloudops/security_enforcer.py +125 -159
  30. runbooks/common/accuracy_validator.py +11 -0
  31. runbooks/common/aws_pricing.py +349 -326
  32. runbooks/common/aws_pricing_api.py +211 -212
  33. runbooks/common/aws_profile_manager.py +40 -36
  34. runbooks/common/aws_utils.py +74 -79
  35. runbooks/common/business_logic.py +126 -104
  36. runbooks/common/cli_decorators.py +36 -60
  37. runbooks/common/comprehensive_cost_explorer_integration.py +455 -463
  38. runbooks/common/cross_account_manager.py +197 -204
  39. runbooks/common/date_utils.py +27 -39
  40. runbooks/common/decorators.py +29 -19
  41. runbooks/common/dry_run_examples.py +173 -208
  42. runbooks/common/dry_run_framework.py +157 -155
  43. runbooks/common/enhanced_exception_handler.py +15 -4
  44. runbooks/common/enhanced_logging_example.py +50 -64
  45. runbooks/common/enhanced_logging_integration_example.py +65 -37
  46. runbooks/common/env_utils.py +16 -16
  47. runbooks/common/error_handling.py +40 -38
  48. runbooks/common/lazy_loader.py +41 -23
  49. runbooks/common/logging_integration_helper.py +79 -86
  50. runbooks/common/mcp_cost_explorer_integration.py +476 -493
  51. runbooks/common/mcp_integration.py +63 -74
  52. runbooks/common/memory_optimization.py +140 -118
  53. runbooks/common/module_cli_base.py +37 -58
  54. runbooks/common/organizations_client.py +175 -193
  55. runbooks/common/patterns.py +23 -25
  56. runbooks/common/performance_monitoring.py +67 -71
  57. runbooks/common/performance_optimization_engine.py +283 -274
  58. runbooks/common/profile_utils.py +111 -37
  59. runbooks/common/rich_utils.py +201 -141
  60. runbooks/common/sre_performance_suite.py +177 -186
  61. runbooks/enterprise/__init__.py +1 -1
  62. runbooks/enterprise/logging.py +144 -106
  63. runbooks/enterprise/security.py +187 -204
  64. runbooks/enterprise/validation.py +43 -56
  65. runbooks/finops/__init__.py +26 -30
  66. runbooks/finops/account_resolver.py +1 -1
  67. runbooks/finops/advanced_optimization_engine.py +980 -0
  68. runbooks/finops/automation_core.py +268 -231
  69. runbooks/finops/business_case_config.py +184 -179
  70. runbooks/finops/cli.py +660 -139
  71. runbooks/finops/commvault_ec2_analysis.py +157 -164
  72. runbooks/finops/compute_cost_optimizer.py +336 -320
  73. runbooks/finops/config.py +20 -20
  74. runbooks/finops/cost_optimizer.py +484 -618
  75. runbooks/finops/cost_processor.py +332 -214
  76. runbooks/finops/dashboard_runner.py +1006 -172
  77. runbooks/finops/ebs_cost_optimizer.py +991 -657
  78. runbooks/finops/elastic_ip_optimizer.py +317 -257
  79. runbooks/finops/enhanced_mcp_integration.py +340 -0
  80. runbooks/finops/enhanced_progress.py +32 -29
  81. runbooks/finops/enhanced_trend_visualization.py +3 -2
  82. runbooks/finops/enterprise_wrappers.py +223 -285
  83. runbooks/finops/executive_export.py +203 -160
  84. runbooks/finops/helpers.py +130 -288
  85. runbooks/finops/iam_guidance.py +1 -1
  86. runbooks/finops/infrastructure/__init__.py +80 -0
  87. runbooks/finops/infrastructure/commands.py +506 -0
  88. runbooks/finops/infrastructure/load_balancer_optimizer.py +866 -0
  89. runbooks/finops/infrastructure/vpc_endpoint_optimizer.py +832 -0
  90. runbooks/finops/markdown_exporter.py +337 -174
  91. runbooks/finops/mcp_validator.py +1952 -0
  92. runbooks/finops/nat_gateway_optimizer.py +1512 -481
  93. runbooks/finops/network_cost_optimizer.py +657 -587
  94. runbooks/finops/notebook_utils.py +226 -188
  95. runbooks/finops/optimization_engine.py +1136 -0
  96. runbooks/finops/optimizer.py +19 -23
  97. runbooks/finops/rds_snapshot_optimizer.py +367 -411
  98. runbooks/finops/reservation_optimizer.py +427 -363
  99. runbooks/finops/scenario_cli_integration.py +64 -65
  100. runbooks/finops/scenarios.py +1277 -438
  101. runbooks/finops/schemas.py +218 -182
  102. runbooks/finops/snapshot_manager.py +2289 -0
  103. runbooks/finops/types.py +3 -3
  104. runbooks/finops/validation_framework.py +259 -265
  105. runbooks/finops/vpc_cleanup_exporter.py +189 -144
  106. runbooks/finops/vpc_cleanup_optimizer.py +591 -573
  107. runbooks/finops/workspaces_analyzer.py +171 -182
  108. runbooks/integration/__init__.py +89 -0
  109. runbooks/integration/mcp_integration.py +1920 -0
  110. runbooks/inventory/CLAUDE.md +816 -0
  111. runbooks/inventory/__init__.py +2 -2
  112. runbooks/inventory/cloud_foundations_integration.py +144 -149
  113. runbooks/inventory/collectors/aws_comprehensive.py +1 -1
  114. runbooks/inventory/collectors/aws_networking.py +109 -99
  115. runbooks/inventory/collectors/base.py +4 -0
  116. runbooks/inventory/core/collector.py +495 -313
  117. runbooks/inventory/drift_detection_cli.py +69 -96
  118. runbooks/inventory/inventory_mcp_cli.py +48 -46
  119. runbooks/inventory/list_rds_snapshots_aggregator.py +192 -208
  120. runbooks/inventory/mcp_inventory_validator.py +549 -465
  121. runbooks/inventory/mcp_vpc_validator.py +359 -442
  122. runbooks/inventory/organizations_discovery.py +55 -51
  123. runbooks/inventory/rich_inventory_display.py +33 -32
  124. runbooks/inventory/unified_validation_engine.py +278 -251
  125. runbooks/inventory/vpc_analyzer.py +732 -695
  126. runbooks/inventory/vpc_architecture_validator.py +293 -348
  127. runbooks/inventory/vpc_dependency_analyzer.py +382 -378
  128. runbooks/inventory/vpc_flow_analyzer.py +1 -1
  129. runbooks/main.py +49 -34
  130. runbooks/main_final.py +91 -60
  131. runbooks/main_minimal.py +22 -10
  132. runbooks/main_optimized.py +131 -100
  133. runbooks/main_ultra_minimal.py +7 -2
  134. runbooks/mcp/__init__.py +36 -0
  135. runbooks/mcp/integration.py +679 -0
  136. runbooks/monitoring/performance_monitor.py +9 -4
  137. runbooks/operate/dynamodb_operations.py +3 -1
  138. runbooks/operate/ec2_operations.py +145 -137
  139. runbooks/operate/iam_operations.py +146 -152
  140. runbooks/operate/networking_cost_heatmap.py +29 -8
  141. runbooks/operate/rds_operations.py +223 -254
  142. runbooks/operate/s3_operations.py +107 -118
  143. runbooks/operate/vpc_operations.py +646 -616
  144. runbooks/remediation/base.py +1 -1
  145. runbooks/remediation/commons.py +10 -7
  146. runbooks/remediation/commvault_ec2_analysis.py +70 -66
  147. runbooks/remediation/ec2_unattached_ebs_volumes.py +1 -0
  148. runbooks/remediation/multi_account.py +24 -21
  149. runbooks/remediation/rds_snapshot_list.py +86 -60
  150. runbooks/remediation/remediation_cli.py +92 -146
  151. runbooks/remediation/universal_account_discovery.py +83 -79
  152. runbooks/remediation/workspaces_list.py +46 -41
  153. runbooks/security/__init__.py +19 -0
  154. runbooks/security/assessment_runner.py +1150 -0
  155. runbooks/security/baseline_checker.py +812 -0
  156. runbooks/security/cloudops_automation_security_validator.py +509 -535
  157. runbooks/security/compliance_automation_engine.py +17 -17
  158. runbooks/security/config/__init__.py +2 -2
  159. runbooks/security/config/compliance_config.py +50 -50
  160. runbooks/security/config_template_generator.py +63 -76
  161. runbooks/security/enterprise_security_framework.py +1 -1
  162. runbooks/security/executive_security_dashboard.py +519 -508
  163. runbooks/security/multi_account_security_controls.py +959 -1210
  164. runbooks/security/real_time_security_monitor.py +422 -444
  165. runbooks/security/security_baseline_tester.py +1 -1
  166. runbooks/security/security_cli.py +143 -112
  167. runbooks/security/test_2way_validation.py +439 -0
  168. runbooks/security/two_way_validation_framework.py +852 -0
  169. runbooks/sre/production_monitoring_framework.py +167 -177
  170. runbooks/tdd/__init__.py +15 -0
  171. runbooks/tdd/cli.py +1071 -0
  172. runbooks/utils/__init__.py +14 -17
  173. runbooks/utils/logger.py +7 -2
  174. runbooks/utils/version_validator.py +50 -47
  175. runbooks/validation/__init__.py +6 -6
  176. runbooks/validation/cli.py +9 -3
  177. runbooks/validation/comprehensive_2way_validator.py +745 -704
  178. runbooks/validation/mcp_validator.py +906 -228
  179. runbooks/validation/terraform_citations_validator.py +104 -115
  180. runbooks/validation/terraform_drift_detector.py +447 -451
  181. runbooks/vpc/README.md +617 -0
  182. runbooks/vpc/__init__.py +8 -1
  183. runbooks/vpc/analyzer.py +577 -0
  184. runbooks/vpc/cleanup_wrapper.py +476 -413
  185. runbooks/vpc/cli_cloudtrail_commands.py +339 -0
  186. runbooks/vpc/cli_mcp_validation_commands.py +480 -0
  187. runbooks/vpc/cloudtrail_audit_integration.py +717 -0
  188. runbooks/vpc/config.py +92 -97
  189. runbooks/vpc/cost_engine.py +411 -148
  190. runbooks/vpc/cost_explorer_integration.py +553 -0
  191. runbooks/vpc/cross_account_session.py +101 -106
  192. runbooks/vpc/enhanced_mcp_validation.py +917 -0
  193. runbooks/vpc/eni_gate_validator.py +961 -0
  194. runbooks/vpc/heatmap_engine.py +185 -160
  195. runbooks/vpc/mcp_no_eni_validator.py +680 -639
  196. runbooks/vpc/nat_gateway_optimizer.py +358 -0
  197. runbooks/vpc/networking_wrapper.py +15 -8
  198. runbooks/vpc/pdca_remediation_planner.py +528 -0
  199. runbooks/vpc/performance_optimized_analyzer.py +219 -231
  200. runbooks/vpc/runbooks_adapter.py +1167 -241
  201. runbooks/vpc/tdd_red_phase_stubs.py +601 -0
  202. runbooks/vpc/test_data_loader.py +358 -0
  203. runbooks/vpc/tests/conftest.py +314 -4
  204. runbooks/vpc/tests/test_cleanup_framework.py +1022 -0
  205. runbooks/vpc/tests/test_cost_engine.py +0 -2
  206. runbooks/vpc/topology_generator.py +326 -0
  207. runbooks/vpc/unified_scenarios.py +1297 -1124
  208. runbooks/vpc/vpc_cleanup_integration.py +1943 -1115
  209. runbooks-1.1.5.dist-info/METADATA +328 -0
  210. {runbooks-1.1.4.dist-info β†’ runbooks-1.1.5.dist-info}/RECORD +214 -193
  211. runbooks/finops/README.md +0 -414
  212. runbooks/finops/accuracy_cross_validator.py +0 -647
  213. runbooks/finops/business_cases.py +0 -950
  214. runbooks/finops/dashboard_router.py +0 -922
  215. runbooks/finops/ebs_optimizer.py +0 -973
  216. runbooks/finops/embedded_mcp_validator.py +0 -1629
  217. runbooks/finops/enhanced_dashboard_runner.py +0 -527
  218. runbooks/finops/finops_dashboard.py +0 -584
  219. runbooks/finops/finops_scenarios.py +0 -1218
  220. runbooks/finops/legacy_migration.py +0 -730
  221. runbooks/finops/multi_dashboard.py +0 -1519
  222. runbooks/finops/single_dashboard.py +0 -1113
  223. runbooks/finops/unlimited_scenarios.py +0 -393
  224. runbooks-1.1.4.dist-info/METADATA +0 -800
  225. {runbooks-1.1.4.dist-info β†’ runbooks-1.1.5.dist-info}/WHEEL +0 -0
  226. {runbooks-1.1.4.dist-info β†’ runbooks-1.1.5.dist-info}/entry_points.txt +0 -0
  227. {runbooks-1.1.4.dist-info β†’ runbooks-1.1.5.dist-info}/licenses/LICENSE +0 -0
  228. {runbooks-1.1.4.dist-info β†’ runbooks-1.1.5.dist-info}/top_level.txt +0 -0
runbooks/finops/cli.py CHANGED
@@ -13,21 +13,9 @@ console = Console()
13
13
 
14
14
 
15
15
  def welcome_banner() -> None:
16
+ """Production welcome banner for CloudOps & FinOps Platform"""
16
17
  banner = rf"""
17
- [bold red]
18
- /$$$$$$ /$$ /$$ /$$$$$$ /$$$$$$$ /$$ /$$
19
- /$$__ $$| $$ | $$ /$$__ $$ | $$__ $$ | $$ | $$
20
- | $$ \__/| $$ /$$$$$$ /$$ /$$ /$$$$$$$| $$ \ $$ /$$$$$$ /$$$$$$$ | $$ \ $$ /$$ /$$ /$$$$$$$ | $$$$$$$ /$$$$$$ /$$$$$$ | $$ /$$ /$$
21
- | $$ | $$ /$$__ $$| $$ | $$ /$$__ $$| $$ | $$ /$$__ $$ /$$_____/ | $$$$$$$/ | $$ | $$| $$__ $$ | $$__ $$ /$$__ $$ /$$__ $$| $$ | $$ | $$
22
- | $$ | $$| $$ \ $$| $$ | $$| $$ | $$| $$ | $$| $$ \ $$| $$$$$$ | $$__ $$ | $$ | $$| $$ \ $$ | $$ \ $$| $$ \ $$| $$ \ $$| $$ | $$ | $$
23
- | $$ $$| $$| $$ | $$| $$ | $$| $$ | $$| $$ | $$| $$ | $$ \____ $$ | $$ \ $$ | $$ | $$| $$ | $$ | $$ | $$| $$ | $$| $$ | $$| $$ | $$ | $$
24
- | $$$$$$/| $$| $$$$$$/| $$$$$$/| $$$$$$$| $$$$$$/| $$$$$$$/ /$$$$$$$/ | $$ | $$ | $$$$$$/| $$ | $$ | $$$$$$$/| $$$$$$/| $$$$$$/| $$ | $$$$$$/
25
- \______/ |__/ \______/ \______/ \_______/ \______/ | $$____/ |_______/ |__/ |__/ \______/ |__/ |__/ |_______/ \______/ \______/ |__/ \______/
26
- | $$
27
- | $$
28
- |__/
29
- [/]
30
- [bold bright_blue]CloudOps Runbooks FinOps Platform (v{__version__})[/]
18
+ [bold bright_blue]CloudOps & FinOps Runbooks Platform (v{__version__})[/]
31
19
  """
32
20
  console.print(banner)
33
21
 
@@ -54,7 +42,15 @@ def main() -> int:
54
42
 
55
43
  # Create the parser instance to be accessible for get_default
56
44
  parser = argparse.ArgumentParser(
57
- description="CloudOps Runbooks FinOps Platform - Enterprise Multi-Account Cost Optimization"
45
+ description="CloudOps & FinOps Runbooks Platform - Enterprise Multi-Account Cost Optimization",
46
+ epilog="""
47
+ AWS Profile Usage Examples:
48
+ Single Profile: runbooks finops --profile my-account
49
+ Multi-Account LZ: runbooks finops --all-profile
50
+ Legacy Support: runbooks finops --profiles account1 account2 (still supported)
51
+ Legacy All: runbooks finops --all (still supported, use --all-profile instead)
52
+ """,
53
+ formatter_class=argparse.RawDescriptionHelpFormatter,
58
54
  )
59
55
 
60
56
  parser.add_argument(
@@ -63,13 +59,30 @@ def main() -> int:
63
59
  help="Path to a TOML, YAML, or JSON configuration file.",
64
60
  type=str,
65
61
  )
62
+ # AWS Profile Parameters (Standardized)
63
+ parser.add_argument(
64
+ "--profile",
65
+ help="Single AWS profile for targeted analysis (replaces single --profiles usage)",
66
+ type=str,
67
+ )
68
+ parser.add_argument(
69
+ "--all-profile",
70
+ action="store_true",
71
+ help="Multi-account Landing Zone operations across all available AWS profiles",
72
+ )
73
+
74
+ # Legacy Parameters (Backward Compatibility)
66
75
  parser.add_argument(
67
76
  "--profiles",
68
77
  "-p",
69
78
  nargs="+",
70
- help="Specific AWS profiles to use (space-separated)",
79
+ help="[LEGACY] Specific AWS profiles to use (space-separated) - use --profile for single profile",
71
80
  type=str,
72
81
  )
82
+ parser.add_argument(
83
+ "--all", "-a", action="store_true", help="[LEGACY] Use all available AWS profiles - use --all-profile instead"
84
+ )
85
+
73
86
  parser.add_argument(
74
87
  "--regions",
75
88
  "-r",
@@ -77,7 +90,6 @@ def main() -> int:
77
90
  help="AWS regions to check for EC2 instances (space-separated)",
78
91
  type=str,
79
92
  )
80
- parser.add_argument("--all", "-a", action="store_true", help="Use all available AWS profiles")
81
93
  parser.add_argument(
82
94
  "--combine",
83
95
  "-c",
@@ -100,6 +112,29 @@ def main() -> int:
100
112
  type=str,
101
113
  default=["markdown"],
102
114
  )
115
+
116
+ # Convenience export format flags (LEAN enhancement: map to existing --report-type functionality)
117
+ parser.add_argument(
118
+ "--csv",
119
+ action="store_true",
120
+ help="Export to CSV format (convenience flag for --report-type csv)",
121
+ )
122
+ parser.add_argument(
123
+ "--json",
124
+ action="store_true",
125
+ help="Export to JSON format (convenience flag for --report-type json)",
126
+ )
127
+ parser.add_argument(
128
+ "--pdf",
129
+ action="store_true",
130
+ help="Export to PDF format (convenience flag for --report-type pdf)",
131
+ )
132
+ parser.add_argument(
133
+ "--markdown",
134
+ action="store_true",
135
+ help="Export to Markdown format (convenience flag for --report-type markdown)",
136
+ )
137
+
103
138
  parser.add_argument(
104
139
  "--dir",
105
140
  "-d",
@@ -127,23 +162,7 @@ def main() -> int:
127
162
  parser.add_argument(
128
163
  "--audit",
129
164
  action="store_true",
130
- help="Display an audit report with cost anomalies, stopped EC2 instances, unused EBS columes, budget alerts, and more",
131
- )
132
- parser.add_argument(
133
- "--pdca",
134
- action="store_true",
135
- help="Run autonomous PDCA (Plan-Do-Check-Act) cycles for continuous improvement",
136
- )
137
- parser.add_argument(
138
- "--pdca-cycles",
139
- help="Number of PDCA cycles to run (default: 3, 0 for continuous mode)",
140
- type=int,
141
- default=3,
142
- )
143
- parser.add_argument(
144
- "--pdca-continuous",
145
- action="store_true",
146
- help="Run PDCA in continuous mode (until manually stopped)",
165
+ help="Display an audit report with cost anomalies, stopped EC2 instances, unused EBS volumes, budget alerts, and more",
147
166
  )
148
167
 
149
168
  # Enhanced Dashboard Configuration Parameters
@@ -207,28 +226,44 @@ def main() -> int:
207
226
  help="Minimum confidence threshold for validation (default: 99.5%%)",
208
227
  )
209
228
 
210
- # AWS Cost Metrics Parameters (Technical vs Financial Analysis)
229
+ # AWS Cost Metrics Parameters (v1.1.5 - Technical vs Financial Analysis)
230
+ # Note: These parameters are mutually exclusive - specify only one
211
231
  parser.add_argument(
212
232
  "--unblended",
213
233
  action="store_true",
214
- help="Use UnblendedCost metrics for technical analysis (actual resource utilization)",
234
+ help="Use UnblendedCost metrics for technical analysis (actual charges before discounts, ideal for DevOps/SRE teams). Mutually exclusive with other cost metric options.",
215
235
  )
216
236
  parser.add_argument(
217
237
  "--amortized",
218
238
  action="store_true",
219
- help="Use AmortizedCost metrics for financial analysis (accounts for Reserved Instance/Savings Plans discounts)",
239
+ help="Use AmortizedCost metrics for financial analysis (includes RI/Savings Plans amortization, ideal for Finance teams). Mutually exclusive with other cost metric options.",
240
+ )
241
+ parser.add_argument(
242
+ "--tech-focus",
243
+ action="store_true",
244
+ help="Technical analysis focus (UnblendedCost + DevOps optimizations) - comprehensive technical optimization mode. Mutually exclusive with other cost metric options.",
245
+ )
246
+ parser.add_argument(
247
+ "--financial-focus",
248
+ action="store_true",
249
+ help="Financial reporting focus (AmortizedCost + Finance optimizations) - comprehensive financial analysis mode. Mutually exclusive with other cost metric options.",
220
250
  )
221
251
  parser.add_argument(
222
252
  "--dual-metrics",
223
253
  action="store_true",
224
- help="Show both UnblendedCost and AmortizedCost metrics for comprehensive analysis",
254
+ help="Show both UnblendedCost and AmortizedCost metrics side-by-side for comprehensive business analysis (default behavior).",
255
+ )
256
+ parser.add_argument(
257
+ "--export-markdown",
258
+ action="store_true",
259
+ help="Rich-styled markdown export with enhanced formatting and visual indicators (alternative to basic --markdown flag)",
225
260
  )
226
261
 
227
262
  # Business Scenario Support (DoD Requirement)
228
263
  parser.add_argument(
229
264
  "--scenario",
230
265
  type=str,
231
- help="Business scenario analysis (workspaces, rds-snapshots, backup-investigation, nat-gateway, elastic-ip, ebs-optimization, vpc-cleanup)",
266
+ help="Business scenario analysis (workspaces, rds-snapshots, backup-investigation, nat-gateway, elastic-ip, ebs-optimization, vpc-cleanup, ec2-snapshots)",
232
267
  )
233
268
  parser.add_argument(
234
269
  "--help-scenario",
@@ -236,6 +271,38 @@ def main() -> int:
236
271
  help="Display detailed help for specific scenario",
237
272
  )
238
273
 
274
+ # Sprint 1 Cost Optimization Implementation
275
+ parser.add_argument(
276
+ "--sprint1-analysis",
277
+ action="store_true",
278
+ help="Run comprehensive Sprint 1 cost optimization analysis targeting $260K annual savings",
279
+ )
280
+ parser.add_argument(
281
+ "--optimize-nat-gateways",
282
+ action="store_true",
283
+ help="Run NAT Gateway optimization analysis (Target: $20K+ annual savings)",
284
+ )
285
+ parser.add_argument(
286
+ "--cleanup-snapshots",
287
+ action="store_true",
288
+ help="Run EC2 snapshot cleanup analysis (Target: $15K+ annual savings)",
289
+ )
290
+ parser.add_argument(
291
+ "--optimize-elastic-ips",
292
+ action="store_true",
293
+ help="Run Elastic IP optimization analysis (Target: $5K+ annual savings)",
294
+ )
295
+ parser.add_argument(
296
+ "--mcp-validation",
297
+ action="store_true",
298
+ help="Enable MCP validation for β‰₯99.5% accuracy cross-validation",
299
+ )
300
+ parser.add_argument(
301
+ "--validate-mcp",
302
+ action="store_true",
303
+ help="Run standalone MCP validation framework (AWS-2 implementation)",
304
+ )
305
+
239
306
  args = parser.parse_args()
240
307
 
241
308
  config_data: Optional[Dict[str, Any]] = None
@@ -250,14 +317,163 @@ def main() -> int:
250
317
  if hasattr(args, key) and getattr(args, key) == parser.get_default(key):
251
318
  setattr(args, key, value)
252
319
 
320
+ # AWS Profile Parameter Standardization & Backward Compatibility
321
+ from runbooks.common.rich_utils import print_info, print_warning
322
+
323
+ # Handle backward compatibility and parameter standardization
324
+ profile_standardization_applied = False
325
+
326
+ # Case 1: New --profile parameter used (single profile)
327
+ if args.profile:
328
+ # If user also specified legacy --profiles, show warning but prioritize --profile
329
+ if args.profiles:
330
+ print_warning("⚠️ Both --profile and --profiles specified. Using --profile (recommended)")
331
+ # Convert --profile to --profiles format for internal compatibility
332
+ args.profiles = [args.profile]
333
+ profile_standardization_applied = True
334
+
335
+ # Case 2: New --all-profile parameter used (multi-account Landing Zone)
336
+ # argparse converts --all-profile to args.all_profile (hyphen to underscore)
337
+ if getattr(args, "all_profile", False):
338
+ # If user also specified legacy --all, show info message
339
+ if args.all:
340
+ print_info("ℹ️ Both --all-profile and --all specified. Using --all-profile (recommended)")
341
+ # Set internal --all flag for compatibility with existing code
342
+ args.all = True
343
+ profile_standardization_applied = True
344
+
345
+ # Case 3: Legacy --profiles with single profile (suggest --profile)
346
+ if args.profiles and len(args.profiles) == 1 and not args.profile:
347
+ print_info(f"πŸ’‘ Consider using '--profile {args.profiles[0]}' for single profile operations")
348
+
349
+ # Case 4: Legacy --all (suggest --all-profile)
350
+ if args.all and not getattr(args, "all_profile", False):
351
+ print_info("πŸ’‘ Consider using '--all-profile' for multi-account Landing Zone operations")
352
+
353
+ if profile_standardization_applied:
354
+ print_info("βœ… AWS profile parameter standardization applied")
355
+
356
+ # Process convenience export format flags (LEAN enhancement: map to existing functionality)
357
+ convenience_formats = []
358
+ if args.csv:
359
+ convenience_formats.append("csv")
360
+ if args.json:
361
+ convenience_formats.append("json")
362
+ if args.pdf:
363
+ convenience_formats.append("pdf")
364
+ if args.markdown:
365
+ convenience_formats.append("markdown")
366
+
367
+ # If any convenience flags were used, handle them appropriately
368
+ if convenience_formats:
369
+ # Check if --report-type was explicitly specified by checking sys.argv
370
+ report_type_explicit = "--report-type" in sys.argv or "-y" in sys.argv
371
+
372
+ if report_type_explicit:
373
+ # User explicitly set --report-type, so combine with convenience flags
374
+ combined_formats = list(set(args.report_type + convenience_formats))
375
+ args.report_type = combined_formats
376
+ console.print(f"[cyan]ℹ️ Using combined export formats: {', '.join(sorted(combined_formats))}[/]")
377
+ else:
378
+ # User only used convenience flags, replace default with convenience flags only
379
+ args.report_type = convenience_formats
380
+
381
+ # Process cost metrics parameters (v1.1.5 implementation - Enhanced with conflict detection)
382
+ cost_metrics_processed = False
383
+
384
+ # Check for parameter conflicts first
385
+ cost_metric_flags = [args.unblended, args.amortized, args.tech_focus, args.financial_focus]
386
+
387
+ # Validate cost metric parameter conflicts
388
+ if sum(cost_metric_flags) > 1:
389
+ conflicting_params = []
390
+ if args.unblended:
391
+ conflicting_params.append("--unblended")
392
+ if args.amortized:
393
+ conflicting_params.append("--amortized")
394
+ if args.tech_focus:
395
+ conflicting_params.append("--tech-focus")
396
+ if args.financial_focus:
397
+ conflicting_params.append("--financial-focus")
398
+
399
+ console.print(f"[red]❌ Error: Conflicting cost metric parameters: {', '.join(conflicting_params)}[/red]")
400
+ console.print("[yellow]πŸ’‘ Please specify only one cost metric option:[/yellow]")
401
+ console.print("[yellow] β€’ --unblended (technical analysis)[/yellow]")
402
+ console.print("[yellow] β€’ --amortized (financial analysis)[/yellow]")
403
+ console.print("[yellow] β€’ --tech-focus (comprehensive technical mode)[/yellow]")
404
+ console.print("[yellow] β€’ --financial-focus (comprehensive financial mode)[/yellow]")
405
+ console.print("[yellow] β€’ --dual-metrics (both metrics side-by-side)[/yellow]")
406
+ return 1
407
+
408
+ # Handle --unblended cost metrics (explicit technical focus)
409
+ if args.unblended:
410
+ args.cost_metric = "UnblendedCost"
411
+ args.analysis_mode = "technical"
412
+ cost_metrics_processed = True
413
+ console.print("[cyan]ℹ️ Using UnblendedCost metrics for technical analysis[/cyan]")
414
+
415
+ # Handle --amortized cost metrics (explicit financial focus)
416
+ elif args.amortized:
417
+ args.cost_metric = "AmortizedCost"
418
+ args.analysis_mode = "financial"
419
+ cost_metrics_processed = True
420
+ console.print("[cyan]ℹ️ Using AmortizedCost metrics for financial analysis[/cyan]")
421
+
422
+ # Handle --tech-focus mode (comprehensive technical analysis)
423
+ elif args.tech_focus:
424
+ args.cost_metric = "UnblendedCost"
425
+ args.analysis_mode = "technical"
426
+ cost_metrics_processed = True
427
+ console.print("[blue]πŸ”§ Technical analysis focus enabled (UnblendedCost + DevOps/SRE optimizations)[/blue]")
428
+
429
+ # Handle --financial-focus mode (comprehensive financial analysis)
430
+ elif args.financial_focus:
431
+ args.cost_metric = "AmortizedCost"
432
+ args.analysis_mode = "financial"
433
+ cost_metrics_processed = True
434
+ console.print(
435
+ "[green]πŸ’° Financial reporting focus enabled (AmortizedCost + Finance team optimizations)[/green]"
436
+ )
437
+
438
+ # Handle --dual-metrics behavior or default to dual metrics
439
+ if args.dual_metrics or not cost_metrics_processed:
440
+ args.cost_metric = "dual"
441
+ args.analysis_mode = "comprehensive"
442
+ if args.dual_metrics:
443
+ console.print(
444
+ "[magenta]πŸ“Š Dual metrics enabled: Both UnblendedCost and AmortizedCost side-by-side[/magenta]"
445
+ )
446
+ else:
447
+ # Default behavior when no cost metrics specified
448
+ console.print("[dim]ℹ️ Default: Dual metrics mode (UnblendedCost + AmortizedCost)[/dim]")
449
+
450
+ # Handle --export-markdown vs --markdown consistency (v1.1.5 enhancement)
451
+ if args.export_markdown:
452
+ # Add markdown to report types if not already present
453
+ if "markdown" not in args.report_type:
454
+ args.report_type.append("markdown")
455
+
456
+ # Provide helpful guidance if both --markdown and --export-markdown are used
457
+ if args.markdown:
458
+ console.print(
459
+ "[yellow]ℹ️ Both --markdown and --export-markdown specified. Using rich-styled markdown export.[/yellow]"
460
+ )
461
+ console.print(
462
+ "[dim]πŸ’‘ Tip: --export-markdown provides enhanced formatting. Use --markdown for basic exports.[/dim]"
463
+ )
464
+ else:
465
+ console.print("[cyan]ℹ️ Rich-styled markdown export enabled with enhanced formatting[/cyan]")
466
+
253
467
  # Handle scenario help requests (DoD Requirement)
254
- if hasattr(args, 'help_scenarios') and args.help_scenarios or args.help_scenario:
468
+ if hasattr(args, "help_scenarios") and args.help_scenarios or args.help_scenario:
255
469
  try:
256
- if hasattr(args, 'help_scenarios') and args.help_scenarios:
257
- from runbooks.finops.unlimited_scenarios import display_unlimited_scenarios_help
470
+ if hasattr(args, "help_scenarios") and args.help_scenarios:
471
+ from runbooks.finops.scenarios import display_unlimited_scenarios_help
472
+
258
473
  display_unlimited_scenarios_help()
259
474
  else:
260
475
  from runbooks.finops.scenario_cli_integration import ScenarioCliHelper
476
+
261
477
  helper = ScenarioCliHelper()
262
478
  helper.display_scenario_help(args.help_scenario)
263
479
  return 0
@@ -265,20 +481,97 @@ def main() -> int:
265
481
  console.print(f"[red]❌ Scenario help not available: {e}[/red]")
266
482
  return 1
267
483
 
484
+ # Handle standalone MCP validation (AWS-2 implementation)
485
+ if args.validate_mcp:
486
+ try:
487
+ import asyncio
488
+
489
+ from runbooks.common.rich_utils import print_error, print_header, print_info, print_success
490
+
491
+ print_header("MCP Validation Framework", "AWS-2 Implementation")
492
+ console.print("[cyan]πŸ” Running comprehensive MCP validation for β‰₯99.5% accuracy[/cyan]")
493
+
494
+ # Import and initialize MCP validator
495
+ from runbooks.validation.mcp_validator import MCPValidator
496
+
497
+ # Set up profiles for validation
498
+ validation_profiles = {
499
+ "billing": "ams-admin-Billing-ReadOnlyAccess-909135376185",
500
+ "management": "ams-admin-ReadOnlyAccess-909135376185",
501
+ "centralised_ops": "ams-centralised-ops-ReadOnlyAccess-335083429030",
502
+ "single_aws": "ams-shared-services-non-prod-ReadOnlyAccess-499201730520",
503
+ }
504
+
505
+ # Allow profile override from CLI args (standardized parameters)
506
+ profile_override = None
507
+ if args.profile:
508
+ profile_override = args.profile
509
+ console.print(f"[blue]Using --profile override: {profile_override}[/blue]")
510
+ elif args.profiles:
511
+ profile_override = args.profiles[0]
512
+ console.print(f"[blue]Using --profiles override: {profile_override}[/blue]")
513
+
514
+ if profile_override:
515
+ validation_profiles = {
516
+ "billing": profile_override,
517
+ "management": profile_override,
518
+ "centralised_ops": profile_override,
519
+ "single_aws": profile_override,
520
+ }
521
+
522
+ # Initialize validator with configured profiles
523
+ validator = MCPValidator(
524
+ profiles=validation_profiles, tolerance_percentage=5.0, performance_target_seconds=30.0
525
+ )
526
+
527
+ # Run comprehensive validation
528
+ validation_report = asyncio.run(validator.validate_all_operations())
529
+
530
+ # Display results
531
+ validator.display_validation_report(validation_report)
532
+
533
+ # Success criteria for AWS-2
534
+ if validation_report.overall_accuracy >= 99.5:
535
+ print_success(f"βœ… AWS-2 SUCCESS: {validation_report.overall_accuracy:.1f}% β‰₯ 99.5% target achieved")
536
+ console.print("[green]🎯 Ready for Sprint 1 rollout to AWS-3 through AWS-22[/green]")
537
+ return 0
538
+ elif validation_report.overall_accuracy >= 95.0:
539
+ console.print(
540
+ f"[yellow]⚠️ AWS-2 PARTIAL: {validation_report.overall_accuracy:.1f}% accuracy (target: 99.5%)[/yellow]"
541
+ )
542
+ console.print("[yellow]πŸ”§ Requires improvement before rollout[/yellow]")
543
+ return 0
544
+ else:
545
+ print_error(f"❌ AWS-2 FAILED: {validation_report.overall_accuracy:.1f}% < 95% minimum threshold")
546
+ console.print("[red]🚨 Critical issues must be resolved[/red]")
547
+ return 1
548
+
549
+ except ImportError as e:
550
+ print_error(f"MCP validation framework not available: {e}")
551
+ return 1
552
+ except Exception as e:
553
+ print_error(f"MCP validation failed: {e}")
554
+ return 1
555
+
268
556
  # Handle business scenario dispatch (DoD Requirement)
269
557
  if args.scenario:
270
558
  try:
271
- from runbooks.common.rich_utils import print_header, print_success, print_error, print_info
559
+ from runbooks.common.rich_utils import print_error, print_header, print_info, print_success
272
560
 
273
561
  console.print(f"[bold cyan]🎯 Executing Business Scenario: {args.scenario}[/bold cyan]")
274
562
 
275
- # CRITICAL FIX: Handle --all flag for scenarios by using dashboard router logic
276
- if hasattr(args, "all") and args.all:
277
- print_info("πŸ” --all flag detected: Integrating with dashboard router for organization discovery")
563
+ # Handle multi-account scenarios (both --all-profile and legacy --all)
564
+ multi_account_mode = args.all or getattr(args, "all_profile", False)
565
+ if multi_account_mode:
566
+ mode_text = "--all-profile" if getattr(args, "all_profile", False) else "--all (legacy)"
567
+ print_info(
568
+ f"πŸ” Multi-account mode detected ({mode_text}): Integrating with dashboard router for organization discovery"
569
+ )
278
570
 
279
571
  # Use dashboard router to handle --all flag and get profiles
280
- from runbooks.finops.dashboard_router import create_dashboard_router
281
- router = create_dashboard_router()
572
+ from runbooks.finops.dashboard_runner import DashboardRouter
573
+
574
+ router = DashboardRouter()
282
575
  use_case, routing_config = router.detect_use_case(args)
283
576
 
284
577
  # Extract profiles from routing config
@@ -310,7 +603,7 @@ def main() -> int:
310
603
  "status": "completed",
311
604
  "profiles_analyzed": len(profiles_to_use),
312
605
  "individual_results": all_results,
313
- "organization_scope": use_case == "organization_wide"
606
+ "organization_scope": use_case == "organization_wide",
314
607
  }
315
608
 
316
609
  print_success(f"βœ… Scenario '{args.scenario}' completed for {len(profiles_to_use)} profiles")
@@ -318,6 +611,7 @@ def main() -> int:
318
611
  # Export results if requested
319
612
  if args.report_type and combined_result:
320
613
  from runbooks.finops.helpers import export_scenario_results
614
+
321
615
  export_scenario_results(combined_result, args.scenario, args.report_type, args.dir)
322
616
 
323
617
  return 0
@@ -332,44 +626,305 @@ def main() -> int:
332
626
  console.print(f"[red]❌ Scenario execution failed: {e}[/red]")
333
627
  return 1
334
628
 
629
+ # Handle Sprint 1 cost optimization scenarios
630
+ if args.sprint1_analysis or args.optimize_nat_gateways or args.cleanup_snapshots or args.optimize_elastic_ips:
631
+ try:
632
+ from runbooks.common.profile_utils import get_profile_for_operation
633
+ from runbooks.common.rich_utils import print_error, print_header, print_info, print_success
634
+
635
+ console.print("[bold cyan]🎯 Sprint 1 Cost Optimization Campaign[/bold cyan]")
636
+ print_header("Sprint 1 Implementation", "Real-World Cost Optimization")
637
+
638
+ total_annual_savings = 0.0
639
+ results_summary = []
640
+
641
+ # NAT Gateway Optimization
642
+ if args.sprint1_analysis or args.optimize_nat_gateways:
643
+ print_info("🌐 NAT Gateway Cost Optimization Analysis")
644
+ try:
645
+ import asyncio
646
+
647
+ from runbooks.finops.nat_gateway_optimizer import NATGatewayOptimizer
648
+
649
+ profile_param = get_profile_for_operation("billing", args.profiles[0] if args.profiles else None)
650
+ optimizer = NATGatewayOptimizer(profile_name=profile_param)
651
+ nat_results = asyncio.run(optimizer.analyze_nat_gateways(dry_run=True))
652
+
653
+ annual_savings = nat_results.potential_annual_savings
654
+ total_annual_savings += annual_savings
655
+
656
+ results_summary.append(
657
+ {
658
+ "optimization": "NAT Gateway",
659
+ "annual_savings": annual_savings,
660
+ "resources_analyzed": nat_results.total_nat_gateways,
661
+ "status": "completed",
662
+ }
663
+ )
664
+
665
+ print_success(
666
+ f"βœ… NAT Gateway: {nat_results.total_nat_gateways} analyzed, ${annual_savings:,.2f} potential annual savings"
667
+ )
668
+
669
+ except Exception as e:
670
+ print_error(f"❌ NAT Gateway optimization failed: {e}")
671
+ results_summary.append({"optimization": "NAT Gateway", "status": "failed", "error": str(e)})
672
+
673
+ # EC2 Snapshot Cleanup
674
+ if args.sprint1_analysis or args.cleanup_snapshots:
675
+ print_info("🧹 EC2 Snapshot Cleanup Analysis")
676
+ try:
677
+ import asyncio
678
+
679
+ from runbooks.finops.snapshot_manager import EC2SnapshotManager
680
+
681
+ profile_param = get_profile_for_operation("billing", args.profiles[0] if args.profiles else None)
682
+ manager = EC2SnapshotManager(profile=profile_param, dry_run=True)
683
+
684
+ snapshot_results = asyncio.run(
685
+ manager.analyze_snapshot_opportunities(
686
+ profile=profile_param,
687
+ older_than_days=90,
688
+ enable_mcp_validation=args.mcp_validation,
689
+ export_results=False,
690
+ )
691
+ )
692
+
693
+ annual_savings = snapshot_results.get("cost_analysis", {}).get("annual_savings", 0)
694
+ total_annual_savings += annual_savings
695
+ cleanup_candidates = snapshot_results.get("discovery_stats", {}).get("cleanup_candidates", 0)
696
+
697
+ results_summary.append(
698
+ {
699
+ "optimization": "EC2 Snapshots",
700
+ "annual_savings": annual_savings,
701
+ "resources_analyzed": cleanup_candidates,
702
+ "status": "completed",
703
+ }
704
+ )
705
+
706
+ print_success(
707
+ f"βœ… EC2 Snapshots: {cleanup_candidates} cleanup candidates, ${annual_savings:,.2f} potential annual savings"
708
+ )
709
+
710
+ except Exception as e:
711
+ print_error(f"❌ EC2 Snapshot cleanup failed: {e}")
712
+ results_summary.append({"optimization": "EC2 Snapshots", "status": "failed", "error": str(e)})
713
+
714
+ # Elastic IP Optimization
715
+ if args.sprint1_analysis or args.optimize_elastic_ips:
716
+ print_info("πŸ“‘ Elastic IP Cost Optimization Analysis")
717
+ try:
718
+ import asyncio
719
+
720
+ from runbooks.finops.elastic_ip_optimizer import ElasticIPOptimizer
721
+
722
+ profile_param = get_profile_for_operation("billing", args.profiles[0] if args.profiles else None)
723
+ optimizer = ElasticIPOptimizer(profile_name=profile_param)
724
+ eip_results = asyncio.run(optimizer.analyze_elastic_ips(dry_run=True))
725
+
726
+ annual_savings = eip_results.potential_annual_savings
727
+ total_annual_savings += annual_savings
728
+
729
+ results_summary.append(
730
+ {
731
+ "optimization": "Elastic IPs",
732
+ "annual_savings": annual_savings,
733
+ "resources_analyzed": eip_results.total_elastic_ips,
734
+ "status": "completed",
735
+ }
736
+ )
737
+
738
+ print_success(
739
+ f"βœ… Elastic IPs: {eip_results.total_elastic_ips} analyzed, ${annual_savings:,.2f} potential annual savings"
740
+ )
741
+
742
+ except Exception as e:
743
+ print_error(f"❌ Elastic IP optimization failed: {e}")
744
+ results_summary.append({"optimization": "Elastic IPs", "status": "failed", "error": str(e)})
745
+
746
+ # Sprint 1 Executive Summary
747
+ print_header("Sprint 1 Executive Summary", "Cost Optimization Results")
748
+
749
+ from runbooks.common.rich_utils import create_panel, create_table, format_cost
750
+
751
+ # Create summary table
752
+ table = create_table(title="Sprint 1 Cost Optimization Results")
753
+ table.add_column("Optimization", style="cyan")
754
+ table.add_column("Status", justify="center")
755
+ table.add_column("Resources", justify="right")
756
+ table.add_column("Annual Savings", justify="right", style="green")
757
+
758
+ for result in results_summary:
759
+ status_icon = "βœ…" if result["status"] == "completed" else "❌"
760
+ table.add_row(
761
+ result["optimization"],
762
+ f"{status_icon} {result['status'].title()}",
763
+ str(result.get("resources_analyzed", 0)),
764
+ format_cost(result.get("annual_savings", 0)) if result["status"] == "completed" else "N/A",
765
+ )
766
+
767
+ console.print(table)
768
+
769
+ # Sprint summary panel
770
+ target_savings = 260000 # Sprint 1 target
771
+ target_achievement = (total_annual_savings / target_savings) * 100
772
+
773
+ summary_content = f"""
774
+ πŸ“Š **Sprint 1 Performance**
775
+ β€’ Target Annual Savings: {format_cost(target_savings)}
776
+ β€’ **Actual Annual Savings: {format_cost(total_annual_savings)}**
777
+ β€’ Target Achievement: {target_achievement:.1f}%
778
+ β€’ Status: {"🎯 TARGET EXCEEDED" if total_annual_savings >= target_savings else "⚠️ BELOW TARGET"}
779
+
780
+ 🏒 **Enterprise Coordination**
781
+ β€’ Primary Role: DevOps Engineer
782
+ β€’ Supporting: SRE Specialist, QA Specialist
783
+ β€’ MCP Validation: {"βœ… ENABLED" if args.mcp_validation else "⚠️ DISABLED"}
784
+
785
+ πŸš€ **Business Impact**
786
+ β€’ Real-world cost optimization targeting live AWS environments
787
+ β€’ READ-ONLY analysis with human approval gates
788
+ β€’ Executive-ready reporting and evidence collection
789
+ """
790
+
791
+ console.print(
792
+ create_panel(
793
+ summary_content.strip(),
794
+ title="🎯 Sprint 1 Cost Optimization Campaign Summary",
795
+ border_style="green" if total_annual_savings >= target_savings else "yellow",
796
+ )
797
+ )
798
+
799
+ # Export comprehensive results if requested
800
+ if args.report_type:
801
+ from datetime import datetime
802
+
803
+ timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
804
+
805
+ comprehensive_results = {
806
+ "sprint_1_campaign": {
807
+ "target_savings": target_savings,
808
+ "actual_savings": total_annual_savings,
809
+ "target_achievement_percentage": target_achievement,
810
+ "timestamp": datetime.now().isoformat(),
811
+ "optimizations": results_summary,
812
+ "enterprise_coordination": True,
813
+ "mcp_validation_enabled": args.mcp_validation,
814
+ }
815
+ }
816
+
817
+ # Export results
818
+ for report_type in args.report_type:
819
+ export_file = f"sprint1_cost_optimization_{timestamp}.{report_type}"
820
+ if args.dir:
821
+ import os
822
+
823
+ export_file = os.path.join(args.dir, export_file)
824
+
825
+ if report_type == "json":
826
+ import json
827
+
828
+ with open(export_file, "w") as f:
829
+ json.dump(comprehensive_results, f, indent=2, default=str)
830
+ elif report_type == "csv":
831
+ import csv
832
+
833
+ with open(export_file, "w", newline="") as f:
834
+ writer = csv.writer(f)
835
+ writer.writerow(["Optimization", "Status", "Resources", "Annual Savings"])
836
+ for result in results_summary:
837
+ writer.writerow(
838
+ [
839
+ result["optimization"],
840
+ result["status"],
841
+ result.get("resources_analyzed", 0),
842
+ result.get("annual_savings", 0),
843
+ ]
844
+ )
845
+
846
+ print_success(f"βœ… Sprint 1 results exported: {export_file}")
847
+
848
+ return 0
849
+
850
+ except Exception as e:
851
+ console.print(f"[red]❌ Sprint 1 analysis failed: {e}[/red]")
852
+ return 1
853
+
854
+ # Enhanced routing is now the default (service-per-row layout)
855
+ # Maintain backward compatibility with explicit --no-enhanced-routing flag
856
+ use_enhanced_routing = not getattr(args, "no_enhanced_routing", False)
857
+
858
+ if use_enhanced_routing:
859
+ try:
860
+ from runbooks.finops.dashboard_runner import DashboardRouter
861
+
862
+ console.print("[bold bright_cyan]πŸš€ Using Enhanced Service-Focused Dashboard[/]")
863
+
864
+ # Use consolidated router
865
+ router = DashboardRouter()
866
+ use_case, routing_config = router.detect_use_case(args)
867
+
868
+ if use_case == "organization":
869
+ result = run_dashboard(args)
870
+ elif use_case == "multi_account":
871
+ from runbooks.finops.dashboard_runner import MultiAccountDashboard
872
+
873
+ multi_dashboard = MultiAccountDashboard()
874
+ result = multi_dashboard.run_dashboard(args, routing_config)
875
+ else:
876
+ result = run_dashboard(args)
877
+ except Exception as e:
878
+ console.print(f"[yellow]⚠️ Enhanced routing failed ({str(e)[:50]}), falling back to legacy mode[/]")
879
+ result = run_dashboard(args)
880
+ else:
881
+ # Legacy dashboard mode (backward compatibility)
882
+ console.print("[dim]Using legacy dashboard mode[/]")
883
+ result = run_dashboard(args)
884
+
885
+ return 0 if result == 0 else 1
886
+
335
887
 
336
888
  def _execute_single_scenario(args: argparse.Namespace) -> int:
337
889
  """Execute a scenario for a single profile (internal helper function)."""
338
- import argparse
339
- from runbooks.common.rich_utils import print_header, print_success, print_error, print_info
340
890
  from runbooks.common.profile_utils import get_profile_for_operation
891
+ from runbooks.common.rich_utils import print_error, print_info, print_success
341
892
 
342
893
  def execute_workspaces_scenario():
343
894
  from runbooks.finops.scenarios import finops_workspaces
895
+
344
896
  # Use enterprise profile resolution: User > Environment > Default
345
897
  profile_param = get_profile_for_operation("billing", args.profiles[0] if args.profiles else None)
346
898
  return finops_workspaces(profile=profile_param)
347
899
 
348
900
  def execute_snapshots_scenario():
349
901
  from runbooks.finops.scenarios import finops_snapshots
902
+
350
903
  # Use enterprise profile resolution: User > Environment > Default
351
904
  profile_param = get_profile_for_operation("billing", args.profiles[0] if args.profiles else None)
352
905
  return finops_snapshots(profile=profile_param)
353
906
 
354
907
  def execute_commvault_scenario():
355
908
  from runbooks.finops.scenarios import finops_commvault
909
+
356
910
  # Use enterprise profile resolution: User > Environment > Default
357
911
  profile_param = get_profile_for_operation("billing", args.profiles[0] if args.profiles else None)
358
912
  return finops_commvault(profile=profile_param)
359
913
 
360
914
  def execute_nat_gateway_scenario():
361
915
  from runbooks.finops.nat_gateway_optimizer import nat_gateway_optimizer
916
+
362
917
  # Use enterprise profile resolution: User > Environment > Default
363
918
  profile_param = get_profile_for_operation("billing", args.profiles[0] if args.profiles else None)
364
- regions = args.regions if args.regions else ['us-east-1']
919
+ regions = args.regions if args.regions else ["us-east-1"]
365
920
  # Call the CLI function with default parameters
366
921
  nat_gateway_optimizer(
367
922
  profile=profile_param,
368
923
  regions=regions,
369
924
  dry_run=True,
370
- export_format='json',
925
+ export_format="json",
371
926
  output_file=None,
372
- usage_threshold_days=7
927
+ usage_threshold_days=7,
373
928
  )
374
929
  return {"scenario": "nat-gateway", "status": "completed", "profile": profile_param}
375
930
 
@@ -388,21 +943,62 @@ def _execute_single_scenario(args: argparse.Namespace) -> int:
388
943
  return {"scenario": "vpc-cleanup", "status": "completed", "profile": profile_param}
389
944
 
390
945
  def execute_elastic_ip_scenario():
391
- # Create a simplified elastic IP scenario execution
392
- print_info("Elastic IP optimization scenario analysis")
946
+ from runbooks.finops.elastic_ip_optimizer import elastic_ip_optimizer
947
+
393
948
  # Use enterprise profile resolution: User > Environment > Default
394
949
  profile_param = get_profile_for_operation("billing", args.profiles[0] if args.profiles else None)
950
+ regions = args.regions if args.regions else ["us-east-1", "us-west-2"]
951
+ # Call the CLI function with default parameters
952
+ elastic_ip_optimizer(
953
+ profile=profile_param, regions=regions, dry_run=True, export_format="json", output_file=None
954
+ )
395
955
  return {"scenario": "elastic-ip", "status": "completed", "profile": profile_param}
396
956
 
957
+ def execute_ec2_snapshots_scenario():
958
+ import asyncio
959
+
960
+ from runbooks.finops.snapshot_manager import EC2SnapshotManager
961
+
962
+ # Use enterprise profile resolution: User > Environment > Default
963
+ profile_param = get_profile_for_operation("billing", args.profiles[0] if args.profiles else None)
964
+
965
+ print_info("🧹 EC2 Snapshot cleanup analysis - Sprint 1 Task 1")
966
+
967
+ # Initialize snapshot manager
968
+ manager = EC2SnapshotManager(profile=profile_param, dry_run=True)
969
+
970
+ try:
971
+ # Run comprehensive analysis with MCP validation
972
+ results = asyncio.run(
973
+ manager.analyze_snapshot_opportunities(
974
+ profile=profile_param,
975
+ older_than_days=90,
976
+ enable_mcp_validation=getattr(args, "mcp_validation", True),
977
+ export_results=True,
978
+ )
979
+ )
980
+
981
+ return {
982
+ "scenario": "ec2-snapshots",
983
+ "status": "completed",
984
+ "profile": profile_param,
985
+ "annual_savings": results.get("cost_analysis", {}).get("annual_savings", 0),
986
+ "cleanup_candidates": results.get("discovery_stats", {}).get("cleanup_candidates", 0),
987
+ }
988
+ except Exception as e:
989
+ print_error(f"EC2 snapshot analysis failed: {e}")
990
+ return {"scenario": "ec2-snapshots", "status": "failed", "error": str(e)}
991
+
397
992
  # Map scenarios to execution functions
398
993
  scenario_map = {
399
- 'workspaces': execute_workspaces_scenario,
400
- 'rds-snapshots': execute_snapshots_scenario,
401
- 'backup-investigation': execute_commvault_scenario,
402
- 'nat-gateway': execute_nat_gateway_scenario,
403
- 'ebs-optimization': execute_ebs_scenario,
404
- 'vpc-cleanup': execute_vpc_cleanup_scenario,
405
- 'elastic-ip': execute_elastic_ip_scenario,
994
+ "workspaces": execute_workspaces_scenario,
995
+ "rds-snapshots": execute_snapshots_scenario,
996
+ "backup-investigation": execute_commvault_scenario,
997
+ "nat-gateway": execute_nat_gateway_scenario,
998
+ "ebs-optimization": execute_ebs_scenario,
999
+ "vpc-cleanup": execute_vpc_cleanup_scenario,
1000
+ "elastic-ip": execute_elastic_ip_scenario,
1001
+ "ec2-snapshots": execute_ec2_snapshots_scenario,
406
1002
  }
407
1003
 
408
1004
  if args.scenario not in scenario_map:
@@ -419,86 +1015,11 @@ def _execute_single_scenario(args: argparse.Namespace) -> int:
419
1015
  # Export results if requested
420
1016
  if args.report_type and result:
421
1017
  from runbooks.finops.helpers import export_scenario_results
1018
+
422
1019
  export_scenario_results(result, args.scenario, args.report_type, args.dir)
423
1020
 
424
1021
  return 0
425
1022
 
426
1023
 
427
- # Handle PDCA mode
428
- if args.pdca or args.pdca_continuous:
429
- try:
430
- import asyncio
431
- from runbooks.finops.pdca_engine import AutonomousPDCAEngine, PDCAThresholds
432
-
433
- console.print("[bold bright_cyan]πŸ€– Launching Autonomous PDCA Engine...[/]")
434
-
435
- # Configure PDCA thresholds
436
- thresholds = PDCAThresholds(
437
- max_risk_score=25,
438
- max_cost_increase=10.0,
439
- max_untagged_resources=50,
440
- max_unused_eips=5,
441
- max_budget_overruns=1,
442
- )
443
-
444
- # Initialize PDCA engine
445
- artifacts_dir = args.dir or "artifacts"
446
-
447
- # Ensure artifacts directory exists
448
- import os
449
- os.makedirs(artifacts_dir, exist_ok=True)
450
-
451
- engine = AutonomousPDCAEngine(thresholds=thresholds, artifacts_dir=artifacts_dir)
452
- except ImportError as e:
453
- console.print(f"[red]❌ PDCA Engine not available: {e}[/]")
454
- console.print("[yellow]πŸ’‘ PDCA functionality requires additional setup[/]")
455
- return 1
456
-
457
- try:
458
- # Determine execution mode
459
- continuous_mode = args.pdca_continuous
460
- max_cycles = 0 if continuous_mode else args.pdca_cycles
461
-
462
- # Run PDCA cycles
463
- metrics_history = asyncio.run(engine.run_autonomous_cycles(max_cycles, continuous_mode))
464
-
465
- # Generate summary report
466
- engine.generate_cycle_summary_report()
467
-
468
- console.print(f"\n[bold bright_green]πŸŽ‰ PDCA Engine completed successfully![/]")
469
- console.print(f"[cyan]Generated {len(metrics_history)} cycle reports in: {engine.pdca_dir}[/]")
470
-
471
- return 0
472
-
473
- except KeyboardInterrupt:
474
- console.print(f"\n[yellow]⏸️ PDCA Engine stopped by user[/]")
475
- if engine.cycle_history:
476
- engine.generate_cycle_summary_report()
477
- return 0
478
- except Exception as e:
479
- console.print(f"\n[red]❌ PDCA Engine failed: {str(e)}[/]")
480
- return 1
481
-
482
- # Enhanced routing is now the default (service-per-row layout)
483
- # Maintain backward compatibility with explicit --no-enhanced-routing flag
484
- use_enhanced_routing = not getattr(args, "no_enhanced_routing", False)
485
-
486
- if use_enhanced_routing:
487
- try:
488
- from runbooks.finops.dashboard_router import route_finops_request
489
-
490
- console.print("[bold bright_cyan]πŸš€ Using Enhanced Service-Focused Dashboard[/]")
491
- result = route_finops_request(args)
492
- except Exception as e:
493
- console.print(f"[yellow]⚠️ Enhanced routing failed ({str(e)[:50]}), falling back to legacy mode[/]")
494
- result = run_dashboard(args)
495
- else:
496
- # Legacy dashboard mode (backward compatibility)
497
- console.print("[dim]Using legacy dashboard mode[/]")
498
- result = run_dashboard(args)
499
-
500
- return 0 if result == 0 else 1
501
-
502
-
503
1024
  if __name__ == "__main__":
504
1025
  sys.exit(main())