やりたかったこと
前回、ntc-templateの自作に入門したので今回は同じ内容を別のパーサーで試してみます。
やることはshow ip nat translationsのパースです。
公式ドキュメント ttp.readthedocs.io
参考にさせていただいたブログ zaki-hmkc.hatenablog.com
準備
環境
python
* Python 3.9.6
ライブラリ
* ansible-core==2.12.0
* ttp==0.8.1
コレクション
* ansible.netcommon 2.4.0
* ansible.utils 2.4.2
実装
まずは公式ドキュメントのQuick startにあるスクリプト動かして確認を使い方を確認をします。
from ttp import ttp
data_to_parse = """
interface Loopback0
description Router-id-loopback
ip address 192.168.0.113/24
!
interface Vlan778
description CPE_Acces_Vlan
ip address 2002::fd37/124
ip vrf CPE1
!
"""
ttp_template = """
interface {{ interface }}
ip address {{ ip }}/{{ mask }}
description {{ description }}
ip vrf {{ vrf }}
"""
# create parser object and parse data using template:
parser = ttp(data=data_to_parse, template=ttp_template)
parser.parse()
# print result in JSON format
results = parser.result(format='json')[0]
print(results)
# or in csv format
csv_results = parser.result(format='csv')[0]
print(csv_results)
パース前のデータをdata_to_parseに格納、ttp_templateでパースをします。
result表示の際にformatの指定もできるようです。
実行してみます。
ttp# python sample.py
# format JSON
[
[
{
"description": "Router-id-loopback",
"interface": "Loopback0",
"ip": "192.168.0.113",
"mask": "24"
},
{
"description": "CPE_Acces_Vlan",
"interface": "Vlan778",
"ip": "2002::fd37",
"mask": "124",
"vrf": "CPE1"
}
]
]
# format CSV
"description","interface","ip","mask","vrf"
"Router-id-loopback","Loopback0","192.168.0.113","24",""
"CPE_Acces_Vlan","Vlan778","2002::fd37","124","CPE1"
テンプレート部分に注目をすると使い方のイメージがつくと思います。 出力結果を抜き出して変数として登録するようなテンプレートを作成します。
# Templateファイル
interface {{ interface }}
ip address {{ ip }}/{{ mask }}
description {{ description }}
ip vrf {{ vrf }}
# パース前
interface Vlan778
description CPE_Acces_Vlan
ip address 2002::fd37/124
ip vrf CPE1
# パース後
{
"description": "CPE_Acces_Vlan",
"interface": "Vlan778",
"ip": "2002::fd37",
"mask": "124",
"vrf": "CPE1"
}
ある程度テンプレートも用意されているようです。 github.com
それでは他のテンプレートを参考にshow ip nat translationsのパーサーを作成します。 show ip nat translationsの結果は以下です。
Pro Inside global Inside local Outside local Outside global tcp 10.9.0.0:51776 10.1.0.2:51776 10.2.0.2:21 10.2.0.2:21 tcp 10.9.0.0:51778 10.1.0.2:51778 10.2.0.2:21 10.2.0.2:21 tcp 10.9.0.0:56384 10.1.0.2:56384 10.2.0.2:22 10.2.0.2:22 icmp 10.9.0.0:56111 10.1.0.2:56384 10.2.0.2:23 10.2.0.2:23 --- 10.9.0.0 10.1.0.2 --- --- --- 10.9.0.1 10.1.0.3 --- ---
Pro以外の要素はipかip:portのルールで表記されています。
IPのみで表記されているのがstatic natになる想定なので、テンプレートでマッチする条件を調整します。
# dynamic natのエントリー tcp 10.9.0.0:51776 10.1.0.2:51776 10.2.0.2:21 10.2.0.2:21 # static natのエントリー --- 10.9.0.0 10.1.0.2 --- ---
作成したテンプレートが以下です。
<group name="dynamic_nat">
{{ protocol }} {{ inside_global_ip }}:{{ inside_global_port }} {{ inside_local_ip }}:{{ inside_local_port }} {{ outside_local_ip }}:{{ outside_local_port }} {{ outside_global_ip }}:{{ outside_global_port }}
</group>
<group name="static_nat">
{{ protocol }} {{ inside_global_ip | re("(\S+)(?!.*:)") }} {{ inside_local_ip | re("(\S+)(?!.*:)") }} {{ outside_local_ip | re("(\S+)(?!.*:)") }} {{ outside_global_ip | re("(\S+)(?!.*:)") }}
</group>
:が表示結果に含まれていないことを条件にしました。
今回はanisbleでパースをします、templatesディレクトリにテンプレートファイルを配置します。
.
├── sample.yml
└── templates
└── ios_show_ip_nat_translations.ttp <--作成したパーサー
ansible-playbookを作成します。 ansible.utiles.cli_parseを利用します。 tempalte_pathはなぜかtemplates配下を見に行ってくれなかったのでplaybookのある階層からtemplateファイルを指定しています。
---
- name: Test Template Text Parser
hosts: localhost
gather_facts: false
tasks:
- name: parse
ansible.utils.cli_parse:
text: |-
Pro Inside global Inside local Outside local Outside global
tcp 10.9.0.0:51776 10.1.0.2:51776 10.2.0.2:21 10.2.0.2:21
tcp 10.9.0.0:51778 10.1.0.2:51778 10.2.0.2:21 10.2.0.2:21
tcp 10.9.0.0:56384 10.1.0.2:56384 10.2.0.2:22 10.2.0.2:22
icmp 10.9.0.0:56111 10.1.0.2:56384 10.2.0.2:23 10.2.0.2:23
--- 10.9.0.0 10.1.0.2 --- ---
--- 10.9.0.1 10.1.0.3 --- ---
parser:
name: ansible.netcommon.ttp
template_path: ./templates/ios_show_ip_nat_translations.ttp
register: res_parsed
- name: debug
debug:
msg: "{{ res_parsed.parsed[0][0] }}"
確認
ansible-playbookを実行して動作を確認します。
PLAY [Test Template Text Parser] *****************************************************************************************************************************
TASK [parse] *************************************************************************************************************************************************
[WARNING]: Use 'ansible.utils.ttp' for parser name instead of 'ansible.netcommon.ttp'. This feature will be removed from 'ansible.netcommon' collection in a
release after 2022-11-01
ok: [localhost]
TASK [debug] *************************************************************************************************************************************************
ok: [localhost] => {
"msg": {
"dynamic_nat": [
{
"inside_global_ip": "10.9.0.0",
"inside_global_port": "51776",
"inside_local_ip": "10.1.0.2",
"inside_local_port": "51776",
"outside_global_ip": "10.2.0.2",
"outside_global_port": "21",
"outside_local_ip": "10.2.0.2",
"outside_local_port": "21",
"protocol": "tcp"
},
{
"inside_global_ip": "10.9.0.0",
"inside_global_port": "51778",
"inside_local_ip": "10.1.0.2",
"inside_local_port": "51778",
"outside_global_ip": "10.2.0.2",
"outside_global_port": "21",
"outside_local_ip": "10.2.0.2",
"outside_local_port": "21",
"protocol": "tcp"
},
{
"inside_global_ip": "10.9.0.0",
"inside_global_port": "56384",
"inside_local_ip": "10.1.0.2",
"inside_local_port": "56384",
"outside_global_ip": "10.2.0.2",
"outside_global_port": "22",
"outside_local_ip": "10.2.0.2",
"outside_local_port": "22",
"protocol": "tcp"
},
{
"inside_global_ip": "10.9.0.0",
"inside_global_port": "56111",
"inside_local_ip": "10.1.0.2",
"inside_local_port": "56384",
"outside_global_ip": "10.2.0.2",
"outside_global_port": "23",
"outside_local_ip": "10.2.0.2",
"outside_local_port": "23",
"protocol": "icmp"
}
],
"static_nat": [
{
"inside_global_ip": "10.9.0.0",
"inside_local_ip": "10.1.0.2",
"outside_global_ip": "---",
"outside_local_ip": "---",
"protocol": "---"
},
{
"inside_global_ip": "10.9.0.1",
"inside_local_ip": "10.1.0.3",
"outside_global_ip": "---",
"outside_local_ip": "---",
"protocol": "---"
}
]
}
}
PLAY RECAP ***************************************************************************************************************************************************
localhost : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
パースされ、想定通りの結果を得ることができました。 dynamic_natグループはポートの情報も分けて取得することができています。
パース後の要素が2個以上ある場合は自動的にリストになるようです。要素が1個の場合リストにならないため、パース後に呼び出す際に注意が必要です。
テンプレートの書き方を調整すれば常にリストになるようにできるかもしれません。(※)
(※) 2021/12/16更新 解決方法が分かりました usage-automate.hatenablog.com
パース後の結果がけっこう深い階層にあったのがよくわからなかったですが、動作としては問題なかったです。res_parsed.parsed[0][0]
まとめ
TTP入門してみました。テンプレートがとても簡単に作れることができ、かなり便利です。
テンプレート自作ができるようになるとCLIの機器自動化でできることがかなり増えます、よければ参考にしてください。