UNPKG

@pact-toolbox/docker

Version:

Modern Docker container orchestration for Pact Toolbox

518 lines (517 loc) 13.4 kB
import { Logger, colors } from "@pact-toolbox/node-utils"; import * as Docker$1 from "dockerode"; import Docker from "dockerode"; //#region src/types.d.ts type ServiceStatus = "creating" | "running" | "stopping" | "stopped" | "failed" | "healthy" | "unhealthy"; interface ServiceState { id: string; status: ServiceStatus; containerId?: string; startTime?: Date; endTime?: Date; restartCount: number; health?: "healthy" | "unhealthy" | "starting"; error?: Error; ports?: string[]; } interface DockerServiceConfig { containerName: string; image?: string; platform?: string; restart?: string; stopSignal?: string; stopGracePeriod?: number; ulimits?: { Name: string; Soft: number; Hard: number; }[]; dependsOn?: { [key: string]: { condition: string; required?: boolean; }; }; entrypoint?: string | string[]; command?: string[]; ports?: { target: number; published: string | number; protocol?: string; mode?: "host" | "ingress"; }[]; volumes?: string[] | VolumeConfig[]; environment?: string[] | { [key: string]: string; }; envFile?: string | string[]; labels?: { [key: string]: string; }; build?: BuildConfig; profiles?: string[]; expose?: string[]; healthCheck?: Docker$1.HealthConfig; networks?: string[] | { [networkName: string]: NetworkAttachConfig; }; secrets?: string[] | SecretConfig[]; configs?: string[] | ConfigConfig[]; tmpfs?: string | string[]; devices?: string[]; capAdd?: string[]; capDrop?: string[]; privileged?: boolean; user?: string; workingDir?: string; hostname?: string; domainName?: string; macAddress?: string; dns?: string | string[]; dnsSearch?: string | string[]; dnsOpt?: string[]; extraHosts?: string[]; ipc?: string; pid?: string; cgroupns?: string; init?: boolean; isolation?: string; memLimit?: string; memReservation?: string; memSwapLimit?: string; memSwappiness?: number; oomKillDisable?: boolean; oomScoreAdj?: number; cpus?: number; cpuShares?: number; cpuQuota?: number; cpuPeriod?: number; cpusetCpus?: string; cpusetMems?: string; blkioWeight?: number; deviceReadBps?: { path: string; rate: string; }[]; deviceWriteBps?: { path: string; rate: string; }[]; deviceReadIops?: { path: string; rate: number; }[]; deviceWriteIops?: { path: string; rate: number; }[]; logging?: LoggingConfig; deploy?: { replicas?: number; restartPolicy?: { condition?: "on-failure" | "none" | "always" | "unless-stopped"; delay?: string; maxAttempts?: number; window?: string; }; updateConfig?: { parallelism?: number; delay?: string; failureAction?: "continue" | "rollback" | "pause"; monitor?: string; maxFailureRatio?: number; order?: "start-first" | "stop-first"; }; rollbackConfig?: { parallelism?: number; delay?: string; failureAction?: "continue" | "pause"; monitor?: string; maxFailureRatio?: number; order?: "start-first" | "stop-first"; }; resources?: { limits?: { cpus?: string; memory?: string; pids?: number; }; reservations?: { cpus?: string; memory?: string; genericResources?: { discreteResourceSpec?: { kind: string; value: number; }; }[]; }; }; placement?: { constraints?: string[]; preferences?: { spread: string; }[]; maxReplicas?: number; }; labels?: { [key: string]: string; }; mode?: "replicated" | "global"; endpointMode?: "vip" | "dnsrr"; }; } interface VolumeConfig { type: "bind" | "volume" | "tmpfs"; source?: string; target: string; readOnly?: boolean; bind?: { propagation?: "private" | "rprivate" | "shared" | "rshared" | "slave" | "rslave"; createHostPath?: boolean; selinuxOpts?: string; }; volume?: { noCopy?: boolean; }; tmpfs?: { size?: number; mode?: number; }; } interface BuildConfig { context: string; dockerfile?: string; args?: { [key: string]: string; }; ssh?: string | string[]; cache_from?: string[]; cache_to?: string[]; labels?: { [key: string]: string; }; network?: string; shm_size?: string; target?: string; extra_hosts?: string[]; isolation?: string; privileged?: boolean; pull?: boolean; platforms?: string[]; secrets?: string[] | SecretConfig[]; tags?: string[]; ulimits?: { [key: string]: number | { soft: number; hard: number; }; }; } interface NetworkAttachConfig { aliases?: string[]; ipv4Address?: string; ipv6Address?: string; linkLocalIps?: string[]; priority?: number; } interface SecretConfig { source: string; target?: string; uid?: string; gid?: string; mode?: number; } interface ConfigConfig { source: string; target?: string; uid?: string; gid?: string; mode?: number; } interface LoggingConfig { driver?: string; options?: { [key: string]: string; }; } interface NetworkConfig { name: string; driver?: string; driverOpts?: { [key: string]: string; }; attachable?: boolean; internal?: boolean; ipam?: { driver?: string; config?: { subnet?: string; ipRange?: string; gateway?: string; auxAddresses?: { [key: string]: string; }; }[]; options?: { [key: string]: string; }; }; enableIpv6?: boolean; labels?: { [key: string]: string; }; external?: boolean; } interface VolumeDefinition { name: string; driver?: string; driverOpts?: { [key: string]: string; }; labels?: { [key: string]: string; }; external?: boolean; } interface SecretDefinition { name: string; file?: string; external?: boolean; labels?: { [key: string]: string; }; } interface ConfigDefinition { name: string; file?: string; external?: boolean; labels?: { [key: string]: string; }; } interface OrchestratorConfig { networkName: string; volumes?: string[]; networks?: NetworkConfig[]; secrets?: SecretDefinition[]; configs?: ConfigDefinition[]; defaultRestartPolicy?: string; logger?: Logger; } //#endregion //#region src/utils.d.ts declare const DOCKER_SOCKET: string; declare function isDockerInstalled(): boolean; declare function getServiceColor(serviceName: string): typeof colors.cyan; /** * Create a service tag with color */ declare function createServiceTag(serviceName: string): string; /** * Reset service colors for testing */ declare function resetServiceColors(): void; //#endregion //#region src/service.d.ts interface DockerServiceOptions { serviceName?: string; networkName: string; docker: Docker; logger: Logger; } declare class DockerService { #private; readonly serviceName: string; readonly config: DockerServiceConfig; readonly containerName: string; healthCheckFailed: boolean; constructor(config: DockerServiceConfig, options: DockerServiceOptions); prepareImage(): Promise<void>; start(): Promise<void>; stop(): Promise<void>; remove(): Promise<void>; isHealthy(): Promise<boolean>; getState(): Promise<ServiceState>; waitForHealthy(timeoutMs?: number, intervalMs?: number): Promise<void>; streamLogs(): Promise<void>; stopLogStream(): void; /** * Get container logs without streaming */ getLogs(tail?: number): Promise<string[]>; /** * Execute a command in the running container */ exec(command: string[]): Promise<{ stdout: string; stderr: string; exitCode: number; }>; } //#endregion //#region src/orchestrator.d.ts declare class ContainerOrchestrator { #private; constructor(config: OrchestratorConfig); /** * Handle graceful shutdown signals */ setupGracefulShutdown(): void; startServices(serviceConfigs: DockerServiceConfig[]): Promise<void>; streamAllLogs(): Promise<void>; stopAllLogStreams(): void; stopAllServices(): Promise<void>; } //#endregion //#region src/compose-utils.d.ts /** * Parse Docker Compose YAML and convert to our format */ interface ComposeFile { version?: string; services: { [serviceName: string]: any; }; networks?: { [networkName: string]: any; }; volumes?: { [volumeName: string]: any; }; secrets?: { [secretName: string]: any; }; configs?: { [configName: string]: any; }; } /** * Convert Docker Compose service definition to our DockerServiceConfig format */ declare function convertComposeService(serviceName: string, composeService: any): DockerServiceConfig; /** * Convert Docker Compose networks to our NetworkConfig format */ declare function convertComposeNetworks(composeNetworks: { [name: string]: any; }): NetworkConfig[]; /** * Convert Docker Compose volumes to our VolumeDefinition format */ declare function convertComposeVolumes(composeVolumes: { [name: string]: any; }): VolumeDefinition[]; /** * Parse time strings like "1m30s", "45s", "2h" into seconds */ declare function parseTime(timeStr: string): number; /** * Validate service configuration for common issues */ declare function validateServiceConfig(config: DockerServiceConfig): string[]; /** * Resolve relative paths in service configuration */ declare function resolveServicePaths(config: DockerServiceConfig, basePath: string): DockerServiceConfig; /** * Generate a unique container name with optional suffix */ declare function generateContainerName(serviceName: string, projectName?: string, suffix?: string): string; /** * Check if a service should be included based on active profiles */ declare function shouldIncludeService(config: DockerServiceConfig, activeProfiles: string[]): boolean; //#endregion //#region src/error-handler.d.ts declare enum DockerErrorType { ImageNotFound = "IMAGE_NOT_FOUND", ContainerNotFound = "CONTAINER_NOT_FOUND", NetworkNotFound = "NETWORK_NOT_FOUND", VolumeNotFound = "VOLUME_NOT_FOUND", PortInUse = "PORT_IN_USE", InsufficientResources = "INSUFFICIENT_RESOURCES", BuildFailed = "BUILD_FAILED", HealthCheckFailed = "HEALTH_CHECK_FAILED", DependencyFailed = "DEPENDENCY_FAILED", PermissionDenied = "PERMISSION_DENIED", NetworkConflict = "NETWORK_CONFLICT", VolumeInUse = "VOLUME_IN_USE", InvalidConfiguration = "INVALID_CONFIGURATION", Timeout = "TIMEOUT", Unknown = "UNKNOWN", } interface DockerError extends Error { type: DockerErrorType; code?: string | number; context?: any; serviceName?: string; containerName?: string; suggestions?: string[]; } declare class DockerErrorHandler { private logger; constructor(logger: Logger); /** * Parse Docker API error and convert to our enhanced error format */ parseDockerError(error: any, context?: { serviceName?: string; containerName?: string; }): DockerError; private determineNotFoundType; private determineConflictType; private getNotFoundSuggestions; private getConflictSuggestions; /** * Log error with context and suggestions */ logError(error: DockerError, operation: string): void; /** * Handle and log error with automatic parsing */ handleError(error: any, operation: string, context?: { serviceName?: string; containerName?: string; }): DockerError; /** * Wrap async operation with error handling */ withErrorHandling<T>(operation: () => Promise<T>, operationName: string, context?: { serviceName?: string; containerName?: string; }): Promise<T>; /** * Create a retry wrapper for operations that might fail temporarily */ withRetry<T>(operation: () => Promise<T>, operationName: string, options?: { maxRetries?: number; delay?: number; backoff?: boolean; retryableTypes?: DockerErrorType[]; context?: { serviceName?: string; containerName?: string; }; }): Promise<T>; /** * Validate configuration and return issues */ validateConfiguration(config: any, rules: { [field: string]: (value: any) => string | null; }): string[]; } /** * Common validation rules */ declare const ValidationRules: { required: (value: any) => "is required" | null; nonEmpty: (value: any) => "cannot be empty" | null; port: (value: any) => "must be a number between 1 and 65535" | null; path: (value: any) => "must be a string" | "must be an absolute or relative path" | null; imageName: (value: any) => "must be a string" | "must be a valid Docker image name" | null; memory: (value: any) => "must be a string" | "must be a valid memory size (e.g., 512m, 1g)" | null; duration: (value: any) => "must be a string" | "must be a valid duration (e.g., 30s, 1m, 2h)" | null; }; //#endregion export { BuildConfig, ComposeFile, ConfigConfig, ConfigDefinition, ContainerOrchestrator, DOCKER_SOCKET, DockerError, DockerErrorHandler, DockerErrorType, DockerService, DockerServiceConfig, LoggingConfig, NetworkAttachConfig, NetworkConfig, OrchestratorConfig, SecretConfig, SecretDefinition, ServiceState, ServiceStatus, ValidationRules, VolumeConfig, VolumeDefinition, convertComposeNetworks, convertComposeService, convertComposeVolumes, createServiceTag, generateContainerName, getServiceColor, isDockerInstalled, parseTime, resetServiceColors, resolveServicePaths, shouldIncludeService, validateServiceConfig };