forked from coolaj86/goldilocks.js
		
	added way to save POST-ed config
This commit is contained in:
		
							parent
							
								
									ec07b6fcdb
								
							
						
					
					
						commit
						78da05b630
					
				@ -8,67 +8,136 @@ if (!cluster.isMaster) {
 | 
			
		||||
  return;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function run(config) {
 | 
			
		||||
  // TODO spin up multiple workers
 | 
			
		||||
  // TODO use greenlock-cluster
 | 
			
		||||
  function work() {
 | 
			
		||||
    var worker = cluster.fork();
 | 
			
		||||
    worker.on('exit', work).on('online', function () {
 | 
			
		||||
      console.log('[worker]', worker.id, 'online');
 | 
			
		||||
      // Worker is listening
 | 
			
		||||
      worker.send(config);
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
  console.log('config.tcp.bind', config.tcp.bind);
 | 
			
		||||
  work();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function readConfigAndRun(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');
 | 
			
		||||
var PromiseA = require('bluebird');
 | 
			
		||||
var fs = PromiseA.promisifyAll(require('fs'));
 | 
			
		||||
var configStorage;
 | 
			
		||||
function mergeSettings(orig, changes) {
 | 
			
		||||
  Object.keys(changes).forEach(function (key) {
 | 
			
		||||
    // TODO: use an API that can properly handle updating arrays.
 | 
			
		||||
    if (!changes[key] || (typeof changes[key] !== 'object') || Array.isArray(changes[key])) {
 | 
			
		||||
      orig[key] = changes[key];
 | 
			
		||||
    }
 | 
			
		||||
    else if (!orig[key] || typeof orig[key] !== 'object') {
 | 
			
		||||
      orig[key] = changes[key];
 | 
			
		||||
    }
 | 
			
		||||
    else {
 | 
			
		||||
      filename = path.resolve(cwd, 'goldilocks.json');
 | 
			
		||||
      if (fs.existsSync(filename)) {
 | 
			
		||||
        text = fs.readFileSync(filename, 'utf8');
 | 
			
		||||
      } else {
 | 
			
		||||
        text = '{}';
 | 
			
		||||
      }
 | 
			
		||||
      mergeSettings(orig[key], changes[key]);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  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"
 | 
			
		||||
      );
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  });
 | 
			
		||||
}
 | 
			
		||||
function createStorage(filename, filetype) {
 | 
			
		||||
  var recase = require('recase').create({});
 | 
			
		||||
  config = recase.camelCopy(config);
 | 
			
		||||
  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 fillConfig(config, args) {
 | 
			
		||||
  config.debug = config.debug || args.debug;
 | 
			
		||||
 | 
			
		||||
  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.
 | 
			
		||||
  // 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 };
 | 
			
		||||
  config.mdns = Object.assign({}, mdnsDefaults, config.mdns || {});
 | 
			
		||||
  config.mdns = Object.assign({}, mdnsDefaults, config.mdns);
 | 
			
		||||
 | 
			
		||||
  if (!config.tcp) {
 | 
			
		||||
    config.tcp = {};
 | 
			
		||||
@ -100,12 +169,7 @@ function readConfigAndRun(args) {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // maybe this should not go in config... but be ephemeral in some way?
 | 
			
		||||
  if (args.cwd) {
 | 
			
		||||
    config.cwd = args.cwd;
 | 
			
		||||
  }
 | 
			
		||||
  if (!config.cwd) {
 | 
			
		||||
    config.cwd = process.cwd();
 | 
			
		||||
  }
 | 
			
		||||
  config.cwd = args.cwd || config.cwd || process.cwd();
 | 
			
		||||
 | 
			
		||||
  var ipaddr = require('ipaddr.js');
 | 
			
		||||
  var addresses = [];
 | 
			
		||||
@ -135,11 +199,10 @@ function readConfigAndRun(args) {
 | 
			
		||||
 | 
			
		||||
  // TODO maybe move to config.state.addresses (?)
 | 
			
		||||
  config.addresses = addresses;
 | 
			
		||||
  config.device = { hostname: 'daplien-pod' };
 | 
			
		||||
  config.device = { hostname: require('os').hostname() };
 | 
			
		||||
 | 
			
		||||
  config.tunnel = args.tunnel || config.tunnel;
 | 
			
		||||
 | 
			
		||||
  var PromiseA = require('bluebird');
 | 
			
		||||
  var tcpProm, dnsProm;
 | 
			
		||||
 | 
			
		||||
  if (config.tcp.bind) {
 | 
			
		||||
@ -186,18 +249,63 @@ function readConfigAndRun(args) {
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  PromiseA.all([tcpProm, dnsProm])
 | 
			
		||||
    .then(function () {
 | 
			
		||||
      run(config);
 | 
			
		||||
    })
 | 
			
		||||
  return PromiseA.all([tcpProm, dnsProm])
 | 
			
		||||
    .then(function () { return config; })
 | 
			
		||||
    .catch(function (failed) {
 | 
			
		||||
      console.warn("could not bind to the desired ports");
 | 
			
		||||
      Object.keys(failed).forEach(function (key) {
 | 
			
		||||
        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) {
 | 
			
		||||
  // TODO
 | 
			
		||||
  try {
 | 
			
		||||
@ -213,7 +321,7 @@ function readEnv(args) {
 | 
			
		||||
  , debug: process.env.GOLDILOCKS_DEBUG && true
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  readConfigAndRun(Object.assign({}, env, args));
 | 
			
		||||
  run(Object.assign({}, env, args));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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 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() {
 | 
			
		||||
    var PromiseA = require('bluebird');
 | 
			
		||||
    var OAUTH3 = require('../packages/assets/org.oauth3');
 | 
			
		||||
@ -106,7 +83,7 @@ module.exports = function (myDeps, conf, overrideHttp) {
 | 
			
		||||
 | 
			
		||||
    myDeps.PromiseA = PromiseA;
 | 
			
		||||
    myDeps.OAUTH3 = OAUTH3;
 | 
			
		||||
    myDeps.storage = { owners: owners };
 | 
			
		||||
    myDeps.storage = Object.assign({ owners: owners }, myDeps.storage);
 | 
			
		||||
    myDeps.recase = require('recase').create({});
 | 
			
		||||
    myDeps.request = request;
 | 
			
		||||
    myDeps.api = {
 | 
			
		||||
 | 
			
		||||
@ -10,6 +10,16 @@ process.on('message', function (conf) {
 | 
			
		||||
  , net: require('net')
 | 
			
		||||
  };
 | 
			
		||||
  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);
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
@ -152,11 +152,13 @@ module.exports.create = function (deps, conf) {
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        jsonParser(req, res, function () {
 | 
			
		||||
          console.log('config POST body', req.body);
 | 
			
		||||
 | 
			
		||||
          console.log('req.body', req.body);
 | 
			
		||||
 | 
			
		||||
          deps.storage.config.merge(req.body);
 | 
			
		||||
          deps.storage.config.save();
 | 
			
		||||
          // 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.save(req.body);
 | 
			
		||||
          res.setHeader('Content-Type', 'application/json;');
 | 
			
		||||
          res.end('{"success":true}');
 | 
			
		||||
        });
 | 
			
		||||
      });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user