#!/usr/bin/env python3 """FinOps Cloud Cost Scanner - Detect Waste & Recommend Savings.""" import json, subprocess from datetime import datetime from pathlib import Path from typing import Dict, List class FinOpsScanner: def __init__(self, output_dir: str = "./finops-reports"): self.output_dir = Path(output_dir) self.output_dir.mkdir(parents=True, exist_ok=True) self.findings: List[Dict] = [] self.total_potential_savings = 0.0 def _aws_cmd(self, service: str, cmd: str) -> str: full_cmd = f"aws {service} {cmd} --output json --region us-east-1" try: result = subprocess.run(full_cmd, shell=True, capture_output=True, text=True, timeout=60) return result.stdout if result.returncode == 0 else "[]" except Exception: return "[]" def scan_unused_volumes(self) -> List[Dict]: output = self._aws_cmd("ec2", "describe-volumes --filters Name=status,Values=available") try: volumes = json.loads(output).get("Volumes", []) for v in volumes: size_gb = v["Size"] savings = size_gb * 0.08 self.findings.append({ "id": f"FINOPS-001-{v['VolumeId']}", "type": "unused-ebs", "size_gb": size_gb, "monthly_savings": round(savings, 2), "action": f"Delete or snapshot {v['VolumeId']}", }) self.total_potential_savings += savings return self.findings except json.JSONDecodeError: return [] def scan_idle_eips(self) -> List[Dict]: output = self._aws_cmd("ec2", "describe-addresses") try: for addr in json.loads(output).get("Addresses", []): if "AssociationId" not in addr: self.findings.append({ "id": f"FINOPS-003-{addr['PublicIp']}", "type": "unattached-eip", "monthly_savings": 3.60, "action": f"Release EIP {addr['PublicIp']}", }) self.total_potential_savings += 3.60 return self.findings except json.JSONDecodeError: return [] def generate_report(self) -> str: report = { "timestamp": datetime.utcnow().isoformat() + "Z", "total_potential_monthly_savings": round(self.total_potential_savings, 2), "findings_count": len(self.findings), "findings": self.findings, "recommendations": [ {"priority": 1, "action": "Schedule non-prod off-hours", "savings": "65% non-prod"}, {"priority": 2, "action": "Purchase RIs for EKS nodes", "savings": "30-40% compute"}, {"priority": 3, "action": "Enable S3 Intelligent-Tiering", "savings": "40-60% storage"}, {"priority": 4, "action": "Use SPOT for ML training", "savings": "70-90% GPU"}, {"priority": 5, "action": "Rightsize K8s workloads", "savings": "30-50% cluster"}, ], } report_path = self.output_dir / f"finops-{datetime.now().strftime('%Y%m%d')}.json" with open(report_path, "w") as f: json.dump(report, f, indent=2, default=str) print(f" {'='*60}") print(f"FINOPS SCAN: {len(self.findings)} findings | ${self.total_potential_savings:,.2f}/mo potential savings") print(f"{'='*60}") return str(report_path) def run_full_scan(self): self.scan_unused_volumes() self.scan_idle_eips() return self.generate_report() if __name__ == "__main__": FinOpsScanner().run_full_scan()