ansible.utils.cli_parseで出力した辞書から任意のkeyだったりvalueだったりを取り出すのにgenieのdqを使ってみたくなりました
実装するには自分でcustom_filter作るしかなかったので試してみました
Dqの説明は以下
公式ドキュメント
https://pubhub.devnetcloud.com/media/genie-docs/docs/userguide/utils/index.html
環境
ansible 2.10.7
genie 21.2.3
参考にしたGithub
こちらのfilter_pluginを参考に作成しました github.com
from __future__ import absolute_import, division, print_function
__metaclass__ = type
from ansible.module_utils.six import PY3
from ansible.errors import AnsibleError, AnsibleFilterError
try:
from genie.utils import Dq
HAS_GENIE = True
except ImportError:
HAS_GENIE = False
class FilterModule(object):
def __init__(self):
if not PY3:
raise AnsibleFilterError("Genie requires Python 3")
if not HAS_GENIE:
raise AnsibleFilterError("Genie not found. Run 'pip install genie'")
def genie_dq_contains(self, output,value):
try:
parsed_output = Dq(output).contains(value).reconstruct()
except Exception as e:
raise AnsibleFilterError("DQ Error: {0}".format(e))
if parsed_output:
return parsed_output
else:
return None
~~ snip~~
def filters(self):
return {
'genie_dq_contains': self.genie_dq_contains,
'genie_dq_get_values': self.genie_dq_get_values,
'genie_dq_contains_key_value': self.genie_dq_contains_key_value,
'genie_dq_value_operator': self.genie_dq_value_operator,
}
genie_dq_contrainsのメソッドにfilterとして呼び出された際の処理を記載しています、
filtersメソッドはfilter呼び出し時のkeywordを定義しています。
Playbookでこのgenie_dq_contrainsを呼び出すときは以下のようになります
"{{ res_show | genie_dq_contrains('route') }}"
res_showの変数が一番目の変数(output)として入り、valueは2番目の変数(value)としてfilterに渡されます
引き渡された変数はGenieのDqで特定のデータを抽出します
同じ要領で以下4つのメソッドを作成してみました 1. genie_dq_contains 2. genie_dq_get_values 3. genie_dq_contains_key_value 4. genie_dq_value_operator
それでは実際に動かしてみます
下準備
custom_filterを使うには ansible.cfgにfilterパスを追加する必要があります filter_pluginにcustom_filterが配置されているパスを定義します。
[defaults] filter_plugins = filter_plugins
これで下準備は完了です
使い方
動作確認のため、以下のようなPlaybookを書きました
処理の内容としては、ansible.utils.cli_parseでshow ip routeを送信しparser pyatsでパースをします。
この時の戻り値をres_showに格納し、debugモジュールで表示する際にcustom_filterにかけていきます。
---
- hosts: ios01
gather_facts: false
tasks:
- name: コマンド送信
ansible.utils.cli_parse:
command: show ip route
parser:
name: ansible.netcommon.pyats
register: res_show
- name: routeの一覧
ansible.builtin.debug:
msg: "{{ res_show | genie_dq_get_values(value) }}"
vars:
value: 'routes'
- name: 対象のrouteの取得
ansible.builtin.debug:
msg: "{{ res_show | genie_dq_contains_key_value(key,value) }}"
vars:
key: 'routes'
value: "0.0.0.0/0"
- name: 想定のNexthopのrouteをListにしてから対象のrouteを表示
ansible.builtin.debug:
msg: "{{ res_show | genie_dq_contains_key_value(key,value) | genie_dq_contains(route) }}"
vars:
key: 'next_hop'
value: "10.10.20.254"
route: "0.0.0.0/0"
- name: routeのpreference値が01のrouteのみ表示
ansible.builtin.debug:
msg: "{{ res_show | genie_dq_value_operator(arg1,arg2,arg3) }}"
vars:
arg1: 'route_preference'
arg2: '=='
arg3: '1'
実行対象はいつも通り、Devnetのalwaysonのリソースを使わせていただいています。
実行
さっそく実行してみます。 ログが長くなってしまったので、分割して結果について紹介していきます。
custom_filter_genie_dq# ansible-playbook -i inventory/hosts.ini dq_sample.yml
PLAY [ios01] *************************************************************************************************************************************************
TASK [コマンド送信] ************************************************************************************************************************************************
ok: [ios01]
TASK [routeの一覧] **********************************************************************************************************************************************
ok: [ios01] => {
"msg": [
"0.0.0.0/0",
"10.10.20.0/24",
"10.10.20.48/32",
"10.255.255.0/24",
"10.255.255.1/32"
]
}
上記はgenie_dq_get_valuesで'route'のvalueをlistとして格納しています。
単純にルートの数などが知りたいときに使えるかなぁ
TASK [対象のrouteの取得] *******************************************************************************************************************************************
ok: [ios01] => {
"msg": {
"parsed": {
"vrf": {
"default": {
"address_family": {
"ipv4": {
"routes": {
"0.0.0.0/0": {
"active": true,
"metric": 0,
"next_hop": {
"next_hop_list": {
"1": {
"index": 1,
"next_hop": "10.10.20.254",
"outgoing_interface": "GigabitEthernet1"
}
}
},
"route": "0.0.0.0/0",
"route_preference": 1,
"source_protocol": "static",
"source_protocol_codes": "S*"
}
}
}
}
}
}
}
}
}
続いてgenie_dq_contains_key_valueでkeyとvalueの組み合わせを指定しデータを抽出します
routeの中の0.0.0.0/0のデータのみ抜き出しています。
もちろん存在しない場合は空っぽで応答されます。
TASK [想定のNexthopのrouteをListにしてから対象のrouteを表示] *****************************************************************************************************************
ok: [ios01] => {
"msg": {
"parsed": {
"vrf": {
"default": {
"address_family": {
"ipv4": {
"routes": {
"0.0.0.0/0": {
"next_hop": {
"next_hop_list": {
"1": {
"next_hop": "10.10.20.254"
}
}
}
}
}
}
}
}
}
}
}
}
続いてはgenie_dq_contains_key_valueとgenie_dq_containsの合わせ技になります
genie_dq_contains_key_valueでnext_hopが10.10.20.254のデータのみを抽出し、その後
genie_dq_containsでrouteが0.0.0.0/0のものを抽出しています。
(| をつないで複数のfilterをつなぎ合わせることも可能ってのを見せたかった例)
TASK [routeのpreference値が01のrouteのみ表示] ************************************************************************************************************************
ok: [ios01] => {
"msg": {
"parsed": {
"vrf": {
"default": {
"address_family": {
"ipv4": {
"routes": {
"0.0.0.0/0": {
"route_preference": 1
}
}
}
}
}
}
}
}
}
PLAY RECAP ***************************************************************************************************************************************************
ios01 : ok=5 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
最後はgenie_dq_value_operatorです、
route_preferenceが1のデータのみ抽出しています
まとめ
はじめてcustom_filterを作成したが思ったよりも簡単に実装することができました。 パパっとできた割には結構柔軟に使えて便利なfilterになりました
なるべく既存のものを利用し、自分でガリガリ作らないほうがいいってポリシーなんですが
作ってみると楽しくて、これもcustom_filterでできそうだなと考えてしまう頭になってしまいそうです。
ただライセンス周りがいまいちわからず(このブログもダイジョブなんかなとか思ったり) githubで公開できるように勉強中です・・・ (詳しい方いたら教えてください・・・)