invoker 1.0.2 → 1.0.3

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.
checksums.yaml CHANGED
@@ -1,15 +1,7 @@
1
1
  ---
2
- !binary "U0hBMQ==":
3
- metadata.gz: !binary |-
4
- Y2NjYjQ0N2Q3ODViNThlNTM0ODMxMzAxY2YyZGYyODllZDVmYjg5YQ==
5
- data.tar.gz: !binary |-
6
- NzQ4MTA2MWY1ZDdjM2ExMTY4ODQ1MGI5NjJhMTU4ZmJkODQ0NWI2ZA==
7
- !binary "U0hBNTEy":
8
- metadata.gz: !binary |-
9
- MjllZmVkMTIxNmIxYTZlMjFjNjQ4NjIxNGQwNTFkMzcxZWFiYTg3YjdjNjk3
10
- ZTI2YzM5ODk5Mjc5NTRmNWUyYjA4MjExZjQ3MTg2OTg2NTM2MGMxNGI0ODcz
11
- ZTFiZDk1NTU0ZTc2YTIwOGE5OGUxY2E0NjlkNDIzNGJkYTI0ZjU=
12
- data.tar.gz: !binary |-
13
- ZjhhNDM5YjJhNjgzMjcxODJhMGI3ZmI1YmM2ZDY0OTI1NTQzN2NlMjBjNGRj
14
- YmIwMTljMDMwNjlkMjI2OGY2YWI0ZmNiOTgyYWQyOTRjMWRiN2JjNTBlNjcy
15
- ODQ2YWU3ZWQ5M2IxZTZiNGVhMzczN2NlNWEyNmZhMWJiMzljZGQ=
2
+ SHA1:
3
+ metadata.gz: 8949242941b92eea8a78f0a528b62c59dc831b2e
4
+ data.tar.gz: bd867429fa2a66e625549de2a8d2ab532ffe2c6c
5
+ SHA512:
6
+ metadata.gz: b6732d806f3ecfcc9a9d2275baa9068eff0207e789d63fe3b92d19bdcb5474513565b5f7a0d5a3438c4efcb41649c4249304cbf6a16459ddf111707b0cf1c183
7
+ data.tar.gz: 67c02e15182e736bdefb13b2f09e7eda386a9ecf7fd23f7dc44a1c6943ef73965ad5c039bc31cb952688dd46d9114004dcf1ea5d0f932517db2ec6326983eaad
data/.gitignore CHANGED
@@ -6,3 +6,5 @@ pkg/
6
6
  local.ini
7
7
  multi.ini
8
8
  tags
9
+ .rvmrc
10
+ vendor/
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --format progress
data/Rakefile CHANGED
@@ -1,5 +1,7 @@
1
- desc "Run the tests"
2
- task :spec do
3
- spec_files = Dir["spec/**/*.rb"]
4
- sh("bacon -I spec #{spec_files.join(" ")}")
5
- end
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new
5
+
6
+ task :default => :spec
7
+ task :test => :spec
@@ -22,7 +22,7 @@ Gem::Specification.new do |s|
22
22
  s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
23
23
  s.require_paths = ["lib"]
24
24
 
