From e07e4e1af858dde2e1350d6ef1d9b91c2d638497 Mon Sep 17 00:00:00 2001 From: Long Zhang Date: Thu, 3 Oct 2024 11:53:44 +0200 Subject: [PATCH 1/2] minor refactoring and also fix a hardcoded key Signed-off-by: Long Zhang --- app/exporter.py | 14 +++++++------- exporter_config.yaml | 3 ++- main.py | 28 ++++++++++++++++++++-------- package.json | 2 +- 4 files changed, 30 insertions(+), 17 deletions(-) diff --git a/app/exporter.py b/app/exporter.py index 8c6051b..9622175 100644 --- a/app/exporter.py +++ b/app/exporter.py @@ -20,7 +20,7 @@ def __init__( aws_assumed_role_name, group_by, targets, - metrics_type, + metric_type, ): self.polling_interval_seconds = polling_interval_seconds self.metric_name = metric_name @@ -29,7 +29,7 @@ def __init__( self.aws_access_secret = aws_access_secret self.aws_assumed_role_name = aws_assumed_role_name self.group_by = group_by - self.metrics_type = metrics_type # Store metrics + self.metric_type = metric_type # Store metrics # we have verified that there is at least one target self.labels = set(targets[0].keys()) # for now we only support exporting one type of cost (Usage) @@ -41,7 +41,7 @@ def __init__( def run_metrics(self): # every time we clear up all the existing labels before setting new ones - self.aws_daily_cost_usd .clear() + self.aws_daily_cost_usd.clear() for aws_account in self.targets: logging.info("querying cost data for aws account %s" % aws_account["Publisher"]) @@ -70,7 +70,7 @@ def get_aws_account_session_default(self, account_id): ) assumed_role_object = sts_client.assume_role( - RoleArn=f"arn:aws:iam::{account_id}:role/{self.aws_assumed_role_name}",RoleSessionName="AssumeRoleSession1" + RoleArn=f"arn:aws:iam::{account_id}:role/{self.aws_assumed_role_name}", RoleSessionName="AssumeRoleSession1" ) return assumed_role_object["Credentials"] @@ -87,7 +87,7 @@ def query_aws_cost_explorer(self, aws_client, group_by): TimePeriod={"Start": start_date.strftime("%Y-%m-%d"), "End": end_date.strftime("%Y-%m-%d")}, Filter={"Dimensions": {"Key": "RECORD_TYPE", "Values": ["Usage"]}}, Granularity="DAILY", - Metrics=self.metrics_type, # Use dynamic metrics + Metrics=[self.metric_type], # Use dynamic metrics GroupBy=groups, ) @@ -110,12 +110,12 @@ def fetch(self, aws_account): for result in cost_response: if not self.group_by["enabled"]: - cost = float(result["Total"]["UnblendedCost"]["Amount"]) + cost = float(result["Total"][self.metric_type]["Amount"]) self.aws_daily_cost_usd.labels(**aws_account, ChargeType="Usage").set(cost) else: merged_minor_cost = 0 for item in result["Groups"]: - cost = float(item["Metrics"]["UnblendedCost"]["Amount"]) + cost = float(item["Metrics"][self.metric_type]["Amount"]) group_key_values = dict() for i in range(len(self.group_by["groups"])): diff --git a/exporter_config.yaml b/exporter_config.yaml index fe04e89..b3871f1 100644 --- a/exporter_config.yaml +++ b/exporter_config.yaml @@ -26,7 +26,7 @@ metrics: tag_value: other # Allowed values for metric type are AmortizedCost, BlendedCost, NetAmortizedCost, NetUnblendedCost, NormalizedUsageAmount, UnblendedCost, and UsageQuantity metrics_type: AmortizedCost - + - metric_name: aws_daily_cost_usd # change the metric name if needed group_by: # Cost data can be groupped using up to two different groups: DIMENSION, TAG, COST_CATEGORY. @@ -44,6 +44,7 @@ metrics: enabled: false threshold: 10 tag_value: other + # Allowed values for metric type are AmortizedCost, BlendedCost, NetAmortizedCost, NetUnblendedCost, NormalizedUsageAmount, UnblendedCost, and UsageQuantity metrics_type: AmortizedCost target_aws_accounts: diff --git a/main.py b/main.py index 8e57daf..576a94b 100644 --- a/main.py +++ b/main.py @@ -5,6 +5,7 @@ import argparse import logging import os +import signal import sys import time @@ -14,6 +15,10 @@ from app.exporter import MetricExporter +def handle_sigint(sig, frame): + exit() + + def get_configs(): parser = argparse.ArgumentParser(description="AWS Cost Exporter, exposing AWS cost data as Prometheus metrics.") parser.add_argument( @@ -34,9 +39,14 @@ def get_configs(): def validate_configs(config): - valid_metrics_types = [ - "AmortizedCost", "BlendedCost", "NetAmortizedCost", "NetUnblendedCost", - "NormalizedUsageAmount", "UnblendedCost", "UsageQuantity" + valid_metric_types = [ + "AmortizedCost", + "BlendedCost", + "NetAmortizedCost", + "NetUnblendedCost", + "NormalizedUsageAmount", + "UnblendedCost", + "UsageQuantity", ] if len(config["target_aws_accounts"]) == 0: @@ -71,9 +81,11 @@ def validate_configs(config): logging.error("Some label names in group_by are the same as AWS account labels!") sys.exit(1) - # Validate metrics_type - if config_metric["metrics_type"] not in valid_metrics_types: - logging.error(f"Invalid metrics_type: {config_metric['metrics_type']}. It must be one of {', '.join(valid_metrics_types)}.") + # Validate metric_type + if config_metric["metric_type"] not in valid_metric_types: + logging.error( + f"Invalid metric_type: {config_metric['metric_type']}. It must be one of {', '.join(valid_metric_types)}." + ) sys.exit(1) for i in range(1, len(config["target_aws_accounts"])): @@ -99,7 +111,6 @@ def validate_configs(config): sys.exit(1) - def main(config): start_http_server(config["exporter_port"]) while True: @@ -112,7 +123,7 @@ def main(config): targets=config["target_aws_accounts"], metric_name=config_metric["metric_name"], group_by=config_metric["group_by"], - metrics_type=[config_metric["metrics_type"]], + metric_type=config_metric["metric_type"], ) app_metrics.run_metrics() time.sleep(config["polling_interval_seconds"]) @@ -121,6 +132,7 @@ def main(config): if __name__ == "__main__": logger_format = "%(asctime)-15s %(levelname)-8s %(message)s" logging.basicConfig(level=logging.INFO, format=logger_format) + signal.signal(signal.SIGINT, handle_sigint) config = get_configs() validate_configs(config) main(config) diff --git a/package.json b/package.json index 5d8ed1f..5190c94 100644 --- a/package.json +++ b/package.json @@ -1,4 +1,4 @@ { "name": "aws-cost-exporter", - "version": "v1.0.4" + "version": "v1.0.6" } From d4da42d7c2c7ec48b56eaf1ab5ca8aa7fb8f547c Mon Sep 17 00:00:00 2001 From: Long Zhang Date: Thu, 3 Oct 2024 11:59:59 +0200 Subject: [PATCH 2/2] doc: update versions in the docs Signed-off-by: Long Zhang --- README.md | 2 +- deployment/k8s-with-eks/deployment.yaml | 2 +- deployment/k8s/deployment.yaml | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index fe11306..44ccf25 100644 --- a/README.md +++ b/README.md @@ -83,7 +83,7 @@ Modify the `exporter_config.yaml` file first, then use one of the following meth ### Docker ``` -docker run --rm -v ./exporter_config.yaml:/app/exporter_config.yaml -p 9090:9090 -e AWS_ACCESS_KEY=${AWS_ACCESS_KEY} -e AWS_ACCESS_SECRET=${AWS_ACCESS_SECRET} opensourceelectrolux/aws-cost-exporter:v1.0.1 +docker run --rm -v ./exporter_config.yaml:/app/exporter_config.yaml -p 9090:9090 -e AWS_ACCESS_KEY=${AWS_ACCESS_KEY} -e AWS_ACCESS_SECRET=${AWS_ACCESS_SECRET} opensourceelectrolux/aws-cost-exporter:v1.0.6 ``` ### Kubernetes diff --git a/deployment/k8s-with-eks/deployment.yaml b/deployment/k8s-with-eks/deployment.yaml index 5d91ee0..7160980 100644 --- a/deployment/k8s-with-eks/deployment.yaml +++ b/deployment/k8s-with-eks/deployment.yaml @@ -18,7 +18,7 @@ spec: serviceAccountName: aws-cost-exporter containers: - name: aws-cost-exporter - image: "opensourceelectrolux/aws-cost-exporter:v1.0.1" + image: "opensourceelectrolux/aws-cost-exporter:v1.0.6" command: [ "python", "main.py", "-c", "/exporter_config.yaml" ] imagePullPolicy: Always ports: diff --git a/deployment/k8s/deployment.yaml b/deployment/k8s/deployment.yaml index bc2e021..3709ff2 100644 --- a/deployment/k8s/deployment.yaml +++ b/deployment/k8s/deployment.yaml @@ -6,7 +6,7 @@ metadata: app.kubernetes.io/name: aws-cost-exporter app.kubernetes.io/part-of: finops app.kubernetes.io/component: backend - app.kubernetes.io/version: "v1.0.1" + app.kubernetes.io/version: "v1.0.6" app.kubernetes.io/language: python spec: replicas: 1 @@ -24,7 +24,7 @@ spec: spec: containers: - name: aws-cost-exporter - image: "opensourceelectrolux/aws-cost-exporter:v1.0.1" + image: "opensourceelectrolux/aws-cost-exporter:v1.0.6" command: [ "python", "main.py", "-c", "/exporter_config.yaml" ] imagePullPolicy: Always env: