192 lines
5.6 KiB
JavaScript
192 lines
5.6 KiB
JavaScript
// containerService.js
|
|
const docker = require('./dockerClient');
|
|
const calculateCPUPercent = require('../utils/calculateCPU');
|
|
const chalk = require('chalk').default;
|
|
|
|
async function listContainers() {
|
|
const containers = await docker.listContainers({ all: true });
|
|
return Promise.all(containers.map(async (containerInfo) => {
|
|
const container = docker.getContainer(containerInfo.Id);
|
|
const inspect = await container.inspect();
|
|
return {
|
|
id: containerInfo.Id,
|
|
name: containerInfo.Names[0].replace('/', ''),
|
|
image: containerInfo.Image,
|
|
status: containerInfo.State,
|
|
state: containerInfo.Status,
|
|
ports: containerInfo.Ports,
|
|
created: containerInfo.Created,
|
|
labels: containerInfo.Labels || {},
|
|
inspect: {
|
|
config: inspect.Config,
|
|
hostConfig: inspect.HostConfig,
|
|
networkSettings: inspect.NetworkSettings
|
|
}
|
|
};
|
|
}));
|
|
}
|
|
|
|
async function getContainerStats(id) {
|
|
const container = docker.getContainer(id);
|
|
const stats = await container.stats({ stream: false });
|
|
const cpuPercent = calculateCPUPercent(stats);
|
|
const memoryUsage = stats.memory_stats.usage || 0;
|
|
const memoryLimit = stats.memory_stats.limit || 0;
|
|
const memoryPercent = memoryLimit > 0 ? (memoryUsage / memoryLimit) * 100 : 0;
|
|
const networks = stats.networks || {};
|
|
const networkIO = Object.keys(networks).reduce((acc, key) => ({
|
|
rx_bytes: acc.rx_bytes + (networks[key].rx_bytes || 0),
|
|
tx_bytes: acc.tx_bytes + (networks[key].tx_bytes || 0)
|
|
}), { rx_bytes: 0, tx_bytes: 0 });
|
|
return {
|
|
cpu_percent: cpuPercent,
|
|
memory: { usage: memoryUsage, limit: memoryLimit, percent: memoryPercent },
|
|
network: networkIO,
|
|
block_io: stats.blkio_stats
|
|
};
|
|
}
|
|
|
|
async function startContainer(id) {
|
|
const container = docker.getContainer(id);
|
|
await container.start();
|
|
console.log(chalk.yellow('Container started:'), chalk.cyan(id));
|
|
}
|
|
|
|
async function stopContainer(id) {
|
|
const container = docker.getContainer(id);
|
|
await container.stop();
|
|
console.log(chalk.yellow('Container stopped:'), chalk.cyan(id));
|
|
}
|
|
|
|
async function restartContainer(id) {
|
|
const container = docker.getContainer(id);
|
|
await container.restart();
|
|
console.log(chalk.yellow('Container restarted:'), chalk.cyan(id));
|
|
}
|
|
|
|
async function createContainer(options) {
|
|
const container = await docker.createContainer(options);
|
|
await container.start();
|
|
console.log(chalk.green('Container created and started:'), chalk.cyan(container.id));
|
|
return container.id;
|
|
}
|
|
|
|
async function removeContainer(id) {
|
|
const container = docker.getContainer(id);
|
|
try { await container.stop(); } catch (e) {}
|
|
await container.remove();
|
|
console.log(chalk.red('Container deleted:'), chalk.cyan(id));
|
|
}
|
|
|
|
async function getContainerLogs(id) {
|
|
const container = docker.getContainer(id);
|
|
const logs = await container.logs({
|
|
stdout: true,
|
|
stderr: true,
|
|
tail: 100,
|
|
timestamps: true
|
|
});
|
|
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 = {
|
|
listContainers,
|
|
getContainerStats,
|
|
startContainer,
|
|
stopContainer,
|
|
restartContainer,
|
|
createContainer,
|
|
removeContainer,
|
|
getContainerLogs,
|
|
execCommand,
|
|
renameContainer
|
|
};
|