Vyatta(VyOS)のコンフィグをXML化する

このエントリは、[Bluemix(SoftLayer) Advent Calendar 2016](http://qiita.com/advent-calendar/2016/bluemix)の7日目の記事です。

#Vyatta(VyOS)とは
Bluemix Infrastructure(旧SoftLayer)で柔軟なネットワークを構築したいときに、ソフトウェアルータであるVyatta(VyOS)がよく利用されます。
ルーティングはもちろん、ステートフルファイアウォールやVPNなどの機能が使えます。

#Vyattaのコンフィグ
Vyattaの設定は、プロンプトから"show configuration"を実行するといわゆるrunning-configが表示されます。なんかJSONっぽい！

```
vyatta@vy01:~$ show configuration 
firewall {
    all-ping enable
    broadcast-ping disable
    config-trap disable
    group {
        address-group ipsec-peer {
　　　　　　・・・
        }
    }
}
interfaces {
　　　　　　・・・
}
・・・
```

完全にJSONだったら楽だったのですが、残念ながらJSONとも違う謎の記述法で書かれています。少なくとも木構造ではあるようです。
そこでこのコンフィグを扱いやすいデータ記述法に変換して、自動でExcelなどに貼り付けられたら、管理表が最新でないだの、実機と設定が違うだの、運用でありがちな問題から開放されるなーということで、変換ツールを作ってみます。開放されたい！

#コンフィグ変換の方針
変換するツールはどんなのが便利か考えてみました。
<dl>
<dt>形式</dt>
<dd>なんでもよいのですが、VBAでExcelに貼りたかったので今回はMSXMLなどパーサが充実しているXMLにします。</dd>
<dt>ポータビリティ</dt>
<dd>ツールは特定の環境に依存せず使えることが理想です。もっと言えばVyattaの上で実行できれば文句なしです。VyattaはDebianベースのLinuxなので、ある程度のコマンドが使えます。</dd>
<dt>環境を汚さない</dt>
<dd>Vyattaの上で実行するとなれば、一時ファイルとかは残したくないです。あまりゴリゴリ重いこともやりたくないです。</dd>
</dl> 

上記方針を加味した結果、<strong>sedとtrだけでいこうとおもいます</strong>。
Vyatta上でも実行できるし、コンフィグを取ってきてローカルのcygwinやMacで実行してもよし。
やってみてだめだったら大人しくパールのようなものを使って書きます。（PythonはVyattaに入っていました。Rubyはなかった。）

XML化するにあたってルール決めが必要なところは以下のようにしました。

・要素名にハイフンは使わない
要素名にハイフンが含まれるとXMLが扱いにくくなる言語があるため、ハイフンはアンダースコアに変換します。

```
host-name vy01
　　　↓
<host_name>vy01</host_name>
```

・名前っぽいところには属性を使う

```
ethernet eth1 {
    address 172.16.0.1/24
}
　　　↓
<ethernet name="eth1">
    <address>172.16.0.1/24</address>
</ethernet>
```

・宣言だけのところは適宜単語を補う

```
rfc3768-compatibility
　　　↓
<rfc3768_compatibility>true</rfc3768_compatibility>
```

#呪文
上記ルールに沿うようsedのコマンドを機械的に追加していった結果、よくわからないものが出来上がりました。真面目にPythonで書けばよかった！

"show configuration"の結果をパイプで食わせてあげればXMLが出てきます。

```
vyatta@vy01:~$ show configuration | 呪文
```

```bash:呪文
sed 's/enable-default-log/enable_default_log true/' |\
sed 's/rfc3768-compatibility/rfc3768_compatibility true/' |\
sed 's/repository supported/repository_supported/' |\
sed 's/local-zone$/local_zone true/' |\
sed 's/system$/forward_to system/' |\
sed 's@^\( *\)\([^ {}]\+\) \([^ {}]\+\)\( *\)$@\1<\2>\3</\2>\4@' | \
tr '\n' '~' | \
sed 's@\(~ *\)\([^{}~]*\) {~\([^{}]*~*\)\( *\)}~@\1<\2>~\3\4</\2>~@g' | \
sed 's@\(~ *\)\([^{}~]*\) {~\( *\)}~@\1<\2></\2>~@g' | \
sed 's@\(~ *\)\([^{}~]*\) {~\([^{}]*~\)\( *\)}~@\1<\2>~\3\4</\2>~@g' | \
sed 's@\(~ *\)\([^{}~]*\) {~\([^{}]*~\)\( *\)}~@\1<\2>~\3\4</\2>~@g' | \
sed 's@\(~ *\)\([^{}~]*\) {~\([^{}]*~\)\( *\)}~@\1<\2>~\3\4</\2>~@g' | \
sed 's@\(~ *\)\([^{}~]*\) {~\([^{}]*~\)\( *\)}~@\1<\2>~\3\4</\2>~@g' | \
sed 's@\(~ *\)\([^{}~]*\) {~\([^{}]*~\)\( *\)}~@\1<\2>~\3\4</\2>~@g' | \
sed 's@\(~ *\)\([^{}~]*\) {~\([^{}]*~\)\( *\)}~@\1<\2>~\3\4</\2>~@g' | \
sed 's@\(~ *\)\([^{}~]*\) {~\([^{}]*~\)\( *\)}~@\1<\2>~\3\4</\2>~@g' | \
sed 's@^\( *\)\([^{}~]*\) {~\([^{}]*~\)\( *\)}~@\1<\2>~\3\4</\2>~@g' | \
tr '~' '\n' | \
sed 's@<address-group \([^>]\+\)>@<address_group name="\1">@' |\
sed 's@</address-group [^>]\+>@</address_group>@' |\
sed 's@<name \([^>]\+\)>@<rule_set name="\1">@' |\
sed 's@</name [^>]\+>@</rule_set>@' |\
sed 's@<rule \([^>]\+\)>@<rule priority="\1">@' |\
sed 's@</rule [^>]\+>@</rule>@' |\
sed 's@<bonding \([^>]\+\)>@<bonding name="\1">@' |\
sed 's@</bonding [^>]\+>@</bonding>@' |\
sed 's@<vif \([^>]\+\)>@<vif name="\1">@' |\
sed 's@</vif [^>]\+>@</vif>@' |\
sed 's@<vrrp-group \([^>]\+\)>@<vrrp_group name="\1">@' |\
sed 's@</vrrp-group [^>]\+>@</vrrp_group>@' |\
sed 's@<ethernet \([^>]\+\)>@<ethernet name="\1">@' |\
sed 's@</ethernet [^>]\+>@</ethernet>@' |\
sed 's@<loopback \([^>]\+\)>@<loopback name="\1">@' |\
sed 's@</loopback [^>]\+>@</loopback>@' |\
sed 's@<vti \([^>]\+\)>@<vti name="\1">@' |\
sed 's@</vti [^>]\+>@</vti>@' |\
sed 's@<interface-route \([^>]\+\)>@<interface_route network="\1">@' |\
sed 's@</interface-route [^>]\+>@</interface_route>@' |\
sed 's@<next-hop-interface \([^>]\+\)>@<next_hop_interface name="\1">@' |\
sed 's@</next-hop-interface [^>]\+>@</next_hop_interface>@' |\
sed 's@<route \([^>]\+\)>@<route network="\1">@' |\
sed 's@</route [^>]\+>@</route>@' |\
sed 's@<route6 \([^>]\+\)>@<route6 network="\1">@' |\
sed 's@</route6 [^>]\+>@</route6>@' |\
sed 's@<next-hop \([^>]\+\)>@<next_hop address="\1">@' |\
sed 's@</next-hop [^>]\+>@</next_hop>@' |\
sed 's@<remote-router \([^>]\+\)>@<remote_router address="\1">@' |\
sed 's@</remote-router [^>]\+>@</remote_router>@' |\
sed 's@<sync-map \([^>]\+\)>@<sync_map name="\1">@' |\
sed 's@</sync-map [^>]\+>@</sync_map>@' |\
sed 's@<community \([^>]\+\)>@<community name="\1">@' |\
sed 's@</community [^>]\+>@</community>@' |\
sed 's@<device \([^>]\+\)>@<device name="\1">@' |\
sed 's@</device [^>]\+>@</device>@' |\
sed 's@<user \([^>]\+\)>@<user name="\1">@' |\
sed 's@</user [^>]\+>@</user>@' |\
sed 's@<server \([^>]\+\)>@<server name="\1">@' |\
sed 's@</server [^>]\+>@</server>@' |\
sed 's@<repository \([^>]\+\)>@<repository name="\1">@' |\
sed 's@</repository [^>]\+>@</repository>@' |\
sed 's@<facility \([^>]\+\)>@<facility name="\1">@' |\
sed 's@</facility [^>]\+>@</facility>@' |\
sed 's@<esp-group \([^>]\+\)>@<esp_group name="\1">@' |\
sed 's@</esp-group [^>]\+>@</esp_group>@' |\
sed 's@<ike-group \([^>]\+\)>@<ike_group name="\1">@' |\
sed 's@</ike-group [^>]\+>@</ike_group>@' |\
sed 's@<proposal \([^>]\+\)>@<proposal number="\1">@' |\
sed 's@</proposal [^>]\+>@</proposal>@' |\
sed 's@<peer \([^>]\+\)>@<peer name="\1">@' |\
sed 's@</peer [^>]\+>@</peer>@' |\
sed 's@<zone \([^>]\+\)>@<zone name="\1">@' |\
sed 's@</zone [^>]\+>@</zone>@' |\
sed 's@<from \([^>]\+\)>@<from zone="\1">@' |\
sed 's@</from [^>]\+>@</from>@' |\
sed 's@<\([^-<>"]\+\)-\([^>]\+\)>@<\1_\2>@' |\
sed 's@</\([^-<>"]\+\)-\([^>]\+\)>@</\1_\2>@' |\
sed 's@<\([^-<>"]\+\)-\([^>]\+\)>@<\1_\2>@' |\
sed 's@</\([^-<>"]\+\)-\([^>]\+\)>@</\1_\2>@' |\
sed 's@<\([^-<>"]\+\)-\([^>]\+\)>@<\1_\2>@' |\
sed 's@</\([^-<>"]\+\)-\([^>]\+\)>@</\1_\2>@' |\
sed 's@^@    @' |\
sed '1i\<root>' |\
sed '$a\</root>'
```

Vyatta上にファイル置いちゃってもいいよという人は、スクリプトにして/tmpの下とかに置くといいとおもいます。

いろいろサニタイズして大幅に削っていますが、XMLは以下のようなものが出力されます。

```xml
<root>
    <interfaces>
        <ethernet name="eth0">
            <address>192.168.0.1/24</address>
            <duplex>auto</duplex>
            <hw_id>XX:XX:XX:XX:XX:XX</hw_id>
            <smp_affinity>auto</smp_affinity>
            <speed>auto</speed>
            <vrrp>
                <vrrp_group name="1">
                    <advertise_interval>1</advertise_interval>
                    <preempt>true</preempt>
                    <priority>254</priority>
                    <rfc3768_compatibility>true</rfc3768_compatibility>
                    <sync_group>vgroup1</sync_group>
                    <virtual_address>192.168.0.3/24</virtual_address>
                </vrrp_group>
            </vrrp>
        </ethernet>
        <ethernet name="eth1">
            <address>172.16.0.1/24</address>
            <duplex>auto</duplex>
            <hw_id>XX:XX:XX:XX:XX:XX</hw_id>
            <smp_affinity>auto</smp_affinity>
            <speed>auto</speed>
            <vrrp>
                <vrrp_group name="1">
                    <advertise_interval>1</advertise_interval>
                    <preempt>true</preempt>
                    <priority>254</priority>
                    <rfc3768_compatibility>true</rfc3768_compatibility>
                    <sync_group>vgroup1</sync_group>
                    <virtual_address>172.16.0.3/24</virtual_address>
                </vrrp_group>
            </vrrp>
        </ethernet>
        <loopback name="lo">
        </loopback>
    </interfaces>
    <service>
        <dns>
            <forwarding>
                <cache_size>150</cache_size>
                <listen_on>eth1</listen_on>
                <forward_to>system</forward_to>
            </forwarding>
        </dns>
        <ssh>
            <port>22</port>
        </ssh>
    </service>
    <system>
        <config_management>
            <commit_revisions>20</commit_revisions>
        </config_management>
        <console>
            <device name="ttyS0">
                <speed>9600</speed>
            </device>
        </console>
        <gateway_address>192.168.0.1</gateway_address>
        <host_name>vy01</host_name>
        <login>
            <user name="vyatta">
                <authentication>
                    <encrypted_password>****************</encrypted_password>
                </authentication>
                <level>admin</level>
            </user>
        </login>
        <name_server>192.168.0.1</name_server>
        <ntp>
            <server name="ntp.nict.jp">
            </server>
        </ntp>
        <package>
            <auto_sync>1</auto_sync>
            <repository_supported>
                <components>main</components>
                <distribution>stable</distribution>
                <password>****************</password>
                <url>http://packages.vyatta.com/vyatta_supported</url>
                <username>""</username>
            </repository_supported>
        </package>
        <syslog>
            <global>
                <facility name="all">
                    <level>notice</level>
                </facility>
                <facility name="protocols">
                    <level>debug</level>
                </facility>
            </global>
        </syslog>
        <time_zone>Asia/Tokyo</time_zone>
    </system>
</root>
```

これを使ってVBAでExcelにぺたぺた貼ると、図のようなドキュメントが自動で作れました。便利。
![spreadsheet.png](https://qiita-image-store.s3.amazonaws.com/0/151943/81af5e11-4046-3feb-a25c-53958029f490.png)


よく設定変更するところしかテストできていないので、不足がいっぱいあると思います。基本的にsedの行を追加すれば対応できると思いますが、コメントなどで教えていただけますと幸いです。
VBAのコードは来年のアドベントカレンダーにでも。。