meepo 1.5.2

Sign up to get free protection for your applications and to get access to all the features.
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,131 @@
1
+ require 'em-proxy'
2
+ require 'http-parser'
3
+ require "invoker/power/http_parser"
4
+ require "invoker/power/url_rewriter"
5
+
6
+ module Invoker
7
+ module Power
8
+ class InvokerHttpProxy < EventMachine::ProxyServer::Connection
9
+ attr_accessor :host, :ip, :port
10
+ def set_host(host, selected_backend)
11
+ self.host = host
12
+ self.ip = selected_backend[:host]
13
+ self.port = selected_backend[:port]
14
+ end
15
+ end
16
+
17
+ class InvokerHttpsProxy < InvokerHttpProxy
18
+ def post_init
19
+ super
20
+ start_tls
21
+ end
22
+ end
23
+
24
+ class Balancer
25
+ attr_accessor :connection, :http_parser, :session, :protocol, :upgraded_to
26
+
27
+ def self.run(options = {})
28
+ start_http_proxy(InvokerHttpProxy, 'http', options)
29
+ start_http_proxy(InvokerHttpsProxy, 'https', options)
30
+ end
31
+
32
+ def self.start_http_proxy(proxy_class, protocol, options)
33
+ port = protocol == 'http' ? Invoker.config.http_port : Invoker.config.https_port
34
+ EventMachine.start_server('0.0.0.0', port,
35
+ proxy_class, options) do |connection|
36
+ balancer = Balancer.new(connection, protocol)
37
+ balancer.install_callbacks
38
+ end
39
+ end
40
+
41
+ def initialize(connection, protocol)
42
+ @connection = connection
43
+ @protocol = protocol
44
+ @http_parser = HttpParser.new(protocol)
45
+ @session = nil
46
+ @upgraded_to = nil
47
+ @buffer = []
48
+ end
49
+
50
+ def install_callbacks
51
+ http_parser.on_headers_complete { |headers| headers_received(headers) }
52
+ http_parser.on_message_complete { |full_message| complete_message_received(full_message) }
53
+ connection.on_data { |data| upstream_data(data) }
54
+ connection.on_response { |backend, data| backend_data(backend, data) }
55
+ connection.on_finish { |backend, name| frontend_disconnect(backend, name) }
56
+ end
57
+
58
+ def complete_message_received(full_message)
59
+ connection.relay_to_servers(full_message)
60
+ http_parser.reset
61
+ end
62
+
63
+ def headers_received(headers)
64
+ if @session
65
+ return
66
+ end
67
+ @session = UUID.generate()
68
+ if !headers['Host'] || headers['Host'].empty?
69
+ return_error_page(400)
70
+ return
71
+ end
72
+
73
+ dns_check_response = UrlRewriter.new.select_backend_config(headers['Host'])
74
+ if dns_check_response && dns_check_response.port
75
+ connection.server(session, host: dns_check_response.ip, port: dns_check_response.port)
76
+ else
77
+ return_error_page(404)
78
+ http_parser.reset
79
+ connection.close_connection_after_writing
80
+ end
81
+ end
82
+
83
+ def upstream_data(data)
84
+ if upgraded_to == "websocket"
85
+ data
86
+ else
87
+ append_for_http_parsing(data)
88
+ nil
89
+ end
90
+ end
91
+
92
+ def append_for_http_parsing(data)
93
+ http_parser << data
94
+ rescue HTTP::Parser::Error
95
+ http_parser.reset
96
+ connection.close_connection_after_writing
97
+ end
98
+
99
+ def backend_data(backend, data)
100
+ @backend_data = true
101
+
102
+ # check backend data for websockets connection. check for upgrade headers
103
+ # - Upgarde: websocket\r\n
104
+ if data =~ /Upgrade: websocket/
105
+ @upgraded_to = "websocket"
106
+ end
107
+
108
+ data
109
+ end
110
+
111
+ def frontend_disconnect(backend, name)
112
+ http_parser.reset
113
+ unless @backend_data
114
+ return_error_page(503)
115
+ end
116
+ @backend_data = false
117
+ connection.close_connection_after_writing if backend == session
118
+ end
119
+
120
+ private
121
+
122
+ def return_error_page(status)
123
+ http_response = Invoker::Power::HttpResponse.new()
124
+ http_response.status = status
125
+ http_response['Content-Type'] = "text/html; charset=utf-8"
126
+ http_response.use_file_as_body(File.join(File.dirname(__FILE__), "templates/#{status}.html"))
127
+ connection.send_data(http_response.http_string)
128
+ end
129
+ end
130
+ end
131
+ end
@@ -0,0 +1,77 @@
1
+ require "yaml"
2
+
3
+ module Invoker
4
+ module Power
5
+ # Save and Load Invoker::Power config
6
+ class ConfigExists < StandardError; end
7
+
8
+ class Config
9
+ def self.has_config?
10
+ File.exist?(config_file)
11
+ end
12
+
13
+ def self.create(options = {})
14
+ if has_config?
15
+ raise ConfigExists, "Config file already exists at location #{config_file}"
16
+ end
17
+ config = new(options)
18
+ config.save
19
+ end
20
+
21
+ def self.delete
22
+ if File.exist?(config_file)
23
+ File.delete(config_file)
24
+ end
25
+ end
26
+
27
+ def self.config_file
28
+ File.join(Invoker.home, ".invoker", "config")
29
+ end
30
+
31
+ def self.config_dir
32
+ File.join(Invoker.home, ".invoker")
33
+ end
34
+
35
+ def initialize(options = {})
36
+ @config = options
37
+ end
38
+
39
+ def self.load_config
40
+ config_hash = File.open(config_file, "r") { |fl| YAML.load(fl) }
41
+ new(config_hash)
42
+ end
43
+
44
+ def dns_port=(dns_port)
45
+ @config[:dns_port] = dns_port
46
+ end
47
+
48
+ def http_port=(http_port)
49
+ @config[:http_port] = http_port
50
+ end
51
+
52
+ def https_port=(https_port)
53
+ @config[:https_port] = https_port
54
+ end
55
+
56
+ def ipfw_rule_number=(ipfw_rule_number)
57
+ @config[:ipfw_rule_number] = ipfw_rule_number
58
+ end
59
+
60
+ def dns_port; @config[:dns_port]; end
61
+ def http_port; @config[:http_port]; end
62
+ def ipfw_rule_number; @config[:ipfw_rule_number]; end
63
+ def https_port; @config[:https_port]; end
64
+
65
+ def tld
66
+ @config[:tld] || Invoker.default_tld
67
+ end
68
+
69
+ def save
70
+ File.open(self.class.config_file, "w") do |fl|
71
+ YAML.dump(@config, fl)
72
+ end
73
+ self
74
+ end
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,38 @@
1
+ require "logger"
2
+ require 'rubydns'
3
+
4
+ module Invoker
5
+ module Power
6
+ class DNS < RubyDNS::Server
7
+ def self.server_ports
8
+ [
9
+ [:udp, '127.0.0.1', Invoker.config.dns_port],
10
+ [:tcp, '127.0.0.1', Invoker.config.dns_port]
11
+ ]
12
+ end
13
+
14
+ def initialize
15
+ @logger = ::Logger.new($stderr)
16
+ @logger.level = ::Logger::FATAL
17
+ end
18
+
19
+ def process(name, resource_class, transaction)
20
+ if name_matches?(name) && resource_class_matches?(resource_class)
21
+ transaction.respond!("127.0.0.1")
22
+ else
23
+ transaction.fail!(:NXDomain)
24
+ end
25
+ end
26
+
27
+ private
28
+
29
+ def resource_class_matches?(resource_class)
30
+ resource_class == Resolv::DNS::Resource::IN::A
31
+ end
32
+
33
+ def name_matches?(name)
34
+ name =~ /.*\.#{Invoker.config.tld}/
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,68 @@
1
+ module Invoker
2
+ module Power
3
+ class HttpParser
4
+ attr_accessor :host, :parser, :protocol
5
+
6
+ def initialize(protocol)
7
+ @protocol = protocol
8
+ @parser = HTTP::Parser.new
9
+ @header = {}
10
+ initialize_message_content
11
+ parser.on_headers_complete { complete_headers_received }
12
+ parser.on_header_field { |field_name| @last_key = field_name }
13
+ parser.on_header_value { |field_value| header_value_received(field_value) }
14
+
15
+ parser.on_message_complete { complete_message_received }
16
+ end
17
+
18
+ # define a callback for invoking when complete header is parsed
19
+ def on_headers_complete(&block)
20
+ @on_headers_complete_callback = block
21
+ end
22
+
23
+ def header_value_received(value)
24
+ @header[@last_key] = value
25
+ end
26
+
27
+ # define a callback to invoke when a full http message is received
28
+ def on_message_complete(&block)
29
+ @on_message_complete_callback = block
30
+ end
31
+
32
+ def reset
33
+ @header = {}
34
+ initialize_message_content
35
+ parser.reset
36
+ end
37
+
38
+ def << data
39
+ @full_message.write(data)
40
+ parser << data
41
+ end
42
+
43
+ private
44
+
45
+ def complete_message_received
46
+ full_message_string = @full_message.string.dup
47
+ if full_message_string =~ /\r\n\r\n/
48
+ full_message_string.sub!(/\r\n\r\n/, "\r\nX_FORWARDED_PROTO: #{protocol}\r\n\r\n")
49
+ end
50
+ if @on_message_complete_callback
51
+ @on_message_complete_callback.call(full_message_string)
52
+ end
53
+ end
54
+
55
+ def initialize_message_content
56
+ @full_message = StringIO.new
57
+ @full_message.set_encoding('ASCII-8BIT')
58
+ end
59
+
60
+ # gets invoker when complete header is received
61
+ def complete_headers_received
62
+ if @on_headers_complete_callback
63
+ @on_headers_complete_callback.call(@header)
64
+ end
65
+ end
66
+ end
67
+ end
68
+ 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 #{Invoker::VERSION}"
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
@@ -0,0 +1,64 @@
1
+ module Invoker
2
+ module Power
3
+ # for migrating existins users to pf
4
+ class PfMigrate
5
+ def firewall_config_requires_migration?
6
+ return false if !Invoker.darwin?
7
+ # lets not migrate on osx < 10.10
8
+ return false if osx_version < Invoker::Version.new("14.0.0")
9
+ # also verify if firewall config is old
10
+ check_firewall_file?
11
+ end
12
+
13
+ def migrate
14
+ if firewall_config_requires_migration? && ask_user_for_migration
15
+ sudome
16
+ osx_setup = Invoker::Power::OsxSetup.new(Invoker.config.tld)
17
+ osx_setup.install_firewall(Invoker.config.http_port, Invoker.config.https_port)
18
+ drop_to_normal_user
19
+ Invoker::Logger.puts "Invoker has updated its configuration for yosemite."\
20
+ " Please restart OSX to complete the configuration process.".color(:red)
21
+ exit(-1)
22
+ end
23
+ end
24
+
25
+ def ask_user_for_migration
26
+ if not_already_root?
27
+ Invoker::Logger.puts "Invoker has detected you are running OSX 10.10 "\
28
+ " but your invoker configuration does not support it."
29
+ Invoker::Logger.puts "Invoker can update its configuration automaticaly"\
30
+ " but it will require a system reboot.".color(:red)
31
+ Invoker::CLI::Question.agree("Update Invoker configuration (y/n) :")
32
+ else
33
+ true
34
+ end
35
+ end
36
+
37
+ # http://jimeh.me/blog/2010/02/22/built-in-sudo-for-ruby-command-line-tools/
38
+ def sudome
39
+ if not_already_root?
40
+ exec("sudo #{$0} #{ARGV.join(' ')}")
41
+ end
42
+ end
43
+
44
+ def not_already_root?
45
+ ENV["USER"] != "root"
46
+ end
47
+
48
+ def drop_to_normal_user
49
+ EventMachine.set_effective_user(ENV["SUDO_USER"])
50
+ end
51
+
52
+ def osx_version
53
+ osx_kernel_version = `uname -r`.strip
54
+ Invoker::Version.new(osx_kernel_version)
55
+ end
56
+
57
+ def check_firewall_file?
58
+ return false unless File.exist?(Invoker::Power::OsxSetup::FIREWALL_PLIST_FILE)
59
+ firewall_contents = File.read(Invoker::Power::OsxSetup::FIREWALL_PLIST_FILE)
60
+ !!firewall_contents.match(/ipfw/)
61
+ end
62
+ end
63
+ end
64
+ end