291 lines
12 KiB
Go
291 lines
12 KiB
Go
package main
|
||
|
||
import (
|
||
"database/sql"
|
||
"fmt"
|
||
"log"
|
||
"net"
|
||
"os"
|
||
"strings"
|
||
"time"
|
||
)
|
||
|
||
// processPTAFClient обрабатывает клиента для PTAF vendor
|
||
// Вся логика из processClient() перенесена сюда без изменений
|
||
func processPTAFClient(
|
||
db *sql.DB,
|
||
config Config,
|
||
hostname, instanceName string,
|
||
clientInfo ClientInfo,
|
||
appsSettingsList []AppsSettings,
|
||
portAllocator *PortAllocator,
|
||
) bool {
|
||
log.Printf("Количество контейнеров: %d", clientInfo.ContainersCount)
|
||
if clientInfo.PTAFConfig.Valid {
|
||
log.Printf("PTAF Config: установлен (скрыт для безопасности)")
|
||
} else {
|
||
log.Printf("PTAF Config: не установлен")
|
||
}
|
||
if clientInfo.FluentBitPort.Valid {
|
||
log.Printf("Fluent Bit Port: %d", clientInfo.FluentBitPort.Int64)
|
||
}
|
||
|
||
// Проверяем принадлежность хоста к instance клиента
|
||
belongsToInstance := false
|
||
if clientInfo.WAFInstance != "" {
|
||
var err error
|
||
belongsToInstance, err = checkHostBelongsToInstance(db, hostname, clientInfo.WAFInstance)
|
||
if err != nil {
|
||
log.Printf("⚠ Ошибка проверки принадлежности хоста для %s: %v", clientInfo.ClientTitle, err)
|
||
return false
|
||
}
|
||
}
|
||
|
||
// Если хост НЕ принадлежит instance — инициируем миграцию
|
||
if clientInfo.WAFInstance != "" && !belongsToInstance {
|
||
handleClientMigration(clientInfo, hostname)
|
||
return false
|
||
}
|
||
|
||
// Хост принадлежит instance — обрабатываем
|
||
if clientInfo.WAFInstance == "" {
|
||
log.Printf("ℹ Клиент %s не привязан к конкретному instance, обрабатываю", clientInfo.ClientTitle)
|
||
} else {
|
||
log.Printf("✓ Клиент %s принадлежит instance '%s', обрабатываю", clientInfo.ClientTitle, clientInfo.WAFInstance)
|
||
}
|
||
|
||
// Если контейнеров 0 — помечаем все существующие для drain и выходим
|
||
if clientInfo.ContainersCount == 0 {
|
||
log.Printf("⚠ Количество контейнеров равно 0 для %s — помечаю существующие контейнеры для drain", clientInfo.ClientTitle)
|
||
processDrainingContainers(clientInfo.ClientTitle, 0)
|
||
|
||
angieConfigPath := fmt.Sprintf("/etc/angie/http.d/angie-ptaf-%s.conf", clientInfo.ClientTitle)
|
||
if _, err := os.Stat(angieConfigPath); err == nil {
|
||
log.Printf(" → Помечаю конфиг Angie для удаления (контейнеров 0)")
|
||
if err := markAngieConfigForDrain(clientInfo.ClientTitle, false); err != nil {
|
||
log.Printf(" ⚠ Ошибка пометки конфига Angie: %v", err)
|
||
} else {
|
||
log.Printf(" ✓ Конфиг Angie помечен для удаления")
|
||
}
|
||
}
|
||
return false
|
||
}
|
||
|
||
// Автоочистка устаревших маркеров drain
|
||
cleanStaleDrainMarkers(clientInfo)
|
||
|
||
// Получение данных ресурсов
|
||
resourcesData := fetchPTAFResourcesData(db, config, appsSettingsList)
|
||
if len(resourcesData) == 0 {
|
||
log.Printf("⚠ Нет ресурсов с данными для клиента %s, пропускаем", clientInfo.ClientTitle)
|
||
return false
|
||
}
|
||
|
||
// Выделение портов
|
||
resourcePortMap, err := resolvePortsForClient(clientInfo, resourcesData, portAllocator)
|
||
if err != nil {
|
||
log.Printf("Ошибка выделения портов для клиента %s: %v", clientInfo.ClientTitle, err)
|
||
return false
|
||
}
|
||
|
||
// ════════════════════════════════════════════════════════════
|
||
// КРИТИЧЕСКОЕ ИСПРАВЛЕНИЕ: Изменен порядок операций
|
||
// БЫЛО: generateAngieConfigs (с reload) → setupContainers
|
||
// СТАЛО: generateAngieConfigs (БЕЗ reload) → setupContainers → проверка портов → reload Angie
|
||
// ════════════════════════════════════════════════════════════
|
||
|
||
// 1. Генерация конфигурации Angie БЕЗ автоматического reload
|
||
angieChanged, err := generateAngieConfigsWithoutReload(config, clientInfo.ClientTitle, resourcesData, resourcePortMap, clientInfo.Debug)
|
||
if err != nil {
|
||
log.Printf("Ошибка генерации конфигов Angie для %s: %v", clientInfo.ClientTitle, err)
|
||
return false
|
||
}
|
||
|
||
// 2. Создание директорий и конфигов для контейнеров (пересоздание если нужно)
|
||
err = setupContainers(config, clientInfo.ClientTitle, &clientInfo, resourcesData, resourcePortMap)
|
||
if err != nil {
|
||
log.Printf("Ошибка настройки контейнеров для %s: %v", clientInfo.ClientTitle, err)
|
||
return false
|
||
}
|
||
|
||
// 3. Reload Angie только ПОСЛЕ того как все контейнеры готовы
|
||
if angieChanged {
|
||
log.Printf(" → Конфиг Angie изменился, проверяем готовность контейнеров перед reload")
|
||
|
||
// Проверяем что все порты контейнеров слушают
|
||
allPortsReady := true
|
||
totalPorts := 0
|
||
readyPorts := 0
|
||
|
||
for _, res := range resourcesData {
|
||
containerPorts, ok := resourcePortMap[res.L7ResourceID]
|
||
if !ok {
|
||
continue
|
||
}
|
||
|
||
for _, ports := range containerPorts {
|
||
// Проверяем HTTPS порты
|
||
for customPort, dockerPort := range ports.HTTPSPorts {
|
||
totalPorts++
|
||
if checkPortListening("127.0.0.1", dockerPort) {
|
||
readyPorts++
|
||
} else {
|
||
log.Printf(" ⚠ Порт %d (custom %d) для l7ResourceID %d не слушает!",
|
||
dockerPort, customPort, res.L7ResourceID)
|
||
allPortsReady = false
|
||
}
|
||
}
|
||
|
||
// Проверяем HTTP порты
|
||
for customPort, dockerPort := range ports.HTTPPorts {
|
||
totalPorts++
|
||
if checkPortListening("127.0.0.1", dockerPort) {
|
||
readyPorts++
|
||
} else {
|
||
log.Printf(" ⚠ Порт %d (custom %d) для l7ResourceID %d не слушает!",
|
||
dockerPort, customPort, res.L7ResourceID)
|
||
allPortsReady = false
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
log.Printf(" → Портов готово: %d/%d", readyPorts, totalPorts)
|
||
|
||
if !allPortsReady {
|
||
log.Printf("⚠ Не все порты готовы, пропускаем reload Angie для безопасности")
|
||
log.Printf(" → Angie продолжит работать со старой конфигурацией")
|
||
log.Printf(" → При следующем запуске программы будет повторена попытка")
|
||
} else {
|
||
log.Printf(" ✓ Все порты слушают, безопасно перезагрузить Angie")
|
||
reloadAngie() // Функция из containers_part2.go (не возвращает error)
|
||
}
|
||
} else {
|
||
log.Printf(" ℹ Конфиг Angie не изменился, reload не требуется")
|
||
}
|
||
// ════════════════════════════════════════════════════════════
|
||
|
||
log.Printf("Клиент %s успешно обработан", clientInfo.ClientTitle)
|
||
return true
|
||
}
|
||
|
||
// fetchPTAFResourcesData получает данные для каждого ресурса из кеша или API
|
||
// Проверяет что waf_vendor = 'ptaf'
|
||
func fetchPTAFResourcesData(db *sql.DB, config Config, appsSettingsList []AppsSettings) []ResourceData {
|
||
var resourcesData []ResourceData
|
||
|
||
for _, appsSettings := range appsSettingsList {
|
||
if !appsSettings.WAFEnabled.Valid || !appsSettings.WAFEnabled.Bool {
|
||
log.Printf("Пропускаем l7ResourceID %d: waf_enabled = false", appsSettings.L7ResourceID)
|
||
continue
|
||
}
|
||
|
||
// Проверяем waf_vendor - обрабатываем только ресурсы PTAF
|
||
if appsSettings.WAFVendor.Valid && strings.ToLower(strings.TrimSpace(appsSettings.WAFVendor.String)) != "ptaf" {
|
||
log.Printf("Пропускаем l7ResourceID %d: waf_vendor = '%s' (требуется 'ptaf')", appsSettings.L7ResourceID, appsSettings.WAFVendor.String)
|
||
continue
|
||
}
|
||
|
||
if !appsSettings.WAFVendor.Valid || appsSettings.WAFVendor.String == "" {
|
||
log.Printf("Пропускаем l7ResourceID %d: waf_vendor не указан (требуется 'ptaf')", appsSettings.L7ResourceID)
|
||
continue
|
||
}
|
||
|
||
mode := "auto"
|
||
if appsSettings.Mode.Valid {
|
||
mode = strings.ToLower(strings.TrimSpace(appsSettings.Mode.String))
|
||
}
|
||
|
||
if mode != "auto" && mode != "manual" {
|
||
log.Printf("Пропускаем l7ResourceID %d: mode = '%s' (требуется 'auto' или 'manual')", appsSettings.L7ResourceID, mode)
|
||
continue
|
||
}
|
||
|
||
if mode == "manual" {
|
||
log.Printf("l7ResourceID %d: mode = 'manual', waf_vendor = 'ptaf' ✓", appsSettings.L7ResourceID)
|
||
} else if appsSettings.Mode.Valid {
|
||
log.Printf("l7ResourceID %d: mode = 'auto', waf_vendor = 'ptaf' ✓", appsSettings.L7ResourceID)
|
||
} else {
|
||
log.Printf("l7ResourceID %d: mode = NULL (обрабатываем как 'auto'), waf_vendor = 'ptaf' ✓", appsSettings.L7ResourceID)
|
||
}
|
||
|
||
log.Printf("Получение данных для l7ResourceID: %d", appsSettings.L7ResourceID)
|
||
|
||
var serverName string
|
||
var aliases []string
|
||
var origins []OriginItem
|
||
var err error
|
||
|
||
if mode == "manual" {
|
||
serverName, aliases, origins, err = getResourceDataFromManualInfo(db, config, appsSettings.L7ResourceID)
|
||
if err != nil {
|
||
log.Printf("Ошибка получения данных из manual_info для l7ResourceID %d: %v", appsSettings.L7ResourceID, err)
|
||
continue
|
||
}
|
||
log.Printf(" ✓ Данные получены из manual_info (waf_vendor: ptaf)")
|
||
} else {
|
||
var found bool
|
||
serverName, aliases, origins, found, err = getResourceDataFromCache(db, config, appsSettings.L7ResourceID)
|
||
|
||
if found && err == nil {
|
||
log.Printf(" Server Name: %s", serverName)
|
||
log.Printf(" Aliases: %v", aliases)
|
||
log.Printf(" Origins: %d шт.", len(origins))
|
||
} else {
|
||
if err != nil {
|
||
log.Printf(" ⚠ Ошибка чтения кеша: %v, запрашиваю API", err)
|
||
} else {
|
||
log.Printf(" ℹ Данных нет в кеше, запрашиваю API")
|
||
}
|
||
|
||
serverName, err = getServerName(config, appsSettings.L7ResourceID)
|
||
if err != nil {
|
||
log.Printf("Ошибка получения server_name для l7ResourceID %d: %v", appsSettings.L7ResourceID, err)
|
||
continue
|
||
}
|
||
log.Printf(" Server Name: %s", serverName)
|
||
|
||
aliases, err = getAliases(config, appsSettings.L7ResourceID)
|
||
if err != nil {
|
||
log.Printf("Ошибка получения aliases для l7ResourceID %d: %v", appsSettings.L7ResourceID, err)
|
||
continue
|
||
}
|
||
log.Printf(" Aliases: %v", aliases)
|
||
|
||
origins, err = getOrigins(config, appsSettings.L7ResourceID)
|
||
if err != nil {
|
||
log.Printf("Ошибка получения origins для l7ResourceID %d: %v", appsSettings.L7ResourceID, err)
|
||
continue
|
||
}
|
||
log.Printf(" Origins: %d шт.", len(origins))
|
||
|
||
// Сохранение в кеш (опционально, можно закомментировать если функция отсутствует)
|
||
// if err := saveResourceDataToCache(db, appsSettings.L7ResourceID, serverName, aliases, origins); err != nil {
|
||
// log.Printf(" ⚠ Ошибка сохранения в кеш: %v", err)
|
||
// }
|
||
}
|
||
}
|
||
|
||
resourcesData = append(resourcesData, ResourceData{
|
||
L7ResourceID: appsSettings.L7ResourceID,
|
||
ServerName: serverName,
|
||
Aliases: aliases,
|
||
Origins: origins,
|
||
AppsSettings: appsSettings,
|
||
})
|
||
}
|
||
|
||
return resourcesData
|
||
}
|
||
|
||
// checkPortListening проверяет что порт слушает на указанном адресе
|
||
// Функция уже есть в main.go, но для совместимости оставим здесь тоже
|
||
// (Go позволяет дублирование функций в одном package если они приватные)
|
||
func checkPortListeningPTAF(host string, port int) bool {
|
||
conn, err := net.DialTimeout("tcp", fmt.Sprintf("%s:%d", host, port), 2*time.Second)
|
||
if err != nil {
|
||
return false
|
||
}
|
||
conn.Close()
|
||
return true
|
||
}
|