0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

Pythonで Azureの請求アカウントIDから請求データを取得してみました - REST API編

Last updated at Posted at 2022-10-18

概要

Azureの請求アカウントIDとその配下の顧客ID(テナントID)から請求データを取得するPythonプログラムです。
本プログラムは CostManagement REST API を利用して作成されています。
CostManagementモジュールを利用したプログラムでいろいろ問題があったので、改めて REST API を利用しています。

  1. リクエストの返り値が1000件を超える場合でも問題なく動作します
  2. 利用するAPIバージョンを指定することができる

実行環境

macOS Monterey 12.3.1
python 3.8.12

事前準備

この記事 の「事前準備」を完了していること

実行プログラム

REST_QueryTimePeriodByCustomerId.py
import json
import os
import sys
import requests
import argparse
from datetime import *
import time
import logging
import pandas as pd
import numpy as np


# 請求管理者権限を付与されたサービスプリンシパル情報
TENANT_ID = os.environ['APC_TENANT_ID']
CLIENT_ID = os.environ['APC_CLIENT_ID']
CLIENT_KEY = os.environ['APC_CLIENT_KEY']

# 請求アカウントID
BILLING_ACCOUNT_ID = os.environ['BILLING_ACCOUNT_ID']

# REST API 情報
API_URL = 'https://management.azure.com'
API_VERSION = '/providers/Microsoft.CostManagement/query?api-version=2021-10-01'

# 顧客ID
CUSTOMER_ID = 'xxxxxxxx-1111-2222-3333-xxxxxxxxxxxx'
# CUSTOMER_ID = 'yyyyyyyy-4444-5555-6666-yyyyyyyyyyyy'
# CUSTOMER_ID = 'zzzzzzzz-7777-8888-9999-zzzzzzzzzzzz'


# Azureアクセスのためのアクセストークンの取得
def get_azure_access_token() :

    # access_token を取得するためのヘッダ情報
    headers = {
        'Accept': 'application/json',
        'Content-Type': 'application/x-www-form-urlencoded'
    }

    payload = {
        'client_id': CLIENT_ID,
        'scope': API_URL + '/.default',
        'grant_type': 'client_credentials',
        'client_secret': CLIENT_KEY
    }

    # access_token を取得するためのURLを生成
    TokenGet_URL = "https://login.microsoftonline.com/" + \
        TENANT_ID + "/oauth2/v2.0/token"
    # print(TokenGet_URL, "\n")

    # 実行
    response = requests.get(
        TokenGet_URL,
        headers=headers,
        data=payload
    )
    # requrest処理のクローズ
    response.close

    # その結果を取得
    jsonObj = json.loads(response.text)
    try :
        return jsonObj["access_token"]
    except KeyError as err :
        print(err, "Not get\n", jsonObj)
        return ''


# クエリの定義
def define_query_usage_payload(fm_tp, to_tp) :

    # 顧客のサブスクリプション毎の今月の請求料金を取得
    payload = {
        "type": "Usage",
        "timeframe": "Custom",
        "timePeriod": {"from": fm_tp, "to": to_tp},
        "dataset": {
            "granularity": "None",
            "aggregation": {
                "totalCost": {"name": "PreTaxCost", "function": "Sum"},
                "totalQuantity": {"name": "UsageQuantity", "function": "Sum"}
            },
            "grouping": [
                # {"type": "Dimension", "name": "ResellerMPNId"},
                # {"type": "Dimension", "name": "InvoiceId"},
                # {"type": "Dimension", "name": "ResourceGroup"},
                {"type": "Dimension", "name": "ServiceFamily"},
                {"type": "Dimension", "name": "ServiceName"}
                # {"type": "Dimension", "name": "MeterCategory"},
                # {"type": "Dimension", "name": "Meter"},
                # # {"type": "Dimension", "name": "MeterSubcategory"},
                # {"type": "Dimension", "name": "Product"},
                # {"type": "Dimension", "name": "UnitOfMeasure"},
                # {"type": "Dimension", "name": "ProductOrderName"}
             ]
        }
    }
    return payload


# URLの定義
def define_scope_url() :

    # SCOPEの定義
    SCOPE = f'/providers/Microsoft.Billing/billingAccounts/{BILLING_ACCOUNT_ID}/customers/{CUSTOMER_ID}'
    # URLを生成
    Post_URL = f'{API_URL}{SCOPE}{API_VERSION}'
    return Post_URL


