GASに入門して, Claspを使ったGit管理+Gitlab-CIを使ったCI / CDまでやった記録


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 '[email protected]'
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管理しないことにした。

clasp.json
{"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 [email protected]: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

.gitlab-ci.yml
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という文字を深い満足をもって確認する。

 スクリプトエディタをリロードすると、真っ白だったエディタの中には、先ほど作成したお気に入りのアイドルグループ表を作成するスクリプトが表示されていた。


  1. 実際には、私はGASを触ったことがないことで後ろ指を指されたことはありません。 

  2. Gitlab CIを採用したのは社内で使っているからというだけで、深い理由はありません。 

  3. 環境変数に直接入れてしまうと、悪意ある.gitlab-ci.ymlをコミットされた場合に認可情報が流出する懸念があるので、公開リポジトリにpushする場合は工夫する必要があります。