mirror of
https://github.com/balena-os/balena-supervisor.git
synced 2025-02-06 11:10:27 +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 { promises as fs } from 'fs';
|
||||||
|
|
||||||
|
import type { RedsocksConfig, ProxyConfig } from './types';
|
||||||
import * as config from '../config';
|
import * as config from '../config';
|
||||||
import { pathOnRoot } from '../lib/host-utils';
|
import { pathOnRoot } from '../lib/host-utils';
|
||||||
|
|
||||||
@ -25,3 +26,29 @@ export async function setHostname(val: string) {
|
|||||||
// so the change gets reflected on containers
|
// so the change gets reflected on containers
|
||||||
await config.set({ hostname });
|
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 { isRight } from 'fp-ts/lib/Either';
|
||||||
import Reporter from 'io-ts-reporters';
|
import Reporter from 'io-ts-reporters';
|
||||||
|
|
||||||
import type { RedsocksConfig } from './types';
|
|
||||||
import { ProxyConfig } from './types';
|
import { ProxyConfig } from './types';
|
||||||
|
import type { RedsocksConfig } from './types';
|
||||||
import { pathOnBoot, readFromBoot } from '../lib/host-utils';
|
import { pathOnBoot, readFromBoot } from '../lib/host-utils';
|
||||||
import { unlinkAll } from '../lib/fs-utils';
|
import { unlinkAll } from '../lib/fs-utils';
|
||||||
import { isENOENT } from '../lib/errors';
|
import { isENOENT } from '../lib/errors';
|
||||||
|
@ -20,6 +20,10 @@ export const ProxyConfig = t.intersection([
|
|||||||
]);
|
]);
|
||||||
export type ProxyConfig = t.TypeOf<typeof ProxyConfig>;
|
export type ProxyConfig = t.TypeOf<typeof ProxyConfig>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The internal object representation of redsocks.conf, obtained
|
||||||
|
* from RedsocksConf.parse
|
||||||
|
*/
|
||||||
export const RedsocksConfig = t.partial({
|
export const RedsocksConfig = t.partial({
|
||||||
redsocks: ProxyConfig,
|
redsocks: ProxyConfig,
|
||||||
});
|
});
|
||||||
|
@ -3,12 +3,14 @@ import { stripIndent } from 'common-tags';
|
|||||||
import type { SinonStub } from 'sinon';
|
import type { SinonStub } from 'sinon';
|
||||||
|
|
||||||
import * as hostConfig from '~/src/host-config/index';
|
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';
|
import log from '~/lib/supervisor-console';
|
||||||
|
|
||||||
describe('RedsocksConf', () => {
|
describe('RedsocksConf', () => {
|
||||||
describe('stringify', () => {
|
describe('stringify', () => {
|
||||||
it('stringifies RedsocksConfig into config string', () => {
|
it('stringifies RedsocksConfig into config string', () => {
|
||||||
const conf: hostConfig.RedsocksConfig = {
|
const conf: RedsocksConfig = {
|
||||||
redsocks: {
|
redsocks: {
|
||||||
type: 'socks5',
|
type: 'socks5',
|
||||||
ip: 'example.org',
|
ip: 'example.org',
|
||||||
@ -17,7 +19,7 @@ describe('RedsocksConf', () => {
|
|||||||
password: '"bar"',
|
password: '"bar"',
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
const confStr = hostConfig.RedsocksConf.stringify(conf);
|
const confStr = RedsocksConf.stringify(conf);
|
||||||
expect(confStr).to.equal(
|
expect(confStr).to.equal(
|
||||||
stripIndent`
|
stripIndent`
|
||||||
base {
|
base {
|
||||||
@ -42,7 +44,7 @@ describe('RedsocksConf', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('adds double quotes to auth fields if not exists', () => {
|
it('adds double quotes to auth fields if not exists', () => {
|
||||||
const conf: hostConfig.RedsocksConfig = {
|
const conf: RedsocksConfig = {
|
||||||
redsocks: {
|
redsocks: {
|
||||||
type: 'socks5',
|
type: 'socks5',
|
||||||
ip: 'example.org',
|
ip: 'example.org',
|
||||||
@ -51,7 +53,7 @@ describe('RedsocksConf', () => {
|
|||||||
password: 'bar',
|
password: 'bar',
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
const confStr = hostConfig.RedsocksConf.stringify(conf);
|
const confStr = RedsocksConf.stringify(conf);
|
||||||
expect(confStr).to.equal(
|
expect(confStr).to.equal(
|
||||||
stripIndent`
|
stripIndent`
|
||||||
base {
|
base {
|
||||||
@ -84,8 +86,8 @@ describe('RedsocksConf', () => {
|
|||||||
login: 'foo',
|
login: 'foo',
|
||||||
password: 'bar',
|
password: 'bar',
|
||||||
},
|
},
|
||||||
} as unknown as hostConfig.RedsocksConfig;
|
} as unknown as RedsocksConfig;
|
||||||
const confStr = hostConfig.RedsocksConf.stringify(conf);
|
const confStr = RedsocksConf.stringify(conf);
|
||||||
expect(confStr).to.equal(
|
expect(confStr).to.equal(
|
||||||
stripIndent`
|
stripIndent`
|
||||||
base {
|
base {
|
||||||
@ -110,16 +112,16 @@ describe('RedsocksConf', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('stringifies to empty string when provided empty RedsocksConfig', () => {
|
it('stringifies to empty string when provided empty RedsocksConfig', () => {
|
||||||
const conf: hostConfig.RedsocksConfig = {};
|
const conf: RedsocksConfig = {};
|
||||||
const confStr = hostConfig.RedsocksConf.stringify(conf);
|
const confStr = RedsocksConf.stringify(conf);
|
||||||
expect(confStr).to.equal('');
|
expect(confStr).to.equal('');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('stringifies to empty string when provided empty redsocks block', () => {
|
it('stringifies to empty string when provided empty redsocks block', () => {
|
||||||
const conf: hostConfig.RedsocksConfig = {
|
const conf: RedsocksConfig = {
|
||||||
redsocks: {} as hostConfig.ProxyConfig,
|
redsocks: {} as ProxyConfig,
|
||||||
};
|
};
|
||||||
const confStr = hostConfig.RedsocksConf.stringify(conf);
|
const confStr = RedsocksConf.stringify(conf);
|
||||||
expect(confStr).to.equal('');
|
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({
|
expect(conf).to.deep.equal({
|
||||||
redsocks: {
|
redsocks: {
|
||||||
type: 'socks5',
|
type: 'socks5',
|
||||||
@ -184,7 +186,7 @@ describe('RedsocksConf', () => {
|
|||||||
redirector = iptables;
|
redirector = iptables;
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
const conf = hostConfig.RedsocksConf.parse(redsocksConfStr);
|
const conf = RedsocksConf.parse(redsocksConfStr);
|
||||||
expect(conf).to.deep.equal({
|
expect(conf).to.deep.equal({
|
||||||
redsocks: {
|
redsocks: {
|
||||||
type: 'http-connect',
|
type: 'http-connect',
|
||||||
@ -213,7 +215,7 @@ describe('RedsocksConf', () => {
|
|||||||
login = "us}{er";
|
login = "us}{er";
|
||||||
password = "p{}a}}s{{s";
|
password = "p{}a}}s{{s";
|
||||||
}`; // No newlines
|
}`; // No newlines
|
||||||
const conf2 = hostConfig.RedsocksConf.parse(redsocksConfStr2);
|
const conf2 = RedsocksConf.parse(redsocksConfStr2);
|
||||||
expect(conf2).to.deep.equal({
|
expect(conf2).to.deep.equal({
|
||||||
redsocks: {
|
redsocks: {
|
||||||
type: 'http-connect',
|
type: 'http-connect',
|
||||||
@ -244,7 +246,7 @@ describe('RedsocksConf', () => {
|
|||||||
password = pass;
|
password = pass;
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
const conf = hostConfig.RedsocksConf.parse(confStr);
|
const conf = RedsocksConf.parse(confStr);
|
||||||
expect(conf).to.deep.equal(expected);
|
expect(conf).to.deep.equal(expected);
|
||||||
|
|
||||||
const confStr2 = stripIndent`
|
const confStr2 = stripIndent`
|
||||||
@ -256,7 +258,7 @@ describe('RedsocksConf', () => {
|
|||||||
password = pass";
|
password = pass";
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
const conf2 = hostConfig.RedsocksConf.parse(confStr2);
|
const conf2 = RedsocksConf.parse(confStr2);
|
||||||
expect(conf2).to.deep.equal(expected);
|
expect(conf2).to.deep.equal(expected);
|
||||||
|
|
||||||
const confStr3 = stripIndent`
|
const confStr3 = stripIndent`
|
||||||
@ -268,7 +270,7 @@ describe('RedsocksConf', () => {
|
|||||||
password = "pass";
|
password = "pass";
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
const conf3 = hostConfig.RedsocksConf.parse(confStr3);
|
const conf3 = RedsocksConf.parse(confStr3);
|
||||||
expect(conf3).to.deep.equal(expected);
|
expect(conf3).to.deep.equal(expected);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -286,7 +288,7 @@ describe('RedsocksConf', () => {
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
(log.warn as SinonStub).resetHistory();
|
(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(
|
expect((log.warn as SinonStub).lastCall.args[0]).to.equal(
|
||||||
'Invalid redsocks block in redsocks.conf:\n' +
|
'Invalid redsocks block in redsocks.conf:\n' +
|
||||||
'Expecting NumericIdentifier at 0.port but instead got: "bar" (must be be an positive integer)\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();
|
(log.warn as SinonStub).resetHistory();
|
||||||
const conf = hostConfig.RedsocksConf.parse(redsocksConfStr);
|
const conf = RedsocksConf.parse(redsocksConfStr);
|
||||||
expect(
|
expect(
|
||||||
(log.warn as SinonStub).getCalls().map((call) => call.firstArg),
|
(log.warn as SinonStub).getCalls().map((call) => call.firstArg),
|
||||||
).to.deep.equal([
|
).to.deep.equal([
|
||||||
@ -355,7 +357,7 @@ describe('RedsocksConf', () => {
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
(log.warn as SinonStub).resetHistory();
|
(log.warn as SinonStub).resetHistory();
|
||||||
const conf = hostConfig.RedsocksConf.parse(redsocksConfStr);
|
const conf = RedsocksConf.parse(redsocksConfStr);
|
||||||
expect(
|
expect(
|
||||||
(log.warn as SinonStub).getCalls().map((call) => call.firstArg),
|
(log.warn as SinonStub).getCalls().map((call) => call.firstArg),
|
||||||
).to.deep.equal([
|
).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