2015-07-20 00:09:47 -07:00
'use strict' ;
2015-10-19 11:24:21 -07:00
exports = module . exports = {
2019-10-22 22:07:44 -07:00
testRegistryConfig : testRegistryConfig ,
2018-10-12 17:04:04 -07:00
setRegistryConfig : setRegistryConfig ,
2019-10-22 22:07:44 -07:00
injectPrivateFields : injectPrivateFields ,
removePrivateFields : removePrivateFields ,
SECRET _PLACEHOLDER : String . fromCharCode ( 0x25CF ) . repeat ( 8 ) ,
2018-10-12 17:04:04 -07:00
2018-11-19 10:19:46 +01:00
ping : ping ,
2019-08-12 20:38:24 -07:00
info : info ,
2015-10-19 11:40:19 -07:00
downloadImage : downloadImage ,
createContainer : createContainer ,
startContainer : startContainer ,
2019-12-20 10:29:29 -08:00
restartContainer : restartContainer ,
2015-10-19 11:40:19 -07:00
stopContainer : stopContainer ,
2015-12-23 13:23:47 -08:00
stopContainerByName : stopContainer ,
2015-10-20 00:05:07 -07:00
stopContainers : stopContainers ,
2015-10-19 11:40:19 -07:00
deleteContainer : deleteContainer ,
2015-12-23 13:23:47 -08:00
deleteContainerByName : deleteContainer ,
2015-10-19 18:48:56 -07:00
deleteImage : deleteImage ,
2015-10-20 09:36:30 -07:00
deleteContainers : deleteContainers ,
2016-02-18 15:39:27 +01:00
createSubcontainer : createSubcontainer ,
2016-04-18 10:32:22 -07:00
getContainerIdByIp : getContainerIdByIp ,
2017-08-11 22:04:40 +02:00
inspect : inspect ,
2018-02-27 13:21:38 -08:00
inspectByName : inspect ,
2019-12-04 13:17:58 -08:00
execContainer : execContainer ,
2019-03-06 11:54:37 -08:00
getEvents : getEvents ,
2018-11-28 10:39:12 +01:00
memoryUsage : memoryUsage ,
2018-09-13 13:55:49 -07:00
createVolume : createVolume ,
2018-09-15 17:05:04 -07:00
removeVolume : removeVolume ,
clearVolume : clearVolume
2015-10-19 11:24:21 -07:00
} ;
2015-10-19 11:08:23 -07:00
2016-04-18 16:30:58 -07:00
var addons = require ( './addons.js' ) ,
async = require ( 'async' ) ,
assert = require ( 'assert' ) ,
2019-09-23 12:13:21 -07:00
BoxError = require ( './boxerror.js' ) ,
2016-04-18 16:30:58 -07:00
child _process = require ( 'child_process' ) ,
constants = require ( './constants.js' ) ,
2019-12-06 13:52:43 -08:00
debug = require ( 'debug' ) ( 'box:docker' ) ,
2019-12-04 13:17:58 -08:00
Docker = require ( 'dockerode' ) ,
2018-09-13 13:55:49 -07:00
path = require ( 'path' ) ,
2019-07-26 10:49:29 -07:00
settings = require ( './settings.js' ) ,
2018-04-12 11:32:49 -07:00
shell = require ( './shell.js' ) ,
2019-08-06 09:45:16 -07:00
safe = require ( 'safetydance' ) ,
2016-04-18 16:30:58 -07:00
util = require ( 'util' ) ,
_ = require ( 'underscore' ) ;
2019-01-18 14:48:31 -08:00
const CLEARVOLUME _CMD = path . join ( _ _dirname , 'scripts/clearvolume.sh' ) ,
2018-12-20 14:33:29 -08:00
MKDIRVOLUME _CMD = path . join ( _ _dirname , 'scripts/mkdirvolume.sh' ) ;
2018-09-13 13:55:49 -07:00
2019-12-04 14:37:00 -08:00
const DOCKER _SOCKET _PATH = '/var/run/docker.sock' ;
const gConnection = new Docker ( { socketPath : DOCKER _SOCKET _PATH } ) ;
2019-12-04 13:17:58 -08:00
2019-07-26 10:49:29 -07:00
function debugApp ( app ) {
2018-02-08 15:07:49 +01:00
assert ( typeof app === 'object' ) ;
2015-10-19 11:40:19 -07:00
2018-02-08 15:07:49 +01:00
debug ( app . fqdn + ' ' + util . format . apply ( util , Array . prototype . slice . call ( arguments , 1 ) ) ) ;
2015-10-19 11:40:19 -07:00
}
2019-10-22 22:07:44 -07:00
function testRegistryConfig ( auth , callback ) {
assert . strictEqual ( typeof auth , 'object' ) ;
assert . strictEqual ( typeof callback , 'function' ) ;
2019-12-04 13:17:58 -08:00
gConnection . checkAuth ( auth , function ( error /*, data */ ) { // this returns a 500 even for auth errors
2019-10-23 06:11:17 -07:00
if ( error ) return callback ( new BoxError ( BoxError . BAD _FIELD , error , { field : 'serverAddress' } ) ) ;
callback ( ) ;
} ) ;
2019-10-22 22:07:44 -07:00
}
function injectPrivateFields ( newConfig , currentConfig ) {
if ( newConfig . password === exports . SECRET _PLACEHOLDER ) newConfig . password = currentConfig . password ;
}
function removePrivateFields ( registryConfig ) {
assert . strictEqual ( typeof registryConfig , 'object' ) ;
if ( registryConfig . password ) registryConfig . password = exports . SECRET _PLACEHOLDER ;
return registryConfig ;
}
2018-10-12 17:04:04 -07:00
function setRegistryConfig ( auth , callback ) {
assert . strictEqual ( typeof auth , 'object' ) ;
assert . strictEqual ( typeof callback , 'function' ) ;
const isLogin = ! ! auth . password ;
// currently, auth info is not stashed in the db but maybe it should for restore to work?
2019-10-22 22:07:44 -07:00
const cmd = isLogin ? ` docker login ${ auth . serverAddress } --username ${ auth . username } --password ${ auth . password } ` : ` docker logout ${ auth . serverAddress } ` ;
2018-10-12 17:04:04 -07:00
2019-09-23 12:13:21 -07:00
child _process . exec ( cmd , { } , function ( error /*, stdout, stderr */ ) {
if ( error ) return callback ( new BoxError ( BoxError . ACCESS _DENIED , error . message ) ) ;
2018-10-12 17:04:04 -07:00
callback ( ) ;
} ) ;
}
2018-11-19 10:19:46 +01:00
function ping ( callback ) {
assert . strictEqual ( typeof callback , 'function' ) ;
2018-11-23 15:49:47 +01:00
// do not let the request linger
2019-12-04 14:37:00 -08:00
const connection = new Docker ( { socketPath : DOCKER _SOCKET _PATH , timeout : 1000 } ) ;
2018-11-19 10:19:46 +01:00
2019-12-04 13:17:58 -08:00
connection . ping ( function ( error , result ) {
2019-09-23 23:27:32 -07:00
if ( error ) return callback ( new BoxError ( BoxError . DOCKER _ERROR , error ) ) ;
if ( result !== 'OK' ) return callback ( new BoxError ( BoxError . DOCKER _ERROR , 'Unable to ping the docker daemon' ) ) ;
2018-11-19 10:19:46 +01:00
callback ( null ) ;
} ) ;
}
2019-10-27 12:14:27 -07:00
function getRegistryConfig ( image , callback ) {
2019-11-17 11:50:55 -08:00
// https://github.com/docker/distribution/blob/release/2.7/reference/normalize.go#L62
2019-10-27 12:14:27 -07:00
const parts = image . split ( '/' ) ;
2019-11-17 11:50:55 -08:00
if ( parts . length === 1 || ( parts [ 0 ] . match ( /[.:]/ ) === null ) ) return callback ( null , null ) ; // public docker registry
2019-10-27 12:14:27 -07:00
settings . getRegistryConfig ( function ( error , registryConfig ) {
if ( error ) return callback ( error ) ;
// https://github.com/apocas/dockerode#pull-from-private-repos
const auth = {
username : registryConfig . username ,
password : registryConfig . password ,
auth : registryConfig . auth || '' , // the auth token at login time
email : registryConfig . email || '' ,
serveraddress : registryConfig . serverAddress
} ;
callback ( null , auth ) ;
} ) ;
}
2015-10-19 15:37:57 -07:00
function pullImage ( manifest , callback ) {
2019-10-27 12:14:27 -07:00
getRegistryConfig ( manifest . dockerImage , function ( error , authConfig ) {
if ( error ) return callback ( error ) ;
2015-10-19 11:40:19 -07:00
2019-10-27 12:14:27 -07:00
debug ( ` pullImage: will pull ${ manifest . dockerImage } . auth: ${ authConfig ? 'yes' : 'no' } ` ) ;
2015-10-19 11:40:19 -07:00
2019-12-04 13:17:58 -08:00
gConnection . pull ( manifest . dockerImage , { authconfig : authConfig } , function ( error , stream ) {
2019-10-27 12:14:27 -07:00
if ( error ) return callback ( new BoxError ( BoxError . DOCKER _ERROR , 'Unable to pull image. Please check the network or if the image needs authentication. statusCode: ' + error . statusCode ) ) ;
// https://github.com/dotcloud/docker/issues/1074 says each status message
// is emitted as a chunk
stream . on ( 'data' , function ( chunk ) {
var data = safe . JSON . parse ( chunk ) || { } ;
2019-11-15 17:28:43 -08:00
debug ( 'pullImage: %j' , data ) ;
2019-10-27 12:14:27 -07:00
// The data.status here is useless because this is per layer as opposed to per image
if ( ! data . status && data . error ) {
2019-11-15 17:28:43 -08:00
debug ( 'pullImage error %s: %s' , manifest . dockerImage , data . errorDetail . message ) ;
2019-10-27 12:14:27 -07:00
}
} ) ;
stream . on ( 'end' , function ( ) {
2019-11-15 17:28:43 -08:00
debug ( 'downloaded image %s' , manifest . dockerImage ) ;
2019-10-27 12:14:27 -07:00
callback ( null ) ;
} ) ;
2015-10-19 11:40:19 -07:00
2019-10-27 12:14:27 -07:00
stream . on ( 'error' , function ( error ) {
2019-11-15 17:28:43 -08:00
debug ( 'error pulling image %s: %j' , manifest . dockerImage , error ) ;
2015-10-19 11:40:19 -07:00
2019-10-27 12:14:27 -07:00
callback ( new BoxError ( BoxError . DOCKER _ERROR , error . message ) ) ;
} ) ;
2015-10-19 11:40:19 -07:00
} ) ;
} ) ;
}
2015-10-19 15:37:57 -07:00
function downloadImage ( manifest , callback ) {
2015-10-19 15:51:02 -07:00
assert . strictEqual ( typeof manifest , 'object' ) ;
assert . strictEqual ( typeof callback , 'function' ) ;
2019-11-15 17:28:43 -08:00
debug ( 'downloadImage %s' , manifest . dockerImage ) ;
2015-10-19 11:40:19 -07:00
var attempt = 1 ;
2015-11-12 16:22:53 -08:00
async . retry ( { times : 10 , interval : 15000 } , function ( retryCallback ) {
2019-11-15 17:28:43 -08:00
debug ( 'Downloading image %s. attempt: %s' , manifest . dockerImage , attempt ++ ) ;
2015-10-19 11:40:19 -07:00
2015-10-19 15:37:57 -07:00
pullImage ( manifest , function ( error ) {
2015-10-19 11:40:19 -07:00
if ( error ) console . error ( error ) ;
retryCallback ( error ) ;
} ) ;
} , callback ) ;
}
2015-11-10 12:47:48 -08:00
function createSubcontainer ( app , name , cmd , options , callback ) {
2015-10-19 11:40:19 -07:00
assert . strictEqual ( typeof app , 'object' ) ;
2015-11-02 09:34:31 -08:00
assert . strictEqual ( typeof name , 'string' ) ;
2015-10-19 16:22:35 -07:00
assert ( ! cmd || util . isArray ( cmd ) ) ;
2015-11-10 12:47:48 -08:00
assert . strictEqual ( typeof options , 'object' ) ;
2015-10-19 11:40:19 -07:00
assert . strictEqual ( typeof callback , 'function' ) ;
2019-12-04 13:17:58 -08:00
let isAppContainer = ! cmd ; // non app-containers are like scheduler and exec (terminal) containers
2015-10-19 11:40:19 -07:00
var manifest = app . manifest ;
var exposedPorts = { } , dockerPortBindings = { } ;
2018-02-08 15:07:49 +01:00
var domain = app . fqdn ;
2019-06-26 14:21:00 -07:00
const hostname = isAppContainer ? app . id : name ;
2019-06-03 13:45:03 -07:00
const envPrefix = manifest . manifestVersion <= 1 ? '' : 'CLOUDRON_' ;
let stdEnv = [
2015-10-19 11:40:19 -07:00
'CLOUDRON=1' ,
2018-11-22 16:50:02 -08:00
'CLOUDRON_PROXY_IP=172.18.0.1' ,
2019-06-26 14:18:39 -07:00
` CLOUDRON_APP_HOSTNAME= ${ app . id } ` ,
2019-07-26 10:49:29 -07:00
` ${ envPrefix } WEBADMIN_ORIGIN= ${ settings . adminOrigin ( ) } ` ,
` ${ envPrefix } API_ORIGIN= ${ settings . adminOrigin ( ) } ` ,
2019-06-03 13:45:03 -07:00
` ${ envPrefix } APP_ORIGIN=https:// ${ domain } ` ,
` ${ envPrefix } APP_DOMAIN= ${ domain } `
2015-10-19 11:40:19 -07:00
] ;
// docker portBindings requires ports to be exposed
exposedPorts [ manifest . httpPort + '/tcp' ] = { } ;
dockerPortBindings [ manifest . httpPort + '/tcp' ] = [ { HostIp : '127.0.0.1' , HostPort : app . httpPort + '' } ] ;
2015-10-19 16:00:40 -07:00
var portEnv = [ ] ;
2018-08-12 19:33:11 -07:00
for ( let portName in app . portBindings ) {
2018-08-12 22:47:59 -07:00
const hostPort = app . portBindings [ portName ] ;
2019-10-11 20:35:59 -07:00
const portType = ( manifest . tcpPorts && portName in manifest . tcpPorts ) ? 'tcp' : 'udp' ;
2018-08-12 22:47:59 -07:00
const ports = portType == 'tcp' ? manifest . tcpPorts : manifest . udpPorts ;
2015-10-19 11:40:19 -07:00
2018-08-12 22:47:59 -07:00
var containerPort = ports [ portName ] . containerPort || hostPort ;
exposedPorts [ ` ${ containerPort } / ${ portType } ` ] = { } ;
2018-08-12 19:33:11 -07:00
portEnv . push ( ` ${ portName } = ${ hostPort } ` ) ;
2015-10-19 11:40:19 -07:00
2018-08-12 22:47:59 -07:00
dockerPortBindings [ ` ${ containerPort } / ${ portType } ` ] = [ { HostIp : '0.0.0.0' , HostPort : hostPort + '' } ] ;
2015-10-19 11:40:19 -07:00
}
2018-10-11 16:18:38 -07:00
2018-10-11 14:07:43 -07:00
let appEnv = [ ] ;
Object . keys ( app . env ) . forEach ( function ( name ) { appEnv . push ( ` ${ name } = ${ app . env [ name ] } ` ) ; } ) ;
2015-10-19 11:40:19 -07:00
2016-02-11 18:13:42 +01:00
// first check db record, then manifest
2016-09-03 21:02:22 -07:00
var memoryLimit = app . memoryLimit || manifest . memoryLimit || 0 ;
2016-02-11 18:13:42 +01:00
2017-01-19 15:02:12 -08:00
if ( memoryLimit === - 1 ) { // unrestricted
2016-09-03 21:02:22 -07:00
memoryLimit = 0 ;
} else if ( memoryLimit === 0 || memoryLimit < constants . DEFAULT _MEMORY _LIMIT ) { // ensure we never go below minimum (in case we change the default)
memoryLimit = constants . DEFAULT _MEMORY _LIMIT ;
}
2016-02-11 17:00:21 +01:00
2018-02-27 10:51:21 -08:00
// give scheduler tasks twice the memory limit since background jobs take more memory
// if required, we can make this a manifest and runtime argument later
if ( ! isAppContainer ) memoryLimit *= 2 ;
2015-10-19 16:00:40 -07:00
addons . getEnvironment ( app , function ( error , addonEnv ) {
2019-10-24 20:31:45 -07:00
if ( error ) return callback ( error ) ;
2015-10-19 16:00:40 -07:00
2016-06-18 17:53:54 -05:00
// do no set hostname of containers to location as it might conflict with addons names. for example, an app installed in mail
// location may not reach mail container anymore by DNS. We cannot set hostname to fqdn either as that sets up the dns
// name to look up the internal docker ip. this makes curl from within container fail
// Note that Hostname has no effect on DNS. We have to use the --net-alias for dns.
// Hostname cannot be set with container NetworkMode
2015-10-19 16:00:40 -07:00
var containerOptions = {
2019-06-01 09:36:35 -07:00
name : name , // for referencing containers
2015-10-20 10:51:43 -07:00
Tty : isAppContainer ,
2019-06-26 14:21:00 -07:00
Hostname : hostname ,
2015-10-19 16:00:40 -07:00
Image : app . manifest . dockerImage ,
2017-01-20 05:48:25 -08:00
Cmd : ( isAppContainer && app . debugMode && app . debugMode . cmd ) ? app . debugMode . cmd : cmd ,
2018-10-11 14:07:43 -07:00
Env : stdEnv . concat ( addonEnv ) . concat ( portEnv ) . concat ( appEnv ) ,
2015-10-20 10:51:43 -07:00
ExposedPorts : isAppContainer ? exposedPorts : { } ,
2015-10-19 16:00:40 -07:00
Volumes : { // see also ReadonlyRootfs
'/tmp' : { } ,
'/run' : { }
2015-10-19 11:40:19 -07:00
} ,
2015-10-19 16:01:04 -07:00
Labels : {
2018-02-08 15:07:49 +01:00
'fqdn' : app . fqdn ,
2017-09-30 18:17:50 -07:00
'appId' : app . id ,
2018-11-10 18:57:50 -08:00
'isSubcontainer' : String ( ! isAppContainer ) ,
'isCloudronManaged' : String ( true )
2015-10-19 16:01:04 -07:00
} ,
2015-10-19 16:00:40 -07:00
HostConfig : {
2018-09-13 13:55:49 -07:00
Mounts : addons . getMountsSync ( app , app . manifest . addons ) ,
2018-01-16 17:41:18 +01:00
LogConfig : {
Type : 'syslog' ,
Config : {
'tag' : app . id ,
2018-06-04 21:12:55 +02:00
'syslog-address' : 'udp://127.0.0.1:2514' , // see apps.js:validatePortBindings()
2018-01-16 17:41:18 +01:00
'syslog-format' : 'rfc5424'
}
} ,
2015-10-19 16:00:40 -07:00
Memory : memoryLimit / 2 ,
MemorySwap : memoryLimit , // Memory + Swap
2015-10-20 10:51:43 -07:00
PortBindings : isAppContainer ? dockerPortBindings : { } ,
2015-10-19 16:00:40 -07:00
PublishAllPorts : false ,
2017-01-20 05:48:25 -08:00
ReadonlyRootfs : app . debugMode ? ! ! app . debugMode . readonlyRootfs : true ,
2015-10-19 16:00:40 -07:00
RestartPolicy : {
2019-11-13 10:29:29 -08:00
'Name' : isAppContainer ? 'unless-stopped' : 'no' ,
2017-09-30 18:17:50 -07:00
'MaximumRetryCount' : 0
2015-10-19 16:00:40 -07:00
} ,
CpuShares : 512 , // relative to 1024 for system processes
2017-09-30 18:17:50 -07:00
VolumesFrom : isAppContainer ? null : [ app . containerId + ':rw' ] ,
2019-06-01 09:36:35 -07:00
NetworkMode : 'cloudron' , // user defined bridge network
2017-04-20 17:02:28 +00:00
Dns : [ '172.18.0.1' ] , // use internal dns
2017-04-20 21:28:20 +00:00
DnsSearch : [ '.' ] , // use internal dns
2018-11-23 11:23:33 -08:00
SecurityOpt : [ 'apparmor=docker-cloudron-app' ]
2019-06-01 10:48:17 -07:00
} ,
NetworkingConfig : {
EndpointsConfig : {
cloudron : {
Aliases : [ name ] // this allows sub-containers reach app containers by name
}
}
2015-10-20 17:34:47 -07:00
}
2015-10-19 16:00:40 -07:00
} ;
2017-08-11 23:22:48 +01:00
var capabilities = manifest . capabilities || [ ] ;
if ( capabilities . includes ( 'net_admin' ) ) {
containerOptions . HostConfig . CapAdd = [
'NET_ADMIN'
] ;
}
2015-11-10 12:47:48 -08:00
containerOptions = _ . extend ( containerOptions , options ) ;
2015-10-19 11:40:19 -07:00
2017-09-30 18:17:50 -07:00
debugApp ( app , 'Creating container for %s' , app . manifest . dockerImage ) ;
2015-10-19 11:40:19 -07:00
2019-12-04 13:17:58 -08:00
gConnection . createContainer ( containerOptions , function ( error , container ) {
2019-10-22 21:46:32 -07:00
if ( error ) return callback ( new BoxError ( BoxError . DOCKER _ERROR , error ) ) ;
2019-10-23 09:24:51 -07:00
callback ( null , container ) ;
2019-10-22 21:46:32 -07:00
} ) ;
2015-10-19 16:00:40 -07:00
} ) ;
2015-10-19 11:40:19 -07:00
}
2015-10-19 21:33:53 -07:00
function createContainer ( app , callback ) {
2015-11-10 12:47:48 -08:00
createSubcontainer ( app , app . id /* name */ , null /* cmd */ , { } /* options */ , callback ) ;
2015-10-19 21:33:53 -07:00
}
2015-10-19 15:39:26 -07:00
function startContainer ( containerId , callback ) {
2015-10-19 15:51:02 -07:00
assert . strictEqual ( typeof containerId , 'string' ) ;
assert . strictEqual ( typeof callback , 'function' ) ;
2019-12-04 13:17:58 -08:00
var container = gConnection . getContainer ( containerId ) ;
2015-10-19 15:39:26 -07:00
debug ( 'Starting container %s' , containerId ) ;
2015-10-19 11:40:19 -07:00
container . start ( function ( error ) {
2019-09-23 12:13:21 -07:00
if ( error && error . statusCode === 404 ) return callback ( new BoxError ( BoxError . NOT _FOUND ) ) ;
2019-10-20 13:35:19 -07:00
if ( error && error . statusCode === 400 ) return callback ( new BoxError ( BoxError . BAD _FIELD , error ) ) ; // e.g start.sh is not executable
2019-12-20 10:29:29 -08:00
if ( error && error . statusCode !== 304 ) return callback ( new BoxError ( BoxError . DOCKER _ERROR , error ) ) ; // 304 means already started
return callback ( null ) ;
} ) ;
}
function restartContainer ( containerId , callback ) {
assert . strictEqual ( typeof containerId , 'string' ) ;
assert . strictEqual ( typeof callback , 'function' ) ;
var container = gConnection . getContainer ( containerId ) ;
debug ( 'Restarting container %s' , containerId ) ;
container . restart ( function ( error ) {
if ( error && error . statusCode === 404 ) return callback ( new BoxError ( BoxError . NOT _FOUND ) ) ;
if ( error && error . statusCode === 400 ) return callback ( new BoxError ( BoxError . BAD _FIELD , error ) ) ; // e.g start.sh is not executable
if ( error && error . statusCode !== 204 ) return callback ( new BoxError ( BoxError . DOCKER _ERROR , error ) ) ;
2015-10-19 11:40:19 -07:00
return callback ( null ) ;
} ) ;
}
2015-10-19 15:39:26 -07:00
function stopContainer ( containerId , callback ) {
2015-10-19 15:51:02 -07:00
assert ( ! containerId || typeof containerId === 'string' ) ;
assert . strictEqual ( typeof callback , 'function' ) ;
2015-10-19 15:39:26 -07:00
if ( ! containerId ) {
debug ( 'No previous container to stop' ) ;
2015-10-19 11:40:19 -07:00
return callback ( ) ;
}
2019-12-04 13:17:58 -08:00
var container = gConnection . getContainer ( containerId ) ;
2015-10-19 15:39:26 -07:00
debug ( 'Stopping container %s' , containerId ) ;
2015-10-19 11:40:19 -07:00
var options = {
t : 10 // wait for 10 seconds before killing it
} ;
container . stop ( options , function ( error ) {
2019-10-22 21:46:32 -07:00
if ( error && ( error . statusCode !== 304 && error . statusCode !== 404 ) ) return callback ( new BoxError ( BoxError . DOCKER _ERROR , 'Error stopping container:' + error . message ) ) ;
2015-10-19 11:40:19 -07:00
2015-10-19 15:39:26 -07:00
debug ( 'Waiting for container ' + containerId ) ;
2015-10-19 11:40:19 -07:00
container . wait ( function ( error , data ) {
2019-10-22 21:46:32 -07:00
if ( error && ( error . statusCode !== 304 && error . statusCode !== 404 ) ) return callback ( new BoxError ( BoxError . DOCKER _ERROR , 'Error waiting on container:' + error . message ) ) ;
2015-10-19 11:40:19 -07:00
2015-10-19 15:39:26 -07:00
debug ( 'Container %s stopped with status code [%s]' , containerId , data ? String ( data . StatusCode ) : '' ) ;
2015-10-19 11:40:19 -07:00
return callback ( null ) ;
} ) ;
} ) ;
}
2015-10-19 15:39:26 -07:00
function deleteContainer ( containerId , callback ) {
2015-10-19 15:51:02 -07:00
assert ( ! containerId || typeof containerId === 'string' ) ;
assert . strictEqual ( typeof callback , 'function' ) ;
2015-10-19 18:48:56 -07:00
debug ( 'deleting container %s' , containerId ) ;
2015-10-19 15:39:26 -07:00
if ( containerId === null ) return callback ( null ) ;
2015-10-19 11:40:19 -07:00
2019-12-04 13:17:58 -08:00
var container = gConnection . getContainer ( containerId ) ;
2015-10-19 11:40:19 -07:00
var removeOptions = {
force : true , // kill container if it's running
v : true // removes volumes associated with the container (but not host mounts)
} ;
container . remove ( removeOptions , function ( error ) {
if ( error && error . statusCode === 404 ) return callback ( null ) ;
2019-10-23 09:53:46 -07:00
if ( error ) {
debug ( 'Error removing container %s : %j' , containerId , error ) ;
return callback ( new BoxError ( BoxError . DOCKER _ERROR , error ) ) ;
}
2015-10-19 15:39:26 -07:00
2019-10-23 09:53:46 -07:00
callback ( null ) ;
2015-10-19 11:40:19 -07:00
} ) ;
}
2019-01-17 23:32:24 -08:00
function deleteContainers ( appId , options , callback ) {
2015-10-19 18:48:56 -07:00
assert . strictEqual ( typeof appId , 'string' ) ;
2019-01-17 23:32:24 -08:00
assert . strictEqual ( typeof options , 'object' ) ;
2015-10-19 18:48:56 -07:00
assert . strictEqual ( typeof callback , 'function' ) ;
debug ( 'deleting containers of %s' , appId ) ;
2019-01-17 23:32:24 -08:00
let labels = [ 'appId=' + appId ] ;
if ( options . managedOnly ) labels . push ( 'isCloudronManaged=true' ) ;
2019-12-04 13:17:58 -08:00
gConnection . listContainers ( { all : 1 , filters : JSON . stringify ( { label : labels } ) } , function ( error , containers ) {
2019-09-23 23:27:32 -07:00
if ( error ) return callback ( new BoxError ( BoxError . DOCKER _ERROR , error ) ) ;
2015-10-19 18:48:56 -07:00
async . eachSeries ( containers , function ( container , iteratorDone ) {
deleteContainer ( container . Id , iteratorDone ) ;
} , callback ) ;
} ) ;
}
2015-10-20 00:05:07 -07:00
function stopContainers ( appId , callback ) {
assert . strictEqual ( typeof appId , 'string' ) ;
assert . strictEqual ( typeof callback , 'function' ) ;
debug ( 'stopping containers of %s' , appId ) ;
2019-12-04 13:17:58 -08:00
gConnection . listContainers ( { all : 1 , filters : JSON . stringify ( { label : [ 'appId=' + appId ] } ) } , function ( error , containers ) {
2019-09-23 23:27:32 -07:00
if ( error ) return callback ( new BoxError ( BoxError . DOCKER _ERROR , error ) ) ;
2015-10-20 00:05:07 -07:00
async . eachSeries ( containers , function ( container , iteratorDone ) {
stopContainer ( container . Id , iteratorDone ) ;
} , callback ) ;
} ) ;
}
2015-10-19 15:39:26 -07:00
function deleteImage ( manifest , callback ) {
2015-10-19 15:51:02 -07:00
assert ( ! manifest || typeof manifest === 'object' ) ;
assert . strictEqual ( typeof callback , 'function' ) ;
2015-10-19 11:40:19 -07:00
var dockerImage = manifest ? manifest . dockerImage : null ;
if ( ! dockerImage ) return callback ( null ) ;
2016-01-21 14:59:24 -08:00
var removeOptions = {
force : false , // might be shared with another instance of this app
noprune : false // delete untagged parents
} ;
2015-10-19 11:40:19 -07:00
2016-01-21 14:59:24 -08:00
// registry v1 used to pull down all *tags*. this meant that deleting image by tag was not enough (since that
// just removes the tag). we used to remove the image by id. this is not required anymore because aliases are
// not created anymore after https://github.com/docker/docker/pull/10571
2019-12-04 13:17:58 -08:00
gConnection . getImage ( dockerImage ) . remove ( removeOptions , function ( error ) {
2018-10-24 13:09:35 -07:00
if ( error && error . statusCode === 400 ) return callback ( null ) ; // invalid image format. this can happen if user installed with a bad --docker-image
if ( error && error . statusCode === 404 ) return callback ( null ) ; // not found
2016-01-21 14:59:24 -08:00
if ( error && error . statusCode === 409 ) return callback ( null ) ; // another container using the image
2015-10-19 11:40:19 -07:00
2019-10-23 09:24:51 -07:00
if ( error ) {
debug ( 'Error removing image %s : %j' , dockerImage , error ) ;
return callback ( new BoxError ( BoxError . DOCKER _ERROR , error ) ) ;
}
2015-10-19 11:40:19 -07:00
2019-10-23 09:24:51 -07:00
callback ( null ) ;
2015-10-19 11:40:19 -07:00
} ) ;
}
2016-02-18 15:39:27 +01:00
function getContainerIdByIp ( ip , callback ) {
assert . strictEqual ( typeof ip , 'string' ) ;
assert . strictEqual ( typeof callback , 'function' ) ;
2019-12-04 13:17:58 -08:00
gConnection . getNetwork ( 'cloudron' ) . inspect ( function ( error , bridge ) {
2019-12-04 10:29:06 -08:00
if ( error && error . statusCode === 404 ) return callback ( new BoxError ( BoxError . DOCKER _ERROR , 'Unable to find the cloudron network' ) ) ;
2019-09-23 23:27:32 -07:00
if ( error ) return callback ( new BoxError ( BoxError . DOCKER _ERROR , error ) ) ;
2016-02-18 15:39:27 +01:00
var containerId ;
for ( var id in bridge . Containers ) {
2017-11-11 16:54:43 -08:00
if ( bridge . Containers [ id ] . IPv4Address . indexOf ( ip + '/16' ) === 0 ) {
2016-02-18 15:39:27 +01:00
containerId = id ;
break ;
}
}
2019-12-04 10:29:06 -08:00
if ( ! containerId ) return callback ( new BoxError ( BoxError . DOCKER _ERROR , 'No container with that ip' ) ) ;
2016-02-18 15:39:27 +01:00
callback ( null , containerId ) ;
} ) ;
}
2016-04-18 10:32:22 -07:00
2017-08-11 22:04:40 +02:00
function inspect ( containerId , callback ) {
assert . strictEqual ( typeof containerId , 'string' ) ;
assert . strictEqual ( typeof callback , 'function' ) ;
2019-12-04 13:17:58 -08:00
var container = gConnection . getContainer ( containerId ) ;
2017-08-11 22:04:40 +02:00
container . inspect ( function ( error , result ) {
2019-09-23 12:13:21 -07:00
if ( error && error . statusCode === 404 ) return callback ( new BoxError ( BoxError . NOT _FOUND ) ) ;
2019-09-23 23:27:32 -07:00
if ( error ) return callback ( new BoxError ( BoxError . DOCKER _ERROR , error ) ) ;
2018-11-19 10:19:46 +01:00
2018-11-28 10:39:12 +01:00
callback ( null , result ) ;
} ) ;
}
2019-12-04 13:17:58 -08:00
function execContainer ( containerId , options , callback ) {
assert . strictEqual ( typeof containerId , 'string' ) ;
2019-03-06 11:54:37 -08:00
assert . strictEqual ( typeof options , 'object' ) ;
assert . strictEqual ( typeof callback , 'function' ) ;
2019-12-04 13:17:58 -08:00
var container = gConnection . getContainer ( containerId ) ;
2019-03-06 11:54:37 -08:00
2019-12-04 13:17:58 -08:00
container . exec ( options . execOptions , function ( error , exec ) {
if ( error && error . statusCode === 409 ) return callback ( new BoxError ( BoxError . BAD _STATE , error . message ) ) ; // container restarting/not running
if ( error ) return callback ( new BoxError ( BoxError . DOCKER _ERROR , error ) ) ;
exec . start ( options . startOptions , function ( error , stream /* in hijacked mode, this is a net.socket */ ) {
if ( error ) return callback ( new BoxError ( BoxError . DOCKER _ERROR , error ) ) ;
if ( options . rows && options . columns ) {
// there is a race where resizing too early results in a 404 "no such exec"
// https://git.cloudron.io/cloudron/box/issues/549
setTimeout ( function ( ) {
exec . resize ( { h : options . rows , w : options . columns } , function ( error ) { if ( error ) debug ( 'Error resizing console' , error ) ; } ) ;
} , 2000 ) ;
}
callback ( null , stream ) ;
} ) ;
} ) ;
}
function getEvents ( options , callback ) {
assert . strictEqual ( typeof options , 'object' ) ;
assert . strictEqual ( typeof callback , 'function' ) ;
gConnection . getEvents ( options , function ( error , stream ) {
2019-09-23 23:27:32 -07:00
if ( error ) return callback ( new BoxError ( BoxError . DOCKER _ERROR , error ) ) ;
2019-03-06 11:54:37 -08:00
callback ( null , stream ) ;
} ) ;
}
2018-11-28 10:39:12 +01:00
function memoryUsage ( containerId , callback ) {
assert . strictEqual ( typeof containerId , 'string' ) ;
assert . strictEqual ( typeof callback , 'function' ) ;
2019-12-04 13:17:58 -08:00
var container = gConnection . getContainer ( containerId ) ;
2018-11-28 10:39:12 +01:00
container . stats ( { stream : false } , function ( error , result ) {
2019-09-23 12:13:21 -07:00
if ( error && error . statusCode === 404 ) return callback ( new BoxError ( BoxError . NOT _FOUND ) ) ;
2019-09-23 23:27:32 -07:00
if ( error ) return callback ( new BoxError ( BoxError . DOCKER _ERROR , error ) ) ;
2018-11-28 10:39:12 +01:00
2017-08-11 22:04:40 +02:00
callback ( null , result ) ;
} ) ;
}
2019-01-13 19:18:06 -08:00
function createVolume ( app , name , volumeDataDir , callback ) {
2018-09-13 13:55:49 -07:00
assert . strictEqual ( typeof app , 'object' ) ;
assert . strictEqual ( typeof name , 'string' ) ;
2019-01-13 19:18:06 -08:00
assert . strictEqual ( typeof volumeDataDir , 'string' ) ;
2018-09-13 13:55:49 -07:00
assert . strictEqual ( typeof callback , 'function' ) ;
const volumeOptions = {
Name : name ,
Driver : 'local' ,
DriverOpts : { // https://github.com/moby/moby/issues/19990#issuecomment-248955005
type : 'none' ,
device : volumeDataDir ,
o : 'bind'
} ,
Labels : {
'fqdn' : app . fqdn ,
'appId' : app . id
} ,
} ;
2018-12-20 14:33:29 -08:00
// requires sudo because the path can be outside appsdata
shell . sudo ( 'createVolume' , [ MKDIRVOLUME _CMD , volumeDataDir ] , { } , function ( error ) {
2019-12-04 10:29:06 -08:00
if ( error ) return callback ( new BoxError ( BoxError . FS _ERROR , ` Error creating app data dir: ${ error . message } ` ) ) ;
2018-09-13 13:55:49 -07:00
2019-12-04 13:17:58 -08:00
gConnection . createVolume ( volumeOptions , function ( error ) {
2019-09-23 23:27:32 -07:00
if ( error ) return callback ( new BoxError ( BoxError . DOCKER _ERROR , error ) ) ;
2018-09-13 13:55:49 -07:00
callback ( ) ;
} ) ;
} ) ;
}
2019-01-18 14:48:31 -08:00
function clearVolume ( app , name , options , callback ) {
2018-09-15 17:05:04 -07:00
assert . strictEqual ( typeof app , 'object' ) ;
assert . strictEqual ( typeof name , 'string' ) ;
2019-01-18 14:48:31 -08:00
assert . strictEqual ( typeof options , 'object' ) ;
2018-09-15 17:05:04 -07:00
assert . strictEqual ( typeof callback , 'function' ) ;
2019-12-04 13:17:58 -08:00
let volume = gConnection . getVolume ( name ) ;
2019-01-13 19:18:06 -08:00
volume . inspect ( function ( error , v ) {
if ( error && error . statusCode === 404 ) return callback ( ) ;
2019-09-23 23:27:32 -07:00
if ( error ) return callback ( new BoxError ( BoxError . DOCKER _ERROR , error ) ) ;
2019-01-13 19:18:06 -08:00
const volumeDataDir = v . Options . device ;
2019-10-22 21:46:32 -07:00
shell . sudo ( 'clearVolume' , [ CLEARVOLUME _CMD , options . removeDirectory ? 'rmdir' : 'clear' , volumeDataDir ] , { } , function ( error ) {
if ( error ) return callback ( new BoxError ( BoxError . FS _ERROR , error ) ) ;
callback ( ) ;
} ) ;
2019-01-13 19:18:06 -08:00
} ) ;
2018-09-15 17:05:04 -07:00
}
2019-01-17 09:53:51 -08:00
// this only removes the volume and not the data
2019-01-13 19:18:06 -08:00
function removeVolume ( app , name , callback ) {
2018-09-13 13:55:49 -07:00
assert . strictEqual ( typeof app , 'object' ) ;
assert . strictEqual ( typeof name , 'string' ) ;
assert . strictEqual ( typeof callback , 'function' ) ;
2019-12-04 13:17:58 -08:00
let volume = gConnection . getVolume ( name ) ;
2019-01-15 09:36:32 -08:00
volume . remove ( function ( error ) {
2019-10-22 21:46:32 -07:00
if ( error && error . statusCode !== 404 ) return callback ( new BoxError ( BoxError . DOCKER _ERROR , ` removeVolume: Error removing volume of ${ app . id } ${ error . message } ` ) ) ;
2019-01-13 19:18:06 -08:00
2019-01-15 09:36:32 -08:00
callback ( ) ;
2018-09-13 13:55:49 -07:00
} ) ;
}
2019-08-12 20:38:24 -07:00
function info ( callback ) {
assert . strictEqual ( typeof callback , 'function' ) ;
2019-12-04 13:17:58 -08:00
gConnection . info ( function ( error , result ) {
2019-09-23 23:27:32 -07:00
if ( error ) return callback ( new BoxError ( BoxError . DOCKER _ERROR , 'Error connecting to docker' ) ) ;
2019-08-12 20:38:24 -07:00
callback ( null , result ) ;
} ) ;
}