invoker 0.1.2 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,15 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: cb5db9a4721bdd2207ddcc8df052b13535bb9ff2
4
- data.tar.gz: 1595dbd8535de9debb91405e6538eaa2ecc94533
5
- SHA512:
6
- metadata.gz: 799b0482eef58b4c28c4db4bcc6d643488ee37a11597960b1d866b8a8d5469381cc22aba13ca39a6457006242224dff401050979775acddb3061cf2b317a65b8
7
- data.tar.gz: 614f1643928310cc009886cc42cc5552508391fd553c7b555c60e3e867bd4d8a65a7ad45181215c968850d942dc6bf68d8dad2fac0ae73c944cad4d1b9749b90
2
+ !binary "U0hBMQ==":
3
+ metadata.gz: !binary |-
4
+ YWUzNjRlNGZlNTI4Njg4NjJmNmRiYWQ3NzdkZGRhMDM0MzFjMGYxMg==
5
+ data.tar.gz: !binary |-
6
+ N2NkYjJiMGM4YmZjZmM4MzQyYTM4YzgzODg5MWMxN2FiMWI3OTQ5MA==
7
+ !binary "U0hBNTEy":
8
+ metadata.gz: !binary |-
9
+ YWRhNDI1YTNkMDczMDk0YzQ4MzM1NzczN2U1ZGQzNDdiZWRjY2Y4MDQyMGZk
10
+ NjUyY2M0ZDUzMzgzZmJhOWIxY2ZlYmNkNjY0ZTFkYTllMmE1OTQyNWU0OTIz
11
+ ZTA4YzNlNzUyYmViNWMyMzBiMDc3NzFjZDY4ODE4ZTE2NjBlZGU=
12
+ data.tar.gz: !binary |-
13
+ YzgzYzQ4Zjk4ZGMzZTY4ZDFjOGI5N2MxMWMyMGYwMWMwOGZlZWMwZjYxNDAy
14
+ OWI1MzQ2ZGZkMmQ0ZGFjNzk2Zjc4YWM3ZDhkYTQ3YzY0NGMyYzgwMTM0Mzky
15
+ ZTJmNTQwZWEyNjM5ZWU0YzU1NjMyYzEwZTI1MjhmYmZjN2Q1NzY=
data/.gitignore CHANGED
@@ -3,3 +3,6 @@ config/
3
3
  *.gem
4
4
  Gemfile.lock
5
5
  pkg/
6
+ local.ini
7
+ multi.ini
8
+ tags
data/Gemfile CHANGED
@@ -3,4 +3,3 @@ source "https://rubygems.org"
3
3
  gemspec
4
4
 
5
5
  gem 'pry'
6
-
data/TODO ADDED
@@ -0,0 +1,5 @@
1
+ Todos for current release
2
+
3
+ * Implement port support via config file
4
+ * Fix setup command and make it eaiser for people who have pow configured.
5
+ * Possibly add support for foreman?
data/bin/invoker CHANGED
@@ -9,6 +9,3 @@ rescue LoadError
9
9
  end
10
10
 
11
11
  Invoker::Runner.run(ARGV)
12
-
13
-
14
-
data/invoker.gemspec CHANGED
@@ -28,8 +28,13 @@ Gem::Specification.new do |s|
28
28
  s.summary = %q{Something small for Process management}
29
29
  s.add_dependency("slop", "~> 3.4.6")
30
30
  s.add_dependency("iniparse", "~> 1.1.6")
31
- s.add_dependency("colored", "~> 1.2.0")
32
31
  s.add_dependency("formatador", "~> 0.2.4")
32
+ s.add_dependency("eventmachine", "~> 1.0.3")
33
+ s.add_dependency("em-proxy", "~> 0.1.8")
34
+ s.add_dependency("rubydns", "~> 0.6.5")
35
+ s.add_dependency("uuid", "~> 2.3.7")
36
+ s.add_dependency("highline", "~> 1.6.19")
37
+ s.add_dependency("http-parser-lite", "~> 0.6.0")
33
38
  s.add_development_dependency("bacon")
34
39
  s.add_development_dependency("mocha")
35
40
  s.add_development_dependency("mocha-on-bacon")
data/lib/invoker.rb CHANGED
@@ -1,13 +1,18 @@
1
1
  $: << File.dirname(__FILE__) unless $:.include?(File.expand_path(File.dirname(__FILE__)))
