pyATSでConfigを残すけど残しすぎない？ツール

# はじめに
初めまして
本記事が私の初投稿/初アドベントカレンダー参加になります。
わかりにくい点や見にくいところがたくさんあるかもしれませんが、暖かい目で見てくださると幸いです。

# pyATSを利用してConfigの収集してみた
pyATSを使って簡単なツールを作ってみました。
Configを取得して、前回のConfigファイルと比較して差分がなければ残さないというツールです。
削除がめんどくさくて容量が大きくなるのを防げるなーと思いながら作りました。
コードを見るとバレますが、プログラミングはまだまだ下手糞ですので今後きれいにスマートにしていきたいです。

# pyATSとは
pyATSはCiscoが開発しているネットワーク自動化ツールです。
showコマンドを取ってきたり、それを構造化してとってきたり、Configを入れたりすることができます。
詳しくはほかの方々が詳しく説明してくださっていますので見てみてください！
めちゃめちゃお世話になりました！！

https://qiita.com/tahigash/items/05a2cad4365ec1a47f55

https://qiita.com/tokatsu/items/58fbd2b2b219f33b8721

# 環境
今回私が使用していた環境についてです。

・Windows10
・wsl Ubuntu 22.04
・CML 2.4.1
・pyATS 22.10

pyATSはWindowsでは使用できません。
LinuxやmacOSならそのまま使用できます。

私はWindowsのPCを使っているのでwslでUbuntuを用意しました。
環境の構築にはこの方の記事を参考にさせていただきました。

https://ccieojisan.net/post-1829/

CMLにはJumphost経由で使っていますが構成はめちゃめちゃ単純です。

![advent2022.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2989076/cc6cda74-0fcb-5b8e-95a7-f12491228f72.png)



# pyATSに必要なもの
pyATSを動かすにはTestbedというものが必要になります。
Testbedは接続先の情報まとめたYAMLファイルです。
これを手掛かりにpyATSは機器にアクセスしてコマンドを代わりに打ってくれます。
こんな感じのもの

```php:CMLtestbed.yaml
devices:
  R1:
    connections:
      cli:
        ip: 172.16.1.xxx
        protocol: telnet
        proxy: admin1@jumphostubuntu
    credentials:
      default:
        password: xxxx
        username: cisco
      enable:
        password: xxxx
    os: iosxe
    type: iosxe
  admin1@jumphostubuntu:
    os: linux
    type: linux
    connections:
      cli:
        protocol: ssh
        ip: 10.71.xxx.xxx
        port: 22
    credentials:
      default:
        password: xxxx
        username: admin
      enable:
        password: xxxx
```

Jumohost経由であR1にアクセスする必要がありますが、pyATSはそれも大丈夫です。
キーはproxy
```php:CMLtestbed.yaml　Proxy抜粋
devices:
  R1:
    connections:
      cli:
        ip: 172.16.1.xxx
        protocol: telnet
        proxy: admin1@jumphostubuntu　＜－－－－ここ！！！
    credentials:
      default:
        password: xxxx
        username: cisco
      enable:
        password: xxxx
    os: iosxe
    type: iosxe
```

Jumohost先の機器の情報にproxyを追加してjumphostのホスト名を書いてあげるだけ！
簡単すぎてびっくりしたので書いておきました...

このtestbedはExcelで書いたものをそのまま変換することができます。
構成管理にもぴったりですね。

# ツールのコードについて
最初に書いたコードの全体を載せておきます。

