最近仕事で itamae を触っているので、ハマったところや工夫したところをまとめておきます
下記のスニペットはitamae 1.9系で確認してます
最初に読むべきドキュメント
-
https://github.com/itamae-kitchen/itamae#in-japanese
- 日本語はここにまとまってる
-
https://github.com/itamae-kitchen/itamae/wiki
- コマンドの使い方がだいたいまとまってる
ファイルが更新された時のみ特定のコマンドを実行したい
解) notifies
か subscribes
を使う
どっちも似てるけど(いまだに区別ついてないw)だいたいこんな感じ
-
notifies
: 自分が変更された時に他を実行したい -
subscribes
: 他が変更された場合に自分を実行したい
例)/etc/nginx/sites-available/jenkins.conf
が変更されたら nginxをrestartする
execute "/etc/init.d/nginx configtest" do
subscribes :run, "template[/etc/nginx/sites-available/jenkins.conf]"
action :nothing
end
execute "/etc/init.d/nginx restart" do
subscribes :run, "template[/etc/nginx/sites-available/jenkins.conf]"
action :nothing
end
template "/etc/nginx/sites-available/jenkins.conf"
subscribes
と action :nothing
を一緒に使うのがポイント。
(execute
のデフォルトactionは run
なので template
に来る前に実行されてしまう)
例)sources listが更新されたらapt-get update したい
# NOTE: source listが更新されればapt-get updateする
execute "apt-get update" do
subscribes :run, "remote_file[/etc/apt/sources.list.d/jenkins-ci.list]", :immediately
action :nothing
end
remote_file "/etc/apt/sources.list.d/jenkins-ci.list"
package "jenkins"
-
subscribes
の第3引数に:immediately
(コールバック受けたらすぐに実行)つけるのがポイント- デフォルトだと
:delay
(遅延実行)なのでremote_file
直後にapt-get update
が実行されずにpackage "jenkins"
でエラーになることがある :delay
だとレシピ全部流してから実行?(実行タイミングはどこにも書いてないので不明)-
:delay
だと「そのレシピファイルをすべて実行し終わった後に実行される」とのこと
- デフォルトだと
cdした先でコマンドを実行したい
解)cwd
使う
例:よくあるmake実行
execute "cd /usr/local/src/tig"
execute "make configure"
execute "make clean",
execute "make prefix=/usr/local",
execute "make install prefix=/usr/local",
とか実行しても、1行目と2行目のセッションは違うので、意図したディレクトリでmakeが実行されません。(capistranoとかと同じ)
かといって
execute "cd /usr/local/src/tig && make configure"
とか書いてもいいんですがウザいのでそういう時は cwd
が便利。
execute "make configure" do
cwd "/usr/local/src/tig"
end
みたいに書ける。
実行するコマンドが多いと配列でぶん回してもいい。
[
"make configure",
"./configure",
"make clean",
"make prefix=/usr/local",
"make install prefix=/usr/local",
].each do |command|
execute command do
cwd "/usr/local/src/tig"
end
end
ちなみに cwd
は Itamae::Resource::Base 1 に定義されているので execute
以外でも使える。
https://github.com/itamae-kitchen/itamae/blob/v1.9.2/lib/itamae/resource/base.rb#L94
makeでインストールしたいんだけどインストール済ならスキップしたい
package
を使えばインストール済かどうかチェックしていい感じに冪等性担保してくれるんだけど、 execute
使う場合には自前で頑張る必要がある。
解)not_if
を使う
execute "make" do
not_if "/usr/local/bin/tig -v | grep #{note[:tig][:version]}"
end
not_if
のコマンドが失敗した時のみに execute
実行される。
インストールされていなければ tig -v
でこけるし、違うバージョンがインストールされていれば grep
でこける。
個人的には ls
と組み合わせて使うことも多い。
# Jenkinsユーザの秘密鍵が作られていなければ作る
execute "ssh-keygen -t rsa -N '' -C 'jenkins@my.server' -f #{node[:jenkins][:home_dir]}/.ssh/id_rsa" do
not_if "ls #{node[:jenkins][:home_dir]}/.ssh/id_rsa"
end
serverspecからもitamaeのnodeで定義した値を使いたい
こういうやつ
jenkins:
home_dir: "/var/lib/jenkins"
解)spec/spec_helper.rb 辺りに下記を追加
require "yaml"
require "itamae"
ENV["NODE"] ||= "nodes/default.yml"
def node
return @node if @node
hash = YAML.load_file("#{__dir__}/../#{ENV["NODE"]}")
@node = Itamae::Node.new(hash, Specinfra.backend)
end
- ローカル変数だとrequireしても別ファイルから使えないのでメソッドにするのがポイント
-
Itamae::Node
のインスタンスを返すことでnode[:platform]
などの specinfraの Host Inventory もnode
経由で取れるようになる
specはこんな感じ
describe file("#{node[:jenkins][:home_dir]}/.ssh/id_rsa") do
it { should be_file }
it { should be_mode 600 }
it { should be_owned_by "jenkins" }
it { should be_grouped_into 'jenkins' }
end
OSごとに分岐をしたい
解)node[:platform]
を使う
case node[:platform]
when 'debian'
package 'libffi-dev'
when 'redhat'
# CentOSは redhat 扱い。
package 'libffi-devel'
end
さすがにpackage名まではspecinfraで抽象化できないので複数OS考慮しようとするとこういうところで分岐が必要になってくる。
参考になるやつ
- http://serverspec.org/host_inventory.html
- https://github.com/k0kubun/itamae-plugin-recipe-rbenv/blob/v0.5.0/lib/itamae/plugin/recipe/rbenv/dependency.rb
packageからインストールした設定ファイルを一部分だけ変更したい
解)file
リソースの :edit
を使う
例)aptからインストールしたJenkinsの設定ファイルでJAVA_ARGSを編集
package "jenkins"
# Jenkinsの設定が更新されれば再起動
execute "/etc/init.d/jenkins restart" do
subscribes :run, "file[/etc/default/jenkins]"
action :nothing
end
file "/etc/default/jenkins" do
action :edit
block do |content|
content.gsub!(/^JAVA_ARGS=".+".*$/, %q(JAVA_ARGS="-Djava.awt.headless=true -Dhudson.util.ProcessTree.disable=true"))
end
end
行の途中で置換されても困るので /^〜$/
で明示的に行頭と行末を指定するのがいいと思う
".*$
みたいにダブルクオーテーションの後ろで0文字以上マッチングしてるのは、下記のような文字列の行末のコメントアウト部分を削るため
JAVA_ARGS="-Djava.awt.headless=true" # Allow graphs etc. to work even when an X server is present
存在するはずのファイルが無いってエラーになる
# NOTE: itamae local で実行
Dir.glob("/etc/pam.d/*").each do |filename|
file filename do
action :edit
block do |content|
content.gsub!(/pam_ldap\.so/, "pam_sss.so")
end
end
end
こういうレシピを書いていてなぜか /etc/pam.d/smtp
が無いというエラーが。
これ自体は、別のレシピでpostfixをインストールしててその時にこのファイルを消してる(ように見えた)のが原因だった。
で、なんで Dir.glob
でヒットしたファイルがレシピ実行時に存在しないのかというと、itamaeの挙動によるもの。
Dir.glob("/etc/pam.d/*").each do |filename|
file filename do
# 〜
end
end
実は↑の時点では file
リソースの動的定義だけで中のレシピは実行されていない。
itamaeはレシピを全部読み込んだ後にリソースのブロックを評価しているので、レシピ定義時点では /etc/pam.d/smtp
は存在してたかもしれないけど、レシピ実行時には消されていたためファイルが存在しないというエラーになってた。
今回の場合は 下記のように only_if "ls #{filename}"
でエラーは回避できた。(ls
して成功した時のみレシピを実行)
Dir.glob("/etc/pam.d/*").each do |filename|
file filename do
action :edit
only_if "ls #{filename}" # <- これを追加
block do |content|
content.gsub!(/pam_ldap\.so/, "pam_sss.so")
end
end
end
今回みたいにファイルが消えてる場合は only_if "ls 〜"
で回避できるけど、ファイルが追加された場合は編集すべきファイルがそのまま残るというリスクがある。(対処できるんだろうか。。。)
fluentdのプラグインをインストールしたい
解)gem_package
を使う
fluentdのプラグインも普通のgemなので gem_package リソースがそのまま使える。td-agentを使ってる場合は gem_binary
を下記のようにしてやるだけでインストールできる
td-agent 2系
gem_package "fluent-plugin-forest" do
gem_binary "/usr/sbin/td-agent-gem"
end
td-agent 1系
gem_package "fluent-plugin-forest" do
gem_binary "/usr/lib/fluent/ruby/bin/fluent-gem"
end
数が多い場合はこんな風に each
で動的定義するのが楽
[
["fluent-plugin-forest", "0.3.0"],
["fluent-plugin-rewrite", "0.0.13"],
["fluent-plugin-rewrite-tag-filter", "1.5.4"],
].each do |plugin_name, plugin_version|
gem_package plugin_name do
gem_binary "/usr/sbin/td-agent-gem"
version plugin_version
end
end
vagrantのプラグインも同様の方法でインストールできるはず
-
全てのresourceの基底クラス ↩