From 46261f066b3d58157d3c2b53a46da237d29bc68e Mon Sep 17 00:00:00 2001 From: gitops Date: Mon, 4 Aug 2025 12:03:45 +0300 Subject: [PATCH] refactor: restructure library to expose public API directly feat: add new helper methods for RAC operations docs: add comprehensive library usage documentation chore: move example to examples/library_usage directory --- LIBRARY_USAGE.md | 252 +++++++++++++++ benadis_rac.go | 538 ++++++++++++++++++++++++++++----- benadis_rac_helpers.go | 322 ++++++++++++++++++++ example/main.go | 266 ---------------- example/main_test.go | 337 --------------------- examples/library_usage/go.mod | 7 + examples/library_usage/main.go | 108 +++++++ 7 files changed, 1152 insertions(+), 678 deletions(-) create mode 100644 LIBRARY_USAGE.md create mode 100644 benadis_rac_helpers.go delete mode 100644 example/main.go delete mode 100644 example/main_test.go create mode 100644 examples/library_usage/go.mod create mode 100644 examples/library_usage/main.go diff --git a/LIBRARY_USAGE.md b/LIBRARY_USAGE.md new file mode 100644 index 0000000..b77f33a --- /dev/null +++ b/LIBRARY_USAGE.md @@ -0,0 +1,252 @@ +# Использование benadis-rac как библиотеки + +После рефакторинга библиотека benadis-rac может использоваться без импорта внутренних пакетов. + +## Установка + +```bash +go get git.benadis.ru/gitops/benadis-rac +``` + +## Импорт + +```go +import "git.benadis.ru/gitops/benadis-rac" +``` + +## Основное использование + +### Способ 1: Использование функции ManageServiceMode (рекомендуется) + +```go +package main + +import ( + "context" + "time" + benadis_rac "git.benadis.ru/gitops/benadis-rac" +) + +func main() { + // Создаем конфигурацию + config := &benadis_rac.Config{ + // RAC configuration + RACPath: "C:\\Program Files\\1cv8\\8.3.25.1257\\bin\\rac.exe", + ConnectionTimeout: 30 * time.Second, + CommandTimeout: 60 * time.Second, + RetryCount: 3, + RetryDelay: 5 * time.Second, + + // Server configuration + ServerHost: "localhost", + ServerPort: 1541, + RACPort: 1545, + ServerName: "server1c", + BaseName: "test_base", + + // Authentication + ClusterAdmin: "admin", + ClusterAdminPassword: "password", + DBAdmin: "dbadmin", + DBAdminPassword: "dbpassword", + + // Service mode settings + DeniedMessage: "Система находится на техническом обслуживании", + PermissionCode: "maintenance", + } + + // Устанавливаем значения по умолчанию + config.SetDefaults() + + // Валидируем конфигурацию + if err := config.Validate(); err != nil { + panic(err) + } + + // Создаем логгер + logger := benadis_rac.NewLogger("info") + + // Создаем контекст + ctx := context.Background() + + // Включаем сервисный режим + err := benadis_rac.ManageServiceMode(ctx, config, logger, true) + if err != nil { + panic(err) + } + + // Выключаем сервисный режим + err = benadis_rac.ManageServiceMode(ctx, config, logger, false) + if err != nil { + panic(err) + } +} +``` + +### Способ 2: Использование клиента + +```go +package main + +import ( + "context" + benadis_rac "git.benadis.ru/gitops/benadis-rac" +) + +func main() { + // Создаем конфигурацию (аналогично способу 1) + config := &benadis_rac.Config{ + // ... настройки конфигурации + } + config.SetDefaults() + + // Создаем клиент + client, err := benadis_rac.NewClient(config) + if err != nil { + panic(err) + } + + ctx := context.Background() + + // Проверяем текущий статус + enabled, err := client.GetServiceModeStatus(ctx) + if err != nil { + panic(err) + } + + // Включаем сервисный режим + err = client.EnableServiceMode(ctx) + if err != nil { + panic(err) + } + + // Выключаем сервисный режим + err = client.DisableServiceMode(ctx) + if err != nil { + panic(err) + } +} +``` + +## Структура конфигурации + +```go +type Config struct { + // RAC configuration + RACPath string // Путь к исполняемому файлу RAC + ConnectionTimeout time.Duration // Таймаут подключения + CommandTimeout time.Duration // Таймаут выполнения команды + RetryCount int // Количество повторных попыток + RetryDelay time.Duration // Задержка между попытками + + // Server configuration + ServerHost string // Хост сервера 1C + ServerPort int // Порт сервера 1C + RACPort int // Порт RAC + ServerName string // Имя сервера + BaseName string // Имя информационной базы + + // Authentication + ClusterAdmin string // Администратор кластера + ClusterAdminPassword string // Пароль администратора кластера + DBAdmin string // Администратор ИБ + DBAdminPassword string // Пароль администратора ИБ + + // Service mode settings + DeniedMessage string // Сообщение при блокировке + PermissionCode string // Код разрешения +} +``` + +## Интерфейс логгера + +Вы можете использовать встроенный логгер или реализовать свой: + +```go +type Logger interface { + Debug(msg string, args ...any) + Info(msg string, args ...any) + Warn(msg string, args ...any) + Error(msg string, args ...any) + DebugCommand(msg string, command []string) +} + +// Создание встроенного логгера +logger := benadis_rac.NewLogger("info") // debug, info, warn, error +``` + +## Методы управления + +### ManageServiceMode +Основная функция для управления сервисным режимом: +```go +func ManageServiceMode(ctx context.Context, config *Config, logger Logger, enable bool) error +``` + +### Методы клиента +- `EnableServiceMode(ctx context.Context) error` - включить сервисный режим +- `DisableServiceMode(ctx context.Context) error` - выключить сервисный режим +- `GetServiceModeStatus(ctx context.Context) (bool, error)` - получить статус сервисного режима + +## Значения по умолчанию + +```go +const ( + DefaultDeniedMessage = "Техническое обслуживание. Попробуйте позже." + DefaultPermissionCode = "service-mode" + DefaultRACPort = 1545 + DefaultConnectionTimeout = 30 * time.Second + DefaultCommandTimeout = 60 * time.Second + DefaultRetryCount = 3 + DefaultRetryDelay = 5 * time.Second +) +``` + +## Валидация конфигурации + +Обязательные поля: +- `ServerHost` - хост сервера 1C +- `RACPath` - путь к исполняемому файлу RAC +- `BaseName` - имя информационной базы +- `ClusterAdmin` - администратор кластера +- `DBAdmin` - администратор ИБ + +## Обработка ошибок + +Все методы возвращают детализированные ошибки с контекстом: + +```go +if err := client.EnableServiceMode(ctx); err != nil { + log.Printf("Failed to enable service mode: %v", err) +} +``` + +## Миграция с предыдущей версии + +### Было (с внутренними пакетами): +```go +import ( + "git.benadis.ru/gitops/benadis-rac/internal/config" + "git.benadis.ru/gitops/benadis-rac/internal/logger" + "git.benadis.ru/gitops/benadis-rac/internal/service" +) + +// Загрузка из файлов конфигурации +appConfig, err := config.LoadConfig("config.yaml", "secret.yaml", "project.yaml") +log := logger.NewLogger("info") +svc := service.NewServiceModeService(appConfig, log) +``` + +### Стало (только публичный API): +```go +import benadis_rac "git.benadis.ru/gitops/benadis-rac" + +// Прямая передача значений в структуру +config := &benadis_rac.Config{ + RACPath: "path/to/rac", + ServerHost: "localhost", + // ... другие поля +} +logger := benadis_rac.NewLogger("info") +benadis_rac.ManageServiceMode(ctx, config, logger, true) +``` \ No newline at end of file diff --git a/benadis_rac.go b/benadis_rac.go index 5779b5e..cf0c533 100644 --- a/benadis_rac.go +++ b/benadis_rac.go @@ -4,12 +4,51 @@ package benadis_rac import ( "context" "fmt" - - "git.benadis.ru/gitops/benadis-rac/internal/config" - "git.benadis.ru/gitops/benadis-rac/internal/logger" - "git.benadis.ru/gitops/benadis-rac/internal/service" + "log/slog" + "os" + "os/exec" + "path" + "strings" + "time" + "unicode/utf8" ) +// Logger интерфейс для логирования +type Logger interface { + Debug(msg string, args ...any) + Info(msg string, args ...any) + Warn(msg string, args ...any) + Error(msg string, args ...any) + DebugCommand(msg string, command []string) +} + +// Config конфигурация для управления сервисным режимом +type Config struct { + // RAC configuration + RACPath string // Путь к исполняемому файлу RAC + ConnectionTimeout time.Duration // Таймаут подключения + CommandTimeout time.Duration // Таймаут выполнения команды + RetryCount int // Количество повторных попыток + RetryDelay time.Duration // Задержка между попытками + + // Server configuration + ServerHost string // Хост сервера 1C + ServerPort int // Порт сервера 1C + RACPort int // Порт RAC + ServerName string // Имя сервера + BaseName string // Имя информационной базы + + // Authentication + ClusterAdmin string // Администратор кластера + ClusterAdminPassword string // Пароль администратора кластера + DBAdmin string // Администратор ИБ + DBAdminPassword string // Пароль администратора ИБ + + // Service mode settings + DeniedMessage string // Сообщение при блокировке + PermissionCode string // Код разрешения +} + // ServiceModeManager интерфейс для управления сервисным режимом type ServiceModeManager interface { // EnableServiceMode включает сервисный режим @@ -24,101 +63,450 @@ type ServiceModeManager interface { // Client клиент для работы с 1C сервисным режимом type Client struct { - service *service.ServiceModeService + config *Config + logger Logger } -// Config конфигурация для создания клиента -type Config struct { - ConfigPath string // Путь к файлу конфигурации - SecretPath string // Путь к файлу с секретами - ProjectPath string // Путь к файлу проекта - LogLevel string // Уровень логирования (Debug, Info, Warn, Error) +// SlogLogger реализация Logger с использованием slog +type SlogLogger struct { + logger *slog.Logger +} + +// NewLogger создает новый логгер +func NewLogger(level string) Logger { + var logLevel slog.Level + switch strings.ToLower(level) { + case "debug": + logLevel = slog.LevelDebug + case "info": + logLevel = slog.LevelInfo + case "warn": + logLevel = slog.LevelWarn + case "error": + logLevel = slog.LevelError + default: + logLevel = slog.LevelInfo + } + + handler := slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{ + Level: logLevel, + ReplaceAttr: func(groups []string, a slog.Attr) slog.Attr { + if a.Key == slog.SourceKey { + source := a.Value.Any().(*slog.Source) + source.File = path.Base(source.File) + } + return a + }, + }) + + return &SlogLogger{ + logger: slog.New(handler), + } +} + +func (l *SlogLogger) Debug(msg string, args ...any) { + l.logger.Debug(msg, args...) +} + +func (l *SlogLogger) Info(msg string, args ...any) { + l.logger.Info(msg, args...) +} + +func (l *SlogLogger) Warn(msg string, args ...any) { + l.logger.Warn(msg, args...) +} + +func (l *SlogLogger) Error(msg string, args ...any) { + l.logger.Error(msg, args...) +} + +func (l *SlogLogger) DebugCommand(msg string, command []string) { + maskedCommand := maskPasswords(command) + l.logger.Debug(msg, "command", strings.Join(maskedCommand, " ")) +} + +func maskPasswords(command []string) []string { + masked := make([]string, len(command)) + copy(masked, command) + + passwordFlags := []string{"--cluster-pwd", "--infobase-pwd"} + + for i, arg := range masked { + for _, flag := range passwordFlags { + if arg == flag && i+1 < len(masked) { + masked[i+1] = "***" + break + } + } + } + + return masked } // NewClient создает новый клиент для управления сервисным режимом -func NewClient(cfg Config) (*Client, error) { - if cfg.ConfigPath == "" { - cfg.ConfigPath = "config.yaml" - } - if cfg.SecretPath == "" { - cfg.SecretPath = "secret.yaml" - } - if cfg.ProjectPath == "" { - cfg.ProjectPath = "project.yaml" - } - if cfg.LogLevel == "" { - cfg.LogLevel = "Info" - } - - // Загружаем конфигурацию - appConfig, err := config.LoadConfig(cfg.ConfigPath, cfg.SecretPath, cfg.ProjectPath) - if err != nil { - return nil, fmt.Errorf("failed to load config: %w", err) - } - - // Переопределяем уровень логирования если указан - if appConfig.Project != nil && appConfig.Project.ServiceMode != nil { - appConfig.Project.ServiceMode.LogLevel = cfg.LogLevel - } - - // Валидируем конфигурацию - if err := appConfig.Validate(); err != nil { - return nil, fmt.Errorf("config validation failed: %w", err) +func NewClient(config *Config) (*Client, error) { + if config == nil { + return nil, fmt.Errorf("config cannot be nil") } // Создаем логгер - logLevel := "Info" - if appConfig.Project != nil && appConfig.Project.ServiceMode != nil && appConfig.Project.ServiceMode.LogLevel != "" { - logLevel = appConfig.Project.ServiceMode.LogLevel - } - log := logger.NewLogger(logLevel) - - // Создаем сервис - svc := service.NewServiceModeService(appConfig, log) + logger := NewLogger("info") return &Client{ - service: svc, + config: config, + logger: logger, }, nil } +// ManageServiceMode основная функция для управления сервисным режимом +func ManageServiceMode(ctx context.Context, config *Config, logger Logger, enable bool) error { + if config == nil { + return fmt.Errorf("config cannot be nil") + } + if logger == nil { + return fmt.Errorf("logger cannot be nil") + } + + client := &Client{ + config: config, + logger: logger, + } + + if enable { + return client.EnableServiceMode(ctx) + } + return client.DisableServiceMode(ctx) +} + // EnableServiceMode включает сервисный режим func (c *Client) EnableServiceMode(ctx context.Context) error { - return c.service.EnableServiceMode(ctx) + c.logger.Info("Starting service mode enablement") + + // Получаем UUID кластера + clusterUUID, err := c.getClusterUUID(ctx) + if err != nil { + return fmt.Errorf("failed to get cluster UUID: %w", err) + } + + // Получаем UUID информационной базы + infobaseUUID, err := c.getInfobaseUUID(ctx, clusterUUID) + if err != nil { + return fmt.Errorf("failed to get infobase UUID: %w", err) + } + + // Включаем сервисный режим + if err := c.setServiceMode(ctx, clusterUUID, infobaseUUID, true); err != nil { + return fmt.Errorf("failed to enable service mode: %w", err) + } + + // Завершаем все активные сессии + if err := c.terminateAllSessions(ctx, clusterUUID, infobaseUUID); err != nil { + c.logger.Warn("Failed to terminate all sessions", "error", err) + } + + // Проверяем статус + if err := c.verifyServiceMode(ctx, clusterUUID, infobaseUUID, true); err != nil { + return fmt.Errorf("service mode verification failed: %w", err) + } + + c.logger.Info("Service mode enabled successfully") + return nil } // DisableServiceMode выключает сервисный режим func (c *Client) DisableServiceMode(ctx context.Context) error { - return c.service.DisableServiceMode(ctx) + c.logger.Info("Starting service mode disablement") + + // Получаем UUID кластера + clusterUUID, err := c.getClusterUUID(ctx) + if err != nil { + return fmt.Errorf("failed to get cluster UUID: %w", err) + } + + // Получаем UUID информационной базы + infobaseUUID, err := c.getInfobaseUUID(ctx, clusterUUID) + if err != nil { + return fmt.Errorf("failed to get infobase UUID: %w", err) + } + + // Выключаем сервисный режим + if err := c.setServiceMode(ctx, clusterUUID, infobaseUUID, false); err != nil { + return fmt.Errorf("failed to disable service mode: %w", err) + } + + // Проверяем статус + if err := c.verifyServiceMode(ctx, clusterUUID, infobaseUUID, false); err != nil { + return fmt.Errorf("service mode verification failed: %w", err) + } + + c.logger.Info("Service mode disabled successfully") + return nil } // GetServiceModeStatus получает текущий статус сервисного режима -// Возвращает true если сервисный режим включен, false если выключен func (c *Client) GetServiceModeStatus(ctx context.Context) (bool, error) { - return c.service.GetServiceModeStatus(ctx) + // Получаем UUID кластера + clusterUUID, err := c.getClusterUUID(ctx) + if err != nil { + return false, fmt.Errorf("failed to get cluster UUID: %w", err) + } + + // Получаем UUID информационной базы + infobaseUUID, err := c.getInfobaseUUID(ctx, clusterUUID) + if err != nil { + return false, fmt.Errorf("failed to get infobase UUID: %w", err) + } + + // Получаем информацию об информационной базе + args := []string{ + c.config.RACPath, + "infobase", "info", + "--cluster=" + clusterUUID, + "--infobase=" + infobaseUUID, + "--cluster-user=" + c.config.ClusterAdmin, + "--cluster-pwd=" + c.config.ClusterAdminPassword, + "--infobase-user=" + c.config.DBAdmin, + "--infobase-pwd=" + c.config.DBAdminPassword, + c.getRACAddress(), + } + + output, err := c.executeCommand(ctx, args) + if err != nil { + return false, fmt.Errorf("failed to get infobase info: %w", err) + } + + // Проверяем статус сервисного режима + return strings.Contains(output, "sessions-deny") && strings.Contains(output, "on"), nil } -// NewClientFromConfig создает клиент из готовой конфигурации -func NewClientFromConfig(appConfig *config.AppConfig, logLevel string) (*Client, error) { - if logLevel != "" && appConfig.Project != nil && appConfig.Project.ServiceMode != nil { - appConfig.Project.ServiceMode.LogLevel = logLevel +// Константы +const ( + DefaultDeniedMessage = "Техническое обслуживание. Попробуйте позже." + DefaultPermissionCode = "service-mode" + TechnicalMaintenanceMessage = "Техническое обслуживание" + UUIDLength = 36 + UUIDDashCount = 4 + DefaultRACPort = 1545 + DefaultConnectionTimeout = 30 * time.Second + DefaultCommandTimeout = 60 * time.Second + DefaultRetryCount = 3 + DefaultRetryDelay = 5 * time.Second +) + +// getRACAddress возвращает адрес RAC сервера +func (c *Client) getRACAddress() string { + port := c.config.RACPort + if port == 0 { + port = DefaultRACPort } - - // Валидируем конфигурацию - if err := appConfig.Validate(); err != nil { - return nil, fmt.Errorf("config validation failed: %w", err) - } - - // Создаем логгер - logLevelToUse := "Info" - if appConfig.Project != nil && appConfig.Project.ServiceMode != nil && appConfig.Project.ServiceMode.LogLevel != "" { - logLevelToUse = appConfig.Project.ServiceMode.LogLevel - } - log := logger.NewLogger(logLevelToUse) - - // Создаем сервис - svc := service.NewServiceModeService(appConfig, log) - - return &Client{ - service: svc, - }, nil + return fmt.Sprintf("%s:%d", c.config.ServerHost, port) +} + +// executeCommand выполняет RAC команду с retry логикой +func (c *Client) executeCommand(ctx context.Context, args []string) (string, error) { + var lastErr error + retryCount := c.config.RetryCount + if retryCount == 0 { + retryCount = DefaultRetryCount + } + + retryDelay := c.config.RetryDelay + if retryDelay == 0 { + retryDelay = DefaultRetryDelay + } + + for attempt := 1; attempt <= retryCount; attempt++ { + output, err := c.executeCommandOnce(ctx, args) + if err == nil { + return output, nil + } + + lastErr = err + c.logger.Warn("Command execution failed", "attempt", attempt, "error", err) + + if attempt < retryCount { + c.logger.Info("Retrying after delay", "delay", retryDelay) + select { + case <-ctx.Done(): + return "", ctx.Err() + case <-time.After(retryDelay): + } + } + } + + return "", fmt.Errorf("command failed after %d attempts: %w", retryCount, lastErr) +} + +// executeCommandOnce выполняет RAC команду один раз +func (c *Client) executeCommandOnce(ctx context.Context, args []string) (string, error) { + c.logger.DebugCommand("Executing RAC command", args) + + cmdTimeout := c.config.CommandTimeout + if cmdTimeout == 0 { + cmdTimeout = DefaultCommandTimeout + } + + cmdCtx, cancel := context.WithTimeout(ctx, cmdTimeout) + defer cancel() + + cmd := exec.CommandContext(cmdCtx, args[0], args[1:]...) + output, err := cmd.Output() + if err != nil { + c.logger.Error("RAC command failed", "error", err, "output", string(output)) + return "", fmt.Errorf("RAC command failed: %w, output: %s", err, string(output)) + } + + utf8Output := convertToUTF8(output) + c.logger.Debug("RAC command executed successfully", "output_length", len(utf8Output)) + return utf8Output, nil +} + +// convertToUTF8 конвертирует байты в UTF-8 строку +func convertToUTF8(data []byte) string { + if utf8.Valid(data) { + return string(data) + } + + // Пытаемся декодировать как Windows-1251 + runes := make([]rune, 0, len(data)) + for _, b := range data { + if b < 128 { + runes = append(runes, rune(b)) + } else { + // Простая таблица конвертации для основных символов Windows-1251 + switch b { + case 0xC0: // А + runes = append(runes, 'А') + case 0xC1: // Б + runes = append(runes, 'Б') + case 0xC2: // В + runes = append(runes, 'В') + case 0xC3: // Г + runes = append(runes, 'Г') + case 0xC4: // Д + runes = append(runes, 'Д') + case 0xC5: // Е + runes = append(runes, 'Е') + case 0xC6: // Ж + runes = append(runes, 'Ж') + case 0xC7: // З + runes = append(runes, 'З') + case 0xC8: // И + runes = append(runes, 'И') + case 0xC9: // Й + runes = append(runes, 'Й') + case 0xCA: // К + runes = append(runes, 'К') + case 0xCB: // Л + runes = append(runes, 'Л') + case 0xCC: // М + runes = append(runes, 'М') + case 0xCD: // Н + runes = append(runes, 'Н') + case 0xCE: // О + runes = append(runes, 'О') + case 0xCF: // П + runes = append(runes, 'П') + case 0xD0: // Р + runes = append(runes, 'Р') + case 0xD1: // С + runes = append(runes, 'С') + case 0xD2: // Т + runes = append(runes, 'Т') + case 0xD3: // У + runes = append(runes, 'У') + case 0xD4: // Ф + runes = append(runes, 'Ф') + case 0xD5: // Х + runes = append(runes, 'Х') + case 0xD6: // Ц + runes = append(runes, 'Ц') + case 0xD7: // Ч + runes = append(runes, 'Ч') + case 0xD8: // Ш + runes = append(runes, 'Ш') + case 0xD9: // Щ + runes = append(runes, 'Щ') + case 0xDA: // Ъ + runes = append(runes, 'Ъ') + case 0xDB: // Ы + runes = append(runes, 'Ы') + case 0xDC: // Ь + runes = append(runes, 'Ь') + case 0xDD: // Э + runes = append(runes, 'Э') + case 0xDE: // Ю + runes = append(runes, 'Ю') + case 0xDF: // Я + runes = append(runes, 'Я') + case 0xE0: // а + runes = append(runes, 'а') + case 0xE1: // б + runes = append(runes, 'б') + case 0xE2: // в + runes = append(runes, 'в') + case 0xE3: // г + runes = append(runes, 'г') + case 0xE4: // д + runes = append(runes, 'д') + case 0xE5: // е + runes = append(runes, 'е') + case 0xE6: // ж + runes = append(runes, 'ж') + case 0xE7: // з + runes = append(runes, 'з') + case 0xE8: // и + runes = append(runes, 'и') + case 0xE9: // й + runes = append(runes, 'й') + case 0xEA: // к + runes = append(runes, 'к') + case 0xEB: // л + runes = append(runes, 'л') + case 0xEC: // м + runes = append(runes, 'м') + case 0xED: // н + runes = append(runes, 'н') + case 0xEE: // о + runes = append(runes, 'о') + case 0xEF: // п + runes = append(runes, 'п') + case 0xF0: // р + runes = append(runes, 'р') + case 0xF1: // с + runes = append(runes, 'с') + case 0xF2: // т + runes = append(runes, 'т') + case 0xF3: // у + runes = append(runes, 'у') + case 0xF4: // ф + runes = append(runes, 'ф') + case 0xF5: // х + runes = append(runes, 'х') + case 0xF6: // ц + runes = append(runes, 'ц') + case 0xF7: // ч + runes = append(runes, 'ч') + case 0xF8: // ш + runes = append(runes, 'ш') + case 0xF9: // щ + runes = append(runes, 'щ') + case 0xFA: // ъ + runes = append(runes, 'ъ') + case 0xFB: // ы + runes = append(runes, 'ы') + case 0xFC: // ь + runes = append(runes, 'ь') + case 0xFD: // э + runes = append(runes, 'э') + case 0xFE: // ю + runes = append(runes, 'ю') + case 0xFF: // я + runes = append(runes, 'я') + default: + runes = append(runes, rune(b)) + } + } + } + return string(runes) } diff --git a/benadis_rac_helpers.go b/benadis_rac_helpers.go new file mode 100644 index 0000000..9f33330 --- /dev/null +++ b/benadis_rac_helpers.go @@ -0,0 +1,322 @@ +package benadis_rac + +import ( + "context" + "fmt" + "regexp" + "strings" +) + +// getClusterUUID получает UUID кластера +func (c *Client) getClusterUUID(ctx context.Context) (string, error) { + c.logger.Debug("Getting cluster UUID") + + args := []string{ + c.config.RACPath, + "cluster", "list", + c.getRACAddress(), + } + + output, err := c.executeCommand(ctx, args) + if err != nil { + return "", fmt.Errorf("failed to get cluster list: %w", err) + } + + // Ищем UUID кластера в выводе + uuidRegex := regexp.MustCompile(`cluster\s*:\s*([a-fA-F0-9-]{36})`) + matches := uuidRegex.FindStringSubmatch(output) + if len(matches) < 2 { + return "", fmt.Errorf("cluster UUID not found in output") + } + + clusterUUID := matches[1] + c.logger.Debug("Found cluster UUID", "uuid", clusterUUID) + return clusterUUID, nil +} + +// getInfobaseUUID получает UUID информационной базы +func (c *Client) getInfobaseUUID(ctx context.Context, clusterUUID string) (string, error) { + c.logger.Debug("Getting infobase UUID", "cluster", clusterUUID) + + args := []string{ + c.config.RACPath, + "infobase", "summary", "list", + "--cluster=" + clusterUUID, + "--cluster-user=" + c.config.ClusterAdmin, + "--cluster-pwd=" + c.config.ClusterAdminPassword, + c.getRACAddress(), + } + + output, err := c.executeCommand(ctx, args) + if err != nil { + return "", fmt.Errorf("failed to get infobase list: %w", err) + } + + // Ищем информационную базу по имени + lines := strings.Split(output, "\n") + var currentInfobase string + var currentUUID string + + for _, line := range lines { + line = strings.TrimSpace(line) + if strings.HasPrefix(line, "infobase") { + // Извлекаем UUID из строки вида "infobase : uuid" + parts := strings.Split(line, ":") + if len(parts) >= 2 { + currentUUID = strings.TrimSpace(parts[1]) + } + } else if strings.HasPrefix(line, "name") { + // Извлекаем имя из строки вида "name : basename" + parts := strings.Split(line, ":") + if len(parts) >= 2 { + currentInfobase = strings.TrimSpace(parts[1]) + if currentInfobase == c.config.BaseName && currentUUID != "" { + c.logger.Debug("Found infobase UUID", "uuid", currentUUID, "name", currentInfobase) + return currentUUID, nil + } + } + } + } + + return "", fmt.Errorf("infobase UUID not found for name: %s", c.config.BaseName) +} + +// setServiceMode устанавливает сервисный режим +func (c *Client) setServiceMode(ctx context.Context, clusterUUID, infobaseUUID string, enable bool) error { + var action string + if enable { + c.logger.Info("Enabling service mode") + action = "on" + } else { + c.logger.Info("Disabling service mode") + action = "off" + } + + deniedMessage := c.config.DeniedMessage + if deniedMessage == "" { + deniedMessage = DefaultDeniedMessage + } + + permissionCode := c.config.PermissionCode + if permissionCode == "" { + permissionCode = DefaultPermissionCode + } + + args := []string{ + c.config.RACPath, + "infobase", "update", + "--cluster=" + clusterUUID, + "--infobase=" + infobaseUUID, + "--cluster-user=" + c.config.ClusterAdmin, + "--cluster-pwd=" + c.config.ClusterAdminPassword, + "--infobase-user=" + c.config.DBAdmin, + "--infobase-pwd=" + c.config.DBAdminPassword, + "--sessions-deny=" + action, + "--scheduled-jobs-deny=" + action, + c.getRACAddress(), + } + + if enable { + args = append(args, "--denied-message="+deniedMessage) + args = append(args, "--permission-code="+permissionCode) + } + + _, err := c.executeCommand(ctx, args) + if err != nil { + if enable { + return fmt.Errorf("failed to enable service mode: %w", err) + } + return fmt.Errorf("failed to disable service mode: %w", err) + } + + return nil +} + +// terminateAllSessions завершает все активные сессии +func (c *Client) terminateAllSessions(ctx context.Context, clusterUUID, infobaseUUID string) error { + c.logger.Info("Terminating all sessions") + + // Получаем список сессий + sessions, err := c.getSessions(ctx, clusterUUID, infobaseUUID) + if err != nil { + return fmt.Errorf("failed to get sessions: %w", err) + } + + if len(sessions) == 0 { + c.logger.Info("No active sessions found") + return nil + } + + c.logger.Info("Found active sessions", "count", len(sessions)) + + // Завершаем каждую сессию + for _, sessionID := range sessions { + if err := c.terminateSession(ctx, clusterUUID, sessionID); err != nil { + c.logger.Warn("Failed to terminate session", "session", sessionID, "error", err) + continue + } + c.logger.Debug("Session terminated", "session", sessionID) + } + + c.logger.Info("All sessions termination completed") + return nil +} + +// getSessions получает список активных сессий +func (c *Client) getSessions(ctx context.Context, clusterUUID, infobaseUUID string) ([]string, error) { + args := []string{ + c.config.RACPath, + "session", "list", + "--cluster=" + clusterUUID, + "--infobase=" + infobaseUUID, + "--cluster-user=" + c.config.ClusterAdmin, + "--cluster-pwd=" + c.config.ClusterAdminPassword, + c.getRACAddress(), + } + + output, err := c.executeCommand(ctx, args) + if err != nil { + return nil, fmt.Errorf("failed to get session list: %w", err) + } + + // Парсим вывод для извлечения ID сессий + var sessions []string + lines := strings.Split(output, "\n") + + for _, line := range lines { + line = strings.TrimSpace(line) + if strings.HasPrefix(line, "session") { + // Извлекаем UUID из строки вида "session : uuid" + parts := strings.Split(line, ":") + if len(parts) >= 2 { + sessionID := strings.TrimSpace(parts[1]) + if isValidUUID(sessionID) { + sessions = append(sessions, sessionID) + } + } + } + } + + return sessions, nil +} + +// terminateSession завершает конкретную сессию +func (c *Client) terminateSession(ctx context.Context, clusterUUID, sessionID string) error { + args := []string{ + c.config.RACPath, + "session", "terminate", + "--cluster=" + clusterUUID, + "--session=" + sessionID, + "--cluster-user=" + c.config.ClusterAdmin, + "--cluster-pwd=" + c.config.ClusterAdminPassword, + "--message=" + TechnicalMaintenanceMessage, + c.getRACAddress(), + } + + _, err := c.executeCommand(ctx, args) + if err != nil { + return fmt.Errorf("failed to terminate session %s: %w", sessionID, err) + } + + return nil +} + +// verifyServiceMode проверяет статус сервисного режима +func (c *Client) verifyServiceMode(ctx context.Context, clusterUUID, infobaseUUID string, expectedEnabled bool) error { + c.logger.Debug("Verifying service mode", "expected_enabled", expectedEnabled) + + args := []string{ + c.config.RACPath, + "infobase", "info", + "--cluster=" + clusterUUID, + "--infobase=" + infobaseUUID, + "--cluster-user=" + c.config.ClusterAdmin, + "--cluster-pwd=" + c.config.ClusterAdminPassword, + "--infobase-user=" + c.config.DBAdmin, + "--infobase-pwd=" + c.config.DBAdminPassword, + c.getRACAddress(), + } + + output, err := c.executeCommand(ctx, args) + if err != nil { + return fmt.Errorf("failed to get infobase info: %w", err) + } + + // Проверяем статус sessions-deny + expectedSessionsDeny := "off" + if expectedEnabled { + expectedSessionsDeny = "on" + } + + if !strings.Contains(output, "sessions-deny") || !strings.Contains(output, expectedSessionsDeny) { + return fmt.Errorf("sessions-deny verification failed: expected %s, got output: %s", expectedSessionsDeny, output) + } + + // Проверяем статус scheduled-jobs-deny + expectedJobsDeny := "off" + if expectedEnabled { + expectedJobsDeny = "on" + } + + if !strings.Contains(output, "scheduled-jobs-deny") || !strings.Contains(output, expectedJobsDeny) { + return fmt.Errorf("scheduled-jobs-deny verification failed: expected %s, got output: %s", expectedJobsDeny, output) + } + + c.logger.Debug("Service mode verification successful") + return nil +} + +// isValidUUID проверяет, является ли строка валидным UUID +func isValidUUID(uuid string) bool { + if len(uuid) != UUIDLength { + return false + } + + dashCount := strings.Count(uuid, "-") + return dashCount == UUIDDashCount +} + +// SetDefaults устанавливает значения по умолчанию для конфигурации +func (c *Config) SetDefaults() { + if c.ConnectionTimeout == 0 { + c.ConnectionTimeout = DefaultConnectionTimeout + } + if c.CommandTimeout == 0 { + c.CommandTimeout = DefaultCommandTimeout + } + if c.RetryCount == 0 { + c.RetryCount = DefaultRetryCount + } + if c.RetryDelay == 0 { + c.RetryDelay = DefaultRetryDelay + } + if c.RACPort == 0 { + c.RACPort = DefaultRACPort + } + if c.DeniedMessage == "" { + c.DeniedMessage = DefaultDeniedMessage + } + if c.PermissionCode == "" { + c.PermissionCode = DefaultPermissionCode + } +} + +// Validate проверяет корректность конфигурации +func (c *Config) Validate() error { + if c.ServerHost == "" { + return fmt.Errorf("server_host is required") + } + if c.RACPath == "" { + return fmt.Errorf("rac_path is required") + } + if c.BaseName == "" { + return fmt.Errorf("base_name is required") + } + if c.ClusterAdmin == "" { + return fmt.Errorf("cluster_admin is required") + } + if c.DBAdmin == "" { + return fmt.Errorf("db_admin is required") + } + return nil +} \ No newline at end of file diff --git a/example/main.go b/example/main.go deleted file mode 100644 index 68b7306..0000000 --- a/example/main.go +++ /dev/null @@ -1,266 +0,0 @@ -// Package main демонстрирует использование библиотеки benadis-rac -// для управления сервисным режимом 1C -package main - -import ( - "context" - "fmt" - "log" - "os" - "time" - - benadisrac "git.benadis.ru/gitops/benadis-rac" -) - -// ExampleConfig содержит примеры конфигурации для демонстрации -type ExampleConfig struct { - ConfigPath string - SecretPath string - ProjectPath string - LogLevel string -} - -// getDefaultConfig возвращает конфигурацию по умолчанию -func getDefaultConfig() ExampleConfig { - return ExampleConfig{ - ConfigPath: "../config.yaml", - SecretPath: "../secret.yaml", - ProjectPath: "../project.yaml", - LogLevel: "Info", - } -} - -// demonstrateBasicUsage демонстрирует базовое использование библиотеки -func demonstrateBasicUsage() error { - fmt.Println("=== Демонстрация базового использования библиотеки GitOps RAC ===") - - // Получаем конфигурацию - cfg := getDefaultConfig() - - // Создаем клиент - fmt.Println("Создание клиента...") - client, err := benadisrac.NewClient(benadisrac.Config{ - ConfigPath: cfg.ConfigPath, - SecretPath: cfg.SecretPath, - ProjectPath: cfg.ProjectPath, - LogLevel: cfg.LogLevel, - }) - if err != nil { - return fmt.Errorf("ошибка создания клиента: %w", err) - } - fmt.Println("✓ Клиент успешно создан") - - // Создаем контекст с таймаутом - ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) - defer cancel() - - // Получаем текущий статус - fmt.Println("\nПроверка текущего статуса сервисного режима...") - status, err := client.GetServiceModeStatus(ctx) - if err != nil { - return fmt.Errorf("ошибка получения статуса: %w", err) - } - fmt.Printf("✓ Текущий статус сервисного режима: %t\n", status) - - return nil -} - -// demonstrateServiceModeOperations демонстрирует операции с сервисным режимом -func demonstrateServiceModeOperations() error { - fmt.Println("\n=== Демонстрация операций с сервисным режимом ===") - - // Создаем клиент - cfg := getDefaultConfig() - client, err := benadisrac.NewClient(benadisrac.Config{ - ConfigPath: cfg.ConfigPath, - SecretPath: cfg.SecretPath, - ProjectPath: cfg.ProjectPath, - LogLevel: "Debug", // Используем Debug для подробного логирования - }) - if err != nil { - return fmt.Errorf("ошибка создания клиента: %w", err) - } - - ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second) - defer cancel() - - // Получаем начальный статус - initialStatus, err := client.GetServiceModeStatus(ctx) - if err != nil { - return fmt.Errorf("ошибка получения начального статуса: %w", err) - } - fmt.Printf("Начальный статус: %t\n", initialStatus) - - // Если сервисный режим выключен, включаем его - if !initialStatus { - fmt.Println("\nВключение сервисного режима...") - if err := client.EnableServiceMode(ctx); err != nil { - return fmt.Errorf("ошибка включения сервисного режима: %w", err) - } - fmt.Println("✓ Сервисный режим включен") - - // Проверяем статус после включения - status, err := client.GetServiceModeStatus(ctx) - if err != nil { - return fmt.Errorf("ошибка проверки статуса после включения: %w", err) - } - fmt.Printf("✓ Статус после включения: %t\n", status) - - // Ждем немного - time.Sleep(2 * time.Second) - - // Выключаем сервисный режим - fmt.Println("\nВыключение сервисного режима...") - if err := client.DisableServiceMode(ctx); err != nil { - return fmt.Errorf("ошибка выключения сервисного режима: %w", err) - } - fmt.Println("✓ Сервисный режим выключен") - } else { - fmt.Println("\nВыключение сервисного режима...") - if err := client.DisableServiceMode(ctx); err != nil { - return fmt.Errorf("ошибка выключения сервисного режима: %w", err) - } - fmt.Println("✓ Сервисный режим выключен") - - // Проверяем статус после выключения - status, err := client.GetServiceModeStatus(ctx) - if err != nil { - return fmt.Errorf("ошибка проверки статуса после выключения: %w", err) - } - fmt.Printf("✓ Статус после выключения: %t\n", status) - - // Ждем немного - time.Sleep(2 * time.Second) - - // Включаем сервисный режим обратно - fmt.Println("\nВключение сервисного режима обратно...") - if err := client.EnableServiceMode(ctx); err != nil { - return fmt.Errorf("ошибка включения сервисного режима: %w", err) - } - fmt.Println("✓ Сервисный режим включен обратно") - } - - // Финальная проверка статуса - finalStatus, err := client.GetServiceModeStatus(ctx) - if err != nil { - return fmt.Errorf("ошибка получения финального статуса: %w", err) - } - fmt.Printf("\n✓ Финальный статус: %t\n", finalStatus) - - return nil -} - -// demonstrateErrorHandling демонстрирует обработку ошибок -func demonstrateErrorHandling() { - fmt.Println("\n=== Демонстрация обработки ошибок ===") - - // Попытка создать клиент с неверными путями - fmt.Println("Попытка создания клиента с неверными путями...") - _, err := benadisrac.NewClient(benadisrac.Config{ - ConfigPath: "nonexistent-config.yaml", - SecretPath: "nonexistent-secret.yaml", - ProjectPath: "nonexistent-project.yaml", - LogLevel: "Info", - }) - if err != nil { - fmt.Printf("✓ Ожидаемая ошибка: %v\n", err) - } else { - fmt.Println("⚠ Неожиданно: ошибка не возникла") - } - - // Попытка создать клиент с неверным уровнем логирования - fmt.Println("\nПопытка создания клиента с неверным уровнем логирования...") - cfg := getDefaultConfig() - cfg.LogLevel = "InvalidLevel" - client, err := benadisrac.NewClient(benadisrac.Config{ - ConfigPath: cfg.ConfigPath, - SecretPath: cfg.SecretPath, - ProjectPath: cfg.ProjectPath, - LogLevel: cfg.LogLevel, - }) - if err != nil { - fmt.Printf("✓ Ошибка при неверном уровне логирования: %v\n", err) - } else if client != nil { - fmt.Println("✓ Клиент создан, но может использовать уровень логирования по умолчанию") - } -} - -// printUsageInstructions выводит инструкции по использованию -func printUsageInstructions() { - fmt.Println("\n=== Инструкции по использованию библиотеки GitOps RAC ===") - fmt.Println("\n1. Импортируйте библиотеку:") - fmt.Println(` import benadisrac "git.benadis.ru/gitops/benadis-rac/"`) - - fmt.Println("\n2. Создайте конфигурацию:") - fmt.Println(` cfg := benadisrac.Config{`) - fmt.Println(` ConfigPath: "config.yaml",`) - fmt.Println(` SecretPath: "secret.yaml",`) - fmt.Println(` ProjectPath: "project.yaml",`) - fmt.Println(` LogLevel: "Info",`) - fmt.Println(` }`) - - fmt.Println("\n3. Создайте клиент:") - fmt.Println(` client, err := benadisrac.NewClient(cfg)`) - fmt.Println(` if err != nil {`) - fmt.Println(` log.Fatal(err)`) - fmt.Println(` }`) - - fmt.Println("\n4. Используйте методы клиента:") - fmt.Println(` ctx := context.Background()`) - fmt.Println(` status, err := client.GetServiceModeStatus(ctx)`) - fmt.Println(` err = client.EnableServiceMode(ctx)`) - fmt.Println(` err = client.DisableServiceMode(ctx)`) - - fmt.Println("\n5. Доступные уровни логирования: Debug, Info, Warn, Error") - fmt.Println("\n6. Все операции поддерживают context.Context для управления таймаутами") -} - -func main() { - fmt.Println("GitOps RAC Library Example") - fmt.Println("==========================") - - // Проверяем аргументы командной строки - if len(os.Args) > 1 { - switch os.Args[1] { - case "--help", "-h": - printUsageInstructions() - return - case "--basic": - if err := demonstrateBasicUsage(); err != nil { - log.Printf("Ошибка в базовой демонстрации: %v", err) - } - return - case "--operations": - if err := demonstrateServiceModeOperations(); err != nil { - log.Printf("Ошибка в демонстрации операций: %v", err) - } - return - case "--errors": - demonstrateErrorHandling() - return - } - } - - // Выполняем все демонстрации по умолчанию - fmt.Println("Запуск всех демонстраций...") - fmt.Println("Используйте флаги: --basic, --operations, --errors, --help") - fmt.Println() - - // Базовое использование - if err := demonstrateBasicUsage(); err != nil { - log.Printf("Ошибка в базовой демонстрации: %v", err) - } - - // Операции с сервисным режимом (только если базовая демонстрация прошла успешно) - if err := demonstrateServiceModeOperations(); err != nil { - log.Printf("Ошибка в демонстрации операций: %v", err) - } - - // Обработка ошибок - demonstrateErrorHandling() - - // Инструкции - printUsageInstructions() - - fmt.Println("\n=== Демонстрация завершена ===") -} diff --git a/example/main_test.go b/example/main_test.go deleted file mode 100644 index 9ad8b47..0000000 --- a/example/main_test.go +++ /dev/null @@ -1,337 +0,0 @@ -package main - -import ( - "context" - "os" - "testing" - "time" - - benadisrac "git.benadis.ru/gitops/benadis-rac" -) - -// TestGetDefaultConfig тестирует функцию получения конфигурации по умолчанию -func TestGetDefaultConfig(t *testing.T) { - cfg := getDefaultConfig() - - if cfg.ConfigPath == "" { - t.Error("ConfigPath не должен быть пустым") - } - if cfg.SecretPath == "" { - t.Error("SecretPath не должен быть пустым") - } - if cfg.ProjectPath == "" { - t.Error("ProjectPath не должен быть пустым") - } - if cfg.LogLevel == "" { - t.Error("LogLevel не должен быть пустым") - } - - // Проверяем ожидаемые значения - expected := ExampleConfig{ - ConfigPath: "../config.yaml", - SecretPath: "../secret.yaml", - ProjectPath: "../project.yaml", - LogLevel: "Info", - } - - if cfg != expected { - t.Errorf("Неожиданная конфигурация. Получено: %+v, ожидалось: %+v", cfg, expected) - } -} - -// TestClientCreation тестирует создание клиента с различными конфигурациями -func TestClientCreation(t *testing.T) { - tests := []struct { - name string - config benadisrac.Config - expectError bool - }{ - { - name: "Валидная конфигурация", - config: benadisrac.Config{ - ConfigPath: "../config.yaml", - SecretPath: "../secret.yaml", - ProjectPath: "../project.yaml", - LogLevel: "Info", - }, - expectError: false, - }, - { - name: "Несуществующий файл конфигурации", - config: benadisrac.Config{ - ConfigPath: "nonexistent.yaml", - SecretPath: "../secret.yaml", - ProjectPath: "../project.yaml", - LogLevel: "Info", - }, - expectError: true, - }, - { - name: "Несуществующий файл секретов", - config: benadisrac.Config{ - ConfigPath: "../config.yaml", - SecretPath: "nonexistent.yaml", - ProjectPath: "../project.yaml", - LogLevel: "Info", - }, - expectError: true, - }, - { - name: "Несуществующий файл проекта", - config: benadisrac.Config{ - ConfigPath: "../config.yaml", - SecretPath: "../secret.yaml", - ProjectPath: "nonexistent.yaml", - LogLevel: "Info", - }, - expectError: true, - }, - { - name: "Пустые пути (должны использоваться значения по умолчанию)", - config: benadisrac.Config{ - LogLevel: "Debug", - }, - expectError: true, // Файлы по умолчанию не существуют в example директории - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - client, err := benadisrac.NewClient(tt.config) - - if tt.expectError { - if err == nil { - t.Error("Ожидалась ошибка, но её не было") - } - if client != nil { - t.Error("Клиент не должен быть создан при ошибке") - } - } else { - if err != nil { - t.Errorf("Неожиданная ошибка: %v", err) - } - if client == nil { - t.Error("Клиент должен быть создан") - } - } - }) - } -} - -// TestClientOperations тестирует операции клиента (требует валидную конфигурацию) -func TestClientOperations(t *testing.T) { - // Проверяем наличие файлов конфигурации - cfg := getDefaultConfig() - if !fileExists(cfg.ConfigPath) || !fileExists(cfg.SecretPath) || !fileExists(cfg.ProjectPath) { - t.Skip("Пропускаем тест: файлы конфигурации не найдены") - } - - client, err := benadisrac.NewClient(benadisrac.Config{ - ConfigPath: cfg.ConfigPath, - SecretPath: cfg.SecretPath, - ProjectPath: cfg.ProjectPath, - LogLevel: "Info", - }) - if err != nil { - t.Fatalf("Ошибка создания клиента: %v", err) - } - - ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) - defer cancel() - - // Тестируем получение статуса - t.Run("GetServiceModeStatus", func(t *testing.T) { - status, err := client.GetServiceModeStatus(ctx) - if err != nil { - t.Errorf("Ошибка получения статуса: %v", err) - } - // Статус может быть true или false, оба варианта валидны - t.Logf("Текущий статус сервисного режима: %t", status) - }) - - // Тестируем включение сервисного режима - t.Run("EnableServiceMode", func(t *testing.T) { - err := client.EnableServiceMode(ctx) - if err != nil { - t.Errorf("Ошибка включения сервисного режима: %v", err) - } - }) - - // Тестируем выключение сервисного режима - t.Run("DisableServiceMode", func(t *testing.T) { - err := client.DisableServiceMode(ctx) - if err != nil { - t.Errorf("Ошибка выключения сервисного режима: %v", err) - } - }) -} - -// TestClientWithContext тестирует работу с контекстом -func TestClientWithContext(t *testing.T) { - cfg := getDefaultConfig() - if !fileExists(cfg.ConfigPath) || !fileExists(cfg.SecretPath) || !fileExists(cfg.ProjectPath) { - t.Skip("Пропускаем тест: файлы конфигурации не найдены") - } - - client, err := benadisrac.NewClient(benadisrac.Config{ - ConfigPath: cfg.ConfigPath, - SecretPath: cfg.SecretPath, - ProjectPath: cfg.ProjectPath, - LogLevel: "Info", - }) - if err != nil { - t.Fatalf("Ошибка создания клиента: %v", err) - } - - // Тестируем таймаут контекста - t.Run("ContextTimeout", func(t *testing.T) { - ctx, cancel := context.WithTimeout(context.Background(), 1*time.Millisecond) - defer cancel() - - // Ждем, чтобы контекст истек - time.Sleep(2 * time.Millisecond) - - _, err := client.GetServiceModeStatus(ctx) - if err == nil { - t.Log("Операция завершилась быстрее таймаута или таймаут не обрабатывается") - } else { - t.Logf("Ожидаемая ошибка таймаута: %v", err) - } - }) - - // Тестируем отмену контекста - t.Run("ContextCancellation", func(t *testing.T) { - ctx, cancel := context.WithCancel(context.Background()) - cancel() // Отменяем контекст сразу - - _, err := client.GetServiceModeStatus(ctx) - if err == nil { - t.Log("Операция завершилась до проверки отмены или отмена не обрабатывается") - } else { - t.Logf("Ожидаемая ошибка отмены: %v", err) - } - }) -} - -// TestDemonstrateBasicUsage тестирует функцию демонстрации базового использования -func TestDemonstrateBasicUsage(t *testing.T) { - cfg := getDefaultConfig() - if !fileExists(cfg.ConfigPath) || !fileExists(cfg.SecretPath) || !fileExists(cfg.ProjectPath) { - t.Skip("Пропускаем тест: файлы конфигурации не найдены") - } - - err := demonstrateBasicUsage() - if err != nil { - t.Errorf("Ошибка в демонстрации базового использования: %v", err) - } -} - -// TestDemonstrateErrorHandling тестирует функцию демонстрации обработки ошибок -func TestDemonstrateErrorHandling(t *testing.T) { - // Эта функция не должна вызывать панику - defer func() { - if r := recover(); r != nil { - t.Errorf("Функция demonstrateErrorHandling вызвала панику: %v", r) - } - }() - - demonstrateErrorHandling() -} - -// TestMainWithArguments тестирует обработку аргументов командной строки -func TestMainWithArguments(t *testing.T) { - tests := []struct { - name string - args []string - }{ - {"Help", []string{"program", "--help"}}, - {"Help short", []string{"program", "-h"}}, - {"Errors", []string{"program", "--errors"}}, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - // Сохраняем оригинальные аргументы - origArgs := os.Args - defer func() { os.Args = origArgs }() - - // Устанавливаем тестовые аргументы - os.Args = tt.args - - // Проверяем, что функции не вызывают панику - defer func() { - if r := recover(); r != nil { - t.Errorf("Функция вызвала панику с аргументами %v: %v", tt.args, r) - } - }() - - // Тестируем отдельные функции вместо main() - switch tt.name { - case "Help", "Help short": - printUsageInstructions() - case "Errors": - demonstrateErrorHandling() - } - }) - } -} - -// Вспомогательные функции - -// fileExists проверяет существование файла -func fileExists(filename string) bool { - _, err := os.Stat(filename) - return !os.IsNotExist(err) -} - -// BenchmarkClientCreation бенчмарк создания клиента -func BenchmarkClientCreation(b *testing.B) { - cfg := getDefaultConfig() - if !fileExists(cfg.ConfigPath) || !fileExists(cfg.SecretPath) || !fileExists(cfg.ProjectPath) { - b.Skip("Пропускаем бенчмарк: файлы конфигурации не найдены") - } - - config := benadisrac.Config{ - ConfigPath: cfg.ConfigPath, - SecretPath: cfg.SecretPath, - ProjectPath: cfg.ProjectPath, - LogLevel: "Info", - } - - b.ResetTimer() - for i := 0; i < b.N; i++ { - client, err := benadisrac.NewClient(config) - if err != nil { - b.Fatalf("Ошибка создания клиента: %v", err) - } - _ = client - } -} - -// BenchmarkGetStatus бенчмарк получения статуса -func BenchmarkGetStatus(b *testing.B) { - cfg := getDefaultConfig() - if !fileExists(cfg.ConfigPath) || !fileExists(cfg.SecretPath) || !fileExists(cfg.ProjectPath) { - b.Skip("Пропускаем бенчмарк: файлы конфигурации не найдены") - } - - client, err := benadisrac.NewClient(benadisrac.Config{ - ConfigPath: cfg.ConfigPath, - SecretPath: cfg.SecretPath, - ProjectPath: cfg.ProjectPath, - LogLevel: "Info", - }) - if err != nil { - b.Fatalf("Ошибка создания клиента: %v", err) - } - - ctx := context.Background() - - b.ResetTimer() - for i := 0; i < b.N; i++ { - _, err := client.GetServiceModeStatus(ctx) - if err != nil { - b.Fatalf("Ошибка получения статуса: %v", err) - } - } -} diff --git a/examples/library_usage/go.mod b/examples/library_usage/go.mod new file mode 100644 index 0000000..5d8c864 --- /dev/null +++ b/examples/library_usage/go.mod @@ -0,0 +1,7 @@ +module example + +go 1.24.5 + +require git.benadis.ru/gitops/benadis-rac v0.0.0 + +replace git.benadis.ru/gitops/benadis-rac => ../.. diff --git a/examples/library_usage/main.go b/examples/library_usage/main.go new file mode 100644 index 0000000..04876b5 --- /dev/null +++ b/examples/library_usage/main.go @@ -0,0 +1,108 @@ +package main + +import ( + "context" + "log" + "time" + + benadisrac "git.benadis.ru/gitops/benadis-rac" +) + +func main() { + // Пример 1: Использование функции ManageServiceMode (рекомендуемый способ) + log.Println("=== Пример 1: Использование ManageServiceMode ===") + + // Создаем конфигурацию + config := &benadisrac.Config{ + // RAC конфигурация + RACPath: "C:\\Program Files\\1cv8\\8.3.25.1257\\bin\\rac.exe", + ConnectionTimeout: 30 * time.Second, + CommandTimeout: 60 * time.Second, + RetryCount: 3, + RetryDelay: 5 * time.Second, + + // Настройки сервера + ServerHost: "localhost", + ServerPort: 1541, + RACPort: 1545, + ServerName: "server1c", + BaseName: "test_base", + + // Аутентификация + ClusterAdmin: "admin", + ClusterAdminPassword: "password", + DBAdmin: "dbadmin", + DBAdminPassword: "dbpassword", + + // Настройки режима обслуживания + DeniedMessage: "Система находится в режиме обслуживания", + PermissionCode: "MaintenanceMode", + } + + // Устанавливаем значения по умолчанию + config.SetDefaults() + + // Валидируем конфигурацию + if err := config.Validate(); err != nil { + log.Fatalf("Ошибка валидации конфигурации: %v", err) + } + + // Создаем логгер + logger := benadisrac.NewLogger("info") + + // Создаем контекст + ctx := context.Background() + + // Включаем режим обслуживания + log.Println("Включение режима обслуживания...") + if err := benadisrac.ManageServiceMode(ctx, config, logger, true); err != nil { + log.Printf("Ошибка включения режима обслуживания: %v", err) + } else { + log.Println("Режим обслуживания успешно включен") + } + + // Ждем немного + time.Sleep(2 * time.Second) + + // Отключаем режим обслуживания + log.Println("Отключение режима обслуживания...") + if err := benadisrac.ManageServiceMode(ctx, config, logger, false); err != nil { + log.Printf("Ошибка отключения режима обслуживания: %v", err) + } else { + log.Println("Режим обслуживания успешно отключен") + } + + // Пример 2: Использование Client напрямую + log.Println("\n=== Пример 2: Использование Client напрямую ===") + + // Создаем клиента с той же конфигурацией + client, err := benadisrac.NewClient(config) + if err != nil { + log.Fatalf("Ошибка создания клиента: %v", err) + } + + // Получаем статус режима обслуживания + log.Println("Получение статуса режима обслуживания...") + status, err := client.GetServiceModeStatus(ctx) + if err != nil { + log.Printf("Ошибка получения статуса: %v", err) + } else { + log.Printf("Текущий статус режима обслуживания: %t", status) + } + + // Включаем режим обслуживания + log.Println("Включение режима обслуживания через Client...") + if err := client.EnableServiceMode(ctx); err != nil { + log.Printf("Ошибка включения режима обслуживания: %v", err) + } else { + log.Println("Режим обслуживания успешно включен") + } + + // Отключаем режим обслуживания + log.Println("Отключение режима обслуживания через Client...") + if err := client.DisableServiceMode(ctx); err != nil { + log.Printf("Ошибка отключения режима обслуживания: %v", err) + } else { + log.Println("Режим обслуживания успешно отключен") + } +}