トップ 追記

2017-05-27 [長年日記]

_ Google Apps Script開発をもうちょっとモダンにしてみる

よりモダンな開発サイクルへ

前回から早くも1年半の月日が流れてしまったが、今回のテーマは

  • CI/CD と組み合わせてモダンなワークフローの完成形を目指す
  • そのために Node.js ベースのテストフレームワークがちゃんと動くようにする
    • Google サービスのオブジェクトを stub/mock する
  • CoffeeScript をやめて ES2015 へ

の辺り。

前回の記事で残った課題

前回調べた手法を使うと Codegs を利用して Node.js で書きながら 1ソースに build できる。このコードを gas upload すれば GAS 上で動くので、

  • コードを手元の好きなエディタで編集し、
  • Git でバージョン管理し、
  • 適切に module 分割しつつ最終的に GAS で動かす状態に持っていく

ことはできた。ただし、build プロセスが GAS 向けに最適化されており、最終的に全自動にするにはあまり向いていなかった。

gasifyの発見

個人的にはモダン Web 開発の文脈では Browserify と組み合わせてみる実験を

Rails + Browserify + Mithril + cmsxで動くもん書いてみた - あーありがち(2015-09-23)

でしてるんだけど、その後、Google Apps Script 開発方面にこれの影響が来ていたことを知る。

なるほどなるほど。gasify がキモらしい。

fossamagna/gasify: Browserify plugin for Google Apps Script

確かにこれを使うとものすごく手軽に lodash を使うことができた*1。これで GAS 用のライブラリになっていない npm の小粒なモジュールも利用可能だ。こりゃーいい。

テスト用ツールチェイン方面の課題

ということで

でいくことにした。

最終的に使ったもの
  • browserify
  • watchify
  • gasify
  • babelify
  • babel
  • babel-preset-es2015
  • babel-core
  • babel-register
  • power-assert
  • intelli-espower-loader
  • mocha
  • sinon
  • gas-manager
  • lodash

こんな感じ。

CI/CDするうえで課題になる認証情報は環境変数で

今回 GAS の開発フローを自動化しようと思ったきっかけは、手離れさせたいのがいちばん強いんだけど、その次は

Google Developers Japan: Apps Script による高度な開発プロセス

だった。Google が公式に紹介するんだから、これまでの課題はいろいろ解消されているに違いないと思ったのだ。

結論から言うと期待は裏切られた。gas-manager でできることとの違いが自分には分からなかった。以前も書いたが、Google Apps Script を使った開発については日本の GAS コミュニティの方が Google 本家より進んでいるんじゃないかと思う。

さて、gas-manager も node-google-apps-script も何が問題かというと、要は認証情報を JSON で保存する形になってしまっているので、API キーとか ssh の秘密鍵より取り回ししにくいのだ。

Authentication with Google Cloud Platform - CircleCI

を見つけて「おっ」と思ったが、これは gcloud コマンドを使うことが前提の話で、Google Drive API を使う話ではなかった。が、ここにヒントがあった。

自分でやったことは以下の四つ。今回は GitHub + CircleCI で行ったが、考え方は他のツールを使っても変わらない。

  1. gas-manager で credential 情報の JSON を作成、保存する
  2. gas-manager を使って Git + GitHub で開発できる状態に
    • gas-project.json は含む
    • gas-config.json は .gitignore へ
    • npm/yarn run で gas upload / download できるように
  3. CircleCI の project に環境変数を作る GOOGLE_CREDENTIAL とか。これに上の gas-config.json の内容を放り込む
  4. circle.yml で以下のように gas-config.json を生成
dependencies:
  override:
    - echo $GOOGLE_CREDENTIAL > $HOME/$CIRCLE_PROJECT_REPONAME/gas-config.json
    - yarn install

これで gas-manager は -c で生成済み gas-config.json を使うことで、認証情報をリポジトリに保存することなく CI 上で認証を通せる。

gasifyを使ってもimport/exportは使えない

確認したバージョンは gasify 0.1.0

最近 JavaScript復習2017 - importとuse strictとメソッド定義 - - あーありがち(2017-02-19)

で勉強したようにイマドキは import / export だよねーと思って喜んで書き始めたのだが、import / export のところで Script Editor がエラーを出す。*2

Google Apps Script は独自仕様の JavaScript 構文 + 独自の JavaScript オブジェクトによるサーバサイドスクリプトで、Babel を通したものでも動かないものはあるということですな。

