ポータルで作成したAzure Logic Appsをインフラのコード化（IaC）で再現（ARMテンプレートからBicepに変換する裏技付き）

こんにちは。

前回の記事で、Azure Logic Appsを使って直感的なサービスの構築（簡単なメール転送サービス）を作成しました。

https://qiita.com/fsdg-takada/items/e0612bf23a772350d85c

ただ、最後の方でチーム開発などをする時はインフラ定義のコード化が必要であるということにも触れました。

そこで今回はAzure Logic Appsのインフラをコード定義してみて、どういう形になるのか？デモを行ってみたいと思います。

## インフラコード化（IaC）とその利点

### インフラのコード化とは？

さて、実演に入るまえに、そもそもインフラのコード化とはどういうことでしょう？

簡単に言うと、「ポータル画面などでリソースを作成するのではなく、リソースをコードで定義し、そのコードをもとにリソースを作成する」ということになります。

### 利点

では、なぜインフラのコード化が必要なのでしょうか？
その利点をいくつかピックアップすると、以下の通りです。

- インフラをコード化することで、同じ環境を簡単に構築できる。
- なにかバグがあって、インフラが動かなくなってその問題が不明な場合。コード化しておけば履歴から前のコードをデプロイし、正常時の状態に戻すことができる。
- リソースが膨大になって、複雑に関係しあう場合でも、順番などを気にせず、Azure側が上手い具合に作成してくれる（ただし、関係性を記述する必要などはあります）

などになります。今回は実演メインにしたいため、このぐらいの説明にとどめましたが、インフラコード化は現時点でデファクトスタンダードになりつつあるので、興味がある方はぜひご自身でもお調べください。

## 実演

※今回、デプロイコマンドなどではAzure CLI（azコマンド）を使用します。インストール方法などは公式をご確認ください。
https://learn.microsoft.com/ja-jp/cli/azure/

### 作成するもの
さて、それでは実演に入っていきます。
今回はインフラのコード化を体験することをメインとしたいので、作成物はより簡単なものとします。

具体的には
- 60分ごとにトリガー発動（ただし、お金がかからないように、未来の日付から開始するように）
- outlookで、自分のメール宛に「test」という件名、本文のメールを送るようにする。

このぐらいのシンプルなものとしました(名前は「original_app」としました。)。

まず、ポータルで作成してみた画面は以下の通りです。
（宛先部分は実際には自分のアドレスを入れています。また、リージョンは東日本リージョンで作成しています。）

![スクリーンショット 2022-10-18 131809.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2883992/90dd2bd8-452e-66c3-5f78-f9fc3cc6a208.png)

また、ポータル上から作成した場合、自動でリソースグループも作成されます。
以下は作成されたリソースグループの画面で、ロジックアプリのリソースだけではなく、outlookに接続するためのリソースも一緒に作成され、グループでまとまっていることが確認できます。

![スクリーンショット 2022-10-18 131809.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2883992/cb0107f3-ff29-6ab9-88b9-1d22f931e1f5.png)

つまり、今回はこのリソースグループの内容とAppデザイナー上でのロジック画面が同じになるような、コードを記述・デプロイの成功を目標にしていきます。

### なぜ、今回Bicep

今回はコード化するのにあたり、ARMテンプレートではなく、Bicepを使用します。

そもそも、AzureのリソースはARMテンプレートをもとにデプロイされます。（これは例えばポータルから作成した場合でも内部的にARMテンプレートに変換されて、最終的にはARMテンプレートをもとにデプロイされています。）

そのため、ARMテンプレートを直接記述してもよいのですが、ARMテンプレートがJSON形式であるのに対し、Bicepは独自の簡潔な記法をしており、すっきりしていてわかりやすいというメリットがあります。
（というか、ARMテンプレートの書きづらさ・読みづらさを解決するために、後発でつくられたものとなります。）

