Ctreepo
parsing and working with configuration of network devices
Install / Use
/learn @ozontech/CtreepoREADME
CTreePO - Configuration Tree Patch Overview
- CTreePO - Configuration Tree Patch Overview
Краткое описание
Библиотека для работы с конфигурацией сетевых устройств:
- преобразование конфигурации в дерево
- поиск/фильтрация конфигурации
- вычисление разницы (diff) между двумя конфигурациями
Быстрый пример (00.quick-start.py)
- Читаем текущий и целевой конфигурации из файлов
- Преобразуем конфигурации в деревья, попутно размечая тегами секции bgp и static routes
- Получаем разницу конфигураций
- Фильтруем разницу (а можно сначала фильтровать текущее/целевое деревья, а потом вычислять разницу между ними)
In [2]: from ctreepo import CTreeEnv, Vendor
In [3]: def get_configs() -> tuple[str, str]:
...: with open(file="./examples/configs/cisco-router-1.txt", mode="r") as f:
...: current_config = f.read()
...: with open(file="./examples/configs/cisco-router-2.txt", mode="r") as f:
...: target_config = f.read()
...: return current_config, target_config
...:
In [4]: def get_ct_environment() -> CTreeEnv:
...: tagging_rules: list[dict[str, str | list[str]]] = [
...: {"regex": r"^router bgp \d+$", "tags": ["bgp"]},
...: {"regex": r"^ip route \S+", "tags": ["static"]},
...: ]
...: return CTreeEnv(
...: vendor=Vendor.CISCO,
...: tagging_rules=tagging_rules,
...: )
...:
In [5]: current_config, target_config = get_configs()
In [6]: env = get_ct_environment()
In [7]: current = env.parse(current_config)
In [8]: target = env.parse(target_config)
In [9]: diff = env.diff(current, target)
In [10]: print("\n!-- разница конфигураций --")
...: print(diff.config)
...:
!-- разница конфигураций --
interface Tunnel2
no ip ospf priority 0
ip ospf priority 1
!
router bgp 64512
no neighbor RR peer-group
address-family ipv4
network 10.255.255.1 mask 255.255.255.255
!
line vty 0 4
no exec-timeout 15 0
exec-timeout 10 0
!
line vty 5 15
no exec-timeout 15 0
exec-timeout 10 0
!
ip name-server 192.168.0.9
!
no ip name-server 192.168.0.3
!
no ip route 192.168.255.1 255.255.255.255 Tunnel2
!
no ip route vrf FVRF 192.66.55.44 255.255.255.255 143.31.31.2
!
In [11]: print("\n!-- разница без секций с тегами bgp и static --")
...: diff_no_routing = env.search(diff, exclude_tags=["bgp", "static"])
...: print(diff_no_routing.config)
...:
!-- разница без секций с тегами bgp и static --
interface Tunnel2
no ip ospf priority 0
ip ospf priority 1
!
line vty 0 4
no exec-timeout 15 0
exec-timeout 10 0
!
line vty 5 15
no exec-timeout 15 0
exec-timeout 10 0
!
ip name-server 192.168.0.9
!
no ip name-server 192.168.0.3
!
In [12]: print("\n!-- разница в секции с тегом bgp --")
...: diff_bgp = env.search(diff, include_tags=["bgp"])
...: print(diff_bgp.config)
...:
!-- разница в секции с тегом bgp --
router bgp 64512
no neighbor RR peer-group
address-family ipv4
network 10.255.255.1 mask 255.255.255.255
!
</details>
<br>
Преобразование в дерево (01.parsing.py)
- Преобразование текстовой конфигурации в дерево на основе отступов в тексте
- Возможность размечать секции/строки тегами для последующей фильтрации
- pre-run и post-run обработка конфига и получившегося дерева, например нормализация входного конфига, обработка баннеров (cisco) и пр.
In [1]: from ctreepo import CTreeEnv, Vendor
In [2]: def get_configs() -> str:
...: with open(file="./examples/configs/cisco-example-1.txt", mode="r") as f:
...: config = f.read()
...: return config
...:
In [3]: def get_ct_environment() -> CTreeEnv:
...: return CTreeEnv(vendor=Vendor.CISCO)
...:
In [4]: config_config = get_configs()
In [5]: env = get_ct_environment()
In [6]: current = env.parse(config_config)
In [7]: print("\n---дерево в виде привычной конфигурации---")
...: print(current.config)
---дерево в виде привычной конфигурации---
service tcp-keepalives-in
!
service timestamps debug datetime msec localtime show-timezone
!
enable secret 5 2Fe034RYzgb7xbt2pYxcpA==
!
aaa group server tacacs+ TacacsGroup
server 192.168.0.100
server 192.168.0.101
!
interface Tunnel1
ip address 10.0.0.2 255.255.255.0
no ip redirects
!
interface Tunnel2
ip address 10.1.0.2 255.255.255.0
no ip redirects
!
interface FastEthernet0
switchport access vlan 100
no ip address
!
router bgp 64512
neighbor 192.168.255.1 remote-as 64512
neighbor 192.168.255.1 update-source Loopback0
address-family ipv4
network 192.168.100.0 mask 255.255.255.0
neighbor 192.168.255.1 activate
!
In [8]: print("\n---конфигурация с маскированными секретами---")
...: print(current.masked_config)
---конфигурация с маскированными секретами---
service tcp-keepalives-in
!
service timestamps debug datetime msec localtime show-timezone
!
enable secret 5 ******
!
aaa group server tacacs+ TacacsGroup
server 192.168.0.100
server 192.168.0.101
!
interface Tunnel1
ip address 10.0.0.2 255.255.255.0
no ip redirects
!
interface Tunnel2
ip address 10.1.0.2 255.255.255.0
no ip redirects
!
interface FastEthernet0
switchport access vlan 100
no ip address
!
router bgp 64512
neighbor 192.168.255.1 remote-as 64512
neighbor 192.168.255.1 update-source Loopback0
address-family ipv4
network 192.168.100.0 mask 255.255.255.0
neighbor 192.168.255.1 activate
!
In [9]: print("\n---дерево в виде патча для устройства---")
...: print(current.patch)
---дерево в виде патча для устройства---
service tcp-keepalives-in
service timestamps debug datetime msec localtime show-timezone
enable secret 5 2Fe034RYzgb7xbt2pYxcpA==
aaa group server tacacs+ TacacsGroup
server 192.168.0.100
server 192.168.0.101
exit
interface Tunnel1
ip address 10.0.0.2 255.255.255.0
no ip redirects
exit
interface Tunnel2
ip address 10.1.0.2 255.255.255.0
no ip redirects
exit
interface FastEthernet0
switchport access vlan 100
no ip address
exit
router bgp 64512
neighbor 192.168.255.1 remote-as 64512
neighbor 192.168.255.1 update-source Loopback0
address-family ipv4
network 192.168.100.0 mask 255.255.255.0
neighbor 192.168.255.1 activate
exit
exit
In [10]: print("\n---патч с маскированными секретами---")
...: print(current.masked_patch)
---патч с маскированными секретами---
service tcp-keepalives-in
service timestamps debug datetime msec localtime show-timezone
enable secret 5 ******
aaa group server tacacs+ TacacsGroup
server 192.168.0.100
server 192.168.0.101
exit
interface Tunnel1
ip address 10.0.0.2 255.255.255.0
no ip redirects
exit
interface Tunnel2
ip address 10.1.0.2 255.255.255.0
no ip redirects
exit
interface FastEthernet0
switchport access vlan 100
no ip address
exit
router bgp 64512
neighbor 192.168.255.1 remote-as 64512
neighbor 192.168.255.1 update-source Loopback0
address-family ipv4
network 192.168.100.0 mask 255.255.255.0
neighbor 192.168.255.1 activate
exit
exit
In [11]: print("\n---дерево в виде формальной конфигурации (аналогично formal в ios-xr)---")
...: print(current.formal_config)
---дерево в виде формальной конфигурации (аналогично formal в ios-xr)---
service tcp-keepalives-in
service timestamps debug datetime msec localtime show-timezone
enable secret 5 2Fe034RYzgb7xbt2pYxcpA==
aaa group server tacacs+ TacacsGroup / server 192.168.0.100
aaa group server tacacs+ TacacsGroup / server 192.168.0.101
interface Tunnel1 / ip address 10.0.0.2 255.255.255.0
interface Tunnel1 / no ip redirects
interface Tunnel2 / ip address 10.1.0.2 255.255.255.0
interface Tunnel2 / no ip redirects
interface FastEthernet0 / switchport access vlan 100
interface FastEthernet0 / no ip address
router bgp 64512 / neighbor 192.168.255.1 remote-as 64512
router bgp 64512 / neighbor 192.168.255.1 update-source Loopback0
router bgp 64512 / address-family ipv4 / network 192.168.100.0 mask 255.255.255.0
router bgp 64512 / address-family ipv4 / neighbor 192.168.255.1 activate
</details>
<br>
Поиск/фильтрация (02.searching.py)
- может быть на основе тегов, проставленных во время преобразования в дерево
- может быть по строке (regex)
- в результате получается копия дерева с которой можно работать так же, как и с оригиналом
In [1]: from ctreepo import CTreeEnv, Vendor
...:
...:
...: def get_configs() -> str:
...: with open(file="./examples/configs/cisco-example-1.txt", mode="r") as f:
...: config = f.read()
...: return config
...:
...:
...: def get_ct_environment() -> CTreeEnv:
...: tagging_rules: list[dict[str, str | list[str]]] = [
...: {"regex": r"^router bgp \d+$", "tags": ["bgp"]},
...: {"regex": r"^interface (Tunnel1) / ip address .*", "tags": ["interface", "tunnel-1-ip"]},
...: {"regex": r"^interface (Tunnel2) / ip address .*",
