refactor(server): replace execSync with execAsync
Signed-off-by: Abhinav Sinha <[email protected]>
This commit is contained in:
+16
-14
@@ -2,6 +2,8 @@ const express = require('express');
|
|||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
const { execSync, exec } = require('child_process');
|
const { execSync, exec } = require('child_process');
|
||||||
|
const { promisify } = require('util');
|
||||||
|
const execAsync = promisify(exec);
|
||||||
const Database = require('better-sqlite3');
|
const Database = require('better-sqlite3');
|
||||||
|
|
||||||
const app = express();
|
const app = express();
|
||||||
@@ -96,14 +98,14 @@ function loadBundles() {
|
|||||||
return loadJsonDir(BUNDLES_DIR);
|
return loadJsonDir(BUNDLES_DIR);
|
||||||
}
|
}
|
||||||
|
|
||||||
function runCommand(cmd, timeoutMs = 15000) {
|
async function runCommand(cmd, timeoutMs = 15000) {
|
||||||
try {
|
try {
|
||||||
const output = execSync(cmd, {
|
const { stdout } = await execAsync(cmd, {
|
||||||
timeout: timeoutMs,
|
timeout: timeoutMs,
|
||||||
encoding: 'utf8',
|
encoding: 'utf8',
|
||||||
env: { ...process.env, KUBECONFIG: process.env.KUBECONFIG || '/root/.kube/config' }
|
env: { ...process.env, KUBECONFIG: process.env.KUBECONFIG || '/root/.kube/config' }
|
||||||
}).trim();
|
});
|
||||||
return { success: true, output };
|
return { success: true, output: stdout.trim() };
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return { success: false, output: (e.stdout || '').trim(), error: (e.stderr || e.message || '').trim() };
|
return { success: false, output: (e.stdout || '').trim(), error: (e.stderr || e.message || '').trim() };
|
||||||
}
|
}
|
||||||
@@ -236,14 +238,14 @@ app.post('/api/sessions/:id/abandon', (req, res) => {
|
|||||||
|
|
||||||
|
|
||||||
// POST /api/scenarios/:id/teardown — run teardown_commands
|
// POST /api/scenarios/:id/teardown — run teardown_commands
|
||||||
app.post('/api/scenarios/:id/teardown', (req, res) => {
|
app.post('/api/scenarios/:id/teardown', async (req, res) => {
|
||||||
const scenarios = loadScenarios();
|
const scenarios = loadScenarios();
|
||||||
const scenario = scenarios.find(s => s.id === req.params.id);
|
const scenario = scenarios.find(s => s.id === req.params.id);
|
||||||
if (!scenario) return res.status(404).json({ error: 'Scenario not found' });
|
if (!scenario) return res.status(404).json({ error: 'Scenario not found' });
|
||||||
const results = [];
|
const results = [];
|
||||||
for (const item of (scenario.teardown_commands || [])) {
|
for (const item of (scenario.teardown_commands || [])) {
|
||||||
const cmd = item.command;
|
const cmd = item.command;
|
||||||
const result = runCommand(cmd, 30000);
|
const result = await runCommand(cmd, 30000);
|
||||||
results.push({ command: cmd, ...result });
|
results.push({ command: cmd, ...result });
|
||||||
}
|
}
|
||||||
res.json({ ok: true, results });
|
res.json({ ok: true, results });
|
||||||
@@ -252,7 +254,7 @@ app.post('/api/scenarios/:id/teardown', (req, res) => {
|
|||||||
// ── Context sync (Feature 3) ──────────────────────────────────────────────────
|
// ── Context sync (Feature 3) ──────────────────────────────────────────────────
|
||||||
|
|
||||||
// POST /api/scenarios/:id/context — inject namespace + banner into active terminals
|
// POST /api/scenarios/:id/context — inject namespace + banner into active terminals
|
||||||
app.post('/api/scenarios/:id/context', (req, res) => {
|
app.post('/api/scenarios/:id/context', async (req, res) => {
|
||||||
const scenarios = loadScenarios();
|
const scenarios = loadScenarios();
|
||||||
const scenario = scenarios.find(s => s.id === req.params.id);
|
const scenario = scenarios.find(s => s.id === req.params.id);
|
||||||
if (!scenario) return res.status(404).json({ error: 'Scenario not found' });
|
if (!scenario) return res.status(404).json({ error: 'Scenario not found' });
|
||||||
@@ -270,7 +272,7 @@ app.post('/api/scenarios/:id/context', (req, res) => {
|
|||||||
].join('');
|
].join('');
|
||||||
|
|
||||||
// Set kubectl context namespace silently
|
// Set kubectl context namespace silently
|
||||||
runCommand(`kubectl config set-context --current --namespace=${ns}`, 5000);
|
await runCommand(`kubectl config set-context --current --namespace=${ns}`, 5000);
|
||||||
|
|
||||||
// 1) Clear screen and write banner as terminal output (never touches shell stdin)
|
// 1) Clear screen and write banner as terminal output (never touches shell stdin)
|
||||||
injectToTerminal(clearScreen + banner);
|
injectToTerminal(clearScreen + banner);
|
||||||
@@ -332,7 +334,7 @@ app.get('/api/scenarios/:id', (req, res) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// POST /api/scenarios/:id/setup — run setup_commands for a scenario
|
// POST /api/scenarios/:id/setup — run setup_commands for a scenario
|
||||||
app.post('/api/scenarios/:id/setup', (req, res) => {
|
app.post('/api/scenarios/:id/setup', async (req, res) => {
|
||||||
const scenarios = loadScenarios();
|
const scenarios = loadScenarios();
|
||||||
const scenario = scenarios.find(s => s.id === req.params.id);
|
const scenario = scenarios.find(s => s.id === req.params.id);
|
||||||
if (!scenario) return res.status(404).json({ error: 'Scenario not found' });
|
if (!scenario) return res.status(404).json({ error: 'Scenario not found' });
|
||||||
@@ -340,7 +342,7 @@ app.post('/api/scenarios/:id/setup', (req, res) => {
|
|||||||
const results = [];
|
const results = [];
|
||||||
for (const item of (scenario.setup_commands || [])) {
|
for (const item of (scenario.setup_commands || [])) {
|
||||||
const cmd = item.command;
|
const cmd = item.command;
|
||||||
const result = runCommand(cmd, 30000);
|
const result = await runCommand(cmd, 30000);
|
||||||
results.push({ command: cmd, ...result });
|
results.push({ command: cmd, ...result });
|
||||||
if (!result.success) {
|
if (!result.success) {
|
||||||
// Non-fatal: setup commands like "kubectl create namespace" fail if already exists
|
// Non-fatal: setup commands like "kubectl create namespace" fail if already exists
|
||||||
@@ -364,7 +366,7 @@ app.post('/api/scenarios/:id/setup', (req, res) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// POST /api/scenarios/:id/validate — validate task-based scenario
|
// POST /api/scenarios/:id/validate — validate task-based scenario
|
||||||
app.post('/api/scenarios/:id/validate', (req, res) => {
|
app.post('/api/scenarios/:id/validate', async (req, res) => {
|
||||||
const scenarios = loadScenarios();
|
const scenarios = loadScenarios();
|
||||||
const scenario = scenarios.find(s => s.id === req.params.id);
|
const scenario = scenarios.find(s => s.id === req.params.id);
|
||||||
if (!scenario) return res.status(404).json({ error: 'Scenario not found' });
|
if (!scenario) return res.status(404).json({ error: 'Scenario not found' });
|
||||||
@@ -374,7 +376,7 @@ app.post('/api/scenarios/:id/validate', (req, res) => {
|
|||||||
let allPassed = true;
|
let allPassed = true;
|
||||||
|
|
||||||
for (const check of (scenario.validation?.commands || [])) {
|
for (const check of (scenario.validation?.commands || [])) {
|
||||||
const result = runCommand(check.command, 5000);
|
const result = await runCommand(check.command, 5000);
|
||||||
// Prefer stdout. Only fall back to stderr for non-API errors:
|
// Prefer stdout. Only fall back to stderr for non-API errors:
|
||||||
// - kubectl auth can-i prints "yes"/"no" to stdout → already in result.output.
|
// - kubectl auth can-i prints "yes"/"no" to stdout → already in result.output.
|
||||||
// - kubectl get <missing-resource> prints "Error from server (NotFound):" to stderr
|
// - kubectl get <missing-resource> prints "Error from server (NotFound):" to stderr
|
||||||
@@ -465,8 +467,8 @@ app.post('/api/progress/reset/:id', (req, res) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// GET /api/health
|
// GET /api/health
|
||||||
app.get('/api/health', (req, res) => {
|
app.get('/api/health', async (req, res) => {
|
||||||
const kube = runCommand('kubectl cluster-info --request-timeout=3s 2>&1 | head -1');
|
const kube = await runCommand('kubectl cluster-info --request-timeout=3s 2>&1 | head -1');
|
||||||
res.json({ api: 'ok', cluster: kube.success ? 'ready' : 'not_ready', cluster_info: kube.output });
|
res.json({ api: 'ok', cluster: kube.success ? 'ready' : 'not_ready', cluster_info: kube.output });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user