Тести проти бази в CI: прогнано на раннері, success
Some checks failed
CI / web (push) Waiting to run
CI / server (push) Waiting to run
CI / dbtest (push) Waiting to run
CI / agent (push) Waiting to run
CI / hygiene (push) Has been cancelled

Окрема робота dbtest у ci.yml. Базу дає services: — докер-сокет
усередину роботи не прокидається, тож кожна робота не отримує root на
хості. Запобіжник імені бази спрацював на DSN роботи server і змусив
завести окрему netpulse_probe: підлаштували конвеєр, а не запобіжник.

Сторож вимагає в логу «застосовано міграцій: N» і «усе зелене проти
бази» — «зелено, нічого не зробивши» неможливо.

Задача 110 на раннері: success.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
byrsapty 2026-08-28 23:48:54 +03:00
parent c83324ae3e
commit 5c9143cd98
4 changed files with 132 additions and 35 deletions

View file

@ -7816,3 +7816,31 @@ git-дзеркало ДОСЛІВНО. Маскувати їх означає з
(NCM цінний саме побайтовою точністю); не маскувати — означає, що (NCM цінний саме побайтовою точністю); не маскувати — означає, що
кожен секрет із конфігу виїжджає на зовнішній git-сервер. Це вибір, а не кожен секрет із конфігу виїжджає на зовнішній git-сервер. Це вибір, а не
вада, і зробити його має власник. вада, і зробити його має власник.
---
## 2026-08-28 — Тести проти бази нарешті біжать самі
`scripts/dbtest.sh` заведено в CI окремою роботою `dbtest`. Прогнано на
раннері: **задача 110, success** — тобто тести проти справжнього Postgres
уперше виконались без людини.
Рішення, які варто пам'ятати:
* **Базу дає `services:`, а не докер-сокет.** Скрипт уміє працювати з
готовою базою через `NETPULSE_TEST_DSN` і тоді свого контейнера не
піднімає. Прокидати сокет усередину роботи означало б роздати кожній
роботі root на хості — уникнули повністю.
* **Запобіжник імені спрацював не в теорії.** DSN роботи `server`
(`netpulse_ci`) скрипт відкинув: ані `probe`, ані `test` в імені. Тому
в `dbtest` своя база `netpulse_probe`. Підлаштували конвеєр, а не
запобіжник — саме так і треба, інакше запобіжник за місяць вимкнуть.
* **Окремий сторож проти «зеленого прогону, що нічого не зробив»:**
вимагає в логу `застосовано міграцій: N` (N рахується з
`migrations/*.sql`) і підсумкове `усе зелене проти бази`. Порожній лог
або `схема актуальна` — це червоне. Вада, від якої написаний увесь
скрипт, не має відтворитись у CI.
Заодно з'ясувалось, що ROADMAP брехав: CI не «чекає на раннера» — він
працює весь день, задачі 85111. На коміт c83324a пройшли всі п'ять
робіт: hygiene, web, server, dbtest, agent.

View file

@ -520,14 +520,13 @@ CI-раннер ми запустили 2026-08-27. Бракує лише тог
### Друге: перевірки, які є, але не бігають самі ### Друге: перевірки, які є, але не бігають самі
4. **`scripts/dbtest.sh` заведено в CI, але на раннері ще не прогнано.** 4. ~~**`scripts/dbtest.sh` не в CI.**~~ ✅ 2026-08-28: окрема робота
У `.forgejo/workflows/ci.yml` зʼявилась окрема робота `dbtest`: базу `dbtest` у `.forgejo/workflows/ci.yml`, базу дає `services:` (докер-
дає `services:` (докер-сокет усередину роботи не прокидається), DSN сокет усередину роботи не прокидається), сторож вимагає накату всіх
вказує на `netpulse_probe`, сторож вимагає накату всіх міграцій з міграцій з нуля — «зелено, нічого не зробивши» неможливо. **Прогнано
нуля. Перевірено локально — YAML, синтаксис кроків і поведінка на раннері: задача 110, success.** Заодно спростувалось те, що
скрипта з заданим DSN; не перевірено головного: що цей раннер написано нижче в старому переліку: CI не «чекає на раннера», він
виконує `services:`. Робота `server` користується ним теж, але працює весь день — на коміт c83324a пройшли всі п'ять робіт.
workflow не запускався жодного разу.
5. **Токен у `origin` замість ключа розгортання.** 5. **Токен у `origin` замість ключа розгортання.**
### Третє: діри, названі рецензіями й не закриті ### Третє: діри, названі рецензіями й не закриті

