前回のおさらいと今回の概要
前回
今回はそこから一歩進んで、
なお、
シナリオ
あるWebサービスの開発現場では、
タスク管理にはRedmineのチケットを使い、
- デプロイ前にリリース予定のチケットをディレクターがチェックするルールだったが、
未チェックのままリリースされる事が多々あった。 - デプロイの履歴が管理されておらず、
過去にどんなリリースがあったかを遡る際に非常に手間がかかっていた。
そこで、
プラグインの仕様
まず1番の問題についてですが、
また、
画面イメージは以下のようになります。


雛形と初期設定
では早速作っていきましょう。まずは雛形を作成します。
$ ruby script/generate redmine_plugin ContinuousDeployment
続いて初期設定です。
Redmine::Plugin.register :redmine_continuous_deployment do
name 'Redmine Continuous Deployment plugin'
author 'Takayuki Kyowa'
description 'Redmine Continuous Deployment plugin'
version '0.0.1'
menu :project_menu, :deploy_history, # ①
{ :controller => 'deployments', :action => 'index' },
:caption => "デプロイ", :last => true,
:param => :project_id # ②
permission :view_deploy_history, { :deployments => [ :index ] }, :public => true # ③
end
今回はメニューの追加先をプロジェクトメニューにしています
permission
:deploy_histories, # パーミッションの名前。パーミッション全体で一意でなければならない。
{ :deployments => [ :index ] }, # 制御対象の画面をあらわすハッシュ。menuメソッドで指定したコントローラ / アクションを指定する。
:public => true # trueにすると常に表示される。
permissionメソッドで注意しなければならないのは、

