azbill: Azure 利用明細・課金情報出力 CLI の紹介

## はじめに

[azbill](https://github.com/yaegashi/azbill) という Azure クラウドサービスの利用明細・課金情報を出力してくれる CLI ツールを Go 言語で作ったので紹介します。主に Enterprise Agreement (EA) で Azure を導入している組織向けで、例えば部署ごと・リソースの種類ごと・タグごとの使用量を算出するといったようなコスト分析のためのデータを出力するツールです。

azbill を使うと EA の課金アカウントや Azure サブスクリプションに含まれるすべてのリソースの使用量・課金額を日次で取得し、CSV や JSONL の形式でファイルに保存できます。

このような利用明細の取得は Azure CLI でもできると思われるかもしれませんが、現行の Azure CLI は[新しいバージョンの Consumption API](https://docs.microsoft.com/en-us/rest/api/consumption/) に対応しておらず、予約 VM の利用状況といったような情報がとれません。 azbill を使えばそのような最新のメトリクスが取得できますし、CSV やフラットな JSON といったような、気の利いた出力が可能です。

azbill は現時点で次の種類の[課金アカウント](https://docs.microsoft.com/ja-jp/azure/cost-management-billing/manage/view-all-accounts)に対応しています。

- Microsoft Online Services Program (個人のクレジットカードによる従量課金契約はこれに含まれます)
- Enterprise Agreement (企業向けの年間契約)

## インストール

azbill には [Windows・macOS・Linux 向けにビルドされたバイナリ](https://github.com/yaegashi/azbill/releases)がありますのでそれをダウンロードしご利用してください。ひとつの実行ファイルのみで動作します。

Go がインストールされているシステムであれば、次のコマンドでインストールすることもできます。

```console
$ go get -u github.com/yaegashi/azbill
```

## 使用法

多くのコマンドラインオプションや環境変数による設定があります。 `azbill -h` で表示されるヘルプを参照してください。

```console
$ azbill -h
Azure billing data exporter

Usage:
  azbill [command]

Available Commands:
  accounts      List billing accounts you have access to
  help          Help about any command
  invoices      List invoices
  login         Force auth-dev login
  subscriptions List subscriptions
  tenants       List tenants
  usage-details List usage details

Flags:
      --auth string         auth source [dev,env,file,cli] (env:AZBILL_AUTH, default:dev)
      --auth-dev string     auth dev store (env:AZBILL_AUTH_DEV, default:auth_dev.json)
      --auth-file string    auth file store (env:AZBILL_AUTH_FILE, default:auth_file.json)
      --client string       Azure client (env:AZURE_CLIENT_ID, default:4a034c56-da44-48ce-90db-039a408974bd)
      --config-dir string   config dir (env:AZBILL_CONFIG_DIR, default:~/.azbill)
      --format string       output format [csv,json,flatten,pretty] (env:AZBILL_FORMAT, default:csv)
  -h, --help                help for azbill
  -o, --output string       output file
  -q, --quiet               quiet
      --tenant string       Azure tenant (env:AZURE_TENANT_ID, default:common)
  -v, --version             version for azbill

Use "azbill [command] --help" for more information about a command.
```

## 認証

`azbill login` で [Azure AD のデバイス承認フロー](https://docs.microsoft.com/ja-jp/azure/active-directory/develop/v2-oauth2-device-code)によるサインインができます。 ブラウザで https://microsoft.com/devicelogin を開き、表示されたコードを入力した後にサインインしてください。

```console
$ azbill login
2020/07/06 23:05:05 To sign in, use a web browser to open the page https://microsoft.com/devicelogin and enter the code HL94ZL7Y8 to authenticate.
2020/07/06 23:05:21 Saving auth-dev token in /Users/yaegashi/.azbill/auth_dev.json
```

ホームとは異なるテナントにゲストとしてサインインするには `--tenant` で指定してください。

```console
$ azbill login --tenant l0wdev.onmicrosoft.com
```

認証トークンは `~/.azbill/auth_dev.json` ファイルに保存されます。 `--auth-dev` オプションにより保存するファイルを指定できます。相対パスを指定した場合は `--config-dir` で指定したディレクトリ (デフォルト `~/.azbill`) 内に保存されます。

また Azure Blob Storage の SAS つき URL を指定することもできます。これは CI/CD サービスの環境で azbill を実行する場合に便利です。 SAS つき URL は秘匿すべき情報ですので、 `--auth-dev` ではなく環境変数 `AZBILL_AUTH_DEV` で設定するようにしてください。

[Azure SDK for Go がサポートする他の認証方式](https://docs.microsoft.com/ja-jp/azure/developer/go/azure-sdk-authorization)を使うこともできます。 Azure CLI で認証済みの場合は `--auth cli` を使うのが簡単です。 Azure CLI で生成したサービスプリンシパルの JSON ファイルを使う場合は `--auth file` を使い、ファイルの場所を `--auth-file` で指定してください。

## 出力フォーマット

出力フォーマットは `--format` で指定します。基本は `csv` または `json` の 2 種類です。カンマ区切りで `flatten` または `pretty` を追加指定できます。

|`--format`|説明|
|---|---|
|`csv`|1レコードが 1 行のCSV 形式で出力します (デフォルト)|
|`json`|1 レコードが 1 JSON オブジェクトで、それらを改行で区切った [JSONL 形式](http://jsonlines.org/)で出力します|
|`json,flatten`|入れ子でない JSON オブジェクトを出力します。<br/>`flatten` でも同じ意味です。|
|`json,pretty`|インデントつきの JSON オブジェクトを出力します。<br/>`pretty` でも同じ意味です。|
|`json,flatten,pretty`|入れ子でないインデントつきの JSON オブジェクトを出力します。<br/>`flatten,pretty` でも同じ意味です。|

### JSON 形式

次は `--format json,pretty` を指定した時の 1 レコードの出力です。 JSON 形式の場合、丸め誤差を避けるために数字は文字列で表現されていることに注意してください。

```json
{
  "properties": {
    "billingPeriodStartDate": "2020-05-03T00:00:00Z",
    "billingPeriodEndDate": "2020-06-02T00:00:00Z",
    "billingProfileId": "/subscriptions/XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX",
    "billingProfileName": "Pay-As-You-Go",
    "subscriptionId": "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX",
    "subscriptionName": "Pay-As-You-Go",
    "date": "2020-06-01T00:00:00Z",
    "meterId": "d54686f0-77ff-43f3-9e7c-2099030d32a7",
    "meterDetails": {
      "meterName": "Public Queries",
      "meterCategory": "Azure DNS",
      "meterSubCategory": "",
      "unitOfMeasure": "10000000"
    },
    "quantity": "0.000997",
    "effectivePrice": "55.3250345781466",
    "cost": "0.055159059474412",
    "unitPrice": "0",
    "billingCurrency": "JPY",
    "resourceLocation": "Unknown",
    "consumedService": "Microsoft.Network",
    "resourceId": "/subscriptions/XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX/resourceGroups/dns/providers/Microsoft.Network/dnszones/l0w.dev",
    "resourceName": "l0w.dev",
    "resourceGroup": "dns",
    "offerId": "MS-AZR-0003P",
    "isAzureCreditEligible": false,
    "publisherType": "Azure",
    "chargeType": "Usage",
    "frequency": "UsageBased"
  },
  "id": "/subscriptions/XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX/providers/Microsoft.Billing/billingPeriods/202006/providers/Microsoft.Consumption/usageDetails/de545724-82e8-f099-bc3b-53c5f0ae1711",
  "name": "de545724-82e8-f099-bc3b-53c5f0ae1711",
  "type": "Microsoft.Consumption/usageDetails",
  "tags": null,
  "kind": "legacy"
}
```


### フラット形式

次は `--format json,flatten,pretty` を指定した時の出力です。フラットな JSON オブジェクトとなります。

```json
{
  "id": "/subscriptions/XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX/providers/Microsoft.Billing/billingPeriods/202006/providers/Microsoft.Consumption/usageDetails/de545724-82e8-f099-bc3b-53c5f0ae1711",
  "kind": "legacy",
  "name": "de545724-82e8-f099-bc3b-53c5f0ae1711",
  "properties.billingCurrency": "JPY",
  "properties.billingPeriodEndDate": "2020-06-02T00:00:00Z",
  "properties.billingPeriodStartDate": "2020-05-03T00:00:00Z",
  "properties.billingProfileId": "/subscriptions/XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX",
  "properties.billingProfileName": "Pay-As-You-Go",
  "properties.chargeType": "Usage",
  "properties.consumedService": "Microsoft.Network",
  "properties.cost": "0.055159059474412",
  "properties.date": "2020-06-01T00:00:00Z",
  "properties.effectivePrice": "55.3250345781466",
  "properties.frequency": "UsageBased",
  "properties.isAzureCreditEligible": false,
  "properties.meterDetails.meterCategory": "Azure DNS",
  "properties.meterDetails.meterName": "Public Queries",
  "properties.meterDetails.meterSubCategory": "",
  "properties.meterDetails.unitOfMeasure": "10000000",
  "properties.meterId": "d54686f0-77ff-43f3-9e7c-2099030d32a7",
  "properties.offerId": "MS-AZR-0003P",
  "properties.publisherType": "Azure",
  "properties.quantity": "0.000997",
  "properties.resourceGroup": "dns",
  "properties.resourceId": "/subscriptions/XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX/resourceGroups/dns/providers/Microsoft.Network/dnszones/l0w.dev",
  "properties.resourceLocation": "Unknown",
  "properties.resourceName": "l0w.dev",
  "properties.subscriptionId": "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX",
  "properties.subscriptionName": "Pay-As-You-Go",
  "properties.unitPrice": "0",
  "tags": null,
  "type": "Microsoft.Consumption/usageDetails"
}
```

### CSV 形式

次は `--format csv` を指定した時の出力です。

```csv
id,kind,name,properties.accountName,properties.accountOwnerId,properties.additionalInfo,properties.billingAccountId,properties.billingAccountName,properties.billingCurrency,properties.billingPeriodEndDate,properties.billingPeriodStartDate,properties.billingProfileId,properties.billingProfileName,properties.chargeType,properties.consumedService,properties.cost,properties.costCenter,properties.date,properties.effectivePrice,properties.frequency,properties.invoiceSection,properties.isAzureCreditEligible,properties.meterDetails.meterCategory,properties.meterDetails.meterName,properties.meterDetails.meterSubCategory,properties.meterDetails.serviceFamily,properties.meterDetails.unitOfMeasure,properties.meterId,properties.offerId,properties.partNumber,properties.planName,properties.product,properties.productOrderId,properties.productOrderName,properties.publisherName,properties.publisherType,properties.quantity,properties.reservationId,properties.reservationName,properties.resourceGroup,properties.resourceId,properties.resourceLocation,properties.resourceName,properties.serviceInfo1,properties.serviceInfo2,properties.subscriptionId,properties.subscriptionName,properties.term,properties.unitPrice,tags,type
/subscriptions/XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX/providers/Microsoft.Billing/billingPeriods/202006/providers/Microsoft.Consumption/usageDetails/de545724-82e8-f099-bc3b-53c5f0ae1711,legacy,de545724-82e8-f099-bc3b-53c5f0ae1711,,,,,,JPY,2020-06-02T00:00:00Z,2020-05-03T00:00:00Z,/subscriptions/XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX,Pay-As-You-Go,Usage,Microsoft.Network,0.055159059474412,,2020-06-01T00:00:00Z,55.3250345781466,UsageBased,,false,Azure DNS,Public Queries,,,10000000,d54686f0-77ff-43f3-9e7c-2099030d32a7,MS-AZR-0003P,,,,,,,Azure,0.000997,,,dns,/subscriptions/XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX/resourceGroups/dns/providers/Microsoft.Network/dnszones/l0w.dev,Unknown,l0w.dev,,,XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX,Pay-As-You-Go,,0,{},Microsoft.Consumption/usageDetails
```

1 行目の CSV ヘッダは `json,flatten` 指定時のキーと同じものです。 JSON 形式では省かれたキーもすべて含まれています。エンコーディングは BOM つきの UTF-8 で、改行コードは CRLF であるため Microsoft Excel で直接開くことができます。

## 例

アクセス可能な[課金アカウント](https://docs.microsoft.com/en-us/azure/cost-management-billing/manage/view-all-accounts)を CSV 形式で出力します。

```console
$ azbill accounts
```

アクセス可能な Azure AD テナントを JSONL 形式で出力します。

```console
$ azbill tenants --format json
```

アクセス可能なサブスクリプションをフラットな JSONL 形式で出力します。

```console
$ azbill subscriptions --format flatten
```

Microsoft Online Service Program アカウント: サブスクリプション `XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX` の 2020 年 6 月の利用明細をフラットなインデントした JSONL 形式で出力します。

```console
$ azbill usage-details --format flatten,pretty -S XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX --start 2020-06-01 --end 2020-06-30 -o usage.jsonl
2020/07/06 23:43:59 Loading auth-dev token in /Users/yaegashi/.azbill/auth_dev.json
2020/07/06 23:43:59 Requesting with consumption.UsageDetailsClient
2020/07/06 23:43:59    scope: "subscriptions/XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX"
2020/07/06 23:43:59   filter: "properties/usageStart eq '2020-06-01' and properties/usageEnd eq '2020-06-30'"
2020/07/06 23:44:11 Writing to "usage.jsonl" in json,flatten,pretty
.                                                       138 records
2020/07/06 23:44:11 Done 138 records in 16.893702ms, 8168.724653 records/sec
```

Enterprise Agreement アカウント: 課金アカウント `XXXXXXXX` のビリング期間 `202006` の利用明細を CSV 形式で出力します。

```console
$ azbill usage-details --format csv -A XXXXXXXX -P 202006 -o usage.csv
2020/07/06 23:45:15 Loading auth-dev token in /Users/yaegashi/.azbill/auth_dev.json
2020/07/06 23:45:15 Requesting with consumption.UsageDetailsClient
2020/07/06 23:45:15    scope: "providers/Microsoft.Billing/billingAccounts/XXXXXXXX/providers/Microsoft.Billing/billingPeriods/202006"
2020/07/06 23:45:15   filter: ""
2020/07/06 23:45:34 Writing to "usage.csv" in csv
..................................................     5000 records
..................................................    10000 records
..................................................    15000 records
....................                                  17001 records
2020/07/06 23:46:38 Done 17001 records in 1m4.325743007s, 264.295431 records/sec
```

## azbill の開発

azbill の CLI コマンド階層およびフラグ処理には [cobra-comder](https://github.com/yaegashi/cobra-cmder) を使用しています ([cobra-cmder で Go の CLI を簡単に作る ](https://qiita.com/yaegashi/items/9acc1d22bcc247542a4e) を参照)。

azbill では [Azure SDK for Go](https://docs.microsoft.com/en-us/rest/api/azure/) を使用していますが、現行の SDK ではまだ対応していない新しいバージョンの [Azure REST API](https://docs.microsoft.com/ja-jp/rest/api/azure/) を使用しています。 [バージョン 2019-11-01 の Consumption 利用明細 API](https://docs.microsoft.com/ja-jp/rest/api/consumption/usagedetails/list) などです。

このため [AutoRest ツール](https://github.com/Azure/autorest)により、[新しいバージョンの OpenAPI 仕様書](https://github.com/Azure/azure-rest-api-specs)から SDK をコード生成しています。生成されたコードは [azure-sdk-for-go ディレクトリ](https://github.com/yaegashi/azbill/tree/master/azure-sdk-for-go)にあります。
生成を自動的に行うシェルスクリプトが [gen.sh](https://github.com/yaegashi/azbill/blob/master/gen.sh) です。

このような AutoRest による Azure SDK コードの生成については、別の記事で詳しく説明したいと思います。