Ansibleのテキストパース用フィルタープラグインを自作してみた


はじめに

以前の記事で、TTP(Template Text Parser)というPythonのパーサーライブラリを使って、L2SWのConfigファイルをパースし、ポート管理表の自動生成を行いました。
L2SWのConfigからポート管理表を自動生成してみた

今回は、このパーサーをAnsibleのカスタムフィルターとして取り込み、xxx_commandモジュールで取得したshowコマンド結果をパースできるようにしてみました。

※ フィルタープラグイン自作例は、以前こちらでも紹介しています。
※ 私が知る限り、パース用フィルタープラグインとして、他にもTextFSMを利用したparse_cli_textfsmや、pyATS/Genieを利用したparse_genie(Ansible Galaxyからインストールが必要。詳細はGitHub/parse_genieに記載。)があります。

セットアップ

Python3.6.7の仮想環境内にインストールしたAnsibleを使いました。当初、バージョン2.9.0でテストしていたのですが、Logging error(KeyError)メッセージが複数出たため、2.8.4を使っています。

追加でTTPのインストールが必要です。

(venv) [centos@localhost ansible]$ pip install ttp

カスタムフィルター

以下のPythonスクリプトを作成しました。

custom_filters_ttp.py
from ansible.errors import AnsibleError

# ttp、jsonのインポート。インポートに失敗した場合、後続の処理でエラー出力できるようにする。
try:
    from ttp import ttp
    HAS_TTP = True
except ImportError:
    HAS_TTP = False

try:
    import json
    HAS_JSON = True
except ImportError:
    HAS_JSON = False


class FilterModule(object):

    def parse_cli_ttp(self, cli_output, template_file):
        if not HAS_TTP:
            raise AnsibleError('parse_cli_ttp filter requires TTP library to be installed')

        if not HAS_JSON:
            raise AnsibleError('parse_cli_ttp filter requires JSON library to be installed')

        with open(template_file, 'rt') as ft:
            ttp_template = ft.read()

        # create parser object and parse data using template
        parser = ttp(data=cli_output, template=ttp_template)
        parser.parse()

        # return result in JSON format
        results = parser.result(format='json')[0]
        return results

    def filters(self):
        return {
            # 左側がPlaybook内で使用するフィルター名、右側が紐付ける関数名。
            'parse_cli_ttp': self.parse_cli_ttp,
        }

ベストプラクティスに従い、Playbookを格納しているディレクトリ配下に、filter_pluginsディレクトリを作成し、その中に本ファイルを格納しました。

また、Ansibleがこのカスタムフィルターを認識できるよう、ansible.cfgの設定を以下の通り書き換えました。

ansible.cfg
[defaults]
filter_plugins     = [Playbook格納ディレクトリのフルパス]/filter_plugins

Playbook

ios_commandモジュールでRunning Configを取得し、続くdebugモジュール内で、取得したConfigと以前の記事で作成したL2インターフェース設定用テンプレートのファイルパスを指定しました。シンプルですね!

playbook_ttp.yml
---

- hosts: cisco
  gather_facts: no
  connection: network_cli

  tasks:
    - name: run show command on remote devices
      ios_command:
        commands: show running-config
      register: result

    - name: display parsed output
      debug:
        msg: "{{ result.stdout[0] | parse_cli_ttp('catalyst2960_template_ttp2.txt') }}"

出力結果

前回とConfigが異なるため結果に違いはありますが、パース自体は問題なくできています。

$ ansible-playbook -i inventory_2960.ini playbook_ttp.yml

PLAY [cisco] *************************************************************************************************

TASK [run show command on remote devices] ********************************************************************
ok: [hqdist1A]

