bitstream/docker/containerService.js

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
};