(Python)AWSの請求金額を取得する

# はじめに
AWS アカウントを取得したので、請求を管理したいと思う。
予定外の出費はできる事なら避けたい。
そこで、Python を使用して AWS の Cost Explorer から請求情報を取得する。

# 環境
実行環境は以下

```
$ sw_vers
ProductName:    Mac OS X
ProductVersion: 10.15.1
BuildVersion:   19B88

$ python --version
Python 3.7.4
```

# API 仕様
- 合計請求額を表示する。
- 各サービスごとの詳細請求額を表示する。
- 請求金額取得対象期間は以下とする。
  - START ： API 実行日を含む月の１日
  - END ： API 実行日の前日
- API 実行日が1日だった場合、前月の請求額を表示する。

# API 実行日と実行月の1日を取得する
`datetime`パッケージを使用して以下のように実行日と実行月の1日を取得するメソッドを作成する。

```
from datetime import datetime, timedelta, date

# 実行月の1日を取得
def get_begin_of_month() -> str:
    return date.today().replace(day=1).isoformat()


# 実行日を取得
def get_today() -> str:
    return date.today().isoformat()
```

# 請求金額取得対象期間を取得する
前述したメソッドを用いて請求金額取得対象期間を返却するメソッドを作成する。
ただし、取得対象期間の START と END が同じ場合、 Cost Explorer の SDK に引数として設定できない。
そのため、取得対象期間の START と END が同じ場合は前月の1日から月末を返却するようにする。

```
# 請求金額取得対象期間を取得
def get_total_cost_date_range() -> (str, str):
    start_date = get_begin_of_month()
    end_date = get_today()

    # get_cost_and_usage()のstartとendに同じ日付は指定不可のため、
    # 「今日が1日」なら、「先月1日から今月1日（今日）」までの範囲にする
    if start_date == end_date:
        end_of_month = datetime.strptime(start_date, '%Y-%m-%d') + timedelta(days=-1)
        begin_of_month = end_of_month.replace(day=1)
        return begin_of_month.date().isoformat(), end_date
    return start_date, end_date
```
# 合計請求額取得を取得する
SDK を用いて取得対象期間の合計請求額取得する。

```
import boto3
client = boto3.client('ce', region_name='us-east-1')

# 合計請求額取得を取得
def get_total_billing(client) -> dict:
    (start_date, end_date) = get_total_cost_date_range()
    response = client.get_cost_and_usage(
        TimePeriod={
            'Start': start_date,
            'End': end_date
        },
        Granularity='MONTHLY',
        Metrics=[
            'AmortizedCost'
        ]
    )
    
    return {
        'start': response['ResultsByTime'][0]['TimePeriod']['Start'],
        'end': response['ResultsByTime'][0]['TimePeriod']['End'],
        'billing': response['ResultsByTime'][0]['Total']['AmortizedCost']['Amount'],
    }
```

# 各サービスの詳細請求金額を取得する
合計請求額と同様に各サービスの詳細請求金額を取得する。

```
import boto3
client = boto3.client('ce', region_name='us-east-1')

# 各サービスの詳細請求金額を取得
def get_service_billings(client) -> list:
    (start_date, end_date) = get_total_cost_date_range()

#CostExplorer.Client.get_cost_and_usage
    response = client.get_cost_and_usage(
        TimePeriod={
            'Start': start_date,
            'End': end_date
        },
        Granularity='MONTHLY',
        Metrics=[
            'AmortizedCost'
        ],
        GroupBy=[
            {
                'Type': 'DIMENSION',
                'Key': 'SERVICE'
            }
        ]
    )

    billings = []

    for item in response['ResultsByTime'][0]['Groups']:
        billings.append({
            'service_name': item['Keys'][0],
            'billing': item['Metrics']['AmortizedCost']['Amount']
        })
    return billings
```

# 作成した API を実行する。
以下、実行結果。

```
{'start': '2020-01-01', 'end': '2020-01-12', 'billing': '0.44'}
[
  {'service_name': 'AWS Cost Explorer', 'billing': '0.4'}, 
  {'service_name': 'AWS Key Management Service', 'billing': '0'}, 
  {'service_name': 'AWS Lambda', 'billing': '0'}, 
  {'service_name': 'Amazon API Gateway', 'billing': '0'}, 
  {'service_name': 'Amazon DynamoDB', 'billing': '0'}, 
  {'service_name': 'Amazon Simple Notification Service', 'billing': '0'}, 
  {'service_name': 'Amazon Simple Storage Service', 'billing': '0'}, 
  {'service_name': 'AmazonCloudWatch', 'billing': '0'}, 
  {'service_name': 'Tax', 'billing': '0.04'}
]
```

# おわりに
この結果を Slack などに表示したいメッセージに加工し、[こちら](https://qiita.com/Nidhog-tm/items/54f15b1205bfab46974c)で作成したbotAPIに組み込む。
こんな感じに表示されるようになる。
<img width="434" alt="スクリーンショット 2020-01-12 16.46.57.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/472784/c331a6cc-ac00-5e05-ff77-ad0491e0380b.png">