• redmineの wikiマクロで graphviz その3

    Wiki Graphviz macro プラグイン
    例によって無保証です。
    説明はその2の方に。

    今回はハマったことというか知ったこととか。
    以下、redmine-0.8.0RC1前提で。


    redmineのwikiマクロに関して

    • ・{{macro_list}}マクロがデフォルトで入っていて、使えるマクロの一覧を表示できる。知らんかった。。
      • マクロ名と登録時に「desc」で指定した内容が表示される
    • {{include(Foo)}}マクロがデフォルトで入っていて、別ページをincludeできる。知らんかった。。
    • wikiマクロの登録は、Redmine::WikiFormatting::Macros.registerにブロックを渡す。
      • descにはwiki記法が使えるが、今にして思えばtextile以外のフォーマッタを使っている場合にマズイか・・・。
      Redmine::WikiFormatting::Macros.register do
        desc <<'EOF'
    Render graph image from the wiki page which is specified by macro-args.

      !{{graphviz(Foo)}}
      !{{graphviz(option=value...,Foo)}}

    * options are:
    ** format={png|jpg}
    ** layout={dot|neato|fdp|twopi|circo}
    ** target={_blank|any}
    ** with_source
    EOF
        macro :graphviz do |wiki_content_obj, args|
    (略)
    • マクロ本体はmacroにシンボルとブロックを指定する
      • ブロックの引数はWikiContentモデルとマクロの引数に指定した文字列を「,」で分割したArray
        • {{マクロ(引数)}}
    • wiki中の{{マクロ}}の都度ブロックが呼び出される
      • ブロックが返した文字列がマクロ展開後のHTMLとして出力される。
        nilを返すと元のマクロ記述がそのまま表示される
      • プレビュー時、ブロックが呼び出されたときに「プレビュー中のテキスト」を得られない
        • graphviz_meマクロで編集/プレビューしながら図を確認できたら便利だよなぁと思ったら渡ってくるのは保存済みのWikiContentモデルだった。
        • 新しいwikiページをプレビューするとobjにはnilが渡ってくる
      • ブロックはActionView::Baseのインスタンスから呼び出される
        • ブロック内でself.to_sしたら"#<ActionView::Base:0xxxxxxxxx>"だった
        • なのでself.controllerからキャッシュメソッドを呼び出したり出来る
        • graphvizマクロの場合、マクロ展開時(Viewのコンテキスト)、画像表示時(コントローラのコンテキスト)のコードをできるだけ共有したかったので都合がよかった
    • ブロックから例外を飛ばすとwikiビュー内にエラー表示できる
      Error executing the マクロ名 macro (メッセージ)

    redmineプラグインに関して

    • プラグインROOTのinit.rbで登録すると利用可能になる
      Redmine::Plugin.register :wiki_graphviz_plugin do
    • プラグイン固有のコントローラのrouteはプラグインROOTのroutes.rbで追加定義できる
      connect 'wiki/:id/:page/graphviz', :controller => 'wiki_graphviz', :action => 'graphviz'

    プラグイン設定関係

    • 管理→プラグイン→(プラグインごとの)設定で表示されるプラグイン設定画面
    • Redmine::Plugin.register時に、settingsで:partialに指定した設定画面を表示できる。
      settings :default => {'cache_seconds' => '0'}, :partial => 'wiki_graphviz/settings'
    • テンプレートはvendor/plugins/プラグイン/app/views/に置く
      • 簡単で便利だけど、:partialの指定を他のプラグインと同じにしてしまうと別のプラグインのテンプレートが適用されてしまうことがある。
      • extra/sample_pluginをセットアップした状態で:partialに同じ値を設定しておいたらsample_pluginの設定ビューが出てきてかなり悩んだ。 
      • わざと存在しないテンプレート名を指定しておくとテンプレートの検索パスがログに出る。これを見る限り、プラグインの検索パスが連結された状態になっているようだ。
        Missing template wiki_graphviz/_settingsi.html.erb in view path 
        ・・・・/redmine-0.8.0_RC1/vendor/plugins/wiki_graphviz_plugin/app/views:
        ・・・・/redmine-0.8.0_RC1/vendor/plugins/sample_plugin/app/views:
        ・・・・/redmine-0.8.0_RC1/app/views 
      • stylesheet_link_tagみたいに:pluginを指定してなんとかしてくれんだろうか
    • プラグインの名前が長すぎると、設定値を保存できるが再表示した際に値が正しく表示されない(デフォルト値が表示される)
    mysql> desc settings;
    +------------+-------------+------+-----+---------+----------------+
    | Field      | Type        | Null | Key | Default | Extra          |
    +------------+-------------+------+-----+---------+----------------+
    | id         | int(11)     | NO   | PRI | NULL    | auto_increment |
    | name       | varchar(30) | NO   |     |         |                |
    | value      | text        | YES  |     | NULL    |                |
    | updated_on | datetime    | YES  |     | NULL    |                |
    +------------+-------------+------+-----+---------+----------------+
    • 以前は「redmine_wiki_graphviz_plugin」という名前にしていた。redmine内部で「plugin_」が前置されて35文字になり、切れているのが原因っぽいが深追いしてない。プラグイン名を短くした。
    mysql> select * from settings;
    (中略)
    | 24 | plugin_redmine_wiki_graphviz_p | --- !map:HashWithIndifferentAccess
    cache_seconds: "2300"
    | 25 | plugin_wiki_graphviz_plugin    | --- !map:HashWithIndifferentAccess
    cache_seconds: "3600"
    (後略)

    Railsに関して
    ※redmine-0.8.0RC1同梱のもの。2.1?

    キャッシュ関係
    • config/environments/環境.rbに設定することで有効になる
      config.action_controller.perform_caching             = true
      config.action_controller.cache_store = :mem_cache_store, "localhost"
    • cache_storeの挙動が微妙に違う。
      • デフォルトのActiveSupport::Cache::MemoryStoreはrubyオブジェクトを格納/取り出しできる。当然といえば当然。
      • ActiveSupport::Cache::FileStoreは文字列前提
      • ActiveSupport::Cache::MemCacheStoreは、(内部で使っているMemCacheの挙動で)値の格納/取り出し時にマーシャリングが行われるので結果的にrubyオブジェクトを(大体)格納できる。
        • :rawオプションでマーシャリングをナシにできる。
    • Catalyst::Plugin::CacheのイメージでいたのでFileStoreからHashが復元されず「おっかしいな~?」てことになった。結局Graphvizマクロプラグインではmemcachedだけ対応とした。

    • ・viewからHEADに何か差し込みたい場合はcontent_forが使える。これは便利な仕組みだな~。
      <% content_for :header_tags do %>
         <%= stylesheet_link_tag "wiki_graphviz.css",
             :plugin => "wiki_graphviz_plugin", :media => :all %>
      <% end %>
    • 画像を出力するときは:text&:content_type指定かつlayoutナシのrender。
      render :text => img, :layout => false, :content_type => "image/png"
    • しかし、charsetをつけられてしまう。。。
      ActionController::Baseのassign_default_content_type_and_charsetを見るにContent-Transfer-Encodingがbinaryに設定されていないとデフォルトのcharsetがついてしまうようだ・・。



  • redmineの wikiマクロで graphviz その2

    wikimacro2.png
    前回のつづき。

    クリッカブルマップを吐き出すところまでいけたので公開。
    Wiki Graphviz macro プラグイン
    例によって無保証です。

    主なfeature

    • wikiマクロ、{{graphviz}}と{{graphviz_me}}を追加
    • wikiページをdot形式で記述し、これらのマクロから呼び出すことでグラフ画像をレンダリング
    • ノードにURL属性を指定するとクリッカブルマップに

    {{graphviz}} マクロ

    {{graphviz(Foo)}}
    {{graphviz(option=value,Foo)}}
    {{graphviz(layout=neato,target=_blank,with_source,Foo)}}
      
    • 指定したwikiページをdotテキストとみなしてグラフ画像をレンダリング。IMGタグを差し込む
    • オプションは以下のとおり
      • format={png|jpg}
        • 画像形式。
        • 現在のところpngとjpegだけ
      • layout={dot|neato|fdp|twopi|circo}
        • レイアウトエンジン指定。
        • デフォルトはdot
      • target={_blank|any}
        • クリッカブルマップを生成する際のTARGET属性に指定する値
        • 別ウィンドウで開くなら_blank
      • with_source
        • 画像の直後に読みこんだdotの内容をPREで出力

    {{graphviz_me}} マクロ

    // {{graphviz_me}}
    // {{graphviz_me(option=value)}}
    // {{graphviz_me(layout=neato,target=_blank,with_source)}}
      
    • 別のwikiページを指定する代わりにこのマクロを含むwikiページをレンダリング対象とする。これ以外は{{graphviz}}マクロと同じ。
    • 必然的に、dotフォーマット上のコメントアウトが必要。コメントアウトしないとレンダラがエラー終了する

    dotの書式や指定できる値については、Graphvizチュートリアルがまとまっていて便利。


    必要なもの
    • Redmine 0.8.0 RC1 or later.
    • Graphvizとrubyバインディング。本家でrpmを配布している。  http://www.graphviz.org
          graphviz-2.20.3-1.el5
          graphviz-gd-2.20.3-1.el5
          graphviz-ruby-2.20.3-1.el5
    • memcached (optional)


    インストール
    • redmine公式サイトの説明にもあるように、プラグインのアーカイブを展開してvendor/plugins/に配置してredmineを再起動するだけ(僕のところはmongrel+mod_proxy_balancerなので)
    オプション
    • レンダリングした画像をキャッシュしておくことができます。キャッシュを用いる場合は、memcachedとcache_storeを設定します。
    • cache_storeの設定例
           e.g.) config/environments/production.rb
           config.action_controller.perform_caching = true
           config.action_controller.cache_store = :mem_cache_store, "localhost"
    • redmineを再起動
    • プラグインの設定ページ(管理→プラグイン→Wiki graphviz macro plugin)で、キャッシュ秒数を1以上に設定

    僕の環境は以下のとおり。

    CentOS 5.2
      ruby-1.8.5-5.el5_2.6
        mongrel-1.1.5
        mongrel_cluster-1.0.5
        redmine-0.8.0_RC1
      memcached-1.2.6-1.el5.rf
      MySQL-server-5.0.51a-tritonn.1.0.10
      graphviz-2.20.3-1.el5
      graphviz-gd-2.20.3-1.el5
      graphviz-ruby-2.20.3-1.el5

    既知の問題
    • wikiのプレビューで編集中の内容をレンダリングできない
      (wikiマクロに編集中の内容を受け取るI/Fがない。。。)
    とりあえず上記の環境では動くみたい。



  • redmineの wikiマクロで graphviz その1

    graphviz_macro.png
    redmineのwikiでgraphvizが使いたい、と思ったのだった。

    どんだけgraphviz好きなんだって感じだけど、まぁ好きだ。

    実際は、別途webdot的なサービスを作って画像挿入のtextile記法、ってやり方で利用してはいたんだけどredmineプラグイン作成の習作も兼ねてやってみた。

    wikiにdotを記述して、別のwikiページからgraphvizマクロで呼び出すとIMGに置き換わる、という流れ。

    Wiki Graphviz macro プラグイン
    例によって無保証です。
    説明はその2の方に。

    環境は以下のとおり
    CentOS 5.2
      MySQL-server-5.0.51a-tritonn.1.0.10
      ruby-1.8.5-5.el5_2.6
        mongrel-1.1.5
        redmine-0.8.0_RC1


    WikiControllerから地味に追いかけていったら元々マクロを拡張するI/Fを持っていて、lib/redmine/wiki_formatting/macros.rbに説明があった。
            #   Redmine::WikiFormatting::Macros.register do
            #     desc "This is my macro"
            #     macro :my_macro do |obj, args|
            #       "My macro output"
            #     end
            #   end
    
    ということらしい。

    次はどこでマクロ登録を行うか。
    redmineのプラグイン機構に乗っかってプラグインの初期化時にやったらよさそう、ということでscript/generateを使ってひな形を作成。

    % script/generate redmine_plugin wiki_graphviz_plugin
          create  vendor/plugins/redmine_wiki_graphviz_plugin/app/controllers
          create  vendor/plugins/redmine_wiki_graphviz_plugin/app/helpers
          create  vendor/plugins/redmine_wiki_graphviz_plugin/app/models
          create  vendor/plugins/redmine_wiki_graphviz_plugin/app/views
          create  vendor/plugins/redmine_wiki_graphviz_plugin/db/migrate
          create  vendor/plugins/redmine_wiki_graphviz_plugin/lib/tasks
          create  vendor/plugins/redmine_wiki_graphviz_plugin/assets/images
          create  vendor/plugins/redmine_wiki_graphviz_plugin/assets/javascripts
          create  vendor/plugins/redmine_wiki_graphviz_plugin/assets/stylesheets
          create  vendor/plugins/redmine_wiki_graphviz_plugin/lang
          create  vendor/plugins/redmine_wiki_graphviz_plugin/test
          create  vendor/plugins/redmine_wiki_graphviz_plugin/README.rdoc
          create  vendor/plugins/redmine_wiki_graphviz_plugin/init.rb
          create  vendor/plugins/redmine_wiki_graphviz_plugin/lang/en.yml
          create  vendor/plugins/redmine_wiki_graphviz_plugin/test/test_helper.rb
    extraのsample_pluginを真似して上記プラグイン名にしたのだけど、これが元で後で痛い目に。「redmine_」が前置された結果、プラグインの名前が30文字を超えてしまいプラグイン設定の更新・参照に問題が発生。
    ※settingsテーブルのname属性がvarchar(30)なことが原因っぽい

    なので、手作業で「wiki_graphviz_plugin」に変更した。
    ※今にしてみれば_pluginを削ったらよかったのか。。。。

    拡張マクロの登録は、プラグインのinit.rbでプラグイン登録と同時にやってみた。これが正しいかはわからない。
    require 'redmine'

    RAILS_DEFAULT_LOGGER.info 'Starting wiki_graphviz_plugin for Redmine'

    Redmine::Plugin.register :wiki_graphviz_plugin do
    name 'Graphviz Wiki-macro Plugin'
    author 'tckz'
    description 'Wiki macro to render the graph using graphviz'
    version '0.0.1'
    settings :default => {'cache_seconds' => '0'}, :partial => 'wiki_graphviz/settings'

    Redmine::WikiFormatting::Macros.register do

    desc "Render the wiki page into graph image using graphviz.\n\n" +
    " !{{graphviz(Foo)}}\n" +
    " {{graphviz(format={png|jpg},Foo)}}\n" +
    " {{graphviz(layout={dot|neato|fdp|twopi|circo},Foo)}}\n"
    macro :graphviz do |obj, args|

    end
    end
    end

    こんな感じで登録しておくと、wikiページ(view)のレンダリング中に呼び出される。マクロ展開後のHTMLを返せばOK。

    dotの構文に誤りがあったときはwikiマクロのエラーとして表示する形にしたいからIMG要素出力時点で一度画像をレンダリングする方向で。dot解釈時点で一旦レンダリングしたものをrailsのキャッシュに置いておいて実際の出力時はキャッシュから返す(ない場合は改めて生成)という感じ。

    その2につづく
  • redmine 0.6.0が出てた

    ふと気がついたら0.6.0が出てた。
    http://www.redmine.org/

    doc/UPGRADINGを見ながらアップグレード。
    • アーカイブredmine-0.6.0.tar.gzを展開
    • config/database.ymlを以前のRAILS_ROOTからコピー
    • config/environment.rbのSMTP周辺を編集
    • rake db:migrate RAILS_ENV="production" でDB移行
      • ちなみにバックアップは自動でとってるので特になし。
      • RailsアプリはDB周辺の移行が自動化されてて偉いなー。
    • files/は何も置いてなかったのでノータッチ。
    • config/mongrel_cluster.ymlを以前のRAILS_ROOTからコピー
    • mongrelを再起動
    うーんシンプル。
    「LDAP認証でADユーザを使ったredMineへのログイン」で書いたiconvの問題も、iconvを使わなくなったようで特に問題なし、と。

  • LDAP認証でADユーザを使ったredMineへのログイン

    プロジェクトをまたいで自分に関連するチケットをリストアップできる、という点に興味があり、巷でウワサのredMineをたててみる。
    LDAPに既に存在するアカウントでログインできる、という点も魅力だ。

    環境


    redMine環境は、FC5、ruby-1.8.5.35-2.fc5、rails (1.2.3)、mongrel (1.0.1)、mongrel_cluster (1.0.2)、redMine-0.5.1。
    Active DirectoryはW2k3で動作。ユーザの姓名には日本語を指定しており、表示名が「赤峰 るび夫」のようになる運用をしている。ログオン名は姓名とは別の英数字IDを任意で指定している。

    ADドメインはad.example.comとする。

    redMineの設定

    • adminでredMineにログイン
    • 管理>認証>新しい認証モード
      名前:適宜。ADドメイン(のNTドメイン表現)と同じにした。
      ホスト:ADサーバホスト名。
      ポート:389
      アカウント:cn=表示名, cn=Users, dc=ad, dc=example, dc=com
    • このユーザはredmineの認証用に作成した。試行錯誤の過程で英字で姓のみのユーザを作成した。後述のinitialize_ldap_conの関係で日本語姓名だとbindできない様子。
    • ディレクトリに対する匿名アクセス可能なアカウントを作った方がよかったかも。
      パスワード:上記ユーザのパスワード
      BaseDN:cn=Users, dc=ad, dc=example, dc=com

    あわせてユーザを作成:true
    ログイン:sAMAccountName
    →ADユーザのプロパティでは、「アカウント ユーザログオン名」
    名前:givenName
    →ADユーザのプロパティでは、「名」
    苗字:sn
    →ADユーザのプロパティでは、「姓」
    メールアドレス:mail
    →ADユーザのプロパティでは、「電子メール」

    • アスタリスクが必須項目マークだと思うんだけど、マークに従えば、ログインだけ指定すればいいことになる。のだけど、テーブルを見るとNotNull制約みたいなので、4項目とも設定した。
    • AD側のユーザも合わせて上記項目を埋めた。

    mysql> desc users;
    +-------------------+-------------+------+-----+---------+----------------+
    | Field             | Type        | Null | Key | Default | Extra          |
    +-------------------+-------------+------+-----+---------+----------------+
    | id                | int(11)     | NO   | PRI | NULL    | auto_increment |
    | login             | varchar(30) | NO   |     |         |                |
    | hashed_password   | varchar(40) | NO   |     |         |                |
    | firstname         | varchar(30) | NO   |     |         |                |
    | lastname          | varchar(30) | NO   |     |         |                |
    | mail              | varchar(60) | NO   |     |         |                |
    | mail_notification | tinyint(1)  | NO   |     | 1       |                |
    | admin             | tinyint(1)  | NO   |     | 0       |                |
    | status            | int(11)     | NO   |     | 1       |                |
    | last_login_on     | datetime    | YES  |     | NULL    |                |
    | language          | varchar(5)  | YES  |     |         |                |
    | auth_source_id    | int(11)     | YES  |     | NULL    |                |
    | created_on        | datetime    | YES  |     | NULL    |                |
    | updated_on        | datetime    | YES  |     | NULL    |                |
    +-------------------+-------------+------+-----+---------+----------------+

    • 認証モードを保存して一覧に戻る。
    • 「テスト」をクリックして「接続しました。」が表示されることを確認する。

    ログイン失敗

    これで上手くいくかなーと思ったら、ログインできない。
    production.logを見たらiconvがこけてるらしい。

    Processing AccountController#login (for *.*.*.* at 2007-09-19 18:43:52) [POST]
      Session ID: feb2a36cbb1a47dd7b0396964d0185e3
      Parameters: {"action"=>"login", "controller"=>"account", "password"=>"[FILTERED]", "login"=>"ログインID"}


    Iconv::IllegalSequence ("姓 名,CN"...):
        /app/models/auth_source_ldap.rb:72:in `iconv'
        /app/models/auth_source_ldap.rb:72:in `initialize_ldap_con'
        /app/models/auth_source_ldap.rb:47:in `authenticate'
        (略)



    認証モードの設定で、ログイン=sAMAccountNameとしていて日本語周辺は回避した、と思っていたのだが、内部的に以下のような流れらしい。
    1. 認証モードで「アカウント」に指定したDNとパスワードでLDAPに接続
    2. この接続を使って、redmineのログインID==sAMAccountNameとなるエントリを問い合わせる。
    3. 問い合わせた結果得られたDN+ログインフォームに入れたパスワードで再度LDAPに接続
    4. 接続できたら認証OK
    なので、(ここの運用だと)3のDNに日本語が含まれ、これをiso-8859-15→utf-8変換しようとしてiconvで死ぬ。ということみたい。

    とりあえず回避

    場当たり的に、ADからの応答もutf-8だと決めてかかることに。
    app/models/auth_source_ldap.rbをちょこっと書き換え。

    private
      def initialize_ldap_con(ldap_user, ldap_password)
        Net::LDAP.new( {:host => self.host,
                        :port => self.port,
                        #:auth => { :method => :simple, :username => Iconv.new('iso-8859-15', 'utf-8').iconv(ldap_user), :password => Iconv.new('iso-8859-15', 'utf-8').iconv(ldap_password) }}
                        :auth => { :method => :simple, :username => ldap_user, :password => ldap_password }}

        )
      end

    おわり

    mongrel_clusterをrestartし、再度ログインフォーム。
    ADユーザのログオン名+パスワードでredMineにログインできた。
    ふー。

    でも「あるAD上のグループに属しているユーザだけredMineを使える」みたいなコントロールはよくわからず。
    うーん。