@pact-toolbox/docker
Version:
Modern Docker container orchestration for Pact Toolbox
518 lines (517 loc) • 13.4 kB
TypeScript
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 };