Python で REST API を利用してリソースグループ内のリソースのデプロイ履歴・操作情報を取得してみました


### 概要
REST API を利用して、リソースグループ内にあるデプロイ名毎にデプロイ情報・履歴を取得する Python プログラムです。
デプロイ履歴（情報）を取得するプログラムフローは以下となっています。

1. Azureリソースに REST API アクセスするためのアクセストークンの取得
2. REST API を利用したデプロイ履歴情報の取得
3. 取得した履歴情報をデプロイ名単位でローカルファイルへ保存
4. REST API を利用したデプロイ操作情報の取得
5. 取得した操作情報をデプロイ名単位でローカルファイルへ保存

### 実行環境

- macOS Monterey 12.1
- python 3.8.3
- Azure CLI 2.28.0

### 前提条件

1. Azure環境がすでに用意されていること（テナント/サブスクリプション）
2. ローカル環境に「azure cli」がインストールされていること
3. リソースグループ（rg_ituru_deploy_test）配下にvNetリソース（vnet_ituru_deploy_test）のAzure環境が構築されていること

### 実行プログラム

```python:GetDepeloymentHistory.py
import os
import time
import json
import argparse
import requests
import sys
from datetime import datetime
from azure.identity import AzureCliCredential, DefaultAzureCredential
from azure.mgmt.resource import ResourceManagementClient

# Azure Info
TENANT_ID = os.environ['AZURE_TENANT_ID']
CLIENT_ID = os.environ['AZURE_CLIENT_ID']
CLIENT_KEY = os.environ['AZURE_CLIENT_SECRET']

# Retrieve subscription ID from environment variable.
SUBSCRIPTION_ID = os.environ["ARM_SUBSCRIPTION_ID"]
API_VERSION = "2021-04-01"

# リソースの管理オブジェクトの取得
def GetResourceManagementClient():
    resource_client = ResourceManagementClient(
        # credential=DefaultAzureCredential()
        credential=AzureCliCredential(),
        subscription_id=SUBSCRIPTION_ID
    )
    return resource_client


# サブスクリプションのリソースグループ一覧の取得
def GetResourceGroup():
    # Obtain the management object for resources.
    resource_client = GetResourceManagementClient()

    # Retrieve the list of resource groups
    group_list = resource_client.resource_groups.list()

    # Show the groups in formatted output
    column_width = 50
    print("Resource Group".ljust(column_width) + "Location")
    print("-" * (column_width * 2))

    for group in list(group_list):
        print(f"{group.name:<{column_width}}{group.location}")


# デプロイ情報・履歴データをローカルファイルへ保存の取得
def DeploymentToLocalFile(item, filename):

    # 保存するファイル名の生成
    print(filename)

    # json.dump関数でファイルに書き込む
    fw = open(filename,'w')
    json.dump(item,fw,indent=2)
    fw.close()


# 指定したリソースグループ内のデプロイ名毎のデプロイ操作情報の取得
def GetDepeloymentOperation(access_token, rg_name, dp_name, now):

    # access_token を含めたヘッダ情報
    headers = {
        'Authorization': 'Bearer %s' % access_token
    }

	# 取得URL
    DeplumentOperation_URL = 'https://management.azure.com/subscriptions/' + \
                        SUBSCRIPTION_ID + '/resourcegroups/' + rg_name + \
                        '/deployments/' + dp_name + '/operations?api-version=' + API_VERSION
    # print(DeplumentOperation_URL)

    # 情報取得のGetリクエスト
    try :
        res2 = requests.get(
            DeplumentOperation_URL,
            headers=headers
        )
    except Exception as err:
        print("Something Fatal Happened")
        print (err)
        sys.exit()
        
    # requrest処理をクローズする
    res2.close

    # res2をjsonファイルに整形しユーザ情報の取得
    item = res2.json()['value']
    if len(item) > 0:
        # print(json.dumps(item, indent = 2))

        # 保存するファイル名の生成
        filename = './output/Deployment_Operation__' + rg_name + '_' + dp_name + '_' + now.strftime('%Y%m%d_%H%M%S') + '.json'

        # デプロイ履歴データをローカルファイルへ
        DeploymentToLocalFile(item, filename)


# 指定したリソースグループ内のデプロイ名毎のデプロイ履歴情報の取得
def GetDepeloymentInfo(access_token, rg_name):

    # access_token を含めたヘッダ情報
    headers = {
        'Authorization': 'Bearer %s' % access_token
    }

	# 取得URL
    DeplumentInfo_URL = 'https://management.azure.com/subscriptions/' + \
                        SUBSCRIPTION_ID + '/resourcegroups/' + rg_name + \
                        '/providers/Microsoft.Resources/deployments/?api-version=' + API_VERSION
    # print(DeplumentInfo_URL)

    # 情報取得のGetリクエスト
    try :
        res1 = requests.get(
            DeplumentInfo_URL,
            headers=headers
        )
    except Exception as err:
        print("Something Fatal Happened")
        print (err)
        sys.exit()
        
    # requrest処理をクローズする
    res1.close

    # デプロイ情報の取得有無判斷
    if len(res1.json()['value']) == 0:
        print("\n AzureCLI もしくは Terraform 等で構築されたリソースグループの場合、取得できません、、、、 \n")
        return 0 

    # res1をjsonファイルに整形しデプロイ履歴情報の取得
    for num, item in enumerate(res1.json()['value']):
        print("\n■ ■ ■ {}".format(item["name"]))
        # print(json.dumps(item, indent = 2))

        # 保存するファイル名の生成
        now = datetime.now()
        filename = './output/Deployment__' + rg_name + '_' + item["name"] + '_' + now.strftime('%Y%m%d_%H%M%S') + '.json'

        # デプロイ情報データをローカルファイルへ
        DeploymentToLocalFile(item, filename)

        # デプロイ操作データの取得
        GetDepeloymentOperation(access_token, rg_name, item["name"], now)

    return num+1


# Azureにアクセスするためのトークンの取得（Access_Token）
def get_azure_access_token() -> str:
    
    # access_token を取得するためのヘッダ情報
    headers = {
        'Accept': 'application/json',
        'Content-Type': 'application/x-www-form-urlencoded'
    }

    payload = {
        'client_id': CLIENT_ID,
        'scope': 'https://management.azure.com/.default',
        'grant_type': 'client_credentials',
        'client_secret': CLIENT_KEY
    }

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

    # アクセストークンの取得
    try :
        response = requests.get(
            TokenGet_URL,
            headers=headers,
            data=payload
        )
    except Exception as err:
        print("Something Fatal Happened")
        print (err)
        sys.exit()

    # requrest処理のクローズ
    response.close

    jsonObj = json.loads(response.text)
    return jsonObj["access_token"]


# メイン
if __name__ == '__main__':
    parser = argparse.ArgumentParser(description='引数なし：リソースグループ一覧の取得、　　引数あり：指定したリソースグループ内のリソース毎のデプロイ情報・履歴の取得')
    parser.add_argument('-g', '--rg', type=str, help='リソースグループ名')
    args = parser.parse_args()

    start = time.time()
    
    if args.rg == None :
        GetResourceGroup()
    else :
        access_token = get_azure_access_token()
        print(f"取得アクセストークン :\n{access_token}\n")
        cnt = GetDepeloymentInfo(access_token, args.rg)
        print("\n  Deplyment_name 数 : " + str(cnt))

    generate_time = time.time() - start

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

### プログラムのHELP表示
 
```
## HELPの表示
$ python GetDepeloymentHistory.py -h                                     

