Replace io.resin labels (and their env vars) with io.balena equivalents

But we keep backwards compatibility by normalizing existing io.resin labels
into io.balena ones, and adding both RESIN_ and BALENA_ env vars for these features.

Change-Type: minor
Signed-off-by: Pablo Carranza Velez <pablo@balena.io>
This commit is contained in:
Pablo Carranza Velez 2018-10-17 16:34:07 +02:00
parent 3b7bf9a4b7
commit ed3f5522ae
10 changed files with 110 additions and 55 deletions

View File

@ -96,7 +96,7 @@ module.exports = class ApplicationManager extends EventEmitter
# Only called for dead containers, so no need to take locks or anything
@services.remove(step.current)
updateMetadata: (step, { force = false, skipLock = false } = {}) =>
skipLock or= checkTruthy(step.current.config.labels['io.resin.legacy-container'])
skipLock or= checkTruthy(step.current.config.labels['io.balena.legacy-container'])
@_lockingIfNecessary step.current.appId, { force, skipLock: skipLock or step.options?.skipLock }, =>
@services.updateMetadata(step.current, step.target)
restart: (step, { force = false, skipLock = false } = {}) =>
@ -591,11 +591,11 @@ module.exports = class ApplicationManager extends EventEmitter
# Either this is a new service, or the current one has already been killed
return @_fetchOrStartStep(current, target, needsDownload, dependenciesMetForStart)
else
strategy = checkString(target.config.labels['io.resin.update.strategy'])
strategy = checkString(target.config.labels['io.balena.update.strategy'])
validStrategies = [ 'download-then-kill', 'kill-then-download', 'delete-then-download', 'hand-over' ]
if !_.includes(validStrategies, strategy)
strategy = 'download-then-kill'
timeout = checkInt(target.config.labels['io.resin.update.handover-timeout'])
timeout = checkInt(target.config.labels['io.balena.update.handover-timeout'])
return @_strategySteps[strategy](current, target, needsDownload, dependenciesMetForStart, dependenciesMetForKill, needsSpecialKill, timeout)
_nextStepsForAppUpdate: (currentApp, targetApp, localMode, availableImages = [], downloading = []) =>
@ -608,12 +608,12 @@ module.exports = class ApplicationManager extends EventEmitter
currentApp ?= emptyApp
if currentApp.services?.length == 1 and targetApp.services?.length == 1 and
targetApp.services[0].serviceName == currentApp.services[0].serviceName and
checkTruthy(currentApp.services[0].config.labels['io.resin.legacy-container'])
checkTruthy(currentApp.services[0].config.labels['io.balena.legacy-container'])
# This is a legacy preloaded app or container, so we didn't have things like serviceId.
# We hack a few things to avoid an unnecessary restart of the preloaded app
# (but ensuring it gets updated if it actually changed)
targetApp.services[0].config.labels['io.resin.legacy-container'] = currentApp.services[0].labels['io.resin.legacy-container']
targetApp.services[0].config.labels['io.resin.service-id'] = currentApp.services[0].labels['io.resin.service-id']
targetApp.services[0].config.labels['io.balena.legacy-container'] = currentApp.services[0].labels['io.balena.legacy-container']
targetApp.services[0].config.labels['io.balena.service-id'] = currentApp.services[0].labels['io.balena.service-id']
targetApp.services[0].serviceId = currentApp.services[0].serviceId
appId = targetApp.appId ? currentApp.appId

View File

@ -313,4 +313,9 @@ module.exports = class Images extends EventEmitter
isSameImage: @isSameImage
_getLocalModeImages: =>
@docker.listImages(filters: label: [ 'io.resin.local.image=1' ])
Promise.join(
@docker.listImages(filters: label: [ 'io.resin.local.image=1' ])
@docker.listImages(filters: label: [ 'io.balena.local.image=1' ])
(legacyImages, currentImages) ->
_.unionBy(legacyImages, currentImages, 'Id')
)

View File

