added way to save POST-ed config

This commit is contained in:
tigerbot 2017-06-09 11:18:05 -06:00
parent ec07b6fcdb
commit 78da05b630
4 changed files with 197 additions and 100 deletions

View File

@ -8,67 +8,136 @@ if (!cluster.isMaster) {
return; return;
} }
function run(config) { var PromiseA = require('bluebird');
// TODO spin up multiple workers var fs = PromiseA.promisifyAll(require('fs'));
// TODO use greenlock-cluster var configStorage;
function work() { function mergeSettings(orig, changes) {
var worker = cluster.fork(); Object.keys(changes).forEach(function (key) {
worker.on('exit', work).on('online', function () { // TODO: use an API that can properly handle updating arrays.
console.log('[worker]', worker.id, 'online'); if (!changes[key] || (typeof changes[key] !== 'object') || Array.isArray(changes[key])) {
// Worker is listening orig[key] = changes[key];
worker.send(config);
});
} }
console.log('config.tcp.bind', config.tcp.bind); else if (!orig[key] || typeof orig[key] !== 'object') {
work(); orig[key] = changes[key];
}
else {
mergeSettings(orig[key], changes[key]);
}
});
}
function createStorage(filename, filetype) {
var recase = require('recase').create({});
var snakeCopy = recase.snakeCopy.bind(recase);
var camelCopy = recase.camelCopy.bind(recase);
var parse, dump;
if (filetype === 'json') {
parse = JSON.parse;
dump = function (arg) { return JSON.stringify(arg, null, ' '); };
} else {
var yaml = require('js-yaml');
parse = function (text) { return yaml.safeLoad(text) || {}; };
dump = yaml.safeDump;
}
function read() {
return fs.readFileAsync(filename)
.catch(function (err) {
if (err.code === 'ENOENT') {
return '';
}
return PromiseA.reject(err);
})
.then(parse)
;
}
var result = {
read: function () {
return read().then(camelCopy);
}
, save: function (changes) {
if (!changes || typeof changes !== 'object' || Array.isArray(changes)) {
return PromiseA.reject(new Error('invalid config'));
}
changes = snakeCopy(changes);
return read()
.then(snakeCopy)
.then(function (current) {
mergeSettings(current, changes);
// TODO: validate/lint the config before we actually write it.
return dump(current);
})
.then(function (newText) {
return fs.writeFileAsync(filename, newText);
})
.then(function () {
return result.read();
})
;
}
};
return result;
}
function checkConfigLocation(cwd, configFile) {
cwd = cwd || process.cwd();
var path = require('path');
var filename;
var prom;
if (configFile) {
filename = path.resolve(cwd, configFile);
prom = fs.readFileAsync(filename);
} else {
prom = PromiseA.reject('blah')
.catch(function () {
filename = path.resolve(cwd, 'goldilocks.yml');
return fs.readFileAsync(filename);
})
.catch(function () {
filename = path.resolve(cwd, 'goldilocks.json');
return fs.readFileAsync(filename);
})
.catch(function () {
filename = path.resolve(cwd, 'etc/goldilocks/goldilocks.yml');
return fs.readFileAsync(filename);
})
.catch(function () {
filename = '/etc/goldilocks/goldilocks.yml';
return fs.readFileAsync(filename);
})
.catch(function () {
filename = path.resolve(cwd, 'goldilocks.yml');
return '';
})
;
}
return prom.then(function (text) {
try {
JSON.parse(text);
return { name: filename, type: 'json' };
} catch (err) {}
try {
require('js-yaml').safeLoad(text);
return { name: filename, type: 'yaml' };
} catch (err) {}
throw new Error('Could not load "' + filename + '" as JSON nor YAML');
});
}
function createConfigStorage(args) {
return checkConfigLocation(args.cwd, args.config)
.then(function (result) {
console.log('config file', result.name, 'is of type', result.type);
configStorage = createStorage(result.name, result.type);
return configStorage.read();
})
;
} }
function readConfigAndRun(args) { function fillConfig(config, args) {
var fs = require('fs');
var path = require('path');
var cwd = args.cwd || process.cwd();
var text;
var filename;
var config;
if (args.config) {
filename = path.resolve(cwd, args.config);
text = fs.readFileSync(filename, 'utf8');
}
else {
filename = path.resolve(cwd, 'goldilocks.yml');
if (fs.existsSync(filename)) {
text = fs.readFileSync(filename, 'utf8');
}
else {
filename = path.resolve(cwd, 'goldilocks.json');
if (fs.existsSync(filename)) {
text = fs.readFileSync(filename, 'utf8');
} else {
text = '{}';
}
}
}
try {
config = JSON.parse(text);
} catch(e) {
try {
config = require('js-yaml').safeLoad(text);
// blank config file
if ('undefined' === typeof config) {
config = {};
}
} catch(e) {
throw new Error(
"Could not load '" + filename + "' as JSON nor YAML"
);
}
}
var recase = require('recase').create({});
config = recase.camelCopy(config);
config.debug = config.debug || args.debug; config.debug = config.debug || args.debug;
if (!config.dns) { if (!config.dns) {
@ -77,7 +146,7 @@ function readConfigAndRun(args) {
// Use Object.assign to add any properties needed but not defined in the mdns config. // Use Object.assign to add any properties needed but not defined in the mdns config.
// It will first copy the defaults into an empty object, then copy any real config over that. // It will first copy the defaults into an empty object, then copy any real config over that.
var mdnsDefaults = { port: 5353, broadcast: '224.0.0.251', ttl: 300 }; var mdnsDefaults = { port: 5353, broadcast: '224.0.0.251', ttl: 300 };
config.mdns = Object.assign({}, mdnsDefaults, config.mdns || {}); config.mdns = Object.assign({}, mdnsDefaults, config.mdns);
if (!config.tcp) { if (!config.tcp) {
config.tcp = {}; config.tcp = {};
@ -100,12 +169,7 @@ function readConfigAndRun(args) {
} }
// maybe this should not go in config... but be ephemeral in some way? // maybe this should not go in config... but be ephemeral in some way?
if (args.cwd) { config.cwd = args.cwd || config.cwd || process.cwd();
config.cwd = args.cwd;
}
if (!config.cwd) {
config.cwd = process.cwd();
}
var ipaddr = require('ipaddr.js'); var ipaddr = require('ipaddr.js');
var addresses = []; var addresses = [];
@ -135,11 +199,10 @@ function readConfigAndRun(args) {
// TODO maybe move to config.state.addresses (?) // TODO maybe move to config.state.addresses (?)
config.addresses = addresses; config.addresses = addresses;
config.device = { hostname: 'daplien-pod' }; config.device = { hostname: require('os').hostname() };
config.tunnel = args.tunnel || config.tunnel; config.tunnel = args.tunnel || config.tunnel;
var PromiseA = require('bluebird');
var tcpProm, dnsProm; var tcpProm, dnsProm;
if (config.tcp.bind) { if (config.tcp.bind) {
@ -186,18 +249,63 @@ function readConfigAndRun(args) {
}); });
} }
PromiseA.all([tcpProm, dnsProm]) return PromiseA.all([tcpProm, dnsProm])
.then(function () { .then(function () { return config; })
run(config);
})
.catch(function (failed) { .catch(function (failed) {
console.warn("could not bind to the desired ports");
Object.keys(failed).forEach(function (key) { Object.keys(failed).forEach(function (key) {
console.log('[error bind]', key, failed[key].code); console.log('[error bind]', key, failed[key].code);
}); });
return PromiseA.reject(new Error("could not bind to the desired ports"));
}); });
} }
function run(args) {
var workers = {};
var cachedConfig;
cluster.on('message', function (worker, message) {
if (message.type !== 'com.daplie.goldilocks.config-change') {
return;
}
configStorage.save(message.changes)
.then(function (config) {
return fillConfig(config, args);
})
.then(function (config) {
cachedConfig = config;
})
.catch(function (err) {
console.error('error changing config', err);
})
;
});
cluster.on('online', function (worker) {
console.log('[worker]', worker.id, 'online');
workers[worker.id] = worker;
// Worker is listening
worker.send(cachedConfig);
});
cluster.on('exit', function (worker) {
delete workers[worker.id];
cluster.fork();
});
createConfigStorage(args)
.then(function (config) {
return fillConfig(config, args);
})
.then(function (config) {
console.log('config.tcp.bind', config.tcp.bind);
cachedConfig = config;
// TODO spin up multiple workers
// TODO use greenlock-cluster
cluster.fork();
})
;
}
function readEnv(args) { function readEnv(args) {
// TODO // TODO
try { try {
@ -213,7 +321,7 @@ function readEnv(args) {
, debug: process.env.GOLDILOCKS_DEBUG && true , debug: process.env.GOLDILOCKS_DEBUG && true
}; };
readConfigAndRun(Object.assign({}, env, args)); run(Object.assign({}, env, args));
} }
var program = require('commander'); var program = require('commander');

View File

@ -17,29 +17,6 @@ module.exports = function (myDeps, conf, overrideHttp) {
var app; var app;
var request; var request;
/*
function _reloadWrite(data, enc, cb) {
// /*jshint validthis: true */ /*
if (this.headersSent) {
this.__write(data, enc, cb);
return;
}
if (!/html/i.test(this.getHeader('Content-Type'))) {
this.__write(data, enc, cb);
return;
}
if (this.getHeader('Content-Length')) {
this.setHeader('Content-Length', this.getHeader('Content-Length') + this.__my_addLen);
}
this.__write(this.__my_livereload);
this.__write(data, enc, cb);
}
*/
function createServeInit() { function createServeInit() {
var PromiseA = require('bluebird'); var PromiseA = require('bluebird');
var OAUTH3 = require('../packages/assets/org.oauth3'); var OAUTH3 = require('../packages/assets/org.oauth3');
@ -106,7 +83,7 @@ module.exports = function (myDeps, conf, overrideHttp) {
myDeps.PromiseA = PromiseA; myDeps.PromiseA = PromiseA;
myDeps.OAUTH3 = OAUTH3; myDeps.OAUTH3 = OAUTH3;
myDeps.storage = { owners: owners }; myDeps.storage = Object.assign({ owners: owners }, myDeps.storage);
myDeps.recase = require('recase').create({}); myDeps.recase = require('recase').create({});
myDeps.request = request; myDeps.request = request;
myDeps.api = { myDeps.api = {

View File

@ -10,6 +10,16 @@ process.on('message', function (conf) {
, net: require('net') , net: require('net')
}; };
deps.proxy = require('./proxy-conn').create(deps, conf); deps.proxy = require('./proxy-conn').create(deps, conf);
deps.storage = {
config: {
save: function (changes) {
process.send({
type: 'com.daplie.goldilocks.config-change',
changes: changes
});
}
}
};
require('./goldilocks.js').create(deps, conf); require('./goldilocks.js').create(deps, conf);
}); });

View File

@ -152,11 +152,13 @@ module.exports.create = function (deps, conf) {
} }
jsonParser(req, res, function () { jsonParser(req, res, function () {
console.log('config POST body', req.body);
console.log('req.body', req.body); // Since we are sending the changes to another process we don't really
// have a good way of seeing if it worked, so always report success
deps.storage.config.merge(req.body); deps.storage.config.save(req.body);
deps.storage.config.save(); res.setHeader('Content-Type', 'application/json;');
res.end('{"success":true}');
}); });
}); });
} }