From a48c788b91ddefad06f657bf0b097b98b9678f0f Mon Sep 17 00:00:00 2001 From: badra001 Date: Fri, 30 Jan 2026 09:31:20 -0500 Subject: [PATCH] accept date --- .../python-tools/billing/aws_monthly_cost.py | 58 +++++++++++-------- 1 file changed, 33 insertions(+), 25 deletions(-) diff --git a/local-app/python-tools/billing/aws_monthly_cost.py b/local-app/python-tools/billing/aws_monthly_cost.py index 0a344f38..722d56fb 100755 --- a/local-app/python-tools/billing/aws_monthly_cost.py +++ b/local-app/python-tools/billing/aws_monthly_cost.py @@ -6,15 +6,28 @@ import argparse import sys from botocore.exceptions import ClientError, ProfileNotFound +from dateutil.parser import parse -__version__ = "1.0.1" +__version__ = "1.0.2" -def get_monthly_range(target_date=None): - """Calculates start of current month and start of next month (exclusive).""" - if target_date is None: +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: + 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: @@ -22,18 +35,15 @@ def get_monthly_range(target_date=None): return start.strftime('%Y-%m-%d'), end.strftime('%Y-%m-%d') -def fetch_aws_costs(profile, region): +def fetch_aws_costs(profile, region, date_arg): try: - # Initialize session with user parameters session = boto3.Session(profile_name=profile, region_name=region) - - # Cost Explorer API is globally managed via us-east-1 endpoint client = session.client('ce', region_name='us-east-1') - start_date, end_date = get_monthly_range() + 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"Region: {region}") print(f"Period: {start_date} to {end_date}\n") response = client.get_cost_and_usage( @@ -48,21 +58,20 @@ def fetch_aws_costs(profile, region): for group in response['ResultsByTime'][0]['Groups']: service_name = group['Keys'][0] - metrics = group['Metrics'] + m = group['Metrics'] - usage_qty = float(metrics['UsageQuantity']['Amount']) - usage_unit = metrics['UsageQuantity']['Unit'] - blended = float(metrics['BlendedCost']['Amount']) - unblended = float(metrics['UnblendedCost']['Amount']) - amortized = float(metrics['AmortizedCost']['Amount']) + 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"{usage_qty:.2f}", usage_unit, f"{blended:.2f}", f"{unblended:.2f}", f"{amortized:.2f}"]) + rows.append([service_name, f"{qty:.2f}", unit, f"{blended:.2f}", f"{unblended:.2f}", f"{amortized:.2f}"]) grand_totals['blended'] += blended grand_totals['unblended'] += unblended grand_totals['amortized'] += amortized - # Sort by Total Cost descending rows.sort(key=lambda x: float(x[5]), reverse=True) filename = f"aws_cost_{start_date[:7]}.csv" @@ -78,17 +87,16 @@ def fetch_aws_costs(profile, region): print(f"✅ Success! Report saved as: {filename}") except ProfileNotFound: - print(f"❌ Error: The profile '{profile}' was not found in your AWS config.") + print(f"❌ Error: Profile '{profile}' not found.") except ClientError as e: print(f"❌ AWS Error: {e}") - except Exception as e: - print(f"❌ Unexpected Error: {e}") if __name__ == "__main__": - parser = argparse.ArgumentParser(description="Generate AWS Monthly Cost CSV") - parser.add_argument('--profile', type=str, help="AWS CLI profile to use", default=None) - parser.add_argument('--region', type=str, help="AWS region (default: us-east-1)", default='us-east-1') + 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.add_argument('--version', action='version', version=f'%(prog)s {__version__}') args = parser.parse_args() - fetch_aws_costs(args.profile, args.region) + fetch_aws_costs(args.profile, args.region, args.date)