TL;DR
- フロント・バックエンドサービスをそれぞれコンテナ化、docker-composeで全てのコンテナを管理する
- monorepoで管理した際に1リポジトリとなるので気軽にGit Hookの処理ができない
- Lefthookを導入してpre-commit時にすべてのコンテナに対してLintツールを動作させるようにした
サンプルコード
1リポジトリで開発環境を管理したい
渋川さんの記事
マイクロサービスほどじゃないけどウェブサービスを分割開発したい人向けDocker設定を集めるスレ
https://qiita.com/shibukawa/items/fd49f98736045789ffc3
を読んでフロントエンドとAPIがごっちゃになっている開発環境ヨクナイ!ってことでサービス単位でコンテナ化してvs codeのリモートコンテナ機能を使って開発環境を再構築をしていたらGitとGItHooksの扱いで躓く。
Git Hooksの扱い
1リポジトリでサービスをコンテナ化してmonorepoを構築した際に.gitはルートディレクトリのみに存在する。
何が起こるかというと、フロントエンド開発時に「Husky+lint-stagedでコミット前にeslintやprettierを実行してコミット前にソースをチェックする」ができなくなります。これは治安が悪くなるってことで調査を進めた結果、試してみたのがLefthookです。
Lefthookを使ってみる
Lefthookとは?ということで公式の説明を引用
The fastest polyglot Git hooks manager out there
Fast and powerful Git hooks manager for Node.js, Ruby or any other type of projects.
- Fast. It is written in Go. Can run commands in parallel.
- Powerful. With a few lines in the config you can check only the changed files on pre-push hook.
- Simple. It is single dependency-free binary which can work in any environment.
- GO製。コマンドを並列実行できる
- かんたんな設定ファイルでpre-pushのhookが使えるようになる
- (GOによる)シングルバイナリなので、どのOSでも実行可能
今回は以下のことを実装しました。
- 起動時にdocker-composeでインスタンス起動
- 起動したインスタンスに対してコマンドを実施
- 更新対象のファイルの拡張子をgrepして対象の拡張子がstageにあるときのみ実行する
※、余談ですが日本語での紹介記事は2つのみ。1つは公式の翻訳ともう一つはRubyでのHusky置き換え記事です。
Lefthook: 多機能GItフックマネージャ
https://techracho.bpsinc.jp/hachi8833/2019_10_16/79052
Git HooksマネージャーのLefthookを試してHusky(+lint-staged)と比較した結果、乗りかえました
https://blog.solunita.net/posts/change-lefthook-instead-of-lintstaged-with-husky/
Lefthookインストール
Lefthookインストールですが、公式サイトのInstallationか、リリースページから直接ダウンロードします。自分はWindows環境なのでプロジェクト内にlefthook.exeをそのまま置いて使っています。
インストール後に対象のリポジトリで以下のコマンドを実行
lefthook install #Windowsで直下に置いている場合は lefthook.exe install
インストールが完了するとリポジトリのルートに「lefthook.yml」が作成されますので、こちらに設定を書きます。
Lefthook設定ファイル解説
# EXAMPLE USAGE
# Refer for explanation to following link:
# https://github.com/Arkweid/lefthook/blob/master/docs/full_guide.md
#
# pre-push:
# commands:
# packages-audit:
# tags: frontend security
# run: yarn audit
# gems-audit:
# tags: backend security
# run: bundle audit
#
# pre-commit:
# parallel: true
# commands:
# eslint:
# glob: "*.{js,ts}"
# run: yarn eslint {staged_files}
# rubocop:
# tags: backend style
# glob: "*.rb"
# exclude: "application.rb|routes.rb"
# run: bundle exec rubocop --force-exclusion {all_files}
# govet:
# tags: backend style
# files: git ls-files -m
# glob: "*.go"
# run: go vet {files}
# scripts:
# "hello.js":
# runner: node
# "any.go":
# runner: go run
pre-commit:
piped: true
commands:
1_docker-compose:
root: .
run: docker-compose up -d
2_eslint:
root: "containers/frontend/"
glob: "*.{js,jsx,ts}"
run: docker exec -it frontend-container yarn eslint-check
3_frontend-test:
root: "containers/frontend/"
glob: "*.{js,jsx,ts}"
run: docker exec -it frontend-container yarn test
4_api-test:
root: "containers/api/"
run: docker exec -it api-container go test
サンプルとインデントの数が違うのはご愛嬌。今回使っている処理は以下の通り。
- pre-commit: コミット実施前に実行してほしい処理
- piped:commandsを名前順に実施する。そのため頭文字に数字をつける
- commands:実行するコマンド
- root:どの階層でコマンドを実施するかを記載
- glob:stagedのファイルのうち、コマンド実行対象とするファイルを選別する
- run:実行するコマンド
コマンドの内容ですが以下の処理を実施しています。
- docker-composeでコンテナを起動
- docker execを使用してフロントエンドコンテナでeslint+Prettierを実施
- docker execを使用してフロントエンドコンテナでテストを実施
- docker execを使用してAPIコンテナでテストを実施
フォルダ・ファイル構成
以下の想定でファイル構成を行っています。
- フロントエンドとAPIサーバをそれぞれコンテナ化して管理
- docker-composeでコンテナを一元管理
- フロントエンドではeslint+Prettierでのソース整形とテストを実施
- APIサーバではテストを実施
- それぞれ正常に実行時のみコミットを実施する
lefthook-docker-node-go-dev-sample
│ .gitignore
│ docker-compose.yml
│ lefthook.exe
│ lefthook.yml
│ LICENSE
│ README.md
│
└─containers
├─api
│ docker-entrypoint.sh
│ Dockerfile
│ go.mod
│ go.sum
│ main.go
│ main_test.go
│
└─frontend
│ .eslintrc.json
│ docker-entrypoint.sh
│ Dockerfile
│ package.json
│ README.md
│ yarn.lock
│
├─node_modules
├─public
│ favicon.ico
│ index.html
│ logo192.png
│ logo512.png
│ manifest.json
│ robots.txt
│
└─src
App.css
App.js
App.test.js
index.css
index.js
logo.svg
serviceWorker.js
setupTests.js
Lefthookでpre-commit時にコンテナに対してコマンド実行
pre-commitをrunしてみる。
.\lefthook.exe run pre-commit
RUNNING HOOKS GROUP: pre-commit
EXECUTE > 1_docker-compose
api-container is up-to-date
frontend-container is up-to-date
EXECUTE > 2_eslint
yarn run v1.22.4
$ eslint --print-config .eslintrc.json | eslint-config-prettier-check
No rules that are unnecessary or conflict with Prettier were found.
Done in 0.69s.
EXECUTE > 3_frontend-test
yarn run v1.22.4
$ CI=true react-scripts test
PASS src/App.test.js
✓ renders learn react link (39ms)
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 2.081s
Ran all test suites.
Done in 3.23s.
EXECUTE > 4_api-test
[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.
[GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production.
- using env: export GIN_MODE=release
- using code: gin.SetMode(gin.ReleaseMode)
[GIN-debug] GET /ping --> github.com/MegaBlackLabel/lefthook-docker-node-go-dev-sample.setupRouter.func1 (3 handlers)
[GIN] 2020/03/15 - 03:43:28 | 200 | 44.742µs | | GET /ping
PASS
ok github.com/MegaBlackLabel/lefthook-docker-node-go-dev-sample 0.014s
SUMMARY: (done in 7.78 seconds)
✔️ 1_docker-compose
✔️ 2_eslint
✔️ 3_frontend-test
✔️ 4_api-test
コマンドが順番に実行されそれぞれの実行結果が表示。最後にサマリーとしてOK・NGが出力されます。
まとめ
- サービス単位でコンテナ化して開発するのは便利だね。でもGit Hooksの処理ができない
- Lefthook使えばできるよ。シングルバイナリだから導入もかんたんだよ
- コンテナ化してもGit Hooksが使えるので複数の開発者がいても治安が維持できそう
以上