From 8cfa3d75ddcb8f18a9a550305caba70fb926e2ec Mon Sep 17 00:00:00 2001
From: Thirumalesh Aaraveti <97395760+athiruma@users.noreply.github.com>
Date: Mon, 15 Apr 2024 13:55:07 +0530
Subject: [PATCH] Added the unit_price for EIP, Nat (#751)

---
 .../common/clouds/aws/price/price.py          | 22 +++++++++++++++
 .../clouds/aws/price/resources_pricing.py     | 28 ++++++++++++++++++-
 .../policy/aws/cleanup/unused_nat_gateway.py  |  4 ++-
 cloud_governance/policy/aws/ip_unattached.py  |  2 ++
 .../helpers/abstract_policy_operations.py     | 14 ++++++++++
 .../helpers/aws/aws_policy_operations.py      |  2 ++
 6 files changed, 70 insertions(+), 2 deletions(-)

diff --git a/cloud_governance/common/clouds/aws/price/price.py b/cloud_governance/common/clouds/aws/price/price.py
index 76ed2d48..0510015a 100644
--- a/cloud_governance/common/clouds/aws/price/price.py
+++ b/cloud_governance/common/clouds/aws/price/price.py
@@ -6,6 +6,7 @@
 import json
 from pkg_resources import resource_filename
 
+from cloud_governance.common.logger.init_logger import logger
 # Search product filter
 from cloud_governance.main.environment_variables import environment_variables
 
@@ -123,3 +124,24 @@ def get_ec2_price(self, resource: str, item_data: dict):
                 if item_data['VolumeType'] == 'sc1':
                     ebs_monthly_cost = self.get_ebs_cost(volume_type='sc1', region=self.region) * item_data['Size']
             return round(ebs_monthly_cost, 3)
+
+    def get_service_pricing(self, service_code: str, filter_list: list) -> float:
+        """
+        This method returns the price of the product by service
+        :param service_code:
+        :type service_code:
+        :param filter_list:
+        :type filter_list:
+        :return:
+        :rtype:
+        """
+        try:
+            data = self.__client.get_products(ServiceCode=service_code, Filters=filter_list)
+            od = json.loads(data['PriceList'][0])['terms']['OnDemand']
+            id1 = list(od)[0]
+            id2 = list(od[id1]['priceDimensions'])[0]
+            return float(od[id1]['priceDimensions'][id2]['pricePerUnit']['USD'])
+        except Exception as err:
+            print(err)
+            logger.error(err)
+            return 0
diff --git a/cloud_governance/common/clouds/aws/price/resources_pricing.py b/cloud_governance/common/clouds/aws/price/resources_pricing.py
index 4534a32c..b13da57d 100644
--- a/cloud_governance/common/clouds/aws/price/resources_pricing.py
+++ b/cloud_governance/common/clouds/aws/price/resources_pricing.py
@@ -1,5 +1,6 @@
 
 from cloud_governance.common.clouds.aws.price.price import AWSPrice
+from cloud_governance.common.utils.configs import DEFAULT_ROUND_DIGITS
 from cloud_governance.main.environment_variables import environment_variables
 
 
@@ -21,7 +22,8 @@ def ec2_instance_type_cost(self, instance_type: str, hours: float):
         This method returns the cost of ec2 instance types cost
         @return:
         """
-        cost = float(self._aws_pricing.get_price(instance=instance_type, os='Linux', region=self._aws_pricing.get_region_name(self.region)))
+        cost = float(self._aws_pricing.get_price(instance=instance_type, os='Linux',
+                                                 region=self._aws_pricing.get_region_name(self.region)))
         return cost * hours
 
     def get_ebs_cost(self, volume_size: int, volume_type: str, hours: float):
@@ -45,3 +47,27 @@ def get_const_prices(self, resource_type: str, hours: int):
         if resource_type == 'eip':
             return self.IP_HOURLY_COST * hours
 
+    def get_eip_unit_price(self):
+        """
+        This method returns the ElasticIp Price
+        :return:
+        :rtype:
+        """
+        return self.IP_HOURLY_COST
+
+    def get_nat_gateway_unit_price(self, region_name: str):
+        """
+        This method returns the unit price of NatGateway
+        :param region_name:
+        :type region_name:
+        :return:
+        :rtype:
+        """
+        service_code = 'AmazonEC2'
+        filter_dict = [
+            {"Field": "productFamily", "Value": "NAT Gateway", "Type": "TERM_MATCH"},
+            {"Field": "regionCode", "Value": region_name, "Type": "TERM_MATCH"},
+            {"Field": "groupDescription", "Value": "Hourly charge for NAT Gateways", "Type": "TERM_MATCH"},
+        ]
+        unit_price = self._aws_pricing.get_service_pricing(service_code, filter_dict)
+        return round(unit_price, DEFAULT_ROUND_DIGITS)
diff --git a/cloud_governance/policy/aws/cleanup/unused_nat_gateway.py b/cloud_governance/policy/aws/cleanup/unused_nat_gateway.py
index 26d47de8..f51bbcb8 100644
--- a/cloud_governance/policy/aws/cleanup/unused_nat_gateway.py
+++ b/cloud_governance/policy/aws/cleanup/unused_nat_gateway.py
@@ -58,6 +58,7 @@ def run_policy_operations(self):
         :return:
         :rtype:
         """
+        unit_price = self._resource_pricing.get_nat_gateway_unit_price(region_name=self._region)
         unused_nat_gateways = []
         nat_gateways = self._ec2_operations.get_nat_gateways()
         for nat_gateway in nat_gateways:
@@ -83,7 +84,8 @@ def run_policy_operations(self):
                                                         resource_action=self.RESOURCE_ACTION,
                                                         cloud_name=self._cloud_name,
                                                         resource_type='NatGateway',
-                                                        resource_state=nat_gateway.get('State')
+                                                        resource_state=nat_gateway.get('State'),
+                                                        unit_price=unit_price
                                                         if not cleanup_result else "Deleted")
                     unused_nat_gateways.append(resource_data)
             if not cleanup_result:
