Init
This commit is contained in:
201
internal/config/config.go
Normal file
201
internal/config/config.go
Normal file
@@ -0,0 +1,201 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"git.benadis.ru/gitops/benadis-rac/internal/constants"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
// Config представляет основную конфигурацию приложения
|
||||
type Config struct {
|
||||
RACPath string `yaml:"rac_path"`
|
||||
ConnectionTimeout string `yaml:"connection_timeout"`
|
||||
CommandTimeout string `yaml:"command_timeout"`
|
||||
RetryCount int `yaml:"retry_count"`
|
||||
RetryDelay string `yaml:"retry_delay"`
|
||||
}
|
||||
|
||||
// GetConnectionTimeout возвращает ConnectionTimeout как time.Duration
|
||||
func (c *Config) GetConnectionTimeout() (time.Duration, error) {
|
||||
return time.ParseDuration(c.ConnectionTimeout)
|
||||
}
|
||||
|
||||
// GetCommandTimeout возвращает CommandTimeout как time.Duration
|
||||
func (c *Config) GetCommandTimeout() (time.Duration, error) {
|
||||
return time.ParseDuration(c.CommandTimeout)
|
||||
}
|
||||
|
||||
// GetRetryDelay возвращает RetryDelay как time.Duration
|
||||
func (c *Config) GetRetryDelay() (time.Duration, error) {
|
||||
return time.ParseDuration(c.RetryDelay)
|
||||
}
|
||||
|
||||
// ProjectCredentials представляет учетные данные для конкретного проекта
|
||||
type ProjectCredentials struct {
|
||||
ClusterAdmin string `yaml:"cluster_admin,omitempty"`
|
||||
ClusterAdminPassword string `yaml:"cluster_admin_password,omitempty"`
|
||||
DBAdmin string `yaml:"db_admin,omitempty"`
|
||||
DBAdminPassword string `yaml:"db_admin_password,omitempty"`
|
||||
}
|
||||
|
||||
// Secret представляет секретную конфигурацию
|
||||
type Secret struct {
|
||||
ClusterAdmin string `yaml:"cluster_admin"`
|
||||
ClusterAdminPassword string `yaml:"cluster_admin_password"`
|
||||
DBAdmin string `yaml:"db_admin"`
|
||||
DBAdminPassword string `yaml:"db_admin_password"`
|
||||
Projects map[string]*ProjectCredentials `yaml:"projects,omitempty"`
|
||||
}
|
||||
|
||||
// ServiceModeConfig представляет конфигурацию service-mode из project.yaml
|
||||
type ServiceModeConfig struct {
|
||||
ServerHost string `yaml:"server_host"`
|
||||
ServerPort int `yaml:"server_port"`
|
||||
RACPort int `yaml:"rac_port"`
|
||||
LogLevel string `yaml:"log_level"`
|
||||
ServerName string `yaml:"server_name"`
|
||||
BaseName string `yaml:"base_name"`
|
||||
Command string `yaml:"command"`
|
||||
}
|
||||
|
||||
// ProjectConfig представляет конфигурацию проекта
|
||||
type ProjectConfig struct {
|
||||
ServiceMode *ServiceModeConfig `yaml:"service-mode"`
|
||||
}
|
||||
|
||||
// AppConfig объединяет все конфигурации
|
||||
type AppConfig struct {
|
||||
Config *Config
|
||||
Secret *Secret
|
||||
Project *ProjectConfig
|
||||
}
|
||||
|
||||
// LoadConfig загружает конфигурацию из файлов
|
||||
func LoadConfig(configPath, secretPath, projectPath string) (*AppConfig, error) {
|
||||
config, err := loadConfigFile(configPath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf(constants.ErrLoadConfig, err)
|
||||
}
|
||||
|
||||
secret, err := loadSecretFile(secretPath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf(constants.ErrLoadSecret, err)
|
||||
}
|
||||
|
||||
project, err := loadProjectFile(projectPath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to load project config: %w", err)
|
||||
}
|
||||
|
||||
return &AppConfig{
|
||||
Config: config,
|
||||
Secret: secret,
|
||||
Project: project,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// loadConfigFile загружает основной конфигурационный файл
|
||||
func loadConfigFile(path string) (*Config, error) {
|
||||
data, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read config file: %w", err)
|
||||
}
|
||||
|
||||
var config Config
|
||||
if err := yaml.Unmarshal(data, &config); err != nil {
|
||||
return nil, fmt.Errorf("failed to unmarshal config: %w", err)
|
||||
}
|
||||
|
||||
return &config, nil
|
||||
}
|
||||
|
||||
// loadSecretFile загружает файл с секретами
|
||||
func loadSecretFile(path string) (*Secret, error) {
|
||||
data, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read secret file: %w", err)
|
||||
}
|
||||
|
||||
var secret Secret
|
||||
if err := yaml.Unmarshal(data, &secret); err != nil {
|
||||
return nil, fmt.Errorf("failed to unmarshal secret: %w", err)
|
||||
}
|
||||
|
||||
return &secret, nil
|
||||
}
|
||||
|
||||
// loadProjectFile загружает файл с настройками проекта
|
||||
func loadProjectFile(path string) (*ProjectConfig, error) {
|
||||
data, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read project file: %w", err)
|
||||
}
|
||||
|
||||
var project ProjectConfig
|
||||
if err := yaml.Unmarshal(data, &project); err != nil {
|
||||
return nil, fmt.Errorf("failed to unmarshal project: %w", err)
|
||||
}
|
||||
|
||||
return &project, nil
|
||||
}
|
||||
|
||||
// Validate проверяет корректность конфигурации
|
||||
func (ac *AppConfig) Validate() error {
|
||||
if ac.Config.RACPath == "" {
|
||||
return fmt.Errorf(constants.ErrRACPathRequired)
|
||||
}
|
||||
if ac.Project == nil || ac.Project.ServiceMode == nil {
|
||||
return fmt.Errorf("project service-mode configuration is required")
|
||||
}
|
||||
if ac.Project.ServiceMode.ServerHost == "" {
|
||||
return fmt.Errorf(constants.ErrServerHostRequired)
|
||||
}
|
||||
if ac.Project.ServiceMode.BaseName == "" {
|
||||
return fmt.Errorf(constants.ErrBaseNameRequired)
|
||||
}
|
||||
if ac.Secret.ClusterAdmin == "" {
|
||||
return fmt.Errorf(constants.ErrClusterAdminRequired)
|
||||
}
|
||||
if ac.Secret.DBAdmin == "" {
|
||||
return fmt.Errorf(constants.ErrDBAdminRequired)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetRACAddress возвращает адрес для подключения к RAC
|
||||
func (ac *AppConfig) GetRACAddress() string {
|
||||
return fmt.Sprintf("%s:%d", ac.Project.ServiceMode.ServerHost, ac.Project.ServiceMode.RACPort)
|
||||
}
|
||||
|
||||
// GetClusterCredentials возвращает учетные данные кластера с учетом приоритета проекта
|
||||
func (ac *AppConfig) GetClusterCredentials(projectName string) (string, string) {
|
||||
// Проверяем настройки для конкретного проекта
|
||||
if ac.Secret.Projects != nil {
|
||||
if projectCreds, exists := ac.Secret.Projects[projectName]; exists {
|
||||
if projectCreds.ClusterAdmin != "" {
|
||||
return projectCreds.ClusterAdmin, projectCreds.ClusterAdminPassword
|
||||
}
|
||||
}
|
||||
}
|
||||
// Возвращаем глобальные настройки
|
||||
return ac.Secret.ClusterAdmin, ac.Secret.ClusterAdminPassword
|
||||
}
|
||||
|
||||
// GetDBCredentials возвращает учетные данные базы данных с учетом приоритета проекта
|
||||
func (ac *AppConfig) GetDBCredentials(projectName string) (string, string) {
|
||||
// Проверяем настройки для конкретного проекта
|
||||
if ac.Secret.Projects != nil {
|
||||
if projectCreds, exists := ac.Secret.Projects[projectName]; exists {
|
||||
if projectCreds.DBAdmin != "" {
|
||||
return projectCreds.DBAdmin, projectCreds.DBAdminPassword
|
||||
}
|
||||
}
|
||||
}
|
||||
// Возвращаем глобальные настройки
|
||||
return ac.Secret.DBAdmin, ac.Secret.DBAdminPassword
|
||||
}
|
||||
209
internal/config/config_test.go
Normal file
209
internal/config/config_test.go
Normal file
@@ -0,0 +1,209 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestLoadConfig(t *testing.T) {
|
||||
// Создаем временные файлы для тестирования
|
||||
configContent := `server_host: localhost
|
||||
server_port: 1540
|
||||
rac_port: 1545
|
||||
rac_path: "/path/to/rac"
|
||||
connection_timeout: 30s
|
||||
command_timeout: 60s
|
||||
retry_count: 3
|
||||
retry_delay: 5s
|
||||
log_level: Debug
|
||||
server_name: "Test Server"`
|
||||
|
||||
secretContent := `cluster_admin: "admin"
|
||||
cluster_admin_password: "password"
|
||||
db_admin: "dbadmin"
|
||||
db_admin_password: "dbpassword"`
|
||||
|
||||
projectContent := `service-mode:
|
||||
server_host: "localhost"
|
||||
server_port: 1540
|
||||
rac_port: 1545
|
||||
base_name: "TestDB"
|
||||
log_level: "Info"`
|
||||
|
||||
// Создаем временные файлы
|
||||
configFile, err := os.CreateTemp("", "config_*.yaml")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create temp config file: %v", err)
|
||||
}
|
||||
defer os.Remove(configFile.Name())
|
||||
|
||||
secretFile, err := os.CreateTemp("", "secret_*.yaml")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create temp secret file: %v", err)
|
||||
}
|
||||
defer os.Remove(secretFile.Name())
|
||||
|
||||
projectFile, err := os.CreateTemp("", "project_*.yaml")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create temp project file: %v", err)
|
||||
}
|
||||
defer os.Remove(projectFile.Name())
|
||||
|
||||
// Записываем содержимое
|
||||
if _, err := configFile.WriteString(configContent); err != nil {
|
||||
t.Fatalf("Failed to write config file: %v", err)
|
||||
}
|
||||
configFile.Close()
|
||||
|
||||
if _, err := secretFile.WriteString(secretContent); err != nil {
|
||||
t.Fatalf("Failed to write secret file: %v", err)
|
||||
}
|
||||
secretFile.Close()
|
||||
|
||||
if _, err := projectFile.WriteString(projectContent); err != nil {
|
||||
t.Fatalf("Failed to write project file: %v", err)
|
||||
}
|
||||
projectFile.Close()
|
||||
|
||||
// Тестируем загрузку конфигурации
|
||||
appConfig, err := LoadConfig(configFile.Name(), secretFile.Name(), projectFile.Name())
|
||||
if err != nil {
|
||||
t.Fatalf("LoadConfig failed: %v", err)
|
||||
}
|
||||
|
||||
// Проверяем основную конфигурацию
|
||||
if appConfig.Project.ServiceMode.ServerHost != "localhost" {
|
||||
t.Errorf("Expected ServerHost 'localhost', got '%s'", appConfig.Project.ServiceMode.ServerHost)
|
||||
}
|
||||
|
||||
if appConfig.Project.ServiceMode.ServerPort != 1540 {
|
||||
t.Errorf("Expected ServerPort 1540, got %d", appConfig.Project.ServiceMode.ServerPort)
|
||||
}
|
||||
|
||||
if appConfig.Project.ServiceMode.RACPort != 1545 {
|
||||
t.Errorf("Expected RACPort 1545, got %d", appConfig.Project.ServiceMode.RACPort)
|
||||
}
|
||||
|
||||
connectionTimeout, err := appConfig.Config.GetConnectionTimeout()
|
||||
if err != nil {
|
||||
t.Errorf("Failed to parse ConnectionTimeout: %v", err)
|
||||
}
|
||||
if connectionTimeout != 30*time.Second {
|
||||
t.Errorf("Expected ConnectionTimeout 30s, got %v", connectionTimeout)
|
||||
}
|
||||
|
||||
// Проверяем секретную конфигурацию
|
||||
if appConfig.Secret.ClusterAdmin != "admin" {
|
||||
t.Errorf("Expected ClusterAdmin 'admin', got '%s'", appConfig.Secret.ClusterAdmin)
|
||||
}
|
||||
|
||||
if appConfig.Secret.DBAdminPassword != "dbpassword" {
|
||||
t.Errorf("Expected DBAdminPassword 'dbpassword', got '%s'", appConfig.Secret.DBAdminPassword)
|
||||
}
|
||||
|
||||
// Проверяем проектную конфигурацию
|
||||
if appConfig.Project.ServiceMode.BaseName != "TestDB" {
|
||||
t.Errorf("Expected BaseName 'TestDB', got '%s'", appConfig.Project.ServiceMode.BaseName)
|
||||
}
|
||||
|
||||
if appConfig.Project.ServiceMode.LogLevel != "Info" {
|
||||
t.Errorf("Expected LogLevel 'Info', got '%s'", appConfig.Project.ServiceMode.LogLevel)
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidate(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
config *AppConfig
|
||||
expectErr bool
|
||||
}{
|
||||
{
|
||||
name: "valid config",
|
||||
config: &AppConfig{
|
||||
Config: &Config{
|
||||
RACPath: "/path/to/rac",
|
||||
},
|
||||
Secret: &Secret{
|
||||
ClusterAdmin: "admin",
|
||||
DBAdmin: "dbadmin",
|
||||
},
|
||||
Project: &ProjectConfig{
|
||||
ServiceMode: &ServiceModeConfig{
|
||||
ServerHost: "localhost",
|
||||
BaseName: "TestDB",
|
||||
},
|
||||
},
|
||||
},
|
||||
expectErr: false,
|
||||
},
|
||||
{
|
||||
name: "missing server host",
|
||||
config: &AppConfig{
|
||||
Config: &Config{
|
||||
RACPath: "/path/to/rac",
|
||||
},
|
||||
Secret: &Secret{
|
||||
ClusterAdmin: "admin",
|
||||
DBAdmin: "dbadmin",
|
||||
},
|
||||
Project: &ProjectConfig{
|
||||
ServiceMode: &ServiceModeConfig{
|
||||
ServerHost: "",
|
||||
BaseName: "TestDB",
|
||||
},
|
||||
},
|
||||
},
|
||||
expectErr: true,
|
||||
},
|
||||
{
|
||||
name: "missing cluster admin",
|
||||
config: &AppConfig{
|
||||
Config: &Config{
|
||||
RACPath: "/path/to/rac",
|
||||
},
|
||||
Secret: &Secret{
|
||||
ClusterAdmin: "",
|
||||
DBAdmin: "dbadmin",
|
||||
},
|
||||
Project: &ProjectConfig{
|
||||
ServiceMode: &ServiceModeConfig{
|
||||
ServerHost: "localhost",
|
||||
BaseName: "TestDB",
|
||||
},
|
||||
},
|
||||
},
|
||||
expectErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
err := tt.config.Validate()
|
||||
if tt.expectErr && err == nil {
|
||||
t.Error("Expected error but got none")
|
||||
}
|
||||
if !tt.expectErr && err != nil {
|
||||
t.Errorf("Expected no error but got: %v", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetRACAddress(t *testing.T) {
|
||||
config := &AppConfig{
|
||||
Project: &ProjectConfig{
|
||||
ServiceMode: &ServiceModeConfig{
|
||||
ServerHost: "example.com",
|
||||
RACPort: 1545,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
expected := "example.com:1545"
|
||||
actual := config.GetRACAddress()
|
||||
|
||||
if actual != expected {
|
||||
t.Errorf("Expected '%s', got '%s'", expected, actual)
|
||||
}
|
||||
}
|
||||
291
internal/constants/constants.go
Normal file
291
internal/constants/constants.go
Normal file
@@ -0,0 +1,291 @@
|
||||
package constants
|
||||
|
||||
import "time"
|
||||
|
||||
// Версия приложения
|
||||
const (
|
||||
// AppVersion версия приложения
|
||||
AppVersion = "1.0.0"
|
||||
)
|
||||
|
||||
// Константы для сервисного режима
|
||||
const (
|
||||
// DefaultDeniedMessage сообщение по умолчанию при включении сервисного режима
|
||||
DefaultDeniedMessage = "Техническое обслуживание. Попробуйте позже."
|
||||
|
||||
// DefaultPermissionCode код разрешения для сервисного режима
|
||||
DefaultPermissionCode = "service-mode"
|
||||
|
||||
// TechnicalMaintenanceMessage сообщение для завершения сессий
|
||||
TechnicalMaintenanceMessage = "Техническое обслуживание"
|
||||
)
|
||||
|
||||
// Константы таймаутов
|
||||
const (
|
||||
// DEFAULT_CONNECTION_TIMEOUT таймаут подключения по умолчанию
|
||||
DEFAULT_CONNECTION_TIMEOUT = 30 * time.Second
|
||||
|
||||
// DEFAULT_COMMAND_TIMEOUT таймаут выполнения команды по умолчанию
|
||||
DEFAULT_COMMAND_TIMEOUT = 60 * time.Second
|
||||
|
||||
// DEFAULT_RETRY_COUNT количество попыток повтора по умолчанию
|
||||
DEFAULT_RETRY_COUNT = 3
|
||||
|
||||
// DEFAULT_RETRY_DELAY задержка между попытками по умолчанию
|
||||
DEFAULT_RETRY_DELAY = 5 * time.Second
|
||||
)
|
||||
|
||||
// Константы для RAC команд
|
||||
const (
|
||||
// RAC_INFOBASE_UPDATE команда обновления информационной базы
|
||||
RAC_INFOBASE_UPDATE = "infobase"
|
||||
|
||||
// RAC_SESSION_LIST команда получения списка сессий
|
||||
RAC_SESSION_LIST = "session"
|
||||
|
||||
// RAC_CLUSTER_LIST команда получения списка кластеров
|
||||
RAC_CLUSTER_LIST = "cluster"
|
||||
)
|
||||
|
||||
// Константы состояний сервисного режима
|
||||
const (
|
||||
// SERVICE_MODE_ON включение сервисного режима
|
||||
SERVICE_MODE_ON = "on"
|
||||
|
||||
// SERVICE_MODE_OFF выключение сервисного режима
|
||||
SERVICE_MODE_OFF = "off"
|
||||
)
|
||||
|
||||
// Константы для CLI параметров
|
||||
const (
|
||||
// DefaultConfigPath путь к файлу конфигурации по умолчанию
|
||||
DefaultConfigPath = "config.yaml"
|
||||
|
||||
// DefaultSecretPath путь к файлу секретов по умолчанию
|
||||
DefaultSecretPath = "secret.yaml"
|
||||
|
||||
// CmdEnable команда включения сервисного режима
|
||||
CmdEnable = "enable"
|
||||
|
||||
// CmdDisable команда отключения сервисного режима
|
||||
CmdDisable = "disable"
|
||||
|
||||
// CmdStatus команда проверки статуса
|
||||
CmdStatus = "status"
|
||||
)
|
||||
|
||||
// Константы для сообщений ошибок
|
||||
const (
|
||||
// ErrCommandFailed сообщение об ошибке выполнения команды
|
||||
ErrCommandFailed = "command failed after %d attempts: %w"
|
||||
|
||||
// ErrFailedToParseCommandTimeout сообщение об ошибке парсинга таймаута команды
|
||||
ErrFailedToParseCommandTimeout = "failed to parse command timeout: %w"
|
||||
|
||||
// ErrRACCommandFailed сообщение об ошибке RAC команды
|
||||
ErrRACCommandFailed = "RAC command failed: %w, output: %s"
|
||||
|
||||
// ErrGetClusterList сообщение об ошибке получения списка кластеров
|
||||
ErrGetClusterList = "failed to get cluster list: %w"
|
||||
|
||||
// ErrClusterUUIDNotFound сообщение об ошибке поиска UUID кластера
|
||||
ErrClusterUUIDNotFound = "cluster UUID not found in output"
|
||||
|
||||
// ErrGetInfobaseList сообщение об ошибке получения списка информационных баз
|
||||
ErrGetInfobaseList = "failed to get infobase list: %w"
|
||||
|
||||
// ErrInfobaseUUIDNotFound сообщение об ошибке поиска UUID информационной базы
|
||||
ErrInfobaseUUIDNotFound = "infobase UUID not found for name: %s"
|
||||
|
||||
// ErrUnknownCommand сообщение об неизвестной команде
|
||||
ErrUnknownCommand = "unknown command: %s"
|
||||
|
||||
// ErrEnableServiceMode сообщение об ошибке включения сервисного режима
|
||||
ErrEnableServiceMode = "failed to enable service mode: %w"
|
||||
|
||||
// ErrDisableServiceMode сообщение об ошибке отключения сервисного режима
|
||||
ErrDisableServiceMode = "failed to disable service mode: %w"
|
||||
|
||||
// ErrGetSessions сообщение об ошибке получения списка сессий
|
||||
ErrGetSessions = "failed to get sessions: %w"
|
||||
|
||||
// ErrGetSessionList сообщение об ошибке получения списка сессий
|
||||
ErrGetSessionList = "failed to get session list: %w"
|
||||
|
||||
// ErrTerminateSession сообщение об ошибке завершения сессии
|
||||
ErrTerminateSession = "failed to terminate session %s: %w"
|
||||
|
||||
// ErrGetInfobaseInfo сообщение об ошибке получения информации о базе
|
||||
ErrGetInfobaseInfo = "failed to get infobase info: %w"
|
||||
|
||||
// ErrSessionsDenyVerification сообщение об ошибке проверки блокировки сессий
|
||||
ErrSessionsDenyVerification = "sessions-deny verification failed: expected %s, got %s"
|
||||
|
||||
// ErrScheduledJobsDenyVerification сообщение об ошибке проверки блокировки заданий
|
||||
ErrScheduledJobsDenyVerification = "scheduled-jobs-deny verification failed: expected %s, got %s"
|
||||
|
||||
// ErrLoadConfig сообщение об ошибке загрузки конфигурации
|
||||
ErrLoadConfig = "failed to load config: %w"
|
||||
|
||||
// ErrLoadSecret сообщение об ошибке загрузки секретов
|
||||
ErrLoadSecret = "failed to load secret: %w"
|
||||
|
||||
// ErrConfigValidation сообщение об ошибке валидации конфигурации
|
||||
ErrConfigValidation = "config validation failed: %w"
|
||||
|
||||
// ErrGetClusterUUID сообщение об ошибке получения UUID кластера
|
||||
ErrGetClusterUUID = "failed to get cluster UUID: %w"
|
||||
|
||||
// ErrGetInfobaseUUID сообщение об ошибке получения UUID информационной базы
|
||||
ErrGetInfobaseUUID = "failed to get infobase UUID: %w"
|
||||
|
||||
// ErrDetermineServiceModeStatus сообщение об ошибке определения статуса сервисного режима
|
||||
ErrDetermineServiceModeStatus = "failed to determine service mode status: %w"
|
||||
|
||||
// ErrReadConfigFile сообщение об ошибке чтения файла конфигурации
|
||||
ErrReadConfigFile = "failed to read config file: %w"
|
||||
|
||||
// ErrUnmarshalConfig сообщение об ошибке парсинга конфигурации
|
||||
ErrUnmarshalConfig = "failed to unmarshal config: %w"
|
||||
|
||||
// ErrReadSecretFile сообщение об ошибке чтения файла секретов
|
||||
ErrReadSecretFile = "failed to read secret file: %w"
|
||||
|
||||
// ErrUnmarshalSecret сообщение об ошибке парсинга секретов
|
||||
ErrUnmarshalSecret = "failed to unmarshal secret: %w"
|
||||
|
||||
// ErrServerHostRequired сообщение об обязательном поле server_host
|
||||
ErrServerHostRequired = "server_host is required"
|
||||
|
||||
// ErrRACPathRequired сообщение об обязательном поле rac_path
|
||||
ErrRACPathRequired = "rac_path is required"
|
||||
|
||||
// ErrBaseNameRequired сообщение об обязательном поле base_name
|
||||
ErrBaseNameRequired = "base_name is required"
|
||||
|
||||
// ErrClusterAdminRequired сообщение об обязательном поле cluster_admin
|
||||
ErrClusterAdminRequired = "cluster_admin is required"
|
||||
|
||||
// ErrDBAdminRequired сообщение об обязательном поле db_admin
|
||||
ErrDBAdminRequired = "db_admin is required"
|
||||
)
|
||||
|
||||
// Константы для сообщений логгера
|
||||
const (
|
||||
// LogMsgTerminatingAllSessions сообщение о завершении всех сессий
|
||||
LogMsgTerminatingAllSessions = "Terminating all sessions"
|
||||
|
||||
// LogMsgNoActiveSessions сообщение об отсутствии активных сессий
|
||||
LogMsgNoActiveSessions = "No active sessions found"
|
||||
|
||||
// LogMsgFoundActiveSessions сообщение о найденных активных сессиях
|
||||
LogMsgFoundActiveSessions = "Found active sessions"
|
||||
|
||||
// LogMsgSessionTerminated сообщение о завершении сессии
|
||||
LogMsgSessionTerminated = "Session terminated"
|
||||
|
||||
// LogMsgAllSessionsTerminated сообщение о завершении всех сессий
|
||||
LogMsgAllSessionsTerminated = "All sessions termination completed"
|
||||
|
||||
// LogMsgVerifyingServiceMode сообщение о проверке сервисного режима
|
||||
LogMsgVerifyingServiceMode = "Verifying service mode"
|
||||
|
||||
// LogMsgServiceModeEnabled сообщение об успешном включении сервисного режима
|
||||
LogMsgServiceModeEnabled = "Service mode enabled successfully"
|
||||
|
||||
// LogMsgServiceModeDisabled сообщение об успешном отключении сервисного режима
|
||||
LogMsgServiceModeDisabled = "Service mode disabled successfully"
|
||||
|
||||
// LogMsgServiceModeVerificationSuccessful сообщение об успешной проверке сервисного режима
|
||||
LogMsgServiceModeVerificationSuccessful = "Service mode verification successful"
|
||||
|
||||
// LogMsgExecutingRACCommand сообщение о выполнении RAC команды
|
||||
LogMsgExecutingRACCommand = "Executing RAC command"
|
||||
|
||||
// LogMsgRACCommandExecutedSuccessfully сообщение об успешном выполнении RAC команды
|
||||
LogMsgRACCommandExecutedSuccessfully = "RAC command executed successfully"
|
||||
|
||||
// LogMsgRACCommandFailed сообщение об ошибке выполнения RAC команды
|
||||
LogMsgRACCommandFailed = "RAC command failed"
|
||||
|
||||
// LogMsgFailedToTerminateSession сообщение об ошибке завершения сессии
|
||||
LogMsgFailedToTerminateSession = "Failed to terminate session"
|
||||
|
||||
// LogMsgCommandExecutionFailed сообщение об ошибке выполнения команды
|
||||
LogMsgCommandExecutionFailed = "Command execution failed"
|
||||
|
||||
// LogMsgRACCommand сообщение о RAC команде
|
||||
LogMsgRACCommand = "RAC command"
|
||||
|
||||
// LogMsgEnablingServiceMode сообщение о включении сервисного режима
|
||||
LogMsgEnablingServiceMode = "Enabling service mode"
|
||||
|
||||
// LogMsgDisablingServiceMode сообщение об отключении сервисного режима
|
||||
LogMsgDisablingServiceMode = "Disabling service mode"
|
||||
|
||||
// LogMsgFailedToTerminateAllSessions сообщение об ошибке завершения всех сессий
|
||||
LogMsgFailedToTerminateAllSessions = "Failed to terminate all sessions"
|
||||
|
||||
// LogMsgFailedToParseRetryDelay сообщение об ошибке парсинга задержки повтора
|
||||
LogMsgFailedToParseRetryDelay = "Failed to parse retry delay, using default"
|
||||
|
||||
// LogMsgRetryingAfterDelay сообщение о повторе после задержки
|
||||
LogMsgRetryingAfterDelay = "Retrying after delay"
|
||||
|
||||
// LogMsgRACCommandFailedAfterAllRetries сообщение об ошибке RAC команды после всех попыток
|
||||
LogMsgRACCommandFailedAfterAllRetries = "RAC command failed after all retries"
|
||||
|
||||
// LogMsgGettingClusterUUID сообщение о получении UUID кластера
|
||||
LogMsgGettingClusterUUID = "Getting cluster UUID"
|
||||
|
||||
// LogMsgFoundClusterUUID сообщение о найденном UUID кластера
|
||||
LogMsgFoundClusterUUID = "Found cluster UUID"
|
||||
|
||||
// LogMsgGettingInfobaseUUID сообщение о получении UUID информационной базы
|
||||
LogMsgGettingInfobaseUUID = "Getting infobase UUID"
|
||||
|
||||
// LogMsgFoundInfobaseUUID сообщение о найденном UUID информационной базы
|
||||
LogMsgFoundInfobaseUUID = "Found infobase UUID"
|
||||
)
|
||||
|
||||
// Константы для статусных сообщений
|
||||
const (
|
||||
// StatusServiceModeEnabled статус включенного сервисного режима
|
||||
StatusServiceModeEnabled = "Service mode: ENABLED"
|
||||
|
||||
// StatusServiceModeDisabled статус отключенного сервисного режима
|
||||
StatusServiceModeDisabled = "Service mode: DISABLED"
|
||||
|
||||
// MsgErrorLoadingConfig сообщение об ошибке загрузки конфигурации
|
||||
MsgErrorLoadingConfig = "Error loading config: %v\n"
|
||||
|
||||
// MsgConfigValidationError сообщение об ошибке валидации конфигурации
|
||||
MsgConfigValidationError = "Config validation error: %v\n"
|
||||
|
||||
// MsgVersionFormat формат вывода версии
|
||||
MsgVersionFormat = "GitOps RAC v%s\n"
|
||||
)
|
||||
|
||||
// Константы для magic numbers
|
||||
const (
|
||||
// UUIDLength длина UUID
|
||||
UUIDLength = 36
|
||||
|
||||
// UUIDDashCount количество дефисов в UUID
|
||||
UUIDDashCount = 4
|
||||
|
||||
// DefaultRACPort порт RAC по умолчанию
|
||||
DefaultRACPort = 1545
|
||||
|
||||
// DefaultMainTimeout таймаут main функции
|
||||
DefaultMainTimeout = 5
|
||||
)
|
||||
|
||||
// Константы для логгера
|
||||
const (
|
||||
// PasswordMask маска для паролей
|
||||
PasswordMask = "***"
|
||||
|
||||
// PasswordFlags флаги паролей через запятую для маскирования
|
||||
PasswordFlags = "--cluster-pwd,--infobase-pwd"
|
||||
)
|
||||
110
internal/logger/logger.go
Normal file
110
internal/logger/logger.go
Normal file
@@ -0,0 +1,110 @@
|
||||
package logger
|
||||
|
||||
import (
|
||||
"log/slog"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
|
||||
"git.benadis.ru/gitops/benadis-rac/internal/constants"
|
||||
)
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
l := slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{
|
||||
AddSource: true,
|
||||
Level: logLevel,
|
||||
ReplaceAttr: func(groups []string, a slog.Attr) slog.Attr {
|
||||
if a.Key == slog.SourceKey {
|
||||
s := a.Value.Any().(*slog.Source)
|
||||
s.File = path.Base(s.File)
|
||||
}
|
||||
return a
|
||||
},
|
||||
}))
|
||||
l = l.With(slog.Group("App info",
|
||||
slog.String("version", constants.AppVersion),
|
||||
))
|
||||
|
||||
return &SlogLogger{logger: l}
|
||||
}
|
||||
|
||||
// Debug логирует отладочное сообщение
|
||||
func (l *SlogLogger) Debug(msg string, args ...any) {
|
||||
l.logger.Debug(msg, args...)
|
||||
}
|
||||
|
||||
// Info логирует информационное сообщение
|
||||
func (l *SlogLogger) Info(msg string, args ...any) {
|
||||
l.logger.Info(msg, args...)
|
||||
}
|
||||
|
||||
// Warn логирует предупреждение
|
||||
func (l *SlogLogger) Warn(msg string, args ...any) {
|
||||
l.logger.Warn(msg, args...)
|
||||
}
|
||||
|
||||
// Error логирует ошибку
|
||||
func (l *SlogLogger) Error(msg string, args ...any) {
|
||||
l.logger.Error(msg, args...)
|
||||
}
|
||||
|
||||
// DebugCommand логирует команду с маскированием паролей
|
||||
func (l *SlogLogger) DebugCommand(msg string, command []string) {
|
||||
maskedCommand := maskPasswords(command)
|
||||
l.logger.Debug(msg, "command", strings.Join(maskedCommand, " "))
|
||||
}
|
||||
|
||||
// maskPasswords маскирует пароли в команде
|
||||
func maskPasswords(command []string) []string {
|
||||
masked := make([]string, len(command))
|
||||
copy(masked, command)
|
||||
|
||||
// Преобразуем строку флагов в массив
|
||||
passwordFlags := strings.Split(constants.PasswordFlags, ",")
|
||||
|
||||
for i, arg := range masked {
|
||||
for _, flag := range passwordFlags {
|
||||
if strings.HasPrefix(arg, flag+"=") {
|
||||
// Формат: --flag=value
|
||||
parts := strings.SplitN(arg, "=", 2)
|
||||
if len(parts) == 2 {
|
||||
masked[i] = parts[0] + "=" + constants.PasswordMask
|
||||
}
|
||||
} else if arg == flag && i+1 < len(masked) {
|
||||
// Формат: --flag value
|
||||
masked[i+1] = constants.PasswordMask
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return masked
|
||||
}
|
||||
238
internal/logger/logger_test.go
Normal file
238
internal/logger/logger_test.go
Normal file
@@ -0,0 +1,238 @@
|
||||
package logger
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestMaskPasswords(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
command []string
|
||||
expected []string
|
||||
}{
|
||||
{
|
||||
name: "mask cluster password with equals",
|
||||
command: []string{
|
||||
"rac", "localhost:1545", "infobase", "update",
|
||||
"--cluster-pwd=secret123",
|
||||
"--other-flag=value",
|
||||
},
|
||||
expected: []string{
|
||||
"rac", "localhost:1545", "infobase", "update",
|
||||
"--cluster-pwd=***",
|
||||
"--other-flag=value",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "mask infobase password with equals",
|
||||
command: []string{
|
||||
"rac", "localhost:1545", "infobase", "update",
|
||||
"--infobase-pwd=secret456",
|
||||
},
|
||||
expected: []string{
|
||||
"rac", "localhost:1545", "infobase", "update",
|
||||
"--infobase-pwd=***",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "mask password with space separator",
|
||||
command: []string{
|
||||
"rac", "localhost:1545", "infobase", "update",
|
||||
"--cluster-pwd", "secret789",
|
||||
"--other-flag", "value",
|
||||
},
|
||||
expected: []string{
|
||||
"rac", "localhost:1545", "infobase", "update",
|
||||
"--cluster-pwd", "***",
|
||||
"--other-flag", "value",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "mask both passwords",
|
||||
command: []string{
|
||||
"rac", "localhost:1545", "infobase", "update",
|
||||
"--cluster-pwd=cluster_secret",
|
||||
"--infobase-pwd=infobase_secret",
|
||||
},
|
||||
expected: []string{
|
||||
"rac", "localhost:1545", "infobase", "update",
|
||||
"--cluster-pwd=***",
|
||||
"--infobase-pwd=***",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "no passwords to mask",
|
||||
command: []string{
|
||||
"rac", "localhost:1545", "cluster", "list",
|
||||
"--some-flag=value",
|
||||
},
|
||||
expected: []string{
|
||||
"rac", "localhost:1545", "cluster", "list",
|
||||
"--some-flag=value",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "mixed format passwords",
|
||||
command: []string{
|
||||
"rac", "localhost:1545", "infobase", "update",
|
||||
"--cluster-pwd=secret1",
|
||||
"--infobase-pwd", "secret2",
|
||||
},
|
||||
expected: []string{
|
||||
"rac", "localhost:1545", "infobase", "update",
|
||||
"--cluster-pwd=***",
|
||||
"--infobase-pwd", "***",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
actual := maskPasswords(tt.command)
|
||||
|
||||
if len(actual) != len(tt.expected) {
|
||||
t.Errorf("Expected length %d, got %d", len(tt.expected), len(actual))
|
||||
return
|
||||
}
|
||||
|
||||
for i, expected := range tt.expected {
|
||||
if actual[i] != expected {
|
||||
t.Errorf("At index %d: expected '%s', got '%s'", i, expected, actual[i])
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewLogger(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
level string
|
||||
expected string // Мы не можем напрямую проверить уровень, но можем проверить что логгер создается
|
||||
}{
|
||||
{"debug level", "debug", "debug"},
|
||||
{"info level", "info", "info"},
|
||||
{"warn level", "warn", "warn"},
|
||||
{"error level", "error", "error"},
|
||||
{"unknown level defaults to info", "unknown", "info"},
|
||||
{"empty level defaults to info", "", "info"},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
logger := NewLogger(tt.level)
|
||||
if logger == nil {
|
||||
t.Error("Expected logger to be created, got nil")
|
||||
}
|
||||
|
||||
// Проверяем что логгер реализует интерфейс Logger
|
||||
var _ Logger = logger
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSlogLoggerMethods(t *testing.T) {
|
||||
// Создаем логгер для тестирования
|
||||
logger := NewLogger("debug")
|
||||
|
||||
// Тестируем что методы не паникуют
|
||||
t.Run("debug method", func(t *testing.T) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
t.Errorf("Debug method panicked: %v", r)
|
||||
}
|
||||
}()
|
||||
logger.Debug("test debug message", "key", "value")
|
||||
})
|
||||
|
||||
t.Run("info method", func(t *testing.T) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
t.Errorf("Info method panicked: %v", r)
|
||||
}
|
||||
}()
|
||||
logger.Info("test info message", "key", "value")
|
||||
})
|
||||
|
||||
t.Run("warn method", func(t *testing.T) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
t.Errorf("Warn method panicked: %v", r)
|
||||
}
|
||||
}()
|
||||
logger.Warn("test warn message", "key", "value")
|
||||
})
|
||||
|
||||
t.Run("error method", func(t *testing.T) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
t.Errorf("Error method panicked: %v", r)
|
||||
}
|
||||
}()
|
||||
logger.Error("test error message", "key", "value")
|
||||
})
|
||||
|
||||
t.Run("debug command method", func(t *testing.T) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
t.Errorf("DebugCommand method panicked: %v", r)
|
||||
}
|
||||
}()
|
||||
command := []string{"rac", "--cluster-pwd=secret", "command"}
|
||||
logger.DebugCommand("test command", command)
|
||||
})
|
||||
}
|
||||
|
||||
func TestMaskPasswordsEdgeCases(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
command []string
|
||||
expected []string
|
||||
}{
|
||||
{
|
||||
name: "empty command",
|
||||
command: []string{},
|
||||
expected: []string{},
|
||||
},
|
||||
{
|
||||
name: "single element",
|
||||
command: []string{"rac"},
|
||||
expected: []string{"rac"},
|
||||
},
|
||||
{
|
||||
name: "password flag at end without value",
|
||||
command: []string{
|
||||
"rac", "localhost:1545", "--cluster-pwd",
|
||||
},
|
||||
expected: []string{
|
||||
"rac", "localhost:1545", "--cluster-pwd",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "password flag with empty value",
|
||||
command: []string{
|
||||
"rac", "localhost:1545", "--cluster-pwd=",
|
||||
},
|
||||
expected: []string{
|
||||
"rac", "localhost:1545", "--cluster-pwd=***",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
actual := maskPasswords(tt.command)
|
||||
|
||||
if len(actual) != len(tt.expected) {
|
||||
t.Errorf("Expected length %d, got %d", len(tt.expected), len(actual))
|
||||
return
|
||||
}
|
||||
|
||||
for i, expected := range tt.expected {
|
||||
if actual[i] != expected {
|
||||
t.Errorf("At index %d: expected '%s', got '%s'", i, expected, actual[i])
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
333
internal/rac/rac.go
Normal file
333
internal/rac/rac.go
Normal file
@@ -0,0 +1,333 @@
|
||||
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)
|
||||
}
|
||||
235
internal/rac/service_mode.go
Normal file
235
internal/rac/service_mode.go
Normal file
@@ -0,0 +1,235 @@
|
||||
package rac
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"git.benadis.ru/gitops/benadis-rac/internal/constants"
|
||||
)
|
||||
|
||||
// EnableServiceMode включает сервисный режим
|
||||
func (c *Client) EnableServiceMode(ctx context.Context, params ServiceModeParams) error {
|
||||
c.logger.Info(constants.LogMsgEnablingServiceMode, "cluster_uuid", params.ClusterUUID, "infobase_uuid", params.InfobaseUUID)
|
||||
|
||||
// Сначала отключаем всех пользователей
|
||||
if err := c.TerminateAllSessions(ctx, params.ClusterUUID, params.InfobaseUUID); err != nil {
|
||||
c.logger.Warn(constants.LogMsgFailedToTerminateAllSessions, "error", err)
|
||||
// Продолжаем выполнение, так как это не критичная ошибка
|
||||
}
|
||||
|
||||
// Получаем учетные данные
|
||||
clusterUser, clusterPwd := c.config.GetClusterCredentials("")
|
||||
dbUser, dbPwd := c.config.GetDBCredentials("")
|
||||
|
||||
// Включаем сервисный режим
|
||||
args := []string{
|
||||
c.config.GetRACAddress(),
|
||||
"infobase", "update",
|
||||
"--cluster=" + params.ClusterUUID,
|
||||
"--infobase=" + params.InfobaseUUID,
|
||||
"--sessions-deny=" + constants.SERVICE_MODE_ON,
|
||||
"--scheduled-jobs-deny=" + constants.SERVICE_MODE_ON,
|
||||
"--denied-message=" + params.DeniedMessage,
|
||||
"--permission-code=" + params.PermissionCode,
|
||||
"--cluster-user=" + clusterUser,
|
||||
"--cluster-pwd=" + clusterPwd,
|
||||
"--infobase-user=" + dbUser,
|
||||
"--infobase-pwd=" + dbPwd,
|
||||
}
|
||||
|
||||
_, err := c.ExecuteCommand(ctx, args)
|
||||
if err != nil {
|
||||
return fmt.Errorf(constants.ErrEnableServiceMode, err)
|
||||
}
|
||||
|
||||
c.logger.Info(constants.LogMsgServiceModeEnabled)
|
||||
|
||||
// Верифицируем результат
|
||||
return c.VerifyServiceMode(ctx, params.ClusterUUID, params.InfobaseUUID, true)
|
||||
}
|
||||
|
||||
// DisableServiceMode выключает сервисный режим
|
||||
func (c *Client) DisableServiceMode(ctx context.Context, params ServiceModeParams) error {
|
||||
c.logger.Info(constants.LogMsgDisablingServiceMode, "cluster_uuid", params.ClusterUUID, "infobase_uuid", params.InfobaseUUID)
|
||||
|
||||
// Получаем учетные данные
|
||||
clusterUser, clusterPwd := c.config.GetClusterCredentials("")
|
||||
dbUser, dbPwd := c.config.GetDBCredentials("")
|
||||
|
||||
args := []string{
|
||||
c.config.GetRACAddress(),
|
||||
"infobase", "update",
|
||||
"--cluster=" + params.ClusterUUID,
|
||||
"--infobase=" + params.InfobaseUUID,
|
||||
"--sessions-deny=" + constants.SERVICE_MODE_OFF,
|
||||
"--scheduled-jobs-deny=" + constants.SERVICE_MODE_OFF,
|
||||
"--cluster-user=" + clusterUser,
|
||||
"--cluster-pwd=" + clusterPwd,
|
||||
"--infobase-user=" + dbUser,
|
||||
"--infobase-pwd=" + dbPwd,
|
||||
}
|
||||
|
||||
_, err := c.ExecuteCommand(ctx, args)
|
||||
if err != nil {
|
||||
return fmt.Errorf(constants.ErrDisableServiceMode, err)
|
||||
}
|
||||
|
||||
c.logger.Info(constants.LogMsgServiceModeDisabled)
|
||||
|
||||
// Верифицируем результат
|
||||
return c.VerifyServiceMode(ctx, params.ClusterUUID, params.InfobaseUUID, false)
|
||||
}
|
||||
|
||||
// TerminateAllSessions принудительно завершает все сессии пользователей
|
||||
func (c *Client) TerminateAllSessions(ctx context.Context, clusterUUID, infobaseUUID string) error {
|
||||
c.logger.Info(constants.LogMsgTerminatingAllSessions, "cluster_uuid", clusterUUID, "infobase_uuid", infobaseUUID)
|
||||
|
||||
// Получаем список сессий
|
||||
sessions, err := c.GetSessions(ctx, clusterUUID, infobaseUUID)
|
||||
if err != nil {
|
||||
return fmt.Errorf(constants.ErrGetSessions, err)
|
||||
}
|
||||
|
||||
if len(sessions) == 0 {
|
||||
c.logger.Info(constants.LogMsgNoActiveSessions)
|
||||
return nil
|
||||
}
|
||||
|
||||
c.logger.Info(constants.LogMsgFoundActiveSessions, "count", len(sessions))
|
||||
|
||||
// Завершаем каждую сессию
|
||||
for _, sessionUUID := range sessions {
|
||||
if err := c.TerminateSession(ctx, clusterUUID, sessionUUID); err != nil {
|
||||
c.logger.Warn(constants.LogMsgFailedToTerminateSession, "session_uuid", sessionUUID, "error", err)
|
||||
// Продолжаем с другими сессиями
|
||||
} else {
|
||||
c.logger.Debug(constants.LogMsgSessionTerminated, "session_uuid", sessionUUID)
|
||||
}
|
||||
}
|
||||
|
||||
c.logger.Info(constants.LogMsgAllSessionsTerminated)
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetSessions получает список активных сессий
|
||||
func (c *Client) GetSessions(ctx context.Context, clusterUUID, infobaseUUID string) ([]string, error) {
|
||||
// Получаем учетные данные кластера
|
||||
clusterUser, clusterPwd := c.config.GetClusterCredentials("")
|
||||
|
||||
args := []string{
|
||||
c.config.GetRACAddress(),
|
||||
"session", "list",
|
||||
"--cluster=" + clusterUUID,
|
||||
"--infobase=" + infobaseUUID,
|
||||
"--cluster-user=" + clusterUser,
|
||||
"--cluster-pwd=" + clusterPwd,
|
||||
}
|
||||
|
||||
output, err := c.ExecuteCommand(ctx, args)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf(constants.ErrGetSessionList, err)
|
||||
}
|
||||
|
||||
var sessions []string
|
||||
lines := strings.Split(output, "\n")
|
||||
|
||||
for _, line := range lines {
|
||||
line = strings.TrimSpace(line)
|
||||
// Ищем строки вида "session : <uuid>"
|
||||
if strings.HasPrefix(line, "session") && strings.Contains(line, ":") {
|
||||
parts := strings.SplitN(line, ":", 2)
|
||||
if len(parts) == 2 {
|
||||
sessionUUID := strings.TrimSpace(parts[1])
|
||||
// Проверяем, что это действительно UUID (36 символов с дефисами)
|
||||
if len(sessionUUID) == constants.UUIDLength && strings.Count(sessionUUID, "-") == constants.UUIDDashCount {
|
||||
sessions = append(sessions, sessionUUID)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return sessions, nil
|
||||
}
|
||||
|
||||
// TerminateSession завершает конкретную сессию
|
||||
func (c *Client) TerminateSession(ctx context.Context, clusterUUID, sessionUUID string) error {
|
||||
// Получаем учетные данные кластера
|
||||
clusterUser, clusterPwd := c.config.GetClusterCredentials("")
|
||||
|
||||
args := []string{
|
||||
c.config.GetRACAddress(),
|
||||
"session", "terminate",
|
||||
"--cluster=" + clusterUUID,
|
||||
"--session=" + sessionUUID,
|
||||
"--error-message=" + constants.TechnicalMaintenanceMessage,
|
||||
"--cluster-user=" + clusterUser,
|
||||
"--cluster-pwd=" + clusterPwd,
|
||||
}
|
||||
|
||||
_, err := c.ExecuteCommand(ctx, args)
|
||||
if err != nil {
|
||||
return fmt.Errorf(constants.ErrTerminateSession, sessionUUID, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// VerifyServiceMode проверяет состояние сервисного режима
|
||||
func (c *Client) VerifyServiceMode(ctx context.Context, clusterUUID, infobaseUUID string, expectedEnabled bool) error {
|
||||
c.logger.Info(constants.LogMsgVerifyingServiceMode, "cluster_uuid", clusterUUID, "infobase_uuid", infobaseUUID, "expected_enabled", expectedEnabled)
|
||||
|
||||
// Получаем учетные данные
|
||||
clusterUser, clusterPwd := c.config.GetClusterCredentials("")
|
||||
dbUser, dbPwd := c.config.GetDBCredentials("")
|
||||
|
||||
args := []string{
|
||||
c.config.GetRACAddress(),
|
||||
"infobase", "info",
|
||||
"--cluster=" + clusterUUID,
|
||||
"--infobase=" + infobaseUUID,
|
||||
"--cluster-user=" + clusterUser,
|
||||
"--cluster-pwd=" + clusterPwd,
|
||||
"--infobase-user=" + dbUser,
|
||||
"--infobase-pwd=" + dbPwd,
|
||||
}
|
||||
|
||||
output, err := c.ExecuteCommand(ctx, args)
|
||||
if err != nil {
|
||||
return fmt.Errorf(constants.ErrGetInfobaseInfo, err)
|
||||
}
|
||||
|
||||
lines := strings.Split(output, "\n")
|
||||
var sessionsDeny, scheduledJobsDeny string
|
||||
|
||||
for _, line := range lines {
|
||||
line = strings.TrimSpace(line)
|
||||
if strings.HasPrefix(line, "sessions-deny") {
|
||||
parts := strings.Split(line, ":")
|
||||
if len(parts) >= 2 {
|
||||
sessionsDeny = strings.TrimSpace(parts[1])
|
||||
}
|
||||
} else if strings.HasPrefix(line, "scheduled-jobs-deny") {
|
||||
parts := strings.Split(line, ":")
|
||||
if len(parts) >= 2 {
|
||||
scheduledJobsDeny = strings.TrimSpace(parts[1])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
expectedState := constants.SERVICE_MODE_OFF
|
||||
if expectedEnabled {
|
||||
expectedState = constants.SERVICE_MODE_ON
|
||||
}
|
||||
|
||||
if sessionsDeny != expectedState {
|
||||
return fmt.Errorf(constants.ErrSessionsDenyVerification, expectedState, sessionsDeny)
|
||||
}
|
||||
|
||||
if scheduledJobsDeny != expectedState {
|
||||
return fmt.Errorf(constants.ErrScheduledJobsDenyVerification, expectedState, scheduledJobsDeny)
|
||||
}
|
||||
|
||||
c.logger.Info(constants.LogMsgServiceModeVerificationSuccessful, "sessions_deny", sessionsDeny, "scheduled_jobs_deny", scheduledJobsDeny)
|
||||
return nil
|
||||
}
|
||||
124
internal/service/service.go
Normal file
124
internal/service/service.go
Normal file
@@ -0,0 +1,124 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"git.benadis.ru/gitops/benadis-rac/internal/config"
|
||||
"git.benadis.ru/gitops/benadis-rac/internal/constants"
|
||||
"git.benadis.ru/gitops/benadis-rac/internal/logger"
|
||||
"git.benadis.ru/gitops/benadis-rac/internal/rac"
|
||||
)
|
||||
|
||||
// ServiceModeService сервис для управления сервисным режимом
|
||||
type ServiceModeService struct {
|
||||
racClient *rac.Client
|
||||
config *config.AppConfig
|
||||
logger logger.Logger
|
||||
}
|
||||
|
||||
// NewServiceModeService создает новый сервис
|
||||
func NewServiceModeService(cfg *config.AppConfig, log logger.Logger) *ServiceModeService {
|
||||
racClient := rac.NewClient(cfg, log)
|
||||
return &ServiceModeService{
|
||||
racClient: racClient,
|
||||
config: cfg,
|
||||
logger: log,
|
||||
}
|
||||
}
|
||||
|
||||
// EnableServiceMode включает сервисный режим
|
||||
func (s *ServiceModeService) EnableServiceMode(ctx context.Context) error {
|
||||
s.logger.Info("Starting service mode enablement")
|
||||
|
||||
// Получаем UUID кластера
|
||||
clusterUUID, err := s.racClient.GetClusterUUID(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf(constants.ErrGetClusterUUID, err)
|
||||
}
|
||||
|
||||
// Получаем UUID информационной базы
|
||||
infobaseUUID, err := s.racClient.GetInfobaseUUID(ctx, clusterUUID)
|
||||
if err != nil {
|
||||
return fmt.Errorf(constants.ErrGetInfobaseUUID, err)
|
||||
}
|
||||
|
||||
// Подготавливаем параметры
|
||||
params := rac.ServiceModeParams{
|
||||
ClusterUUID: clusterUUID,
|
||||
InfobaseUUID: infobaseUUID,
|
||||
Enable: true,
|
||||
DeniedMessage: constants.DefaultDeniedMessage,
|
||||
PermissionCode: constants.DefaultPermissionCode,
|
||||
}
|
||||
|
||||
// Включаем сервисный режим
|
||||
if err := s.racClient.EnableServiceMode(ctx, params); err != nil {
|
||||
return fmt.Errorf(constants.ErrEnableServiceMode, err)
|
||||
}
|
||||
|
||||
s.logger.Info(constants.LogMsgServiceModeEnabled)
|
||||
return nil
|
||||
}
|
||||
|
||||
// DisableServiceMode выключает сервисный режим
|
||||
func (s *ServiceModeService) DisableServiceMode(ctx context.Context) error {
|
||||
s.logger.Info("Starting service mode disablement")
|
||||
|
||||
// Получаем UUID кластера
|
||||
clusterUUID, err := s.racClient.GetClusterUUID(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf(constants.ErrGetClusterUUID, err)
|
||||
}
|
||||
|
||||
// Получаем UUID информационной базы
|
||||
infobaseUUID, err := s.racClient.GetInfobaseUUID(ctx, clusterUUID)
|
||||
if err != nil {
|
||||
return fmt.Errorf(constants.ErrGetInfobaseUUID, err)
|
||||
}
|
||||
|
||||
// Подготавливаем параметры
|
||||
params := rac.ServiceModeParams{
|
||||
ClusterUUID: clusterUUID,
|
||||
InfobaseUUID: infobaseUUID,
|
||||
Enable: false,
|
||||
}
|
||||
|
||||
// Выключаем сервисный режим
|
||||
if err := s.racClient.DisableServiceMode(ctx, params); err != nil {
|
||||
return fmt.Errorf(constants.ErrDisableServiceMode, err)
|
||||
}
|
||||
|
||||
s.logger.Info(constants.LogMsgServiceModeDisabled)
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetServiceModeStatus получает текущий статус сервисного режима
|
||||
func (s *ServiceModeService) GetServiceModeStatus(ctx context.Context) (bool, error) {
|
||||
s.logger.Info("Getting service mode status")
|
||||
|
||||
// Получаем UUID кластера
|
||||
clusterUUID, err := s.racClient.GetClusterUUID(ctx)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf(constants.ErrGetClusterUUID, err)
|
||||
}
|
||||
|
||||
// Получаем UUID информационной базы
|
||||
infobaseUUID, err := s.racClient.GetInfobaseUUID(ctx, clusterUUID)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf(constants.ErrGetInfobaseUUID, err)
|
||||
}
|
||||
|
||||
// Проверяем статус (используем VerifyServiceMode для проверки включенного состояния)
|
||||
err = s.racClient.VerifyServiceMode(ctx, clusterUUID, infobaseUUID, true)
|
||||
if err != nil {
|
||||
// Если проверка на включенное состояние не прошла, проверяем выключенное
|
||||
err = s.racClient.VerifyServiceMode(ctx, clusterUUID, infobaseUUID, false)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf(constants.ErrDetermineServiceModeStatus, err)
|
||||
}
|
||||
return false, nil // Сервисный режим выключен
|
||||
}
|
||||
|
||||
return true, nil // Сервисный режим включен
|
||||
}
|
||||
Reference in New Issue
Block a user