マイグレーション
次にマイグレーションを作成します。今回必要になるのはデプロイの履歴を表現するモデルです。名前はDeployHistoryとしましょう。
$ ruby script/generate redmine_plugin_model ContinuousDeployment DeployHistory $ mv db/migrate/20100314093908_create_deploy_histories.rb db/migrate/001_create_deploy_histories.rb
DeployHistoryモデルは関連モデルに以下の3つを持たせます。
- デプロイ時のプロジェクト
- デプロイを実施したユーザ
(ボタンを押した人) - デプロイ時のチェンジセット
他にもデプロイ開始時刻や終了時刻、
class CreateDeployHistories < ActiveRecord::Migration
def self.up
create_table :deploy_histories do |t|
t.column :project_id, :integer # デプロイ時のプロジェクト
t.column :deployer_id, :integer # デプロイしたユーザ
t.column :changeset_id, :integer # デプロイ時のチェンジセット(リビジョン)
t.column :started_on, :datetime # 開始時間
t.column :deployed_on, :datetime # 終了時間
t.column :return_code, :integer # デプロイの実行結果。コマンドの終了ステータスを保存する。
t.column :log, :text # デプロイのログ
end
end
def self.down
drop_table :deploy_histories
end
end
一通り作成したらmigrateを実行します。
$ RAILS_ENV=production rake db:migrate_plugins
モデルの実装
続いてモデルです。関連と終了ステータスの名前だけを書いたシンプルなコードです。
class DeployHistory < ActiveRecord::Base
belongs_to :project
belongs_to :deployer, :class_name => 'User', :foreign_key => 'deployer_id'
belongs_to :changeset
def result_name
self.return_code == 0 ? "成功" : "失敗"
end
end
デプロイ履歴画面の実装
さて、
$ ruby script/generate redmine_plugin_controller ContinuousDeployment deployments index
コントローラから実装していきます。
class DeploymentsController < ApplicationController
before_filter :find_project # ①
def index
@histories = DeployHistory.find(:all)
end
private
def find_project
@project = Project.find(params[:project_id])
end
プロジェクト配下では@projectインスタンス変数が必須になる為、
次にビューの実装です。デプロイボタンがあること以外は前回とほぼ同様のコードになっています。デプロイを実施するアクション名はdeployとしました。
<style type="text/css">
table.deploy-histories {
width: 80%;
}
tr.deploy-history {
text-align:center;
}
</style>
<h2>デプロイ</h2>
<div style='margin-bottom: 10px'>
<%= button_to "デプロイ!", { :action => "deploy", :project_id => params[:project_id] }, :confirm => "デプロイします。よろしいですか?" %>
</div>
<table class="list deploy-histories">
<thead><tr>
<th>完了日時</th>
<th>開始日時</th>
<th>結果</th>
<th>担当者</th>
<th>リビジョン</th>
<th>詳細</th>
</tr></thead>
<tbody>
<%- @histories.each do |history| -%>
<tr class="<%= cycle("odd", "even") %> deploy-history">
<td><%=h history.deployed_on.strftime("%F %H:%M") %></td>
<td><%=h history.started_on.strftime("%F %H:%M") %></td>
<td><%=h history.result_name %></td>
<td><%=h history.deployer.login %></td>
<td><%=h history.changeset.revision %></td>
<td><%= link_to "URL", :action => "show", :id => history.id, :project_id => history.project_id %></td>
</tr>
<%- end -%>
</tbody>
</table>
デプロイコマンドの実装
では核心のdeployアクションに入りましょう。このアクションでチケットのステータスの確認や、
def deploy
### ① チケットの状態チェック
warning_status = IssueStatus.find(:first, :conditions => { :name => "実装完了" })
if @project.issues.map(&:status).include?(warning_status)
flash[:error] = "未チェックのチケットが存在します!"
redirect_to :action => "index", :project_id => params[:project_id]
return
end
### ② リポジトリの更新、最新リビジョン番号の取得
repository = @project.repository
repository.fetch_changesets
latest_changeset = Changeset.find(:first, :conditions => { :repository_id => repository }, :order => "revision DESC")
### ③ デプロイの実行
start = Time.now
cmd = "cap deploy 2>&1"
finish = Time.now
### ④ 実行結果の確認
out = `#{cmd}`
return_code = $? ? $?.exitstatus : 8929
### ⑤ 履歴の作成
DeployHistory.create!(:project => @project,
:changeset => latest_changeset,
:log => out,
:started_on => start,
:deployed_on => finish,
:deployer => User.current)
### ⑥ indexアクションに戻る
flash[:notice] = "デプロイに成功しました! リビジョン: #{latest_changeset.revision}"
redirect_to :action => "index", :project_id => params[:project_id]
end
だいぶ長いコードになりました。順々に説明していきます。
まず、
![図4 flash[:error]とflash[:notice]のデザイン 図4 flash[:error]とflash[:notice]のデザイン](/assets/images/dev/serial/01/ruby/0036/thumb/TH800_004.jpg)

②では履歴の保存用にリポジトリの最新リビジョンを取得しています。注意しなければならないのは、
③, ④ではデプロイの実施と前後の処理時間を取得しています。Linuxのコマンドを実行しているだけなのでcapistranoである必要性は特になく、
そして⑤で履歴を作成し、
まだデプロイしてもアクティビティへ表示されるようにはなっていませんが、
また、
アクティビティへの表示
さて、
アクティビティ機能はメニューなどと同様にプラグインから利用する事が可能ですが、
- アクティビティの提供元となるモデルを指定する
- モデルに対してアクティビティを生成するための共通インターフェースを実装する
今回はもちろんDeployHistoryモデルが提供元です。
まず、
activity_provider :deploy_histories, :class_name => "DeployHistory"
次にモデルで実装する共通インターフェースですが、
acts_as_activity_provider :timestamp => "deployed_on",
:author_key => :deployer_id,
:find_options => { :include => :project } # ①
acts_as_event :title => Proc.new {|inst| "リビジョン#{inst.changeset.revision}番がデプロイされました。"},
:description => :log, # 120文字に短縮されます。
:datetime => :deployed_on,
:url => Proc.new {|inst| {:controller => 'deployments', :action => 'show', :id => inst.id, :project_id => inst.project_id }},
:author => :deployer
①ではfind_
これで完了です。再起動してアクティビティを見ると、
おわりに
今回はContinuousDeploymentの開発を通して、
ちなみにデプロイやビルドとRedmineとの連動、
最終回となる次回はプラグイン開発の落ち穂拾いと、