Cisco機器のshow inventory結果を表にする

# やること
Cisco機器の型番/シリアル番号を整理することが多いので、show inventoryの結果をcsvに出力するコードを書きました。
使うのはPythonモジュールの[TextFSM](https://github.com/google/textfsm)と、そのテンプレート集である[ntc-templates](https://github.com/networktocode/ntc-templates)です。

#環境
Python 3.7.3
Anaconda 4.8.1
Windows10

#フォルダ構成
入力フォルダ、出力フォルダ、templateフォルダを.pyと同ディレクトリに用意します。

```python:フォルダ構成
.
│  get_inventory.py
│
├─input
│      catalyst.cfg
│      nexus.cfg
│
├─output
│
└─template
        cisco_ios_show_inventory.template
        cisco_nxos_show_inventory.template
        cisco_nxos_show_inventory_r2.template
```

#入力ファイル
WS-C2960X-24TS-L の『show inventory raw』と、N3K-C3172PQ-10GE の『show inventory all』の出力結果を入力用に用意しました。

<details><summary>Catalystのコマンド結果(入力用)</summary><div>

```rb
catalyst#sh inv raw
NAME: "C29xx Stacking", DESCR: "Catalyst C29xx Switch Stack"
PID:                   , VID:      , SN:            

NAME: "1", DESCR: "WS-C2960X-24TS-L"
PID: WS-C2960X-24TS-L  , VID: V05  , SN: FCW2115BXXX

NAME: "Switch 1 - Fan Container", DESCR: "Fan Container"
PID:                   , VID:      , SN:            

NAME: "Switch 1 - Power Supply Container", DESCR: "Power Supply Container"
PID:                   , VID:      , SN:            

NAME: "Switch 1 - RPS Container", DESCR: "RPS Container"
PID:                   , VID:      , SN:            

NAME: "Switch 1 - WS-C2960X-24TS-L - Power Supply 0", DESCR: "Switch 1 - WS-C2960X-24TS-L - Power Supply 0"
PID:                   , VID:      , SN: LIT21123XXX

NAME: "Switch 1 - WS-C2960X-24TS-L - Fan 0", DESCR: "Switch 1 - WS-C2960X-24TS-L - Fan 0"
PID:                   , VID:      , SN:            

NAME: "Switch 1 - WS-C2960X-24TS-L - Sensor 0", DESCR: "Switch 1 - WS-C2960X-24TS-L - Sensor 0"
PID:                   , VID:      , SN:            

NAME: "Switch 1 - WS-C2960X-24TS-L - Fixed Module 0", DESCR: "Switch 1 - WS-C2960X-24TS-L - Fixed Module 0"
PID:                   , VID:      , SN:            

NAME: "GigabitEthernet1/0/25 Container", DESCR: "GigabitEthernet Container"
PID:                   , VID:      , SN:            

NAME: "GigabitEthernet1/0/26 Container", DESCR: "GigabitEthernet Container"
PID:                   , VID:      , SN:            

NAME: "GigabitEthernet1/0/27 Container", DESCR: "GigabitEthernet Container"
PID:                   , VID:      , SN:            

NAME: "GigabitEthernet1/0/27", DESCR: "1000BaseSX SFP"
PID: GLC-SX-MMD          , VID: V01  , SN: FNS17070XXX     

NAME: "Gi1/0/27 Module Temperature Sensor", DESCR: "GigabitEthernet1/0/27 Module Temperature Sensor"
PID:                   , VID:      , SN:            

NAME: "Gi1/0/27 Supply Voltage Sensor", DESCR: "GigabitEthernet1/0/27 Supply Voltage Sensor"
PID:                   , VID:      , SN:            

NAME: "Gi1/0/27 Bias Current Sensor", DESCR: "GigabitEthernet1/0/27 Bias Current Sensor"
PID:                   , VID:      , SN:            

NAME: "Gi1/0/27 Transmit Power Sensor", DESCR: "GigabitEthernet1/0/27 Transmit Power Sensor"
PID:                   , VID:      , SN:            

NAME: "Gi1/0/27 Receive Power Sensor", DESCR: "GigabitEthernet1/0/27 Receive Power Sensor"
PID:                   , VID:      , SN:            

NAME: "GigabitEthernet1/0/28 Container", DESCR: "GigabitEthernet Container"
PID:                   , VID:      , SN:            

NAME: "GigabitEthernet1/0/28", DESCR: "1000BaseSX SFP"
PID: GLC-SX-MMD          , VID: V01  , SN: FNS17070XXX     

NAME: "Gi1/0/28 Module Temperature Sensor", DESCR: "GigabitEthernet1/0/28 Module Temperature Sensor"
PID:                   , VID:      , SN:            

NAME: "Gi1/0/28 Supply Voltage Sensor", DESCR: "GigabitEthernet1/0/28 Supply Voltage Sensor"
PID:                   , VID:      , SN:            

NAME: "Gi1/0/28 Bias Current Sensor", DESCR: "GigabitEthernet1/0/28 Bias Current Sensor"
PID:                   , VID:      , SN:            

NAME: "Gi1/0/28 Transmit Power Sensor", DESCR: "GigabitEthernet1/0/28 Transmit Power Sensor"
PID:                   , VID:      , SN:            

NAME: "Gi1/0/28 Receive Power Sensor", DESCR: "GigabitEthernet1/0/28 Receive Power Sensor"
PID:                   , VID:      , SN:            

NAME: "StackPort1", DESCR: "StackPort1"
PID:                   , VID:      , SN:            

NAME: "GigabitEthernet1/0/1", DESCR: "GigabitEthernet1/0/1"
PID:                   , VID:      , SN:            

NAME: "GigabitEthernet1/0/2", DESCR: "GigabitEthernet1/0/2"
PID:                   , VID:      , SN:            

NAME: "GigabitEthernet1/0/3", DESCR: "GigabitEthernet1/0/3"
PID:                   , VID:      , SN:            

NAME: "GigabitEthernet1/0/4", DESCR: "GigabitEthernet1/0/4"
PID:                   , VID:      , SN:            

NAME: "GigabitEthernet1/0/5", DESCR: "GigabitEthernet1/0/5"
PID:                   , VID:      , SN:            

NAME: "GigabitEthernet1/0/6", DESCR: "GigabitEthernet1/0/6"
PID:                   , VID:      , SN:            

NAME: "GigabitEthernet1/0/7", DESCR: "GigabitEthernet1/0/7"
PID:                   , VID:      , SN:            

NAME: "GigabitEthernet1/0/8", DESCR: "GigabitEthernet1/0/8"
PID:                   , VID:      , SN:            

NAME: "GigabitEthernet1/0/9", DESCR: "GigabitEthernet1/0/9"
PID:                   , VID:      , SN:            

NAME: "GigabitEthernet1/0/10", DESCR: "GigabitEthernet1/0/10"
PID:                   , VID:      , SN:            

NAME: "GigabitEthernet1/0/11", DESCR: "GigabitEthernet1/0/11"
PID:                   , VID:      , SN:            

NAME: "GigabitEthernet1/0/12", DESCR: "GigabitEthernet1/0/12"
PID:                   , VID:      , SN:            

NAME: "GigabitEthernet1/0/13", DESCR: "GigabitEthernet1/0/13"
PID:                   , VID:      , SN:            

NAME: "GigabitEthernet1/0/14", DESCR: "GigabitEthernet1/0/14"
PID:                   , VID:      , SN:            

NAME: "GigabitEthernet1/0/15", DESCR: "GigabitEthernet1/0/15"
PID:                   , VID:      , SN:            

NAME: "GigabitEthernet1/0/16", DESCR: "GigabitEthernet1/0/16"
PID:                   , VID:      , SN:            

NAME: "GigabitEthernet1/0/17", DESCR: "GigabitEthernet1/0/17"
PID:                   , VID:      , SN:            

NAME: "GigabitEthernet1/0/18", DESCR: "GigabitEthernet1/0/18"
PID:                   , VID:      , SN:            

NAME: "GigabitEthernet1/0/19", DESCR: "GigabitEthernet1/0/19"
PID:                   , VID:      , SN:            

NAME: "GigabitEthernet1/0/20", DESCR: "GigabitEthernet1/0/20"
PID:                   , VID:      , SN:            

NAME: "GigabitEthernet1/0/21", DESCR: "GigabitEthernet1/0/21"
PID:                   , VID:      , SN:            

NAME: "GigabitEthernet1/0/22", DESCR: "GigabitEthernet1/0/22"
PID:                   , VID:      , SN:            

NAME: "GigabitEthernet1/0/23", DESCR: "GigabitEthernet1/0/23"
PID:                   , VID:      , SN:            

NAME: "GigabitEthernet1/0/24", DESCR: "GigabitEthernet1/0/24"
PID:                   , VID:      , SN:            


catalyst#
```

</div></details>
<details><summary>Nexusのコマンド結果(入力用)</summary><div>

```rb
nexus# sh inv all
NAME: "Chassis",  DESCR: "Nexus 3172 Chassis"                    
PID: N3K-C3172PQ-10GE    ,  VID: V02 ,  SN: FOC2325RXXX          

NAME: "Slot 1",  DESCR: "48x10GE + 6x40G Supervisor"            
PID: N3K-C3172PQ-10GE    ,  VID: V02 ,  SN: FOC23248XXX          

NAME: "Power Supply 1",  DESCR: "Nexus 3172 Chassis Power Supply"       
PID: N2200-PAC-400W-B    ,  VID: V03 ,  SN: LIT23192XXX          

NAME: "Power Supply 2",  DESCR: "Nexus 3172 Chassis Power Supply"       
PID: N2200-PAC-400W-B    ,  VID: V03 ,  SN: LIT23193XXX          

NAME: "Fan 1",  DESCR: "Nexus 3172 Chassis Fan Module"         
PID: NXA-FAN-30CFM-B     ,  VID: V00 ,  SN: N/A                  

NAME: "Fan 2",  DESCR: "Nexus 3172 Chassis Fan Module"         
PID: NXA-FAN-30CFM-B     ,  VID: V00 ,  SN: N/A                  

NAME: "Fan 3",  DESCR: "Nexus 3172 Chassis Fan Module"         
PID: NXA-FAN-30CFM-B     ,  VID: V00 ,  SN: N/A                  

NAME: "Fan 4",  DESCR: "Nexus 3172 Chassis Fan Module"         
PID: NXA-FAN-30CFM-B     ,  VID: V00 ,  SN: N/A                  
 
NAME: Ethernet1/47,  DESCR: CISCO-AVAGO                             
PID: 10Gbase-SR          ,  VID: SFBR-709SMZ-CS1 ,  SN: AVD23059XXX          

NAME: Ethernet1/48,  DESCR: CISCO-OPLINK                            
PID: 10Gbase-SR          ,  VID: TPP4XGDS0CCISE2G,  SN: OPM23050XXX          

nexus# 

```

</div></details>

#Templatesファイル
Catalyst、Nexusともに『show inventory』のTemplatesを使用。[^1]
Catalystはそのままで問題なさそうでしたが、Nexusは少し編集しました。
編集後のTemplatesは末尾『_r2』としています。



<details><summary>cisco_ios_show_inventory.template</summary><div>

```rb:cisco_ios_show_inventory.template
Value NAME (.*)
Value DESCR (.*)
Value PID (([\S+]+|.*))
Value VID (.*)
Value SN ([\w+\d+]+)

Start
  ^NAME:\s+"${NAME}",\s+DESCR:\s+"${DESCR}"
  ^PID:\s+${PID}.*,.*VID:\s+${VID},.*SN:\s+${SN} -> Record
  ^PID:\s+,.*VID:\s+${VID},.*SN: -> Record
  ^PID:\s+${PID}.*,.*VID:\s+${VID},.*SN: -> Record
  ^PID:\s+,.*VID:\s+${VID},.*SN:\s+${SN} -> Record
  ^PID:\s+${PID}.*,.*VID:\s+${VID}.*
  ^PID:\s+,.*VID:\s+${VID}.*
  ^.*SN:\s+${SN} -> Record
  ^.*SN: -> Record

```

</div></details>


<details><summary>cisco_nxos_show_inventory_r2.template</summary><div>

```rb:cisco_nxos_show_inventory_r2.template
Value NAME (.*)
Value DESCR (.*)
Value PID ([^,]\S+)
Value VID ((V\d+)|[^,]\S+)  #変更：Vから始まらない場合を考慮 ex.SFBR-709SMZ-CS1
Value SN ([\d+\w+/]+)

Start
  ^NAME:\s+"${NAME}",\s+DESCR:\s+"${DESCR}"
  ^NAME:\s+${NAME},\s+DESCR:\s+${DESCR}   # 追加：""で囲まれない場合を考慮 ex.Ethernet1/47
  ^PID:\s+${PID}\s+,.*VID:\s+${VID}\s*,.*SN:\s+${SN} -> Record   # 追加：VID直後に,が入る場合を考慮 ex.TPP4XGDS0CCISE2G,
  ^PID:\s+${PID}.*,.*VID:\s+${VID}.*SN:\s+${SN} -> Record
  ^PID:\s+,.*VID:\s+${VID}.*SN: -> Record
  ^PID:\s+${PID}.*,.*VID:\s+${VID}.*SN: -> Record
  ^PID:\s+,.*VID:\s+${VID}.*SN:\s+${SN} -> Record
  ^PID:\s+${PID}.*,.*VID:\s+${VID}.*
  ^PID:\s+,.*VID:\s+${VID}.*
  ^.*SN:\s+${SN} -> Record
  ^.*SN: -> Record
```
Templateを使用する場合は#コメントを削除してください。（Templatesのコメントアウト方法が分かりませんでした。）

</div></details>

<details><summary>cisco_nxos_show_inventory.template（参考）</summary><div>

```rb:cisco_nxos_show_inventory.template
Value NAME (.*)
Value DESCR (.*)
Value PID ([^,]\S+)
Value VID (V\d+)
Value SN ([\d+\w+/]+)

Start
  ^NAME:\s+"${NAME}",\s+DESCR:\s+"${DESCR}"
  ^PID:\s+${PID}.*,.*VID:\s+${VID}.*SN:\s+${SN} -> Record
  ^PID:\s+,.*VID:\s+${VID}.*SN: -> Record
  ^PID:\s+${PID}.*,.*VID:\s+${VID}.*SN: -> Record
  ^PID:\s+,.*VID:\s+${VID}.*SN:\s+${SN} -> Record
  ^PID:\s+${PID}.*,.*VID:\s+${VID}.*
  ^PID:\s+,.*VID:\s+${VID}.*
  ^.*SN:\s+${SN} -> Record
  ^.*SN: -> Record
```
</div></details>

# 実装

```python:get_inventory.py
import os
from datetime import datetime
import pandas as pd
import textfsm
import pathlib
import re


# outputフォルダ作成
def make_output_folder(out_path):
    # outputフォルダ作成
    date_str = datetime.now().strftime("%Y%m%d_%H-%M-%S")
    output_f = os.path.join(out_path, "output_" + date_str)
    os.mkdir(output_f)
    return output_f


if __name__ == '__main__':
    # カレントディレクトリを.pyフォルダに移動
    current_path = os.path.dirname(os.path.abspath(__file__))
    os.chdir(current_path)

    # inputフォルダ、ouputフォルダを指定
    input_folder = os.path.join(current_path, "input")
    output_folder = make_output_folder(os.path.join(current_path, "output"))

    # inputフォルダを読み込む 拡張子はtxt,cfg,log
    input_cfg_file_list = [str(p) for p in pathlib.Path(input_folder).glob("**/*")
                           if re.search(".*\.(txt|cfg|log)$", str(p))]

    # merge用DataFrame
    merge_result_df = pd.DataFrame()

    # inputファイルを一つずつ処理
    for cfg_path in input_cfg_file_list:
        cfg_base_name = os.path.basename(cfg_path)
        with open(cfg_path) as f:
            cfg_text = f.read()

        # TextFSMのテンプレートを決定する処理
        tmp_cfg_list = cfg_text.split("\n")
        for line in tmp_cfg_list:
            # showコマンドの出力1行目までスキップ
            if line in "#" or line in ">" or line == "":
                continue
            # showコマンドの出力からテンプレートを決定
            # NX-OSのテンプレート
            if re.search("Nexus ", line) is not None:
                template_path = r'./template/cisco_nxos_show_inventory_r2.template'
                break
            # IOSのテンプレート
            elif re.search("(WS-|Cisco|Catalyst)", line) is not None:
                template_path = r'./template/cisco_ios_show_inventory.template'
                break

        # fsmで結果を解析、抽出
        with open(template_path) as f:
            fsm = textfsm.TextFSM(f)
        output = fsm.ParseText(cfg_text)
        
        # 解析結果をDataFrameに変換
        df = pd.DataFrame(output, columns=fsm.header)

        # ファイルをcsvに保存
        out_name = os.path.splitext(cfg_base_name)[0] + ".csv"
        df.to_csv(os.path.join(output_folder, out_name), index=False, header=fsm.header)

        # 各出力結果を集積
        df["HOST"] = cfg_base_name
        merge_result_df = merge_result_df.append(df, sort=False)

    # 集積した出力結果をcsvに保存(HOSTを1列目に移動)
    cols = ['HOST'] + [col for col in df if col != 'HOST']
    merge_result_df = merge_result_df[cols]
    merge_result_df.to_csv(os.path.join(output_folder, "merge_output_file.csv"), index=False, header=True)
```

# 出力結果
outputフォルダにホストごとのcsvファイルとすべての結果をマージしたcsvファイルが作成されます。
マージ版のcsvファイルは以下の通りです(一部抜粋)。『show inventory』の結果に加え、1列目にファイル名が追加されています。

| HOST         | NAME                                         | DESCR                                           | PID              | VID              | SN          |
|--------------|----------------------------------------------|-------------------------------------------------|------------------|------------------|-------------|
| catalyst.cfg | C29xx Stacking                               | Catalyst C29xx Switch Stack                     |                  |                  |             |
| catalyst.cfg | 1                                            | WS-C2960X-24TS-L                                | WS-C2960X-24TS-L | V05              | FCW2115BXXX |
| catalyst.cfg | Switch 1 - Fan Container                     | Fan Container                                   |                  |                  |             |
| catalyst.cfg | Switch 1 - Power Supply Container            | Power Supply Container                          |                  |                  |             |
| catalyst.cfg | Switch 1 - RPS Container                     | RPS Container                                   |                  |                  |             |
| catalyst.cfg | Switch 1 - WS-C2960X-24TS-L - Power Supply 0 | Switch 1 - WS-C2960X-24TS-L - Power Supply 0    |                  |                  | LIT21123XXX |
| catalyst.cfg | Switch 1 - WS-C2960X-24TS-L - Fan 0          | Switch 1 - WS-C2960X-24TS-L - Fan 0             |                  |                  |             |
| catalyst.cfg | Switch 1 - WS-C2960X-24TS-L - Sensor 0       | Switch 1 - WS-C2960X-24TS-L - Sensor 0          |                  |                  |             |
| catalyst.cfg | Switch 1 - WS-C2960X-24TS-L - Fixed Module 0 | Switch 1 - WS-C2960X-24TS-L - Fixed Module 0    |                  |                  |             |
| catalyst.cfg | GigabitEthernet1/0/25 Container              | GigabitEthernet Container                       |                  |                  |             |
| catalyst.cfg | GigabitEthernet1/0/26 Container              | GigabitEthernet Container                       |                  |                  |             |
| catalyst.cfg | GigabitEthernet1/0/27 Container              | GigabitEthernet Container                       |                  |                  |             |
| catalyst.cfg | GigabitEthernet1/0/27                        | 1000BaseSX SFP                                  | GLC-SX-MMD       | V01              | FNS17070XXX |
| catalyst.cfg | GigabitEthernet1/0/28                        | 1000BaseSX SFP                                  | GLC-SX-MMD       | V01              | FNS17070XXX |
| nexus.cfg    | Chassis                                      | Nexus 3172 Chassis                              | N3K-C3172PQ-10GE | V02              | FOC2325RXXX |
| nexus.cfg    | Slot 1                                       | 48x10GE + 6x40G Supervisor                      | N3K-C3172PQ-10GE | V02              | FOC23248XXX |
| nexus.cfg    | Power Supply 1                               | Nexus 3172 Chassis Power Supply                 | N2200-PAC-400W-B | V03              | LIT23192XXX |
| nexus.cfg    | Power Supply 2                               | Nexus 3172 Chassis Power Supply                 | N2200-PAC-400W-B | V03              | LIT23193XXX |
| nexus.cfg    | Fan 1                                        | Nexus 3172 Chassis Fan Module                   | NXA-FAN-30CFM-B  | V00              | N/A         |
| nexus.cfg    | Fan 2                                        | Nexus 3172 Chassis Fan Module                   | NXA-FAN-30CFM-B  | V00              | N/A         |
| nexus.cfg    | Fan 3                                        | Nexus 3172 Chassis Fan Module                   | NXA-FAN-30CFM-B  | V00              | N/A         |
| nexus.cfg    | Fan 4                                        | Nexus 3172 Chassis Fan Module                   | NXA-FAN-30CFM-B  | V00              | N/A         |
| nexus.cfg    | Ethernet1/47                                 | CISCO-AVAGO                                     | 10Gbase-SR       | SFBR-709SMZ-CS1  | AVD23059XXX |
| nexus.cfg    | Ethernet1/48                                 | CISCO-OPLINK                                    | 10Gbase-SR       | TPP4XGDS0CCISE2G | OPM23050XXX |

# 参照
[TextFSMでシスコルータの型名とシリアルを抽出する方法](http://kooshin.hateblo.jp/entry/2017/11/28/000800)

[^1]: 2020年5月時点では、show inventory "all"、show inventory "raw"のTemplatesは無い模様(https://github.com/networktocode/ntc-templates/tree/master/templates)