Take update locks for host-config changes

This adds update-lock support to hostname changes via the host-config
endpoint, in addition to proxy changes as changing the hostname may
cause an engine restart from the OS.

Change-type: minor
This commit is contained in:
Felipe Lalanne 2024-11-27 15:07:23 -03:00
parent 21becded50
commit a2d4b31b23
No known key found for this signature in database
GPG Key ID: 03E696BFD472B26A
2 changed files with 45 additions and 34 deletions

View File

@ -92,40 +92,47 @@ export async function patch(
conf: HostConfiguration | LegacyHostConfiguration,
force: boolean = false,
): Promise<void> {
const apps = await applicationManager.getCurrentApps();
const appIds = Object.keys(apps).map((strId) => parseInt(strId, 10));
const ops: Array<() => Promise<void>> = [];
if (conf.network.hostname != null) {
await setHostname(conf.network.hostname);
const hostname = conf.network.hostname;
ops.push(async () => await setHostname(hostname));
}
if (conf.network.proxy != null) {
const { noProxy, ...targetConf } = conf.network.proxy;
ops.push(async () => {
const proxyConf = await readProxy();
let currentConf: ProxyConfig | undefined = undefined;
if (proxyConf) {
delete proxyConf.noProxy;
currentConf = proxyConf;
}
// Merge current & target redsocks.conf
const patchedConf = patchProxy(
{
redsocks: currentConf,
},
{
redsocks: targetConf,
},
);
await setProxy(patchedConf, noProxy);
});
}
if (ops.length > 0) {
// It's possible for appIds to be an empty array, but patch shouldn't fail
// as it's not dependent on there being any running user applications.
const apps = await applicationManager.getCurrentApps();
const appIds = Object.keys(apps).map((strId) => parseInt(strId, 10));
const lockOverride = await config.get('lockOverride');
return updateLock.withLock(
await updateLock.withLock(
appIds,
async () => {
const proxyConf = await readProxy();
let currentConf: ProxyConfig | undefined = undefined;
if (proxyConf) {
delete proxyConf.noProxy;
currentConf = proxyConf;
}
// Merge current & target redsocks.conf
const patchedConf = patchProxy(
{
redsocks: currentConf,
},
{
redsocks: targetConf,
},
);
await setProxy(patchedConf, noProxy);
() => Promise.all(ops.map((fn) => fn())),
{
force: force || lockOverride,
},
{ force: force || lockOverride },
);
}
}

View File

@ -112,7 +112,7 @@ describe('host-config', () => {
}
});
it('patches if update locks are present but force is specified', async () => {
it('patches proxy if update locks are present but force is specified', async () => {
(applicationManager.getCurrentApps as SinonStub).resolves(currentApps);
try {
@ -129,17 +129,21 @@ describe('host-config', () => {
expect(await config.get('hostname')).to.equal(defaultConf.hostname);
});
it('patches hostname regardless of update locks', async () => {
it('prevents hostname patch if there are update locks', async () => {
(applicationManager.getCurrentApps as SinonStub).resolves(currentApps);
try {
await 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');
} catch (e: unknown) {
expect.fail(`Expected hostConfig.patch to not throw, but got ${e}`);
}
await expect(patch({ network: { hostname: 'test' } }, true)).to.not.be
.rejected;
// /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 hostname if update locks are present but force is specified', async () => {
(applicationManager.getCurrentApps as SinonStub).resolves(currentApps);
await expect(patch({ network: { hostname: 'test' } })).to.be.rejected;
expect(await config.get('hostname')).to.equal(defaultConf.hostname);
});
it('patches hostname without modifying other fields', async () => {