Azure Purview CLI & 移行用サンプル スクリプトのご紹介

# はじめに
**[Azure Purview](https://azure.microsoft.com/ja-jp/services/purview/)** は Microsoft Azure の統合データ ガバナンスのためのサービスです。Purview は 2021 年 6 月現在プレビュー中ということもあり、REST API や SDK など周辺の仕組みも徐々に拡充されているところです。

本記事では、GitHub 上で開発が進んでいる **[Azure Purview CLI (`purviewcli`)](https://github.com/tayganr/purviewcli)** のインストール及び設定方法、使い方についてご紹介した後、`purviewcli` を使った Purview リソースの移行用サンプル スクリプトについてご紹介します。

なお、本記事では 2021 年 6 月 15 日時点の最新バージョン `purviewcli==0.1.17` を利用しています。

https://github.com/tayganr/purviewcli

# Azure Purview CLI (`purviewcli`)
`purviewcli` は Azure Purview の REST API をコマンド ライン インターフェイス (CLI) で操作するための Python 製のツールです。

100 以上のコマンドを利用可能で、Purview アカウント内のデータ ソースやスキャンなどの各種リソースに対する CRUD 操作や、Purview アカウント自体の管理も行えます。

## インストール & 設定
インストール & 設定の大まかな流れを記載します。詳細は YouTube で解説されているため、こちらも合わせてご覧ください。(GitHub の README からもこの動画にリンクされています)

https://www.youtube.com/watch?v=ycr1G5iMM6U

### 1. インストール
pip でインストールします。Python バージョンの明示的な指定はありませんが、3 系を使うのがベターでしょう。

```
pip install purviewcli
```

### 2. 環境変数 `PURVIEW_NAME` の設定
Azure Purview アカウント名を環境変数 `PURVIEW_NAME` にセットします。なお、環境変数を設定せずに `purviewcli` の各コマンドの最後に `--purviewName=<val>` を付与することでも指定可能です。

### 3. 認証 (Authentication) の設定
`purviewcli` は [azure-identity](https://github.com/Azure/azure-sdk-for-python/tree/master/sdk/identity/azure-identity) ライブラリの [DefaultAzureCredential](https://github.com/Azure/azure-sdk-for-python/tree/master/sdk/identity/azure-identity#defaultazurecredential) クラスを認証に用います。DefaultAzureCredential クラスは以下の順番で認証を試行し、認証に成功したらそこでストップします。

\# | 種類 | 内容
--- | --- | ---
1 | 環境変数 | 環境変数にサービス プリンシパルなどのアカウント情報が設定されている場合、そのアカウント情報を認証に用います
2 | マネージド ID | マネージド ID が有効化された Azure VM などでアプリケーションを稼働させる場合、マネージド ID を認証に用います
3 | Visual Studio Code | ユーザーが Visual Studio Code の Azure アカウント拡張でサインイン済みの場合、そのユーザーを認証に用います
4 | Azure CLI | ユーザーが `az login` コマンドでサインイン済みの場合、そのユーザーを認証に用います
5 | Azure PowerShell | ユーザーが `Connect-AzAccount` コマンドでサインイン済みの場合、そのユーザーを認証に用います
6 | インタラクティブ | (既定では無効) もし有効化されている場合は、デフォルトのブラウザーで認証をインタラクティブに行います

1 の環境変数の方式では、サービス プリンシパルを作成し、Application (client) ID / Directory (tenant) ID / Client Secret を控えておき、以下の環境変数にセットします。

- `AZURE_CLIENT_ID`
- `AZURE_TENANT_ID`
- `AZURE_CLIENT_SECRET`

サービス プリンシパルの作成手順については以下をご参照下さい。

https://docs.microsoft.com/ja-jp/azure/active-directory/develop/howto-create-service-principal-portal

### 4. 認可 (Authorization) の設定
3 で利用するサービス プリンシパル / マネージド ID / ユーザーには、操作対象の Azure Purview アカウントに対して以下の Azure RBAC ロールが付与されている必要があります。

- Purview Data Curator (Purview データ キュレーター)
- Purview Data Source Administrator (Purview データ ソース管理者)

Azure ポータルの場合、Azure Purivew アカウント の [アクセス制御 (IAM)] から RBAC ロールの付与が行えます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/284925/97d4924d-7e83-7a32-0324-4f5a5ed66cd5.png)

以上でインストールと設定は完了です。これで `purviewcli` のコマンド、例えばデータ ソースの読み取り `pv scan readDatasources` などが行えるはずです。

### 2021/10/22 追記
Purview のロールの割り当てについて、Purview Studio の [Collections] からコレクションごとに行うようになりました。これに伴い、**コレクション管理者** という新たなロールが追加されています。Purview CLI のコマンドを実行するには、このロールをサービス プリンシパル / マネージド ID / ユーザーに付与する必要があります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/284925/27f13c28-c057-c66f-b1bb-c798a2b9b933.png)

## コマンド例 & 出力例
以下、コマンド例と出力例をいくつかピックアップして記載します。利用可能なコマンドの一覧については `purviewcli` の [GitHub の README](https://github.com/tayganr/purviewcli) をご覧ください。

### データ ソース一覧の読み取り
`pv scan readDatasources` で行います。

- `value` という名前の配列にデータ ソースの一覧が入ります
- `kind` が `Collection` になっているのはデータ ソースの「コレクション」で、他のデータ ソースの `parentCollection` から参照する形で利用されます

```shell:データソース一覧の読み取り
$ pv scan readDatasources
{
    "count": 6,
    "value": [
        {
            "id": "datasources/rg-hinakaza-openhack-mdw",
            "kind": "Collection",
            "name": "rg-hinakaza-openhack-mdw",
            "properties": {
                "collection": null,
                "createdAt": "2021-06-14T15:36:45.7178733Z",
                "lastModifiedAt": "2021-06-14T15:37:08.6895367Z",
                "parentCollection": null
            }
        },
        {
            "id": "datasources/southridge-catalog",
            "kind": "AzureCosmosDb",
            "name": "southridge-catalog",
            "properties": {
                "accountUri": "https://southridge-catalog-xxx.documents.azure.com:443/",
                "collection": null,
                "createdAt": "2021-06-14T15:36:47.1387372Z",
                "lastModifiedAt": "2021-06-14T15:37:10.1197566Z",
                "location": "westus2",
                "parentCollection": {
                    "referenceName": "rg-hinakaza-openhack-mdw",
                    "type": "DataSourceReference"
                },
                "resourceGroup": "rg-hinakaza-openhack-mdw",
                "resourceName": "southridge-catalog-xxx",
                "subscriptionId": "xxx"
            }
        },

        # (中略)

        {
            "id": "datasources/AzureMultipleSourcesOpenhackMdw",
            "kind": "AzureResourceGroup",
            "name": "AzureMultipleSourcesOpenhackMdw",
            "properties": {
                "collection": null,
                "createdAt": "2021-06-14T15:37:07.0160838Z",
                "lastModifiedAt": "2021-06-14T15:37:07.0160839Z",
                "parentCollection": {
                    "referenceName": "rg-hinakaza-openhack-mdw",
                    "type": "DataSourceReference"
                },
                "resourceGroup": "rg-hinakaza-openhack-mdw",
                "subscriptionId": "xxx"
            }
        }
    ]
}
```

### スキャン一覧の読み取り
`pv scan readScans --dataSourceName=<val>` で行います。

- `value` という名前の配列にスキャンの一覧が入ります
- `kind` が認証方式を表します
- `credential` の `referenceName` は、Purview アカウントにリンクされた Key Vault に登録されたクレデンシャル情報への参照を表します

```shell:スキャン一覧の読み取り1
$ pv scan readScans --dataSourceName=southridge-catalog
{
    "count": 1,
    "value": [
        {
            "id": "datasources/southridge-catalog/scans/Scan-southridge-catalog",
            "kind": "AzureCosmosDbCredential",
            "name": "Scan-southridge-catalog",
            "properties": {
                "createdAt": "2021-06-14T15:36:53.7845476Z",
                "credential": {
                    "credentialType": "AccountKey",
                    "referenceName": "Credential-SouthridgeCatalog-CosmosDbAuthentication"
                },
                "databaseName": null,
                "lastModifiedAt": "2021-06-14T15:37:16.9645408Z",
                "scanRulesetName": "AzureCosmosDb",
                "scanRulesetType": "System"
            }
        }
    ]
}
```

```shell:スキャン一覧の読み取り2
$ pv scan readScans --dataSourceName=southridge-sqlserver
{
    "count": 2,
    "value": [
        {
            "id": "datasources/southridge-sqlserver/scans/Scan-CloudSales",
            "kind": "AzureSqlDatabaseMsi",
            "name": "Scan-CloudSales",
            "properties": {
                "createdAt": "2021-06-14T15:36:55.7211982Z",
                "databaseName": "CloudSales",
                "enableLineage": false,
                "lastModifiedAt": "2021-06-14T15:37:18.859219Z",
                "scanRulesetName": "AzureSqlDatabase",
                "scanRulesetType": "System",
                "serverEndpoint": "southridge-sqlserver-xxx.database.windows.net"
            }
        },
        {
            "id": "datasources/southridge-sqlserver/scans/Scan-CloudStreaming",
            "kind": "AzureSqlDatabaseMsi",
            "name": "Scan-CloudStreaming",
            "properties": {
                "createdAt": "2021-06-14T15:36:57.0513579Z",
                "databaseName": "CloudStreaming",
                "enableLineage": false,
                "lastModifiedAt": "2021-06-14T15:37:20.1669944Z",
                "scanRulesetName": "AzureSqlDatabase",
                "scanRulesetType": "System",
                "serverEndpoint": "southridge-sqlserver-xxx.database.windows.net"
            }
        }
    ]
}
```

(参考: クレデンシャル情報の画面)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/284925/9dc68396-149a-ca63-ef7e-38824f897ffe.png)

### データ ソースの登録
`pv scan putDataSource --dataSourceName=<val> --payload-file=<val>` で行います。

- `--payload-file` の形式は https://github.com/tayganr/purviewcli/tree/master/samples/json で定義されています
- データ ソースに親のコレクションへの参照がある場合、コレクションを先に登録しておかないとデータ ソースの登録に失敗します
- リクエストのペイロード中の余分な項目は無視されます
    - 例えば以下例では `createdAt` などの項目をリクエストのペイロードに含めていますが無視されており、`pv scan putDataSource` のレスポンスの `createdAt` にはコマンド実行時の日時が入っています
- そのため `pv scan readDatasources` のレスポンスの `value` 配列内の一要素をファイルとして保存することで `--payload-file` に指定できます

```shell:データソース(コレクション)の登録
# ダミーのペイロード ファイルを作成
$ cat <<EOF > collection1.json
{
    "id": "datasources/rg-hinakaza-openhack-mdw",
    "kind": "Collection",
    "name": "rg-hinakaza-openhack-mdw",
    "properties": {
        "collection": null,
        "createdAt": "2021-06-14T15:36:45.7178733Z",
        "lastModifiedAt": "2021-06-14T15:37:08.6895367Z",
        "parentCollection": null
    }
}
EOF

$ pv scan putDataSource --dataSourceName=collection1 --payload-file=collection1.json
{
    "id": "datasources/collection1",
    "kind": "Collection",
    "name": "collection1",
    "properties": {
        "collection": null,
        "createdAt": "2021-06-14T16:56:25.0191689Z",
        "lastModifiedAt": "2021-06-14T16:56:25.0191689Z",
        "parentCollection": null
    }
}
```

```shell:データソースの登録
# ダミーのペイロード ファイルを作成
$ cat <<EOF > datasource1.json
{
    "id": "datasources/southridge-catalog",
    "kind": "AzureCosmosDb",
    "name": "southridge-catalog",
    "properties": {
        "accountUri": "https://southridge-catalog-xxx.documents.azure.com:443/",
        "collection": null,
        "createdAt": "2021-03-10T16:56:51.109248Z",
        "lastModifiedAt": "2021-03-10T16:56:51.1092482Z",
        "location": "westus2",
        "parentCollection": {
            "referenceName": "collection1",
            "type": "DataSourceReference"
        },
        "resourceGroup": "rg-hinakaza-openhack-mdw",
        "resourceName": "southridge-catalog-xxx",
        "subscriptionId": "xxx"
    }
}
EOF

$ pv scan putDataSource --dataSourceName=datasource1 --payload-file=datasource1.json
{
    "id": "datasources/datasource1",
    "kind": "AzureCosmosDb",
    "name": "datasource1",
    "properties": {
        "accountUri": "https://southridge-catalog-xxx.documents.azure.com:443/",
        "collection": null,
        "createdAt": "2021-06-14T16:57:46.8343957Z",
        "lastModifiedAt": "2021-06-14T16:57:46.8343958Z",
        "location": "westus2",
        "parentCollection": {
            "referenceName": "collection1",
            "type": "DataSourceReference"
        },
        "resourceGroup": "rg-hinakaza-openhack-mdw",
        "resourceName": "southridge-catalog-xxx",
        "subscriptionId": "xxx"
    }
}
```

### スキャンの登録
`pv scan putScan --dataSourceName=<val> --scanName=<val> --payload-file=<val>` で行います。

- `--payload-file` の形式は https://github.com/tayganr/purviewcli/tree/master/samples/json を参照します
- データ ソースが先に登録されている必要があります
- クレデンシャルを参照している場合は先にクレデンシャルを登録しておく必要があります
- リクエストのペイロード中の余分な項目は無視されます
- そのため `pv scan readScans --dataSourceName=<val>` のレスポンスの `value` 配列内の一要素をファイルとして保存することで `--payload-file` に指定できます

```shell:スキャンの登録
# ダミーのペイロード ファイルを作成
$ cat <<EOF > scan1.json
{
  "id": "datasources/southridge-catalog/scans/Scan-southridge-catalog",
  "kind": "AzureCosmosDbCredential",
  "name": "Scan-southridge-catalog",
  "properties": {
    "createdAt": "2021-03-10T17:00:42.4671102Z",
    "credential": {
      "credentialType": "AccountKey",
      "referenceName": "Credential-SouthridgeCatalog-CosmosDbAuthentication"
    },
    "databaseName": null,
    "lastModifiedAt": "2021-03-11T06:07:48.5865426Z",
    "scanRulesetName": "AzureCosmosDb",
    "scanRulesetType": "System"
  }
}
EOF

$ pv scan putScan --dataSourceName=datasource1 --scanName=scan1 --payload-file=scan1.json
{
    "id": "datasources/datasource1/scans/scan1",
    "kind": "AzureCosmosDbCredential",
    "name": "scan1",
    "properties": {
        "createdAt": "2021-06-14T17:08:41.115475Z",
        "credential": {
            "credentialType": "AccountKey",
            "referenceName": "Credential-SouthridgeCatalog-CosmosDbAuthentication"
        },
        "databaseName": null,
        "lastModifiedAt": "2021-06-14T17:08:41.115475Z",
        "scanRulesetName": "AzureCosmosDb",
        "scanRulesetType": "System"
    }
}
```

# 移行用サンプル スクリプトのご紹介
Purview アカウント配下のデータ ソースやスキャンなどのリソースをまとめてエクスポート & インポートするサンプル スクリプトを以下の GitHub リポジトリで公開しています。

Purview のリソースを JSON 形式のファイル群としてバックアップしたり、別の Purview アカウントにインポートするといった用途でお使い頂けます。

サンプル スクリプトは `purviewcli` と JSON 用ユーティリティ ツールの `jq` を利用しています。`purviewcli` については本記事などを参考に設定頂き、`jq` については https://stedolan.github.io/jq/ などから入手ください。

https://github.com/nakazax-org/purview-migration-helper

## スクリプトで出来ること
- 課題として...
    - これまで見てきたように、`purviewcli` の一括参照系のコマンド (`pv scan readDatasources` や `pv scan readScans` など) はリソース群を JSON の配列として返却します
    - 一方で、`purviewcli` の登録系のコマンド (`pv scan putDataSource` や `pv scan putScan` など) は 1 JSON ファイルを引数として指定する必要があります
- エクスポート用のサンプル スクリプトを用いることで `purviewcli` の一括参照系のコマンドのレスポンス JSON 内の配列の 1 要素を 1 JSON ファイルに分割してエクスポートできます
- インポート用のサンプル スクリプトを用いることで、エクスポートした JSON ファイル群を Purview アカウントにインポートできます

## スクリプトの実行方法
`purviewcli` のインストール & 設定後に実行します。

```shell:移行用サンプルスクリプト
# エクスポート
$ ./scripts/sample-export-resources.sh
# インポート
$ ./scripts/sample-import-resources.sh "/path/to/payloads"
```

以下はエクスポートを実行した後の画面キャプチャです。各リソースが 1 つ 1 つの JSON ファイルに分かれて出力されます。
![image.png](https://github.com/nakazax/purview-migration-helper/blob/main/docs/images/exported-payloads-1.png?raw=true)

以上です。