invoker 1.0.4 → 1.1.0

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 (72) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +5 -0
  3. data/.rubocop.yml +30 -0
  4. data/.travis.yml +1 -0
  5. data/Gemfile +1 -0
  6. data/bin/invoker +4 -8
  7. data/invoker.gemspec +10 -11
  8. data/lib/invoker.rb +95 -21
  9. data/lib/invoker/cli.rb +126 -0
  10. data/lib/invoker/cli/pinger.rb +23 -0
  11. data/lib/invoker/cli/question.rb +15 -0
  12. data/lib/invoker/cli/tail.rb +34 -0
  13. data/lib/invoker/cli/tail_watcher.rb +34 -0
  14. data/lib/invoker/command_worker.rb +28 -2
  15. data/lib/invoker/commander.rb +34 -236
  16. data/lib/invoker/config.rb +5 -0
  17. data/lib/invoker/daemon.rb +126 -0
  18. data/lib/invoker/dns_cache.rb +23 -0
  19. data/lib/invoker/errors.rb +1 -0
  20. data/lib/invoker/ipc.rb +45 -0
  21. data/lib/invoker/ipc/add_command.rb +12 -0
  22. data/lib/invoker/ipc/add_http_command.rb +10 -0
  23. data/lib/invoker/ipc/base_command.rb +24 -0
  24. data/lib/invoker/ipc/client_handler.rb +26 -0
  25. data/lib/invoker/ipc/dns_check_command.rb +16 -0
  26. data/lib/invoker/ipc/list_command.rb +11 -0
  27. data/lib/invoker/ipc/message.rb +170 -0
  28. data/lib/invoker/ipc/message/list_response.rb +33 -0
  29. data/lib/invoker/ipc/message/tail_response.rb +10 -0
  30. data/lib/invoker/ipc/ping_command.rb +10 -0
  31. data/lib/invoker/ipc/reload_command.rb +12 -0
  32. data/lib/invoker/ipc/remove_command.rb +12 -0
  33. data/lib/invoker/{command_listener → ipc}/server.rb +6 -11
  34. data/lib/invoker/ipc/tail_command.rb +11 -0
  35. data/lib/invoker/ipc/unix_client.rb +60 -0
  36. data/lib/invoker/parsers/config.rb +1 -0
  37. data/lib/invoker/power/balancer.rb +17 -7
  38. data/lib/invoker/power/config.rb +6 -3
  39. data/lib/invoker/power/dns.rb +22 -21
  40. data/lib/invoker/power/http_response.rb +1 -1
  41. data/lib/invoker/power/power.rb +3 -0
  42. data/lib/invoker/power/powerup.rb +3 -2
  43. data/lib/invoker/power/setup.rb +6 -4
  44. data/lib/invoker/process_manager.rb +187 -0
  45. data/lib/invoker/process_printer.rb +27 -38
  46. data/lib/invoker/reactor.rb +19 -38
  47. data/lib/invoker/reactor/reader.rb +53 -0
  48. data/lib/invoker/version.rb +1 -1
  49. data/readme.md +1 -1
  50. data/spec/invoker/cli/pinger_spec.rb +22 -0
  51. data/spec/invoker/cli/tail_watcher_spec.rb +39 -0
  52. data/spec/invoker/cli_spec.rb +27 -0
  53. data/spec/invoker/command_worker_spec.rb +30 -0
  54. data/spec/invoker/commander_spec.rb +57 -127
  55. data/spec/invoker/config_spec.rb +21 -0
  56. data/spec/invoker/daemon_spec.rb +34 -0
  57. data/spec/invoker/invoker_spec.rb +31 -0
  58. data/spec/invoker/ipc/client_handler_spec.rb +44 -0
  59. data/spec/invoker/ipc/dns_check_command_spec.rb +32 -0
  60. data/spec/invoker/ipc/message/list_response_spec.rb +22 -0
  61. data/spec/invoker/ipc/message_spec.rb +45 -0
  62. data/spec/invoker/ipc/unix_client_spec.rb +29 -0
  63. data/spec/invoker/power/setup_spec.rb +1 -1
  64. data/spec/invoker/process_manager_spec.rb +98 -0
  65. data/spec/invoker/reactor_spec.rb +6 -0
  66. data/spec/spec_helper.rb +15 -24
  67. metadata +107 -77
  68. data/lib/invoker/command_listener/client.rb +0 -45
  69. data/lib/invoker/parsers/option_parser.rb +0 -106
  70. data/lib/invoker/power.rb +0 -7
  71. data/lib/invoker/runner.rb +0 -98
  72. data/spec/invoker/command_listener/client_spec.rb +0 -52
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 36f535d2a652c9164fbe92e7df1e9aff140223d2
4
- data.tar.gz: f84168b2628f0f40024de2b46c1de5017a9069d1
3
+ metadata.gz: 17e01fb0ade988c21a62f385d51e3b07e58f9016
4
+ data.tar.gz: 2e4e0c36f2df111493b7c05dea7e0ae99244de2c
5
5
  SHA512:
