Ansible Advent Calendar 2023の4日目の記事です。
概要
リスト内辞書に定義されたIPアドレスの情報をKeyにしてソートしたかったのでやり方を考えてみました。
例えば下記のようなデータです
destination: - ip: 10.0.1.1 name: 宛先2 - ip: 2.0.1.1 name: 宛先1
リストを2.0.1.1 -> 10.0.1.1の順番に並び替えをするようなものです。
環境
Python 3.9.16 ansible [core 2.15.6] jinja 3.1.2 netaddr 0.9.0 jmespath 1.0.1
手順
オリジナル
まずはsortフィルタのみを使って確認してみます。
用意したPlaybookは以下です、データip以外の要素は使わないので省略しています。
mapフィルタを利用しipの値のみを抜き出したリスト生成しソートしています
- name: 2023 Advent Carender hosts: localhost gather_facts: false tasks: - name: set sample data ansible.builtin.set_fact: data: - ip: 10.0.0.1 - ip: 10.0.0.11 - ip: 1.0.0.1 - ip: 2.0.0.1 - ip: 2.0.1.1 - ip: 0.0.0.2 - ip: 0.0.0.1 - ip: 0.0.0.10 - ip: 255.255.255.255 - ip: 0.0.1.161 - name: original ansible.builtin.debug: msg: "{{ data | map(attribute='ip') | sort }}"
結果を見ると、文字列としてソートされているのIPアドレス順にはなっていません。
1.0.0.1のあとは2.0.1.1が来てほしいところです。
PLAY [2023 Advent Carender] *************************************************************************************************************** TASK [set sample data] ******************************************************************************************************************** ok: [localhost] TASK [original] *************************************************************************************************************************** ok: [localhost] => { "msg": [ "0.0.0.1", "0.0.0.10", "0.0.0.2", "0.0.1.161", "1.0.0.1", "10.0.0.1", "10.0.0.11", "2.0.0.1", "2.0.1.1", "255.255.255.255" ] } PLAY RECAP ******************************************************************************************************************************** localhost : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
カスタムフィルターを作る
IPアドレスをソートするためにまず思いつたのがカスタムフィルタを作るでした。
pythonの標準ライブラリであるipaddressを使えばIPアドレスを数値に変換することができるので、数値に変換後にソートすることで
IPアドレスをソートができると考えました
import ipaddress def ip_sort(data): return sorted(data, key=lambda x: int(ipaddress.ip_address(x['ip']))) class FilterModule(object): def filters(self): return { "ip_sort": ip_sort, }
カスタムフィルタはplaybookが存在する場所に作成したcustom_filterディレクトリに配置し ansible.cfgを以下のように設定しフィルタを利用可能にします
[defaults] filter_plugins = custom_filter/
作成したPlaybookは以下です、ip_sortフィルタをかけた時点でソートが完了しているので その後mapフィルタでipの値をリストに抽出し表示します。
--- - name: 2023 Advent Carender Custom Filter hosts: localhost gather_facts: false tasks: - name: set sample data ansible.builtin.set_fact: data: - ip: 10.0.0.1 - ip: 10.0.0.11 - ip: 1.0.0.1 - ip: 2.0.0.1 - ip: 2.0.1.1 - ip: 0.0.0.2 - ip: 0.0.0.1 - ip: 0.0.0.10 - ip: 255.255.255.255 - ip: 0.0.1.161 - name: custom filter ansible.builtin.debug: msg: "{{ data | ip_sort | map(attribute='ip') }}"
実行したところ、想定通りにソートされていることが確認できました。 やったね
PLAY [2023 Advent Carender Custom Filter] ************************************************************************************************* TASK [set sample data] ******************************************************************************************************************** ok: [localhost] TASK [custom filter] ********************************************************************************************************************** ok: [localhost] => { "msg": [ "0.0.0.1", "0.0.0.2", "0.0.0.10", "0.0.1.161", "1.0.0.1", "2.0.0.1", "2.0.1.1", "10.0.0.1", "10.0.0.11", "255.255.255.255" ] } PLAY RECAP ******************************************************************************************************************************** localhost : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
ちなみに、IPアドレスを数値に変換すると0.0.0.0が0
に255.255.255.255は4294967295
になります。
ipaddrフィルタで頑張る
いきなり、カスタムフィルタを使って解決を目指してしまいましたが、Ansibleが提供している機能だけでどうにかできないか?も考えてみました Ansibleにはipaddrフィルタというものが存在しており、先ほどIPアドレスをIntに変換したのと同じことができます
--- - name: 2023 Advent Carender ipaddr filter hosts: localhost gather_facts: false tasks: - name: set sample data ansible.builtin.set_fact: data: - ip: 10.0.0.1 - ip: 10.0.0.11 - ip: 1.0.0.1 - ip: 2.0.0.1 - ip: 2.0.1.1 - ip: 0.0.0.2 - ip: 0.0.0.1 - ip: 0.0.0.10 - ip: 255.255.255.255 - ip: 0.0.1.161 - name: ipaddr filter ansible.builtin.debug: msg: "{{ data | map(attribute='ip') | ansible.utils.ipaddr('int') | sort | ansible.utils.ipaddr }}"
実行したところ、こちらも想定通りに並び替えができました(まずはこっち思いつきたかった)
PLAY [2023 Advent Carender JQ and ipaddr filter] ****************************************************************************************** TASK [set sample data] ******************************************************************************************************************** ok: [localhost] TASK [ipaddr filter] ************************************************************************************************************************* ok: [localhost] => { "msg": [ "0.0.0.1", "0.0.0.2", "0.0.0.10", "0.0.1.161", "1.0.0.1", "2.0.0.1", "2.0.1.1", "10.0.0.1", "10.0.0.11", "255.255.255.255" ] } PLAY RECAP ******************************************************************************************************************************** localhost : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
おまけ
いままではIPの値の身を抽出した並び替えましたが、ip以外のkeyが定義されていて、そのKeyを取り出したい場合も考えてます。
keyとして追加したnameにはソート後の順番を書いてます、ソートしてnameを取り出したときに1~10まできれいに並べます。
selectattrフィルタで要素を抽出しています。
--- - name: 2023 Advent Carender ipaddr filter hosts: localhost gather_facts: false tasks: - name: set sample data ansible.builtin.set_fact: data: - ip: 10.0.0.1 name: 8 - ip: 10.0.0.11 name: 9 - ip: 1.0.0.1 name: 5 - ip: 2.0.0.1 name: 6 - ip: 2.0.1.1 name: 7 - ip: 0.0.0.2 name: 2 - ip: 0.0.0.1 name: 1 - ip: 0.0.0.10 name: 3 - ip: 255.255.255.255 name: 10 - ip: 0.0.1.161 name: 4 - name: ipaddr filter ansible.builtin.debug: msg: "{{ element.name }}" loop: "{{ data | map(attribute='ip') | ansible.utils.ipaddr('int') | sort | ansible.utils.ipaddr }}" vars: element: "{{ data | selectattr('ip','eq',item) | first }}"
実行結果
PLAY [2023 Advent Carender ipaddr filter] ************************************************************************************************* TASK [set sample data] ******************************************************************************************************************** ok: [localhost] TASK [ipaddr filter] ********************************************************************************************************************** ok: [localhost] => (item=0.0.0.1) => { "msg": "1" } ok: [localhost] => (item=0.0.0.2) => { "msg": "2" } ok: [localhost] => (item=0.0.0.10) => { "msg": "3" } ok: [localhost] => (item=0.0.1.161) => { "msg": "4" } ok: [localhost] => (item=1.0.0.1) => { "msg": "5" } ok: [localhost] => (item=2.0.0.1) => { "msg": "6" } ok: [localhost] => (item=2.0.1.1) => { "msg": "7" } ok: [localhost] => (item=10.0.0.1) => { "msg": "8" } ok: [localhost] => (item=10.0.0.11) => { "msg": "9" } ok: [localhost] => (item=255.255.255.255) => { "msg": "10" } PLAY RECAP ******************************************************************************************************************************** localhost : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
ほかにもjson_queryフィルタを使っても同じようなこともできますね。
- name: json_query ansible.builtin.debug: msg: "{{ data | community.general.json_query('[*].ip') | ansible.utils.ipaddr('int') | sort | ansible.utils.ipaddr }}"
以上、小ネタでした~