GAS(Google Apps Script)の更新を行うカスタムアクション作った
こんにちは、株式会社TERASSでエンジニアをしているiwaseshiです。
作ったもの
提題の通りです。
開発のモチベーション
一番は「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にしようと思います)
Discussion