python jinja2(part1) cisco機器のconfig作成に便利なjinja2.Environment()設定
テンプレートエンジンって便利?
これまで、ネットワーク機器のコンフィグを自動生成するような目的で、テンプレートエンジンを使うのは「大げさ」だと思っていた・・・(簡単なtemplateの利用方法:python標準のstring.Templateを用いたcisco config作成スクリプト - 技術メモ)
- WEBの場合はデザイナ、プログラマの分業に役立つだろうけど、コンフィグ作成程度で分業はないよね。
- そもそも、テンプレートエンジン独自の命令を覚えるのが面倒。慣れた言語の標準ベースで使える諸機能(ヒアドキュメント、文字列置換等)で十分。
とはいえ、自動生成ツールを作るたび、書き捨てスクリプトの山が増えていくだけの生産性の悪い現状は改善したいと思い、jinja2を試してみた。
試行錯誤
jinja2をインストールして、各種のjinja2入門サイトを参考にしながら"素"のまま使ってみたが、
- ブラケットを多用する記法はciscoのコンフィグに馴染まないため、テンプレートの可読性が悪い(カーリーブラケットのJUNOSやPAN-OSなら気にならないのだろうけど)。
- jinja2が勝手に行う、改行と空白制御をコントロールするのが面倒(ハイフン付け)で、結果、タイピング量も多くなりがち。
等々、残念な第一印象・・・。
腰を据えて、jinja2の公式ドキュメント(http://jinja.pocoo.org/docs/dev/)を読み、試行錯誤した結果、
とすれば、テンプレートをスッキリと仕上げることが可能なことが分かった。
同時に、pythonらしいふるまいをしてくれる、いくつかの便利な機能も発見。
以上を踏まえて・・・
Catalyst3560(8ポートモデル)のL1~L2設定をjinja2で作ってみた。「データをテンプレートに当てはめただけ」の試験にならないよう、jinja2の色々な機能を試せるように、以下の制約を行う。
- default設定がコンフィグに現れない、IOSのコンフィグ表示機能をjinja2で実装すること(例えば、vlan 1やspeed/duplexのauto設定が表示されないこと)
- Gi0/1をアップリンクと想定し、jinja2でアクセス側のVlan設定を読み取り、アップリンクのtrunk情報を自動生成すること
jinja2のみでは手を焼きそうな部分は、pythonの自作関数(reorder_vlan, separate_vlanが該当)をjinja2のfilter登録で対応した。
以下、jinja2利用の改善策を適用したお試しスクリプト(第一弾)。
#!/usr/bin/env python3 import re import ruamel.yaml as yaml import jinja2 def reorder_vlan(csv): """ convert disorderd vlans(csv) into ios configuration format. input : " 100 , 2, 3 - 5, 4 , 3,, " return: "2-5,100" """ values = split_vlan(csv).split(',') csv = values[0] for i in range(1, len(values)): csv += '-' if int(values[i]) - int(values[i - 1]) == 1 else ',' csv += values[i] return re.sub(r"-(?:\d+-){1,}", '-', csv) def split_vlan(csv): """ split disorderd vlans(csv), and uniq|sort them out. input : " 100 , 2, 3 - 5, 4 , 3,, " return: "2,3,4,5,100" """ re_number = re.compile(r"^(?:\s*(\d+)\s*|\s*(\d+)\s*-\s*(\d+)\s*)$") values = set() for val in csv.split(','): mo = re_number.search(val) if mo: if mo.group(1): values.add(int(mo.group(1))) else: values.update(range(int(mo.group(2)), int(mo.group(3)) + 1)) return ','.join(map(str, sorted(values))) yaml_data = """ FastEthernet0/1: vlan: 1 speed: auto duplex: auto FastEthernet0/2: description: "" vlan: 1 duplex: "" FastEthernet0/3: description: port0/3 vlan: 2 speed: 10 duplex: full FastEthernet0/4: description: port0/4 vlan: 102 speed: 10 duplex: half FastEthernet0/5: description: port0/5 vlan: 101 speed: 10 duplex: half FastEthernet0/6: description: port0/6 vlan: 100 speed: 10 duplex: half FastEthernet0/7: description: port0/7 vlan: 10 speed: auto duplex: auto FastEthernet0/8: description: port0/8 vlan: 3 speed: 100 duplex: full """ dict_ = yaml.load(yaml_data, Loader = yaml.RoundTripLoader) j2_template = """\ {# {{ intf_phy | pprint }} #} ! ! interface configuration(C3560-8PC-S) ! vtp mode off ! {% set vlan_csv = intf_phy.values() | map(attribute = 'vlan') | join(',') | split_vlan %} vlan {{ vlan_csv.lstrip('1,') | reorder_vlan }} ! % for k, v in intf_phy.items(): interface {{ k }} % if v.description: description {{ v.description }} % endif % if v.vlan and v.vlan != 1: switchport access vlan {{ v.vlan }} % endif switchport mode access % if v.speed and v.speed != "auto": speed {{ v.speed }} % endif % if v.duplex and v.duplex != "auto": duplex {{ v.duplex }} % endif ! % endfor interface GigabitEthernet0/1 description uplink switchport trunk encapsulation dot1q switchport trunk allowed vlan {{ vlan_csv | reorder_vlan }} switchport mode trunk !""" j2_env = jinja2.Environment( line_statement_prefix = '%', line_comment_prefix = '%%', trim_blocks = True, keep_trailing_newline = True, extensions = ['jinja2.ext.do', 'jinja2.ext.loopcontrols'], ) j2_env.filters.update({ 'reorder_vlan' : reorder_vlan, 'split_vlan' : split_vlan, }) t = j2_env.from_string(j2_template) print(t.render(intf_phy = dict_))
以下、実行結果。
! ! interface configuration(C3560-8PC-S) ! vtp mode off ! vlan 2-3,10,100-102 ! interface FastEthernet0/1 switchport mode access ! interface FastEthernet0/2 switchport mode access ! interface FastEthernet0/3 description port0/3 switchport access vlan 2 switchport mode access speed 10 duplex full ! interface FastEthernet0/4 description port0/4 switchport access vlan 102 switchport mode access speed 10 duplex half ! interface FastEthernet0/5 description port0/5 switchport access vlan 101 switchport mode access speed 10 duplex half ! interface FastEthernet0/6 description port0/6 switchport access vlan 100 switchport mode access speed 10 duplex half ! interface FastEthernet0/7 description port0/7 switchport access vlan 10 switchport mode access ! interface FastEthernet0/8 description port0/8 switchport access vlan 3 switchport mode access speed 100 duplex full ! interface GigabitEthernet0/1 description uplink switchport trunk encapsulation dot1q switchport trunk allowed vlan 1-3,10,100-102 switchport mode trunk
*1:jinja2を採用しているansibleでもtrim_blocksを有効化しているらしい