Добавляю уже существующий проект в репозиторий GIT

This commit is contained in:
2026-02-09 20:45:47 +03:00
commit 5bbb585d9f
11 changed files with 2198 additions and 0 deletions

65
1c-migration.py Executable file
View 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
View File

@@ -0,0 +1,16 @@
# Проект: Автоматизация задач с использованием Cursor AI
![Логотип](https://ru.wikipedia.org/wiki/%D0%A4%D0%B0%D0%B9%D0%BB:Cursor_logo.svg)
> Краткое описание проекта
## 🚀 Работа с 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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