@ -1,5 +1,6 @@
import * as Bluebird from 'bluebird';
import { fs } from 'mz';
import * as _ from 'lodash';
import * as constants from '../lib/constants';
import Docker = require('../lib/docker-utils');
@ -17,11 +18,7 @@ export class NetworkManager {
}
public getAll(): Bluebird<Network[]> {
return Bluebird.resolve(this.docker.listNetworks({
filters: {
label: [ 'io.resin.supervised' ],
},
}))
return this.getWithBothLabels()
.map((network: { Name: string }) => {
return this.docker.getNetwork(network.Name).inspect()
.then((net) => {
@ -90,4 +87,22 @@ export class NetworkManager {
});
}
private getWithBothLabels() {
return Bluebird.join(
this.docker.listNetworks({
filters: {
label: [ 'io.resin.supervised' ],
},
}),
this.docker.listNetworks({
filters: {
label: [ 'io.balena.supervised' ],
},
}),
(legacyNetworks, currentNetworks) => {
return _.unionBy(currentNetworks, legacyNetworks, 'Id');
},
);
}
}

View File

@ -9,6 +9,7 @@ import {
import logTypes = require('../lib/log-types');
import { checkInt } from '../lib/validation';
import { Logger } from '../logger';
import * as ComposeUtils from './utils';
import {
DockerIPAMConfig,
@ -83,7 +84,7 @@ export class Network {
},
enableIPv6: network.EnableIPv6,
internal: network.Internal,
labels: _.omit(network.Labels, [ 'io.resin.supervised' ]),
labels: _.omit(ComposeUtils.normalizeLabels(network.Labels), [ 'io.balena.supervised' ]),
options: network.Options,
};
@ -125,6 +126,7 @@ export class Network {
labels: { },
options: { },
});
net.config.labels = ComposeUtils.normalizeLabels(net.config.labels);
return net;
}
@ -177,7 +179,7 @@ export class Network {
EnableIPv6: this.config.enableIPv6,
Internal: this.config.internal,
Labels: _.merge({}, {
'io.resin.supervised': 'true',
'io.balena.supervised': 'true',
}, this.config.labels),
};
}

View File

@ -88,7 +88,7 @@ module.exports = class ServiceManager extends EventEmitter
@logger.logSystemEvent(logTypes.removeDeadServiceError, { service, error: err })
getAllByAppId: (appId) =>
@getAll("io.resin.app-id=#{appId}")
@getAll("app-id=#{appId}")
stopAllByAppId: (appId) =>
Promise.map @getAllByAppId(appId), (service) =>
@ -174,10 +174,20 @@ module.exports = class ServiceManager extends EventEmitter
.finally =>
@reportChange(containerId)
_listWithBothLabels: (labelList) =>
listWithPrefix = (prefix) =>
@docker.listContainers({ all: true, filters: label: _.map(labelList, (v) -> prefix + v) })
Promise.join(
listWithPrefix('io.resin.')
listWithPrefix('io.balena.')
(legacyContainers, currentContainers) ->
_.unionBy(legacyContainers, currentContainers, 'Id')
)
# Gets all existing containers that correspond to apps
getAll: (extraLabelFilters = []) =>
filters = label: [ 'io.resin.supervised' ].concat(extraLabelFilters)
@docker.listContainers({ all: true, filters })
filterLabels = [ 'supervised' ].concat(extraLabelFilters)
@_listWithBothLabels(filterLabels)
.mapSeries (container) =>
@docker.getContainer(container.Id).inspect()
.then(Service.fromDockerContainer)
@ -190,7 +200,7 @@ module.exports = class ServiceManager extends EventEmitter
# Returns the first container matching a service definition
get: (service) =>
@getAll("io.resin.service-id=#{service.serviceId}")
@getAll("service-id=#{service.serviceId}")
.filter((currentService) -> currentService.isEqualConfig(service))
.then (services) ->
if services.length == 0
@ -218,7 +228,7 @@ module.exports = class ServiceManager extends EventEmitter
getByDockerContainerId: (containerId) =>
@docker.getContainer(containerId).inspect()
.then (container) ->
if !container.Config.Labels['io.resin.supervised']?
if !(container.Config.Labels['io.balena.supervised']? or container.Config.Labels['io.resin.supervised']?)
return null
return Service.fromDockerContainer(container)
@ -267,7 +277,7 @@ module.exports = class ServiceManager extends EventEmitter
.then =>
@start(targetService)
.then =>
@waitToKill(currentService, targetService.config.labels['io.resin.update.handover-timeout'])
@waitToKill(currentService, targetService.config.labels['io.balena.update.handover-timeout'])
.then =>
@kill(currentService)

View File

