Привет Habr!
Я уже начинал предыдущую свою статью Yast Another Config Manipulation или зачем изобретать велосипед? словами благодарности книге Натальи Самойленко Python для сетевых инженеров
Начну и эту. Если вы сетевой инженер и не знакомы с Python - начните с этой книги!
А еще помочь вам может моя библиотека Astarmiko — это продвинутый Python-инструментарий для управления и автоматизации корпоративной сетевой инфраструктуры через SSH, который родился при пошаговом выполнении заданий из книги.
Так как в моем распоряжении была вся корпоративная сеть (нашего филиала), мне быстро наскучило играть в "песочнице".
Что представляет из себя стандартный словарь для подключения с помощью netmiko:
{Router1: {
device_type: cisco_ios,
ip: some_ip_1,
user: some_user,
password: some_password
}
}
Понимая, что учетка на tacacs'е у меня одна - выкинул атрибуты user и password
Хорошо бы чтобы код питона различал сам тип устройства - Router (R) или коммутатор уровня L2 или L3. Еще несколько "хорошо бы"...
На самом деле мое внутреннее ТЗ звучало так:
звонит МарьВанна, что-то сеть не работает, и надо быстро понять в какой порт, какого коммутатора, на какой площадке включен ее компьютер?
И второе ТЗ звучало - что то с наскока не получилось rancid заставить архивировать huawei, а тем более я же питонист теперь :-) давай сам напишу бэкап.
А в результате вышло - добавь в ACL на доступ к консоли на всех 150 устройствах IP адрес нового сотрудника службы безопасности, или проанализируй ACL на всех роутерах и если этого правила нет, добавь. А роутеры у нас и Cisco и Huawei и Eltex. Или ежедневный бэкап всех конфигов всей активки (активного сетевого оборудования) в случае его изменения с прошлого дня.
В основе всего этого (и много другого) уже лет 5 трудится моя библиотека astarmiko
Небольшое отступление для будущих поколений, желающим понять этимологию этого слова
вы знаете, что paramiko - (па́рамико) происходит от японского слова 「пара-мико」 (парашютист + 子 «ко» – суффикс, означающий «ребёнок» или «маленький»).
Смысл: - 「パラ」 (пара) – сокращение от "парашют" (англ. parachute), но здесь подразумевается "parallel" (параллельный). - 「ミコ」 (мико) – может означать «маленький» (как в "ребёнок").
Идея: Создатель модуля (Jeff Forcier, 2003) хотел передать идею «лёгкого» и «проворного» SSH-клиента, который работает параллельно.
Название Netmiko – это комбинация "Net" (сеть) и "miko" (от Paramiko).
Смысл: - "Net" – указывает на работу с сетевыми устройствами (Cisco, Juniper и др.). - "miko" – отсылка к Paramiko, так как Netmiko построен на его основе.
-
Идея: Автор (Kirk Byers, 2014) хотел подчеркнуть, что это "сетевой (Net) вариант Paramiko", упрощающий работу с оборудованием.
A в далеком 1998 году, регистрируя свою первую почту, я придумал себе alias astar И не думал я совсем об "a Star" просто зовут меня Андрей Старков (хотя я потом свое погоняло обыгрывал в этом смысле) и с тех пор - AstarPhone, AstarIPad, AstarMacAir, Astarpoint и т.д.
так что у моей надстройки над netmiko просто не было шансов называться по другому :-)
Главная идея модуля astarmiko
это сеть как класс в объектно-ориентированном программировании. Т.е. все мое активное оборудование это класс Activka(конфиг_моей_сети)
Инициализация класса: myactivka = Activka('activka_byname.yaml'), где 'activka_byname.yaml' - файл в формате YAML хранящий информацию о всём оборудовании, (см. activka_byname_ru.md)
Объекты этого класса имеют разные свойства:
-- ma.devices - список имен всех устройств в сети
-- ma.realdevices - те же имена но все буквы прописные и пропущены все не буквы не цифры (.isalnum())
-- ma.levels - список имен с типом активки R, L2, L3
-- ma.segment - список имен по сегментом сети (смотри ниже)
И разные "глаголы" - действия send_command, getinfo и т.п.
Общий принцип использования класса Activka
myactivka.выполнить(где = myactivka.выбрать_по критериям, что = одна_команда_или_список )
В основу справочника для модуля лёг YAML файл (activka_byname.yaml) следующего формата:
Router1:
device_type: cisco_ios
ip: some_ip_1
Router2:
device_type: huawei
ip: some_ip_2
Switch1:
device_type: huaweivrrp
ip: some_ip_3
Switch2:
device_type: cisco_ios
ip: some_ip_4
LEVEL:
Router1: R
Router2: R
Switch1: L3
Switch2: L2
SEGMENT:
Router1: ``
Router2: Othe_area_of_Network
Switch1: City1
Switch2: Place2
где:
device_type - тип устройства как определено в netmiko
ip - ip адрес этого устройства
LEVEL - маршрутизатор (R), L2 или L3 коммутатор, от уровня зависит имеет ли смысл выполнять запрос arp таблицы (R), таблицы MAC адресов (L2) или и того и другого (L3) - да мало ли еще зачем вы можете использовать в логике ваших программ
SEGMENT:
тут необходимо пояснение.
схему нашей сети можно представить как

