GASに入門して, Claspを使ったGit管理+Gitlab-CIを使ったCDまでやってみた
今冬、初めて雪が窓の外をちらついた日のことだ。
GASを使ってスプレッドシートを更新する業務の自動化を推進している同僚が、保守性・メンテナンス性が悪くて困っているという話をしていた。
「バージョン管理をしたらいいよ」というアドバイスが方々から飛んだ。私もそのアドバイスをしたうちの一人だった。そのとき、私はGASを触ったことが一度もなかった。
それから30分後、私はなぜかGASを触ることになっていた。何の偶然か、私はこのタイミングで突然ドライブの特定のディレクトリに存在する全てのスプレッドシートからあるカラムを取得する必要性に迫られたのだ。
GASに入門する
私はGASとは何かを検索したのちに、まずは適当なスプレッドシートを作成し、その上に初めてのGASを作成した。
エディタは簡単に開いた。作成したスプレッドシート上でTools -> Script editorとクリックするだけだった。
エディタ上には、Code.gs
という謎の拡張子のファイルに空の関数が用意されていた。私はおもむろに入門サイトからハローワールドをコピペする。
function myFunction() {
Logger.log('Hello, GAS')
}
Run
をクリックすると、画面下に実行ログが表示された。輝かしいこのコードこそ、私の初めてのGASコードである。
これで「GASも触ったことがないだなんて」と後ろ指を指される人生とはおさらばだ。1
もう少し実用的な例を実装してみよう。スプレッドシートを新たに作成してみたい。
調べたところ、スプレッドシートに値をまとめて入れるには、二次元配列が便利なようだ。
何かちょうどいい二次元配列はないかと脳内にスキャンをかけると、引っかかったのは私のお気に入りのアイドルグループだった。
/**
* @returns {string} spreadsheetId
*/
function generateFavoriteIdols() {
const spreadSheet = SpreadsheetApp.create('faborite-idols')
const idols = [
// Lengths of all arrays have to match.
['Joshima', 'Kokubun', 'Matsuoka', 'Nagase', ''], // TOKIO
['Ohno', 'Matsumoto', 'Aiba', 'Ninomiya', 'Sakurai'], // Arashi
]
spreadSheet.getActiveSheet().getRange(1, 1, 2, 5).setValues(idols)
return spreadSheet.getId()
}
ついでに、生成したスプレッドシートからアイドルの名前を読み取る関数も書いてみよう。
これができれば、私が業務で求められていた仕事はこなせることだろう。
/**
* @param {string} spreadSheetId
* @returns {string[][]}
*/
function readFavoriteIdols(spreadSheetId) {
const spreadsheet = SpreadsheetApp.openById(spreadSheetId)
return spreadsheet.getActiveSheet().getRange(1, 1, 2, 5).getValues()
.map(l => l.map(e => String(e)))
}
Claspを使ってGit管理する
仕事は済んだ。しかし私の胸には、一時間前に無責任にも放った「Gitでバージョン管理するといいよ」という自らの言葉がいつまでも刺さっていた。
私はブラウザを開いて、「どうかいい感じの何かが出てくれ」というぼやっとした祈りと共に、検索欄に「gas git 管理」と入力した。
無事にいくつかの検索結果を得た私は、Claspを使って先ほど作ったコードをGit管理することにした。
Claspのインストールと事前準備は下の記事を参考にした。
claspでGASのソースをGit管理
https://qiita.com/zaki-lknr/items/b4954c222c1c1db92caf
さっそく先ほどまで触っていたスクリプトエディタのURLからIDっぽいところをコピペしてきてclasp clone
してみると、無事コードをローカルに引っ張ってくることができた。
$ mkdir gas-sample && cd $_
gas-sample$ clasp clone ***
Warning: files in subfolder are not accounted for unless you set a '.claspignore' file.
Cloned 2 files.
└─ appsscript.json
└─ Code.js
Not ignored files:
└─ Code.js
└─ appsscript.json
Ignored files:
└─ .clasp.json
.gs
だった拡張子が、.js
になっている。これは触れてやらないのが気遣いというやつだったかもしれない。
いくつかのファイルを落とせたら、次はこれをgitで管理する。
ユーザ名とメールアドレスは何でもいいが、自分のものを使うのが無難だ。
gas-sample$ git init
gas-sample$ git config user.email 'me@example.com'
gas-sample$ git config user.name 'me'
gas-sample$ git add appsscript.json Code.js
gas-sample$ git commit -m 'initial commit'
ところで、.clasp.json
の中を見たところ、私が先ほどclasp clone
するときに使ったスクリプトIDが書いてあった。
これはCDの中で設定した方が後々便利そうなので、ここではGit管理しないことにした。
{"scriptId":"***"}
ここまででバージョン管理システムの導入が完了した。私の胸は歓喜に打ち震えている。
バージョン管理システムを導入したからには、何か修正を入れてみたい。
私はもう一つアイドルグループを追加することを思いついた。
const idols = [
// Lengths of all arrays have to match.
['Joshima', 'Kokubun', 'Matsuoka', 'Nagase', ''], // TOKIO
['Ohno', 'Matsumoto', 'Aiba', 'Ninomiya', 'Sakurai'], // Arashi
+ ['', '', '', '', ''], // SMAP
]
- spreadSheet.getActiveSheet().getRange(1, 1, 2, 5).setValues(idols)
+ spreadSheet.getActiveSheet().getRange(1, 1, 3, 5).setValues(idols)
return spreadSheet.getId()
const spreadsheet = SpreadsheetApp.openById(spreadSheetId)
- return spreadsheet.getActiveSheet().getRange(1, 1, 2, 5).getValues()
+ return spreadsheet.getActiveSheet().getRange(1, 1, 3, 5).getValues()
.map(l => l.map(e => String(e)))
できた。
さっそくこの修正をGitに入れて、デプロイしてみよう。
gas-sample$ git add Code.js
gas-sample$ git commit -m 'Add SMAP to idols'
gas-sample$ clasp push
はやる気持ちを抑えてブラウザでスクリプトエディタをリロードすると、無事修正が反映されていることが確認できた。
Gitlab CIを使って自動でデプロイする
人は、バージョン管理ができるようになったら、次はCI / CDを回したくなるものだ。
私も一人の矮小な人間にすぎないということだろう。次の瞬間、私はGitlabを開き、新たにリポジトリを作成していた。2
GitlabでClone with SSLの横に書かれたURLをコピーし、これをローカルのリポジトリでorigin
という名前に関連付ける。
masterブランチをoriginのmasterブランチに対してpushすれば、Gitlabが私のコードを管理してくれるようになる。
gas-sample$ git remote add origin git@gitlab.com:me/gas-sample.git
gas-sample$ git push -u origin master
ところで、Gitlabでは.gitlab-ci.yml
というファイルをコミットすると自動的にCI / CDを回してくれるようになる。
つまり、.gitlab-ci.yml
というファイルを作り、そこにclasp push
を実行するよう書くだけで、Gitlabに対してgit push
するとGASが自動的に更新されるようになるのだ。
とりあえずclasp push
するだけの.gitlab-ci.yml
を書こう。
認可情報とデプロイ先のスクリプトIDを環境変数へ逃がしたため、予めGitlabのリポジトリの設定 -> CI/CD -> Variablesに認可情報をCLASPRC_JSON
, スクリプトIDをSCRIPT_ID
として入力することが必要だ。3
stages:
- upload
upload:
stage: upload
image: node:10.23.2-alpine3.11
script:
# Never hard-code .clasprc.json, EVER
- cp $CLASPRC_JSON ~/.clasprc.json
- echo "{\"scriptId\":\"${SCRIPT_ID}\"}" > .clasp.json
- npx @google/clasp push
特に、認可情報(~/.clasprc.json
)の流出には気を付けなければならない。これは決してGitで管理してはならない情報だ。~/.clasprc.json
がログなど見えるところに残っていることに気がついたら、すぐにトークンを無効化しよう。
Apps with access to your account
https://myaccount.google.com/permissions?pli=1
これでCI / CDを実現することができる。
さっそくpushして、Gitlab上でパイプラインが流れるのを見てみよう。
gas-sample$ git add .gitlab-ci.yml
gas-sample$ git commit -m 'add .gitlab-ci.yml'
gas-sample$ git push
Gitlab CIの実行ログには、"Job succeeded"の美しい文字が並ぶ。
これでGitlabにpushするだけで全てが完結するようになった。
Executing "step_script" stage of the job script
00:22
$ cp $CLASPRC_JSON ~/.clasprc.json
$ echo "{\"scriptId\":\"${SCRIPT_ID}\"}" > .clasp.json
$ npx @google/clasp push
npx: installed 160 in 16.186s
└─ Code.js
└─ appsscript.json
Pushed 2 files.
Cleaning up file based variables
00:00
Job succeeded
ここまでくれば、unittestの自動実行やAltJSを使った開発だって簡単だ。
あの微妙に使い勝手の良いスクリプトエディタへの未練を捨て去り、普段使っているIntellij ideaやVSCodeで開発をしてもいいのだ。もちろんvimやemacsを使ってもいい。
最後に、Gitlab-CIの環境変数のオーバーライドを利用して、別のスクリプトIDに対してデプロイを試してこの記事を終わりにしよう。
これができれば、開発環境・本番環境の切り替えができるだろう。
新たにスプレッドシートを作り、Tools -> Script editorをクリックする。URLのIDっぽいところを控えておく。
Gitlabに行き、ロケットのアイコンからPipelinesを開いて、Run Pipeline
をクリックする。
VariablesにSCRIPT_ID
をvariable key, 控えておいたScriptIdをvariable valueとして、Run Pipeline
をクリックする。
緑色のチェックとpassedという文字を深い満足をもって確認する。
スクリプトエディタをリロードすると、真っ白だったエディタの中には、先ほど作成したお気に入りのアイドルグループ表を作成するスクリプトが表示されていた。