mirror of
https://github.com/balena-os/balena-supervisor.git
synced 2025-01-31 16:35:23 +00:00
Issues #389 and #390: Remove /host_run/dbus and /host/var/lib/connman bind mounts for non-ResinOS-1.X devices
On ResinOS 2.X the default mounts should not include the previously deprecated host_run, and there's no connman which makes the connman mount confusing. This is a breaking change as it is not backwards-compatible on non-ResinOS instances of the supervisor. Change-Type: major Signed-off-by: Pablo Carranza Velez <pablo@resin.io>
This commit is contained in:
parent
f7c702b845
commit
b64ed9568c
@ -214,8 +214,8 @@ fetch = (app, setDeviceUpdateState = true) ->
|
||||
throw err
|
||||
|
||||
shouldMountKmod = (image) ->
|
||||
device.getOSVersion().then (osVersion) ->
|
||||
return false if not /^Resin OS 1./.test(osVersion)
|
||||
device.isResinOSv1().then (isV1) ->
|
||||
return false if not isV1
|
||||
Promise.using docker.imageRootDirMounted(image), (rootDir) ->
|
||||
utils.getOSVersion(rootDir + '/etc/os-release')
|
||||
.then (version) ->
|
||||
@ -225,112 +225,113 @@ shouldMountKmod = (image) ->
|
||||
return false
|
||||
|
||||
application.start = start = (app) ->
|
||||
volumes = utils.defaultVolumes
|
||||
binds = utils.defaultBinds(app.appId)
|
||||
alreadyStarted = false
|
||||
Promise.try ->
|
||||
# Parse the env vars before trying to access them, that's because they have to be stringified for knex..
|
||||
return [ JSON.parse(app.env), JSON.parse(app.config) ]
|
||||
.spread (env, conf) ->
|
||||
if env.PORT?
|
||||
portList = env.PORT
|
||||
.split(',')
|
||||
.map((port) -> port.trim())
|
||||
.filter(isValidPort)
|
||||
device.isResinOSv1().then (isV1) ->
|
||||
volumes = utils.defaultVolumes(isV1)
|
||||
binds = utils.defaultBinds(app.appId, isV1)
|
||||
alreadyStarted = false
|
||||
Promise.try ->
|
||||
# Parse the env vars before trying to access them, that's because they have to be stringified for knex..
|
||||
return [ JSON.parse(app.env), JSON.parse(app.config) ]
|
||||
.spread (env, conf) ->
|
||||
if env.PORT?
|
||||
portList = env.PORT
|
||||
.split(',')
|
||||
.map((port) -> port.trim())
|
||||
.filter(isValidPort)
|
||||
|
||||
if app.containerId?
|
||||
# If we have a container id then check it exists and if so use it.
|
||||
container = docker.getContainer(app.containerId)
|
||||
containerPromise = container.inspectAsync().return(container)
|
||||
else
|
||||
containerPromise = Promise.rejected()
|
||||
if app.containerId?
|
||||
# If we have a container id then check it exists and if so use it.
|
||||
container = docker.getContainer(app.containerId)
|
||||
containerPromise = container.inspectAsync().return(container)
|
||||
else
|
||||
containerPromise = Promise.rejected()
|
||||
|
||||
# If there is no existing container then create one instead.
|
||||
containerPromise.catch ->
|
||||
fetch(app)
|
||||
.then (imageInfo) ->
|
||||
logSystemEvent(logTypes.installApp, app)
|
||||
device.updateState(status: 'Installing')
|
||||
# If there is no existing container then create one instead.
|
||||
containerPromise.catch ->
|
||||
fetch(app)
|
||||
.then (imageInfo) ->
|
||||
logSystemEvent(logTypes.installApp, app)
|
||||
device.updateState(status: 'Installing')
|
||||
|
||||
ports = {}
|
||||
portBindings = {}
|
||||
if portList?
|
||||
portList.forEach (port) ->
|
||||
ports[port + '/tcp'] = {}
|
||||
portBindings[port + '/tcp'] = [ HostPort: port ]
|
||||
ports = {}
|
||||
portBindings = {}
|
||||
if portList?
|
||||
portList.forEach (port) ->
|
||||
ports[port + '/tcp'] = {}
|
||||
portBindings[port + '/tcp'] = [ HostPort: port ]
|
||||
|
||||
if imageInfo?.Config?.Cmd
|
||||
cmd = imageInfo.Config.Cmd
|
||||
else
|
||||
cmd = [ '/bin/bash', '-c', '/start' ]
|
||||
if imageInfo?.Config?.Cmd
|
||||
cmd = imageInfo.Config.Cmd
|
||||
else
|
||||
cmd = [ '/bin/bash', '-c', '/start' ]
|
||||
|
||||
restartPolicy = createRestartPolicy({ name: conf['RESIN_APP_RESTART_POLICY'], maximumRetryCount: conf['RESIN_APP_RESTART_RETRIES'] })
|
||||
shouldMountKmod(app.imageId)
|
||||
.then (shouldMount) ->
|
||||
binds.push('/bin/kmod:/bin/kmod:ro') if shouldMount
|
||||
docker.createContainerAsync(
|
||||
Image: app.imageId
|
||||
Cmd: cmd
|
||||
Tty: true
|
||||
Volumes: volumes
|
||||
Env: _.map env, (v, k) -> k + '=' + v
|
||||
ExposedPorts: ports
|
||||
HostConfig:
|
||||
Privileged: true
|
||||
NetworkMode: 'host'
|
||||
PortBindings: portBindings
|
||||
Binds: binds
|
||||
RestartPolicy: restartPolicy
|
||||
)
|
||||
.tap ->
|
||||
logSystemEvent(logTypes.installAppSuccess, app)
|
||||
restartPolicy = createRestartPolicy({ name: conf['RESIN_APP_RESTART_POLICY'], maximumRetryCount: conf['RESIN_APP_RESTART_RETRIES'] })
|
||||
shouldMountKmod(app.imageId)
|
||||
.then (shouldMount) ->
|
||||
binds.push('/bin/kmod:/bin/kmod:ro') if shouldMount
|
||||
docker.createContainerAsync(
|
||||
Image: app.imageId
|
||||
Cmd: cmd
|
||||
Tty: true
|
||||
Volumes: volumes
|
||||
Env: _.map env, (v, k) -> k + '=' + v
|
||||
ExposedPorts: ports
|
||||
HostConfig:
|
||||
Privileged: true
|
||||
NetworkMode: 'host'
|
||||
PortBindings: portBindings
|
||||
Binds: binds
|
||||
RestartPolicy: restartPolicy
|
||||
)
|
||||
.tap ->
|
||||
logSystemEvent(logTypes.installAppSuccess, app)
|
||||
.catch (err) ->
|
||||
logSystemEvent(logTypes.installAppError, app, err)
|
||||
throw err
|
||||
.tap (container) ->
|
||||
logSystemEvent(logTypes.startApp, app)
|
||||
device.updateState(status: 'Starting')
|
||||
container.startAsync()
|
||||
.catch (err) ->
|
||||
logSystemEvent(logTypes.installAppError, app, err)
|
||||
throw err
|
||||
.tap (container) ->
|
||||
logSystemEvent(logTypes.startApp, app)
|
||||
device.updateState(status: 'Starting')
|
||||
container.startAsync()
|
||||
.catch (err) ->
|
||||
statusCode = '' + err.statusCode
|
||||
# 304 means the container was already started, precisely what we want :)
|
||||
if statusCode is '304'
|
||||
alreadyStarted = true
|
||||
return
|
||||
statusCode = '' + err.statusCode
|
||||
# 304 means the container was already started, precisely what we want :)
|
||||
if statusCode is '304'
|
||||
alreadyStarted = true
|
||||
return
|
||||
|
||||
if statusCode is '500' and err.json.trim().match(/exec format error$/)
|
||||
# Provide a friendlier error message for "exec format error"
|
||||
device.getDeviceType()
|
||||
.then (deviceType) ->
|
||||
throw new Error("Application architecture incompatible with #{deviceType}: exec format error")
|
||||
else
|
||||
# rethrow the same error
|
||||
throw err
|
||||
.catch (err) ->
|
||||
# If starting the container failed, we remove it so that it doesn't litter
|
||||
container.removeAsync(v: true)
|
||||
if statusCode is '500' and err.json.trim().match(/exec format error$/)
|
||||
# Provide a friendlier error message for "exec format error"
|
||||
device.getDeviceType()
|
||||
.then (deviceType) ->
|
||||
throw new Error("Application architecture incompatible with #{deviceType}: exec format error")
|
||||
else
|
||||
# rethrow the same error
|
||||
throw err
|
||||
.catch (err) ->
|
||||
# If starting the container failed, we remove it so that it doesn't litter
|
||||
container.removeAsync(v: true)
|
||||
.then ->
|
||||
app.containerId = null
|
||||
knex('app').update(app).where(appId: app.appId)
|
||||
.finally ->
|
||||
logSystemEvent(logTypes.startAppError, app, err)
|
||||
throw err
|
||||
.then ->
|
||||
app.containerId = null
|
||||
knex('app').update(app).where(appId: app.appId)
|
||||
.finally ->
|
||||
logSystemEvent(logTypes.startAppError, app, err)
|
||||
throw err
|
||||
.then ->
|
||||
app.containerId = container.id
|
||||
device.updateState(commit: app.commit)
|
||||
logger.attach(app)
|
||||
.tap (container) ->
|
||||
# Update the app info, only if starting the container worked.
|
||||
knex('app').update(app).where(appId: app.appId)
|
||||
.then (affectedRows) ->
|
||||
knex('app').insert(app) if affectedRows == 0
|
||||
.tap ->
|
||||
if alreadyStarted
|
||||
logSystemEvent(logTypes.startAppNoop, app)
|
||||
else
|
||||
logSystemEvent(logTypes.startAppSuccess, app)
|
||||
.finally ->
|
||||
device.updateState(status: 'Idle')
|
||||
app.containerId = container.id
|
||||
device.updateState(commit: app.commit)
|
||||
logger.attach(app)
|
||||
.tap (container) ->
|
||||
# Update the app info, only if starting the container worked.
|
||||
knex('app').update(app).where(appId: app.appId)
|
||||
.then (affectedRows) ->
|
||||
knex('app').insert(app) if affectedRows == 0
|
||||
.tap ->
|
||||
if alreadyStarted
|
||||
logSystemEvent(logTypes.startAppNoop, app)
|
||||
else
|
||||
logSystemEvent(logTypes.startAppSuccess, app)
|
||||
.finally ->
|
||||
device.updateState(status: 'Idle')
|
||||
|
||||
validRestartPolicies = [ 'no', 'always', 'on-failure', 'unless-stopped' ]
|
||||
# Construct a restart policy based on its name and maximumRetryCount.
|
||||
|
@ -224,3 +224,8 @@ do ->
|
||||
|
||||
exports.getOSVersion = memoizePromise ->
|
||||
utils.getOSVersion(config.hostOsVersionPath)
|
||||
|
||||
exports.isResinOSv1 = memoizePromise ->
|
||||
exports.getOSVersion().then (osVersion) ->
|
||||
return true if /^Resin OS 1./.test(osVersion)
|
||||
return false
|
@ -265,42 +265,45 @@ do ->
|
||||
docker.modem.dialAsync = Promise.promisify(docker.modem.dial)
|
||||
createContainer = (options, internalId) ->
|
||||
Promise.using writeLockImages(), ->
|
||||
knex('image').select().where('repoTag', options.Image)
|
||||
.then (images) ->
|
||||
throw new Error('Only images created via the Supervisor can be used for creating containers.') if images.length == 0
|
||||
knex.transaction (tx) ->
|
||||
Promise.try ->
|
||||
return internalId if internalId?
|
||||
tx.insert({}, 'id').into('container')
|
||||
.then ([ id ]) ->
|
||||
return id
|
||||
.then (id) ->
|
||||
options.HostConfig ?= {}
|
||||
options.Volumes ?= {}
|
||||
_.assign(options.Volumes, utils.defaultVolumes)
|
||||
options.HostConfig.Binds = utils.defaultBinds("containers/#{id}")
|
||||
query = ''
|
||||
query = "name=#{options.Name}&" if options.Name?
|
||||
optsf =
|
||||
path: "/containers/create?#{query}"
|
||||
method: 'POST'
|
||||
options: options
|
||||
statusCodes:
|
||||
200: true
|
||||
201: true
|
||||
404: 'no such container'
|
||||
406: 'impossible to attach'
|
||||
500: 'server error'
|
||||
utils.validateKeys(options, utils.validContainerOptions)
|
||||
.then ->
|
||||
utils.validateKeys(options.HostConfig, utils.validHostConfigOptions)
|
||||
.then ->
|
||||
docker.modem.dialAsync(optsf)
|
||||
.then (data) ->
|
||||
containerId = data.Id
|
||||
tx('container').update({ containerId }).where({ id })
|
||||
.return(data)
|
||||
exports.createContainer = (req, res) ->
|
||||
Promise.join(
|
||||
knex('image').select().where('repoTag', options.Image)
|
||||
device.isResinOSv1()
|
||||
(images, isV1) ->
|
||||
throw new Error('Only images created via the Supervisor can be used for creating containers.') if images.length == 0
|
||||
knex.transaction (tx) ->
|
||||
Promise.try ->
|
||||
return internalId if internalId?
|
||||
tx.insert({}, 'id').into('container')
|
||||
.then ([ id ]) ->
|
||||
return id
|
||||
.then (id) ->
|
||||
options.HostConfig ?= {}
|
||||
options.Volumes ?= {}
|
||||
_.assign(options.Volumes, utils.defaultVolumes(isV1))
|
||||
options.HostConfig.Binds = utils.defaultBinds("containers/#{id}", isV1)
|
||||
query = ''
|
||||
query = "name=#{options.Name}&" if options.Name?
|
||||
optsf =
|
||||
path: "/containers/create?#{query}"
|
||||
method: 'POST'
|
||||
options: options
|
||||
statusCodes:
|
||||
200: true
|
||||
201: true
|
||||
404: 'no such container'
|
||||
406: 'impossible to attach'
|
||||
500: 'server error'
|
||||
utils.validateKeys(options, utils.validContainerOptions)
|
||||
.then ->
|
||||
utils.validateKeys(options.HostConfig, utils.validHostConfigOptions)
|
||||
.then ->
|
||||
docker.modem.dialAsync(optsf)
|
||||
.then (data) ->
|
||||
containerId = data.Id
|
||||
tx('container').update({ containerId }).where({ id })
|
||||
.return(data)
|
||||
)
|
||||
exports.createContainer = (req, res) ->
|
||||
createContainer(req.body)
|
||||
.then (data) ->
|
||||
res.json(data)
|
||||
|
@ -242,26 +242,32 @@ exports.getOSVersion = (path) ->
|
||||
console.log('Could not get OS Version: ', err, err.stack)
|
||||
return undefined
|
||||
|
||||
exports.defaultVolumes = {
|
||||
'/data': {}
|
||||
'/lib/modules': {}
|
||||
'/lib/firmware': {}
|
||||
'/host/var/lib/connman': {}
|
||||
'/host/run/dbus': {}
|
||||
}
|
||||
exports.defaultVolumes = (includeV1Volumes) ->
|
||||
volumes = {
|
||||
'/data': {}
|
||||
'/lib/modules': {}
|
||||
'/lib/firmware': {}
|
||||
'/host/run/dbus': {}
|
||||
}
|
||||
if includeV1Volumes
|
||||
volumes['/host/var/lib/connman'] = {}
|
||||
volumes['/host_run/dbus'] = {}
|
||||
return volumes
|
||||
|
||||
exports.getDataPath = (identifier) ->
|
||||
return config.dataPath + '/' + identifier
|
||||
|
||||
exports.defaultBinds = (dataPath) ->
|
||||
return [
|
||||
exports.defaultBinds = (dataPath, includeV1Binds) ->
|
||||
binds = [
|
||||
exports.getDataPath(dataPath) + ':/data'
|
||||
'/lib/modules:/lib/modules'
|
||||
'/lib/firmware:/lib/firmware'
|
||||
'/run/dbus:/host_run/dbus'
|
||||
'/run/dbus:/host/run/dbus'
|
||||
'/var/lib/connman:/host/var/lib/connman'
|
||||
]
|
||||
if includeV1Binds
|
||||
binds.push('/run/dbus:/host_run/dbus')
|
||||
binds.push('/var/lib/connman:/host/var/lib/connman')
|
||||
return binds
|
||||
|
||||
exports.validComposeOptions = [
|
||||
'command'
|
||||
|
Loading…
x
Reference in New Issue
Block a user