mirror of
https://github.com/balena-os/balena-supervisor.git
synced 2025-01-29 15:44:13 +00:00
Add HostConfig.patchProxy method
Signed-off-by: Christina Ying Wang <christina@balena.io>
This commit is contained in:
parent
9c6681bb23
commit
f17f7efe60
@ -1,5 +1,6 @@
|
||||
import { promises as fs } from 'fs';
|
||||
|
||||
import type { RedsocksConfig, ProxyConfig } from './types';
|
||||
import * as config from '../config';
|
||||
import { pathOnRoot } from '../lib/host-utils';
|
||||
|
||||
@ -25,3 +26,29 @@ export async function setHostname(val: string) {
|
||||
// so the change gets reflected on containers
|
||||
await config.set({ hostname });
|
||||
}
|
||||
|
||||
export function patchProxy(
|
||||
currentConf: RedsocksConfig,
|
||||
inputConf: Partial<{
|
||||
redsocks: Partial<ProxyConfig>;
|
||||
}>,
|
||||
): RedsocksConfig {
|
||||
const patchedConf: RedsocksConfig = {};
|
||||
|
||||
// If input is empty, redsocks config should be removed
|
||||
if (!inputConf || Object.keys(inputConf).length === 0) {
|
||||
return patchedConf;
|
||||
}
|
||||
|
||||
if (inputConf.redsocks && Object.keys(inputConf.redsocks).length > 0) {
|
||||
// This method assumes that currentConf is a full ProxyConfig
|
||||
// for backwards compatibility, so patchedConf.redsocks should
|
||||
// also be a full ProxyConfig.
|
||||
const patched = {
|
||||
...currentConf.redsocks,
|
||||
...inputConf.redsocks,
|
||||
} as Record<string, any>;
|
||||
patchedConf.redsocks = patched as ProxyConfig;
|
||||
}
|
||||
return patchedConf;
|
||||
}
|
||||
|
@ -3,8 +3,8 @@ import path from 'path';
|
||||
import { isRight } from 'fp-ts/lib/Either';
|
||||
import Reporter from 'io-ts-reporters';
|
||||
|
||||
import type { RedsocksConfig } from './types';
|
||||
import { ProxyConfig } from './types';
|
||||
import type { RedsocksConfig } from './types';
|
||||
import { pathOnBoot, readFromBoot } from '../lib/host-utils';
|
||||
import { unlinkAll } from '../lib/fs-utils';
|
||||
import { isENOENT } from '../lib/errors';
|
||||
|
@ -20,6 +20,10 @@ export const ProxyConfig = t.intersection([
|
||||
]);
|
||||
export type ProxyConfig = t.TypeOf<typeof ProxyConfig>;
|
||||
|
||||
/**
|
||||
* The internal object representation of redsocks.conf, obtained
|
||||
* from RedsocksConf.parse
|
||||
*/
|
||||
export const RedsocksConfig = t.partial({
|
||||
redsocks: ProxyConfig,
|
||||
});
|
||||
|
@ -3,12 +3,14 @@ import { stripIndent } from 'common-tags';
|
||||
import type { SinonStub } from 'sinon';
|
||||
|
||||
import * as hostConfig from '~/src/host-config/index';
|
||||
import type { RedsocksConfig, ProxyConfig } from '~/src/host-config/types';
|
||||
import { RedsocksConf } from '~/src/host-config/index';
|
||||
import log from '~/lib/supervisor-console';
|
||||
|
||||
describe('RedsocksConf', () => {
|
||||
describe('stringify', () => {
|
||||
it('stringifies RedsocksConfig into config string', () => {
|
||||
const conf: hostConfig.RedsocksConfig = {
|
||||
const conf: RedsocksConfig = {
|
||||
redsocks: {
|
||||
type: 'socks5',
|
||||
ip: 'example.org',
|
||||
@ -17,7 +19,7 @@ describe('RedsocksConf', () => {
|
||||
password: '"bar"',
|
||||
},
|
||||
};
|
||||
const confStr = hostConfig.RedsocksConf.stringify(conf);
|
||||
const confStr = RedsocksConf.stringify(conf);
|
||||
expect(confStr).to.equal(
|
||||
stripIndent`
|
||||
base {
|
||||
@ -42,7 +44,7 @@ describe('RedsocksConf', () => {
|
||||
});
|
||||
|
||||
it('adds double quotes to auth fields if not exists', () => {
|
||||
const conf: hostConfig.RedsocksConfig = {
|
||||
const conf: RedsocksConfig = {
|
||||
redsocks: {
|
||||
type: 'socks5',
|
||||
ip: 'example.org',
|
||||
@ -51,7 +53,7 @@ describe('RedsocksConf', () => {
|
||||
password: 'bar',
|
||||
},
|
||||
};
|
||||
const confStr = hostConfig.RedsocksConf.stringify(conf);
|
||||
const confStr = RedsocksConf.stringify(conf);
|
||||
expect(confStr).to.equal(
|
||||
stripIndent`
|
||||
base {
|
||||
@ -84,8 +86,8 @@ describe('RedsocksConf', () => {
|
||||
login: 'foo',
|
||||
password: 'bar',
|
||||
},
|
||||
} as unknown as hostConfig.RedsocksConfig;
|
||||
const confStr = hostConfig.RedsocksConf.stringify(conf);
|
||||
} as unknown as RedsocksConfig;
|
||||
const confStr = RedsocksConf.stringify(conf);
|
||||
expect(confStr).to.equal(
|
||||
stripIndent`
|
||||
base {
|
||||
@ -110,16 +112,16 @@ describe('RedsocksConf', () => {
|
||||
});
|
||||
|
||||
it('stringifies to empty string when provided empty RedsocksConfig', () => {
|
||||
const conf: hostConfig.RedsocksConfig = {};
|
||||
const confStr = hostConfig.RedsocksConf.stringify(conf);
|
||||
const conf: RedsocksConfig = {};
|
||||
const confStr = RedsocksConf.stringify(conf);
|
||||
expect(confStr).to.equal('');
|
||||
});
|
||||
|
||||
it('stringifies to empty string when provided empty redsocks block', () => {
|
||||
const conf: hostConfig.RedsocksConfig = {
|
||||
redsocks: {} as hostConfig.ProxyConfig,
|
||||
const conf: RedsocksConfig = {
|
||||
redsocks: {} as ProxyConfig,
|
||||
};
|
||||
const confStr = hostConfig.RedsocksConf.stringify(conf);
|
||||
const confStr = RedsocksConf.stringify(conf);
|
||||
expect(confStr).to.equal('');
|
||||
});
|
||||
});
|
||||
@ -150,7 +152,7 @@ describe('RedsocksConf', () => {
|
||||
}
|
||||
|
||||
`;
|
||||
const conf = hostConfig.RedsocksConf.parse(redsocksConfStr);
|
||||
const conf = RedsocksConf.parse(redsocksConfStr);
|
||||
expect(conf).to.deep.equal({
|
||||
redsocks: {
|
||||
type: 'socks5',
|
||||
@ -184,7 +186,7 @@ describe('RedsocksConf', () => {
|
||||
redirector = iptables;
|
||||
}
|
||||
`;
|
||||
const conf = hostConfig.RedsocksConf.parse(redsocksConfStr);
|
||||
const conf = RedsocksConf.parse(redsocksConfStr);
|
||||
expect(conf).to.deep.equal({
|
||||
redsocks: {
|
||||
type: 'http-connect',
|
||||
@ -213,7 +215,7 @@ describe('RedsocksConf', () => {
|
||||
login = "us}{er";
|
||||
password = "p{}a}}s{{s";
|
||||
}`; // No newlines
|
||||
const conf2 = hostConfig.RedsocksConf.parse(redsocksConfStr2);
|
||||
const conf2 = RedsocksConf.parse(redsocksConfStr2);
|
||||
expect(conf2).to.deep.equal({
|
||||
redsocks: {
|
||||
type: 'http-connect',
|
||||
@ -244,7 +246,7 @@ describe('RedsocksConf', () => {
|
||||
password = pass;
|
||||
}
|
||||
`;
|
||||
const conf = hostConfig.RedsocksConf.parse(confStr);
|
||||
const conf = RedsocksConf.parse(confStr);
|
||||
expect(conf).to.deep.equal(expected);
|
||||
|
||||
const confStr2 = stripIndent`
|
||||
@ -256,7 +258,7 @@ describe('RedsocksConf', () => {
|
||||
password = pass";
|
||||
}
|
||||
`;
|
||||
const conf2 = hostConfig.RedsocksConf.parse(confStr2);
|
||||
const conf2 = RedsocksConf.parse(confStr2);
|
||||
expect(conf2).to.deep.equal(expected);
|
||||
|
||||
const confStr3 = stripIndent`
|
||||
@ -268,7 +270,7 @@ describe('RedsocksConf', () => {
|
||||
password = "pass";
|
||||
}
|
||||
`;
|
||||
const conf3 = hostConfig.RedsocksConf.parse(confStr3);
|
||||
const conf3 = RedsocksConf.parse(confStr3);
|
||||
expect(conf3).to.deep.equal(expected);
|
||||
});
|
||||
|
||||
@ -286,7 +288,7 @@ describe('RedsocksConf', () => {
|
||||
}
|
||||
`;
|
||||
(log.warn as SinonStub).resetHistory();
|
||||
const conf = hostConfig.RedsocksConf.parse(redsocksConfStr);
|
||||
const conf = RedsocksConf.parse(redsocksConfStr);
|
||||
expect((log.warn as SinonStub).lastCall.args[0]).to.equal(
|
||||
'Invalid redsocks block in redsocks.conf:\n' +
|
||||
'Expecting NumericIdentifier at 0.port but instead got: "bar" (must be be an positive integer)\n' +
|
||||
@ -323,7 +325,7 @@ describe('RedsocksConf', () => {
|
||||
}
|
||||
`;
|
||||
(log.warn as SinonStub).resetHistory();
|
||||
const conf = hostConfig.RedsocksConf.parse(redsocksConfStr);
|
||||
const conf = RedsocksConf.parse(redsocksConfStr);
|
||||
expect(
|
||||
(log.warn as SinonStub).getCalls().map((call) => call.firstArg),
|
||||
).to.deep.equal([
|
||||
@ -355,7 +357,7 @@ describe('RedsocksConf', () => {
|
||||
}
|
||||
`;
|
||||
(log.warn as SinonStub).resetHistory();
|
||||
const conf = hostConfig.RedsocksConf.parse(redsocksConfStr);
|
||||
const conf = RedsocksConf.parse(redsocksConfStr);
|
||||
expect(
|
||||
(log.warn as SinonStub).getCalls().map((call) => call.firstArg),
|
||||
).to.deep.equal([
|
||||
@ -375,3 +377,56 @@ describe('RedsocksConf', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('src/host-config', () => {
|
||||
describe('patchProxy', () => {
|
||||
it('patches RedsocksConfig with new values', () => {
|
||||
const current = {
|
||||
redsocks: {
|
||||
type: 'socks5',
|
||||
ip: 'example.org',
|
||||
port: 1080,
|
||||
login: '"foo"',
|
||||
password: '"bar"',
|
||||
},
|
||||
} as RedsocksConfig;
|
||||
const input = {
|
||||
redsocks: {
|
||||
type: 'http-connect',
|
||||
ip: 'test.balena.io',
|
||||
},
|
||||
} as any;
|
||||
const patched = hostConfig.patchProxy(current, input);
|
||||
expect(patched).to.deep.equal({
|
||||
redsocks: {
|
||||
// Patched fields are updated
|
||||
type: 'http-connect',
|
||||
ip: 'test.balena.io',
|
||||
// Unpatched fields retain their original values
|
||||
port: 1080,
|
||||
login: '"foo"',
|
||||
password: '"bar"',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('returns empty RedsocksConfig if redsocks config block is empty or invalid', () => {
|
||||
const current: RedsocksConfig = {
|
||||
redsocks: {
|
||||
type: 'socks5',
|
||||
ip: 'example.org',
|
||||
port: 1080,
|
||||
login: '"foo"',
|
||||
password: '"bar"',
|
||||
},
|
||||
};
|
||||
expect(hostConfig.patchProxy(current, { redsocks: {} })).to.deep.equal(
|
||||
{},
|
||||
);
|
||||
expect(
|
||||
hostConfig.patchProxy(current, { redsocks: true } as any),
|
||||
).to.deep.equal({});
|
||||
expect(hostConfig.patchProxy(current, {})).to.deep.equal({});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
Loading…
x
Reference in New Issue
Block a user