실제 환경 배포 결함 수정 3: EnvironmentFile 줄 단위 파싱(공백 값), 사후 절차 실패 시 current 되돌리기, 보존 디렉터리 이름 유일화
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5.1
parent
3641a703db
commit
0862f6fae9
@@ -118,7 +118,7 @@ async function installRelease(ctx, { module, version, appDir, desc, action, toVe
|
||||
if (desc.copyPaths && !existsSync(join(tmp, 'package.json'))) { await ops.remove(tmp).catch(() => {}); throw Object.assign(new Error('artifact 에 package.json 이 없습니다'), { code: 'bad_artifact' }); }
|
||||
await ops.writeJson(join(tmp, 'REVISION.json'), { module, version, upstreamVersion: readJson(join(tmp, 'package.json'), {}).version || null, installedAt: new Date().toISOString(), by: svc.asUser ? svc.user : svc.me });
|
||||
let kept = null;
|
||||
if (existsSync(relDir)) { if (prev && prev.replace(/\/$/, '') === relDir) { kept = `${relDir}.prev-${new Date().toISOString().replace(/[-:.TZ]/g, '').slice(0, 14)}`; await ops.rename(relDir, kept); prevDir = kept; } else await ops.remove(relDir); }
|
||||
if (existsSync(relDir)) { if (prev && prev.replace(/\/$/, '') === relDir) { const base = `${relDir}.prev-${new Date().toISOString().replace(/[-:.TZ]/g, '').slice(0, 14)}`; kept = base; for (let n = 1; existsSync(kept); n++) kept = `${base}.${n}`; await ops.rename(relDir, kept); prevDir = kept; } else await ops.remove(relDir); }
|
||||
await ops.rename(tmp, relDir);
|
||||
rmSync(tgz, { force: true }); return { dir: relDir, asUser: svc.asUser ? svc.user : null, replacedRunning: kept };
|
||||
}, '릴리스 풀기');
|
||||
@@ -139,9 +139,10 @@ async function installOrRollback(ctx, action) {
|
||||
for (const h of desc.preStop || []) await ctx.step(`pre:${h}`, () => hook(ctx, h, { appDir, cur: relDir, unit, svc }), `사전 절차 ${h}`);
|
||||
await ctx.step('unit', () => writeUnit(ctx, desc, unit, appDir, version, svc), '실행 유닛 갱신');
|
||||
await ctx.step('switch', async () => { await ops.switchLink(join(appDir, 'current'), relDir); await ops.writeJson(join(appDir, '.previous.json'), { previous: prev, at: new Date().toISOString() }); return { from: prev, to: relDir }; }, 'current 전환');
|
||||
for (const h of desc.postStart || []) await ctx.step(`post:${h}`, () => hook(ctx, h, { appDir, cur: relDir, unit, svc }), `사후 절차 ${h}`);
|
||||
const self = module === 'agent'; let healthRes = { ok: true };
|
||||
const revert = async () => { if (prev && existsSync(prev)) { await ops.switchLink(join(appDir, 'current'), prev).catch(() => {}); await ctx.sys.systemctl('restart', unit, { optional: true }); } };
|
||||
// 사후 절차(마이그레이션 등)가 실패하면 current 를 이전 릴리스로 되돌린다 — 그대로 두면 다음 재시작 때 마이그레이션 없는 새 코드가 뜬다(실제 환경 결함 수정).
|
||||
for (const h of desc.postStart || []) { try { await ctx.step(`post:${h}`, () => hook(ctx, h, { appDir, cur: relDir, unit, svc }), `사후 절차 ${h}`); } catch (e) { await revert(); throw Object.assign(e, { code: e.code || 'post_hook_failed', partial: { rolledBackTo: prev, hook: h } }); } }
|
||||
if (!self && desc.layout !== 'agent-feature') {
|
||||
try { await ctx.step('restart', () => ctx.sys.systemctl('restart', unit), '서비스 재시작'); for (const u of desc.extraUnits || []) await ctx.sys.systemctl('restart', u, { optional: true }); }
|
||||
catch (e) { await revert(); throw Object.assign(e, { code: e.code || 'restart_failed', partial: { rolledBackTo: prev } }); }
|
||||
|
||||
Reference in New Issue
Block a user