watermint.org - Takayuki Okazaki's note

Go: 複数のReader/Writerに対する帯域幅制御

ネットワークやディスクへの読み書き処理の際、帯域幅制御をしたい場合があります。低優先度の処理などによって主となるビジネスロジックが阻害されないよう制御するといった目的や、一つのサーバ資源で複数のサービスを提供するときに一つのサービスが資源を消費しすぎないようにしたいといった目的があります。

Goでこのような処理を書きたいとき、ざっと調べてみたところgo-flowrateという実装が見つかりました。

func main() {
    f, _ := os.Open("data.dat")

    // 100バイト/秒の帯域幅制御付きのラッパーをつくる
    f2 := flowrate.NewReader(f, 100)

    // 実際の処理
    // ...
}

使い方もシンプルできっちり制御できるのでこれでいいのですが、制御できるのが一つのio.Readerまたはio.Writerのみに対してのみ可能で、複数のio.Readerio.Writerに対しては制御することができません。

並列処理が必要なプログラムを書いているとちょっとこれではそのまま使えそうにありません。もう少しほかの選択肢を探してもよいのですが、ちょうどいいGoのプログラミング課題ということで新しく実装してみることにしました。

出来上がり

まずはでき上がったライブラリを紹介します。bwlimitとしてGithub上に公開してあります。利用例は次のようなイメージです。

func main() {
    // 100バイト/秒に制限
    bwlimit := NewBwlimit(100, false)

    // 複数のReader
    f1, _ := os.Open("data1.dat")
    f2, _ := os.Open("data2.dat")

    // ラッパーを作成
    fr1 := bwlimit.Reader(f1)
    fr2 := bwlimit.Reader(f2)

    // 実際の処理
    // ...

    // すべてのReaderがClose()されるかEOFになるまで待機
    bwlimit.Wait()
}

まずは帯域幅制御を制御するオブジェクト(上図ではbwlimit)を作り、さらにそれぞれラッパーを作成します。NewBwlimitの第二引数は帯域幅制限しているときRead/Write処理を待たせるかどうかです。

あとはラッパーを普通のio.Readerとして扱うだけです。並列処理をしていて、すべてのReaderまたはWriterについて処理が完了したかどうか知りたくなったので待機するWait()もつくりました。

仕組み

キューを作る方法などいくつか実装方法を検討しましたが最終的にはかなりシンプルな構造になりました。トヨタ生産方式の本を思い出してヒントを得ました。

タクトタイムという一定時間のリズムを刻んで、各拍子のタイミングで利用可能な帯域を現在有効なReader/Writerで分割して利用するアイデアです。

たとえばタクトタイムを1秒間に10回に設定したとします。帯域幅設定を1000バイト/秒にした場合、タクトタイム1回あたりに利用可能な帯域幅は100バイトです。有効なReaderが2つであればこの100バイトをわけあって、50バイトずつの読み取りを許可する枠を設定します。実際に、Reader側が読み取るかどうかはわかりませんが、読み取り可能な上限を制御することでこのような流量制御を行っています。

流量制御と障害防止

あるタクトタイムのときに、読み取りが実際に発生せず50バイト分の枠がまるまるある状態で、次のタクトタイムになったとき読み取り可能枠を50 + 50バイト分にはしないようにしました。流量はこれによってすこし減ってしまいますが、意図しない障害を防ぐためです。

読み取り可能枠が溜まりにたまって一気に転送が行われてしまうと、他のサービスへ影響がでることはもちろん、場合によってはルーターなどのバッファサイズを超えるなどして障害が発生することも考えられます。

利用例

また別の記事として各予定ですが、Dropboxへのファイルアップローダーをいま作っています。このファイルアップローダーで並列してファイルのアップロードをする際に、帯域幅制御をつけたかったのがこのライブラリ作成のモチベーションです。

Disk image file (.dmg) from command line

I prefer .dmg instaed of zip for archiving project data, etc. .dmg is handy for refering files, modify contents without extract files to somewhere.

.dmg can usable as like USB drive. Disk Utility tool can create/update .dmg from folder with various options. Options are like encryption, readonly, compression, etc.

But if you have tens of folders to archive, it’s better to use command line tools.

Create encrypted .dmg file

hdiutil is command line version of Disk Utility app. This command can mount/unmount/create/update disk image files. Please see man hdiutil for more detail.

Below script is part of my workflow of archiving project files. I’m using encrypted .dmg for archive. The script require prepare password file under $HOME/.dmg-password. Please create and store password for .dmg without LF.

