tkuchikiの日記

新ブログ https://blog.tkuchiki.net

chef で FileEdit を使うときは、write_file を使ってファイルを書き出さないほうが良い(かもしれない)

執筆時の chef のバージョンは以下のとおりです。

$ rpm -q chef
chef-11.16.4-1.el6.x86_64

chef でファイルの変更をする場合、

chef/file_edit.rb at 11.16.4 · opscode/chef · GitHub

を使うことで簡単に行うことができます。

実際にコードを書く場合は、

file "/tmp/testfile" do
  f = Chef::Util::FileEdit.new(path)
  f.insert_line_if_no_match(/hogehoge/, "hogehoge")
  f.write_file
end

のように記述することでファイルを変更することができます。

メソッドを簡単に説明すると、

  • insert_line_if_no_match は、第一引数の regex に match しなければ第二引数の文字列を追記する
  • write_file は、元のファイルに .old をつけて mv して、新しくファイルを作り直す

です。

ただ、write_file はファイルを変更する上では問題ないのですが、
これを使うと chef 実行時に diff が出力されません。
ファイルをどのように変更したか確認したい場合に困ります。
また、chef の backup にも保存されません。
実際に実行すると以下の様なログが出力されます。

* file[/tmp/testfile] action create (up to date)

up to date と出力されていますが、
実際はファイルに文字列が追記されています。

これを解決する方法は、Chefのレシピでsed的な事を実施 - Qiita が参考になりました。
ありがとうございます。

https://github.com/opscode/chef/blob/11.16.4/lib/chef/util/file_edit.rb#L27
の @editor.lines には、ファイルの行が配列で格納されているので、
send(:editor).lines で取得して、join で結合してファイルに出力する、といった方法のようです。
コードにすると以下のようになります。

file "/tmp/testfile" do
  f = Chef::Util::FileEdit.new(path)
  f.insert_line_if_no_match(/hogehoge/, "hogehoge")
  content f.send(:editor).lines.join
end

この場合、content を使ってファイルを変更するため、
diff が出力されますし、backup にも保存されます。

write_file があるので、そちらを使ったほうが良さそうな感じはしますが、
diff と backup のことを考えると content を使って内容を上書きする方法が良さそうです。