docker-pilot
Version:
A powerful, scalable Docker CLI library for managing containerized applications of any size
452 lines • 16 kB
JavaScript
"use strict";
/**
* Docker utility functions
* Provides Docker-specific operations and checks
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.DockerUtils = void 0;
const child_process_1 = require("child_process");
const Logger_1 = require("./Logger");
class DockerUtils {
constructor(logger) {
this.logger = logger || new Logger_1.Logger();
}
/**
* Check if Docker is installed and running
*/
async checkDockerStatus() {
const dockerInfo = {
version: '',
serverVersion: '',
isRunning: false,
hasCompose: false
};
try {
// Check Docker client version
const clientVersion = (0, child_process_1.execSync)('docker --version', {
encoding: 'utf8',
stdio: 'pipe'
}).trim();
dockerInfo.version = clientVersion;
// Check if Docker daemon is running
const serverInfo = (0, child_process_1.execSync)('docker info', {
encoding: 'utf8',
stdio: 'pipe'
});
dockerInfo.isRunning = true;
// Extract server version from docker info
const versionMatch = serverInfo.match(/Server Version:\s*(.+)/);
if (versionMatch && versionMatch[1]) {
dockerInfo.serverVersion = versionMatch[1].trim();
}
}
catch (error) {
this.logger.debug('Docker client or daemon not available', error);
}
try {
// Check Docker Compose
const composeVersion = (0, child_process_1.execSync)('docker compose version', {
encoding: 'utf8',
stdio: 'pipe'
}).trim();
dockerInfo.hasCompose = true;
dockerInfo.composeVersion = composeVersion;
}
catch (error) {
this.logger.debug('Docker Compose not available', error);
}
return dockerInfo;
}
/**
* Execute Docker command
*/
async executeDockerCommand(command, args = [], options = {}) {
const fullCommand = `docker ${command} ${args.join(' ')}`.trim();
if (!options.silent) {
this.logger.command(fullCommand);
}
return this.executeCommand(fullCommand, options);
}
/**
* Execute Docker Compose command
*/
async executeComposeCommand(command, args = [], options = {}) {
const composeCmd = options.composeFile
? `docker compose -f ${options.composeFile}`
: 'docker compose';
const fullCommand = `${composeCmd} ${command} ${args.join(' ')}`.trim();
if (!options.silent) {
this.logger.command(fullCommand);
}
return this.executeCommand(fullCommand, options);
}
/**
* Generic command execution
*/
async executeCommand(command, options = {}) {
const startTime = Date.now();
return new Promise((resolve) => {
try {
const result = (0, child_process_1.execSync)(command, {
encoding: 'utf8',
stdio: options.silent ? 'pipe' : 'inherit',
cwd: options.cwd || process.cwd(),
timeout: options.timeout || 300000 // 5 minutes default
});
resolve({
success: true,
output: result,
executionTime: Date.now() - startTime
});
}
catch (error) {
resolve({
success: false,
error: error.message,
exitCode: error.status,
executionTime: Date.now() - startTime
});
}
});
}
/**
* Get container information
*/
async getContainers(projectName) {
try {
const filter = projectName
? `--filter label=com.docker.compose.project=${projectName}`
: '';
const result = await this.executeDockerCommand('ps', [
'--format', '"{{json .}}"',
'--all',
filter
], { silent: true });
if (!result.success || !result.output) {
return [];
}
const containers = [];
const lines = result.output.trim().split('\n').filter(line => line.trim());
for (const line of lines) {
try {
const cleanLine = line.replace(/^"|"$/g, '');
const containerData = JSON.parse(cleanLine);
containers.push({
id: containerData.ID || '',
name: containerData.Names || '',
image: containerData.Image || '',
state: containerData.State || '',
status: containerData.Status || '',
ports: containerData.Ports ? containerData.Ports.split(', ') : [],
created: containerData.CreatedAt || '',
networks: containerData.Networks ? containerData.Networks.split(', ') : []
});
}
catch (parseError) {
this.logger.debug('Error parsing container data', { line, error: parseError });
}
}
return containers;
}
catch (error) {
this.logger.error('Failed to get container information', error);
return [];
}
}
/**
* Get service status from Docker Compose
*/
async getServiceStatus(_projectName, serviceName, options) {
try {
const args = ['ps', '--format', 'json'];
if (serviceName) {
args.push(serviceName);
}
const result = await this.executeComposeCommand('ps', args, {
silent: true,
...(options?.composeFile && { composeFile: options.composeFile })
});
if (!result.success || !result.output) {
return [];
}
const services = [];
const lines = result.output.trim().split('\n').filter(line => line.trim());
for (const line of lines) {
try {
const serviceData = JSON.parse(line);
services.push({
name: serviceData.Service || serviceData.Name || '',
state: this.normalizeState(serviceData.State || ''),
health: this.normalizeHealth(serviceData.Health || ''),
uptime: serviceData.Status || '',
ports: serviceData.Publishers ?
serviceData.Publishers.map((p) => `${p.PublishedPort}:${p.TargetPort}`) :
[],
image: serviceData.Image || '',
containerId: serviceData.ID || ''
});
}
catch (parseError) {
this.logger.debug('Error parsing service data', { line, error: parseError });
}
}
return services;
}
catch (error) {
this.logger.error('Failed to get service status', error);
return [];
}
}
/**
* Get project status
*/ async getProjectStatus(projectName) {
const services = await this.getServiceStatus(projectName);
// const containers = await this.getContainers(projectName); // Commented out for now
const runningServices = services.filter(s => s.state === 'running').length;
const healthyServices = services.filter(s => s.health === 'healthy').length;
// Get networks and volumes
const networks = await this.getProjectNetworks(projectName);
const volumes = await this.getProjectVolumes(projectName);
return {
projectName,
totalServices: services.length,
runningServices,
healthyServices,
services,
networks,
volumes,
lastUpdated: new Date()
};
}
/**
* Get project networks
*/
async getProjectNetworks(projectName) {
try {
const result = await this.executeDockerCommand('network', [
'ls',
'--filter', `label=com.docker.compose.project=${projectName}`,
'--format', '{{.Name}}'
], { silent: true });
if (result.success && result.output) {
return result.output.trim().split('\n').filter(name => name.trim());
}
}
catch (error) {
this.logger.debug('Failed to get project networks', error);
}
return [];
}
/**
* Get project volumes
*/
async getProjectVolumes(projectName) {
try {
const result = await this.executeDockerCommand('volume', [
'ls',
'--filter', `label=com.docker.compose.project=${projectName}`,
'--format', '{{.Name}}'
], { silent: true });
if (result.success && result.output) {
return result.output.trim().split('\n').filter(name => name.trim());
}
}
catch (error) {
this.logger.debug('Failed to get project volumes', error);
}
return [];
}
/**
* Normalize container state
*/
normalizeState(state) {
const normalizedState = state.toLowerCase();
if (normalizedState.includes('running'))
return 'running';
if (normalizedState.includes('exited') || normalizedState.includes('stopped'))
return 'stopped';
if (normalizedState.includes('starting'))
return 'starting';
if (normalizedState.includes('stopping'))
return 'stopping';
if (normalizedState.includes('error') || normalizedState.includes('failed'))
return 'error';
return 'unknown';
}
/**
* Normalize health status
*/
normalizeHealth(health) {
const normalizedHealth = health.toLowerCase();
if (normalizedHealth.includes('healthy'))
return 'healthy';
if (normalizedHealth.includes('unhealthy'))
return 'unhealthy';
if (normalizedHealth.includes('starting'))
return 'starting';
return 'none';
}
/**
* Get container logs
*/ async getLogs(serviceName, options = {}) {
const args = ['logs'];
if (options.follow)
args.push('-f');
if (options.tail)
args.push('--tail', options.tail.toString());
if (options.since)
args.push('--since', options.since);
if (options.until)
args.push('--until', options.until);
args.push(serviceName);
if (options.follow) {
// For following logs, return the child process
const composeCmd = options.composeFile
? ['compose', '-f', options.composeFile, ...args]
: ['compose', ...args];
const child = (0, child_process_1.spawn)('docker', composeCmd, {
stdio: 'inherit',
cwd: process.cwd()
});
return child;
}
else {
// For static logs, return the result
return this.executeComposeCommand('logs', args, {
silent: true,
...(options.composeFile && { composeFile: options.composeFile })
});
}
}
/**
* Get container statistics
*/
async getStats(serviceName) {
try {
const args = ['stats', '--no-stream', '--format', 'json'];
if (serviceName) {
args.push(serviceName);
}
const result = await this.executeDockerCommand('stats', args, { silent: true });
if (!result.success || !result.output) {
return [];
}
const stats = [];
const lines = result.output.trim().split('\n').filter(line => line.trim());
for (const line of lines) {
try {
stats.push(JSON.parse(line));
}
catch (parseError) {
this.logger.debug('Error parsing stats data', { line, error: parseError });
}
}
return stats;
}
catch (error) {
this.logger.error('Failed to get container stats', error);
return [];
}
}
/**
* Clean Docker system
*/ async cleanSystem(options = {}) {
const args = ['prune', '-f'];
if (options.volumes)
args.push('--volumes');
if (options.images)
args.push('-a');
return this.executeDockerCommand('system', args);
}
/**
* Pull images for services
*/
async pullImages(serviceName) {
const args = ['pull'];
if (serviceName) {
args.push(serviceName);
}
return this.executeComposeCommand('pull', args);
}
/**
* Build services
*/
async buildServices(serviceName, options = {}) {
const args = ['build'];
if (options.noCache)
args.push('--no-cache');
if (options.pull)
args.push('--pull');
if (serviceName)
args.push(serviceName);
return this.executeComposeCommand('build', args);
}
/**
* Scale services
*/
async scaleService(serviceName, replicas) {
return this.executeComposeCommand('up', ['-d', '--scale', `${serviceName}=${replicas}`, serviceName]);
}
/**
* Execute command in service container
*/
async execInService(serviceName, command, options = {}) {
const args = ['exec'];
if (!options.interactive)
args.push('-T');
if (options.user)
args.push('-u', options.user);
args.push(serviceName, ...command);
if (options.interactive && options.tty) {
// For interactive sessions, return the child process
const child = (0, child_process_1.spawn)('docker', ['compose', ...args], {
stdio: 'inherit',
cwd: process.cwd()
});
return child;
}
else {
// For non-interactive commands, return the result
return this.executeComposeCommand('exec', args);
}
}
/**
* Get Docker Compose configuration
*/
async getComposeConfig() {
try {
const result = await this.executeComposeCommand('config', ['--format', 'json'], { silent: true });
if (result.success && result.output) {
return JSON.parse(result.output);
}
}
catch (error) {
this.logger.error('Failed to get Docker Compose configuration', error);
}
return null;
}
/**
* Validate Docker Compose file
*/
async validateComposeFile(filePath) {
try {
const args = ['config'];
if (filePath) {
args.unshift('-f', filePath);
}
const result = await this.executeComposeCommand('config', args, { silent: true });
return {
valid: result.success,
errors: result.success ? [] : [result.error || 'Unknown validation error']
};
}
catch (error) {
return {
valid: false,
errors: [error instanceof Error ? error.message : 'Unknown validation error']
};
}
}
}
exports.DockerUtils = DockerUtils;
//# sourceMappingURL=DockerUtils.js.map