GASのWebアプリケーションをGithubActions & Claspでデプロイする

#はじめに
こんにちは！
最近なにかと話題のGAS.
実はWebアプリケーションとして公開できるのですが，
スプレッドシートを簡易的なデータベースとして使えたり，環境変数も設定できて簡易的なアプリケーションでやりたいことは一通りできます！(自分は社内でGAS製Slackアプリをいくつか運用してます．)
**そしてなんといっても無料なところが素晴らしいです！**
![gas_web_app_menu.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/366323/d1fdee6f-ef6e-5a35-22c0-5ccb8e60eec8.png)



今回は,そんな**GAS製WebアプリをGithubへのpushをトリガーにデプロイする方法**を紹介します!

## 解決できるお悩み

- GASのスクリプトエディタがリッチでないから，ローカルで好きなエディタで作業して，編集後にブラウザ上のGASエディタにコピペしてるけど,めんどくさい
- Webアプリをデプロイするにいろいろ画面をぽちぽちしないといけないのがめんどくさい

## 今回構築するシステムの構成図
![system.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/366323/952e5571-89d7-4d78-577d-2b61db219f92.png)


# 使うもの
- Github Actions
- Clasp : Google製のGAS用コマンドラインツール!これがあって本当に助かった．．

# 大まかな流れ
-- 準備
1. 手元でclaspの認証情報などを集める
2. 1で集めた情報をgithub secretsに設定する

-- ここからGithubActions
3. secretsの値を埋め込んで認証用jsonを作成・配置する
4. 3のJsonを元にclaspコマンドでdeployする！

# 手順　全部で6ステップ！

## その１:Claspのインストール & ログイン
まずは今回大活躍するツールClaspを手元の環境にインストール．
https://github.com/google/clasp

```sh
npm install -g @google/clasp
```

インストールが完了したら，以下のコマンドを使ってログインしてください．この時使用するアカウントは，今回デプロイしたいGASを所有しているアカウントにしてください

```sh
clasp login
```

ログイン完了すると，ホームディレクトリに`.clasprc.json`が作成されます．このファイルに記載されている情報をあとで使います．


## その2:Google Apps Script APIを有効にする

以下にアクセスしてGoogle Apps Script APIをONに設定します

https://script.google.com/home/usersettings
![api_setting.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/366323/38361458-c937-3522-5572-79965159a7ae.png)

## その3:作成済みのGASのコードを手元にcloneする
カレントディレクトリにGASのコードと設定ファイル(appsscript.jsonと.clasp.json)がcloneされるので，すでにレポジトリ管理している場合は適宜ディレクトリ移動してください.

```sh
clasp clone "スクリプトID"
```
スクリプトIDはGASエディタのURLの次の部分のことです

```
https://script.google.com/d/**ここ**/edit?mid=hoge&uiv=fuga
```

自分はsrcディレクトリ配下にコードをおいたのでこんな感じになりました．この時点で手元に元々あったGASコードは破棄してもいいかもしれません．手元の方が最新であればsrc/index.jsを上書きしましょう．

```
src
 └── appsscript.json
 └── .clasp.json
 └── index.js(このファイル名はGASエディタで設定されたもの)
```
## その4:Github Secretsに必要な情報を登録していく．
ここでActionsでclaspを使うための準備をします．
secretsはレポジトリ のsettingsのSecretsタブから設定できます！
![secrets.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/366323/ac8f71d4-d65d-87c3-89be-3120ad9d70fa.png)
今回設定する必要のあるシークレットは全部7つです．
骨の折れる作業ですが，ここまできたらもう一息です！
以下がシークレットに保存したい内容です
1. アクセストークン
2. IDトークン
3. リフレッシュトークン
4. クライアントID
5. クライアントシークレット
6. スクリプトID
7. デプロイメントID

それぞれのActionsでの役割は以下です
1~5:`clasp login`するため
6: `clasp push`するため
7: `clasp deploy --deploymentId $DEPLOYMENT_ID`デプロイして最新バージョンを公開するため

### 1~5 をログイン時に生成された`.clasprc.json`から抜き出す
以下などでファイルの中身を確認してください．

