adminix 0.1.49 → 0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (77) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -0
  3. data/adminix.gemspec +1 -4
  4. data/app/assets/images/logo.png +0 -0
  5. data/app/assets/javascripts/application.js +50 -0
  6. data/app/assets/javascripts/bootstrap.min.js +7 -0
  7. data/app/assets/javascripts/dataTables.bootstrap4.js +184 -0
  8. data/app/assets/javascripts/jquery.dataTables.js +15243 -0
  9. data/app/assets/javascripts/jquery.min.js +2 -0
  10. data/app/assets/javascripts/sb-admin-2.min.js +6 -0
  11. data/app/assets/stylesheets/bootstrap.min.css +6 -0
  12. data/app/assets/stylesheets/dataTables.bootstrap.css +314 -0
  13. data/app/assets/stylesheets/dataTables.responsive.css +106 -0
  14. data/app/assets/stylesheets/font-awesome.min.css +4 -0
  15. data/app/assets/stylesheets/sb-admin-2.min.css +5 -0
  16. data/app/views/scripts/restart_watcher.sh.erb +9 -0
  17. data/app/views/scripts/run_script.sh.erb +12 -0
  18. data/app/views/scripts/start_process.sh.erb +7 -0
  19. data/app/views/scripts/stop_process.sh.erb +7 -0
  20. data/app/views/scripts/update_process.sh.erb +24 -0
  21. data/app/views/scripts/update_watcher.sh.erb +3 -0
  22. data/app/views/web/dashboard.html.erb +90 -0
  23. data/app/views/web/job.html.erb +46 -0
  24. data/app/views/web/link.html.erb +12 -0
  25. data/app/views/web/loadstamp.html.erb +57 -0
  26. data/app/views/web/log.html.erb +49 -0
  27. data/app/views/web/partials/footer.html.erb +11 -0
  28. data/app/views/web/partials/header.html.erb +50 -0
  29. data/bin/install_adminix +40 -0
  30. data/bin/push +13 -0
  31. data/development.log +0 -0
  32. data/exe/adminix +91 -28
  33. data/lib/adminix.rb +42 -5
  34. data/lib/adminix/config.rb +170 -96
  35. data/lib/adminix/entities.rb +5 -0
  36. data/lib/adminix/entities/job.rb +54 -0
  37. data/lib/adminix/entities/log.rb +21 -0
  38. data/lib/adminix/entities/service.rb +211 -0
  39. data/lib/adminix/entities/sysload_stamp.rb +37 -0
  40. data/lib/adminix/entities/variable.rb +32 -0
  41. data/lib/adminix/helpers.rb +7 -2
  42. data/lib/adminix/helpers/command.rb +73 -0
  43. data/lib/adminix/helpers/files.rb +82 -0
  44. data/lib/adminix/helpers/log_reader.rb +16 -0
  45. data/lib/adminix/helpers/net_http.rb +63 -0
  46. data/lib/adminix/helpers/output.rb +28 -0
  47. data/lib/adminix/helpers/systemctl.rb +54 -0
  48. data/lib/adminix/services.rb +3 -0
  49. data/lib/adminix/services/app_service.rb +143 -0
  50. data/lib/adminix/services/logs_service.rb +13 -0
  51. data/lib/adminix/services/system_load_service.rb +16 -0
  52. data/lib/adminix/version.rb +1 -1
  53. data/lib/adminix/watcher.rb +76 -144
  54. data/lib/adminix/web.rb +4 -0
  55. data/lib/adminix/web/router.rb +98 -0
  56. data/lib/adminix/web/server.rb +60 -0
  57. data/lib/adminix/web/view_helper.rb +14 -0
  58. data/lib/event_machine.rb +2 -0
  59. data/lib/event_machine/http_server.rb +2 -0
  60. data/lib/event_machine/http_server/response.rb +314 -0
  61. data/lib/event_machine/http_server/server.rb +107 -0
  62. data/lib/event_machine/tail.rb +2 -0
  63. data/lib/event_machine/tail/filetail.rb +470 -0
  64. data/lib/event_machine/tail/globwatcher.rb +294 -0
  65. metadata +60 -45
  66. data/lib/adminix/errors.rb +0 -7
  67. data/lib/adminix/helpers/file.rb +0 -13
  68. data/lib/adminix/helpers/http.rb +0 -19
  69. data/lib/adminix/log_watch_handler.rb +0 -23
  70. data/lib/adminix/server_setup.rb +0 -76
  71. data/lib/adminix/service.rb +0 -179
  72. data/lib/adminix/setup.rb +0 -3
  73. data/lib/adminix/setup/routes.rb +0 -113
  74. data/lib/adminix/setup/services.rb +0 -139
  75. data/lib/adminix/setup/views.rb +0 -183
  76. data/lib/adminix/system.rb +0 -106
  77. data/views/daemon_scripts/upstart.conf.erb +0 -23
