commit
a8f094eb53
|
@ -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
|
||||||
|
|
|
@ -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
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
|
@ -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 - включение\отключение отправки файлов в удалённый репозиторий
|
||||||
|
|
18
app/main.py
18
app/main.py
|
@ -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")
|
||||||
|
|
|
@ -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)
|
|
@ -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)
|
||||||
|
|
Loading…
Reference in New Issue