2
2
 
3
- require "colored"
4
3
  require "formatador"
4
+ require 'rubydns'
5
+ require 'em-proxy'
6
+ require 'http-parser'
5
7
  require "ostruct"
8
+ require "uuid"
9
+ require "highline"
6
10
  require "invoker/version"
7
11
  require "invoker/logger"
8
12
  require "invoker/runner"
9
13
  require "invoker/command_listener/server"
10
14
  require "invoker/command_listener/client"
15
+ require "invoker/power"
11
16
  require "invoker/errors"
12
17
  require "invoker/parsers/config"
13
18
  require "invoker/parsers/option_parser"
@@ -16,3 +21,23 @@ require "invoker/command_worker"
16
21
  require "invoker/reactor"
17
22
  require "invoker/event/manager"
18
23
  require "invoker/process_printer"
24
+
25
+ module Invoker
26
+ def self.darwin?
27
+ ruby_platform.downcase.include?("darwin")
28
+ end
29
+
30
+ def self.ruby_platform
31
+ RUBY_PLATFORM
32
+ end
33
+
34
+ def self.can_run_balancer?(throw_warning = true)
35
+ return false unless darwin?
36
+ return true if File.exists?(Invoker::Power::Config::CONFIG_LOCATION)
37
+
38
+ if throw_warning
39
+ Invoker::Logger.puts("Invoker has detected setup has not been run. Domain feature will not work without running setup command.".color(:red))
40
+ end
41
+ false
42
+ end
43
+ end
@@ -37,7 +37,7 @@ module Invoker
37
37
  reload_command(b_command_label, b_rest_args)
38
38
  }
39
39
  else
40
- Invoker::Logger.puts("\n Invalid command".red)
40
+ Invoker::Logger.puts("\n Invalid command".color(:red))
41
41
  end
42
42
  end
43
43
  end
@@ -1,7 +1,7 @@
1
1
  module Invoker
2
2
  class CommandWorker
3
3
  attr_accessor :command_label, :pipe_end, :pid, :color
4
-
4
+
5
5
  def initialize(command_label, pipe_end, pid, color)
6
6
  @command_label = command_label
7
7
  @pipe_end = pipe_end
@@ -24,7 +24,7 @@ module Invoker
24
24
 
25
25
  # Print the lines received over the network
26
26
  def receive_line(line)
27
- Invoker::Logger.puts "#{@command_label.send(color)} : #{line}"
27
+ Invoker::Logger.puts "#{@command_label.color(color)} : #{line}"
28
28
  end
29
29
 
30
30
  def to_h
@@ -5,17 +5,17 @@ require "json"
5
5
  module Invoker
6
6
  class Commander
7
7
  MAX_PROCESS_COUNT = 10
8
- LABEL_COLORS = ['green', 'yellow', 'blue', 'magenta', 'cyan']
8
+ LABEL_COLORS = [:green, :yellow, :blue, :magenta, :cyan]
9
9
  attr_accessor :reactor, :workers, :thread_group, :open_pipes
10
10
  attr_accessor :event_manager, :runnables
11
-
11
+
12
12
  def initialize
13
13
  # mapping between open pipes and worker classes
14
14
  @open_pipes = {}
15
15
 
16
16
  # mapping between command label and worker classes
17
17
  @workers = {}
18
-
18
+
19
19
  @thread_group = ThreadGroup.new()
20
20
  @worker_mutex = Mutex.new()
21
21
 
@@ -35,7 +35,9 @@ module Invoker
35
35
  install_interrupt_handler()
36
36
  unix_server_thread = Thread.new { Invoker::CommandListener::Server.new() }
37
37
  thread_group.add(unix_server_thread)
38
+ run_power_server()
38
39
  Invoker::CONFIG.processes.each { |process_info| add_command(process_info) }
40
+ at_exit { kill_workers }
39
41
  start_event_loop()
40
42
  end
41
43
 
@@ -65,9 +67,7 @@ module Invoker
65
67
  #
66
68
  # @param command_label [String] Command label of process specified in config file.
67
69
  def add_command_by_label(command_label)
68
- process_info = Invoker::CONFIG.processes.detect {|pconfig|
69
- pconfig.label == command_label
70
- }
70
+ process_info = Invoker::CONFIG.process(command_label)
71
71
  if process_info
72
72
  add_command(process_info)
