UNPKG

11.8 kBJavaScriptView Raw
1// Generated by CoffeeScript 1.3.3
2(function() {
3 var Crossover, cluster, domain, express, fs, os, rest, spawn, temp, util, uuid, wrench,
4 __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
5
6 cluster = require("cluster");
7
8 domain = require("domain");
9
10 express = require("express");
11
12 fs = require("fs");
13
14 os = require("os");
15
16 rest = require("restler");
17
18 spawn = require("child_process").spawn;
19
20 temp = require("temp");
21
22 util = require("util");
23
24 uuid = require("node-uuid");
25
26 wrench = require("wrench");
27
28 module.exports.version = require("../package.json").version;
29
30 Crossover = (function() {
31
32 function Crossover(options) {
33 this.options = options;
34 this.execute = __bind(this.execute, this);
35
36 this.slave = __bind(this.slave, this);
37
38 this.master = __bind(this.master, this);
39
40 this.listen = __bind(this.listen, this);
41
42 this.test_worker = __bind(this.test_worker, this);
43
44 this.spawn_worker = __bind(this.spawn_worker, this);
45
46 this.prepare_npm = __bind(this.prepare_npm, this);
47
48 this.prepare_worker = __bind(this.prepare_worker, this);
49
50 this.app = null;
51 this.listening = false;
52 this.stopping = false;
53 this.workers = [];
54 this.root = temp.mkdirSync("crossover");
55 }
56
57 Crossover.prototype.prepare_worker = function(slug, env, cb) {
58 var target,
59 _this = this;
60 target = this.root + "/" + uuid.v1();
61 this.log("preparing worker: " + slug);
62 return this.read_env(env, function(env) {
63 if (slug.substring(0, 4) === "http") {
64 return rest.get(slug, {
65 decoding: "buffer"
66 }).on("complete", function(result) {
67 return fs.mkdir(target, function(err) {
68 return fs.writeFile(target + "/app.tgz", result, "binary", function(err) {
69 return _this.execute("tar", ["xzf", "app.tgz"], {
70 cwd: target
71 }, function() {
72 return _this.prepare_npm(target, function(target) {
73 return cb(target, env);
74 });
75 });
76 });
77 });
78 });
79 } else {
80 wrench.copyDirSyncRecursive(slug, target);
81 return _this.prepare_npm(target, function(target) {
82 return cb(target, env);
83 });
84 }
85 });
86 };
87
88 Crossover.prototype.read_env = function(env, cb) {
89 var _this = this;
90 if (!env) {
91 return cb({});
92 } else if (env.substring(0, 4) === "http") {
93 return rest.get(env).on("complete", function(result) {
94 return cb(_this.read_env_data(result));
95 });
96 } else {
97 return fs.readFile(env, function(err, data) {
98 return cb(_this.read_env_data(data.toString()));
99 });
100 }
101 };
102
103 Crossover.prototype.read_env_data = function(data) {
104 var env, line, parts, _i, _len, _ref;
105 env = {};
106 _ref = data.split("\n");
107 for (_i = 0, _len = _ref.length; _i < _len; _i++) {
108 line = _ref[_i];
109 parts = line.split("=");
110 env[parts.shift()] = parts.join("=");
111 }
112 return env;
113 };
114
115 Crossover.prototype.prepare_npm = function(target, cb) {
116 var _this = this;
117 this.log("resolving dependencies");
118 return this.execute("npm", ["install"], {
119 cwd: target
120 }, function() {
121 return _this.execute("npm", ["rebuild"], {
122 cwd: target
123 }, function() {
124 return cb(target);
125 });
126 });
127 };
128
129 Crossover.prototype.spawn_worker = function(dir, cb) {
130 var old_cwd, old_env, worker;
131 old_env = process.env;
132 old_cwd = process.cwd();
133 process.env = this.env || {};
134 process.chdir(dir);
135 worker = cluster.fork();
136 process.chdir(old_cwd);
137 process.env = old_env;
138 this.log("forked worker " + worker.process.pid);
139 return worker.on("message", function(msg) {
140 if (msg.cmd === "ready") {
141 this.send({
142 cmd: "start",
143 dir: dir
144 });
145 if (cb) {
146 return cb(this);
147 }
148 }
149 });
150 };
151
152 Crossover.prototype.test_worker = function(dir, env, cb) {
153 var old_cwd, old_env, worker;
154 old_env = process.env;
155 old_cwd = process.cwd();
156 process.env = this.env || {};
157 process.chdir(dir);
158 worker = cluster.fork();
159 process.chdir(old_cwd);
160 process.env = old_env;
161 this.log("forked worker " + worker.process.pid);
162 return worker.on("message", function(msg) {
163 if (msg.cmd === "ready") {
164 return this.send({
165 cmd: "test",
166 dir: dir
167 });
168 } else if (msg.cmd === "success") {
169 return cb(null);
170 } else if (msg.cmd === "failure") {
171 return cb(msg.err);
172 }
173 });
174 };
175
176 Crossover.prototype.listen = function(slug, env, port) {
177 var _this = this;
178 if (!slug) {
179 this.error("Must specify a slug.");
180 }
181 if (cluster.isMaster) {
182 this.admin().listen(this.options["managementPort"], function() {
183 return console.log("[master] listening on management port: " + _this.options["managementPort"]);
184 });
185 return this.prepare_worker(slug, env, function(slug, env) {
186 _this.slug = slug;
187 _this.env = env;
188 return _this.master();
189 });
190 } else {
191 return this.slave(port);
192 }
193 };
194
195 Crossover.prototype.master = function() {
196 var num, _i, _ref,
197 _this = this;
198 for (num = _i = 1, _ref = this.options.concurrency; 1 <= _ref ? _i <= _ref : _i >= _ref; num = 1 <= _ref ? ++_i : --_i) {
199 this.spawn_worker(this.slug, function(worker) {
200 return _this.workers.push(worker);
201 });
202 }
203 return cluster.on("exit", function(worker) {
204 _this.log("worker " + worker.pid + " died");
205 _this.workers.splice(_this.workers.indexOf(worker), 1);
206 return _this.spawn_worker(_this.slug, function(worker) {
207 return _this.workers.push(worker);
208 });
209 });
210 };
211
212 Crossover.prototype.slave = function(port) {
213 var _this = this;
214 process.on("message", function(msg) {
215 switch (msg.cmd) {
216 case "start":
217 _this.log("starting app");
218 _this.listening = false;
219 _this.app = require(msg.dir + "/index");
220 _this.app.on("close", function() {
221 _this.log("requests completed, exiting");
222 return process.exit(0);
223 });
224 return _this.app.listen(port, function() {
225 _this.log("listening on port: " + port);
226 return _this.listening = true;
227 });
228 case "test":
229 _this.log("launching test app from slug");
230 try {
231 _this.app = require(msg.dir + "/index");
232 return _this.app.listen(0, function() {
233 return process.send({
234 cmd: "success"
235 });
236 });
237 } catch (err) {
238 return process.send({
239 cmd: "failure",
240 err: err.toString()
241 });
242 }
243 break;
244 case "stop":
245 if (!_this.stopping) {
246 _this.stopping = true;
247 if (_this.listening) {
248 _this.log("turning off new connections to app");
249 _this.app.close();
250 return setTimeout((function() {
251 _this.log("giving up on remaining connections");
252 return process.exit(0);
253 }), 30000);
254 } else {
255 _this.log("app not listening yet, exiting");
256 return process.exit(0);
257 }
258 }
259 }
260 });
261 return process.send({
262 cmd: "ready"
263 });
264 };
265
266 Crossover.prototype.admin = function() {
267 var admin,
268 _this = this;
269 admin = require("express").createServer(express.bodyParser(), express.basicAuth("admin", (this.options['auth'] || "").toString()));
270 admin.get("/status", function(req, res) {
271 res.contentType("application/json");
272 return res.send(JSON.stringify({
273 version: module.exports.version
274 }));
275 });
276 admin.post("/release", function(req, res) {
277 var dom;
278 dom = domain.create();
279 dom.on("error", function(err) {
280 _this.log("failed to launch: " + err);
281 res.writeHead(403);
282 return res.end("error");
283 });
284 return dom.run(function() {
285 var env, slug;
286 slug = req.body.slug;
287 env = req.body.env;
288 _this.log("releasing: " + slug + " " + env);
289 return _this.prepare_worker(slug, env, function(slug, env) {
290 return _this.test_worker(slug, env, function(err) {
291 var worker, _i, _len, _ref;
292 if (err) {
293 _this.log("error in slug, aborting spawn: " + err);
294 res.writeHead(403);
295 return res.end("error");
296 } else {
297 _this.log("test successful");
298 _this.slug = slug;
299 _this.env = env;
300 _ref = _this.workers;
301 for (_i = 0, _len = _ref.length; _i < _len; _i++) {
302 worker = _ref[_i];
303 worker.send({
304 cmd: "stop"
305 });
306 }
307 return res.send("ok");
308 }
309 });
310 });
311 });
312 });
313 return admin;
314 };
315
316 Crossover.prototype.format_log = function(args) {
317 var arg, formatted, pid, _i, _len;
318 pid = cluster.isMaster ? "master" : "worker:" + process.pid;
319 formatted = ["[" + pid + "]"];
320 for (_i = 0, _len = args.length; _i < _len; _i++) {
321 arg = args[_i];
322 formatted.push(arg);
323 }
324 return formatted;
325 };
326
327 Crossover.prototype.log = function() {
328 return console.log.apply(console, this.format_log(arguments));
329 };
330
331 Crossover.prototype.error = function() {
332 var arg, args, _i, _len;
333 args = ["[" + process.pid + "]"];
334 for (_i = 0, _len = arguments.length; _i < _len; _i++) {
335 arg = arguments[_i];
336 args.push(arg);
337 }
338 console.error.apply(console, args);
339 return process.exit(1);
340 };
341
342 Crossover.prototype.execute = function(command, args, options, cb) {
343 var child,
344 _this = this;
345 child = spawn(command, args, options);
346 child.stdout.on("data", function(data) {
347 var part, _i, _len, _ref, _results;
348 _ref = data.toString().replace(/\n$/, '').split("\n");
349 _results = [];
350 for (_i = 0, _len = _ref.length; _i < _len; _i++) {
351 part = _ref[_i];
352 if (process.env.DEBUG) {
353 _results.push(console.log("[compiler] " + part));
354 } else {
355 _results.push(void 0);
356 }
357 }
358 return _results;
359 });
360 child.stderr.on("data", function(data) {
361 var part, _i, _len, _ref, _results;
362 _ref = data.toString().replace(/\n$/, '').split("\n");
363 _results = [];
364 for (_i = 0, _len = _ref.length; _i < _len; _i++) {
365 part = _ref[_i];
366 if (process.env.DEBUG) {
367 _results.push(console.log("[compiler] " + part));
368 } else {
369 _results.push(void 0);
370 }
371 }
372 return _results;
373 });
374 return child.on("exit", function(code) {
375 return cb(code);
376 });
377 };
378
379 return Crossover;
380
381 })();
382
383 module.exports.create = function(slug) {
384 return new Crossover(slug);
385 };
386
387}).call(this);