package rac import ( "context" "fmt" "os/exec" "strings" "time" "unicode/utf8" "git.benadis.ru/gitops/benadis-rac/internal/config" "git.benadis.ru/gitops/benadis-rac/internal/constants" "git.benadis.ru/gitops/benadis-rac/internal/logger" ) // Client представляет клиент для работы с RAC type Client struct { config *config.AppConfig logger logger.Logger } // NewClient создает новый RAC клиент func NewClient(cfg *config.AppConfig, log logger.Logger) *Client { return &Client{ config: cfg, logger: log, } } // ServiceModeParams параметры для управления сервисным режимом type ServiceModeParams struct { ClusterUUID string InfobaseUUID string Enable bool DeniedMessage string PermissionCode string } // ExecuteCommand выполняет RAC команду с retry логикой func (c *Client) ExecuteCommand(ctx context.Context, args []string) (string, error) { var lastErr error retryCount := c.config.Config.RetryCount if retryCount == 0 { retryCount = constants.DEFAULT_RETRY_COUNT } retryDelay, err := c.config.Config.GetRetryDelay() if err != nil { c.logger.Warn(constants.LogMsgFailedToParseRetryDelay, "error", err) retryDelay = constants.DEFAULT_RETRY_DELAY } for attempt := 1; attempt <= retryCount; attempt++ { c.logger.Debug(constants.LogMsgExecutingRACCommand, "attempt", attempt, "max_attempts", retryCount) c.logger.DebugCommand(constants.LogMsgRACCommand, append([]string{c.config.Config.RACPath}, args...)) output, err := c.executeCommandOnce(ctx, args) if err == nil { c.logger.Debug(constants.LogMsgRACCommandExecutedSuccessfully, "output_length", len(output)) return output, nil } lastErr = err c.logger.Warn(constants.LogMsgRACCommandFailed, "attempt", attempt, "error", err) if attempt < retryCount { c.logger.Debug(constants.LogMsgRetryingAfterDelay, "delay", retryDelay) select { case <-ctx.Done(): return "", ctx.Err() case <-time.After(retryDelay): // Продолжаем } } } c.logger.Error(constants.LogMsgRACCommandFailedAfterAllRetries, "error", lastErr) return "", fmt.Errorf(constants.ErrCommandFailed, retryCount, lastErr) } // executeCommandOnce выполняет RAC команду один раз func (c *Client) executeCommandOnce(ctx context.Context, args []string) (string, error) { cmdTimeout, err := c.config.Config.GetCommandTimeout() if err != nil { return "", fmt.Errorf(constants.ErrFailedToParseCommandTimeout, err) } ctx, cancel := context.WithTimeout(ctx, cmdTimeout) defer cancel() cmd := exec.CommandContext(ctx, c.config.Config.RACPath, args...) output, err := cmd.CombinedOutput() if err != nil { // Преобразуем вывод в корректную кодировку outputStr := convertToUTF8(output) return "", fmt.Errorf(constants.ErrRACCommandFailed, err, outputStr) } return convertToUTF8(output), nil } // GetClusterUUID получает UUID кластера по имени сервера func (c *Client) GetClusterUUID(ctx context.Context) (string, error) { c.logger.Info(constants.LogMsgGettingClusterUUID) args := []string{ c.config.GetRACAddress(), "cluster", "list", } output, err := c.ExecuteCommand(ctx, args) if err != nil { return "", fmt.Errorf(constants.ErrGetClusterList, err) } // Парсим вывод для получения UUID кластера lines := strings.Split(output, "\n") for _, line := range lines { line = strings.TrimSpace(line) if strings.HasPrefix(line, "cluster") { parts := strings.Split(line, ":") if len(parts) >= 2 { uuid := strings.TrimSpace(parts[1]) c.logger.Debug(constants.LogMsgFoundClusterUUID, "uuid", uuid) return uuid, nil } } } return "", fmt.Errorf(constants.ErrClusterUUIDNotFound) } // GetInfobaseUUID получает UUID информационной базы по имени func (c *Client) GetInfobaseUUID(ctx context.Context, clusterUUID string) (string, error) { c.logger.Info(constants.LogMsgGettingInfobaseUUID, "cluster_uuid", clusterUUID) // Получаем учетные данные кластера (пока используем пустое имя проекта для глобальных настроек) clusterUser, clusterPwd := c.config.GetClusterCredentials("") args := []string{ c.config.GetRACAddress(), "infobase", "summary", "list", "--cluster=" + clusterUUID, "--cluster-user=" + clusterUser, "--cluster-pwd=" + clusterPwd, } output, err := c.ExecuteCommand(ctx, args) if err != nil { return "", fmt.Errorf(constants.ErrGetInfobaseList, err) } // Парсим вывод для поиска информационной базы по имени lines := strings.Split(output, "\n") var currentInfobaseUUID string for _, line := range lines { line = strings.TrimSpace(line) if strings.HasPrefix(line, "infobase") { parts := strings.Split(line, ":") if len(parts) >= 2 { currentInfobaseUUID = strings.TrimSpace(parts[1]) } } else if strings.HasPrefix(line, "name") && currentInfobaseUUID != "" { parts := strings.Split(line, ":") if len(parts) >= 2 { name := strings.TrimSpace(parts[1]) if name == c.config.Project.ServiceMode.BaseName { c.logger.Debug(constants.LogMsgFoundInfobaseUUID, "uuid", currentInfobaseUUID, "name", name) return currentInfobaseUUID, nil } } } } return "", fmt.Errorf(constants.ErrInfobaseUUIDNotFound, c.config.Project.ServiceMode.BaseName) } // convertToUTF8 преобразует байты из CP866 (DOS) или Windows-1251 в UTF-8 func convertToUTF8(data []byte) string { // Проверяем, является ли строка уже валидным UTF-8 if utf8.Valid(data) { return string(data) } // Преобразование из CP866 (DOS) в UTF-8 // Это основная кодировка для сообщений об ошибках RAC runes := make([]rune, 0, len(data)) for _, b := range data { if b < 128 { // ASCII символы остаются без изменений runes = append(runes, rune(b)) } else { // Преобразование символов CP866 в Unicode switch b { // Кириллические символы CP866 case 0x80: runes = append(runes, 'А') // А case 0x81: runes = append(runes, 'Б') // Б case 0x82: runes = append(runes, 'В') // В case 0x83: runes = append(runes, 'Г') // Г case 0x84: runes = append(runes, 'Д') // Д case 0x85: runes = append(runes, 'Е') // Е case 0x86: runes = append(runes, 'Ж') // Ж case 0x87: runes = append(runes, 'З') // З case 0x88: runes = append(runes, 'И') // И case 0x89: runes = append(runes, 'Й') // Й case 0x8A: runes = append(runes, 'К') // К case 0x8B: runes = append(runes, 'Л') // Л case 0x8C: runes = append(runes, 'М') // М case 0x8D: runes = append(runes, 'Н') // Н case 0x8E: runes = append(runes, 'О') // О case 0x8F: runes = append(runes, 'П') // П case 0x90: runes = append(runes, 'Р') // Р case 0x91: runes = append(runes, 'С') // С case 0x92: runes = append(runes, 'Т') // Т case 0x93: runes = append(runes, 'У') // У case 0x94: runes = append(runes, 'Ф') // Ф case 0x95: runes = append(runes, 'Х') // Х case 0x96: runes = append(runes, 'Ц') // Ц case 0x97: runes = append(runes, 'Ч') // Ч case 0x98: runes = append(runes, 'Ш') // Ш case 0x99: runes = append(runes, 'Щ') // Щ case 0x9A: runes = append(runes, 'Ъ') // Ъ case 0x9B: runes = append(runes, 'Ы') // Ы case 0x9C: runes = append(runes, 'Ь') // Ь case 0x9D: runes = append(runes, 'Э') // Э case 0x9E: runes = append(runes, 'Ю') // Ю case 0x9F: runes = append(runes, 'Я') // Я case 0xA0: runes = append(runes, 'а') // а case 0xA1: runes = append(runes, 'б') // б case 0xA2: runes = append(runes, 'в') // в case 0xA3: runes = append(runes, 'г') // г case 0xA4: runes = append(runes, 'д') // д case 0xA5: runes = append(runes, 'е') // е case 0xA6: runes = append(runes, 'ж') // ж case 0xA7: runes = append(runes, 'з') // з case 0xA8: runes = append(runes, 'и') // и case 0xA9: runes = append(runes, 'й') // й case 0xAA: runes = append(runes, 'к') // к case 0xAB: runes = append(runes, 'л') // л case 0xAC: runes = append(runes, 'м') // м case 0xAD: runes = append(runes, 'н') // н case 0xAE: runes = append(runes, 'о') // о case 0xAF: 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, 'я') // я default: // Для неизвестных символов оставляем как есть runes = append(runes, rune(b)) } } } return string(runes) }