@xec-sh/core
Version:
Universal shell execution engine
222 lines • 8.38 kB
JavaScript
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