73
73
  end
@@ -97,7 +97,7 @@ module Invoker
97
97
  return false unless worker
98
98
  signal_to_use = rest_args ? Array(rest_args).first : 'INT'
99
99
 
100
- Invoker::Logger.puts("Removing #{command_label} with signal #{signal_to_use}".red)
100
+ Invoker::Logger.puts("Removing #{command_label} with signal #{signal_to_use}".color(:red))
101
101
  kill_or_remove_process(worker.pid, signal_to_use, command_label)
102
102
  end
103
103
 
@@ -129,7 +129,19 @@ module Invoker
129
129
  end
130
130
  @runnables = []
131
131
  end
132
-
132
+
133
+ def run_power_server
134
+ return unless Invoker.can_run_balancer?(false)
135
+
136
+ powerup_id = Invoker::Power::Powerup.fork_and_start()
137
+ wait_on_pid("powerup_manager", powerup_id)
138
+ at_exit {
139
+ begin
140
+ Process.kill("INT", powerup_id)
141
+ rescue Errno::ESRCH; end
142
+ }
143
+ end
144
+
133
145
  private
134
146
  def start_event_loop
135
147
  loop do
@@ -152,7 +164,7 @@ module Invoker
152
164
  remove_worker(command_label, false)
153
165
  false
154
166
  end
155
-
167
+
156
168
  def process_kill(pid, signal_to_use)
157
169
  if signal_to_use.to_i == 0
158
170
  Process.kill(signal_to_use, pid)
@@ -166,7 +178,7 @@ module Invoker
166
178
  LABEL_COLORS.push(selected_color)
167
179
  selected_color
168
180
  end
169
-
181
+
170
182
  # Remove worker from all collections
171
183
  def remove_worker(command_label, trigger_event = true)
172
184
  worker = @workers[command_label]
@@ -210,7 +222,7 @@ module Invoker
210
222
  thread = Thread.new do
211
223
  Process.wait(pid)
212
224
  message = "Process with command #{command_label} exited with status #{$?.exitstatus}"
213
- Invoker::Logger.puts("\n#{message}".red)
225
+ Invoker::Logger.puts("\n#{message}".color(:red))
214
226
  notify_user(message)
215
227
  event_manager.trigger(command_label, :exit)
216
228
  end
@@ -228,7 +240,7 @@ module Invoker
228
240
  end
229
241
 
230
242
  def check_and_notify_with_terminal_notifier(message)
231
- return unless RUBY_PLATFORM.downcase.include?("darwin")
243
+ return unless Invoker.darwin?
232
244
 
233
245
  command_path = `which terminal-notifier`
234
246
  if command_path && !command_path.empty?
@@ -238,15 +250,19 @@ module Invoker
238
250
 
239
251
  def install_interrupt_handler
240
252
  Signal.trap("INT") do
241
- @workers.each {|key,worker|
242
- begin
243
- Process.kill("INT", worker.pid)
244
- rescue Errno::ESRCH
245
- end
246
- }
253
+ kill_workers()
247
254
  exit(0)
248
255
  end
249
256
  end
250
257
 
258
+ def kill_workers
259
+ @workers.each {|key,worker|
260
+ begin
261
+ Process.kill("INT", worker.pid)
262
+ rescue Errno::ESRCH
263
+ end
264
+ }
265
+ @workers = {}
266
+ end
251
267
  end
252
268
  end
@@ -9,6 +9,7 @@ module Invoker
9
9
  end
10
10
  end
11
11
 
12
+ class NoValidPortFound < StandardError; end
12
13
  class InvalidConfig < StandardError; end
13
14
  end
14
15
  end
@@ -3,10 +3,30 @@ require 'iniparse'
3
3
  module Invoker
4
4
  module Parsers
5
5
  class Config
6
- attr_accessor :processes
7
- def initialize(filename)
6
+ PORT_REGEX = /\$PORT/
7
+ attr_accessor :processes, :power_config
8
+
9
+ def initialize(filename, port)
8
10
  @ini_content = File.read(filename)
11
+ @port = port
9
12
  @processes = process_ini(@ini_content)
13
+ if Invoker.can_run_balancer?
14
+ @power_config = Invoker::Power::Config.load_config()
15
+ end
16
+ end
17
+
18
+ def http_port
19
+ power_config && power_config.http_port
20
+ end
21
+
22
+ def dns_port
23
+ power_config && power_config.dns_port
24
+ end
25
+
26
+ def process(label)
27
+ processes.detect {|pconfig|
28
+ pconfig.label == label
29
+ }
10
30
  end
