tkm9

自分向け技術メモ(?)

rails で ファイルをディレクトリに入れたものをzip圧縮して返す

rails 4.2 / ruby 2.1.5

やりたいこと

zip ファイルを一時ファイルとして生成して、レスポンスで返したい。

返した zip を展開すると

└ test_dir
   ├── text.txt
   └── foo.png

こんなかんじに、test_dir の中にファイルが格納されてる状態になっててほしい。

コード

今回 rubyzip を使用しました。

require 'zip'
def zip_test
  filename  = 'test_zip.zip'
  dirname   = "test_dir"
  temp_file = Tempfile.new(filename)
  begin
    Zip::File.open(temp_file.path, Zip::File::CREATE) do |zip|
      # (1) ZIP内にディレクトリを作成
      zip.mkdir dirname

      # (2) 作ったディレクトリにファイルを書き込む1
      zip.get_output_stream( dirname + "/test.txt" ){ |s| s.print( "AAAAAAA" ) }

      # (3) 作ったディレクトリにファイルを書き込む2(既存ファイルから)
      open("/tmp/foo.png") do |f|
        zip.get_output_stream( dirname + "/" + File.basename(f.path)){|s| s.print( f.read ) }
      end

    end
    zip_data = File.read(temp_file.path)
    send_data(zip_data, :type => 'application/zip', :filename => filename)
  ensure
    temp_file.close
    temp_file.unlink
  end
end

こんな感じにしました。

Data は複数形だったのですね

何も考えずに

$ bundle exec rails g model TestData

したら

[WARNING] The model name 'TestData' was recognized as a plural, using the singular 'TestDatum' instead. Override with --force-plural or setup custom inflection rules for this noun before running the generator.

といわれ、TestDatumってなに笑 なにそれ笑 ちょっとrailsさん笑 とか思ったけど、単数形:Datum 複数形:Data なんですね...知らなかった。

まあでも Data も一般的に単数にも使うみたいだし、しかも Datum ってあんまり馴染みないし、Data でいいかなと個人的に思いました。
今回は結局モデル名を全然別のにしちゃったのだけど。


なんか、色々調べたりしたいことあるんだけど最近忙しくて時間とれないや

梅雨明けくらいから本気だす(?)

State Machine の エラー:NoMethodError: protected method `around_validation'

state machine の gem を導入したら、

NoMethodError: protected method `around_validation' called for #< StateMachine::Machine:0x007f60dfc57f28 >

といったエラーが出たのでその対応メモ。

環境

状況

state machine を導入するとバリデーション実行時エラーとなる。

エラー:

$ bundle exec rails c
Loading development environment (Rails 4.2.0)
  > e = Entry.new
 => #<Entry id: nil, title: nil, body: nil, status: nil, created_at: nil, updated_at: nil>

  > e.save
   (0.2ms)  begin transaction
   (0.2ms)  rollback transaction
  NoMethodError: protected method `around_validation' called for #<StateMachine::Machine:0x007f60df3c52e8>

対応

NoMethodError: protected method `around_validation' in Rails 4.1.0.beta1 · Issue #295 · pluginaweek/state_machine · GitHub でのやりとりを見る限り

  • state_machines-activerecord という gem をインストールする
  • around_validation メソッドを public にする

くらいしかなさそう。

今回は後者の対応として以下内容の config/initializers/state_machine.rb を作成しました。

# Rails 4.1.0.rc1 and StateMachine don't play nice
# https://github.com/pluginaweek/state_machine/issues/295

require 'state_machine/version'

unless StateMachine::VERSION == '1.2.0'
  # If you see this message, please test removing this file
  # If it's still required, please bump up the version above
  Rails.logger.warn "Please remove me, StateMachine version has changed"
end

module StateMachine::Integrations::ActiveModel
  public :around_validation
end

issue にあったやつのコピペです。

しかし、state machine はもうメンテしないのかな?

json の view を Dalli(memcached) で キャッシュする

アクションの結果をキャッシュしたかったんだけど最近の rails って Action cache って無くなっちゃってたよね?
ということで代わりに Dalli に json をキャッシュさせてみたメモ。

環境

cache する

text = render_to_string( action_name, { formats: [:json], handlers: [:jbuilder] } )
Rails.cache.write( KEY, text )

