はじめに
Ansibleではデータの整形や加工、変換をする際Jinja2の提供するフィルタを使うことができます。
しかし、jinja2の提供するbuilt in filters だけでは不十分であったり痒いところに手が届かないということが多々ありました。
その際、カスタムモジュールの作成で対応していたのですが、カスタムフィルタも自作できるということを知ったので色々試してみました。
フィルタについてはAnsible filter公式ドキュメントをご覧ください。
実行環境
$ 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の設定
カスタムフィルタの配置箇所は個人専用、チーム用、プロジェクト用等、
使用用途によって変えると良いかと思います。
filter_plugins = [利用したいカスタムフィルタを配置したフルパス]/filter_plugins
カスタムフィルタ
ansible.cfgで指定したカスタムフィルタのパスにコードを配置。
#!/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と同様の記法で呼び出し。
- 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
複数カスタムフィルタを作成したり、フィルタでパラメータを複数指定することもできます。
カスタムフィルタ
#!/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
---
- 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をコールするときなど外部から変数を受け取る場合はアサーションやコンバージョンの機能が必要になる場面は多い印象です。
公式ドキュメントを読んでいるとまだまだ使いこなせていない機能がたくさんあるのでどんどん使っていきたいです。