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,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