render の内容を取得するには render_to_string を使う。
で、それを Rails.cache.write してキャッシュする。

cache した json をrender

response.headers['Content-Type'] = 'application/json; charset=utf-8'
render :text => Rails.cache.read( KEY )

json だと、render 時に Content-Type をセットしてやる必要がありました。

Chef の リポジトリ/クックブック の generate 系コマンドについて

雛形をgenerateしてくれるコマンドがいくつかあるようだけど違いが不明だったので実行してみた。

リポジトリgenerate

以下3つやってみる。

  • chef generate app test_repo1
  • chef generate repo test_repo2
  • knife solo init test_repo3 ※要knife-soloインストール

実行結果

実行するとディレクトリができるので tree で見てみるよ。

chef generate app test_repo1
$ tree
.
├── README.md
├── cookbooks
│   └── test_repo1
│       ├── Berksfile
│       ├── chefignore
│       ├── metadata.rb
│       ├── recipes
│       │   └── default.rb
│       └── spec
│           ├── spec_helper.rb
│           └── unit
│               └── recipes
│                   └── default_spec.rb
└── test
    └── integration
        └── default
            └── serverspec
                ├── default_spec.rb
                └── spec_helper.rb

10 directories, 9 files
chef generate repo test_repo2
$ tree
.
├── LICENSE
├── README.md
├── Rakefile
├── certificates
│   └── README.md
├── chefignore
├── config
│   └── rake.rb
├── cookbooks
│   └── README.md
├── data_bags
│   └── README.md
├── environments
│   └── README.md
└── roles
    └── README.md

6 directories, 10 files
knife solo init test_repo3
$ tree
.
├── Berksfile
├── cookbooks
├── data_bags
├── environments
├── nodes
├── roles
└── site-cookbooks

6 directories, 1 file

感想

  • chef generate app test_repo1

レポジトリというかむしろレシピメインで申し訳程度にリポジトリな感じ

  • chef generate repo test_repo2

レポジトリといったらこれが普通なのかな?これはレシピの雛形は作られないから別途つくらないといけないね

  • knife solo init test_repo3

→ chef solo 用なのかな?でもこれくらいなら自分で mkdir してもあんま変わらなそう

クックブック

以下2つを実行。

  • chef generate cookbook test_cookbook1
  • knife cookbook create test_cookbook2 -o ./

実行結果

chef generate cookbook test_cookbook1
$ tree
.
├── Berksfile
├── README.md
├── chefignore
├── metadata.rb
├── recipes
│   └── default.rb
├── spec
│   ├── spec_helper.rb
│   └── unit
│       └── recipes
│           └── default_spec.rb
└── test
    └── integration
        └── default
            └── serverspec
                ├── default_spec.rb
                └── spec_helper.rb

8 directories, 9 files
knife cookbook create test_cookbook2 -o ./
$ tree
.
├── CHANGELOG.md
├── README.md
├── attributes
├── definitions
├── files
│   └── default
├── libraries
├── metadata.rb
├── providers
├── recipes
│   └── default.rb
├── resources
└── templates
    └── default

10 directories, 4 files

感想

  • chef generate cookbook test_cookbook1

→ミニマム構成にテストを追加しました的な

  • knife cookbook create test_cookbook2 -o ./

→ひととおりのディレクトリが用意されてそう。自作リソースを配置する用のディレクトリとか必須でないディレクトリも作られる。あとテスト系のは作られない

あと、ダウンロードしてきてクックブックを配置したり、berkshelf を使うパターンもあるね。

でもぶっちゃけ、どれも所詮 雛形で大したものが生成されるわけじゃないし自分で考えながら作ったほうがいいのかもしれない。

Vagrant + Chef + VirtualBox で 開発環境チャレンジ(2)

主に Chef について。

あれこれやんないで、まずはシンプルにレシピを作って適用してみようの回

Chefについて復習

Chef とは、サーバ構築・デプロイを管理・自動化するためのソフトウェア。

