UNPKG

docker-pilot

Version:

A powerful, scalable Docker CLI library for managing containerized applications of any size

452 lines 16 kB
"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