invoker 0.0.2

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