diff --git a/cost-estimator/estimate-costs.py b/cost-estimator/estimate-costs.py index ea6f14ad..4e71173c 100755 --- a/cost-estimator/estimate-costs.py +++ b/cost-estimator/estimate-costs.py @@ -1,5 +1,6 @@ #!/usr/bin/env python3 +import pprint import subprocess import sys import os.path @@ -14,6 +15,7 @@ import argparse import math import re +import pyairtable class AWSResource: @@ -468,7 +470,8 @@ def test_authenticated(service): class Account: - def __init__(self, orgs, space_names): + def __init__(self, name, orgs, space_names): + self.name = name self.org_names = orgs self.space_names = space_names if space_names else [] @@ -486,6 +489,7 @@ def __init__(self, orgs, space_names): self.input_workbook_file = None self.output_workbook_file = None self.reporter = Reporter() + self.airtable = Airtable() def report_orgs(self): for org_name in self.org_names: @@ -536,6 +540,86 @@ def report_summary(self, reporter): for key, value in sorted(self.es_total_instance_plans.items()): reporter.log(f" {key}: {value}") + def upload_airtable_estimate(self, airtable, account_name): + headline = f"Cost estimate for org: {self.org_names}" + if len(self.space_names) > 0: + headline += f", spaces: {self.space_names}" + + price_lookup= airtable.price_dict() + print(f"Fetched {len(price_lookup)} Price Records from AirTable") + + summary_record_id = airtable.summary_table.create( + {"Source": "cost-estimator", "Account": account_name, "Description": headline} + )['id'] + + airtable_batch = [] + + # memory-quota + if len(self.space_names) == 0: + at_memory_quota = self.memory_quota / 1024 + else: + # If we are producing an estimate for a set of space(s), then the memory usage is + # whatever memory is used by those spaces. Round up the memory usage to the nearest + # integer because we charge for memory on a per GB basis, so any partial use of a GB + # should be treated as a whole GB for accounting purposes + at_memory_quota = math.ceil( + self.memory_usage / 1024 + ) + airtable_batch.append({ + "Resource Summary": [summary_record_id], + "Resource Pricelist": [price_lookup['memory-quota']], + "Units": at_memory_quota + }) + # rds-storage + if self.rds_total_allocation > 0: + airtable_batch.append({ + "Resource Summary": [summary_record_id], + "Resource Pricelist": [price_lookup['rds-storage']], + "Units": math.ceil(self.rds_total_allocation) + }) + # s3-storage + if self.s3_total_storage > 0: + airtable_batch.append({ + "Resource Summary": [summary_record_id], + "Resource Pricelist": [price_lookup['s3-storage']], + "Units": math.ceil(self.s3_total_storage / ( 1024 * 1024 * 1024 )) + }) + + # es-storage + if self.es_total_volume_storage > 0: + airtable_batch.append({ + "Resource Summary": [summary_record_id], + "Resource Pricelist": [price_lookup['es-storage']], + "Units": math.ceil(self.es_total_volume_storage) + }) + # rds-plans + if len(self.rds_total_instance_plans) > 0: + for plan, units in sorted(self.rds_total_instance_plans.items()): + airtable_batch.append({ + "Resource Summary": [summary_record_id], + "Resource Pricelist": [price_lookup[plan]], + "Units": units + }) + # redis-plans + if len(self.redis_total_instance_plans) > 0: + for plan, units in sorted(self.redis_total_instance_plans.items()): + airtable_batch.append({ + "Resource Summary": [summary_record_id], + "Resource Pricelist": [price_lookup[plan]], + "Units": units + }) + # es-plans + if len(self.es_total_instance_plans) > 0: + for plan, units in sorted(self.es_total_instance_plans.items()): + airtable_batch.append({ + "Resource Summary": [summary_record_id], + "Resource Pricelist": [price_lookup[plan]], + "Units": units + }) + result = airtable.resource_table.batch_create(airtable_batch) + print(f"Created {len(result)} AirTable records") + + def generate_cost_estimate(self, reporter): estimate_map = { # Usage @@ -631,6 +715,24 @@ def generate_cost_estimate(self, reporter): workbook.save(filename=self.output_workbook_file) print(f"Saved cost estimate to: {self.output_workbook_file}") +class Airtable: + QUOTE_BASE_ID='appprdUNzFPO9avLd' + RESOURCE_ENTRY_TABLE_ID='tbl5fX9qzivwgMnD2' + RESOURCE_PRICING_TABLE_ID='tblr5evoP1pKGcUxl' + RESOURCE_SUMMARY_TABLE_ID='tblCj7JYRsYlqtruU' + + def __init__(self): + self.api = pyairtable.Api(os.environ['AIRTABLE_API_KEY']) + self.summary_table = self.api.table(self.QUOTE_BASE_ID, self.RESOURCE_SUMMARY_TABLE_ID) + self.pricing_table = self.api.table(self.QUOTE_BASE_ID, self.RESOURCE_PRICING_TABLE_ID) + self.resource_table = self.api.table(self.QUOTE_BASE_ID, self.RESOURCE_ENTRY_TABLE_ID) + + def price_dict(self): + price_dict = {} + for rp in self.pricing_table.all(fields=['Name']): + price_dict[rp['fields']['Name']] = (rp['id']) + return price_dict + class Reporter: """ @@ -725,10 +827,11 @@ def main(): print("space names only allowed when specifying a single organization") exit(1) + account_name = org_names[-1] if args.account_name: - output_file = args.account_name + ".xlsx" - else: - output_file = org_names[-1] + ".xlsx" + account_name = args.account_name + output_file = account_name + ".xlsx" + if not os.path.exists(cost_estimate_file): print( @@ -744,7 +847,7 @@ def main(): print(f"Info: Authenticated, starting...", file=sys.stderr) - acct = Account(orgs=org_names, space_names=space_names) + acct = Account(name=account_name,orgs=org_names, space_names=space_names) acct.report_orgs() if len(org_names) > 1: acct.report_summary(acct.reporter) @@ -752,6 +855,7 @@ def main(): acct.input_workbook_file = cost_estimate_file acct.output_workbook_file = output_file acct.generate_cost_estimate(acct.reporter) + acct.upload_airtable_estimate(acct.airtable, acct.name) if __name__ == "__main__":