module.exports = function(io, authentication) { var pty = require('pty.js'); var proc = require('child_process'); var docker = require(__dirname+'/../docker')(); var module={}; var idtoname = {}; function broadcast(signal, data) { console.log("<= signal: "+signal); io.sockets.emit(signal, data); } function exec(cmd, callback) { if (cmd.length>40) { console.log("== "+cmd.slice(0, 30+cmd.slice(30).indexOf(' '))+" ..."); } else { console.log("== "+cmd); } proc.exec(cmd, {maxBuffer: 10*1024*1024}, callback); } function fail(txt, data) { console.log("** "+txt, data); } var oldcontainer = null; function containerinspect(error, stdout, stderr) { if (error || stderr) return fail("inspect docker containers failed", { error: error, stderr: stderr, stdout: stdout }); idtoname = {}; JSON.parse(stdout).forEach(function(n) { if (n.State.Running) idtoname[n.Id] = n.Name.replace(/^\//, ''); }); if (oldcontainer && oldcontainer==stdout) return; // do not resend same containers oldcontainer = stdout; broadcast("containers", stdout); } function containerlist(error, stdout, stderr) { if (error || stderr) return fail("list docker containers failed", { error: error, stderr: stderr, stdout: stdout }); exec("docker inspect "+stdout.trim().replace(/\n/g, " "), containerinspect); } function updatecontainers(error, stdout, stderr) { if (error || stderr) return fail("update docker container failed", { error: error, stderr: stderr, stdout: stdout }); exec("docker ps -aq --no-trunc ", containerlist); } var oldimage = null; function imageinspect(error, stdout, stderr) { if (error || stderr) return fail("inspect docker images failed", { error: error, stderr: stderr, stdout: stdout }); if (oldimage && oldimage==stdout) return; // do not resend same images oldimage = stdout; broadcast("images", stdout); } function imagelist(error, stdout, stderr) { if (error || stderr) return fail("list docker images failed", { error: error, stderr: stderr, stdout: stdout }); exec("docker inspect "+stdout.trim().replace(/\n/g, " "), imageinspect); } function updateimages(error, stdout, stderr) { if (error || stderr) return fail("update docker images failed", { error: error, stderr: stderr, stdout: stdout }); exec("docker images -q --no-trunc", imagelist); } function connection(socket, userdata) { console.log("=> new connection from "+userdata.username); function emit(signal, data, info) { if (typeof data == 'string' && !data.match("\n")) { console.log("<- signal: "+signal+"("+data+")"); } else { console.log("<- signal: "+signal); } if (info) console.log(info); socket.emit(signal, data); } function fail(txt, data) { console.log("** "+txt, data); emit("fail", txt); } function modify(cmd, name) { if (!name.match(/^[a-z0-9][-_:.+a-z0-9]*$/i)) return this.fail("illegal instance name"); exec("docker "+cmd+" "+name, updatecontainers); } function createContainer(cmds) { if (cmds.length>0) exec(cmds.shift(), function(error, stdout, stderr) { if (error || stderr) return this.fail("create container failed", { error: error, stderr: stderr, stdout: stdout }); createContainer(cmds); }) else updatecontainers(); } function containers() { console.log("-> containers"); if (oldcontainer) emit("containers", oldcontainer); else updatecontainers(); } function images() { console.log("-> images"); if (oldimage) emit("images", oldimage); else updateimages(); } function start(name) { console.log("-> start("+name+")"); modify("start", name); } function stop(name) { console.log("-> stop("+name+")"); modify("stop", name); } function pause(name) { console.log("-> pause("+name+")"); modify("pause", name); } function unpause(name) { console.log("-> unpause("+name+")"); modify("unpause", name); } function remove(name) { console.log("-> remove("+name+")"); modify("rm", name); } function create(data) { console.log("-> create"); var d = new docker.Docker(); var dc = new d.Containers(); createContainer(dc.creation(data)); } function logs(name) { console.log("-> logs("+name+")"); var l = proc.spawn("docker", ["logs", "-f", name]) .on('close', function(code) { emit('logs', {name: name, type: 'done'}); }); l.stdout.on('data', function(data) { emit('logs', {name: name, type: 'stdout', text: data.toString()}); }); l.stderr.on('data', function(data) { emit('logs', {name: name, type: 'stderr', text: data.toString()}); }); } var bash_connections = {}; function new_bash(name) { if (!name.match(/^[a-z0-9][-_:.+a-z0-9]*$/i)) return this.fail("illegal instance name"); if (bash_connections[name]) return; bash_connections[name] = pty.spawn("docker", ["exec", "-it", name, "bash", "-i"]); bash_connections[name].stdout.on('data', function(data) { emit('bash-data', {name: name, type: 'stdout', text: data.toString()}); }); } function bash_start(name) { console.log("-> bash-start("+name+")"); new_bash(name); } function bash_input(data) { console.log("-> bash-input("+data.name+", "+data.text+")"); new_bash(data.name); bash_connections[data.name].stdin.resume(); bash_connections[data.name].stdin.write(data.text); } function bash_end(name, text) { console.log("-> bash-end("+name+")"); if (!bash_connections[name]) return; bash_connections[name].stdin.close(); delete bash_connections[name]; bash_connections[name] = null; } socket .on("containers", containers) .on("images", images) .on("start", start) .on("stop", stop) .on("pause", pause) .on("unpause", unpause) .on("remove", remove) .on("create", create) .on('logs', logs) .on('bash-start', bash_start) .on('bash-input', bash_input) .on('bash-end', bash_end); } function stats() { var res = {}; var fs = require('fs'); var async = require('async'); var base = "/sys/fs/cgroup/"; var dataPoints = { "cpuacct": [ "usage" ], "memory": [ "usage_in_bytes", "limit_in_bytes", "max_usage_in_bytes" ] }; async.forEachOf(idtoname, function(name, id, cb1) { res[name] = {}; async.forEachOf(dataPoints, function(val1, category, cb2) { res[name][category] = {}; async.each(val1, function(element, cb3) { var file = base + category + '/docker/' + id + '/' + category + '.' + element; fs.readFile(file, 'utf8', function(err, data) { res[name][category][element] = { date: (new Date()).getTime(), data: {} }; if (err) {cb3(); console.log(err); return} data.trim().split('\n').forEach(function(v, i) { var vals = v.split(' '); switch (vals.length) { case 1: res[name][category][element].data = parseInt(vals[0]); break; case 2: res[name][category][element].data[vals[0]] = parseInt(vals[1]); break; } }); cb3(); }); }, function(err) { cb2(); }); }, function(err) { cb1(); }); }, function(err) { if (err) return; broadcast("stats", res); }); } // Handle Connection require('socketio-auth')(io, { authenticate: function (socket, data, callback) { console.log("=> authenticate: ", data.username); //get credentials sent by the client var username = data.username; var password = data.password; authentication(data.username, data.password, function() {console.log("####LOGIN-SUCESS####");callback(null, true)}, function() {console.log("####LOGIN-FAIL####");callback(new Error("wrong credentials"))}); }, postAuthenticate: connection, timeout: "none" }); // Regular Update of Stats setInterval(function() { stats(); }, 1000); // Regular Update of Images and Containers setInterval(function() { updateimages(); updatecontainers(); }, 10000); return module; }