usage: GetDepeloymentHistory.py [-h] [-g RG]

引数なし：リソースグループ一覧の取得、　　引数あり：指定したリソースグループ内のリソース毎のデプロイ履歴・操作情報の取得

optional arguments:
  -h, --help      show this help message and exit
  -g RG, --rg RG  リソースグループ名
```

### プログラムの実行
 
まずは最初に、リソースグループ一覧の取得

```
$ python GetDepeloymentHistory.py

Resource Group                                    Location
----------------------------------------------------------------------------------------------------
rg_AzureStackHCI                                  eastus
rg_NetworkWatcher                                 japaneast
     ：
    省略
     ：
rg_ituru_vm02                                     japaneast
rg_ituru_deploy_test                              japaneast

  取得時間:0.4982631206512451 [sec] 
```
 
上記のリソースグループ一覧から、指定したリソースグループのデプロイ情報・履歴を取得し、ローカルファイルへ保存

```
$ python GetDepeloymentHistory.py -g rg_ituru_deploy_test

取得アクセストークン :
eyJ0eXAiOiJ・・・省略・・・VjCV6lrbWc8Dw

■ ■ ■ Microsoft.VirtualNetwork-20220201112413
./output/Deployment__rg_ituru_deploy_test_Microsoft.VirtualNetwork-20220201112413_20220201_160456.json
./output/Deployment_Operation__rg_ituru_deploy_test_Microsoft.VirtualNetwork-20220201112413_20220201_160456.json

  Deplyment_name 数 : 1

  取得時間:0.49964094161987305 [sec] 