Chefの定めるフォーマット(rubyDSL)にて、各サーバがどうあるべきか(どういう設定で何がインストールされて...)といった設定を記述していく。
設定内容はChefサーバに保持され、その管理下に置かれたChefクライアントが定期的にChefサーバに問い合わせ、設定をChefクライアント自身に適用することで、各種設定変更やインストール作業などインフラ系のもろもろ、手順が一元管理できる上に適用を自動化することができる。
Chefクライアントへ設定を適用することは「収束」と表現され、「収束」は、何度実行しても結果が同じくなるという”べき等性”を有する。

Chefの設定の内容は、「リソース」という単位で記述する。
リソースは、実行条件、アクション、通知、属性 を定義したコードのかたまりで、例えば、こんなの。

directory "/tmp/folder" do
  owner 'root'
  group 'root'
  mode '0755'
  action :create
end

これを1こないし複数積み重ねたものが「レシピ」と呼ばれるもので、それの上位に「クックブック」さらにそれの集合が「リポジトリ」となる。
リソース>レシピ>クックブック>リポジトリ という関係。
なお、レシピを適当作ってしまうと前述の「べき等性」が失われるので作るならそのへんを意識する必要がある。

リソースについて参考

なお、Chef は、Chefサーバ・Chefクライアントがセットでの運用となるが、Chef-Zeroというものもあり、こちらを使えば Chef サーバを自身に立ち上げそこ経由で自身(クライアント)に設定を適用してくれる。お手軽を求めるなら Chef-Zero が良さそう。

かんたんなレシピを作る

chef-apply コマンドを使うのが一番お手軽みたい。
Chef が入っているマシンで、chef-apply コマンドを実行すればよい。

レシピを記述したファイルが recipe.rb だとしたら、

chef-apply recipe.rb

としてやればOK。

ゲスト用意

まっさらな CentOS7 を vagrant で用意しておきました。
ここからは、全部ゲスト側のCentOS7での操作です。

chefDK インストール
$ wget https://opscode-omnibus-packages.s3.amazonaws.com/el/6/x86_64/chefdk-0.4.0-1.x86_64.rpm
sudo rpm -ivh chefdk-0.4.0-1.x86_64.rpm
$ chef -v
Chef Development Kit Version: 0.4.0
chef-apply が動くか確認
$ cd /vagrant
$ vi rails_app.rb
  file 'motd' do
    content 'hello world'
  end

$ chef-apply rails_app.rb
Recipe: (chef-apply cookbook)::(chef-apply recipe)
  * file[motd] action create
    - create new file motd
    - update content in file motd from none to b94d27
    --- motd	2015-03-08 03:47:21.000000000 -0400
    +++ ./.motd20150308-7574-1x5883w	2015-03-08 03:47:21.000000000 -0400
    @@ -1 +1,2 @@
    +hello world
    - restore selinux security context

うん、ちゃんと chef-apply rails_app.rb が実行できて、レシピ通りファイルが作成された。

レシピ書いてく

vi rails_app.rb
selinux_config =<<-EOS
  SELINUX=disabled
  SELINUXTYPE=targeted
EOS
file "/etc/selinux/config" do
  content selinux_config
  owner 'root'
  mode '644'
end

bash "selinux off" do
  user "root"
  code 'setenforce 0'
end

%w(yum-fastestmirror git gcc gcc-c++ openssl-devel readline-devel
   libffi-devel python-devel).each do |package_name|
  package package_name do
    action :install
  end
end

service 'iptables' do
  action [:disable, :stop]
end

directory "/usr/local/rbenv" do
  owner  "root"
  action :create
end
git "/usr/local/rbenv" do
  repository "https://github.com/sstephenson/rbenv.git"
  revision   "master"
  user       "root"
  action     :sync
end

directory "/usr/local/rbenv/plugins/ruby-build" do
  owner  "root"
  recursive true
  action :create
end
git "/usr/local/rbenv/plugins/ruby-build" do
  repository "https://github.com/sstephenson/ruby-build.git"
  revision   "master"
  action     :sync
end

rbenv_config =<<-EOS
  export RBENV_ROOT="/usr/local/rbenv"
  export PATH="${RBENV_ROOT}/bin:${PATH}"
  eval "$(rbenv init --no-rehash -)"
EOS
file "/etc/profile.d/rbenv.sh" do
  content rbenv_config
  owner 'root'
  mode '644'
end
bash "rbenv_init" do
  code 'source /etc/profile.d/rbenv.sh'
end

