mirror of
https://github.com/balena-os/balena-supervisor.git
synced 2025-01-31 00:23:57 +00:00
refactor: Convert host-config module to typescript
Change-type: patch Signed-off-by: Cameron Diver <cameron@balena.io>
This commit is contained in:
parent
59887bad57
commit
f4f67a5afc
@ -30,6 +30,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/bluebird": "^3.5.25",
|
||||
"@types/common-tags": "^1.8.0",
|
||||
"@types/dockerode": "^2.5.10",
|
||||
"@types/event-stream": "^3.3.34",
|
||||
"@types/express": "^4.11.1",
|
||||
@ -37,6 +38,7 @@
|
||||
"@types/lockfile": "^1.0.0",
|
||||
"@types/lodash": "^4.14.119",
|
||||
"@types/memoizee": "^0.4.2",
|
||||
"@types/mkdirp": "^0.5.2",
|
||||
"@types/mz": "0.0.32",
|
||||
"@types/node": "^10.12.17",
|
||||
"@types/request": "^2.48.1",
|
||||
@ -50,6 +52,7 @@
|
||||
"chai-events": "0.0.1",
|
||||
"coffee-loader": "^0.9.0",
|
||||
"coffeescript": "^1.12.7",
|
||||
"common-tags": "^1.8.0",
|
||||
"copy-webpack-plugin": "^4.6.0",
|
||||
"dbus-native": "^0.2.5",
|
||||
"deep-object-diff": "^1.1.0",
|
||||
|
@ -1,127 +0,0 @@
|
||||
Promise = require 'bluebird'
|
||||
_ = require 'lodash'
|
||||
systemd = require './lib/systemd'
|
||||
path = require 'path'
|
||||
constants = require './lib/constants'
|
||||
fs = Promise.promisifyAll(require('fs'))
|
||||
{ writeFileAtomic } = require './lib/fs-utils'
|
||||
mkdirp = Promise.promisify(require('mkdirp'))
|
||||
|
||||
ENOENT = (err) -> err.code is 'ENOENT'
|
||||
|
||||
redsocksHeader = '''
|
||||
base {
|
||||
log_debug = off;
|
||||
log_info = on;
|
||||
log = stderr;
|
||||
daemon = off;
|
||||
redirector = iptables;
|
||||
}
|
||||
|
||||
redsocks {
|
||||
local_ip = 127.0.0.1;
|
||||
local_port = 12345;
|
||||
|
||||
'''
|
||||
|
||||
redsocksFooter = '}\n'
|
||||
|
||||
proxyFields = [ 'type', 'ip', 'port', 'login', 'password' ]
|
||||
|
||||
proxyBasePath = path.join(constants.rootMountPoint, constants.bootMountPoint, 'system-proxy')
|
||||
redsocksConfPath = path.join(proxyBasePath, 'redsocks.conf')
|
||||
noProxyPath = path.join(proxyBasePath, 'no_proxy')
|
||||
|
||||
readProxy = ->
|
||||
fs.readFileAsync(redsocksConfPath)
|
||||
.then (redsocksConf) ->
|
||||
lines = new String(redsocksConf).split('\n')
|
||||
conf = {}
|
||||
for line in lines
|
||||
for proxyField in proxyFields
|
||||
if proxyField in [ 'login', 'password' ]
|
||||
m = line.match(new RegExp(proxyField + '\\s*=\\s*\"(.*)\"\\s*;'))
|
||||
else
|
||||
m = line.match(new RegExp(proxyField + '\\s*=\\s*([^;\\s]*)\\s*;'))
|
||||
if m?
|
||||
conf[proxyField] = m[1]
|
||||
return conf
|
||||
.catch ENOENT, ->
|
||||
return null
|
||||
.then (conf) ->
|
||||
if !conf?
|
||||
return null
|
||||
else
|
||||
fs.readFileAsync(noProxyPath)
|
||||
.then (noProxy) ->
|
||||
conf.noProxy = new String(noProxy).split('\n')
|
||||
return conf
|
||||
.catch ENOENT, ->
|
||||
return conf
|
||||
|
||||
generateRedsocksConfEntries = (conf) ->
|
||||
val = ''
|
||||
for field in proxyFields
|
||||
if conf[field]?
|
||||
v = conf[field]
|
||||
if field in [ 'login', 'password' ]
|
||||
v = "\"#{v}\""
|
||||
val += "\t#{field} = #{v};\n"
|
||||
return val
|
||||
|
||||
setProxy = (conf) ->
|
||||
Promise.try ->
|
||||
if _.isEmpty(conf)
|
||||
fs.unlinkAsync(redsocksConfPath)
|
||||
.catch(ENOENT, _.noop)
|
||||
.then ->
|
||||
fs.unlinkAsync(noProxyPath)
|
||||
.catch(ENOENT, _.noop)
|
||||
else
|
||||
mkdirp(proxyBasePath)
|
||||
.then ->
|
||||
if _.isArray(conf.noProxy)
|
||||
writeFileAtomic(noProxyPath, conf.noProxy.join('\n'))
|
||||
.then ->
|
||||
redsocksConf = ''
|
||||
redsocksConf += redsocksHeader
|
||||
redsocksConf += generateRedsocksConfEntries(conf)
|
||||
redsocksConf += redsocksFooter
|
||||
writeFileAtomic(redsocksConfPath, redsocksConf)
|
||||
.then ->
|
||||
systemd.restartService('resin-proxy-config')
|
||||
.then ->
|
||||
systemd.restartService('redsocks')
|
||||
|
||||
hostnamePath = path.join(constants.rootMountPoint, '/etc/hostname')
|
||||
readHostname = ->
|
||||
fs.readFileAsync(hostnamePath)
|
||||
.then (hostnameData) ->
|
||||
return _.trim(new String(hostnameData))
|
||||
|
||||
setHostname = (val, configModel) ->
|
||||
configModel.set(hostname: val)
|
||||
.then ->
|
||||
systemd.restartService('resin-hostname')
|
||||
|
||||
|
||||
exports.get = ->
|
||||
Promise.join(
|
||||
readProxy()
|
||||
readHostname()
|
||||
(proxy, hostname) ->
|
||||
return {
|
||||
network: {
|
||||
proxy
|
||||
hostname
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
exports.patch = (conf, configModel) ->
|
||||
Promise.try ->
|
||||
if !_.isUndefined(conf?.network?.proxy)
|
||||
setProxy(conf.network.proxy)
|
||||
.then ->
|
||||
if !_.isUndefined(conf?.network?.hostname)
|
||||
setHostname(conf.network.hostname, configModel)
|
182
src/host-config.ts
Normal file
182
src/host-config.ts
Normal file
@ -0,0 +1,182 @@
|
||||
import * as Bluebird from 'bluebird';
|
||||
import { stripIndent } from 'common-tags';
|
||||
import * as _ from 'lodash';
|
||||
import * as mkdirCb from 'mkdirp';
|
||||
import { fs } from 'mz';
|
||||
import * as path from 'path';
|
||||
|
||||
import Config = require('./config');
|
||||
import * as constants from './lib/constants';
|
||||
import { ENOENT } from './lib/errors';
|
||||
import { writeFileAtomic } from './lib/fs-utils';
|
||||
import * as systemd from './lib/systemd';
|
||||
|
||||
const mkdirp = Bluebird.promisify(mkdirCb) as (
|
||||
path: string,
|
||||
opts?: any,
|
||||
) => Bluebird<mkdirCb.Made>;
|
||||
|
||||
const redsocksHeader = stripIndent`
|
||||
base {
|
||||
log_debug = off;
|
||||
log_info = on;
|
||||
log = stderr;
|
||||
daemon = off;
|
||||
redirector = iptables;
|
||||
}
|
||||
|
||||
redsocks {
|
||||
local_ip = 127.0.0.1;
|
||||
local_port = 12345;
|
||||
`;
|
||||
|
||||
const redsocksFooter = '}\n';
|
||||
|
||||
const proxyFields = ['type', 'ip', 'port', 'login', 'password'];
|
||||
|
||||
const proxyBasePath = path.join(
|
||||
constants.rootMountPoint,
|
||||
constants.bootMountPoint,
|
||||
'system-proxy',
|
||||
);
|
||||
const redsocksConfPath = path.join(proxyBasePath, 'redsocks.conf');
|
||||
const noProxyPath = path.join(proxyBasePath, 'no_proxy');
|
||||
|
||||
interface ProxyConfig {
|
||||
[key: string]: string | string[];
|
||||
}
|
||||
|
||||
interface HostConfig {
|
||||
network: {
|
||||
proxy?: ProxyConfig;
|
||||
hostname?: string;
|
||||
};
|
||||
}
|
||||
|
||||
const isAuthField = (field: string): boolean =>
|
||||
_.includes(['login', 'password'], field);
|
||||
|
||||
const memoizedAuthRegex = _.memoize(
|
||||
(proxyField: string) => new RegExp(proxyField + '\\s*=\\s*"(.*)"\\s*;'),
|
||||
);
|
||||
|
||||
const memoizedRegex = _.memoize(
|
||||
proxyField => new RegExp(proxyField + '\\s*=\\s*([^;\\s]*)\\s*;'),
|
||||
);
|
||||
|
||||
async function readProxy(): Promise<ProxyConfig | undefined> {
|
||||
const conf: ProxyConfig = {};
|
||||
let redsocksConf: string;
|
||||
try {
|
||||
redsocksConf = await fs.readFile(redsocksConfPath, 'utf-8');
|
||||
} catch (e) {
|
||||
if (!ENOENT(e)) {
|
||||
throw e;
|
||||
}
|
||||
return;
|
||||
}
|
||||
const lines = redsocksConf.split('\n');
|
||||
|
||||
for (const line of lines) {
|
||||
for (const proxyField of proxyFields) {
|
||||
let match: string[] | null = null;
|
||||
if (isAuthField(proxyField)) {
|
||||
match = line.match(memoizedAuthRegex(proxyField));
|
||||
} else {
|
||||
match = line.match(memoizedRegex(proxyField));
|
||||
}
|
||||
|
||||
if (match != null) {
|
||||
conf[proxyField] = match[1];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
const noProxy = await fs.readFile(noProxyPath, 'utf-8');
|
||||
conf.noProxy = noProxy.split('\n');
|
||||
} catch (e) {
|
||||
if (!ENOENT(e)) {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
return conf;
|
||||
}
|
||||
|
||||
function generateRedsocksConfEntries(conf: ProxyConfig): string {
|
||||
let val = '';
|
||||
for (const field of proxyFields) {
|
||||
let v = conf[field];
|
||||
if (v != null) {
|
||||
if (isAuthField(field)) {
|
||||
v = `"${v}"`;
|
||||
}
|
||||
val += `\t${field} = ${v};\n`;
|
||||
}
|
||||
}
|
||||
return val;
|
||||
}
|
||||
|
||||
async function setProxy(maybeConf: ProxyConfig | null): Promise<void> {
|
||||
if (_.isEmpty(maybeConf)) {
|
||||
try {
|
||||
await Promise.all([fs.unlink(redsocksConfPath), fs.unlink(noProxyPath)]);
|
||||
} catch (e) {
|
||||
if (!ENOENT(e)) {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// We know that maybeConf is not null due to the _.isEmpty check above,
|
||||
// but the compiler doesn't
|
||||
const conf = maybeConf as ProxyConfig;
|
||||
await mkdirp(proxyBasePath);
|
||||
if (_.isArray(conf.noProxy)) {
|
||||
await writeFileAtomic(noProxyPath, conf.noProxy.join('\n'));
|
||||
}
|
||||
const redsocksConf = `${redsocksHeader}${generateRedsocksConfEntries(
|
||||
conf,
|
||||
)}${redsocksFooter}`;
|
||||
await writeFileAtomic(redsocksConfPath, redsocksConf);
|
||||
}
|
||||
|
||||
await systemd.restartService('resin-proxy-config');
|
||||
await systemd.restartService('redsocks');
|
||||
}
|
||||
|
||||
const hostnamePath = path.join(constants.rootMountPoint, '/etc/hostname');
|
||||
async function readHostname() {
|
||||
const hostnameData = await fs.readFile(hostnamePath, 'utf-8');
|
||||
return _.trim(hostnameData);
|
||||
}
|
||||
|
||||
async function setHostname(val: string, configModel: Config) {
|
||||
await configModel.set({ hostname: val });
|
||||
await systemd.restartService('resin-hostname');
|
||||
}
|
||||
|
||||
// Don't use async/await here to maintain the bluebird
|
||||
// promises being returned
|
||||
export function get(): Bluebird<HostConfig> {
|
||||
return Bluebird.join(readProxy(), readHostname(), (proxy, hostname) => {
|
||||
return {
|
||||
network: {
|
||||
proxy,
|
||||
hostname,
|
||||
},
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
export function patch(conf: HostConfig, configModel: Config): Bluebird<void> {
|
||||
const promises = [];
|
||||
if (conf != null && conf.network != null) {
|
||||
if (conf.network.proxy != null) {
|
||||
promises.push(setProxy(conf.network.proxy));
|
||||
}
|
||||
if (conf.network.hostname != null) {
|
||||
promises.push(setHostname(conf.network.hostname, configModel));
|
||||
}
|
||||
}
|
||||
return Bluebird.all(promises).return();
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user