```

Outputファイルの確認

```
$ ls -l ./output        
total 32
drwxr-xr-x   6 ituru  staff   192  2  1 16:08 ./
drwxr-xr-x  12 ituru  staff   384  2  1 16:05 ../
-rw-r--r--   1 ituru  staff  1483  2  1 16:04 Deployment_Operation__rg_ituru_deploy_test_Microsoft.VirtualNetwork-20220201112413_20220201_160456.json
-rw-r--r--   1 ituru  staff  2511  2  1 16:04 Deployment__rg_ituru_deploy_test_Microsoft.VirtualNetwork-20220201112413_20220201_160456.json
```

デプロイ履歴情報（Deployment__rg_xxxxxxxxxxx.json）

```
{
  "id": "/subscriptions/xxxxxxxx-1717-dada-9779-zzzzzzzzzzzz/resourceGroups/rg_ituru_deploy_test/providers/Microsoft.Resources/deployments/Microsoft.VirtualNetwork-20220201112413",
  "name": "Microsoft.VirtualNetwork-20220201112413",
  "type": "Microsoft.Resources/deployments",
  "tags": {
    "primaryResourceId": "/subscriptions/xxxxxxxx-1717-dada-9779-zzzzzzzzzzzz/resourceGroups/rg_ituru_deploy_test/providers/Microsoft.Network/virtualNetworks/vnet_ituru_deploy_test",
    "marketplaceItemId": "Microsoft.VirtualNetwork-ARM"
  },
  "properties": {
    "templateHash": "15981414355539578904",
    "parameters": {
      "location": {
        "type": "String",
        "value": "japaneast"
      },
      "extendedLocation": {
        "type": "Object",
        "value": {}
      },
      "virtualNetworkName": {
        "type": "String",
        "value": "vnet_ituru_deploy_test"
      },
      "resourceGroup": {
        "type": "String",
        "value": "rg_ituru_deploy_test"
      },
      "addressSpaces": {
        "type": "Array",
        "value": [
          "10.6.0.0/16"
        ]
      },
      "ipv6Enabled": {
        "type": "Bool",
        "value": false
      },
      "subnetCount": {
        "type": "Int",
        "value": 1
      },
      "subnet0_name": {
        "type": "String",
        "value": "default"
      },
      "subnet0_addressRange": {
        "type": "String",
        "value": "10.6.0.0/24"
      },
      "ddosProtectionPlanEnabled": {
        "type": "Bool",
        "value": false
      },
      "firewallEnabled": {
        "type": "Bool",
        "value": false
      },
      "bastionEnabled": {
        "type": "Bool",
        "value": false
      }
    },
    "mode": "Incremental",
    "debugSetting": {
      "detailLevel": "None"
    },
    "provisioningState": "Succeeded",
    "timestamp": "2022-02-01T02:24:55.4064206Z",
    "duration": "PT4.7454974S",
    "correlationId": "a891f3f3-4477-4545-acac-d748ffdd2857",
    "providers": [
      {
        "namespace": "Microsoft.Network",
        "resourceTypes": [
          {
            "resourceType": "VirtualNetworks",
            "locations": [
              "japaneast"
            ]
          }
        ]
      }
    ],
    "dependencies": [],
    "outputResources": [
      {
        "id": "/subscriptions/xxxxxxxx-1717-dada-9779-zzzzzzzzzzzz/resourceGroups/rg_ituru_deploy_test/providers/Microsoft.Network/VirtualNetworks/vnet_ituru_deploy_test"
      }
    ],
    "validationLevel": "Template"
  }
}
```

デプロイ操作情報（Deployment_Operation__rg_xxxxxxxxxxx.json）

```
[
  {
    "id": "/subscriptions/xxxxxxxx-1717-dada-9779-zzzzzzzzzzzz/resourceGroups/rg_ituru_deploy_test/providers/Microsoft.Resources/deployments/Microsoft.VirtualNetwork-20220201112413/operations/557B59888B6CD293",
    "operationId": "557B59888B6CD293",
    "properties": {
      "provisioningOperation": "Create",
      "provisioningState": "Succeeded",
      "timestamp": "2022-02-01T02:24:55.2896236Z",
      "duration": "PT2.9130169S",
      "trackingId": "3838ccbb-f42d-4c26-92be-b46005570e40",
      "serviceRequestId": "f597efef-c06c-4ee4-8683-083f3a862760",
      "statusCode": "OK",
      "targetResource": {
        "id": "/subscriptions/xxxxxxxx-1717-dada-9779-zzzzzzzzzzzz/resourceGroups/rg_ituru_deploy_test/providers/Microsoft.Network/VirtualNetworks/vnet_ituru_deploy_test",
        "resourceType": "Microsoft.Network/VirtualNetworks",
        "resourceName": "vnet_ituru_deploy_test"
      }
    }
  },
  {
    "id": "/subscriptions/xxxxxxxx-1717-dada-9779-zzzzzzzzzzzz/resourceGroups/rg_ituru_deploy_test/providers/Microsoft.Resources/deployments/Microsoft.VirtualNetwork-20220201112413/operations/00585579225949072200",
    "operationId": "00585579225949072200",
    "properties": {
      "provisioningOperation": "EvaluateDeploymentOutput",
      "provisioningState": "Succeeded",
      "timestamp": "2022-02-01T02:24:55.3801548Z",
      "duration": "PT3.0035481S",
      "trackingId": "5252d103-4555-4c4c-821c-0c5ef88ccc34",
      "statusCode": "OK"
    }
  }
]
```

### まとめ

このプログラムで、指定するリソースグループ内にあるリソースのデプロイ履歴・操作情報をサクッと取得し、その内容を json形式 でローカルファイルに保存できました。

この２つのデプロイ履歴情報とデプロイ操作情報の json ファイルは、Azure Portalから「リソースグループ」ー「設定：デプロイ」からデプロイ名を選択し、「展開の詳細」ー「ダウンロード」から入手できる「deployment.json（履歴情報）」「deployment_operations.json（操作情報）」の２つのファイルと同等となっています。

ただ、この２つのデプロイ情報は、AzurePortalからリソースを作成した場合のみ取得可能(?)であり、AzureCLI / Terraform 等でリソースを作成した場合には取得不可となります。なんでだろ、、、、、

AzureCLI / Terraform 等でリソースを作成した場合、デプロイ情報の取得は「テンプレートのエクスポート」しかないんでしょうか？、、、、、、、