meepo 1.5.2

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 (108) hide show
  1. checksums.yaml +7 -0
  2. data/.coveralls.yml +1 -0
  3. data/.gitignore +16 -0
  4. data/.rspec +2 -0
  5. data/.rubocop.yml +29 -0
  6. data/.travis.yml +10 -0
  7. data/Dockerfile +7 -0
  8. data/Gemfile +13 -0
  9. data/MIT-LICENSE +20 -0
  10. data/Rakefile +15 -0
  11. data/TODO +5 -0
  12. data/bin/invoker +7 -0
  13. data/contrib/completion/invoker-completion.bash +70 -0
  14. data/contrib/completion/invoker-completion.zsh +62 -0
  15. data/examples/hello_sinatra.rb +26 -0
  16. data/examples/sample.ini +3 -0
  17. data/invoker.gemspec +43 -0
  18. data/lib/invoker.rb +152 -0
  19. data/lib/invoker/cli.rb +159 -0
  20. data/lib/invoker/cli/pinger.rb +23 -0
  21. data/lib/invoker/cli/question.rb +15 -0
  22. data/lib/invoker/cli/tail.rb +34 -0
  23. data/lib/invoker/cli/tail_watcher.rb +34 -0
  24. data/lib/invoker/command_worker.rb +60 -0
  25. data/lib/invoker/commander.rb +95 -0
  26. data/lib/invoker/daemon.rb +126 -0
  27. data/lib/invoker/dns_cache.rb +23 -0
  28. data/lib/invoker/errors.rb +17 -0
  29. data/lib/invoker/event/manager.rb +79 -0
  30. data/lib/invoker/ipc.rb +45 -0
  31. data/lib/invoker/ipc/add_command.rb +12 -0
  32. data/lib/invoker/ipc/add_http_command.rb +10 -0
  33. data/lib/invoker/ipc/base_command.rb +24 -0
  34. data/lib/invoker/ipc/client_handler.rb +26 -0
  35. data/lib/invoker/ipc/dns_check_command.rb +17 -0
  36. data/lib/invoker/ipc/list_command.rb +11 -0
  37. data/lib/invoker/ipc/message.rb +170 -0
  38. data/lib/invoker/ipc/message/list_response.rb +35 -0
  39. data/lib/invoker/ipc/message/tail_response.rb +10 -0
  40. data/lib/invoker/ipc/ping_command.rb +10 -0
  41. data/lib/invoker/ipc/reload_command.rb +12 -0
  42. data/lib/invoker/ipc/remove_command.rb +12 -0
  43. data/lib/invoker/ipc/server.rb +26 -0
  44. data/lib/invoker/ipc/tail_command.rb +11 -0
  45. data/lib/invoker/ipc/unix_client.rb +60 -0
  46. data/lib/invoker/logger.rb +13 -0
  47. data/lib/invoker/parsers/config.rb +184 -0
  48. data/lib/invoker/parsers/procfile.rb +86 -0
  49. data/lib/invoker/power/balancer.rb +131 -0
  50. data/lib/invoker/power/config.rb +77 -0
  51. data/lib/invoker/power/dns.rb +38 -0
  52. data/lib/invoker/power/http_parser.rb +68 -0
  53. data/lib/invoker/power/http_response.rb +81 -0
  54. data/lib/invoker/power/pf_migrate.rb +64 -0
  55. data/lib/invoker/power/port_finder.rb +49 -0
  56. data/lib/invoker/power/power.rb +3 -0
  57. data/lib/invoker/power/powerup.rb +29 -0
  58. data/lib/invoker/power/setup.rb +90 -0
  59. data/lib/invoker/power/setup/distro/arch.rb +15 -0
  60. data/lib/invoker/power/setup/distro/base.rb +57 -0
  61. data/lib/invoker/power/setup/distro/debian.rb +11 -0
  62. data/lib/invoker/power/setup/distro/mint.rb +10 -0
  63. data/lib/invoker/power/setup/distro/opensuse.rb +11 -0
  64. data/lib/invoker/power/setup/distro/redhat.rb +11 -0
  65. data/lib/invoker/power/setup/distro/ubuntu.rb +10 -0
  66. data/lib/invoker/power/setup/files/invoker_forwarder.sh.erb +17 -0
  67. data/lib/invoker/power/setup/files/socat_invoker.service +12 -0
  68. data/lib/invoker/power/setup/linux_setup.rb +105 -0
  69. data/lib/invoker/power/setup/osx_setup.rb +137 -0
  70. data/lib/invoker/power/templates/400.html +40 -0
  71. data/lib/invoker/power/templates/404.html +40 -0
  72. data/lib/invoker/power/templates/503.html +40 -0
  73. data/lib/invoker/power/url_rewriter.rb +40 -0
  74. data/lib/invoker/process_manager.rb +198 -0
  75. data/lib/invoker/process_printer.rb +43 -0
  76. data/lib/invoker/reactor.rb +37 -0
  77. data/lib/invoker/reactor/reader.rb +54 -0
  78. data/lib/invoker/version.rb +47 -0
  79. data/readme.md +25 -0
  80. data/spec/invoker/cli/pinger_spec.rb +22 -0
  81. data/spec/invoker/cli/tail_watcher_spec.rb +39 -0
  82. data/spec/invoker/cli_spec.rb +27 -0
  83. data/spec/invoker/command_worker_spec.rb +45 -0
  84. data/spec/invoker/commander_spec.rb +152 -0
  85. data/spec/invoker/config_spec.rb +361 -0
  86. data/spec/invoker/daemon_spec.rb +34 -0
  87. data/spec/invoker/event/manager_spec.rb +67 -0
  88. data/spec/invoker/invoker_spec.rb +71 -0
  89. data/spec/invoker/ipc/client_handler_spec.rb +54 -0
  90. data/spec/invoker/ipc/dns_check_command_spec.rb +32 -0
  91. data/spec/invoker/ipc/message/list_response_spec.rb +24 -0
  92. data/spec/invoker/ipc/message_spec.rb +49 -0
  93. data/spec/invoker/ipc/unix_client_spec.rb +29 -0
  94. data/spec/invoker/power/balancer_spec.rb +22 -0
  95. data/spec/invoker/power/config_spec.rb +18 -0
  96. data/spec/invoker/power/http_parser_spec.rb +32 -0
  97. data/spec/invoker/power/http_response_spec.rb +34 -0
  98. data/spec/invoker/power/pf_migrate_spec.rb +87 -0
  99. data/spec/invoker/power/port_finder_spec.rb +16 -0
  100. data/spec/invoker/power/setup/linux_setup_spec.rb +103 -0
  101. data/spec/invoker/power/setup/osx_setup_spec.rb +105 -0
  102. data/spec/invoker/power/setup_spec.rb +4 -0
  103. data/spec/invoker/power/url_rewriter_spec.rb +70 -0
  104. data/spec/invoker/power/web_sockets_spec.rb +61 -0
  105. data/spec/invoker/process_manager_spec.rb +130 -0
  106. data/spec/invoker/reactor_spec.rb +6 -0
  107. data/spec/spec_helper.rb +43 -0
  108. metadata +389 -0