```php:auto_config_collection.py
##import##
from genie.testbed import load
from genie.conf import Genie
from genie.utils.config import Config
import datetime
import os
import glob

##接続先の指定##
target = 'R1'

##testbed の読み込み 使うtestbedの指定##
tb = load("testbed/CMLtestbed.yaml")
print('\n###testbed loading###')

##接続先の指定 testbedに記載してあるものを使う##
dev = tb.devices[target]

##接続 ()の中のおかげで接続時に設定を変えるコマンドが投入されない##
dev.connect(init_exec_commands=[], init_config_commands=[])
print('\n###connecting###')

##configを取得##
print('\n### config Loading ###')
config = dev.execute("show run")
print('\n### config Loading complete ###')

##最新のファイルのパスを取得##
list_of_files = glob.glob('CML_config/*') # * means all if need specific format then *.csv
latest_file = max(list_of_files, key=os.path.getctime)
print (latest_file)

##時間の取得##
dt_now = datetime.datetime.now().strftime('%Y-%m-%d_%H:%M:%S')
print(dt_now)

##最新ファイルの読み込み
with open(latest_file) as f1:
   file_read1 = f1.read()
##現在のログを一時保存
with open('CML_config/' + dt_now + '_logfile.log', 'w' ) as log:
   log.write(config)
##一時保存したログの読み込み
with open('CML_config/' + dt_now + '_logfile.log') as f2:
   file_read2 = f2.read()

##差分取得
if not file_read1 == file_read2 :
	##差分があればそのままログを保存
	print(file_read1 == config)
	print('\n### Writing completed ###')
else:
	##差分がなければログを削除
	os.remove('CML_config/' + dt_now + '_logfile.log')
	print('not difference')

##切断##
dev.disconnect()
print('\n###disconnect###')
```

# コードの中身について
少しずつコードの解説をしてみます。

## import
pyATSも必要なライブラリをimportしながらコードを書いていきます。

```php:auto_config_collection.py　import抜粋
##import##　
from genie.testbed import load
from genie.conf import Genie
from genie.utils.config import Config
import datetime
import os
import glob
```

## 接続
```php:auto_config_collection.py　接続抜粋
##接続先の指定##
target = 'R1'

##testbed の読み込み 使うtestbedの指定##
tb = load("testbed/CMLtestbed.yaml")
print('\n###testbed loading###')

##接続先の指定 testbedに記載してあるものを使う##
dev = tb.devices[target]

##接続 ()の中のおかげで接続時に設定を変えるコマンドが投入されない##
dev.connect(init_exec_commands=[], init_config_commands=[])
print('\n###connecting###')
```
targetという変数にconfigを取得したい機器のhost名を書きます。
ここを空にするとtestbedに記載したすべての機器に対してアクションをします。

tbという変数にはtestbedのパスを入れます。

devという変数はtargetとtbをまとめています。
毎回全部を書くのは面倒ですからね
例えば接続するときはこんな感じになるのを
load("testbed/CMLtestbed.yaml").devices[target].connect()
こうすることができます。
tb.connect()
こんなに省略できたら幸せですね。
今後何かアクションをする際にはこれを使いまわすことになります。

dev.connect(init_exec_commands=[], init_config_commands=[])
の（）の中はオプションになります。極論なくてもいいです。
pyATSは接続するときに下記のコマンドを自動で投入します。
```:
R1(config)#no logging console
R1(config)#line console 0
R1(config-line)#exec-timeout 0
R1(config-line)#end
```
これらを止めてくれるのが
init_exec_commands=[], init_config_commands=[]
です。
通信影響があったりするコマンドではなさそうですが、怖い方は入れておきましょう。

## configの取得
```php:auto_config_collection.py　config取得抜粋
##configを取得##
print('\n### config Loading ###')
config = dev.execute("show run")
print('\n### config Loading complete ###')
```

今回はshowコマンドを取るだけなのでexecuteを使います。
executeは単純にコマンドを投入するだけです。

他にもparseやlearn,Configureなどがあります。
parseはshowコマンドを構造化してファイルとして残しておいてくれます。コマンドの省略はNG！

learnは Genie Ops モデルに沿った情報取得を行います。
https://pubhub.devnetcloud.com/media/genie-feature-browser/docs/#/parsers
↑に則ったものを返してくれます。

configureは自動的にconf tをしてコンフィグレーションモードに入り、設定投入後自動でendも打ってくれます。
IOSXRに対してはcommitまでしてくれます。
すごい！！まさに自動化

## Configの保存と比較
```php:php:auto_config_collection.py　保存と差分抜粋
##時間の取得##
dt_now = datetime.datetime.now().strftime('%Y-%m-%d_%H:%M:%S')
print(dt_now)

##最新ファイルの読み込み
with open(latest_file) as f1:
   file_read1 = f1.read()
##現在のログを一時保存
with open('CML_config/' + dt_now + '_logfile.log', 'w' ) as log:
   log.write(config)
##一時保存したログの読み込み
with open('CML_config/' + dt_now + '_logfile.log') as f2:
   file_read2 = f2.read()

##差分取得
if not file_read1 == file_read2 :
	##差分があればそのままログを保存
	print(file_read1 == config)
	print('\n### Writing completed ###')
else:
	##差分がなければログを削除
	os.remove('CML_config/' + dt_now + '_logfile.log')
	print('not difference')

##切断##
dev.disconnect()
print('\n###disconnect###')
```

