うさラボ

お勉強と備忘録

genie dqのcustom_filter自作してみた

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

filter_dq.pyというスクリプトを作成しました

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で公開できるように勉強中です・・・ (詳しい方いたら教えてください・・・)