import Docker from 'dockerode';
import { DockerClient } from '../docker/client.js';
export interface ContainerListOptions {
all?: boolean;
filters?: Record<string, string[]>;
}
export interface ContainerCreateOptions {
Image: string;
Cmd?: string[];
Env?: string[];
ExposedPorts?: Record<string, {}>;
HostConfig?: Docker.HostConfig;
Labels?: Record<string, string>;
name?: string;
WorkingDir?: string;
User?: string;
Tty?: boolean;
OpenStdin?: boolean;
AttachStdout?: boolean;
AttachStderr?: boolean;
}
export interface ContainerLogsOptions {
follow?: boolean;
stdout?: boolean;
stderr?: boolean;
since?: number | string;
until?: number | string;
timestamps?: boolean;
tail?: number;
}
export class ContainerManager {
constructor(private dockerClient: DockerClient) {}
private getDocker(): Docker {
return this.dockerClient.getDocker();
}
async listContainers(options: ContainerListOptions = {}): Promise<Docker.ContainerInfo[]> {
const docker = this.getDocker();
return await docker.listContainers({
all: options.all ?? false,
filters: options.filters ? JSON.stringify(options.filters) : undefined,
});
}
async createContainer(options: ContainerCreateOptions): Promise<Docker.Container> {
const docker = this.getDocker();
const createOptions: Docker.ContainerCreateOptions = {
Image: options.Image,
Cmd: options.Cmd,
Env: options.Env,
ExposedPorts: options.ExposedPorts,
HostConfig: options.HostConfig,
Labels: options.Labels,
name: options.name,
WorkingDir: options.WorkingDir,
User: options.User,
Tty: options.Tty ?? false,
OpenStdin: options.OpenStdin ?? false,
AttachStdout: options.AttachStdout ?? true,
AttachStderr: options.AttachStderr ?? true,
};
return await docker.createContainer(createOptions);
}
async getContainer(idOrName: string): Promise<Docker.Container> {
const docker = this.getDocker();
return docker.getContainer(idOrName);
}
async startContainer(idOrName: string): Promise<void> {
const container = await this.getContainer(idOrName);
await container.start();
}
async stopContainer(idOrName: string, timeout?: number): Promise<void> {
const container = await this.getContainer(idOrName);
await container.stop({ t: timeout });
}
async restartContainer(idOrName: string, timeout?: number): Promise<void> {
const container = await this.getContainer(idOrName);
await container.restart({ t: timeout });
}
async killContainer(idOrName: string, signal?: string): Promise<void> {
const container = await this.getContainer(idOrName);
await container.kill({ signal });
}
async removeContainer(idOrName: string, options?: { force?: boolean; volumes?: boolean }): Promise<void> {
const container = await this.getContainer(idOrName);
await container.remove({
force: options?.force ?? false,
v: options?.volumes ?? false,
});
}
async inspectContainer(idOrName: string): Promise<Docker.ContainerInspectInfo> {
const container = await this.getContainer(idOrName);
return await container.inspect();
}
async getContainerLogs(idOrName: string, options: ContainerLogsOptions = {}): Promise<any> {
const container = await this.getContainer(idOrName);
const logs = await (container.logs as any)({
follow: options.follow ?? false,
stdout: options.stdout ?? true,
stderr: options.stderr ?? true,
since: options.since,
until: options.until,
timestamps: options.timestamps ?? false,
tail: options.tail,
});
// Dockerode returns different types based on options, convert to stream-like
if (options.follow) {
return logs as any;
}
// For non-following, wrap Buffer in a readable stream
const { Readable } = await import('stream');
return Readable.from([logs]) as any;
}
async getContainerStats(idOrName: string, stream: boolean = false): Promise<any> {
const container = await this.getContainer(idOrName);
const stats = await (container.stats as any)({ stream });
return stats as any;
}
async pauseContainer(idOrName: string): Promise<void> {
const container = await this.getContainer(idOrName);
await container.pause();
}
async unpauseContainer(idOrName: string): Promise<void> {
const container = await this.getContainer(idOrName);
await container.unpause();
}
async getContainerHealthCheck(idOrName: string): Promise<any> {
const container = await this.getContainer(idOrName);
const inspectInfo = await container.inspect();
const health = (inspectInfo as any).State?.Health;
if (!health) {
return {
status: 'none',
message: 'Container does not have a health check configured',
hasHealthCheck: false,
};
}
return {
status: health.Status || 'unknown',
hasHealthCheck: true,
failingStreak: health.FailingStreak || 0,
log: health.Log || [],
healthCheckConfig: (inspectInfo as any).Config?.Healthcheck || null,
lastHealthCheck: health.Log && health.Log.length > 0
? health.Log[health.Log.length - 1]
: null,
};
}
}