つまり、BicepはARMテンプレートの書きづらいというデメリットを補うためにつくられた、Azureリソース作成専用の記法となります。
（厳密にはデプロイ時にBicepで書かれたコードはJSONに変換されます）

先日行われたMicrosoft Igniteでも、ARMテンプレートよりもBicepを使用している人の方が多いようで、こちらが標準になっている気がしたので、今回はBicepを採用します。
（ちなみに、コードのインフラについてはTerraformという選択肢もあります）

### Bicepによるコード記述

それでは上記の内容を再現するための、Bicepコードを記載していきます。
まず、Bicepを使えるように以下のコマンドでインストール（最新版へのアップデート）を行いましょう

```bash
az bicep install && az bicep upgrade
```

その上で記述していくのですが、以下の問題が...

- Azure Logic AppsのBicepの記述が情報少なく、公式ドキュメント見てもわかりずらい（というかARMテンプレートであってもわかりずらい）

ということで、裏技を使ってしまいます。

どんな裏技かというとポータル上で作成したものからARMテンプレートを出力。そのうえで、そのARMテンプレートをBicepにデコンパイルするという方法です。

逆説的ではありますが、本来Azure Logic Appsの強みは直感的に作成できることにあります。
そのため、この手法をとることで、Azure Logic Appsの強みを活かしながらのコードによる共有・同じ環境の再現など、IaCの恩恵も受けることができるかと思います（ただし、万能ではありません。）

流れは以下の通りです。

- まず、Azureポータル上で作成したAzure Logic Appsリソースが含まれている、リソースグループを選択。今回自分の場合はoriginal_app_groupというリソースグループになります。
- 左側メニューから、「テンプレートのエクスポート」を選択。すると、このリソースグループのARMテンプレート（jsonファイル）が表示されます。自身のローカル環境などにjson形式のファイルを作成し、VSCode上などで貼り付けます（ここではapp.jsonとします）
![スクリーンショット 2022-10-18 131809.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2883992/acfcf28c-a7ae-fea8-e087-1a8c77c2e466.png)
![スクリーンショット 2022-10-18 131809.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2883992/439fe146-87df-11ec-2f1d-b22f0af62a50.png)
- app.jsonがあるディレクトリ内で以下のコマンドを実行します。すると、同ディレクトリ内にデコンパイルされたapp.bicepファイルができあがります。
```bash
az bicep decompile --file app.json
```
![スクリーンショット 2022-10-18 131809.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2883992/7f8790c6-9f71-8cce-67e3-671fb2609d58.png)

