DockerでRuby on RailsのCI環境を作ろうとした話
少し前にCIサーバーとして使っていたMac miniがお亡くなりになり、CIサーバーどうしようと考えていました。 Dockerを使いCI環境を作り、さくらのクラウドにあるステージング用のサーバーで動かせば良いのでは! と思い立ち土日に挑戦してみました。
Mac上でCI環境を作る
Docker ToolboxをインストールすればDockerMachineやDocker Client(dockerコマンド)、VirtualBoxなどがインストールされます。
とりあえず、何も考えず docker-machine コマンドで Docker用のサーバー(環境)がVirtualBox上に立ち上がります。
$ docker-machine create -d virtualbox
オプションを指定してDockerサーバーメモリーやCPU数を増やしたりもできます。
後は docker コマンドを使い、Dockerイメージを作ったり、走らせたりして環境を作ります。 Dockerイメージへのアプリのインストール等の作業はDockerファイルに書きます。
CI環境作り
今回作るのは普通のRuby on RailsアプリのRSpecをJenkins上で実行します。そこで必要になるものは、
- Jenkns
- RDB: PostgreSQL
- Ruby、Ruby on Rails, Capybara, 等をインストールするのに必要なライブラリー
- PhantomJS 等のコマンド
- a_JP.UTF-8 ローケール
Rubyのインストールは Jenknsの rbenv Plugin がインストールしてくれるので環境には、OS標準のRubyを入れておけば良いです。
Ruby on Railsはご存知のように bundler がインストールしてくれるので、C言語拡張ライブラリーが構築できる環境があればOKです。
1. Jenkinsの公式Dockerイメージ
Jenkinsの公式Dockerイメージ が公開されています。これをベースにすれば楽じゃないか!
当然、Jenkinsは動きますが PostgreSQLインストールで ja_JP.UTF-8 ローケールを作ろうとしたのですが上手くいきません・・・ いろいろとトライしたのですがベースのUbuntuが通常とは異なるようであきらめました。
2. Java Runtime Environment がインストール出来ない !
JenkinsはJavaで出来ているので Java Runtime Environment (JRE) が必要です。apt-getでJenkinsをインストールすると依存関係から OpenJDK がインストールされるのですがインストール中の
Setting up ca-certificates-java ....
で止まってしまいます。仕方ないので、まずは OracleのJREを以下のようなDockerfileインストールしていました。
RUN apt-get install -y software-properties-common RUN add-apt-repository -y ppa:webupd8team/java RUN apt-get update RUN echo "oracle-java7-installer shared/accepted-oracle-license-v1-1 boolean true" | debconf-set-selections ENV DEBIAN_FRONTEND noninteractive RUN apt-get install -y oracle-java7-installer
でもなぁ〜 と思い、いろいろ検索していたら qiita に解決方法がありました。 Dockerサーバーを作るさいに以下のようなオプションを指定すれば上手くいきます。
$ docker-machine create --driver virtualbox --engine-storage-driver overlay dev
3. やっぱりsshdは必要
Docker内にはsshdを立ち上げてsshログインしたりせず、ログ・ディレクトリー等をホストのディレクトリーにバインドシして使うべきだ! という考え もあるようですが、やはり上手くいかない時の調査ようにsshログインできた方が良いですよね。
USER root RUN apt-get install -y openssh-server RUN mkdir /var/run/sshd RUN echo 'root:パスワード' | chpasswd RUN sed -i 's/PermitRootLogin without-password/PermitRootLogin yes/' /etc/ssh/sshd_config RUN sed 's@session\s*required\s*pam_loginuid.so@session optional pam_loginuid.so@g' -i /etc/pam.d/sshd ENV NOTVISIBLE "in users profile" RUN echo "export VISIBLE=now" >> /etc/profile EXPOSE 22 CMD ["/usr/sbin/sshd", "-D"]
を Dockerfileの最後に書いておくとsshログイン出来ます。rootでログインするのは気持ち悪いですが・・・ 最終的な環境でネット上からはsshできないようにiptables(Firewall)等で制限しておいて下さいね。
4. 出来ました!
Dockerfile は以下のようになりました
FROM ubuntu:14.04 USER root # IMPORTANT http://qiita.com/moutend/items/18682948811175ed0e1d # docker-machine create -d virtualbox --engine-storage-driver overlay NAME RUN apt-get update RUN locale-gen ja_JP.UTF-8 RUN ln -sf /usr/share/zoneinfo/Asia/Tokyo /etc/localtime ## Libs RUN apt-get install -y wget git-core ruby build-essential bison flex sqlite3 autoconf \ libreadline6-dev zlib1g-dev libssl-dev libyaml-dev libxml2-dev libxslt1-dev libncurses5-dev libsqlite3-dev ## Tools RUN apt-get install -y fonts-takao-mincho libfontconfig1-dev libicu-dev libfreetype6 libpng-dev libjpeg-dev RUN wget -O /usr/local/bin/phantomjs https://github.com/Pyppe/phantomjs2.0-ubuntu14.04x64/raw/master/bin/phantomjs RUN chmod 755 /usr/local/bin/phantomjs ## PostgreSQL RUN apt-get install -y postgresql-9.3 libpq-dev postgresql-client-9.3 postgresql-contrib-9.3 RUN sed -i 's/local\s*all\s*all\s*peer/local\tall\tall\tmd5/' /etc/postgresql/9.3/main/pg_hba.conf USER postgres RUN /etc/init.d/postgresql start &&\ psql --command "CREATE USER テストアカウント WITH CREATEDB PASSWORD 'パスワード';" &&\ psql --command "CREATE DATABASE テスト用データベース WITH OWNER テストアカウント TEMPLATE template0 ENCODING 'UTF8' LC_COLLATE 'ja_JP.UTF-8' LC_CTYPE 'ja_JP.UTF-8';" ## Jenkins RUN wget -q -O - http://pkg.jenkins-ci.org/debian/jenkins-ci.org.key | sudo apt-key add - RUN sh -c 'echo deb http://pkg.jenkins-ci.org/debian binary/ > /etc/apt/sources.list.d/jenkins.list' RUN apt-get update RUN apt-get install -y jenkins ## SSH RUN apt-get install -y openssh-server RUN mkdir /var/run/sshd RUN echo 'root:パスワード' | chpasswd RUN sed -i 's/PermitRootLogin without-password/PermitRootLogin yes/' /etc/ssh/sshd_config RUN sed 's@session\s*required\s*pam_loginuid.so@session optional pam_loginuid.so@g' -i /etc/pam.d/sshd ENV NOTVISIBLE "in users profile" RUN echo "export VISIBLE=now" >> /etc/profile EXPOSE 22 ## Startup COPY startup.sh /usr/local/bin/startup.sh ENTRYPOINT ["startup.sh"]
- startup.sh は
#!/bin/bash /etc/init.d/postgresql start /etc/init.d/jenkins start /usr/sbin/sshd -D
5. クラウド上のUbuntuで動かない!
クラウド上の UbuntuサーバーにDocker環境 を作り、Mac上で作ったDockerイメージをsave, load したのですがなぜか PostgreSQLが起動しません (Jenkins, sshd は起動されます)。
# /etc/init.d/postgresql start Starting PostgreSQL 9.3 database server * The PostgreSQL server failed to start. Please check the log output: 2014-12-01 17:41:37 UTC FATAL: could not access private key file "/etc/ssl/private/ssl-cert-snakeoil.key": Permission denied
調べてみると、ここにissue がありました。 しかし、解決しませんでした・・・・ 疲れた ・・・
Dockerの完成度はこんなものなのでしょうか???
今回の結論
というわけで、ステージングサーバーにJenkinsを直接インストールしました ^^);
ステージングサーバーのruby(gem)は /usr/local/ にインストールされているので、Jenkinsのrbenvとは独立しています。RDB(PostgreSQL)は共有されますがtest用databaseは別なので問題なく同じサーバーで動きますした。
Jenkins CI環境でドラック&ドロップのテストが落ちた時にやった事
いまだに Jenkins で消耗してます ^^;
2015-12-17 追記
現象
現在のメンテしているプロジェクトは下のようなCI環境で行っています、 この中のエンドツーエンド テストのうち、jQuery-UIのSortableを使った並べ替え部分のテストが通っていない事に気がつきました。
- OS: Mac miniの上のVMWareで動くUbuntu 14.04 server
- CI: Jenkins
- Test: RSpec feature + Capybara + PhantomJS (poltergeis)
ドラック&ドロップの部分で例外等は発生してないのですが、全く処理が無視されていて結果としてRSpecが失敗になります。それ以外のJavaScriptを使うUIのRSpecは通っています。
もちろん開発環境のMacではRSpecは通りますし、Mac/Ubuntuで動くアプリも正常に動作します。
原因追求
ログをチェックするなど基本的な事は行い、後はGoogle先生に問い合わせた先のIssueを読んだりで半日近い時間が過ぎましたが解決へのヒントは全く得られず・・・・
古いバージョンのPhanotomJSはXvbfなどが無いとheadless環境でテストが出来ないようでししたが、今は問題無いです。試しに Xvbfを入れてみましたが結果は同じでした。
次の日の朝、ふとGUI(X Window System)のあるUbuntuで動かしたらどうだろうと思い、VMWare上に環境を作り、RSpecを実行すると・・・・ 通ります !!
ということで、CI環境の Ubuntu を Server版ではなく Desktop版 にすれば良いことがわかりました。 幸運なことに(?) Mac mini + VMWare でUbuntuを動かしているので Desktop変えることは出来ます。 しかし、作業時間も掛かるるし、非力な Mac mini 上で Desktop版を動かすのはねぇ・・・ と悩みました。
もう少し検討
本当にGUI(X Window System)が動いてないとPhanotomJSは正しく動作しないのでしょうか?
試しに、GRUBの設定を変え GUI(X Window System) が起動しないようにして、 GUIの動いてないUbuntuにSSHでログインしてRSpecを実行すると・・・・ 通りました ^^)/
多分、PantommJSでドラック&ドロップを動作させるには、Xやマウスなどの何らかのライブラリー等のインストールが必要なのだと思われます。Server版ではこのライブラリーが入らないのではないでしょうか?
疲れたのこれ以上は追求しませんでしたが・・・
判りました! 日本語フォントをインストールすれば良いようです。
対処結果
現在の Server版をやめてDesktop版を入れるのは面倒なので Ubuntu/Ubuntuサーバーにデスクトップをインストールする手順 を参考に現行の Ubuntu serverにDesktopをインストールし、GRUBの設定を変え GUI(X Window System) が起動しないようにしました。
Ubuntu なら
$ sudo apt-get install fonts-takao-mincho
めでたしめでたし・・・・
山口小夜子の映画「氷の花火」を見た個人的な記憶
めずらしく、技術的ではないことを書きました。
「山口小夜子」覚えてますか? 知ってますか? ファッションとは全く無縁な私がこんなことを書くのは恥ずかしいのですが、若いころ山口小夜子は憧れの人でした。
先週の土曜日に、映画 「氷の花火」 を見て気まました。映画は山口小夜子の遺品の整理風景から始まり、彼女と一緒に仕事をした人たちへのインタビューから彼女の一生を描き、最後に現在のモデルを使い山口小夜子を再現するというプロジェクトという内容でファンとしてはとても見応えのある内容でした。
幾つかのメモ:
- 山口小夜子といえば黒髪のおかっぱに切れ長の目ですが、実はくっくりとした目で切れ長はメークで作っていた!
- 昔、デーモニッシュな資生堂のCMで知った セルジュルタンス のモロッコ・マラケシュの邸宅でのインタビューや小夜子と映った昔の画とは妄想が膨らむ収穫だった
- 資生堂で小夜子の出たCMを作った方々へのインタビューを見ると、かなりアーティスティックなCMを作れる、それを認めてくれる経営者のいた経済成長中の日本があったんだなぁ〜 というノスタルジー
- 遺品の整理シーンに出てきた蔵書。コクトー、寺山修司、タルコフスキー、世阿弥・・・私も持っていたものが多い、彼女の書いた本を読んだりして影響を受けていたんだろうな
彼女は軽薄な女よ:
私が若い頃、不思議な画廊のマダムと友達になり、画廊にも出入りしたり、マダムと食事に行ったり、お芝居に行ったり・・・そんな中で私が山口小夜子のファンだと言うと、「彼女は軽薄な女よ・・・・」と答えたことはずっと記憶に残っていました。マダムは芸術家の芸術にせっする態度のには厳しい哲学を持った人でした、当然ながらたくさんの美術、お芝居、それらに関係するパーティーの中で暮らしている方でした。
山口小夜子はファッションモデルとして世界一の座を射止めた人です、高田賢三が「かぐや姫が降りてきたという感じ・・・」とインタビューの中で語っていましたが、たしかにランウェイの彼女には神がった美しさがありました。
しかし、トップモデルをずっと続けることはできません。モデルを辞めた後の彼女は、舞踏、お芝居、若い芸術家とのコラボなど、いろいろなものに取り組んでいる画像、共演者たちのインタビューが流れながら映画は進んでいきました。 そこに映る彼女には心ときめく山口小夜子を発見できませんでした。 彼女自身の頑張りはわかります、しかし表現者(芸術家)としてまだまだだったのでしょうか。画廊のマダムの言っていた事がわかった気がしました。
一度頂点を極めてしまった人のその後は辛い・・・・ しかし、彼女が山口小夜子という素晴らしい世界を見せてくれた事は事実です。
ありがとう、山口小夜子さん。
プロビジョニングツールをChef-soloからItamaeに替えてみました
Chef から Itamae ?
EY-Officeではサーバーソフトの構築を行うプロビジョニングツールとして現在は Chef-solo を使っています。しかし Chef-solo は終了するらしいのですが、その移行先が良いように思えず悩みながら使い続けてきました。
そんなとき登場してきた Itamae はシンプルでとても魅力を感じました。詳しくは クックパッド開発者ブログのItamae記事 をご覧下さい。
今回時間があったので、EY-Officeの開発支援サーバー (Redmine, Git/Gnatara, CI ...)の全プロビジョニングをChefからItamaeに置き換えてみました。書き換えたレシピは約700行です、そこで感じた事を書きます。
Itamaeの良いところ、良くないところ
1. 情報が貧弱
まず良くない点ですが、Chefのドキュメント に比べると情報の量、質ともに低いです。どうしても思ったように動作しない場合は Itamae の実装を読むことが度々ありました。
公式なドキュメントは
今回、参考になった情報は
2. Chefとは動作モデルが違うぞ!
最初に戸惑ったのでは、Chefとの動作モデルの違いです。
Chef-solo の利用イメージは
- Chefを開発環境にインストール
- 利用開始時にChefのRubyインタプリターを含む実行環境をターゲットにインストール (knife solo prepare コマンド)
- レシピの作成・修正
- 実行 (knife solo cooke コマンド)
- レシピの文法チェック等
- レシピを含むcookbookをターゲットへ転送
- レシピをターゲットで実行
- 問題があれば 3.に戻る
ですが、Itamaeは
- Itamaeを開発環境にインストール
- レシピの作成・修正
- 実行 (itamaeコマンド)
- レシピに対応する操作をsshを使いターゲット上で実行
- 問題があれば 2.に戻る
ChefのレシピのRubyコードはターゲット上で実行されますが、Itamaeはレシピは開発環境で実行されます。 したがって、ターゲットの状況によって処理を変更する部分、たとえば not_if や only_if にはRubyのコードは書けず、shellのコードを書く必要があります。
例
Chef
execute "apt-get-update" do command "apt-get update" only_if { File.mtime('/var/lib/apt/lists') < Time.now - 86400 } end
Itamae
execute "apt-get-update" do command "apt-get update" only_if "exit $(( `stat -c %Y /var/lib/apt/lists` > (`date +%s` - 86400) ))" end
実行時にターゲット上の情報を動的に取得できないので、今回はレシピ(コード)で自動的に取得するのをあきらめnode で指定するようにした部分もありました。
3. Chefに似ているけど・・・
Itamae のResource (DSL, 命令?)はChefと似た作りになっていて、そのまま動くものもありますが、少し違う部分もあります。また無いものもあります。
- Chef の
cooke_file
はremote_file
- Chef の
remote_file
に相当するものは無い? - ファイル等のモード指定に8進数が使えない
user
のaction: :modify
が出来ないcron
Resource が無い- ・・・
4. defineを使おう
無いResourceを簡単に作れる Definitions があり、これを使って足りないResourceを補いました。
# URL指定でネット上のファイルをダウンロードするResource define :external_file, url: nil, owner: nil, group: nil, mode: nil do p = params execute "download #{p[:name]} from #{p[:url]}" do cmd = "curl -s -f -L -o #{p[:name]} #{p[:url]}" cmd += " && chown #{p[:owner]} #{p[:name]}" if p[:owner] cmd += " && chgrp #{p[:group]} #{p[:name]}" if p[:group] cmd += " && chmod #{p[:mode]} #{p[:name]}" if p[:mode] command cmd not_if "test -e #{p[:name]}" end end # crontab 設定用 Resource define :cron_for, crontab: nil do user = params[:name] crontab = params[:crontab] crontab_path = "/tmp/crontab.#{rand(10000)}" file crontab_path do content crontab not_if "crontab -u #{user} -l" end execute "crontab -u #{user}" do command "crontab -u #{user} #{crontab_path} && rm #{crontab_path}" only_if "test -e #{crontab_path}" end end
利用レシピ
ruby_package = "ruby-2.2.2" external_file "/usr/local/src/#{ruby_package}.tar.bz2" do url "http://cache.ruby-lang.org/pub/ruby/2.2/#{ruby_package}.tar.bz2" end cron_for "root" do crontab <<-EOH # PATH=/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin # 30 5 * * * /usr/local/bin/backup.sh >> /var/tmp/bakup.log 2>&1 EOH end
Chef の cron
Resource は crontab形式を知らなくてもDSLで設定出来るようになっていましたが、define であまり高度な機能を作る事は考えない方が良いと思いました。やってみると直ぐに限界が見えます。
どうしても高度な Resourceが必要な場合は Quirra: itamae resourceの書き方 を参考に Resource を作った方が良いと思います。
5. シンプルで早い
動作モデルの違いやResourceの充実度の違いはありますが、Itamaeはレシピが実行されるまでの時間が早くとても快適です!
Chef-soloを利用していて、複雑化するChefの推奨移行環境に疑問を持つ方は、ぜひ一度 Itamaeを試してみると良いと思います。
今回作ったレシピ
今回作ったレシピの一部です(ubuntu 14.04用です)
execute "apt-get-update" do command "apt-get update" only_if "exit $(( `stat -c %Y /var/lib/apt/lists` > (`date +%s` - 86400) ))" end execute "ensure ja_JP.UTF-8" do command "locale-gen ja_JP.UTF-8" not_if "grep -c 'ja_JP.UTF-8' /var/lib/locales/supported.d/local" end execute "ensure JST" do command <<-EOH echo "Asia/Tokyo" | sudo tee /etc/timezone sudo dpkg-reconfigure --frontend noninteractive tzdata EOH not_if "date | grep -c 'JST'" end # shell を zsh に変更 (ユーザーは node[:user]で指定) file "/etc/passwd" do action :edit block do |content| content.gsub!("/home/#{node[:user]}:/bin/bash", "/home/#{node[:user]}:/bin/zsh") end end
postgreSQLをインストールし DBのencoding, localeを変更する
%w{postgresql-9.3 libpq-dev postgresql-client-9.3 postgresql-contrib-9.3}.each do |pkg| package pkg do action :install end end service "postgresql" do action [:start, :enable] end link "/usr/local/bin/psql" do to "/usr/local/pgsql/bin/psql" end execute "initdb" do user "postgres" cwd "/var/lib/postgresql/9.3/" command <<-EOH rm -rf main /usr/lib/postgresql/9.3/bin/initdb -D main --encoding=UTF-8 --locale=ja_JP.UTF-8 rm main/pg_hba.conf main/pg_ident.conf main/postgresql.conf ln -s /etc/ssl/certs/ssl-cert-snakeoil.pem main/server.crt ln -s /etc/ssl/private/ssl-cert-snakeoil.key main/server.key EOH action :nothing end execute "re-initdb with ja_JP.UTF-8" do command "echo re-initdb" notifies :stop, 'service[postgresql]', :immediately notifies :run, 'execute[initdb]', :immediately notifies :start, 'service[postgresql]', :immediately not_if "/usr/lib/postgresql/9.3/bin/psql -U postgres -c \"select * from pg_database where datname ='template0'\" " + "| grep -c 'ja_JP.UTF-8'" end
注) specinfra 2.41.1 で Ubuntu14.04でPostgreSQL起動中判定の問題が対処されたので暫定パッチは不要になりました :-)
GitLabを使ったプライベートGitリポジトリーの管理を止め改造版 Ginatra にしました。
プライベートGitリポジトリーの管理
私の会社 EY-Ofiice は零細企業なので プライベートGitリポジトリー はGitHub等の有料サービスではなく、さくらクラウド上のサーバーで GitLab を動かしてきました。 しかし、
- 通常の開発は一人でしか行わないのでpull request等の機能はいらない
- GitLabは処理が重くサーバーのメモリーを増やさないと遅すぎる
- 複数人で開発するような仕事ではお客様側でGitHub等を用意してくれる
- 教育でGitLabを使う事があるが、その時だけGitLab用サーバーを用意すれば良い気がする
ということで、あまりメリットを感じていませんでした。
Gitを使い始めた頃は GitWeb でリポジトリーや履歴が表示と。自作した新規リポジトリー作成CGIで運用していました。
Ginatra
Git用のWebツール を見ていたらSinatraベースの Ginatra ( GitHub ) を知り、インストールしてみました。
Ginatraは Gitリポジトリーのファイル(コード)や履歴の表示ツールです。Sinatraベースなので動作も軽いし、メンテも楽で、今風の画面デザインも気に入りました。
Ginatraの改造
ただし、不満もあり Fork し機能を追加。変更しました。
1. リポジトリーの階層化
Ginatraは複数のGitリポジトリーディレクトリーをサポートしていますが、フラットに管理しています。EY-Officeでは docs(ドキュメント), edu(教育用), products(製品)・・・などのディレクトリーの下にGitリポジトリーを置いています。GitLabには Groupという機能がありこれを使っていました。
やはり、グループが無いと不便なので Repo クラスに group属性を追加し group:name をレポジトリーの識別子として使うように改造しました。 また、一覧表示は Bootstrap の Collapse(Accordion) を使い表示するようにしました。
2. 新規リポジトリーの作成
Web上かから新規にGitリポジトリーが作成作成出来るようにしました。Ginatraで使っているRugged (libgit2のRubyインターフェース)に git init のAPIがあったので簡単にできました。
3. その他
- なぜかUTF-8を含むテキストファイルをbinaryとして表示してしまう問題をアドホックに対処
- gitレポジトリーへのアクセスは ssh を使っているので URL表示等を ssh に変更
- Unicornで使う為のコンフィグ等の追加
などです。まだ RSpec を修正していません ^^;
RedmineのコンテンツをMarkdownに変換しました
Textile vs Markdown
軽量マークアップ言語にはたくさんの種類があります。私も教育で使うテキストの作成やRedmineのWikiでは長年 Textile を使って来ました。 しかし、最近はGithubの標準のマークアップ言語がMarkdownだったり、Atom(エディター)をはじめたくさんのMarkdownをサポートするツールが現れ、私も MacDownというツールが気に入り開発ドキュメント等のMarkdown化がどんんどん進んでいます。このブログもはてな記法ではなくMarkdownを使っています。
TextileとMarkdownを比べると 標準のMarkdown は記述能力が低くテキストを作るには能力不足ですが、 GitHubの拡張 や php Markdown Extra などの拡張機能を使えば、ほぼ同等です。
Redmine
Redmineのマークアップ言語のデフォルトはTextileですが、Version 2.5からMarkdownをサポートするようになりました。ただし、Redmineサーバー全体の設定なので新規にRedmineを始める人以外には使いにくいものでした。 redmine_persist_wfmtのようなプラグインを使えばコンテンツ(Wiki, Ticket...)単位でTextitle/Markdownが選択できるのでこれを使えば良いかもしれません。
しかし、私はRedmineのWikiを教育でよく使っています。お客様毎に教育で使うコード、ヒント、参考情報・・・などをWikiを使い提供しています。しかも以前に作ったWikiを元に作成する事が多いので、そのTextileとMarkdwonが混在するのは望ましくありません。
そこで、Textileコンテンツを全てMarkdownに変換してしま事にしました。
Textile to Markdown
"Convert Textile to Markdown" で検索すると Pandoc というドキュメント変換ツールが良く出来てきます。このツールは色々なドキュメント形式を相互変換できる素晴らしいツールですが、やはり完璧ではありませんでした。 Markdown→TextileではGitHub拡張をサポートしていますが、Textile→MarkdownではテーブルをGitHub拡張に変換してくれたりしません・・・
そこで、自作を検討しました。MarkdownとTexitleは似ています、正規表現を使えばほとんど変換できます。
def textile_to_markdown(textile) d = [] pre = false table_header = false text_line = false textile.each_line do |s| s.chomp! if pre if s =~ /<\/pre>/ d << "~~~" pre = false else d << s end next end s.gsub!(/(^|\s)\*([^\s\*].*?)\*(\s|$)/, " **\\2** ") s.gsub!(/(^|\s)@([^\s].*?)@(\s|$)/, " `\\2` ") s.gsub!(/(^|\s)-([^\s].*?)-(\s|$)/, " ~~\\2~~ ") s.gsub!(/"(.*?)":(.*?)\.html/, " [\\1](\\2.html) ") d << "" if text_line text_line = false case s when /^<pre>/ d << "~~~" pre = true when /^\*\*\* (.*)$/ d << " * " + $1 when /^\*\* (.*)$/ d << " * " + $1 when /^\* (.*)$/ d << "* " + $1 when /^\#\#\# (.*)$/ d << " 1. " + $1 when /^\#\# (.*)$/ d << " 1. " + $1 when /^\# (.*)$/ d << "1. " + $1 when /^h(\d)\. (.*)$/ d << "#" * $1.to_i + " " + $2 when /^!(.*?)!/ d << "![](#{$1})" when /^\|_\./ d << s.gsub("|_.", "| ") table_header = true when /^\|/ d << s.gsub(/\=\..+?\|/, ":---:|").gsub(/\s+.+?\s+\|/, "---|") if table_header table_header = false d << s.gsub("|=.", "| ") when /^\s*$/ d << s else d << s text_line = true end end d.join("\n") + "\n" end
このコードは全てのTextileフォーマットをサポートしているわけではありませんが、私が使ってる機能はほぼ網羅しています。 変換されたMarkdownはGitHub拡張やphp Markdown Extraの機能を使っています。
さてRedmineでは色々なところにTextileが書けますが、今回は Wiki, Ticket, Ticketの履歴のコンテンツに対応しました。必要があれば追加して下さい。
def update_content(model, attrbute) total = model.count step = total / 10 puts " #{model}.#{attrbute} : #{total}" model.all.each_with_index do |rec, ix| n = ix + 1 puts sprintf("%8d", n) if n % step == 0 rec[attrbute] = textile_to_markdown(rec[attrbute]) if rec[attrbute] rec.save! end end update_content(WikiContent, :text) update_content(Issue, :description) update_content(Journal, :notes)
コードは Gistにも置きました。
このコードを以下のように runnder で実行するとTextileがMarkdownに変換されます。
$ rails runner -e production tools/textile2md.rb
注意: このプログラムは全てのTextile形式を変換できるものではありません、またバグ等で正しく変換できない場合もありますので、変換前に必ずRDBのバックアップを取って下さい。
iOS 開発者のためのバックエンド入門 (2)
iOS開発者の勉強会 yidev 第20回勉強会 で iOS 開発者のためのバックエンド入門 (2) という発表をしました。 iOSだけでなく、Androidの開発者の方にも参考になる話だと思います。
今回の内容は、Ruby on Railsを使い簡単なバックエンドをその場で作ってみるという実演付きの話をしました
Ruby, Ruby on Railsに付いて
- 良いところ、悪いところ
- MacへのRuby, Ruby on Railsのインストール方法
Ruby on Rails を使い簡単なバックエンドを作る
- クライアントになるiOS アプリの紹介 ーー Swiftで作りました
- Ruby on RailsのScaffoldでバックエンドを作る実演
バックエンドをHerokuにデプロイ
- 出来たバックエンドを代表的なPaaS (Platform as a Service) である、Heroku にデプロイする
- ライブラリー構成を少し変え、Herokuにgit pushするだけでデプロイが完了
- Herokuへのデプロイ手順はHerokuのページにもありますが、RailsGirlのHeroku の Rails アプリをアップページ が判りやすいです
コード、資料
使ったコードは GitHub におきました