mirror of
https://github.com/balena-os/balena-supervisor.git
synced 2024-12-27 09:12:31 +00:00
7b68ee4c4f
The OS since v2.82.6 will monitor changes to config.json and restart the relevant services to apply the changes. There is no need to trigger restart of the services via the supervisor. Users on older OS versions will need to update their OS or restart the services manually as OS loses support after 2y. Change-type: patch Closes: #2160
215 lines
6.6 KiB
TypeScript
215 lines
6.6 KiB
TypeScript
import { expect } from 'chai';
|
|
import { testfs, TestFs } from 'mocha-pod';
|
|
import * as path from 'path';
|
|
import { SinonStub, stub } from 'sinon';
|
|
import * as fs from 'fs/promises';
|
|
|
|
import * as hostConfig from '~/src/host-config';
|
|
import * as config from '~/src/config';
|
|
import * as applicationManager from '~/src/compose/application-manager';
|
|
import { InstancedAppState } from '~/src/types/state';
|
|
import * as updateLock from '~/lib/update-lock';
|
|
import { UpdatesLockedError } from '~/lib/errors';
|
|
import * as dbus from '~/lib/dbus';
|
|
import { pathOnBoot, pathOnRoot } from '~/lib/host-utils';
|
|
import {
|
|
createApps,
|
|
createService,
|
|
DEFAULT_NETWORK,
|
|
} from '~/test-lib/state-helper';
|
|
|
|
describe('host-config', () => {
|
|
let tFs: TestFs.Disabled;
|
|
let currentApps: InstancedAppState;
|
|
const APP_ID = 1;
|
|
const SERVICE_NAME = 'one';
|
|
const proxyBase = pathOnBoot('system-proxy');
|
|
const redsocksConf = path.join(proxyBase, 'redsocks.conf');
|
|
const noProxy = path.join(proxyBase, 'no_proxy');
|
|
const hostname = pathOnRoot('/etc/hostname');
|
|
const appLockDir = pathOnRoot(updateLock.lockPath(APP_ID));
|
|
|
|
before(async () => {
|
|
await config.initialized();
|
|
|
|
// Create current state
|
|
currentApps = createApps(
|
|
{
|
|
services: [
|
|
await createService({
|
|
running: true,
|
|
appId: APP_ID,
|
|
serviceName: SERVICE_NAME,
|
|
}),
|
|
],
|
|
networks: [DEFAULT_NETWORK],
|
|
},
|
|
false,
|
|
);
|
|
|
|
// Set up test fs
|
|
tFs = testfs({
|
|
[redsocksConf]: testfs.from(
|
|
'test/data/mnt/boot/system-proxy/redsocks.conf',
|
|
),
|
|
[noProxy]: testfs.from('test/data/mnt/boot/system-proxy/no_proxy'),
|
|
[hostname]: 'deadbeef',
|
|
// Create a lock. This won't prevent host config patch unless
|
|
// there are current apps present, in which case an updates locked
|
|
// error will be thrown.
|
|
[appLockDir]: {
|
|
[SERVICE_NAME]: {
|
|
'updates.lock': '',
|
|
},
|
|
},
|
|
});
|
|
});
|
|
|
|
beforeEach(async () => {
|
|
await tFs.enable();
|
|
// Stub external dependencies
|
|
stub(dbus, 'servicePartOf').resolves('');
|
|
stub(dbus, 'restartService').resolves();
|
|
});
|
|
|
|
afterEach(async () => {
|
|
await tFs.restore();
|
|
(dbus.servicePartOf as SinonStub).restore();
|
|
(dbus.restartService as SinonStub).restore();
|
|
});
|
|
|
|
it('reads proxy configs and hostname', async () => {
|
|
const { network } = await hostConfig.get();
|
|
expect(network).to.have.property('hostname', 'deadbeef');
|
|
expect(network).to.have.property('proxy');
|
|
expect(network.proxy).to.have.property('ip', 'example.org');
|
|
expect(network.proxy).to.have.property('port', 1080);
|
|
expect(network.proxy).to.have.property('type', 'socks5');
|
|
expect(network.proxy).to.have.property('login', 'foo');
|
|
expect(network.proxy).to.have.property('password', 'bar');
|
|
expect(network.proxy).to.have.deep.property('noProxy', [
|
|
'152.10.30.4',
|
|
'253.1.1.0/16',
|
|
]);
|
|
});
|
|
|
|
it('prevents patch if update locks are present', async () => {
|
|
stub(applicationManager, 'getCurrentApps').resolves(currentApps);
|
|
|
|
try {
|
|
await hostConfig.patch({ network: { hostname: 'test' } });
|
|
expect.fail('Expected hostConfig.patch to throw UpdatesLockedError');
|
|
} catch (e: unknown) {
|
|
expect(e).to.be.instanceOf(UpdatesLockedError);
|
|
}
|
|
|
|
(applicationManager.getCurrentApps as SinonStub).restore();
|
|
});
|
|
|
|
it('patches if update locks are present but force is specified', async () => {
|
|
stub(applicationManager, 'getCurrentApps').resolves(currentApps);
|
|
|
|
try {
|
|
await hostConfig.patch({ network: { hostname: 'deadreef' } }, true);
|
|
expect(await config.get('hostname')).to.equal('deadreef');
|
|
} catch (e: unknown) {
|
|
expect.fail(`Expected hostConfig.patch to not throw, but got ${e}`);
|
|
}
|
|
|
|
(applicationManager.getCurrentApps as SinonStub).restore();
|
|
});
|
|
|
|
it('patches hostname', async () => {
|
|
await hostConfig.patch({ network: { hostname: 'test' } });
|
|
// /etc/hostname isn't changed until the balena-hostname service
|
|
// is restarted by the OS.
|
|
expect(await config.get('hostname')).to.equal('test');
|
|
});
|
|
|
|
it('patches proxy', async () => {
|
|
await hostConfig.patch({
|
|
network: {
|
|
proxy: {
|
|
ip: 'example2.org',
|
|
port: 1090,
|
|
type: 'http-relay',
|
|
login: 'bar',
|
|
password: 'foo',
|
|
noProxy: ['balena.io', '222.22.2.2'],
|
|
},
|
|
},
|
|
});
|
|
const { network } = await hostConfig.get();
|
|
expect(network).to.have.property('proxy');
|
|
expect(network.proxy).to.have.property('ip', 'example2.org');
|
|
expect(network.proxy).to.have.property('port', 1090);
|
|
expect(network.proxy).to.have.property('type', 'http-relay');
|
|
expect(network.proxy).to.have.property('login', 'bar');
|
|
expect(network.proxy).to.have.property('password', 'foo');
|
|
expect(network.proxy).to.have.deep.property('noProxy', [
|
|
'balena.io',
|
|
'222.22.2.2',
|
|
]);
|
|
});
|
|
|
|
it('skips restarting proxy services when part of redsocks-conf.target', async () => {
|
|
(dbus.servicePartOf as SinonStub).resolves('redsocks-conf.target');
|
|
await hostConfig.patch({
|
|
network: {
|
|
proxy: {
|
|
ip: 'example2.org',
|
|
port: 1090,
|
|
type: 'http-relay',
|
|
login: 'bar',
|
|
password: 'foo',
|
|
noProxy: ['balena.io', '222.22.2.2'],
|
|
},
|
|
},
|
|
});
|
|
expect(dbus.restartService as SinonStub).to.not.have.been.called;
|
|
const { network } = await hostConfig.get();
|
|
expect(network).to.have.property('proxy');
|
|
expect(network.proxy).to.have.property('ip', 'example2.org');
|
|
expect(network.proxy).to.have.property('port', 1090);
|
|
expect(network.proxy).to.have.property('type', 'http-relay');
|
|
expect(network.proxy).to.have.property('login', 'bar');
|
|
expect(network.proxy).to.have.property('password', 'foo');
|
|
expect(network.proxy).to.have.deep.property('noProxy', [
|
|
'balena.io',
|
|
'222.22.2.2',
|
|
]);
|
|
});
|
|
|
|
it('patches redsocks.conf to be empty if prompted', async () => {
|
|
await hostConfig.patch({ network: { proxy: {} } });
|
|
const { network } = await hostConfig.get();
|
|
expect(network).to.have.property('proxy', undefined);
|
|
expect(await fs.readdir(proxyBase)).to.not.have.members([
|
|
'redsocks.conf',
|
|
'no_proxy',
|
|
]);
|
|
});
|
|
|
|
it('patches no_proxy to be empty if prompted', async () => {
|
|
await hostConfig.patch({
|
|
network: {
|
|
proxy: {
|
|
noProxy: [],
|
|
},
|
|
},
|
|
});
|
|
const { network } = await hostConfig.get();
|
|
expect(network).to.have.property('proxy');
|
|
expect(network.proxy).to.not.have.property('noProxy');
|
|
expect(await fs.readdir(proxyBase)).to.not.have.members(['no_proxy']);
|
|
});
|
|
|
|
it("doesn't update hostname or proxy when both are empty", async () => {
|
|
const { network } = await hostConfig.get();
|
|
await hostConfig.patch({ network: {} });
|
|
const { network: newNetwork } = await hostConfig.get();
|
|
expect(network.hostname).to.equal(newNetwork.hostname);
|
|
expect(network.proxy).to.deep.equal(newNetwork.proxy);
|
|
});
|
|
});
|