Add container exec and rename support
This commit is contained in:
parent
43e27eb713
commit
10a5a8e216
|
|
@ -89,6 +89,94 @@ async function getContainerLogs(id) {
|
||||||
return logs.toString();
|
return logs.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function execCommand(id, command) {
|
||||||
|
const container = docker.getContainer(id);
|
||||||
|
|
||||||
|
// Check if container is running
|
||||||
|
const info = await container.inspect();
|
||||||
|
if (!info.State.Running) {
|
||||||
|
throw new Error('Container is not running');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create exec instance
|
||||||
|
const exec = await container.exec({
|
||||||
|
Cmd: ['/bin/sh', '-c', command],
|
||||||
|
AttachStdout: true,
|
||||||
|
AttachStderr: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Start exec and capture output
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
exec.start({ hijack: true, stdin: false }, (err, stream) => {
|
||||||
|
if (err) {
|
||||||
|
return reject(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
let output = '';
|
||||||
|
let errorOutput = '';
|
||||||
|
|
||||||
|
// Docker multiplexes stdout and stderr into a single stream
|
||||||
|
// Each frame has an 8-byte header
|
||||||
|
stream.on('data', (chunk) => {
|
||||||
|
// Parse Docker stream format
|
||||||
|
let offset = 0;
|
||||||
|
while (offset < chunk.length) {
|
||||||
|
if (chunk.length - offset < 8) break;
|
||||||
|
|
||||||
|
const header = chunk.slice(offset, offset + 8);
|
||||||
|
const streamType = header[0]; // 1=stdout, 2=stderr
|
||||||
|
const size = header.readUInt32BE(4);
|
||||||
|
|
||||||
|
offset += 8;
|
||||||
|
|
||||||
|
if (chunk.length - offset < size) break;
|
||||||
|
|
||||||
|
const payload = chunk.slice(offset, offset + size).toString('utf8');
|
||||||
|
|
||||||
|
if (streamType === 1) {
|
||||||
|
output += payload;
|
||||||
|
} else if (streamType === 2) {
|
||||||
|
errorOutput += payload;
|
||||||
|
}
|
||||||
|
|
||||||
|
offset += size;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
stream.on('end', async () => {
|
||||||
|
try {
|
||||||
|
const execInfo = await exec.inspect();
|
||||||
|
console.log(chalk.blue('Command executed:'), chalk.cyan(command), chalk.yellow('Exit code:'), execInfo.ExitCode);
|
||||||
|
|
||||||
|
resolve({
|
||||||
|
output: (output + errorOutput).trim(),
|
||||||
|
exitCode: execInfo.ExitCode,
|
||||||
|
error: execInfo.ExitCode !== 0 ? errorOutput.trim() : null
|
||||||
|
});
|
||||||
|
} catch (inspectError) {
|
||||||
|
reject(inspectError);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
stream.on('error', (streamError) => {
|
||||||
|
reject(streamError);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Set timeout (30 seconds)
|
||||||
|
setTimeout(() => {
|
||||||
|
stream.destroy();
|
||||||
|
reject(new Error('Command execution timeout'));
|
||||||
|
}, 30000);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function renameContainer(id, newName) {
|
||||||
|
const container = docker.getContainer(id);
|
||||||
|
await container.rename({ name: newName });
|
||||||
|
console.log(chalk.yellow('Container renamed:'), chalk.cyan(id), chalk.green('→'), chalk.cyan(newName));
|
||||||
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
listContainers,
|
listContainers,
|
||||||
getContainerStats,
|
getContainerStats,
|
||||||
|
|
@ -97,5 +185,7 @@ module.exports = {
|
||||||
restartContainer,
|
restartContainer,
|
||||||
createContainer,
|
createContainer,
|
||||||
removeContainer,
|
removeContainer,
|
||||||
getContainerLogs
|
getContainerLogs,
|
||||||
|
execCommand,
|
||||||
|
renameContainer
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -98,4 +98,39 @@ router.get('/:id/logs', async (req, res) => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Execute command in container
|
||||||
|
router.post('/:id/exec', async (req, res) => {
|
||||||
|
try {
|
||||||
|
const { command } = req.body;
|
||||||
|
|
||||||
|
if (!command) {
|
||||||
|
return res.status(400).json({ error: 'Command is required' });
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await containerService.execCommand(req.params.id, command);
|
||||||
|
res.json(result);
|
||||||
|
} catch (error) {
|
||||||
|
res.status(500).json({
|
||||||
|
error: error.message || 'Failed to execute command',
|
||||||
|
exitCode: 1
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Rename container
|
||||||
|
router.post('/:id/rename', async (req, res) => {
|
||||||
|
try {
|
||||||
|
const { name } = req.query;
|
||||||
|
|
||||||
|
if (!name) {
|
||||||
|
return res.status(400).json({ error: 'New name is required' });
|
||||||
|
}
|
||||||
|
|
||||||
|
await containerService.renameContainer(req.params.id, name);
|
||||||
|
res.json({ message: 'Container renamed successfully' });
|
||||||
|
} catch (error) {
|
||||||
|
res.status(500).json({ error: 'Failed to rename container' });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
module.exports = router;
|
module.exports = router;
|
||||||
Loading…
Reference in New Issue