以上で基本的なデコンパイルは終了です。実際にできたファイルを以下の通りです。
(qiita上でBicepの補完がなかったので、少しに見づらいです。また、識別情報や個人情報などの部分は「」で囲み、日本語で説明を書いております。そのため、実際はそれぞれ違う値が入るはずです)。
```
param connections_office365_name string = 'office365'
param workflows_original_app_name string = 'original_app'

resource connections_office365_name_resource 'Microsoft.Web/connections@2016-06-01' = {
  name: connections_office365_name
  location: 'japaneast'
  kind: 'V1'
  properties: {
    displayName: '「Outlookメールアドレス」'
    statuses: [
      {
        status: 'Connected'
      }
    ]
    customParameterValues: {
    }
    nonSecretParameterValues: {
    }
    createdTime: '2022-10-18T03:00:22.4616241Z'
    changedTime: '2022-10-18T03:00:28.2968823Z'
    api: {
      name: connections_office365_name
      displayName: 'Office 365 Outlook'
      description: 'Microsoft Office 365 is a cloud-based service that is designed to help meet your organization\'s needs for robust security, reliability, and user productivity.'
      iconUri: 'https://connectoricons-prod.azureedge.net/releases/v1.0.1599/1.0.1599.3017/${connections_office365_name}/icon.png'
      brandColor: '#0078D4'
      id: '/subscriptions/「サブスクリプションID」/providers/Microsoft.Web/locations/japaneast/managedApis/${connections_office365_name}'
      type: 'Microsoft.Web/locations/managedApis'
    }
    testLinks: [
      {
        requestUri: 'https://management.azure.com:443/subscriptions/「サブスクリプションID」/resourceGroups/original_app_group/providers/Microsoft.Web/connections/${connections_office365_name}/extensions/proxy/testconnection?api-version=2016-06-01'
        method: 'get'
      }
    ]
  }
}

resource workflows_original_app_name_resource 'Microsoft.Logic/workflows@2017-07-01' = {
  name: workflows_original_app_name
  location: 'japaneast'
  properties: {
    state: 'Enabled'
    definition: {
      '$schema': 'https://schema.management.azure.com/providers/Microsoft.Logic/schemas/2016-06-01/workflowdefinition.json#'
      contentVersion: '1.0.0.0'
      parameters: {
        '$connections': {
          defaultValue: {
          }
          type: 'Object'
        }
      }
      triggers: {
        '繰り返し': {
          recurrence: {
            frequency: 'Minute'
            interval: 60
            startTime: '2030-03-24T15:00:00Z'
          }
          evaluatedRecurrence: {
            frequency: 'Minute'
            interval: 60
            startTime: '2030-03-24T15:00:00Z'
          }
          type: 'Recurrence'
        }
      }
      actions: {
        'メールの送信_(V2)': {
          runAfter: {
          }
          type: 'ApiConnection'
          inputs: {
            body: {
              Body: '<p>test</p>'
              Importance: 'Normal'
              Subject: 'test'
              To: '「Outlookメールアドレス」'
            }
            host: {
              connection: {
                name: '@parameters(\'$connections\')[\'office365\'][\'connectionId\']'
              }
            }
            method: 'post'
            path: '/v2/Mail'
          }
        }
      }
      outputs: {
      }
    }
    parameters: {
      '$connections': {
        value: {
          office365: {
            connectionId: connections_office365_name_resource.id
            connectionName: 'office365'
            id: '/subscriptions/「サブスクリプションID」/providers/Microsoft.Web/locations/japaneast/managedApis/office365'
          }
        }
      }
    }
  }
}
```

これで自分では一切コードを書くことがなく、Bicepのコードに落とし込むことができました。

ここからデプロイしていきたいのですが...
残念ながらこのままではデプロイエラーとなる箇所があるので、修正してきます。

その前に、VSCode上でBicepの拡張機能があるので、入れておきましょう。
https://marketplace.visualstudio.com/items?itemName=ms-azuretools.vscode-bicep

拡張機能をいれることで、Bicepの記述としてベストではないところ、エラーになるところに警告文をだしてくれるようになります。
もちろん、すべてを直せればベストですが、今回は警告文のうち、エラーとなってしまうものを修正します。

具体的には32行目の部分。
```
requestUri: 'https://management.azure.com:443/subscriptions/「サブスクリプションID」/resourceGroups/original_app_group/providers/Microsoft.Web/connections/${connections_office365_name}/extensions/proxy/testconnection?api-version=2016-06-01'
```

黄色波線の部分にカーソルを充てると警告文の内容をみることができます。
どうやら、`https://management.azure.com` の部分をハードコーディングしてはいけない、という内容みたいです。該当のリンクも載っているので、それをもとに以下に修正します。

![スクリーンショット 2022-10-18 131809.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2883992/068c1353-607c-b688-326c-6f7b92373751.png)

修正後のコードは以下の通り

```
requestUri: '${environment().gallery}:443/subscriptions/「サブスクリプションID」/resourceGroups/original_app_group/providers/Microsoft.Web/connections/${connections_office365_name}/extensions/proxy/testconnection?api-version=2016-06-01'
```

また、上記のコードを見てみると、`resourceGroups/original_app_group`の部分が気になります。
このままだと、ポータルから作ったoriginal_app_groupというリソースグループ内で作成されたものとつながってしまうため、修正する必要があります。