bash "rbenv install 2.2.1" do
  # リソースが変わるとPATHはリセットされてしまうらしい、のでとりあえず毎回sourceしとく
  user   "root"
  code   "source /etc/profile.d/rbenv.sh; rbenv install 2.2.1"
  not_if { ::File.exists? "/usr/local/rbenv/versions/2.2.1" }
end
bash "rbenv rehash" do
  user   "root"
  code   "source /etc/profile.d/rbenv.sh; rbenv rehash"
end
bash "rbenv global 2.2.1" do
  user   "root"
  code   "source /etc/profile.d/rbenv.sh; rbenv global 2.2.1"
end

gem_package 'bundler' do
  gem_binary '/usr/local/rbenv/shims/gem'
  action :install
end

yum_package 'vim' do
  action :remove
end
remote_file "/tmp/vim-7.4.tar.bz2" do
  source "ftp://ftp.vim.org/pub/vim/unix/vim-7.4.tar.bz2"
end
bash 'install_vim' do
  user 'root'
  code <<-EOS
    cd /tmp
    tar jxvf /tmp/vim-7.4.tar.bz2
    cd /tmp/vim74
    ./configure --disable-selinux --enable-multibyte --with-features=huge --enable-rubyinterp --enable-pythoninterp --disable-xsmp-interact --disable-xsmp --without-x --disable-gui
    make
    make install
  EOS
end

で、実行する。

sudo chef-apply rails_app.rb

これで、CentOSのまっさらな状態から以下を自動で実行できます。

  • selinux オフ
  • iptables オフ
  • いくつかのyumパッケージインストール
  • vimインストール
  • rbenvの下での ruby インストール

結構強引な記述もありますがより良い記述が見つかったらこっそり直そうかと思います。

あとは、レシピを1ファイルに書くんじゃなくて cookbook 化して、設定を外出ししたりとかしていけばいいのかな?

Vagrant + Chef + VirtualBox で 開発環境チャレンジ(1)

社内のあるrailsアプリを今後チームでやっていこうという話になりまして、いい機会なので Vagrant/Chef で環境を作って、これをメンバーにばらまきたいと思いました。
ということで、その事前調査のメモです。

バージョン

最終目標

こんなことがやりたいです。

  • Vagrant、Chef、VirtualBox をあらかじめメンバー環境にインストールしてもらう
  • 社内gitリポジトリVagrant/Chef の設定ファイルを作成しておく
  • メンバーはリポジトリからcloneし、コマンド1発叩くと仮想環境が自動で出来上がる
  • アプリに必要なミドルウェアを追加した際などはその設定をpush、メンバーはpull&コマンド実行

この記事の目標

最終目標はrailsの環境構築ですが、Vagrant + Chef でひとまず動くようにする。

調べ始めて思ったこと

Chef流行りはじめのころの情報は多いのですが、現状から見ると記述が違ってたりほかのやり方がありそうだったりで、その辺の見極めがちょっと大変かもと思いました。

今回参考にしたページです。

VirtualBoxインストール

これはインストール済なので省略。インストール自体は Downloads – Oracle VM VirtualBox ここからインストーラを落としてすぐできるはずです。

Vagrantインストール

Download Vagrant - Vagrant
こちらから 1.7.2 Mac OSX 用をダウンロード。インストーラ実行。

$ which vagrant
/usr/bin/vagrant
$ vagrant --version
Vagrant 1.7.2

Chef

以前は gem でインストールするのが主流だったようですが、現在は ChefDK(Chef Development Kit という全部入りのキット)が提供されているようですので、こちらを利用します。
Chef Development Kit | Chef Downloads | Chef

キットは以下を含むとのこと。

  • Chef
  • Berkshelf
  • Test Kitchen
  • Foodcritic
  • その他Chefツール
    • Chef Client
    • Knife
    • Ohai
    • Chef Zero

僕の場合、この中で使うのは Chef、Berkshelf、Chef Zero、Knife あたりになるかな?
インストールすると chef コマンドが使えるようになります。

$ which chef
/opt/chefdk/bin/chef
$ chef -v
Chef Development Kit Version: 0.4.0

rubyについて

インストール自体はこちらもインストーラ実行するだけで簡単です。

