From b2b1b111b3ba6fd51a5ad51b2b31a02d16ff0f1d Mon Sep 17 00:00:00 2001 From: Felipe Lalanne Date: Tue, 21 Sep 2021 23:48:17 +0000 Subject: [PATCH] Ignore the supervisor in the target state Starting with v3 state endpoint, the supervisor may receive the configuration for the supervisor service on the target state. This commit allows the supervisor to filter out the supervisor container from the current and target state to let the update-balena-supervisor script handle the creation and update of the supervisor container. Updating and creating the supervisor container will be handled by a future commit --- src/compose/app.ts | 21 ++++++++--- src/lib/supervisor-metadata.ts | 64 ++++++++++++++++++++++++++++++++++ 2 files changed, 80 insertions(+), 5 deletions(-) create mode 100644 src/lib/supervisor-metadata.ts diff --git a/src/compose/app.ts b/src/compose/app.ts index 38b38b07..bf1ff313 100644 --- a/src/compose/app.ts +++ b/src/compose/app.ts @@ -26,6 +26,7 @@ import { checkTruthy, checkString } from '../lib/validation'; import { ServiceComposeConfig, DeviceMetadata } from './types/service'; import { ImageInspectInfo } from 'dockerode'; import { pathExistsOnHost } from '../lib/fs-utils'; +import { getSupervisorMetadata } from '../lib/supervisor-metadata'; export interface AppConstructOpts { appId: number; @@ -773,20 +774,26 @@ export class App { ...opts, }; + const supervisorMeta = await getSupervisorMetadata(); + const isService = (svc: ServiceComposeConfig) => - !svc.labels || - !svc.labels['io.balena.image.class'] || + svc.labels?.['io.balena.image.class'] == null || svc.labels['io.balena.image.class'] === 'service'; const isDataStore = (svc: ServiceComposeConfig) => - !svc.labels || - !svc.labels['io.balena.image.store'] || + svc.labels?.['io.balena.image.store'] == null || svc.labels['io.balena.image.store'] === 'data'; + const isSupervisor = (svc: ServiceComposeConfig) => + app.uuid === supervisorMeta.uuid && + (svc.serviceName === supervisorMeta.serviceName || + // keep compatibility with older supervisor releases + svc.serviceName === 'main'); + // In the db, the services are an array, but here we switch them to an // object so that they are consistent const services: Service[] = await Promise.all( - (JSON.parse(app.services) ?? []) + JSON.parse(app.services ?? []) .filter( // For the host app, `io.balena.image.*` labels indicate special way // to install the service image, so we ignore those we don't know how to @@ -795,6 +802,9 @@ export class App { (svc: ServiceComposeConfig) => !app.isHost || (isService(svc) && isDataStore(svc)), ) + // Ignore the supervisor service itself from the target state for now + // until the supervisor can update itself + .filter((svc: ServiceComposeConfig) => !isSupervisor(svc)) .map(async (svc: ServiceComposeConfig) => { // Try to fill the image id if the image is downloaded let imageInfo: ImageInspectInfo | undefined; @@ -819,6 +829,7 @@ export class App { ); }), ); + return new App( { appId: app.appId, diff --git a/src/lib/supervisor-metadata.ts b/src/lib/supervisor-metadata.ts new file mode 100644 index 00000000..80aac4ef --- /dev/null +++ b/src/lib/supervisor-metadata.ts @@ -0,0 +1,64 @@ +import * as memoizee from 'memoizee'; +import * as config from '../config'; +import { InternalInconsistencyError } from './errors'; + +export type SupervisorMetadata = { + uuid: string; + serviceName: string; +}; + +/** + * Although it might feel unsettling to hardcode these ids here. + * the main purpose of app uuids is to have environment independent + * apps. These ids will be the same in balena-cloud.com and balena-staging.com + * and they should be the same in open-balena instances for target state + * v3 to work with those instances. + * + * This will only be necessary until the supervisor becomes an actual app + * on balena + */ +const SUPERVISOR_APPS: { [arch: string]: SupervisorMetadata } = { + amd64: { + uuid: '52e35121417640b1b28a680504e4039b', + serviceName: 'balena-supervisor', + }, + aarch64: { + uuid: '900de4f3cbac4b9bbd232885a35e407b', + serviceName: 'balena-supervisor', + }, + armv7hf: { + uuid: '2e66a95795c149959c69472a8c2f92b8', + serviceName: 'balena-supervisor', + }, + i386: { + uuid: '531b357e155c480cbec0fdd33041a1f5', + serviceName: 'balena-supervisor', + }, + rpi: { + uuid: '6822565f766e413e96d9bebe2227cdcc', + serviceName: 'balena-supervisor', + }, +}; + +/** + * Get the metadata from the supervisor container + * + * This is needed for the supervisor to identify itself on the target + * state and on getStatus() in device-state.ts + * + * TODO: remove this once the supervisor knows how to update itself + */ +export const getSupervisorMetadata = memoizee( + async () => { + const deviceArch = await config.get('deviceArch'); + const meta: SupervisorMetadata = SUPERVISOR_APPS[deviceArch]; + if (meta == null) { + throw new InternalInconsistencyError( + `Unknown device architecture ${deviceArch}. Could not find matching supervisor metadata.`, + ); + } + + return meta; + }, + { promise: true }, +);