そのためにはまず、新しいリソースグループを作成しましょう。
（最終的に余分なお金などを発生させないためにも既存のものを使わず、新しいリソースグループを作成し、終わったら削除することをお勧めします。）

今回は「copy_app_group」というリソースグループを作成します。
そのためには以下のコマンドを実行します。

```bash
az group create --location japaneast --name copy_app_group
```

もし、サブスクリプションをセットするようにメッセージが出た場合は以下を実行します。

```bash
az account set --subscription サブスクリプションID
```




念のため、ポータル画面を確認すると、きちんと作成されていることが確認できました。

![スクリーンショット 2022-10-18 131809.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2883992/d53110c5-60e0-82ac-e283-7bc741b81420.png)

さて、このまま先ほどのコードを修正してもいいのですが、実際問題として、デプロイ時にリソースグループを指定。そのリソースグループ内に含まれるリソース同士でつながるようにしたいという流れになると思います。

そのため、コード内で直接「copy_app_group」を指定しまうと、結局ほかのリソースグループにデプロイするときにずれが発生してしまうため、パラメータ化したいと思います。

以下、もろもろを修正した最終的なコードとなります。追加・修正箇所は//でコメントをしています。
（ついでに、アプリ名も修正・環境ごとにアプリ名をdev-copy-nameのようにできるように修正しました。）
（また、もし使用するサブスクリプションを変えた場合はコード内のサブスクリプションIDも修正する必要があります。）

```
// デプロイ時にアプリ名の先頭に環境名をつけれるようにするため、パラメータ変数を用意
param app_environment string
// リソースグループのパラメータ変数
param resource_group string
param connections_office365_name string = 'office365'
// アプリ名の変数・値を変更
param workflows_copy_app_name string = '${app_environment}_copy_app'

resource connections_office365_name_resource 'Microsoft.Web/connections@2016-06-01' = {
  name: connections_office365_name
  location: 'japaneast'
  kind: 'V1'
  properties: {
    displayName: '「Outlookメールアドレス」'
    statuses: [
      {
        status: 'Connected'
      }
    ]
    customParameterValues: {
    }
    nonSecretParameterValues: {
    }
    createdTime: '2022-10-18T03:00:22.4616241Z'
    changedTime: '2022-10-18T03:00:28.2968823Z'
    api: {
      name: connections_office365_name
      displayName: 'Office 365 Outlook'
      description: 'Microsoft Office 365 is a cloud-based service that is designed to help meet your organization\'s needs for robust security, reliability, and user productivity.'
      iconUri: 'https://connectoricons-prod.azureedge.net/releases/v1.0.1599/1.0.1599.3017/${connections_office365_name}/icon.png'
      brandColor: '#0078D4'
      id: '/subscriptions/「サブスクリプションID」/providers/Microsoft.Web/locations/japaneast/managedApis/${connections_office365_name}'
      type: 'Microsoft.Web/locations/managedApis'
    }
    testLinks: [
      {
        // ハードコーディングの修正
        // また、リソースグループはパラメータ変数を使用
        requestUri: '${environment().gallery}:443/subscriptions/「サブスクリプションID」/resourceGroups/${resource_group}/providers/Microsoft.Web/connections/${connections_office365_name}/extensions/proxy/testconnection?api-version=2016-06-01'
        method: 'get'
      }
    ]
  }
}

// 変更(original -> copy)
resource workflows_copy_app_name_resource 'Microsoft.Logic/workflows@2017-07-01' = {
  // 変更(original -> copy)
  name: workflows_copy_app_name
  location: 'japaneast'
  properties: {
    state: 'Enabled'
    definition: {
      '$schema': 'https://schema.management.azure.com/providers/Microsoft.Logic/schemas/2016-06-01/workflowdefinition.json#'
      contentVersion: '1.0.0.0'
      parameters: {
        '$connections': {
          defaultValue: {
          }
          type: 'Object'
        }
      }
      triggers: {
        '繰り返し': {
          recurrence: {
            frequency: 'Minute'
            interval: 60
            startTime: '2030-03-24T15:00:00Z'
          }
          evaluatedRecurrence: {
            frequency: 'Minute'
            interval: 60
            startTime: '2030-03-24T15:00:00Z'
          }
          type: 'Recurrence'
        }
      }
      actions: {
        'メールの送信_(V2)': {
          runAfter: {
          }
          type: 'ApiConnection'
          inputs: {
            body: {
              Body: '<p>test</p>'
              Importance: 'Normal'
              Subject: 'test'
              To: '「Outlookのアドレス」'
            }
            host: {
              connection: {
                name: '@parameters(\'$connections\')[\'office365\'][\'connectionId\']'
              }
            }
            method: 'post'
            path: '/v2/Mail'
          }
        }
      }
      outputs: {
      }
    }
    parameters: {
      '$connections': {
        value: {
          office365: {
            connectionId: connections_office365_name_resource.id
            connectionName: 'office365'
            id: '/subscriptions/「サブスクリプションID」/providers/Microsoft.Web/locations/japaneast/managedApis/office365'
          }
        }
      }
    }
  }
}

```

