AnsibleとServerspecでcronについてちょっとハマった話

大栗です。

最近こんなブログが書かれたことで
_3_KDDI_ChatWork_-_Ansible_Packer_CFn
と煽られているので、泣きながらAnsibleのテストをServerspecで書いています。cron設定をAnsibleで書いてServerspecでテストする時に少しハマったので紹介します。

やりたい事

普通にcronの設定をAnsibleで記述してServerspecでcronの内容のテストをする、と単純なことです。

ちなみに『kitchen-ansiblepushを利用したAnsible roleのテスト環境構築』を参考にTest Kitchenの環境を作りました。

ハマった事

まず普通にAnsibleでcronの設定を書いてみます。こんな感じでcronのタスクを書きました。

- name: cron setting
  cron:
    name: cron setting
    minute: "*/5"
    user: ec2-user
    job: "ls -al ~/ | logger -t cron-set -p local0.info"
    cron_file: cron-set

そして、Serverspecでは『Ansibleのテストで使いたい8つのServerspec』を参考に書いてみました。

describe cron do
  it { should have_entry('*/5 * * * * ls -al ~/ | logger -t cron-set -p local0.info').with_user('ec2-user') }
end

Serverspecを実行すると、failしてしまいます。。。

Cron
  should have entry "*/5 * * * * ls -al ~/ | logger -t cron-set -p local0.info" (FAILED - 1)

Failures:

  1) Cron should have entry "*/5 * * * * ls -al ~/ | logger -t cron-set -p local0.info"
     On host `54.250.174.181'
     Failure/Error: it { should have_entry('*/5 * * * * ls -al ~/ | logger -t cron-set -p local0.info').with_user('ec2-user') }
       expected Cron to have entry "*/5 * * * * ls -al ~/ | logger -t cron-set -p local0.info"
       sudo -p 'Password: ' /bin/sh -c crontab\ -u\ ec2-user\ -l\ \|\ grep\ -v\ \'\^\[\[:space:\]\]\*\#\'\ \|\ grep\ --\ \^\\\\\\\*/5\\\ \\\\\\\*\\\ \\\\\\\*\\\ \\\\\\\*\\\ \\\\\\\*\\\ ls\\\ -al\\\ \\\~/\\\ \\\|\\\ logger\\\ -t\\\ cron-set\\\ -p\\\ local0.info\$

     # ./spec/cron-setting/cron-setting_spec.rb:4:in `block (2 levels) in <top (required)>'

Finished in 2.51 seconds (files took 1.21 seconds to load)
1 example, 1 failure

原因は?

原因はAnsibleでcron_fileを設定していることが原因でした。

Ansibleではcron_fileを設定しないと/var/spool/cron/<ユーザ名>にcronを記述するのですが、cron_fileを設定すると/etc/cron.d/<cron_fileの値>にcron設定を記述します。一方Serverspecのcronではcrontabの内容を取得するようです。つまり/var/spool/cron/<ユーザ名>の内容です。今回Ansibleでcron_fileを設定していたのでcron設定の場所が異なりテストでfailしたという事でした。

解決策

一番簡単な解決策はAnsibleでcron_fileを設定しないことです。

- name: cron setting
  cron:
    name: cron setting
    minute: "*/5"
    user: ec2-user
    job: "ls -al ~/ | logger -t cron-set -p local0.info"

すると、こんな感じにパスします。

Cron
  should have entry "*/5 * * * * ls -al ~/ | logger -t cron-set -p local0.info"

Finished in 0.46809 seconds (files took 2.06 seconds to load)
1 example, 0 failures

どうしても/etc/cron.d/<cron_fileの値>に記述したいという場合はServerspec側でテストの記述を変える必要があります。この場合はfileで中身を確認します。matchでは正規表現リテラルを記述するのですが、固定の文字列が含まれているかを確認したいだけなのでRegexp.escapeRegexp.compileでこんな感じに書いてみました。

describe file('/etc/cron.d/cron-set') do
  its(:content) { should match Regexp.compile(Regexp.escape('*/5 * * * * ec2-user ls -al ~/ | logger -t cron-set -p local0.info')) }
end

こんな感じにテストをパスします。

File "/etc/cron.d/cron-set"
  content
    should match /\*\/5\ \*\ \*\ \*\ \*\ ec2\-user\ ls\ \-al\ ~\/\ \|\ logger\ \-t\ cron\-set\ \-p\ local0\.info/

Finished in 1.89 seconds (files took 1.45 seconds to load)
1 example, 0 failures

さいごに

ちゃんとドキュメントを読め!と怒られる内容でした。ServerspecではcronのResource Typeで/etc/cron.d/以下の内容を見ない模様なのでAnsibleのcronとServerspecのcronでは微妙に仕様は異なると言うことです。

何はともあれ『さばんなちほーでも「Ansible Roleのテスト書いてるぜ」と言えるよう』なって良かったです。

参考資料

Ansible Docs » cron - Manage cron.d and crontab entries.

Serverspec Resource Types cron