'use strict'; /** * redeploy.js — запуск операции redeploy для инстанса tf-docs через Nubes API. * * Авторизация: Bearer-токен из tf_provider/secrets/test.token. * Токен НЕ печатается. * * Запуск: node llm_project/tests/redeploy.js [--uid ] [--profile test] */ const fs = require('fs'); const argv = process.argv.slice(2); function arg(name, def) { const i = argv.indexOf('--' + name); return i !== -1 && argv[i + 1] ? argv[i + 1] : def; } const UID = arg('uid', '7a48a863-9b40-4cef-a352-033ee9dde8de'); const TOKEN_FILE = arg('token', '/home/naeel/TF/tf_provider/secrets/test.token'); const BASE = arg('base', 'https://lk-api-gateway-test.ngcloud.ru/api/v1/svc'); const ACTION = arg('action', 'redeploy'); const HEADERS = () => ({ Authorization: 'Bearer ' + fs.readFileSync(TOKEN_FILE, 'utf8').trim(), 'Content-Type': 'application/json', 'User-Agent': 'Mozilla/5.0', }); async function api(method, path, body) { const opts = { method, headers: HEADERS() }; if (body !== undefined) opts.body = JSON.stringify(body); const resp = await fetch(BASE + path, opts); const text = await resp.text(); let json = null; try { json = JSON.parse(text); } catch { /* не JSON */ } return { status: resp.status, text, json, location: resp.headers.get('location') }; } /** Извлекает UID из заголовка Location (последний сегмент пути). */ function uidFromLocation(loc) { if (!loc) return ''; const clean = loc.split('?')[0].replace(/\/+$/, ''); const parts = clean.split('/'); return parts[parts.length - 1] || ''; } function sleep(ms) { return new Promise((r) => setTimeout(r, ms)); } async function main() { console.log('Инстанс:', UID, '| операция:', ACTION); // 1) Состояние инстанса: доступные операции + текущие params. const st = await api('GET', `/instances/${UID}`); if (st.status !== 200) { console.error('Ошибка GET /instances:', st.status, st.text.slice(0, 300)); process.exit(1); } const inst = (st.json && st.json.instance) || {}; console.log('Статус:', inst.explainedStatus, '| inProgress:', inst.operationIsInProgress, '| pending:', inst.operationIsPending); const instanceParams = (inst.state && inst.state.params) || {}; const ops = inst.availableOperations || []; const op = ops.find((o) => String(o.operation).toLowerCase() === ACTION.toLowerCase()); if (!op) { console.error('Операция', ACTION, 'недоступна. Доступные:', ops.map((o) => o.operation).join(', ')); process.exit(1); } console.log('svcOperationId:', op.svcOperationId); // 2) Создать операцию. const created = await api('POST', '/instanceOperations', { instanceUid: UID, svcOperationId: op.svcOperationId, operation: ACTION, }); let opUid = ''; if (created.json && (created.json.uid || created.json.instanceOperationUid || created.json.opUid)) { opUid = created.json.uid || created.json.instanceOperationUid || created.json.opUid; } if (!opUid) opUid = uidFromLocation(created.location); if (!opUid) opUid = created.text.replace(/^"|"$/g, '').trim(); console.log('Создана операция:', created.status, '| opUid:', opUid); if (!opUid || opUid.length < 8) { console.error('Не удалось получить opUid. Ответ:', created.text.slice(0, 300)); process.exit(1); } // 2б) Установить требуемые CFS-параметры (например startupConfiguration) из live-состояния. const cfs = await api('GET', `/instanceOperations/${opUid}?fields=cfsParams`); const cfsParams = (cfs.json && cfs.json.instanceOperation && cfs.json.instanceOperation.cfsParams) || []; for (const p of cfsParams) { const val = instanceParams[p.svcOperationCfsParam]; if (val === undefined) { console.log(' параметр', p.svcOperationCfsParam, '(id=' + p.svcOperationCfsParamId + ') — в состоянии нет, пропуск'); continue; } const paramValue = typeof val === 'string' ? val : JSON.stringify(val); const pr = await api('POST', '/instanceOperationCfsParams', { instanceOperationUid: opUid, svcOperationCfsParamId: p.svcOperationCfsParamId, paramValue: paramValue, }); console.log(' параметр', p.svcOperationCfsParam, '=', paramValue, '->', pr.status); } // 3) Запустить операцию. const run = await api('POST', `/instanceOperations/${opUid}/run`, {}); console.log('Запуск:', run.status, run.text.slice(0, 120)); // 4) Ждать завершения. const deadline = Date.now() + 6 * 60 * 1000; let last = ''; while (Date.now() < deadline) { await sleep(5000); const o = await api('GET', `/instanceOperations/${opUid}?fields=dtFinish,isSuccessful,errorLog,isInProgress,isPending,duration,stages`); if (o.status !== 200) { console.log(' poll:', o.status); continue; } const d = (o.json && (o.json.instanceOperation || o.json)) || {}; const line = ` inProgress=${d.isInProgress} pending=${d.isPending} finish=${d.dtFinish || '—'} ok=${d.isSuccessful}`; if (line !== last) { console.log(line); last = line; } if (d.dtFinish && !d.isInProgress && !d.isPending) { if (d.isSuccessful) { console.log('РЕДЕПЛОЙ ЗАВЕРШЁН УСПЕШНО'); } else { console.error('РЕДЕПЛОЙ ЗАВЕРШЁН С ОШИБКОЙ:', d.errorLog || '(нет errorLog)'); process.exit(2); } return; } } console.error('Таймаут ожидания операции'); process.exit(3); } main().catch((e) => { console.error('FATAL', e.message); process.exit(1); });