Ansible のモジュールを check mode と差分表示に対応させる

はじめに

Ansible の実行時に --check を指定すると check mode (dry run) として (リモートに変更を加えることなく) 実行され,--diff を指定すると変更内容の差分が表示されます. ただしモジュール側がこれらのオプションをサポートしている場合に限ります*1

check mode や差分表示は事前確認が容易になるという点で利便性が高いため,可能な限りモジュールを対応させたくなります.

これらの対応方法について調べてみると Check Mode (“Dry Run”) で若干触れられていますが,実際には対応済みモジュールのコードを読み解く必要があります*2

そこで今回はこの対応方法についてまとめておきたいと考えました.

Ansible 環境

ansible 2.6.0 (devel ad69ef88e7) last updated 2018/05/20 18:09:04 (GMT +900)
  config file = None
  configured module search path = [u'/Users/krdlab/.ansible/plugins/modules', u'/usr/share/ansible/plugins/modules']
  ansible python module location = /Users/krdlab/.provisioning/dev/projects/misc/krdlab/ansible/lib/ansible
  executable location = /Users/krdlab/.provisioning/dev/projects/misc/krdlab/ansible/bin/ansible
  python version = 2.7.11 (default, Oct 20 2016, 16:27:36) [GCC 4.2.1 Compatible Apple LLVM 7.0.2 (clang-700.1.81)]

ベースとなるサンプルモジュール

path で指定したファイルを作成/削除するだけのモジュールを用意しました.

https://github.com/krdlab/examples/blob/master/ansible-module-check-and-diff/version1/simplefile.py

以下のように実行します.

$ ansible all -i ',localhost' -c local --module-path version1 -m simplefile -a 'path=/tmp/testfile state=present'
localhost | SUCCESS => {
    "changed": true,
    "simplefile": {
        "path": "/tmp/testfile",
        "state": "present"
    }
}

まだ check mode には対応していないコードであるため,当然 check mode では期待通りに動きません.

$ ansible all -i ',localhost' -c local --module-path version1 -m simplefile -a 'path=/tmp/testfile state=present' --check
localhost | SKIPPED     # ← スキップされる

check mode のサポート

check mode のサポートを追加したコード全体は,以下のリンク先の通りです.

https://github.com/krdlab/examples/blob/master/ansible-module-check-and-diff/version2/simplefile.py

AnsibleModulesupports_check_mode = True を指定すると check mode でもスキップされることなくモジュールが実行されます.

    module = AnsibleModule(
        argument_spec=dict(
            ...
        ),
        supports_check_mode=True    # ← これ
    )

module.check_mode で check mode かどうかの判定が可能です.

check mode 時は実際の変更をリモートに加えることなく,現在のリモートの状態と指定されたパラメータを比較して「変更が発生するか?」だけを判定させます. 例えば AWS 系のモジュールだと,describe API を呼び出して得られた情報と指定されたパラメータの内容とを比較して "changed" か "ok" かを決定しています.

今回の simplefile モジュールも (リモートではありませんが) 現在の状態と指定されたパラメータに基づいて,変更が発生する場合に changed = True を返すように実装しています (以下のリンク先参照).

https://github.com/krdlab/examples/blob/master/ansible-module-check-and-diff/version2/simplefile.py#L50

また戻り値*3をきちんと返すようにすると,register を使っていても後続のタスクが失敗しなくなるためおすすめです*4

差分表示のサポート

差分表示のサポートを追加したコード全体は,以下のリンク先の通りです.

https://github.com/krdlab/examples/blob/master/ansible-module-check-and-diff/version3/simplefile.py

module.exit_jsondiff パラメータを渡すと,--diff を指定して実行した場合に変更差分が表示されるようになります.

    ...
    module.exit_json(changed=changed, diff=diff, simplefile=params)
                                     # ↑これ

diff の構造は以下のものが期待されます.

diff = dict(
    before=dict(
        変更前の状態
    ),
    after=dict(
        変更後の状態
    )
)

beforeafter の中身は比較可能となるようにモジュール作成者が決定します. userfile モジュールなどを参考に実装すると良さそうです.

今回の simplefile モジュールだと以下のような情報を返しています.

def __create_diff(self):
    path = self.expected['path']
    act_state, exp_state = self.__file_state(), self.expected['state']

    diff = dict(before=dict(path=path), after=dict(path=path))  # path は変化しないので必ず入れる
    if act_state != exp_state:                                  # state は変化する場合だけ diff に取り込む
        diff['before']['state'] = act_state
        diff['after']['state'] = exp_state
    return diff

上記のようなデータを返すと,--diff を付けて実行したときに以下のような表示が得られます.ちなみにこれは,既に存在している /tmp/testfileabsent にする場合のものです.

--- before
+++ after
@@ -1,4 +1,4 @@
 {
     "path": "/tmp/testfile",
-    "state": "present"
+    "state": "absent"
 }

おわりに

Ansible のモジュールを check mode に対応させることで,実際に変更を適用する前に変更が発生するのかどうかを知ることができます. 差分表示にまで対応させれば「あれ?なんの変更だっけ?」といったときに役立ちます. また可能であれば check mode でも戻り値を返すようにしておくと,regsiter を使ってい他場合に後続タスクが失敗しなくなります.

やはり適用前に確認はしたいですから,check mode に対応することは結構重要なことだと思います*5

というわけで今回はその対応方法の紹介でした.

*1:実際に Ansible の標準モジュールの中にも未対応なものがちらほらみつかります.コアモジュール以外は "not required" とされているようです → Check Mode (“Dry Run”)

*2:もしかしてどこかにまとまっているんだろうか?

*3:ドキュメントで Return Value として定義されているデータ

*4:どうしても返せない場合もあるのですが……

*5:なのでこれが取り込まれると嬉しいなぁ…… → https://github.com/ansible/ansible/pull/39846