import Dockerode , { DockerOptions , NetworkInspectInfo } from "dockerode" ; import path from "path" ; import { log } from "../logger" ; import { Host } from "./types" ; import { URL } from "url" ; import fs from "fs" ; import { runInContainer } from "./functions/run-in-container" ; import { logSystemDiagnostics } from "../log-system-diagnostics" ; import "../testcontainers-properties-file" ; type DockerClient = { host : Host ; dockerode : Dockerode ; } ; const getDockerClient = async ( ) : Promise < DockerClient > => { const strategies = [ new ConfigurationStrategy ( ) , new UnixSocketStrategy ( ) , new NpipeSocketStrategy ( ) , ] ; for ( const strategy of strategies ) { if ( strategy . isApplicable ( ) ) { log . debug ( `Found applicable Docker client strategy: ${ strategy . getName ( ) }` ) ; const dockerConfig = strategy . getDockerConfig ( ) ; const dockerode = new Dockerode ( createDockerodeOptions ( dockerConfig ) ) ; log . debug ( `Testing Docker client strategy URI: ${ dockerConfig . uri }` ) ; if ( await isDockerDaemonReachable ( dockerode ) ) { const host = await resolveHost ( dockerode , dockerConfig . uri ) ; log . info ( `Using Docker client strategy: ${ strategy . getName ( ) }, Docker host: ${ host }` ) ; logSystemDiagnostics ( ) ; return { host , dockerode } ; } else { log . warn ( `Docker client strategy ${ strategy . getName ( ) } is not reachable` ) ; } } } throw new Error ( "No Docker client strategy found" ) ; } ; const createDockerodeOptions = ( dockerConfig ) => { const dockerOptions = { } ; if ( dockerConfig . socketPath ) { dockerOptions . socketPath = dockerConfig . socketPath ; } else { const { hostname , port } = new URL ( dockerConfig . uri ) ; dockerOptions . host = hostname ; dockerOptions . port = port ; } if ( dockerConfig . ssl ) { dockerOptions . ca = dockerConfig . ssl . ca ; dockerOptions . cert = dockerConfig . ssl . cert ; dockerOptions . key = dockerConfig . ssl . key ; } return dockerOptions ; } ; const isDockerDaemonReachable = async ( dockerode ) : Promise < boolean > => { try { const response = await dockerode . ping ( ) ; return response . toString ( ) === "OK" ; } catch ( err ) { log . warn ( `Docker daemon is not reachable: ${ err }` ) ; return false ; } } ; type DockerConfig = { uri : string ; socketPath ? : string ; ssl ? : { ca : string ; cert : string ; key : string ; } ; } ; interface DockerClientStrategy { isApplicable ( ) : boolean ; getDockerConfig ( ) : DockerConfig ; getName ( ) : string ; } class ConfigurationStrategy implements DockerClientStrategy { getDockerConfig ( ) { const { DOCKER_HOST , DOCKER_TLS_VERIFY , DOCKER_CERT_PATH } = process . env ; return { uri : DOCKER_HOST ! , ssl : DOCKER_TLS_VERIFY === "1" && DOCKER_CERT_PATH !== undefined ? { ca : path . resolve ( DOCKER_CERT_PATH , "ca.pem" ) , cert : path . resolve ( DOCKER_CERT_PATH , "cert.pem" ) , key : path . resolve ( DOCKER_CERT_PATH , "key.pem" ) , } : undefined , } ; } isApplicable ( ) { return process . env . DOCKER_HOST !== undefined ; } getName ( ) { return "ConfigurationStrategy" ; } } class UnixSocketStrategy implements DockerClientStrategy { getDockerConfig ( ) { return { uri : "unix:///var/run/docker.sock" , socketPath : "/var/run/docker.sock" , } ; } isApplicable ( ) { return process . platform === "linux" || process . platform === "darwin" ; } getName ( ) { return "UnixSocketStrategy" ; } } class NpipeSocketStrategy implements DockerClientStrategy { getDockerConfig ( ) { return { uri : "npipe:////./pipe/docker_engine" , socketPath : "//./pipe/docker_engine" , } ; } isApplicable ( ) { return process . platform === "win32" ; } getName ( ) { return "NpipeSocketStrategy" ; } } const resolveHost = async ( dockerode , uri ) : Promise < string > => { if ( process . env . TESTCONTAINERS_HOST_OVERRIDE !== undefined ) { return process . env . TESTCONTAINERS_HOST_OVERRIDE ; } const { protocol , hostname } = new URL ( uri ) ; switch ( protocol ) { case "http:" : case "https:" : case "tcp:" : return hostname ; case "unix:" : case "npipe:" : { if ( isInContainer ( ) ) { const gateway = await findGateway ( dockerode ) ; if ( gateway !== undefined ) { return gateway ; } const defaultGateway = await findDefaultGateway ( dockerode ) ; if ( defaultGateway !== undefined ) { return defaultGateway ; } } return "localhost" ; } default : throw new Error ( `Unsupported protocol: ${ protocol }` ) ; } } ; const findGateway = async ( dockerode ) : Promise < string | undefined > => { const inspectResult = await dockerode . getNetwork ( "bridge" ) . inspect ( ) ; return inspectResult ?. IPAM ?. Config ?. find ( ( config ) => config . Gateway !== undefined ) ?. Gateway ; } ; const findDefaultGateway = async ( dockerode ) : Promise < string | undefined > => runInContainer ( dockerode , "alpine:3.5" , [ "sh" , "-c" , "ip route|awk '/default/ { print $3 }'" ] ) ; const isInContainer = ( ) => fs . existsSync ( "/.dockerenv" ) ; export const dockerClient = getDockerClient ( ) ;