25
- s.homepage = %q{http://github.com/code-mancers/invoker}
25
+ s.homepage = %q{http://invoker.codemancers.com}
26
26
  s.licenses = ["MIT"]
27
27
  s.require_paths = ["lib"]
28
28
  s.summary = %q{Something small for Process management}
@@ -35,8 +35,8 @@ Gem::Specification.new do |s|
35
35
  s.add_dependency("uuid", "~> 2.3.7")
36
36
  s.add_dependency("highline", "~> 1.6.19")
37
37
  s.add_dependency("http-parser-lite", "~> 0.6.0")
38
- s.add_development_dependency("bacon")
38
+ s.add_dependency("dotenv", "~> 0.9.0")
39
+ s.add_development_dependency("rspec")
39
40
  s.add_development_dependency("mocha")
40
- s.add_development_dependency("mocha-on-bacon")
41
41
  s.add_development_dependency("rake")
42
42
  end
@@ -1,5 +1,6 @@
1
1
  $: << File.dirname(__FILE__) unless $:.include?(File.expand_path(File.dirname(__FILE__)))
2
2
 
3
+ require "fileutils"
3
4
  require "formatador"
4
5
  require 'rubydns'
5
6
  require 'em-proxy'
@@ -14,6 +15,7 @@ require "invoker/command_listener/server"
14
15
  require "invoker/command_listener/client"
15
16
  require "invoker/power"
16
17
  require "invoker/errors"
18
+ require "invoker/parsers/procfile"
17
19
  require "invoker/parsers/config"
18
20
  require "invoker/parsers/option_parser"
19
21
  require "invoker/commander"
@@ -161,15 +161,16 @@ module Invoker
161
161
  process_kill(pid, signal_to_use)
162
162
  true
163
163
  rescue Errno::ESRCH
164
+ Invoker::Logger.puts("Killing process with #{pid} and name #{command_label} failed".color(:red))
164
165
  remove_worker(command_label, false)
165
166
  false
166
167
  end
167
168
 
168
169
  def process_kill(pid, signal_to_use)
169
170
  if signal_to_use.to_i == 0
170
- Process.kill(signal_to_use, pid)
171
+ Process.kill(signal_to_use, -Process.getpgid(pid))
171
172
  else
172
- Process.kill(signal_to_use.to_i, pid)
173
+ Process.kill(signal_to_use.to_i, -Process.getpgid(pid))
173
174
  end
174
175
  end
175
176
 
@@ -203,16 +204,17 @@ module Invoker
203
204
 
204
205
  event_manager.schedule_event(command_label, :exit) { remove_worker(command_label) }
205
206
 
207
+ spawn_options = {
208
+ :chdir => process_info.dir || ENV['PWD'], :out => write_pipe, :err => write_pipe,
209
+ :pgroup => true, :close_others => true, :in => :close
210
+ }
211
+
206
212
  if defined?(Bundler)
207
213
  Bundler.with_clean_env do
208
- spawn(process_info.cmd,
209
- :chdir => process_info.dir || "/", :out => write_pipe, :err => write_pipe
210
- )
214
+ spawn(process_info.cmd, spawn_options)
211
215
  end
212
216
  else
213
- spawn(process_info.cmd,
214
- :chdir => process_info.dir || "/", :out => write_pipe, :err => write_pipe
215
- )
217
+ spawn(process_info.cmd, spawn_options)
216
218
  end
217
219
  end
218
220
 
@@ -258,8 +260,9 @@ module Invoker
258
260
  def kill_workers
259
261
  @workers.each {|key,worker|
260
262
  begin
261
- Process.kill("INT", worker.pid)
263
+ Process.kill("INT", -Process.getpgid(worker.pid))
262
264
  rescue Errno::ESRCH
265
+ puts "Error killing #{key}"
263
266
  end
264
267
  }
265
268
  @workers = {}
@@ -11,5 +11,6 @@ module Invoker
11
11
 
12
12
  class NoValidPortFound < StandardError; end
13
13
  class InvalidConfig < StandardError; end
14
+ class InvalidFile < StandardError; end
14
15
  end
15
16
  end
@@ -1,4 +1,5 @@
1
1
  require 'iniparse'
2
+ require 'dotenv'
2
3
 
3
4
  module Invoker
4
5
  module Parsers
@@ -7,9 +8,9 @@ module Invoker
7
8
  attr_accessor :processes, :power_config
8
9
 
9
10
  def initialize(filename, port)
10
- @ini_content = File.read(filename)
11
+ @filename = filename
11
12
  @port = port
12
- @processes = process_ini(@ini_content)
13
+ @processes = load_config
13
14
  if Invoker.can_run_balancer?
14
15
  @power_config = Invoker::Power::Config.load_config()
15
16
  end
@@ -24,25 +25,52 @@ module Invoker
24
25
  end
25
26
 
26
27
  def process(label)
27
- processes.detect {|pconfig|
28
- pconfig.label == label
29
- }
28
+ processes.detect { |pconfig| pconfig.label == label }
30
29
  end
31
30
 
32
31
  private
33
- def process_ini(ini_content)
32
+ def load_config
33
+ if is_ini?
34
+ process_ini
35
+ elsif is_procfile?
36
+ process_procfile
37
+ else
38
+ Invoker::Logger.puts("\n Invalid config file. Invoker requires an ini or a Procfile.".color(:red))
39
+ abort
40
+ end
41
+ end
42
+
43
+ def process_ini
44
+ ini_content = File.read(@filename)
34
45
  document = IniParse.parse(ini_content)
35
46
  document.map do |section|
36
47
  check_directory(section["directory"])
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
48
+ process_command_from_section(section)
49
+ end
50
+ end
51
+
52
+ def process_procfile
53
+ load_env
54
+ procfile = Invoker::Parsers::Procfile.new(@filename)
55
+ procfile.entries.map do |name, command|
56
+ section = { "label" => name, "command" => command }
57
+ process_command_from_section(section)
58
+ end
59
+ end
60
+
61
+ def process_command_from_section(section)
62
+ if supports_subdomain?(section)
63
+ port = pick_port(section)
64
+ make_option_for_subdomain(section, port)
65
+ else
66
+ make_option(section)
43
67
  end
44
68
  end
45
69
 
70
+ def load_env
71
+ Dotenv.load
72
+ end
73
+
46
74
  def pick_port(section)
47
75
  if section['command'] =~ PORT_REGEX
48
76
  @port += 1
@@ -56,7 +84,7 @@ module Invoker
56
84
  def make_option_for_subdomain(section, port)
57
85
  OpenStruct.new(
58
86
  port: port,
59
- label: section.key,
87
+ label: section["label"] || section.key,
60
88
  dir: section["directory"],
61
89
  cmd: replace_port_in_command(section["command"], port)
62
90
  )
@@ -64,7 +92,7 @@ module Invoker
64
92
 
65
93
  def make_option(section)
66
94
  OpenStruct.new(
67
- label: section.key,
95
+ label: section["label"] || section.key,
68
96
  dir: section["directory"],
69
97
  cmd: section["command"]
70
98
  )
@@ -88,6 +116,13 @@ module Invoker
88
116
  end
89
117
  end
90
118
 
119
+ def is_ini?
120
+ File.extname(@filename) == '.ini'
121
+ end
122
+
123
+ def is_procfile?
124
+ @filename =~ /Procfile/
125
+ end
91
126
  end
92
127
  end
93
128
  end
@@ -8,7 +8,7 @@ module Invoker
8
8
  selected_command = nil
9
9
  opts = Slop.parse(args, help: true) do
10
10
  on :v, "Print the version" do
11
- Invoker::Logger.puts Invoker::VERSION
11
+ selected_command = OpenStruct.new(:command => 'version')
12
12
  end
13
13
  on :p, :port=, "Port series to be used for starting rack servers", as: Integer
14
14
 
@@ -0,0 +1,86 @@
1
+ module Invoker
2
+ module Parsers
3
+ # rip off from foreman
4
+ class Procfile
5
+ # Initialize a Procfile
6
+ #
7
+ # @param [String] filename (nil) An optional filename to read from
8
+ #
9
+ def initialize(filename=nil)
10
+ @entries = []
11
+ load(filename) if filename
12
+ end
13
+
14
+ # Yield each +Procfile+ entry in order
15
+ #
16
+ def entries
17
+ return @entries unless block_given?
18
+ @entries.each do |(name, command)|
19
+ yield name, command
20
+ end
21
+ end
22
+
23
+ # Retrieve a +Procfile+ command by name
24
+ #
25
+ # @param [String] name The name of the Procfile entry to retrieve
26
+ #
27
+ def [](name)
28
+ @entries.detect { |n,c| name == n }.last
29
+ end
30
+
31
+ # Create a +Procfile+ entry
32
+ #
33
+ # @param [String] name The name of the +Procfile+ entry to create
34
+ # @param [String] command The command of the +Procfile+ entry to create
35
+ #
36
+ def []=(name, command)
37
+ delete name
38
+ @entries << [name, command]
39
+ end
40
+
41
+ # Remove a +Procfile+ entry
42
+ #
43
+ # @param [String] name The name of the +Procfile+ entry to remove
44
+ #
45
+ def delete(name)
46
+ @entries.reject! { |n,c| name == n }
47
+ end
48
+
49
+ # Load a Procfile from a file
50
+ #
51
+ # @param [String] filename The filename of the +Procfile+ to load
52
+ #
53
+ def load(filename)
54
+ @entries.replace parse(filename)
55
+ end
56
+
57
+ # Save a Procfile to a file
58
+ #
59
+ # @param [String] filename Save the +Procfile+ to this file
60
+ #
61
+ def save(filename)
62
+ File.open(filename, 'w') do |file|
63
+ file.puts self.to_s
64
+ end
65
+ end
66
+
67
+ # Get the +Procfile+ as a +String+
68
+ #
69
+ def to_s
70
+ @entries.map do |name, command|
71
+ [ name, command ].join(": ")
72
+ end.join("\n")
73
+ end
74
+
75
+ private
76
+
77
+ def parse(filename)
78
+ File.read(filename).gsub("\r\n","\n").split("\n").map do |line|
79
+ if line =~ /^([A-Za-z0-9_-]+):\s*(.+)$/
80
+ [$1, $2]
81
+ end
82
+ end.compact
83
+ end
84
+ end
85
+ end
86
+ end
@@ -1,6 +1,7 @@
1
1
  require "invoker/power/config"
2
2
  require "invoker/power/port_finder"
3
3
  require "invoker/power/setup"
4
+ require "invoker/power/http_response"
4
5
  require "invoker/power/dns"
5
6
  require "invoker/power/balancer"
6
7
  require "invoker/power/powerup"
@@ -73,7 +73,8 @@ module Invoker
73
73
  connection.relay_to_servers(@buffer.join)
74
74
  @buffer = []
75
75
  else
76
- connection.unbind
76
+ return_error_page(404)
77
+ connection.close_connection_after_writing
77
78
  end
78
79
  end
79
80
 
@@ -95,11 +96,17 @@ module Invoker
95
96
  end
96
97
 
97
98
  def backend_data(backend, data)
99
+ @backend_data = true
98
100
  data
99
101
  end
100
102
 
101
103
  def frontend_disconnect(backend, name)
102
104
  http_parser.reset()
105
+ unless @backend_data
106
+ Invoker::Logger.puts("\nApplication not running. Returning error page.".color(:red))
107
+ return_error_page(503)
108
+ end
109
+ @backend_data = false
103
110
  connection.close_connection_after_writing() if backend == session
104
111
  end
105
112
 
@@ -113,6 +120,14 @@ module Invoker
113
120
  nil
114
121
  end
115
122
  end
123
+
124
+ def return_error_page(status)
125
+ http_response = Invoker::Power::HttpResponse.new()
126
+ http_response.status = status
127
+ http_response['Content-Type'] = "text/html; charset=utf-8"
128
+ http_response.use_file_as_body(File.join(File.dirname(__FILE__), "templates/#{status}.html"))
129
+ connection.send_data(http_response.http_string)
130
+ end
116
131
  end
117
132
  end
118
133
  end
@@ -0,0 +1,81 @@
1
+ require 'time'
2
+
3
+ module Invoker
4
+ module Power
5
+ class HttpResponse
6
+ STATUS_MAPS = {
7
+ 200 => "OK",
8
+ 201 => "Created",
9
+ 202 => "Accepted",
10
+ 204 => "No Content",
11
+ 205 => "Reset Content",
12
+ 206 => "Partial Content",
13
+ 301 => "Moved Permanently",
14
+ 302 => "Found",
15
+ 304 => "Not Modified",
16
+ 400 => "Bad Request",
17
+ 401 => "Unauthorized",
18
+ 402 => "Payment Required",
19
+ 403 => "Forbidden",
20
+ 404 => "Not Found",
21
+ 411 => "Length Required",
22
+ 500 => "Internal Server Error",
23
+ 501 => "Not Implemented",
24
+ 502 => "Bad Gateway",
25
+ 503 => "Service Unavailable",
26
+ 504 => "Gateway Timeout"
27
+ }
28
+
29
+ HTTP_HEADER_FIELDS = [
30
+ 'Cache-Control', 'Connection', 'Date',
31
+ 'Pragma', 'Trailer', 'Transfer-Encoding',
32
+ 'Accept-Ranges', 'Age', 'Etag',
33
+ 'Server', 'Location', 'Allow',
34
+ 'Content-Encoding', 'Content-Language', 'Content-Location',
35
+ 'Content-MD5', 'Content-Range',
36
+ 'Content-Type', 'Expires',
37
+ 'Last-Modified', 'extension-header'
38
+ ]
39
+
40
+ attr_accessor :header, :body, :status
41
+
42
+ def initialize
43
+ @header = {}
44
+ header['Server'] = "Invoker 1.1"
45
+ header['Date'] = Time.now.httpdate
46
+ @status = 200
47
+ @body = ""
48
+ end
49
+
50
+ def []=(key, value)
51
+ header[key] = value
52
+ end
53
+
54
+ def use_file_as_body(file_name)
55
+ if file_name && File.exists?(file_name)
56
+ file_content = File.read(file_name)
57
+ self.body = file_content
58
+ else
59
+ raise Invoker::Errors:InvalidFile, "Invalid file as body"
60
+ end
61
+ end
62
+
63
+ def http_string
64
+ final_string = []
65
+ final_string << "HTTP/1.1 #{status} #{STATUS_MAPS[status]}"
66
+
67
+ if header['Transfer-Encoding'].nil? && body.empty?
68
+ header['Content-Length'] = body.length
69
+ end
70
+
71
+ HTTP_HEADER_FIELDS.each do |key|
72
+ if value = header[key]
73
+ final_string << "#{key}: #{value}"
74
+ end
75
+ end
76
+
77
+ final_string.join("\r\n") + "\r\n\r\n" + body
78
+ end
79
+ end
80
+ end
81
+ end