Ansibleでカスタムフィルタを使ってみる

# はじめに
Ansibleではデータの整形や加工、変換をする際Jinja2の提供するフィルタを使うことができます。
しかし、jinja2の提供する[built in filters](https://jinja.palletsprojects.com/en/2.11.x/templates/#builtin-filters) だけでは不十分であったり痒いところに手が届かないということが多々ありました。
その際、カスタムモジュールの作成で対応していたのですが、カスタムフィルタも自作できるということを知ったので色々試してみました。
フィルタについては[Ansible filter公式ドキュメント](https://docs.ansible.com/ansible/2.3/playbooks_filters.html)をご覧ください。
# 実行環境
```
$ ansible --version
ansible 2.9.13
python version = 3.8.5 (default, Jul 21 2020, 10:48:26) [Clang 11.0.3 (clang-1103.0.32.62)]
```

# 検証

### ansible.cfgの設定
カスタムフィルタの配置箇所は個人専用、チーム用、プロジェクト用等、
使用用途によって変えると良いかと思います。

```:ansible.cfg
filter_plugins = [利用したいカスタムフィルタを配置したフルパス]/filter_plugins
```
### カスタムフィルタ

ansible.cfgで指定したカスタムフィルタのパスにコードを配置。

```python:filter_plugins/my_custom_filters.py
#!/usr/bin/python
from ansible.errors import AnsibleError

class FilterModule(object):
     def filters(self):
         return {
             # [Playbook内でしていするfilter名]: 関数名
             'square': self.square_int
         }

     def square_int(self, number):
         if type(number) is not int:
             raise AnsibleError('{}\'s type is {} not integer'.format(number,type(number)))
         number = number * number
         return number
```
### Playbook
通常のbuiltinfilterと同様の記法で呼び出し。

```yaml:site.yml
- hosts: localhost
  gather_facts: false
  tasks:
    - debug:
        msg: "{{ 5 | square }}"
```

### 実行結果

```
PLAY [localhost] *******************************************************************************************************************************************************************************************

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

PLAY RECAP *************************************************************************************************************************************************************************************************
localhost                  : ok=1    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
```

複数カスタムフィルタを作成したり、フィルタでパラメータを複数指定することもできます。
### カスタムフィルタ

```python:filter_plugins/my_custom_filters.py
#!/usr/bin/python
from ansible.errors import AnsibleError

class FilterModule(object):
     def filters(self):
         return {
            # [Playbook内でしていするfilter名]: 関数名
             'square': self.square_int,
             'total' : self.total
         }

     def square_int(self, number):
         if type(number) is not int:
             raise AnsibleError('{}\'s type is {} not integer'.format(number,type(number)))
         number = number * number
         return number

     def total(self, *args):
         total = 0
         for i in args:
             if type(i) is not int:
                 raise AnsibleError('{}\'s type is {} not integer'.format(i,type(i)))
             total = total + i
         return total
```
### Playbook

```yaml:site.yml
---
- hosts: localhost
  gather_facts: false
  tasks:
    - debug:
        msg: "{{ 5 | square }}"

    - debug:
        msg:
          - "{{ 1 | total(2,3) }}"
          - "{{ 1 | total(2,3,4,5) }}"
```

### 実行結果

```
PLAY [localhost] *******************************************************************************************************************************************************************************************

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

TASK [debug] ***********************************************************************************************************************************************************************************************
ok: [localhost] => {
    "msg": [
        "6",
        "15"
    ]
}

PLAY RECAP *************************************************************************************************************************************************************************************************
localhost                  : ok=2    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
```

# 最後に
assertモジュールと組み合わせて変数のチェックをしたりset_factモジュールと組み合わせて自在に変数定義したり色々応用がききます。
あまりAnsibleエンジン単体でカスタムフィルタを使う場面はないですが、他のサービスからAnsibleTowerのAPIをコールするときなど外部から変数を受け取る場合はアサーションやコンバージョンの機能が必要になる場面は多い印象です。
公式ドキュメントを読んでいるとまだまだ使いこなせていない機能がたくさんあるのでどんどん使っていきたいです。