11
31
 
12
32
  private
@@ -14,16 +34,60 @@ module Invoker
14
34
  document = IniParse.parse(ini_content)
15
35
  document.map do |section|
16
36
  check_directory(section["directory"])
17
- OpenStruct.new(label: section.key, dir: section["directory"], cmd: section["command"])
37
+ if supports_subdomain?(section)
38
+ port = pick_port(section)
39
+ make_option_for_subdomain(section, port)
40
+ else
41
+ make_option(section)
42
+ end
43
+ end
44
+ end
45
+
46
+ def pick_port(section)
47
+ if section['command'] =~ PORT_REGEX
48
+ @port += 1
49
+ elsif section['port']
50
+ section['port']
51
+ else
52
+ nil
18
53
  end
19
54
  end
20
55
 
56
+ def make_option_for_subdomain(section, port)
57
+ OpenStruct.new(
58
+ port: port,
59
+ label: section.key,
60
+ dir: section["directory"],
61
+ cmd: replace_port_in_command(section["command"], port)
62
+ )
63
+ end
64
+
65
+ def make_option(section)
66
+ OpenStruct.new(
67
+ label: section.key,
68
+ dir: section["directory"],
69
+ cmd: section["command"]
70
+ )
71
+ end
72
+
73
+ def supports_subdomain?(section)
74
+ (section['command'] =~ PORT_REGEX) || section['port']
75
+ end
76
+
21
77
  def check_directory(app_dir)
22
78
  if app_dir && !app_dir.empty? && !File.directory?(app_dir)
23
79
  raise Invoker::Errors::InvalidConfig.new("Invalid directory #{app_dir}")
24
80
  end
25
81
  end
82
+
83
+ def replace_port_in_command(command, port)
84
+ if command =~ PORT_REGEX
85
+ command.gsub(PORT_REGEX, port.to_s)
86
+ else
87
+ command
88
+ end
89
+ end
90
+
26
91
  end
27
92
  end
28
93
  end
29
-
@@ -6,16 +6,29 @@ module Invoker
6
6
  class OptionParser
7
7
  def self.parse(args)
8
8
  selected_command = nil
9
-
10
9
  opts = Slop.parse(args, help: true) do
11
10
  on :v, "Print the version" do
12
11
  Invoker::Logger.puts Invoker::VERSION
13
12
  end
13
+ on :p, :port=, "Port series to be used for starting rack servers", as: Integer
14
14
 
15
15
  command 'start' do
16
16
  banner "Usage : invoker start config.ini \n Start Invoker Process Manager"
17
+ on :p, :port=, "Port series to be used for starting rack servers", as: Integer
17
18
  run do |cmd_opts, cmd_args|
18
- selected_command = OpenStruct.new(:command => 'start', :file => cmd_args.first)
19
+ port = cmd_opts.to_hash[:port] || 9000
20
+ selected_command = OpenStruct.new(
21
+ :command => 'start',
22
+ :file => cmd_args.first,
23
+ :port => port
24
+ )
25
+ end
26
+ end
27
+
28
+ command 'setup' do
29
+ banner "Usage : invoker setup \n Sets up firewall rules for subdomains"
30
+ run do |cmd_opts, cmd_args|
31
+ selected_command = OpenStruct.new(:command => 'setup')
19
32
  end
20
33
  end
21
34
 
@@ -54,7 +67,7 @@ module Invoker
54
67
  run do |cmd_opts, cmd_args|
55
68
  signal_to_use = cmd_opts.to_hash[:signal] || 'INT'
56
69
  selected_command = OpenStruct.new(
57
- :command => 'reload',
70
+ :command => 'reload',
58
71
  :command_key => cmd_args.first,
59
72
  :signal => signal_to_use
60
73
  )
@@ -65,7 +78,6 @@ module Invoker
65
78
  selected_command || create_default_command(args, opts)
66
79
  end
67
80
 
68
-
69
81
  # If user specifies no command either show help message or start the invoker
70
82
  # process supervisor.
71
83
  #
@@ -74,7 +86,8 @@ module Invoker
74
86
  # @return [OpenStruct, false] returns default command or nil
75
87
  def self.create_default_command(args, opts)
