概要
Azureの請求アカウントIDとその配下の顧客ID(テナントID)から請求データを取得するPythonプログラムです。
本プログラムは CostManagement REST API を利用して作成されています。
CostManagementモジュールを利用したプログラムでいろいろ問題があったので、改めて REST API を利用しています。
- リクエストの返り値が1000件を超える場合でも問題なく動作します
- 利用する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型で渡せます、、、、