@@ -0,0 +1,107 @@
1
+ require 'rubygems'
2
+ require 'eventmachine'
3
+
4
+ module EventMachine
5
+ module HttpServer
6
+ class Server < EM::P::HeaderAndContentProtocol
7
+
8
+ # everything starts from here.
9
+ # Protocol::HeaderAndContentProtocol does the dirty job for us
10
+ # it will pass headers and content, we just need to parse the headers
11
+ # the fill the right variables
12
+ def receive_request(headers, content)
13
+
14
+ # save the whole headers array, verbatim
15
+ @http_headers = headers
16
+
17
+ # parse the headers into an hash to be able to access them like:
18
+ # @http[:host]
19
+ # @http[:content_type]
20
+ @http = headers_2_hash headers
21
+
22
+ # parse the HTTP request
23
+ parse_first_header headers.first
24
+
25
+ # save the binary content
26
+ @http_content = content
27
+
28
+ # invoke the method in the user-provided instance
29
+ if respond_to?(:process_http_request)
30
+ process_http_request
31
+ end
32
+ rescue Exception => e
33
+ # invoke the method in the user-provided instance
34
+ if respond_to?(:http_request_errback)
35
+ http_request_errback e
36
+ end
37
+ end
38
+
39
+ # parse the first HTTP header line
40
+ # get the http METHOD, URI and PROTOCOL
41
+ def parse_first_header(line)
42
+
43
+ # split the line into: METHOD URI PROTOCOL
44
+ # eg: GET / HTTP/1.1
45
+ parsed = line.split(' ')
46
+
47
+ # a correct request has three parts
48
+ return bad_parsing unless parsed.size == 3
49
+
50
+ @http_request_method, uri, @http_protocol = parsed
51
+
52
+ # optional query string
53
+ @http_request_uri, @http_query_string = uri.split('?')
54
+ end
55
+
56
+ def bad_parsing
57
+ code = 400
58
+ desc = "Bad request"
59
+ string = respond_to?(:http_error_string) ? http_error_string(code, desc) : default_error_string(code, desc)
60
+ send_error string
61
+ raise("#{code} #{desc}")
62
+ end
63
+
64
+ def default_error_string(code, desc)
65
+ string = "HTTP/1.1 #{code} #{desc}\r\n"
66
+ string << "Connection: close\r\n"
67
+ string << "Content-type: text/plain\r\n"
68
+ string << "\r\n"
69
+ string << "Detected error: HTTP code #{code}"
70
+ end
71
+
72
+ # send back to the client an HTTP error
73
+ def send_error(string)
74
+ send_data string
75
+ close_connection_after_writing
76
+ end
77
+
78
+ end
79
+ end
80
+ end
81
+
82
+
83
+ if __FILE__ == $0
84
+
85
+ class HTTPHandler < EM::HttpServer::Server
86
+
87
+ def process_http_request
88
+ puts @http_request_method
89
+ puts @http_request_uri
90
+ puts @http_query_string
91
+ puts @http_protocol
92
+ puts @http[:cookie]
93
+ puts @http[:content_type]
94
+ puts @http_content
95
+ puts @http.inspect
96
+ end
97
+
98
+ end
99
+
100
+ port = 8080
101
+ # all the events are handled here
102
+ EM::run do
103
+ EM::start_server("0.0.0.0", port, HTTPHandler)
104
+ puts "Listening on port #{port}..."
105
+ end
106
+
107
+ end
@@ -0,0 +1,2 @@
1
+ require_relative 'tail/filetail'
2
+ require_relative 'tail/globwatcher'
@@ -0,0 +1,470 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "eventmachine"
4
+ require "logger"
5
+
6
+ EventMachine.epoll if EventMachine.epoll?
7
+ EventMachine.kqueue = true if EventMachine.kqueue?
8
+
9
+ # Tail a file.
10
+ #
11
+ # Example
12
+ # class Tailer < EventMachine::FileTail
13
+ # def receive_data(data)
14
+ # puts "Got #{data.length} bytes"
15
+ # end
16
+ #
17
+ # # Optional
18
+ # def eof
19
+ # puts "Got EOF!"
20
+ # # If you want to stop
21
+ # stop
22
+ # end
23
+ # end
24
+ #
25
+ # # Now add it to EM
26
+ # EM.run do
27
+ # EM.file_tail("/var/log/messages", Tailer)
28
+ # end
29
+ #
30
+ # # Or this way:
31
+ # EM.run do
32
+ # Tailer.new("/var/log/messages")
33
+ # end
34
+ #
35
+ # See also: EventMachine::FileTail#receive_data
36
+ class EventMachine::FileTail
37
+ # Maximum size to read at a time from a single file.
38
+ CHUNKSIZE = 65536
39
+
40
+ #MAXSLEEP = 2
41
+
42
+ FORCE_ENCODING = !! (defined? Encoding)
43
+
44
+ # The path of the file being tailed
45
+ attr_reader :path
46
+
47
+ # The current file read position
48
+ attr_reader :position
49
+
50
+ # If this tail is closed
51
+ attr_reader :closed
52
+
53
+ # Check interval when checking symlinks for changes. This is only useful
54
+ # when you are actually tailing symlinks.
55
+ attr_accessor :symlink_check_interval
56
+
57
+ # Check interval for looking for a file if we are tailing it and it has
58
+ # gone missing.
59
+ attr_accessor :missing_file_check_interval
60
+
61
+ # Tail a file
62
+ #
63
+ # * path is a string file path to tail
64
+ # * startpos is an offset to start tailing the file at. If -1, start at end of
65
+ # file.
66
+ #
67
+ # If you want debug messages, run ruby with '-d' or set $DEBUG
68
+ #
69
+ # See also: EventMachine::file_tail
70
+ #
71
+ public
72
+ def initialize(path, startpos=-1, &block)
73
+ @path = path
74
+ @logger = Logger.new(STDERR)
75
+ @logger.level = ($DEBUG and Logger::DEBUG or Logger::WARN)
76
+ @logger.debug("Tailing #{path} starting at position #{startpos}")
77
+
78
+ @file = nil
79
+ @want_eof_handling = false
80
+ @want_read = false
81
+ @want_reopen = false
82
+ @reopen_on_eof = false
83
+ @symlink_timer = nil
84
+ @missing_file_check_timer = nil
85
+ @read_timer = nil
86
+ @symlink_target = nil
87
+ @symlink_stat = nil
88
+
89
+ @symlink_check_interval = 1
90
+ @missing_file_check_interval = 1
91
+
92
+ read_file_metadata
93
+
94
+ if @filestat.directory?
95
+ on_exception Errno::EISDIR.new(@path)
96
+ end
97
+
98
+ if block_given?
99
+ @handler = block
100
+ @buffer = BufferedTokenizer.new
101
+ end
102
+
103
+ EventMachine::next_tick do
104
+ open
105
+ next unless @file
106
+
107
+ if (startpos == -1)
108
+ @position = @file.sysseek(0, IO::SEEK_END)
109
+ # TODO(sissel): if we don't have inotify or kqueue, should we
110
+ # schedule a next read, here?
111
+ # Is there a race condition between setting the file position and
112
+ # watching given the two together are not atomic?
113
+ else
114
+ @position = @file.sysseek(startpos, IO::SEEK_SET)
115
+ schedule_next_read
116
+ end
117
+ watch
118
+ end # EventMachine::next_tick
119
+ end # def initialize
120
+
121
+ # This method is called when a tailed file has data read.
122
+ #
123
+ # * data - string data read from the file.
124
+ #
125
+ # If you want to read lines from your file, you should use BufferedTokenizer
126
+ # (which comes with EventMachine):
127
+ # class Tailer < EventMachine::FileTail
128
+ # def initialize(*args)
129
+ # super(*args)
130
+ # @buffer = BufferedTokenizer.new
131
+ # end
132
+ #
133
+ # def receive_data(data)
134
+ # @buffer.extract(data).each do |line|
135
+ # # do something with 'line'
136
+ # end
137
+ # end
138
+ public
139
+ def receive_data(data)
140
+ if @handler # FileTail.new called with a block
141
+ @buffer.extract(data).each do |line|
142
+ @handler.call(self, line)
143
+ end
144
+ else
145
+ on_exception NotImplementedError.new("#{self.class.name}#receive_data is not "\
146
+ "implemented. Did you forget to implement this in your subclass or "\
147
+ "module?")
148
+ end
149
+ end # def receive_data
150
+
151
+ def on_exception(exception)
152
+ @logger.error("Exception raised. Using default handler in #{self.class.name}")
153
+ raise exception
154
+ end
155
+
156
+ # This method is called when a tailed file reaches EOF.
157
+ #
158
+ # If you want to stop reading this file, call close(), otherwise
159
+ # this eof is handled as normal tailing does. The default
160
+ # EOF handler is to do nothing.
161
+ public
162
+ def eof
163
+ @logger.debug { 'EOF' }
164
+ # do nothing, subclassers should implement this.
165
+ end # def eof
166
+
167
+ # notify is invoked by EM::watch_file when the file you are tailing has been
168
+ # modified or otherwise needs to be acted on.
169
+ private
170
+ def notify(status)
171
+ @logger.debug { "notify: #{status} on #{path}" }
172
+ if status == :modified
173
+ schedule_next_read
174
+ elsif status == :moved
175
+ # read to EOF, then reopen.
176
+ schedule_next_read
177
+ elsif status == :unbind
178
+ # :unbind is called after the :deleted handler
179
+ # :deleted happens on FreeBSD's newsyslog instead of :moved
180
+ # clean up @watch since its reference is wiped in EM's file_deleted callback
181
+ @watch = nil
182
+ end
183
+ end # def notify
184
+
185
+ # Open (or reopen, if necessary) our file and schedule a read.
186
+ private
187
+ def open
188
+ return if @closed
189
+ @file.close if @file && !@file.closed?
190
+ return unless File.exists?(@path)
191
+ begin
192
+ @logger.debug "Opening file #{@path}"
193
+ @file = File.open(@path, "r")
194
+ rescue Errno::ENOENT => e
195
+ @logger.info("File not found: '#{@path}' (#{e})")
196
+ on_exception(e)
197
+ end
198
+
199
+ @naptime = 0
200
+ @logger.debug { 'EOF' }
201
+ @position = 0
202
+ schedule_next_read
203
+ end # def open
204
+
205
+ # Close this filetail
206
+ public
207
+ def close
208
+ @closed = true
209
+ @want_read = false
210
+ EM.schedule do
211
+ @watch.stop_watching if @watch
212
+ EventMachine::cancel_timer(@read_timer) if @read_timer
213
+ @symlink_timer.cancel if @symlink_timer
214
+ @missing_file_check_timer.cancel if @missing_file_check_timer
215
+ @file.close if @file
216
+ end
217
+ end # def close
218
+
219
+ # More rubyesque way of checking if this tail is closed
220
+ public
221
+ def closed?
222
+ @closed
223
+ end
224
+
225
+ # Watch our file.
226
+ private
227
+ def watch
228
+ @watch.stop_watching if @watch
229
+ @symlink_timer.cancel if @symlink_timer
230
+ return unless File.exists?(@path)
231
+
232
+ @logger.debug "Starting watch on #{@path}"
233
+ callback = proc { |what| notify(what) }
234
+ @watch = EventMachine::watch_file(@path, EventMachine::FileTail::FileWatcher, callback)
235
+ watch_symlink if @symlink_target
236
+ end # def watch
237
+
238
+ # Watch a symlink
239
+ # EM doesn't currently support watching symlinks alone (inotify follows
240
+ # symlinks by default), so let's periodically stat the symlink.
241
+ private
242
+ def watch_symlink(&block)
243
+ @symlink_timer.cancel if @symlink_timer
244
+
245
+ @logger.debug "Launching timer to check for symlink changes since EM can't right now: #{@path}"
246
+ @symlink_timer = EM::PeriodicTimer.new(@symlink_check_interval) do
247
+ begin
248
+ @logger.debug("Checking #{@path}")
249
+ read_file_metadata do |filestat, linkstat, linktarget|
250
+ handle_fstat(filestat, linkstat, linktarget)
251
+ end
252
+ rescue Errno::ENOENT
253
+ # The file disappeared. Wait for it to reappear.
254
+ # This can happen if it was deleted or moved during log rotation.
255
+ @logger.debug "File not found, waiting for it to reappear. (#{@path})"
256
+ end # begin/rescue ENOENT
257
+ end # EM::PeriodicTimer
258
+ end # def watch_symlink
259
+
260
+ private
261
+ def schedule_next_read
262
+ if !@want_read
263
+ @want_read = true
264
+ @read_timer = EventMachine::add_timer(@naptime) do
265
+ @want_read = false
266
+ read
267
+ end
268
+ end # if !@want_read
269
+ end # def schedule_next_read
270
+
271
+ # Read CHUNKSIZE from our file and pass it to .receive_data()
272
+ private
273
+ def read
274
+ return if @closed
275
+
276
+ data = nil
277
+ @logger.debug "#{self}: Reading..."
278
+ begin
279
+ data = @file.sysread(CHUNKSIZE)
280
+ rescue EOFError, IOError
281
+ schedule_eof
282
+ return
283
+ end
284
+
285
+ data.force_encoding(@file.external_encoding) if FORCE_ENCODING
286
+
287
+ # Won't get here if sysread throws EOF
288
+ @position += data.length
289
+ @naptime = 0
290
+
291
+ # Subclasses should implement receive_data
292
+ receive_data(data)
293
+ schedule_next_read
294
+ end # def read
295
+
296
+ # Do EOF handling on next EM iteration
297
+ private
298
+ def schedule_eof
299
+ if !@want_eof_handling
300
+ eof # Call our own eof event
301
+ @want_eof_handling = true
302
+ EventMachine::next_tick do
303
+ handle_eof
304
+ end # EventMachine::next_tick
305
+ end # if !@want_eof_handling
306
+ end # def schedule_eof
307
+
308
+ private
309
+ def schedule_reopen
310
+ if !@want_reopen
311
+ EventMachine::next_tick do
312
+ @want_reopen = false
313
+ open
314
+ watch
315
+ end
316
+ end # if !@want_reopen
317
+ end # def schedule_reopen
318
+
319
+ private
320
+ def handle_eof
321
+ @want_eof_handling = false
322
+
323
+ if @reopen_on_eof
324
+ @reopen_on_eof = false
325
+ schedule_reopen
326
+ end
327
+
328
+ # EOF actions:
329
+ # - Check if the file inode/device is changed
330
+ # - If symlink, check if the symlink has changed
331
+ # - Otherwise, do nothing
332
+ begin
333
+ read_file_metadata do |filestat, linkstat, linktarget|
334
+ handle_fstat(filestat, linkstat, linktarget)
335
+ end
336
+ rescue Errno::ENOENT
337
+ # The file disappeared. Wait for it to reappear.
338
+ # This can happen if it was deleted or moved during log rotation.
339
+ @missing_file_check_timer = EM::PeriodicTimer.new(@missing_file_check_interval) do
340
+ begin
341
+ read_file_metadata do |filestat, linkstat, linktarget|
342
+ handle_fstat(filestat, linkstat, linktarget)
343
+ end
344
+ @missing_file_check_timer.cancel
345
+ rescue Errno::ENOENT
346
+ # The file disappeared. Wait for it to reappear.
347
+ # This can happen if it was deleted or moved during log rotation.
348
+ @logger.debug "File not found, waiting for it to reappear. (#{@path})"
349
+ end # begin/rescue ENOENT
350
+ end # EM::PeriodicTimer
351
+ end # begin/rescue ENOENT
352
+ end # def handle_eof
353
+
354
+ private
355
+ def read_file_metadata(&block)
356
+ begin
357
+ filestat = File.stat(@path)
358
+ symlink_stat = nil
359
+ symlink_target = nil
360
+
361
+ if filestat.symlink?
362
+ symlink_stat = File.lstat(@path) rescue nil
363
+ symlink_target = File.readlink(@path) rescue nil
364
+ end
365
+ rescue Errno::ENOENT
366
+ raise
367
+ rescue => e
368
+ @logger.debug("File stat on '#{@path}' failed")
369
+ on_exception e
370
+ end
371
+
372
+ if block_given?
373
+ yield filestat, symlink_stat, symlink_target
374
+ end
375
+
376
+ @filestat = filestat
377
+ @symlink_stat = symlink_stat
378
+ @symlink_target = symlink_target
379
+ end # def read_file_metadata
380
+
381
+ # Handle fstat changes appropriately.
382
+ private
383
+ def handle_fstat(filestat, symlinkstat, symlinktarget)
384
+ # If the symlink target changes, the filestat.ino is very likely to have
385
+ # changed since that is the stat on the resolved file (that the link points
386
+ # to). However, we'll check explicitly for the symlink target changing
387
+ # for better debuggability.
388
+ if symlinktarget
389
+ if symlinkstat.ino != @symlink_stat.ino
390
+ @logger.debug "Inode or device changed on symlink. Reopening..."
391
+ @reopen_on_eof = true
392
+ schedule_next_read
393
+ elsif symlinktarget != @symlink_target
394
+ @logger.debug "Symlink target changed. Reopening..."
395
+ @reopen_on_eof = true
396
+ schedule_next_read
397
+ end
398
+ elsif (filestat.ino != @filestat.ino or filestat.rdev != @filestat.rdev)
399
+ @logger.debug "Inode or device changed. Reopening..."
400
+ @logger.debug filestat
401
+ @reopen_on_eof = true
402
+ schedule_next_read
403
+ elsif (filestat.size < @filestat.size)
404
+ # If the file size shrank, assume truncation and seek to the beginning.
405
+ @logger.info("File likely truncated... #{path}")
406
+ @position = @file.sysseek(0, IO::SEEK_SET)
407
+ schedule_next_read
408
+ end
409
+ end # def handle_fstat
410
+
411
+ def to_s
412
+ return "#{self.class.name}(#{@path}) @ pos:#{@position}"
413
+ end # def to_s
414
+ end # class EventMachine::FileTail
415
+
416
+ # Internal usage only. This class is used by EventMachine::FileTail
417
+ # to watch files you are tailing.
418
+ #
419
+ # See also: EventMachine::FileTail#watch
420
+ class EventMachine::FileTail::FileWatcher < EventMachine::FileWatch
421
+ def initialize(block)
422
+ @logger = Logger.new(STDERR)
423
+ @logger.level = ($DEBUG and Logger::DEBUG or Logger::WARN)
424
+ @callback = block
425
+ end # def initialize
426
+
427
+ def file_modified
428
+ @callback.call(:modified)
429
+ end # def file_modified
430
+
431
+ def file_moved
432
+ @callback.call(:moved)
433
+ end # def file_moved
434
+
435
+ def file_deleted
436
+ @callback.call(:deleted)
437
+ end # def file_deleted
438
+
439
+ def unbind
440
+ @callback.call(:unbind)
441
+ end # def unbind
442
+ end # class EventMachine::FileTail::FileWatch < EventMachine::FileWatch
443
+
444
+ # Add EventMachine::file_tail
445
+ module EventMachine
446
+ # Tail a file.
447
+ #
448
+ # path is the path to the file to tail.
449
+ # handler should be a module implementing 'receive_data' or
450
+ # must be a subclasses of EventMachine::FileTail
451
+ #
452
+ # For example:
453
+ # EM::file_tail("/var/log/messages", MyHandler)
454
+ #
455
+ # If a block is given, and the handler is not specified or does
456
+ # not implement EventMachine::FileTail#receive_data, then it
457
+ # will be called as such:
458
+ # EM::file_tail(...) do |filetail, line|
459
+ # # filetail is the FileTail instance watching the file
460
+ # # line is the line read from the file
461
+ # end
462
+ def self.file_tail(path, handler=nil, *args, &block)
463
+ # This code mostly styled on what EventMachine does in many of it's other
464
+ # methods.
465
+ args = [path, *args]
466
+ klass = klass_from_handler(EventMachine::FileTail, handler, *args);
467
+ c = klass.new(*args, &block)
468
+ return c
469
+ end # def self.file_tail
470
+ end # module EventMachine