76
88
  if args.first && File.exists?(args.first) && File.file?(args.first)
77
- OpenStruct.new(:command => "start", :file => args.first)
89
+ port = opts.to_hash[:port] || 9000
90
+ OpenStruct.new(:command => "start", :file => args.first, :port => port)
78
91
  else
79
92
  Invoker::Logger.puts opts.inspect
80
93
  false
@@ -0,0 +1,6 @@
1
+ require "invoker/power/config"
2
+ require "invoker/power/port_finder"
3
+ require "invoker/power/setup"
4
+ require "invoker/power/dns"
5
+ require "invoker/power/balancer"
6
+ require "invoker/power/powerup"
@@ -0,0 +1,118 @@
1
+ module Invoker
2
+ module Power
3
+ class BalancerConnection < EventMachine::ProxyServer::Connection
4
+ attr_accessor :host, :ip, :port
5
+ def set_host(host, selected_backend)
6
+ self.host = host
7
+ self.ip = selected_backend[:host]
8
+ self.port = selected_backend[:port]
9
+ end
10
+ end
11
+
12
+ class BalancerParser
13
+ attr_accessor :host, :parser
14
+ def initialize
15
+ @parser = HTTP::Parser.new()
16
+ @header = {}
17
+ @parser.on_headers_complete { headers_received() }
18
+ @parser.on_header_field { |field_name|
19
+ @last_key = field_name
20
+ }
21
+ @parser.on_header_value { |value| header_value_received(value) }
22
+ end
23
+
24
+ def headers_received
25
+ @header_completion_callback.call(@header)
26
+ end
27
+
28
+ def header_value_received(value)
29
+ @header[@last_key] = value
30
+ end
31
+
32
+ def on_headers_complete(&block)
33
+ @header_completion_callback = block
34
+ end
35
+
36
+ def reset; @parser.reset(); end
37
+
38
+ def <<(data)
39
+ @parser << data
40
+ end
41
+ end
42
+
43
+ class Balancer
44
+ attr_accessor :connection, :http_parser, :session
45
+
46
+ def self.run(options = {})
47
+ EventMachine.start_server('0.0.0.0', Invoker::CONFIG.http_port,
48
+ BalancerConnection, options) do |connection|
49
+ balancer = Balancer.new(connection)
50
+ balancer.install_callbacks
51
+ end
52
+ end
53
+
54
+ def initialize(connection)
55
+ @connection = connection
56
+ @http_parser = BalancerParser.new()
57
+ @session = nil
58
+ @buffer = []
59
+ end
60
+
61
+ def install_callbacks
62
+ http_parser.on_headers_complete { |header| headers_received(header) }
63
+ connection.on_data {|data| upstream_data(data) }
64
+ connection.on_response { |backend, data| backend_data(backend, data) }
65
+ connection.on_finish { |backend, name| frontend_disconnect(backend, name) }
66
+ end
67
+
68
+ def headers_received(header)
69
+ @session = UUID.generate()
70
+ config = select_backend_config(header['Host'])
71
+ if config
72
+ connection.server(session, host: '0.0.0.0', port: config.port)
73
+ connection.relay_to_servers(@buffer.join)
74
+ @buffer = []
75
+ else
76
+ connection.unbind
77
+ end
78
+ end
79
+
80
+ def upstream_data(data)
81
+ unless session
82
+ @buffer << data
83
+ append_for_http_parsing(data)
84
+ nil
85
+ else
86
+ data
87
+ end
88
+ end
89
+
90
+ def append_for_http_parsing(data)
91
+ http_parser << data
92
+ rescue HTTP::Parser::Error
93
+ http_parser.reset
94
+ connection.close_connection_after_writing
95
+ end
96
+
97
+ def backend_data(backend, data)
98
+ data
99
+ end
100
+
101
+ def frontend_disconnect(backend, name)
102
+ http_parser.reset()
103
+ connection.close_connection_after_writing() if backend == session
104
+ end
105
+
106
+ private
107
+ def select_backend_config(host)
108
+ matching_string = host.match(/(\w+)\.dev(\:\d+)?$/)
109
+ return nil unless matching_string
110
+ if selected_app = matching_string[1]
111
+ Invoker::CONFIG.process(selected_app)
112
+ else
113
+ nil
114
+ end
115
+ end
116
+ end
117
+ end
118
+ end