gasify の作者が babel preset を作り始めようとしているようで、もしかしかたらこっちの方が本命になるかもしれないけど、まだ何も中身はない模様 ;-)

fossamagna/babel-preset-gas: Babel preset for all Goolge Apps Script plugins.

ということで 2017-05時点では module.exports と require() を使いましょう。

Mocha + babel-register + intelli-espower-loaderは順番に注意

確認したバージョンは

  • mocha 3.4.1
  • intelli-espower-loader 1.0.1
  • babel-register 6.24.1

intelli-espower-loader を先に食わせろ。

具体的なコマンドは以下のようになった。

mocha --require intelli-espower-loader --require babel-register --recursive test

Googleサービス用オブジェクトをSinonでstub out

分かったこと。

  1. gas-local のメリットはよく分からない
  2. Sinon はちゃんと存在しているメソッドがないと stub out できない

テストのサイクルを速くたくさん回すために CI を利用したい、しかし GAS は Google 上の独自サービスあってこそのもの、Node.js だけではすべての機能を動かすことはできない、だから GAS 独自オブジェクトは stub/mock に差し替えてテストを動かす必要がある。

そこで最近は gas-local を使うという話が

Apps Scriptによるより高度な開発プロセス/More Advanced Development Process with Apps Script // Speaker Deck

にあったのだが、gas-local は mock も使えるテストフレームワークと書かれている割にテストの方法についての記述が見つからない。そこでこっちを追うのはやめて、普通に Node.js 向けの代表的なツールチェインで完結させることにした。

上に挙げた mocha のコマンドで以下のものが揃っている。

  • テストランナーは Mocha
  • アサーションは PowerAssert
  • 構文は Babel を使って ES2015 で

そして JS で test double と言えば Sinon だろう。

Sinon.JS - Standalone test spies, stubs and mocks for JavaScript. Works with any unit testing framework.

こいつで stub out してやれば、まぁなんとかなるだろうと思っていたが、ちょっとハマった。

それは

存在しないオブジェクト、存在しないメソッドは stub out できない

というもの。そらそうか。

確認したバージョンは Sinon 2.3.2

今回試しに作ったコードでは

FetchUrlApp

を使っていたので、

class Sender {
  urlFetcher() {
    return (typeof FetchUrlApp == 'object') ? FetchUrlApp : {fetch: function(){}};
  }
}

として間接的に stub out 可能なオブジェクトを用意することとした。具体的に stub out するコードはこんな感じ。

var sender = new Sender();
sinon.stub(sender, 'urlFetcher').callsFake(function() {
  return {
    fetch: function(uri, params) {
      return params.payload;
    }
  }
});

何をしているかというと、FetchUrlApp.fetch(uri, params) で POST を行う部分を stub out している。

  1. urlFetcher() というメソッドで本物の FetchUrlApp かそれを stub out 可能な構造のオブジェクトを得る
  2. stub out 可能なオブジェクトとは stub out したいメソッドをすでに持っているオブジェクト。ここでは fetch() メソッドを利用する予定なので、Node.js 環境で実行した際には {fetch: function(){}} というオブジェクトを返すようにしている。

てな具合。慣れないとうへぇって感じだし、結局 production 環境で動くことの確認は手動で一度でも動かしてみないとダメなので、本当に全自動というわけにはいかないが、安定して回るようになれば Node.js だけで高速に開発サイクルを回せるので、なかなかよいと思う。

何より、テスト向けに出てきたツールの中に GAS 開発独自のものはないので、GAS 以外のノウハウが活かしやすい。これは大きい。全部見返してみても、セットアップが済んでしまえば、あとは import / export が使えないことに気をつける以外は特別な部分はほぼないはずだ。

これであとは staging と production を切り替えられるような仕組みを用意すれば、普通の Web アプリのように GitHub Workflow でだいたいの開発を回せるはずだ。

よしよし。

*1 これまでは underscoreGS とか使っていた。

*2 Drive API を通じて独自にバージョン管理をし始めるとこういう時に何が起きてるか分からなくて困るのだが、落ち着いて build 済み JS を Script Editor にコピペしてみよう。


2017-04-21 [長年日記]

_ PHPアプリのDB schemaをRubyのActiveRecordだけでdumpする

背景

schema.rbが欲しいのです。

我々は賢いので。

