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.
- runbooks/__init__.py +31 -2
- runbooks/__init___optimized.py +18 -4
- runbooks/_platform/__init__.py +1 -5
- runbooks/_platform/core/runbooks_wrapper.py +141 -138
- runbooks/aws2/accuracy_validator.py +812 -0
- runbooks/base.py +7 -0
- runbooks/cfat/assessment/compliance.py +1 -1
- runbooks/cfat/assessment/runner.py +1 -0
- runbooks/cfat/cloud_foundations_assessment.py +227 -239
- runbooks/cli/__init__.py +1 -1
- runbooks/cli/commands/cfat.py +64 -23
- runbooks/cli/commands/finops.py +1005 -54
- runbooks/cli/commands/inventory.py +138 -35
- runbooks/cli/commands/operate.py +9 -36
- runbooks/cli/commands/security.py +42 -18
- runbooks/cli/commands/validation.py +432 -18
- runbooks/cli/commands/vpc.py +81 -17
- runbooks/cli/registry.py +22 -10
- runbooks/cloudops/__init__.py +20 -27
- runbooks/cloudops/base.py +96 -107
- runbooks/cloudops/cost_optimizer.py +544 -542
- runbooks/cloudops/infrastructure_optimizer.py +5 -4
- runbooks/cloudops/interfaces.py +224 -225
- runbooks/cloudops/lifecycle_manager.py +5 -4
- runbooks/cloudops/mcp_cost_validation.py +252 -235
- runbooks/cloudops/models.py +78 -53
- runbooks/cloudops/monitoring_automation.py +5 -4
- runbooks/cloudops/notebook_framework.py +177 -213
- runbooks/cloudops/security_enforcer.py +125 -159
- runbooks/common/accuracy_validator.py +11 -0
- runbooks/common/aws_pricing.py +349 -326
- runbooks/common/aws_pricing_api.py +211 -212
- runbooks/common/aws_profile_manager.py +40 -36
- runbooks/common/aws_utils.py +74 -79
- runbooks/common/business_logic.py +126 -104
- runbooks/common/cli_decorators.py +36 -60
- runbooks/common/comprehensive_cost_explorer_integration.py +455 -463
- runbooks/common/cross_account_manager.py +197 -204
- runbooks/common/date_utils.py +27 -39
- runbooks/common/decorators.py +29 -19
- runbooks/common/dry_run_examples.py +173 -208
- runbooks/common/dry_run_framework.py +157 -155
- runbooks/common/enhanced_exception_handler.py +15 -4
- runbooks/common/enhanced_logging_example.py +50 -64
- runbooks/common/enhanced_logging_integration_example.py +65 -37
- runbooks/common/env_utils.py +16 -16
- runbooks/common/error_handling.py +40 -38
- runbooks/common/lazy_loader.py +41 -23
- runbooks/common/logging_integration_helper.py +79 -86
- runbooks/common/mcp_cost_explorer_integration.py +476 -493
- runbooks/common/mcp_integration.py +63 -74
- runbooks/common/memory_optimization.py +140 -118
- runbooks/common/module_cli_base.py +37 -58
- runbooks/common/organizations_client.py +175 -193
- runbooks/common/patterns.py +23 -25
- runbooks/common/performance_monitoring.py +67 -71
- runbooks/common/performance_optimization_engine.py +283 -274
- runbooks/common/profile_utils.py +111 -37
- runbooks/common/rich_utils.py +201 -141
- runbooks/common/sre_performance_suite.py +177 -186
- runbooks/enterprise/__init__.py +1 -1
- runbooks/enterprise/logging.py +144 -106
- runbooks/enterprise/security.py +187 -204
- runbooks/enterprise/validation.py +43 -56
- runbooks/finops/__init__.py +26 -30
- runbooks/finops/account_resolver.py +1 -1
- runbooks/finops/advanced_optimization_engine.py +980 -0
- runbooks/finops/automation_core.py +268 -231
- runbooks/finops/business_case_config.py +184 -179
- runbooks/finops/cli.py +660 -139
- runbooks/finops/commvault_ec2_analysis.py +157 -164
- runbooks/finops/compute_cost_optimizer.py +336 -320
- runbooks/finops/config.py +20 -20
- runbooks/finops/cost_optimizer.py +484 -618
- runbooks/finops/cost_processor.py +332 -214
- runbooks/finops/dashboard_runner.py +1006 -172
- runbooks/finops/ebs_cost_optimizer.py +991 -657
- runbooks/finops/elastic_ip_optimizer.py +317 -257
- runbooks/finops/enhanced_mcp_integration.py +340 -0
- runbooks/finops/enhanced_progress.py +32 -29
- runbooks/finops/enhanced_trend_visualization.py +3 -2
- runbooks/finops/enterprise_wrappers.py +223 -285
- runbooks/finops/executive_export.py +203 -160
- runbooks/finops/helpers.py +130 -288
- runbooks/finops/iam_guidance.py +1 -1
- runbooks/finops/infrastructure/__init__.py +80 -0
- runbooks/finops/infrastructure/commands.py +506 -0
- runbooks/finops/infrastructure/load_balancer_optimizer.py +866 -0
- runbooks/finops/infrastructure/vpc_endpoint_optimizer.py +832 -0
- runbooks/finops/markdown_exporter.py +337 -174
- runbooks/finops/mcp_validator.py +1952 -0
- runbooks/finops/nat_gateway_optimizer.py +1512 -481
- runbooks/finops/network_cost_optimizer.py +657 -587
- runbooks/finops/notebook_utils.py +226 -188
- runbooks/finops/optimization_engine.py +1136 -0
- runbooks/finops/optimizer.py +19 -23
- runbooks/finops/rds_snapshot_optimizer.py +367 -411
- runbooks/finops/reservation_optimizer.py +427 -363
- runbooks/finops/scenario_cli_integration.py +64 -65
- runbooks/finops/scenarios.py +1277 -438
- runbooks/finops/schemas.py +218 -182
- runbooks/finops/snapshot_manager.py +2289 -0
- runbooks/finops/types.py +3 -3
- runbooks/finops/validation_framework.py +259 -265
- runbooks/finops/vpc_cleanup_exporter.py +189 -144
- runbooks/finops/vpc_cleanup_optimizer.py +591 -573
- runbooks/finops/workspaces_analyzer.py +171 -182
- runbooks/integration/__init__.py +89 -0
- runbooks/integration/mcp_integration.py +1920 -0
- runbooks/inventory/CLAUDE.md +816 -0
- runbooks/inventory/__init__.py +2 -2
- runbooks/inventory/cloud_foundations_integration.py +144 -149
- runbooks/inventory/collectors/aws_comprehensive.py +1 -1
- runbooks/inventory/collectors/aws_networking.py +109 -99
- runbooks/inventory/collectors/base.py +4 -0
- runbooks/inventory/core/collector.py +495 -313
- runbooks/inventory/drift_detection_cli.py +69 -96
- runbooks/inventory/inventory_mcp_cli.py +48 -46
- runbooks/inventory/list_rds_snapshots_aggregator.py +192 -208
- runbooks/inventory/mcp_inventory_validator.py +549 -465
- runbooks/inventory/mcp_vpc_validator.py +359 -442
- runbooks/inventory/organizations_discovery.py +55 -51
- runbooks/inventory/rich_inventory_display.py +33 -32
- runbooks/inventory/unified_validation_engine.py +278 -251
- runbooks/inventory/vpc_analyzer.py +732 -695
- runbooks/inventory/vpc_architecture_validator.py +293 -348
- runbooks/inventory/vpc_dependency_analyzer.py +382 -378
- runbooks/inventory/vpc_flow_analyzer.py +1 -1
- runbooks/main.py +49 -34
- runbooks/main_final.py +91 -60
- runbooks/main_minimal.py +22 -10
- runbooks/main_optimized.py +131 -100
- runbooks/main_ultra_minimal.py +7 -2
- runbooks/mcp/__init__.py +36 -0
- runbooks/mcp/integration.py +679 -0
- runbooks/monitoring/performance_monitor.py +9 -4
- runbooks/operate/dynamodb_operations.py +3 -1
- runbooks/operate/ec2_operations.py +145 -137
- runbooks/operate/iam_operations.py +146 -152
- runbooks/operate/networking_cost_heatmap.py +29 -8
- runbooks/operate/rds_operations.py +223 -254
- runbooks/operate/s3_operations.py +107 -118
- runbooks/operate/vpc_operations.py +646 -616
- runbooks/remediation/base.py +1 -1
- runbooks/remediation/commons.py +10 -7
- runbooks/remediation/commvault_ec2_analysis.py +70 -66
- runbooks/remediation/ec2_unattached_ebs_volumes.py +1 -0
- runbooks/remediation/multi_account.py +24 -21
- runbooks/remediation/rds_snapshot_list.py +86 -60
- runbooks/remediation/remediation_cli.py +92 -146
- runbooks/remediation/universal_account_discovery.py +83 -79
- runbooks/remediation/workspaces_list.py +46 -41
- runbooks/security/__init__.py +19 -0
- runbooks/security/assessment_runner.py +1150 -0
- runbooks/security/baseline_checker.py +812 -0
- runbooks/security/cloudops_automation_security_validator.py +509 -535
- runbooks/security/compliance_automation_engine.py +17 -17
- runbooks/security/config/__init__.py +2 -2
- runbooks/security/config/compliance_config.py +50 -50
- runbooks/security/config_template_generator.py +63 -76
- runbooks/security/enterprise_security_framework.py +1 -1
- runbooks/security/executive_security_dashboard.py +519 -508
- runbooks/security/multi_account_security_controls.py +959 -1210
- runbooks/security/real_time_security_monitor.py +422 -444
- runbooks/security/security_baseline_tester.py +1 -1
- runbooks/security/security_cli.py +143 -112
- runbooks/security/test_2way_validation.py +439 -0
- runbooks/security/two_way_validation_framework.py +852 -0
- runbooks/sre/production_monitoring_framework.py +167 -177
- runbooks/tdd/__init__.py +15 -0
- runbooks/tdd/cli.py +1071 -0
- runbooks/utils/__init__.py +14 -17
- runbooks/utils/logger.py +7 -2
- runbooks/utils/version_validator.py +50 -47
- runbooks/validation/__init__.py +6 -6
- runbooks/validation/cli.py +9 -3
- runbooks/validation/comprehensive_2way_validator.py +745 -704
- runbooks/validation/mcp_validator.py +906 -228
- runbooks/validation/terraform_citations_validator.py +104 -115
- runbooks/validation/terraform_drift_detector.py +447 -451
- runbooks/vpc/README.md +617 -0
- runbooks/vpc/__init__.py +8 -1
- runbooks/vpc/analyzer.py +577 -0
- runbooks/vpc/cleanup_wrapper.py +476 -413
- runbooks/vpc/cli_cloudtrail_commands.py +339 -0
- runbooks/vpc/cli_mcp_validation_commands.py +480 -0
- runbooks/vpc/cloudtrail_audit_integration.py +717 -0
- runbooks/vpc/config.py +92 -97
- runbooks/vpc/cost_engine.py +411 -148
- runbooks/vpc/cost_explorer_integration.py +553 -0
- runbooks/vpc/cross_account_session.py +101 -106
- runbooks/vpc/enhanced_mcp_validation.py +917 -0
- runbooks/vpc/eni_gate_validator.py +961 -0
- runbooks/vpc/heatmap_engine.py +185 -160
- runbooks/vpc/mcp_no_eni_validator.py +680 -639
- runbooks/vpc/nat_gateway_optimizer.py +358 -0
- runbooks/vpc/networking_wrapper.py +15 -8
- runbooks/vpc/pdca_remediation_planner.py +528 -0
- runbooks/vpc/performance_optimized_analyzer.py +219 -231
- runbooks/vpc/runbooks_adapter.py +1167 -241
- runbooks/vpc/tdd_red_phase_stubs.py +601 -0
- runbooks/vpc/test_data_loader.py +358 -0
- runbooks/vpc/tests/conftest.py +314 -4
- runbooks/vpc/tests/test_cleanup_framework.py +1022 -0
- runbooks/vpc/tests/test_cost_engine.py +0 -2
- runbooks/vpc/topology_generator.py +326 -0
- runbooks/vpc/unified_scenarios.py +1297 -1124
- runbooks/vpc/vpc_cleanup_integration.py +1943 -1115
- runbooks-1.1.5.dist-info/METADATA +328 -0
- {runbooks-1.1.4.dist-info β runbooks-1.1.5.dist-info}/RECORD +214 -193
- runbooks/finops/README.md +0 -414
- runbooks/finops/accuracy_cross_validator.py +0 -647
- runbooks/finops/business_cases.py +0 -950
- runbooks/finops/dashboard_router.py +0 -922
- runbooks/finops/ebs_optimizer.py +0 -973
- runbooks/finops/embedded_mcp_validator.py +0 -1629
- runbooks/finops/enhanced_dashboard_runner.py +0 -527
- runbooks/finops/finops_dashboard.py +0 -584
- runbooks/finops/finops_scenarios.py +0 -1218
- runbooks/finops/legacy_migration.py +0 -730
- runbooks/finops/multi_dashboard.py +0 -1519
- runbooks/finops/single_dashboard.py +0 -1113
- runbooks/finops/unlimited_scenarios.py +0 -393
- runbooks-1.1.4.dist-info/METADATA +0 -800
- {runbooks-1.1.4.dist-info β runbooks-1.1.5.dist-info}/WHEEL +0 -0
- {runbooks-1.1.4.dist-info β runbooks-1.1.5.dist-info}/entry_points.txt +0 -0
- {runbooks-1.1.4.dist-info β runbooks-1.1.5.dist-info}/licenses/LICENSE +0 -0
- {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
|
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
|
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
|
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
|
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 (
|
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,
|
468
|
+
if hasattr(args, "help_scenarios") and args.help_scenarios or args.help_scenario:
|
255
469
|
try:
|
256
|
-
if hasattr(args,
|
257
|
-
from runbooks.finops.
|
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
|
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
|
-
#
|
276
|
-
|
277
|
-
|
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.
|
281
|
-
|
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 [
|
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=
|
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
|
-
|
392
|
-
|
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
|
-
|
400
|
-
|
401
|
-
|
402
|
-
|
403
|
-
|
404
|
-
|
405
|
-
|
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())
|