Files
benadis-rac/internal/rac/rac.go
2025-08-04 11:03:25 +03:00

334 lines
9.6 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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)
}