NTC-TemplatesにテンプレートのPRを出すまで

`TextFSM`というパーサ向けにネットワーク機器のshowコマンドの結果をパースするテンプレート集`NTC-Templates`があります。新規テンプレートを数件PR出してマージされたので、PRを出す流れとテンプレート作成時のポイントをまとめました。[基本は開発ガイドライン](https://ntc-templates.readthedocs.io/en/latest/dev/extending/)に従って開発を行います。

https://github.com/networktocode/ntc-templates

# 環境準備

Pythonのパッケージ管理ソフトの`poetry`と`Docker`を予めインストールしておきます。

https://python-poetry.org/docs/#installation

https://docs.docker.com/engine/install/

Ubuntuの場合はバージョンが古いもののaptでインストール可能です。

```
sudo apt install python3-poetry
```

# 開発の流れ

## リポジトリ準備

リポジトリをforkして`git clone`後、`master`からブランチを切ります。

## TextFSMテンプレート

`ntc_templates/templates`フォルダ内に`{ベンダー名}_{OS名}_{コマンド名}.textfsm`というファイル名でTextFSMテンプレートを作成します。

ファイル名の例
- `cisco_ios_show_ip_route.textfsm`：Cisco IOSの`show ip route`コマンド
- `cisco_nxos_show_interface_brief.textfsm`：Cisco NXOSの`show interface bried`コマンド
- `juniper_junos_show_vlans.textfsm`：Juniper junosの`show vlans`こまｎ

下記は私が作成したテンプレートの例です。
```fsas_sir_show_vlan.textfsm
Value Required VLAN_ID (\d+)
Value VLAN_NAME (\S+)
Value List INTERFACES (\S+\s+\d+(\s+\d+)?)

Start
  ^VID\s+Interface\s+Tag\s+Type\s+Description
  ^---- -> VLANS
  ^Total\s+Count
  ^\s*$$
  ^. -> Error

VLANS
  ^\d+ -> Continue.Record
  ^${VLAN_ID}\s+${INTERFACES}\s+\S+\s+\S+\s+${VLAN_NAME}\s*$$
  ^\s+${INTERFACES}\s+\S+\s*$$
  ^\s*$$ -> Start
  ^. -> Error
```

## indexファイル

`ntc_templates/templates/index`ファイルがテンプレートの一覧となっており、他のテンプレートに倣って記述します。記載順序が決められおり、守らないとテストでNGとなります。

- ベンダー名、OS名のアルファベット順
- テンプレート名の長い順 (長さが等しい場合はアルファベット順)
- OS間に空行を入れる

```text:index (抜粋)
fortinet_execute_ping.textfsm, .*, fortinet, exec[[ute]] ping
fortinet_execute_time.textfsm, .*, fortinet, exec[[ute]] ti[[me]]

fsas_sir_show_system_information.textfsm, .*, fsas_sir, sh[[ow]] sys[[tem]] i[[nformation]]
fsas_sir_show_ip_ospf_neighbor.textfsm, .*, fsas_sir, sh[[ow]] ip ospf nei[[ghbor]]
fsas_sir_show_ip_route.textfsm, .*, fsas_sir, sh[[ow]] ip ro[[ute]]
fsas_sir_show_ether.textfsm, .*, fsas_sir, sh[[ow]] eth[[er]]
fsas_sir_show_vlan.textfsm, .*, fsas_sir, sh[[ow]] vl[[an]]

hp_comware_display_lldp_neighbor-information_verbose.textfsm, .*, hp_comware, di[[splay]] ll[[dp]] n[[eighbor-information]] v[[erbose]]
hp_comware_display_lldp_neighbor-information_list.textfsm, .*, hp_comware, di[[splay]] ll[[dp]] n[[eighbor-information]] l[[ist]]
hp_comware_display_ip_vpn-instance_instance-name.textfsm, .*, hp_comware, di[[splay]] ip vpn[[-instance]] in[[instance-name]]
```

## テストデータ

開発対象のコマンドの出力結果を`tests/{ベンダー名}_{OS名}/コマンド名/`フォルダ配下に`.raw`ファイルを作成し記載します。`.raw`ファイルのファイル名はテンプレートのファイル名をベースにしていることが多いです。`.raw`ファイルは機種やバージョンによって複数用意しても大丈夫です。実行コマンドの行は除いて出力結果のみを記載します。

```fsas_sir_show_vlan.raw

VID  Interface          Tag           Type     Description
---- ------------------ ------------- -------- --------------------------------
1    ether 1 1          untagged      port     default
2    ether 2 1          untagged      port     lan2
     ether 2 2          untagged
     ether 2 3          untagged
501  pseudo-ether 1     untagged      port     wwan
1005 ether 2 1          dot1q-tagged  port     v1005

Total Count :   4
```

## テンプレート変換結果

テストデータをテンプレートでパースした結果をYAMLファイルとして保存する必要があります。YAMLファイルの作成には開発用コマンドを使用します。

事前に環境を準備します。
```bash
# 仮想環境の作成と開発に必要なパッケージのインストール
poetry install
# 仮想環境に入る
poetry shell
# テスト用Dockerコンテナの作成
invoke build
```

下記コマンドでYAMLファイルを生成します。
```bash
# 単一ファイルから生成
invoke gen-yaml-file <.rawファイル>

# フォルダを指定して一括生成
invoke gen-yaml-folder  <フォルダ>
```

下記のようなYAMLファイルが生成されます。
```yaml:fsas_sir_show_vlan.yml
---
parsed_sample:
  - interfaces:
      - "ether 1 1"
    vlan_id: "1"
    vlan_name: "default"
  - interfaces:
      - "ether 2 1"
      - "ether 2 2"
      - "ether 2 3"
    vlan_id: "2"
    vlan_name: "lan2"
  - interfaces:
      - "pseudo-ether 1"
    vlan_id: "501"
    vlan_name: "wwan"
  - interfaces:
      - "ether 2 1"
    vlan_id: "1005"
    vlan_name: "v1005"
```

## テスト

前項のYAMLファイルと同様に`poetry shell`で仮想環境に入った上で下記コマンドを実行します。
```
invoke tests
```
上記コマンドでは全項目が実行されますが、下記のコマンドで特定のテストのみを実施可能です。
>bandit            : Run bandit to validate basic static code security analysis.
  black             : Run black to check that Python files adhere to its style standards.
  coverage           : Run the coverage report against pytest.
  flake8             : Run flake8 to check that Python files adhere to its style standards.
  mypy               : Run mypy to validate typing-hints.
  pylint             : Run pylint code analysis.
  pydocstyle         : Run pydocstyle to validate docstring formatting adheres to NTC defined standards.
  pytest             : Run pytest for the specified name and Python version.
  tests              : Run all tests for the specified name and Python version.
  yamllint           : Run yamllint to validate formatting adheres to NTC defined YAML standards.

結果に問題なければOKです。

## PRを出す

プルリクを出して承認されるのを待ちましょう。新規テンプレートの場合は1テンプレートごとにPRを出してくださいとのことです。

# テンプレート作成のポイント

私がPRで指摘された点をメインにポイントをまとめました。

## 1.空白の読み込みは`\s+`

コマンドの出力結果の空白を読み込む場合は長さに関わらず`\s+`を使用しましょう。
スペース1文字分しか空いていないときでも`\s+`がよいようです。

```plaintext:コマンド出力結果例
Current-time : Tue Dec 31 21:29:06 2024
Startup-time : Tue Dec 31 11:29:03 2024
System : Si-R G120
```

```plaintext:テンプレート例
Start
  ^Current-time\s+:\s+${CURRENT_TIME}\s*$$
  ^Startup-time\s+:\s+${STARTUP_TIME}\s*$$
  ^System\s+:\s+${SYSTEM}\s*$$
```

## 2.正規表現はシンプルに

正規表現はなるべくシンプルにしましょう。例えば`cisco_ios_show_ip_route`ではIPアドレスの正規表現が`\d{1,3}.\d{1,3}.\d{1,3}.\d{1,3}`となっていますが、私が作成したテンプレートでは`\S+`を使用するようにレビューを受けました。

## 3.フィールド名は既存テンプレートの真似をしよう

フィールド名は機種によって差異がありますが、標準化が徐々に進められており、他機種のテンプレートと揃えることが望ましいです。
下記の機器を参考に合わせていくと良いでしょう。
- cisco_ios
- juniper_junos
- arista_eos