6
- metadata.gz: b0fb344263af24e67273104073e872b070bfeb73f2a9e782953f29d355216ab5d2c18c254b5d175619ba0e6bd7bd4461e2956e01b5d022574005f54d38640854
7
- data.tar.gz: 1e758cf427cd35b11773c06896d3b3fc2ebd9fab50ac8c60ff06379cab2369fc41eb5353f9a53e6e34c5a5b5e04664bb9570ffa001ed6dc7b9fd867aa305fa6b
6
+ metadata.gz: a7601b89cd0db7b1cc0e780885d14f27ec652739702b5c2a59cf961b677db347bfd9e764ada4a53057de80a418ca3d486949c1a6909ee414505a8b5323593708
7
+ data.tar.gz: 552b15ffb14c384d02f3ec972ccd8f3a2191afedab5bfa899f19c9fb1ecf0e4df86c65cdbfa475df8babfc746401bc6d914bcb26f0abda2f6fd04283dab092c3
data/.gitignore CHANGED
@@ -8,3 +8,8 @@ multi.ini
8
8
  tags
9
9
  .rvmrc
10
10
  vendor/
11
+ _site
12
+ .bundle
13
+ coverage
14
+ invoker_profile/
15
+ *.pid
@@ -0,0 +1,30 @@
1
+ AllCops:
2
+ RunRailsCops: false
3
+ Excludes:
4
+ - db/**
5
+
6
+ HashSyntax:
7
+ Description: 'Use either hash rocket or 1.9 styled hashes.'
8
+ Enabled: false
9
+
10
+ LineLength:
11
+ Max: 100
12
+
13
+ # Disable Certain Tests
14
+
15
+ Documentation:
16
+ Description: 'Document classes and non-namespace modules.'
17
+ Enabled: false
18
+
19
+ StringLiterals:
20
+ Description: 'Checks if uses of quotes match the configured preference.'
21
+ Enabled: false
22
+
23
+ Encoding:
24
+ Description: 'Use UTF-8 as the source file encoding.'
25
+ Enabled: false
26
+
27
+ SignalException:
28
+ Description: 'Do not enforce use of fail when raising exceptions.'
29
+ # Valid values are: semantic, only_raise and only_fail
30
+ EnforcedStyle: only_raise
@@ -2,6 +2,7 @@ language: ruby
2
2
  rvm:
3
3
  - 1.9.3
4
4
  - 2.0.0
5
+ - 2.1.0
5
6
 
6
7
  script: bundle exec rake spec
7
8
 
data/Gemfile CHANGED
@@ -5,3 +5,4 @@ gemspec
5
5
  gem 'pry'
6
6
 
7
7
  gem 'coveralls', require: false
8
+ gem "simplecov", require: false
@@ -1,11 +1,7 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
- begin
4
- require "invoker"
5
- rescue LoadError
6
- invoker_lib_path = File.expand_path('../../lib', __FILE__)
7
- $:.unshift(invoker_lib_path)
8
- require "invoker"
9
- end
3
+ invoker_lib_path = File.expand_path('../../lib', __FILE__)
4
+ $:.unshift(invoker_lib_path)
5
+ require "invoker"
10
6
 
11
- Invoker::Runner.run(ARGV)
7
+ Invoker::CLI.start(ARGV)
@@ -25,17 +25,16 @@ Gem::Specification.new do |s|
25
25
  s.licenses = ["MIT"]
26
26
  s.require_paths = ["lib"]
27
27
  s.summary = %q{Something small for Process management}
28
- s.add_dependency("slop", "~> 3.4.6")
29
- s.add_dependency("rainbow", "~> 1.1.4")
30
- s.add_dependency("iniparse", "~> 1.1.6")
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")
38
- s.add_dependency("dotenv", "~> 0.9.0")
28
+ s.add_dependency("thor", "~> 0.19")
29
+ s.add_dependency("rainbow", "~> 2.0")
30
+ s.add_dependency("iniparse", "~> 1.1")
31
+ s.add_dependency("formatador", "~> 0.2")
32
+ s.add_dependency("eventmachine", "~> 1.0")
33
+ s.add_dependency("em-proxy", "~> 0.1")
34
+ s.add_dependency("rubydns", "~> 0.7")
35
+ s.add_dependency("uuid", "~> 2.3")
36
+ s.add_dependency("http-parser-lite", "~> 0.6")
37
+ s.add_dependency("dotenv", "~> 0.9")
39
38
  s.add_development_dependency("rspec")
40
39
  s.add_development_dependency("mocha")
41
40
  s.add_development_dependency("rake")
@@ -2,44 +2,118 @@ $: << File.dirname(__FILE__) unless $:.include?(File.expand_path(File.dirname(__
2
2
 
3
3
  require "fileutils"
4
4
  require "formatador"
5
- require 'rubydns'
6
- require 'em-proxy'
7
- require 'http-parser'
5
+
8
6
  require "ostruct"
9
7
  require "uuid"
10
- require "highline"
8
+ require "json"
9
+ require "rainbow"
10
+ require "rainbow/ext/string"
11
+
11
12
  require "invoker/version"
12
13
  require "invoker/logger"
13
- require "invoker/runner"
14
- require "invoker/command_listener/server"
15
- require "invoker/command_listener/client"
16
- require "invoker/power"
14
+ require "invoker/daemon"
15
+ require "invoker/cli"
16
+ require "invoker/dns_cache"
17
+ require "invoker/ipc"
18
+ require "invoker/power/config"
19
+ require "invoker/power/port_finder"
20
+ require "invoker/power/setup"
21
+ require "invoker/power/powerup"
17
22
  require "invoker/errors"
18
23
  require "invoker/parsers/procfile"
19
24
  require "invoker/parsers/config"
20
- require "invoker/parsers/option_parser"
21
25
  require "invoker/commander"
26
+ require "invoker/process_manager"
22
27
  require "invoker/command_worker"
23
28
  require "invoker/reactor"
24
29
  require "invoker/event/manager"
25
30
  require "invoker/process_printer"
26
31
 
27
32
  module Invoker
28
- def self.darwin?
29
- ruby_platform.downcase.include?("darwin")
30
- end
33
+ class << self
34
+ attr_accessor :config, :tail_watchers, :commander
35
+ attr_accessor :dns_cache, :daemonize
31
36
 
32
- def self.ruby_platform
33
- RUBY_PLATFORM
34
- end
37
+ alias_method :daemonize?, :daemonize
38
+
39
+ def darwin?
40
+ ruby_platform.downcase.include?("darwin")
41
+ end
42
+
43
+ def ruby_platform
44
+ RUBY_PLATFORM
45
+ end
46
+
47
+ def load_invoker_config(file, port)
48
+ @config = Invoker::Parsers::Config.new(file, port)
49
+ @dns_cache = Invoker::DNSCache.new(@invoker_config)
50
+ @tail_watchers = Invoker::CLI::TailWatcher.new
51
+ @commander = Invoker::Commander.new
52
+ end
53
+
54
+ def close_socket(socket)
55
+ socket.close
56
+ rescue StandardError => error
57
+ Invoker::Logger.puts "Error removing socket #{error}"
58
+ end
59
+
60
+ def daemon
61
+ @daemon ||= Invoker::Daemon.new
62
+ end
35
63
 
36
- def self.can_run_balancer?(throw_warning = true)
37
- return false unless darwin?
38
- return true if File.exists?(Invoker::Power::Config::CONFIG_LOCATION)
64
+ def can_run_balancer?(throw_warning = true)
65
+ return false unless darwin?
66
+ return true if File.exist?(Invoker::Power::Config::CONFIG_LOCATION)
67
+
68
+ if throw_warning
69
+ Invoker::Logger.puts("Invoker has detected setup has not been run. Domain feature will not work without running setup command.".color(:red))
70
+ end
71
+ false
72
+ end
73
+
74
+ def setup_config_location
75
+ config_location = File.join(Dir.home, '.invoker')
76
+ return config_location if Dir.exist?(config_location)
77
+
78
+ if File.exist?(config_location)
79
+ old_config = File.read(config_location)
80
+ FileUtils.rm_f(config_location)
81
+ end
82
+
83
+ FileUtils.mkdir(config_location)
84
+
85
+ migrate_old_config(old_config, config_location) if old_config
86
+ config_location
87
+ end
88
+
89
+ def run_without_bundler
90
+ if defined?(Bundler)
91
+ Bundler.with_clean_env do
92
+ yield
93
+ end
94
+ else
95
+ yield
96
+ end
97
+ end
98
+
99
+ def notify_user(message)
100
+ run_without_bundler { check_and_notify_with_terminal_notifier(message) }
101
+ end
102
+
103
+ def check_and_notify_with_terminal_notifier(message)
104
+ return unless Invoker.darwin?
105
+
106
+ command_path = `which terminal-notifier`
107
+ if command_path && !command_path.empty?
108
+ system("terminal-notifier -message '#{message}' -title Invoker")
109
+ end
110
+ end
39
111
 
40
- if throw_warning
41
- Invoker::Logger.puts("Invoker has detected setup has not been run. Domain feature will not work without running setup command.".color(:red))
112
+ def migrate_old_config(old_config, config_location)
113
+ new_config = File.join(config_location, 'config')
114
+ File.open(new_config, 'w') do |file|
115
+ file.write(old_config)
116
+ end
42
117
  end
43
- false
44
118
  end
45
119
  end
@@ -0,0 +1,126 @@
1
+ require "socket"
2
+ require "thor"
3
+
4
+ module Invoker
5
+ class CLI < Thor
6
+ def self.start(*args)
7
+ cli_args = args.flatten
8
+ # If it is not a valid task, it is probably file argument
9
+ if default_start_command?(cli_args)
10
+ args = [cli_args.unshift("start")]
11
+ end
12
+ super(*args)
13
+ end
14
+
15
+ desc "setup", "Run Invoker setup"
16
+ def setup
17
+ Invoker::Power::Setup.install
18
+ end
19
+
20
+ desc "version", "Print Invoker version"
21
+ def version
22
+ Invoker::Logger.puts Invoker::VERSION
23
+ end
24
+ map %w(-v --version) => :version
25
+
26
+ desc "uninstall", "Uninstall Invoker and all installed files"
27
+ def uninstall
28
+ Invoker::Power::Setup.uninstall
29
+ end
30
+
31
+ desc "start CONFIG_FILE", "Start Invoker Server"
32
+ option :port, type: :numeric, banner: "Port series to be used for starting rack servers"
33
+ option :daemon,
34
+ type: :boolean,
35
+ banner: "Daemonize the server into the background",
36
+ aliases: [:d]
37
+ def start(file)
38
+ Invoker.setup_config_location
39
+ port = options[:port] || 9000
40
+ Invoker.daemonize = options[:daemon]
41
+ Invoker.load_invoker_config(file, port)
42
+ warn_about_terminal_notifier
43
+ pinger = Invoker::CLI::Pinger.new(unix_socket)
44
+ abort("Invoker is already running".color(:red)) if pinger.invoker_running?
45
+ Invoker.commander.start_manager
46
+ end
47
+
48
+ desc "add process", "Add a program to Invoker server"
49
+ def add(name)
50
+ unix_socket.send_command('add', process_name: name)
51
+ end
52
+
53
+ desc "add_http process_name port", "Add an external http process to Invoker DNS server"
54
+ def add_http(name, port)
55
+ unix_socket.send_command('add_http', process_name: name, port: port)
56
+ end
57
+
58
+ desc "tail process1 process2", "Tail a particular process"
59
+ def tail(*names)
60
+ tailer = Invoker::CLI::Tail.new(names)
61
+ tailer.run
62
+ end
63
+
64
+ desc "reload process", "Reload a process managed by Invoker"
65
+ option :signal,
66
+ banner: "Signal to send for killing the process, default is SIGINT",
67
+ aliases: [:s]
68
+ def reload(name)
69
+ signal = options[:signal] || 'INT'
70
+ unix_socket.send_command('reload', process_name: name, signal: signal)
71
+ end
72
+
73
+ desc "list", "List all running processes"
74
+ def list
75
+ unix_socket.send_command('list') do |response_object|
76
+ Invoker::ProcessPrinter.new(response_object).tap { |printer| printer.print_table }
77
+ end
78
+ end
79
+
80
+ desc "remove process", "Stop a process managed by Invoker"
81
+ option :signal,
82
+ banner: "Signal to send for killing the process, default is SIGINT",
83
+ aliases: [:s]
84
+ def remove(name)
85
+ signal = options[:signal] || 'INT'
86
+ unix_socket.send_command('remove', process_name: name, signal: signal)
87
+ end
88
+
89
+ desc "stop", "Stop Invoker daemon"
90
+ def stop
91
+ Invoker.daemon.stop
92
+ end
93
+
94
+ private
95
+
96
+ def self.default_start_command?(args)
97
+ command_name = args.first
98
+ command_name &&
99
+ !command_name.match(/^-/) &&
100
+ !valid_tasks.include?(command_name)
101
+ end
102
+
103
+ def self.valid_tasks
104
+ tasks.keys + ["help"]
105
+ end
106
+
107
+ def unix_socket
108
+ Invoker::IPC::UnixClient.new
109
+ end
110
+
111
+ def warn_about_terminal_notifier
112
+ if RUBY_PLATFORM.downcase.include?("darwin")
113
+ command_path = `which terminal-notifier`
114
+ if !command_path || command_path.empty?
115
+ Invoker::Logger.puts "You can enable OSX notification for processes "\
116
+ "by installing terminal-notifier gem".color(:red)
117
+ end
118
+ end
119
+ end
120
+ end
121
+ end
122
+
123
+ require "invoker/cli/question"
124
+ require "invoker/cli/tail_watcher"
125
+ require "invoker/cli/tail"
126
+ require "invoker/cli/pinger"
@@ -0,0 +1,23 @@
1
+ require "timeout"
2
+
3
+ module Invoker
4
+ class CLI::Pinger
5
+ attr_accessor :unix_client
6
+ def initialize(unix_client)
7
+ @unix_client = unix_client
8
+ end
9
+
10
+ def invoker_running?
11
+ response = send_ping_and_read_response
12
+ response && response.status == 'pong'
13
+ end
14
+
15
+ private
16
+
17
+ def send_ping_and_read_response
18
+ Timeout.timeout(2) { unix_client.send_and_receive('ping') }
19
+ rescue Timeout::Error
20
+ nil
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,15 @@
1
+ module Invoker
2
+ class CLI::Question
3
+ def self.agree(question_text)
4
+ $stdout.print(question_text)
5
+ answer = $stdin.gets
6
+ answer.strip!
7
+ if answer =~ /\Ay(?:es)?|no?\Z/i
8
+ answer =~ /\Ay(?:es)?\Z/i
9
+ else
10
+ $stdout.puts "Please enter 'yes' or 'no'."
11
+ agree(question_text)
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,34 @@
1
+ module Invoker
2
+ class CLI::Tail
3
+ attr_accessor :process_names
4
+ def initialize(process_names)
5
+ verify_process_name(process_names)
6
+ @process_names = process_names
7
+ @unix_socket = Invoker::IPC::UnixClient.new
8
+ end
9
+
10
+ def run
11
+ socket = @unix_socket.send_and_wait('tail', process_names: process_names)
12
+ trap('INT') { socket.close }
13
+ loop do
14
+ message = read_next_line(socket)
15
+ break unless message
16
+ puts message.tail_line
17
+ end
18
+ end
19
+
20
+ private
21
+
22
+ def verify_process_name(process_names)
23
+ if process_names.empty?
24
+ abort("Tail command requires one or more process name")
25
+ end
26
+ end
27
+
28
+ def read_next_line(socket)
29
+ Invoker::IPC.message_from_io(socket)
30
+ rescue
31
+ nil
32
+ end
33
+ end
34
+ end