Ansible Tower(AWX)のREST API経由で変数を渡した時に変数を展開するフィルターを作ってみた

Ansible Tower(AWX)でREST API経由でJinja2の変数を渡すと展開されなかったので一先ず暫定として変数を展開するフィルターを作ってみました。
変数が展開されない事象の詳細については以下を参照ください。

[Ansible Tower(AWX)にREST API経由で変数を渡すと使用できない仕様について調べてみた](https://qiita.com/sky_jokerxx/items/2e013fd64aeba84e3bc7)

# 1. set_ansible_varsフィルター

https://github.com/sky-joker/ore-ore-ansible/blob/master/plugins/filter/set_ansible_vars.py

# 2. 使い方

## 2-1. インストール

(1) フィルターをダウンロードします。

```shell-session
# curl -L https://raw.githubusercontent.com/sky-joker/ore-ore-ansible/master/plugins/filter/set_ansible_vars.py -O
```

(2) フィルターは以下のどちらかに配置します。

* Playbookが保存されているカレントディレクトリに `filter_plugins` ディレクトリを作成して、その配下にフィルターを置く。
* 任意の場所に保存する場合は、Ansible Tower(AWX)の設定にある `ジョブ` の `追加の環境変数` へ [ANSIBLE_FILTER_PLUGINS](https://docs.ansible.com/ansible/2.4/config.html#envvar-ANSIBLE_FILTER_PLUGINS) にフィルターが保存されているパスを設定して追加します。

## 2-2. 使用例

REST APIから受け取った文字列の変数を展開するには、フィルターの引数でPlaybook内に定義されている変数を渡す必要があります。
例えば以下の例を参考にしてください。

### 2-2-1. テスト用Playbook

```yaml
---
- name: Filter Test.
  hosts: all
  gather_facts: no
  vars:
    message1: test message1
    message2:
      msg: test message2
    message3:
      - test message3-1
      - test message3-2
  tasks:
    - set_fact:
        message4: test message4

    - shell: ls
      register: shell_ret

    - debug: msg="msg1"
    - debug: msg="{{ msg1 | set_ansible_vars(message1) }}"

    - debug: msg="msg2"
    - debug: msg="{{ msg2 | set_ansible_vars(message2) }}"

    - debug: msg="msg3"
    - debug: msg="{{ msg3 | set_ansible_vars(message3) }}"

    - debug: msg="msg4"
    - debug: msg="{{ msg4 | set_ansible_vars(message1, message2, message3) }}"
    - set_fact:
        dict_test: "{{ msg4 | set_ansible_vars(message1, message2, message3) }}"
    - debug: msg="dict_msg1 {{ dict_test.msg1 }}"
    - debug: msg="dict_msg2 {{ dict_test.msg2 }}"
    - debug: msg="dict_msg3 {{ dict_test.msg3 }}"

    - debug: msg="msg5"
    - debug: msg="{{ msg5 | set_ansible_vars(message1, message2, message3) }}"
    - set_fact:
        list_test: "{{ msg5 | set_ansible_vars(message1, message2, message3) }}"
    - debug: msg="list_1 {{ list_test[0] }}"
    - debug: msg="list_2 {{ list_test[1] }}"
    - debug: msg="list_3 {{ list_test[2] }}"

    - debug: msg="msg6"
    - debug: msg="{{ msg6 | set_ansible_vars(message4) }}"

    - debug: msg="msg7"
    - debug: msg="{{ msg7 | set_ansible_vars(shell_ret) }}"
```

### 2-2-2. テスト用スクリプト

```python
#!/usr/bin/env python3
from collections import defaultdict
import json
import requests
from requests.packages.urllib3.exceptions import InsecureRequestWarning
requests.packages.urllib3.disable_warnings(InsecureRequestWarning)

def main():
    # Ansible Tower
    url = "https://192.168.0.234/api/v1/job_templates/13/launch/"
    user = "admin"
    passwd = "redhat"

    headers = {"Content-Type": "application/json"}
    nested_dict = lambda: defaultdict(nested_dict)
    data = nested_dict()
    data["extra_vars"]["msg1"] = "{{ message1 }}"
    data["extra_vars"]["msg2"] = "{{ message2.msg }}"
    data["extra_vars"]["msg3"] = "{{ message3 }}"
    data["extra_vars"]["msg4"] = '{"msg1": "{{ message1 }}", "msg2": "{{ message2.msg }}", "msg3": "{{ message3 }}"}' # 文字列として渡す
    data["extra_vars"]["msg5"] = '["{{ message1 }}", "{{ message2 }}", "{{ message3 }}"]' # 文字列として渡す
    data["extra_vars"]["msg6"] = "{{ message4 }}"
    data["extra_vars"]["msg7"] = "{{ shell_ret.stdout }}"

    r = requests.post(url,
                      headers=headers,
                      auth=(user, passwd),
                      data=json.dumps(data),
                      verify=False)

    print(r.status_code)
    print(json.dumps(json.loads(r.text), indent=2))

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

### 2-2-3. 結果

`msg1` の `{{ message1 }}` や `msg2` の `{{ message2.msg }}` が展開されていることが確認できます。

```
PLAY [Filter Test.] ************************************************************

TASK [set_fact] ****************************************************************
ok: [localhost]

TASK [command] *****************************************************************
changed: [localhost]

TASK [debug] *******************************************************************
ok: [localhost] => {
    "msg": "msg1"
}

TASK [debug] *******************************************************************
ok: [localhost] => {
    "msg": "test message1"
}

TASK [debug] *******************************************************************
ok: [localhost] => {
    "msg": "msg2"
}

TASK [debug] *******************************************************************
ok: [localhost] => {
    "msg": "test message2"
}

TASK [debug] *******************************************************************
ok: [localhost] => {
    "msg": "msg3"
}

TASK [debug] *******************************************************************
ok: [localhost] => {
    "msg": [
        "test message3-1", 
        "test message3-2"
    ]
}

TASK [debug] *******************************************************************
ok: [localhost] => {
    "msg": "msg4"
}

TASK [debug] *******************************************************************
ok: [localhost] => {
    "msg": {
        "msg1": "test message1", 
        "msg2": "test message2", 
        "msg3": "[u'test message3-1', u'test message3-2']"
    }
}

TASK [set_fact] ****************************************************************
ok: [localhost]

TASK [debug] *******************************************************************
ok: [localhost] => {
    "msg": "dict_msg1 test message1"
}

TASK [debug] *******************************************************************
ok: [localhost] => {
    "msg": "dict_msg2 test message2"
}

TASK [debug] *******************************************************************
ok: [localhost] => {
    "msg": "dict_msg3 [u'test message3-1', u'test message3-2']"
}

TASK [debug] *******************************************************************
ok: [localhost] => {
    "msg": "msg5"
}

TASK [debug] *******************************************************************
ok: [localhost] => {
    "msg": [
        "test message1", 
        "{u'msg': u'test message2'}", 
        "[u'test message3-1', u'test message3-2']"
    ]
}

TASK [set_fact] ****************************************************************
ok: [localhost]

TASK [debug] *******************************************************************
ok: [localhost] => {
    "msg": "list_1 test message1"
}

TASK [debug] *******************************************************************
ok: [localhost] => {
    "msg": "list_2 {u'msg': u'test message2'}"
}

TASK [debug] *******************************************************************
ok: [localhost] => {
    "msg": "list_3 [u'test message3-1', u'test message3-2']"
}

TASK [debug] *******************************************************************
ok: [localhost] => {
    "msg": "msg6"
}

TASK [debug] *******************************************************************
ok: [localhost] => {
    "msg": "test message4"
}

TASK [debug] *******************************************************************
ok: [localhost] => {
    "msg": "msg7"
}

TASK [debug] *******************************************************************
ok: [localhost] => {
    "msg": "filter_plugins\nmain.yml"
}

PLAY RECAP *********************************************************************
localhost                  : ok=24   changed=1    unreachable=0    failed=0   
```

# 3. 注意点

REST API経由から渡した変数をset_factで再利用する場合は文字列で渡す必要があります。
例えば、dictやlistは文字列にして渡さないとユニコードとして変換されます。
同じPlaybook内で再利用しない場合(他モジュールへのオプションの引数)は、おそらく...問題ないと思います。

# 4. 最後に

REST API経由で渡した変数が文字列に変換されて困っていましたが、一先ずこれで展開できるようになりました。
ただ、このやり方は展開する文字列変数に対応したPlaybook内変数をフィルターに引数として渡す必要があるので手間ですが。。。
ちなみにフィルターを使ったPlaybookを `ansible-playbook` コマンドで実行しても結果は同じになるように作っています。

# 5. 参考

[その他のテスト結果](https://github.com/sky-joker/ore-ore-ansible/tree/master/test/units/plugins/filter/set_ansible_vars/result)