ここからはあまりpyATS絡んでこないですね。
取得したConfigを一度保存します。
今回は中身の文字列に差異がなければ差分なしと判断するため、== で比較しました。
保存したconfigと最新のconfigファイルの中身を比較して差分がなければ取得したConfigは削除します。
差分があった場合はそのままファイルは残します。

一度保存する工程はいらなかったのですが、差分が正しく取れなかったので泣く泣く一度保存しました...

最後は接続が残ってしまわないようにdisconnectで切断します。

# 完成！
これでconfigを取得して、前回のConfigと比較してくれるツールが出来上がりました。
試しに動かしてみます。

フォルダにはIPアドレスを設定していないconfigファイルがあります。

```php:python auto_config_collection.py
###testbed loading###

2022-12-06 20:50:38,124: %UNICON-INFO: +++ R1 logfile /tmp/R1-cli-20221206T205038123.log +++

2022-12-06 20:50:38,124: %UNICON-INFO: +++ Unicon plugin iosxe (unicon.plugins.iosxe) +++

2022-12-06 20:50:38,127: %UNICON-INFO: connection via proxy admin1@jumphostubuntu

2022-12-06 20:50:38,147: %UNICON-INFO: +++ connection to spawn: ssh -l admin1 10.71.xxx.xxx -p 22, id: 140073218694432 +++

2022-12-06 20:50:38,148: %UNICON-INFO: connection to admin1@jumphostubuntu
admin1@10.71.xxx.xxx's password:
Welcome to Ubuntu 22.04.1 LTS (GNU/Linux 5.15.0-48-generic x86_64)

 * Documentation:  https://help.ubuntu.com
 * Management:     https://landscape.canonical.com
 * Support:        https://ubuntu.com/advantage

15 updates can be applied immediately.
To see these additional updates run: apt list --upgradable


The list of available updates is more than a week old.
To check for new updates run: sudo apt update
*** System restart required ***
Last login: Tue Dec  6 20:46:22 2022 from 10.70.xxx.xxx
​​​​​​​: command not found
​​​​​​​: command not found
admin1@jumphostubuntu:~$

2022-12-06 20:50:38,957: %UNICON-INFO: +++ initializing handle +++

2022-12-06 20:50:38,959: %UNICON-INFO: connection to R1
telnet 172.16.1.xxx
Trying 172.16.1.xxx...
Connected to 172.16.1.xxx.
Escape character is '^]'.

**************************************************************************
* IOSv is strictly limited to use for evaluation, demonstration and IOS  *
* education. IOSv is provided as-is and is not supported by Cisco's      *
* Technical Advisory Center. Any use or disclosure, in whole or in part, *
* of the IOSv Software or Documentation to any third party for any       *
* purposes is expressly prohibited except as otherwise authorized by     *
* Cisco in writing.                                                      *
**************************************************************************

User Access Verification

Password:
**************************************************************************
* IOSv is strictly limited to use for evaluation, demonstration and IOS  *
* education. IOSv is provided as-is and is not supported by Cisco's      *
* Technical Advisory Center. Any use or disclosure, in whole or in part, *
* of the IOSv Software or Documentation to any third party for any       *
* purposes is expressly prohibited except as otherwise authorized by     *
* Cisco in writing.                                                      *
**************************************************************************
R1>

2022-12-06 20:50:39,622: %UNICON-INFO: +++ initializing handle +++
enable
Password:
R1#
###connecting###

### config Loading ###


2022-12-06 20:50:40,135: %UNICON-INFO: +++ R1 with via 'cli': executing command 'show run' +++
show run
Building configuration...


Current configuration : 3203 bytes
!
! Last configuration change at 12:46:30 UTC Thu Dec 1 2022
!
version 15.9
service timestamps debug datetime msec
service timestamps log datetime msec
no service password-encryption
!
hostname R1
!
boot-start-marker
boot-end-marker
!
!
no logging console
enable secret 9 $9$uEupliwExQ7/1f$sr72O0nIFheUkpnBZ5WADx0Ucyi2vvom2GTzNlBKEuI
enable password xxxx
!
no aaa new-model
!
!
!
mmi polling-interval 60
no mmi auto-configure
no mmi pvc
mmi snmp-timeout 180
!
!
!
!
!
!
!
!
!
!
!
ip domain name cisco.com
ip cef
no ipv6 cef
!
multilink bundle-name authenticated
!
!
!
!
username cisco password 0 xxxx
!
redundancy
!
!
!
!
!
!
!
!
!
!
!
!
!
!
!
interface GigabitEthernet0/0
 no ip address
 shutdown
 duplex auto
 speed auto
 media-type rj45
!
interface GigabitEthernet0/1
 no ip address
 shutdown
 duplex auto
 speed auto
 media-type rj45
!
interface GigabitEthernet0/2
 ip address 172.16.1.xxx 255.255.255.0
 duplex auto
 speed auto
 media-type rj45
!
interface GigabitEthernet0/3
 no ip address
 shutdown
 duplex auto
 speed auto
 media-type rj45
!
ip forward-protocol nd
!
!
no ip http server
no ip http secure-server
ip ssh version 2
!
ipv6 ioam timestamp
!
!
!
control-plane
!
banner exec ^C
**************************************************************************
* IOSv is strictly limited to use for evaluation, demonstration and IOS  *
* education. IOSv is provided as-is and is not supported by Cisco's      *
* Technical Advisory Center. Any use or disclosure, in whole or in part, *
* of the IOSv Software or Documentation to any third party for any       *
* purposes is expressly prohibited except as otherwise authorized by     *
* Cisco in writing.                                                      *
**************************************************************************^C
banner incoming ^C
**************************************************************************
* IOSv is strictly limited to use for evaluation, demonstration and IOS  *
* education. IOSv is provided as-is and is not supported by Cisco's      *
* Technical Advisory Center. Any use or disclosure, in whole or in part, *
* of the IOSv Software or Documentation to any third party for any       *
* purposes is expressly prohibited except as otherwise authorized by     *
* Cisco in writing.                                                      *
**************************************************************************^C
banner login ^C
**************************************************************************
* IOSv is strictly limited to use for evaluation, demonstration and IOS  *
* education. IOSv is provided as-is and is not supported by Cisco's      *
* Technical Advisory Center. Any use or disclosure, in whole or in part, *
* of the IOSv Software or Documentation to any third party for any       *
* purposes is expressly prohibited except as otherwise authorized by     *
* Cisco in writing.                                                      *
**************************************************************************^C
!
line con 0
 exec-timeout 0 0
line aux 0
line vty 0 4
 exec-timeout 0 0
 password xxxx
 login
 transport input telnet
!
no scheduler allocate
!
end

R1#

### config Loading complete ###
CML_config/2022-12-05_18:01:46_logfile.log
2022-12-06_20:50:42
False <--------差分あり！！！！

### Writing completed ###

###disconnect###
```
差分あり　と出て　### Writing completed ###　とあるのでファイルがそのまま残りましたね。

もう一度動かしてみましょう。
差分はないはずですので not difference が出力されれば成功です。

長くなるので最後だけ抜粋します。

```php:python auto_config_collection.py
### config Loading complete ###
CML_config/2022-12-06_20:50:42_logfile.log
2022-12-06_20:55:54
not difference<-----差分なし！！！

###disconnect###
```

想定通り動いてくれました。
これで無駄なConfigを残さず、容量を節約できそうです！

# 最後に
今回は非常に簡単なコードを組み合わせて作りました。
ですが、もっとpyATSの機能を使いこなせば楽なコードが書けそうです。parseとかDiffとか......
これを.bashにしてスケジューラに入れたりすると完全に自動化できそう...今度やってみます。

非常に雑なコードですが想定の動きをしてくれるとプログラミングは楽しいなと改めて感じました。
これを作るにあたってWSLを入れたりpyATSをインストールしたり、いろいろやってきたのでそちらも記念に書いてみたいです。

大したことない内容を長々と書きましたがここまで見てくださりありがとうございました。
見やすい記事にできるようにちょくちょくいじってみようと思います。

それでは良いクリスマスを！！！！