diff --git a/local-app/python-tools/billing/aws_monthly_cost.py b/local-app/python-tools/billing/aws_monthly_cost.py index 722d56fb..62ded8a7 100755 --- a/local-app/python-tools/billing/aws_monthly_cost.py +++ b/local-app/python-tools/billing/aws_monthly_cost.py @@ -8,26 +8,19 @@ from botocore.exceptions import ClientError, ProfileNotFound from dateutil.parser import parse -__version__ = "1.0.2" +__version__ = "1.0.3" def get_monthly_range(date_str=None): - """ - Parses a date string and returns the first day of that month - and the first day of the following month. - """ if date_str: try: target_date = parse(date_str).date() - except ValueError: + except (ValueError, TypeError): print(f"❌ Error: Could not parse date '{date_str}'. Use YYYY-MM or YYYY-MM-DD.") sys.exit(1) else: target_date = datetime.date.today() - # Start of the month for the provided date start = target_date.replace(day=1) - - # Start of the next month if start.month == 12: end = start.replace(year=start.year + 1, month=1) else: @@ -43,8 +36,7 @@ def fetch_aws_costs(profile, region, date_arg): start_date, end_date = get_monthly_range(date_arg) print(f"--- AWS Monthly Cost Summary v{__version__} ---") - print(f"Profile: {profile or 'default'}") - print(f"Period: {start_date} to {end_date}\n") + print(f"Period: {start_date} to {end_date}\n") response = client.get_cost_and_usage( TimePeriod={'Start': start_date, 'End': end_date}, @@ -61,42 +53,38 @@ def fetch_aws_costs(profile, region, date_arg): m = group['Metrics'] qty = float(m['UsageQuantity']['Amount']) - unit = m['UsageQuantity']['Unit'] blended = float(m['BlendedCost']['Amount']) unblended = float(m['UnblendedCost']['Amount']) amortized = float(m['AmortizedCost']['Amount']) - rows.append([service_name, f"{qty:.2f}", unit, f"{blended:.2f}", f"{unblended:.2f}", f"{amortized:.2f}"]) + rows.append([service_name, f"{qty:.2f}", f"{blended:.2f}", f"{unblended:.2f}", f"{amortized:.2f}"]) grand_totals['blended'] += blended grand_totals['unblended'] += unblended grand_totals['amortized'] += amortized - rows.sort(key=lambda x: float(x[5]), reverse=True) + rows.sort(key=lambda x: float(x[4]), reverse=True) filename = f"aws_cost_{start_date[:7]}.csv" with open(filename, 'w', newline='') as f: writer = csv.writer(f) - writer.writerow(['Service', 'Usage Quantity', 'Unit', 'Blended Cost', 'Unblended Cost', 'Total (Amortized)']) + writer.writerow(['Service', 'Usage Quantity', 'Blended Cost', 'Unblended Cost', 'Total (Amortized)']) writer.writerows(rows) - writer.writerow(['GRAND TOTAL', 'NA', 'NA', + writer.writerow(['GRAND TOTAL', 'NA', f"{grand_totals['blended']:.2f}", f"{grand_totals['unblended']:.2f}", f"{grand_totals['amortized']:.2f}"]) - print(f"✅ Success! Report saved as: {filename}") + print(f"✅ Report saved: {filename}") - except ProfileNotFound: - print(f"❌ Error: Profile '{profile}' not found.") - except ClientError as e: - print(f"❌ AWS Error: {e}") + except Exception as e: + print(f"❌ Error: {e}") if __name__ == "__main__": - parser = argparse.ArgumentParser(description="AWS Monthly Cost to CSV") - parser.add_argument('date', nargs='?', help="Date in YYYY-MM or YYYY-MM-DD format (default: today)") - parser.add_argument('--profile', type=str, help="AWS CLI profile") - parser.add_argument('--region', type=str, default='us-east-1', help="AWS region") + parser = argparse.ArgumentParser() + parser.add_argument('date', nargs='?', help="YYYY-MM or YYYY-MM-DD") + parser.add_argument('--profile', type=str) + parser.add_argument('--region', type=str, default='us-east-1') parser.add_argument('--version', action='version', version=f'%(prog)s {__version__}') - args = parser.parse_args() fetch_aws_costs(args.profile, args.region, args.date)