2014年4月24日木曜日

PackerでVagrantのboxを作成

今までVagrantのboxはVeeweeを使って作っていたのだけど、今回からPackerを使ってみることにした。

PackerはVeeweeみたいにテンプレートが付属していないので自分で用意する必要があるのだけど、一から自分で書くのはハマりどころも多くて大変だなーと思っててずっと手を出していなかったんだよね。
でもまぁみんな野良テンプレートとか作ってるだろうから、それをベースにカスタマイズしてみるかーと検索してたら、Opscode改めChefがBentoっていうPackerのテンプレート集を出しているのを知ったのでこれを使うことにした。

Packerのインストール

Packer
http://www.packer.io/

公式サイトからバイナリパッケージをインストール。

Macの場合はHomebrewからもインストールできる。

brew tap homebrew/binary
brew install packer

Bentoをダウンロード

Bento by opscode
http://opscode.github.io/bento/

公式サイトからアーカイブをダウンロードするかgitでcloneする。
きっとどんどん新しくなっていくだろうからcloneしておくのがいいかな。

git clone https://github.com/opscode/bento.git

boxをビルドしてみる

bentoのpacker用テンプレートが入っているディレクトリに移動してpacker buildを実行する。
カスタマイズしたければJSONファイルをいじる。

cd bento/packer
packer build -only=virtualbox-iso \
             -var 'chef_version=latest' \
             -var 'mirror=http://ftp.iij.ad.jp/pub/linux/centos' \
             centos-6.5-x86_64.json

いくつかオプションを指定してCentOS 6.5のboxを作成してみた。
そのままだと、テンプレートで定義されてる全builderでboxが生成されてしまうので、-onlyでVirtualBox用のみ生成するようにした。
-varはテンプレート内の変数を上書き指定するオプションで、ここではISOイメージのミラーサイトを国内にして、最新のChefをインストールするように指定した。
ちなみに、chef_versionを指定しない場合は、omnibus-chefはインストールされないようになっていた。

これでbuildsディレクトリの下にboxが生成されるので、あとはvagrant box addしておしまい。

vagrant box add CentOS-6.5-x86_64 ../builds/virtualbox/opscode_centos-6.5_chef-latest.box

2014年4月16日水曜日

Ohai 7でプラグインの仕様が変わった

先日リリースされたChef Client 11.12.0から密かに同梱されているOhaiが7.0系に差し替わったのだが、これが結構な仕様変更を含んでいて、Ohai Pluginなどを自作して使ってる人は気をつけないとハマる模様。

プラグインの仕様変更

いちおう互換性が保持されるようになっていてv6のプラグインもそのまま動くのだけれど、実行するとこんな感じのWARNINGが出る。

[2014-04-16T15:50:02+09:00] WARN: [DEPRECATION] Plugin at /tmp/example/plugins/foo.rb is a version 6 plugin. Version 6 plugins will not be supported in future releases of Ohai. Please upgrade your plugin to version 7 plugin syntax. For more information visit here: docs.opscode.com/ohai_custom.html

細かい点で色々仕様が変わってはいるのだけど、一番大きいのはプラグインの書き方が変わったことと、プラグイン名を指定していた部分がアトリビュート名を指定するように変わったこと。

新しいプラグインの書き方

従来のプラグインはこんな感じだった。

provides "foo"

require_plugin "baz"

foo Mash.new
foo[:data1] = "foo1"
foo[:data2] = "foo2"

Ohai 7からはこんな感じになる。

Ohai.plugin(:Foo) do
  provides "foo"
  depends  "bar/baz"

  collect_data do
    foo Mash.new
    foo[:data1] = "foo1"
    foo[:data2] = "foo2"
  end

  collect_data(:windows) do
    foo Mash.new
    foo[:data1] = "win_foo1"
    foo[:data2] = "win_foo2"
  end
end

v6ではベタ書きスクリプトっぽかったのが、v7ではオブジェクトっぽく内容を定義する形になった。
上の例には書いていないが、普通にメソッドを書いたり共通処理用のクラスを別途まとめたりもできるようになっているので、複雑な処理が必要な場合にだいぶ書きやすくなったと思う。

collect_dataがデータを収集する部分なんだけど、引数でプラットフォーム毎の内容に分けて書くことができるようになった。
該当するプラットフォームのcollect_dataがあるとそちらが呼ばれて、なければ:defaultが指定されているcollect_dataが呼ばれる(指定しない場合は:default)。

