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
@@ -1,527 +0,0 @@
|
|
1
|
-
#!/usr/bin/env python3
|
2
|
-
"""
|
3
|
-
Enhanced FinOps Dashboard Runner
|
4
|
-
|
5
|
-
This module provides enterprise-grade FinOps dashboard capabilities including:
|
6
|
-
- Multi-profile AWS cost analysis with Rich console formatting
|
7
|
-
- Advanced audit reporting with PDF/CSV/JSON export
|
8
|
-
- Resource utilization tracking and optimization recommendations
|
9
|
-
- Budget monitoring and alerting integration
|
10
|
-
- Trend analysis and forecasting capabilities
|
11
|
-
"""
|
12
|
-
|
13
|
-
import argparse
|
14
|
-
import csv
|
15
|
-
import json
|
16
|
-
from collections import defaultdict
|
17
|
-
from datetime import datetime, timedelta
|
18
|
-
from pathlib import Path
|
19
|
-
from typing import Any, Dict, List, Optional, Tuple, Union
|
20
|
-
|
21
|
-
import boto3
|
22
|
-
from rich import box
|
23
|
-
from rich.console import Console
|
24
|
-
from rich.panel import Panel
|
25
|
-
from rich.progress import Progress, SpinnerColumn, TextColumn, track
|
26
|
-
from rich.status import Status
|
27
|
-
from rich.table import Column, Table
|
28
|
-
from rich.tree import Tree
|
29
|
-
|
30
|
-
from ..common.rich_utils import get_console
|
31
|
-
|
32
|
-
# Import FinOpsConfig for backward compatibility with tests
|
33
|
-
from .finops_dashboard import FinOpsConfig
|
34
|
-
|
35
|
-
console = Console()
|
36
|
-
|
37
|
-
|
38
|
-
class EnhancedFinOpsDashboard:
|
39
|
-
"""Enhanced FinOps Dashboard with production-tested capabilities from runbooks finops"""
|
40
|
-
|
41
|
-
def __init__(self, config: Optional[Dict[str, Any]] = None):
|
42
|
-
self.config = config or {}
|
43
|
-
self.console = Console()
|
44
|
-
self.rich_console = self.console # Use the console instance directly
|
45
|
-
|
46
|
-
# Export directory setup
|
47
|
-
self.export_dir = Path("artifacts/finops-exports")
|
48
|
-
self.export_dir.mkdir(parents=True, exist_ok=True)
|
49
|
-
|
50
|
-
def get_aws_profiles(self) -> List[str]:
|
51
|
-
"""Get available AWS profiles from AWS CLI configuration"""
|
52
|
-
try:
|
53
|
-
import configparser
|
54
|
-
import os
|
55
|
-
|
56
|
-
aws_config_path = os.path.expanduser("~/.aws/config")
|
57
|
-
aws_credentials_path = os.path.expanduser("~/.aws/credentials")
|
58
|
-
|
59
|
-
profiles = set()
|
60
|
-
|
61
|
-
# Parse AWS config file
|
62
|
-
if os.path.exists(aws_config_path):
|
63
|
-
config = configparser.ConfigParser()
|
64
|
-
config.read(aws_config_path)
|
65
|
-
for section in config.sections():
|
66
|
-
if section.startswith("profile "):
|
67
|
-
profiles.add(section.replace("profile ", ""))
|
68
|
-
elif section == "default":
|
69
|
-
profiles.add("default")
|
70
|
-
|
71
|
-
# Parse AWS credentials file
|
72
|
-
if os.path.exists(aws_credentials_path):
|
73
|
-
credentials = configparser.ConfigParser()
|
74
|
-
credentials.read(aws_credentials_path)
|
75
|
-
profiles.update(credentials.sections())
|
76
|
-
|
77
|
-
return sorted(list(profiles))
|
78
|
-
|
79
|
-
except Exception as e:
|
80
|
-
console.print(f"ā ļø Error reading AWS profiles: {e}", style="yellow")
|
81
|
-
return []
|
82
|
-
|
83
|
-
def get_account_info(self, profile: str) -> Dict[str, Any]:
|
84
|
-
"""Get AWS account information for a profile"""
|
85
|
-
try:
|
86
|
-
session = boto3.Session(profile_name=profile)
|
87
|
-
sts = session.client("sts")
|
88
|
-
|
89
|
-
identity = sts.get_caller_identity()
|
90
|
-
|
91
|
-
return {
|
92
|
-
"account_id": identity["Account"],
|
93
|
-
"user_arn": identity["Arn"],
|
94
|
-
"user_id": identity["UserId"],
|
95
|
-
"profile": profile,
|
96
|
-
"status": "active",
|
97
|
-
}
|
98
|
-
|
99
|
-
except Exception as e:
|
100
|
-
return {"account_id": "N/A", "profile": profile, "status": "error", "error": str(e)}
|
101
|
-
|
102
|
-
def get_resource_audit_data(self, profile: str, regions: Optional[List[str]] = None) -> Dict[str, Any]:
|
103
|
-
"""
|
104
|
-
Get comprehensive resource audit data for a profile
|
105
|
-
|
106
|
-
Enhanced with additional resource types and cost impact analysis
|
107
|
-
"""
|
108
|
-
audit_data = {
|
109
|
-
"profile": profile,
|
110
|
-
"account_info": self.get_account_info(profile),
|
111
|
-
"untagged_resources": 0,
|
112
|
-
"stopped_instances": 0,
|
113
|
-
"unused_volumes": 0,
|
114
|
-
"unused_eips": 0,
|
115
|
-
"budget_alerts": 0,
|
116
|
-
"cost_optimization_opportunities": [],
|
117
|
-
"regional_breakdown": {},
|
118
|
-
"total_potential_savings": 0.0,
|
119
|
-
}
|
120
|
-
|
121
|
-
if audit_data["account_info"]["status"] == "error":
|
122
|
-
return audit_data
|
123
|
-
|
124
|
-
try:
|
125
|
-
session = boto3.Session(profile_name=profile)
|
126
|
-
|
127
|
-
# Default to common regions if none specified
|
128
|
-
if not regions:
|
129
|
-
regions = ["us-east-1", "us-west-2", "eu-west-1"]
|
130
|
-
|
131
|
-
for region in regions:
|
132
|
-
region_data = self._audit_region_resources(session, region)
|
133
|
-
audit_data["regional_breakdown"][region] = region_data
|
134
|
-
|
135
|
-
# Aggregate data
|
136
|
-
audit_data["untagged_resources"] += region_data["untagged_resources"]
|
137
|
-
audit_data["stopped_instances"] += region_data["stopped_instances"]
|
138
|
-
audit_data["unused_volumes"] += region_data["unused_volumes"]
|
139
|
-
audit_data["unused_eips"] += region_data["unused_eips"]
|
140
|
-
audit_data["total_potential_savings"] += region_data["potential_savings"]
|
141
|
-
audit_data["cost_optimization_opportunities"].extend(region_data["optimization_opportunities"])
|
142
|
-
|
143
|
-
# Get budget information
|
144
|
-
audit_data["budget_alerts"] = self._get_budget_alerts(session)
|
145
|
-
|
146
|
-
except Exception as e:
|
147
|
-
console.print(f"ā ļø Error auditing resources for {profile}: {e}", style="yellow")
|
148
|
-
|
149
|
-
return audit_data
|
150
|
-
|
151
|
-
def _audit_region_resources(self, session: boto3.Session, region: str) -> Dict[str, Any]:
|
152
|
-
"""Audit resources in a specific region"""
|
153
|
-
region_data = {
|
154
|
-
"region": region,
|
155
|
-
"untagged_resources": 0,
|
156
|
-
"stopped_instances": 0,
|
157
|
-
"unused_volumes": 0,
|
158
|
-
"unused_eips": 0,
|
159
|
-
"potential_savings": 0.0,
|
160
|
-
"optimization_opportunities": [],
|
161
|
-
}
|
162
|
-
|
163
|
-
try:
|
164
|
-
ec2 = session.client("ec2", region_name=region)
|
165
|
-
|
166
|
-
# Get stopped EC2 instances
|
167
|
-
instances_response = ec2.describe_instances(
|
168
|
-
Filters=[{"Name": "instance-state-name", "Values": ["stopped"]}]
|
169
|
-
)
|
170
|
-
|
171
|
-
stopped_instances = []
|
172
|
-
for reservation in instances_response["Reservations"]:
|
173
|
-
for instance in reservation["Instances"]:
|
174
|
-
stopped_instances.append(
|
175
|
-
{
|
176
|
-
"instance_id": instance["InstanceId"],
|
177
|
-
"instance_type": instance["InstanceType"],
|
178
|
-
"launch_time": instance.get("LaunchTime"),
|
179
|
-
"tags": instance.get("Tags", []),
|
180
|
-
}
|
181
|
-
)
|
182
|
-
|
183
|
-
region_data["stopped_instances"] = len(stopped_instances)
|
184
|
-
|
185
|
-
# Calculate potential savings from stopped instances (rough estimate)
|
186
|
-
# Assume average $50/month per stopped instance in savings opportunity
|
187
|
-
region_data["potential_savings"] += len(stopped_instances) * 50.0
|
188
|
-
|
189
|
-
if stopped_instances:
|
190
|
-
region_data["optimization_opportunities"].append(
|
191
|
-
{
|
192
|
-
"type": "stopped_instances",
|
193
|
-
"count": len(stopped_instances),
|
194
|
-
"description": f"{len(stopped_instances)} stopped EC2 instances - consider termination",
|
195
|
-
"potential_savings": len(stopped_instances) * 50.0,
|
196
|
-
"priority": "high",
|
197
|
-
}
|
198
|
-
)
|
199
|
-
|
200
|
-
# Get unused EBS volumes
|
201
|
-
volumes_response = ec2.describe_volumes(Filters=[{"Name": "status", "Values": ["available"]}])
|
202
|
-
|
203
|
-
unused_volumes = volumes_response["Volumes"]
|
204
|
-
region_data["unused_volumes"] = len(unused_volumes)
|
205
|
-
|
206
|
-
# Note: EBS cost calculation requires real AWS Cost Explorer pricing data
|
207
|
-
# Hardcoded pricing estimates removed per compliance requirements
|
208
|
-
volume_savings = 0 # Cannot calculate without real AWS pricing API
|
209
|
-
region_data["potential_savings"] += volume_savings
|
210
|
-
|
211
|
-
if unused_volumes:
|
212
|
-
region_data["optimization_opportunities"].append(
|
213
|
-
{
|
214
|
-
"type": "unused_volumes",
|
215
|
-
"count": len(unused_volumes),
|
216
|
-
"description": f"{len(unused_volumes)} unused EBS volumes",
|
217
|
-
"potential_savings": volume_savings,
|
218
|
-
"priority": "medium",
|
219
|
-
}
|
220
|
-
)
|
221
|
-
|
222
|
-
# Get unused Elastic IPs
|
223
|
-
eips_response = ec2.describe_addresses()
|
224
|
-
unused_eips = [eip for eip in eips_response["Addresses"] if "InstanceId" not in eip]
|
225
|
-
region_data["unused_eips"] = len(unused_eips)
|
226
|
-
|
227
|
-
# Unused EIP cost: $3.65/month each
|
228
|
-
eip_savings = len(unused_eips) * 3.65
|
229
|
-
region_data["potential_savings"] += eip_savings
|
230
|
-
|
231
|
-
if unused_eips:
|
232
|
-
region_data["optimization_opportunities"].append(
|
233
|
-
{
|
234
|
-
"type": "unused_eips",
|
235
|
-
"count": len(unused_eips),
|
236
|
-
"description": f"{len(unused_eips)} unused Elastic IPs",
|
237
|
-
"potential_savings": eip_savings,
|
238
|
-
"priority": "high",
|
239
|
-
}
|
240
|
-
)
|
241
|
-
|
242
|
-
# Count untagged resources (simplified check)
|
243
|
-
untagged_count = 0
|
244
|
-
for instance in stopped_instances:
|
245
|
-
if not instance["tags"]:
|
246
|
-
untagged_count += 1
|
247
|
-
for volume in unused_volumes:
|
248
|
-
if not volume.get("Tags"):
|
249
|
-
untagged_count += 1
|
250
|
-
|
251
|
-
region_data["untagged_resources"] = untagged_count
|
252
|
-
|
253
|
-
except Exception as e:
|
254
|
-
console.print(f"ā ļø Error auditing {region}: {e}", style="yellow")
|
255
|
-
|
256
|
-
return region_data
|
257
|
-
|
258
|
-
def _get_budget_alerts(self, session: boto3.Session) -> int:
|
259
|
-
"""Get budget alert count"""
|
260
|
-
try:
|
261
|
-
budgets = session.client("budgets")
|
262
|
-
|
263
|
-
# Get account ID for budgets API
|
264
|
-
sts = session.client("sts")
|
265
|
-
account_id = sts.get_caller_identity()["Account"]
|
266
|
-
|
267
|
-
response = budgets.describe_budgets(AccountId=account_id)
|
268
|
-
return len(response.get("Budgets", []))
|
269
|
-
|
270
|
-
except Exception:
|
271
|
-
return 0 # Budgets API might not be accessible
|
272
|
-
|
273
|
-
def generate_audit_report(
|
274
|
-
self, profiles: Optional[List[str]] = None, regions: Optional[List[str]] = None
|
275
|
-
) -> Dict[str, Any]:
|
276
|
-
"""Generate comprehensive audit report for specified profiles"""
|
277
|
-
|
278
|
-
if not profiles:
|
279
|
-
profiles = self.get_aws_profiles()
|
280
|
-
if not profiles:
|
281
|
-
console.print("ā No AWS profiles found", style="red")
|
282
|
-
return {}
|
283
|
-
|
284
|
-
audit_results = {
|
285
|
-
"report_metadata": {
|
286
|
-
"generated_at": datetime.now().isoformat(),
|
287
|
-
"profiles_analyzed": len(profiles),
|
288
|
-
"regions_analyzed": len(regions) if regions else 3,
|
289
|
-
"report_type": "comprehensive_audit",
|
290
|
-
},
|
291
|
-
"profile_data": {},
|
292
|
-
"summary": {
|
293
|
-
"total_untagged_resources": 0,
|
294
|
-
"total_stopped_instances": 0,
|
295
|
-
"total_unused_volumes": 0,
|
296
|
-
"total_unused_eips": 0,
|
297
|
-
"total_budget_alerts": 0,
|
298
|
-
"total_potential_savings": 0.0,
|
299
|
-
"optimization_opportunities": [],
|
300
|
-
},
|
301
|
-
}
|
302
|
-
|
303
|
-
with Progress(
|
304
|
-
SpinnerColumn(), TextColumn("[progress.description]{task.description}"), console=console
|
305
|
-
) as progress:
|
306
|
-
for profile in profiles:
|
307
|
-
task = progress.add_task(f"Auditing profile {profile}...", total=None)
|
308
|
-
|
309
|
-
profile_data = self.get_resource_audit_data(profile, regions)
|
310
|
-
audit_results["profile_data"][profile] = profile_data
|
311
|
-
|
312
|
-
# Aggregate summary data
|
313
|
-
summary = audit_results["summary"]
|
314
|
-
summary["total_untagged_resources"] += profile_data["untagged_resources"]
|
315
|
-
summary["total_stopped_instances"] += profile_data["stopped_instances"]
|
316
|
-
summary["total_unused_volumes"] += profile_data["unused_volumes"]
|
317
|
-
summary["total_unused_eips"] += profile_data["unused_eips"]
|
318
|
-
summary["total_budget_alerts"] += profile_data["budget_alerts"]
|
319
|
-
summary["total_potential_savings"] += profile_data["total_potential_savings"]
|
320
|
-
summary["optimization_opportunities"].extend(profile_data["cost_optimization_opportunities"])
|
321
|
-
|
322
|
-
progress.remove_task(task)
|
323
|
-
|
324
|
-
return audit_results
|
325
|
-
|
326
|
-
def display_audit_report(self, audit_results: Dict[str, Any]):
|
327
|
-
"""Display audit report with enhanced Rich formatting"""
|
328
|
-
|
329
|
-
summary = audit_results["summary"]
|
330
|
-
profile_data = audit_results["profile_data"]
|
331
|
-
|
332
|
-
# Report header
|
333
|
-
header_panel = Panel.fit(
|
334
|
-
f"[bold bright_cyan]š¢ AWS FinOps Comprehensive Audit Report[/bold bright_cyan]\n\n"
|
335
|
-
f"š Profiles Analyzed: [yellow]{len(profile_data)}[/yellow]\n"
|
336
|
-
f"š Generated: [green]{audit_results['report_metadata']['generated_at'][:19]}[/green]\n"
|
337
|
-
f"š° Total Savings Potential: [bold green]${summary['total_potential_savings']:.2f}/month[/bold green]",
|
338
|
-
title="Audit Report",
|
339
|
-
style="bright_cyan",
|
340
|
-
)
|
341
|
-
console.print(header_panel)
|
342
|
-
|
343
|
-
# Summary table
|
344
|
-
summary_table = Table(title="š Executive Summary", box=box.ASCII_DOUBLE_HEAD, style="bright_cyan")
|
345
|
-
summary_table.add_column("Metric", style="cyan", width=25)
|
346
|
-
summary_table.add_column("Count", style="yellow", width=10)
|
347
|
-
summary_table.add_column("Impact", style="green", width=20)
|
348
|
-
|
349
|
-
summary_table.add_row("Untagged Resources", str(summary["total_untagged_resources"]), "Compliance Risk")
|
350
|
-
summary_table.add_row(
|
351
|
-
"Stopped Instances",
|
352
|
-
str(summary["total_stopped_instances"]),
|
353
|
-
f"${summary['total_stopped_instances'] * 50:.0f}/month potential",
|
354
|
-
)
|
355
|
-
summary_table.add_row("Unused Volumes", str(summary["total_unused_volumes"]), "Storage waste")
|
356
|
-
summary_table.add_row(
|
357
|
-
"Unused EIPs", str(summary["total_unused_eips"]), f"${summary['total_unused_eips'] * 3.65:.0f}/month waste"
|
358
|
-
)
|
359
|
-
summary_table.add_row("Budget Alerts", str(summary["total_budget_alerts"]), "Monitoring coverage")
|
360
|
-
|
361
|
-
console.print(summary_table)
|
362
|
-
|
363
|
-
# Profile-specific table
|
364
|
-
profile_table = Table(
|
365
|
-
title="š„ Profile-Specific Analysis", show_lines=True, box=box.ASCII_DOUBLE_HEAD, style="bright_cyan"
|
366
|
-
)
|
367
|
-
|
368
|
-
profile_table.add_column("Profile", justify="center", width=20)
|
369
|
-
profile_table.add_column("Account ID", justify="center", width=15)
|
370
|
-
profile_table.add_column("Untagged", width=10)
|
371
|
-
profile_table.add_column("Stopped EC2", width=12)
|
372
|
-
profile_table.add_column("Unused Vol", width=12)
|
373
|
-
profile_table.add_column("Unused EIP", width=12)
|
374
|
-
profile_table.add_column("Savings", width=12)
|
375
|
-
|
376
|
-
for profile, data in profile_data.items():
|
377
|
-
account_info = data["account_info"]
|
378
|
-
profile_table.add_row(
|
379
|
-
profile,
|
380
|
-
account_info["account_id"] if account_info["status"] == "active" else "ERROR",
|
381
|
-
str(data["untagged_resources"]),
|
382
|
-
str(data["stopped_instances"]),
|
383
|
-
str(data["unused_volumes"]),
|
384
|
-
str(data["unused_eips"]),
|
385
|
-
f"${data['total_potential_savings']:.0f}",
|
386
|
-
)
|
387
|
-
|
388
|
-
console.print(profile_table)
|
389
|
-
|
390
|
-
# Top optimization opportunities
|
391
|
-
if summary["optimization_opportunities"]:
|
392
|
-
console.print("\n[bold blue]šÆ Top Optimization Opportunities[/bold blue]")
|
393
|
-
|
394
|
-
# Sort by potential savings
|
395
|
-
sorted_opportunities = sorted(
|
396
|
-
summary["optimization_opportunities"], key=lambda x: x.get("potential_savings", 0), reverse=True
|
397
|
-
)
|
398
|
-
|
399
|
-
for i, opp in enumerate(sorted_opportunities[:10], 1): # Top 10
|
400
|
-
priority_color = {"high": "red", "medium": "yellow", "low": "green"}
|
401
|
-
color = priority_color.get(opp.get("priority", "low"), "white")
|
402
|
-
|
403
|
-
console.print(
|
404
|
-
f"{i:2d}. [bold {color}]{opp['description']}[/bold {color}] "
|
405
|
-
f"([green]${opp.get('potential_savings', 0):.0f}/month[/green])"
|
406
|
-
)
|
407
|
-
|
408
|
-
def export_audit_report(
|
409
|
-
self, audit_results: Dict[str, Any], formats: List[str] = ["json", "csv"]
|
410
|
-
) -> Dict[str, str]:
|
411
|
-
"""Export audit report in multiple formats"""
|
412
|
-
|
413
|
-
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
414
|
-
export_files = {}
|
415
|
-
|
416
|
-
# JSON export
|
417
|
-
if "json" in formats:
|
418
|
-
json_file = self.export_dir / f"finops_audit_report_{timestamp}.json"
|
419
|
-
with open(json_file, "w") as f:
|
420
|
-
json.dump(audit_results, f, indent=2, default=str)
|
421
|
-
export_files["json"] = str(json_file)
|
422
|
-
|
423
|
-
# CSV export
|
424
|
-
if "csv" in formats:
|
425
|
-
csv_file = self.export_dir / f"finops_audit_summary_{timestamp}.csv"
|
426
|
-
with open(csv_file, "w", newline="") as f:
|
427
|
-
writer = csv.writer(f)
|
428
|
-
|
429
|
-
# Header
|
430
|
-
writer.writerow(
|
431
|
-
[
|
432
|
-
"Profile",
|
433
|
-
"Account_ID",
|
434
|
-
"Untagged_Resources",
|
435
|
-
"Stopped_Instances",
|
436
|
-
"Unused_Volumes",
|
437
|
-
"Unused_EIPs",
|
438
|
-
"Budget_Alerts",
|
439
|
-
"Potential_Savings_Monthly",
|
440
|
-
]
|
441
|
-
)
|
442
|
-
|
443
|
-
# Data rows
|
444
|
-
for profile, data in audit_results["profile_data"].items():
|
445
|
-
writer.writerow(
|
446
|
-
[
|
447
|
-
profile,
|
448
|
-
data["account_info"]["account_id"],
|
449
|
-
data["untagged_resources"],
|
450
|
-
data["stopped_instances"],
|
451
|
-
data["unused_volumes"],
|
452
|
-
data["unused_eips"],
|
453
|
-
data["budget_alerts"],
|
454
|
-
f"${data['total_potential_savings']:.2f}",
|
455
|
-
]
|
456
|
-
)
|
457
|
-
|
458
|
-
export_files["csv"] = str(csv_file)
|
459
|
-
|
460
|
-
return export_files
|
461
|
-
|
462
|
-
def run_comprehensive_audit(
|
463
|
-
self,
|
464
|
-
profiles: Optional[List[str]] = None,
|
465
|
-
regions: Optional[List[str]] = None,
|
466
|
-
export_formats: List[str] = ["json", "csv"],
|
467
|
-
display_report: bool = True,
|
468
|
-
) -> Dict[str, Any]:
|
469
|
-
"""Run comprehensive FinOps audit with reporting and export"""
|
470
|
-
|
471
|
-
console.print("[bold bright_cyan]š Starting Enhanced FinOps Audit...[/bold bright_cyan]")
|
472
|
-
|
473
|
-
# Generate audit data
|
474
|
-
audit_results = self.generate_audit_report(profiles, regions)
|
475
|
-
|
476
|
-
if not audit_results:
|
477
|
-
console.print("ā No audit data generated", style="red")
|
478
|
-
return {}
|
479
|
-
|
480
|
-
# Display report
|
481
|
-
if display_report:
|
482
|
-
self.display_audit_report(audit_results)
|
483
|
-
|
484
|
-
# Export results
|
485
|
-
if export_formats:
|
486
|
-
console.print(f"\nš Exporting report in formats: {', '.join(export_formats)}")
|
487
|
-
export_files = self.export_audit_report(audit_results, export_formats)
|
488
|
-
|
489
|
-
console.print("ā
Export completed:")
|
490
|
-
for format_type, file_path in export_files.items():
|
491
|
-
console.print(f" š {format_type.upper()}: {file_path}")
|
492
|
-
|
493
|
-
# Summary of potential savings
|
494
|
-
total_savings = audit_results["summary"]["total_potential_savings"]
|
495
|
-
if total_savings > 0:
|
496
|
-
annual_savings = total_savings * 12
|
497
|
-
console.print(f"\nš° [bold green]Total Optimization Potential:[/bold green]")
|
498
|
-
console.print(f" Monthly: [yellow]${total_savings:.2f}[/yellow]")
|
499
|
-
console.print(f" Annual: [green]${annual_savings:.2f}[/green]")
|
500
|
-
|
501
|
-
return audit_results
|
502
|
-
|
503
|
-
|
504
|
-
# CLI integration functions
|
505
|
-
def enhanced_audit_cli(
|
506
|
-
profiles: Optional[str] = None,
|
507
|
-
regions: Optional[str] = None,
|
508
|
-
export_formats: str = "json,csv",
|
509
|
-
output_dir: Optional[str] = None,
|
510
|
-
) -> None:
|
511
|
-
"""CLI command for enhanced FinOps audit"""
|
512
|
-
|
513
|
-
profile_list = profiles.split(",") if profiles else None
|
514
|
-
region_list = regions.split(",") if regions else None
|
515
|
-
format_list = export_formats.split(",") if export_formats else ["json"]
|
516
|
-
|
517
|
-
dashboard = EnhancedFinOpsDashboard()
|
518
|
-
|
519
|
-
if output_dir:
|
520
|
-
dashboard.export_dir = Path(output_dir)
|
521
|
-
dashboard.export_dir.mkdir(parents=True, exist_ok=True)
|
522
|
-
|
523
|
-
audit_results = dashboard.run_comprehensive_audit(
|
524
|
-
profiles=profile_list, regions=region_list, export_formats=format_list, display_report=True
|
525
|
-
)
|
526
|
-
|
527
|
-
return audit_results
|