なお、先ほど、"以前は gem でインストールするのが主流で..." と書きましたが、Chef自体は今も変わらず gem で提供されています。
ですが、Chef 含め必要な gem は、この ChefDK に ruby がバンドルされていて、そちらにインストールされます。
なので、システムで利用しているrubyには影響ないのですね。

例えば gem の操作をしたいときは、

chef gem 〜

のように、chef コマンドを経由することで ChefDK の ruby に対して操作できます。

もしくは、eval "$(chef shell-init bash)" を実行することで、ruby をシステムのものからChefDKのものに切り替えることもできます。
※上記は bash となっていますが各個のシェル名(bash zsh sh powershell posh)とする必要があります
eval "$(chef shell-init bash)"実行の場合、ログインしなおすと元に戻るので bash_profile とかに追記しておくと良いかもしれません

今回は eval "$(chef shell-init bash)" しておきます。
でも今回の作業内容的に不要な気がします。が、一応実行。

実行前 ruby

ruby 2.0.0p481 (2014-05-08 revision 45883) [universal.x86_64-darwin14]

実行後 ruby

ruby 2.1.4p265 (2014-10-27 revision 48166) [x86_64-darwin12.0]

Vagrantプラグイン インストール

$ vagrant plugin install vagrant-vbguest
$ vagrant plugin install vagrant-chef-zero
$ vagrant plugin install vagrant-omnibus

インストールしたプラグインのバージョン確認

$ vagrant plugin list
vagrant-chef-zero (0.7.1)
vagrant-omnibus (1.4.1)
vagrant-share (1.1.3, system)
vagrant-vbguest (0.10.0)

vagrant-share は多分 Vagrant Cloud に Vagrant環境を公開するためのプラグインでデフォで入ってるやつだと思います。

vagrant設定ファイル作成

chefとの連携はいったん置いておいて、vagrant のみで CentOS7 の環境を作ってみます。

vagrantのゲストOSのイメージについては、boxというファイルが利用されます。
この box はネット上でいろいろなOSのものがアップロードされており、利用することができます。
box については、Discover Vagrant Boxes | Atlas by HashiCorp こちらから選べます。

今回は pmmresende/CentOS7 にしようと思います。

$ mkdir vagrant
$ cd vagrant
$ vagrant init CentOS7 pmmresende/CentOS7
$ vagrant up
Bringing machine 'default' up with 'virtualbox' provider...
==> default: Box 'CentOS7' could not be found. Attempting to find and install...
    default: Box Provider: virtualbox
    default: Box Version: >= 0
==> default: Loading metadata for box 'pmmresende/CentOS7'
    default: URL: https://atlas.hashicorp.com/pmmresende/CentOS7
The box you're adding has a name different from the name you
requested. For boxes with metadata, you cannot override the name.
If you're adding a box using `vagrant box add`, don't specify
the `--name` parameter. If the box is being added via a Vagrantfile,
change the `config.vm.box` value to match the name below.

Requested name: CentOS7
Actual name: pmmresende/CentOS7

あれ、エラーになっちゃった。

box を A list of base boxes for Vagrant - Vagrantbox.es から選んできて再度実行。
こっちの場合はURLになる。

$ vagrant init CentOS7 https://f0fff3908f081cb6461b407be80daf97f07ac418.googledrive.com/host/0BwtuV7VyVTSkUG1PM3pCeDJ4dVE/centos7.box

こんどは大丈夫でした。

$ vagrant ssh

でログインできます。

あと、最初は CentOS6.5 でやったのですが、GuestAdditions関連のエラーが発生しました。
どうやらゲストOSのカーネルが古いのが原因のようだったのでCentOS7としました。
参考になるかもしれませんのでその際みてたページを列挙します。

現時点でのVagrantfileの中身はこんな感じでございます。(コメント部は削りました)

# -*- mode: ruby -*-
# vi: set ft=ruby :
Vagrant.configure(2) do |config|
  config.vm.box = "CentOS7"
  config.vm.box_url = "https://f0fff3908f081cb6461b407be80daf97f07ac418.googledrive.com/host/0BwtuV7VyVTSkUG1PM3pCeDJ4dVE/centos7.box"
end

プライベートネットワークの設定

ホストとゲスト間の通信を行えるようにしたいので、Vagrantfile を編集します。(必須じゃないです)

