Приймач на зонді лежав без діла — тепер під'єднаний. Окремий стрім StreamLogs, а не контрольний канал: сплеск логів під час аварії не має заважати heartbeat і командам. Хост зіставляється за адресою джерела на зонді: у сервера немає контексту мережі клієнта, а один приватний діапазон трапляється в десятках кабінетів. Невідома адреса не привід викинути подію. Подія, що збіглася зі зразком у ncm.device_policies.syslog_match, ставить позачерговий збір конфігу. Типовий зразок покриває Cisco, HP/Huawei, Juniper і MikroTik — навмисно широкий: зайвий бекап коштує секунд, пропущений — цілої зміни. Заразом увесь репозиторій прогнано через gofmt: CI, написаний два кроки тому, перевіряє це і впав би на 29 файлах із порушеннями, накопиченими за весь проєкт. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
79 lines
2.8 KiB
Go
79 lines
2.8 KiB
Go
package store
|
||
|
||
import (
|
||
"context"
|
||
|
||
"github.com/jackc/pgx/v5"
|
||
npv1 "github.com/netpulse/netpulse/gen/go/netpulse/v1"
|
||
)
|
||
|
||
// defaultSyslogMatch — за чим упізнаємо зміну конфігу, якщо політика
|
||
// власного зразка не задала.
|
||
//
|
||
// Три родини покривають майже все, що трапляється в мережах:
|
||
//
|
||
// %SYS-5-CONFIG_I Cisco IOS/NX-OS — вихід із режиму конфігурації
|
||
// CFGCHG / CONFIG_I HP, Huawei, Eltex і похідні
|
||
// commit / configure Juniper та MikroTik
|
||
//
|
||
// Зразок навмисно широкий: зайвий бекап коштує кількох секунд сесії,
|
||
// а пропущений — цілої зміни, про яку ніхто не дізнається.
|
||
const defaultSyslogMatch = `%SYS-[0-9]-CONFIG_I|CFGCHG|CONFIG_I|` +
|
||
`[Cc]ommit complete|[Cc]onfiguration changed|system,info configuration changed`
|
||
|
||
// SyslogBackupTriggers каже, яким хостам подія в журналі щойно
|
||
// призначила позачерговий збір конфігу.
|
||
//
|
||
// Порівняння зразка робить Postgres, а не Go: політика зберігає його
|
||
// рядком у базі, і тягнути всі політики в пам'ять заради кожної пачки
|
||
// журналу означало б робити роботу там, де для неї немає даних.
|
||
//
|
||
// Хости без device_id пропускаються самі собою: подія з невідомої адреси
|
||
// не належить нікому, і збирати з неї нічого.
|
||
func (s *Store) SyslogBackupTriggers(
|
||
ctx context.Context,
|
||
tenantID string,
|
||
entries []*npv1.SyslogEntry,
|
||
) ([]string, error) {
|
||
devices := make([]string, 0, len(entries))
|
||
messages := make([]string, 0, len(entries))
|
||
for _, e := range entries {
|
||
if e.GetDeviceId() == "" || e.GetMessage() == "" {
|
||
continue
|
||
}
|
||
devices = append(devices, e.GetDeviceId())
|
||
messages = append(messages, e.GetMessage())
|
||
}
|
||
if len(devices) == 0 {
|
||
return nil, nil
|
||
}
|
||
|
||
var out []string
|
||
err := s.InTenantTx(ctx, tenantID, func(tx pgx.Tx) error {
|
||
rows, err := tx.Query(ctx, `
|
||
WITH ev(device_id, message) AS (
|
||
SELECT * FROM unnest($2::uuid[], $3::text[])
|
||
)
|
||
SELECT DISTINCT ev.device_id::text
|
||
FROM ev
|
||
JOIN ncm.device_policies dp
|
||
ON dp.device_id = ev.device_id AND dp.tenant_id = $1
|
||
WHERE dp.on_syslog
|
||
AND dp.profile_id IS NOT NULL
|
||
AND ev.message ~ COALESCE(NULLIF(dp.syslog_match, ''), $4)
|
||
`, tenantID, devices, messages, defaultSyslogMatch)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
defer rows.Close()
|
||
for rows.Next() {
|
||
var id string
|
||
if err := rows.Scan(&id); err != nil {
|
||
return err
|
||
}
|
||
out = append(out, id)
|
||
}
|
||
return rows.Err()
|
||
})
|
||
return out, err
|
||
}
|