Support for several compose features

Plus a few bugfixes.

* Add support for cgroup_parent

* Add support for specifying a single value in tmpfs

* Fix support for extra_hosts

* Add support for group_add

* Add support for pid mode (only host and empty value are supported for now)

* Add support for pids_limit

* Add support for security_opt

* Add support for storage_opt

* Add support for userns_mode

* Add support for ipc (except for another container's)

* Add support for mac_address

* Add support for oom_kill_disable

* Add support for 'user' compose option

* Add support for working_dir and fix support for user when image specifies it

* Add support for bind-mounting the balena socket using a label

Signed-off-by: Pablo Carranza Velez <pablo@resin.io>
This commit is contained in:
Pablo Carranza Velez 2018-02-08 11:29:51 -08:00
parent 38c3a8bdf3
commit ec22bfcb29

View File

@ -58,6 +58,22 @@ getStopSignal = (service, imageInfo) ->
sig = sig.toString() sig = sig.toString()
return sig return sig
getUser = (service, imageInfo) ->
user = ''
if service.user?
user = service.user
else if imageInfo?.Config?.User?
user = imageInfo.Config.User
return user
getWorkingDir = (service, imageInfo) ->
workingDir = ''
if service.workingDir?
workingDir = service.workingDir
else if imageInfo?.Config?.WorkingDir?
workingDir = imageInfo.Config.WorkingDir
return workingDir
buildHealthcheckTest = (test) -> buildHealthcheckTest = (test) ->
if _.isString(test) if _.isString(test)
return [ 'CMD-SHELL', test ] return [ 'CMD-SHELL', test ]
@ -151,6 +167,7 @@ module.exports = class Service
@cpuset @cpuset
@nanoCpus @nanoCpus
@domainname @domainname
@oomKillDisable
@oomScoreAdj @oomScoreAdj
@dns @dns
@dnsSearch @dnsSearch
@ -166,6 +183,17 @@ module.exports = class Service
@readOnly @readOnly
@sysctls @sysctls
@hostname @hostname
@cgroupParent
@groupAdd
@pid
@pidsLimit
@securityOpt
@storageOpt
@usernsMode
@ipc
@macAddress
@user
@workingDir
} = _.mapKeys(serviceProperties, (v, k) -> _.camelCase(k)) } = _.mapKeys(serviceProperties, (v, k) -> _.camelCase(k))
@networks ?= {} @networks ?= {}
@ -193,6 +221,7 @@ module.exports = class Service
@domainname ?= '' @domainname ?= ''
@oomScoreAdj ?= 0 @oomScoreAdj ?= 0
@oomKillDisable ?= false
@tmpfs ?= [] @tmpfs ?= []
@extraHosts ?= [] @extraHosts ?= []
@ -200,16 +229,29 @@ module.exports = class Service
@dnsSearch ?= [] @dnsSearch ?= []
@dnsOpt ?= [] @dnsOpt ?= []
@ulimitsArray ?= [] @ulimitsArray ?= []
@groupAdd ?= []
@stopSignal ?= null @stopSignal ?= null
@stopGracePeriod ?= null @stopGracePeriod ?= null
@healthcheck ?= null @healthcheck ?= null
@init ?= null @init ?= null
@readOnly ?= false @readOnly ?= false
@macAddress ?= null
@sysctls ?= {} @sysctls ?= {}
@hostname ?= '' @hostname ?= ''
@cgroupParent ?= ''
@pid ?= ''
@pidsLimit ?= 0
@securityOpt ?= []
@storageOpt ?= {}
@usernsMode ?= ''
@user ?= ''
@workingDir ?= ''
if _.isEmpty(@ipc)
@ipc = 'shareable'
# If the service has no containerId, it is a target service and has to be normalised and extended # If the service has no containerId, it is a target service and has to be normalised and extended
if !@containerId? if !@containerId?
@ -217,7 +259,7 @@ module.exports = class Service
if @networkMode not in [ 'host', 'bridge', 'none' ] if @networkMode not in [ 'host', 'bridge', 'none' ]
@networkMode = "#{@appId}_#{@networkMode}" @networkMode = "#{@appId}_#{@networkMode}"
@networks = _.mapKeys @networks, (v, k) -> @networks = _.mapKeys @networks, (v, k) =>
if k not in [ 'host', 'bridge', 'none' ] if k not in [ 'host', 'bridge', 'none' ]
return "#{@appId}_#{k}" return "#{@appId}_#{k}"
else else
@ -230,6 +272,8 @@ module.exports = class Service
@entrypoint = getEntrypoint(serviceProperties, opts.imageInfo) @entrypoint = getEntrypoint(serviceProperties, opts.imageInfo)
@stopSignal = getStopSignal(serviceProperties, opts.imageInfo) @stopSignal = getStopSignal(serviceProperties, opts.imageInfo)
@healthcheck = getHealthcheck(serviceProperties, opts.imageInfo) @healthcheck = getHealthcheck(serviceProperties, opts.imageInfo)
@workingDir = getWorkingDir(serviceProperties, opts.imageInfo)
@user = getUser(serviceProperties, opts.imageInfo)
@extendEnvVars(opts) @extendEnvVars(opts)
@extendLabels(opts.imageInfo) @extendLabels(opts.imageInfo)
@extendAndSanitiseVolumes(opts.imageInfo) @extendAndSanitiseVolumes(opts.imageInfo)
@ -243,6 +287,9 @@ module.exports = class Service
if @dnsSearch? if @dnsSearch?
if !Array.isArray(@dnsSearch) if !Array.isArray(@dnsSearch)
@dnsSearch = [ @dns ] @dnsSearch = [ @dns ]
if @tmpfs?
if !Array.isArray(@tmpfs)
@tmpfs = [ @tmpfs ]
@nanoCpus = Math.round(Number(@cpus) * 10 ** 9) @nanoCpus = Math.round(Number(@cpus) * 10 ** 9)
@ -258,6 +305,7 @@ module.exports = class Service
d = new Duration(@stopGracePeriod) d = new Duration(@stopGracePeriod)
@stopGracePeriod = d.seconds() @stopGracePeriod = d.seconds()
@oomKillDisable = Boolean(@oomKillDisable)
@readOnly = Boolean(@readOnly) @readOnly = Boolean(@readOnly)
if Array.isArray(@sysctls) if Array.isArray(@sysctls)
@ -286,6 +334,9 @@ module.exports = class Service
@volumes.push('/lib/modules:/lib/modules') @volumes.push('/lib/modules:/lib/modules')
if checkTruthy(@labels['io.resin.features.firmware']) if checkTruthy(@labels['io.resin.features.firmware'])
@volumes.push('/lib/firmware:/lib/firmware') @volumes.push('/lib/firmware:/lib/firmware')
if checkTruthy(@labels['io.resin.features.balena-socket'])
@volumes.push('/var/run/balena.sock:/var/run/balena.sock')
@environment['DOCKER_HOST'] ?= 'unix:///var/run/balena.sock'
if checkTruthy(@labels['io.resin.features.supervisor-api']) if checkTruthy(@labels['io.resin.features.supervisor-api'])
@_addSupervisorApi(opts) @_addSupervisorApi(opts)
else else
@ -452,6 +503,7 @@ module.exports = class Service
nanoCpus: container.HostConfig.NanoCpus nanoCpus: container.HostConfig.NanoCpus
cpuset: container.HostConfig.CpusetCpus cpuset: container.HostConfig.CpusetCpus
domainname: container.Config.Domainname domainname: container.Config.Domainname
oomKillDisable: container.HostConfig.OomKillDisable
oomScoreAdj: container.HostConfig.OomScoreAdj oomScoreAdj: container.HostConfig.OomScoreAdj
dns: container.HostConfig.Dns dns: container.HostConfig.Dns
dnsSearch: container.HostConfig.DnsSearch dnsSearch: container.HostConfig.DnsSearch
@ -466,6 +518,16 @@ module.exports = class Service
readOnly: container.HostConfig.ReadonlyRootfs readOnly: container.HostConfig.ReadonlyRootfs
sysctls: container.HostConfig.Sysctls sysctls: container.HostConfig.Sysctls
hostname: hostname hostname: hostname
cgroupParent: container.HostConfig.CgroupParent
groupAdd: container.HostConfig.GroupAdd
pid: container.HostConfig.PidMode
pidsLimit: container.HostConfig.PidsLimit
securityOpt: container.HostConfig.SecurityOpt
storageOpt: container.HostConfig.StorageOpt
usernsMode: container.HostConfig.UsernsMode
ipc: container.HostConfig.IpcMode
macAddress: container.Config.MacAddress
user: container.Config.User
} }
# I've seen docker use either 'no' or '' for no restart policy, so we normalise to 'no'. # I've seen docker use either 'no' or '' for no restart policy, so we normalise to 'no'.
if service.restartPolicy.Name == '' if service.restartPolicy.Name == ''
@ -523,6 +585,7 @@ module.exports = class Service
ExposedPorts: @exposedPorts ExposedPorts: @exposedPorts
Labels: @labels Labels: @labels
Domainname: @domainname Domainname: @domainname
User: @user
HostConfig: HostConfig:
Memory: @memLimit Memory: @memLimit
MemoryReservation: @memReservation MemoryReservation: @memReservation
@ -539,6 +602,7 @@ module.exports = class Service
CpuQuota: @cpuQuota CpuQuota: @cpuQuota
CpusetCpus: @cpuset CpusetCpus: @cpuset
OomScoreAdj: @oomScoreAdj OomScoreAdj: @oomScoreAdj
OomKillDisable: @oomKillDisable
Tmpfs: tmpfs Tmpfs: tmpfs
Dns: @dns Dns: @dns
DnsSearch: @dnsSearch DnsSearch: @dnsSearch
@ -546,6 +610,14 @@ module.exports = class Service
Ulimits: @ulimitsArray Ulimits: @ulimitsArray
ReadonlyRootfs: @readOnly ReadonlyRootfs: @readOnly
Sysctls: @sysctls Sysctls: @sysctls
CgroupParent: @cgroupParent
ExtraHosts: @extraHosts
GroupAdd: @groupAdd
PidMode: @pid
PidsLimit: @pidsLimit
SecurityOpt: @securityOpt
UsernsMode: @usernsMode
IpcMode: @ipc
} }
if @stopSignal? if @stopSignal?
conf.StopSignal = @stopSignal conf.StopSignal = @stopSignal
@ -568,6 +640,10 @@ module.exports = class Service
conf.HostConfig.Init = true conf.HostConfig.Init = true
if !_.isEmpty(@hostname) if !_.isEmpty(@hostname)
conf.Hostname = @hostname conf.Hostname = @hostname
if !_.isEmpty(@storageOpt)
conf.HostConfig.StorageOpt = @storageOpt
if @macAddress?
conf.MacAddress = @macAddress
return conf return conf
# TODO: when we support network configuration properly, return endpointConfig: conf # TODO: when we support network configuration properly, return endpointConfig: conf
@ -599,6 +675,7 @@ module.exports = class Service
'cpuset' 'cpuset'
'domainname' 'domainname'
'oomScoreAdj' 'oomScoreAdj'
'oomKillDisable'
'healthcheck' 'healthcheck'
'stopSignal' 'stopSignal'
'stopGracePeriod' 'stopGracePeriod'
@ -606,6 +683,14 @@ module.exports = class Service
'readOnly' 'readOnly'
'sysctls' 'sysctls'
'hostname' 'hostname'
'cgroupParent'
'pid'
'pidsLimit'
'storageOpt'
'usernsMode'
'ipc'
'macAddress'
'user'
] ]
arraysToCompare = [ arraysToCompare = [
'volumes' 'volumes'
@ -618,6 +703,8 @@ module.exports = class Service
'tmpfs' 'tmpfs'
'extraHosts' 'extraHosts'
'ulimitsArray' 'ulimitsArray'
'groupAdd'
'securityOpt'
] ]
isEq = _.isEqual(_.pick(this, propertiesToCompare), _.pick(otherService, propertiesToCompare)) and isEq = _.isEqual(_.pick(this, propertiesToCompare), _.pick(otherService, propertiesToCompare)) and
_.isEqual(_.omit(@environment, [ 'RESIN_DEVICE_NAME_AT_INIT' ]), _.omit(otherService.environment, [ 'RESIN_DEVICE_NAME_AT_INIT' ])) and _.isEqual(_.omit(@environment, [ 'RESIN_DEVICE_NAME_AT_INIT' ]), _.omit(otherService.environment, [ 'RESIN_DEVICE_NAME_AT_INIT' ])) and