目次
はじめに
皆さんはバグ管理システムは何をお使いですか?
当社ではRedmineを使うケースがしばしばあります。Redmineはチケットが割り当てられたときなどに通知をメールで送信してくれますが、メールだとどこかスピード感に欠けると感じたことがあるのではないでしょうか。
昨今、「DevOps」という単語がトレンドとして上がってきていることもあってか、チャットを採用している開発チームが増えてきていますし、Redmineの通知もチャットを通じて送信してくれないかな、と感じることもあると思います。しかし、チャットを導入しようにも、セキュリティの都合などからクラウドサービスは採用できないというケースもあるでしょう。
そこで今回は、DockerによるRedmineとRocket.Chat(*1)の構築と、RedmineとRocket.Chatの連携の設定をしていきます(*2)。
*1 オープンソースソフトウェアのウェブチャットサーバ。インターネット上で提供されているSlackのようなチャットサービスを、プライベートな独自のプラットフォーム上で構築することができます。
*2 前提として、AWSのEC2インスタンス(CentOS)上に構築します。別環境の場合は適宜読み替える箇所が出てくると思いますが、ご了承ください。AWSマネジメントコンソールの画面は2019/02/26時点のものです。
CentOSにDockerでRedmineを構築する
それではまず、CentOSにDockerでRedmineを構築します。念の為インスタンスの作成から手順を載せておきます。
インスタンスの作成
1.AWSマネジメントコンソールにログインし、「EC2」→サイドメニューの「インスタンス」→「インスタンスの作成」の順に選択する。
2.「コミュニティ AMI」の「Cent OS」にチェックを入れ、検索窓に「CentOS 7.6.1810 x86_64 with cloud-init (HVM) – ami-00fff3eb6cfbbc19f」と入力し、出てきたAMIの「選択」をクリックする
3.「インスタンスの詳細の設定」→「ストレージの追加」→「タグの追加」→「セキュリティグループの設定」を適宜行い、インスタンスを作成する
DockerによるRedmineのインストール
Tera Termにて、作成したインスタンスに接続して、Dockerのインストールと、DockerによるRedmineのインストールをします。
1.rootユーザに変更する
1 |
sudo su |
2.Dockerをインストールする
1 2 3 |
yum install -y yum-utils yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo yum install -y docker-ce |
3.起動時の設定をする
1 2 |
systemctl start docker systemctl enable docker |
4.docker-composeをインストールする
1 2 |
curl -L https://github.com/docker/compose/releases/download/1.23.2/docker-compose-`uname -s`-`uname -m` -o /usr/local/bin/docker-compose chmod +x /usr/local/bin/docker-compose |
5. SELinuxを無効にする。
1 2 |
vi /etc/sysconfig/selinux setenforce 0 |
1 2 3 4 5 6 7 8 9 10 11 12 |
# This file controls the state of SELinux on the system. # SELINUX= can take one of these three values: # enforcing - SELinux security policy is enforced. # permissive - SELinux prints warnings instead of enforcing. # disabled - No SELinux policy is loaded. #SELINUX=enforcing SELINUX=disabled # SELINUXTYPE= can take one of three values: # targeted - Targeted processes are protected, # minimum - Modification of targeted policy. Only selected processes are protected. # mls - Multi Level Security protection. SELINUXTYPE=targeted |
6.Redmine用のディレクトリを作成し、docker-compose.ymlファイルを作成する
1 2 3 |
mkdir -p /var/www/redmine cd /var/www/redmine vi docker-compose.yml |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
version: '3.5' services: redmine: image: redmine:passenger container_name: redmine ports: - 3000:3000 environment: TZ: Asia/Tokyo REDMINE_DB_MYSQL: mysql REDMINE_DB_DATABASE: redmine REDMINE_DB_USERNAME: redmine REDMINE_DB_PASSWORD: redmine REDMINE_DB_ENCODING: utf8 depends_on: - mysql restart: always volumes: - /var/www/redmine/files:/usr/src/redmine/files - /var/www/redmine/log:/usr/src/redmine/log - /var/www/redmine/plugins:/usr/src/redmine/plugins - /var/www/redmine/public/themes:/usr/src/redmine/public/themes mysql: image: mysql:5.7 container_name: mysql restart: always environment: TZ: Asia/Tokyo MYSQL_ROOT_PASSWORD: devops MYSQL_DATABASE: redmine MYSQL_USER: redmine MYSQL_PASSWORD: redmine volumes: - mysql-data:/var/lib/mysql command: mysqld --character-set-server=utf8 --collation-server=utf8_unicode_ci volumes: mysql-data: name: mysql-redmine |
7.ユーザ権限を変更する
1 2 3 4 5 |
sudo gpasswd -a $USER docker sudo systemctl restart docker exit sudo su - cd /var/www/redmine |
8.docker-composeでRedmineを立ち上げる
1 2 |
docker-compose up -d docker ps |
9.RedmineとMySQLの設定をする
1 2 |
docker exec redmine bundle exec rake redmine:load_default_data RAILS_ENV=production REDMINE_LANG=ja docker exec -it mysql mysql_config_editor set --host=localhost --user=redmine --password |
10.下記URLにアクセスし、Redmineのホーム画面が表示されることを確認する
http://[インスタンスのパブリックIPアドレス]:3000/
Rocket.Chatとの連携プラグインのインストール
次に、Rocket.Chatとの連携プラグインをインストールします。
1.必要なライブラリをインストールする
1 2 |
docker exec redmine gem install 'slim-rails' docker exec redmine gem install 'validate_url' |
2.Chatとの連携プラグインをインストールする
1 2 |
docker exec redmine git clone git://github.com/alphanodes/redmine_messenger.git plugins/redmine_messenger docker exec redmine bundle exec rake redmine:plugins:migrate RAILS_ENV=production |
3.コンテナを再起動する
1 |
docker restart redmine |
4.Redmineの管理者ユーザでログインし、ヘッダーメニューの「管理」→「プラグイン」にアクセスし、「Redmine Messenger」プラグインが表示されていることを確認する
※管理者ユーザは最初、下記情報でログインすることができます。
ユーザID : admin
パスワード : admin
CentOSにDockerでRocket.Chatを構築する
次に別のCentOSにDockerでRocket.Chatを構築します。
CentOSのインスタンス作成手順はRedmineのものとほぼ(追加したタグ「Name」の値以外)同じなので、割愛します。
DockerによるRocket.Chatのインストール
Tera Termにて、作成したインスタンスに接続して、Dockerのインストールと、DockerによるRocket.Chatのインストールをします。
1.rootユーザに変更する
1 |
sudo su |
2.Dockerをインストールする
1 2 3 |
yum install -y yum-utils yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo yum install -y docker-ce |
3.起動時の設定をする
1 2 |
systemctl start docker systemctl enable docker |
4.docker-composeをインストールする
1 2 |
curl -L https://github.com/docker/compose/releases/download/1.23.2/docker-compose-`uname -s`-`uname -m` -o /usr/local/bin/docker-compose chmod +x /usr/local/bin/docker-compose |
5.MongoDBのコンテナイメージを取得する
1 |
docker pull mongo |
6.Rocket.Chatのコンテナイメージを取得する
1 |
docker pull rocketchat/rocket.chat |
7.Rocket.Chat用のディレクトリを作成し、docker-compose.ymlファイルを作成する
1 2 3 4 |
mkdir -p /var/www/rocket.chat/data/runtime/db mkdir -p /var/www/rocket.chat/data/dump cd /var/www/rocket.chat vi docker-compose.yml |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
version: '3' services: db: image: mongo:latest volumes: - ./data/runtime/db:/data/db - ./data/dump:/dump command: mongod --smallfiles rocketchat: image: rocketchat/rocket.chat:latest environment: MONGO_URL: mongodb://db:27017/rocketchat ROOT_URL: http://localhost:3000 Accounts_UseDNSDomainCheck: 1 links: - db:db volumes: - ./uploads:/app/uploads depends_on: - db ports: - 3000:3000 |
8.ユーザ権限を変更する
1 2 3 4 5 |
sudo gpasswd -a $USER docker sudo systemctl restart docker exit sudo su - cd /var/www/rocket.chat |
9.docker-composeでRocket.Chatを立ち上げる
1 2 |
docker-compose up -d docker ps |
10.下記URLにアクセスし、Rocket.Chatのセットアップウィザード画面が表示されることを確認する
※セットアップウィザード画面は初期表示時のみで、それ以外の場合はホーム画面が表示される
http://[インスタンスのパブリックIPアドレス]:3000/
Rocket.Chatの初期設定をする
1.下記の通りに管理者情報を入力し、「次へ」をクリックする
名前 : 任意の名前
ユーザ名 : [姓]-[名] ※姓と名は先頭のみ大文字のアルファベット
組織の電子メール: 任意のメールアドレス
パスワード : 任意のパスワード
2.組織情報はスキップできるので、今回はそのまま「次へ」をクリックする
※任意で設定してください。
3.サーバ情報もスキップできるので、今回はそのまま「次へ」をクリックする
※任意で設定してください。
4.今回は、サーバーを登録する項目で「次を自分で実施し、スタンドアローン利用する」を選択し、「次へ」をクリックする
5.「ワークスペースを開く」をクリックする
6.Rocket.Chatのユーザホーム画面が表示されることを確認する
RedmineとRocket.Chatの連携設定をする
以上でインストールが完了したので、ついにRedmineとRocket.Chatの連携設定をします。
Rocket.Chatの設定
1.サイドメニューの[新しいチャンネルを作成]をクリックする
2.下記の通りにチャンネル情報を入力し、「作成」をクリック
プライベートグループ : 任意(デフォルトはオン)
読み取り専用チャンネル: デフォルト
放送チャンネル : デフォルト
チャンネル名 : 任意
ユーザを招待 : 任意(自分以外に招待したい人を追加する。後で追加することもできる。)
3.チャンネルが作成されていることを確認する
4.サイドメニューの「オプション」→「管理」→「サービス連携」の順にクリックする
5.「新しいサービス連携」をクリックする
6.「着信WebHook」をクリックする
7.下記の通りに項目を設定し、「変更を保存」をクリックする
有効 : 「はい」を選択
名前(オプション): 任意の名前
投稿先チャンネル : 投稿先のチャンネルを指定
※上記以外はデフォルトでもOKです。
8.「WebHook URL」をコピーする
Redmineの設定
1.ヘッダーメニューの「管理」→「プラグイン」の順にクリックし、「Redmine Messenger」の「設定」をクリックする
※ここで設定した内容が、各Redmine上のプロジェクトでデフォルトとなります。
2.下記の通りに項目を設定し、「適用」をクリックする
メッセンジャーのURL : コピーしたWebHook URL(ホスト名がlocalhostになっている場合は、適宜置き換える)
メッセンジャーのチャンネル : 通知先のRocket.Chatのチャンネル名
ウォッチャーを表示する : チェックを入れる(有効にする)
※上記以外はデフォルトでもOKです。
動作確認をする
動作確認のため、Redmineでプロジェクトとチケットを作成してみます。
Rocket.Chatを見てみると、作成したチャンネルに投稿が来ている!
しかし、少し味気ないような気がします。せめて担当者やウォッチャーにメンション(通知)を飛ばしたいですね。ということで、スクリプトを少しいじってみましょう。
Redmine Messengerで担当者とウォッチャーに通知が来るようにカスタマイズする
Redmine Adminユーザだけではわかりづらいので、RedmineとRocket.Chatにそれぞれ下記のユーザを追加しました。
【Redmine】
1人目
ログインID: yamauchi
名 : Kentaro
姓 : Yamauchi
2人目
ログインID: mirai
名 : Sou
姓 : Mirai
【Rocket.Chat】
1人目
名前 : 未来 創
ユーザ名: Mirai-Sou
Redmineの設定の変更
メンションを送る際にRedmineが送信する名前の情報がデフォルトだと「名 姓」になっているので、変更します。
「名 姓」のままがいい場合はRocket.Chatのユーザ名を「名-姓」にすれば問題なく設定できます。
1.ヘッダーメニューの「管理」→「設定」→「表示」タブにアクセスし、下記項目の設定を変更し、「保存」をクリックする
デフォルト言語 : 「Japanese(日本語)」
ユーザ名の表示形式: 「姓 名」
Redmine Messengerのカスタマイズ
Redmine Messengerはチケットの更新時などにウォッチャーを送信しないようになっているようです。そこで、Redmine Messengerのソースコード(Ruby)を少しカスタマイズします。
1.「/usr/src/redmine/plugins/redmine_messenger/lib/redmine_messenger/patches/issue_patch.rb」をコンテナ内からホストにコピーしてくる
1 |
docker cp redmine:/usr/src/redmine/plugins/redmine_messenger/lib/redmine_messenger/patches/issue_patch.rb issue_patch.rb |
2.ホストで「issue_patch.rb」を編集
1 |
vi issue_patch.rb |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 |
module RedmineMessenger module Patches module IssuePatch def self.included(base) base.send(:include, InstanceMethods) base.class_eval do after_create :send_messenger_create after_update :send_messenger_update end end module InstanceMethods def send_messenger_create channels = Messenger.channels_for_project project url = Messenger.url_for_project project return unless channels.present? && url return if is_private? && !Messenger.setting_for_project(project, :post_private_issues) set_language_if_valid Setting.default_language attachment = {} if description.present? && Messenger.setting_for_project(project, :new_include_description) attachment[:text] = Messenger.markup_format(description) end attachment[:fields] = [{ title: I18n.t(:field_status), value: ERB::Util.html_escape(status.to_s), short: true }, { title: I18n.t(:field_priority), value: ERB::Util.html_escape(priority.to_s), short: true }] if assigned_to.present? attachment[:fields] << { title: I18n.t(:field_assigned_to), value: ERB::Util.html_escape(assigned_to.to_s), short: true } end if RedmineMessenger.setting?(:display_watchers) && watcher_users.count > 0 attachment[:fields] << { title: I18n.t(:field_watcher), value: ERB::Util.html_escape(watcher_users.join(', ')), short: true } end Messenger.speak(l(:label_messenger_issue_created, project_url: "<#{Messenger.object_url project}|#{ERB::Util.html_escape(project)}>", url: send_messenger_mention_url(project, description), user: author), channels, url, attachment: attachment, project: project) end def send_messenger_update return if current_journal.nil? channels = Messenger.channels_for_project project url = Messenger.url_for_project project return unless channels.present? && url && Messenger.setting_for_project(project, :post_updates) return if is_private? && !Messenger.setting_for_project(project, :post_private_issues) return if current_journal.private_notes? && !Messenger.setting_for_project(project, :post_private_notes) set_language_if_valid Setting.default_language attachment = {} if current_journal.notes.present? && Messenger.setting_for_project(project, :updated_include_description) attachment[:text] = Messenger.markup_format(current_journal.notes) end fields = current_journal.details.map { |d| Messenger.detail_to_field d } if status_id != status_id_was fields << { title: I18n.t(:field_status), value: ERB::Util.html_escape(status.to_s), short: true } end if priority_id != priority_id_was fields << { title: I18n.t(:field_priority), value: ERB::Util.html_escape(priority.to_s), short: true } end if assigned_to.present? fields << { title: I18n.t(:field_assigned_to), value: ERB::Util.html_escape(assigned_to.to_s), short: true } end attachment[:fields] = fields if fields.any? # 20190204 Yamauchi added start if RedmineMessenger.setting?(:display_watchers) && watcher_users.count > 0 attachment[:fields] << { title: I18n.t(:field_watcher), value: ERB::Util.html_escape(watcher_users.join(', ')), short: true } end # 20190204 Yamauchi added end Messenger.speak(l(:label_messenger_issue_updated, project_url: "<#{Messenger.object_url project}|#{ERB::Util.html_escape(project)}>", url: send_messenger_mention_url(project, current_journal.notes), user: current_journal.user), channels, url, attachment: attachment, project: project) end private def send_messenger_mention_url(project, text) mention_to = '' if Messenger.setting_for_project(project, :auto_mentions) || Messenger.textfield_for_project(project, :default_mentions).present? mention_to = Messenger.mentions(project, text) end "<#{Messenger.object_url(self)}|#{ERB::Util.html_escape(self)}>#{mention_to}" end end end end end |
3.ホストからコンテナ内に「issue_patch.rb」をコピー
1 |
docker cp issue_patch.rb redmine:/usr/src/redmine/plugins/redmine_messenger/lib/redmine_messenger/patches/issue_patch.rb |
4.コンテナを再起動する
1 |
docker restart redmine |
Rocket.Chatのカスタムスクリプト
最後に、Rocket.Chat側に送信されたメッセージを通知させるようにカスタマイズします。
1.サイドメニューの「オプション」→「管理」→「サービス連携」の順にクリックする
2.先程作成した「着信WebHook – [任意の名前]」をクリックする
3.下記の通りに項目を設定し、「変更を保存」をクリックする
スクリプトを有効にする: はい
Script: 次の内容
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
class Script { /** * @params {object} request */ process_incoming_request({ request }) { var responseText = request.content.text; var fields = request.content.attachments[0].fields; // 担当者をチャット向けに変換 var assignedField = fields.filter(function(value, index) { return value.title == "担当者" }); if (assignedField.length != 0) { var assignedValue = assignedField[0].value; responseText += "\n担当者: @" + assignedValue.split(" ").join("-"); } // ウォッチャーをチャット向けに変換 var watcherField = fields.filter(function(value, index) { return value.title == "ウォッチャー" }); if (watcherField.length != 0) { responseText += "\nウォッチャー: "; var watchers = watcherField[0].value.split(", "); watchers.forEach(function(value, index, array) { responseText += "@" + value.split(" ").join("-"); if (index != array.length - 1) { responseText += ", " } }); } // メッセージを設定 request.content.text = responseText; return { content : request.content }; } } |
動作確認をする
再度動作確認のため、Redmineでチケットの更新をしてみます。
Rocket.Chatを見てみると、作成したチャンネルに今度は通知が来ている!デスクトップ通知も来てほしかったのですが、証明書がない場合はChromeだとブロックされてしまうようです。Firefoxやデスクトップアプリの場合は、デスクトップ通知も問題なく来ました。担当者とウォッチャーにメンションが飛んでいることも確認できました!
ちなみに、Rocket.Chatでは、自分宛てにメンションがついている投稿だけを表示する機能があるので、埋もれても問題なく表示することができます。
おわりに
いかがでしたでしょうか。今回はRedmineとRocket.Chatの構築と連携の設定に加えて、少しプラグインをカスタマイズしてみました。RedmineのプラグインはRuby、Rocket.ChatのカスタムスクリプトはJavaScript(Meteor)で書くことができるので、比較的ネットでも記事を見つけやすく、意外と簡単にカスタマイズできると思います。複数環境を用意する場合も、docker-compose.ymlがあれば構築は簡単ですし、Dockerイメージファイルを作成すれば環境の複製も比較的容易になります。今後は、これらの環境の構築や連携の設定などを自動化し、GUIベースで簡単に環境構築できるようにしていきたいと考えています。
執筆者プロフィール
- 社内の開発プロジェクトの技術支援や、Javaにおける社内標準フレームワークの開発を担当しています。Spring BootとTDDに手を出しつつ、LINE Botとかもいじったりしています。最近はマイクロサービスを勉強しつつ、クラウドアプリケーションを開発できるエンジニアの育成にも力を入れてます!