Jupyter NotebookでCiscoルータコンフィグ生成ツールの作成

Jupyter Notebookでipywidgetとjinja2を使い、Ciscoルータ（IOS）のコンフィグ生成ツールを作成します。

ipywidgetはJupyter Notebook上にテキストボックスやボタンを配置できるUIライブラリです。jinja2はpythonのテンプレートエンジンライブラリです。今回はコンフィグ作成に必要なパラメータをテキストボックスに入力し、ボタンを押すとCiscoルータのコンフィグ作成するようなツールを作ってみます。

早速ですが、Pythonコードと実行結果から確認していきます。

# Pythonコード

```python:python
from IPython.display import display, clear_output
from jinja2 import Template
from ipywidgets import Text, BoundedIntText, Button

global_template_txt = '''
hostname {{t_hostname}}
no ip domain-lookup
'''

recursive_template_txt = '''
int giga {{t_int_num}}
 no switchport
 ip address {{t_ip_address}} {{t_mask}}
 no shutdown
'''

global_template = Template(global_template_txt)
recursive_template = Template(recursive_template_txt)

tbox_hostname = Text (value='L3-SW1', description='hostname')
tbox_interface_num = BoundedIntText(value=0, min=0, max=5, step=1, description='Interface数:')
button = Button(description='generate config')
    
display(tbox_hostname)
display(tbox_interface_num)
display(button)

interface_boxes = []

def on_button_clicked(b) :
    global interface_boxes
    global_config = global_template.render(t_hostname = tbox_hostname.value)
    print(global_config)
    
    for i in range(tbox_interface_num.value) :
        recursive_config = recursive_template.render(t_int_num=interface_boxes[i].children[0].value,
                                                     t_ip_address=interface_boxes[i].children[1].value, 
                                                     t_mask=interface_boxes[i].children[2].value)
        print(recursive_config)
    
def on_value_change(change) :  
    global interface_boxes
    clear_output()
    display(tbox_hostname)
    display(tbox_interface_num)
    
    interface_boxes = []
    for i in range(change['new']) :
        interface_elements = [
            Text(value='1/0/1', description='I/F num'),
            Text(value='10.10.10.1',  description='I/F ip add'), 
            Text(value='255.255.255.0',  description='I/F mask')]
        interface_boxes.append(ipywidgets.Box(children = interface_elements))
        display(interface_boxes[i])
    
    display(button)
    button.on_click(on_button_clicked)

tbox_interface_num.observe(on_value_change, names='value')
button.on_click(on_button_clicked)
```
# 実行結果

### 初期画面
![Screenshot from 2017-11-26 20-32-37.png](https://qiita-image-store.s3.amazonaws.com/0/180164/faed59c9-3309-38ee-f44b-1d5e3b3757be.png)

### Interface数変更時

![Screenshot from 2017-11-26 20-32-48.png](https://qiita-image-store.s3.amazonaws.com/0/180164/331ee295-4ba0-db12-e44c-3d0be1cc2804.png)


### generate configボタン押下時

![Screenshot from 2017-11-26 20-33-13.png](https://qiita-image-store.s3.amazonaws.com/0/180164/d1d406e1-bf15-8537-a4db-32992d06641b.png)


# コンフィグの説明

実行順に見ていきます。

### テンプレートの定義

```python:python
global_template_txt = '''
hostname {{t_hostname}}
no ip domain-lookup
'''

recursive_template_txt = '''
int giga {{t_int_num}}
 no switchport
 ip address {{t_ip_address}} {{t_mask}}
 no shutdown
'''

global_template = Template(global_template_txt)
recursive_template = Template(recursive_template_txt)
```
ここでCiscoコンフィグのテンプレートを定義しています。グローバルコンフィグがglobal_templateに対応し、インターフェースコンフィグがrecursive_templateに対応しています。{{}}で囲まれた部分が変数になっており、あとから値を埋め込む箇所です。

### 初期画面の出力

```python:python
tbox_hostname = Text (value='L3-SW1', description='hostname')
tbox_interface_num = BoundedIntText(value=0, min=0, max=5, step=1, description='Interface数:')
button = Button(description='generate config')
    
display(tbox_hostname)
display(tbox_interface_num)
display(button)
```

ここでホスト名とInterface数を入力するテキストボックス、及びコンフィグ作成ボタンを描画しています。

### ipywidgetのイベント検出

```python:python
tbox_interface_num.observe(on_value_change, names='value')
button.on_click(on_button_clicked)
```

一番最後の2行は、Interface数が変更された場合とボタンが押された場合に、それぞれon_value_change関数、on_button_clicked関数を呼び出すための記述です。

### on_value_cange関数

```python:python
def on_value_change(change) :  
    global interface_boxes
    clear_output()
    display(tbox_hostname)
    display(tbox_interface_num)
    
    interface_boxes = []
    for i in range(change['new']) :
        interface_elements = [
            Text(value='1/0/1', description='I/F num'),
            Text(value='10.10.10.1',  description='I/F ip add'), 
            Text(value='255.255.255.0',  description='I/F mask')]
        interface_boxes.append(ipywidgets.Box(children = interface_elements))
        display(interface_boxes[i])
    
    display(button)
    button.on_click(on_button_clicked)
```

on_value_change関数では、Interface数の変更に応じて画面に表示するインターフェースコンフィグ用のテキストボックス数を変えています。使うInterfaceが1つならテキストボックスも1行分、使うInterfaceが2つならテキストボックスも2行分出力させます。

### on_button_clicked関数

```python:python
def on_button_clicked(b) :
    global interface_boxes
    global_config = global_template.render(t_hostname = tbox_hostname.value)
    print(global_config)
    
    for i in range(tbox_interface_num.value) :
        recursive_config = recursive_template.render(t_int_num=interface_boxes[i].children[0].value,
                                                     t_ip_address=interface_boxes[i].children[1].value, 
                                                     t_mask=interface_boxes[i].children[2].value)
        print(recursive_config)
```

on_button_clicked関数では、コンフィグ作成・出力を行っています。コンフィグ作成ボタンが押された時に、global_templateは1回だけ、recursive_templateはInterface数分だけ繰り返し処理を行い、各テキストボックスの値をテンプレートの変数部分に埋め込んだあと、作成されたコンフィグを画面出力しています。

# さいごに

正直なところ、自分が使う分にはipywidgetよりもテキストベースで直接パラメータ入力したほうが効率的です。
どちらかというと、プロジェクトやチーム内で共用するなど、他人に使ってもらうことを想定しています。