```
vim ~/.clasprc.json
```

おそらく以下のようなjsonになっていると思うので該当する値を一個ずつシークレットに登録してください．
今回Actions用yamlのサンプルでは次のような名前で登録しているのでそれに合わせていただくと楽かもしれません

.clasprc.jsonの中身

```json

{
  "token": {
    "access_token": "アクセストークン",
    "scope": "hoge",
    "token_type": "Bearer",
    "id_token": "IDトークン",
    "expiry_date": 1606063959625,
    "refresh_token": "リフレッシュトークン"
  },
  "oauth2ClientSettings": {
    "clientId": "クライアント",
    "clientSecret": "クラインアントシークレット",
    "redirectUri": "http://localhost"
  },
  "isLocalCreds": false
}

```
サンプルyamlで使うシークレットの名前
secrets.HOGEのHOGEの部分

``` yaml
    env:
      ACCESS_TOKEN: ${{ secrets.ACCESS_TOKEN }}
      ID_TOKEN: ${{ secrets.ID_TOKEN }}
      REFRESH_TOKEN: ${{ secrets.REFRESH_TOKEN }}
      CLIENT_ID: ${{ secrets.CLIENT_ID }}
      CLIENT_SECRET: ${{ secrets.CLIENT_SECRET }}
      SCRIPT_ID: ${{ secrets.SCRIPT_ID }}
      DEPLOYMENT_ID: ${{ secrets.DEPLOYMENT_ID }}
```

### 6 をclone時に生成された.clasp.jsonから抜き出す
こちらも以下のようなjson担っていると思うので値をシークレットに保存してください．サンプルでは`SCRIPT_ID`で保存してます．

```json
{"scriptId":"スクリプトID"}
```

### 7をGASエディタ上から取得する
デプロイメントIDはGASをWebアプリケーションとして公開した際のURLから取得できます．
GASエディタのメニューで，
公開　-> ウェブアプリケーションとして公開
を選択すると次のダイアログが出現すると思います．
赤枠部分のURLが公開URLです．
![deploymentid.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/366323/985912ef-dd57-9b3b-0bec-1e9034068578.png)

デプロイメントIDはこのURLのうちの以下の部分です

```
https://script.google.com/macros/s/**ここ**/exec
```
これもシークレットに保存してください
サンプルでは`DEPLOYMENT_ID`で保存してます．

これでシークレット登録作業は終わりです．お疲れ様でした．

##その5:Github Actionsで使用するshellscriptを追加する
いよいよ大詰めです．
Actionsの中で先ほどのシークレットの情報をかき集めて再びjsonファイルにしてあげる必要があります．
そのために以下のshellscriptを適当な位置に配置してください．
サンプルではsrcと同じ階層にdeploymentディレクトリをつくり，その中に配置しています

### .clasprc.json生成スクリプト

setup.sh

```sh
#!/bin/sh

LOGIN=$(cat <<-END
    {
        "token": {
            "access_token": "$ACCESS_TOKEN",
            "refresh_token": "$REFRESH_TOKEN",
            "scope": "https://www.googleapis.com/auth/cloud-platform https://www.googleapis.com/auth/drive.file https://www.googleapis.com/auth/service.management https://www.googleapis.com/auth/script.deployments https://www.googleapis.com/auth/logging.read https://www.googleapis.com/auth/script.webapp.deploy https://www.googleapis.com/auth/userinfo.profile openid https://www.googleapis.com/auth/userinfo.email https://www.googleapis.com/auth/script.projects https://www.googleapis.com/auth/drive.metadata.readonly",
            "token_type": "Bearer",
            "id_token": "$ID_TOKEN",
            "expiry_date": 1595752666211
        },
        "oauth2ClientSettings": {
            "clientId": "$CLIENT_ID",
            "clientSecret": "$CLIENT_SECRET",
            "redirectUri": "http://localhost"
        },
        "isLocalCreds": false
    }
END
)

echo $LOGIN > ~/.clasprc.json
```

### .clasp.json生成スクリプト

setup_claspjson.sh

```sh
#!/bin/sh

CLASPJSON=$(cat <<-END
    {
        "scriptId": "$SCRIPT_ID"
    }
END
)

echo $CLASPJSON > ~/.clasp.json
```