@ -201,13 +201,13 @@ export class Service {
service.appId || 0,
service.serviceName || '',
);
config.labels = Service.extendLabels(
config.labels = ComposeUtils.normalizeLabels(Service.extendLabels(
config.labels || { },
options,
service.appId || 0,
service.serviceId || 0,
service.serviceName || '',
);
));
// Any other special case handling
if (config.networkMode === 'host' && !config.hostname) {
@ -427,7 +427,7 @@ export class Service {
image: container.Config.Image,
environment: conversions.envArrayToObject(container.Config.Env || [ ]),
privileged: container.HostConfig.Privileged || false,
labels: container.Config.Labels || { },
labels: ComposeUtils.normalizeLabels(container.Config.Labels || { }),
running: container.State.Running,
restart,
capAdd: container.HostConfig.CapAdd || [ ],
@ -471,9 +471,9 @@ export class Service {
tty: container.Config.Tty || false,
};
svc.appId = checkInt(container.Config.Labels['io.resin.app-id']) || null;
svc.serviceId = checkInt(container.Config.Labels['io.resin.service-id']) || null;
svc.serviceName = container.Config.Labels['io.resin.service-name'];
svc.appId = checkInt(svc.config.labels['io.balena.app-id']) || null;
svc.serviceId = checkInt(svc.config.labels['io.balena.service-id']) || null;
svc.serviceName = svc.config.labels['io.balena.service-name'];
const nameMatch = container.Name.match(/.*_(\d+)_(\d+)$/);
svc.imageId = nameMatch != null ? checkInt(nameMatch[1]) || null : null;
@ -788,10 +788,10 @@ export class Service {
serviceName: string,
): { [labelName: string]: string } {
let newLabels = _.defaults(labels, {
'io.resin.supervised': 'true',
'io.resin.app-id': appId.toString(),
'io.resin.service-id': serviceId.toString(),
'io.resin.service-name': serviceName,
'io.balena.supervised': 'true',
'io.balena.app-id': appId.toString(),
'io.balena.service-id': serviceId.toString(),
'io.balena.service-name': serviceName,
});
const imageLabels = _.get(imageInfo, 'Config.Labels', { });

View File

@ -300,45 +300,48 @@ export function addFeaturesFromLabels(
service: Service,
options: DeviceMetadata,
): void {
if (checkTruthy(service.config.labels['io.resin.features.dbus'])) {
const setEnvVariables = function (key: string, val: string) {
service.config.environment[`RESIN_${key}`] = val;
service.config.environment[`BALENA_${key}`] = val;
};
if (checkTruthy(service.config.labels['io.balena.features.dbus'])) {
service.config.volumes.push('/run/dbus:/host/run/dbus');
}
if (
checkTruthy(service.config.labels['io.resin.features.kernel-modules']) &&
checkTruthy(service.config.labels['io.balena.features.kernel-modules']) &&
options.hostPathExists.modules
) {
service.config.volumes.push('/lib/modules:/lib/modules');
}
if (
checkTruthy(service.config.labels['io.resin.features.firmware']) &&
checkTruthy(service.config.labels['io.balena.features.firmware']) &&
options.hostPathExists.firmware
) {
service.config.volumes.push('/lib/firmware:/lib/firmware');
}
if (checkTruthy(service.config.labels['io.resin.features.balena-socket'])) {
if (checkTruthy(service.config.labels['io.balena.features.balena-socket'])) {
service.config.volumes.push('/var/run/balena.sock:/var/run/balena.sock');
if (service.config.environment['DOCKER_HOST'] == null) {
service.config.environment['DOCKER_HOST'] = 'unix:///var/run/balena.sock';
}
}
if (checkTruthy('io.resin.features.resin-api')) {
service.config.environment['RESIN_API_KEY'] = options.deviceApiKey;
if (checkTruthy('io.balena.features.balena-api')) {
setEnvVariables('API_KEY', options.deviceApiKey);
}
if (checkTruthy(service.config.labels['io.resin.features.supervisor-api'])) {
service.config.environment['RESIN_SUPERVISOR_PORT'] = options.listenPort.toString();
service.config.environment['RESIN_SUPERVISOR_API_KEY'] = options.apiSecret;
if (checkTruthy(service.config.labels['io.balena.features.supervisor-api'])) {
setEnvVariables('SUPERVISOR_PORT', options.listenPort.toString());
setEnvVariables('SUPERVISOR_API_KEY', options.apiSecret);
if (service.config.networkMode === 'host') {
service.config.environment['RESIN_SUPERVISOR_HOST'] = '127.0.0.1';
service.config.environment['RESIN_SUPERVISOR_ADDRESS'] = `http://127.0.0.1:${options.listenPort}`;
setEnvVariables('SUPERVISOR_HOST', '127.0.0.1');
setEnvVariables('SUPERVISOR_ADDRESS', `http://127.0.0.1:${options.listenPort}`);
} else {
service.config.environment['RESIN_SUPERVISOR_HOST'] = options.supervisorApiHost;
service.config.environment['RESIN_SUPERVISOR_ADDRESS'] =
`http://${options.supervisorApiHost}:${options.listenPort}`;
setEnvVariables('SUPERVISOR_HOST', options.supervisorApiHost);
setEnvVariables('SUPERVISOR_ADDRESS', `http://${options.supervisorApiHost}:${options.listenPort}`);
service.config.networks[constants.supervisorNetworkInterface] = { };
}
} else {
@ -442,3 +445,14 @@ export function normalizeNullValues(obj: Dictionary<any>): void {
}
});
}
export function normalizeLabels(
labels: { [key: string]: string },
): { [key: string]: string } {
const legacyLabels = _.mapKeys(_.pickBy(labels, (_v, k) => _.startsWith(k, 'io.resin.')), (_v, k) => {
return k.replace(/resin/g, 'balena'); // e.g. io.resin.features.resin-api -> io.balena.features.balena-api
});
const balenaLabels = _.pickBy(labels, (_v, k) => _.startsWith(k, 'io.balena.'));
const otherLabels = _.pickBy(labels, (_v, k) => !(_.startsWith(k, 'io.balena.') || _.startsWith(k, 'io.resin.')));
return _.assign({}, otherLabels, legacyLabels, balenaLabels);
}

View File

@ -8,6 +8,7 @@ constants = require '../lib/constants'
{ NotFoundError } = require '../lib/errors'
{ defaultLegacyVolume } = require '../lib/migration'
{ safeRename } = require '../lib/fs-utils'
ComposeUtils = require './utils'
module.exports = class Volumes
constructor: ({ @docker, @logger }) ->
@ -20,19 +21,27 @@ module.exports = class Volumes
name: name
appId: appId
config: {
labels: _.omit(volume.Labels, _.keys(constants.defaultVolumeLabels))
labels: _.omit(ComposeUtils.normalizeLabels(volume.Labels), _.keys(constants.defaultVolumeLabels))
driverOpts: volume.Options
}
handle: volume
}
_listWithBothLabels: =>
Promise.join(
@docker.listVolumes(filters: label: [ 'io.resin.supervised' ])
@docker.listVolumes(filters: label: [ 'io.balena.supervised' ])
(legacyVolumesResponse, currentVolumesResponse) ->
legacyVolumes = legacyVolumesResponse.Volumes ? []
currentVolumes = currentVolumesResponse.Volumes ? []
return _.unionBy(legacyVolumes, currentVolumes, 'Name')
)
getAll: =>
@docker.listVolumes(filters: label: [ 'io.resin.supervised' ])
.then (response) =>
volumes = response.Volumes ? []
Promise.map volumes, (volume) =>
@docker.getVolume(volume.Name).inspect()
.then(@format)
@_listWithBothLabels()
.map (volume) =>
@docker.getVolume(volume.Name).inspect()
.then(@format)
getAllByAppId: (appId) =>
@getAll()

View File

@ -34,7 +34,7 @@ const constants = {
imageCleanupErrorIgnoreTimeout: 3600 * 1000,
maxDeltaDownloads: 3,
defaultVolumeLabels: {
'io.resin.supervised': 'true',
'io.balena.supervised': 'true',
},
bootBlockDevice: '/dev/mmcblk0p1',
hostConfigVarPrefix: 'RESIN_HOST_',

View File

@ -3,7 +3,7 @@ m = require 'mochainon'
{ Network } = require '../src/compose/network'
describe 'compose/network.coffee', ->
describe 'compose/network', ->
describe 'compose config -> internal config', ->
@ -70,6 +70,6 @@ describe 'compose/network.coffee', ->
EnableIPv6: false,
Internal: false,
Labels: {
'io.resin.supervised': 'true'
'io.balena.supervised': 'true'
}
})
})