diff --git a/cloud_governance/policy/aws/ip_unattached.py b/cloud_governance/policy/aws/ip_unattached.py
index 87d01370..9c884de9 100644
--- a/cloud_governance/policy/aws/ip_unattached.py
+++ b/cloud_governance/policy/aws/ip_unattached.py
@@ -18,6 +18,7 @@ def run_policy_operations(self):
         :return:
         :rtype:
         """
+        unit_price = self._resource_pricing.get_eip_unit_price()
         addresses = self._ec2_operations.get_elastic_ips()
         active_cluster_ids = self._get_active_cluster_ids()
         unattached_addresses = []
@@ -43,6 +44,7 @@ def run_policy_operations(self):
                                                         resource_action=self.RESOURCE_ACTION,
                                                         cloud_name=self._cloud_name,
                                                         resource_type='PublicIPv4',
+                                                        unit_price=unit_price,
                                                         resource_state='disassociated' if not cleanup_result else "Deleted"
                                                         )
                     unattached_addresses.append(resource_data)
diff --git a/cloud_governance/policy/helpers/abstract_policy_operations.py b/cloud_governance/policy/helpers/abstract_policy_operations.py
index 1b27a208..cb92bcca 100644
--- a/cloud_governance/policy/helpers/abstract_policy_operations.py
+++ b/cloud_governance/policy/helpers/abstract_policy_operations.py
@@ -172,6 +172,18 @@ def _get_all_volumes(self):
         """
         raise NotImplementedError("This method not yet implemented")
 
+    def __current_savings_year(self, unit_price: float):
+        """
+        This method returns the savings this year
+        :param unit_price:
+        :type unit_price:
+        :return:
+        :rtype:
+        """
+        year_end_date = datetime.utcnow().date().replace(month=12, day=31)
+        total_days = (year_end_date - datetime.utcnow().date()).days + 1
+        return total_days * unit_price
+
     # ES Schema format
 
     def _get_es_schema(self, resource_id: str, user: str, skip_policy: str, cleanup_days: int, dry_run: str,
@@ -193,6 +205,8 @@ def _get_es_schema(self, resource_id: str, user: str, skip_policy: str, cleanup_
             f'Resource{resource_action}': cleanup_result,
             'PublicCloud': cloud_name,
             'ExpireDays': self._days_to_take_action,
+            'UnitPrice': kwargs.get('unit_price', 0),
+            'TotalYearlySavings': self.__current_savings_year(kwargs.get('unit_price', 0)),
             'index-id': f'{current_date}-{cloud_name.lower()}-{self.account.lower()}-{region.lower()}-{resource_id}-{resource_state.lower()}'
         }
         if kwargs.get('launch_time'):
diff --git a/cloud_governance/policy/helpers/aws/aws_policy_operations.py b/cloud_governance/policy/helpers/aws/aws_policy_operations.py
index c6ed507b..70bafadf 100644
--- a/cloud_governance/policy/helpers/aws/aws_policy_operations.py
+++ b/cloud_governance/policy/helpers/aws/aws_policy_operations.py
@@ -3,6 +3,7 @@
 
 from cloud_governance.common.clouds.aws.cloudwatch.cloudwatch_operations import CloudWatchOperations
 from cloud_governance.common.clouds.aws.ec2.ec2_operations import EC2Operations
+from cloud_governance.common.clouds.aws.price.resources_pricing import ResourcesPricing
 from cloud_governance.common.clouds.aws.s3.s3_operations import S3Operations
 from cloud_governance.policy.helpers.abstract_policy_operations import AbstractPolicyOperations
 from cloud_governance.common.logger.init_logger import logger
@@ -18,6 +19,7 @@ def __init__(self):
         self._ec2_client = boto3.client('ec2', region_name=self._region)
         self._ec2_operations = EC2Operations(region=self._region)
         self._cloudwatch = CloudWatchOperations(region=self._region)
+        self._resource_pricing = ResourcesPricing()
         self._s3_client = boto3.client('s3')
         self._iam_client = boto3.client('iam')