また、v6ではrequire_pluginで依存するプラグインを指定していたが、v7からはdependsで依存するアトリビュートを指定するようになった。
利用する側はプラグイン名を知る必要はなく、「どのアトリビュートが使いたい」というのを指定すれば、Ohaiの本体が自動的にそのアトリビュートを提供するプラグインを読み込んでくれる。ここ割りと重要。

Rubyプログラムから使用する場合の注意点

プラグインの仕様が変わったことで利用の仕方も少し変わった。

普通に全プラグインを使う場合は従来と同じでOhai::System#all_pluginsを呼び出せば全部実行してくれる。

require "ohai"

ohai = Ohai::System.new
ohai.all_plugins

p ohai.data

自作プラグインなど特定のプラグインだけを実行したい場合は少し変わったのだけど、これがv6用プラグインとv7用プラグインで若干違っていたりしてカオスな感じがする。

require_pluginを使うパターン

v6用とv7用が混在している場合はこのやり方しかないように思う。

require "ohai"

Ohai::Config[:plugin_path] << "/path/to/oreore/plugins"

ohai = Ohai::System.new
ohai.load_plugins
ohai.require_plugin("foo/bar")      # v7の場合はアトリビュート名
ohai.require_plugin("hoge_fuga")    # v6の場合はプラグイン名

p ohai.data

まずload_pluginsというのが事前に必要になった。これを呼ばないとプラグインがOhaiに認識されない。
ちなみにload_pluginsを呼んでもv6用プラグインは読み込まれない。なぜならv6用プラグインは「読み込まれる=実行される」なので、問答無用で実行されてしまうから。
ただしv6用のプラグインであることは認識される(ソースファイル内の文字列とかで判定してるんだろうか?)。

require_pluginにはv7用プラグインの場合はアトリビュート名を、v6用の場合はプラグイン名を指定する。
v7用プラグインはload_pluginsによってprovidesなどのメタ情報がロードされるので、それを使って解決しているのだろう。
v6は前述のとおり読み込んでしまったら実行されてしまうので、読み込んでない状態では中に書かれているprovidesとかも認識されない。よってプラグイン名を指定することになるようだ。
ちなみにv7用プラグインのプラグインを指定しても動かない。

このへんの仕組みはOhai::ProvidesMapとかOhai::Loaderがやってるようなので、興味のある人はソースを読んでみるといいかも。

all_pluginsに引数を渡すパターン

このやり方はv6用プラグインが混在している環境では使えない。
というのも、v6用プラグインは指定できず、無条件に実行されてしまうから(この挙動が仕様なのか現バージョンの不具合なのかは不明)。

require "ohai"

Ohai::Config[:plugin_path] << "/path/to/oreore/plugins"

ohai = Ohai::System.new
ohai.all_plugins([
  "foo/bar",
  "languages/ruby"
])

p ohai.data

all_pluginsの引数にアトリビュート名を指定すると、該当するアトリビュートを提供するプラグインのみが実行される。
ただし、(v6用の)プラグイン名は指定できない。そして指定してないにも関わらず、存在するv6プラグインは全て実行される。

ちなみにload_pluginsは不要。というかall_pluginsが内部で呼び出している。

2014年4月7日月曜日

Rubyでクラスが継承されたら何かする

Rubyではクラスが継承されると親クラスのClass#inheritedというメソッドが呼ばれる。

class Foo
  def self.inherited(subclass)
    puts "#{self} is inherited by #{subclass}"
  end
end

class Bar < Foo
end

Baz = Class.new(Foo)
Foo is inherited by Bar
Foo is inherited by #<Class:0x007fdd11969330>

Class.newでサブクラスを生成すると無名のクラスが生成されて、最初に定数に代入されたタイミングでクラスの名前が設定されるというキモい仕様があるので、Class.new(Foo)のタイミングでは名前が出ていない。

Class#inheritedが呼ばれるタイミングは「クラス定義文の実行直前」とのことなので、上記のBarクラスにあれこれメソッドとか定義してあっても、inheritedが呼ばれた段階ではまだ記述したメソッドが存在しないことに注意。
ちょうど上記のように単に継承しただけの状態と同じクラスになっている。

似たようなものにModule#includedModule#extendedがある。それぞれモジュールがinclude/extendされた時に呼ばれる。

こんな機能を使うことはそうそうないと思うけど、仕事で使ってたとあるライブラリではプラグインを自動的にプラグインマネージャ的なものに登録する仕組みとして使われていた。

参考