это разные кольца ВОЛС, есть еще много дополнительных каналов по РРЛ, спутникам, ВЧ, и в моей программе управления (Putty + SupperPutty) оборудование разбито по каталогам SEG A ..... SEG I, на сервере, где хранятся бэкапы конфигов оборудования созданы каталоги с такими же именами. Я мыслю о сети такими кусками, так проще траблшутить.
В общем SEGMENT - это любое понятное вам разбиение вашей сети на какие то сегменты
Структуру модуля можно представить следующим образом
astarmiko/base.py
— базовый классActivka
для управления устройствамиastarmiko/async_exec.py
— классActivkaAsync
для параллельного выполнения SSH-командastarmiko/log_config.py
— гибкая настройка логирования (JSON, stdout)scripts/fh.py
— утилита FindHost: поиск устройства по IP или MAC (с нее то все и началось)scripts/acm.py
— утилита ACtivkaManagment: CLI для использования библиотеки из командной строкиYAML/
— конфигурации, маппинг команд и данные сегментовTEMPLATES/
— шаблоны TextFSM для структурированного парсинга
в реальности YAML/
и TEMPLATES/
могут лежать в любом месте, но это место (../) должно описываться в конфиге как localpath
Любая программа, базирующаяся на astarmiko требует для работы конфигурационный файл в формате YAML (его легко создать из других форматов и легко читать и править руками)
fh.py например ищет в той же папке, где находится сама файл fh.yaml,
но пример файла конфигурации я назвал:
astarmiko.yaml его примерное содержимое:
localpath: ~/astarmiko/
базовый путь относительно которого строятся пути до папок или файлов, используемых в astarmiko. textFSM шаблоны или справочники (в формате YAML) хранятся именно в ~/astarmiko/ TEMPLATES/ и ~/astarmiko/ YAML/
путем экспериментов я пришел к тому, что все мои подпрограммы увязаны друг с другом и если расположить в другом месте, пользователю моего модуля придется делать много движений внутри кода, а так путь работает однозначно хоть в nt хоть в posix системах
учетные данные - имя пользователя и пароль
стандартные учетные данные, используемые для подключения к оборудованию по ssh, обычно это tacacs или radius учетка
user: gAAAAABn7NmekcfGhIjrwJRXL6v0QRm3SAz4dz-GSm16gu7dpBIyw5omo-A1d3-LjaNwPwTN6Vg-1jzW5_0aPeFbwe0p6TZtsQ==
password: gAAAAABn7Nme6Kb4cI-sqsyApPFm2JsqLtp-
2Hds7Jov8MY50XBx3s1VKOIXgA3FKjIa_FjpqkbdDsG6bWwzobwhw9SOrSwHOA==
первоначально, при создании конфигурационного файла записываете имя пользователя и пароль открытым текстом, а потом шифруете, используя CLI интерфейс модуля astarconf
список первых октетов mac адресов IP телефонов
phone_mac:
805e
-
001a
Если у вас в сети используются IP телефоны, чтобы понять что порт на коммутаторе это конечный порт для компьютера подключенного через телефон, надо знать какие mac адреса, которые "светятся" на порту, относятся к телефонам. Так как количество брендов IP телефонов в сети не бесконечно, и у всех телефонов одного бренда mac адрес начинаются одинаково, создается такой список
templpath: ~/astarmiko/TEMPLATES/
Путь, где хранятся textFSM шаблоны
dict_of_cmd: ~/astarmiko/YAML/commands.yaml
Путь к файлу commands.yaml, в котором находятся стандартные, чаще всего используемые команды. Именно для них всегда имеются шаблоны textFSM
(или другими словами - добавляя в этот файл свои команды, позаботьтесь о наличии шаблонов textFSM для всего парка вашего оборудования)
logging: True/False
Включение/отключение логов
logfile: /some_path/astarmiko.log
Расположение лог файла
loglevel: INFO/WARNING/DEBUG etc
Уровень лога
log_format_str:
здесь можно указать формат строки лога, если хотите отличный от дефолтного
add_account: (дополнительные учетки)
В любых правилах бывают исключения. У меня есть оборудование, где не получается настроить tacacs или какие то другие причины и приходится использовать локальные или другие учетки. Все они перечисляются здесь. модуль подключения к оборудованию из astarmiko при невозможности подключиться со стандартной учеткой, попробует ниже перечисленные и только потом вернет сообщение об ошибке
Учетные данные точно так же шифруются с помощью модуля astarconf
password: gAAAAABoB1JEZiWkUNiaXAs-ItSPTUrPyBCP4jyZBLWF0P9SGahWSD5ZWNK9QFxCbkPCnPFdqmigVQx8vmrMAbkz09mJRNH7HA==
user: gAAAAABoB1JEHSOPYy3-0SAUhGgzlRnXTf56-1frsFs9d2CRYuwqRtfAQRgZYF0ohraFCN74IaR1P3Zdr1BONnNZVAa8d9Yyvw==password: gAAAAABoB1JEUFWn9R_rx-MaZ7QFyPiVK5mh4VgHZmpiAcTLV8EZ3QM99gK8ZVkjAqSXnOjGGOGSL3SU0e85Pcc9f33BQ-Q4og==
user: gAAAAABoB1JE-WO-0EEjVG7-mmK1fSMGEzRaCdVxW1WW81ZBSFzxkYmMT1PvWevC7RI9Ey6b-xiv4hGLXq0wFrWifTYwDNRxgg==
Вышеназванный commands.yaml:
В свой работе любой сетевой администратор часто выполняет однотипные команды, посмотреть таблицу arp, общую таблицу mac адресов, mac адреса на определенном порту, вывести список всех IP интерфейсов и т.п.
сама библиотека astarmiko позволяет автоматизировать выполнение этих команд сразу на многих устройствах, а словарь в command.yaml позволяет хранить соответствие между смыслом команды и ее конкретным исполнением на оборудовании того или иного бренда
также здесь хранится параметр mac_delimeters - по сути стиль вывода (и ввода) mac адреса в консоль оборудования, от бренда к бренду может отличаться не только порядок записи 6 по 2 или 3 по 4 но и разделитель точка или тире или двоеточие
Пример из файла - посмотреть на каком порту светится определенный mac address или сколько mac адресов "светится" за определенным портом. Не ленитесь прописывать свойство desc(ription) - пригодится в будущем
"mac_addr_tbl_by":
desc: 'show mac addres table and filter by mac'
cisco_ios: 'show mac address-table | in {}'
huawei: 'display mac-address | in {}'
huawei_vrpv8: 'display mac-address | in {}'
eltex: 'show mac address-table | in {}'
"mac_addr_tbl_byport":
desc: 'show mac addres table on defined port'
cisco_ios: 'show mac address-table interface {}'
huawei: 'display mac-address {}'
huawei_vrpv8: 'display mac-address interface {}'
eltex: 'show mac address-table | in {}'
Обратите внимание, вот что я называю плохим программированием - для разных линеек коммутаторов Huawei требуется разное написание команды "покажи мак-адреса на интерфейсе"
Эти три файла - activka_byname.yaml, commands.yaml, astarmiko.yaml необходимая база для работы библиотеки astarmiko
commands.yaml, astarmiko.yaml базовые есть на github
activka_byname.yaml есть там же в виде "болванки". Сам я создавал из exel'евского файла в котором у меня расписано все оборудование с именами, адресами и т.п. - оставил несколько столбцов, вывел в CSV и на скорую руку на питоне из этого CSV создал yaml файл
подробное описание базовых функций, конфигурационных файлов можно найти на github в каталоге DOCUMENTATION на английском и русском языках, здесь кратко расскажу о базовых из astarmiko/base.py
Основной модуль astarmiko/base.py
Содержит классы Activka (базовый класс представления всего активного сетевого оборудования) и наследуемый ему ActivkaBackup (базовый класс для бэкапа конфигов оборудования)
Activka functions:
choose(device, withoutname = False)
создает словарь для подключения в формате netmiko для использования с ConnectHandler(**device), где device - имя устройства (из activka_byname.yaml) а withoutname - селектор выбора формата словаря {dictionary for conect} if False (default) или {device_name:{dictionary for conect}} if True
filter(device_type = None, levels = None, segment = None)
выбирает из activka_byname.yaml список устройств по трем параметрам
device_type - тип устройства netmiko
levels - 'R' - router 'L3' - L3 swith 'L2' - L2 switch
segment - сегмент сети (см. activka_byname_ru.md)
setconfig, getinfo, get_curr_config, list_of_all_ip_intf
Записать команды конфигурации на устройство, получить любую информацию (show commands), получить текущую конфигурацию, получить список всех IP интерфейсов
setconfig_on_devices, execute_on_devices
Записать команды конфигурации сразу на множество устройств, получить любую информацию со множество устройств (show commands)
ActivkaBackup functions:
(это была часть моего внутреннего ТЗ - бэкап конфигов)
setupbackup_servers
Я имею 2 сервера для хранения конфигураций оборудования (main & second) так как скрипт архивирования может запускаться на main server по cron'у и в этом случае для скрипта main - это локальный сервер, доступ к каталогу с конфигами средствами ОС
или его можно запустить со своей рабочей станции, и тогда main server так же как и second становится удаленным сервером и доступ к нему определяется через
setupprotocol_handlers
compare_configs( device: str, ignore_lines: List[str] = None) -> Dict[str, Any]
сравнивает текущую конфигурацию устройства с последней сохраненной на сервере и возвращает различия
get_backup_config, write_backup
получает последний сохраненный конфиг из файла на сервере и создает файл на сервере и записывает в него текущую конфигурацию устройства, на эти функции мапируются _getbackup_config_local, _getbackup_config_ftp, _writebackup_local, _writebackup_ftp в зависимости от места запуска скрипта
Отдельные функции вне класса
setup_logging
настройка уровня, формата, назначения вывода логгирования
setup_config
функция для настройки различных параметров работы модуля на основе конфигурационного файла в формате YAML
В качестве параметров может быть что угодно - пути для хранения файлов конфигураций, шаблонов, имена и пароли пользователей для доступа, включение, отключение, настройка логов, выбор языка интерфейса, создание словаря стандартных команд для различного типа используемого у вас оборудования
Так как различные конфигурационные параметры требуются и внутри класса Activka и в программах на его основе, я обычно делаю так:
в самом начале
from astarmiko.base import (
Activka,
port_name_normalize,
convert_mac,
is_ip_correct,
nslookup,
setup_config,
snmp_get_oid,
)
потом уже в main() каким либо образом сообщаю как зовут основной конфиг файл (пример из FindHost) и инициализирую внутри класса Activka окружение для работы путем выполнения setup_config(config_path):
path_to_cfg = os.path.abspath(os.path.join(base_dir, f"{file_name}.yaml"))
if os.path.exists(path_to_cfg):
setup_config(path_to_cfg)
else:
config_path = os.path.expanduser("~/astarmiko/fh.yaml")
if os.path.exists(config_path):
setup_config(config_path)
else:
print("The fh (findhost)requires a configuration file fh.yaml either in the same folder as
fh.py
or in ~/astarmiko/YAML/")
sys.exit()
В этот момент внутри класса инициализируется объект конфига ac = Astarconf() смотри мою статью Yast Another Config Manipulation или зачем изобретать велосипед?
snmp_get_oid
Видимо в будущем выполнение snmpget и подобного выведу в отдельный модуль, а пока эта функция потребовалась потому, что весь astarmiko написан на использовании ssh доступа к оборудованию, а в моей сети появились файерволы российского производства Континент-4, которые не имеют полноценного CLI но которые являются маршрутизатором для определенных сегментов сети и arp и mac-address таблицы по ssh с них не получишь, только по snmp
send_commands
Основная команда общения с активкой. Два режима работы - exec_mode и config_mode. Использует _tryconnect() для проверки доступности, и выбора учеток для подключения
port_name_normalize
Появление этой команды вызвано тем, что на устройствах Huawei нельзя вывод одной команды использовать для выполнения другой команды, устройства Huawei, например возвращают в списке интерфейсов как GE0/0/1 а при вводе команд надо писать GI0/0/1
get_port_by_mac
Функция ищет на каком порту "светится" устройство с определенным mac-address и является ли этот порт конечным (за ним только это устройство или это устройство с IP телефоном), или же к порту подключен следующий в цепочке коммутатор
convert_mac
В реальной жизни маршрутизатор может быть одного брэнда (и возвращающий ARP таблицу в одном формате) а коммутатор к нему подключенный другого бренда, и, чтобы на нем искать по mac адресу устройство, необходимо указывать это mac адрес в другом формате.
Эта функция знает к какому формату преобразовать mac-address основываясь на device_type и формате адреса, привязанном к этому device_type
del_exeption
во многих коммутаторах фирмы Cisco в текущем конфиге может быть строка вида 'ntp clock-period SOME-NUMBER' где SOME-NUMBER' меняется и сравнение конфигов текущего и предыдущего всегда покажет что они различны, хотя это не так.
Если модуль используется для бэкапа конфигов оборудования (или сравнения) в конфигурационном файле должен быть словарь вида
{
ignore_list:
\- "some_ignore_lines"
\- "another_some_ignore_lines"
}
templatizator
Функция преобразует вывод на консоль оборудования результата выполнения какой либо команды в список списков с помощью textFSM template.
Могут быть стандартные шаблоны для стандартных наиболее встречающихся команд и тогда функции достаточно передать только device_type, вывод на консоль и аббревиатуру стандартной команды (см. commands.md и commands.yaml) или же вместо аббревиатуры стандартной команды можно передать флаг special = True (специальная, заранее не определенная команда) и имя файла шаблона для этой команды
ПРИМЕР ИСПОЛЬЗОВАНИЯ
Чаще всего в своей работе я открываю редактор (в Windows - LiClipse, в Linux - vim), шаблонно копирую откуда-нибудь
#!/usr/bin/python3
#-*- coding: utf-8 -*-
import re
import yaml
import argparse
import asyncio
import os
import sys
import subprocess
from astarmiko.base import (
Activka,
port_name_normalize,
convert_mac,
is_ip_correct,
nslookup,
setup_config,
snmp_get_oid,
)
setup_config("~/astarmiko/astarmiko.yaml")
ma = Activka("activka_byname.yaml")
from astarmiko.base import ac
и дальше оперирую объектами ma и ac, например
У меня все роутеры в activka_byname.yaml прописаны по их Looppack0 IP (ну это естественно), а дескрипшен на лупбэках просто description router_id
а мне вдруг захотелось description router_id ip_address_of_this_interface
быстро пишу:
routers = ma.filter(levels='R')
with open(file, encoding="utf8") as f:
todo =
yaml.safe
_load(f)
updated_todo = {vendor: [line if not line.startswith('description router-id') else f'{line}{{ma.choose[router][ip]}' for line in commands] for vendor, commands in todo.items() }
for router in routers:
ma.send_command(router, updated_todo[ma.device_type], mode="config")
делаем файл file:
`{'cisco':
- 'interface loopback 0'
- 'description router-id '
'huawei':
- 'interface loopback 0'
- 'description router-id '
'eltex':
- 'interface loopback 0'
- 'description router-id ' }`
и вуаля, по всем маршрутизаторам, причем эта команда то одинакова на всех моих брендах, но ведь могут синтаксис может быть и сильно разный
Еще примеры. Прямо в состав модуля astarmiko входят два скрипта:
scripts/
fh.py
— утилита FindHost: поиск устройства по IP или MAC (с нее то все и началось)scripts/
acm.py
— утилита ACtivkaManagment: CLI для использования библиотеки из командной строки
Для первой fh.py
требуется создать еще один файл networks_byip.yaml - это словарь вида
{3rd_octet: Router_Name}
Для себя я исхожу из предположения (и это не предположение а знание) что чаще всего оперирую сетями с маской /24, даже если локация большая и там много VLAN - все они с маской /24 или /23
Если где то ( на каждой нашей подстанции и т.п.) сеть с маской /24 побита на более мелкие, все они терминируются на одном (или двух в случае VRRP) маршрутизаторе (L3 коммутаторе). Получил этот yaml файл я с помощью своего astarmiko, пройдя по всем устройствам 'R' (функция ma.list_of_all_ip_intf появилась исключительно ради этого хотя она и вторична после ma.send_commands() и ma.getinfo())
Команда fh сама расскажет лучше меня:
fh
usage: fh [-h] [-s SEG] [-r REPEAT] [-f FILE_TO_SAVE] ip
fh: error: the following arguments are required: ip
fh -h
usage: fh [-h] [-s SEG] [-r REPEAT] [-f FILE_TO_SAVE] ip
Поиск хоста по IP/MAC/имени
positional arguments:
ip IP или MAC адрес хоста или имя без домена
optional arguments:
-h, --help show this help message and exit
-s SEG Имя сегмента сети из файла active_by_name.yaml чтобы остановиться введите q используется в случае
ввода MAC адреса
-r REPEAT Искать сразу несколько адресов программа спросит следующий должно быть установлено в True
-f FILE_TO_SAVE сохранить вывод в файл
acm.py
это чистой воды порождение ChatGPT, с помощью которого я доводил до презентабельного вида уже 5 лет работающий код и который предложил мне - а давай создадим CLI к твоему модулю, и я согласился.
Он еще предложил RESTfull API создать, так что и это не проблема, можно сделать, написать какое-нибудь веб приложение и оперировать мышкой (if you want) но я отказался, мне не актуально
acm тоже расскажет сам за себя:
`acm --help usage: acm [-h] --device DEVICE [DEVICE ...] [--cmd CMD] [--cmd-file CMD_FILE] [--conf CONF] [--rsyslog] [--loki] [--elastic]
{show,set}
AstarMiko Async CLI
positional arguments:
{show,set} Operation to perform
optional arguments:
-h, --help show this help message and exit
--device DEVICE [DEVICE ...]
Device name(s)
--cmd CMD Command as string or JSON
--cmd-file CMD_FILE Path to file with commands in JSON or txt format
--conf CONF Config file path
--rsyslog
--loki
--elastic`
например для прописывание IP в decription loopback 0 можно было не писать скрипт на питоне, а выполнить (вывод шумный, потому что включил для примера logging):
`acm --device KUKU-AR6280-2 --cmd-file "/root/loop.txt" set
DEBUG: commands = ['interface LoopBack0', 'description router_id 10.200.140.9']
Executing config commands: 0%| | 0/1Connecting to 10.22.240.9...
{"time": "2025-06-16 11:45:17,895", "level": "INFO", "message": "Connecting to 10.200.140.9..."}
{"time": "2025-06-16 11:45:18,002", "level": "INFO", "message": "Connected (version 2.0, client HUAWEI-1.5)"}
{"time": "2025-06-16 11:45:19,565", "level": "INFO", "message": "Authentication (password) successful!"}
{"time": "2025-06-16 11:45:35,823", "level": "INFO", "message": "{"device": "kuku-ar6280-2", "level": "INFO", "message": "Connecting to 10.200.140.9"}"}
{"time": "2025-06-16 11:45:35,823", "level": "INFO", "message": "{"device": "kuku-ar6280-2", "level": "INFO", "message": "Commands are successfully executed"}"}
Executing config commands: 100%|███████████████████████████████████████████████████████████████████████████| 1/1 [00:22<00:00, 22.16s/it]
{
"success": {
"kuku-ar6280-2": "system-view\nEnter system view, return user view with Ctrl+Z.\n[kuku-AR6280-2]interface LoopBack0\n[kuku-AR6280-2-LoopBack0]description router_id 10.200.140.9\n[nes-AR6280-2-LoopBack0] ^\nError: Unrecognized command found at '^' position."
},
"failed": {},
"unreachable": []
}
не смотрите на якобы ошибку, netmiko по умолчанию вставляет return, на это huawei и ругается, но set config отрабатывает, проверил :-)
если вам нравится bash больше чем python с помощью acm, grep, awk и т.п можете получить инфу, обработать, записать в конфиг
На github в astaraiki/astarmiko/example
лежит пример - config_backup.py
Я его эксплуатирую около 5 лет и он складывает в каталоги с именем SEG_A, SEG_B и т.д. бэкапы конфигов в формате имени файла DevName-YYYYMMDD, работает у меня по крону каждый вечер, но новый файл записывает только если конфигурация устройства с прошлого дня изменилась
Мой ActivkaBackup поддерживает локальный доступ к файлам и по ftp
Если интересно на гитхабе у меня есть ветка variant2
И вот там в astarmiko/base.py класс ActivkaBackup с фантазиями AI и с доступом по scp и по sftp, но так как протестировать я не могу и не хочу, я отказался от этого, оставил проверенный годами код
P.S.
Вы конечно скажете - при таком количестве активки нормальные люди используют ansible, документируют в netbox и вообще не комильфо. И скорее всего Вы абсолютно правы
НО! кто сказал что я нормальный :-) ?
или по другому - когда ты админ филиала где над тобой в головной конторе по твоим направлениям отвечает человек 6-7, каждый за свое, а ты с с еще двумя товарищами за все + еще столько же - руки не доходят. Этим летом, например, на энтузиазме пробуем внедрять netbox, но через 3 недели стартует кампания по переводу 400 пользователей с винды на линукс и все, забыли о свободном времени...
И потом, не забывайте что это был учебный проект, который имеет место быть
и ....
pip(3) install astarmiko
И может вам понравится!