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 }