invoker 1.0.2 → 1.0.3

Sign up to get free protection for your applications and to get access to all the features.
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