UNPKG

knowhow-server

Version:

A personal workflow engine that can manage and use any network connected host

431 lines (387 loc) 10.8 kB
/** * Utility class for common agent functions */ var KnowhowShell = require('knowhow-shell'); //var KnowhowShell = require('../../knowhow-shell/knowhow-shell'); //var ttyPool = new require('../../knowhow-shell/tty-pool')(2,10); //var ttyPool = new require('knowhow-shell/tty-pool')(2,10); var KHShell = new KnowhowShell() var Datastore = require('nedb'); var os = require("os"); var crypto = require('crypto'); var logger=require('../log-control').logger; var async = require('async'); var http = require('http'); db = new Datastore({ filename: './agents.db', autoload: true }); exports.db = db; /** * Updates an agent in the database */ var updateAgent = function(agent, callback) { loadAgent(agent, function(err, loadedAgent) { if (!loadedAgent) { loadedAgent = agent; } else { var props = Object.getOwnPropertyNames(agent); props.forEach(function(prop){ //logger.debug('updateAgent: updating property: agent.'+prop); loadedAgent[prop]=agent[prop]; }); } if (agent.message) { loadedAgent.message = agent.message; } if (agent.status) { loadedAgent.status = agent.status; } if (agent.progress) { loadedAgent.progress = agent.progress; } db.update({ '_id': loadedAgent._id}, loadedAgent, function(err,docs) { if(err) { callback(err); } else if (callback) { callback(undefined,docs[0]); } }); }); }; exports.updateAgent = updateAgent; /** * checks if an agent is alive * * @param the agent to check * @param callback to run afterwards */ var heartbeat = function(agent, callback) { if (this.agent) { agent=this.agent; } //logger.debug('heartbeat checking status for: '+agent.user+'@'+agent.host+':'+agent.port); var options = { host : agent.host, port : agent.port, path : '/api/agentInfo', method : 'GET', headers: { 'Content-Type': 'application/json' } }; var request = http.request(options, function(res) { //logger.debug("processing status response: "); var output = ''; //logger.debug(options.host + ' ' + res.statusCode); res.setEncoding('utf8'); res.on('data', function (chunk) { output += chunk; }); res.on('end', function() { //logger.info("done."); obj = JSON.parse(output); console.log(output); logger.debug("agent status check: "+obj.status); if (obj.status == 'READY') { agent.progress=0; agent.message=''; callback(undefined, agent); } else { callback(new Error("unable to connect"),agent); } }); //res.end(); }); request.on('error', function(er) { //logger.error(er.stack); logger.error('heartbeat could not connect to agent: '+agent.user+'@'+agent.host+':'+agent.port, er); callback(new Error("unable to connect"),agent); }); request.end(); }; exports.heartbeat = heartbeat; /** * Lists all agents */ function listAgents(serverInfo, callback) { db.find({}, function(err, docs) { if (err) { callback(err); return; } //logger.debug('found '+docs.length+' agents'); //logger.debug(docs); // docs.forEach(function(agent) { // console.log(agent); // }); var agentSort = function (agent1, agent2) { var agentName1 = agent1.host+agent1.port+agent1.user; var agentName2 = agent2.host+agent2.port+agent2.user return (agentName1 > agentName2); } var result = docs.sort(agentSort); for (index in docs) { if (serverInfo.newestVersions && ((docs[index].shellversion < serverInfo.newestVersions['knowhow-shell']) || !docs[index].shellversion) ) { docs[index].shellUpgradeAvailable = true; if (!docs[index].message) { docs[index].message = "knowhow-shell upgrade available"; } } else { delete docs[index].shellUpgradeAvailable } if (serverInfo.newestVersions && ((docs[index].version < serverInfo.newestVersions['knowhow-agent']) || !docs[index].version) ) { docs[index].agentUpgradeAvailable = true; if (!docs[index].message) { docs[index].message = "agent upgrade available"; } } else { delete docs[index].agentUpgradeAvailable; } } if (callback) { callback(undefined, result); } }); }; exports.listAgents = listAgents; /** * Loads an agent from the database * @param agent * @param callback */ function loadAgent(agent, callback) { if (!agent) { logger.error("no agent data provided to loadAgent"); if (callback) { callback(new Error("no agent provided")); } return; } var queryParams = {}; //order of the query params matters if (agent._id) { queryParams._id=agent._id; } else { queryParams = {$and: []}; if (agent.user) { queryParams.$and.push({user: agent.user}); } if (agent.port) { queryParams.$or=[{port: +agent.port}, {port: ""+agent.port} ]; } if (agent.host) { queryParams.$and.push({host: agent.host}); } } //logger.debug("query agents:"); //console.log(queryParams); db.find(queryParams, function(err, doc) { logger.debug(doc); if (err) { callback(err); return; } // docs.forEach(function(agent) { // console.log(agent); // }); logger.debug("found: "+doc.length); if (callback) { callback(undefined, doc[0]); } }); }; exports.loadAgent = loadAgent; /** * looks up a password for a user * * @param userName - the user to look up * @param callback */ function lookupPasswordForUser(userName, callback) { if (!userName) { callback(new Error("no username provided")); return; } logger.debug("searching for password for: "+userName); db.find({user: userName, passwordEnc: { $exists: true } }, function(err, doc) { logger.debug("password search complete"); if (err) { logger.error(err.stack) callback(err); return; } //console.log("found "+doc.length); //doc.forEach(function(agent) { // console.log(agent); //}); if (callback) { if (doc[0]) callback(undefined, doc[0].passwordEnc); else callback(new Error("unable to find password for user: "+userName)); } }); }; exports.lookupPasswordForUser = lookupPasswordForUser; /** * Checks if an agent id exists * * @param the agentId * @param callback */ exports.doesAgentIdExist = function(agentId, callback) { var queryParams = {} queryParams._id = agentId; db.findOne(queryParams, function(err, doc) { if (err) { if (callback) { callback(err); } return false; } if (!doc) { if (callback) { callback(new Error("Agent does not exist")); } return false; } // docs.forEach(function(agent) { // console.log(agent); // }); if (callback) { callback(undefined, doc); } return true; }); } var deleteAgent = function(agent, callback) { if (!agent) { callback(new Error("deleteAgent: no agent provided")); return; } var gotResponse = false; loadAgent( agent, function(err, loadedAgent) { if (!loadedAgent) { //agent.message = 'agent does not exist'; //eventEmitter.emit('agent-error',agent); logger.error("agent does not exist: "+agent.user+"@"+agent.host+":"+agent.port); callback(new Error("agent does not exist: "+agent.host+":"+agent.port)); return; } if (err) { logger.error(err.stack); callback(err); return; } logger.info("deleting agent: "+loadedAgent.host+":"+loadedAgent.port); var options = { host : loadedAgent.host, port : loadedAgent.port, path : '/delete', method : 'GET', headers: { 'Content-Type': 'application/json' } }; var request = http.request(options, function(response) { gotResponse = true; logger.debug("processing delete response: "); var output = ''; //logger.debug(options.host + ' ' + response.statusCode); response.setEncoding('utf8'); response.on('data', function (chunk) { output += chunk; }); response.on('end', function() { logger.info("request to delete done."); //var obj = JSON.parse(output); //logger.info(obj.status); db.remove({ _id: loadedAgent._id }, {}, function (err, numRemoved) { callback(err, numRemoved); eventEmitter.emit('agent-delete',agent); }); }); }); request.on('error', function(er) { logger.error('no agent running on: '+loadedAgent.user+'@'+loadedAgent.host+':'+loadedAgent.port+" "+er.message); db.remove({ _id: loadedAgent._id }, {}, function (err, numRemoved) { if (err) { logger.error(err.stack); } logger.info("removed agent " +loadedAgent.user+'@'+loadedAgent.host+':'+loadedAgent.port+" num removed="+numRemoved); if (!gotResponse) { callback(new Error("unable to contact agent, but removed from internal database.")); } eventEmitter.emit('agent-delete',loadedAgent); }); }); request.end(); }); }; exports.deleteAgent = deleteAgent; /** * executes a job url on a particular agent using ssh */ exports.executeJob = function(jobURL, agent, callback) { fileControl.load(jobURL, function(err,content) { if (err) { main_callback(err); return; } try { job = JSON.parse(content); if (agent.login && !agent.user) { job.script.env.USER=agent.login; } else { job.script.env.USER=agent.user; } if (agent.passwordEnc) { job.script.env.PASSWORD=decrypt(agent.passwordEnc,serverInfo.cryptoKey); } else if (agent.password) { job.script.env.PASSWORD=agent.password; } job.script.env.HOST=agent.host; if (agent.ip) { job.script.env.HOST=agent.ip; } job.script.env.LOGIN=agent.login; job.script.env.PORT=agent.port; job.script.env.AGENT_ID=agent._id; } catch(err) { callback(err); return; } KHShell.executeJobAsSubProcess(job,function(err) { if (err) { callback(err); } else { callback(); } }); }); }; /** * Encrypts a value using an encryption key * * @param text to encrypt * @param cryptoKey - the crypto key to use */ function encrypt(text, cryptoKey){ var cipher = crypto.createCipher('aes-256-cbc',cryptoKey) var crypted = cipher.update(text,'utf8','hex') crypted += cipher.final('hex'); return crypted; } /** * Decrypts a value using an encrpytion key * * @param text to decrypt * @param cryptoKEy - the crypto key to use */ function decrypt(text, cryptoKey){ logger.debug("deciphering: "+text+" with key: "+cryptoKey); var decipher = crypto.createDecipher('aes-256-cbc',cryptoKey) var dec = decipher.update(text,'hex','utf8') dec += decipher.final('utf8'); return dec; } exports.encrypt = encrypt; exports.decrypt = decrypt;