賢いかどうかはともかく、DB schema を手軽に確認する方法として schema.rb はそこそこ優れてると思うわけですよ。少なくとも何百とテーブルがあるわけじゃないならね。インデントも揃ってるし、SQL よりは読みやすく感じる。しかもこれが自動で更新され続けるんだから Rails をやる時は schema.rb は重宝します。特に普段触っていないものは。

ActiveRecordだけでschema dumpする

基本的には

ruby - ActiveRecord Schema Dump without rails - Stack Overflow

のお話。ただし注意点があって、

  • establish_connection は ConnectionPool を返す
  • そこからさらに connection を取得しないと Dumper に渡せない

具体的に言うと

AR::SD.dump(AR::Base.establish_connection.connection)

になるってことです。

上のコードはこれを避けるためなのか ActiveRecord::Base.connection を直接渡しているんだけど、手元ではうまく動かなかったので、動きを追ったらそうだった、ということでした。

Rails風にDB接続設定を持つPHPアプリのDBをActiveRecordでdump

上の方法で schema.rb は作成できるようになったけど、問題は DBMS 接続情報をどう取得するか。Rails なら database.yml になっているのでそれさえ探せばいいんだけど、この方式になっているフレームワークばかりではない。

具体的に今回のターゲットは Laravel なんだけど、Laravel の場合は大丈夫。

app/config/database.php にある

return array(
  'default'    => xxxx,
  'connection' => array(
    ...
  )
);

の部分に接続情報はまとまっている。この情報だけを取得するための以下のようなコードを用意する。

wrapper_script.php

<?php
print json_encode(require('app/config/database.php'));

これでデータベース接続情報を JSON で stdout に出力できる。こいつを Ruby 側から

JSON.parse(`php wrapper_script.php`)

とすれば Ruby のオブジェクトとして PHP アプリの接続情報を取得できる。あとは Ruby 側で好きに加工すればよい。

※ 先頭の <?php は特に忘れやすいので注意!!!

残りの課題は
  • driver -> adapter 変換
  • dsn -> 個別情報分解

の辺り。

探してみたけど、Ruby には単体の DSN parser はなさそうなので、自分で書いた方が早いかもしれない。それか

larskanis/ruby-odbc: ODBC binding for Ruby

辺りを使うと何か嬉しいことがあるかも。そこは試してませんので悪しからず。

Tags: PHP Ruby DBMS

2017-04-20 [長年日記]

_ mitmproxyで自動的にweinreを読み込ませるスクリプト書いたのでモバイルのデバッグが捗る

今さらなネタばかりですいません。

背景

2017年4月現在では Safari, Chrome に remote debug の機能が入っているので、対応している環境ではこれを使うことでスマホのブラウザの挙動に対しても効率的にデバッグできるようになってきている*1

が、なんかどうも安定しないし、例えば Windows からは Safari のリモートデバッグ使えるのかなぁ?

ということで環境を選びにくい古の weinre なわけだけど、これの何が問題って

  1. weinre の script を serve しなければいけない
  2. デバッグしたい HTML に <script> を書いて weinre のコードを読み込ませる必要がある

ところ。

対策

実はもう何年も前に思いついてはいたんだけど、誰か作ってるでしょと思ったら見つけることができず、どうでもよくなって放置していたんだけど、上の問題は

  • weinre を serve するサーバパッケージを用意する
  • local proxy サーバで HTML を書き換えてあらゆる HTML で自動的に weinre を読み込む <script> を入れる

ことで解決する。

準備

ということで作った。

まずは以下の道具を用意。

weinre はいつの間にかそれ専用の weinre コマンドが npm 上にできてたので、

$ npm install weinre -g

mitmproxy は Python で書かれているので普段 Python を使っている人は pip でもいいし、使ってない人はバイナリパッケージがあるのでそれを使うといい。Homebrew 使ってる人は

$ brew install mitmproxy

がいちばん簡単。

スクリプト

使い方

1) weinre を localhost 以外の IP アドレスで起動する

localhost でしか listen しないと weinre を起動した PC からしか繋がらない。

$ weinre --boundHost 192.168.0.5 --httpPort 8282

みたいな感じ。

2) スクリプトを読み込みながら mitmproxy を起動

この時、1 で起動した weinre の情報を与えてやる。上の例で言うと

$ mitmproxy -s "weinre.py 192.168.0.5 8282"

になる。

weinre も mitmproxy もデフォルトでは 8080 で立ち上がろうとするので、どちらかには port を明示的に指定して譲らなければいけない。

