noah 0.0.5-jruby → 0.1-jruby

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (81) hide show
  1. data/.gemtest +0 -0
  2. data/.gitignore +10 -0
  3. data/LICENSE +201 -0
  4. data/README.md +68 -212
  5. data/Rakefile +65 -41
  6. data/TODO.md +65 -0
  7. data/bin/noah +2 -1
  8. data/bin/noah-watcher.rb +103 -0
  9. data/config.ru +6 -3
  10. data/config/warble.rb +18 -0
  11. data/examples/README.md +116 -0
  12. data/examples/cluster.ru +2 -0
  13. data/examples/custom-watcher.rb +10 -0
  14. data/examples/httpclient-server.rb +7 -0
  15. data/examples/httpclient.rb +12 -0
  16. data/examples/httpclient2.rb +28 -0
  17. data/examples/js/FABridge.js +1452 -0
  18. data/examples/js/WebSocketMain.swf +830 -0
  19. data/examples/js/swfobject.js +851 -0
  20. data/examples/js/web_socket.js +312 -0
  21. data/examples/logger.rb +11 -0
  22. data/examples/reconfiguring-sinatra-watcher.rb +11 -0
  23. data/examples/reconfiguring-sinatra.rb +33 -0
  24. data/examples/simple-post.rb +17 -0
  25. data/examples/websocket.html +24 -0
  26. data/examples/websocket.rb +41 -0
  27. data/lib/noah.rb +6 -8
  28. data/lib/noah/app.rb +20 -268
  29. data/lib/noah/application_routes.rb +70 -0
  30. data/lib/noah/ark.rb +0 -0
  31. data/lib/noah/configuration_routes.rb +81 -0
  32. data/lib/noah/custom_watcher.rb +79 -0
  33. data/lib/noah/ephemeral_routes.rb +47 -0
  34. data/lib/noah/helpers.rb +37 -14
  35. data/lib/noah/host_routes.rb +69 -0
  36. data/lib/noah/models.rb +86 -5
  37. data/lib/noah/models/applications.rb +41 -0
  38. data/lib/noah/models/configurations.rb +49 -0
  39. data/lib/noah/models/ephemerals.rb +54 -0
  40. data/lib/noah/models/hosts.rb +56 -0
  41. data/lib/noah/models/services.rb +54 -0
  42. data/lib/noah/models/watchers.rb +62 -0
  43. data/lib/noah/passthrough.rb +11 -0
  44. data/lib/noah/service_routes.rb +71 -0
  45. data/lib/noah/validations.rb +1 -0
  46. data/lib/noah/validations/watcher_validations.rb +48 -0
  47. data/lib/noah/version.rb +1 -1
  48. data/lib/noah/watcher_routes.rb +45 -0
  49. data/noah.gemspec +25 -17
  50. data/spec/application_spec.rb +30 -30
  51. data/spec/configuration_spec.rb +78 -14
  52. data/spec/ephemeral_spec.rb +59 -0
  53. data/spec/host_spec.rb +21 -21
  54. data/spec/noahapp_application_spec.rb +6 -6
  55. data/spec/noahapp_configuration_spec.rb +5 -5
  56. data/spec/noahapp_ephemeral_spec.rb +115 -0
  57. data/spec/noahapp_host_spec.rb +3 -3
  58. data/spec/noahapp_service_spec.rb +10 -10
  59. data/spec/noahapp_watcher_spec.rb +123 -0
  60. data/spec/service_spec.rb +27 -27
  61. data/spec/spec_helper.rb +13 -22
  62. data/spec/support/db/.keep +0 -0
  63. data/spec/support/test-redis.conf +8 -0
  64. data/spec/watcher_spec.rb +62 -0
  65. data/views/index.haml +21 -15
  66. metadata +189 -146
  67. data/Gemfile.lock +0 -83
  68. data/doc/coverage/index.html +0 -138
  69. data/doc/coverage/jquery-1.3.2.min.js +0 -19
  70. data/doc/coverage/jquery.tablesorter.min.js +0 -15
  71. data/doc/coverage/lib-helpers_rb.html +0 -393
  72. data/doc/coverage/lib-models_rb.html +0 -1449
  73. data/doc/coverage/noah_rb.html +0 -2019
  74. data/doc/coverage/print.css +0 -12
  75. data/doc/coverage/rcov.js +0 -42
  76. data/doc/coverage/screen.css +0 -270
  77. data/lib/noah/applications.rb +0 -46
  78. data/lib/noah/configurations.rb +0 -49
  79. data/lib/noah/hosts.rb +0 -54
  80. data/lib/noah/services.rb +0 -57
  81. data/lib/noah/watchers.rb +0 -18