# CustomerIDから顧客情報の取得
def get_customers_cost_by_billingid(access_token, fm_tp, to_tp) :

    # 取得するためのヘッダ情報
    headers = {
        'Accept': 'application/json',
        'Content-Type': 'application/json; charset=utf-8',
        'Authorization': 'Bearer %s' % access_token
    }

    # 取得するためのURL情報
    Post_URL = define_scope_url()

    # クエリの定義
    payload = define_query_usage_payload(fm_tp, to_tp)

    # 1000件毎(仕様)のデータ取得    
    df_cost = pd.DataFrame()
    while True :
        # 実行
        print("\n" ,Post_URL)
        response = requests.post(
            Post_URL,
            headers=headers,
            data=json.dumps(payload)
        )
        # requrest処理のクローズ
        response.close

        try :
            print(response)
            response.raise_for_status()
            next_link, df_rows = df_customer_cost(response.json())

            df_cost = pd.concat([df_cost, df_rows])

            if next_link is None :
                break
            elif len(next_link) > 0 :
                Post_URL = next_link
            else :
                break
        except requests.exceptions.RequestException as e :
            logging.exception("get_customers_cost_by_billingid request failed. message=(%s)\n", e.response.text)
            sys.exit()
    
    # データの再インデックス化
    df_cost = df_cost.reset_index(drop=True)
    return df_cost


def df_customer_cost(res_json):

    # カラム名の取得してから、値を取得
    df_colums = pd.DataFrame(res_json['properties']['columns'])
    df_rows = pd.DataFrame(res_json['properties']['rows'], columns=df_colums['name'])

    # 1000件を超える場合のURL(next_link)の取得
    next_link = res_json['properties']['nextLink']
    # print("\n next_link = " ,next_link)
    return next_link, df_rows


# 取得した全データの画面出力
def data_to_display(df_cost):

    # 取得した全データの表示
    df_cost = df_cost.drop(columns=['Currency'])
    print(df_cost.to_markdown(floatfmt=",.0f"))
    # print(df_cost.to_markdown())
    print(df_cost.dtypes)


# 取得した全データの保存
def data_to_csv(df_cost):

    # 取得した全データのcsvファイルへ保存
    now = datetime.now()
    filename = './data_customers_cost_by_billingid/' + now.strftime('%Y%m%d%H%M%S') + '.csv'
    print(filename)
    df_cost.to_csv(filename, index=False, encoding="utf-8")


# データ取得のための From-To 日付確認
def check_time_period(fm_tp, to_tp):
    try :
        fm_datetime = datetime.strptime(fm_tp, '%Y-%m-%d')
        to_datetime = datetime.strptime(to_tp, '%Y-%m-%d')

        if to_datetime < fm_datetime :
            print("Error ---> 開始日 ≦ 終了日 ")
            return False
        else :
            return True
    except ValueError as e :
        print("ValueError = ", e)
        return False


if __name__ == '__main__':
    parser = argparse.ArgumentParser(description='ある顧客の請求情報の取得')
    parser.add_argument('-f', '--fm', type=str, required=True, help='データを取得する開始日(yyyy-mm-dd)')
    parser.add_argument('-t', '--to', type=str, required=True, help='データを取得する終了日(yyyy-mm-dd)')
    parser.add_argument('--mode', type=str, default='lo', help='lo(取得データを画面出力) / csv(取得データをcsv保存)')
    args = parser.parse_args()

    if check_time_period(args.fm, args.to) == False:
        print("NG", "\n")
        sys.exit()

    start = time.time()
    access_token = get_azure_access_token()

    if len(access_token) > 0 :
        print("\n取得アクセストークン : ")
        print(access_token, "\n")
        df_cost = get_customers_cost_by_billingid(access_token, args.fm, args.to)

        if args.mode == 'csv':
            # 取得した全データを保存
            data_to_csv(df_cost)
        else :
            # 取得した全データを出力
            data_to_display(df_cost)

        # 合計請求金額と件数の表示
        total = df_cost['PreTaxCost'].sum()
        count = len(df_cost)
        print("\n", "合計請求金額 : {:,.0f}".format(total), "\tレコード件数 : {:,.0f}".format(count))

    generate_time = time.time() - start
    print("\n取得時間:{0}".format(generate_time) + " [sec]\n")

プログラムのHELP表示

$ python REST_QueryTimePeriodByCustomerId.py -h                         
usage: REST_QueryTimePeriodByCustomerId.py [-h] -f FM -t TO [--mode MODE]

ある顧客の請求情報の取得