TASK [display parsed output] *********************************************************************************
ok: [hqdist1A] => {
    "msg": [
        {
            "l2_interfaces": [
                {
                    "description": "<< Connect hqdist1 and hqdist2 >>",
                    "duplex": "auto",
                    "mode": "trunk",
                    "port_no": "Port-channel1",
                    "portfast": "x",
                    "speed": "auto",
                    "status": "o",
                    "vlan": "1,101"
                },
                {
                    "description": "<< To hqborder1 Fa1 >>",
                    "duplex": "full",
                    "mode": "access",
                    "port_no": "FastEthernet0/1",
                    "portfast": "x",
                    "speed": "100",
                    "status": "o",
                    "vlan": "200"
                },
                {
                    "description": "<< To hqborder2 Fa1 >>",
                    "duplex": "full",
                    "mode": "access",
                    "port_no": "FastEthernet0/2",
                    "portfast": "x",
                    "speed": "100",
                    "status": "o",
                    "vlan": "202"
                },
                {
                    "description": "<< To hqaccess1 Fa0/23 >>",
                    "duplex": "full",
                    "mode": "access",
                    "port_no": "FastEthernet0/3",
                    "portfast": "x",
                    "speed": "100",
                    "status": "o",
                    "vlan": "100"
                },
                {
                    "duplex": "auto",
                    "mode": "trunk",
                    "port_no": "FastEthernet0/4",
                    "portfast": "x",
                    "speed": "auto",
                    "status": "o",
                    "vlan": "1"
                },
                {
                    "duplex": "auto",
                    "mode": "access",
                    "port_no": "FastEthernet0/5",
                    "portfast": "x",
                    "speed": "auto",
                    "status": "x",
                    "vlan": "1"
                },
                {
                    "duplex": "auto",
                    "mode": "access",
                    "port_no": "FastEthernet0/6",
                    "portfast": "x",
                    "speed": "auto",
                    "status": "x",
                    "vlan": "1"
                },
                {
                    "duplex": "auto",
                    "mode": "access",
                    "port_no": "FastEthernet0/7",
                    "portfast": "x",
                    "speed": "auto",
                    "status": "x",
                    "vlan": "1"
                },
                {
                    "duplex": "auto",
                    "mode": "access",
                    "port_no": "FastEthernet0/8",
                    "portfast": "x",
                    "speed": "auto",
                    "status": "x",
                    "vlan": "1"
                },
                {
                    "duplex": "auto",
                    "mode": "access",
                    "port_no": "FastEthernet0/9",
                    "portfast": "x",
                    "speed": "auto",
                    "status": "x",
                    "vlan": "1"
                },
                {
                    "duplex": "auto",
                    "mode": "access",
                    "port_no": "FastEthernet0/10",
                    "portfast": "x",
                    "speed": "auto",
                    "status": "x",
                    "vlan": "1"
                },
                {
                    "duplex": "auto",
                    "mode": "access",
                    "port_no": "FastEthernet0/11",
                    "portfast": "x",
                    "speed": "auto",
                    "status": "x",
                    "vlan": "1"
                },
                {
                    "duplex": "auto",
                    "mode": "access",
                    "port_no": "FastEthernet0/12",
                    "portfast": "x",
                    "speed": "auto",
                    "status": "o",
                    "vlan": "203"
                },
                {
                    "duplex": "auto",
                    "mode": "access",
                    "port_no": "FastEthernet0/13",
                    "portfast": "x",
                    "speed": "auto",
                    "status": "o",
                    "vlan": "100"
                },
                {
                    "duplex": "auto",
                    "mode": "access",
                    "port_no": "FastEthernet0/14",
                    "portfast": "x",
                    "speed": "auto",
                    "status": "x",
                    "vlan": "1"
                },
                {
                    "duplex": "auto",
                    "mode": "access",
                    "port_no": "FastEthernet0/15",
                    "portfast": "x",
                    "speed": "auto",
                    "status": "x",
                    "vlan": "1"
                },
                {
                    "duplex": "auto",
                    "mode": "access",
                    "port_no": "FastEthernet0/16",
                    "portfast": "x",
                    "speed": "auto",
                    "status": "x",
                    "vlan": "1"
                },
                {
                    "duplex": "auto",
                    "mode": "access",
                    "port_no": "FastEthernet0/17",
                    "portfast": "x",
                    "speed": "auto",
                    "status": "x",
                    "vlan": "1"
                },
                {
                    "duplex": "auto",
                    "mode": "access",
                    "port_no": "FastEthernet0/18",
                    "portfast": "x",
                    "speed": "auto",
                    "status": "x",
                    "vlan": "1"
                },
                {
                    "duplex": "auto",
                    "mode": "access",
                    "port_no": "FastEthernet0/19",
                    "portfast": "x",
                    "speed": "auto",
                    "status": "x",
                    "vlan": "1"
                },
                {
                    "duplex": "auto",
                    "mode": "access",
                    "port_no": "FastEthernet0/20",
                    "portfast": "x",
                    "speed": "auto",
                    "status": "x",
                    "vlan": "1"
                },
                {
                    "duplex": "auto",
                    "mode": "access",
                    "port_no": "FastEthernet0/21",
                    "portfast": "x",
                    "speed": "auto",
                    "status": "x",
                    "vlan": "1"
                },
                {
                    "duplex": "auto",
                    "mode": "access",
                    "port_no": "FastEthernet0/22",
                    "portfast": "x",
                    "speed": "auto",
                    "status": "x",
                    "vlan": "1"
                },
                {
                    "description": "<< To hqdist2 Fa0/23 >>",
                    "duplex": "auto",
                    "mode": "trunk",
                    "port_no": "FastEthernet0/23",
                    "portfast": "x",
                    "speed": "auto",
                    "status": "o",
                    "vlan": "1,101"
                },
                {
                    "description": "<< To hqdist2 Fa0/24 >>",
                    "duplex": "auto",
                    "mode": "trunk",
                    "port_no": "FastEthernet0/24",
                    "portfast": "x",
                    "speed": "auto",
                    "status": "o",
                    "vlan": "1,101"
                },
                {
                    "duplex": "auto",
                    "mode": "access",
                    "port_no": "GigabitEthernet0/1",
                    "portfast": "x",
                    "speed": "auto",
                    "status": "o",
                    "vlan": "1"
                },
                {
                    "duplex": "auto",
                    "mode": "access",
                    "port_no": "GigabitEthernet0/2",
                    "portfast": "x",
                    "speed": "auto",
                    "status": "o",
                    "vlan": "1"
                }
            ]
        }
    ]
}

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

最後に

今回作ったプラグインはGitHub/ansible-ttpにアップしています。Configだけでなく、他のshowコマンドの出力結果も比較的簡単にパース出来ますので、ぜひ使ってみて頂ければと思います。