added way to save POST-ed config
This commit is contained in:
parent
ec07b6fcdb
commit
78da05b630
|
@ -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');
|
||||||
|
|
25
lib/app.js
25
lib/app.js
|
@ -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 = {
|
||||||
|
|
|
@ -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);
|
||||||
});
|
});
|
||||||
|
|
|
@ -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}');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue