1.1.0
This commit is contained in:
sergey 2025-06-18 13:08:12 +00:00
commit a8f094eb53
9 changed files with 85 additions and 21 deletions

2
.gitignore vendored
View File

@ -10,4 +10,4 @@ app/config/
!app/config/config-template.ini !app/config/config-template.ini
!app/config/.env-template !app/config/.env-template
app/backups/ app/backups/
!app/backups/.keep !/app/backups/.keep

View File

@ -10,9 +10,8 @@ Python 3.13
- "netmiko>=4.5.0" - для подключения к сетевым устройствам - "netmiko>=4.5.0" - для подключения к сетевым устройствам
- "schedule>=1.2.2" - для периодического выполнения backup - "schedule>=1.2.2" - для периодического выполнения backup
Для запуска. Для запуска.
Переменные окружения (см. app/config/.env-template) Переменные окружения (см. app/config/.env-template)
app/config/config.ini app/config/config.ini
Пример конфиг файла см. app/config/config-template.ini Пример конфиг файла см. app/config/config-template.ini
Положить файл со списком устройств в папку config

View File

@ -36,7 +36,7 @@ def read_device_list() -> dict[str, dict[str, str]]:
return all_devices return all_devices
def connect_to_device(vendor: str, host: str) -> ConnectHandler: def connect_to_device(vendor: str, host: str, name: str="") -> ConnectHandler:
conn_conf: dict = { conn_conf: dict = {
"host": host, "host": host,
"username": cfg.net_dev.user, "username": cfg.net_dev.user,
@ -61,14 +61,14 @@ def connect_to_device(vendor: str, host: str) -> ConnectHandler:
conn_conf["username"] = cfg.net_dev.domain + "\\" + cfg.net_dev.user conn_conf["username"] = cfg.net_dev.domain + "\\" + cfg.net_dev.user
try: try:
connection: ConnectHandler = ConnectHandler(**conn_conf) connection: ConnectHandler = ConnectHandler(**conn_conf)
log.info("connect to %r", host) log.info("connect to %s %s", name, host)
return connection return connection
except exceptions.NetmikoAuthenticationException: except exceptions.NetmikoAuthenticationException:
log.warning("Authentication error %r ", host) log.warning("Authentication error %s %s ", name, host)
except exceptions.NetmikoTimeoutException: except exceptions.NetmikoTimeoutException:
log.warning("Connection time out error %r ", host) log.warning("Connection time out error %s %s ", name, host)
except Exception as e: except Exception as e:
log.warning("Connection error %r: %r", host, e) log.warning("Connection error %s %s: %s", name, host, e)
def send_command(connection: ConnectHandler, command: str) -> str: def send_command(connection: ConnectHandler, command: str) -> str:
@ -84,6 +84,7 @@ def save_mikrotik_bcp(host: str, name: str) -> None:
connection = connect_to_device( connection = connect_to_device(
vendor="mikrotik", vendor="mikrotik",
host=host, host=host,
name=name,
) )
if connection is None: if connection is None:
return return
@ -112,7 +113,7 @@ def save_snr_bcp(host: str, name: str) -> None:
connection.disconnect() connection.disconnect()
log.info("disconnected from %r", name) log.info("disconnected from %r", name)
if result == "NetmikoTimeoutException": if result == "NetmikoTimeoutException":
log.warning("Timeout read config from %r", name) log.warning("Timeout read config from %s", name)
return return
with open(os.path.join(cfg.bcp.dir, name), "w") as f: with open(os.path.join(cfg.bcp.dir, name), "w") as f:
f.write(result) f.write(result)
@ -130,14 +131,13 @@ def save_cisco_sb_bcp(host: str, name: str) -> None:
return return
result = send_command(connection, "show running-config") result = send_command(connection, "show running-config")
connection.disconnect() connection.disconnect()
log.info("disconnected from %r", name) log.info("disconnected from %s", name)
if result == "NetmikoTimeoutException": if result == "NetmikoTimeoutException":
log.warning("Timeout read config from %r", name) log.warning("Timeout read config from %s", name)
return return
with open(os.path.join(cfg.bcp.dir, name), "w") as f: with open(os.path.join(cfg.bcp.dir, name), "w") as f:
f.write(result) f.write(result)
log.info("Backup saved") log.info("Backup saved")
add_file_and_commit(file_name=name) add_file_and_commit(file_name=name)
@ -157,5 +157,4 @@ def save_cisco_bcp(host: str, name: str) -> None:
with open(os.path.join(cfg.bcp.dir, name), "w") as f: with open(os.path.join(cfg.bcp.dir, name), "w") as f:
f.write(result) f.write(result)
log.info("Backup saved") log.info("Backup saved")
add_file_and_commit(file_name=name) add_file_and_commit(file_name=name)

View File

@ -1,6 +1,5 @@
from dataclasses import dataclass from dataclasses import dataclass
import logging import logging
import configparser import configparser
import os import os
@ -81,6 +80,19 @@ class ConfigGit:
username: str = os.getenv("GIT_USERNAME") username: str = os.getenv("GIT_USERNAME")
token: str = os.getenv("GIT_TOKEN") token: str = os.getenv("GIT_TOKEN")
@dataclass
class ConfigMail:
send: bool = config["mail"].getboolean("send")
if send:
user: str = os.getenv('MAIL_USER')
pwd: str = os.getenv('MAIL_PWD')
server: str = config['mail'].get('server')
port: int = config['mail'].getint('port')
from_address : str = config['mail'].get('from_address')
to_address : str = config['mail'].get('to_address')
subject : str = config['mail'].get('subject')
@dataclass() @dataclass()
class ConfigAll: class ConfigAll:
@ -89,6 +101,7 @@ class ConfigAll:
bcp: ConfigBcp = ConfigBcp bcp: ConfigBcp = ConfigBcp
net_dev: ConfigNetDev = ConfigNetDev net_dev: ConfigNetDev = ConfigNetDev
git: ConfigGit = ConfigGit git: ConfigGit = ConfigGit
mail: ConfigMail = ConfigMail
cfg = ConfigAll cfg = ConfigAll

View File

@ -8,4 +8,10 @@ NET_DEV_PWD=password
# название токена от для подключения к репозиторию # название токена от для подключения к репозиторию
GIT_USERNAME=git_user GIT_USERNAME=git_user
# токен для подключения к репозиторию # токен для подключения к репозиторию
GIT_TOKEN=bla-bla-lba GIT_TOKEN=bla-bla-lba
# пользователь и пароль для отправки почты
MAIL_USER=mail_user
# токен для подключения к репозиторию
MAIL_PWD=bla-bla-lba

View File

@ -3,6 +3,16 @@
# False \ True - включение\отключение отправки оповещения в телеграм # False \ True - включение\отключение отправки оповещения в телеграм
send: False send: False
[mail]
# False \ True - включение\отключение отправки оповещения на почту
send: False
server: smtp.example.com
port: 25
from_address: net-bcp@example.com
to_address: engineer@example.com, network_manager@example.com
subject: Warning in backup network device
# Конфиг гит репозитория # Конфиг гит репозитория
[git] [git]
# False \ True - включение\отключение отправки файлов в удалённый репозиторий # False \ True - включение\отключение отправки файлов в удалённый репозиторий

View File

@ -7,14 +7,22 @@ from backup import (
save_cisco_sb_bcp, save_cisco_sb_bcp,
save_cisco_bcp, save_cisco_bcp,
) )
from messages import send_mail
from repo import check_bcp_repo, push_to_remote from repo import check_bcp_repo, push_to_remote
import schedule import schedule
import time import time
import os
def main(): def main():
check_bcp_repo() check_bcp_repo()
device_dict = read_device_list() device_dict = read_device_list()
log_dir: str = os.path.join(os.path.abspath(os.path.dirname(__file__)), "logs")
log_file: str = os.path.join(log_dir, "logfile.log")
if os.path.isfile(log_file):
with open(log_file, 'w') as file:
file.write('')
log.info("log file clear")
for device in device_dict: for device in device_dict:
current_device = device_dict[device] current_device = device_dict[device]
if current_device["vendor"].lower() == "mikrotik": if current_device["vendor"].lower() == "mikrotik":
@ -40,6 +48,12 @@ def main():
save_cisco_bcp(host=current_device["ip"], name=current_device["name"]) save_cisco_bcp(host=current_device["ip"], name=current_device["name"])
if cfg.git.push: if cfg.git.push:
push_to_remote() push_to_remote()
if cfg.mail.send:
if os.path.isfile(log_file):
with open(log_file, 'r', encoding='utf-8') as file:
text = file.read()
send_mail(text)
if __name__ == "__main__": if __name__ == "__main__":
@ -50,7 +64,7 @@ if __name__ == "__main__":
main() main()
while True: while True:
schedule.run_pending() schedule.run_pending()
time.sleep(60) time.sleep(120)
except KeyboardInterrupt: except KeyboardInterrupt:
log.info("Manual app stopped") log.info("Manual app stopped")
log.info("App stopped") log.info("App stopped")

23
app/messages.py Normal file
View File

@ -0,0 +1,23 @@
import smtplib
from email.message import EmailMessage
import logging as log
from config import cfg
def send_mail(text: str) -> None:
log.info('sending a message')
msg = EmailMessage()
msg['Subject'] = cfg.mail.subject
msg['From'] = cfg.mail.from_address
msg['To'] = cfg.mail.to_address
msg.set_content(text)
try:
smtp = smtplib.SMTP(cfg.mail.server, cfg.mail.port)
smtp.starttls()
smtp.ehlo()
smtp.login(cfg.mail.user, cfg.mail.pwd)
smtp.send_message(msg)
smtp.quit()
log.info('message sent successfully')
except smtplib.SMTPException as err:
log.warning(err)

View File

@ -1,6 +1,7 @@
from git import Repo, InvalidGitRepositoryError, Git from git import Repo, InvalidGitRepositoryError, Git
from config import cfg from config import cfg
import logging as log import logging as log
from datetime import datetime
def check_remote_repo(repo: Repo): def check_remote_repo(repo: Repo):
@ -20,7 +21,6 @@ def check_remote_repo(repo: Repo):
log.info( log.info(
"merge from remote repo ", "merge from remote repo ",
) )
else: else:
remote = repo.remote(name=cfg.git.branch) remote = repo.remote(name=cfg.git.branch)
remote.set_url( remote.set_url(
@ -48,21 +48,21 @@ def check_bcp_repo(path: str = cfg.bcp.dir) -> None:
config.set_value("user", "email", cfg.git.mail) config.set_value("user", "email", cfg.git.mail)
if cfg.git.push: if cfg.git.push:
check_remote_repo(repo) check_remote_repo(repo)
except Exception as e: except Exception as e:
log.warning("Error: %r", e) log.warning("Error: %r", e)
def add_file_and_commit(file_name: str) -> None: def add_file_and_commit(file_name: str) -> None:
current_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
repo = Repo(cfg.bcp.dir) repo = Repo(cfg.bcp.dir)
if file_name in repo.untracked_files: if file_name in repo.untracked_files:
repo.git.add(file_name) repo.git.add(file_name)
repo.index.commit("add %s %s" % (file_name, current_time))
log.info("%r added in repo", file_name) log.info("%r added in repo", file_name)
repo.index.commit("add file %s" % file_name)
if bool(repo.git.diff(file_name)): if bool(repo.git.diff(file_name)):
repo.index.add([file_name]) repo.index.add([file_name])
repo.index.commit("update file %s" % file_name) repo.index.commit("update %s %s" % (file_name, current_time))
log.info("change in %r commited", file_name) log.info("change in %r commited", file_name)
else: else:
log.info("no changed in file %r", file_name) log.info("no changed in file %r", file_name)