Dockerコンテナ内でinotifywaitとawkでファイルを監視する
Pythonエンジニア見習いです。今回はDockerコンテナ内でファイルの編集を自動的に検知して対応するユニットテストを実行する方法を紹介します。
まず、ファイルを監視して、スクリプトを実行してくれるプロダクトは以下のとおりです。
inotifywait
(C) +awk
watchman
(C)guard
(Ruby)gulp-watch
(Node.js)watchdog
(Python)
Dockerで様々な環境のコンテナを作っていくと、このプロダクトではRubyを使うけれど、あのプロダクトでは使っていないという現象が頻繁に起こります。例えば、guard
(Ruby)でファイル監視スクリプトを組んだとしてもPythonしかインストールされていない環境では、新たにRubyをインストールしなければなりません。さらに通常パッケージマネージャーなどでインストールできるバージョンは変更される可能性があり、安定性に難があります。そこで、rbenv
等でバージョンを固定するのですが、
- 毎回ビルドする時間がけっこう増える
- コンテナの容量が増加する
- ビルドツールをインストールしなければならない
rbenv
のパスをとおさなければならない
等のデメリットを考えるとあまりいい手ではありません。
そこで、ファイル監視にinotifywait
を用いることで、上記の問題を解決できます(パッケージマネージャーでインストール可能かつ枯れている)。しかし、inotifywait
はファイルの変更を検知してくれるだけで、ファイルの実行まではやってくれません。そこで、inotifywait
の出力を読み取って、スクリプトを起動するものが必要になります。しかし、RubyやPython、Perl等の高級なスクリプト言語は、Alpine linux等の軽量イメージにはインストールされていなことがしばしばあります。そこでAWKを使うことでこの問題を解決します。
例えば、以下のディレクトリ構造があったとして、Pythonのコードが編集されたときにテストが走るようにしたいとします。また、Pythonのユニットテストはnosetests
で実行するものとします。
- module/
- a/
- foo.py
- bar.py
- a/
- test/
- a/
- test_foo.py
- test_bar.py
- a/
- run-python-test
inotifywait
は、module/a/foo.pyが編集されたとすると
module/a/ MODIFY foo.py
と出力します。
これを踏まえると監視スクリプトは以下のようにワンライナーで書くことが出来ます。
inotifywait -m -e modify -r module | awk '$3 ~ /\.py$/ {system("nosetests")}'
一応これでも動くのですが、これでは、各ファイルを編集するごとにすべてのテストが動いてしまい、時間の無駄です。そこで編集したファイルに対応するテストのみ動くようにしたものが以下のものです。
inotifywait -e modify -m -r module test |
awk '$3 ~ /\.py$/ {system("./run-python-test "$1" "$3)}'
run-python-test
DIR_NAME=$(echo $1 | sed 's/^module/test/')
FILE_NAME=$(echo $2 | sed 's/^test_//')
nosetests ${DIR_NAME}test_${FILENAME}
また、その他の言語のファイルについても監視を増やしたい場合は以下のように増やすことが出来ます。
inotifywait -e modify -m -r module test |
awk ' \
$3 ~ /\.py$/ {system("./run-python-test "$1" "$3)} \
$3 ~ /\.js$/ {system("./run-javascript-test "$1" "$3)} \
$3 ~ /\.rb$/ {system("./run-ruby-test "$1" "$3)}'
結論
inotifywait
とawk
を使えば、様々な環境で、ファイルを監視することが出来ます。さらに、余計なものをインストールしなくても良いので、ビルド時間の短縮やコンテナの容量の節約等のご利益があります。