paloma blog

NWエンジニアやってます。主に自宅環境のお遊びを書きます。Pythonもちょっと。タイトルは好きなカクテルから。

pythonのjinja2で1:Nの可変パラメータをテンプレートに渡した時のメモ

NW機器のconfigを作るとき別のconfigを置換するのもいいんですが、jinja2でテンプレートから読み込んで作るという方法を覚えてから精度とスピードが格段に上がり、以前にはもう戻れません。

csvから読み込むようにするとcsvだけ管理すればいいので楽チンです。

今回いわゆる1:1と1:Nの値が複合したパラメータをレンダリングが出来たのでメモしておきます。
職場で使っているのをそのまま書くわけには行きませんので仮値での再現です。

ケース

今回はBIG-IP LTMのプール作成の際に本件に直面しました。
本番、テスト環境といったバーチャルサーバをいろいろ立てるけどそれぞれメンバー数が違ったりね。
BIG-IPはGUIでポチポチ設定するのは面倒くさいので、なるべくコマンドで流すようにしています。

メンバーの様な可変になる値もcsvから読み込んで、テンプレートからこういう出力を作りたいわけです。

  • poolA
    • node1:80
    • node2:80
    • node3:80
  • poolA_test
    • node4:80
  • poolB
    • node5:80

これを1つのテンプレートから起こしたい。

やりたいことの整理

LBの設計次第ですが、今回のケースは以下です。

  • 固定のパラメータ作成
    • プール名
    • バランシング方式
    • ポート
    • モニター
  • 可変のパラメータ作成
    • プールメンバー
  • 上記をまとめたパラメータをテンプレートに渡す

固定の方は完全に1:1書き方になるので、この2つのcsvは分けないといけませんね。

最終形を整理する

最終形を最初に想定できると書くべき内容が想像しやすいのでいいですね。

csvから読み込むと辞書型で格納されます。
テンプレートに送る段階で各値の{key1: value1, key2: value2 ... }が格納されていればOKということになりますね。

jinja2触りたての頃はいろんな変数がごっちゃになっていましたがようやく見るべきところがわかってきました。

やったこと

  1. 固定パラメータ用の変数作成
  2. 可変パラメータ用の変数作成
  3. 可変パラメータ用変数を加工
  4. テンプレートに送る用の変数にマージ
  5. ループさせてrenderに送る

各ファイル

  • createpool.py

処理の本体です。

from jinja2 import Environment, FileSystemLoader
import csv

env = Environment(loader=FileSystemLoader('./', encoding='utf8'))
template = env.get_template('temple.txt')

# 1. 固定パラメータ用の変数作成
with open('poolsetting.csv', 'r') as f:
    data = csv.DictReader(f)
    settings = [ x for x in data ]

# 2. 可変パラメータ用の変数作成
with open('poolmember.csv', 'r') as f:
    data = csv.DictReader(f)
    members = [x for x in data]

poolmember = {}

# 3. 可変パラメータ用変数を加工
# キーとリストを格納
for k in members[0]:
    poolmember[k] = []

# 各キーに中身を追加
for m in members:
    for k,v in m.items():
       poolmember[k].append(v)

# 4. テンプレートに送る用の変数にマージ       
for s in settings:
    for k,v in poolmember.items():
        # キーが同じだったらマージ
        if s['poolname'] == k:
            s.update({k:v})
            # テンプレートに渡すようにリネーム
            s['member'] = s.pop(k)

# 5. ループさせてrenderに送る
for param in settings:
    print(template.render(param))

  • poolsetting.csv

固定パラメータ用のファイル

poolname,port,monitor
POOLA,80,tcp
POOLA_test,80,tcp
POOLB,8080,tcp
  • poolmember.csv

可変パラメータ用のファイル

プール名がキーになるので書き方が先ほどと逆になります。

POOLA,POOLA_test,POOLB
node1,node4,node6
node2,node5
node3
  • temple.txt

値を渡すテンプレートです。
私はltmのshellからそのまま入力しますので、tmshコマンドから書いてます。
投入パラメータが見やすいように改行してます。

### create {{ poolname }}

tmsh create ltm pool {{ poolname }} { \
  load-balancing-mode least-connections-members \
{%- for node in member %}
{%- if node != None %}
  members add { \
    {{ node }}:{{ port }} \
  } \
{%- endif %}
{%- endfor %}
  monitor {{ monitor }} \
}

debug(手動で)

各処理を見ていこうと思いましたが、本件が長くなってしまうので次回に切り出したいと思います。

詳細はこちらに記載しました。
pythonのjinja2で1:Nの可変パラメータをテンプレートに渡した時のメモ debug編 - paloma blog

最終形の出力だけ書きます。

>>> settings[0]
{'poolname': 'POOL1', 'port': '80', 'monitor': 'tcp', 'member': ['node1', 'node2', 'node3']}

想定通りの形になりました。
settingsをループで回して各キーがテンプレートに渡りますね。
member内の値はtemple.txt内のループで更に渡されると。

実行

実行します。

(python3) masashi@PC-ubuntu:~/python3$ python createpool.py 
### create POOLA

tmsh create ltm pool POOLA { \
  load-balancing-mode least-connections-members \
  members add { \
    node1:80 \
  } \
  members add { \
    node2:80 \
  } \
  members add { \
    node3:80 \
  } \
  monitor tcp \
}

### create POOLA_test

tmsh create ltm pool POOLA_test { \
  load-balancing-mode least-connections-members \
  members add { \
    node4:80 \
  } \
  members add { \
    node5:80 \
  } \
  monitor tcp \
}

### create POOLB

tmsh create ltm pool POOLB { \
  load-balancing-mode least-connections-members \
  members add { \
    node6:8080 \
  } \
  monitor tcp \
}

OK!
それぞれのプールでメンバーがバラけてます。
これをファイルにリダイレクトすれば投入Configの出来上がりです。
リダイレクトはshellでもpython内に書いてもどちらでもいいですね。

まとめ

jijna2を使ってパラメータを1:1で渡す分にはそれほど難しくないのですが、今回の様な可変の値を入れ込もうとするとアイデアが浮かばず頓挫していました。
ある日パラメータをマージするという知見(辞書型のupdate)を得ましたので、今回望む結果を得ることができました。

ciscoACLでも使えそうですが、テンプレファイルがブレースだらけで汚くなりそうなので使わないでおこうと思いますw

参考サイト

Jinja2の基本的な使い方 - Qiita

おまけ

調べたらbuilt-inのstring.Templateという似たクラスがあるようですね。
こっち先に出会っていたらこれで試していたかもな。

python標準のstring.Templateを用いたcisco config作成スクリプト - 技術メモ