import Docker = require ( "dockerode" ) ; import execa from "execa" ; import * as fs from "fs" ; import ld from "lodash" ; import os from "os" ; import * as sb from "stream-buffers" ; import * as util from "util" ; export async function dockerExec ( container , command ) { const exec = await container . exec ( { AttachStdin : false , AttachStdout : true , AttachStderr : true , Cmd : command } ) ; const output = await exec . start ( { } ) ; const buf = new sb . WritableStreamBuffer ( ) ; const errBuf = new sb . WritableStreamBuffer ( ) ; exec . modem . demuxStream ( output , buf , errBuf ) ; return new Promise < string > ( ( res , rej ) => { output . on ( "end" , async ( ) => { const inspectInfo = await exec . inspect ( ) ; if ( inspectInfo . Running !== false ) { rej ( new Error ( `dockerExec: ${ util . inspect ( command ) } stream ended with process still running?!` ) ) ; return ; } if ( inspectInfo . ExitCode !== 0 ) { let msg = `dockerExec: ${ util . inspect ( command ) } process ` + `exited with error (code: ${ inspectInfo . ExitCode })` ; const stderr = errBuf . getContentsAsString ( ) ; if ( stderr ) msg += "\n" + stderr ; rej ( new Error ( msg ) ) ; return ; } res ( buf . getContentsAsString ( ) || "" ) ; } ) ; } ) ; } export async function dockerPull ( docker , imageName , indent = "" ) { function printStatus ( data ) { if ( ! ( data instanceof Buffer ) ) throw new Error ( `Unknown status: ${ data }` ) ; const msgs = data . toString ( ) . split ( "\n" ) . filter ( ( m ) => m . trim ( ) !== "" ) ; for ( const mString of msgs ) { const msg = JSON . parse ( mString ) ; let s = msg . status ; if ( ! s ) { console . log ( `${ indent }Docker pull status:` , msg ) ; continue ; } if ( msg . id ) s += ` id=${ msg . id }` ; const prog = msg . progressDetail ; if ( prog && prog . current != null ) s += ` Progress: ${ prog . current }/${ prog . total }` ; console . log ( `${ indent }  ${ s }` ) ; } } if ( ! imageName . includes ( ":" ) ) { throw new Error ( `dockerPull: imageName must include tag or be an ID` ) ; } console . log ( `${ indent }Pulling docker image ${ imageName }` ) ; return new Promise < void > ( ( res , rej ) => { let errored = false ; function mkErr ( e ) { if ( errored ) return ; errored = true ; const msg = `Error pulling image: ${ e }` ; console . log ( `${ indent }${ msg }` ) ; rej ( new Error ( msg ) ) ; } docker . pull ( imageName , ( err , stream ) => { if ( err ) return mkErr ( err ) ; stream . on ( "error" , ( e ) => { return mkErr ( e ) ; } ) ; stream . on ( "data" , ( data ) => { try { printStatus ( data ) ; } catch ( e ) { return mkErr ( e ) ; } } ) ; stream . on ( "end" , ( ) => { if ( errored ) return ; console . log ( `${ indent }Pull complete` ) ; res ( ) ; } ) ; } ) ; } ) ; } export async function deleteContainer ( docker , name ) { let ctr ; try { ctr = docker . getContainer ( name ) ; } catch ( e ) { return ; } try { await ctr . stop ( ) ; } catch ( e ) { } try { await ctr . remove ( ) ; } catch ( e ) { } } export async function getNetwork ( docker , container ) { const info = await container . inspect ( ) ; const networkName = Object . keys ( info . NetworkSettings . Networks ) [ 0 ] ; return docker . getNetwork ( networkName ) ; } export async function createNetwork ( docker , name ) { return docker . createNetwork ( { Name : name } ) ; } export async function addToNetwork ( container , network ) { try { await network . connect ( { Container : container . id } ) ; } catch ( e ) { if ( ! ( typeof e . message === "string" && e . message . includes ( "already exists in network" ) ) ) { throw e ; } } } export async function removeFromNetwork ( container , network ) { try { await network . disconnect ( { Container : container . id } ) ; } catch ( e ) { if ( ! ( typeof e . message === "string" && e . message . includes ( "is not connected to network" ) ) ) { throw e ; } } } export function inContainer ( ) { try { return fs . readFileSync ( "/proc/self/cgroup" ) . toString ( ) . includes ( "/docker/" ) ; } catch ( err ) { return false ; } } export async function getSelfContainer ( docker ) { if ( os . platform ( ) === "win32" ) return null ; const entries = fs . readFileSync ( "/proc/self/cgroup" ) . toString ( ) . split ( /\r?\n/ ) ; if ( entries . length === 0 ) throw new Error ( "Cannot get own container id!" ) ; const entry = entries [ 0 ] ; const [ , , path ] = entry . split ( ":" ) ; const psplit = path . split ( "/" ) ; const id = ld . last ( psplit ) ; if ( id === undefined ) throw new Error ( "Cannot get own container id!" ) ; return docker . getContainer ( id ) ; } export function secondsSince ( start ) { return ( Date . now ( ) - start ) / 1000 ; } export interface DockerUtilOpts { dockerHost ? : string ; } function dockerArgs ( opts , ... moreArgs ) { const args = [ ] ; if ( opts . dockerHost ) args . push ( "-H" , opts . dockerHost ) ; return args . concat ( moreArgs ) ; } example export async function deleteAllContainers ( filter , opts = { } ) { try { let args = dockerArgs ( opts , "ps" , "-a" , "-q" , "--filter" , filter ) ; const { stdout : ctrList } = await execa ( "docker" , args ) ; if ( ! ctrList ) return ; const ctrs = ctrList . split ( /\s+/ ) ; args = dockerArgs ( opts , "rm" , "-f" , ... ctrs ) ; if ( ctrs . length > 0 ) await execa ( "docker" , args ) ; } catch ( err ) { if ( /invalid argument/ . test ( err . stderr || "" ) ) throw err ; console . log ( `Error deleting containers (ignored):` , err ) ; } } example export async function deleteAllNetworks ( filter , opts = { } ) { try { let args = dockerArgs ( opts , "network" , "ls" , "-q" , "--filter" , filter ) ; const { stdout : netList } = await execa ( "docker" , args ) ; if ( ! netList ) return ; const nets = netList . split ( /\s+/ ) ; args = dockerArgs ( opts , "network" , "rm" , ... nets ) ; if ( nets . length > 0 ) await execa ( "docker" , args ) ; } catch ( err ) { if ( /invalid argument/ . test ( err . stderr || "" ) ) throw err ; console . log ( `Error deleting networks (ignored):` , err ) ; } } remarks example export async function deleteAllImages ( filter , opts = { } ) { try { let args = dockerArgs ( opts , "image" , "ls" , "-q" , "--filter" , filter ) ; const { stdout : imgList } = await execa ( "docker" , args ) ; if ( ! imgList ) return ; const imgs = ld . uniq ( imgList . split ( /\s+/m ) ) ; args = dockerArgs ( opts , "rmi" , "-f" , ... imgs ) ; if ( imgs . length > 0 ) await execa ( "docker" , args ) ; } catch ( err ) { if ( /invalid argument/ . test ( err . stderr || "" ) ) throw err ; console . log ( `Error deleting images (ignored):` , err ) ; } } remarks export async function proxyFromContainer ( localPort , target ) { if ( ! inContainer ( ) ) return async ( ) => { } ; await execa ( "apt-get" , [ "install" , "socat" ] ) ; const child = execa ( "socat" , [ `tcp-listen:${ localPort },reuseaddr,fork` , `tcp:${ target }` ] , { stdio : "inherit" , } ) ; return async ( ) => { child . kill ( ) ; } ; }