【Github Actions】Gitに上げている静的WebサイトをCommit時にGitHub ActionsでAzure Storageに自動デプロイする

機会あってUnityで制作中のWebGLアプリをAzureのBlobStorageに入れて管理することになりました。しかし、開発中にいちいち格納するの面倒くさいな…という思いから自動デプロイするワークフローを構築することを決断。以下の記事にチャレンジしました。

https://learn.microsoft.com/ja-jp/azure/storage/blobs/storage-blobs-static-site-github-actions?tabs=openid#code-try-2

こういうのをやりたい
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/477084/dd59c430-09bd-be15-8586-e667f9d2ba18.png)

とはいえ、初心者の私に読んだだけ直ぐ理解するのは厳しい内容だったので、私なりの理解・解釈を交えて解説しながら手順を説明していきます。

# はじめに

### 前提
この記事は非エンジニアの私が経験した苦しみを他者に味わってほしくない、もっとわかりやすく仕組みを知れたらいいなという思いで作成したものです。認識や進め方が非効率的で誤りがあるかもしれません。その際には容赦なくご指摘頂けると幸いです。

### 動作環境
![Windows11 22H2 OSビルド22621.2861](https://img.shields.io/badge/Windows11-22H2_OSビルド_22621.2861-<COLOR>)
![Azure CLI 2.55.0](https://img.shields.io/badge/AzureCLI-2.55.0-<COLOR>)
![VScode 1.85.1](https://img.shields.io/badge/VisualStudioCode-1.85.1-<COLOR>)

# 前提条件を整える
Azureアカウント・サブスクリプションとGithubアカウント、格納するコンテンツの作成は省略します。
### 格納先のStorageとAzure CDNを作る
[元の記事](https://learn.microsoft.com/ja-jp/azure/storage/blobs/storage-blobs-static-site-github-actions?tabs=openid#code-try-2)の指示に従い、格納先とAzureCDNを作っておきます。
Storageはわかるのですが、AzureCDNを使う目的が余りわかっていません…
AzureCDN自体はWebサイトやビデオといったコンテンツをユーザー近くのサーバにキャッシュするサービスなので、GitHub連携の際に使われるんじゃないんでしょうか（想像）。

https://learn.microsoft.com/ja-jp/azure/cdn/cdn-create-a-storage-account-with-cdn

上記記事、**「ストレージ アカウントの Azure CDN を有効にする」** まで進めましょう。

次にVScodeのコマンドラインでAzure CLIを使うため以下を参考にAzure CLIをインストール。

https://learn.microsoft.com/ja-jp/cli/azure/install-azure-cli-windows?tabs=azure-cli

インストールしたらVScodeにMicrosoftアカウントを紐づけておきましょう。
VScodeの左下にある人間のアイコンをクリックして **「クラウドの変更を有効にします」** を押すとMicrosoftアカウントへのログインができます。

![スクショ.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/477084/5284d668-57cc-3648-6ff1-e2ae50416436.png)

# OpenID Connectを使った認証資格情報を作成する
Microsoft Entra アプリを作成しGitHubへ短期間のトークンを使って認証する仕組みをくみ上げます。GitHubにはOpenID Connectという仕組みがあるようで、クラウドプロバイダとセキュアに認証できるようですね。

https://docs.github.com/ja/actions/deployment/security-hardening-your-deployments/about-security-hardening-with-openid-connect#example-subject-claims

コマンドラインツールで以下のコードを打ち、アプリケーションを作成します。Microsoft EntraはもともとAzure Active Directory (AD) という名前だった名残かadという文字が見えますね。myAppの部分はお好みの名前に変えてもOKです。
```Azure CLI:Azure CLI
az ad app create --display-name myApp
```
JSON形式で作成したアプリの情報が並びますが、`appid`を控えておきましょう。

次にサービスプリンシパルを作成します。
説明が難しいのですが、要はMicrosoft Entra内で作成したアプリのID・パスワード認証を委任する役割？のようです。次のコードを打ちましょう。

```Azure CLI:Azure CLI
az ad sp create --id $appId
```

このコードを打つとまたしても大量のJSON形式の情報が並びますが、その中に`appid`以外にもう一つ同じ桁数の`id`と、`appOwnerOrganizationId`が生成されるので控えておきましょう。

余談：`appOwnerOrganizationId`は前のバージョンだと`appOwnerTenantId`とされていたようですね。[元の記事](https://learn.microsoft.com/ja-jp/azure/storage/blobs/storage-blobs-static-site-github-actions?tabs=openid#code-try-2)ではそう表記されているのでご注意を。

https://github.com/Azure/azure-cli/issues/22977

続いてサブスクリプションとオブジェクト別にユーザーのロールを割り当てます。
`$resourceGroupName`にストレージアカウントを置いたリソースグループの名前を、
`$subscriptionId`に作成したサブスクリプションのID（AzurePortalから確認）を、
`$assigneeObjectId`に一つ前に控えた`id`を入れて以下コードを実行しましょう。

```Azure CLI:Azure CLI
az role assignment create --role contributor --scope /subscriptions/$subscriptionId/resourceGroups/$resourceGroupName --subscription $subscriptionId --assignee-object-id  $assigneeObjectId --assignee-principal-type ServicePrincipal
```
この後[元の記事](https://learn.microsoft.com/ja-jp/azure/storage/blobs/storage-blobs-static-site-github-actions?tabs=openid#code-try-2)では以下のコードをAzure CLIで打ち込みますが、私の場合は「実行結果」にあるようにエラーが発生してしまいました。
```Azure CLI:Azure CLI
az rest --method POST --uri 'https://graph.microsoft.com/beta/applications/<APPLICATION-OBJECT-ID>/federatedIdentityCredentials' --body '{"name":"<CREDENTIAL-NAME>","issuer":"https://token.actions.githubusercontent.com","subject":"repo:organization/repository:ref:refs/heads/main","description":"Testing","audiences":["api://AzureADTokenExchange"]}'
```

```実行結果:実行結果
Bad Request({"error":{"code":"BadRequest","message":"Write requests (excluding DELETE) must contain the Content-Type header declaration.","innerError":{"date":"2023-12-26T19:54:51","request-id":"xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx","client-request-id":"xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"}}})
```

従ってここからは下記記事の **「フェデレーション ID 資格情報をアプリに構成する GitHub Actions」** を参考にPortalからサービスプリンシパルを作成しフェデレーション資格情報を追加します。

https://learn.microsoft.com/ja-jp/entra/workload-id/workload-identity-federation-create-trust?pivots=identity-wif-apps-methods-azp

先ほど作成したアプリケーション（この例では"myApp"）をAzurePortal上から検索。
[管理から] [証明書とシークレット] を選択し、[フェデレーション資格情報] タブを選択してから、[資格情報の追加]をクリックしましょう。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/477084/a0252197-7da9-273e-34b1-64c5e4cb3f55.png)


[フェデレーション資格情報シナリオ]を[Azure リソースをデプロイする GitHub Action]に選択、組織とリポジトリは今回デプロイ元にするGitHubのリモートリポジトリの情報を入力してください。エンティティ型は今回はBranchへのcommitをトリガーとするので、「Branch」としましょう。そうすると下の入力欄にどのBranchでトリガーするか聞かれるので、今回はmainにして開発用ブランチから本番移行する時にデプロイする仕様にします。最後にこの資格情報自体の[名前]を書いてクレデンシャルの発行完了です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/477084/39c0f103-5451-4c71-ad23-1e6c252580f8.png)

作成したアプリケーションのOverview = 概要を確認しましょう。
GitHubと連携する時に必要な各種IDが記載されています。
使用しているサブスクリプションのIDとともに、コピーして控えておきましょう。
- アプリケーション（クライアント）ID
- ディレクトリ（テナント）ID
- サブスクリプションID

# GitHub SecretにAzure側の連携先情報を格納する
以下の記事を参考にGitHubの公式サイトからデプロイ対象のリポジトリに入り、Actionsを設定します。

https://learn.microsoft.com/ja-jp/azure/developer/github/connect-from-azure?tabs=azure-portal%2Cwindows#use-the-azure-login-action-with-openid-connect

Settings→Secrets and variables→Actionsの順で開きましょう

![スクリーンショット 2023-12-30 13.09.37.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/477084/1f797d36-eafa-af10-2de3-421beec96ea5.png)

開いたら緑色の「New repository secret」を押しNameとSecretを入力しておきます。
上記画像の通りNameに大文字で定数名を、SecretにさっきAzurePortalで確認した値を入れておきましょう。

# Azureログイン→デプロイ→ログアウトする処理を書く

[元の記事](https://learn.microsoft.com/ja-jp/azure/storage/blobs/storage-blobs-static-site-github-actions?tabs=openid#code-try-2)の **"ワークフローを追加する"** を参考にして、対象のリポジトリのページから **"Actions"** を選択。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/477084/42fd8bb8-b5c2-6846-eb35-6336308f9572.png)

青字で光る **”set up a workflow yourself”** を選びます。

![スクリーンショット 2023-12-30 19.51.22.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/477084/b5dbfe6d-e68f-b4e6-ad76-a7279f0b6484.png)

コードを書け！と言われるので、[元の記事](https://learn.microsoft.com/ja-jp/azure/storage/blobs/storage-blobs-static-site-github-actions?tabs=openid#code-try-2)にあったコードを参考にした以下を貼付け。
一部大文字での＜＞部分は今回作ったリソースにあわせ値を格納してください。

```YAML:YAML
name: CI with OpenID Connect

on:
    push:
        branches: [ main ]

permissions:
      id-token: write
      contents: read

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v3
    - uses: azure/login@v1
      with:
       client-id: ${{ secrets.AZURE_CLIENT_ID }}
       tenant-id: ${{ secrets.AZURE_TENANT_ID }}
       subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}

    - name: Upload to blob storage
      uses: azure/CLI@v1
      with:
        inlineScript: |
            az storage blob upload-batch --account-name <STORAGE_ACCOUNT_NAME> --overwrite --auth-mode key -d '$web' -s .
    - name: Purge CDN endpoint
      uses: azure/CLI@v1
      with:
        inlineScript: |
           az cdn endpoint purge --content-paths  "/*" --profile-name "CDN_PROFILE_NAME" --name "CDN_ENDPOINT" --resource-group "RESOURCE_GROUP"

  # Azure logout
    - name: logout
      run: |
            az logout
      if: always()
```

（--overwirteという記述を私の方で追加しています。そうでないとすでにAzureStorage側に同名のフォルダが有っても上書き保存してくれません。[この記事](https://github.com/Azure/azure-cli/issues/21779)によるとどうやらAzure CLIのバージョンが原因のようです。）

# 動作確認
これでVisual Studio codeからmainブランチにプッシュするなどしてみてください。
Azureストレージにデータが格納されればOKです。
ワークロードがいつ起動したかどうかは、GitHubの"Actions"からも確認可能です。