And update permission like chmod 600 $HOME/.dmg-password to prevent read from other users. This sequence using password and encryption. But it’s not strong enough, reason described below.

#!/bin/sh

if [ $# -lt 2 ]; then
  echo $0 SRC_DIR DEST_DIR
  exit 1
fi

SRC=$1
DST=$2
PWD="$HOME/.dmg-password"

if [ ! -e $PWD ]; then
  echo Disk Image password not found
  exit 2
fi

for t in "$SRC"/*; do
  if [ -d "$t" ]; then
    echo Creating: $t
    n=$(basename $t)
    cat $PWD |                \
      hdiutil create          \
        -srcfolder "$t"       \
        -fs HFS+              \
        -encryption AES-128   \
        -format UDBZ          \
        -stdinpass            \
        "$DST/$n.dmg"
  fi
done

Preset password for .dmg in Key Chain

It’s kind of pain in neck entering password for opening .dmg everytime. If you open .dmg from Finder.app, the password dialog refuse copy & paste operation.

Password dialog

There is option “remember password in my keychain”. Concept is similar to this.

The password for disk image is stored in keychain which identified by UUID of .dmg. The UUID is referable by command like below.

$ hdiutil isencrypted YOURDISKIMAGE.dmg

Now you can store password through security command.

$ security add-generic-password -a (UUID above) -D "disk image password" -s (YOUR DISK IMAGE).dmg -w (PASSWORD OF DISK IMAGE)

Unfortunatelly, there are no option like -stdinpass. So the password must be passed through command line argument. This mean optential leak through ps command or shell history.

By the way, I’m using below script for preset password to disk images.

#!/bin/sh

if [ $# -lt 1 ]; then
  echo $0 [dmg file]...
  exit 1
fi

PWD=$HOME/.dmg-password

for FILE in "$@"; do
  UUID=$(hdiutil isencrypted "$FILE" 2>&1 | grep uuid | awk '{print $2}')
  BASE=$(basename $FILE)

  echo File: $BASE
  echo UUID: $UUID

  security add-generic-password -a $UUID -D "disk image password" -s $BASE -w $(cat $PWD)
done

When opening .dmg from Finder, operating system ask authorisation of using password by diskimages-helper.

Confirmation dialog

You can skip this dialog by -A option of security command, but this option authorise for all applications. It’s better not use this -A option for better security.

ディスクイメージファイル(.dmg)をコマンドラインから

Macでたくさんのファイルをひとまとめにするのであればzipで圧縮するよりも、ディスクイメージファイルにしたほうが便利なこともあります。zipだと標準では中身を確認するのに毎回どこかに展開しないといけないですし、ファイルの内容を変更するのも面倒です。zip用のツールを使えばおそらくそれらも解決できると思いますが、個人的にはディスクイメージファイル(.dmg)を使うことの方が多いです。

ディスクイメージファイルは外付けディスクのようなイメージでマウントして使えます。macOSに標準添付されている「ディスクユーティリティー」ツールを使えばこのイメージファイルを簡単に作ることができます。読み取り専用、読み書き可能、暗号化、圧縮などオプションを選ぶこともできます。

終了したプロジェクトのファイル一式は.dmgファイルとしてまとめていますが、まとめたいプロジェクトファイルがたくさんあったり、ワークフローを自動化したいとなってくるとコマンドライン操作を覚えると便利です。

ディスクイメージファイルの作成

hdiutilコマンドを使うとディスクイメージファイルの作成や設定変更、ディスクイメージファイルのマウント、アンマウントなどさまざま操作可能です。詳しくはman hdiutilをご参照いただくとして手元での利用例をご紹介します。

フォルダ以下にあるサブフォルダをそれぞれ.dmgとして一括変換したい場合に使っているスクリプトです。それぞれ一応暗号化しておこうということで、暗号化用のパスワードを$HOME/.dmg-passwordというファイルにあらかじめ格納してあります。パスワードファイルには最後に改行が入らないようにつくっておいてください。

あとパーミッションも chmod 600 $HOME/.dmg-passwordなど他者から読み出せないようにしておきます。そうはいっても、後述しますが一連のプロセスはさほど安全性が高いわけではありませんので「添付ファイルはパスワードつきzipでパスワードは別メールですぐ送信!」とあまり変わらないセキュリティーレベルとお考えいただいていいかと思います。

#!/bin/sh

if [ $# -lt 2 ]; then
  echo $0 SRC_DIR DEST_DIR
  exit 1
fi

SRC=$1
DST=$2
PWD="$HOME/.dmg-password"

if [ ! -e $PWD ]; then
  echo Disk Image password not found
  exit 2
fi

for t in "$SRC"/*; do
  if [ -d "$t" ]; then
    echo Creating: $t
    n=$(basename $t)
    cat $PWD |                \
      hdiutil create          \
        -srcfolder "$t"       \
        -fs HFS+              \
        -encryption AES-128   \
        -format UDBZ          \
        -stdinpass            \
        "$DST/$n.dmg"
  fi
done

ディスクイメージファイルのパスワードをKeyChainにプリセットしておく

暗号化しておいたのはいいですが、作成したディスクイメージファイルを開く際にいちいちパスワードを入力するのは面倒です。Finderからダブルクリックしてマウントしようとしたときに聞かれるパスワードダイアログはコピー&ペーストを受け入れてくれないというのもさらに面倒さを増しています。

パスワードダイアログ

パスワードをキーチェインに記憶させるというオプションがありますが、あらかじめコマンドラインから記憶させておけばいいという発想です。

ディスクイメージファイルに対するパスワードは一般パスワードとして各ディスクイメージファイルのUUIDと対応して管理されています。このUUIDは

$ hdiutil isencrypted ディスクイメージファイル.dmg

というようにisencryptedオプションで知ることができます。キーチェインにパスワードを追加するには

$ security add-generic-password -a (上記のUUID) -D "disk image password" -s (ファイル名).dmg -w (パスワード)

というように実行すればよいでしょう。securityコマンドの大変残念な点はhdiutil-stdinpassのように標準入力からパスワードを受け取る手段がどうやらなさそうな点です。

コマンド引数として実行してしまうと、シェルの履歴に残ったり、psコマンドなどでパスワードが漏えいしてしまうケースがあります。このような点から上述のように「添付ファイルはパスワードつきzipでパスワードは別メールですぐ送信!」というセキュリティーレベルとたいして変わらないと思っています。

さて、上記を合わせて次のようなスクリプトにしてあります。

#!/bin/sh

if [ $# -lt 1 ]; then
  echo $0 [dmg file]...
  exit 1
fi

PWD=$HOME/.dmg-password

for FILE in "$@"; do
  UUID=$(hdiutil isencrypted "$FILE" 2>&1 | grep uuid | awk '{print $2}')
  BASE=$(basename $FILE)

  echo File: $BASE
  echo UUID: $UUID

  security add-generic-password -a $UUID -D "disk image password" -s $BASE -w $(cat $PWD)
done

このようにキーチェインにパスワードを設定したファイルを開こうとすると次のようにキーチェインからパスワードを取り出していいか確認されます。

確認ダイアログ

このダイアログをsecurityコマンドの-Aオプションでスキップする手段もありますがすべてのアプリケーションに対して許可を出してしまいます。少し面倒ですが、ここは一手間かけておいたほうがいいでしょう。

TumblrからJekyll + Github Pagesへ

3年ほど前にWordPressからTumblrへ移行しました。特に大きな問題があったわけではないのですが、最近ソースコードを挿入した記事をいくつか書こうとしたところTumblrのMarkdownではうまく表現され切れない点にすこしストレスを感じていました。表示をあれこれいじっている時間ももったいなくなってきたのでいっそのこと、GFM (GitHub Flavored Markdown)で書けるようなサービスか、システムに移行しようと考え始めました。

あれこれ調べているうちに、どうやらJekyllをつかってGithub Pagesへ移行するのがスムーズそうだなと調べ始めて評価し、次の点で問題なさそうだったので移行することにしました。

  • GFMで書ける
  • 既存のURL構成を引き継げる
  • Tumblrからの記事取り込みもできる
  • シンプルなテーマがある

逆に移行に当たってできなくなる機能もあります。たとえばソーシャルメディア連携です。Tumblrでホストしてもらえば、Tumblr上のソーシャルメディアでリブログなど拡散手段があります。ほかには予約投稿やTwitter連携といった機能も使えなくなります。このあたりはトレードオフですが、ソーシャルメディアで拡散していただくよりも、そもそも記事が書きづらくて筆無精になるのであれば優先順位は明らかです。

ソーシャルメディア連携もあとからIFTTTなど何か使えば実現できるでしょうし、予約投稿も必要になれば決まった時間にgit pushするよう何か構成を考えればいいだけです。

移行の効果

移行中のあれこれは後述することにしてまずは、移行によってどういったメリットがあったかみてみます。

WordPressから移行した際にはサイトの速度が劇的に改善しましたが、今回は比較的軽微な差に収まっています。

TumblrでのPageSpeed結果

まずはTumblrでのPageSpeed計測結果から。

Tumblr / モバイル

モバイル用のスコアは67点。画像サイズが大きいと警告がでているのと、キャッシュ設定などのところで最適化の余地があるとの判定です。

Tumblr / デスクトップ

デスクトップ用のスコアは77点。モバイルと同様に画像が大きいと警告が出ています。いずれにしても体感的にはもっさりした感覚は全くなく、表示速度などの不満は特にありませんでした。

Jekyll + Github PagesでPageSpeed結果

Jekyll + Github Pages / モバイル

モバイル用のスコアは72点と少しだけTumblrでの表示より改善しています。これはTumblrのインフラスピードとの差というよりは、採用しているテーマのCSSやJavaScriptの差がほとんどだと考えられます。

Jekyll + Github Pages / デスクトップ

デスクトップ用のスコアは77点とTumblrでの結果と同じです。

その他の違い

Tumblrではモバイル対応していないテーマを使っていたので、TumblrのUIが優先して表示されています。一方、Jekyllで今回利用したLanyonはモバイルフレンドリーなテーマで、今回どちらでも同じLook and Feelでサイトを表示できるようになりました。

移行

移行作業はJekyllの使い方を覚えるところから全部でおおよそ5〜6時間かかったかと思います。Jekyllはかなり活発に開発されているようなのであまりここで細々と手順を書いてもすぐ使いもにならなくなってしまう気がするのでおおまかな流れだけご紹介しておきます。

Jekyll環境の設定

最近はこの手のツールを使うときはすべてDockerを使って独立した環境で実行するようにしています。再現性もありますし、手元の環境もシンプルに保てます。Jekyllを使うには既存のDockerイメージを使えば充分ですが、いくつかテーマを試したりしているうちに依存関係のあるライブラリをまとめてイメージとして持っていたほうが便利だったので次のような内容のDockerfileを作っています。

FROM jekyll/jekyll:builder

RUN apk add --no-cache --virtual build-dependencies build-base
RUN apk add --no-cache libxml2-dev libxslt-dev
RUN apk add --no-cache ruby-dev curl-dev zlib-dev yaml-dev
RUN gem install nokogiri
RUN gem install minima
RUN gem install jekyll-import

jekyll-importは後述のTumblrから記事を取り込んだときに使ったものです。このDockerfileをビルドしておきます。たとえばsiteディレクトリ以下につくっていくならこんな感じにコンテナを実行して、テンプレートを作ったりjekyll-importを実行したりできます。

$ docker run --rm -i $(pwd)/site:/srv/jekyll -p 4000:4000 -t (ビルドしたときのタグ名) bash

今回はあれこれテーマをダウンロードし、jekyll-importでTumblrから記事をざっくり取り込んで表示確認をしながらレイアウトを決定しました。

Tumblrからのインポート

jekyll-importのTumblrについての記事にコマンドが書かれていますのでこれを実行します。 移行といっても今回はTumblrで書いた記事が12記事しかなかったので、ある程度の部分は詳しく調べずに手作業でファイルを修正していくというスタイルで解決しました。

今回実行した際は次のように実行しました。

ruby -rubygems -e 'require "jekyll-import";
    JekyllImport::Importers::Tumblr.run({
      "url"            => "http://(TumblrのID).tumblr.com",
      "format"         => "html",
      "grab_images"    => true,
      "add_highlights" => true,
      "rewrite_urls"   => true
    })'

最初formatはMarkdown(md)を指定していましたが、うまく記事が取りこめなかったのでhtmlで取り込み、あとで手動でMarkdownに変換しました。

Tumblrでは記事のURLが /post/(記事ID)/(記事タイトル)というフォーマットでしたが、Jekyllでは/(年)/(月)/(日)/(記事タイトル)となるので変換が必要です。rewrite_urlsで相当するアドレスにリダイレクトするファイルを作ってくれるので、リンクはそのまま維持できます。

ただ困ったことに生々されたリダイレクト用のindex.htmlでは/(年)-(月)-(日)-(記事タイトル)へのリダイレクトになっていてうまく動きません。あれこれ探すのも面倒だったのでここは手動で直してしまいました。

grab_imagesという設定があり画像をもってきてくれそうな印象がありましたが、これもうまく動かなかったので手作業で画像をもってきたり、手元に保存してあったものを使ったりして再構成しました。

テーマ選び

いくつかサンプルを見ながら選びましたがシンプルなものがよかったのでLanyonにしました。ほかにもシンプルなデザインのものはありましたが、Lanyonは欲しい機能がおおむね入っていることと、CSSやJavaScriptを含めても全体の構成がシンプルで全体が見渡しやすいことが気に入りました。

切り替え

できあがったJekyllのページ一式をGithub Pagesへプッシュし、あとはCloudFlareでDNSの切り替えをすれば終了です。

Jekyllも開発スピードが速そうなので、開発についていくとしたらかなり大変そうです。ただでき上がったサイトはかなりシンプルなHTML、CSS、JavaScriptと画像ファイルだけで構成されているのでセキュリティー上の理由などで更新しなければいけないケースはかなり少ないと思います。

そういった意味では、一度環境ができ上がってしまえばTumblrでプレビューにあれこれ悩みながら記事を書くよりも手元の環境で確実なプレビューを見てから公開できるワークフローをつくったほうがより生産性が高そうだということで今は満足しています。

画像を含めたオーサリングのワークフローをつくったり、プレビューから予定投稿などまでいろいろ作り込みたいところですがそのあたりはまた今度じっくり作り込んでみることにします。

Dropbox Business utilities written in Golang

今夏ごろからDropbox Business向けのツールをちょこちょこと開発しています。Dropbox Business APIは一般的なOAuth2を使った認証と、HTTPでのAPIのためたいていのプログラミング言語で簡単に扱えます。また、DropboxからPython、Ruby、Java、JavaScript、.NET、Objective-C、SwiftなどSDKもリリースされているのでこれらを使えばより簡単です。

ちなみにこれほど多くのSDKをメンテナンスするのはかなり手間ですが、それを解消するためにStoneというオープンソースが公開されています。StoneはAPI仕様記述からSDKを生成するツールで一度この仕様記述をメンテナンスしていけばSDKは自動生成できます。Dropbox APIの仕様についても同様にStoneでの仕様記述があり、公開されているSDKは自動生成されたものをベースにしています。

つくったツール

作成したのは下記二つのツールです。

  • dcfg … G Suiteのグループで管理されている組織、グループ構成をDropbox Businessチーム用に同期する
  • dreport … Dropbox Businessチームに関するメンバー一覧などをレポートとしてCSVで出力する

いずれもCLIツールでシステム管理者が利用する想定のものです。ただ、昨今はシステム管理といってもWindowsだけでなくMacなど様々なシステム環境があります。このように利用用途からかんがえて複数プラットホームで利用できたほうが便利なので、個人的には初めてとなるGolangを採用してみました。

はじめてのGolang

一番最近よく使っていたプログラミング言語はScalaだったので、カルチャーギャップというか、プログラミング言語がもつ信念のようなところに大きな差があって少し戸惑いました。

Scalaのようなオブジェクト指向的にも関数型的にもかけるプログラミング言語の直後ということもあって、Golangの書き方はややまどろっこしく感じます。ただこのまどろっこしさも次第に慣れていくことで、また考え方がわかってきたことでさほど気にならなくなってきました。

どこに書いてあったかソースは失念しましたがたとえば、「関数のなかで、計算量のオーダーが O(1) → O(n)のように変わるような書き方はあえてさせないことで、関数を使う側がどういうオーダーの計算量を常に意識させるようにしている」というような説明がありました。Golangのテストフレームワークがassertをあえて実装していない、といったこともそうですが、プログラミング言語やフレームワークで様々なヒューマンエラーを減らしていこうとする考え方とはまた違う思想を感じます。

この考え方を真に受けてコードを書いていったので、でき上がったプログラムはにたようなforループやif分岐がたくさんでき上がりました。今回、コードをかくに当たってあまり他の方のコードを読んだりしなかったので本流とはだいぶ外れているかもしれません。

パッケージ管理

Golangで戸惑ったのがパッケージ管理です。GoのプログラムはGOPATH環境変数で指定したディレクトリ以下にすべての依存ファイル、そして自分のプロジェクトファイルも置いていくという方式です。

自分のプロジェクトをたとえば、github.com/watermint/dcfg で公開する予定ならば下図のように GOPATH以下に置いていきます。外部ライブラリたとえば、github.com/dropbox/dropbox-sdk-go-unofficialを使うのであればこれらもgo getコマンドを経由して同様にGOPATH以下に配置します。

$GOPATH / src / github.com / watermint / dcfg

ひょっとすると、Goの考え方でAPIは一度公開したならば同じ名前で同じように使えるべき。という信念があるのかもしれません。 この方式で困るのは複数プロジェクトを進行しているときに、異なるバージョンのライブラリを利用したいや、ライブラリのバージョンをしばらく固定しておきたいときにはやっかいです。

また別の環境でビルドする際にも依存するライブラリの管理が煩雑になります。こういった問題に対しRubyであればgem、Javaなどではmaven、gradle、sbtなどパッケージ管理は様々あり、これらで管理するのが当たり前という風なイメージを持っています。Golangでも同じようなものを探してみましたが、いまひとつこれといったものがどれなのかわかりませんでした。

これも今回はあまり細かく調べずに、glideというツールを利用しました。glideをつかうと、依存するライブラリ郡を自分のプロジェクトフォルダ以下のvendorというフォルダにまとめてくれます。バージョン指定も可能ですし、一括してライブラリ郡をダウンロードするなど細々面倒を見てくれます。

また今回つかったdropbox-go-sdk-unofficialは名前の通りアンオフィシャルSDKで、まだ活発に大規模なリファクタリングがおこなわれていてバージョンを固定しないとまともに開発できない。。という現状もありglideによるパッケージ管理はもっとはやく導入しておけばと悔やまれました。

おそらく他のツールでも同様によしなに準備してくれるのだと思います。

ビルド

本格的にコーディングを始める前に、ビルド環境だけは念入りに準備することにしました。ビルド環境のOSのバージョンが違うことで何か問題がでたりするとかなりの時間を浪費してしまいますし、いろんなところで新しくビルドしようとおもったときにも手間がかかります。

今回はご多分に漏れずDockerを使っています。Dockerfileを用意しておいて、Dockerコンテナ上でビルドをしてそのバイナリを手元環境でテストするというワークフローを確立しました。

ついでにTravis CIでもテストが自動的に実行されるようにしておきました。テストカバレッジなども集計するという流れを自動化しておくこともできますが、これもなかなか情報が分散していて地味に手間のかかる作業でした。

これから環境を用意されようと思われる方は下記の.travis.ymlをご参考にしていただければと思います。

language: go
go:
 - 1.7
 - tip

before_install:
 - go get golang.org/x/tools/cmd/cover
 - go get github.com/modocache/gover
 - go get github.com/mattn/goveralls
 - go get github.com/Masterminds/glide

install:
 - glide install

script:
 - go list -f '{{if len .TestGoFiles}}"go test -coverprofile={{.Dir}}/.coverprofile {{.ImportPath}}"{{end}}' $(glide novendor) | xargs -L 1 sh -c
 - gover

after_success:
 - goveralls -coverprofile=gover.coverprofile -service=travis-ci -repotoken $COVERALLS_TOKEN

ここで登場するcover、gover、goverallsはカバレッジを取得、集約するためのものです。よく見るとgoverは前職の同僚が作ったものですね。こんなところでお目にかかるとは誇らしいものです。

ところでscript:のセクションはややトリッキーな書き方をしています。これもあれこれ情報を集めながら試行錯誤したものでこれでいいのか微妙なところですがひとまず動作しているのでこれで。。

- go list -f '{{if len .TestGoFiles}}"go test -coverprofile={{.Dir}}/.coverprofile {{.ImportPath}}"{{end}}' $(glide novendor) | xargs -L 1 sh -c

go testというのでカバレッジがとれますが残念ながらgo testでは1つのソースファイルについてのみテストとカバレッジ測定となってプロジェクト全体ではとれません。そこで、ソースコードひとつずつについてgo testを実行してカバレッジデータのファイルを個別につくってあとで結合するという力技です。

Retrospective

今回はCLIツールを作りましたが、利用シーンを考えるとGUIツールもぜひ作ってみたいところです。 GolangでのGUIライブラリはまだこれといった決定打がないようで、ひょっとしたらJavaでつくるか、SwiftをつかってiOSアプリにしてしまうほうがいいのかもしれません。

今回CLIとは別にGinというライブラリを使ってWebサーバを立ち上げるパターンも試作してみました。Play framework等と比べればまだまだツールサポートなども弱いので生産性に課題を感じましたがしっかり作り込めばGUIでつくるよりも将来性がありそうです。

このあたりはまた次回の研究課題です。