necro 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.
- checksums.yaml +15 -0
- data/.gitignore +5 -0
- data/.travis.yml +6 -0
- data/Gemfile +6 -0
- data/Rakefile +5 -0
- data/bin/necro +13 -0
- data/lib/necro.rb +19 -0
- data/lib/necro/command_listener.rb +3 -0
- data/lib/necro/command_listener/client.rb +34 -0
- data/lib/necro/command_listener/server.rb +31 -0
- data/lib/necro/command_worker.rb +30 -0
- data/lib/necro/commander.rb +157 -0
- data/lib/necro/config.rb +19 -0
- data/lib/necro/errors.rb +14 -0
- data/lib/necro/reactor.rb +61 -0
- data/lib/necro/runner.rb +101 -0
- data/necro.gemspec +30 -0
- data/necro.ini.example +13 -0
- data/readme.md +47 -0
- data/spec/necro/command_listener/client_spec.rb +57 -0
- data/spec/necro/command_worker_spec.rb +4 -0
- data/spec/necro/commander_spec.rb +92 -0
- data/spec/spec_helper.rb +29 -0
- metadata +169 -0
checksums.yaml
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
---
|
2
|
+
!binary "U0hBMQ==":
|
3
|
+
metadata.gz: !binary |-
|
4
|
+
NDcwZDQzZWIxNWM4Y2FhMjVmNzYyODE0MDdhMjk3Nzc1ZjMyNzFmOA==
|
5
|
+
data.tar.gz: !binary |-
|
6
|
+
ZjAyZDg0NmNjMTU0MmQ0Y2NjY2MwMGZjMTE3ZDhhZDQ5NDNlOGI0MA==
|
7
|
+
!binary "U0hBNTEy":
|
8
|
+
metadata.gz: !binary |-
|
9
|
+
YmRkZGViN2M5NjYzYTAxYjU2YmIxMGVhYTAyN2VlNDg5MjEwMzBiNGY5NWNl
|
10
|
+
YWRhNDM1YTY4ZDc4OGUyMGVkZWEwMmQ4MjM2M2RhNDc2MmM4OGZmYWEwZGQ2
|
11
|
+
YWVmYjRkNjFhZDgxNDE0NmNjY2I0NjI2YjhhODc3ODBlZDA4Yjk=
|
12
|
+
data.tar.gz: !binary |-
|
13
|
+
YWE4ZjI0NDA2OTg4NTJkNDNkMmU5ZWUwNTg5ZjI3ODMwY2QzMGY3YzM1MmM0
|
14
|
+
OGNiZmE3YTU1NjUxODk2ZGMzNjljOWUwNzhlOWY0YzJhYTM4ZjZjZDM3ZTRh
|
15
|
+
ODg1Mzc2NmI1NGZiMDkzMWE5NzU4YmYwYjRiNzJlMTM5M2EzODM=
|
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/Rakefile
ADDED
data/bin/necro
ADDED
data/lib/necro.rb
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
$: << File.dirname(__FILE__) unless $:.include?(File.expand_path(File.dirname(__FILE__)))
|
2
|
+
|
3
|
+
module Necro
|
4
|
+
VERSION = "0.0.2"
|
5
|
+
end
|
6
|
+
|
7
|
+
require "colored"
|
8
|
+
require_relative "necro/runner"
|
9
|
+
require_relative "necro/command_listener"
|
10
|
+
require_relative "necro/errors"
|
11
|
+
require_relative "necro/config"
|
12
|
+
require_relative "necro/commander"
|
13
|
+
require_relative "necro/command_worker"
|
14
|
+
require_relative "necro/reactor"
|
15
|
+
|
16
|
+
|
17
|
+
|
18
|
+
|
19
|
+
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module Necro
|
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
|
+
Necro::COMMANDER.add_command_by_label(command_label)
|
24
|
+
when 'remove'
|
25
|
+
Necro::COMMANDER.remove_command(command_label, rest_args)
|
26
|
+
when 'reload'
|
27
|
+
Necro::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 Necro
|
4
|
+
module CommandListener
|
5
|
+
class Server
|
6
|
+
SOCKET_PATH = "/tmp/necro"
|
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 = Necro::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 Necro
|
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,157 @@
|
|
1
|
+
require "io/console"
|
2
|
+
require 'pty'
|
3
|
+
|
4
|
+
module Necro
|
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 = Necro::Reactor.new
|
20
|
+
Thread.abort_on_exception = true
|
21
|
+
end
|
22
|
+
|
23
|
+
def start_manager
|
24
|
+
if !Necro::CONFIG.processes || Necro::CONFIG.processes.empty?
|
25
|
+
raise Necro::Errors::InvalidConfig.new("No processes configured in config file")
|
26
|
+
end
|
27
|
+
unix_server_thread = Thread.new { Necro::CommandListener::Server.new() }
|
28
|
+
thread_group.add(unix_server_thread)
|
29
|
+
Necro::CONFIG.processes.each { |process_info| add_command(process_info) }
|
30
|
+
reactor.start
|
31
|
+
end
|
32
|
+
|
33
|
+
def add_command(process_info)
|
34
|
+
m, s = PTY.open
|
35
|
+
s.raw! # disable newline conversion.
|
36
|
+
|
37
|
+
pid = run_command(process_info, s)
|
38
|
+
|
39
|
+
s.close()
|
40
|
+
|
41
|
+
selected_color = LABEL_COLORS.shift()
|
42
|
+
LABEL_COLORS.push(selected_color)
|
43
|
+
worker = Necro::CommandWorker.new(process_info.label, m, pid, selected_color)
|
44
|
+
|
45
|
+
add_worker(worker)
|
46
|
+
wait_on_pid(process_info.label,pid)
|
47
|
+
end
|
48
|
+
|
49
|
+
def add_command_by_label(command_label)
|
50
|
+
process_info = Necro::CONFIG.processes.detect {|pconfig|
|
51
|
+
pconfig.label == command_label
|
52
|
+
}
|
53
|
+
if process_info
|
54
|
+
add_command(process_info)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def reload_command(command_label)
|
59
|
+
remove_command(command_label)
|
60
|
+
add_command_by_label(command_label)
|
61
|
+
end
|
62
|
+
|
63
|
+
def remove_command(command_label, rest_args)
|
64
|
+
worker = workers[command_label]
|
65
|
+
signal_to_use = rest_args ? Array(rest_args).first : 'INT'
|
66
|
+
|
67
|
+
if worker
|
68
|
+
$stdout.puts("Removing #{command_label} with signal #{signal_to_use}".red)
|
69
|
+
process_kill(worker.pid, signal_to_use)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def get_worker_from_fd(fd)
|
74
|
+
open_pipes[fd.fileno]
|
75
|
+
end
|
76
|
+
|
77
|
+
def get_worker_from_label(label)
|
78
|
+
workers[label]
|
79
|
+
end
|
80
|
+
|
81
|
+
private
|
82
|
+
def process_kill(pid, signal_to_use)
|
83
|
+
if signal_to_use.to_i == 0
|
84
|
+
Process.kill(signal_to_use, pid)
|
85
|
+
else
|
86
|
+
Process.kill(signal_to_use.to_i, pid)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
# Remove worker from all collections
|
91
|
+
def remove_worker(command_label)
|
92
|
+
@worker_mutex.synchronize do
|
93
|
+
worker = @workers[command_label]
|
94
|
+
if worker
|
95
|
+
@open_pipes.delete(worker.pipe_end.fileno)
|
96
|
+
@reactor.remove_from_monitoring(worker.pipe_end)
|
97
|
+
@workers.delete(command_label)
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
# add worker to global collections
|
103
|
+
def add_worker(worker)
|
104
|
+
@worker_mutex.synchronize do
|
105
|
+
@open_pipes[worker.pipe_end.fileno] = worker
|
106
|
+
@workers[worker.command_label] = worker
|
107
|
+
@reactor.add_to_monitor(worker.pipe_end)
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
def run_command(process_info, write_pipe)
|
112
|
+
if defined?(Bundler)
|
113
|
+
Bundler.with_clean_env do
|
114
|
+
spawn(process_info.cmd,
|
115
|
+
:chdir => process_info.dir || "/", :out => write_pipe, :err => write_pipe
|
116
|
+
)
|
117
|
+
end
|
118
|
+
else
|
119
|
+
spawn(process_info.cmd,
|
120
|
+
:chdir => process_info.dir || "/", :out => write_pipe, :err => write_pipe
|
121
|
+
)
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
def wait_on_pid(command_label,pid)
|
126
|
+
raise Necro::Errors::ToomanyOpenConnections if @thread_group.enclosed?
|
127
|
+
thread = Thread.new do
|
128
|
+
Process.wait(pid)
|
129
|
+
message = "Process with command #{command_label} exited with status #{$?.exitstatus}"
|
130
|
+
$stdout.puts("\n#{message}".red)
|
131
|
+
notify_user(message)
|
132
|
+
remove_worker(command_label)
|
133
|
+
end
|
134
|
+
@thread_group.add(thread)
|
135
|
+
end
|
136
|
+
|
137
|
+
def notify_user(message)
|
138
|
+
if defined?(Bundler)
|
139
|
+
Bundler.with_clean_env do
|
140
|
+
check_and_notify_with_terminal_notifier(message)
|
141
|
+
end
|
142
|
+
else
|
143
|
+
check_and_notify_with_terminal_notifier(message)
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
def check_and_notify_with_terminal_notifier(message)
|
148
|
+
return unless RUBY_PLATFORM.downcase.include?("darwin")
|
149
|
+
|
150
|
+
command_path = `which terminal-notifier`
|
151
|
+
if command_path && !command_path.empty?
|
152
|
+
system("terminal-notifier -message '#{message}' -title Necro")
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
end
|
157
|
+
end
|
data/lib/necro/config.rb
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
require "yaml"
|
2
|
+
require 'iniparse'
|
3
|
+
|
4
|
+
module Necro
|
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
|
data/lib/necro/errors.rb
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
module Necro
|
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 Necro
|
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 = Necro::COMMANDER.get_worker_from_fd(ready_fd)
|
37
|
+
begin
|
38
|
+
data = read_data(ready_fd)
|
39
|
+
command_worker.receive_data(data)
|
40
|
+
rescue Necro::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 Necro::Errors::ProcessTerminated.new(ready_fd,sock_data.join)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|
61
|
+
end
|
data/lib/necro/runner.rb
ADDED
@@ -0,0 +1,101 @@
|
|
1
|
+
require "slop"
|
2
|
+
require "ostruct"
|
3
|
+
require "socket"
|
4
|
+
|
5
|
+
module Necro
|
6
|
+
class Runner
|
7
|
+
def self.run(args)
|
8
|
+
|
9
|
+
selected_command = nil
|
10
|
+
|
11
|
+
Slop.parse(args, help: true) do
|
12
|
+
on :v, "Print the version" do
|
13
|
+
$stdout.puts Necro::VERSION
|
14
|
+
end
|
15
|
+
|
16
|
+
command 'start' do
|
17
|
+
banner "Usage : necro start config.ini \n Start Necro 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 : necro 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 : necro 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
|
+
run_command(selected_command)
|
45
|
+
end
|
46
|
+
|
47
|
+
def self.run_command(selected_command)
|
48
|
+
return unless selected_command
|
49
|
+
case selected_command.command
|
50
|
+
when 'start'
|
51
|
+
start_server(selected_command)
|
52
|
+
when 'add'
|
53
|
+
add_command(selected_command)
|
54
|
+
when 'remove'
|
55
|
+
remove_command(selected_command)
|
56
|
+
else
|
57
|
+
$stdout.puts "Invalid command"
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def self.start_server(selected_command)
|
62
|
+
config = Necro::Config.new(selected_command.file)
|
63
|
+
Necro.const_set(:CONFIG, config)
|
64
|
+
warn_about_terminal_notifier()
|
65
|
+
commander = Necro::Commander.new()
|
66
|
+
Necro.const_set(:COMMANDER, commander)
|
67
|
+
commander.start_manager()
|
68
|
+
end
|
69
|
+
|
70
|
+
def self.add_command(selected_command)
|
71
|
+
socket = UNIXSocket.open(Necro::CommandListener::Server::SOCKET_PATH)
|
72
|
+
socket.puts("add #{selected_command.command_key}")
|
73
|
+
socket.flush()
|
74
|
+
socket.close()
|
75
|
+
end
|
76
|
+
|
77
|
+
def self.remove_command(selected_command)
|
78
|
+
socket = UNIXSocket.open(Necro::CommandListener::Server::SOCKET_PATH)
|
79
|
+
socket.puts("remove #{selected_command.command_key} #{selected_command.signal}")
|
80
|
+
socket.flush()
|
81
|
+
socket.close()
|
82
|
+
end
|
83
|
+
|
84
|
+
def self.refresh_command(selected_command)
|
85
|
+
socket = UNIXSocket.open(Necro::CommandListener::Server::SOCKET_PATH)
|
86
|
+
socket.puts("reload #{selected_command.command_key}")
|
87
|
+
socket.flush()
|
88
|
+
socket.close()
|
89
|
+
end
|
90
|
+
|
91
|
+
def self.warn_about_terminal_notifier
|
92
|
+
if RUBY_PLATFORM.downcase.include?("darwin")
|
93
|
+
command_path = `which terminal-notifier`
|
94
|
+
if !command_path || command_path.empty?
|
95
|
+
$stdout.puts("You can enable OSX notification for processes by installing terminal-notification gem".red)
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
end
|
101
|
+
end
|
data/necro.gemspec
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
Gem::Specification.new do |s|
|
4
|
+
s.name = %q{necro}
|
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/necro}
|
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
|
+
|
data/necro.ini.example
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
[rails]
|
2
|
+
directory = /home/gnufied/god_particle
|
3
|
+
command = zsh -c 'bundle exec rails s -p 5000'
|
4
|
+
|
5
|
+
[dj]
|
6
|
+
directory = /home/gnufied/god_particle
|
7
|
+
command = zsh -c 'bundle exec ruby script/delayed_job'
|
8
|
+
|
9
|
+
|
10
|
+
[events]
|
11
|
+
directory = /home/gnufied/god_particle
|
12
|
+
command = zsh -c 'bundle exec ruby script/event_server'
|
13
|
+
|
data/readme.md
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
Necro is a gem for managing processes in development environment.
|
2
|
+
|
3
|
+
[](https://travis-ci.org/code-mancers/necro)
|
4
|
+
|
5
|
+
|
6
|
+
## Usage ##
|
7
|
+
|
8
|
+
You need to start by creating a `ini` file which will define processes you want to manage using necro. An example
|
9
|
+
`ini` file is included in the repo.
|
10
|
+
|
11
|
+
[rails]
|
12
|
+
directory = /home/gnufied/god_particle
|
13
|
+
command = zsh -c 'bundle exec rails s -p 5000'
|
14
|
+
|
15
|
+
[dj]
|
16
|
+
directory = /home/gnufied/god_particle
|
17
|
+
command = zsh -c 'bundle exec ruby script/delayed_job'
|
18
|
+
|
19
|
+
|
20
|
+
[events]
|
21
|
+
directory = /home/gnufied/god_particle
|
22
|
+
command = zsh -c 'bundle exec ruby script/event_server'
|
23
|
+
|
24
|
+
After that you can start process manager via:
|
25
|
+
|
26
|
+
~> necro start necro.ini
|
27
|
+
|
28
|
+
Above command will start all your processes in one terminal with their stdout/stderr merged and labelled.
|
29
|
+
|
30
|
+
Now additionally you can control individual process by,
|
31
|
+
|
32
|
+
# Will try to stop running delayed job by sending SIGINT to the process
|
33
|
+
~> necro remove dj
|
34
|
+
|
35
|
+
# If Process can't be killed by SIGINT send a custom signal
|
36
|
+
~> necro remove dj -s 9
|
37
|
+
|
38
|
+
# add and start running
|
39
|
+
~> necro add dj
|
40
|
+
|
41
|
+
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.
|
42
|
+
|
43
|
+
|
44
|
+
|
45
|
+
|
46
|
+
|
47
|
+
|
@@ -0,0 +1,57 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe Necro::CommandListener::Client do
|
4
|
+
describe "add command" do
|
5
|
+
before do
|
6
|
+
@client_socket = mock()
|
7
|
+
@client = Necro::CommandListener::Client.new(@client_socket)
|
8
|
+
end
|
9
|
+
|
10
|
+
it "should run if read from socket" do
|
11
|
+
necro_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 = Necro::CommandListener::Client.new(@client_socket)
|
23
|
+
end
|
24
|
+
|
25
|
+
it "with specific signal" do
|
26
|
+
necro_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
|
+
necro_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 = Necro::CommandListener::Client.new(@client_socket)
|
46
|
+
end
|
47
|
+
|
48
|
+
it "should print error if read from socket" do
|
49
|
+
necro_commander.expects(:remove_command).never()
|
50
|
+
necro_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,92 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe "Necro::Commander" do
|
4
|
+
|
5
|
+
describe "With no processes configured" do
|
6
|
+
before do
|
7
|
+
@commander = Necro::Commander.new()
|
8
|
+
end
|
9
|
+
|
10
|
+
it "should throw error" do
|
11
|
+
necro_config.stubs(:processes).returns([])
|
12
|
+
|
13
|
+
lambda {
|
14
|
+
@commander.start_manager()
|
15
|
+
}.should.raise(Necro::Errors::InvalidConfig)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
describe "#add_command_by_label" do
|
20
|
+
before do
|
21
|
+
@commander = Necro::Commander.new()
|
22
|
+
end
|
23
|
+
|
24
|
+
it "should find command by label and start it, if found" do
|
25
|
+
necro_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 = Necro::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 = Necro::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
|
+
necro_config.stubs(:processes).returns([OpenStruct.new(:label => "sleep", :cmd => "sleep 4", :dir => ENV['HOME'])])
|
71
|
+
@commander = Necro::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
|
data/spec/spec_helper.rb
ADDED
@@ -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 "necro"
|
9
|
+
|
10
|
+
|
11
|
+
def necro_config
|
12
|
+
if Necro.const_defined?(:CONFIG)
|
13
|
+
Necro::CONFIG
|
14
|
+
else
|
15
|
+
Necro.const_set(:CONFIG, mock())
|
16
|
+
Necro::CONFIG
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def necro_commander
|
21
|
+
if Necro.const_defined?(:COMMANDER)
|
22
|
+
Necro::COMMANDER
|
23
|
+
else
|
24
|
+
Necro.const_set(:COMMANDER, mock())
|
25
|
+
Necro::COMMANDER
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
|
metadata
ADDED
@@ -0,0 +1,169 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: necro
|
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
|
+
- necro
|
115
|
+
extensions: []
|
116
|
+
extra_rdoc_files: []
|
117
|
+
files:
|
118
|
+
- .gitignore
|
119
|
+
- .travis.yml
|
120
|
+
- Gemfile
|
121
|
+
- Rakefile
|
122
|
+
- bin/necro
|
123
|
+
- lib/necro.rb
|
124
|
+
- lib/necro/command_listener.rb
|
125
|
+
- lib/necro/command_listener/client.rb
|
126
|
+
- lib/necro/command_listener/server.rb
|
127
|
+
- lib/necro/command_worker.rb
|
128
|
+
- lib/necro/commander.rb
|
129
|
+
- lib/necro/config.rb
|
130
|
+
- lib/necro/errors.rb
|
131
|
+
- lib/necro/reactor.rb
|
132
|
+
- lib/necro/runner.rb
|
133
|
+
- necro.gemspec
|
134
|
+
- necro.ini.example
|
135
|
+
- readme.md
|
136
|
+
- spec/necro/command_listener/client_spec.rb
|
137
|
+
- spec/necro/command_worker_spec.rb
|
138
|
+
- spec/necro/commander_spec.rb
|
139
|
+
- spec/spec_helper.rb
|
140
|
+
homepage: http://github.com/code-mancers/necro
|
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/necro/command_listener/client_spec.rb
|
166
|
+
- spec/necro/command_worker_spec.rb
|
167
|
+
- spec/necro/commander_spec.rb
|
168
|
+
- spec/spec_helper.rb
|
169
|
+
has_rdoc:
|