🟢

GAS(Google Apps Script)の更新を行うカスタムアクション作った

2023/09/01に公開

こんにちは、株式会社TERASSでエンジニアをしているiwaseshiです。

作ったもの

提題の通りです。
https://github.com/iwaseshi/clasp-deploy-action

開発のモチベーション

一番は「Custom Actions書いてみてー」というだけだったのですが、せっかくならそこそこ役に立つものを作りたいと思いました。

そこで目をつけたのが、claspを使用したGASへの自動デプロイです。

筆者は既にGithub Action上でClaspを使用したデプロイをしており、その処理の過程を一つのCustom Actionsに移行することにしました。

GASの自動デプロイ処理についてと気になっていたところ

Github Action上でclasp pushを使用する手法はクラスメソッドさんの記事で紹介されていました。

特徴的なのはclasp pushに必要な.clasprc.jsonはToken等のクレデンシャルを含んでいるため、Create clasprc.json箇所でsecretsから値を参照して都度生成するようになっていることです。

以下は実際に上記記事内で紹介されているActionの内容です。

name: CD (Build and deploy to production)

on:
  push:
    branches:
      - main
  workflow_dispatch:
jobs:
  deploy:
    runs-on: ubuntu-20.04
    timeout-minutes: 15
    steps:
      - name: Checkout
        uses: actions/checkout@v2
      - name: Setup Node.js 16.x
        uses: actions/setup-node@v2
        with:
          node-version: '16.x'
      - name: Install Clasp
        run: |
          npm init -y
          npm install clasp -g
      - name: Create clasprc.json
        run: |
          echo \{\"token\":\{\"access_token\":\"${{ secrets.ACCESS_TOKEN}}\",\"scope\":\"https://www.googleapis.com/auth/userinfo.email https://www.googleapis.com/auth/drive.metadata.readonly https://www.googleapis.com/auth/script.projects https://www.googleapis.com/auth/script.webapp.deploy https://www.googleapis.com/auth/logging.read openid https://www.googleapis.com/auth/userinfo.profile https://www.googleapis.com/auth/drive.file https://www.googleapis.com/auth/script.deployments https://www.googleapis.com/auth/service.management https://www.googleapis.com/auth/cloud-platform\",\"token_type\":\"Bearer\",\"id_token\":\"${{ secrets.ID_TOKEN }}\",\"expiry_date\":1620870307822,\"refresh_token\":\"${{ secrets.REFRESH_TOKEN }}\"\},\"oauth2ClientSettings\":\{\"clientId\":\"${{ secrets.CLIENTID }}\",\"clientSecret\":\"${{ secrets.CLIENTSECRET }}\",\"redirectUri\":\"http://localhost\"\},\"isLocalCreds\":false\} > ~/.clasprc.json
      - name: Deploy
        run: |
          clasp push

今めっちゃ横にスクロールしませんでした????

そうです、Create clasprc.jsonの作成処理をymlファイルに書くとかなり読むのが辛いです。

ぶっちゃけコピペすればええやん…とは思いますが、clasprc.jsonに記述する項目が増えたりすることがないこともないかもなので、せっかくなら見通しの良い処理を書けたらいいなぁと思ってしまいます。(※決して元の記事を批判したい意図はありません

また、クラスメソッドさん以外にもQiita等に記事は上がってますが、筆者が目を通した範囲だと上記の記事と同じ手法をとっており、みんな同じことしてんなら需要あるかしら…と感じたのでこれをCustom Actionsにすることにしました。

さっそく作る

Custom Actionsを作るには、以下のようにGitリポジトリのルートにaction.ymlを作成しこちらに処理の流れを記載すればOKです。

clasp-deploy-action
|
|-- action.yml

action.yml

name: "Clasp Deploy"
description: "Custom action to deploy using clasp"
inputs:
  ACCESS_TOKEN:
    description: "Access Token"
    required: true
  ID_TOKEN:
    description: "ID Token"
    required: true
  REFRESH_TOKEN:
    description: "Refresh Token"
    required: true
  CLIENTID:
    description: "Client ID"
    required: true
  CLIENTSECRET:
    description: "Client Secret"
    required: true
runs:
  using: "composite"
    - name: Create clasprc.json
      run: |
        echo \{\"token\":\{\"access_token\":\"${{ inputs.ACCESS_TOKEN }}\",\"scope\":\"https://www.googleapis.com/auth/userinfo.email https://www.googleapis.com/auth/drive.metadata.readonly https://www.googleapis.com/auth/script.projects https://www.googleapis.com/auth/script.webapp.deploy https://www.googleapis.com/auth/logging.read openid https://www.googleapis.com/auth/userinfo.profile https://www.googleapis.com/auth/drive.file https://www.googleapis.com/auth/script.deployments https://www.googleapis.com/auth/service.management https://www.googleapis.com/auth/cloud-platform\",\"token_type\":\"Bearer\",\"id_token\":\"${{ inputs.ID_TOKEN }}\",\"expiry_date\":1620870307822,\"refresh_token\":\"${{ inputs.REFRESH_TOKEN }}\"\},\"oauth2ClientSettings\":\{\"clientId\":\"${{ inputs.CLIENTID }}\",\"clientSecret\":\"${{ inputs.CLIENTSECRET }}\",\"redirectUri\":\"http://localhost\"\},\"isLocalCreds\":false\} > ~/.clasprc.json
      shell: bash

ぶっちゃけミニマムだとこれだけでもいいです。

あとは先ほどのCDを記述しているymlでこのclasp-deploy-actionを参照するようにすればOKです。

name: CD (Build and deploy to production)

on:
  push:
    branches:
      - main
  workflow_dispatch:
jobs:
  deploy:
    runs-on: ubuntu-20.04
    timeout-minutes: 15
    steps:
      - name: Checkout
        uses: actions/checkout@v2
      - name: Setup Node.js 16.x
        uses: actions/setup-node@v2
        with:
          node-version: '16.x'
      - name: Install Clasp
        run: |
          npm init -y
          npm install clasp -g
      - name: Create clasprc.json
        uses: xxx/clasp-deploy-action@main
        with:
          ACCESS_TOKEN: ${{ secrets.ACCESS_TOKEN }}
          ID_TOKEN: ${{ secrets.ID_TOKEN }}
          REFRESH_TOKEN: ${{ secrets.REFRESH_TOKEN }}
          CLIENTID: ${{ secrets.CLIENTID }}
          CLIENTSECRET: ${{ secrets.CLIENTSECRET }}
      - name: Deploy
        run: |
          clasp push

これで一旦CD側ではめっちゃ横にスクロールしなくてよくなりました。

ですが、まだaction.yml側では解消されていませんので、clasprc.json作成処理を別定義してその処理をaction.ymlで呼び出すようにしてみましょう。

今回は、TypeScriptで処理を書いてみます。

TypeScriptで処理を記述する。

一旦必要なファイルを足しました。

clasp-deploy-action
|
|-- action.yml
|-- index.ts
|-- package.json
|-- package-lock.json

さっそくindex.tsにclasprc.json作成処理を書いてみましょう。

import fs from "fs";
import os from "os";
import path from "path";

const scopes = [
    "https://www.googleapis.com/auth/userinfo.email",
    "https://www.googleapis.com/auth/drive.metadata.readonly",
    "https://www.googleapis.com/auth/script.projects",
    "https://www.googleapis.com/auth/script.webapp.deploy",
    "https://www.googleapis.com/auth/logging.read",
    "openid",
    "https://www.googleapis.com/auth/userinfo.profile",
    "https://www.googleapis.com/auth/drive.file",
    "https://www.googleapis.com/auth/script.deployments",
    "https://www.googleapis.com/auth/service.management",
    "https://www.googleapis.com/auth/cloud-platform"
];

const data = {
    token: {
        access_token: process.env.ACCESS_TOKEN,
        scope: scopes.join(" "),
        token_type: "Bearer",
        id_token: process.env.ID_TOKEN,
        expiry_date: 1620870307822,
        refresh_token: process.env.REFRESH_TOKEN,
    },
    oauth2ClientSettings: {
        clientId: process.env.CLIENTID,
        clientSecret: process.env.CLIENTSECRET,
        redirectUri: "http://localhost",
    },
    isLocalCreds: false,
};

fs.writeFileSync(path.join(os.homedir(), ".clasprc.json"), JSON.stringify(data, null, 2));

作成されるclasprc.jsonの全容がわかりやすくなりました。

これなら仮にjsonの内容が変わるようなことがあっても、迷わずに変更をとりこめそうです。

次にindex.tsを使用できるようにaction.yml側も変更します。

name: "Clasp Deploy"
description: "Custom action to deploy using clasp"
inputs:
  ACCESS_TOKEN:
    description: "Access Token"
    required: true
  ID_TOKEN:
    description: "ID Token"
    required: true
  REFRESH_TOKEN:
    description: "Refresh Token"
    required: true
  CLIENTID:
    description: "Client ID"
    required: true
  CLIENTSECRET:
    description: "Client Secret"
    required: true
runs:
  using: "composite"
  steps:
    - name: Install dependencies
      run: npm install
      shell: bash
    - name: Set environment variables
      run: |
        echo "ACCESS_TOKEN=${{ inputs.ACCESS_TOKEN }}" >> $GITHUB_ENV
        echo "ID_TOKEN=${{ inputs.ID_TOKEN }}" >> $GITHUB_ENV
        echo "REFRESH_TOKEN=${{ inputs.REFRESH_TOKEN }}" >> $GITHUB_ENV
        echo "CLIENTID=${{ inputs.CLIENTID }}" >> $GITHUB_ENV
        echo "CLIENTSECRET=${{ inputs.CLIENTSECRET }}" >> $GITHUB_ENV
      shell: bash
    - name: Create clasprc.json
      run: npx ts-node ${{ github.action_path }}/index.ts
      shell: bash

特徴的な変更点を見てみましょう。

まずは、inputsで受け取った各種クレデンシャルを環境変数から参照できるように$GITHUB_ENVに設定しなおしている箇所です。

これはindex.ts中のprocess.env.~~によるアクセスを可能にさせるためです。

index.tsの実行時引数で渡してもいいのですが、そうするとrunの箇所がまた結局横長になってしまうのでこの手法をとっています。

次にts-nodeでindex.tsを直接実行できるようにしています。

とりあえず実行できるか確認したかったので直に呼んでいますが、ts-nodeをしようするためにあらかじめnpm installでts-nodeを引いてこれるようにpackage.jsonに依存関係を定義しておきましょう。

package.json

{
  "dependencies": {
    "ts-node": "^9.1.1",
    "typescript": "^4.1.5"
  },
  "devDependencies": {
    "@types/node": "^20.5.7"
  }
}

CD側は先ほどと変更はありません。

リリースタグを切っていれば、@mainではなくバージョンを指定して実行できます。
筆者は一応v0.0.1で切っていますが、他のaction用のイメージに合わせるならv1から切っていくのがいいかもしれません。

これでとりあえず当初の目的は達成できました。
本来ならビルドしたJSを実行して〜〜とかしたほうがいいとは思うのですが、一旦はここまで。
(↑もしやったらリリースもv1にしようと思います)

Terass Tech Blog

Discussion