Merge pull request #814 from balena-io/fix-migration-from-legacy-apps

fix: When updating from a legacy supervisor, use updated resource ids…
This commit is contained in:
CameronDiver 2018-11-28 18:39:30 +01:00 committed by GitHub
commit e281b4d5c2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 94 additions and 10 deletions

View File

@ -13,7 +13,7 @@ validation = require './lib/validation'
systemd = require './lib/systemd'
updateLock = require './lib/update-lock'
{ singleToMulticontainerApp } = require './lib/migration'
{ ENOENT, EISDIR } = require './lib/errors'
{ ENOENT, EISDIR, NotFoundError } = require './lib/errors'
DeviceConfig = require './device-config'
ApplicationManager = require './application-manager'
@ -144,11 +144,94 @@ module.exports = class DeviceState extends EventEmitter
applyTargetHealthy = conf.offlineMode or !@applyInProgress or @applications.fetchesInProgress > 0 or cycleTimeWithinInterval
return applyTargetHealthy
normaliseLegacy: =>
migrateLegacyApps: (balenaApi) =>
console.log('Migrating ids for legacy app...')
@db.models('app').select()
.then (apps) =>
if apps.length == 0
console.log('No app to migrate')
return
app = apps[0]
services = JSON.parse(app.services)
# Check there's a main service, with legacy-container set
if services.length != 1
console.log("App doesn't have a single service, ignoring")
return
service = services[0]
if !service.labels['io.resin.legacy-container'] and !service.labels['io.balena.legacy-container']
console.log('Service is not marked as legacy, ignoring')
return
console.log("Getting release #{app.commit} for app #{app.appId} from API")
balenaApi.get(
resource: 'release'
options:
filter:
belongs_to__application: app.appId
commit: app.commit
status: 'success'
expand:
contains__image: [ 'image' ]
)
.then (releasesFromAPI) =>
if releasesFromAPI.length == 0
throw new Error('No compatible releases found in API')
release = releasesFromAPI[0]
releaseId = release.id
image = release.contains__image[0].image[0]
imageId = image.id
serviceId = image.is_a_build_of__service.__id
imageUrl = image.is_stored_at__image_location
if image.content_hash
imageUrl += "@#{image.content_hash}"
console.log("Found a release with releaseId #{releaseId}, imageId #{imageId}, serviceId #{serviceId}")
console.log("Image location is #{imageUrl}")
Promise.join(
@applications.docker.getImage(service.image).inspect().catchReturn(NotFoundError, null)
@db.models('image').where(name: service.image).select()
(imageFromDocker, imagesFromDB) =>
@db.transaction (trx) ->
Promise.try ->
if imagesFromDB.length > 0
console.log('Deleting existing image entry in db')
trx('image').where(name: service.image).del()
else
console.log('No image in db to delete')
.then ->
if imageFromDocker?
console.log('Inserting fixed image entry in db')
newImage = {
name: imageUrl,
appId: app.appId,
serviceId: serviceId,
serviceName: service.serviceName,
imageId: imageId,
releaseId: releaseId,
dependent: 0
dockerImageId: imageFromDocker.Id
}
trx('image').insert(newImage)
else
console.log('Image is not downloaded, so not saving it to db')
.then ->
service.image = imageUrl
service.serviceID = serviceId
service.imageId = imageId
service.releaseId = releaseId
delete service.labels['io.resin.legacy-container']
delete service.labels['io.balena.legacy-container']
app.services = JSON.stringify([ service ])
app.releaseId = releaseId
console.log('Updating app entry in db')
trx('app').update(app).where({ appId: app.appId })
)
normaliseLegacy: (balenaApi) =>
# When legacy apps are present, we kill their containers and migrate their /data to a named volume
# (everything else is handled by the knex migration)
console.log('Killing legacy containers')
@applications.services.killAllLegacy()
# We also need to get the releaseId, serviceId, imageId and updated image URL
@migrateLegacyApps(balenaApi)
.then =>
console.log('Killing legacy containers')
@applications.services.killAllLegacy()
.then =>
console.log('Migrating legacy app volumes')
@applications.getTargetApps()
@ -171,11 +254,7 @@ module.exports = class DeviceState extends EventEmitter
'targetStateSet', 'offlineMode'
])
.then (conf) =>
Promise.try =>
if validation.checkTruthy(conf.legacyAppsPresent)
@normaliseLegacy()
.then =>
@applications.init()
@applications.init()
.then =>
if !validation.checkTruthy(conf.initialConfigSaved)
@saveInitialConfig()

View File

@ -23,6 +23,7 @@ startupConfigFields = [
'mixpanelHost'
'loggingEnabled'
'localMode'
'legacyAppsPresent'
]
module.exports = class Supervisor extends EventEmitter
@ -60,6 +61,10 @@ module.exports = class Supervisor extends EventEmitter
enableLogs: checkTruthy(conf.loggingEnabled),
localMode: checkTruthy(conf.localMode)
})
.then =>
if checkTruthy(conf.legacyAppsPresent)
console.log('Legacy app detected, running migration')
@deviceState.normaliseLegacy(@apiBinder.balenaApi)
.then =>
@deviceState.init()
.then =>