Bicep を使って Deployment Scripts で Python を動かす

## Introduction
実際に需要があるかはさておき [Azure Center for SAP solutions](https://learn.microsoft.com/ja-jp/azure/sap/center-sap-solutions/overview) の前提条件の環境を Bicep で構成する際に必要だったので解説します。

経緯としては、SUSE の IP アドレス (たまに変わるやつ)を NSG で穴あけする必要があり、IP アドレスに関しては、python3-susepubliccloudinfo パッケージで提供されている pint ツールを使う必要があったためです。つまりパブリッククラウドプロバイダのリージョンサーバ IP を取得するには Python パッケージを動かす必要がありました。

折角 Bicep 使ってるし、立てる時いちいち IP 調べるのも面倒なので、Bicep のデプロイと同時に IP 取得できればと思って試してみた形です。

実際の Deployment Scripts を使用したサンプルコードはこちらになります。

https://github.com/TakumiSeo/Azure-Deployment-Env-ACSS-Bicep/tree/main

また SUSE の IP 取得方法はこちらです。クラウドプロバイダーによって変わります。

https://www.suse.com/c/accessing-the-public-cloud-update-infrastructure-via-a-proxy/

## Bicep コード
結論、Deployment Script を使用して、Azure Container Instance を一時的に立て、そこでコマンドを実行し、結果を Bicep で output として受け取る流れです。

``` bash
resource deploymentScript 'Microsoft.Resources/deploymentScripts@2020-10-01' = {
  name: deploymentScriptName
  location: location
  kind: 'AzureCLI'
  identity: {
    type: 'UserAssigned'
    userAssignedIdentities: {
      '${userAssignedIdentity.id}': {}
    }
  }
  properties: {
    azCliVersion: '2.55.0'
    timeout: 'PT5M'
    retentionInterval: 'P1D'
    scriptContent: loadTextContent('../../scripts/suseenpoint.sh')
  }
  dependsOn: [
    roleAssignment
    blobContainer
  ]
}
output suseIp array = deploymentScript.properties.outputs.servers
```
https://learn.microsoft.com/ja-jp/azure/azure-resource-manager/templates/deployment-script-template

:::note info
Deployment Scripts を利用する利点です

* デプロイ スクリプトは、好みの開発環境で開発できる
* スクリプトは、テンプレートまたは外部スクリプト ファイルに埋め込むことができる
* スクリプト言語とプラットフォームを指定可能
* 現時点(2024/06)では、Linux 環境の Azure PowerShell および Azure CLI のデプロイ スクリプトがサポートされている
* スクリプトにコマンド ライン引数を渡すことを許可します
* スクリプトの出力を指定して、デプロイに渡すことができます
:::

ではそれぞれのプロパティを見ていきます。

### kind プロパティ
上の備考で述べていますが、現時点では「Linux 環境の Azure PowerShell および Azure CLI のデプロイ スクリプト」がサポートされています。
その為、今回はプロパティの kind で Azure CLI を指定しています。

### identity プロパティ
次に identity プロパティに関して説明します。
Deployment Scripts の API バージョン 2020-10-01 以降では、Deployment Scripts の実行に 2 つのプリンシパルが関係します。
- デプロイプリンシパル
    - テンプレートのデプロイに使用される
        - ストレージアカウントと Azure Container Instance に対する権限をもつ
        - これは スクリプトの実行に必要な基となるリソース
        - 実際には以下の権限が必要
```json
  "permissions": [
    {
      "actions": [
        "Microsoft.Storage/storageAccounts/*",
        "Microsoft.ContainerInstance/containerGroups/*",
        "Microsoft.Resources/deployments/*",
        "Microsoft.Resources/deploymentScripts/*"
      ],
    }
  ]
```
- デプロイ スクリプト プリンシパル
    - Azure に対して認証を行い、Azure CLI または PowerShell を呼び出す
    - 指定された場合、スクリプト サービスは、Deployment Scripts の呼び出し前に Connect-AzAccount -Identity mまたは az login を呼び出す

なので操作を行うために十分な権限を与えたユーザー割り当てマネージド ID と依存関係になります。今回は操作を行うために十分な権限であるBLOB の共同作成者を与えています。

```bash
resource userAssignedIdentity 'Microsoft.ManagedIdentity/userAssignedIdentities@2023-01-31' = {
  name: userAssignedIdentityName
  location: location
}

resource roleAssignment 'Microsoft.Authorization/roleAssignments@2020-04-01-preview' = {
  name: roleAssignmentName
  properties: {
    principalId: userAssignedIdentity.properties.principalId
    roleDefinitionId: blobContributorRoleDefinitionId
    principalType: 'ServicePrincipal'
  }
}
```

### properties プロパティ
ここでは表記の通り、バージョンやタイムアウトを設定しています。
また "scriptContent" において、実際に AzureCLI で実行するコマンドと戻り値を指定します。
以下が .sh スクリプトです。

```sh
apt install python3-pip
apt install jq
pip3 install susepubliccloudinfo

suse_iplist=$(pint microsoft servers --json --region=japaneast  | jq -r '.servers[].ip')
declare -a ip_array
while IFS= read -r line; do
    ip_array+=("$line")
done <<< "$suse_iplist"
jq -n --argjson servers "$(printf '%s\n' "${ip_array[@]}" | jq -R . | jq -s .)" '{servers: $servers}' > $AZ_SCRIPTS_OUTPUT_PATH
```

特に重要なところが、`'{servers: $servers}' > $AZ_SCRIPTS_OUTPUT_PATH` になります。
ここが Bicep の output である `output suseIp array = deploymentScript.properties.outputs.servers` に渡しているところ、つまり、スクリプトの出力を指定して、デプロイに渡している所になります。

:::note info
GitHub Copilot が無かったら無理だった点 

jq で NSG 用に 複数の ip アドレスのデータを整形するために jq を使用しています。。
でも整形しているコマンドを見るとわかると思いますが、Copilot なしでは無理でした。。
:::

### 参考
コマンド上でデータの整形は終わっているため、後は NSG の設定に埋め込むだけです。
```bash
@description('SUSE IP list')
param suseIpList array

~~~~~

securityRules: [
      {
        name: 'Allow-SUSE'
        properties: {
          protocol: 'Tcp'
          sourcePortRange: '*'
          destinationPortRange: '*'
          sourceAddressPrefixes: suseIpList
          destinationAddressPrefix: 'VirtualNetwork'
          access: 'Allow'
          priority: 200
          direction: 'Inbound'

```

## Wrap Up
今回は Bicep で Deployment Scripts を使用して、Python を動かし、SUSE linux の IP アドレスを取得しました。Python ファイルを直接動かしたわけではないですが、パッケージを使用して、内部的には .py ファイルを実行しています。
重い処理はできないと思いますが、Python 以外ももちろん動かせるので、この様に動的に変わる情報などを、Bicep のデプロイ時についでに取得してみてはいかがでしょうか。
ではでは