Use compose long syntax for /lib/firmware and /lib/modules

As /lib/firmware and /lib/modules are always expected to be in the host
filesystem, using long syntax ensures that the Supervisor container
cannot start on an invalid host. (Long syntax volumes error when the host
path does not exist, whereas short syntax volumes create the host path.)

Change-type: major
Closes: #1965
Signed-off-by: Christina Wang <christina@balena.io>
This commit is contained in:
Christina Wang 2022-06-14 10:54:11 -07:00
parent 51c5456af9
commit 7531685b4f
9 changed files with 25 additions and 88 deletions

View File

@ -14,3 +14,4 @@ services:
io.balena.features.balena-api: '1' io.balena.features.balena-api: '1'
io.balena.features.dbus: '1' io.balena.features.dbus: '1'
io.balena.features.balena-socket: '1' io.balena.features.balena-socket: '1'
io.balena.features.kernel-modules: '1'

View File

@ -42,6 +42,9 @@ if [ -n "${BALENA_ROOT_CA}" ]; then
fi fi
fi fi
# TODO: Remove this symlink and modprobe call when the Supervisor
# can update itself. At that point, we should run modprobe through
# the bind mount as we have the kernel-modules docker-compose label.
# Mount the host kernel module path onto the expected location # Mount the host kernel module path onto the expected location
# We need to do this as busybox doesn't support using a custom location # We need to do this as busybox doesn't support using a custom location
if [ ! -d /lib/modules ]; then if [ ! -d /lib/modules ]; then

View File

