UNPKG

@xec-sh/core

Version:

Universal shell execution engine

222 lines 8.38 kB
import { DockerError } from '../core/error.js'; export class DockerContainer { constructor(engine, adapter, config) { this.engine = engine; this.adapter = adapter; this.config = config; this.isStarted = false; this.isRemoved = false; this.isContainerCreated = false; this.containerName = config.name || `xec-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`; } get name() { return this.containerName; } get started() { return this.isStarted; } get removed() { return this.isRemoved; } async start() { if (this.isRemoved) { throw new DockerError(this.containerName, 'start', new Error('Container has been removed')); } if (this.isStarted) { return this; } try { if (this.isContainerCreated) { await this.adapter.startContainer(this.containerName); } else { if ('runContainer' in this.adapter) { await this.adapter.runContainer({ name: this.containerName, image: this.config.image, volumes: this.config.volumes ? this.formatVolumes(this.config.volumes) : undefined, env: this.config.env, ports: this.config.ports ? this.formatPorts(this.config.ports) : undefined, network: this.config.network, restart: this.config.restart, workdir: this.config.workdir, user: this.config.user, labels: this.config.labels, privileged: this.config.privileged, healthcheck: this.config.healthcheck, command: this.config.command ? (Array.isArray(this.config.command) ? this.config.command : ['sh', '-c', this.config.command]) : undefined }); } else { const createOptions = { name: this.containerName, image: this.config.image, volumes: this.config.volumes ? this.formatVolumes(this.config.volumes) : undefined, env: this.config.env, ports: this.config.ports ? this.formatPorts(this.config.ports) : undefined }; await this.adapter.createContainer(createOptions); await this.adapter.startContainer(this.containerName); } this.isContainerCreated = true; } this.isStarted = true; return this; } catch (error) { if (error instanceof DockerError) throw error; throw new DockerError(this.containerName, 'start', error instanceof Error ? error : new Error(String(error))); } } exec(strings, ...values) { if (!this.isStarted) { throw new DockerError(this.containerName, 'exec', new Error('Container is not started')); } const dockerEngine = this.engine.docker({ container: this.containerName, user: this.config.user, workdir: this.config.workdir }); return dockerEngine.run(strings, ...values); } async execRaw(command, args) { if (!this.isStarted) { throw new DockerError(this.containerName, 'exec', new Error('Container is not started')); } const dockerEngine = this.engine.docker({ container: this.containerName, user: this.config.user, workdir: this.config.workdir }); const fullCommand = args && args.length > 0 ? `${command} ${args.join(' ')}` : command; const result = await dockerEngine.run([fullCommand], ...[]); return result; } async logs(options) { if (!this.isStarted) { throw new DockerError(this.containerName, 'logs', new Error('Container is not started')); } return this.adapter.getLogs(this.containerName, options); } async streamLogs(onData, options) { if (!this.isStarted) { throw new DockerError(this.containerName, 'streamLogs', new Error('Container is not started')); } return this.adapter.streamLogs(this.containerName, onData, options); } async follow(onData, options) { return this.streamLogs(onData, { ...options, follow: true }); } async stop(timeout) { if (!this.isStarted || this.isRemoved) { return; } await this.adapter.stopContainer(this.containerName); this.isStarted = false; } async remove(force = false) { if (this.isRemoved) { return; } if (this.isStarted && !force) { await this.stop(); } await this.adapter.removeContainer(this.containerName, force); this.isRemoved = true; this.isContainerCreated = false; } async restart() { if (!this.isStarted) { throw new DockerError(this.containerName, 'restart', new Error('Container is not started')); } await this.stop(); await this.start(); } async waitForHealthy(timeout = 30000) { if (!this.isStarted) { throw new DockerError(this.containerName, 'waitForHealthy', new Error('Container is not started')); } return this.adapter.waitForHealthy(this.containerName, timeout); } async stats() { if (!this.isStarted) { throw new DockerError(this.containerName, 'stats', new Error('Container is not started')); } return this.adapter.getStats(this.containerName); } async inspect() { return this.adapter.inspectContainer(this.containerName); } async copyTo(localPath, containerPath) { if (!this.isStarted) { throw new DockerError(this.containerName, 'copyTo', new Error('Container is not started')); } return this.adapter.copyToContainer(localPath, this.containerName, containerPath); } async copyFrom(containerPath, localPath) { if (!this.isStarted) { throw new DockerError(this.containerName, 'copyFrom', new Error('Container is not started')); } return this.adapter.copyFromContainer(this.containerName, containerPath, localPath); } async getIpAddress(network) { const info = await this.inspect(); const networks = info.NetworkSettings?.Networks; if (!networks) { return null; } if (network) { return networks[network]?.IPAddress || null; } for (const net of Object.values(networks)) { if (net.IPAddress) { return net.IPAddress; } } return null; } formatVolumes(volumes) { if (!volumes) return []; if (Array.isArray(volumes)) { return volumes; } return Object.entries(volumes).map(([host, container]) => `${host}:${container}`); } formatPorts(ports) { if (!ports) return []; if (Array.isArray(ports)) { return ports; } return Object.entries(ports).map(([host, container]) => `${host}:${container}`); } } export function createDockerContext(engine, config) { const adapter = engine.getAdapter('docker'); if (!adapter) { throw new Error('Docker adapter not available'); } const exec = (strings, ...values) => { if (config.name) { const dockerEngine = engine.docker({ container: config.name, user: config.user, workdir: config.workdir }); return dockerEngine.run(strings, ...values); } else { throw new Error('Container name must be specified for direct execution'); } }; const context = Object.assign(exec, { start: async () => { const container = new DockerContainer(engine, adapter, config); return container.start(); } }); return context; } //# sourceMappingURL=docker-api.js.map