以上でBicepファイルの作成は完了です。


### デプロイ


ここからはデプロイに入っていきます。

デプロイするにあたってはコードで指定したパラメータを指定します。
- 今回はdev環境にデプロイすると想定し、app-environment=devを指定。（つまり、dev_copy_appという名前のアプリ名になるはず）
- デプロイするリソースグループは先ほど作成したcopy_app_groupとしたいので、resource_group=copy_app_groupを指定。

これらを踏まえ、実行するコマンドは以下の通りです。
(※--resource-groupのcopy_app_groupはcopy_app_groupにデプロイするということ。resource_group=copy_app_groupはコード内のresource_groupにcopy_app_groupを代入するという意味であり、両方必要なことに注意してください)

```bash
az deployment group create --resource-group copy_app_group --template-file app.bicep --parameters app_environment=dev resource_group=copy_app_group
```

JSON形式の値が返ってくれば成功となります。
実際にポータル上で確認してみましょう。

![スクリーンショット 2022-10-18 131809.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2883992/ac990d1b-e1f5-c2f9-71bb-f942af1fcce2.png)

copy_app_groupというリソースグループ内に「dev_copy_app」というロジックアプリのリソースとoffice365接続用のリソースが作成できていることが確認できました。


つづいて、Logic デザイナーの画面。大枠はよさそうですが、outlook側の認証が原因でエラーになってます...

![スクリーンショット 2022-10-18 131809.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2883992/e79de65d-5508-cb3f-5538-d68ea6c33cc0.png)

ここについてはしょうがないと割り切り、新規追加から認証してあげます（もしかしたら、上手な方法が別にあるかもしれないです。）
すると下記画像の通り、きちんと前の内容と同様「test」という件名・本文を送るような設定がされており、再現されていたことを確認できました。
（ちなみに一度認証してあげれば再度デプロイした時には認証エラーとならなくなりました。）

![スクリーンショット 2022-10-18 131809.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2883992/f4c6337a-ae93-9a09-cc5a-71451c919ba3.png)

以上、一部手動部分もありましたが、基本的にBicepのコードから、インフラの構築を体験することができました。

最後に作成したリソースグループを削除するため、下記のコードを実行し、クリーンアップしたいと思います。
```bash
az group delete -n copy_app_group
```

## まとめ
今回はBicepを使用し、Azure Logic Appsをデプロイしてみました。
また、Bicepの記法がわからない際に、いったんポータルで作成してみて、ポータル画面からARMテンプレートを確認。Bicepにデコンパイルする方法も試しました。

ここまでお読みいただきありがとうございました。ぜひ、参考にしてみてください。