invoker 0.0.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.
@@ -0,0 +1,15 @@
1
+ ---
2
+ !binary "U0hBMQ==":
3
+ metadata.gz: !binary |-
4
+ ODk1Y2NiOGY1ODllNzlkMDI3ZTAxYzg4MzBiOTQ1Nzg4MTQ3NjM3OQ==
5
+ data.tar.gz: !binary |-
6
+ ODE0MmVhZDAyNzQ0NWIwZTdmYjVlOWRmYzFjZDdiM2I2NzViYjE4NQ==
7
+ !binary "U0hBNTEy":
8
+ metadata.gz: !binary |-
9
+ MDAyNDNhZmYzMmE5NTg0NzFhMWRmNTEwMTM5MjdhNzA0YzAwMTZhY2QxYzRm
10
+ ZWRiZmRhZjk0NGFmODU4NTMwMDgyNGNkNGVjNDA5M2I4NWEyOTYwZmEyYTVh
11
+ OWMwNGU2ZmU3NTUyNGNmMGU2YzJhNTA2NzU5M2JkZWQ3NDFhOGY=
12
+ data.tar.gz: !binary |-
13
+ MDVmZmQ1OWMxMGM1NTRjM2YzNzJjMjBiNjIzNDA1ZThhZDZmZDI0M2VkM2I4
14
+ NDNmYjM1ZGZmYTkyOWZmNjg0NjJjOWI3ZTlmMmQ4N2RjNjU0MGVlOGFhMDZi
15
+ YjE3NWUyOGU2ZjY5MjU0OWRjMmZhNmZlMjc2Njk3MWUzMTliZDI=
@@ -0,0 +1,4 @@
1
+ invoker.ini
2
+ config/
3
+ *.gem
4
+
@@ -0,0 +1,6 @@
1
+ language: ruby
2
+ rvm:
3
+ - 1.9.3
4
+ - 2.0.0
5
+ script: bundle exec rake spec
6
+
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ source "https://rubygems.org"
2
+
3
+ gemspec
4
+
5
+ gem 'pry'
6
+
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2013-2014 Hemant Kumar
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,5 @@
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
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
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
10
+
11
+ Invoker::Runner.run(ARGV)
12
+
13
+
14
+
@@ -0,0 +1,30 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = %q{invoker}
5
+ s.version = "0.0.2"
6
+
7
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
8
+ s.authors = ["Hemant Kumar"]
9
+ s.date = %q{2013-05-04}
10
+ s.description = %q{Something small for process management}
11
+ s.email = %q{hemant@codemancers.com}
12
+
13
+ s.files = `git ls-files`.split("\n")
14
+ s.test_files = `git ls-files -- {spec,features}/*`.split("\n")
15
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
16
+ s.require_paths = ["lib"]
17
+
18
+ s.homepage = %q{http://github.com/code-mancers/invoker}
19
+ s.licenses = ["MIT"]
20
+ s.require_paths = ["lib"]
21
+ s.summary = %q{Something small for Process management}
22
+ s.add_dependency("slop")
23
+ s.add_dependency("iniparse")
24
+ s.add_dependency("colored")
25
+ s.add_development_dependency("bacon")
26
+ s.add_development_dependency("mocha")
27
+ s.add_development_dependency("mocha-on-bacon")
28
+ s.add_development_dependency("rake")
29
+ end
30
+
@@ -0,0 +1,19 @@
1
+ $: << File.dirname(__FILE__) unless $:.include?(File.expand_path(File.dirname(__FILE__)))
2
+
3
+ module Invoker
4
+ VERSION = "0.0.2"
5
+ end
6
+
7
+ require "colored"
8
+ require_relative "invoker/runner"
9
+ require_relative "invoker/command_listener"
10
+ require_relative "invoker/errors"
11
+ require_relative "invoker/config"
12
+ require_relative "invoker/commander"
13
+ require_relative "invoker/command_worker"
14
+ require_relative "invoker/reactor"
15
+
16
+
17
+
18
+
19
+
@@ -0,0 +1,3 @@
1
+ require_relative "command_listener/server"
2
+ require_relative "command_listener/client"
3
+
@@ -0,0 +1,34 @@
1
+ module Invoker
2
+ module CommandListener
3
+ class Client
4
+ attr_accessor :client_socket
5
+ def initialize(client_socket)
6
+ @client_socket = client_socket
7
+ end
8
+
9
+ def read_and_execute
10
+ command_info = client_socket.read()
11
+ if command_info && !command_info.empty?
12
+ worker_command, command_label, rest_args = command_info.strip.split(" ")
13
+ if worker_command && command_label
14
+ run_command(worker_command, command_label, rest_args)
15
+ end
16
+ end
17
+ client_socket.close()
18
+ end
19
+
20
+ def run_command(worker_command, command_label, rest_args = nil)
21
+ case worker_command
22
+ when 'add'
23
+ Invoker::COMMANDER.add_command_by_label(command_label)
24
+ when 'remove'
25
+ Invoker::COMMANDER.remove_command(command_label, rest_args)
26
+ when 'reload'
27
+ Invoker::COMMANDER.reload_command(command_label)
28
+ else
29
+ $stdout.puts("\n Invalid command".red)
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,31 @@
1
+ require "fileutils"
2
+
3
+ module Invoker
4
+ module CommandListener
5
+ class Server
6
+ SOCKET_PATH = "/tmp/invoker"
7
+ def initialize
8
+ @open_clients = []
9
+ clean_old_socket()
10
+ UNIXServer.open(SOCKET_PATH) do |client|
11
+ loop do
12
+ client_socket = client.accept
13
+ process_client(client_socket)
14
+ end
15
+ end
16
+ end
17
+
18
+ def clean_old_socket
19
+ if File.exists?(SOCKET_PATH)
20
+ FileUtils.rm(SOCKET_PATH, :force => true)
21
+ end
22
+ end
23
+
24
+ def process_client(client_socket)
25
+ client = Invoker::CommandListener::Client.new(client_socket)
26
+ client.read_and_execute
27
+ end
28
+ end
29
+ end
30
+
31
+ end
@@ -0,0 +1,30 @@
1
+ module Invoker
2
+ class CommandWorker
3
+ attr_accessor :command_label, :pipe_end, :pid, :color
4
+
5
+ def initialize(command_label, pipe_end, pid, color)
6
+ @command_label = command_label
7
+ @pipe_end = pipe_end
8
+ @pid = pid
9
+ @color = color
10
+ end
11
+
12
+ # Copied verbatim from Eventmachine code
13
+ def receive_data data
14
+ (@buf ||= '') << data
15
+
16
+ while @buf.slice!(/(.*?)\r?\n/)
17
+ receive_line($1)
18
+ end
19
+ end
20
+
21
+ def unbind
22
+ # $stdout.print(".")
23
+ end
24
+
25
+ # Print the lines received over the network
26
+ def receive_line(line)
27
+ $stdout.puts "#{@command_label.send(color)} : #{line}"
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,165 @@
1
+ require "io/console"
2
+ require 'pty'
3
+
4
+ module Invoker
5
+ class Commander
6
+ MAX_PROCESS_COUNT = 10
7
+ LABEL_COLORS = ['green', 'yellow', 'blue', 'magenta', 'cyan']
8
+ attr_accessor :reactor, :workers, :thread_group, :open_pipes
9
+
10
+ def initialize
11
+ # mapping between open pipes and worker classes
12
+ @open_pipes = {}
13
+
14
+ # mapping between command label and worker classes
15
+ @workers = {}
16
+
17
+ @thread_group = ThreadGroup.new()
18
+ @worker_mutex = Mutex.new()
19
+ @reactor = Invoker::Reactor.new
20
+ Thread.abort_on_exception = true
21
+ end
22
+
23
+ def start_manager
24
+ if !Invoker::CONFIG.processes || Invoker::CONFIG.processes.empty?
25
+ raise Invoker::Errors::InvalidConfig.new("No processes configured in config file")
26
+ end
27
+ install_interrupt_handler()
28
+ unix_server_thread = Thread.new { Invoker::CommandListener::Server.new() }
29
+ thread_group.add(unix_server_thread)
30
+ Invoker::CONFIG.processes.each { |process_info| add_command(process_info) }
31
+ reactor.start
32
+ end
33
+
34
+ def add_command(process_info)
35
+ m, s = PTY.open
36
+ s.raw! # disable newline conversion.
37
+
38
+ pid = run_command(process_info, s)
39
+
40
+ s.close()
41
+
42
+ selected_color = LABEL_COLORS.shift()
43
+ LABEL_COLORS.push(selected_color)
44
+ worker = Invoker::CommandWorker.new(process_info.label, m, pid, selected_color)
45
+
46
+ add_worker(worker)
47
+ wait_on_pid(process_info.label,pid)
48
+ end
49
+
50
+ def add_command_by_label(command_label)
51
+ process_info = Invoker::CONFIG.processes.detect {|pconfig|
52
+ pconfig.label == command_label
53
+ }
54
+ if process_info
55
+ add_command(process_info)
56
+ end
57
+ end
58
+
59
+ def reload_command(command_label)
60
+ remove_command(command_label)
61
+ add_command_by_label(command_label)
62
+ end
63
+
64
+ def remove_command(command_label, rest_args)
65
+ worker = workers[command_label]
66
+ signal_to_use = rest_args ? Array(rest_args).first : 'INT'
67
+
68
+ if worker
69
+ $stdout.puts("Removing #{command_label} with signal #{signal_to_use}".red)
70
+ process_kill(worker.pid, signal_to_use)
71
+ end
72
+ end
73
+
74
+ def get_worker_from_fd(fd)
75
+ open_pipes[fd.fileno]
76
+ end
77
+
78
+ def get_worker_from_label(label)
79
+ workers[label]
80
+ end
81
+
82
+ private
83
+ def process_kill(pid, signal_to_use)
84
+ if signal_to_use.to_i == 0
85
+ Process.kill(signal_to_use, pid)
86
+ else
87
+ Process.kill(signal_to_use.to_i, pid)
88
+ end
89
+ end
90
+
91
+ # Remove worker from all collections
92
+ def remove_worker(command_label)
93
+ @worker_mutex.synchronize do
94
+ worker = @workers[command_label]
95
+ if worker
96
+ @open_pipes.delete(worker.pipe_end.fileno)
97
+ @reactor.remove_from_monitoring(worker.pipe_end)
98
+ @workers.delete(command_label)
99
+ end
100
+ end
101
+ end
102
+
103
+ # add worker to global collections
104
+ def add_worker(worker)
105
+ @worker_mutex.synchronize do
106
+ @open_pipes[worker.pipe_end.fileno] = worker
107
+ @workers[worker.command_label] = worker
108
+ @reactor.add_to_monitor(worker.pipe_end)
109
+ end
110
+ end
111
+
112
+ def run_command(process_info, write_pipe)
113
+ if defined?(Bundler)
114
+ Bundler.with_clean_env do
115
+ spawn(process_info.cmd,
116
+ :chdir => process_info.dir || "/", :out => write_pipe, :err => write_pipe
117
+ )
118
+ end
119
+ else
120
+ spawn(process_info.cmd,
121
+ :chdir => process_info.dir || "/", :out => write_pipe, :err => write_pipe
122
+ )
123
+ end
124
+ end
125
+
126
+ def wait_on_pid(command_label,pid)
127
+ raise Invoker::Errors::ToomanyOpenConnections if @thread_group.enclosed?
128
+ thread = Thread.new do
129
+ Process.wait(pid)
130
+ message = "Process with command #{command_label} exited with status #{$?.exitstatus}"
131
+ $stdout.puts("\n#{message}".red)
132
+ notify_user(message)
133
+ remove_worker(command_label)
134
+ end
135
+ @thread_group.add(thread)
136
+ end
137
+
138
+ def notify_user(message)
139
+ if defined?(Bundler)
140
+ Bundler.with_clean_env do
141
+ check_and_notify_with_terminal_notifier(message)
142
+ end
143
+ else
144
+ check_and_notify_with_terminal_notifier(message)
145
+ end
146
+ end
147
+
148
+ def check_and_notify_with_terminal_notifier(message)
149
+ return unless RUBY_PLATFORM.downcase.include?("darwin")
150
+
151
+ command_path = `which terminal-notifier`
152
+ if command_path && !command_path.empty?
153
+ system("terminal-notifier -message '#{message}' -title Invoker")
154
+ end
155
+ end
156
+
157
+ def install_interrupt_handler
158
+ Signal.trap("INT") do
159
+ @workers.each {|key,worker| Process.kill("INT", worker.pid) }
160
+ exit(0)
161
+ end
162
+ end
163
+
164
+ end
165
+ end
@@ -0,0 +1,19 @@
1
+ require "yaml"
2
+ require 'iniparse'
3
+
4
+ module Invoker
5
+ class Config
6
+ attr_accessor :processes
7
+ def initialize(filename)
8
+ @ini_content = File.read(filename)
9
+ @processes = process_ini(@ini_content)
10
+ end
11
+
12
+ def process_ini(ini_content)
13
+ document = IniParse.parse(ini_content)
14
+ document.map do |section|
15
+ OpenStruct.new(label: section.key, dir: section["directory"], cmd: section["command"])
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,14 @@
1
+ module Invoker
2
+ module Errors
3
+ class ToomanyOpenConnections < StandardError; end
4
+ class ProcessTerminated < StandardError
5
+ attr_accessor :message, :ready_fd
6
+ def initialize(ready_fd, message)
7
+ @ready_fd = ready_fd
8
+ @message = message
9
+ end
10
+ end
11
+
12
+ class InvalidConfig < StandardError; end
13
+ end
14
+ end
@@ -0,0 +1,61 @@
1
+ module Invoker
2
+ class Reactor
3
+ attr_accessor :monitored_fds
4
+ def initialize
5
+ @monitored_fds = []
6
+ end
7
+
8
+ def add_to_monitor(fd)
9
+ @monitored_fds << fd
10
+ end
11
+
12
+ def remove_from_monitoring(fd)
13
+ @monitored_fds.delete(fd)
14
+ end
15
+
16
+ def start
17
+ loop do
18
+ watch_on_pipe
19
+ end
20
+ end
21
+
22
+ def watch_on_pipe
23
+ ready_read_fds,ready_write_fds,read_error_fds = select(monitored_fds,[],[],0.05)
24
+
25
+ if ready_read_fds && !ready_read_fds.empty?
26
+ handle_read_event(ready_read_fds)
27
+ end
28
+ end
29
+
30
+ def handle_read_event(ready_read_fds)
31
+ ready_fds = ready_read_fds.flatten.compact
32
+ ready_fds.each {|ready_fd| process_read(ready_fd) }
33
+ end
34
+
35
+ def process_read(ready_fd)
36
+ command_worker = Invoker::COMMANDER.get_worker_from_fd(ready_fd)
37
+ begin
38
+ data = read_data(ready_fd)
39
+ command_worker.receive_data(data)
40
+ rescue Invoker::Errors::ProcessTerminated
41
+ command_worker.unbind()
42
+ end
43
+ end
44
+
45
+ def read_data(ready_fd)
46
+ sock_data = []
47
+ begin
48
+ while(t_data = ready_fd.read_nonblock(64))
49
+ sock_data << t_data
50
+ end
51
+ rescue Errno::EAGAIN
52
+ return sock_data.join
53
+ rescue Errno::EWOULDBLOCK
54
+ return sock_data.join
55
+ rescue
56
+ raise Invoker::Errors::ProcessTerminated.new(ready_fd,sock_data.join)
57
+ end
58
+ end
59
+
60
+ end
61
+ end
@@ -0,0 +1,105 @@
1
+ require "slop"
2
+ require "ostruct"
3
+ require "socket"
4
+
5
+ module Invoker
6
+ class Runner
7
+ def self.run(args)
8
+
9
+ selected_command = nil
10
+
11
+ opts = Slop.parse(args, help: true) do
12
+ on :v, "Print the version" do
13
+ $stdout.puts Invoker::VERSION
14
+ end
15
+
16
+ command 'start' do
17
+ banner "Usage : invoker start config.ini \n Start Invoker Process Manager"
18
+ run do |cmd_opts, cmd_args|
19
+ selected_command = OpenStruct.new(:command => 'start', :file => cmd_args.first)
20
+ end
21
+ end
22
+
23
+ command 'add' do
24
+ banner "Usage : invoker add process_label \n Start the process with given process_label"
25
+ run do |cmd_opts, cmd_args|
26
+ selected_command = OpenStruct.new(:command => 'add', :command_key => cmd_args.first)
27
+ end
28
+ end
29
+
30
+ command 'remove' do
31
+ banner "Usage : invoker remove process_label \n Stop the process with given label"
32
+ on :s, :signal=, "Signal to send for killing the process, default is SIGINT", as: String
33
+
34
+ run do |cmd_opts, cmd_args|
35
+ signal_to_use = cmd_opts.to_hash[:signal] || 'INT'
36
+ selected_command = OpenStruct.new(
37
+ :command => 'remove',
38
+ :command_key => cmd_args.first,
39
+ :signal => signal_to_use
40
+ )
41
+ end
42
+ end
43
+ end
44
+ unless selected_command
45
+ $stdout.puts opts.inspect
46
+ else
47
+ run_command(selected_command)
48
+ end
49
+ end
50
+
51
+ def self.run_command(selected_command)
52
+ return unless selected_command
53
+ case selected_command.command
54
+ when 'start'
55
+ start_server(selected_command)
56
+ when 'add'
57
+ add_command(selected_command)
58
+ when 'remove'
59
+ remove_command(selected_command)
60
+ else
61
+ $stdout.puts "Invalid command"
62
+ end
63
+ end
64
+
65
+ def self.start_server(selected_command)
66
+ config = Invoker::Config.new(selected_command.file)
67
+ Invoker.const_set(:CONFIG, config)
68
+ warn_about_terminal_notifier()
69
+ commander = Invoker::Commander.new()
70
+ Invoker.const_set(:COMMANDER, commander)
71
+ commander.start_manager()
72
+ end
73
+
74
+ def self.add_command(selected_command)
75
+ socket = UNIXSocket.open(Invoker::CommandListener::Server::SOCKET_PATH)
76
+ socket.puts("add #{selected_command.command_key}")
77
+ socket.flush()
78
+ socket.close()
79
+ end
80
+
81
+ def self.remove_command(selected_command)
82
+ socket = UNIXSocket.open(Invoker::CommandListener::Server::SOCKET_PATH)
83
+ socket.puts("remove #{selected_command.command_key} #{selected_command.signal}")
84
+ socket.flush()
85
+ socket.close()
86
+ end
87
+
88
+ def self.refresh_command(selected_command)
89
+ socket = UNIXSocket.open(Invoker::CommandListener::Server::SOCKET_PATH)
90
+ socket.puts("reload #{selected_command.command_key}")
91
+ socket.flush()
92
+ socket.close()
93
+ end
94
+
95
+ def self.warn_about_terminal_notifier
96
+ if RUBY_PLATFORM.downcase.include?("darwin")
97
+ command_path = `which terminal-notifier`
98
+ if !command_path || command_path.empty?
99
+ $stdout.puts("You can enable OSX notification for processes by installing terminal-notification gem".red)
100
+ end
101
+ end
102
+ end
103
+
104
+ end
105
+ end
@@ -0,0 +1,57 @@
1
+ Invoker is a gem for managing processes in development environment.
2
+
3
+ [![Build Status](https://travis-ci.org/code-mancers/invoker.png)](https://travis-ci.org/code-mancers/invoker)
4
+
5
+
6
+ ## Usage ##
7
+
8
+ First we need to install `invoker` gem to get command line utility called `invoker`, we can do that via:
9
+
10
+ gem install invoker
11
+
12
+ Currently it only works with Ruby 1.9.3 and 2.0.
13
+
14
+ You need to start by creating a `ini` file which will define processes you want to manage using invoker. An example
15
+ `ini` file is included in the repo.
16
+
17
+ [rails]
18
+ directory = /home/gnufied/god_particle
19
+ command = zsh -c 'bundle exec rails s -p 5000'
20
+
21
+ [dj]
22
+ directory = /home/gnufied/god_particle
23
+ command = zsh -c 'bundle exec ruby script/delayed_job'
24
+
25
+
26
+ [events]
27
+ directory = /home/gnufied/god_particle
28
+ command = zsh -c 'bundle exec ruby script/event_server'
29
+
30
+ After that you can start process manager via:
31
+
32
+ ~> invoker start invoker.ini
33
+
34
+ Above command will start all your processes in one terminal with their stdout/stderr merged and labelled.
35
+
36
+ Now additionally you can control individual process by,
37
+
38
+ # Will try to stop running delayed job by sending SIGINT to the process
39
+ ~> invoker remove dj
40
+
41
+ # If Process can't be killed by SIGINT send a custom signal
42
+ ~> invoker remove dj -s 9
43
+
44
+ # add and start running
45
+ ~> invoker add dj
46
+
47
+ You can also enable OSX notifications for crashed processes by installing `terminal-notification` gem. It is not a dependency, but can be useful if something crashed and you weren't paying attention.
48
+
49
+
50
+ ## Bug reports, Feature requests ##
51
+
52
+ Please use [Github Issue Tracker](https://github.com/code-mancers/invoker/issues) for feature requests or bug reports.
53
+
54
+
55
+
56
+
57
+
@@ -0,0 +1,57 @@
1
+ require "spec_helper"
2
+
3
+ describe Invoker::CommandListener::Client do
4
+ describe "add command" do
5
+ before do
6
+ @client_socket = mock()
7
+ @client = Invoker::CommandListener::Client.new(@client_socket)
8
+ end
9
+
10
+ it "should run if read from socket" do
11
+ invoker_commander.expects(:add_command_by_label).with("foo")
12
+ @client_socket.expects(:read).returns("add foo\n")
13
+ @client_socket.expects(:close)
14
+
15
+ @client.read_and_execute()
16
+ end
17
+ end
18
+
19
+ describe "remove command" do
20
+ before do
21
+ @client_socket = mock()
22
+ @client = Invoker::CommandListener::Client.new(@client_socket)
23
+ end
24
+
25
+ it "with specific signal" do
26
+ invoker_commander.expects(:remove_command).with("foo", "9")
27
+ @client_socket.expects(:read).returns("remove foo 9\n")
28
+ @client_socket.expects(:close)
29
+
30
+ @client.read_and_execute()
31
+ end
32
+
33
+ it "with default signal" do
34
+ invoker_commander.expects(:remove_command).with("foo",nil)
35
+ @client_socket.expects(:read).returns("remove foo\n")
36
+ @client_socket.expects(:close)
37
+
38
+ @client.read_and_execute()
39
+ end
40
+ end
41
+
42
+ describe "invalid command" do
43
+ before do
44
+ @client_socket = mock()
45
+ @client = Invoker::CommandListener::Client.new(@client_socket)
46
+ end
47
+
48
+ it "should print error if read from socket" do
49
+ invoker_commander.expects(:remove_command).never()
50
+ invoker_commander.expects(:add_command_by_label).never()
51
+ @client_socket.expects(:read).returns("eugh foo\n")
52
+ @client_socket.expects(:close)
53
+
54
+ @client.read_and_execute
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,4 @@
1
+ require "spec_helper"
2
+
3
+ describe "Command Worker" do
4
+ end
@@ -0,0 +1,92 @@
1
+ require "spec_helper"
2
+
3
+ describe "Invoker::Commander" do
4
+
5
+ describe "With no processes configured" do
6
+ before do
7
+ @commander = Invoker::Commander.new()
8
+ end
9
+
10
+ it "should throw error" do
11
+ invoker_config.stubs(:processes).returns([])
12
+
13
+ lambda {
14
+ @commander.start_manager()
15
+ }.should.raise(Invoker::Errors::InvalidConfig)
16
+ end
17
+ end
18
+
19
+ describe "#add_command_by_label" do
20
+ before do
21
+ @commander = Invoker::Commander.new()
22
+ end
23
+
24
+ it "should find command by label and start it, if found" do
25
+ invoker_config.stubs(:processes).returns([OpenStruct.new(:label => "resque", :cmd => "foo", :dir => "bar")])
26
+ @commander.expects(:add_command).returns(true)
27
+
28
+ @commander.add_command_by_label("resque")
29
+ end
30
+ end
31
+
32
+ describe "#remove_command" do
33
+ describe "when a worker is found" do
34
+ before do
35
+ @commander = Invoker::Commander.new()
36
+ @commander.workers.expects(:[]).returns(OpenStruct.new(:pid => "bogus"))
37
+ end
38
+
39
+ describe "if a signal is specified" do
40
+ it "should use that signal to kill the worker" do
41
+ @commander.expects(:process_kill).with("bogus", "HUP").returns(true)
42
+ @commander.remove_command("resque", "HUP")
43
+ end
44
+ end
45
+
46
+ describe "if no signal is specified" do
47
+ it "should use INT signal" do
48
+ @commander.expects(:process_kill).with("bogus", "INT").returns(true)
49
+ @commander.remove_command("resque", nil)
50
+ end
51
+ end
52
+ end
53
+
54
+ describe "when no worker is found" do
55
+ before do
56
+ @commander = Invoker::Commander.new()
57
+ @commander.workers.expects(:[]).returns(nil)
58
+ end
59
+
60
+ it "should not kill anything" do
61
+ @commander.expects(:process_kill).never()
62
+ @commander.remove_command("resque", "HUP")
63
+ end
64
+ end
65
+
66
+ end
67
+
68
+ describe "#add_command" do
69
+ before do
70
+ invoker_config.stubs(:processes).returns([OpenStruct.new(:label => "sleep", :cmd => "sleep 4", :dir => ENV['HOME'])])
71
+ @commander = Invoker::Commander.new()
72
+ end
73
+
74
+ it "should populate workers and open_pipes" do
75
+ @commander.reactor.expects(:start).returns(true)
76
+ @commander.start_manager()
77
+ @commander.open_pipes.should.not.be.empty
78
+ @commander.workers.should.not.be.empty
79
+
80
+ worker = @commander.workers['sleep']
81
+
82
+ worker.should.not.equal nil
83
+ worker.command_label.should.equal "sleep"
84
+ worker.color.should.equal "green"
85
+
86
+
87
+ pipe_end_worker = @commander.open_pipes[worker.pipe_end.fileno]
88
+ pipe_end_worker.should.not.equal nil
89
+ end
90
+ end
91
+
92
+ end
@@ -0,0 +1,29 @@
1
+ require "bacon"
2
+ require "mocha-on-bacon"
3
+
4
+ __LIB_PATH__ = File.join(File.dirname(__FILE__), "..")
5
+ $: << __LIB_PATH__
6
+
7
+ require "pry"
8
+ require "invoker"
9
+
10
+
11
+ def invoker_config
12
+ if Invoker.const_defined?(:CONFIG)
13
+ Invoker::CONFIG
14
+ else
15
+ Invoker.const_set(:CONFIG, mock())
16
+ Invoker::CONFIG
17
+ end
18
+ end
19
+
20
+ def invoker_commander
21
+ if Invoker.const_defined?(:COMMANDER)
22
+ Invoker::COMMANDER
23
+ else
24
+ Invoker.const_set(:COMMANDER, mock())
25
+ Invoker::COMMANDER
26
+ end
27
+ end
28
+
29
+
metadata ADDED
@@ -0,0 +1,169 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: invoker
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.2
5
+ platform: ruby
6
+ authors:
7
+ - Hemant Kumar
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2013-05-04 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ version_requirements: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - ! '>='
17
+ - !ruby/object:Gem::Version
18
+ version: '0'
19
+ type: :runtime
20
+ prerelease: false
21
+ name: slop
22
+ requirement: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ! '>='
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ version_requirements: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - ! '>='
31
+ - !ruby/object:Gem::Version
32
+ version: '0'
33
+ type: :runtime
34
+ prerelease: false
35
+ name: iniparse
36
+ requirement: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ! '>='
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ version_requirements: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - ! '>='
45
+ - !ruby/object:Gem::Version
46
+ version: '0'
47
+ type: :runtime
48
+ prerelease: false
49
+ name: colored
50
+ requirement: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ! '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - ! '>='
59
+ - !ruby/object:Gem::Version
60
+ version: '0'
61
+ type: :development
62
+ prerelease: false
63
+ name: bacon
64
+ requirement: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ! '>='
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ version_requirements: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - ! '>='
73
+ - !ruby/object:Gem::Version
74
+ version: '0'
75
+ type: :development
76
+ prerelease: false
77
+ name: mocha
78
+ requirement: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ! '>='
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ version_requirements: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - ! '>='
87
+ - !ruby/object:Gem::Version
88
+ version: '0'
89
+ type: :development
90
+ prerelease: false
91
+ name: mocha-on-bacon
92
+ requirement: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ! '>='
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ - !ruby/object:Gem::Dependency
98
+ version_requirements: !ruby/object:Gem::Requirement
99
+ requirements:
100
+ - - ! '>='
101
+ - !ruby/object:Gem::Version
102
+ version: '0'
103
+ type: :development
104
+ prerelease: false
105
+ name: rake
106
+ requirement: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ! '>='
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ description: Something small for process management
112
+ email: hemant@codemancers.com
113
+ executables:
114
+ - invoker
115
+ extensions: []
116
+ extra_rdoc_files: []
117
+ files:
118
+ - .gitignore
119
+ - .travis.yml
120
+ - Gemfile
121
+ - MIT-LICENSE
122
+ - Rakefile
123
+ - bin/invoker
124
+ - invoker.gemspec
125
+ - lib/invoker.rb
126
+ - lib/invoker/command_listener.rb
127
+ - lib/invoker/command_listener/client.rb
128
+ - lib/invoker/command_listener/server.rb
129
+ - lib/invoker/command_worker.rb
130
+ - lib/invoker/commander.rb
131
+ - lib/invoker/config.rb
132
+ - lib/invoker/errors.rb
133
+ - lib/invoker/reactor.rb
134
+ - lib/invoker/runner.rb
135
+ - readme.md
136
+ - spec/invoker/command_listener/client_spec.rb
137
+ - spec/invoker/command_worker_spec.rb
138
+ - spec/invoker/commander_spec.rb
139
+ - spec/spec_helper.rb
140
+ homepage: http://github.com/code-mancers/invoker
141
+ licenses:
142
+ - MIT
143
+ metadata: {}
144
+ post_install_message:
145
+ rdoc_options: []
146
+ require_paths:
147
+ - lib
148
+ required_ruby_version: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - ! '>='
151
+ - !ruby/object:Gem::Version
152
+ version: '0'
153
+ required_rubygems_version: !ruby/object:Gem::Requirement
154
+ requirements:
155
+ - - ! '>='
156
+ - !ruby/object:Gem::Version
157
+ version: '0'
158
+ requirements: []
159
+ rubyforge_project:
160
+ rubygems_version: 2.0.3
161
+ signing_key:
162
+ specification_version: 4
163
+ summary: Something small for Process management
164
+ test_files:
165
+ - spec/invoker/command_listener/client_spec.rb
166
+ - spec/invoker/command_worker_spec.rb
167
+ - spec/invoker/commander_spec.rb
168
+ - spec/spec_helper.rb
169
+ has_rdoc: