Добавляю уже существующий проект в репозиторий GIT
This commit is contained in:
65
1c-migration.py
Executable file
65
1c-migration.py
Executable file
@@ -0,0 +1,65 @@
|
||||
#!/usr/bin/python3
|
||||
import ssh as s
|
||||
import config
|
||||
|
||||
# Загружаем конфигурацию
|
||||
cfg = config.get_config()
|
||||
|
||||
# Извлекаем настройки из конфигурации
|
||||
server = cfg['ssh']['hostname']
|
||||
arhive_srv_pgsql = cfg['postgresql']['archive_server']
|
||||
restore_srv_pgsql = cfg['postgresql']['restore_server']
|
||||
backup_date = cfg['postgresql']['backup_date']
|
||||
extra = cfg['postgresql']['extra_backup']
|
||||
postgres_user = cfg['postgresql']['postgres_user']
|
||||
postgres_password = cfg['postgresql']['postgres_password']
|
||||
|
||||
arhive_bases_name = cfg['migration']['archive_bases_name']
|
||||
restore_bases_name = cfg['migration']['restore_bases_name']
|
||||
scheduled_jobs_deny = cfg['migration']['scheduled_jobs_deny']
|
||||
sessions_deny = cfg['migration']['sessions_deny']
|
||||
|
||||
lxc_conteiner_name = cfg['c1']['lxc_container_name']
|
||||
c1_claster_user = cfg['c1']['cluster_user']
|
||||
c1_claster_pass = cfg['c1']['cluster_password']
|
||||
db_server = cfg['c1']['db_server']
|
||||
db_name = cfg['c1'].get('db_name', '') # Если не указано, будет использовано имя базы 1С
|
||||
db_user = cfg['c1']['db_user']
|
||||
db_password = cfg['c1']['db_password']
|
||||
|
||||
infobase_user = cfg['c1']['infobase_user']
|
||||
infobase_password = cfg['c1']['infobase_password']
|
||||
|
||||
|
||||
c = s.ssh(server)
|
||||
c.connect()
|
||||
|
||||
# Устанавливаем конфигурацию кластера 1С
|
||||
c.set_c1_config(lxc_conteiner_name, c1_claster_user, c1_claster_pass)
|
||||
|
||||
#version_c1 = c.cluster_version()
|
||||
#print(f"Версия кластера 1с: {version_c1}")
|
||||
|
||||
#c.cluster_daemon_start()
|
||||
|
||||
#c1_id = c.cluster_id()
|
||||
#print("id кластера 1с: "+c1_id)
|
||||
|
||||
for arhive_base_name, restore_base_name in zip(arhive_bases_name, restore_bases_name):
|
||||
# Используем db_name из конфига, если не указано - используем имя базы 1С
|
||||
actual_db_name = db_name if db_name else arhive_base_name
|
||||
base_update = c.base_info_update(
|
||||
arhive_base_name, db_server, actual_db_name, db_user, db_password,
|
||||
infobase_user, infobase_password,
|
||||
scheduled_jobs_deny, sessions_deny
|
||||
)
|
||||
|
||||
# base_info = c.base_info(
|
||||
# arhive_base_name, infobase_user, infobase_password
|
||||
# )
|
||||
|
||||
# print(f"Название базы : {base_info.get('name')}")
|
||||
# print(f"Сервер баз данных : {base_info.get('db-server')}")
|
||||
# print(f"Пользователь баз данных : {base_info.get('db-user')}")
|
||||
|
||||
c.close()
|
||||
16
README.md
Normal file
16
README.md
Normal file
@@ -0,0 +1,16 @@
|
||||
# Проект: Автоматизация задач с использованием Cursor AI
|
||||
|
||||

|
||||
|
||||
> Краткое описание проекта
|
||||
|
||||
## 🚀 Работа с GIT:
|
||||
```bash
|
||||
lxc shell code
|
||||
cd /root/lib/ssh_client
|
||||
source bin/activate
|
||||
git status
|
||||
git add .
|
||||
git commit -a -m 'Реструктуризировал проект'
|
||||
git push -u origin main
|
||||
exit
|
||||
400
c1_cluster.py
Normal file
400
c1_cluster.py
Normal file
@@ -0,0 +1,400 @@
|
||||
#!/usr/bin/python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Модуль для работы с кластером 1С через SSH
|
||||
"""
|
||||
import logging
|
||||
from typing import Optional, List, Dict
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class C1ClusterOperations:
|
||||
"""
|
||||
Класс для операций с кластером 1С через SSH
|
||||
"""
|
||||
def __init__(self, ssh_client, srv_1c: str = "", c1_claster_user: str = "", c1_claster_pass: str = ""):
|
||||
"""
|
||||
Инициализация модуля 1С кластера
|
||||
|
||||
Args:
|
||||
ssh_client: Экземпляр SSHBase для выполнения команд
|
||||
srv_1c: Имя LXC контейнера с 1С
|
||||
c1_claster_user: Пользователь кластера 1С
|
||||
c1_claster_pass: Пароль кластера 1С
|
||||
"""
|
||||
self.ssh = ssh_client
|
||||
self.srv_1c = srv_1c
|
||||
self.c1_claster_user = c1_claster_user
|
||||
self.c1_claster_pass = c1_claster_pass
|
||||
|
||||
def set_srv_1c(self, srv_1c: str) -> None:
|
||||
"""
|
||||
Установка имени LXC контейнера с 1С
|
||||
|
||||
Args:
|
||||
srv_1c: Имя LXC контейнера с 1С
|
||||
"""
|
||||
if not srv_1c or not isinstance(srv_1c, str) or not srv_1c.strip():
|
||||
raise ValueError("srv_1c должен быть непустой строкой")
|
||||
self.srv_1c = srv_1c
|
||||
logger.info(f"Установлено имя контейнера 1С: {srv_1c}")
|
||||
|
||||
def set_cluster_credentials(self, c1_claster_user: str, c1_claster_pass: str) -> None:
|
||||
"""
|
||||
Установка учетных данных кластера 1С
|
||||
|
||||
Args:
|
||||
c1_claster_user: Пользователь кластера 1С
|
||||
c1_claster_pass: Пароль кластера 1С
|
||||
"""
|
||||
if not c1_claster_user or not isinstance(c1_claster_user, str) or not c1_claster_user.strip():
|
||||
raise ValueError("c1_claster_user должен быть непустой строкой")
|
||||
if not c1_claster_pass or not isinstance(c1_claster_pass, str) or not c1_claster_pass.strip():
|
||||
raise ValueError("c1_claster_pass должен быть непустой строкой")
|
||||
self.c1_claster_user = c1_claster_user
|
||||
self.c1_claster_pass = c1_claster_pass
|
||||
logger.info(f"Установлены учетные данные кластера 1С для пользователя: {c1_claster_user}")
|
||||
|
||||
def set_cluster_user(self, c1_claster_user: str) -> None:
|
||||
"""
|
||||
Установка пользователя кластера 1С
|
||||
|
||||
Args:
|
||||
c1_claster_user: Пользователь кластера 1С
|
||||
"""
|
||||
if not c1_claster_user or not isinstance(c1_claster_user, str) or not c1_claster_user.strip():
|
||||
raise ValueError("c1_claster_user должен быть непустой строкой")
|
||||
self.c1_claster_user = c1_claster_user
|
||||
logger.info(f"Установлен пользователь кластера 1С: {c1_claster_user}")
|
||||
|
||||
def set_cluster_password(self, c1_claster_pass: str) -> None:
|
||||
"""
|
||||
Установка пароля кластера 1С
|
||||
|
||||
Args:
|
||||
c1_claster_pass: Пароль кластера 1С
|
||||
"""
|
||||
if not c1_claster_pass or not isinstance(c1_claster_pass, str) or not c1_claster_pass.strip():
|
||||
raise ValueError("c1_claster_pass должен быть непустой строкой")
|
||||
self.c1_claster_pass = c1_claster_pass
|
||||
logger.info("Пароль кластера 1С обновлен")
|
||||
|
||||
def _validate_config(self) -> None:
|
||||
"""
|
||||
Проверка наличия необходимых конфигурационных параметров
|
||||
|
||||
Raises:
|
||||
ValueError: Если не установлены необходимые параметры
|
||||
"""
|
||||
if not self.srv_1c or not self.srv_1c.strip():
|
||||
raise ValueError("srv_1c не установлен. Используйте set_srv_1c() или передайте при инициализации")
|
||||
if not self.c1_claster_user or not self.c1_claster_user.strip():
|
||||
raise ValueError("c1_claster_user не установлен. Используйте set_cluster_user() или передайте при инициализации")
|
||||
if not self.c1_claster_pass or not self.c1_claster_pass.strip():
|
||||
raise ValueError("c1_claster_pass не установлен. Используйте set_cluster_password() или передайте при инициализации")
|
||||
|
||||
def cluster_version(self) -> str:
|
||||
"""
|
||||
Получение версии кластера 1С
|
||||
|
||||
Returns:
|
||||
str: Версия кластера
|
||||
"""
|
||||
self._validate_config()
|
||||
|
||||
try:
|
||||
logger.info(f"Получение версии кластера 1С из контейнера {self.srv_1c}")
|
||||
std, err = self.ssh.cmd(f'lxc exec {self.srv_1c} -- ls -lh /opt/1cv8/x86_64/ | grep 8.3')
|
||||
if err and err.strip():
|
||||
logger.error(f"Ошибка получения версии кластера: {err}")
|
||||
raise Exception(f"Ошибка получения версии кластера: {err}")
|
||||
version = std.split(' ')[-1].strip()
|
||||
if not version:
|
||||
logger.error("Не удалось определить версию кластера")
|
||||
raise Exception("Не удалось определить версию кластера")
|
||||
logger.info(f"Версия кластера 1С: {version}")
|
||||
return version
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка при получении версии кластера: {e}")
|
||||
raise Exception(f"Ошибка при получении версии кластера: {e}")
|
||||
|
||||
def cluster_daemon_start(self) -> str:
|
||||
"""
|
||||
Запуск демона кластера 1С
|
||||
|
||||
Returns:
|
||||
str: Вывод ошибок (если есть)
|
||||
"""
|
||||
self._validate_config()
|
||||
|
||||
try:
|
||||
logger.info(f"Запуск демона кластера 1С в контейнере {self.srv_1c}")
|
||||
std, err = self.ssh.cmd(f'lxc exec {self.srv_1c} -- /opt/1cv8/x86_64/{self.cluster_version()}/ras --daemon cluster')
|
||||
if err and err.strip():
|
||||
logger.warning(f"Предупреждение при запуске демона: {err}")
|
||||
else:
|
||||
logger.info("Демон кластера 1С запущен успешно")
|
||||
return err
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка при запуске демона кластера: {e}")
|
||||
raise Exception(f"Ошибка при запуске демона кластера: {e}")
|
||||
|
||||
def cluster_id(self) -> str:
|
||||
"""
|
||||
Получение ID кластера 1С
|
||||
|
||||
Returns:
|
||||
str: ID кластера
|
||||
"""
|
||||
self._validate_config()
|
||||
|
||||
try:
|
||||
logger.info(f"Получение ID кластера 1С из контейнера {self.srv_1c}")
|
||||
std, err = self.ssh.cmd(f'lxc exec {self.srv_1c} -- /opt/1cv8/x86_64/{self.cluster_version()}/rac cluster list')
|
||||
if err and err.strip():
|
||||
logger.error(f"Ошибка получения ID кластера: {err}")
|
||||
raise Exception(f"Ошибка получения ID кластера: {err}")
|
||||
if not std or not std.strip():
|
||||
logger.error("Пустой ответ при получении ID кластера")
|
||||
raise Exception("Пустой ответ при получении ID кластера")
|
||||
cluster_id = std.split('\n')[0].split(':')[1].strip()
|
||||
if not cluster_id:
|
||||
logger.error("Не удалось извлечь ID кластера")
|
||||
raise Exception("Не удалось извлечь ID кластера")
|
||||
logger.info(f"ID кластера 1С: {cluster_id}")
|
||||
return cluster_id
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка при получении ID кластера: {e}")
|
||||
raise Exception(f"Ошибка при получении ID кластера: {e}")
|
||||
|
||||
def base_list(self) -> List[Dict[str, List[str]]]:
|
||||
"""
|
||||
Получение списка баз данных 1С
|
||||
|
||||
Returns:
|
||||
list[dict]: Список словарей с информацией о базах данных
|
||||
"""
|
||||
self._validate_config()
|
||||
|
||||
try:
|
||||
logger.info(f"Получение списка баз данных 1С из контейнера {self.srv_1c}")
|
||||
std, err = self.ssh.cmd(
|
||||
f"lxc exec {self.srv_1c} -- /opt/1cv8/x86_64/{self.cluster_version()}/rac "
|
||||
f"infobase summary list --cluster={self.cluster_id()} "
|
||||
f"--cluster-user={self.c1_claster_user} --cluster-pwd='{self.c1_claster_pass}'"
|
||||
)
|
||||
|
||||
if err and err.strip():
|
||||
logger.error(f"Ошибка получения списка баз 1С: {err}")
|
||||
raise Exception(f"Ошибка получения списка баз 1С: {err}")
|
||||
|
||||
list_of_base_info = []
|
||||
# Разделяем по двойным переводам строк (каждая база отделена двумя \n)
|
||||
for raw_info in std.split('\n\n')[:-1]:
|
||||
if not raw_info.strip():
|
||||
continue
|
||||
|
||||
# Исправлено: raw_info уже разделен, не нужно снова split('\n\n')
|
||||
lines = raw_info.split('\n')
|
||||
if len(lines) < 3:
|
||||
continue
|
||||
|
||||
try:
|
||||
bases_info = {
|
||||
"id": lines[0].split(' : ')[1].split() if ' : ' in lines[0] else [],
|
||||
"name": lines[1].split(' : ')[1].split() if ' : ' in lines[1] else [],
|
||||
"description": lines[2].split(' : ')[1].split() if ' : ' in lines[2] else []
|
||||
}
|
||||
list_of_base_info.append(bases_info)
|
||||
except (IndexError, ValueError) as e:
|
||||
# Пропускаем некорректно отформатированные записи
|
||||
continue
|
||||
|
||||
logger.info(f"Найдено {len(list_of_base_info)} баз данных 1С")
|
||||
return list_of_base_info
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка при получении списка баз 1С: {e}")
|
||||
raise Exception(f"Ошибка при получении списка баз 1С: {e}")
|
||||
|
||||
def base_id(self, base_name: str) -> Optional[str]:
|
||||
"""
|
||||
Получает ID базы данных 1С по её имени
|
||||
|
||||
Args:
|
||||
base_name: Имя базы данных
|
||||
|
||||
Returns:
|
||||
str: ID базы данных или None, если база не найдена
|
||||
"""
|
||||
self._validate_config()
|
||||
|
||||
if not base_name or not isinstance(base_name, str) or not base_name.strip():
|
||||
raise ValueError("base_name должен быть непустой строкой")
|
||||
|
||||
try:
|
||||
logger.debug(f"Поиск ID базы данных {base_name} в контейнере {self.srv_1c}")
|
||||
for base in self.base_list():
|
||||
if base.get('name') and len(base['name']) > 0 and base['name'][0] == base_name:
|
||||
if base.get('id') and len(base['id']) > 0:
|
||||
base_id = base['id'][0]
|
||||
logger.info(f"Найден ID базы данных {base_name}: {base_id}")
|
||||
return base_id
|
||||
logger.warning(f"База данных {base_name} не найдена")
|
||||
return None
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка при получении ID базы данных: {e}")
|
||||
raise Exception(f"Ошибка при получении ID базы данных: {e}")
|
||||
|
||||
def base_info(self, base_name: str, infobase_user: str, infobase_password: str) -> Optional[Dict[str, str]]:
|
||||
"""
|
||||
Получение информации о базе данных 1С
|
||||
|
||||
Args:
|
||||
base_name: Имя базы данных
|
||||
infobase_user: Пользователь информационной базы
|
||||
infobase_password: Пароль пользователя информационной базы
|
||||
|
||||
Returns:
|
||||
dict: Словарь с информацией о базе данных или None, если база не найдена
|
||||
"""
|
||||
self._validate_config()
|
||||
|
||||
# Валидация входных данных
|
||||
for param_name, param_value in [
|
||||
('base_name', base_name),
|
||||
('infobase_user', infobase_user),
|
||||
('infobase_password', infobase_password)
|
||||
]:
|
||||
if not param_value or not isinstance(param_value, str) or not param_value.strip():
|
||||
raise ValueError(f"{param_name} должен быть непустой строкой")
|
||||
|
||||
try:
|
||||
base_id = self.base_id(base_name)
|
||||
if not base_id:
|
||||
logger.warning(f"База данных {base_name} не найдена")
|
||||
return None
|
||||
|
||||
logger.info(f"Получение информации о базе данных {base_name} (ID: {base_id})")
|
||||
std, err = self.ssh.cmd(
|
||||
f"lxc exec {self.srv_1c} -- /opt/1cv8/x86_64/{self.cluster_version()}/rac "
|
||||
f"infobase info --cluster={self.cluster_id()} "
|
||||
f"--infobase={base_id} "
|
||||
f"--infobase-user={infobase_user} --infobase-pwd='{infobase_password}' "
|
||||
f"--cluster-user={self.c1_claster_user} --cluster-pwd='{self.c1_claster_pass}'"
|
||||
)
|
||||
|
||||
if err and err.strip():
|
||||
logger.error(f"Ошибка получения информации о базе данных: {err}")
|
||||
raise Exception(f"Ошибка получения информации о базе данных: {err}")
|
||||
|
||||
result = {}
|
||||
|
||||
# Разделяем входную строку по переводам строк
|
||||
lines = std.strip().split('\n')
|
||||
|
||||
for line in lines:
|
||||
# Пропускаем пустые строки
|
||||
if not line.strip():
|
||||
continue
|
||||
|
||||
# Разделяем строку на имя параметра и значение
|
||||
if ':' in line:
|
||||
# Разделяем только по первому вхождению ':'
|
||||
# чтобы корректно обрабатывать значения с двоеточиями
|
||||
parts = line.split(':', 1)
|
||||
|
||||
# Извлекаем и очищаем имя параметра и значение
|
||||
param_name = parts[0].strip()
|
||||
param_value = parts[1].strip() if len(parts) > 1 else ''
|
||||
|
||||
# Добавляем в словарь
|
||||
if param_name: # добавляем только если имя параметра не пустое
|
||||
result[param_name] = param_value
|
||||
|
||||
logger.info(f"Информация о базе данных {base_name} получена успешно")
|
||||
return result
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка при получении информации о базе данных: {e}")
|
||||
raise Exception(f"Ошибка при получении информации о базе данных: {e}")
|
||||
|
||||
def base_info_update(self, base_name: str, db_server: str, db_name: str, db_user: str, db_password: str,
|
||||
infobase_user: str, infobase_password: str,
|
||||
scheduled_jobs_deny: str = "off", sessions_deny: str = "off") -> Optional[str]:
|
||||
"""
|
||||
Обновляет информацию о базе данных 1С
|
||||
|
||||
Args:
|
||||
base_name: Имя базы данных 1С
|
||||
db_server: Сервер PostgreSQL
|
||||
db_name: Имя базы данных PostgreSQL
|
||||
db_user: Пользователь PostgreSQL
|
||||
db_password: Пароль PostgreSQL
|
||||
infobase_user: Пользователь информационной базы
|
||||
infobase_password: Пароль пользователя информационной базы
|
||||
scheduled_jobs_deny: Запрет запланированных заданий (on/off), по умолчанию "off"
|
||||
sessions_deny: Запрет сеансов (on/off), по умолчанию "off"
|
||||
|
||||
Returns:
|
||||
str: ID обновленной базы данных или None при ошибке
|
||||
"""
|
||||
self._validate_config()
|
||||
|
||||
# Валидация входных данных
|
||||
for param_name, param_value in [
|
||||
('base_name', base_name),
|
||||
('db_server', db_server),
|
||||
('db_name', db_name),
|
||||
('db_user', db_user),
|
||||
('infobase_user', infobase_user),
|
||||
('infobase_password', infobase_password)
|
||||
]:
|
||||
if not param_value or not isinstance(param_value, str) or not param_value.strip():
|
||||
raise ValueError(f"{param_name} должен быть непустой строкой")
|
||||
|
||||
# db_password может быть пустой строкой, проверяем только тип
|
||||
if not isinstance(db_password, str):
|
||||
raise ValueError("db_password должен быть строкой")
|
||||
|
||||
# Валидация scheduled_jobs_deny и sessions_deny
|
||||
if scheduled_jobs_deny not in ["on", "off"]:
|
||||
raise ValueError("scheduled_jobs_deny должен быть 'on' или 'off'")
|
||||
if sessions_deny not in ["on", "off"]:
|
||||
raise ValueError("sessions_deny должен быть 'on' или 'off'")
|
||||
|
||||
try:
|
||||
logger.info(f"Обновление информации о базе данных {base_name}")
|
||||
base_id = self.base_id(base_name)
|
||||
if not base_id:
|
||||
logger.error(f"База данных '{base_name}' не найдена")
|
||||
raise Exception(f"База данных '{base_name}' не найдена")
|
||||
|
||||
logger.info(f"Обновление параметров базы данных {base_name} (ID: {base_id})")
|
||||
std, err = self.ssh.cmd(
|
||||
f"lxc exec {self.srv_1c} -- /opt/1cv8/x86_64/{self.cluster_version()}/rac "
|
||||
f"infobase update --cluster={self.cluster_id()} "
|
||||
f"--infobase={base_id} --dbms=PostgreSQL "
|
||||
f"--db-server={db_server} --db-name={db_name} "
|
||||
f"--db-user={db_user} --db-pwd={db_password} "
|
||||
f"--infobase-user={infobase_user} --infobase-pwd='{infobase_password}' "
|
||||
f"--cluster-user={self.c1_claster_user} --cluster-pwd='{self.c1_claster_pass}' "
|
||||
f"--scheduled-jobs-deny={scheduled_jobs_deny} --sessions-deny={sessions_deny}"
|
||||
)
|
||||
|
||||
if err and err.strip():
|
||||
logger.error(f"Ошибка обновления базы данных: {err}")
|
||||
raise Exception(f"Ошибка обновления базы данных: {err}")
|
||||
|
||||
# Проверяем, что обновление прошло успешно
|
||||
for base in self.base_list():
|
||||
if base['name'] and len(base['name']) > 0 and base['name'][0] == base_name:
|
||||
if base['id'] and len(base['id']) > 0:
|
||||
logger.info(f"Информация о базе данных {base_name} обновлена успешно")
|
||||
return base['id'][0]
|
||||
logger.warning(f"Не удалось подтвердить обновление базы данных {base_name}")
|
||||
return None
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка при обновлении информации о базе данных: {e}")
|
||||
raise Exception(f"Ошибка при обновлении информации о базе данных: {e}")
|
||||
|
||||
|
||||
112
config.py
Normal file
112
config.py
Normal file
@@ -0,0 +1,112 @@
|
||||
#!/usr/bin/python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Конфигурационный файл для SSH клиента и миграции 1С
|
||||
ВНИМАНИЕ: Не коммитьте этот файл в публичные репозитории!
|
||||
Пароли хранятся в открытом виде в этом файле.
|
||||
"""
|
||||
|
||||
# SSH настройки
|
||||
SSH_CONFIG = {
|
||||
"hostname": "g.it.cln.su",
|
||||
"port": 22222, # Порт должен быть int
|
||||
"username": "root",
|
||||
"pkey_file": "/root/.ssh/id_rsa",
|
||||
"host_keys": "~/.ssh/known_hosts"
|
||||
}
|
||||
|
||||
# PostgreSQL настройки
|
||||
POSTGRESQL_CONFIG = {
|
||||
"archive_server": "1c.it.cln.su",
|
||||
"restore_server": "postgres.it.cln.su",
|
||||
"backup_date": "16.12.2025",
|
||||
"extra_backup": True,
|
||||
"postgres_user": "postgres",
|
||||
"postgres_password": "PrestigePostgres"
|
||||
}
|
||||
|
||||
# 1C настройки
|
||||
C1_CONFIG = {
|
||||
"lxc_container_name": "c1", # Имя LXC контейнера с сервером 1С
|
||||
"cluster_user": "neon",
|
||||
"cluster_password": "Pre$tige310582",
|
||||
|
||||
# Настройки для обновления базы 1С (используются в c1_base_info_update)
|
||||
# "db_server": "/tmp",
|
||||
# "db_user": "usr1cv8",
|
||||
# "db_password": "",
|
||||
|
||||
"db_server": "postgres.it.cln.su",
|
||||
"db_user": "postgres",
|
||||
"db_password": "PrestigePostgres",
|
||||
|
||||
# "db_name": "", # Имя базы данных PostgreSQL (если пустое, будет использовано имя базы 1С)
|
||||
|
||||
"infobase_user": "neon",
|
||||
"infobase_password": "$F%G^H&J*K"
|
||||
}
|
||||
|
||||
# Списки баз данных для миграции и примеров использования
|
||||
MIGRATION_CONFIG = {
|
||||
"archive_bases_name": [
|
||||
# 'konsaltpt-buhg',
|
||||
# 'vpr-ut-crm',
|
||||
# 'quant-ut',
|
||||
# 'kompromis-test',
|
||||
# 'luna-ut',
|
||||
# 'messinia-buhg',
|
||||
# 'morea-buhg',
|
||||
# 'horen-ut',
|
||||
'salon',
|
||||
'lmotor-ut',
|
||||
'staretail',
|
||||
'uran-ut',
|
||||
],
|
||||
"restore_bases_name": None, # Если None, будет использован archive_bases_name
|
||||
"bases": None, # Список баз для обработки в примерах (example_c1_cluster.py, example_postgresql.py)
|
||||
# Если None, будет использован archive_bases_name
|
||||
"scheduled_jobs_deny": "on", # Запрет запланированных заданий для всех баз (on/off)
|
||||
"sessions_deny": "off" # Запрет сеансов для всех баз (on/off)
|
||||
}
|
||||
|
||||
def get_config():
|
||||
"""
|
||||
Возвращает конфигурацию проекта
|
||||
|
||||
Returns:
|
||||
dict: Словарь с конфигурацией, содержащий секции:
|
||||
- ssh: настройки SSH подключения
|
||||
- postgresql: настройки PostgreSQL
|
||||
- c1: настройки 1С кластера
|
||||
- migration: настройки миграции баз данных (включая список баз для примеров)
|
||||
"""
|
||||
config = {
|
||||
'ssh': SSH_CONFIG.copy(),
|
||||
'postgresql': POSTGRESQL_CONFIG.copy(),
|
||||
'c1': C1_CONFIG.copy(),
|
||||
'migration': MIGRATION_CONFIG.copy()
|
||||
}
|
||||
|
||||
# Если restore_bases_name не указан, используем archive_bases_name
|
||||
if config['migration']['restore_bases_name'] is None:
|
||||
config['migration']['restore_bases_name'] = config['migration']['archive_bases_name'].copy()
|
||||
|
||||
# Если bases не указан, используем archive_bases_name для примеров
|
||||
if config['migration']['bases'] is None:
|
||||
config['migration']['bases'] = config['migration']['archive_bases_name'].copy()
|
||||
|
||||
# Валидация scheduled_jobs_deny и sessions_deny
|
||||
scheduled_jobs_deny = config['migration'].get('scheduled_jobs_deny', 'off')
|
||||
sessions_deny = config['migration'].get('sessions_deny', 'off')
|
||||
|
||||
if scheduled_jobs_deny not in ['on', 'off']:
|
||||
raise ValueError(f"scheduled_jobs_deny должен быть 'on' или 'off', получено: {scheduled_jobs_deny}")
|
||||
if sessions_deny not in ['on', 'off']:
|
||||
raise ValueError(f"sessions_deny должен быть 'on' или 'off', получено: {sessions_deny}")
|
||||
|
||||
# Устанавливаем значения по умолчанию, если не указаны
|
||||
config['migration']['scheduled_jobs_deny'] = scheduled_jobs_deny
|
||||
config['migration']['sessions_deny'] = sessions_deny
|
||||
|
||||
return config
|
||||
|
||||
385
example_c1_cluster.py
Executable file
385
example_c1_cluster.py
Executable file
@@ -0,0 +1,385 @@
|
||||
#!/usr/bin/python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Примеры использования модуля c1_cluster.py новым способом
|
||||
Демонстрирует прямое использование C1ClusterOperations без основного класса ssh
|
||||
"""
|
||||
import config
|
||||
from ssh_base import SSHBase
|
||||
from c1_cluster import C1ClusterOperations
|
||||
|
||||
|
||||
def example_basic_usage():
|
||||
"""
|
||||
Пример базового использования модуля c1_cluster
|
||||
"""
|
||||
print("=" * 60)
|
||||
print("Пример 1: Базовое использование модуля c1_cluster")
|
||||
print("=" * 60)
|
||||
|
||||
# Загружаем конфигурацию
|
||||
cfg = config.get_config()
|
||||
|
||||
# Создаем SSH подключение
|
||||
ssh_client = SSHBase(
|
||||
hostname=cfg['ssh']['hostname'],
|
||||
port=cfg['ssh']['port'],
|
||||
username=cfg['ssh']['username'],
|
||||
pkey_file=cfg['ssh']['pkey_file'],
|
||||
host_keys=cfg['ssh']['host_keys']
|
||||
)
|
||||
|
||||
# Подключаемся
|
||||
ssh_client.connect()
|
||||
|
||||
# Создаем экземпляр модуля 1С кластера с параметрами из конфига
|
||||
lxc_container = cfg['c1']['lxc_container_name']
|
||||
cluster_user = cfg['c1']['cluster_user']
|
||||
cluster_password = cfg['c1']['cluster_password']
|
||||
c1_cluster = C1ClusterOperations(ssh_client, lxc_container, cluster_user, cluster_password)
|
||||
|
||||
# Получаем версию кластера
|
||||
version = c1_cluster.cluster_version()
|
||||
print(f"Версия кластера 1С: {version}")
|
||||
|
||||
# Получаем ID кластера
|
||||
cluster_id = c1_cluster.cluster_id()
|
||||
print(f"ID кластера 1С: {cluster_id}")
|
||||
|
||||
# Закрываем соединение
|
||||
ssh_client.close()
|
||||
print("\n")
|
||||
|
||||
|
||||
def example_get_base_list():
|
||||
"""
|
||||
Пример получения списка баз данных 1С
|
||||
"""
|
||||
print("=" * 60)
|
||||
print("Пример 2: Получение списка баз данных 1С")
|
||||
print("=" * 60)
|
||||
|
||||
cfg = config.get_config()
|
||||
|
||||
ssh_client = SSHBase(
|
||||
hostname=cfg['ssh']['hostname'],
|
||||
port=cfg['ssh']['port'],
|
||||
username=cfg['ssh']['username'],
|
||||
pkey_file=cfg['ssh']['pkey_file']
|
||||
)
|
||||
ssh_client.connect()
|
||||
|
||||
# Создаем экземпляр модуля 1С кластера с параметрами из конфига
|
||||
lxc_container = cfg['c1']['lxc_container_name']
|
||||
cluster_user = cfg['c1']['cluster_user']
|
||||
cluster_password = cfg['c1']['cluster_password']
|
||||
c1_cluster = C1ClusterOperations(ssh_client, lxc_container, cluster_user, cluster_password)
|
||||
|
||||
# Получаем список баз данных
|
||||
bases = c1_cluster.base_list()
|
||||
|
||||
print(f"Найдено баз данных: {len(bases)}")
|
||||
for base in bases:
|
||||
if base.get('name') and len(base['name']) > 0:
|
||||
base_name = base['name'][0]
|
||||
base_id = base['id'][0] if base.get('id') and len(base['id']) > 0 else 'N/A'
|
||||
print(f" - {base_name} (ID: {base_id})")
|
||||
|
||||
ssh_client.close()
|
||||
print("\n")
|
||||
|
||||
|
||||
def example_get_base_info():
|
||||
"""
|
||||
Пример получения информации о конкретных базах данных из списка в конфиге
|
||||
"""
|
||||
print("=" * 60)
|
||||
print("Пример 3: Получение информации о базах данных 1С")
|
||||
print("=" * 60)
|
||||
|
||||
cfg = config.get_config()
|
||||
|
||||
ssh_client = SSHBase(
|
||||
hostname=cfg['ssh']['hostname'],
|
||||
port=cfg['ssh']['port'],
|
||||
username=cfg['ssh']['username'],
|
||||
pkey_file=cfg['ssh']['pkey_file']
|
||||
)
|
||||
ssh_client.connect()
|
||||
|
||||
# Создаем экземпляр модуля 1С кластера с параметрами из конфига
|
||||
lxc_container = cfg['c1']['lxc_container_name']
|
||||
cluster_user = cfg['c1']['cluster_user']
|
||||
cluster_password = cfg['c1']['cluster_password']
|
||||
infobase_user = cfg['c1']['infobase_user']
|
||||
infobase_password = cfg['c1']['infobase_password']
|
||||
c1_cluster = C1ClusterOperations(ssh_client, lxc_container, cluster_user, cluster_password)
|
||||
|
||||
# Получаем список баз из конфигурации
|
||||
bases_to_process = cfg['migration'].get('bases', [])
|
||||
|
||||
if not bases_to_process:
|
||||
print("Список баз для обработки не указан в конфигурации (migration.bases)")
|
||||
ssh_client.close()
|
||||
return
|
||||
|
||||
print(f"Обработка {len(bases_to_process)} баз данных из конфигурации:")
|
||||
print(f" Список баз: {', '.join(bases_to_process)}\n")
|
||||
|
||||
# Обрабатываем каждую базу из списка
|
||||
for base_name in bases_to_process:
|
||||
print(f"{'=' * 60}")
|
||||
print(f"База данных: {base_name}")
|
||||
print(f"{'=' * 60}")
|
||||
|
||||
try:
|
||||
# Получаем ID базы
|
||||
base_id = c1_cluster.base_id(base_name)
|
||||
if base_id:
|
||||
print(f"ID базы данных '{base_name}': {base_id}")
|
||||
|
||||
# Получаем полную информацию о базе
|
||||
base_info = c1_cluster.base_info(base_name, infobase_user, infobase_password)
|
||||
|
||||
if base_info:
|
||||
print(f"\nИнформация о базе данных '{base_name}':")
|
||||
for key, value in base_info.items():
|
||||
print(f" {key}: {value}")
|
||||
else:
|
||||
print(f"Не удалось получить информацию о базе '{base_name}'")
|
||||
else:
|
||||
print(f"База данных '{base_name}' не найдена в кластере")
|
||||
except Exception as e:
|
||||
print(f"Ошибка при обработке базы '{base_name}': {e}")
|
||||
|
||||
print()
|
||||
|
||||
ssh_client.close()
|
||||
print("\n")
|
||||
|
||||
|
||||
def example_update_base_info():
|
||||
"""
|
||||
Пример обновления информации о базах данных 1С из списка в конфиге
|
||||
"""
|
||||
print("=" * 60)
|
||||
print("Пример 4: Обновление информации о базах данных 1С")
|
||||
print("=" * 60)
|
||||
|
||||
cfg = config.get_config()
|
||||
|
||||
ssh_client = SSHBase(
|
||||
hostname=cfg['ssh']['hostname'],
|
||||
port=cfg['ssh']['port'],
|
||||
username=cfg['ssh']['username'],
|
||||
pkey_file=cfg['ssh']['pkey_file']
|
||||
)
|
||||
ssh_client.connect()
|
||||
|
||||
# Создаем экземпляр модуля 1С кластера с параметрами из конфига
|
||||
lxc_container = cfg['c1']['lxc_container_name']
|
||||
cluster_user = cfg['c1']['cluster_user']
|
||||
cluster_password = cfg['c1']['cluster_password']
|
||||
infobase_user = cfg['c1']['infobase_user']
|
||||
infobase_password = cfg['c1']['infobase_password']
|
||||
c1_cluster = C1ClusterOperations(ssh_client, lxc_container, cluster_user, cluster_password)
|
||||
|
||||
# Параметры базы данных PostgreSQL
|
||||
db_server = cfg['c1']['db_server']
|
||||
db_name = cfg['c1'].get('db_name', '')
|
||||
db_user = cfg['c1']['db_user']
|
||||
db_password = cfg['c1']['db_password']
|
||||
|
||||
# Параметры управления доступом из конфигурации миграции
|
||||
scheduled_jobs_deny = cfg['migration'].get('scheduled_jobs_deny', 'off')
|
||||
sessions_deny = cfg['migration'].get('sessions_deny', 'off')
|
||||
|
||||
# Получаем список баз из конфигурации
|
||||
bases_to_process = cfg['migration'].get('bases', [])
|
||||
|
||||
if not bases_to_process:
|
||||
print("Список баз для обработки не указан в конфигурации (migration.bases)")
|
||||
ssh_client.close()
|
||||
return
|
||||
|
||||
print(f"Обработка {len(bases_to_process)} баз данных из конфигурации:")
|
||||
print(f" Список баз: {', '.join(bases_to_process)}")
|
||||
print(f" Параметры подключения:")
|
||||
print(f" Сервер БД: {db_server}")
|
||||
print(f" Пользователь БД: {db_user}")
|
||||
print(f" db_name из конфига: {db_name if db_name else '(будет использовано имя базы 1С)'}")
|
||||
print(f" Параметры управления доступом:")
|
||||
print(f" Запрет запланированных заданий (scheduled_jobs_deny): {scheduled_jobs_deny}")
|
||||
print(f" Запрет сеансов (sessions_deny): {sessions_deny}")
|
||||
print()
|
||||
|
||||
# Обрабатываем каждую базу из списка
|
||||
for base_name in bases_to_process:
|
||||
print(f"{'=' * 60}")
|
||||
print(f"База данных: {base_name}")
|
||||
print(f"{'=' * 60}")
|
||||
|
||||
try:
|
||||
# Если db_name не указан, используем имя базы 1С
|
||||
actual_db_name = db_name if db_name else base_name
|
||||
|
||||
print(f" Обновление параметров:")
|
||||
print(f" Сервер БД: {db_server}")
|
||||
print(f" Имя БД: {actual_db_name}")
|
||||
print(f" Пользователь БД: {db_user}")
|
||||
print(f" Запрет запланированных заданий: {scheduled_jobs_deny}")
|
||||
print(f" Запрет сеансов: {sessions_deny}")
|
||||
|
||||
# Обновляем информацию о базе
|
||||
updated_base_id = c1_cluster.base_info_update(
|
||||
base_name, db_server, actual_db_name, db_user, db_password,
|
||||
infobase_user, infobase_password,
|
||||
scheduled_jobs_deny, sessions_deny
|
||||
)
|
||||
|
||||
if updated_base_id:
|
||||
print(f" ✓ База данных успешно обновлена. ID: {updated_base_id}")
|
||||
else:
|
||||
print(f" ✗ Не удалось обновить базу данных '{base_name}'")
|
||||
except Exception as e:
|
||||
print(f" ✗ Ошибка при обновлении базы '{base_name}': {e}")
|
||||
|
||||
print()
|
||||
|
||||
ssh_client.close()
|
||||
print("\n")
|
||||
|
||||
|
||||
def example_workflow():
|
||||
"""
|
||||
Пример полного рабочего процесса: получение версии, запуск демона, получение списка баз
|
||||
"""
|
||||
print("=" * 60)
|
||||
print("Пример 5: Полный рабочий процесс")
|
||||
print("=" * 60)
|
||||
|
||||
cfg = config.get_config()
|
||||
|
||||
ssh_client = SSHBase(
|
||||
hostname=cfg['ssh']['hostname'],
|
||||
port=cfg['ssh']['port'],
|
||||
username=cfg['ssh']['username'],
|
||||
pkey_file=cfg['ssh']['pkey_file']
|
||||
)
|
||||
ssh_client.connect()
|
||||
|
||||
# Создаем экземпляр модуля 1С кластера с параметрами из конфига
|
||||
lxc_container = cfg['c1']['lxc_container_name']
|
||||
cluster_user = cfg['c1']['cluster_user']
|
||||
cluster_password = cfg['c1']['cluster_password']
|
||||
c1_cluster = C1ClusterOperations(ssh_client, lxc_container, cluster_user, cluster_password)
|
||||
|
||||
try:
|
||||
# Шаг 1: Получаем версию кластера
|
||||
print("Шаг 1: Получение версии кластера...")
|
||||
version = c1_cluster.cluster_version()
|
||||
print(f" Версия: {version}")
|
||||
|
||||
# Шаг 2: Запускаем демон кластера
|
||||
print("\nШаг 2: Запуск демона кластера...")
|
||||
err = c1_cluster.cluster_daemon_start()
|
||||
if err:
|
||||
print(f" Предупреждение: {err}")
|
||||
else:
|
||||
print(" Демон запущен успешно")
|
||||
|
||||
# Шаг 3: Получаем ID кластера
|
||||
print("\nШаг 3: Получение ID кластера...")
|
||||
cluster_id = c1_cluster.cluster_id()
|
||||
print(f" ID кластера: {cluster_id}")
|
||||
|
||||
# Шаг 4: Получаем список баз данных
|
||||
print("\nШаг 4: Получение списка баз данных...")
|
||||
bases = c1_cluster.base_list()
|
||||
print(f" Найдено баз: {len(bases)}")
|
||||
|
||||
# Шаг 5: Для каждой базы получаем ID
|
||||
print("\nШаг 5: Получение ID для каждой базы...")
|
||||
for base in bases:
|
||||
if base.get('name') and len(base['name']) > 0:
|
||||
base_name = base['name'][0]
|
||||
base_id = c1_cluster.base_id(base_name)
|
||||
print(f" {base_name}: {base_id}")
|
||||
|
||||
except Exception as e:
|
||||
print(f"Ошибка: {e}")
|
||||
finally:
|
||||
ssh_client.close()
|
||||
|
||||
print("\n")
|
||||
|
||||
|
||||
def example_context_manager_style():
|
||||
"""
|
||||
Пример использования в стиле context manager (с try/finally)
|
||||
"""
|
||||
print("=" * 60)
|
||||
print("Пример 6: Использование с обработкой ошибок")
|
||||
print("=" * 60)
|
||||
|
||||
cfg = config.get_config()
|
||||
ssh_client = None
|
||||
|
||||
try:
|
||||
ssh_client = SSHBase(
|
||||
hostname=cfg['ssh']['hostname'],
|
||||
port=cfg['ssh']['port'],
|
||||
username=cfg['ssh']['username'],
|
||||
pkey_file=cfg['ssh']['pkey_file']
|
||||
)
|
||||
ssh_client.connect()
|
||||
|
||||
# Создаем экземпляр модуля 1С кластера с параметрами из конфига
|
||||
lxc_container = cfg['c1']['lxc_container_name']
|
||||
cluster_user = cfg['c1']['cluster_user']
|
||||
cluster_password = cfg['c1']['cluster_password']
|
||||
c1_cluster = C1ClusterOperations(ssh_client, lxc_container, cluster_user, cluster_password)
|
||||
|
||||
# Получаем версию и список баз
|
||||
version = c1_cluster.cluster_version()
|
||||
print(f"Версия кластера: {version}")
|
||||
|
||||
bases = c1_cluster.base_list()
|
||||
print(f"Список баз данных ({len(bases)} шт.):")
|
||||
for base in bases[:3]: # Показываем только первые 3
|
||||
if base.get('name') and len(base['name']) > 0:
|
||||
print(f" - {base['name'][0]}")
|
||||
|
||||
except Exception as e:
|
||||
print(f"Произошла ошибка: {e}")
|
||||
finally:
|
||||
if ssh_client:
|
||||
ssh_client.close()
|
||||
print("Соединение закрыто")
|
||||
|
||||
print("\n")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
print("\n" + "=" * 60)
|
||||
print("Примеры использования модуля c1_cluster.py")
|
||||
print("=" * 60 + "\n")
|
||||
|
||||
# Раскомментируйте нужные примеры для запуска
|
||||
|
||||
# example_basic_usage()
|
||||
# example_get_base_list()
|
||||
# example_get_base_info()
|
||||
example_update_base_info()
|
||||
# example_workflow()
|
||||
# example_context_manager_style()
|
||||
|
||||
print("Для запуска примеров раскомментируйте соответствующие функции в конце файла")
|
||||
print("\nПримеры демонстрируют:")
|
||||
print(" 1. Базовое использование модуля")
|
||||
print(" 2. Получение списка баз данных")
|
||||
print(" 3. Получение информации о базе данных")
|
||||
print(" 4. Обновление информации о базе данных")
|
||||
print(" 5. Полный рабочий процесс")
|
||||
print(" 6. Использование с обработкой ошибок")
|
||||
|
||||
569
example_postgresql.py
Executable file
569
example_postgresql.py
Executable file
@@ -0,0 +1,569 @@
|
||||
#!/usr/bin/python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Примеры использования модуля postgresql.py новым способом
|
||||
Демонстрирует прямое использование PostgreSQLOperations без основного класса ssh
|
||||
"""
|
||||
import config
|
||||
from ssh_base import SSHBase
|
||||
from postgresql import PostgreSQLOperations
|
||||
|
||||
|
||||
def example_basic_usage():
|
||||
"""
|
||||
Пример базового использования модуля postgresql
|
||||
"""
|
||||
print("=" * 60)
|
||||
print("Пример 1: Базовое использование модуля postgresql")
|
||||
print("=" * 60)
|
||||
|
||||
# Загружаем конфигурацию
|
||||
cfg = config.get_config()
|
||||
|
||||
# Создаем SSH подключение
|
||||
ssh_client = SSHBase(
|
||||
hostname=cfg['ssh']['hostname'],
|
||||
port=cfg['ssh']['port'],
|
||||
username=cfg['ssh']['username'],
|
||||
pkey_file=cfg['ssh']['pkey_file'],
|
||||
host_keys=cfg['ssh']['host_keys']
|
||||
)
|
||||
|
||||
# Подключаемся
|
||||
ssh_client.connect()
|
||||
|
||||
# Создаем экземпляр модуля PostgreSQL
|
||||
pg = PostgreSQLOperations(ssh_client)
|
||||
|
||||
# Получаем список баз данных
|
||||
srv_pgsql = cfg['postgresql']['archive_server']
|
||||
bases = pg.bases_list(srv_pgsql)
|
||||
|
||||
print(f"Сервер PostgreSQL: {srv_pgsql}")
|
||||
print(f"Найдено баз данных: {len(bases)}")
|
||||
for base in bases[:5]: # Показываем первые 5
|
||||
print(f" - {base}")
|
||||
|
||||
# Закрываем соединение
|
||||
ssh_client.close()
|
||||
print("\n")
|
||||
|
||||
|
||||
def example_get_bases_list():
|
||||
"""
|
||||
Пример получения и вывода списка баз данных
|
||||
"""
|
||||
print("=" * 60)
|
||||
print("Пример 2: Получение списка баз данных PostgreSQL")
|
||||
print("=" * 60)
|
||||
|
||||
cfg = config.get_config()
|
||||
|
||||
ssh_client = SSHBase(
|
||||
hostname=cfg['ssh']['hostname'],
|
||||
port=cfg['ssh']['port'],
|
||||
username=cfg['ssh']['username'],
|
||||
pkey_file=cfg['ssh']['pkey_file']
|
||||
)
|
||||
ssh_client.connect()
|
||||
|
||||
pg = PostgreSQLOperations(ssh_client)
|
||||
|
||||
srv_pgsql = cfg['postgresql']['archive_server']
|
||||
|
||||
# Получаем список баз данных
|
||||
bases = pg.bases_list(srv_pgsql)
|
||||
|
||||
print(f"Сервер: {srv_pgsql}")
|
||||
print(f"Всего баз данных: {len(bases)}")
|
||||
print("\nСписок баз данных:")
|
||||
pg.bases_list_print(srv_pgsql)
|
||||
|
||||
ssh_client.close()
|
||||
print("\n")
|
||||
|
||||
|
||||
def example_get_bases_size():
|
||||
"""
|
||||
Пример получения размеров баз данных
|
||||
"""
|
||||
print("=" * 60)
|
||||
print("Пример 3: Получение размеров баз данных PostgreSQL")
|
||||
print("=" * 60)
|
||||
|
||||
cfg = config.get_config()
|
||||
|
||||
ssh_client = SSHBase(
|
||||
hostname=cfg['ssh']['hostname'],
|
||||
port=cfg['ssh']['port'],
|
||||
username=cfg['ssh']['username'],
|
||||
pkey_file=cfg['ssh']['pkey_file']
|
||||
)
|
||||
ssh_client.connect()
|
||||
|
||||
pg = PostgreSQLOperations(ssh_client)
|
||||
|
||||
srv_pgsql = cfg['postgresql']['archive_server']
|
||||
|
||||
print(f"Сервер: {srv_pgsql}")
|
||||
print("\nРазмеры баз данных:")
|
||||
print("-" * 60)
|
||||
print(f"{'База данных':<30} | Размер")
|
||||
print("-" * 60)
|
||||
pg.bases_size_print(srv_pgsql)
|
||||
|
||||
ssh_client.close()
|
||||
print("\n")
|
||||
|
||||
|
||||
def example_backup_single_base():
|
||||
"""
|
||||
Пример создания бэкапа одной базы данных
|
||||
"""
|
||||
print("=" * 60)
|
||||
print("Пример 4: Создание бэкапа одной базы данных")
|
||||
print("=" * 60)
|
||||
|
||||
cfg = config.get_config()
|
||||
|
||||
ssh_client = SSHBase(
|
||||
hostname=cfg['ssh']['hostname'],
|
||||
port=cfg['ssh']['port'],
|
||||
username=cfg['ssh']['username'],
|
||||
pkey_file=cfg['ssh']['pkey_file']
|
||||
)
|
||||
ssh_client.connect()
|
||||
|
||||
pg = PostgreSQLOperations(ssh_client)
|
||||
|
||||
srv_pgsql = cfg['postgresql']['archive_server']
|
||||
|
||||
# Получаем список баз и берем первую для примера
|
||||
bases = pg.bases_list(srv_pgsql)
|
||||
if bases:
|
||||
base_name = bases[0]
|
||||
print(f"Создание бэкапа базы данных: {base_name}")
|
||||
print(f"Сервер: {srv_pgsql}")
|
||||
|
||||
try:
|
||||
results = pg.bases_backup(srv_pgsql, base_name)
|
||||
for result in results:
|
||||
if result['success']:
|
||||
print(f"✓ Бэкап базы '{result['base']}' создан успешно")
|
||||
else:
|
||||
print(f"✗ Ошибка при создании бэкапа '{result['base']}': {result['stderr']}")
|
||||
except Exception as e:
|
||||
print(f"Ошибка: {e}")
|
||||
else:
|
||||
print("Базы данных не найдены")
|
||||
|
||||
ssh_client.close()
|
||||
print("\n")
|
||||
|
||||
|
||||
def example_backup_multiple_bases():
|
||||
"""
|
||||
Пример создания бэкапа нескольких баз данных из списка в конфиге
|
||||
"""
|
||||
print("=" * 60)
|
||||
print("Пример 5: Создание бэкапа нескольких баз данных")
|
||||
print("=" * 60)
|
||||
|
||||
cfg = config.get_config()
|
||||
|
||||
ssh_client = SSHBase(
|
||||
hostname=cfg['ssh']['hostname'],
|
||||
port=cfg['ssh']['port'],
|
||||
username=cfg['ssh']['username'],
|
||||
pkey_file=cfg['ssh']['pkey_file']
|
||||
)
|
||||
ssh_client.connect()
|
||||
|
||||
pg = PostgreSQLOperations(ssh_client)
|
||||
|
||||
srv_pgsql = cfg['postgresql']['archive_server']
|
||||
|
||||
# Получаем список баз из конфигурации для примеров
|
||||
bases_to_backup = cfg['migration'].get('bases', [])
|
||||
|
||||
if not bases_to_backup:
|
||||
print("Список баз для бэкапа не указан в конфигурации (migration.bases)")
|
||||
print("Используем все базы с сервера")
|
||||
bases_to_backup = None
|
||||
|
||||
print(f"Сервер: {srv_pgsql}")
|
||||
if bases_to_backup:
|
||||
print(f"Базы для бэкапа: {', '.join(bases_to_backup)}")
|
||||
else:
|
||||
print("Бэкап всех баз данных")
|
||||
|
||||
try:
|
||||
# Если указан список баз, создаем бэкап для каждой
|
||||
if bases_to_backup:
|
||||
for base_name in bases_to_backup:
|
||||
print(f"\nСоздание бэкапа базы: {base_name}")
|
||||
results = pg.bases_backup(srv_pgsql, base_name)
|
||||
for result in results:
|
||||
if result['success']:
|
||||
print(f" ✓ Бэкап базы '{result['base']}' создан успешно")
|
||||
else:
|
||||
print(f" ✗ Ошибка: {result['stderr']}")
|
||||
else:
|
||||
# Бэкап всех баз
|
||||
results = pg.bases_backup(srv_pgsql, None)
|
||||
print(f"\nОбработано баз: {len(results)}")
|
||||
successful = sum(1 for r in results if r['success'])
|
||||
failed = len(results) - successful
|
||||
print(f"Успешно: {successful}, Ошибок: {failed}")
|
||||
|
||||
for result in results:
|
||||
if not result['success']:
|
||||
print(f" ✗ Ошибка при бэкапе '{result['base']}': {result['stderr']}")
|
||||
except Exception as e:
|
||||
print(f"Ошибка: {e}")
|
||||
|
||||
ssh_client.close()
|
||||
print("\n")
|
||||
|
||||
|
||||
def example_backup_all_bases():
|
||||
"""
|
||||
Пример создания бэкапа всех баз данных
|
||||
"""
|
||||
print("=" * 60)
|
||||
print("Пример 6: Создание бэкапа всех баз данных")
|
||||
print("=" * 60)
|
||||
|
||||
cfg = config.get_config()
|
||||
|
||||
ssh_client = SSHBase(
|
||||
hostname=cfg['ssh']['hostname'],
|
||||
port=cfg['ssh']['port'],
|
||||
username=cfg['ssh']['username'],
|
||||
pkey_file=cfg['ssh']['pkey_file']
|
||||
)
|
||||
ssh_client.connect()
|
||||
|
||||
pg = PostgreSQLOperations(ssh_client)
|
||||
|
||||
srv_pgsql = cfg['postgresql']['archive_server']
|
||||
|
||||
print(f"Сервер: {srv_pgsql}")
|
||||
print("Создание бэкапа всех баз данных...")
|
||||
|
||||
try:
|
||||
result = pg.bases_backup_all(srv_pgsql)
|
||||
print("✓ Бэкап всех баз данных завершен успешно")
|
||||
except Exception as e:
|
||||
print(f"✗ Ошибка при создании бэкапа: {e}")
|
||||
|
||||
ssh_client.close()
|
||||
print("\n")
|
||||
|
||||
|
||||
def example_create_and_drop_base():
|
||||
"""
|
||||
Пример создания и удаления базы данных
|
||||
"""
|
||||
print("=" * 60)
|
||||
print("Пример 7: Создание и удаление базы данных")
|
||||
print("=" * 60)
|
||||
|
||||
cfg = config.get_config()
|
||||
|
||||
ssh_client = SSHBase(
|
||||
hostname=cfg['ssh']['hostname'],
|
||||
port=cfg['ssh']['port'],
|
||||
username=cfg['ssh']['username'],
|
||||
pkey_file=cfg['ssh']['pkey_file']
|
||||
)
|
||||
ssh_client.connect()
|
||||
|
||||
pg = PostgreSQLOperations(ssh_client)
|
||||
|
||||
srv_pgsql = cfg['postgresql']['restore_server']
|
||||
test_base_name = "test_base_example"
|
||||
|
||||
print(f"Сервер: {srv_pgsql}")
|
||||
print(f"Тестовая база: {test_base_name}")
|
||||
|
||||
try:
|
||||
# Создаем базу данных
|
||||
print("\n1. Создание базы данных...")
|
||||
pg.base_create(srv_pgsql, test_base_name)
|
||||
print(f"✓ База данных '{test_base_name}' создана")
|
||||
|
||||
# Проверяем, что база создана
|
||||
bases = pg.bases_list(srv_pgsql)
|
||||
if test_base_name in bases:
|
||||
print(f"✓ База '{test_base_name}' найдена в списке баз")
|
||||
|
||||
# Удаляем базу данных
|
||||
print("\n2. Удаление базы данных...")
|
||||
pg.base_drop(srv_pgsql, test_base_name)
|
||||
print(f"✓ База данных '{test_base_name}' удалена")
|
||||
|
||||
# Проверяем, что база удалена
|
||||
bases = pg.bases_list(srv_pgsql)
|
||||
if test_base_name not in bases:
|
||||
print(f"✓ База '{test_base_name}' отсутствует в списке баз")
|
||||
|
||||
except Exception as e:
|
||||
print(f"✗ Ошибка: {e}")
|
||||
|
||||
ssh_client.close()
|
||||
print("\n")
|
||||
|
||||
|
||||
def example_restore_base():
|
||||
"""
|
||||
Пример восстановления базы данных из бэкапа
|
||||
"""
|
||||
print("=" * 60)
|
||||
print("Пример 8: Восстановление базы данных из бэкапа")
|
||||
print("=" * 60)
|
||||
|
||||
cfg = config.get_config()
|
||||
|
||||
ssh_client = SSHBase(
|
||||
hostname=cfg['ssh']['hostname'],
|
||||
port=cfg['ssh']['port'],
|
||||
username=cfg['ssh']['username'],
|
||||
pkey_file=cfg['ssh']['pkey_file']
|
||||
)
|
||||
ssh_client.connect()
|
||||
|
||||
pg = PostgreSQLOperations(ssh_client)
|
||||
|
||||
archive_server = cfg['postgresql']['archive_server']
|
||||
restore_server = cfg['postgresql']['restore_server']
|
||||
backup_date = cfg['postgresql']['backup_date']
|
||||
extra = cfg['postgresql']['extra_backup']
|
||||
|
||||
# Используем первую базу из списка миграции
|
||||
archive_bases = cfg['migration']['archive_bases_name']
|
||||
restore_bases = cfg['migration']['restore_bases_name']
|
||||
|
||||
if archive_bases and restore_bases:
|
||||
for archive_base, restore_base in zip(archive_bases, restore_bases):
|
||||
print(f"Восстановление базы данных:")
|
||||
print(f" Архив: {archive_server}")
|
||||
print(f" База в архиве: {archive_base}")
|
||||
print(f" Сервер восстановления: {restore_server}")
|
||||
print(f" База для восстановления: {restore_base}")
|
||||
print(f" Дата бэкапа: {backup_date}")
|
||||
print(f" Экстра-бэкап: {extra}")
|
||||
|
||||
try:
|
||||
# Сначала удаляем старую базу, если существует
|
||||
print(f"\n1. Удаление старой базы '{restore_base}' (если существует)...")
|
||||
try:
|
||||
pg.base_drop(restore_server, restore_base)
|
||||
print(f" ✓ Старая база удалена")
|
||||
except Exception:
|
||||
print(f" База не существует или уже удалена")
|
||||
|
||||
# Создаем новую базу
|
||||
print(f"\n2. Создание новой базы '{restore_base}'...")
|
||||
pg.base_create(restore_server, restore_base)
|
||||
print(f" ✓ База создана")
|
||||
|
||||
# Восстанавливаем из бэкапа
|
||||
print(f"\n3. Восстановление из бэкапа...")
|
||||
pg.base_restore(
|
||||
archive_server, restore_server, backup_date,
|
||||
archive_base, restore_base, extra
|
||||
)
|
||||
print(f" ✓ База '{restore_base}' восстановлена успешно")
|
||||
|
||||
except Exception as e:
|
||||
print(f" ✗ Ошибка при восстановлении: {e}")
|
||||
|
||||
ssh_client.close()
|
||||
print("\n")
|
||||
|
||||
|
||||
def example_manage_backups():
|
||||
"""
|
||||
Пример управления старыми бэкапами
|
||||
"""
|
||||
print("=" * 60)
|
||||
print("Пример 9: Управление старыми бэкапами")
|
||||
print("=" * 60)
|
||||
|
||||
cfg = config.get_config()
|
||||
|
||||
ssh_client = SSHBase(
|
||||
hostname=cfg['ssh']['hostname'],
|
||||
port=cfg['ssh']['port'],
|
||||
username=cfg['ssh']['username'],
|
||||
pkey_file=cfg['ssh']['pkey_file']
|
||||
)
|
||||
ssh_client.connect()
|
||||
|
||||
pg = PostgreSQLOperations(ssh_client)
|
||||
|
||||
srv_pgsql = cfg['postgresql']['archive_server']
|
||||
backup_path = f'/backup/pgsql/{srv_pgsql}'
|
||||
days_old = 30 # Удалять бэкапы старше 30 дней
|
||||
|
||||
print(f"Путь к бэкапам: {backup_path}")
|
||||
print(f"Удаление бэкапов старше: {days_old} дней")
|
||||
|
||||
try:
|
||||
# Получаем список старых директорий
|
||||
print("\n1. Поиск старых бэкапов...")
|
||||
old_dirs = pg.file_list(backup_path, days_old)
|
||||
if old_dirs.strip():
|
||||
dirs_list = [d.strip() for d in old_dirs.split('\n') if d.strip()]
|
||||
print(f"Найдено директорий для удаления: {len(dirs_list)}")
|
||||
for d in dirs_list[:5]: # Показываем первые 5
|
||||
print(f" - {d}")
|
||||
else:
|
||||
print("Старые бэкапы не найдены")
|
||||
|
||||
# Удаляем старые бэкапы
|
||||
print(f"\n2. Удаление бэкапов старше {days_old} дней...")
|
||||
pg.delete_old_backups(backup_path, days_old)
|
||||
print("✓ Удаление завершено")
|
||||
|
||||
except Exception as e:
|
||||
print(f"✗ Ошибка: {e}")
|
||||
|
||||
ssh_client.close()
|
||||
print("\n")
|
||||
|
||||
|
||||
def example_full_workflow():
|
||||
"""
|
||||
Пример полного рабочего процесса: список -> размеры -> бэкап
|
||||
"""
|
||||
print("=" * 60)
|
||||
print("Пример 10: Полный рабочий процесс")
|
||||
print("=" * 60)
|
||||
|
||||
cfg = config.get_config()
|
||||
|
||||
ssh_client = SSHBase(
|
||||
hostname=cfg['ssh']['hostname'],
|
||||
port=cfg['ssh']['port'],
|
||||
username=cfg['ssh']['username'],
|
||||
pkey_file=cfg['ssh']['pkey_file']
|
||||
)
|
||||
ssh_client.connect()
|
||||
|
||||
pg = PostgreSQLOperations(ssh_client)
|
||||
|
||||
srv_pgsql = cfg['postgresql']['archive_server']
|
||||
|
||||
try:
|
||||
# Шаг 1: Получаем список баз
|
||||
print("Шаг 1: Получение списка баз данных...")
|
||||
bases = pg.bases_list(srv_pgsql)
|
||||
print(f" Найдено баз: {len(bases)}")
|
||||
|
||||
# Шаг 2: Получаем размеры баз
|
||||
print("\nШаг 2: Получение размеров баз данных...")
|
||||
sizes = pg.bases_size(srv_pgsql)
|
||||
print(f" Получены размеры для {len(sizes)} баз")
|
||||
for size_info in sizes[:3]: # Показываем первые 3
|
||||
base_name = size_info[0]
|
||||
size = ''.join(size_info[1]) if size_info[1] else 'N/A'
|
||||
print(f" {base_name}: {size}")
|
||||
|
||||
# Шаг 3: Создаем бэкап первой базы (если есть)
|
||||
if bases:
|
||||
print(f"\nШаг 3: Создание бэкапа базы '{bases[0]}'...")
|
||||
results = pg.bases_backup(srv_pgsql, bases[0])
|
||||
for result in results:
|
||||
if result['success']:
|
||||
print(f" ✓ Бэкап создан успешно")
|
||||
else:
|
||||
print(f" ✗ Ошибка: {result['stderr']}")
|
||||
|
||||
except Exception as e:
|
||||
print(f"Ошибка: {e}")
|
||||
finally:
|
||||
ssh_client.close()
|
||||
|
||||
print("\n")
|
||||
|
||||
|
||||
def example_context_manager_style():
|
||||
"""
|
||||
Пример использования с обработкой ошибок
|
||||
"""
|
||||
print("=" * 60)
|
||||
print("Пример 11: Использование с обработкой ошибок")
|
||||
print("=" * 60)
|
||||
|
||||
cfg = config.get_config()
|
||||
ssh_client = None
|
||||
|
||||
try:
|
||||
ssh_client = SSHBase(
|
||||
hostname=cfg['ssh']['hostname'],
|
||||
port=cfg['ssh']['port'],
|
||||
username=cfg['ssh']['username'],
|
||||
pkey_file=cfg['ssh']['pkey_file']
|
||||
)
|
||||
ssh_client.connect()
|
||||
|
||||
pg = PostgreSQLOperations(ssh_client)
|
||||
|
||||
srv_pgsql = cfg['postgresql']['archive_server']
|
||||
|
||||
# Получаем список баз и их размеры
|
||||
bases = pg.bases_list(srv_pgsql)
|
||||
print(f"Сервер: {srv_pgsql}")
|
||||
print(f"Найдено баз данных: {len(bases)}")
|
||||
|
||||
if bases:
|
||||
print("\nПервые 5 баз данных:")
|
||||
for base in bases[:5]:
|
||||
print(f" - {base}")
|
||||
|
||||
except Exception as e:
|
||||
print(f"Произошла ошибка: {e}")
|
||||
finally:
|
||||
if ssh_client:
|
||||
ssh_client.close()
|
||||
print("\nСоединение закрыто")
|
||||
|
||||
print("\n")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
print("\n" + "=" * 60)
|
||||
print("Примеры использования модуля postgresql.py")
|
||||
print("=" * 60 + "\n")
|
||||
|
||||
# Раскомментируйте нужные примеры для запуска
|
||||
|
||||
# example_basic_usage()
|
||||
example_get_bases_list()
|
||||
# example_get_bases_size()
|
||||
# example_backup_single_base()
|
||||
# example_backup_multiple_bases()
|
||||
# example_backup_all_bases()
|
||||
# example_create_and_drop_base()
|
||||
# example_restore_base()
|
||||
# example_manage_backups()
|
||||
# example_full_workflow()
|
||||
# example_context_manager_style()
|
||||
|
||||
print("Для запуска примеров раскомментируйте соответствующие функции в конце файла")
|
||||
print("\nПримеры демонстрируют:")
|
||||
print(" 1. Базовое использование модуля")
|
||||
print(" 2. Получение списка баз данных")
|
||||
print(" 3. Получение размеров баз данных")
|
||||
print(" 4. Создание бэкапа одной базы")
|
||||
print(" 5. Создание бэкапа нескольких баз из конфига")
|
||||
print(" 6. Создание бэкапа всех баз")
|
||||
print(" 7. Создание и удаление базы данных")
|
||||
print(" 8. Восстановление базы из бэкапа")
|
||||
print(" 9. Управление старыми бэкапами")
|
||||
print(" 10. Полный рабочий процесс")
|
||||
print(" 11. Использование с обработкой ошибок")
|
||||
|
||||
|
||||
368
postgresql.py
Normal file
368
postgresql.py
Normal file
@@ -0,0 +1,368 @@
|
||||
#!/usr/bin/python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Модуль для работы с PostgreSQL через SSH
|
||||
"""
|
||||
import logging
|
||||
from datetime import datetime
|
||||
from typing import Optional, List, Dict, Union
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class PostgreSQLOperations:
|
||||
"""
|
||||
Класс для операций с PostgreSQL через SSH
|
||||
"""
|
||||
def __init__(self, ssh_client):
|
||||
"""
|
||||
Инициализация модуля PostgreSQL
|
||||
|
||||
Args:
|
||||
ssh_client: Экземпляр SSHBase для выполнения команд
|
||||
"""
|
||||
self.ssh = ssh_client
|
||||
|
||||
def bases_list(self, srv_pgsql: str) -> List[str]:
|
||||
"""
|
||||
Получение списка баз данных PostgreSQL
|
||||
|
||||
Args:
|
||||
srv_pgsql: Сервер PostgreSQL
|
||||
|
||||
Returns:
|
||||
list: Список имен баз данных
|
||||
"""
|
||||
try:
|
||||
logger.info(f"Получение списка баз данных PostgreSQL с сервера {srv_pgsql}")
|
||||
std, err = self.ssh.cmd(f'psql -h {srv_pgsql} -U postgres -t -A -q -c "select datname from pg_database"')
|
||||
if err and err.strip():
|
||||
logger.error(f"Ошибка получения списка баз: {err}")
|
||||
raise Exception(f"Ошибка получения списка баз: {err}")
|
||||
|
||||
bases = [b.strip() for b in std.split('\n') if b.strip()]
|
||||
# Удаляем системные базы
|
||||
for sys_base in ['postgres', 'template0', 'template1']:
|
||||
if sys_base in bases:
|
||||
bases.remove(sys_base)
|
||||
logger.info(f"Найдено {len(bases)} баз данных")
|
||||
return bases
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка при получении списка баз данных: {e}")
|
||||
raise Exception(f"Ошибка при получении списка баз данных: {e}")
|
||||
|
||||
def bases_size(self, srv_pgsql: str) -> List[List[Union[str, List[str]]]]:
|
||||
"""
|
||||
Получение размеров всех баз данных PostgreSQL
|
||||
|
||||
Args:
|
||||
srv_pgsql: Сервер PostgreSQL
|
||||
|
||||
Returns:
|
||||
list: Список списков [имя_базы, размер, ошибки]
|
||||
"""
|
||||
if not srv_pgsql or not isinstance(srv_pgsql, str) or not srv_pgsql.strip():
|
||||
raise ValueError("srv_pgsql должен быть непустой строкой")
|
||||
|
||||
try:
|
||||
logger.info(f"Получение размеров баз данных PostgreSQL с сервера {srv_pgsql}")
|
||||
base_size = []
|
||||
for base in self.bases_list(srv_pgsql):
|
||||
std, err = self.ssh.cmd(f'psql -h {srv_pgsql} -U postgres -c "SELECT pg_size_pretty( pg_database_size( \'{base}\' ) );"')
|
||||
base_size.append([base, std.split('\n')[2:-3], err.split('\n')])
|
||||
logger.info(f"Получены размеры для {len(base_size)} баз данных")
|
||||
return base_size
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка при получении размеров баз данных: {e}")
|
||||
raise Exception(f"Ошибка при получении размеров баз данных: {e}")
|
||||
|
||||
def bases_size_print(self, srv_pgsql: str) -> None:
|
||||
"""
|
||||
Вывод размеров баз данных PostgreSQL в консоль
|
||||
|
||||
Args:
|
||||
srv_pgsql: Сервер PostgreSQL
|
||||
"""
|
||||
for base_info in self.bases_size(srv_pgsql):
|
||||
print(f"{base_info[0].ljust(20)} | {''.join(base_info[1])}")
|
||||
|
||||
def bases_list_print(self, srv_pgsql: str) -> None:
|
||||
"""
|
||||
Вывод списка баз данных PostgreSQL в консоль
|
||||
|
||||
Args:
|
||||
srv_pgsql: Сервер PostgreSQL
|
||||
"""
|
||||
for base in self.bases_list(srv_pgsql):
|
||||
print(base)
|
||||
|
||||
def bases_backup(self, srv_pgsql: str, base_name: Optional[str] = None) -> List[Dict[str, Union[str, bool]]]:
|
||||
"""
|
||||
Создание бэкапа базы данных PostgreSQL
|
||||
|
||||
Args:
|
||||
srv_pgsql: Сервер PostgreSQL
|
||||
base_name: Имя базы данных (если None, бэкапятся все базы)
|
||||
|
||||
Returns:
|
||||
list: Список словарей с результатами бэкапа, каждый содержит:
|
||||
- 'base': имя базы данных
|
||||
- 'stdout': стандартный вывод
|
||||
- 'stderr': вывод ошибок
|
||||
- 'success': True/False в зависимости от успешности операции
|
||||
|
||||
Raises:
|
||||
ValueError: При невалидных входных данных
|
||||
Exception: При ошибке создания бэкапа
|
||||
"""
|
||||
# Валидация входных данных
|
||||
if not srv_pgsql or not isinstance(srv_pgsql, str) or not srv_pgsql.strip():
|
||||
raise ValueError("srv_pgsql должен быть непустой строкой")
|
||||
|
||||
if base_name is not None and (not isinstance(base_name, str) or not base_name.strip()):
|
||||
raise ValueError("base_name должен быть None или непустой строкой")
|
||||
|
||||
try:
|
||||
date = datetime.now().strftime("%d.%m.%Y")
|
||||
backup_dir = f'/backup/pgsql/{srv_pgsql}/{date}-extra/'
|
||||
logger.info(f"Создание бэкапа базы данных {base_name or 'всех баз'} с сервера {srv_pgsql}")
|
||||
std, err = self.ssh.cmd(f'mkdir -p {backup_dir}')
|
||||
if err and err.strip():
|
||||
logger.error(f"Ошибка создания директории: {err}")
|
||||
raise Exception(f"Ошибка создания директории: {err}")
|
||||
|
||||
results = []
|
||||
if base_name:
|
||||
logger.info(f"Создание бэкапа базы {base_name}")
|
||||
std, err = self.ssh.cmd(f'pg_dump -h {srv_pgsql} -U postgres -Fd -w {base_name} -j 10 -f {backup_dir}{base_name}.dump')
|
||||
success = not (err and err.strip())
|
||||
results.append({
|
||||
'base': base_name,
|
||||
'stdout': std,
|
||||
'stderr': err,
|
||||
'success': success
|
||||
})
|
||||
if not success:
|
||||
logger.error(f"Ошибка бэкапа базы {base_name}: {err}")
|
||||
raise Exception(f"Ошибка бэкапа базы {base_name}: {err}")
|
||||
logger.info(f"Бэкап базы {base_name} создан успешно")
|
||||
else:
|
||||
bases = self.bases_list(srv_pgsql)
|
||||
logger.info(f"Создание бэкапа {len(bases)} баз данных")
|
||||
for base in bases:
|
||||
logger.info(f"Создание бэкапа базы {base}")
|
||||
std, err = self.ssh.cmd(f'pg_dump -h {srv_pgsql} -U postgres -Fd -w {base} -j 10 -f {backup_dir}{base}.dump')
|
||||
success = not (err and err.strip())
|
||||
results.append({
|
||||
'base': base,
|
||||
'stdout': std,
|
||||
'stderr': err,
|
||||
'success': success
|
||||
})
|
||||
if success:
|
||||
logger.info(f"Бэкап базы {base} создан успешно")
|
||||
else:
|
||||
logger.warning(f"Ошибка при создании бэкапа базы {base}: {err}")
|
||||
return results
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка при создании бэкапа: {e}")
|
||||
raise Exception(f"Ошибка при создании бэкапа: {e}")
|
||||
|
||||
def bases_backup_all(self, srv_pgsql: str) -> str:
|
||||
"""
|
||||
Создание бэкапа всех баз данных PostgreSQL
|
||||
|
||||
Args:
|
||||
srv_pgsql: Сервер PostgreSQL
|
||||
|
||||
Returns:
|
||||
str: Результат последней операции бэкапа
|
||||
"""
|
||||
try:
|
||||
date = datetime.now().strftime("%d.%m.%Y")
|
||||
backup_dir = f'/backup/pgsql/{srv_pgsql}-{date}/'
|
||||
logger.info(f"Создание бэкапа всех баз данных с сервера {srv_pgsql}")
|
||||
std, err = self.ssh.cmd(f'mkdir -p {backup_dir}')
|
||||
if err and err.strip():
|
||||
logger.error(f"Ошибка создания директории: {err}")
|
||||
raise Exception(f"Ошибка создания директории: {err}")
|
||||
|
||||
bases = self.bases_list(srv_pgsql)
|
||||
logger.info(f"Создание бэкапа {len(bases)} баз данных")
|
||||
for base in bases:
|
||||
logger.info(f"Создание бэкапа базы {base}")
|
||||
std, err = self.ssh.cmd(f'pg_dump -h {srv_pgsql} -U postgres -Fd -w {base} -j 10 -f {backup_dir}{base}.dump')
|
||||
if err and err.strip():
|
||||
logger.error(f"Ошибка бэкапа базы {base}: {err}")
|
||||
raise Exception(f"Ошибка бэкапа базы {base}: {err}")
|
||||
logger.info(f"Бэкап базы {base} создан успешно")
|
||||
logger.info(f"Бэкап всех баз данных завершен")
|
||||
return std
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка при создании бэкапа всех баз: {e}")
|
||||
raise Exception(f"Ошибка при создании бэкапа всех баз: {e}")
|
||||
|
||||
def base_create(self, srv_pgsql: str, base_name: str) -> str:
|
||||
"""
|
||||
Создание базы данных PostgreSQL
|
||||
|
||||
Args:
|
||||
srv_pgsql: Сервер PostgreSQL
|
||||
base_name: Имя базы данных
|
||||
|
||||
Returns:
|
||||
str: Результат выполнения команды
|
||||
"""
|
||||
if not srv_pgsql or not isinstance(srv_pgsql, str) or not srv_pgsql.strip():
|
||||
raise ValueError("srv_pgsql должен быть непустой строкой")
|
||||
if not base_name or not isinstance(base_name, str) or not base_name.strip():
|
||||
raise ValueError("base_name должен быть непустой строкой")
|
||||
|
||||
try:
|
||||
logger.info(f"Создание базы данных {base_name} на сервере {srv_pgsql}")
|
||||
std, err = self.ssh.cmd(f'createdb -h {srv_pgsql} -U postgres -w {base_name}')
|
||||
if err and err.strip():
|
||||
logger.error(f"Ошибка создания базы данных: {err}")
|
||||
raise Exception(f"Ошибка создания базы данных: {err}")
|
||||
logger.info(f"База данных {base_name} создана успешно")
|
||||
return std
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка при создании базы данных: {e}")
|
||||
raise Exception(f"Ошибка при создании базы данных: {e}")
|
||||
|
||||
def base_drop(self, srv_pgsql: str, base_name: str) -> str:
|
||||
"""
|
||||
Удаление базы данных PostgreSQL
|
||||
|
||||
Args:
|
||||
srv_pgsql: Сервер PostgreSQL
|
||||
base_name: Имя базы данных
|
||||
|
||||
Returns:
|
||||
str: Результат выполнения команды
|
||||
"""
|
||||
if not srv_pgsql or not isinstance(srv_pgsql, str) or not srv_pgsql.strip():
|
||||
raise ValueError("srv_pgsql должен быть непустой строкой")
|
||||
if not base_name or not isinstance(base_name, str) or not base_name.strip():
|
||||
raise ValueError("base_name должен быть непустой строкой")
|
||||
|
||||
try:
|
||||
logger.info(f"Удаление базы данных {base_name} с сервера {srv_pgsql}")
|
||||
std, err = self.ssh.cmd(f'dropdb -h {srv_pgsql} -U postgres -w {base_name} -f')
|
||||
if err and err.strip():
|
||||
logger.error(f"Ошибка удаления базы данных: {err}")
|
||||
raise Exception(f"Ошибка удаления базы данных: {err}")
|
||||
logger.info(f"База данных {base_name} удалена успешно")
|
||||
return std
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка при удалении базы данных: {e}")
|
||||
raise Exception(f"Ошибка при удалении базы данных: {e}")
|
||||
|
||||
def base_restore(self, arhive_srv_pgsql: str, restore_srv_pgsql: str, backup_date: str,
|
||||
arhive_base_name: str, restore_base_name: str, extra: bool = False) -> str:
|
||||
"""
|
||||
Восстановление базы данных PostgreSQL из бэкапа
|
||||
|
||||
Args:
|
||||
arhive_srv_pgsql: Сервер с архивом
|
||||
restore_srv_pgsql: Сервер для восстановления
|
||||
backup_date: Дата бэкапа
|
||||
arhive_base_name: Имя базы в архиве
|
||||
restore_base_name: Имя базы для восстановления
|
||||
extra: Флаг экстра-бэкапа
|
||||
|
||||
Returns:
|
||||
str: Результат выполнения команды
|
||||
"""
|
||||
# Валидация входных данных
|
||||
for param_name, param_value in [
|
||||
('arhive_srv_pgsql', arhive_srv_pgsql),
|
||||
('restore_srv_pgsql', restore_srv_pgsql),
|
||||
('backup_date', backup_date),
|
||||
('arhive_base_name', arhive_base_name),
|
||||
('restore_base_name', restore_base_name)
|
||||
]:
|
||||
if not param_value or not isinstance(param_value, str) or not param_value.strip():
|
||||
raise ValueError(f"{param_name} должен быть непустой строкой")
|
||||
|
||||
try:
|
||||
if extra:
|
||||
backup_path = f'/backup/pgsql/{arhive_srv_pgsql}/{backup_date}-extra/{arhive_base_name}.dump'
|
||||
else:
|
||||
backup_path = f'/backup/pgsql/{arhive_srv_pgsql}-{backup_date}/{arhive_base_name}.dump'
|
||||
|
||||
logger.info(f"Восстановление базы данных {restore_base_name} из {backup_path}")
|
||||
std, err = self.ssh.cmd(
|
||||
f'pg_restore -h {restore_srv_pgsql} -U postgres -w -Fd -j10 -d {restore_base_name} {backup_path}'
|
||||
)
|
||||
if err and err.strip():
|
||||
logger.error(f"Ошибка восстановления базы данных: {err}")
|
||||
raise Exception(f"Ошибка восстановления базы данных: {err}")
|
||||
logger.info(f"База данных {restore_base_name} восстановлена успешно")
|
||||
return std
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка при восстановлении базы данных: {e}")
|
||||
raise Exception(f"Ошибка при восстановлении базы данных: {e}")
|
||||
|
||||
def file_list(self, path: str, days: Union[int, str]) -> str:
|
||||
"""
|
||||
Получение списка директорий старше указанного количества дней
|
||||
|
||||
Args:
|
||||
path: Путь для поиска
|
||||
days: Количество дней (строка или число)
|
||||
|
||||
Returns:
|
||||
str: Список директорий
|
||||
"""
|
||||
if not path or not isinstance(path, str) or not path.strip():
|
||||
raise ValueError("path должен быть непустой строкой")
|
||||
|
||||
try:
|
||||
days_str = str(days)
|
||||
logger.info(f"Поиск директорий старше {days} дней в {path}")
|
||||
std, err = self.ssh.cmd(f'/usr/bin/find {path} -type d -maxdepth 1 -mtime +{days_str} -print')
|
||||
if err and err.strip():
|
||||
logger.error(f"Ошибка поиска файлов: {err}")
|
||||
raise Exception(f"Ошибка поиска файлов: {err}")
|
||||
return std
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка при получении списка файлов: {e}")
|
||||
raise Exception(f"Ошибка при получении списка файлов: {e}")
|
||||
|
||||
def delete_old_backups(self, path: str, days: Union[int, str]) -> None:
|
||||
"""
|
||||
Удаление старых бэкапов
|
||||
|
||||
Args:
|
||||
path: Путь к директории с бэкапами
|
||||
days: Количество дней (строка или число)
|
||||
"""
|
||||
if not path or not isinstance(path, str) or not path.strip():
|
||||
raise ValueError("path должен быть непустой строкой")
|
||||
|
||||
try:
|
||||
days_str = str(days)
|
||||
logger.info(f"Удаление бэкапов старше {days} дней из {path}")
|
||||
dirs, err = self.ssh.cmd(f'/usr/bin/find {path} -type d -maxdepth 1 -mtime +{days_str} -print')
|
||||
if err and err.strip():
|
||||
logger.error(f"Ошибка поиска директорий: {err}")
|
||||
raise Exception(f"Ошибка поиска директорий: {err}")
|
||||
|
||||
dirs_list = [d.strip() for d in dirs.split('\n') if d.strip()]
|
||||
logger.info(f"Найдено {len(dirs_list)} директорий для удаления")
|
||||
for d in dirs_list:
|
||||
logger.info(f"Удаление директории {d}")
|
||||
std, err = self.ssh.cmd(f'rm -r {d}')
|
||||
if err and err.strip():
|
||||
logger.error(f"Ошибка удаления директории {d}: {err}")
|
||||
raise Exception(f"Ошибка удаления директории {d}: {err}")
|
||||
logger.info(f"Удаление завершено")
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка при удалении старых бэкапов: {e}")
|
||||
raise Exception(f"Ошибка при удалении старых бэкапов: {e}")
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
5
pyvenv.cfg
Normal file
5
pyvenv.cfg
Normal file
@@ -0,0 +1,5 @@
|
||||
home = /usr/bin
|
||||
include-system-site-packages = false
|
||||
version = 3.12.3
|
||||
executable = /usr/bin/python3.12
|
||||
command = /usr/bin/python3 -m venv /root/lib/ssh_client
|
||||
16
requirements.txt
Normal file
16
requirements.txt
Normal file
@@ -0,0 +1,16 @@
|
||||
# Зависимости проекта SSH Client для миграции 1С
|
||||
#
|
||||
# Установка зависимостей:
|
||||
# pip install -r requirements.txt
|
||||
#
|
||||
# Или с указанием версии Python:
|
||||
# python3 -m pip install -r requirements.txt
|
||||
|
||||
# SSH клиент для подключения к удаленным серверам
|
||||
paramiko>=2.12.0,<4.0.0
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
49
ssh.py
Executable file
49
ssh.py
Executable file
@@ -0,0 +1,49 @@
|
||||
#!/usr/bin/python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Основной модуль SSH клиента для миграции 1С
|
||||
Объединяет базовые SSH операции, PostgreSQL и 1С кластер
|
||||
"""
|
||||
from ssh_base import SSHBase
|
||||
from postgresql import PostgreSQLOperations
|
||||
from c1_cluster import C1ClusterOperations
|
||||
|
||||
|
||||
class ssh(SSHBase, PostgreSQLOperations, C1ClusterOperations):
|
||||
"""
|
||||
Класс SSH клиента с поддержкой PostgreSQL и 1С кластера
|
||||
|
||||
Наследует функциональность от:
|
||||
- SSHBase: базовые SSH операции (connect, cmd, close)
|
||||
- PostgreSQLOperations: операции с PostgreSQL
|
||||
- C1ClusterOperations: операции с кластером 1С
|
||||
"""
|
||||
def __init__(self, hostname: str = "test", port: int = 22222, username: str = "root",
|
||||
pkey_file: str = "/root/.ssh/id_rsa", host_keys: str = "~/.ssh/known_hosts") -> None:
|
||||
"""
|
||||
Инициализация SSH клиента
|
||||
|
||||
Args:
|
||||
hostname: Имя хоста или IP адрес
|
||||
port: Порт SSH (int)
|
||||
username: Имя пользователя
|
||||
pkey_file: Путь к приватному ключу
|
||||
host_keys: Путь к файлу known_hosts
|
||||
"""
|
||||
# Инициализируем базовый SSH класс
|
||||
SSHBase.__init__(self, hostname, port, username, pkey_file, host_keys)
|
||||
# Инициализируем модули операций
|
||||
PostgreSQLOperations.__init__(self, self)
|
||||
C1ClusterOperations.__init__(self, self, "", "", "")
|
||||
|
||||
def set_c1_config(self, srv_1c: str, c1_claster_user: str, c1_claster_pass: str) -> None:
|
||||
"""
|
||||
Установка конфигурации кластера 1С
|
||||
|
||||
Args:
|
||||
srv_1c: Имя LXC контейнера с 1С
|
||||
c1_claster_user: Пользователь кластера 1С
|
||||
c1_claster_pass: Пароль кластера 1С
|
||||
"""
|
||||
self.set_srv_1c(srv_1c)
|
||||
self.set_cluster_credentials(c1_claster_user, c1_claster_pass)
|
||||
213
ssh_base.py
Normal file
213
ssh_base.py
Normal file
@@ -0,0 +1,213 @@
|
||||
#!/usr/bin/python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Базовый модуль для SSH подключений
|
||||
"""
|
||||
import paramiko
|
||||
import os
|
||||
import time
|
||||
import logging
|
||||
from typing import List
|
||||
|
||||
# Настройка логирования
|
||||
logger = logging.getLogger(__name__)
|
||||
if not logger.handlers:
|
||||
handler = logging.StreamHandler()
|
||||
formatter = logging.Formatter(
|
||||
'%(asctime)s - %(name)s - %(levelname)s - %(message)s',
|
||||
datefmt='%Y-%m-%d %H:%M:%S'
|
||||
)
|
||||
handler.setFormatter(formatter)
|
||||
logger.addHandler(handler)
|
||||
logger.setLevel(logging.INFO)
|
||||
|
||||
|
||||
class SSHBase:
|
||||
"""
|
||||
Базовый класс для SSH подключений
|
||||
"""
|
||||
def __init__(self, hostname: str = "test", port: int = 22222, username: str = "root",
|
||||
pkey_file: str = "/root/.ssh/id_rsa", host_keys: str = "~/.ssh/known_hosts") -> None:
|
||||
"""
|
||||
Инициализация SSH клиента
|
||||
|
||||
Args:
|
||||
hostname: Имя хоста или IP адрес
|
||||
port: Порт SSH (int)
|
||||
username: Имя пользователя
|
||||
pkey_file: Путь к приватному ключу
|
||||
host_keys: Путь к файлу known_hosts
|
||||
|
||||
Raises:
|
||||
ValueError: При невалидных входных данных
|
||||
FileNotFoundError: При отсутствии файла ключа
|
||||
paramiko.ssh_exception.SSHException: При ошибке инициализации
|
||||
"""
|
||||
# Валидация входных данных
|
||||
if not hostname or not isinstance(hostname, str) or not hostname.strip():
|
||||
raise ValueError("hostname должен быть непустой строкой")
|
||||
|
||||
try:
|
||||
self.port = int(port)
|
||||
if not (1 <= self.port <= 65535):
|
||||
raise ValueError("port должен быть в диапазоне 1-65535")
|
||||
except (ValueError, TypeError):
|
||||
raise ValueError(f"port должен быть целым числом, получено: {type(port).__name__}")
|
||||
|
||||
if not username or not isinstance(username, str) or not username.strip():
|
||||
raise ValueError("username должен быть непустой строкой")
|
||||
|
||||
if not pkey_file or not isinstance(pkey_file, str):
|
||||
raise ValueError("pkey_file должен быть непустой строкой")
|
||||
|
||||
if not os.path.exists(pkey_file):
|
||||
raise FileNotFoundError(f"Файл приватного ключа не найден: {pkey_file}")
|
||||
|
||||
self.hostname = hostname.strip()
|
||||
self.user = username.strip()
|
||||
self.host_keys = host_keys
|
||||
self.s = None
|
||||
self.stdin = None
|
||||
self.stdout = None
|
||||
self.stderr = None
|
||||
|
||||
try:
|
||||
logger.info(f"Инициализация SSH клиента для {hostname}:{port}")
|
||||
self.key = paramiko.RSAKey.from_private_key_file(pkey_file)
|
||||
self.s = paramiko.SSHClient()
|
||||
self.s.load_system_host_keys()
|
||||
if host_keys:
|
||||
expanded_host_keys = os.path.expanduser(host_keys)
|
||||
if os.path.exists(expanded_host_keys):
|
||||
self.s.load_host_keys(expanded_host_keys)
|
||||
self.s.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||
logger.debug("SSH клиент успешно инициализирован")
|
||||
except FileNotFoundError as e:
|
||||
logger.error(f"Не найден файл ключа или known_hosts: {e}")
|
||||
raise FileNotFoundError(f"Не найден файл ключа или known_hosts: {e}")
|
||||
except paramiko.ssh_exception.SSHException as e:
|
||||
logger.error(f"Ошибка инициализации SSH клиента: {e}")
|
||||
raise paramiko.ssh_exception.SSHException(f"Ошибка инициализации SSH клиента: {e}")
|
||||
|
||||
def connect(self) -> None:
|
||||
"""
|
||||
Подключение к SSH серверу
|
||||
|
||||
Raises:
|
||||
paramiko.ssh_exception.SSHException: При ошибке подключения
|
||||
paramiko.ssh_exception.AuthenticationException: При ошибке аутентификации
|
||||
"""
|
||||
if not self.s:
|
||||
logger.error("SSH клиент не инициализирован")
|
||||
raise paramiko.ssh_exception.SSHException("SSH клиент не инициализирован")
|
||||
|
||||
try:
|
||||
logger.info(f"Подключение к {self.hostname}:{self.port}")
|
||||
self.s.connect(self.hostname, self.port, pkey=self.key, username=self.user)
|
||||
logger.info(f"Успешно подключено к {self.hostname}:{self.port}")
|
||||
except paramiko.ssh_exception.AuthenticationException as e:
|
||||
logger.error(f"Ошибка аутентификации при подключении к {self.hostname}:{self.port}: {e}")
|
||||
raise paramiko.ssh_exception.AuthenticationException(f"Ошибка аутентификации: {e}")
|
||||
except paramiko.ssh_exception.SSHException as e:
|
||||
logger.error(f"Ошибка подключения к {self.hostname}:{self.port}: {e}")
|
||||
raise paramiko.ssh_exception.SSHException(f"Ошибка подключения к {self.hostname}:{self.port}: {e}")
|
||||
except Exception as e:
|
||||
logger.error(f"Неожиданная ошибка при подключении к {self.hostname}:{self.port}: {e}")
|
||||
raise Exception(f"Неожиданная ошибка при подключении: {e}")
|
||||
|
||||
def cmd(self, command: str, sleep: float = 0.1, out_to_print: bool = False) -> List[str]:
|
||||
"""
|
||||
Выполнение команды на удаленном сервере
|
||||
|
||||
Args:
|
||||
command: Команда для выполнения
|
||||
sleep: Задержка после выполнения команды (секунды)
|
||||
out_to_print: Не используется (оставлено для обратной совместимости)
|
||||
|
||||
Returns:
|
||||
list: [stdout, stderr] - вывод команды и ошибки
|
||||
|
||||
Raises:
|
||||
ValueError: При невалидных входных данных
|
||||
paramiko.ssh_exception.SSHException: При ошибке выполнения команды
|
||||
"""
|
||||
# Валидация входных данных
|
||||
if not command or not isinstance(command, str) or not command.strip():
|
||||
raise ValueError("command должен быть непустой строкой")
|
||||
|
||||
if not isinstance(sleep, (int, float)) or sleep < 0:
|
||||
raise ValueError("sleep должен быть неотрицательным числом")
|
||||
|
||||
if not self.s:
|
||||
logger.error("SSH клиент не подключен")
|
||||
raise paramiko.ssh_exception.SSHException("SSH клиент не подключен. Вызовите connect() перед выполнением команд.")
|
||||
|
||||
try:
|
||||
logger.debug(f"Выполнение команды: {command}")
|
||||
self.stdin, self.stdout, self.stderr = self.s.exec_command(command)
|
||||
|
||||
# Ждем завершения команды и получаем код возврата
|
||||
exit_status = self.stdout.channel.recv_exit_status()
|
||||
|
||||
time.sleep(sleep)
|
||||
|
||||
stdout_data = self.stdout.read().decode('utf-8', errors='replace')
|
||||
stderr_data = self.stderr.read().decode('utf-8', errors='replace')
|
||||
|
||||
# Если команда завершилась с ошибкой, добавляем информацию в stderr
|
||||
if exit_status != 0:
|
||||
logger.warning(f"Команда завершилась с кодом возврата {exit_status}: {command}")
|
||||
if not stderr_data.strip():
|
||||
stderr_data = f"Команда завершилась с кодом возврата {exit_status}"
|
||||
else:
|
||||
logger.debug(f"Команда выполнена успешно: {command}")
|
||||
|
||||
return [stdout_data, stderr_data]
|
||||
except paramiko.ssh_exception.SSHException as e:
|
||||
logger.error(f"Ошибка выполнения команды '{command}': {e}")
|
||||
raise paramiko.ssh_exception.SSHException(f"Ошибка выполнения команды '{command}': {e}")
|
||||
except Exception as e:
|
||||
logger.error(f"Неожиданная ошибка при выполнении команды '{command}': {e}")
|
||||
raise Exception(f"Неожиданная ошибка при выполнении команды '{command}': {e}")
|
||||
|
||||
def close(self) -> None:
|
||||
"""
|
||||
Закрытие SSH соединения
|
||||
|
||||
Безопасно закрывает все открытые соединения и потоки,
|
||||
игнорируя ошибки, которые могут возникнуть при закрытии
|
||||
уже закрытых соединений.
|
||||
"""
|
||||
logger.info(f"Закрытие SSH соединения с {self.hostname}:{self.port}")
|
||||
# Закрываем потоки в правильном порядке
|
||||
streams_to_close = [
|
||||
('stdin', self.stdin),
|
||||
('stdout', self.stdout),
|
||||
('stderr', self.stderr)
|
||||
]
|
||||
|
||||
for name, stream in streams_to_close:
|
||||
if stream is not None:
|
||||
try:
|
||||
if hasattr(stream, 'close'):
|
||||
stream.close()
|
||||
except (AttributeError, OSError, paramiko.ssh_exception.SSHException):
|
||||
# Игнорируем ошибки при закрытии потоков
|
||||
pass
|
||||
finally:
|
||||
setattr(self, name, None)
|
||||
|
||||
# Закрываем SSH соединение
|
||||
if self.s is not None:
|
||||
try:
|
||||
self.s.close()
|
||||
except (AttributeError, OSError, paramiko.ssh_exception.SSHException):
|
||||
# Игнорируем ошибки при закрытии соединения
|
||||
pass
|
||||
finally:
|
||||
self.s = None
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user