PythonとJinja2でネットワークコンフィグ生成をテンプレート化

#これは何？
Ciscoのコンフィグ*を生成するPythonスクリプト。Jinja2を使ってパラメータを埋めるサンプル。何かと汎用性が高そうなので、自分用にメモ。変数を含むコンフィグテンプレートと、変数に対する値をまとめたCSVの二つを用意して、スクリプト実行すれば複数のコンフィグがファイル出力される。
*CiscoでなくてももちろんOK。

# Jinja2って何？

Python用のテンプレートエンジン。詳しくは、[本家を参照](http://jinja.pocoo.org/)。

[Jinja2 ネーミングの由来](http://jinja.pocoo.org/docs/2.9/faq/#why-is-it-called-jinja)
> The name Jinja was chosen because it’s the name of a Japanese temple and temple and template share a similar pronunciation. It is not named after the city in Uganda.

日本のテンプル（神社）は、テンプレートと発音が似ているから。
（ウガンダにJinjaという都市があるけど、それじゃないよ）

# 0. 準備
pipでJinja2を使えるようにしておく。
ファイルを三つと、ディレクトリを一つ用意（それぞれ、以下で解説）。

```bash
~ $pip3 list | grep Jinja2
Jinja2 (2.8)
~ $ls
build_templates.py	config_template.txt	configs			inventory.csv
~ $
```

![SS 2017-06-06 17.37.04.png](https://qiita-image-store.s3.amazonaws.com/0/157325/c5c5fe0e-3648-01a3-02ce-7968e38f7b9b.png)

1. configs ... 複数コンフィグファイルが生成されるディレクトリ。中身は空っぽ。
1. inventory.csv ... コンフィグテンプレートの変数ファイル。
1. config_template.txt ... コンフィグのテンプレートファイル。
1. build_templates.py ... メインのスクリプト。


# 1. configsディレクトリ
空っぽです。

```bash
~ $ls ./configs
~ $
```

# 2. inventory.csv
Excelで作るも良し、変数をスクリプトで管理するも良し。

```bash
~ $cat inventory.csv 
hostName,serialNumber,platformId,site,ipAddress,subnet,username,password,enablepass
Cat01,12345678901,WS-C2960X-48FPD-L,Tokyo,10.10.10.101,255.255.255.0,cisco,C1sco12345,C1$co
Cat02,22345678901,WS-C2960X-48FPD-L,Osaka,10.10.10.102,255.255.255.0,cisco,C1sco12345,C1$co
Cat03,32345678901,WS-C2960X-48FPD-L,Nagoya,10.10.10.103,255.255.255.0,cisco,C1sco12345,C1$co
```
![SS 2017-06-06 17.38.16.png](https://qiita-image-store.s3.amazonaws.com/0/157325/f3fdd099-4f8f-43b0-b073-56e995a1d62b.png)

# 3. config_template.txt
ネットワークエンジニアには見慣れたコンフィグですが、動的に補完したいパラメータを {{変数}} とします。AnsibleでもJinja2が使われているため、違和感がない方も多いかもしれません。

```bash
~ $cat config_template.txt 
hostname {{hostName}}
!
enable password {{enablepass}}
!
username {{username}} password 0 {{password}}
no aaa new-model
!
int vlan 1
ip address {{ipAddress}} {{subnet}}
!
end
```
※参考：[Ansible DocsのTemplating](https://docs.ansible.com/ansible/playbooks_templating.html)
>As already referenced in the variables section, Ansible uses Jinja2 templating to enable dynamic expressions and access to variables.

# 4. build_templates.py
build_templatesは、コンフィグテンプレート（config_template.txt）と変数のCSV（inventory.csv）を入力し、複数のコンフィグファイルを出力する関数のサンプル。

```python:build_templates.py
# -*- coding: utf-8 -*-
import jinja2
import csv

CONFIGS_DIR= "./configs/"
DEVICES="./inventory.csv"
TEMPLATE="./config_template.txt"

def build_templates(template_file, devices):

    templateLoader = jinja2.FileSystemLoader('./')
    templateEnv = jinja2.Environment(loader=templateLoader)
    template = templateEnv.get_template(template_file)

    f = open(devices, 'rt')
    try:
        reader = csv.DictReader(f)
        for dict_row in reader:
            outputText = template.render(dict_row)

            config_filename = CONFIGS_DIR + dict_row['hostName'] + '-' + dict_row['site'] + '-config'
            with open(config_filename, 'w') as config_file:
                config_file.write(outputText)
            print("コンフィグ生成: %s" % config_filename)

    finally:
        f.close()

if __name__ == "__main__":
    build_templates(TEMPLATE, DEVICES)
```
### ポイント
- CSVファイルから行ごと（デバイスごと）にPythonディクショナリで読込（csv.DictReader）
- テンプレートに変数補完（template.render(dict_row)）して出力
- 装置の数だけループ（for dict_row in reader）
- Jinja2の書き方は、分けて書いていますが以下のように一つでも書いても良さそうです

※引用元：[Load a Jinja2 template from the filesystem and render it](http://matthiaseisen.com/pp/patterns/p0198/)

```python:引用
import os
import jinja2


def render(tpl_path, context):
    path, filename = os.path.split(tpl_path)
    return jinja2.Environment(
        loader=jinja2.FileSystemLoader(path or './')
    ).get_template(filename).render(context)
```

# 5. 実行

```bash
~ $python3 build_templates.py 
コンフィグ生成: ./configs/Cat01-Tokyo-config
コンフィグ生成: ./configs/Cat02-Osaka-config
コンフィグ生成: ./configs/Cat03-Nagoya-config
~ $
~ $ls ./configs/
Cat01-Tokyo-config	Cat02-Osaka-config	Cat03-Nagoya-config
~ $
~ $cat ./configs/Cat01-Tokyo-config 
hostname Cat01
!
enable password C1$co
!
username cisco password 0 C1sco12345
no aaa new-model
!
int vlan 1
ip address 10.10.10.101 255.255.255.0
!
end
~ $
~ $
~ $cat ./configs/Cat02-Osaka-config 
hostname Cat02
!
enable password C1$co
!
username cisco password 0 C1sco12345
no aaa new-model
!
int vlan 1
ip address 10.10.10.102 255.255.255.0
!
end
~ $
~ $
```
![SS 2017-06-06 19.20.03.png](https://qiita-image-store.s3.amazonaws.com/0/157325/9d247db8-1d75-8a4c-a606-915d2d430fbe.png)

いい感じですね。他にも使えそうです。

# 参考
[Usage of csv.DictReader](https://courses.cs.washington.edu/courses/cse140/13wi/csv-parsing.html) ... CSVパースの解説がわかりやすい
[CiscoDevNet/apic-em-samples-aradford](https://github.com/CiscoDevNet/apic-em-samples-aradford/tree/master/sample_code/PnP) ... ごちゃごちゃしてるけど、色々入っている