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/.env-template
app/backups/
!app/backups/.keep
!/app/backups/.keep

View File

@ -10,9 +10,8 @@ Python 3.13
- "netmiko>=4.5.0" - для подключения к сетевым устройствам
- "schedule>=1.2.2" - для периодического выполнения backup
Для запуска.
Переменные окружения (см. app/config/.env-template)
app/config/config.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
def connect_to_device(vendor: str, host: str) -> ConnectHandler:
def connect_to_device(vendor: str, host: str, name: str="") -> ConnectHandler:
conn_conf: dict = {
"host": host,
"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
try:
connection: ConnectHandler = ConnectHandler(**conn_conf)
log.info("connect to %r", host)
log.info("connect to %s %s", name, host)
return connection
except exceptions.NetmikoAuthenticationException:
log.warning("Authentication error %r ", host)
log.warning("Authentication error %s %s ", name, host)
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:
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:
@ -84,6 +84,7 @@ def save_mikrotik_bcp(host: str, name: str) -> None:
connection = connect_to_device(
vendor="mikrotik",
host=host,
name=name,
)
if connection is None:
return
@ -112,7 +113,7 @@ def save_snr_bcp(host: str, name: str) -> None:
connection.disconnect()
log.info("disconnected from %r", name)
if result == "NetmikoTimeoutException":
log.warning("Timeout read config from %r", name)
log.warning("Timeout read config from %s", name)
return
with open(os.path.join(cfg.bcp.dir, name), "w") as f:
f.write(result)
@ -130,14 +131,13 @@ def save_cisco_sb_bcp(host: str, name: str) -> None:
return
result = send_command(connection, "show running-config")
connection.disconnect()
log.info("disconnected from %r", name)
log.info("disconnected from %s", name)
if result == "NetmikoTimeoutException":
log.warning("Timeout read config from %r", name)
log.warning("Timeout read config from %s", name)
return
with open(os.path.join(cfg.bcp.dir, name), "w") as f:
f.write(result)
log.info("Backup saved")
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:
f.write(result)
log.info("Backup saved")
add_file_and_commit(file_name=name)

View File

@ -1,6 +1,5 @@
from dataclasses import dataclass
import logging
import configparser
import os
@ -81,6 +80,19 @@ class ConfigGit:
username: str = os.getenv("GIT_USERNAME")
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()
class ConfigAll:
@ -89,6 +101,7 @@ class ConfigAll:
bcp: ConfigBcp = ConfigBcp
net_dev: ConfigNetDev = ConfigNetDev
git: ConfigGit = ConfigGit
mail: ConfigMail = ConfigMail
cfg = ConfigAll

View File

@ -8,4 +8,10 @@ NET_DEV_PWD=password
# название токена от для подключения к репозиторию
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 - включение\отключение отправки оповещения в телеграм
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]
# False \ True - включение\отключение отправки файлов в удалённый репозиторий

View File

@ -7,14 +7,22 @@ from backup import (
save_cisco_sb_bcp,
save_cisco_bcp,
)
from messages import send_mail
from repo import check_bcp_repo, push_to_remote
import schedule
import time
import os
def main():
check_bcp_repo()
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:
current_device = device_dict[device]
if current_device["vendor"].lower() == "mikrotik":
@ -40,6 +48,12 @@ def main():
save_cisco_bcp(host=current_device["ip"], name=current_device["name"])
if cfg.git.push:
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__":
@ -50,7 +64,7 @@ if __name__ == "__main__":
main()
while True:
schedule.run_pending()
time.sleep(60)
time.sleep(120)
except KeyboardInterrupt:
log.info("Manual 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 config import cfg
import logging as log
from datetime import datetime
def check_remote_repo(repo: Repo):
@ -20,7 +21,6 @@ def check_remote_repo(repo: Repo):
log.info(
"merge from remote repo ",
)
else:
remote = repo.remote(name=cfg.git.branch)
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)
if cfg.git.push:
check_remote_repo(repo)
except Exception as e:
log.warning("Error: %r", e)
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)
if file_name in repo.untracked_files:
repo.git.add(file_name)
repo.index.commit("add %s %s" % (file_name, current_time))
log.info("%r added in repo", file_name)
repo.index.commit("add file %s" % file_name)
if bool(repo.git.diff(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)
else:
log.info("no changed in file %r", file_name)