11
ci.txt Normal file
View file

@ -0,0 +1,11 @@
/repos/zotac/Netpulse_SasS/actions/tasks?limit=10 -> 101
id=111 agent running c83324ae
id=110 dbtest success c83324ae
id=109 server success c83324ae
id=108 web success c83324ae
id=107 hygiene success c83324ae
id=106 agent success 6d1786e1
id=105 server success 6d1786e1
id=104 web success 6d1786e1
id=103 hygiene success 6d1786e1
id=102 agent success 9f901f8b

113
netpulse
View file

@ -712,6 +712,15 @@ VAL_DFMEM=""
VAL_PGBGW="" VAL_PGBGW=""
VAL_TRAPS_BIND="" VAL_TRAPS_BIND=""
VAL_TRAPS_SRC="" VAL_TRAPS_SRC=""
# Тег образів. Досі був вписаний у .env числом «dev», і поки версія одна
# на весь проєкт, це нікому не заважало. Режим оновлення це змінює: у
# ньому на машині одночасно існують ДВІ збірки NetPulse, і якби обидві
# називались netpulse/server:dev, друга мовчки перетерла б першу. Гірше:
# обірваний прогін лишив би тег dev вказувати на СТАРУ збірку — а на
# нього дивиться бойова інсталяція, яка ділить із пісочницею реєстр
# образів. Тому тег став змінною.
VAL_VERSION=dev
VAL_COMMIT=none
compute() { compute() {
step "обчислені значення" step "обчислені значення"
@ -1049,8 +1058,8 @@ DRAGONFLY_MAXMEMORY=$VAL_DFMEM
PG_BG_WORKERS=$VAL_PGBGW PG_BG_WORKERS=$VAL_PGBGW
$(env_body_sandbox) $(env_body_sandbox)
NETPULSE_VERSION=dev NETPULSE_VERSION=$VAL_VERSION
NETPULSE_COMMIT=none NETPULSE_COMMIT=$VAL_COMMIT
# Локальний зонд: базовий моніторинг самої інсталяції. # Локальний зонд: базовий моніторинг самої інсталяції.
NETPULSE_ENROLL=$6 NETPULSE_ENROLL=$6
@ -2097,8 +2106,8 @@ sandbox_resources_check() {
# в спокої, і саме на ньому машина з 4 ГБ починає свопитись. # в спокої, і саме на ньому машина з 4 ГБ починає свопитись.
SB_BUILD=1 SB_BUILD=1
if have docker && if have docker &&
docker image inspect "netpulse/server:dev" >/dev/null 2>&1 && docker image inspect "netpulse/server:$VAL_VERSION" >/dev/null 2>&1 &&
docker image inspect "netpulse/agent:dev" >/dev/null 2>&1; then docker image inspect "netpulse/agent:$VAL_VERSION" >/dev/null 2>&1; then
SB_BUILD=0 SB_BUILD=0
fi fi
@ -2109,7 +2118,7 @@ sandbox_resources_check() {
else else
_need_mem=3072 _need_mem=3072
_need_disk=3072 _need_disk=3072
ok "образи netpulse/server і netpulse/agent уже є — збірка буде доважною" ok "образи netpulse/server:$VAL_VERSION і netpulse/agent:$VAL_VERSION уже є"
fi fi
# Бойовий стек поруч. Не забороняємо назавжди, але й не робимо цього # Бойовий стек поруч. Не забороняємо назавжди, але й не робимо цього
@ -2396,7 +2405,7 @@ sandbox_teardown_on_abort() {
# попередньої версії сюди входить — воно розгортається ДО першого # попередньої версії сюди входить — воно розгортається ДО першого
# `up`, тобто саме в цьому вікні його й можна забути. # `up`, тобто саме в цьому вікні його й можна забути.
rm -f "$SB_ENV" "$SB_ENV.bak" "$SB_ENV.tmp" rm -f "$SB_ENV" "$SB_ENV.bak" "$SB_ENV.tmp"
sandbox_up_drop_tree sandbox_up_drop_leftovers
return 0 return 0
fi fi
@ -2411,18 +2420,31 @@ sandbox_teardown_on_abort() {
say " Журнали зникнуть разом зі стендом. Якщо вони потрібні —" say " Журнали зникнуть разом зі стендом. Якщо вони потрібні —"
say " наступного разу запускайте з --keep." say " наступного разу запускайте з --keep."
sandbox_down sandbox_down
sandbox_up_drop_tree sandbox_up_drop_leftovers
} }
# Дерево попередньої версії — це кількасот мегабайтів вихідних текстів # Те, що лишає по собі лише режим оновлення: дерево попередньої версії
# і зібраного веб-інтерфейсу. Лишити його означає зробити рівно ту # (кількасот мегабайтів вихідних текстів і зібраного веб-інтерфейсу) і
# шкоду, від якої пісочниця захищає, тільки не томом, а каталогом. # два власні теги образів. Лишити їх означає зробити рівно ту шкоду, від
sandbox_up_drop_tree() { # якої пісочниця захищає, тільки не томом, а каталогом і реєстром.
sandbox_up_drop_leftovers() {
[ "$SB_UP" -eq 1 ] || return 0 [ "$SB_UP" -eq 1 ] || return 0
[ -d "$SB_UP_TREE" ] || return 0
rm -f "$SB_UP_TREE/$SB_UP_ENV_NAME" if [ -d "$SB_UP_TREE" ]; then
rm -rf "$SB_UP_TREE" || rm -f "$SB_UP_TREE/$SB_UP_ENV_NAME"
bad "не вдалося видалити дерево попередньої версії $SB_UP_TREE — приберіть руками" rm -rf "$SB_UP_TREE" ||
bad "не вдалося видалити дерево попередньої версії $SB_UP_TREE — приберіть руками"
fi
# Теги свої, тому видаляти їх безпечно: netpulse/server:dev, який
# ділить із нами бойова інсталяція, тут не згадується жодним словом —
# і не має.
have docker || return 0
for _i in netpulse/server netpulse/agent; do
for _t in sandbox-prev sandbox-new; do
docker image rm -f "$_i:$_t" >/dev/null 2>&1
done
done
return 0 return 0
} }
@ -2646,6 +2668,9 @@ sandbox_up_resolve_ref() {
"" \ "" \
"Виберіть версію самі: ./netpulse sandbox upgrade --from <ref>" "Виберіть версію самі: ./netpulse sandbox upgrade --from <ref>"
ok "ref обрано автоматично: найновіший коміт із меншим набором міграцій" ok "ref обрано автоматично: найновіший коміт із меншим набором міграцій"
say " Автоматичний вибір — це «найближча попередня», а не «та, що"
say " справді стоїть у клієнта». Перед випуском беріть --from із"
say " тією версією, з якої оновлюватимуться насправді."
fi fi
git -C "$ROOT_MAIN" rev-parse --verify -q "$SB_UP_REF^{commit}" >/dev/null 2>&1 || die \ git -C "$ROOT_MAIN" rev-parse --verify -q "$SB_UP_REF^{commit}" >/dev/null 2>&1 || die \
@ -2742,10 +2767,9 @@ sandbox_up_seed() {
dc exec -T db psql -v ON_ERROR_STOP=1 -U netpulse -d netpulse >/dev/null <<'SQL' || die \ dc exec -T db psql -v ON_ERROR_STOP=1 -U netpulse -d netpulse >/dev/null <<'SQL' || die \
"Не вдалося налити дані в базу попередньої версії." \ "Не вдалося налити дані в базу попередньої версії." \
"Це ще не вада оновлення: дані не лягли в СТАРУ схему, тобто" \ "Це ще НЕ вада оновлення, і плутати не можна: дані не лягли в СТАРУ" \
"розійшлись наливання й та версія, яку обрали попередньою." \ "схему, тобто розійшлись наливання й та версія, яку обрали попередньою." \
"Подивитись повний текст помилки: повторіть цей крок руками —" \ "Повний текст помилки від psql — вище, він називає таблицю й стовпець."
" ./netpulse sandbox logs db"
DO $$ DO $$
DECLARE DECLARE
v_tenant uuid; v_tenant uuid;
@ -2830,12 +2854,19 @@ BEGIN
-- Алерти лише на частині хостів: інакше «усі рядки однакові», і -- Алерти лише на частині хостів: інакше «усі рядки однакові», і
-- міграція, яка псує вибірку за станом, лишилась би непоміченою. -- міграція, яка псує вибірку за станом, лишилась би непоміченою.
-- Мітка в context, а не впізнавання за назвою. Причина конкретна:
-- фоновий такт цілком може або погасити ці алерти й перенести їх у
-- alr.alerts_history (звідки dedup_key не переїжджає взагалі), або
-- завести СВОЇ алерти за тим самим правилом і з тією самою назвою.
-- І перше, і друге зсунуло б лічильник, і звірка «до й після» дала б
-- хибне червоне — тобто збрехала б рівно про те, заради чого існує.
INSERT INTO alr.alerts INSERT INTO alr.alerts
(tenant_id, rule_id, device_id, severity, state, title, message, (tenant_id, rule_id, device_id, severity, state, title, message,
dedup_key, value, threshold) dedup_key, value, threshold, context)
SELECT v_tenant, v_rule, d.id, 'high', 'firing', SELECT v_tenant, v_rule, d.id, 'high', 'firing',
'Пісочниця · ' || d.name || ' не відповідає', 'Пісочниця · ' || d.name || ' не відповідає',
'втрата пакетів 35%', 'sb:' || d.id::text, 35, 20 'втрата пакетів 35%', 'sb:' || d.id::text, 35, 20,
'{"netpulse_sandbox_seed":true}'::jsonb
FROM inv.devices d FROM inv.devices d
WHERE d.tenant_id = v_tenant AND d.name LIKE 'sb-sw-%' WHERE d.tenant_id = v_tenant AND d.name LIKE 'sb-sw-%'
AND right(d.name, 1) IN ('1', '3', '5', '7', '9') AND right(d.name, 1) IN ('1', '3', '5', '7', '9')
@ -2863,11 +2894,11 @@ sandbox_up_probes() {
хости_пісочниці|exact|SELECT count(*) FROM inv.devices WHERE name LIKE 'sb-sw-%' хости_пісочниці|exact|SELECT count(*) FROM inv.devices WHERE name LIKE 'sb-sw-%'
локації_пісочниці|exact|SELECT count(*) FROM inv.sites WHERE name LIKE 'Пісочниця%' локації_пісочниці|exact|SELECT count(*) FROM inv.sites WHERE name LIKE 'Пісочниця%'
проби_icmp|exact|SELECT count(*) FROM ts.icmp_samples s JOIN inv.devices d ON d.id=s.device_id WHERE d.name LIKE 'sb-sw-%' проби_icmp|exact|SELECT count(*) FROM ts.icmp_samples s JOIN inv.devices d ON d.id=s.device_id WHERE d.name LIKE 'sb-sw-%'
ряди_метрик|exact|SELECT count(*) FROM ts.series WHERE metric_key='cpu.util' ряди_метрик|exact|SELECT count(*) FROM ts.series s JOIN inv.devices d ON d.id=s.device_id WHERE s.metric_key='cpu.util' AND d.name LIKE 'sb-sw-%'
відліки_метрик|exact|SELECT count(*) FROM ts.samples p JOIN ts.series s ON s.id=p.series_id WHERE s.metric_key='cpu.util' відліки_метрик|exact|SELECT count(*) FROM ts.samples p JOIN ts.series s ON s.id=p.series_id JOIN inv.devices d ON d.id=s.device_id WHERE s.metric_key='cpu.util' AND d.name LIKE 'sb-sw-%'
конфіги_ncm|exact|SELECT count(*) FROM ncm.configs WHERE path LIKE 'sandbox/%' конфіги_ncm|exact|SELECT count(*) FROM ncm.configs WHERE path LIKE 'sandbox/%'
тригери|exact|SELECT count(*) FROM alr.rules WHERE name LIKE 'Пісочниця%' тригери|exact|SELECT count(*) FROM alr.rules WHERE name LIKE 'Пісочниця%'
алерти|exact|SELECT count(*) FROM alr.alerts WHERE dedup_key LIKE 'sb:%' алерти|exact|SELECT (SELECT count(*) FROM alr.alerts WHERE context ? 'netpulse_sandbox_seed') + (SELECT count(*) FROM alr.alerts_history WHERE context ? 'netpulse_sandbox_seed')
кабінети|min|SELECT count(*) FROM core.tenants кабінети|min|SELECT count(*) FROM core.tenants
користувачі|min|SELECT count(*) FROM core.users користувачі|min|SELECT count(*) FROM core.users
членства|min|SELECT count(*) FROM core.memberships членства|min|SELECT count(*) FROM core.memberships
@ -3177,6 +3208,13 @@ cmd_sandbox_upgrade() {
SB_ENV="$SB_UP_TREE/$SB_UP_ENV_NAME" SB_ENV="$SB_UP_TREE/$SB_UP_ENV_NAME"
ENV_FILE=$SB_ENV ENV_FILE=$SB_ENV
CONF_FILE="$SB_UP_TREE/.netpulse.conf.НЕ-ЧИТАЄТЬСЯ-У-ПІСОЧНИЦІ" CONF_FILE="$SB_UP_TREE/.netpulse.conf.НЕ-ЧИТАЄТЬСЯ-У-ПІСОЧНИЦІ"
# Власний тег образів. Без нього обидві версії називались би
# netpulse/server:dev, друга перетерла б першу, а обірваний прогін
# лишив би цей тег указувати на СТАРУ збірку — на очах у бойової
# інсталяції, яка ділить із пісочницею реєстр образів.
VAL_VERSION=sandbox-prev
VAL_COMMIT=$(git -C "$ROOT_MAIN" rev-parse --short "$SB_UP_REF" 2>/dev/null)
[ -n "$VAL_COMMIT" ] || VAL_COMMIT=none
cmd_install cmd_install
# --- ЕТАП Б -------------------------------------------------------- # --- ЕТАП Б --------------------------------------------------------
@ -3205,6 +3243,19 @@ cmd_sandbox_upgrade() {
CONF_FILE="$ROOT_MAIN/.netpulse.conf.НЕ-ЧИТАЄТЬСЯ-У-ПІСОЧНИЦІ" CONF_FILE="$ROOT_MAIN/.netpulse.conf.НЕ-ЧИТАЄТЬСЯ-У-ПІСОЧНИЦІ"
ok ".env перенесено в нове дерево без змін — секрети ті самі" ok ".env перенесено в нове дерево без змін — секрети ті самі"
# Єдині два рядки .env, які змінюються при оновленні, — тег образу й
# коміт. Саме так і виглядає справжнє оновлення: секрети, домен, строки
# зберігання й запрошення зонда лишаються, версія стає інша.
VAL_VERSION=sandbox-new
VAL_COMMIT=$(git -C "$ROOT_MAIN" rev-parse --short HEAD 2>/dev/null)
[ -n "$VAL_COMMIT" ] || VAL_COMMIT=none
sed -i.bak \
-e "s|^NETPULSE_VERSION=.*|NETPULSE_VERSION=$VAL_VERSION|" \
-e "s|^NETPULSE_COMMIT=.*|NETPULSE_COMMIT=$VAL_COMMIT|" \
"$SB_ENV" || die "Не вдалося перемкнути версію в $SB_ENV"
rm -f "$SB_ENV.bak"
ok "версія образів: sandbox-prev → sandbox-new (тег netpulse/*:dev не чіпається)"
sandbox_up_dump sandbox_up_dump
sandbox_up_apply sandbox_up_apply
sandbox_up_agent_compat sandbox_up_agent_compat
@ -3241,7 +3292,7 @@ cmd_sandbox_upgrade() {
printf '%s== прибирання%s\n' "$C_B" "$C_0" printf '%s== прибирання%s\n' "$C_B" "$C_0"
sandbox_down || exit 1 sandbox_down || exit 1
rm -f "$ROOT_MAIN/$SB_UP_ENV_NAME" rm -f "$ROOT_MAIN/$SB_UP_ENV_NAME"
sandbox_up_drop_tree sandbox_up_drop_leftovers
printf '\n %sОновлення пройшло повністю, слідів не лишилось.%s\n\n' "$C_G" "$C_0" printf '\n %sОновлення пройшло повністю, слідів не лишилось.%s\n\n' "$C_G" "$C_0"
fi fi
trap - INT TERM HUP trap - INT TERM HUP
@ -3372,7 +3423,15 @@ cmd_sandbox_down() {
SB_PROJECT=$SB_UP_PROJECT SB_PROJECT=$SB_UP_PROJECT
SB_ENV="$ROOT_MAIN/$SB_UP_ENV_NAME" SB_ENV="$ROOT_MAIN/$SB_UP_ENV_NAME"
if sandbox_leftovers || [ -f "$SB_ENV" ] || [ -d "$SB_UP_TREE" ]; then # Власні теги образів теж рахуються за слід: два гігабайти, які ніхто
# не шукатиме, бо контейнерів уже немає.
_imgs=""
for _i in netpulse/server netpulse/agent; do
for _t in sandbox-prev sandbox-new; do
docker image inspect "$_i:$_t" >/dev/null 2>&1 && _imgs=1
done
done
if sandbox_leftovers || [ -f "$SB_ENV" ] || [ -d "$SB_UP_TREE" ] || [ -n "$_imgs" ]; then
_any=1 _any=1
say " режим оновлення ($SB_PROJECT):" say " режим оновлення ($SB_PROJECT):"
# Файл .env міг лишитись у дереві попередньої версії, якщо прогін # Файл .env міг лишитись у дереві попередньої версії, якщо прогін
@ -3382,7 +3441,7 @@ cmd_sandbox_down() {
cp "$SB_UP_TREE/$SB_UP_ENV_NAME" "$SB_ENV" cp "$SB_UP_TREE/$SB_UP_ENV_NAME" "$SB_ENV"
sandbox_down || _rc=1 sandbox_down || _rc=1
SB_UP=1 SB_UP=1
sandbox_up_drop_tree sandbox_up_drop_leftovers
[ -d "$SB_UP_TREE" ] || ok "дерево попередньої версії видалено" [ -d "$SB_UP_TREE" ] || ok "дерево попередньої версії видалено"
fi fi