Ansible 自作モジュール作成 ～その3:Change要否を判定したい人生～

前回の記事1：[Ansible 自作モジュール作成 ～その1:引数受け取りたい人生～](https://qiita.com/Ki2neudon/items/f6e1ee4fc8f29e12893f)
前回の記事2：[Ansible 自作モジュール作成 ～その2:ただコマンドを実行したい人生～](https://qiita.com/Ki2neudon/items/75b8c3dd406e11a386c1)

# 振り返り

- モジュールの雛形作った(その1)
- 複数の引数受け取れた(その1)
- 引数をコマンドと結合して実行した(その2)

# 参考

- [Ansibleのモジュール開発（実践編）](https://dev.classmethod.jp/articles/develop-ansible-module-advanced/)
    - 今回もお世話になりますです。

# 初期処理

おさらいもかねて。まずは雛形を作成(参考「前回の記事1」参照)。
テスト的に作成したコマンド実行部分(参考「前回の記事2」参照)は一度取り除いた。

```python:mkfifo.py
#!/usr/bin/python
# -*- coding: utf-8 -*-

from ansible.module_utils.basic import AnsibleModule

# メイン処理
#-----------------------------------------------------------
def main():
    # AnsibleModuleクラス: moduleを作成
    module = AnsibleModule(

        # 引数受け取り
        argument_spec=dict(

            # 引数: data(str型,def:pong)
            data=dict(type='str', default='pong'),
        ),
        # 引数チェックを有効
        supports_check_mode=True
    )

    # 結果dict: resultを作成
    result = dict(
        # key: ping に引数で与えられたkey: data のvalueを格納
        ping=module.params['data'],
    )

    # resultの中身を key=value,の形で出力
    module.exit_json(**result)

if __name__ == '__main__':
    main()
```

これに変数の受け取り部分を実装。

```python:mkfifo.py
: #(省略)
    # AnsibleModuleクラス: moduleを作成
    module = AnsibleModule(

        # 引数受け取り
        argument_spec=dict(

            # 引数: path(必須,str型)
            path=dict(required=True,type='str'),
            # 引数: owner(str型,デフォルト=root)
            owner=dict(type='str',default='root'),
            # 引数: group(str型,デフォルト=root)
            group=dict(type='str',default='root'),
            # 引数: mode(str型,デフォルト=0644)
            mode=dict(type='str',default='0644'),
        ),
        # 引数チェックを有効
        supports_check_mode=True
    )
: #(省略)
```

現時点ではOS版数やディストリビューションによる分岐は設けない。``changed``変数を設けるだけ…だけど、``changed``ってどこに定義するんだ？``AnsibleModule``クラスの外側で良いのかな？ちょっとお試しする。

```python:sakrua.py
#!/usr/bin/python
# -*- coding: utf-8 -*-

from ansible.module_utils.basic import AnsibleModule

# メイン処理
#-----------------------------------------------------------
def main():
    # AnsibleModuleクラス: moduleを作成
    module = AnsibleModule(
        {}
    )

    changed = False  ★この値をTrueに変えたりする

    module.exit_json(changed=changed)

if __name__ == '__main__':
    main()
```

```bash
# changed = Trueの場合
$ ansible -i test_grp 192.168.56.104 -m sakura -M library -u root
192.168.56.104 | CHANGED => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python"
    },
    "changed": true
}

# changed = Falseの場合
$ ansible -i test_grp 192.168.56.104 -m sakura -M library -u root
192.168.56.104 | SUCCESS => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python"
    },
    "changed": false
}
```

よしよし。``AnsibleModule``クラスの外側に定義するわけだ。

# ``Changed``を判定してみる

``changed``が``True``になる条件は以下

1. ``path``で与えられた名前付きパイプが存在しない
2. ``path``で与えられた名前付きパイプの``ower``:``group``:``mode``が引数どおりではない

条件1. に関しては``test -p xxxxx``の``ReturnCode``で判断できる。
条件2. に関しては``stat -c %U:%G:%a``の標準出力で判断できる。

ただし``stat -c %a``の結果は下3桁しか出ないので``stat -c 0%a``で対処する。
また``stdout``は改行コードまで出力されるため比較時に``\n``を差し込んだ。

```python:mkfifo.py
: #(省略)
    # 変更有無を初期化(変更なし)
    changed = False

    # testコマンドでpathに名前付きパイプが存在するか確認
    rc, stdout, stderr = module.run_command("test -p " + module.params['path'])
    # 確認結果
    if ( rc != 0 ):
        # 存在しないので変更対象
        changed = True
    else:
        # 存在する場合はowner:group:modeを取得
        rc, stdout, stderr = module.run_command("stat -c %U:%G:0%a " + module.params['path'])
        # 比較。コマンド結果は最後に改行コード \n が付くことに注意
        if ( stdout != module.params['owner'] + ':' + module.params['group'] + ':' + module.params['mode'] + '\n'):
            # owner:group:modeが異なるので変更対象
            changed = True

    # 終了
    module.exit_json(changed=changed,stdout=stdout,stderr=stderr)
: #(省略)
```

いったんここまでで動かしてみようかな。

```bash
$ ansible -i test_grp 192.168.56.104 -m mkfifo -M library -u root -a "path=/tmp/testfifo owner=root group=root mode=0777"
192.168.56.104 | CHANGED => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python"
    },
    "changed": true,
    "stderr": "",
    "stderr_lines": [],
    "stdout": "",
    "stdout_lines": []
}
```

試しに対象サーバ上に``/tmp/testfifo``を作ってみる。

```bash
# /tmp/testfifo root:root:0644 で作ってみる
$ ssh root@192.168.56.104 "mkfifo /tmp/testfifo; chmod 0644 /tmp/testfifo"
$ ssh root@192.168.56.104 "ls -l /tmp/testfifo"
prw-r--r--. 1 root root 0  6月  1 01:30 2020 /tmp/testfifo
$ ansible -i test_grp 192.168.56.104 -m mkfifo -M library -u root -a "path=/tmp/testfifo owner=root group=root mode=0777"
192.168.56.104 | CHANGED => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python"
    },
    "changed": true,
    "stderr": "",
    "stderr_lines": [],
    "stdout": "root:root:0644\n",
    "stdout_lines": [
        "root:root:0644"
    ]
}

# /tmp/testfifo root:root:0777 にしてみる
$ ssh root@192.168.56.104 "chmod 0777 /tmp/testfifo"
$ ssh root@192.168.56.104 "ls -l /tmp/testfifo"
prwxrwxrwx. 1 root root 0  6月  1 01:30 2020 /tmp/testfifo
$ ansible -i test_grp 192.168.56.104 -m mkfifo -M library -u root -a "path=/tmp/testfifo owner=root group=root mode=0777"
192.168.56.104 | SUCCESS => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python"
    },
    "changed": false,
    "stderr": "",
    "stderr_lines": [],
    "stdout": "root:root:0777\n",
    "stdout_lines": [
        "root:root:0777"
    ]
}
```

おけ。本項はここまで。次はコマンド投入編。。は内容うすくなりそうだな。