ringleader 0.0.2 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +61 -22
- data/assets/app.js +106 -0
- data/assets/backbone-min.js +38 -0
- data/assets/bootstrap/css/bootstrap-responsive.css +815 -0
- data/assets/bootstrap/css/bootstrap-responsive.min.css +9 -0
- data/assets/bootstrap/css/bootstrap.css +4983 -0
- data/assets/bootstrap/css/bootstrap.min.css +9 -0
- data/assets/bootstrap/img/glyphicons-halflings-white.png +0 -0
- data/assets/bootstrap/img/glyphicons-halflings.png +0 -0
- data/assets/bootstrap/js/bootstrap.js +1825 -0
- data/assets/bootstrap/js/bootstrap.min.js +6 -0
- data/assets/favicon.ico +0 -0
- data/assets/index.html +129 -0
- data/assets/jquery-1.7.2.min.js +4 -0
- data/assets/jquery.mustache.js +636 -0
- data/assets/top_hat.png +0 -0
- data/assets/underscore-min.js +32 -0
- data/dev_scripts/Procfile +3 -2
- data/dev_scripts/stubborn.rb +4 -0
- data/dev_scripts/test.yml +4 -2
- data/dev_scripts/wait_fork_tree.rb +15 -0
- data/dev_scripts/wait_test.rb +25 -0
- data/dev_scripts/webserver.rb +4 -0
- data/lib/ringleader.rb +7 -1
- data/lib/ringleader/app.rb +88 -121
- data/lib/ringleader/app_serializer.rb +15 -0
- data/lib/ringleader/cli.rb +43 -56
- data/lib/ringleader/config.rb +58 -15
- data/lib/ringleader/controller.rb +30 -0
- data/lib/ringleader/process.rb +145 -0
- data/lib/ringleader/server.rb +116 -0
- data/lib/ringleader/version.rb +1 -1
- data/lib/ringleader/wait_for_exit.rb +1 -1
- data/ringleader.gemspec +1 -0
- data/screenshot.png +0 -0
- data/spec/fixtures/config.yml +1 -1
- data/spec/fixtures/no_app_port.yml +5 -0
- data/spec/fixtures/no_server_port.yml +6 -0
- data/spec/fixtures/rvm.yml +6 -0
- data/spec/ringleader/config_spec.rb +25 -2
- metadata +48 -3
- data/lib/ringleader/app_proxy.rb +0 -61
data/assets/top_hat.png
ADDED
Binary file
|
@@ -0,0 +1,32 @@
|
|
1
|
+
// Underscore.js 1.3.3
|
2
|
+
// (c) 2009-2012 Jeremy Ashkenas, DocumentCloud Inc.
|
3
|
+
// Underscore is freely distributable under the MIT license.
|
4
|
+
// Portions of Underscore are inspired or borrowed from Prototype,
|
5
|
+
// Oliver Steele's Functional, and John Resig's Micro-Templating.
|
6
|
+
// For all details and documentation:
|
7
|
+
// http://documentcloud.github.com/underscore
|
8
|
+
(function(){function r(a,c,d){if(a===c)return 0!==a||1/a==1/c;if(null==a||null==c)return a===c;a._chain&&(a=a._wrapped);c._chain&&(c=c._wrapped);if(a.isEqual&&b.isFunction(a.isEqual))return a.isEqual(c);if(c.isEqual&&b.isFunction(c.isEqual))return c.isEqual(a);var e=l.call(a);if(e!=l.call(c))return!1;switch(e){case "[object String]":return a==""+c;case "[object Number]":return a!=+a?c!=+c:0==a?1/a==1/c:a==+c;case "[object Date]":case "[object Boolean]":return+a==+c;case "[object RegExp]":return a.source==
|
9
|
+
c.source&&a.global==c.global&&a.multiline==c.multiline&&a.ignoreCase==c.ignoreCase}if("object"!=typeof a||"object"!=typeof c)return!1;for(var f=d.length;f--;)if(d[f]==a)return!0;d.push(a);var f=0,g=!0;if("[object Array]"==e){if(f=a.length,g=f==c.length)for(;f--&&(g=f in a==f in c&&r(a[f],c[f],d)););}else{if("constructor"in a!="constructor"in c||a.constructor!=c.constructor)return!1;for(var h in a)if(b.has(a,h)&&(f++,!(g=b.has(c,h)&&r(a[h],c[h],d))))break;if(g){for(h in c)if(b.has(c,h)&&!f--)break;
|
10
|
+
g=!f}}d.pop();return g}var s=this,I=s._,o={},k=Array.prototype,p=Object.prototype,i=k.slice,J=k.unshift,l=p.toString,K=p.hasOwnProperty,y=k.forEach,z=k.map,A=k.reduce,B=k.reduceRight,C=k.filter,D=k.every,E=k.some,q=k.indexOf,F=k.lastIndexOf,p=Array.isArray,L=Object.keys,t=Function.prototype.bind,b=function(a){return new m(a)};"undefined"!==typeof exports?("undefined"!==typeof module&&module.exports&&(exports=module.exports=b),exports._=b):s._=b;b.VERSION="1.3.3";var j=b.each=b.forEach=function(a,
|
11
|
+
c,d){if(a!=null)if(y&&a.forEach===y)a.forEach(c,d);else if(a.length===+a.length)for(var e=0,f=a.length;e<f;e++){if(e in a&&c.call(d,a[e],e,a)===o)break}else for(e in a)if(b.has(a,e)&&c.call(d,a[e],e,a)===o)break};b.map=b.collect=function(a,c,b){var e=[];if(a==null)return e;if(z&&a.map===z)return a.map(c,b);j(a,function(a,g,h){e[e.length]=c.call(b,a,g,h)});if(a.length===+a.length)e.length=a.length;return e};b.reduce=b.foldl=b.inject=function(a,c,d,e){var f=arguments.length>2;a==null&&(a=[]);if(A&&
|
12
|
+
a.reduce===A){e&&(c=b.bind(c,e));return f?a.reduce(c,d):a.reduce(c)}j(a,function(a,b,i){if(f)d=c.call(e,d,a,b,i);else{d=a;f=true}});if(!f)throw new TypeError("Reduce of empty array with no initial value");return d};b.reduceRight=b.foldr=function(a,c,d,e){var f=arguments.length>2;a==null&&(a=[]);if(B&&a.reduceRight===B){e&&(c=b.bind(c,e));return f?a.reduceRight(c,d):a.reduceRight(c)}var g=b.toArray(a).reverse();e&&!f&&(c=b.bind(c,e));return f?b.reduce(g,c,d,e):b.reduce(g,c)};b.find=b.detect=function(a,
|
13
|
+
c,b){var e;G(a,function(a,g,h){if(c.call(b,a,g,h)){e=a;return true}});return e};b.filter=b.select=function(a,c,b){var e=[];if(a==null)return e;if(C&&a.filter===C)return a.filter(c,b);j(a,function(a,g,h){c.call(b,a,g,h)&&(e[e.length]=a)});return e};b.reject=function(a,c,b){var e=[];if(a==null)return e;j(a,function(a,g,h){c.call(b,a,g,h)||(e[e.length]=a)});return e};b.every=b.all=function(a,c,b){var e=true;if(a==null)return e;if(D&&a.every===D)return a.every(c,b);j(a,function(a,g,h){if(!(e=e&&c.call(b,
|
14
|
+
a,g,h)))return o});return!!e};var G=b.some=b.any=function(a,c,d){c||(c=b.identity);var e=false;if(a==null)return e;if(E&&a.some===E)return a.some(c,d);j(a,function(a,b,h){if(e||(e=c.call(d,a,b,h)))return o});return!!e};b.include=b.contains=function(a,c){var b=false;if(a==null)return b;if(q&&a.indexOf===q)return a.indexOf(c)!=-1;return b=G(a,function(a){return a===c})};b.invoke=function(a,c){var d=i.call(arguments,2);return b.map(a,function(a){return(b.isFunction(c)?c||a:a[c]).apply(a,d)})};b.pluck=
|
15
|
+
function(a,c){return b.map(a,function(a){return a[c]})};b.max=function(a,c,d){if(!c&&b.isArray(a)&&a[0]===+a[0])return Math.max.apply(Math,a);if(!c&&b.isEmpty(a))return-Infinity;var e={computed:-Infinity};j(a,function(a,b,h){b=c?c.call(d,a,b,h):a;b>=e.computed&&(e={value:a,computed:b})});return e.value};b.min=function(a,c,d){if(!c&&b.isArray(a)&&a[0]===+a[0])return Math.min.apply(Math,a);if(!c&&b.isEmpty(a))return Infinity;var e={computed:Infinity};j(a,function(a,b,h){b=c?c.call(d,a,b,h):a;b<e.computed&&
|
16
|
+
(e={value:a,computed:b})});return e.value};b.shuffle=function(a){var b=[],d;j(a,function(a,f){d=Math.floor(Math.random()*(f+1));b[f]=b[d];b[d]=a});return b};b.sortBy=function(a,c,d){var e=b.isFunction(c)?c:function(a){return a[c]};return b.pluck(b.map(a,function(a,b,c){return{value:a,criteria:e.call(d,a,b,c)}}).sort(function(a,b){var c=a.criteria,d=b.criteria;return c===void 0?1:d===void 0?-1:c<d?-1:c>d?1:0}),"value")};b.groupBy=function(a,c){var d={},e=b.isFunction(c)?c:function(a){return a[c]};
|
17
|
+
j(a,function(a,b){var c=e(a,b);(d[c]||(d[c]=[])).push(a)});return d};b.sortedIndex=function(a,c,d){d||(d=b.identity);for(var e=0,f=a.length;e<f;){var g=e+f>>1;d(a[g])<d(c)?e=g+1:f=g}return e};b.toArray=function(a){return!a?[]:b.isArray(a)||b.isArguments(a)?i.call(a):a.toArray&&b.isFunction(a.toArray)?a.toArray():b.values(a)};b.size=function(a){return b.isArray(a)?a.length:b.keys(a).length};b.first=b.head=b.take=function(a,b,d){return b!=null&&!d?i.call(a,0,b):a[0]};b.initial=function(a,b,d){return i.call(a,
|
18
|
+
0,a.length-(b==null||d?1:b))};b.last=function(a,b,d){return b!=null&&!d?i.call(a,Math.max(a.length-b,0)):a[a.length-1]};b.rest=b.tail=function(a,b,d){return i.call(a,b==null||d?1:b)};b.compact=function(a){return b.filter(a,function(a){return!!a})};b.flatten=function(a,c){return b.reduce(a,function(a,e){if(b.isArray(e))return a.concat(c?e:b.flatten(e));a[a.length]=e;return a},[])};b.without=function(a){return b.difference(a,i.call(arguments,1))};b.uniq=b.unique=function(a,c,d){var d=d?b.map(a,d):a,
|
19
|
+
e=[];a.length<3&&(c=true);b.reduce(d,function(d,g,h){if(c?b.last(d)!==g||!d.length:!b.include(d,g)){d.push(g);e.push(a[h])}return d},[]);return e};b.union=function(){return b.uniq(b.flatten(arguments,true))};b.intersection=b.intersect=function(a){var c=i.call(arguments,1);return b.filter(b.uniq(a),function(a){return b.every(c,function(c){return b.indexOf(c,a)>=0})})};b.difference=function(a){var c=b.flatten(i.call(arguments,1),true);return b.filter(a,function(a){return!b.include(c,a)})};b.zip=function(){for(var a=
|
20
|
+
i.call(arguments),c=b.max(b.pluck(a,"length")),d=Array(c),e=0;e<c;e++)d[e]=b.pluck(a,""+e);return d};b.indexOf=function(a,c,d){if(a==null)return-1;var e;if(d){d=b.sortedIndex(a,c);return a[d]===c?d:-1}if(q&&a.indexOf===q)return a.indexOf(c);d=0;for(e=a.length;d<e;d++)if(d in a&&a[d]===c)return d;return-1};b.lastIndexOf=function(a,b){if(a==null)return-1;if(F&&a.lastIndexOf===F)return a.lastIndexOf(b);for(var d=a.length;d--;)if(d in a&&a[d]===b)return d;return-1};b.range=function(a,b,d){if(arguments.length<=
|
21
|
+
1){b=a||0;a=0}for(var d=arguments[2]||1,e=Math.max(Math.ceil((b-a)/d),0),f=0,g=Array(e);f<e;){g[f++]=a;a=a+d}return g};var H=function(){};b.bind=function(a,c){var d,e;if(a.bind===t&&t)return t.apply(a,i.call(arguments,1));if(!b.isFunction(a))throw new TypeError;e=i.call(arguments,2);return d=function(){if(!(this instanceof d))return a.apply(c,e.concat(i.call(arguments)));H.prototype=a.prototype;var b=new H,g=a.apply(b,e.concat(i.call(arguments)));return Object(g)===g?g:b}};b.bindAll=function(a){var c=
|
22
|
+
i.call(arguments,1);c.length==0&&(c=b.functions(a));j(c,function(c){a[c]=b.bind(a[c],a)});return a};b.memoize=function(a,c){var d={};c||(c=b.identity);return function(){var e=c.apply(this,arguments);return b.has(d,e)?d[e]:d[e]=a.apply(this,arguments)}};b.delay=function(a,b){var d=i.call(arguments,2);return setTimeout(function(){return a.apply(null,d)},b)};b.defer=function(a){return b.delay.apply(b,[a,1].concat(i.call(arguments,1)))};b.throttle=function(a,c){var d,e,f,g,h,i,j=b.debounce(function(){h=
|
23
|
+
g=false},c);return function(){d=this;e=arguments;f||(f=setTimeout(function(){f=null;h&&a.apply(d,e);j()},c));g?h=true:i=a.apply(d,e);j();g=true;return i}};b.debounce=function(a,b,d){var e;return function(){var f=this,g=arguments;d&&!e&&a.apply(f,g);clearTimeout(e);e=setTimeout(function(){e=null;d||a.apply(f,g)},b)}};b.once=function(a){var b=false,d;return function(){if(b)return d;b=true;return d=a.apply(this,arguments)}};b.wrap=function(a,b){return function(){var d=[a].concat(i.call(arguments,0));
|
24
|
+
return b.apply(this,d)}};b.compose=function(){var a=arguments;return function(){for(var b=arguments,d=a.length-1;d>=0;d--)b=[a[d].apply(this,b)];return b[0]}};b.after=function(a,b){return a<=0?b():function(){if(--a<1)return b.apply(this,arguments)}};b.keys=L||function(a){if(a!==Object(a))throw new TypeError("Invalid object");var c=[],d;for(d in a)b.has(a,d)&&(c[c.length]=d);return c};b.values=function(a){return b.map(a,b.identity)};b.functions=b.methods=function(a){var c=[],d;for(d in a)b.isFunction(a[d])&&
|
25
|
+
c.push(d);return c.sort()};b.extend=function(a){j(i.call(arguments,1),function(b){for(var d in b)a[d]=b[d]});return a};b.pick=function(a){var c={};j(b.flatten(i.call(arguments,1)),function(b){b in a&&(c[b]=a[b])});return c};b.defaults=function(a){j(i.call(arguments,1),function(b){for(var d in b)a[d]==null&&(a[d]=b[d])});return a};b.clone=function(a){return!b.isObject(a)?a:b.isArray(a)?a.slice():b.extend({},a)};b.tap=function(a,b){b(a);return a};b.isEqual=function(a,b){return r(a,b,[])};b.isEmpty=
|
26
|
+
function(a){if(a==null)return true;if(b.isArray(a)||b.isString(a))return a.length===0;for(var c in a)if(b.has(a,c))return false;return true};b.isElement=function(a){return!!(a&&a.nodeType==1)};b.isArray=p||function(a){return l.call(a)=="[object Array]"};b.isObject=function(a){return a===Object(a)};b.isArguments=function(a){return l.call(a)=="[object Arguments]"};b.isArguments(arguments)||(b.isArguments=function(a){return!(!a||!b.has(a,"callee"))});b.isFunction=function(a){return l.call(a)=="[object Function]"};
|
27
|
+
b.isString=function(a){return l.call(a)=="[object String]"};b.isNumber=function(a){return l.call(a)=="[object Number]"};b.isFinite=function(a){return b.isNumber(a)&&isFinite(a)};b.isNaN=function(a){return a!==a};b.isBoolean=function(a){return a===true||a===false||l.call(a)=="[object Boolean]"};b.isDate=function(a){return l.call(a)=="[object Date]"};b.isRegExp=function(a){return l.call(a)=="[object RegExp]"};b.isNull=function(a){return a===null};b.isUndefined=function(a){return a===void 0};b.has=function(a,
|
28
|
+
b){return K.call(a,b)};b.noConflict=function(){s._=I;return this};b.identity=function(a){return a};b.times=function(a,b,d){for(var e=0;e<a;e++)b.call(d,e)};b.escape=function(a){return(""+a).replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""").replace(/'/g,"'").replace(/\//g,"/")};b.result=function(a,c){if(a==null)return null;var d=a[c];return b.isFunction(d)?d.call(a):d};b.mixin=function(a){j(b.functions(a),function(c){M(c,b[c]=a[c])})};var N=0;b.uniqueId=
|
29
|
+
function(a){var b=N++;return a?a+b:b};b.templateSettings={evaluate:/<%([\s\S]+?)%>/g,interpolate:/<%=([\s\S]+?)%>/g,escape:/<%-([\s\S]+?)%>/g};var u=/.^/,n={"\\":"\\","'":"'",r:"\r",n:"\n",t:"\t",u2028:"\u2028",u2029:"\u2029"},v;for(v in n)n[n[v]]=v;var O=/\\|'|\r|\n|\t|\u2028|\u2029/g,P=/\\(\\|'|r|n|t|u2028|u2029)/g,w=function(a){return a.replace(P,function(a,b){return n[b]})};b.template=function(a,c,d){d=b.defaults(d||{},b.templateSettings);a="__p+='"+a.replace(O,function(a){return"\\"+n[a]}).replace(d.escape||
|
30
|
+
u,function(a,b){return"'+\n_.escape("+w(b)+")+\n'"}).replace(d.interpolate||u,function(a,b){return"'+\n("+w(b)+")+\n'"}).replace(d.evaluate||u,function(a,b){return"';\n"+w(b)+"\n;__p+='"})+"';\n";d.variable||(a="with(obj||{}){\n"+a+"}\n");var a="var __p='';var print=function(){__p+=Array.prototype.join.call(arguments, '')};\n"+a+"return __p;\n",e=new Function(d.variable||"obj","_",a);if(c)return e(c,b);c=function(a){return e.call(this,a,b)};c.source="function("+(d.variable||"obj")+"){\n"+a+"}";return c};
|
31
|
+
b.chain=function(a){return b(a).chain()};var m=function(a){this._wrapped=a};b.prototype=m.prototype;var x=function(a,c){return c?b(a).chain():a},M=function(a,c){m.prototype[a]=function(){var a=i.call(arguments);J.call(a,this._wrapped);return x(c.apply(b,a),this._chain)}};b.mixin(b);j("pop,push,reverse,shift,sort,splice,unshift".split(","),function(a){var b=k[a];m.prototype[a]=function(){var d=this._wrapped;b.apply(d,arguments);var e=d.length;(a=="shift"||a=="splice")&&e===0&&delete d[0];return x(d,
|
32
|
+
this._chain)}});j(["concat","join","slice"],function(a){var b=k[a];m.prototype[a]=function(){return x(b.apply(this._wrapped,arguments),this._chain)}});m.prototype.chain=function(){this._chain=true;return this};m.prototype.value=function(){return this._wrapped}}).call(this);
|
data/dev_scripts/Procfile
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
# loop: ruby sleep_loop.rb 5
|
2
2
|
# listen: sleep 3 && ncat -k -l 10001
|
3
|
+
listen: ncat -k -l 10001
|
3
4
|
# slow_echo: sleep 10 && ruby echo_server.rb
|
4
5
|
# echo: ruby echo_server.rb
|
5
|
-
sleep: sleep 30
|
6
|
-
|
6
|
+
# sleep: sleep 30
|
7
|
+
stubborn: ruby stubborn.rb
|
data/dev_scripts/stubborn.rb
CHANGED
data/dev_scripts/test.yml
CHANGED
@@ -2,7 +2,9 @@
|
|
2
2
|
test:
|
3
3
|
dir: "./dev_scripts"
|
4
4
|
# command: "ncat -k -l 10001"
|
5
|
-
command: "bundle exec foreman start"
|
5
|
+
command: "bundle exec foreman start 2>&1 | tee fail.log"
|
6
|
+
# command: sleep 10
|
6
7
|
server_port: 10000
|
7
|
-
|
8
|
+
app_port: 10001
|
8
9
|
idle_timeout: 10
|
10
|
+
startup_timeout: 5
|
@@ -0,0 +1,25 @@
|
|
1
|
+
#!/usr/bin/env ruby -wKU
|
2
|
+
|
3
|
+
|
4
|
+
pid = Process.spawn "ruby wait_fork_tree.rb", :pgroup => true
|
5
|
+
puts "forked: #{pid}"
|
6
|
+
|
7
|
+
# unicorn-style
|
8
|
+
def reap(pid)
|
9
|
+
begin
|
10
|
+
wpid = Process.waitpid(-1)#, Process::WNOHANG)
|
11
|
+
if wpid
|
12
|
+
puts "got child pid #{wpid}"
|
13
|
+
else
|
14
|
+
puts "no child pid"
|
15
|
+
# return
|
16
|
+
end
|
17
|
+
rescue Errno::ECHILD
|
18
|
+
puts "NO CHILD"
|
19
|
+
break
|
20
|
+
end while true
|
21
|
+
end
|
22
|
+
|
23
|
+
reap pid
|
24
|
+
|
25
|
+
# welp, can't wait for grandchildren. oh well.
|
data/lib/ringleader.rb
CHANGED
@@ -2,12 +2,15 @@ require "ringleader/version"
|
|
2
2
|
|
3
3
|
require "yaml"
|
4
4
|
require "ostruct"
|
5
|
+
require "json"
|
5
6
|
require "celluloid"
|
6
7
|
require "celluloid/io"
|
8
|
+
require "reel"
|
7
9
|
require "pty"
|
8
10
|
require "trollop"
|
9
11
|
require "rainbow"
|
10
12
|
require "color"
|
13
|
+
require "pathname"
|
11
14
|
|
12
15
|
module Ringleader
|
13
16
|
end
|
@@ -16,7 +19,10 @@ require "ringleader/config"
|
|
16
19
|
require "ringleader/name_logger"
|
17
20
|
require "ringleader/wait_for_exit"
|
18
21
|
require "ringleader/wait_for_port"
|
22
|
+
require "ringleader/process"
|
19
23
|
require "ringleader/socket_proxy"
|
20
24
|
require "ringleader/app"
|
21
|
-
require "ringleader/
|
25
|
+
require "ringleader/app_serializer"
|
26
|
+
require "ringleader/controller"
|
27
|
+
require "ringleader/server"
|
22
28
|
require "ringleader/cli"
|
data/lib/ringleader/app.rb
CHANGED
@@ -1,154 +1,121 @@
|
|
1
1
|
module Ringleader
|
2
2
|
|
3
|
-
#
|
3
|
+
# A configured application.
|
4
|
+
#
|
5
|
+
# Listens on a port, starts and runs the app process on demand, and proxies
|
6
|
+
# network data to the process.
|
4
7
|
class App
|
5
|
-
include Celluloid
|
8
|
+
include Celluloid::IO
|
6
9
|
include Celluloid::Logger
|
7
|
-
include NameLogger
|
8
10
|
|
9
|
-
attr_reader :config
|
10
|
-
|
11
|
-
# Create a new App instance.
|
12
|
-
#
|
13
|
-
# config - a configuration object for this app
|
14
11
|
def initialize(config)
|
15
12
|
@config = config
|
16
|
-
@
|
17
|
-
|
18
|
-
|
13
|
+
@process = Process.new(config)
|
14
|
+
enable! unless config.disabled
|
15
|
+
end
|
16
|
+
|
17
|
+
def name
|
18
|
+
@config.name
|
19
|
+
end
|
20
|
+
|
21
|
+
def enabled?
|
22
|
+
@enabled
|
19
23
|
end
|
20
24
|
|
21
|
-
# Public: query if the app is running
|
22
25
|
def running?
|
23
|
-
@running
|
26
|
+
@process.running?
|
24
27
|
end
|
25
28
|
|
26
|
-
# Public: start the application.
|
27
|
-
#
|
28
|
-
# This method is intended to be used synchronously. If the app is already
|
29
|
-
# running, it'll return immediately. If the app hasn't been started, or is
|
30
|
-
# in the process of starting, this method blocks until it starts or fails to
|
31
|
-
# start correctly.
|
32
|
-
#
|
33
|
-
# Returns true if the app started, false if not.
|
34
29
|
def start
|
35
|
-
if
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
if @running
|
41
|
-
true
|
42
|
-
elsif @starting
|
43
|
-
wait :running
|
44
|
-
else
|
45
|
-
start_app
|
30
|
+
return if @process.running?
|
31
|
+
info "starting #{@config.name}..."
|
32
|
+
if @process.start
|
33
|
+
start_activity_timer
|
46
34
|
end
|
47
35
|
end
|
48
36
|
|
49
|
-
# Public: stop the application.
|
50
|
-
#
|
51
|
-
# Sends a SIGHUP to the app's process, and expects it to exit like a sane
|
52
|
-
# and well-behaved application within 30 seconds before sending a SIGTERM.
|
53
37
|
def stop
|
54
|
-
return unless @
|
38
|
+
return unless @process.running?
|
39
|
+
info "stopping #{@config.name}..."
|
40
|
+
stop_activity_timer
|
41
|
+
@process.stop
|
42
|
+
end
|
55
43
|
|
56
|
-
|
57
|
-
|
58
|
-
|
44
|
+
def restart
|
45
|
+
stop
|
46
|
+
start
|
47
|
+
end
|
59
48
|
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
49
|
+
def enable
|
50
|
+
return if @server
|
51
|
+
@server = TCPServer.new @config.host, @config.server_port
|
52
|
+
@enabled = true
|
53
|
+
run!
|
54
|
+
rescue Errno::EADDRINUSE
|
55
|
+
error "could not bind to #{@config.host}:#{@config.server_port} for #{@config.name}!"
|
56
|
+
@server = nil
|
57
|
+
end
|
66
58
|
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
signal :running, true
|
77
|
-
end
|
78
|
-
|
79
|
-
# Internal: callback for when the process has exited.
|
80
|
-
def exited
|
81
|
-
debug "pid #{@pid} has exited"
|
82
|
-
info "exited."
|
83
|
-
@running = false
|
84
|
-
@pid = nil
|
85
|
-
@wait_for_port.terminate if @wait_for_port.alive?
|
86
|
-
@wait_for_exit.terminate if @wait_for_exit.alive?
|
87
|
-
signal :running, false
|
88
|
-
end
|
89
|
-
|
90
|
-
# Private: start the application process and associated infrastructure
|
91
|
-
#
|
92
|
-
# Intended to be synchronous, as it blocks until the app has started (or
|
93
|
-
# failed to start).
|
94
|
-
#
|
95
|
-
# Returns true if the app started, false if not.
|
96
|
-
def start_app
|
97
|
-
@starting = true
|
98
|
-
info "starting process `#{config.command}`"
|
99
|
-
|
100
|
-
# give the child process a terminal so output isn't buffered
|
101
|
-
@master, slave = PTY.open
|
102
|
-
@pid = Process.spawn %Q(bash -c "#{config.command}"),
|
103
|
-
:in => slave,
|
104
|
-
:out => slave,
|
105
|
-
:err => slave,
|
106
|
-
:chdir => config.dir,
|
107
|
-
:pgroup => true
|
108
|
-
slave.close
|
109
|
-
proxy_output @master
|
110
|
-
debug "started with pid #{@pid}"
|
111
|
-
|
112
|
-
@wait_for_exit = WaitForExit.new @pid, Actor.current
|
113
|
-
@wait_for_port = WaitForPort.new config.hostname, config.app_port, Actor.current
|
114
|
-
|
115
|
-
timer = after config.startup_timeout do
|
116
|
-
warn "application startup took more than #{config.startup_timeout}"
|
117
|
-
stop!
|
118
|
-
end
|
59
|
+
def disable
|
60
|
+
info "disabling #{@config.name}..."
|
61
|
+
return unless @server
|
62
|
+
stop_activity_timer
|
63
|
+
@process.stop
|
64
|
+
@server.close
|
65
|
+
@server = nil
|
66
|
+
@enabled = false
|
67
|
+
end
|
119
68
|
|
120
|
-
|
121
|
-
@
|
122
|
-
|
69
|
+
def finalize
|
70
|
+
@server.close if @server
|
71
|
+
end
|
123
72
|
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
73
|
+
def run
|
74
|
+
info "listening for connections for #{@config.name} on #{@config.host}:#{@config.server_port}"
|
75
|
+
loop { handle_connection! @server.accept }
|
76
|
+
rescue IOError
|
77
|
+
@server.close if @server
|
128
78
|
end
|
129
79
|
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
80
|
+
def handle_connection(socket)
|
81
|
+
_, port, host = socket.peeraddr
|
82
|
+
debug "received connection from #{host}:#{port}"
|
83
|
+
|
84
|
+
started = @process.start
|
85
|
+
if started
|
86
|
+
proxy_to_app! socket
|
87
|
+
reset_activity_timer
|
88
|
+
else
|
89
|
+
error "could not start app"
|
90
|
+
socket.close
|
138
91
|
end
|
139
92
|
end
|
140
93
|
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
94
|
+
def proxy_to_app(socket)
|
95
|
+
SocketProxy.new socket, @config.host, @config.app_port
|
96
|
+
end
|
97
|
+
|
98
|
+
def start_activity_timer
|
99
|
+
return if @activity_timer || @config.idle_timeout == 0
|
100
|
+
@activity_timer = every @config.idle_timeout do
|
101
|
+
if @process.running?
|
102
|
+
info "#{@config.name} has been idle for #{@config.idle_timeout} seconds, shutting it down"
|
103
|
+
@process.stop
|
148
104
|
end
|
149
105
|
end
|
150
106
|
end
|
151
107
|
|
152
|
-
|
108
|
+
def reset_activity_timer
|
109
|
+
start_activity_timer
|
110
|
+
@activity_timer.reset
|
111
|
+
end
|
112
|
+
|
113
|
+
def stop_activity_timer
|
114
|
+
if @activity_timer
|
115
|
+
@activity_timer.cancel
|
116
|
+
@activity_timer = nil
|
117
|
+
end
|
118
|
+
end
|
153
119
|
|
120
|
+
end
|
154
121
|
end
|
data/lib/ringleader/cli.rb
CHANGED
@@ -2,8 +2,6 @@ module Ringleader
|
|
2
2
|
class CLI
|
3
3
|
include Celluloid::Logger
|
4
4
|
|
5
|
-
TERMINAL_COLORS = [:red, :green, :yellow, :blue, :magenta, :cyan]
|
6
|
-
|
7
5
|
def run(argv)
|
8
6
|
# hide "shutdown" info message until after opts are validated
|
9
7
|
Celluloid.logger.level = ::Logger::ERROR
|
@@ -18,9 +16,18 @@ module Ringleader
|
|
18
16
|
|
19
17
|
configure_logging(opts.verbose ? "debug" : "info")
|
20
18
|
|
21
|
-
|
22
|
-
|
23
|
-
|
19
|
+
apps = Config.new(argv.first, opts.boring).apps
|
20
|
+
|
21
|
+
controller = Controller.new(apps)
|
22
|
+
Server.new(controller, opts.host, opts.port)
|
23
|
+
|
24
|
+
# gracefully die instead of showing an interrupted sleep below
|
25
|
+
trap("INT") do
|
26
|
+
controller.stop
|
27
|
+
exit
|
28
|
+
end
|
29
|
+
|
30
|
+
sleep
|
24
31
|
end
|
25
32
|
|
26
33
|
def configure_logging(level)
|
@@ -32,41 +39,9 @@ module Ringleader
|
|
32
39
|
end
|
33
40
|
end
|
34
41
|
|
35
|
-
def assign_colors(configs, boring=false)
|
36
|
-
if boring
|
37
|
-
configs.map.with_index do |config, i|
|
38
|
-
config.color = TERMINAL_COLORS[ i % TERMINAL_COLORS.length ]
|
39
|
-
config
|
40
|
-
end
|
41
|
-
else
|
42
|
-
offset = 360/configs.size
|
43
|
-
configs.map.with_index do |config, i|
|
44
|
-
config.color = Color::HSL.new(offset * i, 100, 50).html
|
45
|
-
config
|
46
|
-
end
|
47
|
-
end
|
48
|
-
end
|
49
|
-
|
50
|
-
def start_app_server(app_configs)
|
51
|
-
apps = app_configs.map do |app_config|
|
52
|
-
app = App.new app_config
|
53
|
-
AppProxy.new app, app_config
|
54
|
-
app
|
55
|
-
end
|
56
|
-
|
57
|
-
# gracefully die instead of showing an interrupted sleep below
|
58
|
-
trap("INT") do
|
59
|
-
info "shutting down..."
|
60
|
-
apps.each { |app| app.stop! }
|
61
|
-
exit
|
62
|
-
end
|
63
|
-
|
64
|
-
sleep
|
65
|
-
end
|
66
|
-
|
67
42
|
def die(msg)
|
68
43
|
error msg
|
69
|
-
exit
|
44
|
+
exit(-1)
|
70
45
|
end
|
71
46
|
|
72
47
|
def parser
|
@@ -110,28 +85,40 @@ something like this:
|
|
110
85
|
---
|
111
86
|
# name of app (used in logging)
|
112
87
|
main_app:
|
113
|
-
|
114
|
-
|
115
|
-
#
|
116
|
-
command: "foreman start"
|
117
|
-
|
118
|
-
|
119
|
-
#
|
120
|
-
|
121
|
-
#
|
122
|
-
|
123
|
-
#
|
124
|
-
|
125
|
-
#
|
126
|
-
|
127
|
-
|
128
|
-
|
88
|
+
|
89
|
+
# Required settings
|
90
|
+
dir: "~/apps/main" # Working directory
|
91
|
+
command: "foreman start" # The command to run to start up the app server.
|
92
|
+
# Executed under "bash -c".
|
93
|
+
server_port: 3000 # The port ringleader listens on
|
94
|
+
app_port: 4000 # The port the application listens on
|
95
|
+
|
96
|
+
# Optional settings
|
97
|
+
host: 127.0.0.1 # The host ringleader should listen on
|
98
|
+
idle_timeout: 6000 # Idle timeout in seconds
|
99
|
+
startup_timeout: 180 # Application startup timeout
|
100
|
+
disabled: true # Set the app to be disabled when ringleader starts
|
101
|
+
|
102
|
+
# If you have an application managed by rvm, this setting automatically
|
103
|
+
# adds the rvm-specific shell setup before executing the given command.
|
104
|
+
# This supersedes the `command` setting.
|
105
|
+
rvm: "foreman start"
|
129
106
|
|
130
107
|
OPTIONS
|
131
108
|
banner
|
132
109
|
|
133
|
-
opt "verbose", "log at debug level",
|
134
|
-
|
110
|
+
opt "verbose", "log at debug level",
|
111
|
+
:long => "--verbose", :short => "-v",
|
112
|
+
:type => :boolean, :default => false
|
113
|
+
opt "host", "host for web control panel",
|
114
|
+
:long => "--host", :short => "-H",
|
115
|
+
:default => "localhost"
|
116
|
+
opt "port", "port for the web control panel",
|
117
|
+
:long => "--port", :short => "-p",
|
118
|
+
:type => :integer, :default => 42000
|
119
|
+
opt "boring", "use boring colors instead of a fabulous rainbow",
|
120
|
+
:long => "--boring", :short => "-b",
|
121
|
+
:type => :boolean, :default => false
|
135
122
|
|
136
123
|
end
|
137
124
|
end
|