@@ -0,0 +1,159 @@
1
+ require "socket"
2
+ require "thor"
3
+
4
+ module Invoker
5
+ class CLI < Thor
6
+ def self.start(*args)
7
+ cli_args = args.flatten
8
+ # If it is not a valid task, it is probably file argument
9
+ if default_start_command?(cli_args)
10
+ args = [cli_args.unshift("start")]
11
+ end
12
+ super(*args)
13
+ end
14
+
15
+ desc "setup", "Run Invoker setup"
16
+ option :tld,
17
+ type: :string,
18
+ banner: 'Configure invoker to use a different top level domain'
19
+ def setup
20
+ Invoker::Power::Setup.install(get_tld(options))
21
+ end
22
+
23
+ desc "version", "Print Invoker version"
24
+ def version
25
+ Invoker::Logger.puts Invoker::VERSION
26
+ end
27
+ map %w(-v --version) => :version
28
+
29
+ desc "uninstall", "Uninstall Invoker and all installed files"
30
+ def uninstall
31
+ Invoker::Power::Setup.uninstall
32
+ end
33
+
34
+ desc "start [CONFIG_FILE]", "Start Invoker Server"
35
+ option :port, type: :numeric, banner: "Port series to be used for starting rack servers"
36
+ option :daemon,
37
+ type: :boolean,
38
+ banner: "Daemonize the server into the background",
39
+ aliases: [:d]
40
+ def start(file = nil)
41
+ Invoker.setup_config_location
42
+ port = options[:port] || 9000
43
+ Invoker.daemonize = options[:daemon]
44
+ Invoker.load_invoker_config(file, port)
45
+ warn_about_notification
46
+ warn_about_old_configuration
47
+ pinger = Invoker::CLI::Pinger.new(unix_socket)
48
+ abort("Invoker is already running".color(:red)) if pinger.invoker_running?
49
+ Invoker.commander.start_manager
50
+ end
51
+
52
+ desc "add process", "Add a program to Invoker server"
53
+ def add(name)
54
+ unix_socket.send_command('add', process_name: name)
55
+ end
56
+
57
+ desc "add_http process_name port [IP]", "Add an external http process to Invoker DNS server"
58
+ def add_http(name, port, ip = nil)
59
+ unix_socket.send_command('add_http', process_name: name, port: port, ip: ip)
60
+ end
61
+
62
+ desc "tail process1 process2", "Tail a particular process"
63
+ def tail(*names)
64
+ tailer = Invoker::CLI::Tail.new(names)
65
+ tailer.run
66
+ end
67
+
68
+ desc "reload process", "Reload a process managed by Invoker"
69
+ option :signal,
70
+ banner: "Signal to send for killing the process, default is SIGINT",
71
+ aliases: [:s]
72
+ def reload(name)
73
+ signal = options[:signal] || 'INT'
74
+ unix_socket.send_command('reload', process_name: name, signal: signal)
75
+ end
76
+
77
+ desc "list", "List all running processes"
78
+ def list
79
+ unix_socket.send_command('list') do |response_object|
80
+ Invoker::ProcessPrinter.new(response_object).tap { |printer| printer.print_table }
81
+ end
82
+ end
83
+
84
+ desc "remove process", "Stop a process managed by Invoker"
85
+ option :signal,
86
+ banner: "Signal to send for killing the process, default is SIGINT",
87
+ aliases: [:s]
88
+ def remove(name)
89
+ signal = options[:signal] || 'INT'
90
+ unix_socket.send_command('remove', process_name: name, signal: signal)
91
+ end
92
+
93
+ desc "stop", "Stop Invoker daemon"
94
+ def stop
95
+ Invoker.daemon.stop
96
+ end
97
+
98
+ private
99
+
100
+ def self.default_start_command?(args)
101
+ command_name = args.first
102
+ command_name &&
103
+ !command_name.match(/^-/) &&
104
+ !valid_tasks.include?(command_name)
105
+ end
106
+
107
+ def self.valid_tasks
108
+ tasks.keys + ["help"]
109
+ end
110
+
111
+ def get_tld(options)
112
+ if options[:tld] && !options[:tld].empty?
113
+ options[:tld]
114
+ else
115
+ 'dev'
116
+ end
117
+ end
118
+
119
+ def unix_socket
120
+ Invoker::IPC::UnixClient.new
121
+ end
122
+
123
+ def warn_about_notification
124
+ if Invoker.darwin?
125
+ warn_about_terminal_notifier
126
+ else
127
+ warn_about_libnotify
128
+ end
129
+ end
130
+
131
+ def warn_about_libnotify
132
+ require "libnotify"
133
+ rescue LoadError
134
+ Invoker::Logger.puts "You can install libnotify gem for Invoker notifications "\
135
+ "via system tray".color(:red)
136
+ end
137
+
138
+ def warn_about_terminal_notifier
139
+ if Invoker.darwin?
140
+ command_path = `which terminal-notifier`
141
+ if !command_path || command_path.empty?
142
+ Invoker::Logger.puts "You can enable OSX notification for processes "\
143
+ "by installing terminal-notifier gem".color(:red)
144
+ end
145
+ end
146
+ end
147
+
148
+ def warn_about_old_configuration
149
+ Invoker::Power::PfMigrate.new.tap do |pf_migrator|
150
+ pf_migrator.migrate
151
+ end
152
+ end
153
+ end
154
+ end
155
+
156
+ require "invoker/cli/question"
157
+ require "invoker/cli/tail_watcher"
158
+ require "invoker/cli/tail"
159
+ require "invoker/cli/pinger"
@@ -0,0 +1,23 @@
1
+ require "timeout"
2
+
3
+ module Invoker
4
+ class CLI::Pinger
5
+ attr_accessor :unix_client
6
+ def initialize(unix_client)
7
+ @unix_client = unix_client
8
+ end
9
+
10
+ def invoker_running?
11
+ response = send_ping_and_read_response
12
+ response && response.status == 'pong'
13
+ end
14
+
15
+ private
16
+
17
+ def send_ping_and_read_response
18
+ Timeout.timeout(2) { unix_client.send_and_receive('ping') }
19
+ rescue Timeout::Error
20
+ nil
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,15 @@
1
+ module Invoker
2
+ class CLI::Question
3
+ def self.agree(question_text)
4
+ $stdout.print(question_text)
5
+ answer = $stdin.gets
6
+ answer.strip!
7
+ if answer =~ /\Ay(?:es)?|no?\Z/i
8
+ answer =~ /\Ay(?:es)?\Z/i
9
+ else
10
+ $stdout.puts "Please enter 'yes' or 'no'."
11
+ agree(question_text)
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,34 @@
1
+ module Invoker
2
+ class CLI::Tail
3
+ attr_accessor :process_names
4
+ def initialize(process_names)
5
+ verify_process_name(process_names)
6
+ @process_names = process_names
7
+ @unix_socket = Invoker::IPC::UnixClient.new
8
+ end
9
+
10
+ def run
11
+ socket = @unix_socket.send_and_wait('tail', process_names: process_names)
12
+ trap('INT') { socket.close }
13
+ loop do
14
+ message = read_next_line(socket)
15
+ break unless message
16
+ puts message.tail_line
17
+ end
18
+ end
19
+
20
+ private
21
+
22
+ def verify_process_name(process_names)
23
+ if process_names.empty?
24
+ abort("Tail command requires one or more process name")
25
+ end
26
+ end
27
+
28
+ def read_next_line(socket)
29
+ Invoker::IPC.message_from_io(socket)
30
+ rescue
31
+ nil
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,34 @@
1
+ module Invoker
2
+ # This class defines sockets which are open for watching log files
3
+ class CLI::TailWatcher
4
+ attr_accessor :tail_watchers
5
+
6
+ def initialize
7
+ @tail_mutex = Mutex.new
8
+ @tail_watchers = Hash.new { |h, k| h[k] = [] }
9
+ end
10
+
11
+ def [](process_name)
12
+ @tail_mutex.synchronize { tail_watchers[process_name] }
13
+ end
14
+
15
+ def add(names, socket)
16
+ @tail_mutex.synchronize do
17
+ names.each { |name| tail_watchers[name] << socket }
18
+ end
19
+ end
20
+
21
+ def remove(name, socket)
22
+ @tail_mutex.synchronize do
23
+ client_list = tail_watchers[name]
24
+ client_list.delete(socket)
25
+ purge(name, socket) if client_list.empty?
26
+ end
27
+ end
28
+
29
+ def purge(name, socket)
30
+ tail_watchers.delete(name)
31
+ Invoker.close_socket(socket)
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,60 @@
1
+ module Invoker
2
+ class CommandWorker
3
+ attr_accessor :command_label, :pipe_end, :pid, :color
4
+
5
+ def initialize(command_label, pipe_end, pid, color)
6
+ @command_label = command_label
7
+ @pipe_end = pipe_end
8
+ @pid = pid
9
+ @color = color
10
+ end
11
+
12
+ # Copied verbatim from Eventmachine code
13
+ def receive_data data
14
+ (@buf ||= '') << data
15
+
16
+ while @buf.slice!(/(.*?)\r?\n/)
17
+ receive_line($1)
18
+ end
19
+ end
20
+
21
+ def unbind
22
+ Invoker::Logger.print(".")
23
+ end
24
+
25
+ # Print the lines received over the network
26
+ def receive_line(line)
27
+ tail_watchers = Invoker.tail_watchers[@command_label]
28
+ color_line = "#{@command_label.color(color)} : #{line}"
29
+ if tail_watchers && !tail_watchers.empty?
30
+ json_encoded_tail_response = tail_response(color_line)
31
+ if json_encoded_tail_response
32
+ tail_watchers.each { |tail_socket| send_data(tail_socket, json_encoded_tail_response) }
33
+ end
34
+ else
35
+ Invoker::Logger.puts color_line
36
+ end
37
+ end
38
+
39
+ def to_h
40
+ { command_label: command_label, pid: pid.to_s }
41
+ end
42
+
43
+ def send_data(socket, data)
44
+ socket.write(data)
45
+ rescue
46
+ Invoker::Logger.puts "Removing #{@command_label} watcher #{socket} from list"
47
+ Invoker.tail_watchers.remove(@command_label, socket)
48
+ end
49
+
50
+ private
51
+
52
+ # Encode current line as json and send the response.
53
+ def tail_response(line)
54
+ tail_response = Invoker::IPC::Message::TailResponse.new(tail_line: line)
55
+ tail_response.encoded_message
56
+ rescue
57
+ nil
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,95 @@
1
+ require "io/console"
2
+ require 'pty'
3
+ require "json"
4
+ require "dotenv"
5
+ require "forwardable"
6
+
7
+ module Invoker
8
+ class Commander
9
+ attr_accessor :reactor, :process_manager
10
+ attr_accessor :event_manager, :runnables, :thread_group
11
+ extend Forwardable
12
+
13
+ def_delegators :@process_manager, :start_process_by_name, :stop_process
14
+ def_delegators :@process_manager, :restart_process, :get_worker_from_fd, :process_list
15
+
16
+ def_delegators :@event_manager, :schedule_event, :trigger
17
+ def_delegator :@reactor, :watch_for_read
18
+
19
+ def initialize
20
+ @thread_group = ThreadGroup.new
21
+ @runnable_mutex = Mutex.new
22
+
23
+ @event_manager = Invoker::Event::Manager.new
24
+ @runnables = []
25
+
26
+ @reactor = Invoker::Reactor.new
27
+ @process_manager = Invoker::ProcessManager.new
28
+ Thread.abort_on_exception = true
29
+ end
30
+
31
+ # Start the invoker process supervisor. This method starts a unix server
32
+ # in separate thread that listens for incoming commands.
33
+ def start_manager
34
+ verify_process_configuration
35
+ daemonize_app if Invoker.daemonize?
36
+ install_interrupt_handler
37
+ unix_server_thread = Thread.new { Invoker::IPC::Server.new }
38
+ @thread_group.add(unix_server_thread)
39
+ process_manager.run_power_server
40
+ Invoker.config.autorunnable_processes.each do |process_info|
41
+ process_manager.start_process(process_info)
42
+ Logger.puts("Starting process - #{process_info.label} waiting for #{process_info.sleep_duration} seconds...")
43
+ sleep(process_info.sleep_duration)
44
+ end
45
+ at_exit { process_manager.kill_workers }
46
+ start_event_loop
47
+ end
48
+
49
+ def on_next_tick(*args, &block)
50
+ @runnable_mutex.synchronize do
51
+ @runnables << OpenStruct.new(:args => args, :block => block)
52
+ end
53
+ end
54
+
55
+ def run_runnables
56
+ @runnables.each do |runnable|
57
+ instance_exec(*runnable.args, &runnable.block)
58
+ end
59
+ @runnables = []
60
+ end
61
+
62
+ private
63
+
64
+ def verify_process_configuration
65
+ if !Invoker.config.processes || Invoker.config.processes.empty?
66
+ raise Invoker::Errors::InvalidConfig.new("No processes configured in config file")
67
+ end
68
+ end
69
+
70
+ def start_event_loop
71
+ loop do
72
+ reactor.monitor_for_fd_events
73
+ run_runnables
74
+ run_scheduled_events
75
+ end
76
+ end
77
+
78
+ def run_scheduled_events
79
+ event_manager.run_scheduled_events do |event|
80
+ event.block.call
81
+ end
82
+ end
83
+
84
+ def install_interrupt_handler
85
+ Signal.trap("INT") do
86
+ process_manager.kill_workers
87
+ exit(0)
88
+ end
89
+ end
90
+
91
+ def daemonize_app
92
+ Invoker.daemon.start
93
+ end
94
+ end
95
+ end
@@ -0,0 +1,126 @@
1
+ module Invoker
2
+ # rip off from borg
3
+ # https://github.com/code-mancers/borg/blob/master/lib/borg/borg_daemon.rb
4
+ class Daemon
5
+ attr_reader :process_name
6
+
7
+ def initialize
8
+ @process_name = 'invoker'
9
+ end
10
+
11
+ def start
12
+ if running?
13
+ Invoker::Logger.puts "Invoker daemon is already running"
14
+ exit(0)
15
+ elsif dead?
16
+ File.delete(pid_file) if File.exists?(pid_file)
17
+ end
18
+ Invoker::Logger.puts "Running Invoker daemon"
19
+ daemonize
20
+ end
21
+
22
+ def stop
23
+ kill_process
24
+ end
25
+
26
+ def pid_file
27
+ File.join(Invoker.home, ".invoker", "#{process_name}.pid")
28
+ end
29
+
30
+ def pid
31
+ File.read(pid_file).strip.to_i
32
+ end
33
+
34
+ def log_file
35
+ File.join(Invoker.home, ".invoker", "#{process_name}.log")
36
+ end
37
+
38
+ def daemonize
39
+ if fork
40
+ sleep(2)
41
+ exit(0)
42
+ else
43
+ Process.setsid
44
+ File.open(pid_file, "w") do |file|
45
+ file.write(Process.pid.to_s)
46
+ end
47
+ Invoker::Logger.puts "Invoker daemon log is available at #{log_file}"
48
+ redirect_io(log_file)
49
+ $0 = process_name
50
+ end
51
+ end
52
+
53
+ def kill_process
54
+ pgid = Process.getpgid(pid)
55
+ Process.kill('-TERM', pgid)
56
+ File.delete(pid_file) if File.exist?(pid_file)
57
+ Invoker::Logger.puts "Stopped Invoker daemon"
58
+ end
59
+
60
+ def process_running?
61
+ Process.kill(0, pid)
62
+ true
63
+ rescue Errno::ESRCH
64
+ false
65
+ end
66
+
67
+ def status
68
+ @status ||= check_process_status
69
+ end
70
+
71
+ def pidfile_exists?
72
+ File.exist?(pid_file)
73
+ end
74
+
75
+ def running?
76
+ status == 0
77
+ end
78
+
79
+ # pidfile exists but process isn't running
80
+ def dead?
81
+ status == 1
82
+ end
83
+
84
+ private
85
+
86
+ def check_process_status
87
+ if pidfile_exists? && process_running?
88
+ 0
89
+ elsif pidfile_exists? # but not process_running
90
+ 1
91
+ else
92
+ 3
93
+ end
94
+ end
95
+
96
+ def redirect_io(logfile_name = nil)
97
+ redirect_file_to_target($stdin)
98
+ redirect_stdout(logfile_name)
99
+ redirect_stderr
100
+ end
101
+
102
+ def redirect_stderr
103
+ redirect_file_to_target($stderr, $stdout)
104
+ $stderr.sync = true
105
+ end
106
+
107
+ def redirect_stdout(logfile_name)
108
+ if logfile_name
109
+ begin
110
+ $stdout.reopen logfile_name, "a"
111
+ $stdout.sync = true
112
+ rescue StandardError
113
+ redirect_file_to_target($stdout)
114
+ end
115
+ else
116
+ redirect_file_to_target($stdout)
117
+ end
118
+ end
119
+
120
+ def redirect_file_to_target(file, target = "/dev/null")
121
+ begin
122
+ file.reopen(target)
123
+ rescue; end
124
+ end
125
+ end
126
+ end