@ -24,7 +24,6 @@ import * as config from '../config';
import { checkTruthy, checkString } from '../lib/validation'; import { checkTruthy, checkString } from '../lib/validation';
import { ServiceComposeConfig, DeviceMetadata } from './types/service'; import { ServiceComposeConfig, DeviceMetadata } from './types/service';
import { ImageInspectInfo } from 'dockerode'; import { ImageInspectInfo } from 'dockerode';
import { pathExistsOnHost } from '../lib/fs-utils';
import { getSupervisorMetadata } from '../lib/supervisor-metadata'; import { getSupervisorMetadata } from '../lib/supervisor-metadata';
export interface AppConstructOpts { export interface AppConstructOpts {
@ -738,20 +737,11 @@ export class App {
}, },
); );
const [ const [opts, supervisorApiHost, hostnameOnHost] = await Promise.all([
opts,
supervisorApiHost,
hostPathExists,
hostnameOnHost,
] = await Promise.all([
config.get('extendedEnvOptions'), config.get('extendedEnvOptions'),
dockerUtils dockerUtils
.getNetworkGateway(constants.supervisorNetworkInterface) .getNetworkGateway(constants.supervisorNetworkInterface)
.catch(() => '127.0.0.1'), .catch(() => '127.0.0.1'),
(async () => ({
firmware: await pathExistsOnHost('/lib/firmware'),
modules: await pathExistsOnHost('/lib/modules'),
}))(),
(async () => (async () =>
_.trim( _.trim(
await fs.readFile( await fs.readFile(
@ -764,7 +754,6 @@ export class App {
const svcOpts = { const svcOpts = {
appName: app.name, appName: app.name,
supervisorApiHost, supervisorApiHost,
hostPathExists,
hostnameOnHost, hostnameOnHost,
...opts, ...opts,
}; };

View File

@ -328,10 +328,6 @@ export interface DeviceMetadata {
supervisorApiHost: string; supervisorApiHost: string;
osVersion: string; osVersion: string;
hostnameOnHost: string; hostnameOnHost: string;
hostPathExists: {
modules: boolean;
firmware: boolean;
};
} }
export interface DockerDevice { export interface DockerDevice {

View File

@ -338,13 +338,17 @@ export async function addFeaturesFromLabels(
'io.balena.features.dbus': () => 'io.balena.features.dbus': () =>
service.config.volumes.push('/run/dbus:/host/run/dbus'), service.config.volumes.push('/run/dbus:/host/run/dbus'),
'io.balena.features.kernel-modules': () => 'io.balena.features.kernel-modules': () =>
options.hostPathExists.modules service.config.volumes.push({
? service.config.volumes.push('/lib/modules:/lib/modules') type: 'bind',
: null, source: '/lib/modules',
target: '/lib/modules',
} as LongBind),
'io.balena.features.firmware': () => 'io.balena.features.firmware': () =>
options.hostPathExists.firmware service.config.volumes.push({
? service.config.volumes.push('/lib/firmware:/lib/firmware') type: 'bind',
: null, source: '/lib/firmware',
target: '/lib/firmware',
} as LongBind),
'io.balena.features.balena-socket': () => { 'io.balena.features.balena-socket': () => {
service.config.volumes.push({ service.config.volumes.push({
type: 'bind', type: 'bind',

View File

@ -86,10 +86,6 @@
}, },
"appName": "supervisortest", "appName": "supervisortest",
"supervisorApiHost": "172.17.0.1", "supervisorApiHost": "172.17.0.1",
"hostPathExists": {
"firmware": true,
"modules": true
},
"hostnameOnHost": "7dadabd", "hostnameOnHost": "7dadabd",
"uuid": "a7feb967fac7f559ccf2a006a36bcf5d", "uuid": "a7feb967fac7f559ccf2a006a36bcf5d",
"listenPort": "48484", "listenPort": "48484",

View File

@ -112,10 +112,6 @@
}, },
"appName": "supervisortest", "appName": "supervisortest",
"supervisorApiHost": "172.17.0.1", "supervisorApiHost": "172.17.0.1",
"hostPathExists": {
"firmware": true,
"modules": true
},
"hostnameOnHost": "7dadabd", "hostnameOnHost": "7dadabd",
"uuid": "7dadabd4edec3067948d5952c2f2f26f", "uuid": "7dadabd4edec3067948d5952c2f2f26f",
"listenPort": "48484", "listenPort": "48484",

View File

@ -112,10 +112,6 @@
}, },
"appName": "supervisortest", "appName": "supervisortest",
"supervisorApiHost": "172.17.0.1", "supervisorApiHost": "172.17.0.1",
"hostPathExists": {
"firmware": true,
"modules": true
},
"hostnameOnHost": "7dadabd", "hostnameOnHost": "7dadabd",
"uuid": "7dadabd4edec3067948d5952c2f2f26f", "uuid": "7dadabd4edec3067948d5952c2f2f26f",
"listenPort": "48484", "listenPort": "48484",

View File

@ -682,7 +682,7 @@ describe('compose/service', () => {
expect(service.config.volumes).to.include.members(['/proc:/proc']); expect(service.config.volumes).to.include.members(['/proc:/proc']);
}); });
it('should add `/lib/modules` to the container bind mounts when io.balena.features.kernel-modules is used (if the host path exists)', async () => { it('should add `/lib/modules` to the container bind mounts with long syntax when io.balena.features.kernel-modules is used', async () => {
const service = await Service.fromComposeObject( const service = await Service.fromComposeObject(
{ {
appId: 123456, appId: 123456,
@ -694,41 +694,19 @@ describe('compose/service', () => {
}, },
{ {
appName: 'test', appName: 'test',
hostPathExists: {
modules: true,
},
} as any, } as any,
); );
expect(service.config.volumes).to.include.members([ expect(service.config.volumes).to.deep.include.members([
'/lib/modules:/lib/modules', {
type: 'bind',
source: '/lib/modules',
target: '/lib/modules',
},
]); ]);
}); });
it('should NOT add `/lib/modules` to the container bind mounts when io.balena.features.kernel-modules is used (if the host path does NOT exist)', async () => { it('should add `/lib/firmware` to the container bind mounts with long syntax when io.balena.features.firmware is used', async () => {
const service = await Service.fromComposeObject(
{
appId: 123456,
serviceId: 123456,
serviceName: 'foobar',
labels: {
'io.balena.features.kernel-modules': '1',
},
},
{
appName: 'test',
hostPathExists: {
modules: false,
},
} as any,
);
expect(service.config.volumes).to.not.include.members([
'/lib/modules:/lib/modules',
]);
});
it('should add `/lib/firmware` to the container bind mounts when io.balena.features.firmware is used (if the host path exists)', async () => {
const service = await Service.fromComposeObject( const service = await Service.fromComposeObject(
{ {
appId: 123456, appId: 123456,
@ -740,37 +718,15 @@ describe('compose/service', () => {
}, },
{ {
appName: 'test', appName: 'test',
hostPathExists: {
firmware: true,
},
} as any, } as any,
); );
expect(service.config.volumes).to.include.members([ expect(service.config.volumes).to.deep.include.members([
'/lib/firmware:/lib/firmware',
]);
});
it('should NOT add `/lib/firmware` to the container bind mounts when io.balena.features.firmware is used (if the host path does NOT exist)', async () => {
const service = await Service.fromComposeObject(
{ {
appId: 123456, type: 'bind',
serviceId: 123456, source: '/lib/firmware',
serviceName: 'foobar', target: '/lib/firmware',
labels: {
'io.balena.features.firmware': '1',
}, },
},
{
appName: 'test',
hostPathExists: {
firmware: false,
},
} as any,
);
expect(service.config.volumes).to.not.include.members([
'/lib/firmware:/lib/firmware',
]); ]);
}); });
}); });