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