@@ -0,0 +1,312 @@
1
+ // Copyright: Hiroshi Ichikawa <http://gimite.net/en/>
2
+ // Lincense: New BSD Lincense
3
+ // Reference: http://dev.w3.org/html5/websockets/
4
+ // Reference: http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol
5
+
6
+ (function() {
7
+
8
+ if (window.WebSocket) return;
9
+
10
+ var console = window.console;
11
+ if (!console) console = {log: function(){ }, error: function(){ }};
12
+
13
+ function hasFlash() {
14
+ if ('navigator' in window && 'plugins' in navigator && navigator.plugins['Shockwave Flash']) {
15
+ return !!navigator.plugins['Shockwave Flash'].description;
16
+ }
17
+ if ('ActiveXObject' in window) {
18
+ try {
19
+ return !!new ActiveXObject('ShockwaveFlash.ShockwaveFlash').GetVariable('$version');
20
+ } catch (e) {}
21
+ }
22
+ return false;
23
+ }
24
+
25
+ if (!hasFlash()) {
26
+ console.error("Flash Player is not installed.");
27
+ return;
28
+ }
29
+
30
+ WebSocket = function(url, protocol, proxyHost, proxyPort, headers) {
31
+ var self = this;
32
+ self.readyState = WebSocket.CONNECTING;
33
+ self.bufferedAmount = 0;
34
+ WebSocket.__addTask(function() {
35
+ self.__flash =
36
+ WebSocket.__flash.create(url, protocol, proxyHost || null, proxyPort || 0, headers || null);
37
+
38
+ self.__flash.addEventListener("open", function(fe) {
39
+ try {
40
+ if (self.onopen) self.onopen();
41
+ } catch (e) {
42
+ console.error(e.toString());
43
+ }
44
+ });
45
+
46
+ self.__flash.addEventListener("close", function(fe) {
47
+ try {
48
+ if (self.onclose) self.onclose();
49
+ } catch (e) {
50
+ console.error(e.toString());
51
+ }
52
+ });
53
+
54
+ self.__flash.addEventListener("message", function(fe) {
55
+ var data = decodeURIComponent(fe.getData());
56
+ try {
57
+ if (self.onmessage) {
58
+ var e;
59
+ if (window.MessageEvent) {
60
+ e = document.createEvent("MessageEvent");
61
+ e.initMessageEvent("message", false, false, data, null, null, window);
62
+ } else { // IE
63
+ e = {data: data};
64
+ }
65
+ self.onmessage(e);
66
+ }
67
+ } catch (e) {
68
+ console.error(e.toString());
69
+ }
70
+ });
71
+
72
+ self.__flash.addEventListener("stateChange", function(fe) {
73
+ try {
74
+ self.readyState = fe.getReadyState();
75
+ self.bufferedAmount = fe.getBufferedAmount();
76
+ } catch (e) {
77
+ console.error(e.toString());
78
+ }
79
+ });
80
+
81
+ //console.log("[WebSocket] Flash object is ready");
82
+ });
83
+ }
84
+
85
+ WebSocket.prototype.send = function(data) {
86
+ if (!this.__flash || this.readyState == WebSocket.CONNECTING) {
87
+ throw "INVALID_STATE_ERR: Web Socket connection has not been established";
88
+ }
89
+ var result = this.__flash.send(data);
90
+ if (result < 0) { // success
91
+ return true;
92
+ } else {
93
+ this.bufferedAmount = result;
94
+ return false;
95
+ }
96
+ };
97
+
98
+ WebSocket.prototype.close = function() {
99
+ if (!this.__flash) return;
100
+ if (this.readyState != WebSocket.OPEN) return;
101
+ this.__flash.close();
102
+ // Sets/calls them manually here because Flash WebSocketConnection.close cannot fire events
103
+ // which causes weird error:
104
+ // > You are trying to call recursively into the Flash Player which is not allowed.
105
+ this.readyState = WebSocket.CLOSED;
106
+ if (this.onclose) this.onclose();
107
+ };
108
+
109
+ /**
110
+ * Implementation of {@link <a href="http://www.w3.org/TR/DOM-Level-2-Events/events.html#Events-registration">DOM 2 EventTarget Interface</a>}
111
+ *
112
+ * @param {string} type
113
+ * @param {function} listener
114
+ * @param {boolean} useCapture !NB Not implemented yet
115
+ * @return void
116
+ */
117
+ WebSocket.prototype.addEventListener = function(type, listener, useCapture) {
118
+ if (!('__events' in this)) {
119
+ this.__events = {};
120
+ }
121
+ if (!(type in this.__events)) {
122
+ this.__events[type] = [];
123
+ if ('function' == typeof this['on' + type]) {
124
+ this.__events[type].defaultHandler = this['on' + type];
125
+ this['on' + type] = WebSocket_FireEvent(this, type);
126
+ }
127
+ }
128
+ this.__events[type].push(listener);
129
+ };
130
+
131
+ /**
132
+ * Implementation of {@link <a href="http://www.w3.org/TR/DOM-Level-2-Events/events.html#Events-registration">DOM 2 EventTarget Interface</a>}
133
+ *
134
+ * @param {string} type
135
+ * @param {function} listener
136
+ * @param {boolean} useCapture NB! Not implemented yet
137
+ * @return void
138
+ */
139
+ WebSocket.prototype.removeEventListener = function(type, listener, useCapture) {
140
+ if (!('__events' in this)) {
141
+ this.__events = {};
142
+ }
143
+ if (!(type in this.__events)) return;
144
+ for (var i = this.__events.length; i > -1; --i) {
145
+ if (listener === this.__events[type][i]) {
146
+ this.__events[type].splice(i, 1);
147
+ break;
148
+ }
149
+ }
150
+ };
151
+
152
+ /**
153
+ * Implementation of {@link <a href="http://www.w3.org/TR/DOM-Level-2-Events/events.html#Events-registration">DOM 2 EventTarget Interface</a>}
154
+ *
155
+ * @param {WebSocketEvent} event
156
+ * @return void
157
+ */
158
+ WebSocket.prototype.dispatchEvent = function(event) {
159
+ if (!('__events' in this)) throw 'UNSPECIFIED_EVENT_TYPE_ERR';
160
+ if (!(event.type in this.__events)) throw 'UNSPECIFIED_EVENT_TYPE_ERR';
161
+
162
+ for (var i = 0, l = this.__events[event.type].length; i < l; ++ i) {
163
+ this.__events[event.type][i](event);
164
+ if (event.cancelBubble) break;
165
+ }
166
+
167
+ if (false !== event.returnValue &&
168
+ 'function' == typeof this.__events[event.type].defaultHandler)
169
+ {
170
+ this.__events[event.type].defaultHandler(event);
171
+ }
172
+ };
173
+
174
+ /**
175
+ *
176
+ * @param {object} object
177
+ * @param {string} type
178
+ */
179
+ function WebSocket_FireEvent(object, type) {
180
+ return function(data) {
181
+ var event = new WebSocketEvent();
182
+ event.initEvent(type, true, true);
183
+ event.target = event.currentTarget = object;
184
+ for (var key in data) {
185
+ event[key] = data[key];
186
+ }
187
+ object.dispatchEvent(event, arguments);
188
+ };
189
+ }
190
+
191
+ /**
192
+ * Basic implementation of {@link <a href="http://www.w3.org/TR/DOM-Level-2-Events/events.html#Events-interface">DOM 2 EventInterface</a>}
193
+ *
194
+ * @class
195
+ * @constructor
196
+ */
197
+ function WebSocketEvent(){}
198
+
199
+ /**
200
+ *
201
+ * @type boolean
202
+ */
203
+ WebSocketEvent.prototype.cancelable = true;
204
+
205
+ /**
206
+ *
207
+ * @type boolean
208
+ */
209
+ WebSocketEvent.prototype.cancelBubble = false;
210
+
211
+ /**
212
+ *
213
+ * @return void
214
+ */
215
+ WebSocketEvent.prototype.preventDefault = function() {
216
+ if (this.cancelable) {
217
+ this.returnValue = false;
218
+ }
219
+ };
220
+
221
+ /**
222
+ *
223
+ * @return void
224
+ */
225
+ WebSocketEvent.prototype.stopPropagation = function() {
226
+ this.cancelBubble = true;
227
+ };
228
+
229
+ /**
230
+ *
231
+ * @param {string} eventTypeArg
232
+ * @param {boolean} canBubbleArg
233
+ * @param {boolean} cancelableArg
234
+ * @return void
235
+ */
236
+ WebSocketEvent.prototype.initEvent = function(eventTypeArg, canBubbleArg, cancelableArg) {
237
+ this.type = eventTypeArg;
238
+ this.cancelable = cancelableArg;
239
+ this.timeStamp = new Date();
240
+ };
241
+
242
+
243
+ WebSocket.CONNECTING = 0;
244
+ WebSocket.OPEN = 1;
245
+ WebSocket.CLOSED = 2;
246
+
247
+ WebSocket.__tasks = [];
248
+
249
+ WebSocket.__initialize = function() {
250
+ if (!WebSocket.__swfLocation) {
251
+ //console.error("[WebSocket] set WebSocket.__swfLocation to location of WebSocketMain.swf");
252
+ //return;
253
+ WebSocket.__swfLocation = "js/WebSocketMain.swf";
254
+ }
255
+ var container = document.createElement("div");
256
+ container.id = "webSocketContainer";
257
+ // Puts the Flash out of the window. Note that we cannot use display: none or visibility: hidden
258
+ // here because it prevents Flash from loading at least in IE.
259
+ container.style.position = "absolute";
260
+ container.style.left = "-100px";
261
+ container.style.top = "-100px";
262
+ var holder = document.createElement("div");
263
+ holder.id = "webSocketFlash";
264
+ container.appendChild(holder);
265
+ document.body.appendChild(container);
266
+ swfobject.embedSWF(
267
+ WebSocket.__swfLocation, "webSocketFlash", "8", "8", "9.0.0",
268
+ null, {bridgeName: "webSocket"}, null, null,
269
+ function(e) {
270
+ if (!e.success) console.error("[WebSocket] swfobject.embedSWF failed");
271
+ }
272
+ );
273
+ FABridge.addInitializationCallback("webSocket", function() {
274
+ try {
275
+ //console.log("[WebSocket] FABridge initializad");
276
+ WebSocket.__flash = FABridge.webSocket.root();
277
+ WebSocket.__flash.setCallerUrl(location.href);
278
+ for (var i = 0; i < WebSocket.__tasks.length; ++i) {
279
+ WebSocket.__tasks[i]();
280
+ }
281
+ WebSocket.__tasks = [];
282
+ } catch (e) {
283
+ console.error("[WebSocket] " + e.toString());
284
+ }
285
+ });
286
+ };
287
+
288
+ WebSocket.__addTask = function(task) {
289
+ if (WebSocket.__flash) {
290
+ task();
291
+ } else {
292
+ WebSocket.__tasks.push(task);
293
+ }
294
+ }
295
+
296
+ // called from Flash
297
+ function webSocketLog(message) {
298
+ console.log(decodeURIComponent(message));
299
+ }
300
+
301
+ // called from Flash
302
+ function webSocketError(message) {
303
+ console.error(decodeURIComponent(message));
304
+ }
305
+
306
+ if (window.addEventListener) {
307
+ window.addEventListener("load", WebSocket.__initialize, false);
308
+ } else {
309
+ window.attachEvent("onload", WebSocket.__initialize);
310
+ }
311
+
312
+ })();
@@ -0,0 +1,11 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require File.join(File.dirname(__FILE__), '..','lib','noah','custom_watcher')
4
+ require 'logger'
5
+
6
+ class LoggingWatcher < Noah::CustomWatcher
7
+ redis_host "redis://127.0.0.1:6379/0"
8
+ pattern "//noah"
9
+ destination Proc.new {|x| log = Logger.new(STDOUT); log.debug(x)}
10
+ run!
11
+ end
@@ -0,0 +1,11 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require File.join(File.dirname(__FILE__), '..','lib','noah','custom_watcher')
4
+ require 'em-http-request'
5
+
6
+ class HttpPostWatch < Noah::CustomWatcher
7
+ redis_host "redis://127.0.0.1:6379/0"
8
+ pattern "//noah/configuration/redis_server"
9
+ destination Proc.new {|x| ::EM::HttpRequest.new('http://localhost:4567/webhook', :connection_timeout => 2, :inactivity_timeout => 4).post :body => x}
10
+ run!
11
+ end
@@ -0,0 +1,33 @@
1
+ #!/usr/bin/env ruby
2
+ require 'sinatra'
3
+ require 'ohm'
4
+ require 'open-uri'
5
+ require 'json'
6
+
7
+ set :noah_server, 'http://localhost:5678'
8
+ set :noah_client_name, 'my_sinatra_app'
9
+
10
+ def get_config_from_noah(setting)
11
+ begin
12
+ c = open("#{settings.noah_server}/c/#{settings.noah_client_name}/#{setting}").read
13
+ set setting.to_sym, c
14
+ end
15
+ end
16
+
17
+ get_config_from_noah('redis_server')
18
+
19
+ def get_redis_version
20
+ Ohm.connect :url => settings.redis_server
21
+ Ohm.redis.info["redis_version"]
22
+ end
23
+
24
+ get "/" do
25
+ "Redis version: #{get_redis_version}"
26
+ end
27
+
28
+ put "/webhook" do
29
+ data = JSON.parse(request.body.read)
30
+ settings.redis_server = data["body"]
31
+ resp = {:message => "reconfigured", :setting => data["name"], :body => data["body"]}.to_json
32
+ "#{resp}"
33
+ end
@@ -0,0 +1,17 @@
1
+ require 'sinatra/base'
2
+ require 'json'
3
+
4
+ class NoahPostDemo < Sinatra::Base
5
+ configure do
6
+ set :app_file, __FILE__
7
+ set :server, %w[thin]
8
+ set :logging, true
9
+ set :run, true
10
+ end
11
+
12
+ post '/webhook/?' do
13
+ x = request.body.read
14
+ p JSON.load(x)
15
+ end
16
+
17
+ end
@@ -0,0 +1,24 @@
1
+ <html>
2
+ <head>
3
+ <script src='http://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.min.js'></script>
4
+ <script src='js/swfobject.js'></script>
5
+ <script src='js/FABridge.js'></script>
6
+ <script src='js/web_socket.js'></script>
7
+ <script>
8
+ $(document).ready(function(){
9
+ function debug(str){ $("#debug").append("<p>" + str); };
10
+
11
+ ws = new WebSocket("ws://localhost:3009/");
12
+ ws.onmessage = function(evt) { $("#msg").append("<p>"+evt.data+"</p>"); };
13
+ ws.onclose = function() { debug("socket closed"); };
14
+ ws.onopen = function() {
15
+ debug("connected...");
16
+ };
17
+ });
18
+ </script>
19
+ </head>
20
+ <body>
21
+ <div id="debug"></div>
22
+ <div id="msg"></div>
23
+ </body>
24
+ </html>
@@ -0,0 +1,41 @@
1
+ #!/usr/bin/env ruby
2
+ $:.unshift(File.expand_path(File.join(File.dirname(__FILE__), "..", "lib")))
3
+ require 'rubygems'
4
+ require 'em-websocket'
5
+ require 'em-hiredis'
6
+ require 'thin'
7
+ require 'noah'
8
+ ## Uncomment the following to hardcode a redis url
9
+ #ENV['REDIS_URL'] = "redis://localhost:6379/0"
10
+
11
+ EventMachine.run do
12
+
13
+ # Passing messages...like a boss
14
+ @channel = EventMachine::Channel.new
15
+
16
+ Thin::Server.start Noah::App
17
+ r = EventMachine::Hiredis::Client.connect
18
+ r.psubscribe("//noah/*")
19
+ r.on(:pmessage) do |pattern, event, message|
20
+ @channel.push "(#{event}) #{message}"
21
+ end
22
+
23
+ EventMachine::WebSocket.start(:host => "0.0.0.0", :port => 3009) do |ws|
24
+ ws.onopen {
25
+ sub = @channel.subscribe { |msg|
26
+ ws.send msg
27
+ }
28
+
29
+ @channel.push "#{sub} connected and waiting...."
30
+
31
+ ws.onmessage { |msg|
32
+ @channel.push "<#{sub}>: #{msg}"
33
+ }
34
+
35
+ ws.onclose {
36
+ @channel.unsubscribe(sub)
37
+ }
38
+ }
39
+ end
40
+
41
+ end