runbooks 1.1.4__py3-none-any.whl → 1.1.6__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 +135 -91
- 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 +17 -12
- 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 +99 -79
- 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 +315 -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/aws_decorators.py +2 -3
- runbooks/inventory/check_cloudtrail_compliance.py +2 -4
- runbooks/inventory/check_controltower_readiness.py +152 -151
- runbooks/inventory/check_landingzone_readiness.py +85 -84
- 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/core/formatter.py +11 -0
- runbooks/inventory/draw_org_structure.py +8 -9
- runbooks/inventory/drift_detection_cli.py +69 -96
- runbooks/inventory/ec2_vpc_utils.py +2 -2
- runbooks/inventory/find_cfn_drift_detection.py +5 -7
- runbooks/inventory/find_cfn_orphaned_stacks.py +7 -9
- runbooks/inventory/find_cfn_stackset_drift.py +5 -6
- runbooks/inventory/find_ec2_security_groups.py +48 -42
- runbooks/inventory/find_landingzone_versions.py +4 -6
- runbooks/inventory/find_vpc_flow_logs.py +7 -9
- runbooks/inventory/inventory_mcp_cli.py +48 -46
- runbooks/inventory/inventory_modules.py +103 -91
- runbooks/inventory/list_cfn_stacks.py +9 -10
- runbooks/inventory/list_cfn_stackset_operation_results.py +1 -3
- runbooks/inventory/list_cfn_stackset_operations.py +79 -57
- runbooks/inventory/list_cfn_stacksets.py +8 -10
- runbooks/inventory/list_config_recorders_delivery_channels.py +49 -39
- runbooks/inventory/list_ds_directories.py +65 -53
- runbooks/inventory/list_ec2_availability_zones.py +2 -4
- runbooks/inventory/list_ec2_ebs_volumes.py +32 -35
- runbooks/inventory/list_ec2_instances.py +23 -28
- runbooks/inventory/list_ecs_clusters_and_tasks.py +26 -34
- runbooks/inventory/list_elbs_load_balancers.py +22 -20
- runbooks/inventory/list_enis_network_interfaces.py +26 -33
- runbooks/inventory/list_guardduty_detectors.py +2 -4
- runbooks/inventory/list_iam_policies.py +2 -4
- runbooks/inventory/list_iam_roles.py +5 -7
- runbooks/inventory/list_iam_saml_providers.py +4 -6
- runbooks/inventory/list_lambda_functions.py +38 -38
- runbooks/inventory/list_org_accounts.py +6 -8
- runbooks/inventory/list_org_accounts_users.py +55 -44
- runbooks/inventory/list_rds_db_instances.py +31 -33
- runbooks/inventory/list_rds_snapshots_aggregator.py +192 -208
- runbooks/inventory/list_route53_hosted_zones.py +3 -5
- runbooks/inventory/list_servicecatalog_provisioned_products.py +37 -41
- runbooks/inventory/list_sns_topics.py +2 -4
- runbooks/inventory/list_ssm_parameters.py +4 -7
- runbooks/inventory/list_vpc_subnets.py +2 -4
- runbooks/inventory/list_vpcs.py +7 -10
- runbooks/inventory/mcp_inventory_validator.py +554 -468
- runbooks/inventory/mcp_vpc_validator.py +359 -442
- runbooks/inventory/organizations_discovery.py +63 -55
- runbooks/inventory/recover_cfn_stack_ids.py +7 -8
- runbooks/inventory/requirements.txt +0 -1
- runbooks/inventory/rich_inventory_display.py +35 -34
- runbooks/inventory/run_on_multi_accounts.py +3 -5
- runbooks/inventory/unified_validation_engine.py +281 -253
- runbooks/inventory/verify_ec2_security_groups.py +1 -1
- runbooks/inventory/vpc_analyzer.py +735 -697
- runbooks/inventory/vpc_architecture_validator.py +293 -348
- runbooks/inventory/vpc_dependency_analyzer.py +384 -380
- 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 +461 -454
- 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.6.dist-info/METADATA +327 -0
- runbooks-1.1.6.dist-info/RECORD +489 -0
- 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/RECORD +0 -468
- {runbooks-1.1.4.dist-info → runbooks-1.1.6.dist-info}/WHEEL +0 -0
- {runbooks-1.1.4.dist-info → runbooks-1.1.6.dist-info}/entry_points.txt +0 -0
- {runbooks-1.1.4.dist-info → runbooks-1.1.6.dist-info}/licenses/LICENSE +0 -0
- {runbooks-1.1.4.dist-info → runbooks-1.1.6.dist-info}/top_level.txt +0 -0
runbooks/finops/helpers.py
CHANGED
@@ -35,10 +35,10 @@ styles = getSampleStyleSheet()
|
|
35
35
|
def format_currency(amount) -> str:
|
36
36
|
"""
|
37
37
|
Format currency for business display in notebooks.
|
38
|
-
|
38
|
+
|
39
39
|
Args:
|
40
40
|
amount: Numeric amount to format
|
41
|
-
|
41
|
+
|
42
42
|
Returns:
|
43
43
|
Formatted currency string
|
44
44
|
"""
|
@@ -56,162 +56,165 @@ def format_currency(amount) -> str:
|
|
56
56
|
def create_business_summary_table(scenarios: Dict[str, Any], rich_console: Optional[Console] = None) -> str:
|
57
57
|
"""
|
58
58
|
Create a business-friendly summary table for notebook display.
|
59
|
-
|
59
|
+
|
60
60
|
Args:
|
61
61
|
scenarios: Dictionary of business scenarios
|
62
62
|
rich_console: Optional Rich console for enhanced formatting
|
63
|
-
|
63
|
+
|
64
64
|
Returns:
|
65
65
|
Formatted table string suitable for notebook display
|
66
66
|
"""
|
67
67
|
output = []
|
68
|
-
|
68
|
+
|
69
69
|
# Header
|
70
70
|
output.append("Business Case Summary")
|
71
71
|
output.append("=" * 60)
|
72
72
|
output.append(f"{'Scenario':<25} | {'Annual Savings':<15} | {'Status':<15}")
|
73
73
|
output.append("-" * 60)
|
74
|
-
|
74
|
+
|
75
75
|
# Process scenarios
|
76
76
|
for key, scenario in scenarios.items():
|
77
|
-
if key ==
|
77
|
+
if key == "metadata":
|
78
78
|
continue
|
79
|
-
|
79
|
+
|
80
80
|
# Extract scenario info
|
81
|
-
title = scenario.get(
|
82
|
-
|
81
|
+
title = scenario.get("title", scenario.get("description", key))[:24]
|
82
|
+
|
83
83
|
# Format savings
|
84
|
-
if
|
85
|
-
savings = format_currency(scenario[
|
86
|
-
elif
|
87
|
-
range_data = scenario[
|
84
|
+
if "actual_savings" in scenario:
|
85
|
+
savings = format_currency(scenario["actual_savings"])
|
86
|
+
elif "savings_range" in scenario:
|
87
|
+
range_data = scenario["savings_range"]
|
88
88
|
savings = f"{format_currency(range_data['min'])}-{format_currency(range_data['max'])}"
|
89
89
|
else:
|
90
90
|
savings = "Under investigation"
|
91
|
-
|
91
|
+
|
92
92
|
# Status
|
93
|
-
status = scenario.get(
|
94
|
-
|
93
|
+
status = scenario.get("status", "Analysis ready")[:14]
|
94
|
+
|
95
95
|
output.append(f"{title:<25} | {savings:<15} | {status:<15}")
|
96
|
-
|
96
|
+
|
97
97
|
# Footer
|
98
|
-
if
|
98
|
+
if "metadata" in scenarios:
|
99
99
|
output.append("-" * 60)
|
100
100
|
output.append(f"Generated: {scenarios['metadata'].get('generated_at', 'Unknown')}")
|
101
101
|
output.append(f"Source: {scenarios['metadata'].get('data_source', 'Unknown')}")
|
102
|
-
|
102
|
+
|
103
103
|
return "\n".join(output)
|
104
104
|
|
105
105
|
|
106
106
|
def export_scenarios_to_notebook_html(scenarios: Dict[str, Any], title: str = "FinOps Analysis") -> str:
|
107
107
|
"""
|
108
108
|
Export scenarios as HTML suitable for Jupyter notebook display.
|
109
|
-
|
109
|
+
|
110
110
|
Args:
|
111
111
|
scenarios: Dictionary of business scenarios
|
112
112
|
title: HTML document title
|
113
|
-
|
113
|
+
|
114
114
|
Returns:
|
115
115
|
HTML string for notebook display
|
116
116
|
"""
|
117
117
|
html = []
|
118
|
-
|
118
|
+
|
119
119
|
# HTML header
|
120
120
|
html.append(f"""
|
121
121
|
<div style="font-family: Arial, sans-serif; margin: 20px;">
|
122
122
|
<h2 style="color: #2c5aa0;">{title}</h2>
|
123
123
|
""")
|
124
|
-
|
124
|
+
|
125
125
|
# Process scenarios
|
126
126
|
for key, scenario in scenarios.items():
|
127
|
-
if key ==
|
127
|
+
if key == "metadata":
|
128
128
|
continue
|
129
|
-
|
130
|
-
title_text = scenario.get(
|
131
|
-
|
129
|
+
|
130
|
+
title_text = scenario.get("title", scenario.get("description", key))
|
131
|
+
|
132
132
|
html.append(f'<div style="border: 1px solid #ddd; margin: 10px 0; padding: 15px; border-radius: 5px;">')
|
133
133
|
html.append(f'<h3 style="color: #1f4788; margin-top: 0;">{title_text}</h3>')
|
134
|
-
|
134
|
+
|
135
135
|
# Savings information
|
136
|
-
if
|
137
|
-
savings = format_currency(scenario[
|
138
|
-
html.append(f
|
139
|
-
elif
|
140
|
-
range_data = scenario[
|
141
|
-
min_savings = format_currency(range_data[
|
142
|
-
max_savings = format_currency(range_data[
|
143
|
-
html.append(f
|
136
|
+
if "actual_savings" in scenario:
|
137
|
+
savings = format_currency(scenario["actual_savings"])
|
138
|
+
html.append(f"<p><strong>💰 Annual Savings:</strong> {savings}</p>")
|
139
|
+
elif "savings_range" in scenario:
|
140
|
+
range_data = scenario["savings_range"]
|
141
|
+
min_savings = format_currency(range_data["min"])
|
142
|
+
max_savings = format_currency(range_data["max"])
|
143
|
+
html.append(f"<p><strong>💰 Annual Savings:</strong> {min_savings} - {max_savings}</p>")
|
144
144
|
else:
|
145
|
-
html.append(
|
146
|
-
|
145
|
+
html.append("<p><strong>💰 Annual Savings:</strong> Under investigation</p>")
|
146
|
+
|
147
147
|
# Implementation details
|
148
|
-
if
|
149
|
-
html.append(f
|
150
|
-
|
151
|
-
if
|
152
|
-
html.append(f
|
153
|
-
|
154
|
-
html.append(
|
155
|
-
|
148
|
+
if "implementation_time" in scenario:
|
149
|
+
html.append(f"<p><strong>⏱️ Implementation:</strong> {scenario['implementation_time']}</p>")
|
150
|
+
|
151
|
+
if "risk_level" in scenario:
|
152
|
+
html.append(f"<p><strong>🛡️ Risk Level:</strong> {scenario['risk_level']}</p>")
|
153
|
+
|
154
|
+
html.append("</div>")
|
155
|
+
|
156
156
|
# Metadata footer
|
157
|
-
if
|
158
|
-
metadata = scenarios[
|
157
|
+
if "metadata" in scenarios:
|
158
|
+
metadata = scenarios["metadata"]
|
159
159
|
html.append('<div style="margin-top: 20px; padding: 10px; background-color: #f8f9fa; border-radius: 5px;">')
|
160
|
-
html.append(f
|
161
|
-
html.append(f
|
162
|
-
html.append(
|
163
|
-
|
164
|
-
html.append(
|
165
|
-
|
166
|
-
return
|
160
|
+
html.append(f"<small><strong>Data Source:</strong> {metadata.get('data_source', 'Unknown')}<br>")
|
161
|
+
html.append(f"<strong>Generated:</strong> {metadata.get('generated_at', 'Unknown')}</small>")
|
162
|
+
html.append("</div>")
|
163
|
+
|
164
|
+
html.append("</div>")
|
165
|
+
|
166
|
+
return "".join(html)
|
167
167
|
|
168
168
|
|
169
169
|
def create_roi_analysis_table(business_cases: List[Dict[str, Any]]) -> str:
|
170
170
|
"""
|
171
171
|
Create ROI analysis table for business decision making.
|
172
|
-
|
172
|
+
|
173
173
|
Args:
|
174
174
|
business_cases: List of business case dictionaries
|
175
|
-
|
175
|
+
|
176
176
|
Returns:
|
177
177
|
Formatted ROI analysis table
|
178
178
|
"""
|
179
179
|
output = []
|
180
|
-
|
180
|
+
|
181
181
|
# Header
|
182
182
|
output.append("ROI Analysis Summary")
|
183
183
|
output.append("=" * 80)
|
184
184
|
output.append(f"{'Business Case':<25} | {'Annual Savings':<12} | {'ROI %':<8} | {'Risk':<8} | {'Payback':<10}")
|
185
185
|
output.append("-" * 80)
|
186
|
-
|
186
|
+
|
187
187
|
total_savings = 0
|
188
|
-
|
188
|
+
|
189
189
|
# Process business cases
|
190
190
|
for case in business_cases:
|
191
|
-
title = case.get(
|
192
|
-
|
191
|
+
title = case.get("title", "Unknown Case")[:24]
|
192
|
+
|
193
193
|
# Extract financial metrics
|
194
|
-
savings = case.get(
|
195
|
-
roi = case.get(
|
196
|
-
risk = case.get(
|
197
|
-
payback = case.get(
|
198
|
-
|
194
|
+
savings = case.get("annual_savings", case.get("actual_savings", 0))
|
195
|
+
roi = case.get("roi_percentage", 0)
|
196
|
+
risk = case.get("risk_level", "Medium")[:7]
|
197
|
+
payback = case.get("payback_months", 0)
|
198
|
+
|
199
199
|
# Format values
|
200
200
|
savings_str = format_currency(savings)[:11]
|
201
201
|
roi_str = f"{roi:.0f}%" if roi < 9999 else ">999%"
|
202
202
|
payback_str = f"{payback:.1f}mo" if payback > 0 else "Immediate"
|
203
|
-
|
203
|
+
|
204
204
|
output.append(f"{title:<25} | {savings_str:<12} | {roi_str:<8} | {risk:<8} | {payback_str:<10}")
|
205
|
-
|
205
|
+
|
206
206
|
if isinstance(savings, (int, float)):
|
207
207
|
total_savings += savings
|
208
|
-
|
208
|
+
|
209
209
|
# Summary
|
210
210
|
output.append("-" * 80)
|
211
|
-
output.append(
|
212
|
-
|
211
|
+
output.append(
|
212
|
+
f"{'TOTAL PORTFOLIO':<25} | {format_currency(total_savings):<12} | {'N/A':<8} | {'Mixed':<8} | {'Varies':<10}"
|
213
|
+
)
|
214
|
+
|
213
215
|
return "\n".join(output)
|
214
216
|
|
217
|
+
|
215
218
|
# Custom style for the footer
|
216
219
|
audit_footer_style = ParagraphStyle(
|
217
220
|
name="AuditFooter",
|
@@ -286,14 +289,14 @@ def export_audit_report_to_pdf(
|
|
286
289
|
untagged_display = ""
|
287
290
|
if row.get("untagged_count", 0) > 0:
|
288
291
|
# Format like reference: "EC2: us-east-1: i-1234567890"
|
289
|
-
account_id = row.get(
|
290
|
-
if account_id and account_id !=
|
292
|
+
account_id = row.get("account_id", "unknown")
|
293
|
+
if account_id and account_id != "unknown":
|
291
294
|
untagged_display = f"EC2:\nus-east-1:\ni-{account_id[:10]}"
|
292
295
|
else:
|
293
296
|
untagged_display = f"EC2:\nus-east-1:\ni-unavailable"
|
294
297
|
if row.get("untagged_count", 0) > 1:
|
295
|
-
account_id = row.get(
|
296
|
-
if account_id and account_id !=
|
298
|
+
account_id = row.get("account_id", "unknown")
|
299
|
+
if account_id and account_id != "unknown":
|
297
300
|
untagged_display += f"\n\nRDS:\nus-west-2:\ndb-{account_id[:10]}"
|
298
301
|
else:
|
299
302
|
untagged_display += f"\n\nRDS:\nus-west-2:\ndb-unavailable"
|
@@ -818,7 +821,9 @@ def export_cost_dashboard_to_markdown_legacy(
|
|
818
821
|
|
819
822
|
# Footer
|
820
823
|
markdown_lines.append("---")
|
821
|
-
markdown_lines.append(
|
824
|
+
markdown_lines.append(
|
825
|
+
"*Generated by CloudOps & FinOps Runbooks Dashboard - Enterprise Cost Management Platform*"
|
826
|
+
)
|
822
827
|
markdown_lines.append("")
|
823
828
|
markdown_lines.append(
|
824
829
|
"For more information, visit: [CloudOps Documentation](https://github.com/1xOps/CloudOps-Runbooks)"
|
@@ -949,14 +954,36 @@ def export_cost_dashboard_to_pdf(
|
|
949
954
|
budget_limit = current_cost * 1.3 # 30% buffer
|
950
955
|
forecast = current_cost * 1.05 # 5% forecast increase
|
951
956
|
|
952
|
-
|
957
|
+
# Determine budget type for display
|
958
|
+
budget_type = "DevOps" if "dev" in profile_display else "Prod" if "prod" in profile_display else "QA"
|
959
|
+
|
960
|
+
# Calculate utilization for status determination
|
961
|
+
utilization = (current_cost / budget_limit) * 100 if budget_limit > 0 else 0
|
962
|
+
|
963
|
+
# Status icon based on utilization
|
964
|
+
if utilization >= 100:
|
965
|
+
status_icon = "🚨" # Over budget
|
966
|
+
elif utilization >= 85:
|
967
|
+
status_icon = "⚠️" # Near limit
|
968
|
+
elif utilization >= 70:
|
969
|
+
status_icon = "🟡" # Moderate usage
|
970
|
+
else:
|
971
|
+
status_icon = "✅" # Under budget
|
972
|
+
|
973
|
+
# Concise budget display with icons
|
974
|
+
budget_status = (
|
975
|
+
f"{status_icon} {budget_type}\n💰 ${current_cost:.0f}/${budget_limit:.0f} ({utilization:.0f}%)"
|
976
|
+
)
|
953
977
|
|
954
|
-
|
978
|
+
# Add forecast only if significantly different
|
979
|
+
if abs(forecast - current_cost) > (current_cost * 0.05): # 5% threshold
|
980
|
+
trend_icon = "📈" if forecast > current_cost else "📉"
|
981
|
+
budget_status += f"\n{trend_icon} Est: ${forecast:.0f}"
|
955
982
|
|
956
983
|
if len(data) > 2 and i == 2: # Third row - show "No budgets found" like reference
|
957
|
-
budget_status = "No budgets found
|
984
|
+
budget_status = "ℹ️ No budgets found\n💡 Create in console"
|
958
985
|
else:
|
959
|
-
budget_status = "No budgets found
|
986
|
+
budget_status = "ℹ️ No budgets found\n💡 Create in console"
|
960
987
|
|
961
988
|
# Format EC2 instances like reference
|
962
989
|
ec2_summary = ""
|
@@ -1108,192 +1135,9 @@ def load_config_file(file_path: str) -> Optional[Dict[str, Any]]:
|
|
1108
1135
|
return None
|
1109
1136
|
|
1110
1137
|
|
1111
|
-
def
|
1112
|
-
|
1113
|
-
) ->
|
1114
|
-
"""
|
1115
|
-
Generate PDCA (Plan-Do-Check-Act) continuous improvement report.
|
1116
|
-
|
1117
|
-
:param pdca_metrics: List of PDCA metrics for each profile
|
1118
|
-
:param file_name: The base name of the output file
|
1119
|
-
:param path: Optional directory where the file will be saved
|
1120
|
-
:return: Full path of the generated report or None on error
|
1121
|
-
"""
|
1122
|
-
try:
|
1123
|
-
timestamp = datetime.now().strftime("%Y%m%d_%H%M")
|
1124
|
-
base_filename = f"{file_name}_pdca_report_{timestamp}.json"
|
1125
|
-
|
1126
|
-
if path:
|
1127
|
-
os.makedirs(path, exist_ok=True)
|
1128
|
-
output_filename = os.path.join(path, base_filename)
|
1129
|
-
else:
|
1130
|
-
output_filename = base_filename
|
1131
|
-
|
1132
|
-
# Calculate aggregate metrics
|
1133
|
-
total_risk = sum(m["risk_score"] for m in pdca_metrics)
|
1134
|
-
avg_risk = total_risk / len(pdca_metrics) if pdca_metrics else 0
|
1135
|
-
|
1136
|
-
high_risk_accounts = [m for m in pdca_metrics if m["risk_score"] > 25]
|
1137
|
-
medium_risk_accounts = [m for m in pdca_metrics if 10 < m["risk_score"] <= 25]
|
1138
|
-
low_risk_accounts = [m for m in pdca_metrics if m["risk_score"] <= 10]
|
1139
|
-
|
1140
|
-
total_untagged = sum(m["untagged_count"] for m in pdca_metrics)
|
1141
|
-
total_stopped = sum(m["stopped_count"] for m in pdca_metrics)
|
1142
|
-
total_unused_volumes = sum(m["unused_volumes_count"] for m in pdca_metrics)
|
1143
|
-
total_unused_eips = sum(m["unused_eips_count"] for m in pdca_metrics)
|
1144
|
-
total_budget_overruns = sum(m["budget_overruns"] for m in pdca_metrics)
|
1145
|
-
|
1146
|
-
# Generate improvement recommendations
|
1147
|
-
recommendations = []
|
1148
|
-
|
1149
|
-
# PLAN phase recommendations
|
1150
|
-
if total_untagged > 50:
|
1151
|
-
recommendations.append(
|
1152
|
-
{
|
1153
|
-
"phase": "PLAN",
|
1154
|
-
"priority": "HIGH",
|
1155
|
-
"category": "Compliance",
|
1156
|
-
"issue": f"Found {total_untagged} untagged resources across all accounts",
|
1157
|
-
"action": "Implement mandatory tagging strategy using AWS Config rules",
|
1158
|
-
"expected_outcome": "100% resource compliance within 30 days",
|
1159
|
-
"owner": "Cloud Governance Team",
|
1160
|
-
}
|
1161
|
-
)
|
1162
|
-
|
1163
|
-
if total_unused_eips > 5:
|
1164
|
-
recommendations.append(
|
1165
|
-
{
|
1166
|
-
"phase": "PLAN",
|
1167
|
-
"priority": "MEDIUM",
|
1168
|
-
"category": "Cost Optimization",
|
1169
|
-
"issue": f"Found {total_unused_eips} unused Elastic IPs",
|
1170
|
-
"action": "Schedule monthly EIP cleanup automation",
|
1171
|
-
"expected_outcome": f"Save ~${total_unused_eips * 3.65:.2f}/month",
|
1172
|
-
"owner": "FinOps Team",
|
1173
|
-
}
|
1174
|
-
)
|
1175
|
-
|
1176
|
-
# DO phase recommendations
|
1177
|
-
if high_risk_accounts:
|
1178
|
-
recommendations.append(
|
1179
|
-
{
|
1180
|
-
"phase": "DO",
|
1181
|
-
"priority": "CRITICAL",
|
1182
|
-
"category": "Risk Management",
|
1183
|
-
"issue": f"{len(high_risk_accounts)} accounts have critical risk scores",
|
1184
|
-
"action": "Execute immediate remediation on high-risk accounts",
|
1185
|
-
"expected_outcome": "Reduce risk scores by 70% within 2 weeks",
|
1186
|
-
"owner": "Security Team",
|
1187
|
-
}
|
1188
|
-
)
|
1189
|
-
|
1190
|
-
# CHECK phase recommendations
|
1191
|
-
if avg_risk > 15:
|
1192
|
-
recommendations.append(
|
1193
|
-
{
|
1194
|
-
"phase": "CHECK",
|
1195
|
-
"priority": "HIGH",
|
1196
|
-
"category": "Monitoring",
|
1197
|
-
"issue": f"Average risk score ({avg_risk:.1f}) exceeds threshold",
|
1198
|
-
"action": "Implement automated risk scoring dashboard",
|
1199
|
-
"expected_outcome": "Real-time risk visibility and alerting",
|
1200
|
-
"owner": "DevOps Team",
|
1201
|
-
}
|
1202
|
-
)
|
1203
|
-
|
1204
|
-
# ACT phase recommendations
|
1205
|
-
recommendations.append(
|
1206
|
-
{
|
1207
|
-
"phase": "ACT",
|
1208
|
-
"priority": "MEDIUM",
|
1209
|
-
"category": "Process Improvement",
|
1210
|
-
"issue": "Need continuous improvement framework",
|
1211
|
-
"action": "Establish monthly PDCA review cycles",
|
1212
|
-
"expected_outcome": "25% reduction in average risk score per quarter",
|
1213
|
-
"owner": "Cloud Center of Excellence",
|
1214
|
-
}
|
1215
|
-
)
|
1216
|
-
|
1217
|
-
# Create comprehensive PDCA report
|
1218
|
-
pdca_report = {
|
1219
|
-
"report_metadata": {
|
1220
|
-
"generated_at": datetime.now().isoformat(),
|
1221
|
-
"report_type": "PDCA Continuous Improvement Analysis",
|
1222
|
-
"accounts_analyzed": len(pdca_metrics),
|
1223
|
-
"framework_version": "v1.0",
|
1224
|
-
},
|
1225
|
-
"executive_summary": {
|
1226
|
-
"overall_risk_score": total_risk,
|
1227
|
-
"average_risk_score": round(avg_risk, 2),
|
1228
|
-
"risk_distribution": {
|
1229
|
-
"critical_accounts": len(high_risk_accounts),
|
1230
|
-
"high_risk_accounts": len(medium_risk_accounts),
|
1231
|
-
"low_risk_accounts": len(low_risk_accounts),
|
1232
|
-
},
|
1233
|
-
"key_findings": {
|
1234
|
-
"untagged_resources": total_untagged,
|
1235
|
-
"stopped_instances": total_stopped,
|
1236
|
-
"unused_volumes": total_unused_volumes,
|
1237
|
-
"unused_elastic_ips": total_unused_eips,
|
1238
|
-
"budget_overruns": total_budget_overruns,
|
1239
|
-
},
|
1240
|
-
},
|
1241
|
-
"pdca_analysis": {
|
1242
|
-
"plan_phase": {
|
1243
|
-
"description": "Strategic planning based on current state analysis",
|
1244
|
-
"metrics_collected": len(pdca_metrics),
|
1245
|
-
"baseline_established": True,
|
1246
|
-
},
|
1247
|
-
"do_phase": {
|
1248
|
-
"description": "Implementation of audit data collection",
|
1249
|
-
"data_sources": ["EC2", "RDS", "Lambda", "ELBv2", "Budgets"],
|
1250
|
-
"regions_scanned": "All accessible regions",
|
1251
|
-
},
|
1252
|
-
"check_phase": {
|
1253
|
-
"description": "Analysis of collected audit data",
|
1254
|
-
"risk_assessment_completed": True,
|
1255
|
-
"trends_identified": True,
|
1256
|
-
},
|
1257
|
-
"act_phase": {
|
1258
|
-
"description": "Actionable recommendations for improvement",
|
1259
|
-
"recommendations_generated": len(recommendations),
|
1260
|
-
"prioritization_completed": True,
|
1261
|
-
},
|
1262
|
-
},
|
1263
|
-
"detailed_metrics": pdca_metrics,
|
1264
|
-
"improvement_recommendations": recommendations,
|
1265
|
-
"next_steps": {
|
1266
|
-
"immediate_actions": [
|
1267
|
-
"Review high-risk accounts within 48 hours",
|
1268
|
-
"Implement automated tagging for untagged resources",
|
1269
|
-
"Schedule EIP cleanup automation",
|
1270
|
-
],
|
1271
|
-
"medium_term_goals": [
|
1272
|
-
"Establish monthly PDCA review cycle",
|
1273
|
-
"Implement risk scoring dashboard",
|
1274
|
-
"Create automated remediation workflows",
|
1275
|
-
],
|
1276
|
-
"long_term_objectives": [
|
1277
|
-
"Achieve average risk score below 5",
|
1278
|
-
"Maintain 100% resource compliance",
|
1279
|
-
"Reduce cloud waste by 25%",
|
1280
|
-
],
|
1281
|
-
},
|
1282
|
-
}
|
1283
|
-
|
1284
|
-
# Export to JSON
|
1285
|
-
with open(output_filename, "w", encoding="utf-8") as jsonfile:
|
1286
|
-
json.dump(pdca_report, jsonfile, indent=4, default=str)
|
1287
|
-
|
1288
|
-
return os.path.abspath(output_filename)
|
1289
|
-
|
1290
|
-
except Exception as e:
|
1291
|
-
console.print(f"[bold red]Error generating PDCA improvement report: {str(e)}[/]")
|
1292
|
-
return None
|
1293
|
-
|
1294
|
-
|
1295
|
-
def export_scenario_results(results: Dict[str, Any], scenario_name: str,
|
1296
|
-
report_types: List[str], output_dir: Optional[str] = None) -> bool:
|
1138
|
+
def export_scenario_results(
|
1139
|
+
results: Dict[str, Any], scenario_name: str, report_types: List[str], output_dir: Optional[str] = None
|
1140
|
+
) -> bool:
|
1297
1141
|
"""
|
1298
1142
|
Export business scenario results in specified formats.
|
1299
1143
|
|
@@ -1307,7 +1151,7 @@ def export_scenario_results(results: Dict[str, Any], scenario_name: str,
|
|
1307
1151
|
True if all exports succeeded
|
1308
1152
|
"""
|
1309
1153
|
try:
|
1310
|
-
from runbooks.common.rich_utils import
|
1154
|
+
from runbooks.common.rich_utils import print_error, print_info, print_success
|
1311
1155
|
|
1312
1156
|
output_dir = output_dir or "."
|
1313
1157
|
base_filename = f"finops-scenario-{scenario_name}-{datetime.now().strftime('%Y%m%d-%H%M%S')}"
|
@@ -1317,64 +1161,62 @@ def export_scenario_results(results: Dict[str, Any], scenario_name: str,
|
|
1317
1161
|
|
1318
1162
|
for report_type in report_types:
|
1319
1163
|
try:
|
1320
|
-
if report_type ==
|
1164
|
+
if report_type == "json":
|
1321
1165
|
output_path = os.path.join(output_dir, f"{base_filename}.json")
|
1322
|
-
with open(output_path,
|
1166
|
+
with open(output_path, "w", encoding="utf-8") as f:
|
1323
1167
|
json.dump(results, f, indent=2, default=str)
|
1324
1168
|
print_info(f"📄 JSON export: {output_path}")
|
1325
1169
|
|
1326
|
-
elif report_type ==
|
1170
|
+
elif report_type == "csv":
|
1327
1171
|
output_path = os.path.join(output_dir, f"{base_filename}.csv")
|
1328
1172
|
# Extract key metrics for CSV export
|
1329
|
-
if
|
1330
|
-
business_data = results[
|
1331
|
-
with open(output_path,
|
1173
|
+
if "business_impact" in results:
|
1174
|
+
business_data = results["business_impact"]
|
1175
|
+
with open(output_path, "w", newline="", encoding="utf-8") as f:
|
1332
1176
|
writer = csv.DictWriter(f, fieldnames=business_data.keys())
|
1333
1177
|
writer.writeheader()
|
1334
1178
|
writer.writerow(business_data)
|
1335
1179
|
print_info(f"📊 CSV export: {output_path}")
|
1336
1180
|
|
1337
|
-
elif report_type ==
|
1181
|
+
elif report_type == "pdf":
|
1338
1182
|
output_path = os.path.join(output_dir, f"{base_filename}.pdf")
|
1339
1183
|
# Create basic PDF with scenario results
|
1340
1184
|
doc = SimpleDocTemplate(output_path, pagesize=letter)
|
1341
1185
|
story = []
|
1342
1186
|
|
1343
1187
|
# Title
|
1344
|
-
title = Paragraph(f"FinOps Business Scenario: {scenario_name.title()}",
|
1345
|
-
styles['Heading1'])
|
1188
|
+
title = Paragraph(f"FinOps Business Scenario: {scenario_name.title()}", styles["Heading1"])
|
1346
1189
|
story.append(title)
|
1347
1190
|
story.append(Spacer(1, 12))
|
1348
1191
|
|
1349
1192
|
# Add results summary
|
1350
|
-
if
|
1351
|
-
business_data = results[
|
1193
|
+
if "business_impact" in results:
|
1194
|
+
business_data = results["business_impact"]
|
1352
1195
|
for key, value in business_data.items():
|
1353
|
-
text = Paragraph(f"<b>{key.replace('_', ' ').title()}:</b> {value}",
|
1354
|
-
styles['Normal'])
|
1196
|
+
text = Paragraph(f"<b>{key.replace('_', ' ').title()}:</b> {value}", styles["Normal"])
|
1355
1197
|
story.append(text)
|
1356
1198
|
|
1357
1199
|
doc.build(story)
|
1358
1200
|
print_info(f"📋 PDF export: {output_path}")
|
1359
1201
|
|
1360
|
-
elif report_type ==
|
1202
|
+
elif report_type == "markdown":
|
1361
1203
|
output_path = os.path.join(output_dir, f"{base_filename}.md")
|
1362
|
-
with open(output_path,
|
1204
|
+
with open(output_path, "w", encoding="utf-8") as f:
|
1363
1205
|
f.write(f"# FinOps Business Scenario: {scenario_name.title()}\n\n")
|
1364
1206
|
f.write(f"Generated: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n\n")
|
1365
1207
|
|
1366
1208
|
# Add business impact section
|
1367
|
-
if
|
1209
|
+
if "business_impact" in results:
|
1368
1210
|
f.write("## Business Impact\n\n")
|
1369
|
-
business_data = results[
|
1211
|
+
business_data = results["business_impact"]
|
1370
1212
|
for key, value in business_data.items():
|
1371
1213
|
f.write(f"- **{key.replace('_', ' ').title()}**: {value}\n")
|
1372
1214
|
f.write("\n")
|
1373
1215
|
|
1374
1216
|
# Add technical details section
|
1375
|
-
if
|
1217
|
+
if "technical_details" in results:
|
1376
1218
|
f.write("## Technical Details\n\n")
|
1377
|
-
tech_data = results[
|
1219
|
+
tech_data = results["technical_details"]
|
1378
1220
|
for key, value in tech_data.items():
|
1379
1221
|
f.write(f"- **{key.replace('_', ' ').title()}**: {value}\n")
|
1380
1222
|
|
runbooks/finops/iam_guidance.py
CHANGED
@@ -313,7 +313,7 @@ def handle_cost_explorer_error(error: Exception, profile_name: Optional[str] = N
|
|
313
313
|
|
314
314
|
def _display_single_account_cost_explorer_guidance(error: Exception, profile_name: Optional[str] = None):
|
315
315
|
"""Display context-aware guidance for single account Cost Explorer limitations."""
|
316
|
-
|
316
|
+
|
317
317
|
# Get dynamic date period for CLI examples
|
318
318
|
start_date, end_date = get_aws_cli_example_period()
|
319
319
|
|