import archiver from "archiver" ; import { BoundPorts } from "../bound-ports" ; import { containerLog , log } from "../logger" ; import { Port } from "../port" ; import { HostPortCheck , InternalPortCheck } from "../port-check" ; import { DefaultPullPolicy , PullPolicy } from "../pull-policy" ; import { ReaperInstance } from "../reaper" ; import { DockerImageName } from "../docker-image-name" ; import { StartedTestContainer , TestContainer } from "../test-container" ; import { HostPortWaitStrategy , WaitStrategy } from "../wait-strategy" ; import { Readable } from "stream" ; import { PortForwarderInstance } from "../port-forwarder" ; import { getAuthConfig } from "../registry-auth-locator" ; import { BindMode , BindMount , BuildContext , Command , ContainerName , Dir , Env , EnvKey , EnvValue , ExtraHost , HealthCheck , Host , NetworkMode , TmpFs , } from "../docker/types" ; import { pullImage } from "../docker/functions/image/pull-image" ; import { createContainer } from "../docker/functions/container/create-container" ; import { connectNetwork } from "../docker/functions/network/connect-network" ; import { dockerClient } from "../docker/docker-client" ; import { inspectContainer , InspectResult } from "../docker/functions/container/inspect-container" ; import Dockerode from "dockerode" ; import { startContainer } from "../docker/functions/container/start-container" ; import { containerLogs } from "../docker/functions/container/container-logs" ; import { stopContainer } from "../docker/functions/container/stop-container" ; import { removeContainer } from "../docker/functions/container/remove-container" ; import { putContainerArchive } from "../docker/functions/container/put-container-archive" ; import { GenericContainerBuilder } from "./generic-container-builder" ; import { StartedGenericContainer } from "./started-generic-container" ; export class GenericContainer implements TestContainer { public static fromDockerfile ( context , dockerfileName = "Dockerfile" ) { return new GenericContainerBuilder ( context , dockerfileName ) ; } private readonly imageName : DockerImageName ; protected env : Env = { } ; protected networkMode ? : NetworkMode ; protected networkAliases : string [ ] = [ ] ; protected ports : Port [ ] = [ ] ; protected cmd : Command [ ] = [ ] ; protected bindMounts : BindMount [ ] = [ ] ; protected name ? : ContainerName ; protected tmpFs : TmpFs = { } ; protected healthCheck ? : HealthCheck ; protected waitStrategy ? : WaitStrategy ; protected startupTimeout = 60000 ; protected useDefaultLogDriver = false ; protected privilegedMode = false ; protected ipcMode ? : string ; protected user ? : string ; protected pullPolicy : PullPolicy = new DefaultPullPolicy ( ) ; protected tarToCopy ? : archiver . Archiver ; private extraHosts : ExtraHost [ ] = [ ] ; constructor ( readonly image ) { this . imageName = DockerImageName . fromString ( image ) ; } protected preStart ? ( ) ; public async start ( ) { await pullImage ( { imageName : this . imageName , force : this . pullPolicy . shouldPull ( ) , authConfig : await getAuthConfig ( this . imageName . registry ) , } ) ; if ( ! this . imageName . isReaper ( ) ) { await ReaperInstance . getInstance ( ) ; } if ( this . preStart ) { await this . preStart ( ) ; } if ( ! this . imageName . isHelperContainer ( ) && PortForwarderInstance . isRunning ( ) ) { const portForwarder = await PortForwarderInstance . getInstance ( ) ; this . extraHosts . push ( { host : "host.testcontainers.internal" , ipAddress : portForwarder . getIpAddress ( ) } ) ; } const container = await createContainer ( { imageName : this . imageName , env : this . env , cmd : this . cmd , bindMounts : this . bindMounts , tmpFs : this . tmpFs , exposedPorts : this . ports , name : this . name , networkMode : this . networkAliases . length > 0 ? undefined : this . networkMode , healthCheck : this . healthCheck , useDefaultLogDriver : this . useDefaultLogDriver , privilegedMode : this . privilegedMode , autoRemove : this . imageName . isReaper ( ) , extraHosts : this . extraHosts , ipcMode : this . ipcMode , user : this . user , } ) ; if ( ! this . imageName . isHelperContainer ( ) && PortForwarderInstance . isRunning ( ) ) { const portForwarder = await PortForwarderInstance . getInstance ( ) ; const portForwarderNetworkId = portForwarder . getNetworkId ( ) ; const excludedNetworks = [ portForwarderNetworkId , "none" , "host" ] ; if ( ! this . networkMode || ! excludedNetworks . includes ( this . networkMode ) ) { await connectNetwork ( { containerId : container . id , networkId : portForwarderNetworkId , networkAliases : [ ] , } ) ; } } if ( this . networkMode && this . networkAliases . length > 0 ) { await connectNetwork ( { containerId : container . id , networkId : this . networkMode , networkAliases : this . networkAliases , } ) ; } if ( this . tarToCopy ) { await this . tarToCopy . finalize ( ) ; await putContainerArchive ( { container , stream : this . tarToCopy , containerPath : "/" } ) ; } log . info ( `Starting container ${ this . imageName } with ID: ${ container . id }` ) ; await startContainer ( container ) ; ( await containerLogs ( container ) ) . on ( "data" , ( data ) => containerLog . trace ( `${ container . id }: ${ data . trim ( ) }` ) ) . on ( "err" , ( data ) => containerLog . error ( `${ container . id }: ${ data . trim ( ) }` ) ) ; const inspectResult = await inspectContainer ( container ) ; const boundPorts = BoundPorts . fromInspectResult ( inspectResult ) . filter ( this . ports ) ; await this . waitForContainer ( container , boundPorts ) ; const startedContainer = new StartedGenericContainer ( container , ( await dockerClient ) . host , inspectResult , boundPorts , inspectResult . name ) ; if ( this . postStart ) { await this . postStart ( startedContainer , inspectResult , boundPorts ) ; } return startedContainer ; } protected postStart ? ( container , inspectResult , boundPorts ) ; public withCmd ( cmd ) { this . cmd = cmd ; return this ; } public withName ( name ) { this . name = name ; return this ; } public withEnv ( key , value ) { this . env [ key ] = value ; return this ; } public withTmpFs ( tmpFs ) { this . tmpFs = tmpFs ; return this ; } public withNetworkMode ( networkMode ) { this . networkMode = networkMode ; return this ; } public withNetworkAliases ( ... networkAliases ) { this . networkAliases = networkAliases ; return this ; } public withExtraHosts ( ... extraHosts ) { this . extraHosts . push ( ... extraHosts ) ; return this ; } public withExposedPorts ( ... ports ) { this . ports = ports ; return this ; } protected addExposedPorts ( ... ports ) { this . ports . push ( ... ports ) ; return this ; } public withBindMount ( source , target , bindMode = "rw" ) { this . bindMounts . push ( { source , target , bindMode } ) ; return this ; } public withHealthCheck ( healthCheck ) { this . healthCheck = healthCheck ; return this ; } public withStartupTimeout ( startupTimeout ) { this . startupTimeout = startupTimeout ; return this ; } public withWaitStrategy ( waitStrategy ) { this . waitStrategy = waitStrategy ; return this ; } public withDefaultLogDriver ( ) { this . useDefaultLogDriver = true ; return this ; } public withPrivilegedMode ( ) { this . privilegedMode = true ; return this ; } public withUser ( user ) { this . user = user ; return this ; } public withPullPolicy ( pullPolicy ) { this . pullPolicy = pullPolicy ; return this ; } public withIpcMode ( ipcMode ) { this . ipcMode = ipcMode ; return this ; } public withCopyFileToContainer ( sourcePath , containerPath ) { this . getTarToCopy ( ) . file ( sourcePath , { name : containerPath } ) ; return this ; } public withCopyContentToContainer ( content , containerPath ) { this . getTarToCopy ( ) . append ( content , { name : containerPath } ) ; return this ; } protected getTarToCopy ( ) { if ( ! this . tarToCopy ) { this . tarToCopy = archiver ( "tar" ) ; } return this . tarToCopy ; } private async waitForContainer ( container , boundPorts ) { log . debug ( `Waiting for container to be ready: ${ container . id }` ) ; const waitStrategy = this . getWaitStrategy ( ( await dockerClient ) . host , container ) ; try { await waitStrategy . withStartupTimeout ( this . startupTimeout ) . waitUntilReady ( container , boundPorts ) ; log . info ( "Container is ready" ) ; } catch ( err ) { log . error ( `Container failed to be ready: ${ err }` ) ; try { await stopContainer ( container , { timeout : 0 } ) ; await removeContainer ( container , { removeVolumes : true } ) ; } catch ( stopErr ) { log . error ( `Failed to stop container after it failed to be ready: ${ stopErr }` ) ; } throw err ; } } private getWaitStrategy ( host , container ) { if ( this . waitStrategy ) { return this . waitStrategy ; } const hostPortCheck = new HostPortCheck ( host ) ; const internalPortCheck = new InternalPortCheck ( container ) ; return new HostPortWaitStrategy ( hostPortCheck , internalPortCheck ) ; } }