Один коміт, а не десяток тематичних, свідомо: теми переплетені в
спільних файлах (store.go, docker-compose.yml, deploy/README.md), і
розділити їх можна було б лише індексуванням шматків. Коміти, які не
збираються, гірші за один великий — тим паче що це рівно той стан, який
перевірявся разом.
ЩО ПРАЦЮЄ НА СТЕНДІ Й ПЕРЕВІРЕНО ТАМ
0058 подієві алерти: syslog, ncm, compliance спрацьовують у мить
події; правило з нереалізованим джерелом більше не зберігається
мовчки
0059 snmp.walk і прототипи шаблонів — таблиці з динамічним індексом
описуються шаблоном, а не Go
0060 відкат конфігу: план як різниця, маскування паролів із підписом
плану, обов'язковий контрольний збір, verifying при обриві
0061 кнопки Telegram: довге опитування, авторизація не з callback_data
0062 аудит і архів хостів; тест на AST, що падає на ключі без назви
0063 RLS: три ролі, окремий пул для фонових тактів
0064 строки зберігання даних і сторінка сховища
0065 приймач SNMP-трапів; перевірено справжніми пакетами по дроту,
переклад v1→v2 за RFC 3584 дає правильний OID
0066 ескалації сповіщень
0067 алерт про вичерпання диска
0068 поля заливки конфігу переїхали в каталог профілів
Плюс: 137 тестів вебу з нуля (їх не було взагалі), одинадцять справжніх
вад, знайдених ними й виправлених, і виправлення двох інтеграційних
тестів grpcapi, які мовчки пропускались півтора року.
ЩО ЩЕ НЕ ЗАПУСКАЛОСЬ
netpulse установник: одна команда замість 18 змінних і
593 рядків інструкції
RLS з першого запуску нова інсталяція під політиками одразу;
RLS-EXISTING-INSTALL.md лишається тільки для
старих інсталяцій
.forgejo + CI раннер не зареєстрований
Ці три перевірені компіляцією й міркуванням, але не виконанням.
ГОЛОВНИЙ ВИСНОВОК ДВОХ СЕСІЙ
Зелена перевірка доводить рівно те, що вона перевіряє. Тест ізоляції RLS
був правильний і зелений — і пропустив зламаний вхід, бо перевіряв «чи
не видно чужого», коли зламалось «чи видно своє». Інтеграційні тести
grpcapi були зелені, бо не виконувались. Схема, довідник і протокол
описували те, чого в коді не існувало, і виглядало це як готове.
Тому в кожному завданні цих сесій стояла вимога назвати НЕПОКРИТЕ, а
чотири задачі закінчились не можливістю, а відмовою: правило з
нереалізованим джерелом не зберігається, профіль без команд заливки
каже про це замість мовчазної кнопки, міграція RLS валить сама себе на
таблиці без політики, тест словника аудиту падає на ключі без назви.
Подробиці — HISTORY.md, розділи за 26 і 27 серпня.
361 lines
26 KiB
SQL
361 lines
26 KiB
SQL
-- =====================================================================
|
||
-- NetPulse :: 0060_ncm_rollback.sql
|
||
-- Відкат конфігу перестає бути обіцянкою в схемі.
|
||
--
|
||
-- Таблиця ncm.rollbacks стоїть у базі з 0006 — разом із наміром,
|
||
-- двоетапним погодженням і полем «команди, які реально підуть на
|
||
-- пристрій». Коду під нею немає жодного рядка. Тобто система вміє
|
||
-- зібрати конфіг, зберегти версію, показати різницю, віддзеркалити в
|
||
-- Git і перевірити на відповідність — і не вміє єдиного, заради чого
|
||
-- все це збирають: ПОВЕРНУТИ те, що працювало вчора.
|
||
--
|
||
-- Ця міграція додає три речі, яких бракувало саме коду.
|
||
--
|
||
-- 1. Профіль починає описувати не лише ЯК знімати конфіг, а й ЯК його
|
||
-- заливати. Досі профіль відповідав на «які команди віддати, щоб
|
||
-- побачити конфіг»; тепер — ще й на «як увійти в режим
|
||
-- конфігурації, як прибрати зайвий рядок і як зберегти».
|
||
--
|
||
-- 2. Намір відкату отримує те, без чого він не є доказом: з ЧОГО
|
||
-- відкочували, підпис плану, поділ на «зробимо самі» й «прибрати
|
||
-- руками», результат кожної команди й — головне — контрольний збір
|
||
-- після заливки.
|
||
--
|
||
-- 3. Політика погодження стає рядком у базі, а не звичкою. Вимога
|
||
-- «другу людину» має бути ввімкнена типово й вимикатись окремим
|
||
-- правом, інакше вона не вимога, а порада.
|
||
--
|
||
-- ---------------------------------------------------------------------
|
||
-- Чому заливка — це НЕ «надіслати файл на пристрій»
|
||
-- ---------------------------------------------------------------------
|
||
--
|
||
-- Спокуса зробити відкат як «взяти збережений конфіг і віддати його
|
||
-- пристрою цілком» велика, і вона хибна на всіх родинах, які в нас є.
|
||
-- CLI мережевого заліза не має режиму «замінити конфіг на оцей»: рядки,
|
||
-- надіслані в режим конфігурації, ДОДАЮТЬСЯ до наявного. Хост, у якому
|
||
-- вчора помилково створили VLAN, після такої «заливки» матиме і старий
|
||
-- конфіг, і той VLAN — тобто рівно те, від чого відкочувались.
|
||
--
|
||
-- Тому на пристрій їде РІЗНИЦЯ: рядки, яких бракує, — як є; рядки,
|
||
-- яких бути не повинно, — з префіксом заперечення родини (`no `,
|
||
-- `undo `). Звідси й нові поля профілю, і найважливіше з них —
|
||
-- apply_negate. Порожнє apply_negate чесно означає «ця родина не вміє
|
||
-- прибрати рядок командою»; такі рядки система не вигадує, а показує
|
||
-- людині окремим переліком «прибрати вручну».
|
||
--
|
||
-- ---------------------------------------------------------------------
|
||
-- Чому після заливки обов'язковий контрольний збір
|
||
-- ---------------------------------------------------------------------
|
||
--
|
||
-- Бо відповідь CLI — не доказ. Пристрій відповідає рядком тексту, і
|
||
-- «мовчазна згода» на команду означає «я її прочитав», а не «я її
|
||
-- застосував»: половина платформ мовчки ігнорує рядок, який не
|
||
-- підходить до поточного контексту. Вірити виводу — це той самий
|
||
-- клас помилки, що колись дав нам «Next possible completions» у ролі
|
||
-- версії конфігу в архіві (0034, 0043).
|
||
--
|
||
-- Тому після заливки система йде й ЗНІМАЄ конфіг заново, будує з нього
|
||
-- той самий план ще раз і дивиться, чи лишилось що робити. Нуль команд
|
||
-- — відкат справді відбувся. Не нуль — стан 'mismatch': пристрій НЕ
|
||
-- такий, як хотіли, і про це має дізнатись людина, а не журнал сервера.
|
||
--
|
||
-- Той самий контрольний збір рятує в найгіршому випадку — обриві
|
||
-- зв'язку посеред заливки. Напівзалитий конфіг гірший і за старий, і за
|
||
-- новий, а сервер про нього не знає нічого: результат не приїхав.
|
||
-- Прибиральник переводить таке завдання не у 'failed', а у 'verifying'
|
||
-- — тобто йде дивитись, що реально стало на пристрої. Здогадуватись
|
||
-- тут нема про що: пристрій поруч, його можна спитати.
|
||
-- =====================================================================
|
||
|
||
-- ---------------------------------------------------------------------
|
||
-- Профіль: як заливати конфіг на цю родину заліза
|
||
-- ---------------------------------------------------------------------
|
||
|
||
ALTER TABLE ncm.profiles
|
||
-- Як увійти в режим конфігурації: ["configure terminal"].
|
||
-- Порожньо — родина виконує конфігураційні команди просто із
|
||
-- запрошення (так живе D-Link xStack).
|
||
ADD COLUMN apply_enter jsonb NOT NULL DEFAULT '[]'::jsonb,
|
||
-- Як вийти з нього: ["end"].
|
||
ADD COLUMN apply_exit jsonb NOT NULL DEFAULT '[]'::jsonb,
|
||
-- Чим зберегти, щоб залите пережило перезавантаження.
|
||
ADD COLUMN apply_commit text,
|
||
-- Префікс заперечення рядка: 'no ', 'undo '.
|
||
--
|
||
-- NULL має точний і неприємний зміст: родина не вміє прибрати рядок
|
||
-- однією командою. Це не привід вимкнути відкат — половина відкатів
|
||
-- складається з рядків, які треба ДОДАТИ або переписати, — але й не
|
||
-- привід мовчати: рядки на прибирання підуть людині в перелік
|
||
-- «зробити руками», а не тихо лишаться на пристрої.
|
||
ADD COLUMN apply_negate text,
|
||
-- Чим вийти з вкладеного контексту (`interface …` → `exit`).
|
||
ADD COLUMN apply_block_exit text NOT NULL DEFAULT 'exit',
|
||
-- Головний прапорець. Явний, а не обчислений із «apply_enter не
|
||
-- порожній»: у MikroTik конфігураційні команди теж ідуть без входу
|
||
-- в режим, і відрізнити «родина така» від «профіль не заповнили»
|
||
-- по непрямій ознаці неможливо.
|
||
ADD COLUMN apply_supported boolean NOT NULL DEFAULT false,
|
||
-- Що сказати людині, коли відкат для профілю не налаштований або
|
||
-- налаштований з обмеженням. Порожнє поле в інтерфейсі перетворилось
|
||
-- би на кнопку, яка мовчки не працює.
|
||
ADD COLUMN apply_note text;
|
||
|
||
COMMENT ON COLUMN ncm.profiles.apply_negate IS
|
||
'Префікс заперечення рядка (no /undo ); NULL — родина не вміє прибирати рядок командою';
|
||
COMMENT ON COLUMN ncm.profiles.apply_supported IS
|
||
'Профіль описує заливку конфігу; false — відкат для цих хостів недоступний';
|
||
|
||
-- ---------------------------------------------------------------------
|
||
-- Намір відкату
|
||
-- ---------------------------------------------------------------------
|
||
|
||
-- Два нові стани до наявних семи.
|
||
--
|
||
-- 'verifying' — залито, тепер знімаємо конфіг заново, щоб побачити, що
|
||
-- реально стало. Окремий стан, а не «ще applying»: людина має бачити
|
||
-- різницю між «зараз пишемо на пристрій» і «пишемо вже не ми, а
|
||
-- перевіряємо».
|
||
--
|
||
-- 'mismatch' — найважливіший стан у всій таблиці. Заливка пройшла, а
|
||
-- пристрій вийшов НЕ таким, як хотіли: частина рядків не застосувалась,
|
||
-- частину неможливо прибрати автоматично, або хтось правив конфіг
|
||
-- паралельно. Зводити це до 'failed' означало б поховати різницю між
|
||
-- «нічого не сталось» і «сталось наполовину» — а саме друге вимагає
|
||
-- людини й вимагає її негайно.
|
||
ALTER TYPE ncm.rollback_status ADD VALUE IF NOT EXISTS 'verifying' AFTER 'applying';
|
||
ALTER TYPE ncm.rollback_status ADD VALUE IF NOT EXISTS 'mismatch' AFTER 'applied';
|
||
|
||
ALTER TABLE ncm.rollbacks
|
||
-- З чого відкочуємо: версія, яка стояла на пристрої, коли людина
|
||
-- дивилась на різницю. Потрібна не механіці, а розбору: через місяць
|
||
-- питання «а що там було до того» ставлять саме до цього рядка.
|
||
ADD COLUMN base_config_id uuid REFERENCES ncm.configs(id) ON DELETE SET NULL,
|
||
|
||
-- Зонд фіксується при створенні наміру, як у прогоні команд (0036):
|
||
-- перелік виконавців людина вже бачила, і мовчазна заміна між
|
||
-- погодженням і виконанням — рівно те, чого погодження має не
|
||
-- допускати.
|
||
ADD COLUMN agent_id uuid REFERENCES core.agents(id) ON DELETE SET NULL,
|
||
|
||
-- Навіщо відкочуємо. Текст людини, не системи: саме він читається
|
||
-- першим у розборі й саме його бракує в кожному журналі, де його
|
||
-- немає.
|
||
ADD COLUMN reason text,
|
||
|
||
-- Знімок політики на момент створення наміру.
|
||
--
|
||
-- Саме знімок, а не звірка з поточною політикою при погодженні: інакше
|
||
-- вимкнення вимоги «другої людини» заднім числом легалізувало б усі
|
||
-- наміри, що вже висять у черзі.
|
||
ADD COLUMN requires_approval boolean NOT NULL DEFAULT true,
|
||
|
||
-- Рядки, які система прибрати не може (родина не має заперечення).
|
||
-- Показуються людині ДО підтвердження й лишаються в рядку назавжди:
|
||
-- це половина відповіді на питання «чому після відкату пристрій усе
|
||
-- ще не такий».
|
||
ADD COLUMN manual_lines jsonb NOT NULL DEFAULT '[]'::jsonb,
|
||
|
||
-- sha256 СПРАВЖНЬОГО плану — того, що поїде на пристрій.
|
||
--
|
||
-- Колонка commands зберігає план ЗАМАСКОВАНИМ (див. нижче), тож
|
||
-- звірити «те, що погодили» з «тим, що виконали» за нею не можна.
|
||
-- Підпис звіряється в мить відправки: план перебудовується з
|
||
-- зашифрованих тіл конфігів наново, і якщо він розійшовся з
|
||
-- погодженим — відкат не їде. Це той самий захист, що й перетин
|
||
-- фільтра з підтвердженим переліком у масових командах, тільки
|
||
-- дешевший: одне порівняння 32 байтів.
|
||
ADD COLUMN plan_hash bytea,
|
||
|
||
ADD COLUMN commit_command text,
|
||
ADD COLUMN sent_at timestamptz,
|
||
|
||
-- Відмова — теж рішення, і в неї теж є автор.
|
||
ADD COLUMN rejected_by uuid REFERENCES core.users(id) ON DELETE SET NULL,
|
||
ADD COLUMN rejected_at timestamptz,
|
||
ADD COLUMN decision_note text,
|
||
|
||
-- Результат кожної команди окремо: [{"index":…,"command":…,"output":…}].
|
||
-- Окремо, а не суцільним текстом, бо перше питання до невдалого
|
||
-- відкату — «на якій команді стало».
|
||
ADD COLUMN outcomes jsonb NOT NULL DEFAULT '[]'::jsonb,
|
||
ADD COLUMN committed boolean NOT NULL DEFAULT false,
|
||
|
||
-- Контрольний збір: яким завданням перевіряли й що з нього вийшло.
|
||
ADD COLUMN verify_job_id uuid REFERENCES ncm.jobs(id) ON DELETE SET NULL,
|
||
ADD COLUMN verify_config_id uuid REFERENCES ncm.configs(id) ON DELETE SET NULL,
|
||
-- Скільки команд плану лишилось потрібними ПІСЛЯ заливки. Нуль —
|
||
-- пристрій став таким, як хотіли. Не нуль — стан 'mismatch' і це
|
||
-- число в інтерфейсі.
|
||
ADD COLUMN verify_remaining int;
|
||
|
||
COMMENT ON COLUMN ncm.rollbacks.commands IS
|
||
'План у замаскованому вигляді — для показу й журналу; справжній перебудовується перед відправкою';
|
||
COMMENT ON COLUMN ncm.rollbacks.plan_hash IS
|
||
'sha256 справжнього плану: підпис того, що погодила людина';
|
||
COMMENT ON COLUMN ncm.rollbacks.verify_remaining IS
|
||
'Скільки команд плану лишилось потрібними після контрольного збору; 0 — відкат справді відбувся';
|
||
|
||
-- Маскування плану — не косметика.
|
||
--
|
||
-- У конфізі живуть `snmp-server community`, `username … password …` і
|
||
-- хеші. Тіла конфігів через це лежать зашифрованими в core.secrets
|
||
-- (див. StoreConfig), і покласти ті самі рядки відкритим текстом у
|
||
-- ncm.rollbacks.commands означало б обійти власне шифрування через
|
||
-- сусідню таблицю — причому саме тими рядками, які найцікавіші.
|
||
--
|
||
-- Тому в commands лягає план, пропущений через redact_patterns
|
||
-- профілю, а справжній перебудовується перед відправкою з тих самих
|
||
-- зашифрованих тіл. Людина при погодженні бачить структуру зміни
|
||
-- («буде переписано пароль на vty»), а не сам секрет — і цього для
|
||
-- рішення достатньо.
|
||
|
||
-- Вибірка диспетчера. Предикат навмисно згадує лише ті стани, що
|
||
-- існували до цієї міграції: значення enum, додане щойно, не можна
|
||
-- вживати в тій самій транзакції, де його оголошено.
|
||
CREATE INDEX ncm_rollbacks_pending_idx ON ncm.rollbacks (agent_id, status)
|
||
WHERE status IN ('approved', 'applying');
|
||
CREATE INDEX ncm_rollbacks_tenant_idx ON ncm.rollbacks (tenant_id, created_at DESC);
|
||
|
||
-- ---------------------------------------------------------------------
|
||
-- Політика погодження
|
||
-- ---------------------------------------------------------------------
|
||
|
||
-- Окрема таблиця, а не колонка в налаштуваннях тенанта: у неї є власний
|
||
-- автор і власний час зміни, і питання «хто й коли вимкнув вимогу
|
||
-- другої людини» має мати відповідь у тому ж рядку, що й сама вимога.
|
||
CREATE TABLE ncm.rollback_policy (
|
||
tenant_id uuid PRIMARY KEY REFERENCES core.tenants(id) ON DELETE CASCADE,
|
||
-- Типово УВІМКНЕНО. Значення за замовчуванням тут — це не смак, а
|
||
-- рішення: відкат заливає конфіг на живе залізо, і система, яка
|
||
-- дозволяє зробити це наодинці, поки хтось не увімкне погодження,
|
||
-- насправді його не має.
|
||
require_approval boolean NOT NULL DEFAULT true,
|
||
-- Чи може погодити той самий, хто створив.
|
||
--
|
||
-- Типово НІ — інакше «двоетапне погодження» перетворюється на два
|
||
-- натискання однією людиною, тобто на затримку без перевірки. Але
|
||
-- прапорець потрібен: у мережі на одного інженера вимога другої
|
||
-- людини означає, що відкат не працює ніколи, і тоді її просто
|
||
-- вимкнуть цілком. Хай краще лишиться крок із явним рішенням.
|
||
allow_self_approve boolean NOT NULL DEFAULT false,
|
||
updated_at timestamptz NOT NULL DEFAULT now(),
|
||
updated_by uuid REFERENCES core.users(id) ON DELETE SET NULL
|
||
);
|
||
|
||
ALTER TABLE ncm.rollback_policy ENABLE ROW LEVEL SECURITY;
|
||
ALTER TABLE ncm.rollback_policy FORCE ROW LEVEL SECURITY;
|
||
CREATE POLICY tenant_isolation ON ncm.rollback_policy
|
||
USING (tenant_id = core.current_tenant())
|
||
WITH CHECK (tenant_id = core.current_tenant());
|
||
|
||
GRANT SELECT, INSERT, UPDATE, DELETE ON ncm.rollback_policy
|
||
TO netpulse_app, netpulse_worker;
|
||
|
||
COMMENT ON TABLE ncm.rollback_policy IS
|
||
'Чи вимагає відкат погодження другою людиною — і хто це рішення ухвалив';
|
||
|
||
-- ---------------------------------------------------------------------
|
||
-- Вбудовані профілі: родини, які реально є в мережі
|
||
-- ---------------------------------------------------------------------
|
||
--
|
||
-- Заповнено лише те, що перевірено або однозначно випливає з синтаксису
|
||
-- родини. Решта 140+ профілів лишається з apply_supported = false — і
|
||
-- інтерфейс про це чесно каже. Порожній профіль, який виглядає робочим,
|
||
-- гірший за відсутню кнопку: він обіцяє відкат рівно до того моменту,
|
||
-- коли відкат знадобиться.
|
||
|
||
-- Cisco IOS. Класика, з якої списані всі інші: `configure terminal`,
|
||
-- заперечення через `no `, вихід із контексту `exit`, збереження
|
||
-- `write memory` (а не `copy run start`, який на частині версій
|
||
-- перепитує ім'я файлу й підвисає на очікуванні Enter).
|
||
UPDATE ncm.profiles SET
|
||
apply_enter = '["configure terminal"]'::jsonb,
|
||
apply_exit = '["end"]'::jsonb,
|
||
apply_commit = 'write memory',
|
||
apply_negate = 'no ',
|
||
apply_supported = true
|
||
WHERE tenant_id IS NULL AND key = 'cisco-ios';
|
||
|
||
-- ZTE ZXR10 — CLI родини Cisco з тим самим `configure terminal`/`no `.
|
||
-- Збереження коротше: `write`.
|
||
UPDATE ncm.profiles SET
|
||
apply_enter = '["configure terminal"]'::jsonb,
|
||
apply_exit = '["end"]'::jsonb,
|
||
apply_commit = 'write',
|
||
apply_negate = 'no ',
|
||
apply_supported = true
|
||
WHERE tenant_id IS NULL AND key = 'zte-zxr10';
|
||
|
||
-- ZTE ZXAN (OLT C300/C320/C600) — той самий CLI, що й ZXR10. Профіль
|
||
-- заведено окремо в 0028 через запрошення, а не через синтаксис.
|
||
UPDATE ncm.profiles SET
|
||
apply_enter = '["configure terminal"]'::jsonb,
|
||
apply_exit = '["end"]'::jsonb,
|
||
apply_commit = 'write',
|
||
apply_negate = 'no ',
|
||
apply_supported = true
|
||
WHERE tenant_id IS NULL AND key = 'zte-zxan';
|
||
|
||
-- D-Link DES/DGS (профіль dlink-me, спільний для xStack і Smart /ME —
|
||
-- див. 0043). Тут два свідомі відступи від класики.
|
||
--
|
||
-- Режиму конфігурації немає: команди виконуються просто із запрошення,
|
||
-- тому apply_enter і apply_exit порожні, а вкладених контекстів не
|
||
-- буває — кожен рядок конфігу самодостатній (`create vlan v10 tag 10`).
|
||
--
|
||
-- Заперечення немає ЗОВСІМ, і це не пропуск. У D-Link немає універсального
|
||
-- `no`: створене прибирається `delete`, налаштоване переписується
|
||
-- `config`, увімкнене вимикається `disable`. Вивести з рядка конфігу
|
||
-- потрібне дієслово автоматично неможливо — `create vlan v10 tag 10`
|
||
-- прибирається як `delete vlan v10`, і жодне механічне правило цього не
|
||
-- дасть. Тому рядки на прибирання йдуть людині переліком, а система
|
||
-- заливає лише те, що додає й переписує.
|
||
UPDATE ncm.profiles SET
|
||
apply_enter = '[]'::jsonb,
|
||
apply_exit = '[]'::jsonb,
|
||
apply_commit = 'save',
|
||
apply_negate = NULL,
|
||
apply_block_exit = '',
|
||
apply_supported = true,
|
||
apply_note = 'D-Link не має універсального заперечення рядка: створене прибирається '
|
||
'delete, налаштоване переписується config. Тому зайві рядки система '
|
||
'показує переліком «прибрати вручну», а заливає лише додане й змінене.'
|
||
WHERE tenant_id IS NULL AND key = 'dlink-me';
|
||
|
||
-- MikroTik RouterOS — свідомо БЕЗ відкату, і причина не в бракові часу.
|
||
--
|
||
-- Вивід `export` виглядає як набір команд, але команди в ньому —
|
||
-- `add …`. Повторне виконання `add` не повертає рядок на місце, а
|
||
-- створює ДРУГИЙ такий самий запис: другу адресу на інтерфейсі, друге
|
||
-- правило фаєрвола. Прибирання ж робиться через `remove [find …]` —
|
||
-- тобто через пошук за критерієм, якого в рядку експорту немає.
|
||
--
|
||
-- Тобто механічний відкат на RouterOS не «поки не зроблений», а дає
|
||
-- гарантовано хибний результат. Правильний шлях — `/system backup` або
|
||
-- `/import` файлом, і це інша функція з іншим транспортом.
|
||
UPDATE ncm.profiles SET
|
||
apply_supported = false,
|
||
apply_note = 'RouterOS: рядки експорту — це add, і повторне виконання не повертає '
|
||
'запис, а створює дубль; прибирання потребує remove [find …]. '
|
||
'Автоматичний відкат тут дав би гарантовано хибний результат — '
|
||
'потрібне відновлення з /system backup або /import файлом.'
|
||
WHERE tenant_id IS NULL AND key = 'mikrotik-routeros';
|
||
|
||
-- Juniper JUNOS — теж свідомо без відкату, і теж через формат архіву.
|
||
--
|
||
-- Профіль знімає `show configuration | display omit`, тобто ієрархію у
|
||
-- фігурних дужках. Це не набір команд: віддати такий текст рядками в
|
||
-- CLI неможливо. Правильний шлях на JUNOS — `load override terminal` із
|
||
-- вставкою всього файлу, а він не вкладається в модель «команда →
|
||
-- запрошення → наступна команда», на якій побудований увесь наш CLI.
|
||
--
|
||
-- Альтернатива існує: профіль, що знімає `show configuration |
|
||
-- display set`, дав би рядки `set …` із заперечником `delete `. Це
|
||
-- окрема робота — інший профіль збору й переливання архіву, — і робити
|
||
-- її мовчки, підмінивши формат історії, не можна.
|
||
UPDATE ncm.profiles SET
|
||
apply_supported = false,
|
||
apply_note = 'JUNOS зберігається ієрархією у фігурних дужках — це не набір команд. '
|
||
'Для відкату потрібен профіль зі збором «show configuration | display set» '
|
||
'(рядки set …, заперечення delete …); наявний архів у такому вигляді немає.'
|
||
WHERE tenant_id IS NULL AND key = 'juniper-junos';
|