<commit_before>'use strict';

var _ = require('underscore'),
    async = require('async'),
    clone = require('clone'),
    config = require('./config'),
    events = require('events'),
    exec = require('child_process').exec,
    fs = require('fs'),
    handlebars = require('handlebars'),
    logger = require('./logger'),
    path = require('path'),
    traverse = require('traverse'),
    util = require('util');

var ConfigBuilder = function() {
  this.initialize.apply(this, arguments);
};

util.inherits(ConfigBuilder, events.EventEmitter);
_.extend(ConfigBuilder.prototype, {
  initialize: function(mongo, readyCallback) {
    this.mongo = mongo;

    var templatePath = path.resolve(__dirname, '../templates/nginx.hbs');
    var templateContent = fs.readFileSync(templatePath);
    this.nginxTemplate = handlebars.compile(templateContent.toString());

    this.once('reloaded', readyCallback);

    // Force a reload on initial boot (in case internal changes have been made
    // that would affect the output even if the configuration values haven't
    // changed).
    this.reload({ force: true });
  },

  reload: function(options) {
    var collection = this.mongo.collection('config_versions');
    var cursor = collection.find().sort({ version: -1 }).limit(1);
    cursor.toArray(this.handleFetchConfigVersion.bind(this, options));
  },

  handleFetchConfigVersion: function(options, error, configVersions) {
    if(configVersions) {
      this.lastConfig = configVersions[0];
    }

    this.apis = [];
    if(this.lastConfig && this.lastConfig.config.apis) {
      this.apis = this.lastConfig.config.apis;
    }

    var lastId = null;
    if(this.lastConfig && this.lastConfig._id) {
      lastId = this.lastConfig._id.toHexString();
    }

    var activeId = config.get('version_id');

    if((options && options.force) || (this.lastConfig && lastId !== activeId)) {
      logger.info('Writing new config...');
      this.writeConfigs();
    } else {
      this.poll();
    }
  },

  writeConfigs: function() {
    // Write nginx config's first.
    async.series([
      this.writeNginxConfig.bind(this),
      this.writeGatekeeperConfig.bind(this),
    ], this.handleWriteConfigs.bind(this));
  },

  writeNginxConfig: function(writeCallback) {
    logger.info('Writing nginx config...');

    var apis = _.reject(clone(this.apis), function(api) {
      return (!api.servers || api.servers.length === 0);
    });

    var frontendHosts = _.reject(_.uniq(_.pluck(apis, 'frontend_host')), function(host) {
      return !host;
    });

    apis.forEach(function(api) {
      if(api.balance_algorithm === 'least_conn' || api.balance_algorithm === 'ip_hash') {
        api.defaultBalance = false;
      } else {
        api.defaultBalance = true;
      }
    });

    var newContent = this.nginxTemplate({
      logDir: config.logDir,
      hosts: frontendHosts,
      apis: apis,
    });

    var nginxPath = path.join(config.configDir, 'nginx.conf');

    var write = function() {
      fs.writeFile(nginxPath, newContent, function() {
        logger.info('Nginx config written...');

        this.emit('nginx');

        logger.info('Reloading nginx...');
        exec('sudo /etc/init.d/nginx configtest && sudo /etc/init.d/nginx reload', function(error, stdout, stderr) {
          if(error) {
            logger.error('Error reloading nginx: ', error, stderr, stdout);
          }

          logger.info('Reloading nginx... ', stdout + ' ' + stderr);

          writeCallback(null);
        });
      }.bind(this));
    }.bind(this);

    fs.exists(nginxPath, function(exists) {
      if(exists) {
        fs.readFile(nginxPath, function(error, data) {
          var oldContent = data.toString();
          if(oldContent === newContent) {
            logger.info('Nginx config already up-to-date - skipping...');
            writeCallback(null);
          } else {
            write();
          }
        });
      } else {
        write();
      }
    });
  },

  writeGatekeeperConfig: function(writeCallback) {
    logger.info('Writing gatekeeper config...');

    config.reset({ quiet: true });

    if(this.lastConfig) {
      var values = clone(this.lastConfig.config);
      traverse(values).forEach(function(value) {
        if(value && value._bsontype && value._bsontype === 'ObjectID') {
          this.update(value.toHexString());
        }
      });

      _.extend(values, {
        version_id: this.lastConfig._id.toHexString(),
        version: this.lastConfig.version,
      });

      config.updateRuntime({ apiUmbrella: values }, { quiet: true });
    } else {
      logger.info('No gatekeeper config changes found - reseting...');
    }

    config.saveRuntime(function() {
      logger.info('Gatekeeper config written...');
      writeCallback(null);
    });
  },

  handleWriteConfigs: function() {
    logger.info('Config files written...');

    this.emit('reloaded');
    this.poll();
  },

  // FIXME: We're polling for configuration changes from mongoid right now. Not
  // polling would obviously be better. Using something like Zookeeper might be<commit_msg>Better error logging during config reloading.<commit_after>'use strict';

var _ = require('underscore'),
    async = require('async'),
    clone = require('clone'),
    config = require('./config'),
    events = require('events'),
    exec = require('child_process').exec,
    fs = require('fs'),
    handlebars = require('handlebars'),
    logger = require('./logger'),
    path = require('path'),
    traverse = require('traverse'),
    util = require('util');

var ConfigBuilder = function() {
  this.initialize.apply(this, arguments);
};

util.inherits(ConfigBuilder, events.EventEmitter);
_.extend(ConfigBuilder.prototype, {
  initialize: function(mongo, readyCallback) {
    this.mongo = mongo;

    var templatePath = path.resolve(__dirname, '../templates/nginx.hbs');
    var templateContent = fs.readFileSync(templatePath);
    this.nginxTemplate = handlebars.compile(templateContent.toString());

    this.once('reloaded', readyCallback);

    // Force a reload on initial boot (in case internal changes have been made
    // that would affect the output even if the configuration values haven't
    // changed).
    this.reload({ force: true });
  },

  reload: function(options) {
    var collection = this.mongo.collection('config_versions');
    var cursor = collection.find().sort({ version: -1 }).limit(1);
    cursor.toArray(this.handleFetchConfigVersion.bind(this, options));
  },

  handleFetchConfigVersion: function(options, error, configVersions) {
    if(configVersions) {
      this.lastConfig = configVersions[0];
    }

    this.apis = [];
    if(this.lastConfig && this.lastConfig.config.apis) {
      this.apis = this.lastConfig.config.apis;
    }

    var lastId = null;
    if(this.lastConfig && this.lastConfig._id) {
      lastId = this.lastConfig._id.toHexString();
    }

    var activeId = config.get('version_id');

    if((options && options.force) || (this.lastConfig && lastId !== activeId)) {
      logger.info('Writing new config...');
      this.writeConfigs();
    } else {
      this.poll();
    }
  },

  writeConfigs: function() {
    // Write nginx config's first.
    async.series([
      this.writeNginxConfig.bind(this),
      this.writeGatekeeperConfig.bind(this),
    ], this.handleWriteConfigs.bind(this));
  },

  writeNginxConfig: function(writeCallback) {
    logger.info('Writing nginx config...');

    var apis = _.reject(clone(this.apis), function(api) {
      return (!api.servers || api.servers.length === 0);
    });

    var frontendHosts = _.reject(_.uniq(_.pluck(apis, 'frontend_host')), function(host) {
      return !host;
    });

    apis.forEach(function(api) {
      if(api.balance_algorithm === 'least_conn' || api.balance_algorithm === 'ip_hash') {
        api.defaultBalance = false;
      } else {
        api.defaultBalance = true;
      }
    });

    var newContent = this.nginxTemplate({
      logDir: config.logDir,
      hosts: frontendHosts,
      apis: apis,
    });

    var nginxPath = path.join(config.configDir, 'nginx.conf');

    var write = function() {
      fs.writeFile(nginxPath, newContent, function(error) {
        if(error) {
          logger.error('Error writing nginx config: ', error);
          writeCallback(error);
          return false;
        }

        logger.info('Nginx config written...');

        this.emit('nginx');

        logger.info('Reloading nginx...');
        exec('sudo /etc/init.d/nginx configtest && sudo /etc/init.d/nginx reload', function(error, stdout, stderr) {
          if(error) {
            logger.error('Error reloading nginx: ', error, stderr, stdout);
          }

          logger.info('Nginx reloaded... ', stdout + ' ' + stderr);

          writeCallback(null);
        });
      }.bind(this));
    }.bind(this);

    fs.exists(nginxPath, function(exists) {
      if(exists) {
        fs.readFile(nginxPath, function(error, data) {
          var oldContent = data.toString();
          if(oldContent === newContent) {
            logger.info('Nginx config already up-to-date - skipping...');
            writeCallback(null);
          } else {
            write();
          }
        });
      } else {
        write();
      }
    });
  },

  writeGatekeeperConfig: function(writeCallback) {
    logger.info('Writing gatekeeper config...');

    config.reset({ quiet: true });

    if(this.lastConfig) {
      var values = clone(this.lastConfig.config);
      traverse(values).forEach(function(value) {
        if(value && value._bsontype && value._bsontype === 'ObjectID') {
          this.update(value.toHexString());
        }
      });

      _.extend(values, {
        version_id: this.lastConfig._id.toHexString(),
        version: this.lastConfig.version,
      });

      config.updateRuntime({ apiUmbrella: values }, { quiet: true });
    } else {
      logger.info('No gatekeeper config changes found - reseting...');
    }

    config.saveRuntime(function(error) {
      if(error) {
        logger.error('Error writing gatekeeper config: ', error);
        writeCallback(error);
        return false;
      }

      logger.info('Gatekeeper config written...');
      writeCallback(null);
    });
  },

  handleWriteConfigs: function(error) {
    if(error) {
      logger.error('Error writing configs: ', error);
    } else {
      logger.info('Config files written...');
      this.emit('reloaded');
    }

    this.poll();
  },

  // FIXME: We're polling for configuration changes from mongoid right now. Not
  // polling would obviously be better. Using something like Zookeeper might be
{'fraction_non_alphanumeric': 0.1497090294724986, 'fraction_numerical': 0.0007508916838746011, 'mean_word_length': 2.7086668987121474, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 3, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '7447875', 'n_tokens_mistral': 3213, 'n_tokens_neox': 3094, 'n_words': 632}