# -*- mode: ruby -*-
# vi: set ft=ruby :
Vagrant.configure(2) do |config|
  config.vm.box = "CentOS7"
  config.vm.box_url = "https://f0fff3908f081cb6461b407be80daf97f07ac418.googledrive.com/host/0BwtuV7VyVTSkUG1PM3pCeDJ4dVE/centos7.box"
  # 追記
  config.vm.network :private_network, ip:"192.168.33.11"
end

再起動

$ vagrant reload

これで、ホストからssh vagrant@192.168.33.11 で接続できるようになりました。
vagrantユーザのパスワードは vagrant です

ゲストboxへの Chef のインストール

Vagrantfile に設定を追加することで、boxに自動で Chef をインストールすることができます。

# -*- mode: ruby -*-
# vi: set ft=ruby :
Vagrant.configure(2) do |config|
  config.vm.box = "CentOS7"
  config.vm.box_url = "https://f0fff3908f081cb6461b407be80daf97f07ac418.googledrive.com/host/0BwtuV7VyVTSkUG1PM3pCeDJ4dVE/centos7.box"
  config.vm.network :private_network, ip:"192.168.33.11"
  # 追記
  config.omnibus.chef_version = :latest
end

:latestの部分は Chef のバージョン指定です。
chefの最新バージョンのインストールを指定するために:latestとしました。

再起動

$ vagrant reload

で、Chefがゲスト側にもインストールされます。

Chef 設定ファイル作成

新規に Chef 関連のファイルの雛形を生成します。

$ cd vagrant
$ chef generate repo chef-repo

生成後

$ tree
.
├── Vagrantfile
└── chef-repo
    ├── LICENSE
    ├── README.md
    ├── Rakefile
    ├── certificates
    │   └── README.md
    ├── chefignore
    ├── config
    │   └── rake.rb
    ├── cookbooks
    │   └── README.md
    ├── data_bags
    │   └── README.md
    ├── environments
    │   └── README.md
    └── roles
        └── README.md

Berkshelf で cookbook を作成する

Berkshelf は よそ様の作った cookbook を依存関係を解消しつつ管理してくれるツールです。
これを使って cookbook を作ってみます。
とりあえず ntp の cookbook にしてみます。

Berksfile を作成

Berkshelf で cookbook を管理するための、Berksfile というファイルを新規に作成します。

$ cd chef-repo/
$ vi  Berksfile
source "https://supermarket.chef.io"
cookbook 'ntp'
cookbookインストール

インストール先を cookbooks に指定したいので、berks installじゃなくてにしてます。
ていうかberks installでもエラーにはなんなかったけど、どこに入ったのかよくわからんかった...

berks vendor cookbooks

Vagrant / Chef 連携設定

Vagrantfile に設定を追記します。

# -*- mode: ruby -*-
# vi: set ft=ruby :
Vagrant.configure(2) do |config|
  config.vm.box = "CentOS7"
  config.vm.box_url = "https://f0fff3908f081cb6461b407be80daf97f07ac418.googledrive.com/host/0BwtuV7VyVTSkUG1PM3pCeDJ4dVE/centos7.box"
  config.vm.network :private_network, ip:"192.168.33.11"
  config.omnibus.chef_version = :latest
  # 追加
  config.vm.provision "chef_zero" do |chef|
    chef.cookbooks_path = ["chef-repo/cookbooks"]
    chef.add_recipe "ntp"
  end
end

で、確認。

$ vagrant provision
$ vagrant ssh
$ ps aux | grep ntp
ntp       4892  0.0  0.4  29364  2060 ?        Ss   03:45   0:00 /usr/sbin/ntpd -u ntp:ntp -g
vagrant   4924  0.0  0.1 112656   976 pts/0    S+   03:46   0:00 grep --color=auto ntp

お、入ってる!

今後

続きで、rails 環境を作らねば...

しかしなんだか、達成感が皆無です。
いろんな解説ページを見ながらとりあえずやってみただけだから、あんまり理解できてません。もやもや。

少なくとも Chef に関しては全然知識足りてないので、そこをなんとかしてから続きかな。
今のまま環境つくってメンバーに配るくらいなら VirtualBox のイメージをファイルサーバにただ置いておいたほうがよさそうだ。