optional arguments:
  -h, --help      show this help message and exit
  -f FM, --fm FM  データを取得する開始日(yyyy-mm-dd)
  -t TO, --to TO  データを取得する終了日(yyyy-mm-dd)
  --mode MODE     lo(取得データを画面出力) / csv(取得データをcsv保存)

プログラムの実行(画面表示)

$ python REST_QueryTimePeriodByCustomerId.py -f 2022-09-01 -t 2022-09-15

取得アクセストークン : 
eyJ0eo9i ・・・省略・・・ nvymk5JA 

 https://management.azure.com/providers/Microsoft.Billing/billingAccounts/[BillingAccountId]/customers/[CustomerId]/providers/Microsoft.CostManagement/query?api-version=2021-10-01
<Response [200]>
|    |   PreTaxCost |   UsageQuantity | ServiceFamily             | ServiceName                            |
|---:|-------------:|----------------:|:--------------------------|:---------------------------------------|
|  0 |           18 |               0 | AI + Machine Learning     | Cognitive Services                     |
|  1 |            0 |               0 | Analytics                 | Azure Data Factory v2                  |
|  2 |        5,725 |             407 | Compute                   | Azure App Service                      |
|  3 |            0 |             644 | Compute                   | Functions                              |
|  4 |       26,069 |           1,923 | Compute                   | Virtual Machines                       |
|  5 |           82 |              12 | Compute                   | Virtual Machines Licenses              |
|  6 |          971 |           1,340 | Containers                | Container Registry                     |
|  7 |           32 |              32 | Databases                 | Azure Cosmos DB                        |
|  8 |       14,250 |           1,095 | Databases                 | Azure Database for PostgreSQL          |
|  9 |          675 |             259 | Databases                 | Redis Cache                            |
| 10 |          316 |              15 | Databases                 | SQL Database                           |
| 11 |          440 |           1,844 | Integration               | Logic Apps                             |
| 12 |            0 |               0 | Integration               | Service Bus                            |
| 13 |          653 |             236 | Internet of Things        | Event Hubs                             |
| 14 |          245 |              51 | Management and Governance | Azure Monitor                          |
| 15 |            0 |               1 | Management and Governance | Log Analytics                          |
| 16 |       14,566 |             458 | Networking                | Azure Bastion                          |
| 17 |          606 |              10 | Networking                | Azure DNS                              |
| 18 |            0 |               4 | Networking                | Bandwidth                              |
| 19 |        3,015 |           1,480 | Networking                | Load Balancer                          |
| 20 |            0 |              31 | Networking                | Network Watcher                        |
| 21 |       11,065 |          16,494 | Networking                | Virtual Network                        |
| 22 |        7,947 |             360 | Networking                | VPN Gateway                            |
| 23 |        6,272 |             360 | Security                  | Azure Active Directory Domain Services |
| 24 |            0 |               0 | Security                  | Key Vault                              |
| 25 |          379 |              11 | Storage                   | Backup                                 |
| 26 |       56,861 |           2,438 | Storage                   | Storage                                |
name
PreTaxCost       float64
UsageQuantity    float64
ServiceFamily     object
ServiceName       object
dtype: object

 合計請求金額 : 150,187 	レコード件数 : 27

取得時間:10.419126749038696 [sec]

付録

  • クエリ定義の grouping で指定できる項目一覧
'ResourceGroup','ResourceGroupName','ResourceType','ResourceId','ResourceLocation',
'SubscriptionId','SubscriptionName',
'MeterCategory','MeterSubcategory','Meter','ServiceFamily','UnitOfMeasure','PartNumber',
'BillingAccountName','BillingProfileId','BillingProfileName',
'InvoiceSection','InvoiceSectionId','InvoiceSectionName',
'Product','ResourceGuid','ChargeType','ServiceName','ProductOrderId','ProductOrderName',
'PublisherType','ReservationId','ReservationName','Frequency','InvoiceId','PricingModel',
'CostAllocationRuleName','MarkupRuleName','BillingMonth','Provider','BenefitId','BenefitName',
'CustomerTenantId','CustomerTenantDomainName','ResellerMPNId','PartnerEarnedCreditApplied',
'CustomerName','PartnerName',''.
  • クエリ定義の aggregation で指定できる項目一覧
'UsageQuantity','PreTaxCost','Cost','CostUSD','PreTaxCostUSD'

まとめ

これで CostManagement REST API を利用して Azureの請求データを取得することができました。あとは要望に合わせてクエリ・リクエスト文を編集してください。
なお、CostManagement REST API を利用する場合の time_period には String型で渡せます、、、、

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?