#!/usr/bin/env python3 """ tenant_detail.py — Détail des coûts par famille de service (S3, EC2, ECS, Fargate, etc.) pour UN tenant spécifique, converti en EUR par défaut. Ce script réutilise providers.py (accès AWS/Azure) et default_period() de cost_report.py : il doit rester dans le même dossier que ces deux modules. Utilisation : python tenant_detail.py --tenant analytics-platform python tenant_detail.py --tenant client-c-prod --start 2026-06-01 --end 2026-07-01 python tenant_detail.py --tenant analytics-platform --top 15 python tenant_detail.py --tenant analytics-platform --json python tenant_detail.py --tenant analytics-platform --no-convert # devise d'origine """ import argparse import json import sys from pathlib import Path sys.path.insert(0, str(Path(__file__).parent)) from providers import get_aws_cost, get_azure_cost, convert_result from cost_report import default_period def find_tenant(config: dict, tenant_name: str): """Cherche le tenant par son 'name' dans les sections aws/azure de la config. Retourne (provider, entry) ou (None, None) si introuvable.""" for entry in config.get("aws", []): if entry["name"] == tenant_name: return "aws", entry for entry in config.get("azure", []): if entry["name"] == tenant_name: return "azure", entry return None, None def main(): parser = argparse.ArgumentParser( description="Détail des coûts par famille de service pour un tenant donné" ) parser.add_argument("--tenant", required=True, help="Nom du tenant tel que défini dans config.json") parser.add_argument("--config", default="config.json", help="Chemin vers le fichier de config") parser.add_argument("--start", help="Date de début (YYYY-MM-DD), défaut: 1er du mois en cours") parser.add_argument("--end", help="Date de fin exclusive (YYYY-MM-DD), défaut: demain") parser.add_argument("--currency", default="EUR", help="Devise cible (défaut: EUR)") parser.add_argument("--no-convert", action="store_true", help="Désactive la conversion, garde la devise d'origine") parser.add_argument("--top", type=int, default=None, help="N'afficher que les N services les plus coûteux") parser.add_argument("--json", action="store_true", help="Sortie en JSON brut") args = parser.parse_args() try: with open(args.config) as f: config = json.load(f) except FileNotFoundError: print(f"Fichier de config introuvable : {args.config}", file=sys.stderr) sys.exit(1) provider, entry = find_tenant(config, args.tenant) if entry is None: available = [e["name"] for e in config.get("aws", [])] + \ [e["name"] for e in config.get("azure", [])] print(f"Tenant '{args.tenant}' introuvable dans {args.config}", file=sys.stderr) print(f"Tenants disponibles : {', '.join(available) or '(aucun)'}", file=sys.stderr) sys.exit(1) default_start, default_end = default_period() start = args.start or default_start end = args.end or default_end try: if provider == "aws": data = get_aws_cost(entry, start, end) else: data = get_azure_cost( entry["tenant_id"], entry["client_id"], entry["client_secret"], entry["subscription_id"], start, end, ) except Exception as e: print(f"Erreur lors de la récupération des coûts pour '{args.tenant}' : {e}", file=sys.stderr) sys.exit(1) target_currency = None if args.no_convert else args.currency if target_currency: data = convert_result(data, target_currency) if args.json: print(json.dumps( {"tenant": args.tenant, "provider": provider.upper(), "start": start, "end": end, **data}, indent=2, ensure_ascii=False, )) return print(f"\nDétail des coûts — {args.tenant} ({provider.upper()})") print(f"Période : {start} → {end}") if data.get("original_currency") and data["original_currency"] != data["currency"]: print(f"Total : {data['total']:.2f} {data['currency']} " f"(orig. {data['original_total']:.2f} {data['original_currency']})") else: print(f"Total : {data['total']:.2f} {data['currency']}") if data.get("conversion_error"): print(f"[Conversion échouée, montant affiché dans sa devise d'origine : {data['conversion_error']}]") services = list(data.get("by_service", {}).items()) if args.top: services = services[:args.top] print() if not services: print("Aucun coût détecté sur la période pour ce tenant.") return total = data["total"] or 1 # évite division par zéro header = f"{'Famille de service':45} {'Montant':>12} {'%':>6}" print(header) print("-" * len(header)) for service, amount in services: pct = (amount / total) * 100 print(f"{service:45} {amount:>12.2f} {pct:>5.1f}%") print("-" * len(header)) print(f"{'TOTAL':45} {data['total']:>12.2f} {'100.0':>5}% ({data['currency']})") if __name__ == "__main__": main()