Redmineプラグイン開発の様子について #ginowan_study で紹介した
YassLabという会社で働いています。
今年からアジャイルウェアさんのところでRedmineプラグイン開発のお手伝いをさせて頂いています。
はじめたてのころにいろいろと溜まった情報をRailsDMで話したりしました。
Railsdm 2019に参加した - hanachin temporary
#ginowan_studyでRedmineプラグイン開発どんな様子なのか話しましたが、私の機材のトラブルで囲われての発表だったので聞こえない人もいたかと思いブログにしました。
LT風景#ginowan_study pic.twitter.com/e65PwJQxnO
— デカ@ネガフォルム (@DEKA1066) July 31, 2019
Redmineの動作環境ついておさらい
課題管理ソフトウェアです。どういうことができるかの話は割愛します。
ここでRedmineのビルドマトリックスを眺めましょう、めっちゃマトリックスじゃないですか?
はい
Redmineのバージョンはいくつかあります。アジャイルウェアのLychee Redmineでは現時点でRedmine 3.2、3.3、3.4、4.0をサポートしています
サポート | Lychee Redmine(ライチレッドマイン)でプロジェクトの見える化を。そして管理ももっと楽にしよう。
はい!
RedmineはRailsなので、複数のRubyのバージョンで動きます
- Redmine 4.0: Ruby 2.2 (2.2.2 and later), 2.3, 2.4, 2.5, 2.6
- Redmine 3.4: Ruby 1.9.3, 2.0.0, 2.1, 2.2, 2.3, 2.4
はい!!
はい!!!
広すぎる動作環境を支えるCIと自動テスト
例えば私のRedmineプラグイン開発実体験だとこういうことをがありました
- 仕事のプラグイン開発で特定のRDBMSでは動かないSQLを意図せず書いた
- 仕事のプラグイン開発でRDBMSごとにSQLを書き分けた
- Redmineのバージョンによっては実装されていないAPIを使うプラグインを意図せず書いた
- Rubyのバージョンによっては実装されていないAPIを使うプラグインを意図せず書いた
- RedmineプラグインからはRedmineのコードを直接変更できないのでモンキーパッチをして機能を追加・変更しており、モンキーパッチで本体のメソッドが壊れてしまった
自分はうっかりものなので、こういうのを未然に防ぐために自動テストを整備してRedmine本体同様マトリックスでテストを流そうとしています。 マトリックスで流すとこれらのバグを防げます。ちゃんとテストが書けていれば!
ふつうのサービスのRailsアプリだとこういうマトリックス整えることないと思うんですが、Redmineプラグインはこれを仕事で出来てたのしい。モンキーパッチも仕事で書ける。
一応誤解がないように書いておくけど上記のような問題もだいたい優秀なレビュワーによるコードレビューとテスターによるテストで防いでもらえています。
それでもなお全力をつくして問題がおきるならCIが悪い。
CIの実装上の課題
Redmineプラグイン特有の問題なんですが、プラグインのリポジトリ単体をcloneしてきても動きません。プラグインなのでRedmineにプラグインしなきゃ動かない。
そしてLychee RedmineはRedmineプラグインの集合なのでリポジトリごとにテストの設定がある。
ふつうにやると.circleci/config.yml
コピペまつりになってしまいます。
どうするか?
そうですね、CircleCI orbを使います。 ということでRedmineプラグイン開発用のorbを書いて業務で必要が出てきたときにちょいちょい更新しています。
CircleCI Orb Registry - agileware-jp/redmine-plugin
こういうのをちょいと書くだけで以下のようなことができます。
orbs: redmine: agileware-jp/redmine-plugin@0.0.15 version: 2.1 workflows: version: 2 test: jobs: - redmine/download: redmine_version: trunk - redmine/test: executor: redmine/ruby-26-pg requires: [redmine/download]
さらっと流しましたがいろいろとあるんですよね
RedmineのGemfile
RedmineではGemfile
からconfig/database.yml
の中身を読んでDBMSに対応したアダプターのgemを使っています。
何をいっているかさっぱりわからないと思いますがこういうことです。
# Include database gems for the adapters found in the database # configuration file require 'erb' require 'yaml' database_file = File.join(File.dirname(__FILE__), "config/database.yml") if File.exist?(database_file) database_config = YAML::load(ERB.new(IO.read(database_file)).result) adapters = database_config.values.map {|c| c['adapter']}.compact.uniq if adapters.any? adapters.each do |adapter| case adapter when 'mysql2' gem "mysql2", "~> 0.5.0", :platforms => [:mri, :mingw, :x64_mingw] when /postgresql/ gem "pg", "~> 1.1.4", :platforms => [:mri, :mingw, :x64_mingw] when /sqlite3/ gem "sqlite3", "~> 1.4.0", :platforms => [:mri, :mingw, :x64_mingw] when /sqlserver/ gem "tiny_tds", "~> 1.0.5", :platforms => [:mri, :mingw, :x64_mingw] gem "activerecord-sqlserver-adapter", :platforms => [:mri, :mingw, :x64_mingw] else warn("Unknown database adapter `#{adapter}` found in config/database.yml, use Gemfile.local to load your own database gems") end end else warn("No adapter found in config/database.yml, please configure it first") end else warn("Please configure your config/database.yml first") end
https://github.com/redmine/redmine/blob/be7d39a4d3c2f567f4cc315969d6a5c09221fb45/Gemfile#L42-L71
プラグインのGemfile
RedmineプラグインのGemfileがRedmineのGemfileの中からeval_gemfile
で読み込まれます。
ここで例えばプラグインAがrspecのバージョンを3.8系に固定、プラグインBがrspecのバージョンを3.7系に固定するとどうなるか?
おめでとうございます、bundle install
失敗です👏
これを防ぐために実行時に必要のないgemについてはプラグインごとにGemfile.localというファイルを用意し、プラグインの開発時に対応するプラグインのGemfile.localをRedmine本体に入れてつかう、みたいなことをやっています。詳しくは参考資料を見てください。
参考: Redmine4時代のプラグイン開発 redmine.tokyo #13
orbではどうやっているか
粛々とこの辺がいい感じになって何も設定せずに動くよう直しています。 orbの具体的な使い方についてはもうちょっとorbが成長したら別記事を書きたいと思います。
CIでのレビューで面倒だと思うこととHeroku Review Apps
前述した通り多用な動作環境なので
- 私がレビューするとき、実際に触るためにfetchしてcheckoutして動かしてみて実際さわってみよ!、、、が面倒(めんどうくさがりなので)
- テストするときにテスト環境にブランチをデプロイが手動で不便そう
- 使ってみた感じをチェックしたいのでデプロイお願いします依頼
- テスターの方に動作確認してもらっているのですが(この環境ほんとによくて安心してバグったコードが書けて、なおせる、頭が上がりません。。。)、Redmine/Rubyのこのバージョンだと動きません、あるRDBMSだと動きませんみたいな報告をうけたとき、自分の手元で動作環境合わせるのがちょっと面倒
ということでYassLab社内でも利用実績のあるHeroku Review Apps導入いかがですか?と聞いてみたところ
- テスト用の秘伝のデータがあってそれがないと不便そう
- シード流すのに時間がかかってしまってすばやく確認できなさそう
- テスターというよりマネージャに触ってもらった感じを確認してもらうのに便利そう
などのフィードバックをもらっていたのでいろいろと解決策を考えていたのでした
RedmineプラグインでHeroku Review Appsをつかう
問題: プラグインなので単体で動かない
再掲: Redmineプラグイン特有の問題なんですが、プラグインのリポジトリ単体をcloneしてきても動きません。 今回解決方法としてnull buildpackを使いました。 口頭だと伝わりづらかったと思うので、流れの図です。
こういう流れで単体では動かないコードでもデプロイがうまく動きます。
Heroku Review AppsがPull Requestごとに作成され、消えます。 初回はnull buildpackで無をデプロイするのでデプロイに成功します。 最初からrubyのビルドパック使うと諸事情でコケるのですが、null buildpackを使うとコケないので、Heroku Review Appsのデプロイに失敗した通知がとびません。
app.json解説
なにもしないビルドパックを使うことでとりえあずデプロイが成功します。DBと必要な環境変数を2つ用意しておきます。
{ "addons": ["heroku-postgresql"], "buildpacks": [{"url": "https://github.com/ryandotsmith/null-buildpack"}], "env": { "SECRET_KEY_BASE": {"generator": "secret"}, "REDMINE_LANG": {"value": "en"} } }
問題: seed長い
レビューアプリの親元からコピーするようにします。
具体的にはheroku pg:copy
をします。
他まだ試せていませんが以下のような感じで短縮できそうなのが見えてきました
- seed全部を流さず一部のみ流す
- データ投入を並列で流す/バルクで流す
サンプルリポジトリ
CircleCIの画面はHerokuのAPIキーがガッツリでていておみせできませんが(見せない方法知りたい) .circleci/config.yml
は以下のような感じです。
version: 2.1 orbs: heroku: circleci/heroku@0.0.8 redmine: agileware-jp/redmine-plugin@dev:c39794f executors: ruby-26: docker: - image: circleci/ruby:latest jobs: deploy-review-app: executor: ruby-26 steps: - run: | set -e if [ -z "$CIRCLE_PULL_REQUEST" ]; then echo "Not create pull request yet." exit 1 fi - attach_workspace: at: ~/project - heroku/install - run: | set -e echo 'vendor/bundle' >> .gitignore cat \<<-'EOF' > Procfile web: bin/rails s EOF cat \<<-'EOF' > config/database.yml production: adapter: postgresql EOF - run: | set -e ruby -e 'puts "ruby #{RUBY_VERSION.inspect}"' >> Gemfile echo "gem 'rails_12factor', group: 'production'" >> Gemfile bundle lock - run: | set -e git config --global user.email "seiei@yasslab.jp" git config --global user.name "Seiei Miyagi" git init git add . git add -f Gemfile.lock git add -f config/database.yml git commit -m 'deploy' - run: | set -e export HEROKU_APP_NAME=$(heroku pipelines:info --json redmine-review-app | ruby -rjson -e 'puts JSON.parse($stdin.read).fetch("apps").detect { |a| a.dig("coupling", "stage") == "review" && a.fetch("git_url").end_with?("#{ENV.fetch("CIRCLE_PULL_REQUEST")[/\d+$/]}.git") }&.fetch("name")') if [ -z "$HEROKU_APP_NAME" ]; then echo "heroku review apps not found." exit 1 fi heroku buildpacks:clear --app $HEROKU_APP_NAME git push -f https://heroku:$HEROKU_API_KEY@git.heroku.com/$HEROKU_APP_NAME.git HEAD:master heroku pg:copy --app $HEROKU_APP_NAME --confirm $HEROKU_APP_NAME redmine-review-app-production::DATABASE_URL DATABASE_URL heroku run --app $HEROKU_APP_NAME bin/rails db:migrate heroku run --app $HEROKU_APP_NAME bin/rails redmine:load_default_data heroku ps:scale --app $HEROKU_APP_NAME web=1 workflows: version: 2 test: jobs: - redmine/download: redmine_version: '4.0.4' - redmine/test: executor: redmine/ruby-26-pg requires: [redmine/download] after-script: - deploy-review-app: requires: [redmine/test]
https://github.com/yasslab/hello_world/blob/update_url/.circleci/config.yml
参加者の発表
いろいろトラブっていて冷静にきけなかったけど完全パソコンなしアジャイルLT最高だった...。 あとEdgeTPUおもしろそうだったので触発されてその日帰宅してすぐポチった。 機械学習するぞ〜〜〜(乗り遅れた勢いたら一緒にやりましょう)
他、発表する予定だったネタ
Quine化とAA化が間に合わなくてボツにしたけどQuineの猛者とQuineで戦うために書いた対戦型Quineの構想について懇親会でお話しました。かびさん相談のってくれてありがとうございました。
その後すくすく育ててQuineになった。
telnetで対戦できるQuineなsnake gamehttps://t.co/hYCRTGdtSE pic.twitter.com/aziv7ai6yB
— 𝓜𝓲𝔂𝓪𝓰𝓲 (@hanachin_) August 4, 2019
解説はこちらから
帰り道
すごい早起きしていて疲れで脳がバグっていて歩いて帰った(12km) とはいえ学生のころの飲み会で宜野湾から那覇徒歩帰宅は普通にやっていたので問題なし、PCが重くなったぐらい。 軽いPCほしい。
Redmine課外活動
Redmineの依存gemにpull requestを出したりしています。
CIを直したり: Update .travis.yml by hanachin · Pull Request #51 · naitoh/rbpdf warningsを消したり: Fix test warnings by hanachin · Pull Request #50 · naitoh/rbpdf 不要なファイルを消したり: Remove unused lib/core/image_science.rb by hanachin · Pull Request #49 · naitoh/rbpdf
redmine trunk changesというブログもやっているけど最近はあまり見れてないです。
つながりたい
Redmine開発している方、ruby-jpのSlackの #redmine チャンネルでよろしくお願いしましょう!!!