ここまできたらもう終わったも同然です！
最後にGithub Actionsの設定ファイルを書きます！

#その6:Github Actions用yamlを追加する
レポジトリに.github/workflowsを作成し
その中に以下のdeploy.yamlを配置してください


```yaml

name: Deploy

on:
  push:
　　 #branches: 自分は本番環境用yamlにはこちらを追加して，masterマージのみで反応するようにしてます．
    # - master
    paths:
      - 'src/index.js' #pushを検知したいパスを書く

jobs:
  deploy:
    runs-on: ubuntu-latest

    env:
      ACCESS_TOKEN: ${{ secrets.ACCESS_TOKEN }}
      ID_TOKEN: ${{ secrets.ID_TOKEN }}
      REFRESH_TOKEN: ${{ secrets.REFRESH_TOKEN }}
      CLIENT_ID: ${{ secrets.CLIENT_ID }}
      CLIENT_SECRET: ${{ secrets.CLIENT_SECRET }}
      SCRIPT_ID: ${{ secrets.SCRIPT_ID }}
      DEPLOYMENT_ID: ${{ secrets.DEPLOYMENT_ID }}

    steps:
      - name: Setup repository
        uses: actions/checkout@v2

      - name: Setup Node
        uses: actions/setup-node@v1
        with:
          node-version: '12'

      - name: Install Clasp
        run: npm install -g @google/clasp

      - name: Generate ~/.clasprc.json
        run: |
          bash deployment/setup.sh
      - name: Generate ~/.clasp.json
        run: |
          bash deployment/setup_claspjson.sh
      - name: Push
        run: |
          cd src
          clasp push --force
      - name: DEPLOY
        run: |
          cd src
          clasp deploy --deploymentId $DEPLOYMENT_ID
```

お疲れ様でした！以上で作業は終わりです！
あとは上のファイルで指定したパスに変更を加えてpushすれば
デプロイされるはずです！

#最後に

##運用上の注意
ステージングと本番アプリを分けて運用する場合，
異なるコードをそれぞれのアプリにデプロイしたいと思います．
その場合，GASアプリごとにSCRIPT_IDとDEPLOYMENT_IDが異なるので,シークレットのうちこの２つをアプリケーションごとに用意してください．残りはGmailアカウントが同じなら共有できます！

![yaml.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/366323/fccdbe35-ee3e-c308-0b80-eae6528e8db7.png)



以下は例えば本番環境向けのyamlを書く場合は以下のように別シークレットを用意してください

```
SCRIPT_ID: ${{ secrets.PROD_SCRIPT_ID }}
DEPLOYMENT_ID: ${{ secrets.PROD_DEPLOYMENT_ID }}
```

##clasp deploy コマンドの注意
上記のサンプルyamlを見てもらうと分かるようにdeployコマンドは以下のように**optionをつけてないと最新のアプリケーションが公開されません．**

```
clasp deploy --deploymentId $DEPLOYMENT_ID
```
clasp deployは新しいバージョンを作るだけで公開はしてくれないのです．
自分は頭のなかでdepoy==公開だと思っていたのでここでしばらくハマってしまいました．
違いまとめ

```
clasp push: コードをウェブのGASエディタに反映する(バージョンを作るとは言ってない)
clasp deploy:新しいバージョンを作る（公開するとは言ってない）
clasp deploy --deploymentId $DEPLOYMENT_ID:最新バージョンを公開する
```

以上で本当に最後です．長文お付き合いありがとうございました！

#参考
- [GASの過剰に強強な環境構築【新歓ブログリレー2020 55日目】](https://trap.jp/post/1069/)
- [GAS のGoogle謹製CLIツール clasp
](https://qiita.com/HeRo/items/4e65dcc82783b2766c03)
- [Clasp deploy doesn't update the deployed version with Google Apps Script Web App](https://github.com/google/clasp/issues/63)
- [Backup Google Apps Script using Github Actions](https://www.ravsam.in/blog/backup-google-apps-script-using-github-actions/)
- [clasp公式](https://github.com/google/clasp)