※ mitmproxy は https も proxy できる。mitmproxy で証明書を発行して、クライアントとなる OS、ブラウザにこの証明書をインストールする。この手順は十分簡単なので、公式のドキュメントを参照のこと。

3) ブラウザの proxy 設定で mitmproxy を指定する

これは各ブラウザの設定方法を参照。

これであらゆるサイトについて weinre の機能でリモートデバッグできるようになった。便利。

weinre の使い方についてはバッサリ割愛。

*1 Android 4.4+ / iOS 6+


2017-04-17 [長年日記]

_ GitHub/BitbucketからAccess Tokenを使ってdeployする

あるいはサーバ上で private package を bundle(npm) install したい。

課題

今回の課題はこれ。

private な repos をサーバ上で clone したい

もう少し詳しく書くと直接の git clone もそうだし、bundle install や npm install で private repos から package を install したいが、もちろん

秘密情報は repos に入れたくない

というものである。

※ Access Tokenが使えれば他のサービスでもサーバでもよい。

解決方法

  1. 環境変数と.netrcを使う
  2. 環境変数と動的なURL書き換えを使う
1. 環境変数と.netrcを使う

この方法を知ったきっかけはこれ。

timshadel/heroku-buildpack-github-netrc: Heroku buildpack to access private Github repos over HTTPS without storing user/pass in your files.

やってることは、

  • 環境変数にトークンを保存
  • build 時に環境変数から ~/.netrc に保存し、git の機能で ~/.netrc の情報で認証を通す

これで確かにリポジトリに認証情報を残さずに private repos から clone できる。

もちろん token の権限設定は必要最小限にしておこう。

サーバ上の環境変数設定を簡単に行える環境ならこれが楽だと思う。

2. 環境変数と動的なURL書き換えを使う

Capistrano で clone することで deploy する際には上とは異なる方法を利用することにした。

Easier builds and deployments using Git over HTTPS and OAuth

の情報をもとに Capistrano を叩く環境に変数でトークンを保存したうえで URL を

set :repository, "https://#{fetch(:github_auth_token)}:x-oauth-basic@github.com/{user}/{repos}"

と、cap を叩く時に書き換える。Gemfile や package.json がないならこの方法も使える。

※ Gemfile や package.json がある場合は頑張って上の buildpack と同じようなことをしてあげることになると思う。

手元の環境からも deploy したい場合は dotenv のようなものを使うか、.netrc に情報を保存するのもアリ。Capistrano 側のコードはこんな感じになる。

set :github_auth_token, ENV['GITHUB_AUTH_TOKEN'] || Netrc.read['github.com']['login']

.netrc を読み書きできる便利 gem はこれ。

heroku/netrc: Reads and writes netrc files.

なぜ他の方法ではないのか?

ここから先はちなみに情報。

Git 自体が対応している認証情報の保存方法がいくつかある。最近では自分で手で作業する場合はこのうちのいずれかを利用しているケースが多いと思う。

Git - 認証情報の保存

によると

  • cache (15分だけメモリに保存)
  • store (永続的にファイルに保存)
  • osxkeychain (macのkeychainアプリにパスワードを保存)
  • wincred (Windowsのkeychainのようなもの)

の4つに対応している。

ただしこれらは、人間がパスワードを入力しその認証情報を保存する、という流れを想定しており、Coutinuous Deployment で利用するにはイマイチである。

Git でパスワードを入力せずに push / pull するということでロートルがすぐに思いつくのはパスフレーズなし鍵認証の ssh を使う方法である*1が、今回行おうとしているのは

サーバ上で git clone や bundle install や npm install したい

ということであり、サーバ上に余計なものは入れたくないし、まして Heroku などの PaaS 上では採用できない方法である。(deploy用の公開鍵を保存する機能はあっても秘密鍵を保存する機能が存在しなかったりする。)

なぜ.netrcで可能なのか?

最後になるけど、なんか普通に .netrc って言ってるけど、そもそもこれ何?って話。

  • git は http での転送の際に cURL を利用している
  • cURL は .netrc に対応している

.netrc って古の ftp ですよね?ってぼくは思っていたのですが、cURL 力不足ということのようでした。(実は cURL あまり好きじゃなかった)

cf.

Man page of NETRC

Tags: Git

*1 なぜなら Git で http での push / pull が使いものになるのは 1.6.6 で登場した Smart HTTP が使えるようになる 2009年以降の話であり、それまでは CVS や Subversion でもおなじみだった ssh と組み合わせる方法の方がポピュラーだったのだ。