Overview
エムスリーエンジニアリンググループ AI・機械学習チームでソフトウェアエンジニアをしている中村(po3rin) です。検索とGoが好きです。
AI・機械学習チームでは開発の効率化のため、プロジェクトの雛形を自動的に生成するcookiecutterのプロジェクトtemplateを利用しています。下記はAPI開発で利用しているcookiecutterプロジェクトtemplateについての記事です。
しかし、templateから作成したプロジェクトはtemplateが新しくなった場合に、その変更に追従するのが困難になります。そこで、今回はcruftというツールを導入して、最新cookiecutter templateへの追従を楽にできるようにしました。今回はcruftの紹介と、それをどのように導入したかをお話しします。
- Overview
- cookiecutterの課題
- cruftとは
- 既存プロジェクトへのcruft導入
- CIの設定
- cookiecutter template自体へのcruft導入
- チームへの導入
- まとめ
- We're hiring!
cookiecutterの課題
cookiecutterを使ったプロジェクト作成した後に、template側に重要な更新が入った場合、そのtemplateから作ったプロジェクトにその変更を加えたい時があります。例えばツールのバージョンアップや新しいセキュリティチェックをCIに導入した場合、templateの更新はもちろん、そのtemplateから作った全てのプロジェクトにその変更を加えていく必要があります。
どのプロジェクトに変更を追加したかを管理するのは非常に困難で、弊チームでは重要な変更のときはスプレッドシートを使って変更を適用したプロジェクトにチェックを入れてもらい、まだ変更を取り込んでいないプロジェクトを管理するなどの手作業が発生していました。
また、重要でない改善の反映はプロジェクトまかせになって反映されないことも多く、プロジェクトごとにベストプラクティスに追従できていない状態になっていました。
そこでcruftの登場です。
cruftとは
cruftを使用すると、templateの最新状態への追従や、変更のチェックなどを簡単に行えます。cookiecutter template機能と完全に互換性があるので、cookiecutterを使っているチームはすぐに取り入れることができます。
リポジトリはこちらです。
このツールを導入することで、cookiecutterで作った既存のプロジェクトが最新のtemplateへ追従するのを楽にしました。
既存プロジェクトへのcruft導入
cookiecutter templateから作った既存プロジェクトにcruftを導入する方法を解説します。まずは下記を実行します。
cruft link https://github.com/m3dev/cookiecutter-gokart
上記コマンドで、cookiecutterからプロジェクトを作る時と同じように、templateの値についての質問されるので、プロジェクトを作った時と同じ値を回答します。そうすると下記のようにcruft.json
が作成されます。
{ "template": "https://github.com/m3dev/cookiecutter-gokart", "commit": "25b2ea60fd1b3145908b750fc0e42e130913c7c5", "checkout": null, "context": { "cookiecutter": { "project_name": "xxx", "package_name": "xxx", // ... "_template": "https://github.com/m3dev/cookiecutter-gokart" } }, "directory": null, }
確実にdiffが出てしまうファイルに関してはcruft.json
にskip
フィールドを追加できます。
{ "template": "https://github.com/m3dev/cookiecutter-gokart", ... "skip": [ "xxx", ".venv", "poetry.lock", "pyproject.toml" ] }
この状態で下記を実行するとcruft.json
のcommit
が最新のcookiecutterのcommitに追従していることを確認できます。
cruft check
もし、上記コマンドに失敗した場合は、cruft.json
のcommit
とcookiecutterの最新コミットと差分があるので、その差分をチェックし、可能ならそのまま取り込みます。
cruft update
cruft check
に失敗している場合、CUIでどのアクションを行うかを問われます。cruft.json
のcommit
から最新のコミットまでの変更を確認したい場合は[v]、変更を適用したい場合は[y]を選択します。適用したくない場合は[s]を選択します。これで簡単にcookiecutterの最新のcommitに追従できます。
cruft.json
のcommit
から最新のコミットまでの変更ではなく、最新cookiecutterと現在のプロジェクトの差分を丸々見たい場合は下記を実行します。
cruft diff
古い既存プロジェクトに導入する場合は複数のdiffが出るので、diff単位で確認しながら適用したいdiffだけを適用する下記のコマンドが便利です。
cruft diff | git apply - && git add -p
CIの設定
cruftを使って常に最新の状態を確認して必要なdiffを取り込んだかをチェックするために、弊チームではcruft check
を行うCIを設定しました。弊社ではGitLabを利用しているので、GitLabでの設定方法を主に説明します。
CIでcruft check
するための設定を.gitlab-ci.yml
追加します。この際にマージリクエストにコメントを残したい場合はgitlab-commentが便利です。このツールは弊社SREのyuyabanさんが開発しています。
## `GITLAB_ACCESS_TOKEN`を用意しておく。 cruft_check: stage: test image: <<何かしら素敵なpython image>> before_script: # Gitのセットアップなど... # ... - pip install --upgrade pip - pip install cruft - wget -q https://github.com/yuyaban/gitlab-comment/releases/download/v0.2.3/gitlab-comment_0.2.3_linux_amd64.tar.gz - tar -zxvf gitlab-comment_0.2.3_linux_amd64.tar.gz - chmod +x gitlab-comment - mv gitlab-comment /usr/bin/gitlab-comment - rm gitlab-comment_0.2.3_linux_amd64.tar.gz script: - | export GITLAB_ACCESS_TOKEN=$CRUFT_GITLAB_TOKEN cruft check || exit_code=$? if [ $exit_code -ne 0 ]; then export GITLAB_ACCESS_TOKEN=$CRUFT_GITLAB_TOKEN gitlab-comment post -k cruft_check -u 'Comment.HasMeta && Comment.Meta.TemplateKey == "cruft_check"' --var target:"${CI_JOB_NAME}" exit 1 fi
ここでGitのセットアップをしていますが、これはcruftが内部でcookiecutter templateをcloneしてdiffを確認するためです。cookiecutter templateがプライベートリポジトリにある場合はアクセスできるようにしておきましょう。
このCIの設定は既存プロジェクトにすぐに導入できるように、template化してチーム内で利用できるようにしてあります。詳しくはGitLab CIのtemplate基盤を構築した際に書いた下記のブログをご覧ください。
そして、check結果をMRのコメント欄に通知するために、gitlab-comment.yamlを追加します
これでgitlab-comment
がどのコメントを更新すれば良いかを判別できます。
--- post: default: | hello gitlab-comment ! cruft_check: # update: 'Comment.HasMeta && Comment.Meta.TemplateKey == "cruft_check"' template: | 最新のcookiecutterに追従できていません。ローカルで ```cruft update``` を実行して、最新のcookiecutterに追従しましょう。 https://*****(ドキュメントへのリンクなど)
これでCIでcruft check
をして、もし最新版に追従していない場合はマージリクエストにコメントが残ります。cruftの運用ドキュメントなどへのリンクも置いておくと便利です。
弊チームの運用では、メインのロジックやミドルウェアがプロジェクトごとに違うので、全てのプロジェクトに確実に差分が出てしまいます。そうすると毎回マージリクエストのたびにコメントが投稿されてしまいます。そのため今回はCIにcruft diff
の結果をアップロードする運用は見送りました。
cookiecutter template自体へのcruft導入
cookiecutter templateには上記で説明したCIの設定を置いておくだけでOKです。
さらに発展の運用として、templateでcruft.jsonの共通設定を置いておきたい場合などの対応も紹介します。例えばcruft.json
のskip
フィールドをtemplateに設定として置いておきたい場合などが挙げられます。
そこで、templateにcruft.json
を事前に追加しておくなどの対応ができます。しかし、ここでcruft.json
に設定したcommit
フィールドはどんどん古くなっていきます。その対応としてcookiecutterからプロジェクトを作成した段階で最新のコミットにするようにPost-Generate Hooksを設定できます。
Pre/Post-Generate Hooks機能に関するドキュメントはこちら
プロジェクト作成時にcruft update
が走るようにするコード例は下記になります。cruft update
コマンドに-s
オプションをつけることで、コミットIDの更新だけを行ってくれます。
import subprocess from pathlib import Path import shutil # setup cruft subprocess.check_call(['poetry', 'run', 'cruft', 'update', '-s'])
これをドキュメントの通りhooks
ディレクトリに入れておけばcookiecutterからのプロジェクト作成時に新しいコミットIDに書き換えてくれます。弊社ではskip
するディレクトリやファイルがプロジェクトごとにかなり変わるので、一旦はcruft.json
をtemplateに置いておくのではなく、cruft create
コマンドでできるcruft.json
にプロジェクトごとに手動でskip
などの設定を追加する運用を取っています。
チームへの導入
cruftをチームへ導入する際の障壁はcookiecutterで作った既存の全てのプロジェクトに導入する必要があることです。ここをパッと自動化することは難しいため(何か素敵なアイデアがあれば教えてほしい)、チームのみんなに協力してもらう必要があります。
そのために僕が行った取り組みは下記です。
- 既存プロジェクトへの導入のためのドキュメントを書く
- 参考として導入事例のプロジェクトを一個作る
- 簡単にCIをセットアップできるようにGitLab CI templateを用意
- チームで週1で行っている技術共有会でcruftを紹介。運用方法について合意を取る。
このように大量のプロジェクトに変更する場合は、チームメンバーに協力してもらうための障壁を落とす必要があります。今までセキュリティチェックツールなどを導入する場合には同じようなことをしていましたが、cruft導入を突破してしまえば楽なのでがんばりましょう。
まとめ
cruftの導入によって、最新版cookiecutterへの追従や変更の確認を簡単に行う方法を紹介し、実際にチームに導入する方法なども紹介しました。cookiecutterからプロジェクトを作成してガンガン開発するようなチームであれば是非導入を検討してみてください。
We're hiring!
エムスリーではエンジニアの開発体験をゴリゴリ改善する仲間を募集しています。我こそはという方はぜひ以下からご応募ください!