cloverrose's blog

Python, Machine learning, Emacs, CI/CD, Webアプリなど

Ansible ver1.3の新機能 changed_when を使って、ファイルに追記するPlaybookを書いてみた

Ansibleで今まで書いてきたOSセットアップスクリプトを置き換えていると、.bashrcとかに追記したいときの方法が見つかりませんでした。(copyはAnsible実行側にあるファイルをリモートに置き換えるものなので違う)

(書いた後に、本当はAnsible実行側に完成した.bashrcなどを置いてcopyするべきって思ったんですが、changed_whenの使い方とかの部分は有用だと思ったので残しておきます
また、lineinfileという正規表現などで一行だけ書き換える(または追記する:insertafter)モジュールがAnsible0.7以降で使えることがわかりました。Ansible Modules | AnsibleWorks

やりたいこと

一旦追記した内容を再度追記しようとしてもファイルに変更をしない(追記する順番によって出来上がるファイルの内容が異なっても構わない)

install系のコマンドと同じように変更したかどうかによってchange出力を制御したい

方針

shellアクション内でPythonスクリプトを実行しファイルに追記するのが1回目かの判定と追記を行う
sys.exitの戻り値で変更の有無をAnsibleに伝える(ignore_errorsとregisterとchanged_whenを使う)

注意

changed_whenはAnsible ver1.3で新しく追加された機能です(Documentation | AnsibleWorks)
2013/9/9現在ver1.3はdevelブランチなのでpipなどでは入れることができないので、Githubからdevelブランチを持ってきて自分でビルドする必要があります(develブランチがデフォルトブランチ)

git clone https://github.com/ansible/ansible.git
cd ansible
python setup.py build  # virtualenv環境なのでsudoしない
python setup.py install

別にchanged_whenを使わなくても毎回changeとして扱われるだけなので、気にならない人は大丈夫です。

ソースコード

python script

シェルスクリプトで以前書き込みがあったか判定するのは大変そうなので、Pythonでスクリプトを書きました。

onetime_modify.py

# -*- coding:utf-8 -*-
"""
modify file one time.
if modify exit(1)
if nothing exit(0)
"""
import sys
import os.path


def onetime_modify(filename, text):
    if os.path.isdir(filename):
        sys.exit('{0} is directory'.format(filename))

    should_modify = False
    if os.path.isfile(filename):
        with open(filename) as f:
            content = f.read()
        should_modify = text not in content
    else:
        should_modify = True

    if should_modify:
        with open(filename, 'a') as f:
            f.write(text)
        return 1
    else:
        return 0


if __name__ == '__main__':
    args = sys.argv
    assert len(args) == 3, '{0} filename text'.format(args[0])
    filename = args[1]
    text = '\n'.join(args[2].split('\\n'))
    sys.exit(onetime_modify(filename, text))

改行文字\nはエスケープされて\\nになってしまうので引数を処理しています。\\nとかが入力されるような例外ケースはまだ対応してません。
追記を行った場合にsys.exit(1)を行っています。何もしなかった場合はsys.exit(0)です。


playbook

playbook3.yml

---
- hosts: 127.0.0.1
  user: rose

  tasks:
  - name: modify config file
    shell: python ~/onetime_modify.py hoge.txt 'hello ansible'
    ignore_errors: True
    register: res
    changed_when: res.rc == 1

ignore_errors: True (Playbooks | AnsibleWorks)
sys.exit(1)で終了した場合にエラーとして扱わないようにする。

register: res (Documentation | AnsibleWorks)
シェルを実行した時の戻り値を保存する(名前任意)。-vvvオプションを使うとresの値が見れる。
res.rcでsys.exitの値、その他にres.stdout、res.stderrがある。

changed_when: res.rc == 1
保存した値を使ってファイルに追記があったかどうかを判定。